mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-16 21:25:42 +02:00
Merge branch 'fix/commands-commonjs-runtime-20261011' of /Users/hira/Documents/ECC/ecc-command-runtime-repair-20261011 into integration/ecc-043-20261011
This commit is contained in:
@@ -2003,12 +2003,12 @@ function uninstallInstalledStates(options = {}) {
|
||||
const { isScriptBoundary, retainScriptBoundary } = require('./install/opencode-script-boundary');
|
||||
// Remove owned script bodies first. Keep their CommonJS scope while any
|
||||
// retained or user-created JavaScript still relies on that boundary.
|
||||
const orderedOperations = record.adapter.target === 'opencode'
|
||||
? [...operations.filter(operation => !isScriptBoundary(operation, record.targetRoot)),
|
||||
...operations.filter(operation => isScriptBoundary(operation, record.targetRoot))]
|
||||
: operations;
|
||||
const orderedOperations = [
|
||||
...operations.filter(operation => !isScriptBoundary(operation, record.targetRoot)),
|
||||
...operations.filter(operation => isScriptBoundary(operation, record.targetRoot)),
|
||||
];
|
||||
for (const operation of orderedOperations) {
|
||||
if (record.adapter.target === 'opencode' && retainScriptBoundary(operation, record.targetRoot)) {
|
||||
if (retainScriptBoundary(operation, record.targetRoot)) {
|
||||
retainedPaths.push(operation.destinationPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ module.exports = createInstallTargetAdapter({
|
||||
homeDir: input.homeDir,
|
||||
};
|
||||
|
||||
return modules.flatMap(module => {
|
||||
const operations = modules.flatMap(module => {
|
||||
const paths = Array.isArray(module.paths) ? module.paths : [];
|
||||
return paths
|
||||
.filter(p => !isForeignPlatformPath(p, adapter.target) && !adapter.excludesSourcePath(p))
|
||||
@@ -96,5 +96,14 @@ module.exports = createInstallTargetAdapter({
|
||||
return [adapter.createScaffoldOperation(module.id, sourceRelativePath, planningInput)];
|
||||
});
|
||||
});
|
||||
if (!operations.some(operation => operation.moduleId === 'commands-core'
|
||||
&& normalizeRelativePath(operation.sourceRelativePath).startsWith('scripts/'))) return operations;
|
||||
return [...operations, createRemappedOperation(
|
||||
adapter,
|
||||
'commands-core',
|
||||
'manifests/install-assets/commonjs-scripts-package.json',
|
||||
path.join(adapter.resolveRoot(planningInput), 'scripts', 'package.json'),
|
||||
{ strategy: 'preserve-relative-path' }
|
||||
)];
|
||||
},
|
||||
});
|
||||
|
||||
@@ -64,9 +64,9 @@ module.exports = createInstallTargetAdapter({
|
||||
homeDir: input.homeDir,
|
||||
};
|
||||
|
||||
return modules.flatMap(module => {
|
||||
const operations = modules.flatMap(module => {
|
||||
const paths = Array.isArray(module.paths) ? module.paths : [];
|
||||
const operations = paths
|
||||
const moduleOperations = paths
|
||||
.filter(p => !isForeignPlatformPath(p, 'claude'))
|
||||
.flatMap(sourceRelativePath => {
|
||||
if (
|
||||
@@ -96,7 +96,7 @@ module.exports = createInstallTargetAdapter({
|
||||
});
|
||||
|
||||
if (module.id !== 'hooks-runtime') {
|
||||
return operations;
|
||||
return moduleOperations;
|
||||
}
|
||||
|
||||
return [
|
||||
@@ -107,8 +107,19 @@ module.exports = createInstallTargetAdapter({
|
||||
path.join(adapter.resolveRoot(planningInput), 'scripts', directory, 'package.json'),
|
||||
{ strategy: 'preserve-relative-path' }
|
||||
)),
|
||||
...operations,
|
||||
...moduleOperations,
|
||||
];
|
||||
});
|
||||
if (!operations.some(operation => operation.moduleId === 'commands-core'
|
||||
&& normalizeRelativePath(operation.sourceRelativePath).startsWith('scripts/'))) return operations;
|
||||
// Standalone command entries and their helpers need the same scope even
|
||||
// when hooks are excluded. Reuse the shared boundary asset and owner.
|
||||
return [...operations, createRemappedOperation(
|
||||
adapter,
|
||||
'commands-core',
|
||||
'manifests/install-assets/commonjs-scripts-package.json',
|
||||
path.join(adapter.resolveRoot(planningInput), 'scripts', 'package.json'),
|
||||
{ strategy: 'preserve-relative-path' }
|
||||
)];
|
||||
},
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ const {
|
||||
} = require('./stale-operations-reconciliation');
|
||||
const { buildInstallIndex, rewriteRelativeLinks } = require('./link-rewrite');
|
||||
const { transformInstallContent } = require('./content-transform');
|
||||
const { assertScriptBoundary } = require('./opencode-script-boundary');
|
||||
const { assertScriptBoundary, isScriptBoundary } = require('./opencode-script-boundary');
|
||||
|
||||
function isMarkdownPath(filePath) {
|
||||
return /\.(md|mdx|markdown)$/i.test(String(filePath || ''));
|
||||
@@ -297,7 +297,7 @@ function readPreviousInstallState(plan) {
|
||||
}
|
||||
|
||||
function assertOpenCodeScriptBoundary(plan, writtenDestinations = new Set()) {
|
||||
if (plan.adapter?.target !== 'opencode') return;
|
||||
if (!plan.operations.some(operation => isScriptBoundary(operation, plan.targetRoot))) return;
|
||||
assertSafeInstallOperation(plan, { destinationPath: plan.installStatePath });
|
||||
assertScriptBoundary(plan, {
|
||||
readFile: destinationPath => readInstalledFileNoFollow(plan, { destinationPath }),
|
||||
@@ -742,6 +742,8 @@ function previewInstallPlan(plan) {
|
||||
|
||||
function applyInstallPlan(plan, dependencies = {}) {
|
||||
assertHookConsentReady(plan);
|
||||
// Reject conflicts before locks can create any target directories/files.
|
||||
assertOpenCodeScriptBoundary(plan);
|
||||
if (plan.adapter?.target === 'opencode') {
|
||||
assertSafeInstallOperation(plan, { destinationPath: plan.installStatePath });
|
||||
return withOpenCodeInstallLocks(
|
||||
|
||||
@@ -30,16 +30,59 @@ function scopedJavaScriptFiles(directory, { nested = false, depth = 0 } = {}) {
|
||||
return files;
|
||||
}
|
||||
|
||||
function assertCommonJSMetadata(content, file) {
|
||||
if (content === null) {
|
||||
if (fs.lstatSync(file, { throwIfNoEntry: false })) {
|
||||
throw new Error(`Refusing CommonJS runtime migration: invalid user module boundary at ${file}.`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let metadata;
|
||||
try { metadata = JSON.parse(content.toString('utf8')); }
|
||||
catch { throw new Error(`Refusing CommonJS runtime migration: invalid user module boundary at ${file}.`); }
|
||||
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)
|
||||
|| (metadata.type !== undefined && metadata.type !== 'commonjs')) {
|
||||
throw new Error(`Refusing CommonJS runtime migration: preserve the existing user module boundary at ${file}.`);
|
||||
}
|
||||
}
|
||||
|
||||
function assertSelectedScriptScopes(plan, readFile, scriptsRoot) {
|
||||
const inspected = new Set();
|
||||
for (const operation of plan.operations) {
|
||||
if (operation.kind !== 'copy-file' || !operation.destinationPath.endsWith('.js')) continue;
|
||||
const relative = path.relative(scriptsRoot, operation.destinationPath);
|
||||
if (relative.startsWith(`..${path.sep}`) || relative === '..' || path.isAbsolute(relative)) continue;
|
||||
// Check every selected entry/helper, including nested library boundaries
|
||||
// which the unrelated-user-script scope walk intentionally skips.
|
||||
let directory = path.dirname(operation.destinationPath);
|
||||
while (key(directory) !== key(scriptsRoot)) {
|
||||
const file = path.join(directory, 'package.json');
|
||||
if (!inspected.has(key(file))) {
|
||||
assertCommonJSMetadata(readFile(file), file);
|
||||
inspected.add(key(file));
|
||||
}
|
||||
directory = path.dirname(directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function assertScriptBoundary(plan, { readFile, previousOperations = [], writtenDestinations = new Set(), expectedContent }) {
|
||||
if (plan.adapter?.target !== 'opencode'
|
||||
|| !plan.operations.some(operation => isScriptBoundary(operation, plan.targetRoot))) return;
|
||||
if (!plan.operations.some(operation => isScriptBoundary(operation, plan.targetRoot))) return;
|
||||
const marker = markerPath(plan.targetRoot);
|
||||
assertSelectedScriptScopes(plan, readFile, path.dirname(marker));
|
||||
const content = readFile(marker);
|
||||
assertCommonJSMetadata(content, marker);
|
||||
if (content !== null) {
|
||||
const metadata = JSON.parse(content.toString('utf8'));
|
||||
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)
|
||||
|| (metadata.type !== undefined && metadata.type !== 'commonjs')) {
|
||||
throw new Error(`Refusing OpenCode CommonJS runtime migration: preserve the existing user module boundary at ${marker}.`);
|
||||
const ownedMarker = previousOperations.find(operation => operation.ownership === 'managed'
|
||||
&& isScriptBoundary(operation, plan.targetRoot));
|
||||
// A reinstall bridge may omit digests after preflight validated the old
|
||||
// receipt. At publication the fixed boundary asset still proves its bytes.
|
||||
const expectedMarker = ownedMarker && !ownedMarker.contentSha256
|
||||
? expectedContent(plan.operations.find(operation => isScriptBoundary(operation, plan.targetRoot))) : null;
|
||||
const markerDigest = ownedMarker?.contentSha256 || (expectedMarker
|
||||
? crypto.createHash('sha256').update(expectedMarker).digest('hex') : null);
|
||||
if (ownedMarker && crypto.createHash('sha256').update(content).digest('hex') !== markerDigest) {
|
||||
throw new Error(`Refusing CommonJS runtime migration: preserve the edited user module boundary at ${marker}.`);
|
||||
}
|
||||
// Existing user-owned CommonJS metadata is skipped by the ownership guard;
|
||||
// retaining its bytes does not change anyone's module interpretation.
|
||||
@@ -58,7 +101,7 @@ function assertScriptBoundary(plan, { readFile, previousOperations = [], written
|
||||
const expectedDigest = expected === null
|
||||
? operation?.contentSha256 : crypto.createHash('sha256').update(expected).digest('hex');
|
||||
if (!expectedDigest || digest !== expectedDigest) {
|
||||
throw new Error(`Refusing OpenCode CommonJS runtime migration: unowned or edited JavaScript at ${file} would change module scope. Give user scripts their own package.json scope before retrying.`);
|
||||
throw new Error(`Refusing CommonJS runtime migration: unowned or edited JavaScript at ${file} would change module scope. Give user scripts their own package.json scope before retrying.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
// Actual commands-only install lifecycle and module-scope regression coverage.
|
||||
'use strict';
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
const { pathToFileURL } = require('url');
|
||||
const { createManifestInstallPlan, applyInstallPlan, previewInstallPlan } = require('../../scripts/lib/install-executor');
|
||||
const { withHookConsent } = require('../../scripts/lib/install/hook-consent');
|
||||
const { repairInstalledStates } = require('../../scripts/lib/install-lifecycle');
|
||||
const repo = path.resolve(__dirname, '../..');
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
function snapshot(root) {
|
||||
if (!fs.existsSync(root)) return {};
|
||||
return Object.fromEntries(fs.readdirSync(root, { recursive: true, withFileTypes: true })
|
||||
.map(entry => { const file = path.join(entry.parentPath || entry.path, entry.name);
|
||||
const value = entry.isDirectory() ? '[directory]' : entry.isSymbolicLink()
|
||||
? `[symlink:${fs.readlinkSync(file)}]` : require('crypto').createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
||||
return [path.relative(root, file), value]; }).sort());
|
||||
}
|
||||
function test(name, fn, target = 'claude-project') {
|
||||
const dir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-command-runtime-')));
|
||||
const home = path.join(dir, 'home');
|
||||
const project = path.join(dir, 'project');
|
||||
fs.mkdirSync(home); fs.mkdirSync(project);
|
||||
const root = target === 'claude' ? path.join(home, '.claude') : path.join(project, '.claude');
|
||||
const scripts = path.join(root, 'scripts');
|
||||
const marker = path.join(scripts, 'package.json');
|
||||
const write = (file, bytes) => { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, bytes); };
|
||||
const env = { ...process.env, HOME: home, USERPROFILE: home, CLAUDE_CONFIG_DIR: path.join(home, '.claude') };
|
||||
const run = (file, args) => spawnSync(process.execPath, [file, ...args], {
|
||||
cwd: project, env, encoding: 'utf8', timeout: 30000, maxBuffer: 8 * 1024 * 1024 });
|
||||
const install = () => run(path.join(repo, 'scripts/install-apply.js'), ['--target', target, '--modules', 'commands-core', '--no-hooks', '--json']);
|
||||
const uninstall = () => run(path.join(repo, 'scripts/uninstall.js'), ['--target', target, '--json']);
|
||||
const plan = () => withHookConsent(createManifestInstallPlan({ sourceRoot: repo, homeDir: home,
|
||||
projectRoot: project, env: {}, target, moduleIds: ['commands-core'] }), 'declined');
|
||||
const statePath = path.join(root, 'ecc/install-state.json');
|
||||
const state = () => JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
||||
const ok = result => assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
||||
const health = () => {
|
||||
const file = path.join(scripts, 'skills-health.js');
|
||||
const help = run(file, ['--help']); ok(help); assert.match(help.stdout, /Usage:/);
|
||||
const result = run(file, ['--json', '--home', home, '--skills-root', path.join(dir, 'none'),
|
||||
'--learned-root', path.join(dir, 'none2'), '--imported-root', path.join(dir, 'none3')]);
|
||||
ok(result); assert.deepStrictEqual(JSON.parse(result.stdout).skills, []);
|
||||
};
|
||||
const probe = file => {
|
||||
const result = run('--input-type=module', ['-e', `const m=await import(${JSON.stringify(pathToFileURL(file).href)}); console.log(m.default ?? m.value);`]);
|
||||
ok(result); return result.stdout.trim();
|
||||
};
|
||||
const noPayload = () => {
|
||||
for (const file of ['.mcp.json', 'hooks/hooks.json', 'scripts/hooks']) {
|
||||
assert.ok(!fs.existsSync(path.join(root, file)), `Unexpected ${file}`);
|
||||
}
|
||||
const settings = path.join(root, 'settings.json');
|
||||
if (fs.existsSync(settings)) assert.ok(!JSON.parse(fs.readFileSync(settings, 'utf8')).hooks);
|
||||
assert.ok(!state().operations.some(op => ['hooks-runtime', 'mcp-configs'].includes(op.moduleId)));
|
||||
};
|
||||
try { fn({ dir, home, project, root, scripts, marker, write, install, uninstall, plan,
|
||||
state, statePath, ok, health, probe, noPayload, run, target }); passed++; console.log(`PASS ${name}`); }
|
||||
catch (error) { failed++; console.error(`FAIL ${name}: ${error.stack}`); }
|
||||
finally { fs.rmSync(dir, { recursive: true, force: true }); }
|
||||
}
|
||||
for (const target of ['claude-project', 'claude']) {
|
||||
test(`${target}: fresh ESM commands-only CLI, receipt, reinstall, uninstall`, f => {
|
||||
const projectBytes = '{ "type": "module", "user": "project" }\n';
|
||||
const rootBytes = '{ "type": "module", "user": "claude" }\n';
|
||||
f.write(path.join(f.project, 'package.json'), projectBytes);
|
||||
f.write(path.join(f.root, 'package.json'), rootBytes);
|
||||
f.ok(f.install()); f.health(); f.noPayload();
|
||||
assert.deepStrictEqual(JSON.parse(fs.readFileSync(f.marker, 'utf8')), { type: 'commonjs' });
|
||||
const owned = f.state().operations.filter(op => op.destinationPath === f.marker);
|
||||
assert.strictEqual(owned.length, 1); assert.strictEqual(owned[0].ownership, 'managed');
|
||||
assert.strictEqual(owned[0].moduleId, 'commands-core');
|
||||
assert.ok(f.state().operations.some(op => op.moduleId === 'shared-script-utils' && op.destinationPath.endsWith('utils.js')));
|
||||
assert.ok(!fs.existsSync(path.join(f.scripts, 'lib/package.json')));
|
||||
f.ok(f.install()); f.health(); f.noPayload();
|
||||
assert.strictEqual(f.state().operations.filter(op => op.destinationPath === f.marker).length, 1);
|
||||
f.ok(f.uninstall()); assert.ok(!fs.existsSync(f.marker)); assert.ok(!fs.existsSync(f.statePath));
|
||||
assert.strictEqual(fs.readFileSync(path.join(f.project, 'package.json'), 'utf8'), projectBytes);
|
||||
assert.strictEqual(fs.readFileSync(path.join(f.root, 'package.json'), 'utf8'), rootBytes);
|
||||
}, target);
|
||||
}
|
||||
for (const [scope, bytes] of [
|
||||
['package.json', '{"type":"module"}\n'], ['package.json', '{broken'],
|
||||
['lib/package.json', '{"type":"module"}\n'], ['lib/package.json', '{broken'],
|
||||
['lib/skill-evolution/package.json', '{"type":"module"}\n'],
|
||||
['lib/astra-review/package.json', '{"type":"module"}\n'],
|
||||
['ci/package.json', '{"type":"module"}\n'],
|
||||
['lib/package.json', '[]'], ['lib/package.json', '{"type":"unexpected"}'],
|
||||
]) {
|
||||
test(`refuses incompatible ${scope} ${bytes} before writes`, f => {
|
||||
f.write(path.join(f.project, 'package.json'), '{"type":"module"}\n');
|
||||
f.write(path.join(f.scripts, scope), bytes);
|
||||
const before = snapshot(f.dir);
|
||||
const result = f.install(); assert.notStrictEqual(result.status, 0, 'Install falsely reported success');
|
||||
assert.deepStrictEqual(snapshot(f.dir), before);
|
||||
assert.throws(() => previewInstallPlan(f.plan())); assert.deepStrictEqual(snapshot(f.dir), before);
|
||||
});
|
||||
}
|
||||
for (const userFile of ['user.js', 'lib/user.js', 'lib/skill-evolution/user.js', 'ci/user.js', 'lib/utils.js']) {
|
||||
test(`refuses unowned JS ${userFile} without scope mutation`, f => {
|
||||
f.write(path.join(f.project, 'package.json'), '{"type":"module"}\n');
|
||||
const file = path.join(f.scripts, userFile); f.write(file, 'export const value = 42;\n');
|
||||
assert.strictEqual(f.probe(file), '42'); const before = snapshot(f.dir);
|
||||
const result = f.install(); assert.notStrictEqual(result.status, 0);
|
||||
assert.match(result.stderr + result.stdout, /unowned or edited JavaScript/);
|
||||
assert.deepStrictEqual(snapshot(f.dir), before); assert.strictEqual(f.probe(file), '42');
|
||||
});
|
||||
}
|
||||
for (const bytes of ['{ "type": "commonjs", "user": true }\n', '{"user":true}\n']) {
|
||||
test('compatible scripts metadata remains unchanged and unowned', f => {
|
||||
f.write(f.marker, bytes); const file = path.join(f.scripts, 'user.js'); f.write(file, 'module.exports = 43;\n');
|
||||
f.ok(f.install()); f.health();
|
||||
assert.ok(!f.state().operations.some(op => op.destinationPath === f.marker));
|
||||
f.ok(f.install()); f.health(); f.ok(f.uninstall());
|
||||
assert.strictEqual(fs.readFileSync(f.marker, 'utf8'), bytes); assert.strictEqual(f.probe(file), '43');
|
||||
});
|
||||
}
|
||||
test('unrelated nested ESM package keeps interpretation and bytes', f => {
|
||||
f.write(path.join(f.project, 'package.json'), '{"type":"module"}\n');
|
||||
const nested = path.join(f.scripts, 'user/package.json'); const bytes = '{"type":"module"}\n';
|
||||
const file = path.join(f.scripts, 'user/index.js'); f.write(nested, bytes); f.write(file, 'export const value = 44;\n');
|
||||
f.ok(f.install()); f.health(); assert.strictEqual(f.probe(file), '44');
|
||||
f.ok(f.uninstall()); assert.strictEqual(fs.readFileSync(nested, 'utf8'), bytes); assert.strictEqual(f.probe(file), '44');
|
||||
});
|
||||
test('compatible nested helper scope remains unchanged and unowned', f => {
|
||||
const file = path.join(f.scripts, 'lib/package.json'); const bytes = '{"type":"commonjs","keep":true}\n';
|
||||
f.write(file, bytes); f.ok(f.install()); f.health(); f.ok(f.uninstall());
|
||||
assert.strictEqual(fs.readFileSync(file, 'utf8'), bytes);
|
||||
});
|
||||
test('edited owned JS rejects missing-marker reinstall before any writes', f => {
|
||||
f.ok(f.install()); fs.unlinkSync(f.marker);
|
||||
f.write(path.join(f.scripts, 'lib/utils.js'), 'export const value = 45;\n');
|
||||
const before = snapshot(f.dir); const result = f.install(); assert.notStrictEqual(result.status, 0);
|
||||
assert.match(result.stderr + result.stdout, /unowned or edited JavaScript/); assert.deepStrictEqual(snapshot(f.dir), before);
|
||||
});
|
||||
test('missing owned marker restores safely on reinstall', f => {
|
||||
f.ok(f.install()); fs.unlinkSync(f.marker); f.ok(f.install()); f.health(); f.ok(f.uninstall());
|
||||
});
|
||||
test('repair restores missing owned marker', f => {
|
||||
f.ok(f.install()); fs.unlinkSync(f.marker);
|
||||
const result = repairInstalledStates({ repoRoot: repo, homeDir: f.home, projectRoot: f.project, env: {}, targets: [f.target] });
|
||||
assert.strictEqual(result.summary.errorCount, 0); f.health(); f.ok(f.uninstall());
|
||||
});
|
||||
test('publication rechecks newly introduced unowned JS', f => {
|
||||
assert.throws(() => applyInstallPlan(f.plan(), { beforeOperationWrite({ operation }) {
|
||||
if (operation.destinationPath === f.marker) f.write(path.join(f.scripts, 'user.js'), 'export const value = 46;\n');
|
||||
} }), /unowned or edited JavaScript/);
|
||||
assert.ok(!fs.existsSync(f.marker));
|
||||
});
|
||||
test('publication rechecks nested helper package conflict', f => {
|
||||
assert.throws(() => applyInstallPlan(f.plan(), { beforeOperationWrite({ operation }) {
|
||||
if (operation.destinationPath === f.marker) f.write(path.join(f.scripts, 'lib/package.json'), '{"type":"module"}\n');
|
||||
} }), /module boundary/);
|
||||
assert.ok(!fs.existsSync(f.marker));
|
||||
});
|
||||
test('uninstall retains owned boundary for newly added user JS until it is removed', f => {
|
||||
f.write(path.join(f.project, 'package.json'), '{"type":"module"}\n');
|
||||
f.ok(f.install()); const file = path.join(f.scripts, 'user.js'); f.write(file, 'module.exports = 47;\n');
|
||||
const partial = JSON.parse(f.uninstall().stdout);
|
||||
assert.strictEqual(partial.results[0].status, 'partial');
|
||||
assert.strictEqual(partial.summary.errorCount, 0);
|
||||
assert.ok(partial.results[0].retainedPaths.includes(f.marker));
|
||||
assert.ok(fs.existsSync(f.marker)); assert.strictEqual(f.probe(file), '47');
|
||||
fs.unlinkSync(file); f.ok(f.uninstall()); assert.ok(!fs.existsSync(f.marker)); assert.ok(!fs.existsSync(f.statePath));
|
||||
});
|
||||
for (const scope of ['lib/package.json', 'lib/skill-evolution/package.json', 'ci/package.json']) {
|
||||
test(`existing CommonJS parent still rejects incompatible selected ${scope}`, f => {
|
||||
f.write(f.marker, '{"type":"commonjs","user":true}\n');
|
||||
f.write(path.join(f.scripts, scope), '{"type":"module"}\n'); const before = snapshot(f.dir);
|
||||
assert.notStrictEqual(f.install().status, 0); assert.deepStrictEqual(snapshot(f.dir), before);
|
||||
});
|
||||
}
|
||||
test('edited owned CommonJS package bytes are preserved by rejection', f => {
|
||||
f.ok(f.install()); f.write(f.marker, '{"type":"commonjs","userAdded":true}\n');
|
||||
const before = snapshot(f.dir); const result = f.install(); assert.notStrictEqual(result.status, 0);
|
||||
assert.match(result.stderr + result.stdout, /edited.*module boundary/); assert.deepStrictEqual(snapshot(f.dir), before);
|
||||
});
|
||||
for (const scope of ['package.json', 'lib/package.json']) {
|
||||
test(`symlinked ${scope} refuses before mutation`, f => {
|
||||
const file = path.join(f.scripts, scope); const outside = path.join(f.dir, 'user-package.json');
|
||||
f.write(outside, '{"type":"commonjs"}\n'); fs.mkdirSync(path.dirname(file), { recursive: true });
|
||||
fs.symlinkSync(outside, file); const before = snapshot(f.dir);
|
||||
assert.notStrictEqual(f.install().status, 0); assert.deepStrictEqual(snapshot(f.dir), before);
|
||||
});
|
||||
}
|
||||
for (const scope of ['package.json', 'lib/package.json']) {
|
||||
test(`non-file ${scope} refuses before mutation`, f => {
|
||||
fs.mkdirSync(path.join(f.scripts, scope), { recursive: true }); const before = snapshot(f.dir);
|
||||
assert.notStrictEqual(f.install().status, 0); assert.deepStrictEqual(snapshot(f.dir), before);
|
||||
});
|
||||
}
|
||||
console.log(`Passed: ${passed}`); console.log(`Failed: ${failed}`); process.exitCode = failed ? 1 : 0;
|
||||
Reference in New Issue
Block a user