slicejs-web-framework 3.4.3 → 3.4.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.
Files changed (107) hide show
  1. package/.opencode/opencode.json +14 -14
  2. package/Slice/Components/Structural/ContextManager/ContextManager.js +422 -422
  3. package/Slice/Components/Structural/ContextManager/ContextManagerDebugger.js +468 -468
  4. package/Slice/Components/Structural/Controller/Controller.js +1271 -1198
  5. package/Slice/Components/Structural/Debugger/Debugger.js +1503 -1503
  6. package/Slice/Components/Structural/EventManager/EventManager.js +366 -366
  7. package/Slice/Components/Structural/Logger/Logger.js +225 -225
  8. package/Slice/Components/Structural/Router/Router.js +775 -775
  9. package/Slice/Components/Structural/StylesManager/StylesManager.js +82 -82
  10. package/Slice/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +106 -106
  11. package/Slice/Slice.js +650 -627
  12. package/Slice/tests/build-singleton.test.js +244 -244
  13. package/Slice/tests/bundle-v2-runtime-contract.test.js +738 -738
  14. package/Slice/tests/context-patch-use.test.js +95 -95
  15. package/Slice/tests/fixtures/real-runtime-loader.mjs +21 -21
  16. package/Slice/tests/public-env-typed-accessors.test.js +66 -66
  17. package/api/index.js +281 -281
  18. package/api/utils/publicEnvResolver.js +123 -123
  19. package/package.json +44 -44
  20. package/slicejs-cli/.github/workflows/ci.yml +43 -0
  21. package/slicejs-cli/LICENSE +21 -0
  22. package/slicejs-cli/assets/Slice.js-logo.png +0 -0
  23. package/slicejs-cli/client.js +740 -0
  24. package/slicejs-cli/commands/Print.js +164 -0
  25. package/slicejs-cli/commands/Validations.js +92 -0
  26. package/slicejs-cli/commands/build/build.js +40 -0
  27. package/slicejs-cli/commands/buildProduction/buildProduction.js +577 -0
  28. package/slicejs-cli/commands/bundle/bundle.js +236 -0
  29. package/slicejs-cli/commands/createComponent/VisualComponentTemplate.js +55 -0
  30. package/slicejs-cli/commands/createComponent/createComponent.js +128 -0
  31. package/slicejs-cli/commands/deleteComponent/deleteComponent.js +81 -0
  32. package/slicejs-cli/commands/doctor/doctor.js +516 -0
  33. package/slicejs-cli/commands/getComponent/getComponent.js +695 -0
  34. package/slicejs-cli/commands/init/init.js +473 -0
  35. package/slicejs-cli/commands/listComponents/listComponents.js +172 -0
  36. package/slicejs-cli/commands/startServer/startServer.js +240 -0
  37. package/slicejs-cli/commands/startServer/watchServer.js +66 -0
  38. package/slicejs-cli/commands/types/types.js +583 -0
  39. package/slicejs-cli/commands/utils/LocalCliDelegation.js +53 -0
  40. package/slicejs-cli/commands/utils/PackageManager.js +148 -0
  41. package/slicejs-cli/commands/utils/PathHelper.js +75 -0
  42. package/slicejs-cli/commands/utils/VersionChecker.js +169 -0
  43. package/slicejs-cli/commands/utils/bundling/BundleGenerator.js +2525 -0
  44. package/slicejs-cli/commands/utils/bundling/DependencyAnalyzer.js +933 -0
  45. package/slicejs-cli/commands/utils/loadConfig.js +31 -0
  46. package/slicejs-cli/commands/utils/sliceScripts.js +48 -0
  47. package/slicejs-cli/commands/utils/updateManager.js +471 -0
  48. package/slicejs-cli/package.json +71 -0
  49. package/slicejs-cli/playwright.config.js +51 -0
  50. package/slicejs-cli/pnpm-lock.yaml +1629 -0
  51. package/slicejs-cli/tests/build-command-integration.test.js +87 -0
  52. package/slicejs-cli/tests/build-mangling-e2e.test.js +290 -0
  53. package/slicejs-cli/tests/build-production-e2e.test.js +140 -0
  54. package/slicejs-cli/tests/builder-edge-cases.test.js +322 -0
  55. package/slicejs-cli/tests/bundle-generate-e2e.test.js +115 -0
  56. package/slicejs-cli/tests/bundle-generator.test.js +691 -0
  57. package/slicejs-cli/tests/bundle-v2-register-output.test.js +470 -0
  58. package/slicejs-cli/tests/bundling-dependency-edges.test.js +127 -0
  59. package/slicejs-cli/tests/bundling-imports-unit.test.js +267 -0
  60. package/slicejs-cli/tests/client-launcher-contract.test.js +211 -0
  61. package/slicejs-cli/tests/commands-component-crud.test.js +128 -0
  62. package/slicejs-cli/tests/commands-doctor.test.js +152 -0
  63. package/slicejs-cli/tests/commands-version-checker.test.js +37 -0
  64. package/slicejs-cli/tests/component-registry-parse.test.js +34 -0
  65. package/slicejs-cli/tests/dependency-analyzer.test.js +24 -0
  66. package/slicejs-cli/tests/e2e/bundles.spec.js +91 -0
  67. package/slicejs-cli/tests/e2e/dependency-scenarios.spec.js +56 -0
  68. package/slicejs-cli/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +136 -0
  69. package/slicejs-cli/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +149 -0
  70. package/slicejs-cli/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +45 -0
  71. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.css +106 -0
  72. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.html +5 -0
  73. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.js +158 -0
  74. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Link/Link.js +33 -0
  75. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.css +56 -0
  76. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.html +83 -0
  77. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.js +164 -0
  78. package/slicejs-cli/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +167 -0
  79. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +116 -0
  80. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +44 -0
  81. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +180 -0
  82. package/slicejs-cli/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +20 -0
  83. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Route/Route.js +181 -0
  84. package/slicejs-cli/tests/e2e/fixtures/components/registry.json +12 -0
  85. package/slicejs-cli/tests/e2e/fixtures/vendor-components.mjs +65 -0
  86. package/slicejs-cli/tests/e2e/navigation.spec.js +44 -0
  87. package/slicejs-cli/tests/e2e/render.spec.js +34 -0
  88. package/slicejs-cli/tests/e2e/serve.mjs +264 -0
  89. package/slicejs-cli/tests/e2e/shared-deps.spec.js +61 -0
  90. package/slicejs-cli/tests/e2e/unminified.spec.js +33 -0
  91. package/slicejs-cli/tests/e2e-serve.test.js +148 -0
  92. package/slicejs-cli/tests/fixtures/components.js +8 -0
  93. package/slicejs-cli/tests/fixtures/sliceConfig.json +74 -0
  94. package/slicejs-cli/tests/getcomponent.test.js +435 -0
  95. package/slicejs-cli/tests/helpers/setup.js +102 -0
  96. package/slicejs-cli/tests/init-command-contract.test.js +101 -0
  97. package/slicejs-cli/tests/init-e2e.test.js +82 -0
  98. package/slicejs-cli/tests/init-project-isolation.test.js +98 -0
  99. package/slicejs-cli/tests/local-cli-delegation.test.js +81 -0
  100. package/slicejs-cli/tests/package-manager.test.js +127 -0
  101. package/slicejs-cli/tests/path-helper.test.js +206 -0
  102. package/slicejs-cli/tests/perf-budget.test.js +84 -0
  103. package/slicejs-cli/tests/postinstall-command.test.js +103 -0
  104. package/slicejs-cli/tests/property-mangling-regression.test.js +182 -0
  105. package/slicejs-cli/tests/types-breakage.test.js +491 -0
  106. package/slicejs-cli/tests/types-generator-errors.test.js +361 -0
  107. package/slicejs-cli/tests/types-generator.test.js +348 -0
@@ -0,0 +1,31 @@
1
+ import fs from 'fs-extra';
2
+ import { getConfigPath } from './PathHelper.js';
3
+ import Print from '../Print.js';
4
+
5
+ export async function loadConfig(moduleUrl) {
6
+ try {
7
+ const configPath = getConfigPath(moduleUrl);
8
+ if (!await fs.pathExists(configPath)) {
9
+ return null;
10
+ }
11
+ const rawData = await fs.readFile(configPath, 'utf-8');
12
+ return JSON.parse(rawData);
13
+ } catch (error) {
14
+ Print.error(`Error loading configuration: ${error.message}`);
15
+ return null;
16
+ }
17
+ }
18
+
19
+ export function loadConfigSync(moduleUrl) {
20
+ try {
21
+ const configPath = getConfigPath(moduleUrl);
22
+ if (!fs.existsSync(configPath)) {
23
+ return null;
24
+ }
25
+ const rawData = fs.readFileSync(configPath, 'utf-8');
26
+ return JSON.parse(rawData);
27
+ } catch (error) {
28
+ Print.error(`Error loading configuration: ${error.message}`);
29
+ return null;
30
+ }
31
+ }
@@ -0,0 +1,48 @@
1
+ // commands/utils/sliceScripts.js
2
+ //
3
+ // Single source of truth for the slice:* package scripts configured by the CLI.
4
+ // Used by the `slice postinstall` command in
5
+ // client.js, and `slice init` — so the three can never drift apart (they did:
6
+ // client.js was missing slice:types, and none of them had slice:build).
7
+
8
+ import fs from 'fs';
9
+
10
+ /**
11
+ * Reads a project's package.json and verifies it contains all SLICE_SCRIPTS.
12
+ * Returns { missing: string[] } where each entry is the script name not found.
13
+ */
14
+ export function verifySliceScriptsInPackageJson(pkgPath) {
15
+ const missing = [];
16
+ try {
17
+ if (!fs.existsSync(pkgPath)) {
18
+ return { missing: Object.keys(SLICE_SCRIPTS) };
19
+ }
20
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
21
+ const scripts = pkg.scripts || {};
22
+ for (const script of Object.keys(SLICE_SCRIPTS)) {
23
+ if (!scripts[script]) {
24
+ missing.push(script);
25
+ }
26
+ }
27
+ } catch {
28
+ return { missing: Object.keys(SLICE_SCRIPTS) };
29
+ }
30
+ return { missing };
31
+ }
32
+
33
+ export const SLICE_SCRIPTS = {
34
+ 'slice:init': 'node ./node_modules/slicejs-cli/client.js init',
35
+ 'slice:dev': 'node ./node_modules/slicejs-cli/client.js dev',
36
+ 'slice:build': 'node ./node_modules/slicejs-cli/client.js build',
37
+ 'slice:start': 'node ./node_modules/slicejs-cli/client.js start',
38
+ 'slice:create': 'node ./node_modules/slicejs-cli/client.js component create',
39
+ 'slice:list': 'node ./node_modules/slicejs-cli/client.js component list',
40
+ 'slice:delete': 'node ./node_modules/slicejs-cli/client.js component delete',
41
+ 'slice:get': 'node ./node_modules/slicejs-cli/client.js get',
42
+ 'slice:browse': 'node ./node_modules/slicejs-cli/client.js browse',
43
+ 'slice:sync': 'node ./node_modules/slicejs-cli/client.js sync',
44
+ 'slice:doctor': 'node ./node_modules/slicejs-cli/client.js doctor',
45
+ 'slice:version': 'node ./node_modules/slicejs-cli/client.js version',
46
+ 'slice:help': 'node ./node_modules/slicejs-cli/client.js --help',
47
+ 'slice:types': 'node ./node_modules/slicejs-cli/client.js types generate',
48
+ };
@@ -0,0 +1,471 @@
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, getApiPath, getPath } from "../utils/PathHelper.js";
10
+ import { resolvePackageManager, installCommand } from "../utils/PackageManager.js";
11
+ import path from "path";
12
+ import { fileURLToPath } from "url";
13
+ import fs from "fs-extra";
14
+
15
+ const execAsync = promisify(exec);
16
+
17
+ export class UpdateManager {
18
+ constructor() {
19
+ this.packagesToUpdate = [];
20
+ }
21
+
22
+ getPackageManager() {
23
+ if (!this._packageManager) {
24
+ this._packageManager = resolvePackageManager(getProjectRoot(import.meta.url)).name;
25
+ }
26
+ return this._packageManager;
27
+ }
28
+
29
+ async detectCliInstall() {
30
+ try {
31
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
32
+ const cliRoot = path.join(moduleDir, '../../');
33
+ const projectRoot = getProjectRoot(import.meta.url);
34
+ const packageManager = this.getPackageManager();
35
+ const localNodeModules = path.join(projectRoot, 'node_modules');
36
+
37
+ if (cliRoot.startsWith(localNodeModules)) {
38
+ return { type: 'local', cliRoot, projectRoot, packageManager };
39
+ }
40
+
41
+ // Global pnpm installs live under PNPM_HOME — `npm config get prefix`
42
+ // knows nothing about them (and npm may not even exist on the machine).
43
+ const pnpmHome = process.env.PNPM_HOME;
44
+ if (pnpmHome && cliRoot.startsWith(pnpmHome)) {
45
+ return { type: 'global', cliRoot, projectRoot, packageManager: 'pnpm' };
46
+ }
47
+
48
+ let globalPrefix = '';
49
+ try {
50
+ const { stdout } = await execAsync('npm config get prefix');
51
+ globalPrefix = stdout.toString().trim();
52
+ } catch {}
53
+ const globalNodeModules = globalPrefix ? path.join(globalPrefix, 'node_modules') : '';
54
+ if (globalNodeModules && cliRoot.startsWith(globalNodeModules)) {
55
+ return { type: 'global', cliRoot, projectRoot, packageManager: 'npm' };
56
+ }
57
+
58
+ return { type: 'unknown', cliRoot, projectRoot, packageManager };
59
+ } catch (error) {
60
+ return { type: 'unknown', packageManager: this.getPackageManager() };
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Check for available updates and return structured info
66
+ */
67
+ async checkForUpdates(options = {}) {
68
+ const { silentErrors = false } = options;
69
+
70
+ try {
71
+ const updateInfo = await versionChecker.checkForUpdates(true); // Silent mode
72
+
73
+ if (!updateInfo) {
74
+ return null;
75
+ }
76
+
77
+ const updates = [];
78
+
79
+ if (updateInfo.cli.status === 'outdated') {
80
+ updates.push({
81
+ name: 'slicejs-cli',
82
+ displayName: 'Slice.js CLI',
83
+ current: updateInfo.cli.current,
84
+ latest: updateInfo.cli.latest,
85
+ type: 'cli'
86
+ });
87
+ }
88
+
89
+ if (updateInfo.framework.status === 'outdated') {
90
+ updates.push({
91
+ name: 'slicejs-web-framework',
92
+ displayName: 'Slice.js Framework',
93
+ current: updateInfo.framework.current,
94
+ latest: updateInfo.framework.latest,
95
+ type: 'framework'
96
+ });
97
+ }
98
+
99
+ return {
100
+ hasUpdates: updates.length > 0,
101
+ updates,
102
+ allCurrent: updateInfo.cli.status === 'current' && updateInfo.framework.status === 'current'
103
+ };
104
+ } catch (error) {
105
+ if (!silentErrors) {
106
+ Print.error(`Checking for updates: ${error.message}`);
107
+ }
108
+ return null;
109
+ }
110
+ }
111
+
112
+ async notifyAvailableUpdates() {
113
+ const updateInfo = await this.checkForUpdates({ silentErrors: true });
114
+
115
+ if (!updateInfo || !updateInfo.hasUpdates) {
116
+ return false;
117
+ }
118
+
119
+ this.displayUpdates(updateInfo);
120
+ Print.info("Use your package manager to install updates: npm/pnpm update slicejs-cli slicejs-web-framework");
121
+ return true;
122
+ }
123
+
124
+ /**
125
+ * Display available updates in a formatted way
126
+ */
127
+ displayUpdates(updateInfo) {
128
+ if (!updateInfo || !updateInfo.hasUpdates) {
129
+ return;
130
+ }
131
+
132
+ console.log('');
133
+ Print.warning('📦 Available Updates:');
134
+ console.log('');
135
+
136
+ updateInfo.updates.forEach(pkg => {
137
+ console.log(` ${pkg.type === 'cli' ? '🔧' : '⚡'} ${pkg.displayName}`);
138
+ console.log(` ${pkg.current} → ${pkg.latest}`);
139
+ });
140
+
141
+ console.log('');
142
+ console.log(' 📚 Changelog: https://github.com/VKneider/slice.js/releases');
143
+ console.log('');
144
+ }
145
+
146
+ /**
147
+ * Prompt user to select which packages to update
148
+ */
149
+ async promptForUpdates(updateInfo, options = {}) {
150
+ if (!updateInfo || !updateInfo.hasUpdates) {
151
+ return [];
152
+ }
153
+
154
+ // If --yes flag is set, return all updates
155
+ if (options.yes) {
156
+ return updateInfo.updates.map(pkg => pkg.name);
157
+ }
158
+
159
+ // If specific package flags are set
160
+ if (options.cli || options.framework) {
161
+ const selected = [];
162
+ if (options.cli) {
163
+ const cliUpdate = updateInfo.updates.find(pkg => pkg.type === 'cli');
164
+ if (cliUpdate) selected.push(cliUpdate.name);
165
+ }
166
+ if (options.framework) {
167
+ const frameworkUpdate = updateInfo.updates.find(pkg => pkg.type === 'framework');
168
+ if (frameworkUpdate) selected.push(frameworkUpdate.name);
169
+ }
170
+ return selected;
171
+ }
172
+
173
+ // Interactive selection
174
+ const choices = updateInfo.updates.map(pkg => ({
175
+ name: `${pkg.displayName} (${pkg.current} → ${pkg.latest})`,
176
+ value: pkg.name,
177
+ checked: true
178
+ }));
179
+
180
+ const answers = await inquirer.prompt([
181
+ {
182
+ type: 'checkbox',
183
+ name: 'packages',
184
+ message: 'Which packages do you want to update?',
185
+ choices,
186
+ validate: (answer) => {
187
+ if (answer.length === 0) {
188
+ return 'You must select at least one package';
189
+ }
190
+ return true;
191
+ }
192
+ }
193
+ ]);
194
+
195
+ return answers.packages;
196
+ }
197
+
198
+ async buildUpdatePlan(packages) {
199
+ const plan = [];
200
+ const info = await this.detectCliInstall();
201
+ const pm = info.packageManager || this.getPackageManager();
202
+ for (const pkg of packages) {
203
+ if (pkg === 'slicejs-cli' && info.type === 'global') {
204
+ plan.push({ package: pkg, target: 'global', command: installCommand(pm, 'slicejs-cli@latest', { global: true }) });
205
+ } else {
206
+ plan.push({ package: pkg, target: 'project', command: installCommand(pm, `${pkg}@latest`) });
207
+ }
208
+ }
209
+ return plan;
210
+ }
211
+
212
+ /**
213
+ * Execute npm update command for a specific package
214
+ */
215
+ async updatePackage(packageName) {
216
+ try {
217
+ const pm = this.getPackageManager();
218
+ let installCmd = installCommand(pm, `${packageName}@latest`);
219
+ let options = {};
220
+
221
+ if (packageName === 'slicejs-cli') {
222
+ const info = await this.detectCliInstall();
223
+ if (info.type === 'global') {
224
+ const globalPm = info.packageManager || pm;
225
+ installCmd = installCommand(globalPm, 'slicejs-cli@latest', { global: true });
226
+ } else {
227
+ options.cwd = info.projectRoot || getProjectRoot(import.meta.url);
228
+ }
229
+ } else {
230
+ options.cwd = getProjectRoot(import.meta.url);
231
+ }
232
+
233
+ // Install directly — npm/pnpm upgrade in place. (We used to uninstall
234
+ // first, which left the project without the package whenever the
235
+ // subsequent install failed, e.g. offline or under a release-age policy.)
236
+ const { stdout, stderr } = await execAsync(installCmd, options);
237
+
238
+ return {
239
+ success: true,
240
+ packageName,
241
+ stdout,
242
+ stderr
243
+ };
244
+ } catch (error) {
245
+ return {
246
+ success: false,
247
+ packageName,
248
+ error: error.message
249
+ };
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Update multiple packages with progress indication
255
+ */
256
+ async installUpdates(packages) {
257
+ const results = [];
258
+
259
+ for (const packageName of packages) {
260
+ const spinner = ora(`Updating ${packageName}...`).start();
261
+
262
+ try {
263
+ const result = await this.updatePackage(packageName);
264
+
265
+ if (result.success) {
266
+ spinner.succeed(`${packageName} updated successfully`);
267
+ results.push({ packageName, success: true });
268
+ } else {
269
+ spinner.fail(`Error updating ${packageName}`);
270
+ Print.error(`Details: ${result.error}`);
271
+ results.push({ packageName, success: false, error: result.error });
272
+ }
273
+ } catch (error) {
274
+ spinner.fail(`Error updating ${packageName}`);
275
+ Print.error(`Details: ${error.message}`);
276
+ results.push({ packageName, success: false, error: error.message });
277
+ }
278
+ }
279
+
280
+ return results;
281
+ }
282
+
283
+ /**
284
+ * Main method to check and prompt for updates
285
+ */
286
+ async checkAndPromptUpdates(options = {}) {
287
+ const spinner = ora('Checking for updates...').start();
288
+
289
+ try {
290
+ const updateInfo = await this.checkForUpdates();
291
+ spinner.stop();
292
+
293
+ if (!updateInfo) {
294
+ Print.error('Could not check for updates. Verify your internet connection.');
295
+ return false;
296
+ }
297
+
298
+ if (updateInfo.allCurrent || !updateInfo.hasUpdates) {
299
+ Print.success('✅ All components are up to date!');
300
+ // --update-api works even when no package update runs.
301
+ if (options.updateApi) {
302
+ await this.updateApiIndexIfNeeded(options);
303
+ }
304
+ return true;
305
+ }
306
+
307
+ // Display available updates
308
+ this.displayUpdates(updateInfo);
309
+
310
+ // Get packages to update
311
+ const packagesToUpdate = await this.promptForUpdates(updateInfo, options);
312
+
313
+ if (!packagesToUpdate || packagesToUpdate.length === 0) {
314
+ Print.info('No packages selected for update.');
315
+ return false;
316
+ }
317
+
318
+ // Show plan and confirm installation if not auto-confirmed
319
+ let plan = await this.buildUpdatePlan(packagesToUpdate);
320
+ console.log('');
321
+ Print.info('🧭 Update plan:');
322
+ plan.forEach(item => {
323
+ const where = item.target === 'global' ? 'GLOBAL' : 'PROJECT';
324
+ console.log(` • ${item.package} → ${where}`);
325
+ console.log(` ${item.command}`);
326
+ });
327
+ console.log('');
328
+
329
+ const cliInfo = await this.detectCliInstall();
330
+ if (cliInfo.type === 'global' && !packagesToUpdate.includes('slicejs-cli')) {
331
+ if (!options.yes && !options.cli) {
332
+ const { addCli } = await inquirer.prompt([
333
+ {
334
+ type: 'confirm',
335
+ name: 'addCli',
336
+ message: 'Global CLI detected. Add the global CLI update to the plan?',
337
+ default: true
338
+ }
339
+ ]);
340
+ if (addCli) {
341
+ packagesToUpdate.push('slicejs-cli');
342
+ plan = await this.buildUpdatePlan(packagesToUpdate);
343
+ console.log('');
344
+ Print.info('🧭 Updated plan:');
345
+ plan.forEach(item => {
346
+ const where = item.target === 'global' ? 'GLOBAL' : 'PROJECT';
347
+ console.log(` • ${item.package} → ${where}`);
348
+ console.log(` ${item.command}`);
349
+ });
350
+ console.log('');
351
+ }
352
+ } else {
353
+ Print.warning('Global CLI detected. It is recommended to update slicejs-cli globally to keep aligned with the framework.');
354
+ console.log(` Suggestion: ${installCommand(cliInfo.packageManager || this.getPackageManager(), 'slicejs-cli@latest', { global: true })}`);
355
+ console.log('');
356
+ }
357
+ }
358
+
359
+ if (!options.yes && !options.cli && !options.framework) {
360
+ const { confirm } = await inquirer.prompt([
361
+ {
362
+ type: 'confirm',
363
+ name: 'confirm',
364
+ message: 'Do you want to proceed with the update according to the plan shown?',
365
+ default: true
366
+ }
367
+ ]);
368
+
369
+ if (!confirm) {
370
+ Print.info('Update cancelled.');
371
+ return false;
372
+ }
373
+ }
374
+
375
+ console.log(''); // Line break
376
+ Print.info('📥 Installing updates...');
377
+ console.log('');
378
+
379
+ // Install updates
380
+ const results = await this.installUpdates(packagesToUpdate);
381
+
382
+ // Summary
383
+ console.log('');
384
+ const successCount = results.filter(r => r.success).length;
385
+ const failCount = results.filter(r => !r.success).length;
386
+
387
+ if (failCount === 0) {
388
+ Print.success(`✅ ${successCount} package(s) updated successfully!`);
389
+ } else {
390
+ Print.warning(`⚠️ ${successCount} successful, ${failCount} failed`);
391
+ }
392
+
393
+ if (successCount > 0) {
394
+ console.log('');
395
+ Print.info('💡 It is recommended to restart the development server if it is running.');
396
+ }
397
+
398
+ const frameworkUpdated = results.find(r => r.packageName === 'slicejs-web-framework' && r.success);
399
+ if (frameworkUpdated || options.updateApi) {
400
+ await this.updateApiIndexIfNeeded(options);
401
+ }
402
+
403
+ return failCount === 0;
404
+
405
+ } catch (error) {
406
+ spinner.stop();
407
+ Print.error(`Error during update: ${error.message}`);
408
+ return false;
409
+ }
410
+ }
411
+
412
+ async updateApiIndexIfNeeded(options = {}) {
413
+ try {
414
+ const projectApiPath = getApiPath(import.meta.url, 'index.js');
415
+ const frameworkApiPath = getPath(import.meta.url, 'node_modules', 'slicejs-web-framework', 'api', 'index.js');
416
+
417
+ if (!await fs.pathExists(projectApiPath) || !await fs.pathExists(frameworkApiPath)) {
418
+ return;
419
+ }
420
+
421
+ const projectContent = await fs.readFile(projectApiPath, 'utf-8');
422
+ const frameworkContent = await fs.readFile(frameworkApiPath, 'utf-8');
423
+
424
+ if (projectContent === frameworkContent) {
425
+ return;
426
+ }
427
+
428
+ Print.warning('⚠️ Detected changes in framework api/index.js.');
429
+
430
+ // Overwriting api/index.js is opt-in: `--update-api` is the only way
431
+ // to auto-confirm. `-y/--yes` deliberately does NOT imply it — the
432
+ // project file may carry local changes, so a blanket "yes to updates"
433
+ // must not silently replace it.
434
+ let confirmUpdate = options.updateApi === true;
435
+ if (!confirmUpdate && options.yes === true) {
436
+ Print.info('Skipping api/index.js update (not updated by default).');
437
+ Print.info('Use --update-api to overwrite api/index.js from the framework template.');
438
+ return;
439
+ }
440
+ if (!confirmUpdate) {
441
+ const answers = await inquirer.prompt([
442
+ {
443
+ type: 'confirm',
444
+ name: 'confirm',
445
+ message: 'Update your project api/index.js to the latest framework version?',
446
+ default: false
447
+ }
448
+ ]);
449
+ confirmUpdate = answers.confirm;
450
+ }
451
+
452
+ if (!confirmUpdate) {
453
+ Print.info('Skipping api/index.js update.');
454
+ return;
455
+ }
456
+
457
+ const backupPath = `${projectApiPath}.bak`;
458
+ await fs.copy(projectApiPath, backupPath);
459
+ await fs.writeFile(projectApiPath, frameworkContent, 'utf-8');
460
+ Print.success('✅ api/index.js updated from framework template.');
461
+ Print.info(`Backup created at ${backupPath}`);
462
+ } catch (error) {
463
+ Print.error(`Failed to update api/index.js: ${error.message}`);
464
+ }
465
+ }
466
+ }
467
+
468
+ // Singleton instance
469
+ const updateManager = new UpdateManager();
470
+
471
+ export default updateManager;
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "slicejs-cli",
3
+ "version": "3.6.6",
4
+ "description": "Command client for developing web applications with Slice.js framework",
5
+ "main": "client.js",
6
+ "bin": {
7
+ "slice": "./client.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=20.0.0"
11
+ },
12
+ "files": [
13
+ "client.js",
14
+ "commands",
15
+ "assets"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/vkneider/slicejs-cli.git"
20
+ },
21
+ "scripts": {
22
+ "test": "node --test",
23
+ "slice:dev": "slice dev",
24
+ "slice:build": "slice build",
25
+ "slice:start": "slice start",
26
+ "slice:create": "slice component create",
27
+ "slice:list": "slice component list",
28
+ "slice:delete": "slice component delete",
29
+ "slice:init": "slice init",
30
+ "slice:get": "slice get",
31
+ "slice:browse": "slice browse",
32
+ "slice:sync": "slice sync",
33
+ "slice:version": "slice version",
34
+ "slice:types": "slice types generate",
35
+ "slice:doctor": "node ./node_modules/slicejs-cli/client.js doctor",
36
+ "slice:help": "node ./node_modules/slicejs-cli/client.js --help"
37
+ },
38
+ "keywords": [
39
+ "framework",
40
+ "web",
41
+ "client",
42
+ "cli",
43
+ "slice",
44
+ "slicejs",
45
+ "component",
46
+ "development server"
47
+ ],
48
+ "author": "vkneider",
49
+ "type": "module",
50
+ "packageManager": "pnpm@11.1.1",
51
+ "preferGlobal": false,
52
+ "license": "ISC",
53
+ "dependencies": {
54
+ "@babel/parser": "^7.28.5",
55
+ "@babel/traverse": "^7.28.5",
56
+ "chalk": "^5.6.2",
57
+ "chokidar": "^3.6.0",
58
+ "clean-css": "^5.3.3",
59
+ "cli-table3": "^0.6.5",
60
+ "commander": "^12.0.0",
61
+ "fs-extra": "^11.2.0",
62
+ "html-minifier-terser": "^7.2.0",
63
+ "inquirer": "^12.4.2",
64
+ "ora": "^8.2.0",
65
+ "slicejs-web-framework": "latest",
66
+ "terser": "^5.43.1"
67
+ },
68
+ "devDependencies": {
69
+ "@playwright/test": "^1.60.0"
70
+ }
71
+ }
@@ -0,0 +1,51 @@
1
+ import { defineConfig, devices } from '@playwright/test';
2
+
3
+ const PORT = process.env.E2E_PORT ? Number(process.env.E2E_PORT) : 3210;
4
+ const UNMIN_PORT = PORT + 4;
5
+ const BASE_URL = `http://127.0.0.1:${PORT}`;
6
+ const UNMIN_URL = `http://127.0.0.1:${UNMIN_PORT}`;
7
+
8
+ export default defineConfig({
9
+ testDir: './tests/e2e',
10
+ testMatch: '**/*.spec.js',
11
+ fullyParallel: false,
12
+ workers: 1,
13
+ timeout: 30_000,
14
+ expect: { timeout: 10_000 },
15
+ reporter: process.env.CI ? [['list'], ['html', { open: 'never' }]] : [['list']],
16
+ use: {
17
+ trace: 'retain-on-failure',
18
+ },
19
+ projects: [
20
+ {
21
+ name: 'chromium',
22
+ testIgnore: '**/unminified.spec.js',
23
+ use: { ...devices['Desktop Chrome'], baseURL: BASE_URL },
24
+ },
25
+ {
26
+ name: 'chromium-unminified',
27
+ testMatch: '**/unminified.spec.js',
28
+ use: { ...devices['Desktop Chrome'], baseURL: UNMIN_URL },
29
+ },
30
+ ],
31
+ webServer: [
32
+ {
33
+ command: 'node tests/e2e/serve.mjs',
34
+ url: `${BASE_URL}/slice-env.json`,
35
+ env: { E2E_PORT: String(PORT) },
36
+ timeout: 120_000,
37
+ reuseExistingServer: !process.env.CI,
38
+ stdout: 'pipe',
39
+ stderr: 'pipe',
40
+ },
41
+ {
42
+ command: 'node tests/e2e/serve.mjs',
43
+ url: `${UNMIN_URL}/slice-env.json`,
44
+ env: { E2E_PORT: String(UNMIN_PORT), E2E_MINIFY: 'false' },
45
+ timeout: 120_000,
46
+ reuseExistingServer: !process.env.CI,
47
+ stdout: 'pipe',
48
+ stderr: 'pipe',
49
+ },
50
+ ],
51
+ });