slicejs-web-framework 3.4.4 → 3.5.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 (110) hide show
  1. package/Slice/Components/Structural/Controller/Controller.js +135 -0
  2. package/Slice/Components/Structural/EventManager/EventManager.js +289 -4
  3. package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +368 -30
  4. package/Slice/Components/Structural/LeakInspector/LeakInspector.js +296 -0
  5. package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +56 -0
  6. package/Slice/Slice.js +14 -1
  7. package/Slice/tests/event-emitters.test.js +79 -0
  8. package/Slice/tests/event-registry.test.js +141 -0
  9. package/Slice/tests/leak-orphans.test.js +131 -0
  10. package/api/{middleware → framework}/securityMiddleware.js +18 -74
  11. package/api/framework/server.js +333 -0
  12. package/api/index.js +5 -279
  13. package/api/tests/public-env-resolver.test.js +1 -1
  14. package/package.json +4 -2
  15. package/sliceConfig.schema.json +10 -1
  16. package/src/App/index.html +31 -1
  17. package/src/manifest.json +15 -0
  18. package/src/robots.txt +4 -0
  19. package/src/sitemap.xml +8 -0
  20. package/src/sliceConfig.json +5 -1
  21. package/types/index.d.ts +12 -0
  22. package/slicejs-cli/.github/workflows/ci.yml +0 -43
  23. package/slicejs-cli/LICENSE +0 -21
  24. package/slicejs-cli/assets/Slice.js-logo.png +0 -0
  25. package/slicejs-cli/client.js +0 -740
  26. package/slicejs-cli/commands/Print.js +0 -164
  27. package/slicejs-cli/commands/Validations.js +0 -92
  28. package/slicejs-cli/commands/build/build.js +0 -40
  29. package/slicejs-cli/commands/buildProduction/buildProduction.js +0 -577
  30. package/slicejs-cli/commands/bundle/bundle.js +0 -236
  31. package/slicejs-cli/commands/createComponent/VisualComponentTemplate.js +0 -55
  32. package/slicejs-cli/commands/createComponent/createComponent.js +0 -128
  33. package/slicejs-cli/commands/deleteComponent/deleteComponent.js +0 -81
  34. package/slicejs-cli/commands/doctor/doctor.js +0 -516
  35. package/slicejs-cli/commands/getComponent/getComponent.js +0 -695
  36. package/slicejs-cli/commands/init/init.js +0 -473
  37. package/slicejs-cli/commands/listComponents/listComponents.js +0 -172
  38. package/slicejs-cli/commands/startServer/startServer.js +0 -240
  39. package/slicejs-cli/commands/startServer/watchServer.js +0 -66
  40. package/slicejs-cli/commands/types/types.js +0 -583
  41. package/slicejs-cli/commands/utils/LocalCliDelegation.js +0 -53
  42. package/slicejs-cli/commands/utils/PackageManager.js +0 -148
  43. package/slicejs-cli/commands/utils/PathHelper.js +0 -75
  44. package/slicejs-cli/commands/utils/VersionChecker.js +0 -169
  45. package/slicejs-cli/commands/utils/bundling/BundleGenerator.js +0 -2525
  46. package/slicejs-cli/commands/utils/bundling/DependencyAnalyzer.js +0 -933
  47. package/slicejs-cli/commands/utils/loadConfig.js +0 -31
  48. package/slicejs-cli/commands/utils/sliceScripts.js +0 -48
  49. package/slicejs-cli/commands/utils/updateManager.js +0 -471
  50. package/slicejs-cli/package.json +0 -71
  51. package/slicejs-cli/playwright.config.js +0 -51
  52. package/slicejs-cli/pnpm-lock.yaml +0 -1629
  53. package/slicejs-cli/tests/build-command-integration.test.js +0 -87
  54. package/slicejs-cli/tests/build-mangling-e2e.test.js +0 -290
  55. package/slicejs-cli/tests/build-production-e2e.test.js +0 -140
  56. package/slicejs-cli/tests/builder-edge-cases.test.js +0 -322
  57. package/slicejs-cli/tests/bundle-generate-e2e.test.js +0 -115
  58. package/slicejs-cli/tests/bundle-generator.test.js +0 -691
  59. package/slicejs-cli/tests/bundle-v2-register-output.test.js +0 -470
  60. package/slicejs-cli/tests/bundling-dependency-edges.test.js +0 -127
  61. package/slicejs-cli/tests/bundling-imports-unit.test.js +0 -267
  62. package/slicejs-cli/tests/client-launcher-contract.test.js +0 -211
  63. package/slicejs-cli/tests/commands-component-crud.test.js +0 -128
  64. package/slicejs-cli/tests/commands-doctor.test.js +0 -152
  65. package/slicejs-cli/tests/commands-version-checker.test.js +0 -37
  66. package/slicejs-cli/tests/component-registry-parse.test.js +0 -34
  67. package/slicejs-cli/tests/dependency-analyzer.test.js +0 -24
  68. package/slicejs-cli/tests/e2e/bundles.spec.js +0 -91
  69. package/slicejs-cli/tests/e2e/dependency-scenarios.spec.js +0 -56
  70. package/slicejs-cli/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +0 -136
  71. package/slicejs-cli/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +0 -149
  72. package/slicejs-cli/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +0 -45
  73. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.css +0 -106
  74. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.html +0 -5
  75. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.js +0 -158
  76. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Link/Link.js +0 -33
  77. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.css +0 -56
  78. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.html +0 -83
  79. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.js +0 -164
  80. package/slicejs-cli/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +0 -167
  81. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +0 -116
  82. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +0 -44
  83. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +0 -180
  84. package/slicejs-cli/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +0 -20
  85. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Route/Route.js +0 -181
  86. package/slicejs-cli/tests/e2e/fixtures/components/registry.json +0 -12
  87. package/slicejs-cli/tests/e2e/fixtures/vendor-components.mjs +0 -65
  88. package/slicejs-cli/tests/e2e/navigation.spec.js +0 -44
  89. package/slicejs-cli/tests/e2e/render.spec.js +0 -34
  90. package/slicejs-cli/tests/e2e/serve.mjs +0 -264
  91. package/slicejs-cli/tests/e2e/shared-deps.spec.js +0 -61
  92. package/slicejs-cli/tests/e2e/unminified.spec.js +0 -33
  93. package/slicejs-cli/tests/e2e-serve.test.js +0 -148
  94. package/slicejs-cli/tests/fixtures/components.js +0 -8
  95. package/slicejs-cli/tests/fixtures/sliceConfig.json +0 -74
  96. package/slicejs-cli/tests/getcomponent.test.js +0 -435
  97. package/slicejs-cli/tests/helpers/setup.js +0 -102
  98. package/slicejs-cli/tests/init-command-contract.test.js +0 -101
  99. package/slicejs-cli/tests/init-e2e.test.js +0 -82
  100. package/slicejs-cli/tests/init-project-isolation.test.js +0 -98
  101. package/slicejs-cli/tests/local-cli-delegation.test.js +0 -81
  102. package/slicejs-cli/tests/package-manager.test.js +0 -127
  103. package/slicejs-cli/tests/path-helper.test.js +0 -206
  104. package/slicejs-cli/tests/perf-budget.test.js +0 -84
  105. package/slicejs-cli/tests/postinstall-command.test.js +0 -103
  106. package/slicejs-cli/tests/property-mangling-regression.test.js +0 -182
  107. package/slicejs-cli/tests/types-breakage.test.js +0 -491
  108. package/slicejs-cli/tests/types-generator-errors.test.js +0 -361
  109. package/slicejs-cli/tests/types-generator.test.js +0 -348
  110. /package/api/{utils → framework}/publicEnvResolver.js +0 -0
@@ -1,102 +0,0 @@
1
- import fs from 'fs-extra'
2
- import path from 'path'
3
- import os from 'os'
4
- import { fileURLToPath } from 'url'
5
-
6
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
- // Prefer the sibling framework dev repo when present (local monorepo checkout);
8
- // otherwise fall back to the installed framework package (e.g. CI, where only
9
- // this repo is checked out). Both ship the same src/ + api/ starter structure.
10
- const SIBLING_FW = path.resolve(__dirname, '../../../slice.js')
11
- const PACKAGE_FW = path.resolve(__dirname, '../../node_modules/slicejs-web-framework')
12
- const FW_DIR = fs.pathExistsSync(SIBLING_FW) ? SIBLING_FW : PACKAGE_FW
13
- const FIXTURES_DIR = path.resolve(__dirname, '../fixtures')
14
-
15
- let counter = 0
16
-
17
- export async function createTestProject(options = {}) {
18
- const {
19
- visualComponents = [],
20
- frameworkDir = FW_DIR,
21
- } = options
22
-
23
- const dir = await fs.mkdtemp(path.join(os.tmpdir(), `slice-test-${process.pid}-${counter++}-`))
24
-
25
- const fwExists = await fs.pathExists(frameworkDir)
26
- const srcDir = path.join(dir, 'src')
27
-
28
- if (fwExists) {
29
- const fwSrc = path.join(frameworkDir, 'src')
30
- const fwApi = path.join(frameworkDir, 'api')
31
-
32
- if (await fs.pathExists(fwSrc)) {
33
- await fs.copy(fwSrc, srcDir)
34
- }
35
- if (await fs.pathExists(fwApi)) {
36
- await fs.copy(fwApi, path.join(dir, 'api'))
37
- }
38
- }
39
-
40
- if (!(await fs.pathExists(srcDir))) {
41
- await createMinimalScaffold(dir)
42
- }
43
-
44
- await fs.ensureDir(path.join(srcDir, 'Components', 'Visual'))
45
-
46
- if (visualComponents.length > 0) {
47
- const registryLines = visualComponents.map(n => ` "${n}": "Visual"`).join(',\n')
48
- await fs.writeFile(
49
- path.join(srcDir, 'Components', 'components.js'),
50
- `const components = {\n${registryLines}\n}; export default components;\n`
51
- )
52
- for (const name of visualComponents) {
53
- const compDir = path.join(srcDir, 'Components', 'Visual', name)
54
- await fs.ensureDir(compDir)
55
- await fs.writeFile(path.join(compDir, `${name}.js`), `export default class ${name} {}`)
56
- }
57
- }
58
-
59
- return dir
60
- }
61
-
62
- export async function cleanupTestProject(dir) {
63
- await fs.remove(dir)
64
- }
65
-
66
- export async function withTestProject(fn, options = {}) {
67
- const dir = await createTestProject(options)
68
- const origInitCwd = process.env.INIT_CWD
69
- try {
70
- process.env.INIT_CWD = dir
71
- return await fn(dir)
72
- } finally {
73
- if (origInitCwd === undefined) {
74
- delete process.env.INIT_CWD
75
- } else {
76
- process.env.INIT_CWD = origInitCwd
77
- }
78
- await cleanupTestProject(dir)
79
- }
80
- }
81
-
82
- async function createMinimalScaffold(dir) {
83
- const srcDir = path.join(dir, 'src')
84
- await fs.ensureDir(path.join(srcDir, 'App'))
85
- await fs.ensureDir(path.join(srcDir, 'Components', 'AppComponents', 'AppShell'))
86
- await fs.ensureDir(path.join(srcDir, 'Components', 'Service', 'FetchManager'))
87
- await fs.ensureDir(path.join(srcDir, 'Components', 'Visual'))
88
- await fs.ensureDir(path.join(srcDir, 'Styles'))
89
- await fs.ensureDir(path.join(srcDir, 'Themes'))
90
- await fs.ensureDir(path.join(dir, 'api', 'middleware'))
91
- await fs.ensureDir(path.join(dir, 'api', 'utils'))
92
-
93
- const fixtureConfig = path.join(FIXTURES_DIR, 'sliceConfig.json')
94
- if (await fs.pathExists(fixtureConfig)) {
95
- await fs.copy(fixtureConfig, path.join(srcDir, 'sliceConfig.json'))
96
- }
97
-
98
- const fixtureComponents = path.join(FIXTURES_DIR, 'components.js')
99
- if (await fs.pathExists(fixtureComponents)) {
100
- await fs.copy(fixtureComponents, path.join(srcDir, 'Components', 'components.js'))
101
- }
102
- }
@@ -1,101 +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
-
7
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
- const clientPath = path.join(__dirname, '..', 'client.js');
9
- const source = fs.readFileSync(clientPath, 'utf-8');
10
-
11
- test('init command is registered in client.js', () => {
12
- const hasInit = source.includes('.command("init")');
13
- assert.ok(hasInit, 'client.js must register an "init" command');
14
- });
15
-
16
- test('init command has a description', () => {
17
- const match = source.match(/\.command\(["']init["']\)\s*\.description\(["']([^"']+)["']\)/);
18
- assert.ok(match, 'init command must have a .description() call');
19
- assert.ok(match[1].length > 0, 'init command description must not be empty');
20
- });
21
-
22
- test('init command has -y / --yes option', () => {
23
- const hasShort = source.includes('"-y, --yes');
24
- assert.ok(hasShort, 'init command must have a -y/--yes option for non-interactive use');
25
- });
26
-
27
- test('init command action calls initializeProject with the chosen package manager', () => {
28
- const hasCall = source.includes('initializeProject({ packageManager })');
29
- assert.ok(hasCall, 'init command action must call initializeProject passing the package manager');
30
- });
31
-
32
- test('init command has a --pm option for package manager selection', () => {
33
- assert.ok(source.includes('--pm <packageManager>'), 'init command must expose a --pm option');
34
- });
35
-
36
- test('init command normalizes project name (lowercase, hyphens)', () => {
37
- const hasFilter = source.includes('.toLowerCase()');
38
- assert.ok(hasFilter, 'init command must normalize project name to lowercase');
39
- assert.ok(source.includes('.replace(/\\s+/g'), 'init command must replace spaces with hyphens');
40
- });
41
-
42
- test('init command validates project name', () => {
43
- assert.ok(source.includes("'Project name cannot be empty'"), 'init command must validate non-empty name');
44
- assert.ok(source.includes("'Use a simple name, not a path'"), 'init command must reject path separators');
45
- });
46
-
47
- test('init command creates project directory', () => {
48
- assert.ok(source.includes('fs.mkdirSync(projectDir'), 'init command must create the project directory');
49
- assert.ok(source.includes('process.chdir(projectDir)'), 'init command must chdir into project');
50
- });
51
-
52
- // --- initializeProject (commands/init/init.js) contracts ---
53
-
54
- const initPath = path.join(__dirname, '..', 'commands', 'init', 'init.js');
55
- const initSource = fs.readFileSync(initPath, 'utf-8');
56
-
57
- test('initializeProject creates package.json BEFORE installing anything', () => {
58
- // Without a manifest in the project folder, npm/pnpm walk up the directory
59
- // tree and anchor node_modules OUTSIDE the new project. The manifest must
60
- // exist before the first install command runs.
61
- const manifestIdx = initSource.indexOf('await ensureProjectManifest(');
62
- const installIdx = initSource.indexOf('execSync(installCommand(');
63
- assert.ok(manifestIdx !== -1, 'init.js must call ensureProjectManifest');
64
- assert.ok(installIdx !== -1, 'init.js must install via PackageManager.installCommand');
65
- assert.ok(manifestIdx < installIdx, 'package.json must be created before any install runs');
66
- });
67
-
68
- test('initializeProject does not hardcode npm commands', () => {
69
- assert.ok(!initSource.includes("execSync('npm "), 'init.js must not execSync hardcoded npm commands');
70
- assert.ok(!initSource.includes('execSync(`npm '), 'init.js must not execSync hardcoded npm template commands');
71
- });
72
-
73
- test('initializeProject resolves and installs exact framework/cli versions', () => {
74
- assert.ok(initSource.includes('fetchLatestVersion(\'slicejs-web-framework\')'), 'init.js must resolve latest framework version');
75
- assert.ok(initSource.includes('slicejs-web-framework@${latestVersion}'), 'init.js must build exact framework package spec');
76
- assert.ok(initSource.includes('getRunningCliVersion()'), 'init.js must resolve running CLI version');
77
- assert.ok(initSource.includes('slicejs-cli@${currentCliVersion}'), 'init.js must build exact CLI package spec');
78
- });
79
-
80
- test('initializeProject installs slicejs-cli as devDependency', () => {
81
- assert.ok(
82
- initSource.includes("installCommand(packageManager, cliPackage, { dev: true })"),
83
- 'init.js must install slicejs-cli locally as a devDependency'
84
- );
85
- });
86
-
87
- test('initializeProject persists the packageManager field', () => {
88
- assert.ok(initSource.includes('pkg.packageManager ='), 'init.js must persist the packageManager field in package.json');
89
- });
90
-
91
- test('initializeProject configures pnpm allowBuilds for slicejs-cli', () => {
92
- assert.ok(
93
- initSource.includes("if (packageManager === 'pnpm')")
94
- && initSource.includes('ensurePnpmAllowBuilds(projectRoot)'),
95
- 'init.js must configure pnpm allowBuilds when pnpm is selected'
96
- );
97
- assert.ok(
98
- initSource.includes("allowBuilds:") && initSource.includes("slicejs-cli: true"),
99
- 'init.js must write allowBuilds.slicejs-cli: true in pnpm-workspace.yaml'
100
- );
101
- });
@@ -1,82 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import fs from 'fs-extra';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
- import { spawnSync } from 'node:child_process';
8
- import { isPackageManagerAvailable } from '../commands/utils/PackageManager.js';
9
- import { SLICE_SCRIPTS } from '../commands/utils/sliceScripts.js';
10
-
11
- // REAL end-to-end test of `slice init`: spawns the actual CLI, installs real
12
- // packages from the registry and downloads starter components from GitHub.
13
- // Needs network and takes ~1-2 minutes, so it is opt-in:
14
- //
15
- // SLICE_INIT_E2E=1 node --test tests/init-e2e.test.js
16
- //
17
- const E2E_ENABLED = process.env.SLICE_INIT_E2E === '1';
18
-
19
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
- const CLIENT = path.join(__dirname, '..', 'client.js');
21
-
22
- test('slice init -y creates a fully self-contained project (real run)', { skip: !E2E_ENABLED && 'set SLICE_INIT_E2E=1 to run' }, async () => {
23
- const pm = ['pnpm', 'npm'].find(isPackageManagerAvailable);
24
- assert.ok(pm, 'a package manager must be available');
25
-
26
- // Parent directory WITH a package.json — the ancestor the old code leaked into.
27
- const parent = await fs.mkdtemp(path.join(os.tmpdir(), 'slice-init-e2e-'));
28
- const parentManifest = { name: 'parent-trap', version: '1.0.0' };
29
- await fs.writeJson(path.join(parent, 'package.json'), parentManifest);
30
-
31
- try {
32
- const result = spawnSync(process.execPath, [CLIENT, 'init', '-y', 'demo-app', '--pm', pm], {
33
- cwd: parent,
34
- encoding: 'utf-8',
35
- env: { ...process.env, SLICE_NO_LOCAL_DELEGATION: '1' },
36
- timeout: 600_000
37
- });
38
-
39
- const output = `${result.stdout}\n${result.stderr}`;
40
- assert.equal(result.status, 0, `init must exit 0. Output:\n${output}`);
41
-
42
- const projectDir = path.join(parent, 'demo-app');
43
-
44
- // Everything must live INSIDE the project folder…
45
- const pkg = await fs.readJson(path.join(projectDir, 'package.json'));
46
- assert.equal(pkg.name, 'demo-app');
47
- assert.ok(pkg.packageManager?.startsWith(`${pm}@`), 'packageManager field must pin the chosen PM');
48
- assert.ok(pkg.dependencies?.['slicejs-web-framework'], 'framework must be a dependency');
49
- assert.ok(pkg.devDependencies?.['slicejs-cli'], 'CLI must be a devDependency');
50
- assert.equal(pkg.scripts?.dev, SLICE_SCRIPTS['slice:dev']);
51
- assert.equal(pkg.scripts?.build, SLICE_SCRIPTS['slice:build']);
52
- assert.equal(pkg.scripts?.start, SLICE_SCRIPTS['slice:start']);
53
- for (const [script, command] of Object.entries(SLICE_SCRIPTS)) {
54
- assert.equal(pkg.scripts?.[script], command, `script ${script} must be configured`);
55
- }
56
-
57
- assert.ok(await fs.pathExists(path.join(projectDir, 'node_modules', 'slicejs-web-framework', 'package.json')),
58
- 'framework must be installed inside the project');
59
- assert.ok(await fs.pathExists(path.join(projectDir, 'node_modules', 'slicejs-cli', 'package.json')),
60
- 'CLI must be installed inside the project');
61
-
62
- const lockfile = pm === 'pnpm' ? 'pnpm-lock.yaml' : 'package-lock.json';
63
- assert.ok(await fs.pathExists(path.join(projectDir, lockfile)), `${lockfile} must be inside the project`);
64
-
65
- assert.ok(await fs.pathExists(path.join(projectDir, 'src', 'sliceConfig.json')), 'src/ structure must exist');
66
- assert.ok(await fs.pathExists(path.join(projectDir, 'api', 'index.js')), 'api/ structure must exist');
67
-
68
- const componentsJs = await fs.readFile(path.join(projectDir, 'src', 'Components', 'components.js'), 'utf-8');
69
- for (const name of ['Button', 'MultiRoute', 'Navbar', 'FetchManager']) {
70
- assert.ok(componentsJs.includes(name), `starter component ${name} must be registered`);
71
- }
72
-
73
- // …and the parent must be untouched: same manifest, no node_modules, no lockfile.
74
- assert.deepEqual(await fs.readJson(path.join(parent, 'package.json')), parentManifest,
75
- 'parent package.json must be untouched');
76
- assert.ok(!(await fs.pathExists(path.join(parent, 'node_modules'))), 'no node_modules may leak into the parent');
77
- assert.ok(!(await fs.pathExists(path.join(parent, 'pnpm-lock.yaml'))), 'no lockfile may leak into the parent');
78
- assert.ok(!(await fs.pathExists(path.join(parent, 'package-lock.json'))), 'no lockfile may leak into the parent');
79
- } finally {
80
- await fs.remove(parent);
81
- }
82
- });
@@ -1,98 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import fs from 'fs-extra';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
- import { execSync } from 'node:child_process';
7
- import { ensureProjectManifest } from '../commands/init/init.js';
8
- import { getPackageManagerVersion, isPackageManagerAvailable } from '../commands/utils/PackageManager.js';
9
-
10
- // Regression tests for the "two package.json" bug: `slice init` used to run the
11
- // framework install BEFORE writing the project manifest, so npm/pnpm walked up
12
- // the directory tree and anchored node_modules + the dependency entry in an
13
- // ancestor's package.json (outside the new project folder).
14
-
15
- async function makeParentTrap() {
16
- // Parent directory WITH a package.json — the ancestor npm/pnpm would walk up to.
17
- const parent = await fs.mkdtemp(path.join(os.tmpdir(), 'slice-init-isolation-'));
18
- await fs.writeJson(path.join(parent, 'package.json'), { name: 'parent-trap', version: '1.0.0' });
19
- const projectDir = path.join(parent, 'demo-app');
20
- await fs.ensureDir(projectDir);
21
- return { parent, projectDir };
22
- }
23
-
24
- test('ensureProjectManifest creates package.json inside the project folder', async () => {
25
- const { parent, projectDir } = await makeParentTrap();
26
- try {
27
- await ensureProjectManifest(projectDir, 'npm');
28
-
29
- const pkgPath = path.join(projectDir, 'package.json');
30
- assert.ok(await fs.pathExists(pkgPath), 'package.json must exist inside the project folder');
31
-
32
- const pkg = await fs.readJson(pkgPath);
33
- assert.equal(pkg.name, 'demo-app');
34
- assert.equal(pkg.type, 'module');
35
-
36
- // Parent manifest untouched
37
- const parentPkg = await fs.readJson(path.join(parent, 'package.json'));
38
- assert.deepEqual(parentPkg, { name: 'parent-trap', version: '1.0.0' });
39
- } finally {
40
- await fs.remove(parent);
41
- }
42
- });
43
-
44
- test('ensureProjectManifest persists the packageManager field when the PM version is known', async (t) => {
45
- const pm = ['pnpm', 'npm'].find(isPackageManagerAvailable);
46
- if (!pm) {
47
- t.skip('no package manager binary available');
48
- return;
49
- }
50
- const { parent, projectDir } = await makeParentTrap();
51
- try {
52
- await ensureProjectManifest(projectDir, pm);
53
- const pkg = await fs.readJson(path.join(projectDir, 'package.json'));
54
- const version = getPackageManagerVersion(pm);
55
- if (version) {
56
- assert.equal(pkg.packageManager, `${pm}@${version}`);
57
- }
58
- } finally {
59
- await fs.remove(parent);
60
- }
61
- });
62
-
63
- test('ensureProjectManifest does not overwrite an existing manifest', async () => {
64
- const { parent, projectDir } = await makeParentTrap();
65
- try {
66
- const existing = { name: 'keep-me', version: '2.0.0', scripts: { custom: 'echo hi' } };
67
- await fs.writeJson(path.join(projectDir, 'package.json'), existing);
68
-
69
- await ensureProjectManifest(projectDir, 'npm');
70
-
71
- const pkg = await fs.readJson(path.join(projectDir, 'package.json'));
72
- assert.deepEqual(pkg, existing, 'an existing package.json must be left untouched');
73
- } finally {
74
- await fs.remove(parent);
75
- }
76
- });
77
-
78
- test('with the manifest in place, the package manager anchors to the project folder (not the parent)', async (t) => {
79
- // `npm prefix` resolves the nearest package.json from cwd — exactly the
80
- // walk-up mechanism that caused the bug. No install or network needed.
81
- if (!isPackageManagerAvailable('npm')) {
82
- t.skip('npm binary not available');
83
- return;
84
- }
85
- const { parent, projectDir } = await makeParentTrap();
86
- try {
87
- // Without a manifest, npm anchors to the parent trap…
88
- const before = execSync('npm prefix', { cwd: projectDir, encoding: 'utf-8' }).trim();
89
- assert.equal(await fs.realpath(before), await fs.realpath(parent), 'precondition: npm walks up without a manifest');
90
-
91
- // …and with the manifest created first, it anchors to the project folder.
92
- await ensureProjectManifest(projectDir, 'npm');
93
- const after = execSync('npm prefix', { cwd: projectDir, encoding: 'utf-8' }).trim();
94
- assert.equal(await fs.realpath(after), await fs.realpath(projectDir), 'npm must anchor inside the project folder');
95
- } finally {
96
- await fs.remove(parent);
97
- }
98
- });
@@ -1,81 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import fs from 'node:fs';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
- import {
7
- findNearestLocalCliEntry,
8
- resolveLocalCliCandidate,
9
- shouldDelegateToLocalCli,
10
- isLocalDelegationDisabled
11
- } from '../commands/utils/LocalCliDelegation.js';
12
-
13
- test('isLocalDelegationDisabled returns true when env flag is set', () => {
14
- const env = { SLICE_NO_LOCAL_DELEGATION: '1' };
15
- assert.equal(isLocalDelegationDisabled(env), true);
16
- });
17
-
18
- test('isLocalDelegationDisabled returns false when env flag is missing', () => {
19
- const env = {};
20
- assert.equal(isLocalDelegationDisabled(env), false);
21
- });
22
-
23
- test('shouldDelegateToLocalCli is false when candidate is null', () => {
24
- assert.equal(shouldDelegateToLocalCli('/tmp/current/client.js', null), false);
25
- });
26
-
27
- test('shouldDelegateToLocalCli is false when candidate realpath equals current realpath', () => {
28
- const same = '/tmp/current/client.js';
29
- assert.equal(shouldDelegateToLocalCli(same, same), false);
30
- });
31
-
32
- test('shouldDelegateToLocalCli is true when candidate differs from current', () => {
33
- const current = '/tmp/global/client.js';
34
- const local = '/tmp/project/node_modules/slicejs-cli/client.js';
35
- assert.equal(shouldDelegateToLocalCli(current, local), true);
36
- });
37
-
38
- test('findNearestLocalCliEntry returns null when no candidate resolver hits', () => {
39
- const cwd = path.join('/tmp', 'slice-nonexistent-project');
40
- const resolver = () => null;
41
- const result = findNearestLocalCliEntry(cwd, resolver);
42
- assert.equal(result, null);
43
- });
44
-
45
- test('findNearestLocalCliEntry returns first match while traversing upward', () => {
46
- const cwd = path.resolve('/repo/apps/web/src');
47
- const matchDir = path.resolve('/repo/apps/web');
48
- const matchResult = path.resolve('/repo/apps/web/node_modules/slicejs-cli/client.js');
49
- const calls = [];
50
- const resolver = (dir) => {
51
- calls.push(dir);
52
- if (dir === matchDir) {
53
- return matchResult;
54
- }
55
- return null;
56
- };
57
-
58
- const result = findNearestLocalCliEntry(cwd, resolver);
59
-
60
- assert.equal(result, matchResult);
61
- assert.deepEqual(calls, [cwd, matchDir]);
62
- });
63
-
64
- test('findNearestLocalCliEntry returns null when resolver is not a function', () => {
65
- const result = findNearestLocalCliEntry('/repo/apps/web/src', null);
66
- assert.equal(result, null);
67
- });
68
-
69
- test('resolveLocalCliCandidate returns null when candidate path is a directory', () => {
70
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'slice-cli-delegation-'));
71
- const candidateDirectory = path.join(tempRoot, 'node_modules', 'slicejs-cli', 'client.js');
72
-
73
- fs.mkdirSync(candidateDirectory, { recursive: true });
74
-
75
- try {
76
- const result = resolveLocalCliCandidate(tempRoot);
77
- assert.equal(result, null);
78
- } finally {
79
- fs.rmSync(tempRoot, { recursive: true, force: true });
80
- }
81
- });
@@ -1,127 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import fs from 'node:fs';
4
- import os from 'node:os';
5
- import path from 'node:path';
6
- import {
7
- parseUserAgent,
8
- fromPackageManagerField,
9
- fromLockfile,
10
- detectPackageManager,
11
- resolvePackageManager,
12
- installCommand,
13
- uninstallCommand,
14
- SUPPORTED_PACKAGE_MANAGERS
15
- } from '../commands/utils/PackageManager.js';
16
-
17
- function makeTmpDir() {
18
- return fs.mkdtempSync(path.join(os.tmpdir(), 'slice-pm-test-'));
19
- }
20
-
21
- test('parseUserAgent detects pnpm', () => {
22
- const result = parseUserAgent('pnpm/9.12.0 npm/? node/v20.11.0 linux x64');
23
- assert.equal(result.name, 'pnpm');
24
- assert.equal(result.version, '9.12.0');
25
- });
26
-
27
- test('parseUserAgent detects npm', () => {
28
- const result = parseUserAgent('npm/10.5.0 node/v20.11.0 linux x64 workspaces/false');
29
- assert.equal(result.name, 'npm');
30
- });
31
-
32
- test('parseUserAgent returns null for missing or unknown agent', () => {
33
- assert.equal(parseUserAgent(null), null);
34
- assert.equal(parseUserAgent('bun/1.1.0 node/v20.11.0'), null);
35
- assert.equal(parseUserAgent('yarn/1.22.22 node/v20.11.0'), null);
36
- });
37
-
38
- test('fromPackageManagerField reads corepack-style field', () => {
39
- const dir = makeTmpDir();
40
- try {
41
- fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'x', packageManager: 'pnpm@9.12.0' }));
42
- const result = fromPackageManagerField(dir);
43
- assert.equal(result.name, 'pnpm');
44
- assert.equal(result.version, '9.12.0');
45
- } finally {
46
- fs.rmSync(dir, { recursive: true, force: true });
47
- }
48
- });
49
-
50
- test('fromPackageManagerField returns null without field or manifest', () => {
51
- const dir = makeTmpDir();
52
- try {
53
- assert.equal(fromPackageManagerField(dir), null);
54
- fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'x' }));
55
- assert.equal(fromPackageManagerField(dir), null);
56
- } finally {
57
- fs.rmSync(dir, { recursive: true, force: true });
58
- }
59
- });
60
-
61
- test('fromLockfile detects each supported lockfile', () => {
62
- const cases = [
63
- ['pnpm-lock.yaml', 'pnpm'],
64
- ['package-lock.json', 'npm']
65
- ];
66
- for (const [lockfile, expected] of cases) {
67
- const dir = makeTmpDir();
68
- try {
69
- fs.writeFileSync(path.join(dir, lockfile), '');
70
- assert.equal(fromLockfile(dir).name, expected, `${lockfile} should map to ${expected}`);
71
- } finally {
72
- fs.rmSync(dir, { recursive: true, force: true });
73
- }
74
- }
75
- });
76
-
77
- test('detectPackageManager prefers packageManager field over lockfile and user agent', () => {
78
- const dir = makeTmpDir();
79
- try {
80
- fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name: 'x', packageManager: 'pnpm@9.0.0' }));
81
- fs.writeFileSync(path.join(dir, 'package-lock.json'), '{}');
82
- const result = detectPackageManager(dir, { userAgent: 'npm/10.5.0 node/v20.11.0' });
83
- assert.equal(result.name, 'pnpm');
84
- assert.equal(result.source, 'package-manager-field');
85
- } finally {
86
- fs.rmSync(dir, { recursive: true, force: true });
87
- }
88
- });
89
-
90
- test('detectPackageManager falls back to lockfile, then user agent', () => {
91
- const dir = makeTmpDir();
92
- try {
93
- fs.writeFileSync(path.join(dir, 'pnpm-lock.yaml'), '');
94
- assert.equal(detectPackageManager(dir, { userAgent: 'npm/10.5.0 node/v20' }).name, 'pnpm');
95
- fs.rmSync(path.join(dir, 'pnpm-lock.yaml'));
96
- assert.equal(detectPackageManager(dir, { userAgent: 'npm/10.5.0 node/v20' }).name, 'npm');
97
- } finally {
98
- fs.rmSync(dir, { recursive: true, force: true });
99
- }
100
- });
101
-
102
- test('resolvePackageManager never returns null', () => {
103
- const dir = makeTmpDir();
104
- try {
105
- const result = resolvePackageManager(dir, { userAgent: undefined });
106
- assert.ok(SUPPORTED_PACKAGE_MANAGERS.includes(result.name));
107
- } finally {
108
- fs.rmSync(dir, { recursive: true, force: true });
109
- }
110
- });
111
-
112
- test('installCommand builds the right command per package manager', () => {
113
- assert.equal(installCommand('npm', 'slicejs-web-framework'), 'npm install slicejs-web-framework');
114
- assert.equal(installCommand('pnpm', 'slicejs-web-framework'), 'pnpm add slicejs-web-framework');
115
- assert.equal(installCommand('npm', 'slicejs-cli', { dev: true }), 'npm install -D slicejs-cli');
116
- assert.equal(installCommand('pnpm', 'slicejs-cli', { dev: true }), 'pnpm add -D slicejs-cli');
117
- assert.equal(installCommand('npm', 'slicejs-cli@latest', { global: true }), 'npm install -g slicejs-cli@latest');
118
- assert.equal(installCommand('pnpm', 'slicejs-cli@latest', { global: true }), 'pnpm add -g slicejs-cli@latest');
119
- assert.equal(installCommand('pnpm', ['a', 'b']), 'pnpm add a b');
120
- });
121
-
122
- test('uninstallCommand builds the right command per package manager', () => {
123
- assert.equal(uninstallCommand('npm', 'slicejs-cli'), 'npm uninstall slicejs-cli');
124
- assert.equal(uninstallCommand('pnpm', 'slicejs-cli'), 'pnpm remove slicejs-cli');
125
- assert.equal(uninstallCommand('pnpm', 'slicejs-cli', { global: true }), 'pnpm remove -g slicejs-cli');
126
- assert.equal(uninstallCommand('npm', 'slicejs-cli', { global: true }), 'npm uninstall -g slicejs-cli');
127
- });