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,264 +0,0 @@
1
- // E2E web server: assembles a complete App-Shell + MultiRoute starter
2
- // (framework src/api + the vendored starter components), runs the real
3
- // production build, and serves dist/ over the production serving contract.
4
- // Used as Playwright's `webServer`. Build artifacts live in an os.tmpdir,
5
- // so nothing is written into the repo.
6
- import http from 'node:http';
7
- import path from 'node:path';
8
- import { fileURLToPath } from 'node:url';
9
- import fs from 'fs-extra';
10
- import { createTestProject } from '../helpers/setup.js';
11
-
12
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
- const FIXTURES = path.join(__dirname, 'fixtures', 'components');
14
- const FRAMEWORK_SLICE_JS = path.resolve(
15
- __dirname,
16
- '../../node_modules/slicejs-web-framework/Slice/Slice.js'
17
- );
18
- const PORT = process.env.E2E_PORT ? Number(process.env.E2E_PORT) : 3210;
19
-
20
- const CONTENT_TYPES = {
21
- '.js': 'application/javascript; charset=utf-8',
22
- '.mjs': 'application/javascript; charset=utf-8',
23
- '.json': 'application/json; charset=utf-8',
24
- '.html': 'text/html; charset=utf-8',
25
- '.css': 'text/css; charset=utf-8',
26
- '.svg': 'image/svg+xml',
27
- '.ico': 'image/x-icon',
28
- '.png': 'image/png',
29
- '.woff2': 'font/woff2',
30
- };
31
-
32
- // A shared module imported by two route components that land in different route
33
- // bundles — this is what triggers the vendor-shared bundle. It must transform to
34
- // >2KB (minVendorSharedTransformedSize), so it embeds a data table that survives
35
- // minification.
36
- function buildSharedKitSource() {
37
- const rows = [];
38
- for (let i = 0; i < 60; i += 1) {
39
- rows.push(
40
- ` { id: ${i}, key: 'shared_item_${i}', label: 'Shared registry entry number ${i} reused across multiple routes', enabled: ${i % 2 === 0}, weight: ${i * 7} },`
41
- );
42
- }
43
- return `// Shared kit deliberately imported by several routes.
44
- export const SHARED_TAG = 'shared-kit-v1';
45
-
46
- export const SHARED_TABLE = [
47
- ${rows.join('\n')}
48
- ];
49
-
50
- export const SHARED_PALETTE = { primary: '#4f46e5', secondary: '#06b6d4', surface: '#0b1020', text: '#e6e9f5' };
51
-
52
- export function sharedBadge(label) {
53
- return '[' + SHARED_TAG + '] ' + String(label);
54
- }
55
-
56
- export function sharedLookup(id) {
57
- return SHARED_TABLE.find((row) => row.id === id) || null;
58
- }
59
-
60
- export default { SHARED_TAG, SHARED_TABLE, SHARED_PALETTE, sharedBadge, sharedLookup };
61
- `;
62
- }
63
-
64
- const SHARED_KIT_SOURCE = buildSharedKitSource();
65
-
66
- // Shared modules for the extra dependency scenarios.
67
- const LEAF_SOURCE = `export const LEAF = 'leaf-value';
68
- export function leafTag() { return 'leaf:' + LEAF; }
69
- `;
70
- // mid.js imports leaf.js -> a transitive dependency of any component using mid.
71
- const MID_SOURCE = `import { LEAF, leafTag } from './leaf.js';
72
- export function midValue() { return 'mid(' + LEAF + ')[' + leafTag() + ']'; }
73
- `;
74
- const APP_CONFIG_SOURCE = `const TITLE = 'Configured';
75
- export const VERSION = 3;
76
- export default { title: TITLE, version: VERSION, tagline: 'default-export-works' };
77
- `;
78
-
79
- function componentJs(name, { imports = '', initBody = '' } = {}) {
80
- return `${imports ? imports + '\n\n' : ''}export default class ${name} extends HTMLElement {
81
- constructor(props) {
82
- super();
83
- slice.attachTemplate(this);
84
- slice.controller.setComponentProps(this, props);
85
- }
86
-
87
- init() {
88
- ${initBody}
89
- }
90
- }
91
-
92
- customElements.define('slice-${name.toLowerCase()}', ${name});
93
- `;
94
- }
95
-
96
- async function scaffold(app, createComponent, name, { js, html, css } = {}) {
97
- const ok = createComponent(name, 'Visual');
98
- if (!ok) throw new Error(`[e2e] createComponent failed for ${name}`);
99
- const dir = path.join(app, 'src', 'Components', 'Visual', name);
100
- if (js != null) await fs.writeFile(path.join(dir, `${name}.js`), js, 'utf8');
101
- if (html != null) await fs.writeFile(path.join(dir, `${name}.html`), html, 'utf8');
102
- if (css != null) await fs.writeFile(path.join(dir, `${name}.css`), css, 'utf8');
103
- }
104
-
105
- async function addScenarios(app) {
106
- const createComponent = (await import('../../commands/createComponent/createComponent.js')).default;
107
- const sharedDir = path.join(app, 'src', 'shared');
108
- await fs.ensureDir(sharedDir);
109
- await fs.writeFile(path.join(sharedDir, 'sharedKit.js'), SHARED_KIT_SOURCE, 'utf8');
110
- await fs.writeFile(path.join(sharedDir, 'leaf.js'), LEAF_SOURCE, 'utf8');
111
- await fs.writeFile(path.join(sharedDir, 'mid.js'), MID_SOURCE, 'utf8');
112
- await fs.writeFile(path.join(sharedDir, 'appConfig.js'), APP_CONFIG_SOURCE, 'utf8');
113
-
114
- // (a) vendor-shared: two route components import the same (>2KB) module and
115
- // fall into different route bundles (services vs routing categories).
116
- for (const name of ['ServicesPage', 'RoutingPage']) {
117
- await scaffold(app, createComponent, name, {
118
- js: componentJs(name, {
119
- imports: "import { SHARED_TAG, sharedBadge } from '../../../shared/sharedKit.js';",
120
- initBody:
121
- ` this.dataset.sharedTag = SHARED_TAG;\n this.dataset.sharedBadge = sharedBadge('${name}');`,
122
- }),
123
- });
124
- }
125
-
126
- // (b) transitive dependency: mid.js itself imports leaf.js.
127
- await scaffold(app, createComponent, 'TransitivePage', {
128
- js: componentJs('TransitivePage', {
129
- imports: "import { midValue } from '../../../shared/mid.js';",
130
- initBody: ' this.dataset.transitive = midValue();',
131
- }),
132
- });
133
-
134
- // (c) default-export dependency.
135
- await scaffold(app, createComponent, 'DefaultDepPage', {
136
- js: componentJs('DefaultDepPage', {
137
- imports: "import cfg from '../../../shared/appConfig.js';",
138
- initBody: ' this.dataset.cfgTitle = cfg.title;\n this.dataset.cfgTagline = cfg.tagline;',
139
- }),
140
- });
141
-
142
- // (d) CSS application.
143
- await scaffold(app, createComponent, 'CssProbePage', {
144
- js: componentJs('CssProbePage'),
145
- html: '<div class="css-probe-marker">styled by Slice</div>',
146
- css: '.css-probe-marker { color: rgb(7, 113, 219); font-weight: 700; }',
147
- });
148
-
149
- // Wire all extra routes (keeping the 404 route last).
150
- const entries = [
151
- { path: '/services', component: 'ServicesPage', title: 'Services' },
152
- { path: '/routing', component: 'RoutingPage', title: 'Routing' },
153
- { path: '/transitive', component: 'TransitivePage', title: 'Transitive' },
154
- { path: '/defaultdep', component: 'DefaultDepPage', title: 'DefaultDep' },
155
- { path: '/cssprobe', component: 'CssProbePage', title: 'CssProbe' },
156
- ];
157
- const inserted = entries
158
- .map((e) => ` { path: '${e.path}', component: '${e.component}', metadata: { title: '${e.title}' } },`)
159
- .join('\n');
160
-
161
- const routesPath = path.join(app, 'src', 'routes.js');
162
- let routes = await fs.readFile(routesPath, 'utf8');
163
- routes = routes.replace(/(\n)(\s*)\{ path: '\/404'/, `\n${inserted}\n$2{ path: '/404'`);
164
- await fs.writeFile(routesPath, routes, 'utf8');
165
- }
166
-
167
- async function assembleAndBuild() {
168
- process.env.NODE_ENV = 'production';
169
-
170
- // Framework src + api copied into a throwaway project.
171
- const app = await createTestProject();
172
-
173
- // The bundler discovers the framework's structural components under
174
- // node_modules/slicejs-web-framework — make the installed package resolvable
175
- // from the assembled project so the framework bundle is generated.
176
- const fwPkg = path.resolve(__dirname, '../../node_modules/slicejs-web-framework');
177
- await fs.ensureDir(path.join(app, 'node_modules'));
178
- await fs.ensureSymlink(fwPkg, path.join(app, 'node_modules', 'slicejs-web-framework'), 'dir')
179
- .catch(() => fs.copy(fwPkg, path.join(app, 'node_modules', 'slicejs-web-framework')));
180
-
181
- // Drop in the vendored starter Visual/Service components.
182
- await fs.copy(path.join(FIXTURES, 'Visual'), path.join(app, 'src', 'Components', 'Visual'));
183
- await fs.copy(path.join(FIXTURES, 'Service'), path.join(app, 'src', 'Components', 'Service'));
184
-
185
- process.env.INIT_CWD = app;
186
-
187
- // Add the dependency scenarios (vendor-shared, transitive, default-export, CSS)
188
- // so those bundle paths are built and exercised by the browser specs.
189
- await addScenarios(app);
190
-
191
- // Regenerate components.js from disk (the real `slice component list`).
192
- const listComponents = (await import('../../commands/listComponents/listComponents.js')).default;
193
- listComponents();
194
-
195
- // Real production build. E2E_MINIFY=false exercises the unminified bundle path.
196
- const minify = process.env.E2E_MINIFY !== 'false';
197
- const build = (await import('../../commands/build/build.js')).default;
198
- const ok = await build({ minify, obfuscate: minify });
199
- if (!ok) {
200
- console.error('[e2e] build failed');
201
- process.exit(1);
202
- }
203
-
204
- return path.join(app, 'dist');
205
- }
206
-
207
- function startServer(distDir) {
208
- const server = http.createServer(async (req, res) => {
209
- try {
210
- const pathname = decodeURIComponent(new URL(req.url, 'http://localhost').pathname);
211
-
212
- if (pathname === '/slice-env.json') {
213
- res.setHeader('Content-Type', CONTENT_TYPES['.json']);
214
- res.end(JSON.stringify({ mode: 'production', env: {} }));
215
- return;
216
- }
217
- if (pathname === '/Slice/Slice.js') {
218
- const body = await fs.readFile(FRAMEWORK_SLICE_JS).catch(() => null);
219
- if (!body) { res.statusCode = 404; res.end('Slice.js not found'); return; }
220
- res.setHeader('Content-Type', CONTENT_TYPES['.js']);
221
- res.end(body);
222
- return;
223
- }
224
-
225
- const filePath = path.join(distDir, pathname);
226
- if (!filePath.startsWith(distDir)) { res.statusCode = 403; res.end('forbidden'); return; }
227
-
228
- const stat = await fs.stat(filePath).catch(() => null);
229
- if (stat && stat.isFile()) {
230
- res.setHeader('Content-Type', CONTENT_TYPES[path.extname(filePath)] || 'application/octet-stream');
231
- res.end(await fs.readFile(filePath));
232
- return;
233
- }
234
-
235
- // A missing file WITH an extension is a genuine 404 (don't mask asset
236
- // 404s as the SPA shell — that produces confusing MIME errors).
237
- if (path.extname(pathname)) { res.statusCode = 404; res.end('not found'); return; }
238
-
239
- // Extensionless paths are client routes -> SPA fallback.
240
- const index = await fs.readFile(path.join(distDir, 'App', 'index.html')).catch(() => null);
241
- if (index) { res.setHeader('Content-Type', CONTENT_TYPES['.html']); res.end(index); return; }
242
- res.statusCode = 404;
243
- res.end('not found');
244
- } catch (error) {
245
- res.statusCode = 500;
246
- res.end(String(error));
247
- }
248
- });
249
-
250
- server.listen(PORT, '127.0.0.1', () => {
251
- console.log(`[e2e] app server ready at http://127.0.0.1:${PORT} (dist: ${distDir})`);
252
- });
253
-
254
- const shutdown = () => server.close(() => process.exit(0));
255
- process.on('SIGTERM', shutdown);
256
- process.on('SIGINT', shutdown);
257
- }
258
-
259
- assembleAndBuild()
260
- .then(startServer)
261
- .catch((err) => {
262
- console.error('[e2e] fatal:', err);
263
- process.exit(1);
264
- });
@@ -1,61 +0,0 @@
1
- import { test, expect } from '@playwright/test';
2
- import { parse } from '@babel/parser';
3
-
4
- // Two routes (/services, /routing) live in different bundles and import the same
5
- // module, so the build extracts it into a vendor-shared bundle. These specs
6
- // validate both the produced artifact and that the shared dependency resolves at
7
- // runtime (via window.__SLICE_SHARED_DEPS__) when the pages render.
8
-
9
- test.describe('vendor-shared bundle (shared dependency across routes)', () => {
10
- test('the config advertises a real vendor-shared bundle wired into both routes', async ({ request }) => {
11
- const cfg = await (await request.get('/bundles/bundle.config.json')).json();
12
-
13
- expect(cfg.bundles.vendorShared, 'vendorShared present').toBeTruthy();
14
- expect(cfg.bundles.vendorShared.file).toBe('slice-bundle.vendor-shared.js');
15
- expect(cfg.bundles.vendorShared.dependencyCount).toBeGreaterThanOrEqual(1);
16
-
17
- expect(cfg.routeBundles['/services']).toContain('vendor-shared');
18
- expect(cfg.routeBundles['/routing']).toContain('vendor-shared');
19
- });
20
-
21
- test('the vendor-shared bundle is served as a valid, contract-compliant module', async ({ request }) => {
22
- const res = await request.get('/bundles/slice-bundle.vendor-shared.js');
23
- expect(res.status()).toBe(200);
24
- expect(res.headers()['content-type'] || '').toMatch(/javascript/);
25
-
26
- const code = await res.text();
27
- expect(() => parse(code, { sourceType: 'module', plugins: ['jsx'] })).not.toThrow();
28
- expect(code).toContain('SLICE_BUNDLE_META');
29
- expect(code).toContain('registerAll');
30
- expect(code).toContain('SLICE_BUNDLE_DEPENDENCIES');
31
- // The shared module's own content lives here, not duplicated per route.
32
- expect(code).toContain('shared-kit-v1');
33
- });
34
-
35
- test('/services renders and resolves the shared dependency at runtime', async ({ page }) => {
36
- const errors = [];
37
- page.on('console', (m) => m.type() === 'error' && errors.push(m.text()));
38
- page.on('pageerror', (e) => errors.push(String(e)));
39
-
40
- await page.goto('/services');
41
- await expect(page.locator('slice-servicespage')).toBeAttached();
42
- // The data attribute is set in init() from the shared module — its presence
43
- // proves the vendor-shared dependency was registered and resolved.
44
- await expect(page.locator('slice-servicespage')).toHaveAttribute('data-shared-tag', 'shared-kit-v1');
45
- await expect(page.locator('slice-servicespage')).toHaveAttribute('data-shared-badge', /ServicesPage/);
46
-
47
- expect(errors, `console errors:\n${errors.join('\n')}`).toEqual([]);
48
- });
49
-
50
- test('/routing renders and resolves the same shared dependency', async ({ page }) => {
51
- const errors = [];
52
- page.on('console', (m) => m.type() === 'error' && errors.push(m.text()));
53
- page.on('pageerror', (e) => errors.push(String(e)));
54
-
55
- await page.goto('/routing');
56
- await expect(page.locator('slice-routingpage')).toBeAttached();
57
- await expect(page.locator('slice-routingpage')).toHaveAttribute('data-shared-tag', 'shared-kit-v1');
58
-
59
- expect(errors, `console errors:\n${errors.join('\n')}`).toEqual([]);
60
- });
61
- });
@@ -1,33 +0,0 @@
1
- import { test, expect } from '@playwright/test';
2
-
3
- // Runs against a second server built with E2E_MINIFY=false, so the raw
4
- // (un-minified, un-obfuscated) bundle output is exercised in the browser.
5
- function trackErrors(page) {
6
- const errors = [];
7
- page.on('console', (m) => m.type() === 'error' && errors.push(m.text()));
8
- page.on('pageerror', (e) => errors.push(String(e)));
9
- return errors;
10
- }
11
-
12
- test.describe('unminified production build', () => {
13
- test('boots and renders the home page', async ({ page }) => {
14
- const errors = trackErrors(page);
15
- await page.goto('/');
16
-
17
- await expect(page.locator('slice-app-shell')).toBeAttached();
18
- await expect(page.locator('slice-home-section h1.home__title')).toHaveText(
19
- /Welcome to your Slice app/
20
- );
21
-
22
- expect(errors, `console errors:\n${errors.join('\n')}`).toEqual([]);
23
- });
24
-
25
- test('resolves the vendor-shared dependency with unminified bundles', async ({ page }) => {
26
- const errors = trackErrors(page);
27
- await page.goto('/services');
28
-
29
- await expect(page.locator('slice-servicespage')).toHaveAttribute('data-shared-tag', 'shared-kit-v1');
30
-
31
- expect(errors, `console errors:\n${errors.join('\n')}`).toEqual([]);
32
- });
33
- });
@@ -1,148 +0,0 @@
1
- import { test, describe } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import http from 'node:http';
4
- import path from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
- import fs from 'fs-extra';
7
- import { parse } from '@babel/parser';
8
- import { withTestProject } from './helpers/setup.js';
9
- import build from '../commands/build/build.js';
10
-
11
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
- const FRAMEWORK_SLICE_JS = path.resolve(
13
- __dirname,
14
- '../node_modules/slicejs-web-framework/Slice/Slice.js'
15
- );
16
-
17
- const CONTENT_TYPES = {
18
- '.js': 'application/javascript; charset=utf-8',
19
- '.json': 'application/json; charset=utf-8',
20
- '.html': 'text/html; charset=utf-8',
21
- '.css': 'text/css; charset=utf-8',
22
- };
23
-
24
- // Minimal static server that mirrors the production serving contract the
25
- // framework's api/index.js implements (index/SPA fallback, /slice-env.json,
26
- // /Slice/Slice.js from the framework package, and dist static files), without
27
- // pulling in express. Enough to assert that a production build is servable.
28
- function startServer(distDir) {
29
- const server = http.createServer(async (req, res) => {
30
- try {
31
- const pathname = decodeURIComponent(new URL(req.url, 'http://localhost').pathname);
32
-
33
- if (pathname === '/slice-env.json') {
34
- res.setHeader('Content-Type', CONTENT_TYPES['.json']);
35
- res.end(JSON.stringify({ mode: 'production', env: {} }));
36
- return;
37
- }
38
- if (pathname === '/Slice/Slice.js') {
39
- const body = await fs.readFile(FRAMEWORK_SLICE_JS).catch(() => null);
40
- if (!body) { res.statusCode = 404; res.end('Slice.js not found'); return; }
41
- res.setHeader('Content-Type', CONTENT_TYPES['.js']);
42
- res.end(body);
43
- return;
44
- }
45
-
46
- const filePath = path.join(distDir, pathname);
47
- if (!filePath.startsWith(distDir)) { res.statusCode = 403; res.end('forbidden'); return; }
48
-
49
- const stat = await fs.stat(filePath).catch(() => null);
50
- if (stat && stat.isFile()) {
51
- res.setHeader('Content-Type', CONTENT_TYPES[path.extname(filePath)] || 'application/octet-stream');
52
- res.end(await fs.readFile(filePath));
53
- return;
54
- }
55
-
56
- // SPA fallback -> App/index.html
57
- const index = await fs.readFile(path.join(distDir, 'App', 'index.html')).catch(() => null);
58
- if (index) { res.setHeader('Content-Type', CONTENT_TYPES['.html']); res.end(index); return; }
59
- res.statusCode = 404;
60
- res.end('not found');
61
- } catch (error) {
62
- res.statusCode = 500;
63
- res.end(String(error));
64
- }
65
- });
66
-
67
- return new Promise((resolve) => {
68
- server.listen(0, '127.0.0.1', () => {
69
- resolve({ server, port: server.address().port });
70
- });
71
- });
72
- }
73
-
74
- function closeServer(server) {
75
- return new Promise((resolve) => server.close(resolve));
76
- }
77
-
78
- describe('end-to-end: production build is correctly servable', () => {
79
- test('build() output serves the app shell, framework runtime and valid bundles', async () => {
80
- await withTestProject(async (root) => {
81
- const ok = await build({ minify: false, obfuscate: false });
82
- assert.equal(ok, true, 'build should succeed');
83
-
84
- const distDir = path.join(root, 'dist');
85
- const { server, port } = await startServer(distDir);
86
- const base = `http://127.0.0.1:${port}`;
87
-
88
- try {
89
- // 1. The HTML shell is served and mounts the app + entry module.
90
- const indexRes = await fetch(`${base}/`);
91
- assert.equal(indexRes.status, 200);
92
- const indexHtml = await indexRes.text();
93
- assert.match(indexHtml, /id="app"/);
94
- assert.match(indexHtml, /\/App\/index\.js/);
95
-
96
- // 2. The entry module loads and bootstraps the framework runtime.
97
- const entryRes = await fetch(`${base}/App/index.js`);
98
- assert.equal(entryRes.status, 200);
99
- assert.match(entryRes.headers.get('content-type') || '', /javascript/);
100
- assert.match(await entryRes.text(), /\/Slice\/Slice\.js/);
101
-
102
- // 3. The framework runtime itself is reachable.
103
- const sliceRes = await fetch(`${base}/Slice/Slice.js`);
104
- assert.equal(sliceRes.status, 200);
105
-
106
- // 4. Config + runtime mode endpoints.
107
- const cfgRes = await fetch(`${base}/sliceConfig.json`);
108
- assert.equal(cfgRes.status, 200);
109
- const cfg = await cfgRes.json();
110
- assert.ok(cfg.paths?.components, 'sliceConfig exposes component paths');
111
-
112
- const envRes = await fetch(`${base}/slice-env.json`);
113
- assert.equal((await envRes.json()).mode, 'production');
114
-
115
- // 5. The bundle manifest is served and well-formed.
116
- const manifestRes = await fetch(`${base}/bundles/bundle.config.json`);
117
- assert.equal(manifestRes.status, 200);
118
- const manifest = await manifestRes.json();
119
- assert.equal(manifest.production, true);
120
- assert.equal(manifest.format, 'v2');
121
-
122
- // 6. Every emitted bundle is served as JS and is syntactically valid.
123
- const bundlesDir = path.join(distDir, 'bundles');
124
- const bundleFiles = (await fs.readdir(bundlesDir)).filter(
125
- (f) => f.startsWith('slice-bundle.') && f.endsWith('.js')
126
- );
127
- assert.ok(bundleFiles.length > 0, 'at least one bundle is produced');
128
- for (const file of bundleFiles) {
129
- const res = await fetch(`${base}/bundles/${file}`);
130
- assert.equal(res.status, 200, `${file} should be served`);
131
- assert.match(res.headers.get('content-type') || '', /javascript/);
132
- const code = await res.text();
133
- assert.doesNotThrow(
134
- () => parse(code, { sourceType: 'module', plugins: ['jsx'] }),
135
- `${file} is not valid JS`
136
- );
137
- }
138
-
139
- // 7. Unknown client routes fall back to the SPA shell.
140
- const spaRes = await fetch(`${base}/some/client/route`);
141
- assert.equal(spaRes.status, 200);
142
- assert.match(await spaRes.text(), /id="app"/);
143
- } finally {
144
- await closeServer(server);
145
- }
146
- });
147
- });
148
- });
@@ -1,8 +0,0 @@
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;
@@ -1,74 +0,0 @@
1
- {
2
- "server": {
3
- "port": 3001,
4
- "host": "localhost"
5
- },
6
- "debugger": {
7
- "enabled": false,
8
- "click": "right"
9
- },
10
- "events": {
11
- "enabled": true,
12
- "ui": {
13
- "enabled": true,
14
- "shortcut": "alt+shift+e"
15
- }
16
- },
17
- "context": {
18
- "enabled": true,
19
- "ui": {
20
- "enabled": true,
21
- "shortcut": "alt+shift+c"
22
- }
23
- },
24
- "stylesManager": {
25
- "requestedStyles": ["sliceStyles"]
26
- },
27
- "themeManager": {
28
- "enabled": true,
29
- "defaultTheme": "Slice",
30
- "saveThemeLocally": false,
31
- "useBrowserTheme": false
32
- },
33
- "logger": {
34
- "enabled": true,
35
- "showLogs": {
36
- "console": {
37
- "error": true,
38
- "warning": true,
39
- "info": false
40
- }
41
- }
42
- },
43
- "paths": {
44
- "components": {
45
- "AppComponents": {
46
- "path": "/Components/AppComponents",
47
- "type": "Visual"
48
- },
49
- "Visual": {
50
- "path": "/Components/Visual",
51
- "type": "Visual"
52
- },
53
- "Service": {
54
- "path": "/Components/Service",
55
- "type": "Service"
56
- }
57
- },
58
- "themes": "/Themes",
59
- "styles": "/Styles",
60
- "routesFile": "/routes.js"
61
- },
62
- "router": {
63
- "defaultRoute": "/"
64
- },
65
- "loading": {
66
- "enabled": true
67
- },
68
- "publicFolders": [
69
- "/Themes",
70
- "/Styles",
71
- "/assets",
72
- "/images"
73
- ]
74
- }