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 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
- updateManager.notifyAvailableUpdates().catch(() => {});
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
- setTimeout(() => {
55
- versionChecker.checkForUpdates(false);
56
- }, 100);
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
- .option("-y, --yes [name]", "Skip prompts and initialize with project name")
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
- const prevEnv = process.env.NODE_ENV;
226
- process.env.NODE_ENV = 'production';
227
- try {
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
- const prevEnv = process.env.NODE_ENV;
267
- process.env.NODE_ENV = 'development';
268
- try {
269
- await runWithVersionCheck(async () => {
270
- await startServer({
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
- } finally {
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
- const prevEnv = process.env.NODE_ENV;
288
- process.env.NODE_ENV = 'production';
289
- try {
290
- await runWithVersionCheck(async () => {
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
- } finally {
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 - Manual alternative to postinstall
622
+ // POSTINSTALL COMMAND - Configure npm scripts in existing project
625
623
  sliceClient
626
624
  .command("postinstall")
627
- .description("Configure npm scripts in package.json (alternative to postinstall for --ignore-scripts users)")
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 - Show post-install setup guide
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, update, doctor, setup - Maintenance commands
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
- process.exit(1);
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
- process.exit(1);
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
- process.exit(1);
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 inquirer from 'inquirer';
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 execAsync = promisify(exec);
398
- const { confirmInstall } = await inquirer.prompt([
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 registry.loadRegistry();
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 registry.interactiveInstall();
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 = registry.findComponentInRegistry(componentNames[0]);
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 registry.installComponent(componentInfo.name, actualCategory, options.force);
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 registry.installMultipleComponents(normalizedComponents, category, options.force);
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 registry.loadRegistry();
677
- registry.displayAvailableComponents();
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 registry.loadRegistry();
692
- return await registry.updateAllComponents(options.force);
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');
@@ -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 post.js and `slice postinstall`
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 { getConfigPath, getApiPath, getSrcPath, getDistPath, getPath } from '../utils/PathHelper.js';
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 = loadConfig();
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
- let serverProcess = await startNodeServer(actualPort, mode);
207
+ currentServerProcess = await startNodeServer(actualPort, mode);
221
208
 
222
209
  // Configure watch mode if enabled
223
210
  if (watch) {
224
211
  Print.newLine();
225
- const watcher = setupWatcher(serverProcess, async (changedPath) => {
226
- if (serverProcess) {
227
- serverProcess.kill();
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
- serverProcess = await startNodeServer(actualPort, mode);
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) {
@@ -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 post.js (the postinstall hook), the `slice postinstall` command in
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("Run 'slice update' to install updates when convenient.");
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('Run "slice update --update-api" to update it (a .bak backup is created).');
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.4",
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);