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.
- package/README.md +81 -26
- package/client.js +73 -23
- package/commands/buildProduction/buildProduction.js +6 -3
- package/commands/doctor/doctor.js +68 -3
- package/commands/getComponent/getComponent.js +33 -25
- package/commands/init/init.js +176 -49
- package/commands/utils/PackageManager.js +148 -0
- package/commands/utils/VersionChecker.js +6 -4
- package/commands/utils/sliceScripts.js +23 -0
- package/commands/utils/updateManager.js +54 -35
- package/package.json +12 -1
- package/post.js +13 -19
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -29
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -25
- package/.github/pull_request_template.md +0 -22
- package/.github/workflows/ci.yml +0 -43
- package/AGENTS.md +0 -247
- package/CODE_OF_CONDUCT.md +0 -126
- package/ECOSYSTEM.md +0 -9
- package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +0 -182
- package/playwright.config.js +0 -51
- package/tests/build-command-integration.test.js +0 -87
- package/tests/build-production-e2e.test.js +0 -140
- package/tests/builder-edge-cases.test.js +0 -322
- package/tests/bundle-generate-e2e.test.js +0 -115
- package/tests/bundle-generator.test.js +0 -691
- package/tests/bundle-v2-register-output.test.js +0 -470
- package/tests/bundling-dependency-edges.test.js +0 -127
- package/tests/bundling-imports-unit.test.js +0 -267
- package/tests/client-launcher-contract.test.js +0 -211
- package/tests/client-update-flow-contract.test.js +0 -272
- package/tests/commands-component-crud.test.js +0 -102
- package/tests/commands-doctor.test.js +0 -80
- package/tests/commands-version-checker.test.js +0 -37
- package/tests/component-registry-parse.test.js +0 -34
- package/tests/dependency-analyzer.test.js +0 -24
- package/tests/e2e/bundles.spec.js +0 -91
- package/tests/e2e/dependency-scenarios.spec.js +0 -56
- package/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +0 -136
- package/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +0 -149
- package/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +0 -45
- package/tests/e2e/fixtures/components/Visual/Button/Button.css +0 -106
- package/tests/e2e/fixtures/components/Visual/Button/Button.html +0 -5
- package/tests/e2e/fixtures/components/Visual/Button/Button.js +0 -158
- package/tests/e2e/fixtures/components/Visual/Link/Link.js +0 -33
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.css +0 -56
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.html +0 -83
- package/tests/e2e/fixtures/components/Visual/Loading/Loading.js +0 -164
- package/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +0 -167
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +0 -116
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +0 -44
- package/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +0 -180
- package/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +0 -20
- package/tests/e2e/fixtures/components/Visual/Route/Route.js +0 -181
- package/tests/e2e/fixtures/components/registry.json +0 -12
- package/tests/e2e/fixtures/vendor-components.mjs +0 -65
- package/tests/e2e/navigation.spec.js +0 -44
- package/tests/e2e/render.spec.js +0 -34
- package/tests/e2e/serve.mjs +0 -264
- package/tests/e2e/shared-deps.spec.js +0 -61
- package/tests/e2e/unminified.spec.js +0 -33
- package/tests/e2e-serve.test.js +0 -148
- package/tests/fixtures/components.js +0 -8
- package/tests/fixtures/sliceConfig.json +0 -74
- package/tests/getcomponent.test.js +0 -407
- package/tests/helpers/setup.js +0 -102
- package/tests/init-command-contract.test.js +0 -46
- package/tests/local-cli-delegation.test.js +0 -81
- package/tests/path-helper.test.js +0 -206
- package/tests/perf-budget.test.js +0 -86
- package/tests/postinstall-command.test.js +0 -72
- package/tests/types-breakage.test.js +0 -491
- package/tests/types-generator-errors.test.js +0 -361
- package/tests/types-generator.test.js +0 -346
- package/tests/update-manager-notifications.test.js +0 -88
package/README.md
CHANGED
|
@@ -28,9 +28,9 @@ This repository contains the Slice.js CLI (`slicejs-cli`), the command-line tool
|
|
|
28
28
|
cd slicejs-cli
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
2. **Install dependencies**
|
|
31
|
+
2. **Install dependencies** (the repo pins pnpm via the `packageManager` field)
|
|
32
32
|
```bash
|
|
33
|
-
|
|
33
|
+
pnpm install --frozen-lockfile
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
3. **Test changes locally**
|
|
@@ -50,20 +50,58 @@ This repository contains the Slice.js CLI (`slicejs-cli`), the command-line tool
|
|
|
50
50
|
|
|
51
51
|
## Installation (for users)
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
For a **new project** you don't install anything manually — `slice init` (see Quick
|
|
54
|
+
start below) installs the CLI locally in the project it creates.
|
|
55
|
+
|
|
56
|
+
### Local in an existing project (Recommended)
|
|
54
57
|
|
|
55
58
|
```bash
|
|
56
|
-
npm install slicejs-cli --save-dev
|
|
59
|
+
npm install slicejs-cli --save-dev # or: pnpm add -D slicejs-cli
|
|
57
60
|
```
|
|
58
61
|
|
|
59
|
-
### Global (
|
|
62
|
+
### Global launcher (optional)
|
|
60
63
|
|
|
61
64
|
```bash
|
|
62
|
-
npm install -g slicejs-cli
|
|
65
|
+
npm install -g slicejs-cli # or: pnpm add -g slicejs-cli
|
|
63
66
|
```
|
|
64
67
|
|
|
68
|
+
The launcher delegates to the nearest project-local `node_modules/slicejs-cli`,
|
|
69
|
+
so each project keeps its pinned CLI version.
|
|
70
|
+
|
|
65
71
|
## Main commands
|
|
66
72
|
|
|
73
|
+
Inside initialized projects, prefer package scripts (`pnpm run ...`, `npm run ...`)
|
|
74
|
+
over direct binary calls.
|
|
75
|
+
|
|
76
|
+
Common script workflow:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pnpm run dev
|
|
80
|
+
pnpm run build
|
|
81
|
+
pnpm run start
|
|
82
|
+
pnpm run browse
|
|
83
|
+
pnpm run get -- Button
|
|
84
|
+
pnpm run sync
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Alternative with local devDependency resolution:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pnpm exec slice dev
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
If `slicejs-cli` is installed globally, `slice` can be executed directly from PATH.
|
|
94
|
+
|
|
95
|
+
For pnpm v10+, if build scripts are restricted, configure `allowBuilds` in
|
|
96
|
+
`pnpm-workspace.yaml`:
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
allowBuilds:
|
|
100
|
+
slicejs-cli: true
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`slice init --pm pnpm` now writes this automatically.
|
|
104
|
+
|
|
67
105
|
| Command | Description |
|
|
68
106
|
|---------|-------------|
|
|
69
107
|
| `slice init` | Initialize a Slice.js project |
|
|
@@ -83,17 +121,21 @@ When you install `slicejs-cli`, the `postinstall` script automatically configure
|
|
|
83
121
|
```json
|
|
84
122
|
{
|
|
85
123
|
"scripts": {
|
|
86
|
-
"slice:
|
|
87
|
-
"slice:
|
|
88
|
-
"slice:
|
|
89
|
-
"slice:
|
|
90
|
-
"slice:
|
|
91
|
-
"slice:
|
|
92
|
-
"slice:
|
|
93
|
-
"slice:
|
|
94
|
-
"slice:
|
|
95
|
-
"slice:
|
|
96
|
-
"slice:
|
|
124
|
+
"slice:init": "node ./node_modules/slicejs-cli/client.js init",
|
|
125
|
+
"slice:dev": "node ./node_modules/slicejs-cli/client.js dev",
|
|
126
|
+
"slice:build": "node ./node_modules/slicejs-cli/client.js build",
|
|
127
|
+
"slice:start": "node ./node_modules/slicejs-cli/client.js start",
|
|
128
|
+
"slice:create": "node ./node_modules/slicejs-cli/client.js component create",
|
|
129
|
+
"slice:list": "node ./node_modules/slicejs-cli/client.js component list",
|
|
130
|
+
"slice:delete": "node ./node_modules/slicejs-cli/client.js component delete",
|
|
131
|
+
"slice:get": "node ./node_modules/slicejs-cli/client.js get",
|
|
132
|
+
"slice:browse": "node ./node_modules/slicejs-cli/client.js browse",
|
|
133
|
+
"slice:sync": "node ./node_modules/slicejs-cli/client.js sync",
|
|
134
|
+
"slice:doctor": "node ./node_modules/slicejs-cli/client.js doctor",
|
|
135
|
+
"slice:version": "node ./node_modules/slicejs-cli/client.js version",
|
|
136
|
+
"slice:help": "node ./node_modules/slicejs-cli/client.js --help",
|
|
137
|
+
"slice:update": "node ./node_modules/slicejs-cli/client.js update",
|
|
138
|
+
"slice:types": "node ./node_modules/slicejs-cli/client.js types generate"
|
|
97
139
|
}
|
|
98
140
|
}
|
|
99
141
|
```
|
|
@@ -106,21 +148,34 @@ npx slicejs-cli postinstall
|
|
|
106
148
|
|
|
107
149
|
## Quick start
|
|
108
150
|
|
|
151
|
+
`slice init` creates the project folder itself — no `mkdir` or `npm init` needed.
|
|
152
|
+
Everything (package.json, node_modules, lockfile, src/, api/) lives inside the new folder.
|
|
153
|
+
|
|
109
154
|
```bash
|
|
110
|
-
#
|
|
111
|
-
|
|
112
|
-
|
|
155
|
+
# With npm
|
|
156
|
+
npx slicejs-cli init
|
|
157
|
+
cd my-app
|
|
158
|
+
npm run dev
|
|
159
|
+
```
|
|
113
160
|
|
|
114
|
-
|
|
115
|
-
|
|
161
|
+
```bash
|
|
162
|
+
# With pnpm
|
|
163
|
+
pnpm dlx slicejs-cli init
|
|
164
|
+
cd my-app
|
|
165
|
+
pnpm run dev
|
|
166
|
+
```
|
|
116
167
|
|
|
117
|
-
|
|
118
|
-
npx slicejs-cli init
|
|
168
|
+
Non-interactive (for scripts/CI):
|
|
119
169
|
|
|
120
|
-
|
|
121
|
-
npx slicejs-cli
|
|
170
|
+
```bash
|
|
171
|
+
npx slicejs-cli init -y my-app --pm pnpm
|
|
122
172
|
```
|
|
123
173
|
|
|
174
|
+
init pins the chosen package manager in the `packageManager` field, installs
|
|
175
|
+
`slicejs-web-framework` as a dependency and `slicejs-cli` as a devDependency of
|
|
176
|
+
the new project. Versions are never hard-pinned at install time, so hardened pnpm
|
|
177
|
+
setups (`minimumReleaseAge` quarantine, `ignore-scripts`) work out of the box.
|
|
178
|
+
|
|
124
179
|
## Tests
|
|
125
180
|
|
|
126
181
|
The CLI uses Node.js native test runner:
|
package/client.js
CHANGED
|
@@ -27,6 +27,15 @@ import {
|
|
|
27
27
|
resolveLocalCliCandidate,
|
|
28
28
|
shouldDelegateToLocalCli
|
|
29
29
|
} from './commands/utils/LocalCliDelegation.js';
|
|
30
|
+
import {
|
|
31
|
+
detectPackageManager,
|
|
32
|
+
getAvailablePackageManagers,
|
|
33
|
+
isPackageManagerAvailable,
|
|
34
|
+
resolvePackageManager,
|
|
35
|
+
runScriptCommand,
|
|
36
|
+
SUPPORTED_PACKAGE_MANAGERS
|
|
37
|
+
} from './commands/utils/PackageManager.js';
|
|
38
|
+
import { SLICE_SCRIPTS } from './commands/utils/sliceScripts.js';
|
|
30
39
|
|
|
31
40
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
32
41
|
|
|
@@ -96,6 +105,7 @@ sliceClient
|
|
|
96
105
|
.command("init")
|
|
97
106
|
.description("Initialize a new Slice.js project")
|
|
98
107
|
.option("-y, --yes [name]", "Skip prompts and initialize with project name")
|
|
108
|
+
.option("--pm <packageManager>", "Package manager to use (pnpm or npm). Auto-detected when omitted")
|
|
99
109
|
.action(async (options) => {
|
|
100
110
|
let projectName = 'my-slice-app';
|
|
101
111
|
if (options.yes) {
|
|
@@ -123,16 +133,62 @@ sliceClient
|
|
|
123
133
|
projectName = answers.projectName;
|
|
124
134
|
}
|
|
125
135
|
|
|
136
|
+
// Resolve the package manager: --pm flag → detection → interactive prompt.
|
|
137
|
+
// Detection here has no project root yet (the folder is new), so it relies on
|
|
138
|
+
// npm_config_user_agent (npx / pnpm dlx / PM scripts) or a single available binary.
|
|
139
|
+
let packageManager = options.pm;
|
|
140
|
+
if (packageManager && !SUPPORTED_PACKAGE_MANAGERS.includes(packageManager)) {
|
|
141
|
+
Print.error(`Unsupported package manager "${packageManager}". Use one of: ${SUPPORTED_PACKAGE_MANAGERS.join(', ')}`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (packageManager && !isPackageManagerAvailable(packageManager)) {
|
|
145
|
+
Print.error(`Package manager "${packageManager}" is not available on this system`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (!packageManager) {
|
|
149
|
+
const detected = detectPackageManager(null);
|
|
150
|
+
if (detected) {
|
|
151
|
+
packageManager = detected.name;
|
|
152
|
+
} else if (!options.yes) {
|
|
153
|
+
const available = getAvailablePackageManagers();
|
|
154
|
+
if (available.length === 0) {
|
|
155
|
+
Print.error('No package manager found (pnpm or npm). Install one and retry.');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const { pm } = await inquirer.prompt([
|
|
159
|
+
{
|
|
160
|
+
type: 'list',
|
|
161
|
+
name: 'pm',
|
|
162
|
+
message: 'Which package manager do you want to use?',
|
|
163
|
+
choices: available,
|
|
164
|
+
default: available.includes('pnpm') ? 'pnpm' : available[0]
|
|
165
|
+
}
|
|
166
|
+
]);
|
|
167
|
+
packageManager = pm;
|
|
168
|
+
} else {
|
|
169
|
+
const available = getAvailablePackageManagers();
|
|
170
|
+
packageManager = available.includes('pnpm') ? 'pnpm' : available[0];
|
|
171
|
+
if (!packageManager) {
|
|
172
|
+
Print.error('No package manager found (pnpm or npm). Install one and retry.');
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
126
178
|
const projectDir = path.resolve(projectName);
|
|
127
179
|
|
|
128
180
|
if (fs.existsSync(projectDir)) {
|
|
129
181
|
const contents = fs.readdirSync(projectDir);
|
|
130
182
|
if (contents.length > 0) {
|
|
183
|
+
if (options.yes) {
|
|
184
|
+
Print.error(`Directory "${projectName}" already exists and is not empty. Aborting.`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
131
187
|
const { overwrite } = await inquirer.prompt([
|
|
132
188
|
{
|
|
133
189
|
type: 'confirm',
|
|
134
190
|
name: 'overwrite',
|
|
135
|
-
message: `Directory "${
|
|
191
|
+
message: `Directory "${projectName}" already exists and is not empty. Continue?`,
|
|
136
192
|
default: false
|
|
137
193
|
}
|
|
138
194
|
]);
|
|
@@ -148,9 +204,8 @@ sliceClient
|
|
|
148
204
|
process.chdir(projectDir);
|
|
149
205
|
process.env.INIT_CWD = projectDir;
|
|
150
206
|
|
|
151
|
-
await runWithVersionCheck(() => {
|
|
152
|
-
initializeProject();
|
|
153
|
-
return Promise.resolve();
|
|
207
|
+
await runWithVersionCheck(async () => {
|
|
208
|
+
await initializeProject({ packageManager });
|
|
154
209
|
});
|
|
155
210
|
});
|
|
156
211
|
|
|
@@ -353,7 +408,7 @@ componentCommand
|
|
|
353
408
|
category = categoryAnswer.category;
|
|
354
409
|
}
|
|
355
410
|
|
|
356
|
-
const config =
|
|
411
|
+
const config = sharedLoadConfigSync(import.meta.url);
|
|
357
412
|
if (!config) {
|
|
358
413
|
Print.error("Could not load configuration");
|
|
359
414
|
return;
|
|
@@ -550,6 +605,7 @@ sliceClient
|
|
|
550
605
|
.option("-y, --yes", "Skip confirmation and update all packages automatically")
|
|
551
606
|
.option("--cli", "Update only the Slice.js CLI")
|
|
552
607
|
.option("-f, --framework", "Update only the Slice.js Framework")
|
|
608
|
+
.option("--update-api", "Also overwrite api/index.js with the framework version (never done by default; creates a .bak backup)")
|
|
553
609
|
.action(async (options) => {
|
|
554
610
|
await updateManager.checkAndPromptUpdates(options);
|
|
555
611
|
});
|
|
@@ -570,30 +626,24 @@ sliceClient
|
|
|
570
626
|
.command("postinstall")
|
|
571
627
|
.description("Configure npm scripts in package.json (alternative to postinstall for --ignore-scripts users)")
|
|
572
628
|
.action(() => {
|
|
573
|
-
|
|
629
|
+
// npm sets npm_config_global; pnpm global installs are detected via PNPM_HOME.
|
|
630
|
+
const pnpmHome = process.env.PNPM_HOME;
|
|
631
|
+
const cliEntryPath = fileURLToPath(import.meta.url);
|
|
632
|
+
const isGlobal = process.env.npm_config_global === 'true'
|
|
633
|
+
|| (pnpmHome && cliEntryPath.startsWith(pnpmHome));
|
|
574
634
|
if (isGlobal) {
|
|
575
635
|
console.log('⚠️ Global installation of slicejs-cli detected.');
|
|
576
636
|
console.log(' We strongly recommend using a local installation to avoid version mismatches.');
|
|
577
|
-
console.log(
|
|
637
|
+
console.log(` Uninstall global: ${pnpmHome ? 'pnpm remove -g slicejs-cli' : 'npm uninstall -g slicejs-cli'}`);
|
|
578
638
|
return;
|
|
579
639
|
}
|
|
580
640
|
|
|
581
641
|
const projectRoot = getProjectRoot(import.meta.url);
|
|
582
642
|
const pkgPath = path.join(projectRoot, 'package.json');
|
|
643
|
+
const packageManager = resolvePackageManager(projectRoot).name;
|
|
583
644
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
'slice:start': 'slice start',
|
|
587
|
-
'slice:create': 'slice component create',
|
|
588
|
-
'slice:list': 'slice component list',
|
|
589
|
-
'slice:delete': 'slice component delete',
|
|
590
|
-
'slice:init': 'slice init',
|
|
591
|
-
'slice:get': 'slice get',
|
|
592
|
-
'slice:browse': 'slice browse',
|
|
593
|
-
'slice:sync': 'slice sync',
|
|
594
|
-
'slice:version': 'slice version',
|
|
595
|
-
'slice:update': 'slice update',
|
|
596
|
-
};
|
|
645
|
+
// Shared with post.js and slice init — see commands/utils/sliceScripts.js
|
|
646
|
+
const sliceScripts = SLICE_SCRIPTS;
|
|
597
647
|
|
|
598
648
|
try {
|
|
599
649
|
let pkg = {};
|
|
@@ -618,12 +668,12 @@ sliceClient
|
|
|
618
668
|
}
|
|
619
669
|
|
|
620
670
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), 'utf-8');
|
|
621
|
-
console.log(`✅ slicejs-cli installed successfully. Added ${addedCount}
|
|
622
|
-
console.log(
|
|
671
|
+
console.log(`✅ slicejs-cli installed successfully. Added ${addedCount} package scripts to package.json.`);
|
|
672
|
+
console.log(` Run: ${runScriptCommand(packageManager, 'slice:dev')}`);
|
|
623
673
|
} catch (err) {
|
|
624
674
|
console.log('✅ slicejs-cli installed successfully.');
|
|
625
675
|
console.log(' Could not auto-configure scripts:', err.message);
|
|
626
|
-
console.log(
|
|
676
|
+
console.log(` Configure scripts manually and run: ${runScriptCommand(packageManager, 'slice:dev')}`);
|
|
627
677
|
}
|
|
628
678
|
});
|
|
629
679
|
|
|
@@ -6,7 +6,8 @@ import { minify as terserMinify } from 'terser';
|
|
|
6
6
|
import { minify } from 'html-minifier-terser';
|
|
7
7
|
import CleanCSS from 'clean-css';
|
|
8
8
|
import Print from '../Print.js';
|
|
9
|
-
import { getSrcPath, getDistPath, getConfigPath } from '../utils/PathHelper.js';
|
|
9
|
+
import { getSrcPath, getDistPath, getConfigPath, getProjectRoot } from '../utils/PathHelper.js';
|
|
10
|
+
import { resolvePackageManager, runScriptCommand } from '../utils/PackageManager.js';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Loads configuration from sliceConfig.json
|
|
@@ -441,6 +442,7 @@ async function analyzeBuild() {
|
|
|
441
442
|
*/
|
|
442
443
|
export default async function buildProduction(options = {}) {
|
|
443
444
|
const startTime = Date.now();
|
|
445
|
+
const packageManager = resolvePackageManager(getProjectRoot(import.meta.url)).name;
|
|
444
446
|
|
|
445
447
|
try {
|
|
446
448
|
Print.title('🔨 Building Slice.js project for production...');
|
|
@@ -483,7 +485,7 @@ export default async function buildProduction(options = {}) {
|
|
|
483
485
|
Print.info('Your optimized project is ready in the /dist directory');
|
|
484
486
|
Print.newLine();
|
|
485
487
|
Print.info('Next steps:');
|
|
486
|
-
console.log(
|
|
488
|
+
console.log(` • Use "${runScriptCommand(packageManager, 'start')}" to test the production build`);
|
|
487
489
|
console.log(' • All Slice.js components and architecture preserved');
|
|
488
490
|
|
|
489
491
|
return true;
|
|
@@ -499,6 +501,7 @@ export default async function buildProduction(options = {}) {
|
|
|
499
501
|
*/
|
|
500
502
|
export async function serveProductionBuild(port) {
|
|
501
503
|
try {
|
|
504
|
+
const packageManager = resolvePackageManager(getProjectRoot(import.meta.url)).name;
|
|
502
505
|
const config = loadConfig();
|
|
503
506
|
const defaultPort = config?.server?.port || 3001;
|
|
504
507
|
const finalPort = port || defaultPort;
|
|
@@ -533,7 +536,7 @@ export async function serveProductionBuild(port) {
|
|
|
533
536
|
Print.success(`Production preview server running at http://localhost:${finalPort}`);
|
|
534
537
|
Print.info('Press Ctrl+C to stop the server');
|
|
535
538
|
Print.info('This server previews your production build from /dist');
|
|
536
|
-
Print.warning(
|
|
539
|
+
Print.warning(`This is a preview server - use "${runScriptCommand(packageManager, 'start')}" for the full production server`);
|
|
537
540
|
});
|
|
538
541
|
|
|
539
542
|
} catch (error) {
|
|
@@ -8,6 +8,7 @@ import inquirer from 'inquirer';
|
|
|
8
8
|
import { exec } from 'child_process';
|
|
9
9
|
import { promisify } from 'util';
|
|
10
10
|
import { getProjectRoot, getSrcPath, getApiPath, getConfigPath, getPath } from '../utils/PathHelper.js';
|
|
11
|
+
import { resolvePackageManager, installCommand } from '../utils/PackageManager.js';
|
|
11
12
|
import updateManager from '../utils/updateManager.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -142,6 +143,68 @@ async function checkPort() {
|
|
|
142
143
|
});
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Checks the package manager setup: mixed lockfiles, packageManager field
|
|
148
|
+
* consistency, and a project-local CLI install (required for local delegation).
|
|
149
|
+
* Exported for tests.
|
|
150
|
+
*/
|
|
151
|
+
export async function checkPackageManagerSetup(projectRoot = getProjectRoot(import.meta.url)) {
|
|
152
|
+
const issues = [];
|
|
153
|
+
const suggestions = [];
|
|
154
|
+
|
|
155
|
+
const LOCKFILE_PM = {
|
|
156
|
+
'package-lock.json': 'npm',
|
|
157
|
+
'pnpm-lock.yaml': 'pnpm'
|
|
158
|
+
};
|
|
159
|
+
const lockfiles = [];
|
|
160
|
+
for (const lockfile of Object.keys(LOCKFILE_PM)) {
|
|
161
|
+
if (await fs.pathExists(path.join(projectRoot, lockfile))) {
|
|
162
|
+
lockfiles.push(lockfile);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let pkg = null;
|
|
167
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
168
|
+
if (await fs.pathExists(pkgPath)) {
|
|
169
|
+
try { pkg = await fs.readJson(pkgPath); } catch { /* reported by Dependencies check */ }
|
|
170
|
+
}
|
|
171
|
+
const pmField = typeof pkg?.packageManager === 'string' ? pkg.packageManager.split('@')[0] : null;
|
|
172
|
+
const pm = resolvePackageManager(projectRoot).name;
|
|
173
|
+
|
|
174
|
+
if (lockfiles.length > 1) {
|
|
175
|
+
issues.push(`Mixed lockfiles found: ${lockfiles.join(', ')}`);
|
|
176
|
+
const keep = pmField || pm;
|
|
177
|
+
const keepLockfile = Object.entries(LOCKFILE_PM).find(([, name]) => name === keep)?.[0];
|
|
178
|
+
suggestions.push(`Keep only ${keepLockfile ?? 'the lockfile of your package manager'} and delete the rest`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (pkg && !pmField) {
|
|
182
|
+
issues.push('No "packageManager" field in package.json');
|
|
183
|
+
suggestions.push(`Pin it (e.g. "packageManager": "${pm}@<version>") so every tool resolves the same package manager`);
|
|
184
|
+
} else if (pmField && lockfiles.length === 1 && LOCKFILE_PM[lockfiles[0]] !== pmField) {
|
|
185
|
+
issues.push(`packageManager is "${pmField}" but the lockfile is ${lockfiles[0]}`);
|
|
186
|
+
suggestions.push(`Reinstall with ${pmField} (or update the packageManager field) so they agree`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const localCliPath = path.join(projectRoot, 'node_modules', 'slicejs-cli', 'package.json');
|
|
190
|
+
if (pkg && !(await fs.pathExists(localCliPath))) {
|
|
191
|
+
issues.push('slicejs-cli is not installed locally');
|
|
192
|
+
suggestions.push(`Run "${installCommand(pm, 'slicejs-cli', { dev: true })}" so the launcher delegates to a version pinned per project`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (issues.length === 0) {
|
|
196
|
+
return {
|
|
197
|
+
pass: true,
|
|
198
|
+
message: `Package manager setup is consistent (${pmField || pm})`
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
warn: true,
|
|
203
|
+
message: issues.join(' | '),
|
|
204
|
+
suggestion: suggestions.join(' | ')
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
145
208
|
/**
|
|
146
209
|
* Checks dependencies in package.json
|
|
147
210
|
*/
|
|
@@ -176,8 +239,8 @@ async function checkDependencies() {
|
|
|
176
239
|
warn: true,
|
|
177
240
|
message: `Missing dependencies: ${missing.join(', ')}`,
|
|
178
241
|
suggestion: missing.includes('slicejs-web-framework')
|
|
179
|
-
?
|
|
180
|
-
:
|
|
242
|
+
? `Run "${installCommand(resolvePackageManager(getProjectRoot(import.meta.url)).name, 'slicejs-web-framework@latest')}" in your project`
|
|
243
|
+
: `Run "${installCommand(resolvePackageManager(getProjectRoot(import.meta.url)).name, 'slicejs-cli@latest', { dev: true })}" in your project`,
|
|
181
244
|
missing
|
|
182
245
|
};
|
|
183
246
|
}
|
|
@@ -278,6 +341,7 @@ export default async function runDiagnostics() {
|
|
|
278
341
|
{ name: 'Project Structure', fn: checkDirectoryStructure },
|
|
279
342
|
{ name: 'Configuration', fn: checkConfig },
|
|
280
343
|
{ name: 'Port Availability', fn: checkPort },
|
|
344
|
+
{ name: 'Package Manager', fn: checkPackageManagerSetup },
|
|
281
345
|
{ name: 'Dependencies', fn: checkDependencies },
|
|
282
346
|
{ name: 'Components', fn: checkComponents }
|
|
283
347
|
];
|
|
@@ -340,9 +404,10 @@ export default async function runDiagnostics() {
|
|
|
340
404
|
}
|
|
341
405
|
]);
|
|
342
406
|
if (confirmInstall) {
|
|
407
|
+
const pm = resolvePackageManager(projectRoot).name;
|
|
343
408
|
for (const pkg of depsResult.missing) {
|
|
344
409
|
try {
|
|
345
|
-
const cmd =
|
|
410
|
+
const cmd = installCommand(pm, `${pkg}@latest`);
|
|
346
411
|
Print.info(`Installing ${pkg}...`);
|
|
347
412
|
await execAsync(cmd, { cwd: projectRoot });
|
|
348
413
|
Print.success(`${pkg} installed`);
|
|
@@ -50,6 +50,10 @@ class ComponentRegistry {
|
|
|
50
50
|
this.componentsRegistry = null;
|
|
51
51
|
this.config = null;
|
|
52
52
|
this._configPromise = null;
|
|
53
|
+
// Serializes read-modify-write cycles on components.js: installs run
|
|
54
|
+
// concurrently (runConcurrent), and parallel writers corrupt the file
|
|
55
|
+
// (partial reads) or silently drop registrations (lost updates).
|
|
56
|
+
this._registryLock = Promise.resolve();
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
async _ensureConfig() {
|
|
@@ -249,31 +253,35 @@ filterOfficialComponents(allComponents) {
|
|
|
249
253
|
}
|
|
250
254
|
|
|
251
255
|
async updateLocalRegistrySafe(componentName, category) {
|
|
256
|
+
// Queue behind any in-flight registry update; keep the chain alive even
|
|
257
|
+
// when an update throws so later updates still run.
|
|
258
|
+
const run = this._registryLock.then(() => this._updateLocalRegistry(componentName, category));
|
|
259
|
+
this._registryLock = run.catch(() => {});
|
|
260
|
+
return run;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async _updateLocalRegistry(componentName, category) {
|
|
252
264
|
const componentsPath = getComponentsJsPath(import.meta.url);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
Print.info(`${componentName} already exists in local registry`);
|
|
274
|
-
}
|
|
275
|
-
} catch (error) {
|
|
276
|
-
throw error;
|
|
265
|
+
if (!await fs.pathExists(componentsPath)) {
|
|
266
|
+
const dir = path.dirname(componentsPath);
|
|
267
|
+
await fs.ensureDir(dir);
|
|
268
|
+
const initial = `const components = {};\n\nexport default components;\n`;
|
|
269
|
+
await fs.writeFile(componentsPath, initial, 'utf8');
|
|
270
|
+
}
|
|
271
|
+
const content = await fs.readFile(componentsPath, 'utf8');
|
|
272
|
+
const match = content.match(/const components = ({[\s\S]*?});/);
|
|
273
|
+
if (!match) throw new Error('Invalid components.js format in local project');
|
|
274
|
+
const componentsObj = JSON.parse(match[1]);
|
|
275
|
+
if (!componentsObj[componentName]) {
|
|
276
|
+
componentsObj[componentName] = category;
|
|
277
|
+
const sorted = Object.keys(componentsObj)
|
|
278
|
+
.sort()
|
|
279
|
+
.reduce((obj, key) => { obj[key] = componentsObj[key]; return obj; }, {});
|
|
280
|
+
const newContent = `const components = ${JSON.stringify(sorted, null, 2)};\n\nexport default components;\n`;
|
|
281
|
+
await fs.writeFile(componentsPath, newContent, 'utf8');
|
|
282
|
+
Print.registryUpdate(`Registered ${componentName} in local components.js`);
|
|
283
|
+
} else {
|
|
284
|
+
Print.info(`${componentName} already exists in local registry`);
|
|
277
285
|
}
|
|
278
286
|
}
|
|
279
287
|
|
|
@@ -339,7 +347,7 @@ filterOfficialComponents(allComponents) {
|
|
|
339
347
|
await this.updateLocalRegistrySafe(componentName, category);
|
|
340
348
|
|
|
341
349
|
Print.success(`${componentName} installed successfully from official repository!`);
|
|
342
|
-
console.log(`📁 Location: ${folderSuffix
|
|
350
|
+
console.log(`📁 Location: ${[folderSuffix, categoryPath, componentName].join('/').replace(/\/+/g, '/')}/`);
|
|
343
351
|
console.log(`📄 Files: ${downloadedFiles.join(', ')}`);
|
|
344
352
|
|
|
345
353
|
return true;
|