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,272 +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
-
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,102 +0,0 @@
1
- import { test, describe } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import fs from 'fs-extra';
4
- import path from 'node:path';
5
- import { withTestProject } from './helpers/setup.js';
6
- import createComponent from '../commands/createComponent/createComponent.js';
7
- import deleteComponent from '../commands/deleteComponent/deleteComponent.js';
8
- import listComponentsReal from '../commands/listComponents/listComponents.js';
9
-
10
- const visualDir = (root, name) =>
11
- path.join(root, 'src', 'Components', 'Visual', name);
12
-
13
- describe('component create', () => {
14
- test('creates a Visual component with .js/.css/.html', async () => {
15
- await withTestProject(async (root) => {
16
- const ok = createComponent('Card', 'Visual');
17
- assert.equal(ok, true);
18
- const dir = visualDir(root, 'Card');
19
- assert.ok(await fs.pathExists(path.join(dir, 'Card.js')));
20
- assert.ok(await fs.pathExists(path.join(dir, 'Card.css')));
21
- assert.ok(await fs.pathExists(path.join(dir, 'Card.html')));
22
- });
23
- });
24
-
25
- test('creates a Service component with only a .js file', async () => {
26
- await withTestProject(async (root) => {
27
- const ok = createComponent('Api', 'Service');
28
- assert.equal(ok, true);
29
- const dir = path.join(root, 'src', 'Components', 'Service', 'Api');
30
- assert.ok(await fs.pathExists(path.join(dir, 'Api.js')));
31
- assert.equal(await fs.pathExists(path.join(dir, 'Api.css')), false);
32
- });
33
- });
34
-
35
- test('rejects an invalid component name', async () => {
36
- await withTestProject(() => {
37
- assert.equal(createComponent('1Bad', 'Visual'), false);
38
- assert.equal(createComponent('bad-name', 'Visual'), false);
39
- });
40
- });
41
-
42
- test('rejects an unknown category', async () => {
43
- await withTestProject(() => {
44
- assert.equal(createComponent('Card', 'Nope'), false);
45
- });
46
- });
47
-
48
- test('rejects when the component files already exist', async () => {
49
- await withTestProject(() => {
50
- assert.equal(createComponent('Card', 'Visual'), true);
51
- // Second creation hits the on-disk existence guard.
52
- assert.equal(createComponent('Card', 'Visual'), false);
53
- });
54
- });
55
- });
56
-
57
- describe('component delete', () => {
58
- test('deletes an existing component directory', async () => {
59
- await withTestProject(async (root) => {
60
- createComponent('Card', 'Visual');
61
- assert.ok(await fs.pathExists(visualDir(root, 'Card')));
62
-
63
- const ok = deleteComponent('Card', 'Visual');
64
- assert.equal(ok, true);
65
- assert.equal(await fs.pathExists(visualDir(root, 'Card')), false);
66
- });
67
- });
68
-
69
- test('returns false when the component does not exist', async () => {
70
- await withTestProject(() => {
71
- assert.equal(deleteComponent('DoesNotExist', 'Visual'), false);
72
- });
73
- });
74
-
75
- // Components are PascalCase by convention: both create and delete normalize
76
- // the initial to uppercase, so a lower-case name round-trips correctly.
77
- test('create/delete round-trips a lower-case initial (PascalCase normalization)', async () => {
78
- await withTestProject(async (root) => {
79
- assert.equal(createComponent('card', 'Visual'), true);
80
- assert.ok(await fs.pathExists(visualDir(root, 'Card')), 'folder is created in PascalCase');
81
- assert.equal(deleteComponent('card', 'Visual'), true);
82
- assert.equal(await fs.pathExists(visualDir(root, 'Card')), false);
83
- });
84
- });
85
- });
86
-
87
- describe('component list (registry regeneration)', () => {
88
- test('regenerates components.js from the files on disk', async () => {
89
- await withTestProject(async (root) => {
90
- createComponent('Card', 'Visual');
91
- listComponentsReal();
92
-
93
- const registryPath = path.join(root, 'src', 'Components', 'components.js');
94
- const content = await fs.readFile(registryPath, 'utf8');
95
- const json = JSON.parse(content.match(/const components = ({[\s\S]*?});/)[1]);
96
-
97
- assert.equal(json.Card, 'Visual');
98
- // Starter AppComponents must still be present.
99
- assert.equal(json.AppShell, 'AppComponents');
100
- });
101
- });
102
- });
@@ -1,80 +0,0 @@
1
- import { test, describe } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import fs from 'fs-extra';
4
- import path from 'node:path';
5
- import { withTestProject } from './helpers/setup.js';
6
- import {
7
- checkNodeVersion,
8
- checkDirectoryStructure,
9
- checkConfig,
10
- checkComponents,
11
- } from '../commands/doctor/doctor.js';
12
-
13
- describe('doctor checks', () => {
14
- test('checkNodeVersion passes on the supported runtime', async () => {
15
- const r = await checkNodeVersion();
16
- assert.equal(r.pass, true);
17
- assert.match(r.message, /Node\.js version/);
18
- });
19
-
20
- test('checkDirectoryStructure passes on the starter project', async () => {
21
- await withTestProject(async () => {
22
- const r = await checkDirectoryStructure();
23
- assert.equal(r.pass, true);
24
- });
25
- });
26
-
27
- test('checkDirectoryStructure fails when src/ is missing', async () => {
28
- await withTestProject(async (root) => {
29
- await fs.remove(path.join(root, 'src'));
30
- const r = await checkDirectoryStructure();
31
- assert.equal(r.pass, false);
32
- assert.match(r.message, /Missing directories/);
33
- assert.match(r.message, /src\//);
34
- });
35
- });
36
-
37
- test('checkConfig passes for a valid sliceConfig.json', async () => {
38
- await withTestProject(async () => {
39
- const r = await checkConfig();
40
- assert.equal(r.pass, true);
41
- });
42
- });
43
-
44
- test('checkConfig fails on invalid JSON', async () => {
45
- await withTestProject(async (root) => {
46
- await fs.writeFile(path.join(root, 'src', 'sliceConfig.json'), '{ not valid json ');
47
- const r = await checkConfig();
48
- assert.equal(r.pass, false);
49
- assert.match(r.message, /invalid JSON/);
50
- });
51
- });
52
-
53
- test('checkConfig fails when paths.components is missing', async () => {
54
- await withTestProject(async (root) => {
55
- await fs.writeJson(path.join(root, 'src', 'sliceConfig.json'), { server: { port: 3000 } });
56
- const r = await checkConfig();
57
- assert.equal(r.pass, false);
58
- assert.match(r.message, /missing paths\.components/);
59
- });
60
- });
61
-
62
- test('checkComponents reports OK when every component folder has its .js', async () => {
63
- await withTestProject(async () => {
64
- const r = await checkComponents();
65
- assert.equal(r.pass, true);
66
- assert.match(r.message, /components checked/);
67
- });
68
- });
69
-
70
- test('checkComponents warns when a component folder lacks its .js file', async () => {
71
- await withTestProject(async (root) => {
72
- // AppShell ships with AppShell.js; remove it to simulate a broken component.
73
- const broken = path.join(root, 'src', 'Components', 'AppComponents', 'Broken');
74
- await fs.ensureDir(broken); // directory with no Broken.js
75
- const r = await checkComponents();
76
- assert.equal(r.warn, true);
77
- assert.match(r.message, /missing files/);
78
- });
79
- });
80
- });
@@ -1,37 +0,0 @@
1
- import { test, describe } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import versionChecker from '../commands/utils/VersionChecker.js';
4
-
5
- describe('VersionChecker.compareVersions', () => {
6
- test('detects an outdated version', () => {
7
- assert.equal(versionChecker.compareVersions('1.0.0', '1.0.1'), 'outdated');
8
- assert.equal(versionChecker.compareVersions('1.2.0', '2.0.0'), 'outdated');
9
- });
10
-
11
- test('detects a newer (local) version', () => {
12
- assert.equal(versionChecker.compareVersions('2.0.0', '1.9.9'), 'newer');
13
- });
14
-
15
- test('detects an up-to-date version', () => {
16
- assert.equal(versionChecker.compareVersions('3.5.0', '3.5.0'), 'current');
17
- });
18
-
19
- test('handles version strings of differing length', () => {
20
- assert.equal(versionChecker.compareVersions('1.0', '1.0.0'), 'current');
21
- assert.equal(versionChecker.compareVersions('1.0.0', '1.0.0.1'), 'outdated');
22
- assert.equal(versionChecker.compareVersions('1.0.1', '1.0'), 'newer');
23
- });
24
-
25
- test('returns null when a version is missing', () => {
26
- assert.equal(versionChecker.compareVersions(null, '1.0.0'), null);
27
- assert.equal(versionChecker.compareVersions('1.0.0', undefined), null);
28
- });
29
- });
30
-
31
- describe('VersionChecker.getCurrentVersions', () => {
32
- test('reads the CLI version from the package.json (no network)', async () => {
33
- const current = await versionChecker.getCurrentVersions();
34
- assert.ok(current, 'should resolve current versions');
35
- assert.match(current.cli, /^\d+\.\d+\.\d+/);
36
- });
37
- });
@@ -1,34 +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 { 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
- }, { visualComponents: ['Button'] });
34
- });
@@ -1,24 +0,0 @@
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,91 +0,0 @@
1
- import { test, expect } from '@playwright/test';
2
- import { parse } from '@babel/parser';
3
-
4
- // Recursively collect every emitted bundle file referenced by the config.
5
- // Only real bundle artifacts (slice-bundle.*.js) — not dependency identifiers
6
- // that happen to end in `.js` (e.g. vendorShared.dependencies).
7
- function collectBundleFiles(node, acc = new Set()) {
8
- if (typeof node === 'string') {
9
- if (node.startsWith('slice-bundle.') && node.endsWith('.js')) acc.add(node);
10
- return acc;
11
- }
12
- if (Array.isArray(node)) {
13
- for (const n of node) collectBundleFiles(n, acc);
14
- return acc;
15
- }
16
- if (node && typeof node === 'object') {
17
- for (const v of Object.values(node)) collectBundleFiles(v, acc);
18
- return acc;
19
- }
20
- return acc;
21
- }
22
-
23
- async function getConfig(request) {
24
- const res = await request.get('/bundles/bundle.config.json');
25
- expect(res.status()).toBe(200);
26
- return res.json();
27
- }
28
-
29
- test.describe('bundle quality & content (served production artifacts)', () => {
30
- test('bundle.config.json is well-formed and maps every route', async ({ request }) => {
31
- const cfg = await getConfig(request);
32
- expect(cfg.production).toBe(true);
33
- expect(cfg.format).toBe('v2');
34
- expect(cfg.minified).toBe(true);
35
- expect(cfg.obfuscated).toBe(true);
36
- expect(cfg.bundles.framework, 'framework bundle present').toBeTruthy();
37
- expect(cfg.bundles.critical, 'critical bundle present').toBeTruthy();
38
-
39
- for (const route of ['/', '/about', '/404']) {
40
- expect(Array.isArray(cfg.routeBundles[route]), `route ${route} is mapped`).toBe(true);
41
- expect(cfg.routeBundles[route].length).toBeGreaterThan(0);
42
- }
43
- });
44
-
45
- test('every referenced bundle is served as valid, contract-compliant JS', async ({ request }) => {
46
- const cfg = await getConfig(request);
47
- const files = [...collectBundleFiles(cfg.bundles)];
48
- expect(files.length).toBeGreaterThan(0);
49
-
50
- for (const file of files) {
51
- const res = await request.get(`/bundles/${file}`);
52
- expect(res.status(), `${file} is served`).toBe(200);
53
- expect(res.headers()['content-type'] || '').toMatch(/javascript/);
54
-
55
- const code = await res.text();
56
- expect(() => parse(code, { sourceType: 'module', plugins: ['jsx'] }), `${file} is valid JS`).not.toThrow();
57
- // v2 runtime contract.
58
- expect(code, `${file} exports SLICE_BUNDLE_META`).toContain('SLICE_BUNDLE_META');
59
- expect(code, `${file} exports registerAll`).toContain('registerAll');
60
- // No unresolved relative import may leak into a production bundle.
61
- expect(/\bfrom\s+['"]\.\.?\//.test(code), `${file} leaks a relative import`).toBe(false);
62
- }
63
- });
64
-
65
- test('the framework bundle carries the structural runtime', async ({ request }) => {
66
- const code = await (await request.get('/bundles/slice-bundle.framework.js')).text();
67
- expect(code).toContain('Controller');
68
- expect(code).toContain('registerAll');
69
- });
70
-
71
- test('all starter component classes are registered across the bundles', async ({ request }) => {
72
- const cfg = await getConfig(request);
73
- const files = [...collectBundleFiles(cfg.bundles)];
74
- let combined = '';
75
- for (const file of files) {
76
- combined += await (await request.get(`/bundles/${file}`)).text();
77
- }
78
- // Component names are emitted as string literals in the register* calls and
79
- // survive minification (mangle.properties is off, strings are untouched).
80
- for (const comp of ['AppShell', 'Navbar', 'MultiRoute', 'HomeSection', 'Button', 'AboutSection', 'NotFound']) {
81
- expect(combined.includes(`"${comp}"`), `${comp} is registered in a bundle`).toBe(true);
82
- }
83
- });
84
-
85
- test('bundles are genuinely minified', async ({ request }) => {
86
- const code = await (await request.get('/bundles/slice-bundle.framework.js')).text();
87
- const longestLine = Math.max(...code.split('\n').map((l) => l.length));
88
- expect(longestLine, 'minified output has long lines').toBeGreaterThan(300);
89
- expect(code, 'comments are stripped').not.toContain('/**');
90
- });
91
- });