slicejs-cli 3.5.1 → 3.6.0

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.
Files changed (74) hide show
  1. package/README.md +34 -15
  2. package/client.js +67 -20
  3. package/commands/doctor/doctor.js +69 -3
  4. package/commands/getComponent/getComponent.js +33 -25
  5. package/commands/init/init.js +106 -28
  6. package/commands/utils/PackageManager.js +148 -0
  7. package/commands/utils/VersionChecker.js +6 -4
  8. package/commands/utils/sliceScripts.js +21 -0
  9. package/commands/utils/updateManager.js +54 -35
  10. package/package.json +12 -1
  11. package/post.js +8 -16
  12. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -29
  13. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -25
  14. package/.github/pull_request_template.md +0 -22
  15. package/.github/workflows/ci.yml +0 -43
  16. package/AGENTS.md +0 -247
  17. package/CODE_OF_CONDUCT.md +0 -126
  18. package/ECOSYSTEM.md +0 -9
  19. package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +0 -182
  20. package/playwright.config.js +0 -51
  21. package/tests/build-command-integration.test.js +0 -87
  22. package/tests/build-production-e2e.test.js +0 -140
  23. package/tests/builder-edge-cases.test.js +0 -322
  24. package/tests/bundle-generate-e2e.test.js +0 -115
  25. package/tests/bundle-generator.test.js +0 -691
  26. package/tests/bundle-v2-register-output.test.js +0 -470
  27. package/tests/bundling-dependency-edges.test.js +0 -127
  28. package/tests/bundling-imports-unit.test.js +0 -267
  29. package/tests/client-launcher-contract.test.js +0 -211
  30. package/tests/client-update-flow-contract.test.js +0 -272
  31. package/tests/commands-component-crud.test.js +0 -102
  32. package/tests/commands-doctor.test.js +0 -80
  33. package/tests/commands-version-checker.test.js +0 -37
  34. package/tests/component-registry-parse.test.js +0 -34
  35. package/tests/dependency-analyzer.test.js +0 -24
  36. package/tests/e2e/bundles.spec.js +0 -91
  37. package/tests/e2e/dependency-scenarios.spec.js +0 -56
  38. package/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +0 -136
  39. package/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +0 -149
  40. package/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +0 -45
  41. package/tests/e2e/fixtures/components/Visual/Button/Button.css +0 -106
  42. package/tests/e2e/fixtures/components/Visual/Button/Button.html +0 -5
  43. package/tests/e2e/fixtures/components/Visual/Button/Button.js +0 -158
  44. package/tests/e2e/fixtures/components/Visual/Link/Link.js +0 -33
  45. package/tests/e2e/fixtures/components/Visual/Loading/Loading.css +0 -56
  46. package/tests/e2e/fixtures/components/Visual/Loading/Loading.html +0 -83
  47. package/tests/e2e/fixtures/components/Visual/Loading/Loading.js +0 -164
  48. package/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +0 -167
  49. package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +0 -116
  50. package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +0 -44
  51. package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +0 -180
  52. package/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +0 -20
  53. package/tests/e2e/fixtures/components/Visual/Route/Route.js +0 -181
  54. package/tests/e2e/fixtures/components/registry.json +0 -12
  55. package/tests/e2e/fixtures/vendor-components.mjs +0 -65
  56. package/tests/e2e/navigation.spec.js +0 -44
  57. package/tests/e2e/render.spec.js +0 -34
  58. package/tests/e2e/serve.mjs +0 -264
  59. package/tests/e2e/shared-deps.spec.js +0 -61
  60. package/tests/e2e/unminified.spec.js +0 -33
  61. package/tests/e2e-serve.test.js +0 -148
  62. package/tests/fixtures/components.js +0 -8
  63. package/tests/fixtures/sliceConfig.json +0 -74
  64. package/tests/getcomponent.test.js +0 -407
  65. package/tests/helpers/setup.js +0 -102
  66. package/tests/init-command-contract.test.js +0 -46
  67. package/tests/local-cli-delegation.test.js +0 -81
  68. package/tests/path-helper.test.js +0 -206
  69. package/tests/perf-budget.test.js +0 -86
  70. package/tests/postinstall-command.test.js +0 -72
  71. package/tests/types-breakage.test.js +0 -491
  72. package/tests/types-generator-errors.test.js +0 -361
  73. package/tests/types-generator.test.js +0 -346
  74. package/tests/update-manager-notifications.test.js +0 -88
@@ -1,407 +0,0 @@
1
- import { test, describe, before, after, mock } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import fs from 'fs-extra';
4
- import path from 'path';
5
- import { createTestProject, cleanupTestProject, withTestProject } from './helpers/setup.js';
6
-
7
- describe('runConcurrent', () => {
8
- let runConcurrent;
9
-
10
- before(async () => {
11
- ({ runConcurrent } = await import('../commands/getComponent/getComponent.js'));
12
- });
13
-
14
- test('runs all items', async () => {
15
- const results = [];
16
- const worker = async (item) => { results.push(item); };
17
- await runConcurrent([1, 2, 3], worker, 5);
18
- assert.deepEqual(results.sort(), [1, 2, 3]);
19
- });
20
-
21
- test('respects concurrency limit', async () => {
22
- let concurrent = 0;
23
- let maxConcurrent = 0;
24
- const worker = async (item) => {
25
- concurrent++;
26
- maxConcurrent = Math.max(maxConcurrent, concurrent);
27
- await new Promise(r => setTimeout(r, 10));
28
- concurrent--;
29
- };
30
- await runConcurrent([1, 2, 3, 4, 5], worker, 2);
31
- assert.equal(maxConcurrent <= 2, true);
32
- });
33
-
34
- test('handles empty array', async () => {
35
- const worker = async () => {};
36
- await runConcurrent([], worker, 3);
37
- assert.ok(true);
38
- });
39
-
40
- test('propagates worker errors', async () => {
41
- const worker = async () => { throw new Error('worker fail'); };
42
- await assert.rejects(() => runConcurrent([1], worker, 3), /worker fail/);
43
- });
44
-
45
- test('default concurrency is 3', async () => {
46
- let maxConcurrent = 0;
47
- let running = 0;
48
- const worker = async () => {
49
- running++;
50
- maxConcurrent = Math.max(maxConcurrent, running);
51
- await new Promise(r => setTimeout(r, 10));
52
- running--;
53
- };
54
- await runConcurrent([1, 2, 3, 4, 5], worker);
55
- assert.equal(maxConcurrent <= 3, true);
56
- });
57
- });
58
-
59
- describe('loadConfig', () => {
60
- test('returns config object when sliceConfig.json exists', async () => {
61
- await withTestProject(async (tmpDir) => {
62
- const { loadConfig } = await import('../commands/getComponent/getComponent.js');
63
- const config = await loadConfig();
64
- assert.notEqual(config, null);
65
- assert.equal(typeof config, 'object');
66
- assert.equal(config.server?.port, 3001);
67
- });
68
- });
69
-
70
- test('returns null when sliceConfig.json is missing', async () => {
71
- const tmpDir = await createTestProject();
72
- try {
73
- process.env.INIT_CWD = tmpDir;
74
- const fixtureConfig = new URL('../tests/fixtures/sliceConfig.json', import.meta.url);
75
- const fs = await import('node:fs/promises');
76
- const path = await import('node:path');
77
- const destPath = path.join(tmpDir, 'src', 'sliceConfig.json');
78
- try { await fs.unlink(destPath); } catch {}
79
- const { loadConfig } = await import('../commands/getComponent/getComponent.js');
80
- const config = await loadConfig();
81
- assert.equal(config, null);
82
- } finally {
83
- delete process.env.INIT_CWD;
84
- await cleanupTestProject(tmpDir);
85
- }
86
- });
87
- });
88
-
89
- describe('ComponentRegistry.getAvailableComponents', () => {
90
- let ComponentRegistry;
91
-
92
- before(async () => {
93
- ({ ComponentRegistry } = await import('../commands/getComponent/getComponent.js'));
94
- });
95
-
96
- const makeRegistry = (entries) => {
97
- const registry = new ComponentRegistry();
98
- registry.componentsRegistry = { ...entries };
99
- return registry;
100
- };
101
-
102
- test('returns empty object when registry not loaded', () => {
103
- const registry = new ComponentRegistry();
104
- const result = registry.getAvailableComponents();
105
- assert.deepEqual(result, {});
106
- });
107
-
108
- test('routing components (Route, MultiRoute, Link) return only .js', () => {
109
- const registry = makeRegistry({
110
- Route: 'Visual',
111
- MultiRoute: 'Visual',
112
- Link: 'Visual'
113
- });
114
- const result = registry.getAvailableComponents('Visual');
115
- assert.deepEqual(result.Route.files, ['Route.js']);
116
- assert.deepEqual(result.MultiRoute.files, ['MultiRoute.js']);
117
- assert.deepEqual(result.Link.files, ['Link.js']);
118
- });
119
-
120
- test('NotFound returns .js, .html, .css like other visual components', () => {
121
- const registry = makeRegistry({
122
- NotFound: 'Visual',
123
- Button: 'Visual',
124
- Navbar: 'Visual'
125
- });
126
- const result = registry.getAvailableComponents('Visual');
127
- assert.deepEqual(result.NotFound.files, ['NotFound.js', 'NotFound.html', 'NotFound.css']);
128
- assert.deepEqual(result.Button.files, ['Button.js', 'Button.html', 'Button.css']);
129
- assert.deepEqual(result.Navbar.files, ['Navbar.js', 'Navbar.html', 'Navbar.css']);
130
- });
131
-
132
- test('Service components return only .js', () => {
133
- const registry = makeRegistry({
134
- FetchManager: 'Service'
135
- });
136
- const result = registry.getAvailableComponents('Service');
137
- assert.deepEqual(result.FetchManager.files, ['FetchManager.js']);
138
- });
139
-
140
- test('filters by category - Visual', () => {
141
- const registry = makeRegistry({
142
- Button: 'Visual',
143
- FetchManager: 'Service'
144
- });
145
- const result = registry.getAvailableComponents('Visual');
146
- assert.ok(result.Button);
147
- assert.ok(!result.FetchManager);
148
- });
149
-
150
- test('filters by category - Service', () => {
151
- const registry = makeRegistry({
152
- Button: 'Visual',
153
- FetchManager: 'Service'
154
- });
155
- const result = registry.getAvailableComponents('Service');
156
- assert.ok(!result.Button);
157
- assert.ok(result.FetchManager);
158
- });
159
-
160
- test('returns all categories when no filter', () => {
161
- const registry = makeRegistry({
162
- Button: 'Visual',
163
- FetchManager: 'Service'
164
- });
165
- const result = registry.getAvailableComponents();
166
- assert.ok(result.Button);
167
- assert.ok(result.FetchManager);
168
- });
169
- });
170
-
171
- describe('fetchWithRetry', () => {
172
- let fetchWithRetry;
173
- let originalFetch;
174
-
175
- before(async () => {
176
- ({ fetchWithRetry } = await import('../commands/getComponent/getComponent.js'));
177
- });
178
-
179
- after(() => {
180
- if (originalFetch) globalThis.fetch = originalFetch;
181
- });
182
-
183
- test('succeeds on first attempt', async () => {
184
- originalFetch = globalThis.fetch;
185
- globalThis.fetch = mock.fn(async () => ({
186
- ok: true,
187
- text: async () => 'response body'
188
- }));
189
- const result = await fetchWithRetry('https://example.com');
190
- assert.equal(result, 'response body');
191
- assert.equal(globalThis.fetch.mock.calls.length, 1);
192
- globalThis.fetch = originalFetch;
193
- });
194
-
195
- test('retries and eventually succeeds', async () => {
196
- let attempts = 0;
197
- originalFetch = globalThis.fetch;
198
- globalThis.fetch = mock.fn(async () => {
199
- attempts++;
200
- if (attempts < 3) throw new Error('network error');
201
- return { ok: true, text: async () => 'success after retry' };
202
- });
203
- const result = await fetchWithRetry('https://example.com', 3, 5);
204
- assert.equal(result, 'success after retry');
205
- assert.equal(attempts, 3);
206
- globalThis.fetch = originalFetch;
207
- });
208
-
209
- test('fails after exhausting retries', async () => {
210
- originalFetch = globalThis.fetch;
211
- globalThis.fetch = mock.fn(async () => { throw new Error('persistent error'); });
212
- await assert.rejects(() => fetchWithRetry('https://example.com', 2, 5), /persistent error/);
213
- globalThis.fetch = originalFetch;
214
- });
215
-
216
- test('rejects non-ok response', async () => {
217
- originalFetch = globalThis.fetch;
218
- globalThis.fetch = mock.fn(async () => ({
219
- ok: false,
220
- status: 404,
221
- statusText: 'Not Found'
222
- }));
223
- await assert.rejects(() => fetchWithRetry('https://example.com', 2, 5), /HTTP 404/);
224
- globalThis.fetch = originalFetch;
225
- });
226
- });
227
-
228
- describe('filterOfficialComponents', () => {
229
- let ComponentRegistry;
230
-
231
- before(async () => {
232
- ({ ComponentRegistry } = await import('../commands/getComponent/getComponent.js'));
233
- });
234
-
235
- test('includes Visual and Service components', () => {
236
- const registry = new ComponentRegistry();
237
- const result = registry.filterOfficialComponents({
238
- Button: 'Visual',
239
- FetchManager: 'Service'
240
- });
241
- assert.deepEqual(result, { Button: 'Visual', FetchManager: 'Service' });
242
- });
243
-
244
- test('excludes AppComponents and other categories', () => {
245
- const registry = new ComponentRegistry();
246
- const result = registry.filterOfficialComponents({
247
- Button: 'Visual',
248
- AppShell: 'AppComponents',
249
- SomeOther: 'Unknown'
250
- });
251
- assert.deepEqual(result, { Button: 'Visual' });
252
- });
253
-
254
- test('returns empty object for empty input', () => {
255
- const registry = new ComponentRegistry();
256
- const result = registry.filterOfficialComponents({});
257
- assert.deepEqual(result, {});
258
- });
259
-
260
- test('returns empty when no Visual or Service components', () => {
261
- const registry = new ComponentRegistry();
262
- const result = registry.filterOfficialComponents({
263
- AppShell: 'AppComponents',
264
- Custom: 'Other'
265
- });
266
- assert.deepEqual(result, {});
267
- });
268
- });
269
-
270
- describe('findComponentInRegistry', () => {
271
- let ComponentRegistry;
272
-
273
- before(async () => {
274
- ({ ComponentRegistry } = await import('../commands/getComponent/getComponent.js'));
275
- });
276
-
277
- const makeRegistry = (entries) => {
278
- const registry = new ComponentRegistry();
279
- registry.componentsRegistry = { ...entries };
280
- return registry;
281
- };
282
-
283
- test('finds component by name (case-insensitive)', () => {
284
- const registry = makeRegistry({ Button: 'Visual' });
285
- const result = registry.findComponentInRegistry('button');
286
- assert.deepEqual(result, { name: 'Button', category: 'Visual' });
287
- });
288
-
289
- test('returns null for non-existent component', () => {
290
- const registry = makeRegistry({ Button: 'Visual' });
291
- const result = registry.findComponentInRegistry('NonExistent');
292
- assert.equal(result, null);
293
- });
294
-
295
- test('returns null when registry not loaded', () => {
296
- const registry = new ComponentRegistry();
297
- const result = registry.findComponentInRegistry('Button');
298
- assert.equal(result, null);
299
- });
300
-
301
- test('finds component with exact case', () => {
302
- const registry = makeRegistry({ FetchManager: 'Service' });
303
- const result = registry.findComponentInRegistry('FetchManager');
304
- assert.deepEqual(result, { name: 'FetchManager', category: 'Service' });
305
- });
306
- });
307
-
308
- describe('getLocalComponents', () => {
309
- let ComponentRegistry;
310
-
311
- before(async () => {
312
- ({ ComponentRegistry } = await import('../commands/getComponent/getComponent.js'));
313
- });
314
-
315
- test('returns components from local components.js', async () => {
316
- await withTestProject(async (tmpDir) => {
317
- const registry = new ComponentRegistry();
318
- const result = await registry.getLocalComponents();
319
- assert.deepEqual(result, { Button: 'Visual' });
320
- }, { visualComponents: ['Button'] });
321
- });
322
-
323
- test('returns empty object when no components.js exists', async () => {
324
- const tmpDir = await createTestProject();
325
- try {
326
- process.env.INIT_CWD = tmpDir;
327
- const componentsPath = path.join(tmpDir, 'src', 'Components', 'components.js');
328
- await fs.remove(componentsPath);
329
- const registry = new ComponentRegistry();
330
- const result = await registry.getLocalComponents();
331
- assert.deepEqual(result, {});
332
- } finally {
333
- delete process.env.INIT_CWD;
334
- await cleanupTestProject(tmpDir);
335
- }
336
- });
337
-
338
- test('returns empty object when components.js has invalid format', async () => {
339
- const tmpDir = await createTestProject();
340
- try {
341
- process.env.INIT_CWD = tmpDir;
342
- const componentsPath = path.join(tmpDir, 'src', 'Components', 'components.js');
343
- await fs.writeFile(componentsPath, 'not valid javascript', 'utf8');
344
- const registry = new ComponentRegistry();
345
- const result = await registry.getLocalComponents();
346
- assert.deepEqual(result, {});
347
- } finally {
348
- delete process.env.INIT_CWD;
349
- await cleanupTestProject(tmpDir);
350
- }
351
- });
352
- });
353
-
354
- describe('updateLocalRegistrySafe', () => {
355
- let ComponentRegistry;
356
-
357
- before(async () => {
358
- ({ ComponentRegistry } = await import('../commands/getComponent/getComponent.js'));
359
- });
360
-
361
- test('registers a new component in components.js', async () => {
362
- await withTestProject(async (tmpDir) => {
363
- const registry = new ComponentRegistry();
364
- await registry.updateLocalRegistrySafe('NewComp', 'Visual');
365
- const components = await registry.getLocalComponents();
366
- assert.equal(components.NewComp, 'Visual');
367
- }, { visualComponents: [] });
368
- });
369
-
370
- test('does not duplicate existing component', async () => {
371
- await withTestProject(async (tmpDir) => {
372
- const registry = new ComponentRegistry();
373
- await registry.updateLocalRegistrySafe('Button', 'Visual');
374
- const components = await registry.getLocalComponents();
375
- assert.equal(components.Button, 'Visual');
376
- assert.deepEqual(Object.keys(components).filter(k => k === 'Button'), ['Button']);
377
- }, { visualComponents: ['Button'] });
378
- });
379
- });
380
-
381
- describe('findUpdatableComponents', () => {
382
- let ComponentRegistry;
383
-
384
- before(async () => {
385
- ({ ComponentRegistry } = await import('../commands/getComponent/getComponent.js'));
386
- });
387
-
388
- test('returns empty when no local components match remote registry', async () => {
389
- await withTestProject(async (tmpDir) => {
390
- const registry = new ComponentRegistry();
391
- registry.componentsRegistry = { SomeRemote: 'Visual' };
392
- const result = await registry.findUpdatableComponents();
393
- assert.deepEqual(result, []);
394
- }, { visualComponents: ['Button'] });
395
- });
396
-
397
- test('returns components that exist both locally and remotely', async () => {
398
- await withTestProject(async (tmpDir) => {
399
- const registry = new ComponentRegistry();
400
- registry.componentsRegistry = { Button: 'Visual', FetchManager: 'Service' };
401
- const result = await registry.findUpdatableComponents();
402
- assert.equal(result.length, 1);
403
- assert.equal(result[0].name, 'Button');
404
- assert.equal(result[0].category, 'Visual');
405
- }, { visualComponents: ['Button'] });
406
- });
407
- });
@@ -1,102 +0,0 @@
1
- import fs from 'fs-extra'
2
- import path from 'path'
3
- import os from 'os'
4
- import { fileURLToPath } from 'url'
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
- // Prefer the sibling framework dev repo when present (local monorepo checkout);
8
- // otherwise fall back to the installed framework package (e.g. CI, where only
9
- // this repo is checked out). Both ship the same src/ + api/ starter structure.
10
- const SIBLING_FW = path.resolve(__dirname, '../../../slice.js')
11
- const PACKAGE_FW = path.resolve(__dirname, '../../node_modules/slicejs-web-framework')
12
- const FW_DIR = fs.pathExistsSync(SIBLING_FW) ? SIBLING_FW : PACKAGE_FW
13
- const FIXTURES_DIR = path.resolve(__dirname, '../fixtures')
14
-
15
- let counter = 0
16
-
17
- export async function createTestProject(options = {}) {
18
- const {
19
- visualComponents = [],
20
- frameworkDir = FW_DIR,
21
- } = options
22
-
23
- const dir = await fs.mkdtemp(path.join(os.tmpdir(), `slice-test-${process.pid}-${counter++}-`))
24
-
25
- const fwExists = await fs.pathExists(frameworkDir)
26
- const srcDir = path.join(dir, 'src')
27
-
28
- if (fwExists) {
29
- const fwSrc = path.join(frameworkDir, 'src')
30
- const fwApi = path.join(frameworkDir, 'api')
31
-
32
- if (await fs.pathExists(fwSrc)) {
33
- await fs.copy(fwSrc, srcDir)
34
- }
35
- if (await fs.pathExists(fwApi)) {
36
- await fs.copy(fwApi, path.join(dir, 'api'))
37
- }
38
- }
39
-
40
- if (!(await fs.pathExists(srcDir))) {
41
- await createMinimalScaffold(dir)
42
- }
43
-
44
- await fs.ensureDir(path.join(srcDir, 'Components', 'Visual'))
45
-
46
- if (visualComponents.length > 0) {
47
- const registryLines = visualComponents.map(n => ` "${n}": "Visual"`).join(',\n')
48
- await fs.writeFile(
49
- path.join(srcDir, 'Components', 'components.js'),
50
- `const components = {\n${registryLines}\n}; export default components;\n`
51
- )
52
- for (const name of visualComponents) {
53
- const compDir = path.join(srcDir, 'Components', 'Visual', name)
54
- await fs.ensureDir(compDir)
55
- await fs.writeFile(path.join(compDir, `${name}.js`), `export default class ${name} {}`)
56
- }
57
- }
58
-
59
- return dir
60
- }
61
-
62
- export async function cleanupTestProject(dir) {
63
- await fs.remove(dir)
64
- }
65
-
66
- export async function withTestProject(fn, options = {}) {
67
- const dir = await createTestProject(options)
68
- const origInitCwd = process.env.INIT_CWD
69
- try {
70
- process.env.INIT_CWD = dir
71
- return await fn(dir)
72
- } finally {
73
- if (origInitCwd === undefined) {
74
- delete process.env.INIT_CWD
75
- } else {
76
- process.env.INIT_CWD = origInitCwd
77
- }
78
- await cleanupTestProject(dir)
79
- }
80
- }
81
-
82
- async function createMinimalScaffold(dir) {
83
- const srcDir = path.join(dir, 'src')
84
- await fs.ensureDir(path.join(srcDir, 'App'))
85
- await fs.ensureDir(path.join(srcDir, 'Components', 'AppComponents', 'AppShell'))
86
- await fs.ensureDir(path.join(srcDir, 'Components', 'Service', 'FetchManager'))
87
- await fs.ensureDir(path.join(srcDir, 'Components', 'Visual'))
88
- await fs.ensureDir(path.join(srcDir, 'Styles'))
89
- await fs.ensureDir(path.join(srcDir, 'Themes'))
90
- await fs.ensureDir(path.join(dir, 'api', 'middleware'))
91
- await fs.ensureDir(path.join(dir, 'api', 'utils'))
92
-
93
- const fixtureConfig = path.join(FIXTURES_DIR, 'sliceConfig.json')
94
- if (await fs.pathExists(fixtureConfig)) {
95
- await fs.copy(fixtureConfig, path.join(srcDir, 'sliceConfig.json'))
96
- }
97
-
98
- const fixtureComponents = path.join(FIXTURES_DIR, 'components.js')
99
- if (await fs.pathExists(fixtureComponents)) {
100
- await fs.copy(fixtureComponents, path.join(srcDir, 'Components', 'components.js'))
101
- }
102
- }
@@ -1,46 +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
-
7
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
- const clientPath = path.join(__dirname, '..', 'client.js');
9
- const source = fs.readFileSync(clientPath, 'utf-8');
10
-
11
- test('init command is registered in client.js', () => {
12
- const hasInit = source.includes('.command("init")');
13
- assert.ok(hasInit, 'client.js must register an "init" command');
14
- });
15
-
16
- test('init command has a description', () => {
17
- const match = source.match(/\.command\(["']init["']\)\s*\.description\(["']([^"']+)["']\)/);
18
- assert.ok(match, 'init command must have a .description() call');
19
- assert.ok(match[1].length > 0, 'init command description must not be empty');
20
- });
21
-
22
- test('init command has -y / --yes option', () => {
23
- const hasShort = source.includes('"-y, --yes');
24
- assert.ok(hasShort, 'init command must have a -y/--yes option for non-interactive use');
25
- });
26
-
27
- test('init command action calls initializeProject', () => {
28
- const hasCall = source.includes('initializeProject()');
29
- assert.ok(hasCall, 'init command action must call initializeProject');
30
- });
31
-
32
- test('init command normalizes project name (lowercase, hyphens)', () => {
33
- const hasFilter = source.includes('.toLowerCase()');
34
- assert.ok(hasFilter, 'init command must normalize project name to lowercase');
35
- assert.ok(source.includes('.replace(/\\s+/g'), 'init command must replace spaces with hyphens');
36
- });
37
-
38
- test('init command validates project name', () => {
39
- assert.ok(source.includes("'Project name cannot be empty'"), 'init command must validate non-empty name');
40
- assert.ok(source.includes("'Use a simple name, not a path'"), 'init command must reject path separators');
41
- });
42
-
43
- test('init command creates project directory', () => {
44
- assert.ok(source.includes('fs.mkdirSync(projectDir'), 'init command must create the project directory');
45
- assert.ok(source.includes('process.chdir(projectDir)'), 'init command must chdir into project');
46
- });
@@ -1,81 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import fs from 'node:fs';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
- import {
7
- findNearestLocalCliEntry,
8
- resolveLocalCliCandidate,
9
- shouldDelegateToLocalCli,
10
- isLocalDelegationDisabled
11
- } from '../commands/utils/LocalCliDelegation.js';
12
-
13
- test('isLocalDelegationDisabled returns true when env flag is set', () => {
14
- const env = { SLICE_NO_LOCAL_DELEGATION: '1' };
15
- assert.equal(isLocalDelegationDisabled(env), true);
16
- });
17
-
18
- test('isLocalDelegationDisabled returns false when env flag is missing', () => {
19
- const env = {};
20
- assert.equal(isLocalDelegationDisabled(env), false);
21
- });
22
-
23
- test('shouldDelegateToLocalCli is false when candidate is null', () => {
24
- assert.equal(shouldDelegateToLocalCli('/tmp/current/client.js', null), false);
25
- });
26
-
27
- test('shouldDelegateToLocalCli is false when candidate realpath equals current realpath', () => {
28
- const same = '/tmp/current/client.js';
29
- assert.equal(shouldDelegateToLocalCli(same, same), false);
30
- });
31
-
32
- test('shouldDelegateToLocalCli is true when candidate differs from current', () => {
33
- const current = '/tmp/global/client.js';
34
- const local = '/tmp/project/node_modules/slicejs-cli/client.js';
35
- assert.equal(shouldDelegateToLocalCli(current, local), true);
36
- });
37
-
38
- test('findNearestLocalCliEntry returns null when no candidate resolver hits', () => {
39
- const cwd = path.join('/tmp', 'slice-nonexistent-project');
40
- const resolver = () => null;
41
- const result = findNearestLocalCliEntry(cwd, resolver);
42
- assert.equal(result, null);
43
- });
44
-
45
- test('findNearestLocalCliEntry returns first match while traversing upward', () => {
46
- const cwd = path.resolve('/repo/apps/web/src');
47
- const matchDir = path.resolve('/repo/apps/web');
48
- const matchResult = path.resolve('/repo/apps/web/node_modules/slicejs-cli/client.js');
49
- const calls = [];
50
- const resolver = (dir) => {
51
- calls.push(dir);
52
- if (dir === matchDir) {
53
- return matchResult;
54
- }
55
- return null;
56
- };
57
-
58
- const result = findNearestLocalCliEntry(cwd, resolver);
59
-
60
- assert.equal(result, matchResult);
61
- assert.deepEqual(calls, [cwd, matchDir]);
62
- });
63
-
64
- test('findNearestLocalCliEntry returns null when resolver is not a function', () => {
65
- const result = findNearestLocalCliEntry('/repo/apps/web/src', null);
66
- assert.equal(result, null);
67
- });
68
-
69
- test('resolveLocalCliCandidate returns null when candidate path is a directory', () => {
70
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'slice-cli-delegation-'));
71
- const candidateDirectory = path.join(tempRoot, 'node_modules', 'slicejs-cli', 'client.js');
72
-
73
- fs.mkdirSync(candidateDirectory, { recursive: true });
74
-
75
- try {
76
- const result = resolveLocalCliCandidate(tempRoot);
77
- assert.equal(result, null);
78
- } finally {
79
- fs.rmSync(tempRoot, { recursive: true, force: true });
80
- }
81
- });