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,272 +1,272 @@
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
-
8
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
- const clientPath = path.join(__dirname, '..', 'client.js');
10
- const clientSource = fs.readFileSync(clientPath, 'utf-8');
11
- const ast = parse(clientSource, {
12
- sourceType: 'module',
13
- errorRecovery: true,
14
- loc: true,
15
- ranges: true
16
- });
17
-
18
- function lineOf(node) {
19
- return node && node.loc && node.loc.start ? node.loc.start.line : null;
20
- }
21
-
22
- function walk(node, visit) {
23
- if (!node || typeof node !== 'object') {
24
- return;
25
- }
26
-
27
- visit(node);
28
-
29
- for (const value of Object.values(node)) {
30
- if (Array.isArray(value)) {
31
- for (const item of value) {
32
- walk(item, visit);
33
- }
34
- continue;
35
- }
36
-
37
- walk(value, visit);
38
- }
39
- }
40
-
41
- function isMethodCall(node, methodName) {
42
- return (
43
- node &&
44
- node.type === 'CallExpression' &&
45
- node.callee &&
46
- node.callee.type === 'MemberExpression' &&
47
- node.callee.property &&
48
- node.callee.property.type === 'Identifier' &&
49
- node.callee.property.name === methodName
50
- );
51
- }
52
-
53
- function isCommandUpdateExpression(node) {
54
- if (!node || node.type !== 'CallExpression') {
55
- return false;
56
- }
57
-
58
- if (
59
- node.callee &&
60
- node.callee.type === 'MemberExpression' &&
61
- node.callee.property &&
62
- node.callee.property.type === 'Identifier' &&
63
- node.callee.property.name === 'command' &&
64
- node.arguments[0] &&
65
- node.arguments[0].type === 'StringLiteral' &&
66
- node.arguments[0].value === 'update'
67
- ) {
68
- return true;
69
- }
70
-
71
- if (node.callee && node.callee.type === 'MemberExpression') {
72
- return isCommandUpdateExpression(node.callee.object);
73
- }
74
-
75
- return false;
76
- }
77
-
78
- function getRunWithVersionCheckNode() {
79
- let foundNode = null;
80
-
81
- walk(ast, (node) => {
82
- if (
83
- node.type === 'FunctionDeclaration' &&
84
- node.id &&
85
- node.id.type === 'Identifier' &&
86
- node.id.name === 'runWithVersionCheck'
87
- ) {
88
- foundNode = node;
89
- return;
90
- }
91
-
92
- if (node.type !== 'VariableDeclarator') {
93
- return;
94
- }
95
-
96
- if (!node.id || node.id.type !== 'Identifier' || node.id.name !== 'runWithVersionCheck') {
97
- return;
98
- }
99
-
100
- if (
101
- node.init &&
102
- (node.init.type === 'ArrowFunctionExpression' || node.init.type === 'FunctionExpression')
103
- ) {
104
- foundNode = node.init;
105
- }
106
- });
107
-
108
- return foundNode;
109
- }
110
-
111
- function getFunctionBodyNode(fnNode) {
112
- if (!fnNode) {
113
- return null;
114
- }
115
-
116
- if (fnNode.type === 'FunctionDeclaration' || fnNode.type === 'FunctionExpression' || fnNode.type === 'ArrowFunctionExpression') {
117
- return fnNode.body;
118
- }
119
-
120
- return null;
121
- }
122
-
123
- function getFunctionLikeByName(name) {
124
- let foundNode = null;
125
-
126
- walk(ast, (node) => {
127
- if (
128
- node.type === 'FunctionDeclaration' &&
129
- node.id &&
130
- node.id.type === 'Identifier' &&
131
- node.id.name === name
132
- ) {
133
- foundNode = node;
134
- return;
135
- }
136
-
137
- if (node.type !== 'VariableDeclarator') {
138
- return;
139
- }
140
-
141
- if (!node.id || node.id.type !== 'Identifier' || node.id.name !== name) {
142
- return;
143
- }
144
-
145
- if (
146
- node.init &&
147
- (node.init.type === 'ArrowFunctionExpression' || node.init.type === 'FunctionExpression')
148
- ) {
149
- foundNode = node.init;
150
- }
151
- });
152
-
153
- return foundNode;
154
- }
155
-
156
- test('runWithVersionCheck uses non-blocking update notifications', () => {
157
- const runWithVersionCheckNode = getRunWithVersionCheckNode();
158
- const runWithVersionCheckBody = getFunctionBodyNode(runWithVersionCheckNode);
159
-
160
- assert.ok(
161
- runWithVersionCheckNode,
162
- 'runWithVersionCheck function should exist as a declaration or function-valued variable'
163
- );
164
- assert.ok(runWithVersionCheckBody, 'runWithVersionCheck should have a traversable function body');
165
-
166
- let hasNotifyCall = false;
167
- let hasAwaitedNotifyCall = false;
168
- let notifyCallLine = null;
169
- let hasPromptCall = false;
170
- let promptCallLine = null;
171
-
172
- walk(runWithVersionCheckBody, (node) => {
173
- if (isMethodCall(node, 'notifyAvailableUpdates')) {
174
- hasNotifyCall = true;
175
- notifyCallLine = notifyCallLine ?? lineOf(node);
176
- }
177
- if (
178
- node.type === 'AwaitExpression' &&
179
- isMethodCall(node.argument, 'notifyAvailableUpdates')
180
- ) {
181
- hasAwaitedNotifyCall = true;
182
- }
183
- if (isMethodCall(node, 'checkAndPromptUpdates')) {
184
- hasPromptCall = true;
185
- promptCallLine = promptCallLine ?? lineOf(node);
186
- }
187
- });
188
-
189
- assert.equal(
190
- hasNotifyCall,
191
- true,
192
- `runWithVersionCheck must call updateManager.notifyAvailableUpdates() (found at line ${notifyCallLine ?? 'unknown'})`
193
- );
194
- assert.equal(
195
- hasAwaitedNotifyCall,
196
- false,
197
- `runWithVersionCheck should fire-and-forget notifyAvailableUpdates() without await (notify call line ${notifyCallLine ?? 'unknown'})`
198
- );
199
- assert.equal(
200
- hasPromptCall,
201
- false,
202
- `runWithVersionCheck must not call updateManager.checkAndPromptUpdates() (found at line ${promptCallLine ?? 'unknown'})`
203
- );
204
- });
205
-
206
- test('update command remains explicitly interactive', () => {
207
- let foundAwaitedInteractiveUpdateAction = false;
208
- let updateActionHandlerLine = null;
209
- let relatedPromptCallLine = null;
210
- let handlerReferenceName = null;
211
-
212
- walk(ast, (node) => {
213
- if (
214
- node.type === 'CallExpression' &&
215
- node.callee &&
216
- node.callee.type === 'MemberExpression' &&
217
- node.callee.property &&
218
- node.callee.property.type === 'Identifier' &&
219
- node.callee.property.name === 'action' &&
220
- isCommandUpdateExpression(node.callee.object)
221
- ) {
222
- const actionHandler = node.arguments[0];
223
- if (!actionHandler) {
224
- return;
225
- }
226
-
227
- let resolvedHandler = null;
228
-
229
- if (actionHandler.type === 'Identifier') {
230
- handlerReferenceName = actionHandler.name;
231
- resolvedHandler = getFunctionLikeByName(actionHandler.name);
232
- } else if (
233
- actionHandler.type === 'ArrowFunctionExpression' ||
234
- actionHandler.type === 'FunctionExpression'
235
- ) {
236
- resolvedHandler = actionHandler;
237
- }
238
-
239
- if (!resolvedHandler || resolvedHandler.async !== true) {
240
- return;
241
- }
242
-
243
- const resolvedBody = getFunctionBodyNode(resolvedHandler);
244
- if (!resolvedBody) {
245
- return;
246
- }
247
-
248
- updateActionHandlerLine = updateActionHandlerLine ?? lineOf(resolvedHandler);
249
-
250
- walk(resolvedBody, (actionNode) => {
251
- if (isMethodCall(actionNode, 'checkAndPromptUpdates')) {
252
- relatedPromptCallLine = relatedPromptCallLine ?? lineOf(actionNode);
253
- }
254
-
255
- if (
256
- actionNode.type !== 'AwaitExpression' ||
257
- !isMethodCall(actionNode.argument, 'checkAndPromptUpdates')
258
- ) {
259
- return;
260
- }
261
-
262
- foundAwaitedInteractiveUpdateAction = true;
263
- });
264
- }
265
- });
266
-
267
- assert.equal(
268
- foundAwaitedInteractiveUpdateAction,
269
- true,
270
- `update command action must await checkAndPromptUpdates(...) (action line ${updateActionHandlerLine ?? 'unknown'}, related call line ${relatedPromptCallLine ?? 'unknown'}${handlerReferenceName ? `, handler reference ${handlerReferenceName}` : ''})`
271
- );
272
- });
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
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const clientPath = path.join(__dirname, '..', 'client.js');
10
+ const clientSource = fs.readFileSync(clientPath, 'utf-8');
11
+ const ast = parse(clientSource, {
12
+ sourceType: 'module',
13
+ errorRecovery: true,
14
+ loc: true,
15
+ ranges: true
16
+ });
17
+
18
+ function lineOf(node) {
19
+ return node && node.loc && node.loc.start ? node.loc.start.line : null;
20
+ }
21
+
22
+ function walk(node, visit) {
23
+ if (!node || typeof node !== 'object') {
24
+ return;
25
+ }
26
+
27
+ visit(node);
28
+
29
+ for (const value of Object.values(node)) {
30
+ if (Array.isArray(value)) {
31
+ for (const item of value) {
32
+ walk(item, visit);
33
+ }
34
+ continue;
35
+ }
36
+
37
+ walk(value, visit);
38
+ }
39
+ }
40
+
41
+ function isMethodCall(node, methodName) {
42
+ return (
43
+ node &&
44
+ node.type === 'CallExpression' &&
45
+ node.callee &&
46
+ node.callee.type === 'MemberExpression' &&
47
+ node.callee.property &&
48
+ node.callee.property.type === 'Identifier' &&
49
+ node.callee.property.name === methodName
50
+ );
51
+ }
52
+
53
+ function isCommandUpdateExpression(node) {
54
+ if (!node || node.type !== 'CallExpression') {
55
+ return false;
56
+ }
57
+
58
+ if (
59
+ node.callee &&
60
+ node.callee.type === 'MemberExpression' &&
61
+ node.callee.property &&
62
+ node.callee.property.type === 'Identifier' &&
63
+ node.callee.property.name === 'command' &&
64
+ node.arguments[0] &&
65
+ node.arguments[0].type === 'StringLiteral' &&
66
+ node.arguments[0].value === 'update'
67
+ ) {
68
+ return true;
69
+ }
70
+
71
+ if (node.callee && node.callee.type === 'MemberExpression') {
72
+ return isCommandUpdateExpression(node.callee.object);
73
+ }
74
+
75
+ return false;
76
+ }
77
+
78
+ function getRunWithVersionCheckNode() {
79
+ let foundNode = null;
80
+
81
+ walk(ast, (node) => {
82
+ if (
83
+ node.type === 'FunctionDeclaration' &&
84
+ node.id &&
85
+ node.id.type === 'Identifier' &&
86
+ node.id.name === 'runWithVersionCheck'
87
+ ) {
88
+ foundNode = node;
89
+ return;
90
+ }
91
+
92
+ if (node.type !== 'VariableDeclarator') {
93
+ return;
94
+ }
95
+
96
+ if (!node.id || node.id.type !== 'Identifier' || node.id.name !== 'runWithVersionCheck') {
97
+ return;
98
+ }
99
+
100
+ if (
101
+ node.init &&
102
+ (node.init.type === 'ArrowFunctionExpression' || node.init.type === 'FunctionExpression')
103
+ ) {
104
+ foundNode = node.init;
105
+ }
106
+ });
107
+
108
+ return foundNode;
109
+ }
110
+
111
+ function getFunctionBodyNode(fnNode) {
112
+ if (!fnNode) {
113
+ return null;
114
+ }
115
+
116
+ if (fnNode.type === 'FunctionDeclaration' || fnNode.type === 'FunctionExpression' || fnNode.type === 'ArrowFunctionExpression') {
117
+ return fnNode.body;
118
+ }
119
+
120
+ return null;
121
+ }
122
+
123
+ function getFunctionLikeByName(name) {
124
+ let foundNode = null;
125
+
126
+ walk(ast, (node) => {
127
+ if (
128
+ node.type === 'FunctionDeclaration' &&
129
+ node.id &&
130
+ node.id.type === 'Identifier' &&
131
+ node.id.name === name
132
+ ) {
133
+ foundNode = node;
134
+ return;
135
+ }
136
+
137
+ if (node.type !== 'VariableDeclarator') {
138
+ return;
139
+ }
140
+
141
+ if (!node.id || node.id.type !== 'Identifier' || node.id.name !== name) {
142
+ return;
143
+ }
144
+
145
+ if (
146
+ node.init &&
147
+ (node.init.type === 'ArrowFunctionExpression' || node.init.type === 'FunctionExpression')
148
+ ) {
149
+ foundNode = node.init;
150
+ }
151
+ });
152
+
153
+ return foundNode;
154
+ }
155
+
156
+ test('runWithVersionCheck uses non-blocking update notifications', () => {
157
+ const runWithVersionCheckNode = getRunWithVersionCheckNode();
158
+ const runWithVersionCheckBody = getFunctionBodyNode(runWithVersionCheckNode);
159
+
160
+ assert.ok(
161
+ runWithVersionCheckNode,
162
+ 'runWithVersionCheck function should exist as a declaration or function-valued variable'
163
+ );
164
+ assert.ok(runWithVersionCheckBody, 'runWithVersionCheck should have a traversable function body');
165
+
166
+ let hasNotifyCall = false;
167
+ let hasAwaitedNotifyCall = false;
168
+ let notifyCallLine = null;
169
+ let hasPromptCall = false;
170
+ let promptCallLine = null;
171
+
172
+ walk(runWithVersionCheckBody, (node) => {
173
+ if (isMethodCall(node, 'notifyAvailableUpdates')) {
174
+ hasNotifyCall = true;
175
+ notifyCallLine = notifyCallLine ?? lineOf(node);
176
+ }
177
+ if (
178
+ node.type === 'AwaitExpression' &&
179
+ isMethodCall(node.argument, 'notifyAvailableUpdates')
180
+ ) {
181
+ hasAwaitedNotifyCall = true;
182
+ }
183
+ if (isMethodCall(node, 'checkAndPromptUpdates')) {
184
+ hasPromptCall = true;
185
+ promptCallLine = promptCallLine ?? lineOf(node);
186
+ }
187
+ });
188
+
189
+ assert.equal(
190
+ hasNotifyCall,
191
+ true,
192
+ `runWithVersionCheck must call updateManager.notifyAvailableUpdates() (found at line ${notifyCallLine ?? 'unknown'})`
193
+ );
194
+ assert.equal(
195
+ hasAwaitedNotifyCall,
196
+ false,
197
+ `runWithVersionCheck should fire-and-forget notifyAvailableUpdates() without await (notify call line ${notifyCallLine ?? 'unknown'})`
198
+ );
199
+ assert.equal(
200
+ hasPromptCall,
201
+ false,
202
+ `runWithVersionCheck must not call updateManager.checkAndPromptUpdates() (found at line ${promptCallLine ?? 'unknown'})`
203
+ );
204
+ });
205
+
206
+ test('update command remains explicitly interactive', () => {
207
+ let foundAwaitedInteractiveUpdateAction = false;
208
+ let updateActionHandlerLine = null;
209
+ let relatedPromptCallLine = null;
210
+ let handlerReferenceName = null;
211
+
212
+ walk(ast, (node) => {
213
+ if (
214
+ node.type === 'CallExpression' &&
215
+ node.callee &&
216
+ node.callee.type === 'MemberExpression' &&
217
+ node.callee.property &&
218
+ node.callee.property.type === 'Identifier' &&
219
+ node.callee.property.name === 'action' &&
220
+ isCommandUpdateExpression(node.callee.object)
221
+ ) {
222
+ const actionHandler = node.arguments[0];
223
+ if (!actionHandler) {
224
+ return;
225
+ }
226
+
227
+ let resolvedHandler = null;
228
+
229
+ if (actionHandler.type === 'Identifier') {
230
+ handlerReferenceName = actionHandler.name;
231
+ resolvedHandler = getFunctionLikeByName(actionHandler.name);
232
+ } else if (
233
+ actionHandler.type === 'ArrowFunctionExpression' ||
234
+ actionHandler.type === 'FunctionExpression'
235
+ ) {
236
+ resolvedHandler = actionHandler;
237
+ }
238
+
239
+ if (!resolvedHandler || resolvedHandler.async !== true) {
240
+ return;
241
+ }
242
+
243
+ const resolvedBody = getFunctionBodyNode(resolvedHandler);
244
+ if (!resolvedBody) {
245
+ return;
246
+ }
247
+
248
+ updateActionHandlerLine = updateActionHandlerLine ?? lineOf(resolvedHandler);
249
+
250
+ walk(resolvedBody, (actionNode) => {
251
+ if (isMethodCall(actionNode, 'checkAndPromptUpdates')) {
252
+ relatedPromptCallLine = relatedPromptCallLine ?? lineOf(actionNode);
253
+ }
254
+
255
+ if (
256
+ actionNode.type !== 'AwaitExpression' ||
257
+ !isMethodCall(actionNode.argument, 'checkAndPromptUpdates')
258
+ ) {
259
+ return;
260
+ }
261
+
262
+ foundAwaitedInteractiveUpdateAction = true;
263
+ });
264
+ }
265
+ });
266
+
267
+ assert.equal(
268
+ foundAwaitedInteractiveUpdateAction,
269
+ true,
270
+ `update command action must await checkAndPromptUpdates(...) (action line ${updateActionHandlerLine ?? 'unknown'}, related call line ${relatedPromptCallLine ?? 'unknown'}${handlerReferenceName ? `, handler reference ${handlerReferenceName}` : ''})`
271
+ );
272
+ });
@@ -0,0 +1,34 @@
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 { createTestProject, cleanupTestProject, withTestProject } from './helpers/setup.js';
6
+
7
+ test('generateTypesFile parses JSON from components.js (no eval)', async () => {
8
+ const tmpRoot = await createTestProject({ visualComponents: ['Button'] });
9
+
10
+ try {
11
+ const srcDir = path.join(tmpRoot, 'src');
12
+ const { generateTypesFile } = await import('../commands/types/types.js');
13
+ const result = await generateTypesFile({
14
+ projectRoot: tmpRoot,
15
+ outputPath: path.join(srcDir, 'slice-build.generated.d.ts')
16
+ });
17
+
18
+ assert.ok(result.componentsProcessed > 0);
19
+ assert.equal(fs.existsSync(result.outputPath), true);
20
+
21
+ const declaration = fs.readFileSync(result.outputPath, 'utf8');
22
+ assert.match(declaration, /export interface ButtonProps/);
23
+ } finally {
24
+ await cleanupTestProject(tmpRoot);
25
+ }
26
+ });
27
+
28
+ test('Validations componentExists with JSON.parse (no eval)', async () => {
29
+ await withTestProject(async (tmpDir) => {
30
+ const validations = (await import('../commands/Validations.js')).default;
31
+ assert.equal(validations.componentExists('Button'), true);
32
+ assert.equal(validations.componentExists('NonExistent'), false);
33
+ });
34
+ });
@@ -1,24 +1,24 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import DependencyAnalyzer from '../commands/utils/bundling/DependencyAnalyzer.js';
4
-
5
- test('parseComponentsConfig returns components map', () => {
6
- // Arrange
7
- const analyzer = new DependencyAnalyzer(import.meta.url);
8
- const content = `const components = {"Button": "Visual", "FetchManager": "Service"};\nexport default components;`;
9
-
10
- // Act
11
- const result = analyzer.parseComponentsConfig(content);
12
-
13
- // Assert
14
- assert.deepEqual(result, { Button: 'Visual', FetchManager: 'Service' });
15
- });
16
-
17
- test('parseComponentsConfig throws for invalid config', () => {
18
- // Arrange
19
- const analyzer = new DependencyAnalyzer(import.meta.url);
20
- const content = `const notComponents = {"Button": "Visual"};`;
21
-
22
- // Act / Assert
23
- assert.throws(() => analyzer.parseComponentsConfig(content), /components object not found/);
24
- });
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import DependencyAnalyzer from '../commands/utils/bundling/DependencyAnalyzer.js';
4
+
5
+ test('parseComponentsConfig returns components map', () => {
6
+ // Arrange
7
+ const analyzer = new DependencyAnalyzer(import.meta.url);
8
+ const content = `const components = {"Button": "Visual", "FetchManager": "Service"};\nexport default components;`;
9
+
10
+ // Act
11
+ const result = analyzer.parseComponentsConfig(content);
12
+
13
+ // Assert
14
+ assert.deepEqual(result, { Button: 'Visual', FetchManager: 'Service' });
15
+ });
16
+
17
+ test('parseComponentsConfig throws for invalid config', () => {
18
+ // Arrange
19
+ const analyzer = new DependencyAnalyzer(import.meta.url);
20
+ const content = `const notComponents = {"Button": "Visual"};`;
21
+
22
+ // Act / Assert
23
+ assert.throws(() => analyzer.parseComponentsConfig(content), /components object not found/);
24
+ });
@@ -0,0 +1,8 @@
1
+ const components = {
2
+ "Button": "Visual",
3
+ "Link": "Visual",
4
+ "Loading": "Visual",
5
+ "Navbar": "Visual",
6
+ "NotFound": "Visual",
7
+ "FetchManager": "Service"
8
+ }; export default components;