slicejs-cli 3.3.0 → 3.4.1

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