slicejs-cli 3.3.0 → 3.4.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.
Files changed (46) hide show
  1. package/AGENTS.md +247 -0
  2. package/LICENSE +21 -21
  3. package/client.js +663 -626
  4. package/commands/Print.js +163 -167
  5. package/commands/Validations.js +92 -103
  6. package/commands/build/build.js +40 -40
  7. package/commands/buildProduction/buildProduction.js +576 -579
  8. package/commands/bundle/bundle.js +234 -235
  9. package/commands/createComponent/VisualComponentTemplate.js +55 -55
  10. package/commands/createComponent/createComponent.js +124 -126
  11. package/commands/deleteComponent/deleteComponent.js +77 -77
  12. package/commands/doctor/doctor.js +366 -369
  13. package/commands/getComponent/getComponent.js +684 -747
  14. package/commands/init/init.js +269 -261
  15. package/commands/listComponents/listComponents.js +172 -175
  16. package/commands/startServer/startServer.js +261 -264
  17. package/commands/startServer/watchServer.js +79 -79
  18. package/commands/types/types.js +69 -27
  19. package/commands/utils/LocalCliDelegation.js +53 -53
  20. package/commands/utils/PathHelper.js +75 -68
  21. package/commands/utils/VersionChecker.js +167 -167
  22. package/commands/utils/bundling/BundleGenerator.js +2292 -2292
  23. package/commands/utils/bundling/DependencyAnalyzer.js +925 -933
  24. package/commands/utils/loadConfig.js +31 -0
  25. package/commands/utils/updateManager.js +452 -453
  26. package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +105 -105
  27. package/package.json +58 -46
  28. package/post.js +66 -65
  29. package/tests/bundle-generator.test.js +691 -708
  30. package/tests/bundle-v2-register-output.test.js +470 -470
  31. package/tests/client-launcher-contract.test.js +211 -211
  32. package/tests/client-update-flow-contract.test.js +272 -272
  33. package/tests/component-registry-parse.test.js +34 -0
  34. package/tests/dependency-analyzer.test.js +24 -24
  35. package/tests/fixtures/components.js +8 -0
  36. package/tests/fixtures/sliceConfig.json +74 -0
  37. package/tests/getcomponent.test.js +407 -0
  38. package/tests/helpers/setup.js +97 -0
  39. package/tests/init-command-contract.test.js +46 -0
  40. package/tests/local-cli-delegation.test.js +81 -79
  41. package/tests/path-helper.test.js +206 -0
  42. package/tests/types-breakage.test.js +491 -0
  43. package/tests/types-generator-errors.test.js +361 -0
  44. package/tests/types-generator.test.js +172 -184
  45. package/tests/update-manager-notifications.test.js +88 -88
  46. package/.github/workflows/docs-render-cicd.yml +0 -65
@@ -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
  });
@@ -1,88 +1,88 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import Print from '../commands/Print.js';
4
- import { UpdateManager } from '../commands/utils/updateManager.js';
5
-
6
- test('notifyAvailableUpdates shows advisory output and never invokes interactive update flow', async () => {
7
- const manager = new UpdateManager();
8
- const calls = {
9
- display: 0,
10
- prompted: 0,
11
- info: [],
12
- error: []
13
- };
14
-
15
- manager.checkForUpdates = async () => ({
16
- hasUpdates: true,
17
- updates: [
18
- {
19
- name: 'slicejs-web-framework',
20
- displayName: 'Slice.js Framework',
21
- current: '2.4.3',
22
- latest: '2.5.0',
23
- type: 'framework'
24
- }
25
- ],
26
- allCurrent: false
27
- });
28
-
29
- manager.displayUpdates = () => {
30
- calls.display += 1;
31
- };
32
-
33
- manager.checkAndPromptUpdates = async () => {
34
- calls.prompted += 1;
35
- return true;
36
- };
37
-
38
- const originalInfo = Print.info;
39
- const originalError = Print.error;
40
-
41
- Print.info = (message) => calls.info.push(message);
42
- Print.error = (message) => calls.error.push(message);
43
-
44
- try {
45
- const result = await manager.notifyAvailableUpdates();
46
-
47
- assert.equal(result, true);
48
- assert.equal(calls.display, 1);
49
- assert.equal(calls.prompted, 0);
50
- assert.equal(calls.error.length, 0);
51
- assert.equal(calls.info.length, 1);
52
- assert.match(calls.info[0], /slice update/);
53
- } finally {
54
- Print.info = originalInfo;
55
- Print.error = originalError;
56
- }
57
- });
58
-
59
- test('notifyAvailableUpdates returns false and prints nothing when no updates are available', async () => {
60
- const manager = new UpdateManager();
61
- const calls = {
62
- display: 0,
63
- info: []
64
- };
65
-
66
- manager.checkForUpdates = async () => ({
67
- hasUpdates: false,
68
- updates: [],
69
- allCurrent: true
70
- });
71
-
72
- manager.displayUpdates = () => {
73
- calls.display += 1;
74
- };
75
-
76
- const originalInfo = Print.info;
77
- Print.info = (message) => calls.info.push(message);
78
-
79
- try {
80
- const result = await manager.notifyAvailableUpdates();
81
-
82
- assert.equal(result, false);
83
- assert.equal(calls.display, 0);
84
- assert.equal(calls.info.length, 0);
85
- } finally {
86
- Print.info = originalInfo;
87
- }
88
- });
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import Print from '../commands/Print.js';
4
+ import { UpdateManager } from '../commands/utils/updateManager.js';
5
+
6
+ test('notifyAvailableUpdates shows advisory output and never invokes interactive update flow', async () => {
7
+ const manager = new UpdateManager();
8
+ const calls = {
9
+ display: 0,
10
+ prompted: 0,
11
+ info: [],
12
+ error: []
13
+ };
14
+
15
+ manager.checkForUpdates = async () => ({
16
+ hasUpdates: true,
17
+ updates: [
18
+ {
19
+ name: 'slicejs-web-framework',
20
+ displayName: 'Slice.js Framework',
21
+ current: '2.4.3',
22
+ latest: '2.5.0',
23
+ type: 'framework'
24
+ }
25
+ ],
26
+ allCurrent: false
27
+ });
28
+
29
+ manager.displayUpdates = () => {
30
+ calls.display += 1;
31
+ };
32
+
33
+ manager.checkAndPromptUpdates = async () => {
34
+ calls.prompted += 1;
35
+ return true;
36
+ };
37
+
38
+ const originalInfo = Print.info;
39
+ const originalError = Print.error;
40
+
41
+ Print.info = (message) => calls.info.push(message);
42
+ Print.error = (message) => calls.error.push(message);
43
+
44
+ try {
45
+ const result = await manager.notifyAvailableUpdates();
46
+
47
+ assert.equal(result, true);
48
+ assert.equal(calls.display, 1);
49
+ assert.equal(calls.prompted, 0);
50
+ assert.equal(calls.error.length, 0);
51
+ assert.equal(calls.info.length, 1);
52
+ assert.match(calls.info[0], /slice update/);
53
+ } finally {
54
+ Print.info = originalInfo;
55
+ Print.error = originalError;
56
+ }
57
+ });
58
+
59
+ test('notifyAvailableUpdates returns false and prints nothing when no updates are available', async () => {
60
+ const manager = new UpdateManager();
61
+ const calls = {
62
+ display: 0,
63
+ info: []
64
+ };
65
+
66
+ manager.checkForUpdates = async () => ({
67
+ hasUpdates: false,
68
+ updates: [],
69
+ allCurrent: true
70
+ });
71
+
72
+ manager.displayUpdates = () => {
73
+ calls.display += 1;
74
+ };
75
+
76
+ const originalInfo = Print.info;
77
+ Print.info = (message) => calls.info.push(message);
78
+
79
+ try {
80
+ const result = await manager.notifyAvailableUpdates();
81
+
82
+ assert.equal(result, false);
83
+ assert.equal(calls.display, 0);
84
+ assert.equal(calls.info.length, 0);
85
+ } finally {
86
+ Print.info = originalInfo;
87
+ }
88
+ });
@@ -1,65 +0,0 @@
1
- name: Docs Parse + Render CD
2
-
3
- on:
4
- pull_request:
5
- branches: [main]
6
- push:
7
- branches: [main]
8
- workflow_dispatch:
9
-
10
- permissions:
11
- contents: write
12
-
13
- jobs:
14
- docs-pipeline:
15
- runs-on: ubuntu-latest
16
-
17
- steps:
18
- - name: Checkout
19
- uses: actions/checkout@v4
20
- with:
21
- fetch-depth: 0
22
-
23
- - name: Setup Node
24
- uses: actions/setup-node@v4
25
- with:
26
- node-version: '20'
27
- cache: 'npm'
28
-
29
- - name: Install dependencies
30
- run: npm ci
31
-
32
- - name: Validate markdown docs
33
- run: npm run docs:lint-md
34
-
35
- - name: Generate documentation artifacts
36
- run: npm run docs:generate
37
-
38
- - name: Check generated changes (PR)
39
- if: github.event_name == 'pull_request'
40
- run: |
41
- if ! git diff --quiet; then
42
- echo "Generated files are out of date. Run 'npm run docs:generate' and commit changes."
43
- git status --short
44
- exit 1
45
- fi
46
-
47
- - name: Commit generated docs (main)
48
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
49
- run: |
50
- if ! git diff --quiet; then
51
- git config user.name "github-actions[bot]"
52
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
53
- git add src/Components src/Styles src/routes.js
54
- git commit -m "chore(docs): auto-generate docs artifacts from markdown"
55
- git push
56
- else
57
- echo "No generated changes to commit."
58
- fi
59
-
60
- - name: Trigger Render deploy
61
- if: github.event_name == 'push' && github.ref == 'refs/heads/main' && secrets.RENDER_DEPLOY_HOOK_URL != ''
62
- run: |
63
- curl --fail --silent --show-error --request POST "$RENDER_DEPLOY_HOOK_URL"
64
- env:
65
- RENDER_DEPLOY_HOOK_URL: ${{ secrets.RENDER_DEPLOY_HOOK_URL }}