slicejs-cli 3.6.4 → 3.6.5
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 +0 -1
- package/client.js +59 -63
- package/commands/bundle/bundle.js +5 -3
- package/commands/doctor/doctor.js +103 -27
- package/commands/getComponent/getComponent.js +12 -18
- package/commands/init/init.js +7 -1
- package/commands/startServer/startServer.js +22 -43
- package/commands/types/types.js +3 -0
- package/commands/utils/sliceScripts.js +27 -2
- package/commands/utils/updateManager.js +2 -2
- package/package.json +2 -5
- package/post.js +0 -60
package/README.md
CHANGED
|
@@ -134,7 +134,6 @@ When you install `slicejs-cli`, the `postinstall` script automatically configure
|
|
|
134
134
|
"slice:doctor": "node ./node_modules/slicejs-cli/client.js doctor",
|
|
135
135
|
"slice:version": "node ./node_modules/slicejs-cli/client.js version",
|
|
136
136
|
"slice:help": "node ./node_modules/slicejs-cli/client.js --help",
|
|
137
|
-
"slice:update": "node ./node_modules/slicejs-cli/client.js update",
|
|
138
137
|
"slice:types": "node ./node_modules/slicejs-cli/client.js types generate"
|
|
139
138
|
}
|
|
140
139
|
}
|
package/client.js
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
runScriptCommand,
|
|
36
36
|
SUPPORTED_PACKAGE_MANAGERS
|
|
37
37
|
} from './commands/utils/PackageManager.js';
|
|
38
|
-
import { SLICE_SCRIPTS } from './commands/utils/sliceScripts.js';
|
|
38
|
+
import { SLICE_SCRIPTS, verifySliceScriptsInPackageJson } from './commands/utils/sliceScripts.js';
|
|
39
39
|
|
|
40
40
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
41
41
|
|
|
@@ -44,20 +44,50 @@ const getCategories = () => {
|
|
|
44
44
|
return config && config.paths?.components ? Object.keys(config.paths.components) : [];
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
// Helper to validate port number
|
|
48
|
+
function parsePort(value) {
|
|
49
|
+
if (!value) return undefined;
|
|
50
|
+
const port = parseInt(value, 10);
|
|
51
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
52
|
+
Print.error(`Invalid port: "${value}". Must be a number between 1 and 65535.`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
return port;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function withNodeEnv(env, fn) {
|
|
59
|
+
const prevEnv = process.env.NODE_ENV;
|
|
60
|
+
process.env.NODE_ENV = env;
|
|
61
|
+
listComponents();
|
|
62
|
+
try {
|
|
63
|
+
return await fn();
|
|
64
|
+
} finally {
|
|
65
|
+
process.env.NODE_ENV = prevEnv;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
47
69
|
// Function to run version check for all commands
|
|
48
70
|
async function runWithVersionCheck(commandFunction, ...args) {
|
|
49
71
|
try {
|
|
50
|
-
|
|
72
|
+
const globalOpts = program.opts();
|
|
73
|
+
const skipVersionCheck = globalOpts.noVersionCheck === false;
|
|
74
|
+
|
|
75
|
+
if (!skipVersionCheck) {
|
|
76
|
+
updateManager.notifyAvailableUpdates().catch(() => {});
|
|
77
|
+
}
|
|
51
78
|
|
|
52
79
|
const result = await commandFunction(...args);
|
|
53
80
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
81
|
+
if (!skipVersionCheck) {
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
versionChecker.checkForUpdates(false);
|
|
84
|
+
}, 100);
|
|
85
|
+
}
|
|
57
86
|
|
|
58
87
|
return result;
|
|
59
88
|
} catch (error) {
|
|
60
89
|
Print.error(`Command execution: ${error.message}`);
|
|
90
|
+
console.error(error.stack);
|
|
61
91
|
return false;
|
|
62
92
|
}
|
|
63
93
|
}
|
|
@@ -80,7 +110,7 @@ function maybeDelegateToLocalCli() {
|
|
|
80
110
|
{
|
|
81
111
|
stdio: 'inherit',
|
|
82
112
|
cwd: process.cwd(),
|
|
83
|
-
env: process.env
|
|
113
|
+
env: { ...process.env, SLICE_NO_LOCAL_DELEGATION: '1' }
|
|
84
114
|
}
|
|
85
115
|
);
|
|
86
116
|
|
|
@@ -104,13 +134,12 @@ try {
|
|
|
104
134
|
sliceClient
|
|
105
135
|
.command("init")
|
|
106
136
|
.description("Initialize a new Slice.js project")
|
|
107
|
-
.
|
|
137
|
+
.argument("[name]", "Project name (defaults to my-slice-app)")
|
|
138
|
+
.option("-y, --yes", "Skip prompts")
|
|
108
139
|
.option("--pm <packageManager>", "Package manager to use (pnpm or npm). Auto-detected when omitted")
|
|
109
|
-
.action(async (options) => {
|
|
110
|
-
let projectName = 'my-slice-app';
|
|
111
|
-
if (options.yes) {
|
|
112
|
-
projectName = typeof options.yes === 'string' ? options.yes : projectName;
|
|
113
|
-
} else {
|
|
140
|
+
.action(async (name, options) => {
|
|
141
|
+
let projectName = name ? name.trim().toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '').replace(/-+/g, '-').replace(/^-|-$/g, '') : 'my-slice-app';
|
|
142
|
+
if (!options.yes) {
|
|
114
143
|
const answers = await inquirer.prompt([
|
|
115
144
|
{
|
|
116
145
|
type: 'input',
|
|
@@ -222,15 +251,9 @@ sliceClient
|
|
|
222
251
|
const buildCommand = sliceClient.command("build")
|
|
223
252
|
.description("Build Slice.js project for production")
|
|
224
253
|
.action(async (options) => {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
await runWithVersionCheck(async () => {
|
|
229
|
-
await build(options);
|
|
230
|
-
});
|
|
231
|
-
} finally {
|
|
232
|
-
process.env.NODE_ENV = prevEnv;
|
|
233
|
-
}
|
|
254
|
+
await withNodeEnv('production', () => runWithVersionCheck(async () => {
|
|
255
|
+
await build(options);
|
|
256
|
+
}));
|
|
234
257
|
});
|
|
235
258
|
|
|
236
259
|
buildCommand
|
|
@@ -263,19 +286,13 @@ sliceClient
|
|
|
263
286
|
.option("-p, --port <port>", "Port for development server")
|
|
264
287
|
.option("--no-hmr", "Disable hot module reload (enabled by default)")
|
|
265
288
|
.action(async (options) => {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
mode: 'development',
|
|
272
|
-
port: options.port ? parseInt(options.port) : undefined,
|
|
273
|
-
watch: options.hmr
|
|
274
|
-
});
|
|
289
|
+
await withNodeEnv('development', () => runWithVersionCheck(async () => {
|
|
290
|
+
await startServer({
|
|
291
|
+
mode: 'development',
|
|
292
|
+
port: parsePort(options.port),
|
|
293
|
+
watch: options.hmr
|
|
275
294
|
});
|
|
276
|
-
}
|
|
277
|
-
process.env.NODE_ENV = prevEnv;
|
|
278
|
-
}
|
|
295
|
+
}));
|
|
279
296
|
});
|
|
280
297
|
|
|
281
298
|
// START COMMAND - PRODUCTION MODE
|
|
@@ -284,18 +301,12 @@ sliceClient
|
|
|
284
301
|
.description("Serve production files from dist/ (requires prior slice build)")
|
|
285
302
|
.option("-p, --port <port>", "Port for server")
|
|
286
303
|
.action(async (options) => {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
await startServer({
|
|
292
|
-
mode: 'production',
|
|
293
|
-
port: options.port ? parseInt(options.port) : undefined
|
|
294
|
-
});
|
|
304
|
+
await withNodeEnv('production', () => runWithVersionCheck(async () => {
|
|
305
|
+
await startServer({
|
|
306
|
+
mode: 'production',
|
|
307
|
+
port: parsePort(options.port)
|
|
295
308
|
});
|
|
296
|
-
}
|
|
297
|
-
process.env.NODE_ENV = prevEnv;
|
|
298
|
-
}
|
|
309
|
+
}));
|
|
299
310
|
});
|
|
300
311
|
|
|
301
312
|
// COMPONENT COMMAND GROUP - For local component management
|
|
@@ -597,19 +608,6 @@ sliceClient
|
|
|
597
608
|
});
|
|
598
609
|
});
|
|
599
610
|
|
|
600
|
-
// UPDATE COMMAND
|
|
601
|
-
sliceClient
|
|
602
|
-
.command("update")
|
|
603
|
-
.alias("upgrade")
|
|
604
|
-
.description("Update CLI and framework to latest versions")
|
|
605
|
-
.option("-y, --yes", "Skip confirmation and update all packages automatically")
|
|
606
|
-
.option("--cli", "Update only the Slice.js CLI")
|
|
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)")
|
|
609
|
-
.action(async (options) => {
|
|
610
|
-
await updateManager.checkAndPromptUpdates(options);
|
|
611
|
-
});
|
|
612
|
-
|
|
613
611
|
// DOCTOR COMMAND - Diagnose project issues
|
|
614
612
|
sliceClient
|
|
615
613
|
.command("doctor")
|
|
@@ -621,12 +619,11 @@ sliceClient
|
|
|
621
619
|
});
|
|
622
620
|
});
|
|
623
621
|
|
|
624
|
-
// POSTINSTALL COMMAND -
|
|
622
|
+
// POSTINSTALL COMMAND - Configure npm scripts in existing project
|
|
625
623
|
sliceClient
|
|
626
624
|
.command("postinstall")
|
|
627
|
-
.description("Configure npm scripts in package.json (
|
|
625
|
+
.description("Configure npm scripts in package.json (useful when adding slicejs-cli to an existing project)")
|
|
628
626
|
.action(() => {
|
|
629
|
-
// npm sets npm_config_global; pnpm global installs are detected via PNPM_HOME.
|
|
630
627
|
const pnpmHome = process.env.PNPM_HOME;
|
|
631
628
|
const cliEntryPath = fileURLToPath(import.meta.url);
|
|
632
629
|
const isGlobal = process.env.npm_config_global === 'true'
|
|
@@ -642,7 +639,6 @@ sliceClient
|
|
|
642
639
|
const pkgPath = path.join(projectRoot, 'package.json');
|
|
643
640
|
const packageManager = resolvePackageManager(projectRoot).name;
|
|
644
641
|
|
|
645
|
-
// Shared with post.js and slice init — see commands/utils/sliceScripts.js
|
|
646
642
|
const sliceScripts = SLICE_SCRIPTS;
|
|
647
643
|
|
|
648
644
|
try {
|
|
@@ -701,7 +697,7 @@ Common Usage Examples:
|
|
|
701
697
|
slice list - List all local components
|
|
702
698
|
slice doctor - Run project diagnostics
|
|
703
699
|
slice types generate - Generate TypeScript typings for slice.build
|
|
704
|
-
slice postinstall -
|
|
700
|
+
slice postinstall - Configure npm scripts in package.json
|
|
705
701
|
|
|
706
702
|
Command Categories:
|
|
707
703
|
• init, dev, start - Project lifecycle (development only)
|
|
@@ -709,7 +705,7 @@ Command Categories:
|
|
|
709
705
|
• component <cmd> - Local component management
|
|
710
706
|
• registry <cmd> - Official repository operations
|
|
711
707
|
• types generate - Type declarations from static props
|
|
712
|
-
• version,
|
|
708
|
+
• version, doctor, setup - Maintenance commands
|
|
713
709
|
|
|
714
710
|
Development Workflow:
|
|
715
711
|
• slice init - Initialize project
|
|
@@ -74,7 +74,7 @@ export default async function bundle(options = {}) {
|
|
|
74
74
|
console.error('\n📍 Error code:', error.code);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
throw error;
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -177,7 +177,8 @@ export async function cleanBundles() {
|
|
|
177
177
|
|
|
178
178
|
} catch (error) {
|
|
179
179
|
Print.error('Error cleaning bundles:', error.message);
|
|
180
|
-
|
|
180
|
+
console.error(error.stack);
|
|
181
|
+
throw error;
|
|
181
182
|
}
|
|
182
183
|
}
|
|
183
184
|
|
|
@@ -229,6 +230,7 @@ export async function bundleInfo() {
|
|
|
229
230
|
|
|
230
231
|
} catch (error) {
|
|
231
232
|
Print.error('Error reading information:', error.message);
|
|
232
|
-
|
|
233
|
+
console.error(error.stack);
|
|
234
|
+
throw error;
|
|
233
235
|
}
|
|
234
236
|
}
|
|
@@ -4,12 +4,10 @@ import { createServer } from 'net';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import Table from 'cli-table3';
|
|
6
6
|
import Print from '../Print.js';
|
|
7
|
-
import
|
|
8
|
-
import { exec } from 'child_process';
|
|
9
|
-
import { promisify } from 'util';
|
|
10
|
-
import { getProjectRoot, getSrcPath, getApiPath, getConfigPath, getPath } from '../utils/PathHelper.js';
|
|
7
|
+
import { getProjectRoot, getSrcPath, getApiPath, getConfigPath, getPath, getComponentsJsPath } from '../utils/PathHelper.js';
|
|
11
8
|
import { resolvePackageManager, installCommand } from '../utils/PackageManager.js';
|
|
12
9
|
import updateManager from '../utils/updateManager.js';
|
|
10
|
+
import { parseComponentsRegistry, extractStaticPropsFromSource } from '../types/types.js';
|
|
13
11
|
|
|
14
12
|
/**
|
|
15
13
|
* Checks the Node.js version
|
|
@@ -319,6 +317,100 @@ async function checkComponents() {
|
|
|
319
317
|
}
|
|
320
318
|
}
|
|
321
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Lints component static props for common issues.
|
|
322
|
+
* Uses the same Babel parsing as slice types generate.
|
|
323
|
+
*/
|
|
324
|
+
async function checkComponentProps() {
|
|
325
|
+
const issues = [];
|
|
326
|
+
const registryPath = getComponentsJsPath(import.meta.url);
|
|
327
|
+
const configPath = getConfigPath(import.meta.url);
|
|
328
|
+
|
|
329
|
+
if (!await fs.pathExists(registryPath)) {
|
|
330
|
+
return { pass: true, message: 'No components to check' };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
const content = await fs.readFile(registryPath, 'utf-8');
|
|
335
|
+
const registryMap = parseComponentsRegistry(content, registryPath);
|
|
336
|
+
const entries = Object.entries(registryMap);
|
|
337
|
+
|
|
338
|
+
if (entries.length === 0) {
|
|
339
|
+
return { pass: true, message: 'No components registered' };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Build a category-to-path map from sliceConfig.json
|
|
343
|
+
const categoryPathCache = new Map();
|
|
344
|
+
if (await fs.pathExists(configPath)) {
|
|
345
|
+
try {
|
|
346
|
+
const config = await fs.readJson(configPath);
|
|
347
|
+
if (config.paths?.components) {
|
|
348
|
+
for (const [cat, catMeta] of Object.entries(config.paths.components)) {
|
|
349
|
+
categoryPathCache.set(cat, catMeta.path?.replace(/^[/\\]+/, '') || '');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
} catch {}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const VALID_TYPES = new Set(['string', 'number', 'boolean', 'object', 'array', 'function', 'any']);
|
|
356
|
+
const checked = [];
|
|
357
|
+
|
|
358
|
+
for (const [compName, meta] of entries) {
|
|
359
|
+
const category = typeof meta === 'string' ? meta : (meta.category || '');
|
|
360
|
+
const relPath = categoryPathCache.get(category);
|
|
361
|
+
if (!relPath) continue;
|
|
362
|
+
const jsPath = getSrcPath(import.meta.url, relPath, compName, `${compName}.js`);
|
|
363
|
+
|
|
364
|
+
if (!await fs.pathExists(jsPath)) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const source = await fs.readFile(jsPath, 'utf-8');
|
|
369
|
+
const props = extractStaticPropsFromSource(source, jsPath);
|
|
370
|
+
if (!props) continue;
|
|
371
|
+
|
|
372
|
+
checked.push(compName);
|
|
373
|
+
|
|
374
|
+
for (const [propName, propMeta] of Object.entries(props)) {
|
|
375
|
+
if (propMeta.type === 'any') {
|
|
376
|
+
issues.push({ component: compName, prop: propName, message: `"${propName}" uses type "any" — specify a concrete type` });
|
|
377
|
+
}
|
|
378
|
+
if (propMeta.required && propMeta.type !== 'boolean') {
|
|
379
|
+
issues.push({ component: compName, prop: propName, message: `"${propName}" is required but has no default value` });
|
|
380
|
+
}
|
|
381
|
+
if (propMeta.schema && propMeta.type !== 'object') {
|
|
382
|
+
issues.push({ component: compName, prop: propName, message: `"${propName}" has "schema" but type is "${propMeta.type}" (should be "object")` });
|
|
383
|
+
}
|
|
384
|
+
if (propMeta.items && propMeta.type !== 'array') {
|
|
385
|
+
issues.push({ component: compName, prop: propName, message: `"${propName}" has "items" but type is "${propMeta.type}" (should be "array")` });
|
|
386
|
+
}
|
|
387
|
+
if (!VALID_TYPES.has(propMeta.type)) {
|
|
388
|
+
issues.push({ component: compName, prop: propName, message: `"${propName}" has unknown type "${propMeta.type}"` });
|
|
389
|
+
}
|
|
390
|
+
if (Array.isArray(propMeta.allowedValues) && propMeta.allowedValues.length > 0) {
|
|
391
|
+
const typeMismatch = propMeta.allowedValues.some(v => typeof v !== propMeta.type);
|
|
392
|
+
if (typeMismatch && propMeta.type !== 'any') {
|
|
393
|
+
issues.push({ component: compName, prop: propName, message: `"${propName}" allowedValues type mismatch (expected ${propMeta.type})` });
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (issues.length === 0) {
|
|
400
|
+
return { pass: true, message: `${checked.length} components checked, props look good` };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
warn: true,
|
|
405
|
+
message: `${issues.length} prop issue(s) across ${new Set(issues.map(i => i.component)).size} component(s)`,
|
|
406
|
+
suggestion: issues.map(i => ` ${i.component}.${i.prop}: ${i.message}`).join('\n'),
|
|
407
|
+
issues
|
|
408
|
+
};
|
|
409
|
+
} catch (error) {
|
|
410
|
+
return { warn: true, message: `Cannot check props: ${error.message}` };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
322
414
|
/**
|
|
323
415
|
* Main diagnostic command
|
|
324
416
|
*/
|
|
@@ -328,7 +420,8 @@ export {
|
|
|
328
420
|
checkConfig,
|
|
329
421
|
checkPort,
|
|
330
422
|
checkDependencies,
|
|
331
|
-
checkComponents
|
|
423
|
+
checkComponents,
|
|
424
|
+
checkComponentProps
|
|
332
425
|
};
|
|
333
426
|
|
|
334
427
|
export default async function runDiagnostics() {
|
|
@@ -343,7 +436,8 @@ export default async function runDiagnostics() {
|
|
|
343
436
|
{ name: 'Port Availability', fn: checkPort },
|
|
344
437
|
{ name: 'Package Manager', fn: checkPackageManagerSetup },
|
|
345
438
|
{ name: 'Dependencies', fn: checkDependencies },
|
|
346
|
-
{ name: 'Components', fn: checkComponents }
|
|
439
|
+
{ name: 'Components', fn: checkComponents },
|
|
440
|
+
{ name: 'Component Props', fn: checkComponentProps }
|
|
347
441
|
];
|
|
348
442
|
|
|
349
443
|
const results = [];
|
|
@@ -394,27 +488,9 @@ export default async function runDiagnostics() {
|
|
|
394
488
|
const depsResult = results.find(r => r.name === 'Dependencies');
|
|
395
489
|
if (depsResult && depsResult.warn && Array.isArray(depsResult.missing) && depsResult.missing.length > 0) {
|
|
396
490
|
const projectRoot = getProjectRoot(import.meta.url);
|
|
397
|
-
const
|
|
398
|
-
const
|
|
399
|
-
{
|
|
400
|
-
type: 'confirm',
|
|
401
|
-
name: 'confirmInstall',
|
|
402
|
-
message: `Install missing dependencies in this project now? (${depsResult.missing.join(', ')})`,
|
|
403
|
-
default: true
|
|
404
|
-
}
|
|
405
|
-
]);
|
|
406
|
-
if (confirmInstall) {
|
|
407
|
-
const pm = resolvePackageManager(projectRoot).name;
|
|
408
|
-
for (const pkg of depsResult.missing) {
|
|
409
|
-
try {
|
|
410
|
-
const cmd = installCommand(pm, `${pkg}@latest`);
|
|
411
|
-
Print.info(`Installing ${pkg}...`);
|
|
412
|
-
await execAsync(cmd, { cwd: projectRoot });
|
|
413
|
-
Print.success(`${pkg} installed`);
|
|
414
|
-
} catch (e) {
|
|
415
|
-
Print.error(`Installing ${pkg}: ${e.message}`);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
491
|
+
const pm = resolvePackageManager(projectRoot).name;
|
|
492
|
+
for (const pkg of depsResult.missing) {
|
|
493
|
+
Print.info(`Missing: ${pkg}. Install with: ${installCommand(pm, `${pkg}@latest`)}`);
|
|
418
494
|
}
|
|
419
495
|
}
|
|
420
496
|
|
|
@@ -262,10 +262,8 @@ filterOfficialComponents(allComponents) {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
async updateLocalRegistrySafe(componentName, category) {
|
|
265
|
-
// Queue behind any in-flight registry update; keep the chain alive even
|
|
266
|
-
// when an update throws so later updates still run.
|
|
267
265
|
const run = this._registryLock.then(() => this._updateLocalRegistry(componentName, category));
|
|
268
|
-
this._registryLock = run.catch(() => {});
|
|
266
|
+
this._registryLock = run.catch(() => { /* non-critical: registry update failure is handled by caller */ });
|
|
269
267
|
return run;
|
|
270
268
|
}
|
|
271
269
|
|
|
@@ -611,12 +609,12 @@ filterOfficialComponents(allComponents) {
|
|
|
611
609
|
}
|
|
612
610
|
}
|
|
613
611
|
|
|
612
|
+
const sharedRegistry = new ComponentRegistry();
|
|
613
|
+
|
|
614
614
|
// Main get function
|
|
615
615
|
async function getComponents(componentNames = [], options = {}) {
|
|
616
|
-
const registry = new ComponentRegistry();
|
|
617
|
-
|
|
618
616
|
try {
|
|
619
|
-
await
|
|
617
|
+
await sharedRegistry.loadRegistry();
|
|
620
618
|
} catch (error) {
|
|
621
619
|
Print.error('Could not load component registry from official repository');
|
|
622
620
|
Print.info('Check your internet connection and try again');
|
|
@@ -625,7 +623,7 @@ async function getComponents(componentNames = [], options = {}) {
|
|
|
625
623
|
|
|
626
624
|
// Interactive mode if no components specified
|
|
627
625
|
if (!componentNames || componentNames.length === 0) {
|
|
628
|
-
await
|
|
626
|
+
await sharedRegistry.interactiveInstall();
|
|
629
627
|
return true;
|
|
630
628
|
}
|
|
631
629
|
|
|
@@ -634,7 +632,7 @@ async function getComponents(componentNames = [], options = {}) {
|
|
|
634
632
|
|
|
635
633
|
if (componentNames.length === 1) {
|
|
636
634
|
// Single component install
|
|
637
|
-
const componentInfo =
|
|
635
|
+
const componentInfo = sharedRegistry.findComponentInRegistry(componentNames[0]);
|
|
638
636
|
|
|
639
637
|
if (!componentInfo) {
|
|
640
638
|
Print.error(`Component '${componentNames[0]}' not found in official repository`);
|
|
@@ -646,7 +644,7 @@ async function getComponents(componentNames = [], options = {}) {
|
|
|
646
644
|
const actualCategory = options.service ? 'Service' : componentInfo.category;
|
|
647
645
|
|
|
648
646
|
try {
|
|
649
|
-
await
|
|
647
|
+
await sharedRegistry.installComponent(componentInfo.name, actualCategory, options.force);
|
|
650
648
|
return true;
|
|
651
649
|
} catch (error) {
|
|
652
650
|
Print.error(`Error installing component: ${error.message}`);
|
|
@@ -659,7 +657,7 @@ async function getComponents(componentNames = [], options = {}) {
|
|
|
659
657
|
);
|
|
660
658
|
|
|
661
659
|
try {
|
|
662
|
-
await
|
|
660
|
+
await sharedRegistry.installMultipleComponents(normalizedComponents, category, options.force);
|
|
663
661
|
return true;
|
|
664
662
|
} catch (error) {
|
|
665
663
|
Print.error(`Error installing components: ${error.message}`);
|
|
@@ -670,11 +668,9 @@ async function getComponents(componentNames = [], options = {}) {
|
|
|
670
668
|
|
|
671
669
|
// List components function
|
|
672
670
|
async function listComponents() {
|
|
673
|
-
const registry = new ComponentRegistry();
|
|
674
|
-
|
|
675
671
|
try {
|
|
676
|
-
await
|
|
677
|
-
|
|
672
|
+
await sharedRegistry.loadRegistry();
|
|
673
|
+
sharedRegistry.displayAvailableComponents();
|
|
678
674
|
return true;
|
|
679
675
|
} catch (error) {
|
|
680
676
|
Print.error('Could not load component registry from official repository');
|
|
@@ -685,11 +681,9 @@ async function listComponents() {
|
|
|
685
681
|
|
|
686
682
|
// Sync components function
|
|
687
683
|
async function syncComponents(options = {}) {
|
|
688
|
-
const registry = new ComponentRegistry();
|
|
689
|
-
|
|
690
684
|
try {
|
|
691
|
-
await
|
|
692
|
-
return await
|
|
685
|
+
await sharedRegistry.loadRegistry();
|
|
686
|
+
return await sharedRegistry.updateAllComponents(options.force);
|
|
693
687
|
} catch (error) {
|
|
694
688
|
Print.error('Could not load component registry from official repository');
|
|
695
689
|
Print.info('Check your internet connection and try again');
|
package/commands/init/init.js
CHANGED
|
@@ -193,6 +193,7 @@ export default async function initializeProject(options = {}) {
|
|
|
193
193
|
} else {
|
|
194
194
|
fwSpinner.fail('Failed to ensure latest slicejs-web-framework');
|
|
195
195
|
Print.error(err.message);
|
|
196
|
+
console.error(err.stack);
|
|
196
197
|
return;
|
|
197
198
|
}
|
|
198
199
|
}
|
|
@@ -233,6 +234,7 @@ export default async function initializeProject(options = {}) {
|
|
|
233
234
|
if (fs.existsSync(destinationSrc)) throw new Error(`The "src" directory already exists: ${destinationSrc}`);
|
|
234
235
|
} catch (error) {
|
|
235
236
|
Print.error('Validating destination directories:', error.message);
|
|
237
|
+
console.error(error.stack);
|
|
236
238
|
return;
|
|
237
239
|
}
|
|
238
240
|
|
|
@@ -245,6 +247,7 @@ export default async function initializeProject(options = {}) {
|
|
|
245
247
|
} catch (error) {
|
|
246
248
|
apiSpinner.fail('Error copying API structure');
|
|
247
249
|
Print.error(error.message);
|
|
250
|
+
console.error(error.stack);
|
|
248
251
|
return;
|
|
249
252
|
}
|
|
250
253
|
|
|
@@ -296,6 +299,7 @@ export default async function initializeProject(options = {}) {
|
|
|
296
299
|
} catch (error) {
|
|
297
300
|
srcSpinner.fail('Error creating source structure');
|
|
298
301
|
Print.error(error.message);
|
|
302
|
+
console.error(error.stack);
|
|
299
303
|
return;
|
|
300
304
|
}
|
|
301
305
|
|
|
@@ -412,7 +416,7 @@ export default async function initializeProject(options = {}) {
|
|
|
412
416
|
pkg.scripts['browse'] = SLICE_SCRIPTS['slice:browse'];
|
|
413
417
|
pkg.scripts['sync'] = SLICE_SCRIPTS['slice:sync'];
|
|
414
418
|
|
|
415
|
-
// slice:* namespaced set — shared with
|
|
419
|
+
// slice:* namespaced set — shared with `slice postinstall`
|
|
416
420
|
// (commands/utils/sliceScripts.js) so the three never drift apart.
|
|
417
421
|
Object.assign(pkg.scripts, SLICE_SCRIPTS);
|
|
418
422
|
pkg.scripts['run'] = SLICE_SCRIPTS['slice:dev'];
|
|
@@ -437,6 +441,7 @@ export default async function initializeProject(options = {}) {
|
|
|
437
441
|
} catch (error) {
|
|
438
442
|
pkgSpinner.fail('Failed to configure npm scripts');
|
|
439
443
|
Print.error(error.message);
|
|
444
|
+
console.error(error.stack);
|
|
440
445
|
}
|
|
441
446
|
|
|
442
447
|
const projectName = path.basename(process.cwd());
|
|
@@ -457,6 +462,7 @@ export default async function initializeProject(options = {}) {
|
|
|
457
462
|
|
|
458
463
|
} catch (error) {
|
|
459
464
|
Print.error('Unexpected error initializing project:', error.message);
|
|
465
|
+
console.error(error.stack);
|
|
460
466
|
}
|
|
461
467
|
}
|
|
462
468
|
|
|
@@ -6,23 +6,10 @@ import { spawn } from 'child_process';
|
|
|
6
6
|
import { createServer } from 'net';
|
|
7
7
|
import setupWatcher, { stopWatcher } from './watchServer.js';
|
|
8
8
|
import Print from '../Print.js';
|
|
9
|
-
import {
|
|
9
|
+
import { getApiPath, getSrcPath, getDistPath, getPath } from '../utils/PathHelper.js';
|
|
10
|
+
import { loadConfigSync } from '../utils/loadConfig.js';
|
|
10
11
|
import build from '../build/build.js';
|
|
11
12
|
|
|
12
|
-
/**
|
|
13
|
-
* Loads configuration from sliceConfig.json
|
|
14
|
-
*/
|
|
15
|
-
const loadConfig = () => {
|
|
16
|
-
try {
|
|
17
|
-
const configPath = getConfigPath(import.meta.url);
|
|
18
|
-
const rawData = fs.readFileSync(configPath, 'utf-8');
|
|
19
|
-
return JSON.parse(rawData);
|
|
20
|
-
} catch (error) {
|
|
21
|
-
Print.error(`Loading configuration: ${error.message}`);
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
|
|
26
13
|
/**
|
|
27
14
|
* Checks if a port is available
|
|
28
15
|
*/
|
|
@@ -144,20 +131,6 @@ function startNodeServer(port, mode) {
|
|
|
144
131
|
}
|
|
145
132
|
});
|
|
146
133
|
|
|
147
|
-
// Manejar Ctrl+C
|
|
148
|
-
process.on('SIGINT', () => {
|
|
149
|
-
Print.newLine();
|
|
150
|
-
Print.info('Shutting down server...');
|
|
151
|
-
serverProcess.kill('SIGINT');
|
|
152
|
-
setTimeout(() => {
|
|
153
|
-
process.exit(0);
|
|
154
|
-
}, 100);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
process.on('SIGTERM', () => {
|
|
158
|
-
serverProcess.kill('SIGTERM');
|
|
159
|
-
});
|
|
160
|
-
|
|
161
134
|
// If after 3 seconds we haven't detected startup, assume it's ready
|
|
162
135
|
setTimeout(() => {
|
|
163
136
|
if (!serverStarted) {
|
|
@@ -169,11 +142,25 @@ function startNodeServer(port, mode) {
|
|
|
169
142
|
});
|
|
170
143
|
}
|
|
171
144
|
|
|
145
|
+
let currentServerProcess = null;
|
|
146
|
+
let currentWatcher = null;
|
|
147
|
+
|
|
148
|
+
process.on('SIGINT', () => shutdown());
|
|
149
|
+
process.on('SIGTERM', () => shutdown());
|
|
150
|
+
|
|
151
|
+
function shutdown() {
|
|
152
|
+
Print.newLine();
|
|
153
|
+
Print.info('Shutting down server...');
|
|
154
|
+
if (currentWatcher) stopWatcher(currentWatcher);
|
|
155
|
+
if (currentServerProcess) currentServerProcess.kill('SIGINT');
|
|
156
|
+
setTimeout(() => process.exit(0), 100);
|
|
157
|
+
}
|
|
158
|
+
|
|
172
159
|
/**
|
|
173
160
|
* Main function to start the server
|
|
174
161
|
*/
|
|
175
162
|
export default async function startServer(options = {}) {
|
|
176
|
-
const config =
|
|
163
|
+
const config = loadConfigSync(import.meta.url);
|
|
177
164
|
const defaultPort = config?.server?.port || 3000;
|
|
178
165
|
|
|
179
166
|
const { mode = 'development', port = defaultPort, watch = false } = options;
|
|
@@ -217,14 +204,14 @@ export default async function startServer(options = {}) {
|
|
|
217
204
|
Print.newLine();
|
|
218
205
|
|
|
219
206
|
// Start the server with arguments
|
|
220
|
-
|
|
207
|
+
currentServerProcess = await startNodeServer(actualPort, mode);
|
|
221
208
|
|
|
222
209
|
// Configure watch mode if enabled
|
|
223
210
|
if (watch) {
|
|
224
211
|
Print.newLine();
|
|
225
|
-
|
|
226
|
-
if (
|
|
227
|
-
|
|
212
|
+
currentWatcher = setupWatcher(currentServerProcess, async (changedPath) => {
|
|
213
|
+
if (currentServerProcess) {
|
|
214
|
+
currentServerProcess.kill();
|
|
228
215
|
}
|
|
229
216
|
|
|
230
217
|
// Short delay to ensure port is freed
|
|
@@ -233,19 +220,11 @@ export default async function startServer(options = {}) {
|
|
|
233
220
|
try {
|
|
234
221
|
Print.info('🔄 File changed. Restarting server...');
|
|
235
222
|
|
|
236
|
-
|
|
223
|
+
currentServerProcess = await startNodeServer(actualPort, mode);
|
|
237
224
|
} catch (e) {
|
|
238
225
|
Print.error(`Failed to restart server: ${e.message}`);
|
|
239
226
|
}
|
|
240
227
|
});
|
|
241
|
-
|
|
242
|
-
// Cleanup en exit
|
|
243
|
-
const cleanup = () => {
|
|
244
|
-
stopWatcher(watcher);
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
process.on('SIGINT', cleanup);
|
|
248
|
-
process.on('SIGTERM', cleanup);
|
|
249
228
|
}
|
|
250
229
|
|
|
251
230
|
} catch (error) {
|
package/commands/types/types.js
CHANGED
|
@@ -345,6 +345,9 @@ const generateDeclarationContent = (componentPropsMap) => {
|
|
|
345
345
|
lines.push(' name: K,');
|
|
346
346
|
lines.push(' props?: SliceComponentPropsMap[K]');
|
|
347
347
|
lines.push(' ): Promise<SliceDynamicElement | null>;');
|
|
348
|
+
lines.push(' getComponent<K extends SliceComponentName>(');
|
|
349
|
+
lines.push(' componentSliceId: K | `${K}-${string}`');
|
|
350
|
+
lines.push(' ): SliceDynamicElement | undefined;');
|
|
348
351
|
lines.push(' getComponent<T extends SliceDynamicElement = SliceDynamicElement>(');
|
|
349
352
|
lines.push(' componentSliceId: string');
|
|
350
353
|
lines.push(' ): T | undefined;');
|
|
@@ -1,9 +1,35 @@
|
|
|
1
1
|
// commands/utils/sliceScripts.js
|
|
2
2
|
//
|
|
3
3
|
// Single source of truth for the slice:* package scripts configured by the CLI.
|
|
4
|
-
// Used by
|
|
4
|
+
// Used by the `slice postinstall` command in
|
|
5
5
|
// client.js, and `slice init` — so the three can never drift apart (they did:
|
|
6
6
|
// client.js was missing slice:types, and none of them had slice:build).
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Reads a project's package.json and verifies it contains all SLICE_SCRIPTS.
|
|
12
|
+
* Returns { missing: string[] } where each entry is the script name not found.
|
|
13
|
+
*/
|
|
14
|
+
export function verifySliceScriptsInPackageJson(pkgPath) {
|
|
15
|
+
const missing = [];
|
|
16
|
+
try {
|
|
17
|
+
if (!fs.existsSync(pkgPath)) {
|
|
18
|
+
return { missing: Object.keys(SLICE_SCRIPTS) };
|
|
19
|
+
}
|
|
20
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
21
|
+
const scripts = pkg.scripts || {};
|
|
22
|
+
for (const script of Object.keys(SLICE_SCRIPTS)) {
|
|
23
|
+
if (!scripts[script]) {
|
|
24
|
+
missing.push(script);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
return { missing: Object.keys(SLICE_SCRIPTS) };
|
|
29
|
+
}
|
|
30
|
+
return { missing };
|
|
31
|
+
}
|
|
32
|
+
|
|
7
33
|
export const SLICE_SCRIPTS = {
|
|
8
34
|
'slice:init': 'node ./node_modules/slicejs-cli/client.js init',
|
|
9
35
|
'slice:dev': 'node ./node_modules/slicejs-cli/client.js dev',
|
|
@@ -18,6 +44,5 @@ export const SLICE_SCRIPTS = {
|
|
|
18
44
|
'slice:doctor': 'node ./node_modules/slicejs-cli/client.js doctor',
|
|
19
45
|
'slice:version': 'node ./node_modules/slicejs-cli/client.js version',
|
|
20
46
|
'slice:help': 'node ./node_modules/slicejs-cli/client.js --help',
|
|
21
|
-
'slice:update': 'node ./node_modules/slicejs-cli/client.js update',
|
|
22
47
|
'slice:types': 'node ./node_modules/slicejs-cli/client.js types generate',
|
|
23
48
|
};
|
|
@@ -117,7 +117,7 @@ export class UpdateManager {
|
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
this.displayUpdates(updateInfo);
|
|
120
|
-
Print.info("
|
|
120
|
+
Print.info("Use your package manager to install updates: npm/pnpm update slicejs-cli slicejs-web-framework");
|
|
121
121
|
return true;
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -434,7 +434,7 @@ export class UpdateManager {
|
|
|
434
434
|
let confirmUpdate = options.updateApi === true;
|
|
435
435
|
if (!confirmUpdate && options.yes === true) {
|
|
436
436
|
Print.info('Skipping api/index.js update (not updated by default).');
|
|
437
|
-
Print.info('
|
|
437
|
+
Print.info('Use --update-api to overwrite api/index.js from the framework template.');
|
|
438
438
|
return;
|
|
439
439
|
}
|
|
440
440
|
if (!confirmUpdate) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slicejs-cli",
|
|
3
|
-
"version": "3.6.
|
|
3
|
+
"version": "3.6.5",
|
|
4
4
|
"description": "Command client for developing web applications with Slice.js framework",
|
|
5
5
|
"main": "client.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"client.js",
|
|
14
|
-
"post.js",
|
|
15
14
|
"commands",
|
|
16
15
|
"assets"
|
|
17
16
|
],
|
|
@@ -21,7 +20,6 @@
|
|
|
21
20
|
},
|
|
22
21
|
"scripts": {
|
|
23
22
|
"test": "node --test",
|
|
24
|
-
"postinstall": "node post.js",
|
|
25
23
|
"slice:dev": "slice dev",
|
|
26
24
|
"slice:build": "slice build",
|
|
27
25
|
"slice:start": "slice start",
|
|
@@ -33,7 +31,6 @@
|
|
|
33
31
|
"slice:browse": "slice browse",
|
|
34
32
|
"slice:sync": "slice sync",
|
|
35
33
|
"slice:version": "slice version",
|
|
36
|
-
"slice:update": "slice update",
|
|
37
34
|
"slice:types": "slice types generate",
|
|
38
35
|
"slice:doctor": "node ./node_modules/slicejs-cli/client.js doctor",
|
|
39
36
|
"slice:help": "node ./node_modules/slicejs-cli/client.js --help"
|
|
@@ -71,4 +68,4 @@
|
|
|
71
68
|
"devDependencies": {
|
|
72
69
|
"@playwright/test": "^1.60.0"
|
|
73
70
|
}
|
|
74
|
-
}
|
|
71
|
+
}
|
package/post.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
import { getProjectRoot, getPath } from './commands/utils/PathHelper.js';
|
|
5
|
-
import { SLICE_SCRIPTS } from './commands/utils/sliceScripts.js';
|
|
6
|
-
import { resolvePackageManager, runScriptCommand } from './commands/utils/PackageManager.js';
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
|
|
10
|
-
// npm sets npm_config_global; pnpm does not — for pnpm a global install lives
|
|
11
|
-
// under PNPM_HOME, so detect it by where this script is running from.
|
|
12
|
-
const pnpmHome = process.env.PNPM_HOME;
|
|
13
|
-
const isGlobal = process.env.npm_config_global === 'true'
|
|
14
|
-
|| (pnpmHome && __filename.startsWith(pnpmHome));
|
|
15
|
-
|
|
16
|
-
if (isGlobal) {
|
|
17
|
-
console.log('⚠️ Global installation of slicejs-cli detected.');
|
|
18
|
-
console.log(' We strongly recommend using a local installation to avoid version mismatches.');
|
|
19
|
-
console.log(` Uninstall global: ${pnpmHome ? 'pnpm remove -g slicejs-cli' : 'npm uninstall -g slicejs-cli'}`);
|
|
20
|
-
process.exit(0);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const projectRoot = getProjectRoot(import.meta.url);
|
|
24
|
-
const pkgPath = getPath(import.meta.url, 'package.json');
|
|
25
|
-
const packageManager = resolvePackageManager(projectRoot).name;
|
|
26
|
-
|
|
27
|
-
const sliceScripts = SLICE_SCRIPTS;
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
let pkg = {};
|
|
31
|
-
if (fs.existsSync(pkgPath)) {
|
|
32
|
-
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
33
|
-
} else {
|
|
34
|
-
pkg = {
|
|
35
|
-
name: path.basename(projectRoot),
|
|
36
|
-
version: '1.0.0',
|
|
37
|
-
description: 'Slice.js project',
|
|
38
|
-
scripts: {}
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
pkg.scripts = pkg.scripts || {};
|
|
43
|
-
let addedCount = 0;
|
|
44
|
-
for (const [script, command] of Object.entries(sliceScripts)) {
|
|
45
|
-
if (!pkg.scripts[script]) {
|
|
46
|
-
pkg.scripts[script] = command;
|
|
47
|
-
addedCount++;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), 'utf-8');
|
|
52
|
-
console.log(`✅ slicejs-cli installed successfully. Added ${addedCount} package scripts to package.json.`);
|
|
53
|
-
console.log(` Run: ${runScriptCommand(packageManager, 'slice:dev')}`);
|
|
54
|
-
} catch (err) {
|
|
55
|
-
console.log('✅ slicejs-cli installed successfully.');
|
|
56
|
-
console.log(' Could not auto-configure scripts:', err.message);
|
|
57
|
-
console.log(` Configure scripts manually and run: ${runScriptCommand(packageManager, 'slice:dev')}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
process.exit(0);
|