mirror of
https://github.com/affaan-m/ECC.git
synced 2026-08-17 21:15:40 +02:00
fix(repair): build opencode payload so repair clears doctor's opencode-plugin-not-built warning (#2414) (#2438)
* fix(repair): build opencode plugin payload so repair clears doctor's opencode-plugin-not-built warning (#2414) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor(repair): narrow validation bypass to specific codes; fix test-artifact cleanup (#2414 review) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: affaan <affaan@itomarkets.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
affaan
Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
a511395613
commit
67537ea480
@@ -739,7 +739,8 @@ function createManifestInstallPlan(options = {}) {
|
||||
moduleIds: options.moduleIds || [],
|
||||
includeComponentIds: options.includeComponentIds || [],
|
||||
excludeComponentIds: options.excludeComponentIds || [],
|
||||
target
|
||||
target,
|
||||
exemptValidationCodes: options.exemptValidationCodes || [],
|
||||
});
|
||||
const adapter = getInstallTargetAdapter(target);
|
||||
const operations = dedupeCopyFileOperations(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const fs = require('fs');
|
||||
const { execFileSync } = require('child_process');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
@@ -7,6 +8,9 @@ const { readInstallState, writeInstallState } = require('./install-state');
|
||||
const { assertWithinTrustedRoot } = require('./path-safety');
|
||||
const { createManifestInstallPlan } = require('./install-executor');
|
||||
const { getInstallTargetAdapter, listInstallTargetAdapters } = require('./install-targets/registry');
|
||||
const OPENCODE_BUILD_ARTIFACT = path.join('.opencode', 'dist');
|
||||
const OPENCODE_BUILD_SCRIPT = path.join('scripts', 'build-opencode.js');
|
||||
const OPENCODE_PLUGIN_NOT_BUILT_CODE = 'opencode-plugin-not-built';
|
||||
|
||||
const DEFAULT_REPO_ROOT = path.join(__dirname, '../..');
|
||||
|
||||
@@ -46,6 +50,31 @@ function compareStringArrays(left, right) {
|
||||
return leftValues.every((value, index) => value === rightValues[index]);
|
||||
}
|
||||
|
||||
function hasOpencodeBuildError(issues) {
|
||||
return Array.isArray(issues) && issues.some(issue => issue.code === OPENCODE_PLUGIN_NOT_BUILT_CODE);
|
||||
}
|
||||
|
||||
function getOpencodeBuildValidationIssues(context) {
|
||||
return getInstallTargetAdapter('opencode').validate({
|
||||
homeDir: context.homeDir,
|
||||
repoRoot: context.repoRoot,
|
||||
});
|
||||
}
|
||||
|
||||
function buildOpencodePayload(repoRoot, buildRunner = execFileSync) {
|
||||
buildRunner(process.execPath, [path.join(repoRoot, OPENCODE_BUILD_SCRIPT)], {
|
||||
cwd: repoRoot,
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
}
|
||||
|
||||
function formatBuildErrorMessage(error) {
|
||||
const stderr = typeof error.stderr === 'string' ? error.stderr.trim() : '';
|
||||
const stdout = typeof error.stdout === 'string' ? error.stdout.trim() : '';
|
||||
return stderr || stdout || error.message || 'Failed to build OpenCode payload';
|
||||
}
|
||||
|
||||
function getManagedOperations(state) {
|
||||
return Array.isArray(state && state.operations) ? state.operations.filter(operation => operation.ownership === 'managed') : [];
|
||||
}
|
||||
@@ -876,7 +905,7 @@ function buildDoctorReport(options = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function createRepairPlanFromRecord(record, context) {
|
||||
function createRepairPlanFromRecord(record, context, options = {}) {
|
||||
const state = record.state;
|
||||
if (!state) {
|
||||
throw new Error('No install-state available for repair');
|
||||
@@ -908,7 +937,8 @@ function createRepairPlanFromRecord(record, context) {
|
||||
includeComponentIds: state.request.includeComponents || [],
|
||||
excludeComponentIds: state.request.excludeComponents || [],
|
||||
projectRoot: context.projectRoot,
|
||||
homeDir: context.homeDir
|
||||
homeDir: context.homeDir,
|
||||
exemptValidationCodes: options.exemptValidationCodes || [],
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -931,6 +961,9 @@ function repairInstalledStates(options = {}) {
|
||||
manifestVersion: manifests.modulesVersion,
|
||||
packageVersion: readPackageVersion(repoRoot)
|
||||
};
|
||||
const buildOpencodeRunner = typeof options.buildOpencodePayload === 'function'
|
||||
? options.buildOpencodePayload
|
||||
: buildOpencodePayload;
|
||||
const records = discoverInstalledStates({
|
||||
homeDir: context.homeDir,
|
||||
projectRoot: context.projectRoot,
|
||||
@@ -950,6 +983,44 @@ function repairInstalledStates(options = {}) {
|
||||
}
|
||||
|
||||
try {
|
||||
const needsOpencodeBuild = record.adapter.target === 'opencode'
|
||||
&& hasOpencodeBuildError(getOpencodeBuildValidationIssues(context));
|
||||
const opencodeBuildRepairPath = path.join(context.repoRoot, OPENCODE_BUILD_ARTIFACT);
|
||||
|
||||
if (needsOpencodeBuild && options.dryRun) {
|
||||
const desiredPlan = createRepairPlanFromRecord(record, context, {
|
||||
exemptValidationCodes: [OPENCODE_PLUGIN_NOT_BUILT_CODE],
|
||||
});
|
||||
const operationHealth = summarizeManagedOperationHealth(context.repoRoot, desiredPlan.operations);
|
||||
const repairOperations = [...operationHealth.missing.map(entry => ({ ...entry.operation })), ...operationHealth.drifted.map(entry => ({ ...entry.operation }))];
|
||||
const plannedRepairs = [opencodeBuildRepairPath, ...repairOperations.map(operation => operation.destinationPath)];
|
||||
|
||||
return {
|
||||
adapter: record.adapter,
|
||||
status: 'planned',
|
||||
installStatePath: record.installStatePath,
|
||||
repairedPaths: [],
|
||||
plannedRepairs,
|
||||
stateRefreshed: false,
|
||||
error: null
|
||||
};
|
||||
}
|
||||
|
||||
if (needsOpencodeBuild) {
|
||||
try {
|
||||
buildOpencodeRunner(context.repoRoot);
|
||||
} catch (error) {
|
||||
return {
|
||||
adapter: record.adapter,
|
||||
status: 'error',
|
||||
installStatePath: record.installStatePath,
|
||||
repairedPaths: [],
|
||||
plannedRepairs: [],
|
||||
error: formatBuildErrorMessage(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const desiredPlan = createRepairPlanFromRecord(record, context);
|
||||
const operationHealth = summarizeManagedOperationHealth(context.repoRoot, desiredPlan.operations);
|
||||
|
||||
@@ -965,7 +1036,9 @@ function repairInstalledStates(options = {}) {
|
||||
}
|
||||
|
||||
const repairOperations = [...operationHealth.missing.map(entry => ({ ...entry.operation })), ...operationHealth.drifted.map(entry => ({ ...entry.operation }))];
|
||||
const plannedRepairs = repairOperations.map(operation => operation.destinationPath);
|
||||
const plannedRepairs = needsOpencodeBuild
|
||||
? [opencodeBuildRepairPath, ...repairOperations.map(operation => operation.destinationPath)]
|
||||
: repairOperations.map(operation => operation.destinationPath);
|
||||
|
||||
if (options.dryRun) {
|
||||
return {
|
||||
@@ -990,7 +1063,7 @@ function repairInstalledStates(options = {}) {
|
||||
|
||||
return {
|
||||
adapter: record.adapter,
|
||||
status: repairOperations.length > 0 ? 'repaired' : 'ok',
|
||||
status: (repairOperations.length > 0 || needsOpencodeBuild) ? 'repaired' : 'ok',
|
||||
installStatePath: record.installStatePath,
|
||||
repairedPaths: plannedRepairs,
|
||||
plannedRepairs: [],
|
||||
|
||||
@@ -680,6 +680,7 @@ function resolveInstallPlan(options = {}) {
|
||||
projectRoot: targetPlanningInput.projectRoot,
|
||||
homeDir: targetPlanningInput.homeDir,
|
||||
modules: selectedModules,
|
||||
exemptValidationCodes: options.exemptValidationCodes || [],
|
||||
})
|
||||
: null;
|
||||
|
||||
|
||||
@@ -47,13 +47,16 @@ function getInstallTargetAdapter(targetOrAdapterId) {
|
||||
function planInstallTargetScaffold(options = {}) {
|
||||
const adapter = getInstallTargetAdapter(options.target);
|
||||
const modules = Array.isArray(options.modules) ? options.modules : [];
|
||||
const exemptValidationCodes = new Set(Array.isArray(options.exemptValidationCodes) ? options.exemptValidationCodes : []);
|
||||
const planningInput = {
|
||||
repoRoot: options.repoRoot,
|
||||
projectRoot: options.projectRoot || options.repoRoot,
|
||||
homeDir: options.homeDir,
|
||||
};
|
||||
const validationIssues = adapter.validate(planningInput);
|
||||
const blockingIssues = validationIssues.filter(issue => issue.severity === 'error');
|
||||
const blockingIssues = validationIssues.filter(issue => (
|
||||
issue.severity === 'error' && !exemptValidationCodes.has(issue.code)
|
||||
));
|
||||
if (blockingIssues.length > 0) {
|
||||
throw new Error(blockingIssues.map(issue => issue.message).join('; '));
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ const {
|
||||
repairInstalledStates,
|
||||
uninstallInstalledStates,
|
||||
} = require('../../scripts/lib/install-lifecycle');
|
||||
const { getInstallTargetAdapter } = require('../../scripts/lib/install-targets/registry');
|
||||
const {
|
||||
createInstallState,
|
||||
writeInstallState,
|
||||
@@ -95,6 +96,74 @@ function writeCursorState(projectRoot, overrides = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function createOpencodeStateOptions(homeDir, overrides = {}) {
|
||||
const targetRoot = overrides.targetRoot || path.join(homeDir, '.opencode');
|
||||
const installStatePath = overrides.installStatePath || path.join(targetRoot, 'ecc-install-state.json');
|
||||
|
||||
return {
|
||||
adapter: { id: 'opencode-home', target: 'opencode', kind: 'home' },
|
||||
targetRoot,
|
||||
installStatePath,
|
||||
request: {
|
||||
profile: null,
|
||||
modules: ['commands-core'],
|
||||
includeComponents: [],
|
||||
excludeComponents: [],
|
||||
legacyLanguages: [],
|
||||
legacyMode: false,
|
||||
...(overrides.request || {}),
|
||||
},
|
||||
resolution: {
|
||||
selectedModules: ['commands-core'],
|
||||
skippedModules: [],
|
||||
...(overrides.resolution || {}),
|
||||
},
|
||||
operations: overrides.operations || [],
|
||||
source: {
|
||||
repoVersion: CURRENT_PACKAGE_VERSION,
|
||||
repoCommit: 'abc123',
|
||||
manifestVersion: CURRENT_MANIFEST_VERSION,
|
||||
...(overrides.source || {}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function writeOpencodeState(homeDir, overrides = {}) {
|
||||
const options = createOpencodeStateOptions(homeDir, overrides);
|
||||
writeState(options.installStatePath, options);
|
||||
return {
|
||||
targetRoot: options.targetRoot,
|
||||
installStatePath: options.installStatePath,
|
||||
state: options,
|
||||
};
|
||||
}
|
||||
|
||||
function withTemporarilyMovedPath(filePath, callback) {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
try {
|
||||
return callback(null);
|
||||
} finally {
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.rmSync(filePath, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const backupPath = `${filePath}.backup-${process.pid}-${Date.now()}`;
|
||||
fs.renameSync(filePath, backupPath);
|
||||
|
||||
try {
|
||||
return callback(backupPath);
|
||||
} finally {
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.rmSync(filePath, { recursive: true, force: true });
|
||||
}
|
||||
if (fs.existsSync(backupPath)) {
|
||||
fs.renameSync(backupPath, filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function managedOperation(kind, destinationPath, overrides = {}) {
|
||||
return {
|
||||
kind,
|
||||
@@ -696,6 +765,228 @@ function runTests() {
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('repair builds the OpenCode payload and clears the missing-payload warning', () => {
|
||||
const homeDir = createTempDir('install-lifecycle-home-');
|
||||
const projectRoot = createTempDir('install-lifecycle-project-');
|
||||
|
||||
try {
|
||||
withTemporarilyMovedPath(path.join(REPO_ROOT, '.opencode', 'dist'), () => {
|
||||
writeOpencodeState(homeDir, {
|
||||
request: {
|
||||
profile: null,
|
||||
modules: ['commands-core'],
|
||||
includeComponents: [],
|
||||
excludeComponents: [],
|
||||
legacyLanguages: [],
|
||||
legacyMode: false,
|
||||
},
|
||||
resolution: {
|
||||
selectedModules: ['commands-core'],
|
||||
skippedModules: [],
|
||||
},
|
||||
operations: [],
|
||||
});
|
||||
|
||||
const beforeValidate = getInstallTargetAdapter('opencode').validate({
|
||||
homeDir,
|
||||
repoRoot: REPO_ROOT,
|
||||
});
|
||||
assert.ok(beforeValidate.some(issue => issue.code === 'opencode-plugin-not-built'));
|
||||
|
||||
const beforeDoctor = buildDoctorReport({
|
||||
repoRoot: REPO_ROOT,
|
||||
homeDir,
|
||||
projectRoot,
|
||||
targets: ['opencode'],
|
||||
});
|
||||
assert.strictEqual(beforeDoctor.results[0].status, 'error');
|
||||
assert.ok(beforeDoctor.results[0].issues.some(issue => issue.code === 'resolution-unavailable'));
|
||||
|
||||
let buildCalls = 0;
|
||||
const result = repairInstalledStates({
|
||||
repoRoot: REPO_ROOT,
|
||||
homeDir,
|
||||
projectRoot,
|
||||
targets: ['opencode'],
|
||||
buildOpencodePayload: repoRoot => {
|
||||
buildCalls += 1;
|
||||
const distDir = path.join(repoRoot, '.opencode', 'dist');
|
||||
fs.mkdirSync(path.join(distDir, 'plugins'), { recursive: true });
|
||||
fs.mkdirSync(path.join(distDir, 'tools'), { recursive: true });
|
||||
fs.writeFileSync(path.join(distDir, 'index.js'), 'module.exports = {};\\n');
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(buildCalls, 1);
|
||||
assert.strictEqual(result.results[0].status, 'repaired');
|
||||
assert.ok(fs.existsSync(path.join(REPO_ROOT, '.opencode', 'dist', 'index.js')));
|
||||
|
||||
const afterValidate = getInstallTargetAdapter('opencode').validate({
|
||||
homeDir,
|
||||
repoRoot: REPO_ROOT,
|
||||
});
|
||||
assert.deepStrictEqual(afterValidate, []);
|
||||
|
||||
const afterDoctor = buildDoctorReport({
|
||||
repoRoot: REPO_ROOT,
|
||||
homeDir,
|
||||
projectRoot,
|
||||
targets: ['opencode'],
|
||||
});
|
||||
assert.strictEqual(afterDoctor.results[0].status, 'ok');
|
||||
assert.strictEqual(afterDoctor.results[0].issues.length, 0);
|
||||
});
|
||||
} finally {
|
||||
cleanup(homeDir);
|
||||
cleanup(projectRoot);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('repair dry-run plans the OpenCode payload build without creating it', () => {
|
||||
const homeDir = createTempDir('install-lifecycle-home-');
|
||||
const projectRoot = createTempDir('install-lifecycle-project-');
|
||||
|
||||
try {
|
||||
withTemporarilyMovedPath(path.join(REPO_ROOT, '.opencode', 'dist'), () => {
|
||||
writeOpencodeState(homeDir, {
|
||||
request: {
|
||||
profile: null,
|
||||
modules: ['commands-core'],
|
||||
includeComponents: [],
|
||||
excludeComponents: [],
|
||||
legacyLanguages: [],
|
||||
legacyMode: false,
|
||||
},
|
||||
resolution: {
|
||||
selectedModules: ['commands-core'],
|
||||
skippedModules: [],
|
||||
},
|
||||
operations: [],
|
||||
});
|
||||
|
||||
const result = repairInstalledStates({
|
||||
repoRoot: REPO_ROOT,
|
||||
homeDir,
|
||||
projectRoot,
|
||||
targets: ['opencode'],
|
||||
dryRun: true,
|
||||
buildOpencodePayload: () => {
|
||||
throw new Error('build should not run during dry-run');
|
||||
},
|
||||
});
|
||||
|
||||
assert.strictEqual(result.results[0].status, 'planned');
|
||||
assert.ok(result.results[0].plannedRepairs.includes(path.join(REPO_ROOT, '.opencode', 'dist')));
|
||||
assert.ok(!fs.existsSync(path.join(REPO_ROOT, '.opencode', 'dist', 'index.js')));
|
||||
});
|
||||
} finally {
|
||||
cleanup(homeDir);
|
||||
cleanup(projectRoot);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('withTemporarilyMovedPath cleans up newly created paths when nothing was pre-existing', () => {
|
||||
const filePath = path.join(REPO_ROOT, '.opencode', 'dist');
|
||||
const backupPath = `${filePath}.backup-${process.pid}-test`;
|
||||
|
||||
try {
|
||||
fs.rmSync(filePath, { recursive: true, force: true });
|
||||
fs.rmSync(backupPath, { recursive: true, force: true });
|
||||
|
||||
const result = withTemporarilyMovedPath(filePath, receivedBackupPath => {
|
||||
assert.strictEqual(receivedBackupPath, null);
|
||||
fs.mkdirSync(path.join(filePath, 'plugins'), { recursive: true });
|
||||
fs.mkdirSync(path.join(filePath, 'tools'), { recursive: true });
|
||||
fs.writeFileSync(path.join(filePath, 'index.js'), '// temp build\n');
|
||||
return 'callback-result';
|
||||
});
|
||||
|
||||
assert.strictEqual(result, 'callback-result');
|
||||
assert.ok(!fs.existsSync(filePath), 'Temporary path should be removed after the callback');
|
||||
} finally {
|
||||
fs.rmSync(filePath, { recursive: true, force: true });
|
||||
fs.rmSync(backupPath, { recursive: true, force: true });
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('repair surfaces OpenCode build failures without blocking other targets', () => {
|
||||
const homeDir = createTempDir('install-lifecycle-home-');
|
||||
const projectRoot = createTempDir('install-lifecycle-project-');
|
||||
|
||||
try {
|
||||
withTemporarilyMovedPath(path.join(REPO_ROOT, '.opencode', 'dist'), () => {
|
||||
const cursorTargetRoot = path.join(projectRoot, '.cursor');
|
||||
const cursorStatePath = path.join(cursorTargetRoot, 'ecc-install-state.json');
|
||||
const cursorDestinationPath = path.join(cursorTargetRoot, 'rules', 'coding-style.md');
|
||||
fs.mkdirSync(path.dirname(cursorDestinationPath), { recursive: true });
|
||||
|
||||
writeOpencodeState(homeDir, {
|
||||
request: {
|
||||
profile: null,
|
||||
modules: ['commands-core'],
|
||||
includeComponents: [],
|
||||
excludeComponents: [],
|
||||
legacyLanguages: [],
|
||||
legacyMode: false,
|
||||
},
|
||||
resolution: {
|
||||
selectedModules: ['commands-core'],
|
||||
skippedModules: [],
|
||||
},
|
||||
operations: [],
|
||||
});
|
||||
|
||||
writeState(cursorStatePath, {
|
||||
adapter: { id: 'cursor-project', target: 'cursor', kind: 'project' },
|
||||
targetRoot: cursorTargetRoot,
|
||||
installStatePath: cursorStatePath,
|
||||
request: {
|
||||
profile: null,
|
||||
modules: [],
|
||||
legacyLanguages: ['typescript'],
|
||||
legacyMode: true,
|
||||
},
|
||||
resolution: {
|
||||
selectedModules: ['legacy-cursor-install'],
|
||||
skippedModules: [],
|
||||
},
|
||||
operations: [
|
||||
managedOperation('copy-file', cursorDestinationPath, {
|
||||
sourceRelativePath: 'rules/common/coding-style.md',
|
||||
strategy: 'copy-file',
|
||||
}),
|
||||
],
|
||||
source: {
|
||||
repoVersion: CURRENT_PACKAGE_VERSION,
|
||||
repoCommit: 'abc123',
|
||||
manifestVersion: CURRENT_MANIFEST_VERSION,
|
||||
},
|
||||
});
|
||||
|
||||
const result = repairInstalledStates({
|
||||
repoRoot: REPO_ROOT,
|
||||
homeDir,
|
||||
projectRoot,
|
||||
targets: ['opencode', 'cursor'],
|
||||
buildOpencodePayload: () => {
|
||||
throw new Error('typescript dependency missing');
|
||||
},
|
||||
});
|
||||
|
||||
const opencodeResult = result.results.find(entry => entry.adapter.id === 'opencode-home');
|
||||
const cursorResult = result.results.find(entry => entry.adapter.id === 'cursor-project');
|
||||
|
||||
assert.strictEqual(opencodeResult.status, 'error');
|
||||
assert.ok(opencodeResult.error.includes('typescript dependency missing'));
|
||||
assert.strictEqual(cursorResult.status, 'repaired');
|
||||
assert.ok(fs.existsSync(cursorDestinationPath));
|
||||
});
|
||||
} finally {
|
||||
cleanup(homeDir);
|
||||
cleanup(projectRoot);
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('repair surfaces missing source errors from execution when destination is absent', () => {
|
||||
const homeDir = createTempDir('install-lifecycle-home-');
|
||||
const projectRoot = createTempDir('install-lifecycle-project-');
|
||||
|
||||
@@ -1088,6 +1088,75 @@ function runTests() {
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('planInstallTargetScaffold only exempts explicitly allowed validation codes', () => {
|
||||
const registryPath = require.resolve('../../scripts/lib/install-targets/registry');
|
||||
const opencodeHomePath = require.resolve('../../scripts/lib/install-targets/opencode-home');
|
||||
const originalRegistryEntry = require.cache[registryPath];
|
||||
const originalOpencodeHomeEntry = require.cache[opencodeHomePath];
|
||||
const repoRoot = path.join(__dirname, '..', '..');
|
||||
const homeDir = '/Users/example';
|
||||
|
||||
try {
|
||||
delete require.cache[registryPath];
|
||||
require.cache[opencodeHomePath] = {
|
||||
id: opencodeHomePath,
|
||||
filename: opencodeHomePath,
|
||||
loaded: true,
|
||||
exports: {
|
||||
id: 'opencode-home',
|
||||
target: 'opencode',
|
||||
kind: 'home',
|
||||
supports: target => target === 'opencode',
|
||||
resolveRoot: input => path.join((input.homeDir || '/Users/example'), '.opencode'),
|
||||
getInstallStatePath: input => path.join((input.homeDir || '/Users/example'), '.opencode', 'ecc-install-state.json'),
|
||||
validate: () => ([
|
||||
{ severity: 'error', code: 'opencode-plugin-not-built', message: 'missing payload' },
|
||||
{ severity: 'error', code: 'opencode-other-blocker', message: 'still blocked' },
|
||||
]),
|
||||
planOperations: () => [],
|
||||
},
|
||||
};
|
||||
|
||||
const { planInstallTargetScaffold: sandboxedPlanInstallTargetScaffold } = require('../../scripts/lib/install-targets/registry');
|
||||
|
||||
assert.throws(
|
||||
() => sandboxedPlanInstallTargetScaffold({
|
||||
target: 'opencode',
|
||||
repoRoot,
|
||||
homeDir,
|
||||
exemptValidationCodes: ['opencode-plugin-not-built'],
|
||||
}),
|
||||
/still blocked/
|
||||
);
|
||||
|
||||
require.cache[opencodeHomePath].exports.validate = () => ([
|
||||
{ severity: 'error', code: 'opencode-plugin-not-built', message: 'missing payload' },
|
||||
]);
|
||||
|
||||
const plan = sandboxedPlanInstallTargetScaffold({
|
||||
target: 'opencode',
|
||||
repoRoot,
|
||||
homeDir,
|
||||
exemptValidationCodes: ['opencode-plugin-not-built'],
|
||||
});
|
||||
|
||||
assert.strictEqual(plan.adapter.id, 'opencode-home');
|
||||
assert.deepStrictEqual(plan.operations, []);
|
||||
} finally {
|
||||
if (originalOpencodeHomeEntry) {
|
||||
require.cache[opencodeHomePath] = originalOpencodeHomeEntry;
|
||||
} else {
|
||||
delete require.cache[opencodeHomePath];
|
||||
}
|
||||
|
||||
if (originalRegistryEntry) {
|
||||
require.cache[registryPath] = originalRegistryEntry;
|
||||
} else {
|
||||
delete require.cache[registryPath];
|
||||
}
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user