slicejs-cli 3.1.0 → 3.2.0

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 (35) hide show
  1. package/README.md +375 -375
  2. package/client.js +579 -555
  3. package/commands/Print.js +167 -167
  4. package/commands/Validations.js +103 -103
  5. package/commands/build/build.js +40 -40
  6. package/commands/buildProduction/buildProduction.js +579 -579
  7. package/commands/bundle/bundle.js +235 -235
  8. package/commands/createComponent/VisualComponentTemplate.js +55 -55
  9. package/commands/createComponent/createComponent.js +126 -126
  10. package/commands/deleteComponent/deleteComponent.js +77 -77
  11. package/commands/doctor/doctor.js +369 -369
  12. package/commands/getComponent/getComponent.js +747 -747
  13. package/commands/init/init.js +261 -261
  14. package/commands/listComponents/listComponents.js +175 -175
  15. package/commands/startServer/startServer.js +264 -264
  16. package/commands/startServer/watchServer.js +79 -79
  17. package/commands/types/types.js +538 -0
  18. package/commands/utils/LocalCliDelegation.js +53 -53
  19. package/commands/utils/PathHelper.js +68 -68
  20. package/commands/utils/VersionChecker.js +167 -167
  21. package/commands/utils/bundling/BundleGenerator.js +2292 -2292
  22. package/commands/utils/bundling/DependencyAnalyzer.js +933 -933
  23. package/commands/utils/updateManager.js +453 -453
  24. package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +182 -0
  25. package/package.json +46 -46
  26. package/post.js +25 -25
  27. package/refactor.md +271 -271
  28. package/tests/bundle-generator.test.js +708 -708
  29. package/tests/bundle-v2-register-output.test.js +470 -470
  30. package/tests/client-launcher-contract.test.js +211 -211
  31. package/tests/client-update-flow-contract.test.js +272 -272
  32. package/tests/dependency-analyzer.test.js +24 -24
  33. package/tests/local-cli-delegation.test.js +79 -79
  34. package/tests/types-generator.test.js +356 -0
  35. package/tests/update-manager-notifications.test.js +88 -88
@@ -1,453 +1,453 @@
1
- // commands/utils/updateManager.js
2
-
3
- import { exec } from "child_process";
4
- import { promisify } from "util";
5
- import inquirer from "inquirer";
6
- import ora from "ora";
7
- import Print from "../Print.js";
8
- import versionChecker from "./VersionChecker.js";
9
- import { getProjectRoot } from "../utils/PathHelper.js";
10
- import path from "path";
11
- import { fileURLToPath } from "url";
12
- import fs from "fs-extra";
13
-
14
- const execAsync = promisify(exec);
15
-
16
- export class UpdateManager {
17
- constructor() {
18
- this.packagesToUpdate = [];
19
- }
20
-
21
- async detectCliInstall() {
22
- try {
23
- const moduleDir = path.dirname(fileURLToPath(import.meta.url));
24
- const cliRoot = path.join(moduleDir, '../../');
25
- const projectRoot = getProjectRoot(import.meta.url);
26
- let globalPrefix = '';
27
- try {
28
- const { stdout } = await execAsync('npm config get prefix');
29
- globalPrefix = stdout.toString().trim();
30
- } catch {}
31
- const localNodeModules = path.join(projectRoot, 'node_modules');
32
- const globalNodeModules = globalPrefix ? path.join(globalPrefix, 'node_modules') : '';
33
-
34
- if (cliRoot.startsWith(localNodeModules)) {
35
- return { type: 'local', cliRoot, projectRoot, globalPrefix };
36
- }
37
- if (globalNodeModules && cliRoot.startsWith(globalNodeModules)) {
38
- return { type: 'global', cliRoot, projectRoot, globalPrefix };
39
- }
40
- return { type: 'unknown', cliRoot, projectRoot, globalPrefix };
41
- } catch (error) {
42
- return { type: 'unknown' };
43
- }
44
- }
45
-
46
- /**
47
- * Check for available updates and return structured info
48
- */
49
- async checkForUpdates(options = {}) {
50
- const { silentErrors = false } = options;
51
-
52
- try {
53
- const updateInfo = await versionChecker.checkForUpdates(true); // Silent mode
54
-
55
- if (!updateInfo) {
56
- return null;
57
- }
58
-
59
- const updates = [];
60
-
61
- if (updateInfo.cli.status === 'outdated') {
62
- updates.push({
63
- name: 'slicejs-cli',
64
- displayName: 'Slice.js CLI',
65
- current: updateInfo.cli.current,
66
- latest: updateInfo.cli.latest,
67
- type: 'cli'
68
- });
69
- }
70
-
71
- if (updateInfo.framework.status === 'outdated') {
72
- updates.push({
73
- name: 'slicejs-web-framework',
74
- displayName: 'Slice.js Framework',
75
- current: updateInfo.framework.current,
76
- latest: updateInfo.framework.latest,
77
- type: 'framework'
78
- });
79
- }
80
-
81
- return {
82
- hasUpdates: updates.length > 0,
83
- updates,
84
- allCurrent: updateInfo.cli.status === 'current' && updateInfo.framework.status === 'current'
85
- };
86
- } catch (error) {
87
- if (!silentErrors) {
88
- Print.error(`Checking for updates: ${error.message}`);
89
- }
90
- return null;
91
- }
92
- }
93
-
94
- async notifyAvailableUpdates() {
95
- const updateInfo = await this.checkForUpdates({ silentErrors: true });
96
-
97
- if (!updateInfo || !updateInfo.hasUpdates) {
98
- return false;
99
- }
100
-
101
- this.displayUpdates(updateInfo);
102
- Print.info("Run 'slice update' to install updates when convenient.");
103
- return true;
104
- }
105
-
106
- /**
107
- * Display available updates in a formatted way
108
- */
109
- displayUpdates(updateInfo) {
110
- if (!updateInfo || !updateInfo.hasUpdates) {
111
- return;
112
- }
113
-
114
- console.log('');
115
- Print.warning('📦 Available Updates:');
116
- console.log('');
117
-
118
- updateInfo.updates.forEach(pkg => {
119
- console.log(` ${pkg.type === 'cli' ? '🔧' : '⚡'} ${pkg.displayName}`);
120
- console.log(` ${pkg.current} → ${pkg.latest}`);
121
- });
122
-
123
- console.log('');
124
- console.log(' 📚 Changelog: https://github.com/VKneider/slice.js/releases');
125
- console.log('');
126
- }
127
-
128
- /**
129
- * Prompt user to select which packages to update
130
- */
131
- async promptForUpdates(updateInfo, options = {}) {
132
- if (!updateInfo || !updateInfo.hasUpdates) {
133
- return [];
134
- }
135
-
136
- // If --yes flag is set, return all updates
137
- if (options.yes) {
138
- return updateInfo.updates.map(pkg => pkg.name);
139
- }
140
-
141
- // If specific package flags are set
142
- if (options.cli || options.framework) {
143
- const selected = [];
144
- if (options.cli) {
145
- const cliUpdate = updateInfo.updates.find(pkg => pkg.type === 'cli');
146
- if (cliUpdate) selected.push(cliUpdate.name);
147
- }
148
- if (options.framework) {
149
- const frameworkUpdate = updateInfo.updates.find(pkg => pkg.type === 'framework');
150
- if (frameworkUpdate) selected.push(frameworkUpdate.name);
151
- }
152
- return selected;
153
- }
154
-
155
- // Interactive selection
156
- const choices = updateInfo.updates.map(pkg => ({
157
- name: `${pkg.displayName} (${pkg.current} → ${pkg.latest})`,
158
- value: pkg.name,
159
- checked: true
160
- }));
161
-
162
- const answers = await inquirer.prompt([
163
- {
164
- type: 'checkbox',
165
- name: 'packages',
166
- message: 'Which packages do you want to update?',
167
- choices,
168
- validate: (answer) => {
169
- if (answer.length === 0) {
170
- return 'You must select at least one package';
171
- }
172
- return true;
173
- }
174
- }
175
- ]);
176
-
177
- return answers.packages;
178
- }
179
-
180
- async buildUpdatePlan(packages) {
181
- const plan = [];
182
- const info = await this.detectCliInstall();
183
- for (const pkg of packages) {
184
- if (pkg === 'slicejs-cli') {
185
- if (info.type === 'global') {
186
- plan.push({ package: pkg, target: 'global', command: 'npm install -g slicejs-cli@latest' });
187
- } else {
188
- plan.push({ package: pkg, target: 'project', command: 'npm install slicejs-cli@latest' });
189
- }
190
- } else if (pkg === 'slicejs-web-framework') {
191
- plan.push({ package: pkg, target: 'project', command: 'npm install slicejs-web-framework@latest' });
192
- } else {
193
- plan.push({ package: pkg, target: 'project', command: `npm install ${pkg}@latest` });
194
- }
195
- }
196
- return plan;
197
- }
198
-
199
- /**
200
- * Execute npm update command for a specific package
201
- */
202
- async updatePackage(packageName) {
203
- try {
204
- let installCmd = `npm install ${packageName}@latest`;
205
- let uninstallCmd = `npm uninstall ${packageName}`;
206
- let options = {};
207
-
208
- if (packageName === 'slicejs-cli') {
209
- const info = await this.detectCliInstall();
210
- if (info.type === 'global') {
211
- installCmd = `npm install -g slicejs-cli@latest`;
212
- uninstallCmd = `npm uninstall -g slicejs-cli`;
213
- } else {
214
- options.cwd = info.projectRoot || getProjectRoot(import.meta.url);
215
- }
216
- } else {
217
- options.cwd = getProjectRoot(import.meta.url);
218
- }
219
-
220
- // Try uninstall first (ignore failure)
221
- try {
222
- await execAsync(uninstallCmd, options);
223
- } catch {}
224
-
225
- const { stdout, stderr } = await execAsync(installCmd, options);
226
-
227
- return {
228
- success: true,
229
- packageName,
230
- stdout,
231
- stderr
232
- };
233
- } catch (error) {
234
- return {
235
- success: false,
236
- packageName,
237
- error: error.message
238
- };
239
- }
240
- }
241
-
242
- /**
243
- * Update multiple packages with progress indication
244
- */
245
- async installUpdates(packages) {
246
- const results = [];
247
-
248
- for (const packageName of packages) {
249
- const spinner = ora(`Updating ${packageName}...`).start();
250
-
251
- try {
252
- const result = await this.updatePackage(packageName);
253
-
254
- if (result.success) {
255
- spinner.succeed(`${packageName} updated successfully`);
256
- results.push({ packageName, success: true });
257
- } else {
258
- spinner.fail(`Error updating ${packageName}`);
259
- Print.error(`Details: ${result.error}`);
260
- results.push({ packageName, success: false, error: result.error });
261
- }
262
- } catch (error) {
263
- spinner.fail(`Error updating ${packageName}`);
264
- Print.error(`Details: ${error.message}`);
265
- results.push({ packageName, success: false, error: error.message });
266
- }
267
- }
268
-
269
- return results;
270
- }
271
-
272
- /**
273
- * Main method to check and prompt for updates
274
- */
275
- async checkAndPromptUpdates(options = {}) {
276
- const spinner = ora('Checking for updates...').start();
277
-
278
- try {
279
- const updateInfo = await this.checkForUpdates();
280
- spinner.stop();
281
-
282
- if (!updateInfo) {
283
- Print.error('Could not check for updates. Verify your internet connection.');
284
- return false;
285
- }
286
-
287
- if (updateInfo.allCurrent) {
288
- Print.success('✅ All components are up to date!');
289
- return true;
290
- }
291
-
292
- if (!updateInfo.hasUpdates) {
293
- Print.success('✅ All components are up to date!');
294
- return true;
295
- }
296
-
297
- // Display available updates
298
- this.displayUpdates(updateInfo);
299
-
300
- // Get packages to update
301
- const packagesToUpdate = await this.promptForUpdates(updateInfo, options);
302
-
303
- if (!packagesToUpdate || packagesToUpdate.length === 0) {
304
- Print.info('No packages selected for update.');
305
- return false;
306
- }
307
-
308
- // Show plan and confirm installation if not auto-confirmed
309
- let plan = await this.buildUpdatePlan(packagesToUpdate);
310
- console.log('');
311
- Print.info('🧭 Update plan:');
312
- plan.forEach(item => {
313
- const where = item.target === 'global' ? 'GLOBAL' : 'PROJECT';
314
- console.log(` • ${item.package} → ${where}`);
315
- console.log(` ${item.command}`);
316
- });
317
- console.log('');
318
-
319
- const cliInfo = await this.detectCliInstall();
320
- if (cliInfo.type === 'global' && !packagesToUpdate.includes('slicejs-cli')) {
321
- if (!options.yes && !options.cli) {
322
- const { addCli } = await inquirer.prompt([
323
- {
324
- type: 'confirm',
325
- name: 'addCli',
326
- message: 'Global CLI detected. Add the global CLI update to the plan?',
327
- default: true
328
- }
329
- ]);
330
- if (addCli) {
331
- packagesToUpdate.push('slicejs-cli');
332
- plan = await this.buildUpdatePlan(packagesToUpdate);
333
- console.log('');
334
- Print.info('🧭 Updated plan:');
335
- plan.forEach(item => {
336
- const where = item.target === 'global' ? 'GLOBAL' : 'PROJECT';
337
- console.log(` • ${item.package} → ${where}`);
338
- console.log(` ${item.command}`);
339
- });
340
- console.log('');
341
- }
342
- } else {
343
- Print.warning('Global CLI detected. It is recommended to update slicejs-cli globally to keep aligned with the framework.');
344
- console.log(' Suggestion: npm install -g slicejs-cli@latest');
345
- console.log('');
346
- }
347
- }
348
-
349
- if (!options.yes && !options.cli && !options.framework) {
350
- const { confirm } = await inquirer.prompt([
351
- {
352
- type: 'confirm',
353
- name: 'confirm',
354
- message: 'Do you want to proceed with the update according to the plan shown?',
355
- default: true
356
- }
357
- ]);
358
-
359
- if (!confirm) {
360
- Print.info('Update cancelled.');
361
- return false;
362
- }
363
- }
364
-
365
- console.log(''); // Line break
366
- Print.info('📥 Installing updates...');
367
- console.log('');
368
-
369
- // Install updates
370
- const results = await this.installUpdates(packagesToUpdate);
371
-
372
- // Summary
373
- console.log('');
374
- const successCount = results.filter(r => r.success).length;
375
- const failCount = results.filter(r => !r.success).length;
376
-
377
- if (failCount === 0) {
378
- Print.success(`✅ ${successCount} package(s) updated successfully!`);
379
- } else {
380
- Print.warning(`⚠️ ${successCount} successful, ${failCount} failed`);
381
- }
382
-
383
- if (successCount > 0) {
384
- console.log('');
385
- Print.info('💡 It is recommended to restart the development server if it is running.');
386
- }
387
-
388
- const frameworkUpdated = results.find(r => r.packageName === 'slicejs-web-framework' && r.success);
389
- if (frameworkUpdated) {
390
- await this.updateApiIndexIfNeeded(options);
391
- }
392
-
393
- return failCount === 0;
394
-
395
- } catch (error) {
396
- spinner.stop();
397
- Print.error(`Error during update: ${error.message}`);
398
- return false;
399
- }
400
- }
401
-
402
- async updateApiIndexIfNeeded(options = {}) {
403
- try {
404
- const projectRoot = getProjectRoot(import.meta.url);
405
- const projectApiPath = path.join(projectRoot, 'api', 'index.js');
406
- const frameworkApiPath = path.join(projectRoot, 'node_modules', 'slicejs-web-framework', 'api', 'index.js');
407
-
408
- if (!await fs.pathExists(projectApiPath) || !await fs.pathExists(frameworkApiPath)) {
409
- return;
410
- }
411
-
412
- const projectContent = await fs.readFile(projectApiPath, 'utf-8');
413
- const frameworkContent = await fs.readFile(frameworkApiPath, 'utf-8');
414
-
415
- if (projectContent === frameworkContent) {
416
- return;
417
- }
418
-
419
- Print.warning('⚠️ Detected changes in framework api/index.js.');
420
-
421
- let confirmUpdate = options.yes === true;
422
- if (!confirmUpdate) {
423
- const answers = await inquirer.prompt([
424
- {
425
- type: 'confirm',
426
- name: 'confirm',
427
- message: 'Update your project api/index.js to the latest framework version?',
428
- default: false
429
- }
430
- ]);
431
- confirmUpdate = answers.confirm;
432
- }
433
-
434
- if (!confirmUpdate) {
435
- Print.info('Skipping api/index.js update.');
436
- return;
437
- }
438
-
439
- const backupPath = `${projectApiPath}.bak`;
440
- await fs.copy(projectApiPath, backupPath);
441
- await fs.writeFile(projectApiPath, frameworkContent, 'utf-8');
442
- Print.success('✅ api/index.js updated from framework template.');
443
- Print.info(`Backup created at ${backupPath}`);
444
- } catch (error) {
445
- Print.error(`Failed to update api/index.js: ${error.message}`);
446
- }
447
- }
448
- }
449
-
450
- // Singleton instance
451
- const updateManager = new UpdateManager();
452
-
453
- export default updateManager;
1
+ // commands/utils/updateManager.js
2
+
3
+ import { exec } from "child_process";
4
+ import { promisify } from "util";
5
+ import inquirer from "inquirer";
6
+ import ora from "ora";
7
+ import Print from "../Print.js";
8
+ import versionChecker from "./VersionChecker.js";
9
+ import { getProjectRoot } from "../utils/PathHelper.js";
10
+ import path from "path";
11
+ import { fileURLToPath } from "url";
12
+ import fs from "fs-extra";
13
+
14
+ const execAsync = promisify(exec);
15
+
16
+ export class UpdateManager {
17
+ constructor() {
18
+ this.packagesToUpdate = [];
19
+ }
20
+
21
+ async detectCliInstall() {
22
+ try {
23
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
24
+ const cliRoot = path.join(moduleDir, '../../');
25
+ const projectRoot = getProjectRoot(import.meta.url);
26
+ let globalPrefix = '';
27
+ try {
28
+ const { stdout } = await execAsync('npm config get prefix');
29
+ globalPrefix = stdout.toString().trim();
30
+ } catch {}
31
+ const localNodeModules = path.join(projectRoot, 'node_modules');
32
+ const globalNodeModules = globalPrefix ? path.join(globalPrefix, 'node_modules') : '';
33
+
34
+ if (cliRoot.startsWith(localNodeModules)) {
35
+ return { type: 'local', cliRoot, projectRoot, globalPrefix };
36
+ }
37
+ if (globalNodeModules && cliRoot.startsWith(globalNodeModules)) {
38
+ return { type: 'global', cliRoot, projectRoot, globalPrefix };
39
+ }
40
+ return { type: 'unknown', cliRoot, projectRoot, globalPrefix };
41
+ } catch (error) {
42
+ return { type: 'unknown' };
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Check for available updates and return structured info
48
+ */
49
+ async checkForUpdates(options = {}) {
50
+ const { silentErrors = false } = options;
51
+
52
+ try {
53
+ const updateInfo = await versionChecker.checkForUpdates(true); // Silent mode
54
+
55
+ if (!updateInfo) {
56
+ return null;
57
+ }
58
+
59
+ const updates = [];
60
+
61
+ if (updateInfo.cli.status === 'outdated') {
62
+ updates.push({
63
+ name: 'slicejs-cli',
64
+ displayName: 'Slice.js CLI',
65
+ current: updateInfo.cli.current,
66
+ latest: updateInfo.cli.latest,
67
+ type: 'cli'
68
+ });
69
+ }
70
+
71
+ if (updateInfo.framework.status === 'outdated') {
72
+ updates.push({
73
+ name: 'slicejs-web-framework',
74
+ displayName: 'Slice.js Framework',
75
+ current: updateInfo.framework.current,
76
+ latest: updateInfo.framework.latest,
77
+ type: 'framework'
78
+ });
79
+ }
80
+
81
+ return {
82
+ hasUpdates: updates.length > 0,
83
+ updates,
84
+ allCurrent: updateInfo.cli.status === 'current' && updateInfo.framework.status === 'current'
85
+ };
86
+ } catch (error) {
87
+ if (!silentErrors) {
88
+ Print.error(`Checking for updates: ${error.message}`);
89
+ }
90
+ return null;
91
+ }
92
+ }
93
+
94
+ async notifyAvailableUpdates() {
95
+ const updateInfo = await this.checkForUpdates({ silentErrors: true });
96
+
97
+ if (!updateInfo || !updateInfo.hasUpdates) {
98
+ return false;
99
+ }
100
+
101
+ this.displayUpdates(updateInfo);
102
+ Print.info("Run 'slice update' to install updates when convenient.");
103
+ return true;
104
+ }
105
+
106
+ /**
107
+ * Display available updates in a formatted way
108
+ */
109
+ displayUpdates(updateInfo) {
110
+ if (!updateInfo || !updateInfo.hasUpdates) {
111
+ return;
112
+ }
113
+
114
+ console.log('');
115
+ Print.warning('📦 Available Updates:');
116
+ console.log('');
117
+
118
+ updateInfo.updates.forEach(pkg => {
119
+ console.log(` ${pkg.type === 'cli' ? '🔧' : '⚡'} ${pkg.displayName}`);
120
+ console.log(` ${pkg.current} → ${pkg.latest}`);
121
+ });
122
+
123
+ console.log('');
124
+ console.log(' 📚 Changelog: https://github.com/VKneider/slice.js/releases');
125
+ console.log('');
126
+ }
127
+
128
+ /**
129
+ * Prompt user to select which packages to update
130
+ */
131
+ async promptForUpdates(updateInfo, options = {}) {
132
+ if (!updateInfo || !updateInfo.hasUpdates) {
133
+ return [];
134
+ }
135
+
136
+ // If --yes flag is set, return all updates
137
+ if (options.yes) {
138
+ return updateInfo.updates.map(pkg => pkg.name);
139
+ }
140
+
141
+ // If specific package flags are set
142
+ if (options.cli || options.framework) {
143
+ const selected = [];
144
+ if (options.cli) {
145
+ const cliUpdate = updateInfo.updates.find(pkg => pkg.type === 'cli');
146
+ if (cliUpdate) selected.push(cliUpdate.name);
147
+ }
148
+ if (options.framework) {
149
+ const frameworkUpdate = updateInfo.updates.find(pkg => pkg.type === 'framework');
150
+ if (frameworkUpdate) selected.push(frameworkUpdate.name);
151
+ }
152
+ return selected;
153
+ }
154
+
155
+ // Interactive selection
156
+ const choices = updateInfo.updates.map(pkg => ({
157
+ name: `${pkg.displayName} (${pkg.current} → ${pkg.latest})`,
158
+ value: pkg.name,
159
+ checked: true
160
+ }));
161
+
162
+ const answers = await inquirer.prompt([
163
+ {
164
+ type: 'checkbox',
165
+ name: 'packages',
166
+ message: 'Which packages do you want to update?',
167
+ choices,
168
+ validate: (answer) => {
169
+ if (answer.length === 0) {
170
+ return 'You must select at least one package';
171
+ }
172
+ return true;
173
+ }
174
+ }
175
+ ]);
176
+
177
+ return answers.packages;
178
+ }
179
+
180
+ async buildUpdatePlan(packages) {
181
+ const plan = [];
182
+ const info = await this.detectCliInstall();
183
+ for (const pkg of packages) {
184
+ if (pkg === 'slicejs-cli') {
185
+ if (info.type === 'global') {
186
+ plan.push({ package: pkg, target: 'global', command: 'npm install -g slicejs-cli@latest' });
187
+ } else {
188
+ plan.push({ package: pkg, target: 'project', command: 'npm install slicejs-cli@latest' });
189
+ }
190
+ } else if (pkg === 'slicejs-web-framework') {
191
+ plan.push({ package: pkg, target: 'project', command: 'npm install slicejs-web-framework@latest' });
192
+ } else {
193
+ plan.push({ package: pkg, target: 'project', command: `npm install ${pkg}@latest` });
194
+ }
195
+ }
196
+ return plan;
197
+ }
198
+
199
+ /**
200
+ * Execute npm update command for a specific package
201
+ */
202
+ async updatePackage(packageName) {
203
+ try {
204
+ let installCmd = `npm install ${packageName}@latest`;
205
+ let uninstallCmd = `npm uninstall ${packageName}`;
206
+ let options = {};
207
+
208
+ if (packageName === 'slicejs-cli') {
209
+ const info = await this.detectCliInstall();
210
+ if (info.type === 'global') {
211
+ installCmd = `npm install -g slicejs-cli@latest`;
212
+ uninstallCmd = `npm uninstall -g slicejs-cli`;
213
+ } else {
214
+ options.cwd = info.projectRoot || getProjectRoot(import.meta.url);
215
+ }
216
+ } else {
217
+ options.cwd = getProjectRoot(import.meta.url);
218
+ }
219
+
220
+ // Try uninstall first (ignore failure)
221
+ try {
222
+ await execAsync(uninstallCmd, options);
223
+ } catch {}
224
+
225
+ const { stdout, stderr } = await execAsync(installCmd, options);
226
+
227
+ return {
228
+ success: true,
229
+ packageName,
230
+ stdout,
231
+ stderr
232
+ };
233
+ } catch (error) {
234
+ return {
235
+ success: false,
236
+ packageName,
237
+ error: error.message
238
+ };
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Update multiple packages with progress indication
244
+ */
245
+ async installUpdates(packages) {
246
+ const results = [];
247
+
248
+ for (const packageName of packages) {
249
+ const spinner = ora(`Updating ${packageName}...`).start();
250
+
251
+ try {
252
+ const result = await this.updatePackage(packageName);
253
+
254
+ if (result.success) {
255
+ spinner.succeed(`${packageName} updated successfully`);
256
+ results.push({ packageName, success: true });
257
+ } else {
258
+ spinner.fail(`Error updating ${packageName}`);
259
+ Print.error(`Details: ${result.error}`);
260
+ results.push({ packageName, success: false, error: result.error });
261
+ }
262
+ } catch (error) {
263
+ spinner.fail(`Error updating ${packageName}`);
264
+ Print.error(`Details: ${error.message}`);
265
+ results.push({ packageName, success: false, error: error.message });
266
+ }
267
+ }
268
+
269
+ return results;
270
+ }
271
+
272
+ /**
273
+ * Main method to check and prompt for updates
274
+ */
275
+ async checkAndPromptUpdates(options = {}) {
276
+ const spinner = ora('Checking for updates...').start();
277
+
278
+ try {
279
+ const updateInfo = await this.checkForUpdates();
280
+ spinner.stop();
281
+
282
+ if (!updateInfo) {
283
+ Print.error('Could not check for updates. Verify your internet connection.');
284
+ return false;
285
+ }
286
+
287
+ if (updateInfo.allCurrent) {
288
+ Print.success('✅ All components are up to date!');
289
+ return true;
290
+ }
291
+
292
+ if (!updateInfo.hasUpdates) {
293
+ Print.success('✅ All components are up to date!');
294
+ return true;
295
+ }
296
+
297
+ // Display available updates
298
+ this.displayUpdates(updateInfo);
299
+
300
+ // Get packages to update
301
+ const packagesToUpdate = await this.promptForUpdates(updateInfo, options);
302
+
303
+ if (!packagesToUpdate || packagesToUpdate.length === 0) {
304
+ Print.info('No packages selected for update.');
305
+ return false;
306
+ }
307
+
308
+ // Show plan and confirm installation if not auto-confirmed
309
+ let plan = await this.buildUpdatePlan(packagesToUpdate);
310
+ console.log('');
311
+ Print.info('🧭 Update plan:');
312
+ plan.forEach(item => {
313
+ const where = item.target === 'global' ? 'GLOBAL' : 'PROJECT';
314
+ console.log(` • ${item.package} → ${where}`);
315
+ console.log(` ${item.command}`);
316
+ });
317
+ console.log('');
318
+
319
+ const cliInfo = await this.detectCliInstall();
320
+ if (cliInfo.type === 'global' && !packagesToUpdate.includes('slicejs-cli')) {
321
+ if (!options.yes && !options.cli) {
322
+ const { addCli } = await inquirer.prompt([
323
+ {
324
+ type: 'confirm',
325
+ name: 'addCli',
326
+ message: 'Global CLI detected. Add the global CLI update to the plan?',
327
+ default: true
328
+ }
329
+ ]);
330
+ if (addCli) {
331
+ packagesToUpdate.push('slicejs-cli');
332
+ plan = await this.buildUpdatePlan(packagesToUpdate);
333
+ console.log('');
334
+ Print.info('🧭 Updated plan:');
335
+ plan.forEach(item => {
336
+ const where = item.target === 'global' ? 'GLOBAL' : 'PROJECT';
337
+ console.log(` • ${item.package} → ${where}`);
338
+ console.log(` ${item.command}`);
339
+ });
340
+ console.log('');
341
+ }
342
+ } else {
343
+ Print.warning('Global CLI detected. It is recommended to update slicejs-cli globally to keep aligned with the framework.');
344
+ console.log(' Suggestion: npm install -g slicejs-cli@latest');
345
+ console.log('');
346
+ }
347
+ }
348
+
349
+ if (!options.yes && !options.cli && !options.framework) {
350
+ const { confirm } = await inquirer.prompt([
351
+ {
352
+ type: 'confirm',
353
+ name: 'confirm',
354
+ message: 'Do you want to proceed with the update according to the plan shown?',
355
+ default: true
356
+ }
357
+ ]);
358
+
359
+ if (!confirm) {
360
+ Print.info('Update cancelled.');
361
+ return false;
362
+ }
363
+ }
364
+
365
+ console.log(''); // Line break
366
+ Print.info('📥 Installing updates...');
367
+ console.log('');
368
+
369
+ // Install updates
370
+ const results = await this.installUpdates(packagesToUpdate);
371
+
372
+ // Summary
373
+ console.log('');
374
+ const successCount = results.filter(r => r.success).length;
375
+ const failCount = results.filter(r => !r.success).length;
376
+
377
+ if (failCount === 0) {
378
+ Print.success(`✅ ${successCount} package(s) updated successfully!`);
379
+ } else {
380
+ Print.warning(`⚠️ ${successCount} successful, ${failCount} failed`);
381
+ }
382
+
383
+ if (successCount > 0) {
384
+ console.log('');
385
+ Print.info('💡 It is recommended to restart the development server if it is running.');
386
+ }
387
+
388
+ const frameworkUpdated = results.find(r => r.packageName === 'slicejs-web-framework' && r.success);
389
+ if (frameworkUpdated) {
390
+ await this.updateApiIndexIfNeeded(options);
391
+ }
392
+
393
+ return failCount === 0;
394
+
395
+ } catch (error) {
396
+ spinner.stop();
397
+ Print.error(`Error during update: ${error.message}`);
398
+ return false;
399
+ }
400
+ }
401
+
402
+ async updateApiIndexIfNeeded(options = {}) {
403
+ try {
404
+ const projectRoot = getProjectRoot(import.meta.url);
405
+ const projectApiPath = path.join(projectRoot, 'api', 'index.js');
406
+ const frameworkApiPath = path.join(projectRoot, 'node_modules', 'slicejs-web-framework', 'api', 'index.js');
407
+
408
+ if (!await fs.pathExists(projectApiPath) || !await fs.pathExists(frameworkApiPath)) {
409
+ return;
410
+ }
411
+
412
+ const projectContent = await fs.readFile(projectApiPath, 'utf-8');
413
+ const frameworkContent = await fs.readFile(frameworkApiPath, 'utf-8');
414
+
415
+ if (projectContent === frameworkContent) {
416
+ return;
417
+ }
418
+
419
+ Print.warning('⚠️ Detected changes in framework api/index.js.');
420
+
421
+ let confirmUpdate = options.yes === true;
422
+ if (!confirmUpdate) {
423
+ const answers = await inquirer.prompt([
424
+ {
425
+ type: 'confirm',
426
+ name: 'confirm',
427
+ message: 'Update your project api/index.js to the latest framework version?',
428
+ default: false
429
+ }
430
+ ]);
431
+ confirmUpdate = answers.confirm;
432
+ }
433
+
434
+ if (!confirmUpdate) {
435
+ Print.info('Skipping api/index.js update.');
436
+ return;
437
+ }
438
+
439
+ const backupPath = `${projectApiPath}.bak`;
440
+ await fs.copy(projectApiPath, backupPath);
441
+ await fs.writeFile(projectApiPath, frameworkContent, 'utf-8');
442
+ Print.success('✅ api/index.js updated from framework template.');
443
+ Print.info(`Backup created at ${backupPath}`);
444
+ } catch (error) {
445
+ Print.error(`Failed to update api/index.js: ${error.message}`);
446
+ }
447
+ }
448
+ }
449
+
450
+ // Singleton instance
451
+ const updateManager = new UpdateManager();
452
+
453
+ export default updateManager;