slicejs-cli 3.5.1 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -26
- package/client.js +73 -23
- package/commands/buildProduction/buildProduction.js +6 -3
- package/commands/doctor/doctor.js +68 -3
- package/commands/getComponent/getComponent.js +33 -25
- package/commands/init/init.js +176 -49
- package/commands/utils/PackageManager.js +148 -0
- package/commands/utils/VersionChecker.js +6 -4
- package/commands/utils/sliceScripts.js +23 -0
- package/commands/utils/updateManager.js +54 -35
- package/package.json +12 -1
- package/post.js +13 -19
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -29
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -25
- package/.github/pull_request_template.md +0 -22
- package/.github/workflows/ci.yml +0 -43
- package/AGENTS.md +0 -247
- package/CODE_OF_CONDUCT.md +0 -126
- package/ECOSYSTEM.md +0 -9
- package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +0 -182
- package/playwright.config.js +0 -51
- package/tests/build-command-integration.test.js +0 -87
- package/tests/build-production-e2e.test.js +0 -140
- package/tests/builder-edge-cases.test.js +0 -322
- package/tests/bundle-generate-e2e.test.js +0 -115
- package/tests/bundle-generator.test.js +0 -691
- package/tests/bundle-v2-register-output.test.js +0 -470
- package/tests/bundling-dependency-edges.test.js +0 -127
- package/tests/bundling-imports-unit.test.js +0 -267
- package/tests/client-launcher-contract.test.js +0 -211
- package/tests/client-update-flow-contract.test.js +0 -272
- package/tests/commands-component-crud.test.js +0 -102
- package/tests/commands-doctor.test.js +0 -80
- package/tests/commands-version-checker.test.js +0 -37
- package/tests/component-registry-parse.test.js +0 -34
- package/tests/dependency-analyzer.test.js +0 -24
- package/tests/e2e/bundles.spec.js +0 -91
- package/tests/e2e/dependency-scenarios.spec.js +0 -56
- package/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +0 -136
- package/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +0 -149
- package/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +0 -45
- package/tests/e2e/fixtures/components/Visual/Button/Button.css +0 -106
- package/tests/e2e/fixtures/components/Visual/Button/Button.html +0 -5
- package/tests/e2e/fixtures/components/Visual/Button/Button.js +0 -158
- package/tests/e2e/fixtures/components/Visual/Link/Link.js +0 -33
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.css +0 -56
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.html +0 -83
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.js +0 -164
- package/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +0 -167
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +0 -116
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +0 -44
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +0 -180
- package/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +0 -20
- package/tests/e2e/fixtures/components/Visual/Route/Route.js +0 -181
- package/tests/e2e/fixtures/components/registry.json +0 -12
- package/tests/e2e/fixtures/vendor-components.mjs +0 -65
- package/tests/e2e/navigation.spec.js +0 -44
- package/tests/e2e/render.spec.js +0 -34
- package/tests/e2e/serve.mjs +0 -264
- package/tests/e2e/shared-deps.spec.js +0 -61
- package/tests/e2e/unminified.spec.js +0 -33
- package/tests/e2e-serve.test.js +0 -148
- package/tests/fixtures/components.js +0 -8
- package/tests/fixtures/sliceConfig.json +0 -74
- package/tests/getcomponent.test.js +0 -407
- package/tests/helpers/setup.js +0 -102
- package/tests/init-command-contract.test.js +0 -46
- package/tests/local-cli-delegation.test.js +0 -81
- package/tests/path-helper.test.js +0 -206
- package/tests/perf-budget.test.js +0 -86
- package/tests/postinstall-command.test.js +0 -72
- package/tests/types-breakage.test.js +0 -491
- package/tests/types-generator-errors.test.js +0 -361
- package/tests/types-generator.test.js +0 -346
- package/tests/update-manager-notifications.test.js +0 -88
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import { test, describe } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import { parse } from '@babel/parser';
|
|
4
|
-
import BundleGenerator from '../commands/utils/bundling/BundleGenerator.js';
|
|
5
|
-
|
|
6
|
-
// These tests exercise the cross-module import / variable handling of the
|
|
7
|
-
// bundler in isolation. They are written to assert *correct* behaviour, so a
|
|
8
|
-
// failure here documents a real gap/bug in the bundler rather than the test.
|
|
9
|
-
|
|
10
|
-
function makeGenerator(sliceConfig = {}) {
|
|
11
|
-
const gen = new BundleGenerator(import.meta.url, null, {});
|
|
12
|
-
gen.sliceConfig = sliceConfig;
|
|
13
|
-
return gen;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Evaluate a generated JS snippet in a throwaway scope and return whatever the
|
|
17
|
-
// trailing `return` yields. Used to verify the *runtime* behaviour of the code
|
|
18
|
-
// the bundler emits (not just its shape).
|
|
19
|
-
function evalSnippet(code) {
|
|
20
|
-
// eslint-disable-next-line no-new-func
|
|
21
|
-
return new Function(code)();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
describe('classifyImport', () => {
|
|
25
|
-
const gen = makeGenerator({ publicFolders: ['/assets', '/Themes'] });
|
|
26
|
-
const publicFolders = gen.getConfiguredPublicFolders();
|
|
27
|
-
|
|
28
|
-
test('relative imports are dropped silently (no warning)', () => {
|
|
29
|
-
const r = gen.classifyImport('./Foo.js', publicFolders);
|
|
30
|
-
assert.equal(r.keep, false);
|
|
31
|
-
assert.equal(r.warning, null);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('parent-relative imports are dropped silently', () => {
|
|
35
|
-
const r = gen.classifyImport('../shared/util.js', publicFolders);
|
|
36
|
-
assert.equal(r.keep, false);
|
|
37
|
-
assert.equal(r.warning, null);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test('absolute imports inside publicFolders are kept', () => {
|
|
41
|
-
const r = gen.classifyImport('/assets/lib/x.js', publicFolders);
|
|
42
|
-
assert.equal(r.keep, true);
|
|
43
|
-
assert.equal(r.warning, null);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test('absolute import exactly matching a publicFolder is kept', () => {
|
|
47
|
-
const r = gen.classifyImport('/assets', publicFolders);
|
|
48
|
-
assert.equal(r.keep, true);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('absolute imports outside publicFolders are dropped with warning', () => {
|
|
52
|
-
const r = gen.classifyImport('/secret/key.js', publicFolders);
|
|
53
|
-
assert.equal(r.keep, false);
|
|
54
|
-
assert.match(r.warning, /outside publicFolders/);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test('a publicFolder name must not match by prefix only (/assetsX)', () => {
|
|
58
|
-
const r = gen.classifyImport('/assetsX/x.js', publicFolders);
|
|
59
|
-
assert.equal(r.keep, false, '/assetsX should NOT be treated as inside /assets');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('bare specifier imports are dropped with warning', () => {
|
|
63
|
-
const r = gen.classifyImport('lodash', publicFolders);
|
|
64
|
-
assert.equal(r.keep, false);
|
|
65
|
-
assert.match(r.warning, /bare import/);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('non-string import path is handled defensively', () => {
|
|
69
|
-
const r = gen.classifyImport(undefined, publicFolders);
|
|
70
|
-
assert.equal(r.keep, false);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe('stripImports', () => {
|
|
75
|
-
test('removes relative + bare imports, keeps body intact', () => {
|
|
76
|
-
const gen = makeGenerator({ publicFolders: [] });
|
|
77
|
-
const code = [
|
|
78
|
-
"import Foo from './Foo.js';",
|
|
79
|
-
"import { x } from 'pkg';",
|
|
80
|
-
'const value = 42;',
|
|
81
|
-
'export default value;',
|
|
82
|
-
].join('\n');
|
|
83
|
-
const out = gen.stripImports(code, { collectHoistedImports: true });
|
|
84
|
-
assert.ok(!out.code.includes("from './Foo.js'"));
|
|
85
|
-
assert.ok(!out.code.includes("from 'pkg'"));
|
|
86
|
-
assert.ok(out.code.includes('const value = 42;'));
|
|
87
|
-
assert.deepEqual(out.hoistedImports, []);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test('hoists kept public-folder imports instead of inlining them', () => {
|
|
91
|
-
const gen = makeGenerator({ publicFolders: ['/assets'] });
|
|
92
|
-
const code = "import lib from '/assets/lib.js';\nconst a = lib;";
|
|
93
|
-
const out = gen.stripImports(code, { collectHoistedImports: true });
|
|
94
|
-
assert.equal(out.hoistedImports.length, 1);
|
|
95
|
-
assert.match(out.hoistedImports[0], /\/assets\/lib\.js/);
|
|
96
|
-
// The hoisted import must NOT remain inline in the body.
|
|
97
|
-
assert.ok(!out.code.includes("import lib from '/assets/lib.js'"));
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('code with no imports is returned unchanged', () => {
|
|
101
|
-
const gen = makeGenerator();
|
|
102
|
-
const code = 'const a = 1;\nconst b = 2;';
|
|
103
|
-
const out = gen.stripImports(code, { collectHoistedImports: true });
|
|
104
|
-
assert.equal(out.code, code);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test('falls back to regex scanner when Babel cannot parse', () => {
|
|
108
|
-
const gen = makeGenerator({ publicFolders: [] });
|
|
109
|
-
// `@decorator` + class field syntax with no plugin -> Babel throws -> fallback.
|
|
110
|
-
const code = "import x from './x.js';\nconst valid = 1;\nthis is not ::: valid js @@@";
|
|
111
|
-
// Should not throw; relative import must still be stripped.
|
|
112
|
-
const out = gen.stripImports(code, { collectHoistedImports: true });
|
|
113
|
-
assert.ok(!out.code.includes("from './x.js'"));
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe('extractLocalBindingsFromImportStatement', () => {
|
|
118
|
-
const gen = makeGenerator();
|
|
119
|
-
|
|
120
|
-
test('default import', () => {
|
|
121
|
-
assert.deepEqual(gen.extractLocalBindingsFromImportStatement("import Foo from 'x';"), ['Foo']);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test('named imports', () => {
|
|
125
|
-
assert.deepEqual(
|
|
126
|
-
gen.extractLocalBindingsFromImportStatement("import { a, b } from 'x';").sort(),
|
|
127
|
-
['a', 'b']
|
|
128
|
-
);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test('aliased named import uses the local name', () => {
|
|
132
|
-
assert.deepEqual(gen.extractLocalBindingsFromImportStatement("import { a as b } from 'x';"), ['b']);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test('namespace import', () => {
|
|
136
|
-
assert.deepEqual(gen.extractLocalBindingsFromImportStatement("import * as NS from 'x';"), ['NS']);
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
test('mixed default + named', () => {
|
|
140
|
-
assert.deepEqual(
|
|
141
|
-
gen.extractLocalBindingsFromImportStatement("import Foo, { a, b as c } from 'x';").sort(),
|
|
142
|
-
['Foo', 'a', 'c']
|
|
143
|
-
);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('side-effect import has no bindings', () => {
|
|
147
|
-
assert.deepEqual(gen.extractLocalBindingsFromImportStatement("import 'x';"), []);
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
describe('validateHoistedImportCollisions', () => {
|
|
152
|
-
const gen = makeGenerator();
|
|
153
|
-
|
|
154
|
-
test('throws on the same binding from two different statements', () => {
|
|
155
|
-
assert.throws(
|
|
156
|
-
() => gen.validateHoistedImportCollisions([
|
|
157
|
-
"import Foo from '/assets/a.js';",
|
|
158
|
-
"import Foo from '/assets/b.js';",
|
|
159
|
-
]),
|
|
160
|
-
/binding collision/
|
|
161
|
-
);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test('does not throw when the identical statement appears twice', () => {
|
|
165
|
-
assert.doesNotThrow(() => gen.validateHoistedImportCollisions([
|
|
166
|
-
"import Foo from '/assets/a.js';",
|
|
167
|
-
"import Foo from '/assets/a.js';",
|
|
168
|
-
]));
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test('throws when a hoisted binding collides with a reserved identifier', () => {
|
|
172
|
-
assert.throws(
|
|
173
|
-
() => gen.validateHoistedImportCollisions(
|
|
174
|
-
["import SLICE_BUNDLE_META from '/assets/a.js';"],
|
|
175
|
-
new Set(['SLICE_BUNDLE_META'])
|
|
176
|
-
),
|
|
177
|
-
/reserved identifier collision/
|
|
178
|
-
);
|
|
179
|
-
});
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
describe('transformDependencyContent', () => {
|
|
183
|
-
const gen = makeGenerator();
|
|
184
|
-
|
|
185
|
-
function transformAndEval(content, moduleName = 'shared/util.js') {
|
|
186
|
-
const transformed = gen.transformDependencyContent(content, '__d', moduleName);
|
|
187
|
-
return evalSnippet(`const __d = {};\n${transformed}\nreturn __d;`);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
test('export const / let / var are attached to the exports object', () => {
|
|
191
|
-
const d = transformAndEval('export const A = 1;\nexport let B = 2;\nexport var C = 3;');
|
|
192
|
-
assert.equal(d.A, 1);
|
|
193
|
-
assert.equal(d.B, 2);
|
|
194
|
-
assert.equal(d.C, 3);
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test('export function is attached and callable', () => {
|
|
198
|
-
const d = transformAndEval('export function fn() { return 7; }');
|
|
199
|
-
assert.equal(typeof d.fn, 'function');
|
|
200
|
-
assert.equal(d.fn(), 7);
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test('export default maps to the <basename>Data fallback key', () => {
|
|
204
|
-
const d = transformAndEval('export default 99;', 'shared/util.js');
|
|
205
|
-
assert.equal(d.utilData, 99);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test('plain export { a, b } re-exports local bindings', () => {
|
|
209
|
-
const d = transformAndEval('const a = 10;\nconst b = 20;\nexport { a, b };');
|
|
210
|
-
assert.equal(d.a, 10);
|
|
211
|
-
assert.equal(d.b, 20);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
test('aliased export { internal as Public } exposes the PUBLIC name', () => {
|
|
215
|
-
// A consumer writes `import { Public } from '...'`, so the exports object
|
|
216
|
-
// must carry `Public`, mapped to the value of `internal`.
|
|
217
|
-
const d = transformAndEval('const internal = 55;\nexport { internal as Public };');
|
|
218
|
-
assert.equal(d.Public, 55, 'exported alias name should be the public key');
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
describe('default export resolver (__sliceResolveDefaultExport)', () => {
|
|
223
|
-
const gen = makeGenerator();
|
|
224
|
-
function buildResolver() {
|
|
225
|
-
const lines = gen.getDefaultExportResolverLines().join('\n');
|
|
226
|
-
return evalSnippet(`${lines}\nreturn __sliceResolveDefaultExport;`);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
test('returns .default when present', () => {
|
|
230
|
-
const resolve = buildResolver();
|
|
231
|
-
assert.equal(resolve({ default: 5, other: 9 }, 'm', null), 5);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test('returns the single non-default key when only one exists', () => {
|
|
235
|
-
const resolve = buildResolver();
|
|
236
|
-
assert.equal(resolve({ only: 42 }, 'm', null), 42);
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
test('honours the preferred key', () => {
|
|
240
|
-
const resolve = buildResolver();
|
|
241
|
-
assert.equal(resolve({ a: 1, utilData: 2 }, 'm', 'utilData'), 2);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
test('primitive dependency is returned as-is', () => {
|
|
245
|
-
const resolve = buildResolver();
|
|
246
|
-
assert.equal(resolve(7, 'm', null), 7);
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
describe('toSafeIdentifier', () => {
|
|
251
|
-
const gen = makeGenerator();
|
|
252
|
-
|
|
253
|
-
test('produces a valid JS identifier', () => {
|
|
254
|
-
const id = gen.toSafeIdentifier('My-Component');
|
|
255
|
-
assert.doesNotThrow(() => parse(`const ${id} = 1;`));
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test('distinct component names must not collide to the same identifier', () => {
|
|
259
|
-
// Two registered components differing only by a non-alphanumeric char would
|
|
260
|
-
// otherwise emit two `const <id>` declarations -> duplicate-decl syntax error.
|
|
261
|
-
assert.notEqual(
|
|
262
|
-
gen.toSafeIdentifier('my-btn'),
|
|
263
|
-
gen.toSafeIdentifier('my_btn'),
|
|
264
|
-
'distinct names collapse to the same bundle identifier'
|
|
265
|
-
);
|
|
266
|
-
});
|
|
267
|
-
});
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import { test } from 'node:test';
|
|
2
|
-
import assert from 'node:assert/strict';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { parse } from '@babel/parser';
|
|
7
|
-
import babelTraverse from '@babel/traverse';
|
|
8
|
-
|
|
9
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
const clientPath = path.join(__dirname, '..', 'client.js');
|
|
11
|
-
const source = fs.readFileSync(clientPath, 'utf-8');
|
|
12
|
-
const ast = parse(source, {
|
|
13
|
-
sourceType: 'module',
|
|
14
|
-
plugins: []
|
|
15
|
-
});
|
|
16
|
-
const traverse = babelTraverse.default || babelTraverse;
|
|
17
|
-
|
|
18
|
-
function walk(node, visit) {
|
|
19
|
-
if (!node || typeof node !== 'object') {
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
visit(node);
|
|
24
|
-
|
|
25
|
-
for (const value of Object.values(node)) {
|
|
26
|
-
if (Array.isArray(value)) {
|
|
27
|
-
for (const item of value) {
|
|
28
|
-
walk(item, visit);
|
|
29
|
-
}
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
walk(value, visit);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function getImportedBindings(fromSource) {
|
|
38
|
-
const bindings = new Map();
|
|
39
|
-
|
|
40
|
-
for (const statement of ast.program.body) {
|
|
41
|
-
if (statement.type !== 'ImportDeclaration') {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (statement.source.value !== fromSource) {
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
for (const specifier of statement.specifiers) {
|
|
50
|
-
if (specifier.type === 'ImportSpecifier' && specifier.imported.type === 'Identifier') {
|
|
51
|
-
bindings.set(specifier.imported.name, specifier.local.name);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return bindings;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function getBoundImportedCallPositions(fromSource, importedName) {
|
|
60
|
-
const positions = [];
|
|
61
|
-
const importedBindings = getImportedBindings(fromSource);
|
|
62
|
-
const localName = importedBindings.get(importedName);
|
|
63
|
-
|
|
64
|
-
if (!localName) {
|
|
65
|
-
return positions;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
traverse(ast, {
|
|
69
|
-
CallExpression(callPath) {
|
|
70
|
-
if (callPath.node.callee.type !== 'Identifier') {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (callPath.node.callee.name !== localName) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const binding = callPath.scope.getBinding(localName);
|
|
79
|
-
if (!binding) {
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (binding.path.node.type !== 'ImportSpecifier') {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (binding.path.parent.type !== 'ImportDeclaration') {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (binding.path.parent.source.value !== fromSource) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (binding.path.node.imported.type !== 'Identifier') {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (binding.path.node.imported.name !== importedName) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
positions.push(callPath.node.start);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
return positions;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function getCommandRegistrationPositions() {
|
|
111
|
-
const positions = [];
|
|
112
|
-
|
|
113
|
-
walk(ast.program, (node) => {
|
|
114
|
-
if (node.type !== 'CallExpression') {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
node.callee.type === 'MemberExpression' &&
|
|
120
|
-
!node.callee.computed &&
|
|
121
|
-
node.callee.property.type === 'Identifier' &&
|
|
122
|
-
node.callee.property.name === 'command'
|
|
123
|
-
) {
|
|
124
|
-
positions.push(node.start);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
return positions;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const launcherModulePath = './commands/utils/LocalCliDelegation.js';
|
|
132
|
-
const commandRegistrationPositions = getCommandRegistrationPositions();
|
|
133
|
-
const firstCommandRegistrationPos = Math.min(...commandRegistrationPositions);
|
|
134
|
-
|
|
135
|
-
test('client imports LocalCliDelegation utility', () => {
|
|
136
|
-
const importedBindings = getImportedBindings(launcherModulePath);
|
|
137
|
-
|
|
138
|
-
assert.ok(
|
|
139
|
-
importedBindings.size > 0,
|
|
140
|
-
'Contract clause failed: client.js must import launcher helpers from ./commands/utils/LocalCliDelegation.js'
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
for (const requiredImport of [
|
|
144
|
-
'isLocalDelegationDisabled',
|
|
145
|
-
'findNearestLocalCliEntry',
|
|
146
|
-
'shouldDelegateToLocalCli'
|
|
147
|
-
]) {
|
|
148
|
-
assert.ok(
|
|
149
|
-
importedBindings.has(requiredImport),
|
|
150
|
-
`Contract clause failed: client.js must import ${requiredImport} from ${launcherModulePath}`
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
test('client checks SLICE_NO_LOCAL_DELEGATION behavior before command runtime', () => {
|
|
156
|
-
const isDisabledCalls = getBoundImportedCallPositions(
|
|
157
|
-
launcherModulePath,
|
|
158
|
-
'isLocalDelegationDisabled'
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
assert.ok(
|
|
162
|
-
isDisabledCalls.length > 0,
|
|
163
|
-
'Contract clause failed: client.js must call imported isLocalDelegationDisabled() in launcher path'
|
|
164
|
-
);
|
|
165
|
-
|
|
166
|
-
assert.ok(
|
|
167
|
-
commandRegistrationPositions.length > 0,
|
|
168
|
-
'Contract clause failed: client.js must define at least one .command(...) registration before launcher ordering checks'
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
assert.ok(
|
|
172
|
-
isDisabledCalls.some((pos) => pos < firstCommandRegistrationPos),
|
|
173
|
-
'Contract clause failed: isLocalDelegationDisabled() must be evaluated before command registration/runtime wiring'
|
|
174
|
-
);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
test('client performs local candidate resolution and delegation decision', () => {
|
|
178
|
-
const findNearestCalls = getBoundImportedCallPositions(
|
|
179
|
-
launcherModulePath,
|
|
180
|
-
'findNearestLocalCliEntry'
|
|
181
|
-
);
|
|
182
|
-
const shouldDelegateCalls = getBoundImportedCallPositions(
|
|
183
|
-
launcherModulePath,
|
|
184
|
-
'shouldDelegateToLocalCli'
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
assert.ok(
|
|
188
|
-
findNearestCalls.length > 0,
|
|
189
|
-
'Contract clause failed: client.js must call imported findNearestLocalCliEntry() to resolve local CLI candidate in launcher path'
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
assert.ok(
|
|
193
|
-
shouldDelegateCalls.length > 0,
|
|
194
|
-
'Contract clause failed: client.js must call imported shouldDelegateToLocalCli() to gate delegation in launcher path'
|
|
195
|
-
);
|
|
196
|
-
|
|
197
|
-
assert.ok(
|
|
198
|
-
commandRegistrationPositions.length > 0,
|
|
199
|
-
'Contract clause failed: client.js must define at least one .command(...) registration before launcher ordering checks'
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
assert.ok(
|
|
203
|
-
findNearestCalls.some((pos) => pos < firstCommandRegistrationPos),
|
|
204
|
-
'Contract clause failed: findNearestLocalCliEntry() must execute before command registration/runtime wiring'
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
assert.ok(
|
|
208
|
-
shouldDelegateCalls.some((pos) => pos < firstCommandRegistrationPos),
|
|
209
|
-
'Contract clause failed: shouldDelegateToLocalCli() must execute before command registration/runtime wiring'
|
|
210
|
-
);
|
|
211
|
-
});
|