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/commands/init/init.js
CHANGED
|
@@ -5,10 +5,107 @@ import ora from 'ora';
|
|
|
5
5
|
import Print from '../Print.js';
|
|
6
6
|
import { getProjectRoot, getApiPath, getSrcPath, getPath } from '../utils/PathHelper.js';
|
|
7
7
|
import { execSync } from 'child_process';
|
|
8
|
+
import {
|
|
9
|
+
resolvePackageManager,
|
|
10
|
+
getPackageManagerVersion,
|
|
11
|
+
installCommand
|
|
12
|
+
} from '../utils/PackageManager.js';
|
|
13
|
+
import { SLICE_SCRIPTS } from '../utils/sliceScripts.js';
|
|
8
14
|
|
|
9
15
|
// Import ComponentRegistry class from getComponent
|
|
10
16
|
import { ComponentRegistry } from '../getComponent/getComponent.js';
|
|
11
17
|
|
|
18
|
+
// Fetch the latest published version straight from the npm registry. This is
|
|
19
|
+
// informational only (we never pin installs to it): it avoids depending on
|
|
20
|
+
// `npm view` (absent on pnpm-only machines) and plays nice with pnpm's
|
|
21
|
+
// minimumReleaseAge quarantine, which may legitimately resolve an older version.
|
|
22
|
+
async function fetchLatestVersion(packageName) {
|
|
23
|
+
try {
|
|
24
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
25
|
+
headers: { 'Accept': 'application/json' }
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) return null;
|
|
28
|
+
const data = await response.json();
|
|
29
|
+
return data.version || null;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function ensurePnpmAllowBuilds(projectRoot) {
|
|
36
|
+
const workspacePath = path.join(projectRoot, 'pnpm-workspace.yaml');
|
|
37
|
+
const allowBuildLine = ' slicejs-cli: true';
|
|
38
|
+
|
|
39
|
+
if (!(await fs.pathExists(workspacePath))) {
|
|
40
|
+
await fs.writeFile(workspacePath, `allowBuilds:\n${allowBuildLine}\n`, 'utf8');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const raw = await fs.readFile(workspacePath, 'utf8');
|
|
45
|
+
const lines = raw.split(/\r?\n/);
|
|
46
|
+
const allowIdx = lines.findIndex((line) => /^allowBuilds:\s*$/.test(line));
|
|
47
|
+
|
|
48
|
+
if (allowIdx === -1) {
|
|
49
|
+
const suffix = raw.endsWith('\n') ? '' : '\n';
|
|
50
|
+
await fs.writeFile(workspacePath, `${raw}${suffix}allowBuilds:\n${allowBuildLine}\n`, 'utf8');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let blockEnd = lines.length;
|
|
55
|
+
for (let i = allowIdx + 1; i < lines.length; i++) {
|
|
56
|
+
const line = lines[i];
|
|
57
|
+
if (!line.trim()) continue;
|
|
58
|
+
if (!/^\s/.test(line)) {
|
|
59
|
+
blockEnd = i;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let found = false;
|
|
65
|
+
for (let i = allowIdx + 1; i < blockEnd; i++) {
|
|
66
|
+
if (/^\s+slicejs-cli\s*:/.test(lines[i])) {
|
|
67
|
+
lines[i] = allowBuildLine;
|
|
68
|
+
found = true;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!found) {
|
|
74
|
+
lines.splice(blockEnd, 0, allowBuildLine);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await fs.writeFile(workspacePath, `${lines.join('\n').replace(/\n*$/, '\n')}`, 'utf8');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Create the project manifest BEFORE any install runs. Without a package.json in
|
|
81
|
+
// the project folder, npm/pnpm walk up the directory tree looking for the nearest
|
|
82
|
+
// manifest and anchor node_modules (and the dependency entry) OUTSIDE the project.
|
|
83
|
+
// Exported for tests (init-project-isolation.test.js).
|
|
84
|
+
export async function ensureProjectManifest(projectRoot, packageManager) {
|
|
85
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
86
|
+
if (await fs.pathExists(pkgPath)) return pkgPath;
|
|
87
|
+
|
|
88
|
+
const pkg = {
|
|
89
|
+
name: path.basename(projectRoot),
|
|
90
|
+
version: '1.0.0',
|
|
91
|
+
description: 'Slice.js project',
|
|
92
|
+
main: 'api/index.js',
|
|
93
|
+
type: 'module',
|
|
94
|
+
engines: { node: '>=20.0.0' },
|
|
95
|
+
scripts: {}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Persist the chosen package manager (corepack convention) so every later
|
|
99
|
+
// command — slice update, slice doctor — detects it deterministically.
|
|
100
|
+
const pmVersion = getPackageManagerVersion(packageManager);
|
|
101
|
+
if (pmVersion) {
|
|
102
|
+
pkg.packageManager = `${packageManager}@${pmVersion}`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');
|
|
106
|
+
return pkgPath;
|
|
107
|
+
}
|
|
108
|
+
|
|
12
109
|
// Visual components used by the App Shell + MultiRoute starter project.
|
|
13
110
|
// We install only these on init; newcomers add more on demand with `slice get <Name>`.
|
|
14
111
|
const STARTER_VISUAL_COMPONENTS = [
|
|
@@ -30,32 +127,56 @@ const STARTER_SERVICE_COMPONENTS = [
|
|
|
30
127
|
'LocalStorageManager'
|
|
31
128
|
];
|
|
32
129
|
|
|
33
|
-
export default async function initializeProject(
|
|
130
|
+
export default async function initializeProject(options = {}) {
|
|
34
131
|
try {
|
|
35
132
|
const projectRoot = getProjectRoot(import.meta.url);
|
|
36
133
|
const destinationApi = getApiPath(import.meta.url);
|
|
37
134
|
const destinationSrc = getSrcPath(import.meta.url);
|
|
38
135
|
|
|
136
|
+
// Resolve the package manager chosen in `slice init` (or detect it when
|
|
137
|
+
// initializeProject is invoked directly, e.g. inside an existing folder).
|
|
138
|
+
const packageManager = options.packageManager
|
|
139
|
+
|| resolvePackageManager(projectRoot).name;
|
|
140
|
+
|
|
141
|
+
if (packageManager === 'pnpm') {
|
|
142
|
+
await ensurePnpmAllowBuilds(projectRoot);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 0. CREATE PROJECT MANIFEST FIRST — must exist before any install so the
|
|
146
|
+
// package manager anchors node_modules inside the project folder.
|
|
147
|
+
await ensureProjectManifest(projectRoot, packageManager);
|
|
148
|
+
|
|
39
149
|
const fwSpinner = ora('Ensuring latest Slice framework...').start();
|
|
40
150
|
let latestVersion = null;
|
|
151
|
+
let installedVersion = null;
|
|
41
152
|
let sliceBaseDir;
|
|
42
153
|
try {
|
|
43
|
-
|
|
44
|
-
latestVersion = latest;
|
|
154
|
+
latestVersion = await fetchLatestVersion('slicejs-web-framework');
|
|
45
155
|
const installedPkgPath = getPath(import.meta.url, 'node_modules', 'slicejs-web-framework', 'package.json');
|
|
46
156
|
let installed = null;
|
|
47
157
|
if (await fs.pathExists(installedPkgPath)) {
|
|
48
158
|
const pkg = await fs.readJson(installedPkgPath);
|
|
49
159
|
installed = pkg.version;
|
|
50
160
|
}
|
|
51
|
-
if (installed !==
|
|
52
|
-
|
|
161
|
+
if (!installed || (latestVersion && installed !== latestVersion)) {
|
|
162
|
+
// Install WITHOUT pinning an exact version: the package manager
|
|
163
|
+
// resolves it under its own policies (e.g. pnpm minimumReleaseAge
|
|
164
|
+
// quarantines versions younger than the configured age — pinning
|
|
165
|
+
// the registry's freshest version would make resolution fail).
|
|
166
|
+
execSync(installCommand(packageManager, 'slicejs-web-framework'), { cwd: projectRoot, stdio: 'inherit' });
|
|
167
|
+
}
|
|
168
|
+
if (await fs.pathExists(installedPkgPath)) {
|
|
169
|
+
const pkg = await fs.readJson(installedPkgPath);
|
|
170
|
+
installedVersion = pkg.version;
|
|
53
171
|
}
|
|
54
172
|
sliceBaseDir = getPath(import.meta.url, 'node_modules', 'slicejs-web-framework');
|
|
55
|
-
fwSpinner.succeed(`slicejs-web-framework@${
|
|
173
|
+
fwSpinner.succeed(`slicejs-web-framework@${installedVersion || 'unknown'} ready`);
|
|
174
|
+
if (latestVersion && installedVersion && installedVersion !== latestVersion) {
|
|
175
|
+
Print.info(`Latest published is ${latestVersion}; your package manager resolved ${installedVersion} (release-age policy or cached registry).`);
|
|
176
|
+
}
|
|
56
177
|
} catch (err) {
|
|
57
178
|
// Fallback uses __dirname-style path because it looks for a local development copy,
|
|
58
|
-
// not a project-relative path —
|
|
179
|
+
// not a project-relative path — the install failed, so we fall back to monorepo sibling.
|
|
59
180
|
const fallback = path.join(path.dirname(fileURLToPath(import.meta.url)), '../../../slicejs-web-framework');
|
|
60
181
|
if (await fs.pathExists(fallback)) {
|
|
61
182
|
sliceBaseDir = fallback;
|
|
@@ -67,6 +188,21 @@ export default async function initializeProject(projectType) {
|
|
|
67
188
|
}
|
|
68
189
|
}
|
|
69
190
|
|
|
191
|
+
// 0b. INSTALL THE CLI LOCALLY (devDependency) so the generated scripts
|
|
192
|
+
// (`npm run dev` → `slice dev`) resolve via local delegation to a version
|
|
193
|
+
// pinned per project, as the docs recommend.
|
|
194
|
+
const cliSpinner = ora('Installing slicejs-cli as devDependency...').start();
|
|
195
|
+
try {
|
|
196
|
+
const cliPkgPath = getPath(import.meta.url, 'node_modules', 'slicejs-cli', 'package.json');
|
|
197
|
+
if (!(await fs.pathExists(cliPkgPath))) {
|
|
198
|
+
execSync(installCommand(packageManager, 'slicejs-cli', { dev: true }), { cwd: projectRoot, stdio: 'inherit' });
|
|
199
|
+
}
|
|
200
|
+
cliSpinner.succeed('slicejs-cli installed locally');
|
|
201
|
+
} catch (err) {
|
|
202
|
+
cliSpinner.warn('Could not install slicejs-cli locally — scripts will use the global CLI');
|
|
203
|
+
Print.info(`You can add it later with: ${installCommand(packageManager, 'slicejs-cli', { dev: true })}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
70
206
|
// These derive from sliceBaseDir (which comes from npm install or fallback),
|
|
71
207
|
// so they're already dynamic — no PathHelper needed.
|
|
72
208
|
const apiDir = path.join(sliceBaseDir, 'api');
|
|
@@ -169,20 +305,20 @@ export default async function initializeProject(projectType) {
|
|
|
169
305
|
componentsSpinner.succeed(`All ${successful} Visual components installed successfully`);
|
|
170
306
|
} else if (successful > 0) {
|
|
171
307
|
componentsSpinner.warn(`${successful} components installed, ${failed} failed`);
|
|
172
|
-
Print.info(
|
|
308
|
+
Print.info(`You can install failed components later using "${packageManager} run get -- <component-name>"`);
|
|
173
309
|
} else {
|
|
174
310
|
componentsSpinner.fail('Failed to install Visual components');
|
|
175
311
|
}
|
|
176
312
|
} else {
|
|
177
313
|
componentsSpinner.warn('No Visual components found in registry');
|
|
178
|
-
Print.info(
|
|
314
|
+
Print.info(`You can add components later using "${packageManager} run get -- <component-name>"`);
|
|
179
315
|
}
|
|
180
316
|
|
|
181
317
|
} catch (error) {
|
|
182
318
|
componentsSpinner.fail('Could not download Visual components from official repository');
|
|
183
319
|
Print.error(`Repository error: ${error.message}`);
|
|
184
320
|
Print.info('Project initialized without Visual components');
|
|
185
|
-
Print.info(
|
|
321
|
+
Print.info(`You can add them later using "${packageManager} run get -- <component-name>"`);
|
|
186
322
|
}
|
|
187
323
|
|
|
188
324
|
// 3b. DOWNLOAD STARTER SERVICE COMPONENTS FROM OFFICIAL REPOSITORY
|
|
@@ -208,7 +344,7 @@ export default async function initializeProject(projectType) {
|
|
|
208
344
|
serviceSpinner.succeed(`All ${successful} Service components installed successfully`);
|
|
209
345
|
} else if (successful > 0) {
|
|
210
346
|
serviceSpinner.warn(`${successful} Service components installed, ${failed} failed`);
|
|
211
|
-
Print.info(
|
|
347
|
+
Print.info(`You can install failed components later using "${packageManager} run get -- <component-name>"`);
|
|
212
348
|
} else {
|
|
213
349
|
serviceSpinner.fail('Failed to install Service components');
|
|
214
350
|
}
|
|
@@ -218,7 +354,7 @@ export default async function initializeProject(projectType) {
|
|
|
218
354
|
} catch (error) {
|
|
219
355
|
serviceSpinner.fail('Could not download Service components from official repository');
|
|
220
356
|
Print.error(`Repository error: ${error.message}`);
|
|
221
|
-
Print.info(
|
|
357
|
+
Print.info(`You can add them later using "${packageManager} run get -- <component-name>"`);
|
|
222
358
|
}
|
|
223
359
|
|
|
224
360
|
// 4. CONFIGURE SCRIPTS IN PROJECT package.json
|
|
@@ -243,53 +379,43 @@ export default async function initializeProject(projectType) {
|
|
|
243
379
|
pkg.scripts = pkg.scripts || {};
|
|
244
380
|
pkg.dependencies = pkg.dependencies || {};
|
|
245
381
|
|
|
246
|
-
//
|
|
247
|
-
pkg.scripts['dev'] = 'slice
|
|
248
|
-
pkg.scripts['
|
|
382
|
+
// Main scripts (local CLI path, no global launcher dependency)
|
|
383
|
+
pkg.scripts['dev'] = SLICE_SCRIPTS['slice:dev'];
|
|
384
|
+
pkg.scripts['build'] = SLICE_SCRIPTS['slice:build'];
|
|
385
|
+
pkg.scripts['start'] = SLICE_SCRIPTS['slice:start'];
|
|
249
386
|
|
|
250
387
|
// Component management
|
|
251
|
-
pkg.scripts['component:create'] = 'slice
|
|
252
|
-
pkg.scripts['component:list'] = 'slice
|
|
253
|
-
pkg.scripts['component:delete'] = 'slice
|
|
254
|
-
|
|
255
|
-
//
|
|
256
|
-
pkg.scripts['get'] = 'slice
|
|
257
|
-
pkg.scripts['browse'] = 'slice
|
|
258
|
-
pkg.scripts['sync'] = 'slice
|
|
259
|
-
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
pkg.scripts
|
|
263
|
-
pkg.scripts['
|
|
264
|
-
|
|
265
|
-
// Legacy (compatibility)
|
|
266
|
-
pkg.scripts['slice:init'] = 'slice init';
|
|
267
|
-
pkg.scripts['slice:start'] = 'slice start';
|
|
268
|
-
pkg.scripts['slice:dev'] = 'slice dev';
|
|
269
|
-
pkg.scripts['slice:create'] = 'slice component create';
|
|
270
|
-
pkg.scripts['slice:list'] = 'slice component list';
|
|
271
|
-
pkg.scripts['slice:delete'] = 'slice component delete';
|
|
272
|
-
pkg.scripts['slice:get'] = 'slice get';
|
|
273
|
-
pkg.scripts['slice:browse'] = 'slice browse';
|
|
274
|
-
pkg.scripts['slice:sync'] = 'slice sync';
|
|
275
|
-
pkg.scripts['run'] = 'slice dev';
|
|
388
|
+
pkg.scripts['component:create'] = SLICE_SCRIPTS['slice:create'];
|
|
389
|
+
pkg.scripts['component:list'] = SLICE_SCRIPTS['slice:list'];
|
|
390
|
+
pkg.scripts['component:delete'] = SLICE_SCRIPTS['slice:delete'];
|
|
391
|
+
|
|
392
|
+
// Registry shortcuts
|
|
393
|
+
pkg.scripts['get'] = SLICE_SCRIPTS['slice:get'];
|
|
394
|
+
pkg.scripts['browse'] = SLICE_SCRIPTS['slice:browse'];
|
|
395
|
+
pkg.scripts['sync'] = SLICE_SCRIPTS['slice:sync'];
|
|
396
|
+
|
|
397
|
+
// slice:* namespaced set — shared with post.js and `slice postinstall`
|
|
398
|
+
// (commands/utils/sliceScripts.js) so the three never drift apart.
|
|
399
|
+
Object.assign(pkg.scripts, SLICE_SCRIPTS);
|
|
400
|
+
pkg.scripts['run'] = SLICE_SCRIPTS['slice:dev'];
|
|
276
401
|
|
|
277
402
|
// Module configuration
|
|
278
403
|
pkg.type = 'module';
|
|
279
404
|
pkg.engines = pkg.engines || { node: '>=20.0.0' };
|
|
280
405
|
|
|
281
|
-
// Ensure framework dependency is present
|
|
406
|
+
// Ensure framework dependency is present (the install above normally
|
|
407
|
+
// already wrote it; this is a fallback for the monorepo-sibling path).
|
|
282
408
|
if (!pkg.dependencies['slicejs-web-framework']) {
|
|
283
|
-
pkg.dependencies['slicejs-web-framework'] =
|
|
409
|
+
pkg.dependencies['slicejs-web-framework'] = installedVersion ? `^${installedVersion}` : 'latest';
|
|
284
410
|
}
|
|
285
411
|
|
|
286
412
|
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2), 'utf8');
|
|
287
|
-
pkgSpinner.succeed('
|
|
413
|
+
pkgSpinner.succeed('Package scripts configured successfully');
|
|
288
414
|
|
|
289
415
|
Print.title('New recommended commands:');
|
|
290
|
-
console.log(
|
|
291
|
-
console.log(
|
|
292
|
-
console.log(
|
|
416
|
+
console.log(` ${packageManager} run dev - Start development server`);
|
|
417
|
+
console.log(` ${packageManager} run get - Install components`);
|
|
418
|
+
console.log(` ${packageManager} run browse - Browse components`);
|
|
293
419
|
} catch (error) {
|
|
294
420
|
pkgSpinner.fail('Failed to configure npm scripts');
|
|
295
421
|
Print.error(error.message);
|
|
@@ -300,9 +426,10 @@ export default async function initializeProject(projectType) {
|
|
|
300
426
|
Print.newLine();
|
|
301
427
|
Print.title('Next steps:');
|
|
302
428
|
console.log(` cd ${projectName}`);
|
|
303
|
-
console.log(
|
|
304
|
-
console.log(
|
|
305
|
-
console.log(
|
|
429
|
+
console.log(` ${packageManager} run dev - Start development server`);
|
|
430
|
+
console.log(` ${packageManager} run browse - View available components`);
|
|
431
|
+
console.log(` ${packageManager} run get -- Button - Install specific components`);
|
|
432
|
+
console.log(` ${packageManager} run sync - Update all components to latest versions`);
|
|
306
433
|
|
|
307
434
|
} catch (error) {
|
|
308
435
|
Print.error('Unexpected error initializing project:', error.message);
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// commands/utils/PackageManager.js
|
|
2
|
+
//
|
|
3
|
+
// Package manager detection and command building (pnpm / npm).
|
|
4
|
+
// Resolution priority for an existing project:
|
|
5
|
+
// 1. "packageManager" field in the project package.json (corepack convention)
|
|
6
|
+
// 2. Lockfile present at the project root
|
|
7
|
+
// 3. npm_config_user_agent (set when the CLI runs via `npx` / `pnpm dlx` / a PM script)
|
|
8
|
+
// 4. The only PM binary available on PATH (if exactly one)
|
|
9
|
+
// When everything is ambiguous, detectPackageManager() returns null so callers
|
|
10
|
+
// can prompt the user (interactive init) or fall back to pnpm (non-interactive).
|
|
11
|
+
|
|
12
|
+
import fs from 'fs-extra'
|
|
13
|
+
import path from 'path'
|
|
14
|
+
import { spawnSync } from 'node:child_process'
|
|
15
|
+
|
|
16
|
+
export const SUPPORTED_PACKAGE_MANAGERS = ['pnpm', 'npm']
|
|
17
|
+
|
|
18
|
+
const LOCKFILES = {
|
|
19
|
+
'pnpm-lock.yaml': 'pnpm',
|
|
20
|
+
'package-lock.json': 'npm'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parseUserAgent(userAgent = process.env.npm_config_user_agent) {
|
|
24
|
+
if (!userAgent) return null
|
|
25
|
+
const match = userAgent.match(/^(npm|pnpm)\/(\S+)/)
|
|
26
|
+
if (!match) return null
|
|
27
|
+
return { name: match[1], version: match[2], source: 'user-agent' }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function fromPackageManagerField(projectRoot) {
|
|
31
|
+
try {
|
|
32
|
+
const pkgPath = path.join(projectRoot, 'package.json')
|
|
33
|
+
if (!fs.pathExistsSync(pkgPath)) return null
|
|
34
|
+
const pkg = fs.readJsonSync(pkgPath)
|
|
35
|
+
if (typeof pkg.packageManager !== 'string') return null
|
|
36
|
+
const match = pkg.packageManager.match(/^(npm|pnpm)@(\S+)/)
|
|
37
|
+
if (!match) return null
|
|
38
|
+
return { name: match[1], version: match[2], source: 'package-manager-field' }
|
|
39
|
+
} catch {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function fromLockfile(projectRoot) {
|
|
45
|
+
for (const [lockfile, name] of Object.entries(LOCKFILES)) {
|
|
46
|
+
if (fs.pathExistsSync(path.join(projectRoot, lockfile))) {
|
|
47
|
+
return { name, version: null, source: `lockfile (${lockfile})` }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function runPmBinary(name, args) {
|
|
54
|
+
// On Windows the PM entry points are .cmd shims, which require shell resolution.
|
|
55
|
+
const isWindows = process.platform === 'win32'
|
|
56
|
+
return spawnSync(name, args, {
|
|
57
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
58
|
+
shell: isWindows,
|
|
59
|
+
encoding: 'utf-8'
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isPackageManagerAvailable(name) {
|
|
64
|
+
try {
|
|
65
|
+
const result = runPmBinary(name, ['--version'])
|
|
66
|
+
return result.status === 0
|
|
67
|
+
} catch {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getPackageManagerVersion(name) {
|
|
73
|
+
try {
|
|
74
|
+
const result = runPmBinary(name, ['--version'])
|
|
75
|
+
if (result.status !== 0) return null
|
|
76
|
+
return (result.stdout || '').toString().trim() || null
|
|
77
|
+
} catch {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getAvailablePackageManagers() {
|
|
83
|
+
return SUPPORTED_PACKAGE_MANAGERS.filter(isPackageManagerAvailable)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Detect the package manager for a project.
|
|
88
|
+
* Returns { name, version, source } or null when genuinely ambiguous.
|
|
89
|
+
*/
|
|
90
|
+
export function detectPackageManager(projectRoot, { userAgent = process.env.npm_config_user_agent } = {}) {
|
|
91
|
+
if (projectRoot) {
|
|
92
|
+
const fromField = fromPackageManagerField(projectRoot)
|
|
93
|
+
if (fromField) return fromField
|
|
94
|
+
|
|
95
|
+
const fromLock = fromLockfile(projectRoot)
|
|
96
|
+
if (fromLock) return fromLock
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const fromUa = parseUserAgent(userAgent)
|
|
100
|
+
if (fromUa) return fromUa
|
|
101
|
+
|
|
102
|
+
const available = getAvailablePackageManagers()
|
|
103
|
+
if (available.length === 1) {
|
|
104
|
+
return { name: available[0], version: null, source: 'only available binary' }
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Non-interactive resolution: detection first, then pnpm if available,
|
|
112
|
+
* then whatever binary exists. Never returns null so update/doctor flows
|
|
113
|
+
* always have a usable PM name (commands fail later with a clear error
|
|
114
|
+
* if no PM is actually installed).
|
|
115
|
+
*/
|
|
116
|
+
export function resolvePackageManager(projectRoot, options = {}) {
|
|
117
|
+
const detected = detectPackageManager(projectRoot, options)
|
|
118
|
+
if (detected) return detected
|
|
119
|
+
|
|
120
|
+
const available = getAvailablePackageManagers()
|
|
121
|
+
if (available.includes('pnpm')) return { name: 'pnpm', version: null, source: 'fallback' }
|
|
122
|
+
if (available.includes('npm')) return { name: 'npm', version: null, source: 'fallback' }
|
|
123
|
+
if (available.length > 0) return { name: available[0], version: null, source: 'fallback' }
|
|
124
|
+
return { name: 'pnpm', version: null, source: 'fallback (none detected)' }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function installCommand(pmName, packages, { dev = false, global: isGlobal = false } = {}) {
|
|
128
|
+
const pkgs = Array.isArray(packages) ? packages.join(' ') : packages
|
|
129
|
+
const flags = [isGlobal ? '-g' : '', dev ? '-D' : ''].filter(Boolean).join(' ')
|
|
130
|
+
const flagSuffix = flags ? ` ${flags}` : ''
|
|
131
|
+
if (pmName === 'pnpm') {
|
|
132
|
+
return `${pmName} add${flagSuffix} ${pkgs}`
|
|
133
|
+
}
|
|
134
|
+
return `npm install${flagSuffix} ${pkgs}`
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function uninstallCommand(pmName, packages, { global: isGlobal = false } = {}) {
|
|
138
|
+
const pkgs = Array.isArray(packages) ? packages.join(' ') : packages
|
|
139
|
+
const flagSuffix = isGlobal ? ' -g' : ''
|
|
140
|
+
if (pmName === 'pnpm') {
|
|
141
|
+
return `${pmName} remove${flagSuffix} ${pkgs}`
|
|
142
|
+
}
|
|
143
|
+
return `npm uninstall${flagSuffix} ${pkgs}`
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function runScriptCommand(pmName, script) {
|
|
147
|
+
return `${pmName} run ${script}`
|
|
148
|
+
}
|
|
@@ -5,6 +5,7 @@ import path from "path";
|
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
import Print from "../Print.js";
|
|
7
7
|
import { getProjectRoot, getPath } from "../utils/PathHelper.js";
|
|
8
|
+
import { resolvePackageManager } from "../utils/PackageManager.js";
|
|
8
9
|
|
|
9
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
|
|
@@ -110,17 +111,18 @@ class VersionChecker {
|
|
|
110
111
|
const frameworkStatus = this.compareVersions(current.framework, latest.framework);
|
|
111
112
|
|
|
112
113
|
if (!silent && (cliStatus === 'outdated' || frameworkStatus === 'outdated')) {
|
|
114
|
+
const pm = resolvePackageManager(getProjectRoot(import.meta.url)).name;
|
|
113
115
|
console.log(''); // Line break
|
|
114
116
|
Print.warning('📦 Available Updates:');
|
|
115
|
-
|
|
117
|
+
|
|
116
118
|
if (cliStatus === 'outdated') {
|
|
117
119
|
console.log(` 🔧 CLI: ${current.cli} → ${latest.cli}`);
|
|
118
|
-
console.log(`
|
|
120
|
+
console.log(` ${pm} update slicejs-cli`);
|
|
119
121
|
}
|
|
120
|
-
|
|
122
|
+
|
|
121
123
|
if (frameworkStatus === 'outdated') {
|
|
122
124
|
console.log(` ⚡ Framework: ${current.framework} → ${latest.framework}`);
|
|
123
|
-
console.log(`
|
|
125
|
+
console.log(` ${pm} update slicejs-web-framework`);
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
console.log(' 📚 Changelog: https://github.com/VKneider/slice.js/releases');
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// commands/utils/sliceScripts.js
|
|
2
|
+
//
|
|
3
|
+
// Single source of truth for the slice:* package scripts configured by the CLI.
|
|
4
|
+
// Used by post.js (the postinstall hook), the `slice postinstall` command in
|
|
5
|
+
// client.js, and `slice init` — so the three can never drift apart (they did:
|
|
6
|
+
// client.js was missing slice:types, and none of them had slice:build).
|
|
7
|
+
export const SLICE_SCRIPTS = {
|
|
8
|
+
'slice:init': 'node ./node_modules/slicejs-cli/client.js init',
|
|
9
|
+
'slice:dev': 'node ./node_modules/slicejs-cli/client.js dev',
|
|
10
|
+
'slice:build': 'node ./node_modules/slicejs-cli/client.js build',
|
|
11
|
+
'slice:start': 'node ./node_modules/slicejs-cli/client.js start',
|
|
12
|
+
'slice:create': 'node ./node_modules/slicejs-cli/client.js component create',
|
|
13
|
+
'slice:list': 'node ./node_modules/slicejs-cli/client.js component list',
|
|
14
|
+
'slice:delete': 'node ./node_modules/slicejs-cli/client.js component delete',
|
|
15
|
+
'slice:get': 'node ./node_modules/slicejs-cli/client.js get',
|
|
16
|
+
'slice:browse': 'node ./node_modules/slicejs-cli/client.js browse',
|
|
17
|
+
'slice:sync': 'node ./node_modules/slicejs-cli/client.js sync',
|
|
18
|
+
'slice:doctor': 'node ./node_modules/slicejs-cli/client.js doctor',
|
|
19
|
+
'slice:version': 'node ./node_modules/slicejs-cli/client.js version',
|
|
20
|
+
'slice:help': 'node ./node_modules/slicejs-cli/client.js --help',
|
|
21
|
+
'slice:update': 'node ./node_modules/slicejs-cli/client.js update',
|
|
22
|
+
'slice:types': 'node ./node_modules/slicejs-cli/client.js types generate',
|
|
23
|
+
};
|