slicejs-cli 3.5.1 → 3.6.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 (75) hide show
  1. package/README.md +81 -26
  2. package/client.js +73 -23
  3. package/commands/buildProduction/buildProduction.js +6 -3
  4. package/commands/doctor/doctor.js +68 -3
  5. package/commands/getComponent/getComponent.js +33 -25
  6. package/commands/init/init.js +176 -49
  7. package/commands/utils/PackageManager.js +148 -0
  8. package/commands/utils/VersionChecker.js +6 -4
  9. package/commands/utils/sliceScripts.js +23 -0
  10. package/commands/utils/updateManager.js +54 -35
  11. package/package.json +12 -1
  12. package/post.js +13 -19
  13. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -29
  14. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -25
  15. package/.github/pull_request_template.md +0 -22
  16. package/.github/workflows/ci.yml +0 -43
  17. package/AGENTS.md +0 -247
  18. package/CODE_OF_CONDUCT.md +0 -126
  19. package/ECOSYSTEM.md +0 -9
  20. package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +0 -182
  21. package/playwright.config.js +0 -51
  22. package/tests/build-command-integration.test.js +0 -87
  23. package/tests/build-production-e2e.test.js +0 -140
  24. package/tests/builder-edge-cases.test.js +0 -322
  25. package/tests/bundle-generate-e2e.test.js +0 -115
  26. package/tests/bundle-generator.test.js +0 -691
  27. package/tests/bundle-v2-register-output.test.js +0 -470
  28. package/tests/bundling-dependency-edges.test.js +0 -127
  29. package/tests/bundling-imports-unit.test.js +0 -267
  30. package/tests/client-launcher-contract.test.js +0 -211
  31. package/tests/client-update-flow-contract.test.js +0 -272
  32. package/tests/commands-component-crud.test.js +0 -102
  33. package/tests/commands-doctor.test.js +0 -80
  34. package/tests/commands-version-checker.test.js +0 -37
  35. package/tests/component-registry-parse.test.js +0 -34
  36. package/tests/dependency-analyzer.test.js +0 -24
  37. package/tests/e2e/bundles.spec.js +0 -91
  38. package/tests/e2e/dependency-scenarios.spec.js +0 -56
  39. package/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +0 -136
  40. package/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +0 -149
  41. package/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +0 -45
  42. package/tests/e2e/fixtures/components/Visual/Button/Button.css +0 -106
  43. package/tests/e2e/fixtures/components/Visual/Button/Button.html +0 -5
  44. package/tests/e2e/fixtures/components/Visual/Button/Button.js +0 -158
  45. package/tests/e2e/fixtures/components/Visual/Link/Link.js +0 -33
  46. package/tests/e2e/fixtures/components/Visual/Loading/Loading.css +0 -56
  47. package/tests/e2e/fixtures/components/Visual/Loading/Loading.html +0 -83
  48. package/tests/e2e/fixtures/components/Visual/Loading/Loading.js +0 -164
  49. package/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +0 -167
  50. package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +0 -116
  51. package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +0 -44
  52. package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +0 -180
  53. package/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +0 -20
  54. package/tests/e2e/fixtures/components/Visual/Route/Route.js +0 -181
  55. package/tests/e2e/fixtures/components/registry.json +0 -12
  56. package/tests/e2e/fixtures/vendor-components.mjs +0 -65
  57. package/tests/e2e/navigation.spec.js +0 -44
  58. package/tests/e2e/render.spec.js +0 -34
  59. package/tests/e2e/serve.mjs +0 -264
  60. package/tests/e2e/shared-deps.spec.js +0 -61
  61. package/tests/e2e/unminified.spec.js +0 -33
  62. package/tests/e2e-serve.test.js +0 -148
  63. package/tests/fixtures/components.js +0 -8
  64. package/tests/fixtures/sliceConfig.json +0 -74
  65. package/tests/getcomponent.test.js +0 -407
  66. package/tests/helpers/setup.js +0 -102
  67. package/tests/init-command-contract.test.js +0 -46
  68. package/tests/local-cli-delegation.test.js +0 -81
  69. package/tests/path-helper.test.js +0 -206
  70. package/tests/perf-budget.test.js +0 -86
  71. package/tests/postinstall-command.test.js +0 -72
  72. package/tests/types-breakage.test.js +0 -491
  73. package/tests/types-generator-errors.test.js +0 -361
  74. package/tests/types-generator.test.js +0 -346
  75. 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
- });