slicejs-cli 3.6.1 → 3.6.3

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.
@@ -19,12 +19,12 @@ const COMPONENTS_REGISTRY_URL = 'https://raw.githubusercontent.com/VKneider/slic
19
19
  */
20
20
  const loadConfig = () => sharedLoadConfig(import.meta.url);
21
21
 
22
- const fetchWithRetry = async (url, retries = 3, baseDelay = 500) => {
22
+ const fetchWithRetry = async (url, retries = 3, baseDelay = 500, binary = false) => {
23
23
  for (let attempt = 0; attempt <= retries; attempt++) {
24
24
  try {
25
25
  const response = await fetch(url);
26
26
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
27
- return await response.text();
27
+ return binary ? await response.arrayBuffer() : await response.text();
28
28
  } catch (e) {
29
29
  if (attempt === retries) throw e;
30
30
  const delay = baseDelay * Math.pow(2, attempt);
@@ -185,6 +185,9 @@ filterOfficialComponents(allComponents) {
185
185
  // Logical routing components only need JS
186
186
  if (['Route', 'MultiRoute', 'Link'].includes(name)) {
187
187
  files = [`${name}.js`];
188
+ } else if (name === 'Icon') {
189
+ // Icon component needs font files to render glyphs
190
+ files = [`${name}.js`, `${name}.html`, `${name}.css`, 'slc.eot', 'slc.woff2', 'slc.woff', 'slc.ttf', 'slc.svg'];
188
191
  } else {
189
192
  // Normal visual components need JS, HTML, CSS
190
193
  files = [`${name}.js`, `${name}.html`, `${name}.css`];
@@ -218,12 +221,18 @@ filterOfficialComponents(allComponents) {
218
221
  const total = component.files.length;
219
222
  let done = 0;
220
223
  const spinner = ora(`Downloading ${componentName} 0/${total}`).start();
224
+ const BINARY_EXTENSIONS = ['.eot', '.woff2', '.woff', '.ttf'];
221
225
  const worker = async (fileName) => {
222
226
  const url = `${DOCS_REPO_BASE_URL}/${category}/${componentName}/${fileName}`;
223
227
  const localPath = path.join(targetPath, fileName);
228
+ const isBinary = BINARY_EXTENSIONS.some(ext => fileName.endsWith(ext));
224
229
  try {
225
- const content = await fetchWithRetry(url);
226
- await fs.writeFile(localPath, content, 'utf8');
230
+ const content = await fetchWithRetry(url, 3, 500, isBinary);
231
+ if (isBinary) {
232
+ await fs.writeFile(localPath, Buffer.from(content));
233
+ } else {
234
+ await fs.writeFile(localPath, content, 'utf8');
235
+ }
227
236
  downloadedFiles.push(fileName);
228
237
  } catch (error) {
229
238
  Print.downloadError(fileName);
@@ -32,6 +32,16 @@ async function fetchLatestVersion(packageName) {
32
32
  }
33
33
  }
34
34
 
35
+ function getRunningCliVersion() {
36
+ try {
37
+ const cliRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
38
+ const cliPkg = fs.readJsonSync(path.join(cliRoot, 'package.json'));
39
+ return typeof cliPkg.version === 'string' ? cliPkg.version : null;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+
35
45
  async function ensurePnpmAllowBuilds(projectRoot) {
36
46
  const workspacePath = path.join(projectRoot, 'pnpm-workspace.yaml');
37
47
  const allowBuildLine = ' slicejs-cli: true';
@@ -152,6 +162,9 @@ export default async function initializeProject(options = {}) {
152
162
  let sliceBaseDir;
153
163
  try {
154
164
  latestVersion = await fetchLatestVersion('slicejs-web-framework');
165
+ const frameworkPackage = latestVersion
166
+ ? `slicejs-web-framework@${latestVersion}`
167
+ : 'slicejs-web-framework';
155
168
  const installedPkgPath = getPath(import.meta.url, 'node_modules', 'slicejs-web-framework', 'package.json');
156
169
  let installed = null;
157
170
  if (await fs.pathExists(installedPkgPath)) {
@@ -159,11 +172,7 @@ export default async function initializeProject(options = {}) {
159
172
  installed = pkg.version;
160
173
  }
161
174
  if (!installed || (latestVersion && installed !== latestVersion)) {
162
- // Install WITHOUT pinning an exact version: the package manager
163
- // resolves it under its own policies (e.g. pnpm minimumReleaseAge
164
- // quarantines versions younger than the configured age — pinning
165
- // the registry's freshest version would make resolution fail).
166
- execSync(installCommand(packageManager, 'slicejs-web-framework'), { cwd: projectRoot, stdio: 'inherit' });
175
+ execSync(installCommand(packageManager, frameworkPackage), { cwd: projectRoot, stdio: 'inherit' });
167
176
  }
168
177
  if (await fs.pathExists(installedPkgPath)) {
169
178
  const pkg = await fs.readJson(installedPkgPath);
@@ -194,13 +203,24 @@ export default async function initializeProject(options = {}) {
194
203
  const cliSpinner = ora('Installing slicejs-cli as devDependency...').start();
195
204
  try {
196
205
  const cliPkgPath = getPath(import.meta.url, 'node_modules', 'slicejs-cli', 'package.json');
197
- if (!(await fs.pathExists(cliPkgPath))) {
198
- execSync(installCommand(packageManager, 'slicejs-cli', { dev: true }), { cwd: projectRoot, stdio: 'inherit' });
206
+ const currentCliVersion = getRunningCliVersion();
207
+ const cliPackage = currentCliVersion
208
+ ? `slicejs-cli@${currentCliVersion}`
209
+ : 'slicejs-cli';
210
+
211
+ let installedCliVersion = null;
212
+ if (await fs.pathExists(cliPkgPath)) {
213
+ const pkg = await fs.readJson(cliPkgPath);
214
+ installedCliVersion = pkg.version;
215
+ }
216
+
217
+ if (!installedCliVersion || (currentCliVersion && installedCliVersion !== currentCliVersion)) {
218
+ execSync(installCommand(packageManager, cliPackage, { dev: true }), { cwd: projectRoot, stdio: 'inherit' });
199
219
  }
200
220
  cliSpinner.succeed('slicejs-cli installed locally');
201
221
  } catch (err) {
202
222
  cliSpinner.warn('Could not install slicejs-cli locally — scripts will use the global CLI');
203
- Print.info(`You can add it later with: ${installCommand(packageManager, 'slicejs-cli', { dev: true })}`);
223
+ Print.info(`You can add it later with: ${installCommand(packageManager, `slicejs-cli@${getRunningCliVersion() || 'latest'}`, { dev: true })}`);
204
224
  }
205
225
 
206
226
  // These derive from sliceBaseDir (which comes from npm install or fallback),
@@ -280,6 +300,7 @@ export default async function initializeProject(options = {}) {
280
300
  }
281
301
 
282
302
  // 3. DOWNLOAD ALL VISUAL COMPONENTS FROM OFFICIAL REPOSITORY
303
+ let visualResults = [];
283
304
  const componentsSpinner = ora('Loading component registry...').start();
284
305
  try {
285
306
  const registry = new ComponentRegistry();
@@ -287,25 +308,23 @@ export default async function initializeProject(options = {}) {
287
308
 
288
309
  // Install only the Visual components the starter project uses.
289
310
  const allVisualComponents = STARTER_VISUAL_COMPONENTS;
290
- Print.info(`Installing ${allVisualComponents.length} starter Visual components: ${allVisualComponents.join(', ')}`);
291
311
 
292
312
  if (allVisualComponents.length > 0) {
293
313
  componentsSpinner.text = `Installing ${allVisualComponents.length} starter Visual components...`;
294
314
 
295
- const results = await registry.installMultipleComponents(
315
+ visualResults = await registry.installMultipleComponents(
296
316
  allVisualComponents,
297
317
  'Visual',
298
318
  true // force = true for initial installation
299
319
  );
300
320
 
301
- const successful = results.filter(r => r.success).length;
302
- const failed = results.filter(r => !r.success).length;
321
+ const successful = visualResults.filter(r => r.success).length;
322
+ const failed = visualResults.filter(r => !r.success).length;
303
323
 
304
324
  if (successful > 0 && failed === 0) {
305
325
  componentsSpinner.succeed(`All ${successful} Visual components installed successfully`);
306
326
  } else if (successful > 0) {
307
327
  componentsSpinner.warn(`${successful} components installed, ${failed} failed`);
308
- Print.info(`You can install failed components later using "${packageManager} run get -- <component-name>"`);
309
328
  } else {
310
329
  componentsSpinner.fail('Failed to install Visual components');
311
330
  }
@@ -322,29 +341,28 @@ export default async function initializeProject(options = {}) {
322
341
  }
323
342
 
324
343
  // 3b. DOWNLOAD STARTER SERVICE COMPONENTS FROM OFFICIAL REPOSITORY
344
+ let serviceResults = [];
325
345
  const serviceSpinner = ora('Installing starter Service components...').start();
326
346
  try {
327
347
  const registry = new ComponentRegistry();
328
348
  await registry.loadRegistry();
329
349
 
330
350
  if (STARTER_SERVICE_COMPONENTS.length > 0) {
331
- Print.info(`Installing ${STARTER_SERVICE_COMPONENTS.length} starter Service components: ${STARTER_SERVICE_COMPONENTS.join(', ')}`);
332
351
  serviceSpinner.text = `Installing ${STARTER_SERVICE_COMPONENTS.length} starter Service components...`;
333
352
 
334
- const results = await registry.installMultipleComponents(
353
+ serviceResults = await registry.installMultipleComponents(
335
354
  STARTER_SERVICE_COMPONENTS,
336
355
  'Service',
337
356
  true // force = true for initial installation
338
357
  );
339
358
 
340
- const successful = results.filter(r => r.success).length;
341
- const failed = results.filter(r => !r.success).length;
359
+ const successful = serviceResults.filter(r => r.success).length;
360
+ const failed = serviceResults.filter(r => !r.success).length;
342
361
 
343
362
  if (successful > 0 && failed === 0) {
344
363
  serviceSpinner.succeed(`All ${successful} Service components installed successfully`);
345
364
  } else if (successful > 0) {
346
365
  serviceSpinner.warn(`${successful} Service components installed, ${failed} failed`);
347
- Print.info(`You can install failed components later using "${packageManager} run get -- <component-name>"`);
348
366
  } else {
349
367
  serviceSpinner.fail('Failed to install Service components');
350
368
  }
@@ -422,6 +440,12 @@ export default async function initializeProject(options = {}) {
422
440
  }
423
441
 
424
442
  const projectName = path.basename(process.cwd());
443
+
444
+ const failedCount = [...visualResults, ...serviceResults].filter(r => !r.success).length;
445
+ if (failedCount > 0) {
446
+ Print.warning(`${failedCount} component(s) failed to install — run "${packageManager} run get -- <name>" to retry`);
447
+ }
448
+
425
449
  Print.success(`Project initialized successfully in "${projectName}/"`);
426
450
  Print.newLine();
427
451
  Print.title('Next steps:');
@@ -11,7 +11,7 @@ export default function setupWatcher(serverProcess, onRestart) {
11
11
  Print.info('Watch mode enabled - monitoring file changes...');
12
12
  Print.newLine();
13
13
 
14
- const watcher = chokidar.watch(['src/**/*', 'api/**/*'], {
14
+ const watcher = chokidar.watch(['api/**/*'], {
15
15
  ignored: [
16
16
  /(^|[\/\\])\../, // hidden files
17
17
  '**/node_modules/**',
@@ -29,33 +29,20 @@ export default function setupWatcher(serverProcess, onRestart) {
29
29
 
30
30
  let reloadTimeout;
31
31
 
32
+ const handleChange = (changedPath) => {
33
+ clearTimeout(reloadTimeout);
34
+ reloadTimeout = setTimeout(() => {
35
+ if (onRestart) {
36
+ console.log(chalk.yellow('🔄 API change detected, restarting server...'));
37
+ onRestart(changedPath);
38
+ }
39
+ }, 500);
40
+ };
41
+
32
42
  watcher
33
- .on('change', (path) => {
34
- // Debounce to avoid multiple reloads
35
- clearTimeout(reloadTimeout);
36
- reloadTimeout = setTimeout(() => {
37
- if(onRestart) {
38
- console.log(chalk.yellow('🔄 Changes detected, restarting server...'));
39
- onRestart(path);
40
- } else {
41
- console.log(chalk.yellow('🔄 Changes detected, server will reload automatically... (No handler)'));
42
- }
43
- }, 500);
44
- })
45
- .on('add', (path) => {
46
- // console.log(chalk.green(`➕ New file added: ${path}`));
47
- clearTimeout(reloadTimeout);
48
- reloadTimeout = setTimeout(() => {
49
- if (onRestart) onRestart(path);
50
- }, 500);
51
- })
52
- .on('unlink', (path) => {
53
- // console.log(chalk.red(`➖ File removed: ${path}`));
54
- clearTimeout(reloadTimeout);
55
- reloadTimeout = setTimeout(() => {
56
- if (onRestart) onRestart(path);
57
- }, 500);
58
- })
43
+ .on('change', handleChange)
44
+ .on('add', handleChange)
45
+ .on('unlink', handleChange)
59
46
  .on('error', (error) => {
60
47
  Print.error(`Watcher error: ${error.message}`);
61
48
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slicejs-cli",
3
- "version": "3.6.1",
3
+ "version": "3.6.3",
4
4
  "description": "Command client for developing web applications with Slice.js framework",
5
5
  "main": "client.js",
6
6
  "bin": {
@@ -34,7 +34,9 @@
34
34
  "slice:sync": "slice sync",
35
35
  "slice:version": "slice version",
36
36
  "slice:update": "slice update",
37
- "slice:types": "slice types generate"
37
+ "slice:types": "slice types generate",
38
+ "slice:doctor": "node ./node_modules/slicejs-cli/client.js doctor",
39
+ "slice:help": "node ./node_modules/slicejs-cli/client.js --help"
38
40
  },
39
41
  "keywords": [
40
42
  "framework",
@@ -69,4 +71,4 @@
69
71
  "devDependencies": {
70
72
  "@playwright/test": "^1.60.0"
71
73
  }
72
- }
74
+ }