slicejs-cli 3.6.2 → 3.6.4

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/client.js CHANGED
@@ -1,744 +1,744 @@
1
- #!/usr/bin/env node
2
- import { program } from "commander";
3
- import inquirer from "inquirer";
4
- import initializeProject from "./commands/init/init.js";
5
- import createComponent from "./commands/createComponent/createComponent.js";
6
- import listComponents from "./commands/listComponents/listComponents.js";
7
- import deleteComponent from "./commands/deleteComponent/deleteComponent.js";
8
- import getComponent, { listComponents as listRemoteComponents, syncComponents } from "./commands/getComponent/getComponent.js";
9
- import startServer from "./commands/startServer/startServer.js";
10
- import runDiagnostics from "./commands/doctor/doctor.js";
11
- import versionChecker from "./commands/utils/VersionChecker.js";
12
- import updateManager from "./commands/utils/updateManager.js";
13
- import fs from "fs";
14
- import path from "path";
15
- import { fileURLToPath } from "url";
16
- import { getProjectRoot, getSrcPath, getPath } from "./commands/utils/PathHelper.js";
17
- import { loadConfigSync as sharedLoadConfigSync } from "./commands/utils/loadConfig.js";
18
- import { spawnSync } from "node:child_process";
19
- import validations from "./commands/Validations.js";
20
- import Print from "./commands/Print.js";
21
- import build from './commands/build/build.js';
22
- import { runGenerateTypes } from './commands/types/types.js';
23
- import { cleanBundles, bundleInfo } from './commands/bundle/bundle.js';
24
- import {
25
- isLocalDelegationDisabled,
26
- findNearestLocalCliEntry,
27
- resolveLocalCliCandidate,
28
- shouldDelegateToLocalCli
29
- } from './commands/utils/LocalCliDelegation.js';
30
- import {
31
- detectPackageManager,
32
- getAvailablePackageManagers,
33
- isPackageManagerAvailable,
34
- resolvePackageManager,
35
- runScriptCommand,
36
- SUPPORTED_PACKAGE_MANAGERS
37
- } from './commands/utils/PackageManager.js';
38
- import { SLICE_SCRIPTS } from './commands/utils/sliceScripts.js';
39
-
40
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
41
-
42
- const getCategories = () => {
43
- const config = sharedLoadConfigSync(import.meta.url);
44
- return config && config.paths?.components ? Object.keys(config.paths.components) : [];
45
- };
46
-
47
- // Function to run version check for all commands
48
- async function runWithVersionCheck(commandFunction, ...args) {
49
- try {
50
- updateManager.notifyAvailableUpdates().catch(() => {});
51
-
52
- const result = await commandFunction(...args);
53
-
54
- setTimeout(() => {
55
- versionChecker.checkForUpdates(false);
56
- }, 100);
57
-
58
- return result;
59
- } catch (error) {
60
- Print.error(`Command execution: ${error.message}`);
61
- return false;
62
- }
63
- }
64
-
65
- function maybeDelegateToLocalCli() {
66
- if (isLocalDelegationDisabled(process.env)) {
67
- return;
68
- }
69
-
70
- const currentEntryPath = fileURLToPath(import.meta.url);
71
- const localEntryPath = findNearestLocalCliEntry(process.cwd(), resolveLocalCliCandidate);
72
-
73
- if (!shouldDelegateToLocalCli(currentEntryPath, localEntryPath)) {
74
- return;
75
- }
76
-
77
- const child = spawnSync(
78
- process.execPath,
79
- [localEntryPath, ...process.argv.slice(2)],
80
- {
81
- stdio: 'inherit',
82
- cwd: process.cwd(),
83
- env: process.env
84
- }
85
- );
86
-
87
- process.exit(child.status ?? 1);
88
- }
89
-
90
- maybeDelegateToLocalCli();
91
-
92
- const sliceClient = program;
93
-
94
- try {
95
- const pkgPath = path.join(__dirname, "./package.json");
96
- const pkgRaw = fs.readFileSync(pkgPath, "utf-8");
97
- const pkg = JSON.parse(pkgRaw);
98
- sliceClient.version(pkg.version).description("CLI for managing Slice.js framework components");
99
- } catch {
100
- sliceClient.version("0.0.0").description("CLI for managing Slice.js framework components");
101
- }
102
-
103
- // INIT COMMAND
104
- sliceClient
105
- .command("init")
106
- .description("Initialize a new Slice.js project")
107
- .option("-y, --yes [name]", "Skip prompts and initialize with project name")
108
- .option("--pm <packageManager>", "Package manager to use (pnpm or npm). Auto-detected when omitted")
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 {
114
- const answers = await inquirer.prompt([
115
- {
116
- type: 'input',
117
- name: 'projectName',
118
- message: 'What is the name of your project?',
119
- default: projectName,
120
- filter: (input) => input.trim()
121
- .toLowerCase()
122
- .replace(/\s+/g, '-')
123
- .replace(/[^a-z0-9-]/g, '')
124
- .replace(/-+/g, '-')
125
- .replace(/^-|-$/g, ''),
126
- validate: (input) => {
127
- if (!input || !input.trim()) return 'Project name cannot be empty';
128
- if (input.includes('/') || input.includes('\\')) return 'Use a simple name, not a path';
129
- return true;
130
- }
131
- }
132
- ]);
133
- projectName = answers.projectName;
134
- }
135
-
136
- // Resolve the package manager: --pm flag → detection → interactive prompt.
137
- // Detection here has no project root yet (the folder is new), so it relies on
138
- // npm_config_user_agent (npx / pnpm dlx / PM scripts) or a single available binary.
139
- let packageManager = options.pm;
140
- if (packageManager && !SUPPORTED_PACKAGE_MANAGERS.includes(packageManager)) {
141
- Print.error(`Unsupported package manager "${packageManager}". Use one of: ${SUPPORTED_PACKAGE_MANAGERS.join(', ')}`);
142
- return;
143
- }
144
- if (packageManager && !isPackageManagerAvailable(packageManager)) {
145
- Print.error(`Package manager "${packageManager}" is not available on this system`);
146
- return;
147
- }
148
- if (!packageManager) {
149
- const detected = detectPackageManager(null);
150
- if (detected) {
151
- packageManager = detected.name;
152
- } else if (!options.yes) {
153
- const available = getAvailablePackageManagers();
154
- if (available.length === 0) {
155
- Print.error('No package manager found (pnpm or npm). Install one and retry.');
156
- return;
157
- }
158
- const { pm } = await inquirer.prompt([
159
- {
160
- type: 'list',
161
- name: 'pm',
162
- message: 'Which package manager do you want to use?',
163
- choices: available,
164
- default: available.includes('pnpm') ? 'pnpm' : available[0]
165
- }
166
- ]);
167
- packageManager = pm;
168
- } else {
169
- const available = getAvailablePackageManagers();
170
- packageManager = available.includes('pnpm') ? 'pnpm' : available[0];
171
- if (!packageManager) {
172
- Print.error('No package manager found (pnpm or npm). Install one and retry.');
173
- return;
174
- }
175
- }
176
- }
177
-
178
- const projectDir = path.resolve(projectName);
179
-
180
- if (fs.existsSync(projectDir)) {
181
- const contents = fs.readdirSync(projectDir);
182
- if (contents.length > 0) {
183
- if (options.yes) {
184
- Print.error(`Directory "${projectName}" already exists and is not empty. Aborting.`);
185
- return;
186
- }
187
- const { overwrite } = await inquirer.prompt([
188
- {
189
- type: 'confirm',
190
- name: 'overwrite',
191
- message: `Directory "${projectName}" already exists and is not empty. Continue?`,
192
- default: false
193
- }
194
- ]);
195
- if (!overwrite) {
196
- Print.info('Initialization cancelled.');
197
- return;
198
- }
199
- }
200
- } else {
201
- fs.mkdirSync(projectDir, { recursive: true });
202
- }
203
-
204
- process.chdir(projectDir);
205
- process.env.INIT_CWD = projectDir;
206
-
207
- await runWithVersionCheck(async () => {
208
- await initializeProject({ packageManager });
209
- });
210
- });
211
-
212
- // VERSION COMMAND
213
- sliceClient
214
- .command("version")
215
- .alias("v")
216
- .description("Show version information and check for updates")
217
- .action(async () => {
218
- await versionChecker.showVersionInfo();
219
- });
220
-
221
- // BUILD COMMAND
222
- const buildCommand = sliceClient.command("build")
223
- .description("Build Slice.js project for production")
224
- .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
- }
234
- });
235
-
236
- buildCommand
237
- .command("clean")
238
- .description("Remove all generated bundles")
239
- .action(async () => {
240
- await cleanBundles();
241
- });
242
-
243
- buildCommand
244
- .command("info")
245
- .description("Show information about generated bundles")
246
- .action(async () => {
247
- await bundleInfo();
248
- });
249
-
250
- buildCommand
251
- .option("-a, --analyze", "Analyze project dependencies without bundling")
252
- .option("-v, --verbose", "Show detailed output")
253
- .option("--no-minify", "Disable minification (enabled by default)")
254
- .option("--no-obfuscate", "Disable obfuscation (enabled by default, no prop mangling)")
255
- .option("--preview", "Start preview server after build")
256
- .option("--serve", "Start preview server without building")
257
- .option("--skip-clean", "Skip cleaning dist before build");
258
-
259
- // DEV COMMAND (DEVELOPMENT)
260
- sliceClient
261
- .command("dev")
262
- .description("Start development server with hot reload enabled by default")
263
- .option("-p, --port <port>", "Port for development server")
264
- .option("--no-hmr", "Disable hot module reload (enabled by default)")
265
- .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
- });
275
- });
276
- } finally {
277
- process.env.NODE_ENV = prevEnv;
278
- }
279
- });
280
-
281
- // START COMMAND - PRODUCTION MODE
282
- sliceClient
283
- .command("start")
284
- .description("Serve production files from dist/ (requires prior slice build)")
285
- .option("-p, --port <port>", "Port for server")
286
- .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
- });
295
- });
296
- } finally {
297
- process.env.NODE_ENV = prevEnv;
298
- }
299
- });
300
-
301
- // COMPONENT COMMAND GROUP - For local component management
302
- const componentCommand = sliceClient.command("component").alias("comp").description("Manage local project components");
303
-
304
- // CREATE LOCAL COMPONENT
305
- componentCommand
306
- .command("create [name]")
307
- .alias("new")
308
- .description("Create a new component in your local project")
309
- .option("-c, --category <category>", "Component category (e.g. Visual, Service, AppComponents). Skips the prompt when provided.")
310
- .action(async (name, options) => {
311
- await runWithVersionCheck(async () => {
312
- const categories = getCategories();
313
- if (categories.length === 0) {
314
- Print.error("No categories found in your project configuration");
315
- Print.info("Run 'slice init' to initialize your project first");
316
- Print.commandExample("Initialize project", "slice init");
317
- return;
318
- }
319
-
320
- let componentName = name;
321
- let category = options.category;
322
-
323
- // Prompt only for the values not supplied on the command line. Passing both
324
- // a name and --category runs fully non-interactively (handy for scripts/agents).
325
- const prompts = [];
326
- if (!componentName) {
327
- prompts.push({
328
- type: "input",
329
- name: "componentName",
330
- message: "Enter the component name:",
331
- validate: (input) => {
332
- if (!input) return "Component name cannot be empty";
333
- if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(input)) {
334
- return "Component name must start with a letter and contain only alphanumeric characters";
335
- }
336
- return true;
337
- },
338
- });
339
- }
340
- if (!category) {
341
- prompts.push({
342
- type: "list",
343
- name: "category",
344
- message: "Select the component category:",
345
- choices: categories,
346
- });
347
- }
348
-
349
- if (prompts.length > 0) {
350
- const answers = await inquirer.prompt(prompts);
351
- componentName = componentName ?? answers.componentName;
352
- category = category ?? answers.category;
353
- }
354
-
355
- // createComponent validates the name and the category (and reports a clear
356
- // error listing valid categories if --category is wrong).
357
- const result = createComponent(componentName, category);
358
- if (result) {
359
- Print.success(`Component '${componentName}' created successfully in category '${category}'`);
360
- Print.info("Listing updated components:");
361
- listComponents();
362
- }
363
- });
364
- });
365
-
366
- // LIST LOCAL COMPONENTS
367
- componentCommand
368
- .command("list")
369
- .alias("ls")
370
- .description("List all components in your local project")
371
- .action(async () => {
372
- await runWithVersionCheck(() => {
373
- listComponents();
374
- return Promise.resolve();
375
- });
376
- });
377
-
378
- // DELETE LOCAL COMPONENT
379
- componentCommand
380
- .command("delete [name]")
381
- .alias("remove")
382
- .description("Delete a component from your local project")
383
- .option("-c, --category <category>", "Component category. Skips the category prompt when provided.")
384
- .option("-y, --yes", "Skip the confirmation prompt (for non-interactive use)")
385
- .action(async (name, options) => {
386
- await runWithVersionCheck(async () => {
387
- const categories = getCategories();
388
- if (categories.length === 0) {
389
- Print.error("No categories available. Check your configuration");
390
- Print.info("Run 'slice init' to initialize your project");
391
- return;
392
- }
393
-
394
- try {
395
- let category = options.category;
396
- let componentName = name;
397
-
398
- // Resolve category (prompt only if not provided)
399
- if (!category) {
400
- const categoryAnswer = await inquirer.prompt([
401
- {
402
- type: "list",
403
- name: "category",
404
- message: "Select the component category:",
405
- choices: categories,
406
- }
407
- ]);
408
- category = categoryAnswer.category;
409
- }
410
-
411
- const config = sharedLoadConfigSync(import.meta.url);
412
- if (!config) {
413
- Print.error("Could not load configuration");
414
- return;
415
- }
416
-
417
- if (!config.paths.components[category]) {
418
- Print.error(`Invalid category: '${category}'`);
419
- Print.info(`Available categories: ${categories.join(", ")}`);
420
- return;
421
- }
422
-
423
- const categoryPath = config.paths.components[category].path;
424
- const fullPath = getSrcPath(import.meta.url, categoryPath);
425
-
426
- if (!fs.existsSync(fullPath)) {
427
- Print.error(`Category path does not exist: ${categoryPath}`);
428
- return;
429
- }
430
-
431
- // Resolve component name (prompt with a list only if not provided)
432
- if (!componentName) {
433
- const components = fs.readdirSync(fullPath).filter(item => {
434
- const itemPath = path.join(fullPath, item);
435
- return fs.statSync(itemPath).isDirectory();
436
- });
437
-
438
- if (components.length === 0) {
439
- Print.info(`No components found in category '${category}'`);
440
- return;
441
- }
442
-
443
- const componentAnswer = await inquirer.prompt([
444
- {
445
- type: "list",
446
- name: "componentName",
447
- message: "Select the component to delete:",
448
- choices: components,
449
- }
450
- ]);
451
- componentName = componentAnswer.componentName;
452
- }
453
-
454
- // Confirm unless --yes was passed (passing name + --category + --yes is fully non-interactive)
455
- if (!options.yes) {
456
- const { confirm } = await inquirer.prompt([
457
- {
458
- type: "confirm",
459
- name: "confirm",
460
- message: `Are you sure you want to delete '${componentName}' from '${category}'?`,
461
- default: false,
462
- }
463
- ]);
464
- if (!confirm) {
465
- Print.info("Delete operation cancelled");
466
- return;
467
- }
468
- }
469
-
470
- // deleteComponent validates the name/category and that the component exists.
471
- if (deleteComponent(componentName, category)) {
472
- Print.success(`Component ${componentName} deleted successfully`);
473
- Print.info("Listing updated components:");
474
- listComponents();
475
- }
476
- } catch (error) {
477
- Print.error(`Deleting component: ${error.message}`);
478
- }
479
- });
480
- });
481
-
482
- // REGISTRY COMMAND GROUP - For component registry operations
483
- const registryCommand = sliceClient.command("registry").alias("reg").description("Manage components from official Slice.js repository");
484
-
485
- // TYPES COMMAND GROUP - TypeScript declarations from static props
486
- const typesCommand = sliceClient.command("types").description("Generate TypeScript declarations from component static props");
487
-
488
- typesCommand
489
- .command("generate")
490
- .description("Generate src/slice-build.generated.d.ts for slice.build autocomplete")
491
- .option("-o, --output <path>", "Custom output path", "src/slice-build.generated.d.ts")
492
- .action(async (options) => {
493
- await runWithVersionCheck(async () => {
494
- const projectRoot = getProjectRoot(import.meta.url);
495
- const outputPath = path.isAbsolute(options.output)
496
- ? options.output
497
- : path.join(projectRoot, options.output);
498
-
499
- await runGenerateTypes({
500
- projectRoot,
501
- outputPath
502
- });
503
- });
504
- });
505
-
506
- // GET COMPONENTS FROM REGISTRY
507
- registryCommand
508
- .command("get [components...]")
509
- .description("Download and install components from official repository")
510
- .option("-f, --force", "Force overwrite existing components")
511
- .option("-s, --service", "Install Service components instead of Visual")
512
- .action(async (components, options) => {
513
- await runWithVersionCheck(async () => {
514
- await getComponent(components, {
515
- force: options.force,
516
- service: options.service
517
- });
518
- });
519
- });
520
-
521
- // LIST REGISTRY COMPONENTS
522
- registryCommand
523
- .command("list")
524
- .alias("ls")
525
- .description("List all available components in the official repository")
526
- .action(async () => {
527
- await runWithVersionCheck(async () => {
528
- await listRemoteComponents();
529
- });
530
- });
531
-
532
- // SYNC COMPONENTS FROM REGISTRY
533
- registryCommand
534
- .command("sync")
535
- .description("Update all local components to latest versions from repository")
536
- .option("-f, --force", "Force update without confirmation")
537
- .action(async (options) => {
538
- await runWithVersionCheck(async () => {
539
- await syncComponents({
540
- force: options.force
541
- });
542
- });
543
- });
544
-
545
- // SHORTCUTS - Top-level convenient commands
546
- sliceClient
547
- .command("get [components...]")
548
- .description("Quick install components from registry")
549
- .option("-f, --force", "Force overwrite existing components")
550
- .option("-s, --service", "Install Service components instead of Visual")
551
- .action(async (components, options) => {
552
- await runWithVersionCheck(async () => {
553
- if (!components || components.length === 0) {
554
- Print.info("Use 'slice registry list' to see available components");
555
- Print.commandExample("Get multiple components", "slice get Button Card Input");
556
- Print.commandExample("Get service component", "slice get FetchManager --service");
557
- Print.commandExample("Browse components", "slice browse");
558
- return;
559
- }
560
-
561
- await getComponent(components, {
562
- force: options.force,
563
- service: options.service
564
- });
565
- });
566
- });
567
-
568
- sliceClient
569
- .command("browse")
570
- .description("Quick browse available components")
571
- .action(async () => {
572
- await runWithVersionCheck(async () => {
573
- await listRemoteComponents();
574
- });
575
- });
576
-
577
- sliceClient
578
- .command("sync")
579
- .description("Quick sync local components to latest versions")
580
- .option("-f, --force", "Force update without confirmation")
581
- .action(async (options) => {
582
- await runWithVersionCheck(async () => {
583
- await syncComponents({
584
- force: options.force
585
- });
586
- });
587
- });
588
-
589
- // LIST COMMAND - Quick shortcut for listing local components
590
- sliceClient
591
- .command("list")
592
- .description("Quick list all local components (alias for component list)")
593
- .action(async () => {
594
- await runWithVersionCheck(() => {
595
- listComponents();
596
- return Promise.resolve();
597
- });
598
- });
599
-
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
- // DOCTOR COMMAND - Diagnose project issues
614
- sliceClient
615
- .command("doctor")
616
- .alias("diagnose")
617
- .description("Run diagnostics to check project health")
618
- .action(async () => {
619
- await runWithVersionCheck(async () => {
620
- await runDiagnostics();
621
- });
622
- });
623
-
624
- // POSTINSTALL COMMAND - Manual alternative to postinstall
625
- sliceClient
626
- .command("postinstall")
627
- .description("Configure npm scripts in package.json (alternative to postinstall for --ignore-scripts users)")
628
- .action(() => {
629
- // npm sets npm_config_global; pnpm global installs are detected via PNPM_HOME.
630
- const pnpmHome = process.env.PNPM_HOME;
631
- const cliEntryPath = fileURLToPath(import.meta.url);
632
- const isGlobal = process.env.npm_config_global === 'true'
633
- || (pnpmHome && cliEntryPath.startsWith(pnpmHome));
634
- if (isGlobal) {
635
- console.log('⚠️ Global installation of slicejs-cli detected.');
636
- console.log(' We strongly recommend using a local installation to avoid version mismatches.');
637
- console.log(` Uninstall global: ${pnpmHome ? 'pnpm remove -g slicejs-cli' : 'npm uninstall -g slicejs-cli'}`);
638
- return;
639
- }
640
-
641
- const projectRoot = getProjectRoot(import.meta.url);
642
- const pkgPath = path.join(projectRoot, 'package.json');
643
- const packageManager = resolvePackageManager(projectRoot).name;
644
-
645
- // Shared with post.js and slice init — see commands/utils/sliceScripts.js
646
- const sliceScripts = SLICE_SCRIPTS;
647
-
648
- try {
649
- let pkg = {};
650
- if (fs.existsSync(pkgPath)) {
651
- pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
652
- } else {
653
- pkg = {
654
- name: path.basename(projectRoot),
655
- version: '1.0.0',
656
- description: 'Slice.js project',
657
- scripts: {}
658
- };
659
- }
660
-
661
- pkg.scripts = pkg.scripts || {};
662
- let addedCount = 0;
663
- for (const [script, command] of Object.entries(sliceScripts)) {
664
- if (!pkg.scripts[script]) {
665
- pkg.scripts[script] = command;
666
- addedCount++;
667
- }
668
- }
669
-
670
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), 'utf-8');
671
- console.log(`✅ slicejs-cli installed successfully. Added ${addedCount} package scripts to package.json.`);
672
- console.log(` Run: ${runScriptCommand(packageManager, 'slice:dev')}`);
673
- } catch (err) {
674
- console.log('✅ slicejs-cli installed successfully.');
675
- console.log(' Could not auto-configure scripts:', err.message);
676
- console.log(` Configure scripts manually and run: ${runScriptCommand(packageManager, 'slice:dev')}`);
677
- }
678
- });
679
-
680
- // Enhanced help
681
- sliceClient
682
- .option("--no-version-check", "Skip version check for this command")
683
- .configureHelp({
684
- sortSubcommands: true,
685
- subcommandTerm: (cmd) => cmd.name() + ' ' + cmd.usage()
686
- });
687
-
688
-
689
- // Custom help - SIMPLIFIED for development only
690
- sliceClient.addHelpText('after', `
691
- Common Usage Examples:
692
- slice init - Initialize new Slice.js project
693
- slice dev - Start development server
694
- slice build - Build production output (bundles + dist)
695
- slice start - Start production server
696
- slice get Button Card Input - Install Visual components from registry
697
- slice get FetchManager -s - Install Service component from registry
698
- slice browse - Browse all available components
699
- slice sync - Update local components to latest versions
700
- slice component create - Create new local component
701
- slice list - List all local components
702
- slice doctor - Run project diagnostics
703
- slice types generate - Generate TypeScript typings for slice.build
704
- slice postinstall - Show post-install setup guide
705
-
706
- Command Categories:
707
- • init, dev, start - Project lifecycle (development only)
708
- • get, browse, sync, list - Quick shortcuts
709
- • component <cmd> - Local component management
710
- • registry <cmd> - Official repository operations
711
- • types generate - Type declarations from static props
712
- • version, update, doctor, setup - Maintenance commands
713
-
714
- Development Workflow:
715
- • slice init - Initialize project
716
- • slice dev - Start development server (serves from /src)
717
- • slice build - Build production output to /dist (includes bundles)
718
- • slice start - Serve production build from /dist
719
-
720
- More info: https://slice-js-docs.vercel.app/
721
- `);
722
-
723
- // Default action with better messaging
724
- if (!process.argv.slice(2).length) {
725
- Print.newLine();
726
- Print.info("Start with: slice init");
727
- Print.commandExample("Development", "slice dev");
728
- Print.commandExample("View available components", "slice browse");
729
- Print.info("Use 'slice --help' for full help");
730
- }
731
-
732
- // Error handling for unknown commands
733
- program.on('command:*', () => {
734
- Print.error('Invalid command. See available commands above');
735
- Print.info("Use 'slice --help' for help");
736
- process.exit(1);
737
- });
738
-
739
- // HELP Command
740
- const helpCommand = sliceClient.command("help").description("Display help information for Slice.js CLI").action(() => {
741
- sliceClient.outputHelp();
742
- });
743
-
744
- program.parse();
1
+ #!/usr/bin/env node
2
+ import { program } from "commander";
3
+ import inquirer from "inquirer";
4
+ import initializeProject from "./commands/init/init.js";
5
+ import createComponent from "./commands/createComponent/createComponent.js";
6
+ import listComponents from "./commands/listComponents/listComponents.js";
7
+ import deleteComponent from "./commands/deleteComponent/deleteComponent.js";
8
+ import getComponent, { listComponents as listRemoteComponents, syncComponents } from "./commands/getComponent/getComponent.js";
9
+ import startServer from "./commands/startServer/startServer.js";
10
+ import runDiagnostics from "./commands/doctor/doctor.js";
11
+ import versionChecker from "./commands/utils/VersionChecker.js";
12
+ import updateManager from "./commands/utils/updateManager.js";
13
+ import fs from "fs";
14
+ import path from "path";
15
+ import { fileURLToPath } from "url";
16
+ import { getProjectRoot, getSrcPath, getPath } from "./commands/utils/PathHelper.js";
17
+ import { loadConfigSync as sharedLoadConfigSync } from "./commands/utils/loadConfig.js";
18
+ import { spawnSync } from "node:child_process";
19
+ import validations from "./commands/Validations.js";
20
+ import Print from "./commands/Print.js";
21
+ import build from './commands/build/build.js';
22
+ import { runGenerateTypes } from './commands/types/types.js';
23
+ import { cleanBundles, bundleInfo } from './commands/bundle/bundle.js';
24
+ import {
25
+ isLocalDelegationDisabled,
26
+ findNearestLocalCliEntry,
27
+ resolveLocalCliCandidate,
28
+ shouldDelegateToLocalCli
29
+ } from './commands/utils/LocalCliDelegation.js';
30
+ import {
31
+ detectPackageManager,
32
+ getAvailablePackageManagers,
33
+ isPackageManagerAvailable,
34
+ resolvePackageManager,
35
+ runScriptCommand,
36
+ SUPPORTED_PACKAGE_MANAGERS
37
+ } from './commands/utils/PackageManager.js';
38
+ import { SLICE_SCRIPTS } from './commands/utils/sliceScripts.js';
39
+
40
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
41
+
42
+ const getCategories = () => {
43
+ const config = sharedLoadConfigSync(import.meta.url);
44
+ return config && config.paths?.components ? Object.keys(config.paths.components) : [];
45
+ };
46
+
47
+ // Function to run version check for all commands
48
+ async function runWithVersionCheck(commandFunction, ...args) {
49
+ try {
50
+ updateManager.notifyAvailableUpdates().catch(() => {});
51
+
52
+ const result = await commandFunction(...args);
53
+
54
+ setTimeout(() => {
55
+ versionChecker.checkForUpdates(false);
56
+ }, 100);
57
+
58
+ return result;
59
+ } catch (error) {
60
+ Print.error(`Command execution: ${error.message}`);
61
+ return false;
62
+ }
63
+ }
64
+
65
+ function maybeDelegateToLocalCli() {
66
+ if (isLocalDelegationDisabled(process.env)) {
67
+ return;
68
+ }
69
+
70
+ const currentEntryPath = fileURLToPath(import.meta.url);
71
+ const localEntryPath = findNearestLocalCliEntry(process.cwd(), resolveLocalCliCandidate);
72
+
73
+ if (!shouldDelegateToLocalCli(currentEntryPath, localEntryPath)) {
74
+ return;
75
+ }
76
+
77
+ const child = spawnSync(
78
+ process.execPath,
79
+ [localEntryPath, ...process.argv.slice(2)],
80
+ {
81
+ stdio: 'inherit',
82
+ cwd: process.cwd(),
83
+ env: process.env
84
+ }
85
+ );
86
+
87
+ process.exit(child.status ?? 1);
88
+ }
89
+
90
+ maybeDelegateToLocalCli();
91
+
92
+ const sliceClient = program;
93
+
94
+ try {
95
+ const pkgPath = path.join(__dirname, "./package.json");
96
+ const pkgRaw = fs.readFileSync(pkgPath, "utf-8");
97
+ const pkg = JSON.parse(pkgRaw);
98
+ sliceClient.version(pkg.version).description("CLI for managing Slice.js framework components");
99
+ } catch {
100
+ sliceClient.version("0.0.0").description("CLI for managing Slice.js framework components");
101
+ }
102
+
103
+ // INIT COMMAND
104
+ sliceClient
105
+ .command("init")
106
+ .description("Initialize a new Slice.js project")
107
+ .option("-y, --yes [name]", "Skip prompts and initialize with project name")
108
+ .option("--pm <packageManager>", "Package manager to use (pnpm or npm). Auto-detected when omitted")
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 {
114
+ const answers = await inquirer.prompt([
115
+ {
116
+ type: 'input',
117
+ name: 'projectName',
118
+ message: 'What is the name of your project?',
119
+ default: projectName,
120
+ filter: (input) => input.trim()
121
+ .toLowerCase()
122
+ .replace(/\s+/g, '-')
123
+ .replace(/[^a-z0-9-]/g, '')
124
+ .replace(/-+/g, '-')
125
+ .replace(/^-|-$/g, ''),
126
+ validate: (input) => {
127
+ if (!input || !input.trim()) return 'Project name cannot be empty';
128
+ if (input.includes('/') || input.includes('\\')) return 'Use a simple name, not a path';
129
+ return true;
130
+ }
131
+ }
132
+ ]);
133
+ projectName = answers.projectName;
134
+ }
135
+
136
+ // Resolve the package manager: --pm flag → detection → interactive prompt.
137
+ // Detection here has no project root yet (the folder is new), so it relies on
138
+ // npm_config_user_agent (npx / pnpm dlx / PM scripts) or a single available binary.
139
+ let packageManager = options.pm;
140
+ if (packageManager && !SUPPORTED_PACKAGE_MANAGERS.includes(packageManager)) {
141
+ Print.error(`Unsupported package manager "${packageManager}". Use one of: ${SUPPORTED_PACKAGE_MANAGERS.join(', ')}`);
142
+ return;
143
+ }
144
+ if (packageManager && !isPackageManagerAvailable(packageManager)) {
145
+ Print.error(`Package manager "${packageManager}" is not available on this system`);
146
+ return;
147
+ }
148
+ if (!packageManager) {
149
+ const detected = detectPackageManager(null);
150
+ if (detected) {
151
+ packageManager = detected.name;
152
+ } else if (!options.yes) {
153
+ const available = getAvailablePackageManagers();
154
+ if (available.length === 0) {
155
+ Print.error('No package manager found (pnpm or npm). Install one and retry.');
156
+ return;
157
+ }
158
+ const { pm } = await inquirer.prompt([
159
+ {
160
+ type: 'list',
161
+ name: 'pm',
162
+ message: 'Which package manager do you want to use?',
163
+ choices: available,
164
+ default: available.includes('pnpm') ? 'pnpm' : available[0]
165
+ }
166
+ ]);
167
+ packageManager = pm;
168
+ } else {
169
+ const available = getAvailablePackageManagers();
170
+ packageManager = available.includes('pnpm') ? 'pnpm' : available[0];
171
+ if (!packageManager) {
172
+ Print.error('No package manager found (pnpm or npm). Install one and retry.');
173
+ return;
174
+ }
175
+ }
176
+ }
177
+
178
+ const projectDir = path.resolve(projectName);
179
+
180
+ if (fs.existsSync(projectDir)) {
181
+ const contents = fs.readdirSync(projectDir);
182
+ if (contents.length > 0) {
183
+ if (options.yes) {
184
+ Print.error(`Directory "${projectName}" already exists and is not empty. Aborting.`);
185
+ return;
186
+ }
187
+ const { overwrite } = await inquirer.prompt([
188
+ {
189
+ type: 'confirm',
190
+ name: 'overwrite',
191
+ message: `Directory "${projectName}" already exists and is not empty. Continue?`,
192
+ default: false
193
+ }
194
+ ]);
195
+ if (!overwrite) {
196
+ Print.info('Initialization cancelled.');
197
+ return;
198
+ }
199
+ }
200
+ } else {
201
+ fs.mkdirSync(projectDir, { recursive: true });
202
+ }
203
+
204
+ process.chdir(projectDir);
205
+ process.env.INIT_CWD = projectDir;
206
+
207
+ await runWithVersionCheck(async () => {
208
+ await initializeProject({ packageManager });
209
+ });
210
+ });
211
+
212
+ // VERSION COMMAND
213
+ sliceClient
214
+ .command("version")
215
+ .alias("v")
216
+ .description("Show version information and check for updates")
217
+ .action(async () => {
218
+ await versionChecker.showVersionInfo();
219
+ });
220
+
221
+ // BUILD COMMAND
222
+ const buildCommand = sliceClient.command("build")
223
+ .description("Build Slice.js project for production")
224
+ .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
+ }
234
+ });
235
+
236
+ buildCommand
237
+ .command("clean")
238
+ .description("Remove all generated bundles")
239
+ .action(async () => {
240
+ await cleanBundles();
241
+ });
242
+
243
+ buildCommand
244
+ .command("info")
245
+ .description("Show information about generated bundles")
246
+ .action(async () => {
247
+ await bundleInfo();
248
+ });
249
+
250
+ buildCommand
251
+ .option("-a, --analyze", "Analyze project dependencies without bundling")
252
+ .option("-v, --verbose", "Show detailed output")
253
+ .option("--no-minify", "Disable minification (enabled by default)")
254
+ .option("--no-obfuscate", "Disable obfuscation (enabled by default, no prop mangling)")
255
+ .option("--preview", "Start preview server after build")
256
+ .option("--serve", "Start preview server without building")
257
+ .option("--skip-clean", "Skip cleaning dist before build");
258
+
259
+ // DEV COMMAND (DEVELOPMENT)
260
+ sliceClient
261
+ .command("dev")
262
+ .description("Start development server with hot reload enabled by default")
263
+ .option("-p, --port <port>", "Port for development server")
264
+ .option("--no-hmr", "Disable hot module reload (enabled by default)")
265
+ .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
+ });
275
+ });
276
+ } finally {
277
+ process.env.NODE_ENV = prevEnv;
278
+ }
279
+ });
280
+
281
+ // START COMMAND - PRODUCTION MODE
282
+ sliceClient
283
+ .command("start")
284
+ .description("Serve production files from dist/ (requires prior slice build)")
285
+ .option("-p, --port <port>", "Port for server")
286
+ .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
+ });
295
+ });
296
+ } finally {
297
+ process.env.NODE_ENV = prevEnv;
298
+ }
299
+ });
300
+
301
+ // COMPONENT COMMAND GROUP - For local component management
302
+ const componentCommand = sliceClient.command("component").alias("comp").description("Manage local project components");
303
+
304
+ // CREATE LOCAL COMPONENT
305
+ componentCommand
306
+ .command("create [name]")
307
+ .alias("new")
308
+ .description("Create a new component in your local project")
309
+ .option("-c, --category <category>", "Component category (e.g. Visual, Service, AppComponents). Skips the prompt when provided.")
310
+ .action(async (name, options) => {
311
+ await runWithVersionCheck(async () => {
312
+ const categories = getCategories();
313
+ if (categories.length === 0) {
314
+ Print.error("No categories found in your project configuration");
315
+ Print.info("Run 'slice init' to initialize your project first");
316
+ Print.commandExample("Initialize project", "slice init");
317
+ return;
318
+ }
319
+
320
+ let componentName = name;
321
+ let category = options.category;
322
+
323
+ // Prompt only for the values not supplied on the command line. Passing both
324
+ // a name and --category runs fully non-interactively (handy for scripts/agents).
325
+ const prompts = [];
326
+ if (!componentName) {
327
+ prompts.push({
328
+ type: "input",
329
+ name: "componentName",
330
+ message: "Enter the component name:",
331
+ validate: (input) => {
332
+ if (!input) return "Component name cannot be empty";
333
+ if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(input)) {
334
+ return "Component name must start with a letter and contain only alphanumeric characters";
335
+ }
336
+ return true;
337
+ },
338
+ });
339
+ }
340
+ if (!category) {
341
+ prompts.push({
342
+ type: "list",
343
+ name: "category",
344
+ message: "Select the component category:",
345
+ choices: categories,
346
+ });
347
+ }
348
+
349
+ if (prompts.length > 0) {
350
+ const answers = await inquirer.prompt(prompts);
351
+ componentName = componentName ?? answers.componentName;
352
+ category = category ?? answers.category;
353
+ }
354
+
355
+ // createComponent validates the name and the category (and reports a clear
356
+ // error listing valid categories if --category is wrong).
357
+ const result = createComponent(componentName, category);
358
+ if (result) {
359
+ Print.success(`Component '${componentName}' created successfully in category '${category}'`);
360
+ Print.info("Listing updated components:");
361
+ listComponents();
362
+ }
363
+ });
364
+ });
365
+
366
+ // LIST LOCAL COMPONENTS
367
+ componentCommand
368
+ .command("list")
369
+ .alias("ls")
370
+ .description("List all components in your local project")
371
+ .action(async () => {
372
+ await runWithVersionCheck(() => {
373
+ listComponents();
374
+ return Promise.resolve();
375
+ });
376
+ });
377
+
378
+ // DELETE LOCAL COMPONENT
379
+ componentCommand
380
+ .command("delete [name]")
381
+ .alias("remove")
382
+ .description("Delete a component from your local project")
383
+ .option("-c, --category <category>", "Component category. Skips the category prompt when provided.")
384
+ .option("-y, --yes", "Skip the confirmation prompt (for non-interactive use)")
385
+ .action(async (name, options) => {
386
+ await runWithVersionCheck(async () => {
387
+ const categories = getCategories();
388
+ if (categories.length === 0) {
389
+ Print.error("No categories available. Check your configuration");
390
+ Print.info("Run 'slice init' to initialize your project");
391
+ return;
392
+ }
393
+
394
+ try {
395
+ let category = options.category;
396
+ let componentName = name;
397
+
398
+ // Resolve category (prompt only if not provided)
399
+ if (!category) {
400
+ const categoryAnswer = await inquirer.prompt([
401
+ {
402
+ type: "list",
403
+ name: "category",
404
+ message: "Select the component category:",
405
+ choices: categories,
406
+ }
407
+ ]);
408
+ category = categoryAnswer.category;
409
+ }
410
+
411
+ const config = sharedLoadConfigSync(import.meta.url);
412
+ if (!config) {
413
+ Print.error("Could not load configuration");
414
+ return;
415
+ }
416
+
417
+ if (!config.paths.components[category]) {
418
+ Print.error(`Invalid category: '${category}'`);
419
+ Print.info(`Available categories: ${categories.join(", ")}`);
420
+ return;
421
+ }
422
+
423
+ const categoryPath = config.paths.components[category].path;
424
+ const fullPath = getSrcPath(import.meta.url, categoryPath);
425
+
426
+ if (!fs.existsSync(fullPath)) {
427
+ Print.error(`Category path does not exist: ${categoryPath}`);
428
+ return;
429
+ }
430
+
431
+ // Resolve component name (prompt with a list only if not provided)
432
+ if (!componentName) {
433
+ const components = fs.readdirSync(fullPath).filter(item => {
434
+ const itemPath = path.join(fullPath, item);
435
+ return fs.statSync(itemPath).isDirectory();
436
+ });
437
+
438
+ if (components.length === 0) {
439
+ Print.info(`No components found in category '${category}'`);
440
+ return;
441
+ }
442
+
443
+ const componentAnswer = await inquirer.prompt([
444
+ {
445
+ type: "list",
446
+ name: "componentName",
447
+ message: "Select the component to delete:",
448
+ choices: components,
449
+ }
450
+ ]);
451
+ componentName = componentAnswer.componentName;
452
+ }
453
+
454
+ // Confirm unless --yes was passed (passing name + --category + --yes is fully non-interactive)
455
+ if (!options.yes) {
456
+ const { confirm } = await inquirer.prompt([
457
+ {
458
+ type: "confirm",
459
+ name: "confirm",
460
+ message: `Are you sure you want to delete '${componentName}' from '${category}'?`,
461
+ default: false,
462
+ }
463
+ ]);
464
+ if (!confirm) {
465
+ Print.info("Delete operation cancelled");
466
+ return;
467
+ }
468
+ }
469
+
470
+ // deleteComponent validates the name/category and that the component exists.
471
+ if (deleteComponent(componentName, category)) {
472
+ Print.success(`Component ${componentName} deleted successfully`);
473
+ Print.info("Listing updated components:");
474
+ listComponents();
475
+ }
476
+ } catch (error) {
477
+ Print.error(`Deleting component: ${error.message}`);
478
+ }
479
+ });
480
+ });
481
+
482
+ // REGISTRY COMMAND GROUP - For component registry operations
483
+ const registryCommand = sliceClient.command("registry").alias("reg").description("Manage components from official Slice.js repository");
484
+
485
+ // TYPES COMMAND GROUP - TypeScript declarations from static props
486
+ const typesCommand = sliceClient.command("types").description("Generate TypeScript declarations from component static props");
487
+
488
+ typesCommand
489
+ .command("generate")
490
+ .description("Generate src/slice-build.generated.d.ts for slice.build autocomplete")
491
+ .option("-o, --output <path>", "Custom output path", "src/slice-build.generated.d.ts")
492
+ .action(async (options) => {
493
+ await runWithVersionCheck(async () => {
494
+ const projectRoot = getProjectRoot(import.meta.url);
495
+ const outputPath = path.isAbsolute(options.output)
496
+ ? options.output
497
+ : path.join(projectRoot, options.output);
498
+
499
+ await runGenerateTypes({
500
+ projectRoot,
501
+ outputPath
502
+ });
503
+ });
504
+ });
505
+
506
+ // GET COMPONENTS FROM REGISTRY
507
+ registryCommand
508
+ .command("get [components...]")
509
+ .description("Download and install components from official repository")
510
+ .option("-f, --force", "Force overwrite existing components")
511
+ .option("-s, --service", "Install Service components instead of Visual")
512
+ .action(async (components, options) => {
513
+ await runWithVersionCheck(async () => {
514
+ await getComponent(components, {
515
+ force: options.force,
516
+ service: options.service
517
+ });
518
+ });
519
+ });
520
+
521
+ // LIST REGISTRY COMPONENTS
522
+ registryCommand
523
+ .command("list")
524
+ .alias("ls")
525
+ .description("List all available components in the official repository")
526
+ .action(async () => {
527
+ await runWithVersionCheck(async () => {
528
+ await listRemoteComponents();
529
+ });
530
+ });
531
+
532
+ // SYNC COMPONENTS FROM REGISTRY
533
+ registryCommand
534
+ .command("sync")
535
+ .description("Update all local components to latest versions from repository")
536
+ .option("-f, --force", "Force update without confirmation")
537
+ .action(async (options) => {
538
+ await runWithVersionCheck(async () => {
539
+ await syncComponents({
540
+ force: options.force
541
+ });
542
+ });
543
+ });
544
+
545
+ // SHORTCUTS - Top-level convenient commands
546
+ sliceClient
547
+ .command("get [components...]")
548
+ .description("Quick install components from registry")
549
+ .option("-f, --force", "Force overwrite existing components")
550
+ .option("-s, --service", "Install Service components instead of Visual")
551
+ .action(async (components, options) => {
552
+ await runWithVersionCheck(async () => {
553
+ if (!components || components.length === 0) {
554
+ Print.info("Use 'slice registry list' to see available components");
555
+ Print.commandExample("Get multiple components", "slice get Button Card Input");
556
+ Print.commandExample("Get service component", "slice get FetchManager --service");
557
+ Print.commandExample("Browse components", "slice browse");
558
+ return;
559
+ }
560
+
561
+ await getComponent(components, {
562
+ force: options.force,
563
+ service: options.service
564
+ });
565
+ });
566
+ });
567
+
568
+ sliceClient
569
+ .command("browse")
570
+ .description("Quick browse available components")
571
+ .action(async () => {
572
+ await runWithVersionCheck(async () => {
573
+ await listRemoteComponents();
574
+ });
575
+ });
576
+
577
+ sliceClient
578
+ .command("sync")
579
+ .description("Quick sync local components to latest versions")
580
+ .option("-f, --force", "Force update without confirmation")
581
+ .action(async (options) => {
582
+ await runWithVersionCheck(async () => {
583
+ await syncComponents({
584
+ force: options.force
585
+ });
586
+ });
587
+ });
588
+
589
+ // LIST COMMAND - Quick shortcut for listing local components
590
+ sliceClient
591
+ .command("list")
592
+ .description("Quick list all local components (alias for component list)")
593
+ .action(async () => {
594
+ await runWithVersionCheck(() => {
595
+ listComponents();
596
+ return Promise.resolve();
597
+ });
598
+ });
599
+
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
+ // DOCTOR COMMAND - Diagnose project issues
614
+ sliceClient
615
+ .command("doctor")
616
+ .alias("diagnose")
617
+ .description("Run diagnostics to check project health")
618
+ .action(async () => {
619
+ await runWithVersionCheck(async () => {
620
+ await runDiagnostics();
621
+ });
622
+ });
623
+
624
+ // POSTINSTALL COMMAND - Manual alternative to postinstall
625
+ sliceClient
626
+ .command("postinstall")
627
+ .description("Configure npm scripts in package.json (alternative to postinstall for --ignore-scripts users)")
628
+ .action(() => {
629
+ // npm sets npm_config_global; pnpm global installs are detected via PNPM_HOME.
630
+ const pnpmHome = process.env.PNPM_HOME;
631
+ const cliEntryPath = fileURLToPath(import.meta.url);
632
+ const isGlobal = process.env.npm_config_global === 'true'
633
+ || (pnpmHome && cliEntryPath.startsWith(pnpmHome));
634
+ if (isGlobal) {
635
+ console.log('⚠️ Global installation of slicejs-cli detected.');
636
+ console.log(' We strongly recommend using a local installation to avoid version mismatches.');
637
+ console.log(` Uninstall global: ${pnpmHome ? 'pnpm remove -g slicejs-cli' : 'npm uninstall -g slicejs-cli'}`);
638
+ return;
639
+ }
640
+
641
+ const projectRoot = getProjectRoot(import.meta.url);
642
+ const pkgPath = path.join(projectRoot, 'package.json');
643
+ const packageManager = resolvePackageManager(projectRoot).name;
644
+
645
+ // Shared with post.js and slice init — see commands/utils/sliceScripts.js
646
+ const sliceScripts = SLICE_SCRIPTS;
647
+
648
+ try {
649
+ let pkg = {};
650
+ if (fs.existsSync(pkgPath)) {
651
+ pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
652
+ } else {
653
+ pkg = {
654
+ name: path.basename(projectRoot),
655
+ version: '1.0.0',
656
+ description: 'Slice.js project',
657
+ scripts: {}
658
+ };
659
+ }
660
+
661
+ pkg.scripts = pkg.scripts || {};
662
+ let addedCount = 0;
663
+ for (const [script, command] of Object.entries(sliceScripts)) {
664
+ if (!pkg.scripts[script]) {
665
+ pkg.scripts[script] = command;
666
+ addedCount++;
667
+ }
668
+ }
669
+
670
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2), 'utf-8');
671
+ console.log(`✅ slicejs-cli installed successfully. Added ${addedCount} package scripts to package.json.`);
672
+ console.log(` Run: ${runScriptCommand(packageManager, 'slice:dev')}`);
673
+ } catch (err) {
674
+ console.log('✅ slicejs-cli installed successfully.');
675
+ console.log(' Could not auto-configure scripts:', err.message);
676
+ console.log(` Configure scripts manually and run: ${runScriptCommand(packageManager, 'slice:dev')}`);
677
+ }
678
+ });
679
+
680
+ // Enhanced help
681
+ sliceClient
682
+ .option("--no-version-check", "Skip version check for this command")
683
+ .configureHelp({
684
+ sortSubcommands: true,
685
+ subcommandTerm: (cmd) => cmd.name() + ' ' + cmd.usage()
686
+ });
687
+
688
+
689
+ // Custom help - SIMPLIFIED for development only
690
+ sliceClient.addHelpText('after', `
691
+ Common Usage Examples:
692
+ slice init - Initialize new Slice.js project
693
+ slice dev - Start development server
694
+ slice build - Build production output (bundles + dist)
695
+ slice start - Start production server
696
+ slice get Button Card Input - Install Visual components from registry
697
+ slice get FetchManager -s - Install Service component from registry
698
+ slice browse - Browse all available components
699
+ slice sync - Update local components to latest versions
700
+ slice component create - Create new local component
701
+ slice list - List all local components
702
+ slice doctor - Run project diagnostics
703
+ slice types generate - Generate TypeScript typings for slice.build
704
+ slice postinstall - Show post-install setup guide
705
+
706
+ Command Categories:
707
+ • init, dev, start - Project lifecycle (development only)
708
+ • get, browse, sync, list - Quick shortcuts
709
+ • component <cmd> - Local component management
710
+ • registry <cmd> - Official repository operations
711
+ • types generate - Type declarations from static props
712
+ • version, update, doctor, setup - Maintenance commands
713
+
714
+ Development Workflow:
715
+ • slice init - Initialize project
716
+ • slice dev - Start development server (serves from /src)
717
+ • slice build - Build production output to /dist (includes bundles)
718
+ • slice start - Serve production build from /dist
719
+
720
+ More info: https://slice-js-docs.vercel.app/
721
+ `);
722
+
723
+ // Default action with better messaging
724
+ if (!process.argv.slice(2).length) {
725
+ Print.newLine();
726
+ Print.info("Start with: slice init");
727
+ Print.commandExample("Development", "slice dev");
728
+ Print.commandExample("View available components", "slice browse");
729
+ Print.info("Use 'slice --help' for full help");
730
+ }
731
+
732
+ // Error handling for unknown commands
733
+ program.on('command:*', () => {
734
+ Print.error('Invalid command. See available commands above');
735
+ Print.info("Use 'slice --help' for help");
736
+ process.exit(1);
737
+ });
738
+
739
+ // HELP Command
740
+ const helpCommand = sliceClient.command("help").description("Display help information for Slice.js CLI").action(() => {
741
+ sliceClient.outputHelp();
742
+ });
743
+
744
+ program.parse();