slicejs-cli 3.4.0 → 3.5.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 (35) hide show
  1. package/AGENTS.md +247 -0
  2. package/client.js +63 -64
  3. package/commands/Print.js +11 -15
  4. package/commands/Validations.js +12 -23
  5. package/commands/buildProduction/buildProduction.js +23 -26
  6. package/commands/bundle/bundle.js +10 -11
  7. package/commands/createComponent/createComponent.js +14 -16
  8. package/commands/deleteComponent/deleteComponent.js +6 -6
  9. package/commands/doctor/doctor.js +11 -14
  10. package/commands/getComponent/getComponent.js +99 -162
  11. package/commands/init/init.js +77 -26
  12. package/commands/listComponents/listComponents.js +18 -21
  13. package/commands/startServer/startServer.js +21 -24
  14. package/commands/startServer/watchServer.js +7 -7
  15. package/commands/types/types.js +53 -18
  16. package/commands/utils/PathHelper.js +9 -2
  17. package/commands/utils/VersionChecker.js +3 -3
  18. package/commands/utils/bundling/DependencyAnalyzer.js +8 -16
  19. package/commands/utils/loadConfig.js +31 -0
  20. package/commands/utils/updateManager.js +3 -4
  21. package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +105 -105
  22. package/package.json +14 -2
  23. package/post.js +2 -2
  24. package/tests/bundle-generator.test.js +3 -20
  25. package/tests/component-registry-parse.test.js +34 -0
  26. package/tests/fixtures/components.js +8 -0
  27. package/tests/fixtures/sliceConfig.json +74 -0
  28. package/tests/getcomponent.test.js +407 -0
  29. package/tests/helpers/setup.js +97 -0
  30. package/tests/init-command-contract.test.js +46 -0
  31. package/tests/local-cli-delegation.test.js +7 -5
  32. package/tests/path-helper.test.js +206 -0
  33. package/tests/types-breakage.test.js +491 -0
  34. package/tests/types-generator-errors.test.js +361 -0
  35. package/tests/types-generator.test.js +172 -184
@@ -1,8 +1,8 @@
1
1
  import { test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import fs from 'node:fs';
4
- import os from 'node:os';
5
4
  import path from 'node:path';
5
+ import { createTestProject, cleanupTestProject } from './helpers/setup.js';
6
6
 
7
7
  import {
8
8
  ensureEditorConfigForTypes,
@@ -151,206 +151,194 @@ test('generateDeclarationContent creates build typing map', () => {
151
151
  });
152
152
 
153
153
  test('generateTypesFile creates declaration file from local components', async () => {
154
- const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'slice-types-'));
154
+ const tmpRoot = await createTestProject();
155
155
  const srcDir = path.join(tmpRoot, 'src');
156
- const visualDir = path.join(srcDir, 'Components', 'Visual', 'Button');
157
- const noStaticDir = path.join(srcDir, 'Components', 'Visual', 'Tabs');
158
- const serviceDir = path.join(srcDir, 'Components', 'Service', 'FetchManager');
159
- const outputFile = path.join(srcDir, 'slice-build.generated.d.ts');
160
156
 
161
- fs.mkdirSync(visualDir, { recursive: true });
162
- fs.mkdirSync(noStaticDir, { recursive: true });
163
- fs.mkdirSync(serviceDir, { recursive: true });
164
-
165
- fs.writeFileSync(
166
- path.join(srcDir, 'sliceConfig.json'),
167
- JSON.stringify(
168
- {
169
- paths: {
170
- components: {
171
- Visual: { path: '/Components/Visual', type: 'Visual' },
172
- Service: { path: '/Components/Service', type: 'Service' }
173
- }
157
+ try {
158
+ const visualDir = path.join(srcDir, 'Components', 'Visual', 'Button');
159
+ const noStaticDir = path.join(srcDir, 'Components', 'Visual', 'Tabs');
160
+ const serviceDir = path.join(srcDir, 'Components', 'Service', 'FetchManager');
161
+ const outputFile = path.join(srcDir, 'slice-build.generated.d.ts');
162
+
163
+ fs.mkdirSync(noStaticDir, { recursive: true });
164
+
165
+ fs.writeFileSync(
166
+ path.join(visualDir, 'Button.js'),
167
+ `
168
+ export default class Button extends HTMLElement {
169
+ static props = {
170
+ value: { type: 'string', default: 'Button', allowedValues: ['Button', 'Submit'] },
171
+ size: { type: 'number', allowedValues: [12, 16] },
172
+ disabled: { type: 'boolean', default: false }
173
+ };
174
+ }
175
+ `,
176
+ 'utf8'
177
+ );
178
+
179
+ fs.writeFileSync(
180
+ path.join(noStaticDir, 'Tabs.js'),
181
+ `
182
+ export default class Tabs extends HTMLElement {
183
+ constructor() {
184
+ super();
174
185
  }
175
- },
176
- null,
177
- 2
178
- ),
179
- 'utf8'
180
- );
181
-
182
- fs.writeFileSync(
183
- path.join(srcDir, 'Components', 'components.js'),
184
- `const components = {"Button": "Visual", "Tabs": "Visual", "FetchManager": "Service"};\n\nexport default components;\n`,
185
- 'utf8'
186
- );
187
-
188
- fs.writeFileSync(
189
- path.join(visualDir, 'Button.js'),
190
- `
191
- export default class Button extends HTMLElement {
192
- static props = {
193
- value: { type: 'string', default: 'Button', allowedValues: ['Button', 'Submit'] },
194
- size: { type: 'number', allowedValues: [12, 16] },
195
- disabled: { type: 'boolean', default: false }
196
- };
197
- }
198
- `,
199
- 'utf8'
200
- );
201
-
202
- fs.writeFileSync(
203
- path.join(noStaticDir, 'Tabs.js'),
204
- `
205
- export default class Tabs extends HTMLElement {
206
- constructor() {
207
- super();
208
186
  }
209
- }
210
- `,
211
- 'utf8'
212
- );
213
-
214
- fs.writeFileSync(
215
- path.join(serviceDir, 'FetchManager.js'),
216
- `
217
- export default class FetchManager extends HTMLElement {
218
- static props = {
219
- baseUrl: { type: 'string', required: true }
220
- };
221
- }
222
- `,
223
- 'utf8'
224
- );
225
-
226
- const result = await generateTypesFile({
227
- projectRoot: tmpRoot,
228
- outputPath: outputFile
229
- });
230
-
231
- assert.equal(result.componentsProcessed, 3);
232
- assert.equal(fs.existsSync(result.outputPath), true);
233
-
234
- const declaration = fs.readFileSync(result.outputPath, 'utf8');
235
- assert.match(declaration, /export interface ButtonProps/);
236
- assert.match(declaration, /value\?: 'Button' \| 'Submit';/);
237
- assert.match(declaration, /size\?: 12 \| 16;/);
238
- assert.match(declaration, /export interface TabsProps/);
239
- assert.match(declaration, /\[key: string\]: unknown;/);
240
- assert.match(declaration, /export interface FetchManagerProps/);
241
- assert.match(declaration, /Tabs: TabsProps;/);
242
- assert.match(declaration, /build<K extends SliceComponentName>/);
187
+ `,
188
+ 'utf8'
189
+ );
190
+
191
+ fs.writeFileSync(
192
+ path.join(serviceDir, 'FetchManager.js'),
193
+ `
194
+ export default class FetchManager extends HTMLElement {
195
+ static props = {
196
+ baseUrl: { type: 'string', required: true }
197
+ };
198
+ }
199
+ `,
200
+ 'utf8'
201
+ );
202
+
203
+ fs.writeFileSync(
204
+ path.join(srcDir, 'Components', 'components.js'),
205
+ 'const components = {"Button": "Visual", "Tabs": "Visual", "FetchManager": "Service"};\n\nexport default components;\n',
206
+ 'utf8'
207
+ );
208
+
209
+ const result = await generateTypesFile({
210
+ projectRoot: tmpRoot,
211
+ outputPath: outputFile
212
+ });
213
+
214
+ assert.equal(result.componentsProcessed, 3);
215
+ assert.equal(fs.existsSync(result.outputPath), true);
216
+
217
+ const declaration = fs.readFileSync(result.outputPath, 'utf8');
218
+ assert.match(declaration, /export interface ButtonProps/);
219
+ assert.match(declaration, /value\?: 'Button' \| 'Submit';/);
220
+ assert.match(declaration, /size\?: 12 \| 16;/);
221
+ assert.match(declaration, /export interface TabsProps/);
222
+ assert.match(declaration, /\[key: string\]: unknown;/);
223
+ assert.match(declaration, /export interface FetchManagerProps/);
224
+ assert.match(declaration, /Tabs: TabsProps;/);
225
+ assert.match(declaration, /build<K extends SliceComponentName>/);
226
+ } finally {
227
+ await cleanupTestProject(tmpRoot);
228
+ }
243
229
  });
244
230
 
245
231
  test('ensureEditorConfigForTypes creates jsconfig when missing', async () => {
246
- const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'slice-types-jsconfig-create-'));
232
+ const tmpRoot = await createTestProject();
247
233
  const outputFile = path.join(tmpRoot, 'src', 'slice-build.generated.d.ts');
248
234
 
249
- fs.mkdirSync(path.dirname(outputFile), { recursive: true });
250
- fs.writeFileSync(outputFile, 'export {};\n', 'utf8');
251
-
252
- const result = await ensureEditorConfigForTypes({
253
- projectRoot: tmpRoot,
254
- outputPath: outputFile
255
- });
256
-
257
- assert.equal(result.mode, 'created_jsconfig');
258
-
259
- const jsconfigPath = path.join(tmpRoot, 'jsconfig.json');
260
- assert.equal(fs.existsSync(jsconfigPath), true);
261
-
262
- const jsconfig = JSON.parse(fs.readFileSync(jsconfigPath, 'utf8'));
263
- assert.equal(Array.isArray(jsconfig.include), true);
264
- assert.equal(jsconfig.include.includes('src/Components/**/*.js'), true);
265
- assert.equal(jsconfig.include.includes('src/**/*.d.ts'), true);
266
- assert.equal(jsconfig.include.includes('api/**/*.js'), false);
267
- assert.equal(jsconfig.include.includes('tests/**/*.js'), false);
268
- assert.equal(jsconfig.include.includes('src/**/*.js'), false);
269
- assert.equal(jsconfig.compilerOptions.checkJs, true);
270
- assert.equal(jsconfig.compilerOptions.strictNullChecks, false);
271
- assert.equal(jsconfig.compilerOptions.noImplicitAny, false);
272
- assert.equal(jsconfig.compilerOptions.strict, false);
273
- assert.equal(Array.isArray(jsconfig.exclude), true);
274
- assert.equal(jsconfig.exclude.includes('src/libs/**'), true);
275
- assert.equal(jsconfig.exclude.includes('tests/**'), true);
235
+ try {
236
+ fs.writeFileSync(outputFile, 'export {};\n', 'utf8');
237
+
238
+ const result = await ensureEditorConfigForTypes({
239
+ projectRoot: tmpRoot,
240
+ outputPath: outputFile
241
+ });
242
+
243
+ assert.equal(result.mode, 'created_jsconfig');
244
+
245
+ const jsconfigPath = path.join(tmpRoot, 'jsconfig.json');
246
+ assert.equal(fs.existsSync(jsconfigPath), true);
247
+
248
+ const jsconfig = JSON.parse(fs.readFileSync(jsconfigPath, 'utf8'));
249
+ assert.equal(Array.isArray(jsconfig.include), true);
250
+ assert.equal(jsconfig.include.includes('src/Components/**/*.js'), true);
251
+ assert.equal(jsconfig.include.includes('src/**/*.d.ts'), true);
252
+ assert.equal(jsconfig.include.includes('api/**/*.js'), false);
253
+ assert.equal(jsconfig.include.includes('tests/**/*.js'), false);
254
+ assert.equal(jsconfig.include.includes('src/**/*.js'), false);
255
+ assert.equal(jsconfig.compilerOptions.checkJs, true);
256
+ assert.equal(jsconfig.compilerOptions.strictNullChecks, false);
257
+ assert.equal(jsconfig.compilerOptions.noImplicitAny, false);
258
+ assert.equal(jsconfig.compilerOptions.strict, false);
259
+ assert.equal(Array.isArray(jsconfig.exclude), true);
260
+ assert.equal(jsconfig.exclude.includes('src/libs/**'), true);
261
+ assert.equal(jsconfig.exclude.includes('tests/**'), true);
262
+ } finally {
263
+ await cleanupTestProject(tmpRoot);
264
+ }
276
265
  });
277
266
 
278
- test('ensureNoCheckInPublicVendorFiles adds ts-nocheck to js files in publicFolders', async () => {
279
- const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'slice-types-publicfolders-'));
267
+ test('types functions use PathHelper with explicit projectRoot', async () => {
268
+ const tmpRoot = await createTestProject({ visualComponents: ['Button'] });
280
269
  const srcDir = path.join(tmpRoot, 'src');
281
- const libsDir = path.join(srcDir, 'libs', 'vendor');
282
- const assetsDir = path.join(srcDir, 'assets', 'scripts');
283
-
284
- fs.mkdirSync(libsDir, { recursive: true });
285
- fs.mkdirSync(assetsDir, { recursive: true });
286
-
287
- fs.writeFileSync(
288
- path.join(srcDir, 'sliceConfig.json'),
289
- JSON.stringify(
290
- {
291
- publicFolders: ['/libs', '/assets'],
292
- paths: { components: {} }
293
- },
294
- null,
295
- 2
296
- ),
297
- 'utf8'
298
- );
299
-
300
- const vendorA = path.join(libsDir, 'a.js');
301
- const vendorB = path.join(assetsDir, 'b.js');
302
- fs.writeFileSync(vendorA, 'const a = 1;\n', 'utf8');
303
- fs.writeFileSync(vendorB, '// @ts-nocheck\nconst b = 2;\n', 'utf8');
304
-
305
- const result = await ensureNoCheckInPublicVendorFiles(tmpRoot);
306
-
307
- assert.equal(result.scannedFiles, 2);
308
- assert.equal(result.updatedFiles, 1);
309
- assert.equal(fs.readFileSync(vendorA, 'utf8').startsWith('// @ts-nocheck\n'), true);
310
- assert.equal(fs.readFileSync(vendorB, 'utf8').startsWith('// @ts-nocheck\n'), true);
270
+ const outputFile = path.join(srcDir, 'slice-build.generated.d.ts');
271
+
272
+ try {
273
+ const visualDir = path.join(srcDir, 'Components', 'Visual', 'Button');
274
+ fs.writeFileSync(
275
+ path.join(visualDir, 'Button.js'),
276
+ `export default class Button extends HTMLElement {
277
+ static props = { value: { type: 'string' } };
278
+ }`,
279
+ 'utf8'
280
+ );
281
+
282
+ const result = await generateTypesFile({
283
+ projectRoot: tmpRoot,
284
+ outputPath: outputFile
285
+ });
286
+
287
+ assert.equal(result.componentsProcessed, 1);
288
+ assert.equal(fs.existsSync(result.outputPath), true);
289
+
290
+ const declaration = fs.readFileSync(result.outputPath, 'utf8');
291
+ assert.match(declaration, /export interface ButtonProps/);
292
+ assert.match(declaration, /build<K extends SliceComponentName>/);
293
+ } finally {
294
+ await cleanupTestProject(tmpRoot);
295
+ }
311
296
  });
312
297
 
313
298
  test('ensureEditorConfigForTypes augments existing jsconfig include list', async () => {
314
- const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'slice-types-jsconfig-update-'));
299
+ const tmpRoot = await createTestProject();
315
300
  const outputFile = path.join(tmpRoot, 'src', 'slice-build.generated.d.ts');
316
301
  const jsconfigPath = path.join(tmpRoot, 'jsconfig.json');
317
302
 
318
- fs.mkdirSync(path.dirname(outputFile), { recursive: true });
319
- fs.writeFileSync(outputFile, 'export {};\n', 'utf8');
320
- fs.writeFileSync(
321
- jsconfigPath,
322
- JSON.stringify(
323
- {
324
- compilerOptions: {
325
- allowJs: true
303
+ try {
304
+ fs.writeFileSync(outputFile, 'export {};\n', 'utf8');
305
+ fs.writeFileSync(
306
+ jsconfigPath,
307
+ JSON.stringify(
308
+ {
309
+ compilerOptions: {
310
+ allowJs: true
311
+ },
312
+ include: ['src/Components/**/*.js', 'src/**/*.js', 'api/**/*.js', 'tests/**/*.js']
326
313
  },
327
- include: ['src/Components/**/*.js', 'src/**/*.js', 'api/**/*.js', 'tests/**/*.js']
328
- },
329
- null,
330
- 2
331
- ),
332
- 'utf8'
333
- );
334
-
335
- const result = await ensureEditorConfigForTypes({
336
- projectRoot: tmpRoot,
337
- outputPath: outputFile
338
- });
339
-
340
- assert.equal(result.mode, 'updated_jsconfig');
341
-
342
- const jsconfig = JSON.parse(fs.readFileSync(jsconfigPath, 'utf8'));
343
- assert.equal(jsconfig.include.includes('src/Components/**/*.js'), true);
344
- assert.equal(jsconfig.include.includes('src/**/*.d.ts'), true);
345
- assert.equal(jsconfig.include.includes('src/**/*.js'), false);
346
- assert.equal(jsconfig.include.includes('api/**/*.js'), false);
347
- assert.equal(jsconfig.include.includes('tests/**/*.js'), false);
348
- assert.equal(jsconfig.compilerOptions.allowJs, true);
349
- assert.equal(jsconfig.compilerOptions.checkJs, true);
350
- assert.equal(jsconfig.compilerOptions.noImplicitAny, false);
351
- assert.equal(jsconfig.compilerOptions.strictNullChecks, false);
352
- assert.equal(jsconfig.compilerOptions.strict, false);
353
- assert.equal(Array.isArray(jsconfig.exclude), true);
354
- assert.equal(jsconfig.exclude.includes('src/libs/**'), true);
355
- assert.equal(jsconfig.exclude.includes('tests/**'), true);
314
+ null,
315
+ 2
316
+ ),
317
+ 'utf8'
318
+ );
319
+
320
+ const result = await ensureEditorConfigForTypes({
321
+ projectRoot: tmpRoot,
322
+ outputPath: outputFile
323
+ });
324
+
325
+ assert.equal(result.mode, 'updated_jsconfig');
326
+
327
+ const jsconfig = JSON.parse(fs.readFileSync(jsconfigPath, 'utf8'));
328
+ assert.equal(jsconfig.include.includes('src/Components/**/*.js'), true);
329
+ assert.equal(jsconfig.include.includes('src/**/*.d.ts'), true);
330
+ assert.equal(jsconfig.include.includes('src/**/*.js'), false);
331
+ assert.equal(jsconfig.include.includes('api/**/*.js'), false);
332
+ assert.equal(jsconfig.include.includes('tests/**/*.js'), false);
333
+ assert.equal(jsconfig.compilerOptions.allowJs, true);
334
+ assert.equal(jsconfig.compilerOptions.checkJs, true);
335
+ assert.equal(jsconfig.compilerOptions.noImplicitAny, false);
336
+ assert.equal(jsconfig.compilerOptions.strictNullChecks, false);
337
+ assert.equal(jsconfig.compilerOptions.strict, false);
338
+ assert.equal(Array.isArray(jsconfig.exclude), true);
339
+ assert.equal(jsconfig.exclude.includes('src/libs/**'), true);
340
+ assert.equal(jsconfig.exclude.includes('tests/**'), true);
341
+ } finally {
342
+ await cleanupTestProject(tmpRoot);
343
+ }
356
344
  });