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
@@ -1,79 +1,79 @@
1
- import chokidar from 'chokidar';
2
- import chalk from 'chalk';
3
- import Print from '../Print.js';
4
-
5
- /**
6
- * Configura el watcher para archivos del proyecto
7
- * @param {ChildProcess} serverProcess - Proceso del servidor
8
- * @returns {FSWatcher} - Watcher de chokidar
9
- */
10
- export default function setupWatcher(serverProcess, onRestart) {
11
- Print.info('Watch mode enabled - monitoring file changes...');
12
- Print.newLine();
13
-
14
- const watcher = chokidar.watch(['src/**/*', 'api/**/*'], {
15
- ignored: [
16
- /(^|[\/\\])\../, // archivos ocultos
17
- '**/node_modules/**',
18
- '**/dist/**',
19
- '**/bundles/**',
20
- '**/*.log'
21
- ],
22
- persistent: true,
23
- ignoreInitial: true,
24
- awaitWriteFinish: {
25
- stabilityThreshold: 100,
26
- pollInterval: 50
27
- }
28
- });
29
-
30
- let reloadTimeout;
31
-
32
- watcher
33
- .on('change', (path) => {
34
- // Debounce para evitar mรบltiples 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
- })
59
- .on('error', (error) => {
60
- Print.error(`Watcher error: ${error.message}`);
61
- })
62
- .on('ready', () => {
63
- console.log(chalk.gray('๐Ÿ‘€ Watching for file changes...'));
64
- Print.newLine();
65
- });
66
-
67
- return watcher;
68
- }
69
-
70
- /**
71
- * Detiene el watcher de forma segura
72
- * @param {FSWatcher} watcher - Watcher a detener
73
- */
74
- export function stopWatcher(watcher) {
75
- if (watcher) {
76
- watcher.close();
77
- console.log(chalk.gray('Watch mode stopped'));
78
- }
79
- }
1
+ import chokidar from 'chokidar';
2
+ import chalk from 'chalk';
3
+ import Print from '../Print.js';
4
+
5
+ /**
6
+ * Configures the watcher for project files
7
+ * @param {ChildProcess} serverProcess - Server process
8
+ * @returns {FSWatcher} - Chokidar watcher
9
+ */
10
+ export default function setupWatcher(serverProcess, onRestart) {
11
+ Print.info('Watch mode enabled - monitoring file changes...');
12
+ Print.newLine();
13
+
14
+ const watcher = chokidar.watch(['src/**/*', 'api/**/*'], {
15
+ ignored: [
16
+ /(^|[\/\\])\../, // hidden files
17
+ '**/node_modules/**',
18
+ '**/dist/**',
19
+ '**/bundles/**',
20
+ '**/*.log'
21
+ ],
22
+ persistent: true,
23
+ ignoreInitial: true,
24
+ awaitWriteFinish: {
25
+ stabilityThreshold: 100,
26
+ pollInterval: 50
27
+ }
28
+ });
29
+
30
+ let reloadTimeout;
31
+
32
+ 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
+ })
59
+ .on('error', (error) => {
60
+ Print.error(`Watcher error: ${error.message}`);
61
+ })
62
+ .on('ready', () => {
63
+ console.log(chalk.gray('๐Ÿ‘€ Watching for file changes...'));
64
+ Print.newLine();
65
+ });
66
+
67
+ return watcher;
68
+ }
69
+
70
+ /**
71
+ * Stops the watcher safely
72
+ * @param {FSWatcher} watcher - Watcher to stop
73
+ */
74
+ export function stopWatcher(watcher) {
75
+ if (watcher) {
76
+ watcher.close();
77
+ console.log(chalk.gray('Watch mode stopped'));
78
+ }
79
+ }
@@ -4,6 +4,7 @@ import { parse } from '@babel/parser';
4
4
  import traverse from '@babel/traverse';
5
5
 
6
6
  import Print from '../Print.js';
7
+ import { getConfigPath, getComponentsJsPath, joinRoot } from '../utils/PathHelper.js';
7
8
 
8
9
  const TYPE_MAP = {
9
10
  string: 'string',
@@ -114,22 +115,29 @@ const sortKeys = (obj) => {
114
115
  }, {});
115
116
  };
116
117
 
117
- const parseComponentsRegistry = (content) => {
118
+ const parseComponentsRegistry = (content, filePath) => {
118
119
  const match = content.match(/const components = ({[\s\S]*?});/);
119
120
  if (!match) {
120
- throw new Error('Invalid components.js format. Expected: const components = { ... };');
121
+ throw new Error(`Invalid format in ${filePath}. Expected: const components = { ... };`);
122
+ }
123
+ try {
124
+ return JSON.parse(match[1]);
125
+ } catch (parseError) {
126
+ throw new Error(`Failed to parse components registry in ${filePath}: ${parseError.message}`);
121
127
  }
122
- return JSON.parse(match[1]);
123
128
  };
124
129
 
125
- const extractStaticPropsFromSource = (source) => {
130
+ const extractStaticPropsFromSource = (source, filePath) => {
126
131
  let ast;
127
132
  try {
128
133
  ast = parse(source, {
129
134
  sourceType: 'module',
130
135
  plugins: ['classProperties']
131
136
  });
132
- } catch {
137
+ } catch (parseError) {
138
+ const sourceDesc = filePath || 'unknown file';
139
+ const loc = parseError.loc ? ` at line ${parseError.loc.line}, column ${parseError.loc.column}` : '';
140
+ Print.warning(`Parse error in ${sourceDesc}${loc}: ${parseError.message}`);
133
141
  return null;
134
142
  }
135
143
 
@@ -194,7 +202,7 @@ const DEFAULT_EDITOR_EXCLUDE = ['node_modules', 'dist', 'src/libs/**', 'tests/**
194
202
  const NOISY_INCLUDE_PATTERNS = new Set(['src/**/*.js', 'api/**/*.js', 'tests/**/*.js']);
195
203
 
196
204
  const readPublicFolderExcludes = async (projectRoot) => {
197
- const configPath = path.join(projectRoot, 'src', 'sliceConfig.json');
205
+ const configPath = getConfigPath(import.meta.url, projectRoot);
198
206
  if (!(await fs.pathExists(configPath))) return [];
199
207
 
200
208
  try {
@@ -230,7 +238,7 @@ const collectJavaScriptFiles = async (dirPath) => {
230
238
  };
231
239
 
232
240
  const ensureNoCheckInPublicVendorFiles = async (projectRoot) => {
233
- const configPath = path.join(projectRoot, 'src', 'sliceConfig.json');
241
+ const configPath = getConfigPath(import.meta.url, projectRoot);
234
242
  if (!(await fs.pathExists(configPath))) return { updatedFiles: 0, scannedFiles: 0 };
235
243
 
236
244
  let parsed;
@@ -245,7 +253,7 @@ const ensureNoCheckInPublicVendorFiles = async (projectRoot) => {
245
253
  .map((folder) => String(folder || '').trim())
246
254
  .filter(Boolean)
247
255
  .map((folder) => folder.replace(/^[/\\]+/, ''))
248
- .map((folder) => path.join(projectRoot, 'src', folder));
256
+ .map((folder) => joinRoot(projectRoot, 'src', folder));
249
257
 
250
258
  const uniqueDirs = Array.from(new Set(candidateDirs));
251
259
  let scannedFiles = 0;
@@ -360,9 +368,11 @@ const readCategoryPathFromConfig = async (configPath, category) => {
360
368
  };
361
369
 
362
370
  const loadComponentStaticProps = async ({ projectRoot, registryMap }) => {
363
- const configPath = path.join(projectRoot, 'src', 'sliceConfig.json');
371
+ const configPath = getConfigPath(import.meta.url, projectRoot);
364
372
  const categoryPathCache = new Map();
365
373
  const componentPropsMap = {};
374
+ let skippedCount = 0;
375
+ let processedCount = 0;
366
376
 
367
377
  for (const [componentName, category] of Object.entries(sortKeys(registryMap))) {
368
378
  if (!categoryPathCache.has(category)) {
@@ -372,26 +382,50 @@ const loadComponentStaticProps = async ({ projectRoot, registryMap }) => {
372
382
 
373
383
  const categoryPath = categoryPathCache.get(category);
374
384
  if (!categoryPath) {
385
+ skippedCount++;
375
386
  continue;
376
387
  }
377
388
 
378
- const componentFile = path.join(projectRoot, 'src', categoryPath, componentName, `${componentName}.js`);
389
+ const componentFile = joinRoot(projectRoot, 'src', categoryPath, componentName, `${componentName}.js`);
379
390
  if (!(await fs.pathExists(componentFile))) {
391
+ skippedCount++;
392
+ continue;
393
+ }
394
+
395
+ let source;
396
+ try {
397
+ source = await fs.readFile(componentFile, 'utf8');
398
+ } catch (readError) {
399
+ Print.warning(`Cannot read ${componentFile}: ${readError.message}`);
400
+ skippedCount++;
380
401
  continue;
381
402
  }
382
403
 
383
- const source = await fs.readFile(componentFile, 'utf8');
384
- const props = extractStaticPropsFromSource(source);
385
- componentPropsMap[componentName] = props || { [DYNAMIC_FALLBACK_PROP]: { type: 'any', required: false } };
404
+ const props = extractStaticPropsFromSource(source, componentFile);
405
+ if (props) {
406
+ componentPropsMap[componentName] = props;
407
+ processedCount++;
408
+ } else {
409
+ componentPropsMap[componentName] = { [DYNAMIC_FALLBACK_PROP]: { type: 'any', required: false } };
410
+ }
411
+ }
412
+
413
+ if (skippedCount > 0) {
414
+ Print.info(`Skipped ${skippedCount} component(s) with missing or unreadable files`);
386
415
  }
387
416
 
388
417
  return componentPropsMap;
389
418
  };
390
419
 
391
420
  const generateTypesFile = async ({ projectRoot, outputPath }) => {
392
- const registryPath = path.join(projectRoot, 'src', 'Components', 'components.js');
393
- const registryContent = await fs.readFile(registryPath, 'utf8');
394
- const registryMap = parseComponentsRegistry(registryContent);
421
+ const registryPath = getComponentsJsPath(import.meta.url, projectRoot);
422
+ let registryContent;
423
+ try {
424
+ registryContent = await fs.readFile(registryPath, 'utf8');
425
+ } catch (readError) {
426
+ throw new Error(`Cannot read components registry at ${registryPath}: ${readError.message}`);
427
+ }
428
+ const registryMap = parseComponentsRegistry(registryContent, registryPath);
395
429
 
396
430
  const componentPropsMap = await loadComponentStaticProps({ projectRoot, registryMap });
397
431
 
@@ -412,12 +446,12 @@ const toPosixRelative = (projectRoot, targetPath) => {
412
446
 
413
447
  const ensureEditorConfigForTypes = async ({ projectRoot, outputPath }) => {
414
448
  try {
415
- const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
449
+ const tsconfigPath = joinRoot(projectRoot, 'tsconfig.json');
416
450
  if (await fs.pathExists(tsconfigPath)) {
417
451
  return { mode: 'tsconfig_exists', filePath: tsconfigPath, includeAdded: false };
418
452
  }
419
453
 
420
- const jsconfigPath = path.join(projectRoot, 'jsconfig.json');
454
+ const jsconfigPath = joinRoot(projectRoot, 'jsconfig.json');
421
455
  const declarationGlob = (() => {
422
456
  const relative = toPosixRelative(projectRoot, outputPath);
423
457
  if (!relative) return 'src/**/*.d.ts';
@@ -427,13 +461,17 @@ const ensureEditorConfigForTypes = async ({ projectRoot, outputPath }) => {
427
461
  return `${dir}/**/*.d.ts`;
428
462
  })();
429
463
 
430
- if (!(await fs.pathExists(jsconfigPath))) {
464
+ const writeDefaultJsconfig = async () => {
431
465
  const jsconfig = {
432
466
  compilerOptions: { ...DEFAULT_EDITOR_COMPILER_OPTIONS },
433
467
  include: [...DEFAULT_EDITOR_INCLUDE, declarationGlob],
434
468
  exclude: [...DEFAULT_EDITOR_EXCLUDE]
435
469
  };
436
470
  await fs.writeFile(jsconfigPath, `${JSON.stringify(jsconfig, null, 2)}\n`, 'utf8');
471
+ };
472
+
473
+ if (!(await fs.pathExists(jsconfigPath))) {
474
+ await writeDefaultJsconfig();
437
475
  return { mode: 'created_jsconfig', filePath: jsconfigPath, includeAdded: true };
438
476
  }
439
477
 
@@ -441,14 +479,19 @@ const ensureEditorConfigForTypes = async ({ projectRoot, outputPath }) => {
441
479
  try {
442
480
  jsconfigRaw = await fs.readFile(jsconfigPath, 'utf8');
443
481
  } catch {
444
- return { mode: 'jsconfig_unreadable', filePath: jsconfigPath, includeAdded: false };
482
+ // Don't fail โ€” fall back to writing the default options.
483
+ await writeDefaultJsconfig();
484
+ return { mode: 'reset_jsconfig', reason: 'unreadable', filePath: jsconfigPath, includeAdded: true };
445
485
  }
446
486
 
447
487
  let parsed;
448
488
  try {
449
489
  parsed = JSON.parse(jsconfigRaw);
450
490
  } catch {
451
- return { mode: 'jsconfig_invalid_json', filePath: jsconfigPath, includeAdded: false };
491
+ // The jsconfig was edited into invalid JSON (a typo, comments, trailing commas...).
492
+ // Don't fail โ€” just write the default options so editor IntelliSense keeps working.
493
+ await writeDefaultJsconfig();
494
+ return { mode: 'reset_jsconfig', reason: 'invalid_json', filePath: jsconfigPath, includeAdded: true };
452
495
  }
453
496
 
454
497
  const include = Array.isArray(parsed.include) ? parsed.include : [];
@@ -513,12 +556,10 @@ const runGenerateTypes = async ({ projectRoot, outputPath }) => {
513
556
  Print.info(`jsconfig.json already includes declaration glob. Editor IntelliSense should pick generated types.`);
514
557
  } else if (editorConfig.mode === 'tsconfig_exists') {
515
558
  Print.info(`tsconfig.json detected. Types declaration is generated; ensure include covers ${toPosixRelative(projectRoot, outputPath)}.`);
516
- } else if (editorConfig.mode === 'jsconfig_invalid_json') {
517
- Print.warning(`Could not update jsconfig.json because it contains invalid JSON.`);
518
- Print.info(`Fix ${editorConfig.filePath} and run 'slice types generate' again.`);
519
- } else if (editorConfig.mode === 'jsconfig_unreadable') {
520
- Print.warning(`Could not read jsconfig.json to verify declaration include.`);
521
- Print.info(`Check permissions for ${editorConfig.filePath} and run 'slice types generate' again.`);
559
+ } else if (editorConfig.mode === 'reset_jsconfig') {
560
+ const why = editorConfig.reason === 'invalid_json' ? 'contained invalid JSON' : 'could not be read';
561
+ Print.warning(`jsconfig.json ${why}; wrote the default options so editor IntelliSense keeps working.`);
562
+ Print.info(`Review ${editorConfig.filePath} if you had custom settings there.`);
522
563
  } else if (editorConfig.mode === 'editor_config_error') {
523
564
  Print.warning(`Unexpected editor config setup error: ${editorConfig.errorMessage}`);
524
565
  }
@@ -534,5 +575,6 @@ export {
534
575
  extractStaticPropsFromSource,
535
576
  generateDeclarationContent,
536
577
  generateTypesFile,
578
+ parseComponentsRegistry,
537
579
  runGenerateTypes
538
580
  };
@@ -1,53 +1,53 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
-
4
- function getParentDirectory(dir) {
5
- const parent = path.dirname(dir);
6
- return parent === dir ? null : parent;
7
- }
8
-
9
- export function isLocalDelegationDisabled(env = process.env) {
10
- return env.SLICE_NO_LOCAL_DELEGATION === '1';
11
- }
12
-
13
- export function findNearestLocalCliEntry(startDirectory, resolveCandidate) {
14
- if (!startDirectory || typeof resolveCandidate !== 'function') {
15
- return null;
16
- }
17
-
18
- let current = path.resolve(startDirectory);
19
- while (current) {
20
- const candidate = resolveCandidate(current);
21
- if (candidate) {
22
- return candidate;
23
- }
24
- current = getParentDirectory(current);
25
- }
26
-
27
- return null;
28
- }
29
-
30
- export function resolveLocalCliCandidate(directory) {
31
- const candidate = path.join(directory, 'node_modules', 'slicejs-cli', 'client.js');
32
-
33
- try {
34
- const stats = fs.statSync(candidate);
35
- return stats.isFile() ? candidate : null;
36
- } catch {
37
- return null;
38
- }
39
- }
40
-
41
- export function shouldDelegateToLocalCli(currentEntryPath, localEntryPath) {
42
- if (!localEntryPath) {
43
- return false;
44
- }
45
-
46
- try {
47
- const currentReal = fs.realpathSync(currentEntryPath);
48
- const localReal = fs.realpathSync(localEntryPath);
49
- return currentReal !== localReal;
50
- } catch {
51
- return path.resolve(currentEntryPath) !== path.resolve(localEntryPath);
52
- }
53
- }
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ function getParentDirectory(dir) {
5
+ const parent = path.dirname(dir);
6
+ return parent === dir ? null : parent;
7
+ }
8
+
9
+ export function isLocalDelegationDisabled(env = process.env) {
10
+ return env.SLICE_NO_LOCAL_DELEGATION === '1';
11
+ }
12
+
13
+ export function findNearestLocalCliEntry(startDirectory, resolveCandidate) {
14
+ if (!startDirectory || typeof resolveCandidate !== 'function') {
15
+ return null;
16
+ }
17
+
18
+ let current = path.resolve(startDirectory);
19
+ while (current) {
20
+ const candidate = resolveCandidate(current);
21
+ if (candidate) {
22
+ return candidate;
23
+ }
24
+ current = getParentDirectory(current);
25
+ }
26
+
27
+ return null;
28
+ }
29
+
30
+ export function resolveLocalCliCandidate(directory) {
31
+ const candidate = path.join(directory, 'node_modules', 'slicejs-cli', 'client.js');
32
+
33
+ try {
34
+ const stats = fs.statSync(candidate);
35
+ return stats.isFile() ? candidate : null;
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ export function shouldDelegateToLocalCli(currentEntryPath, localEntryPath) {
42
+ if (!localEntryPath) {
43
+ return false;
44
+ }
45
+
46
+ try {
47
+ const currentReal = fs.realpathSync(currentEntryPath);
48
+ const localReal = fs.realpathSync(localEntryPath);
49
+ return currentReal !== localReal;
50
+ } catch {
51
+ return path.resolve(currentEntryPath) !== path.resolve(localEntryPath);
52
+ }
53
+ }
@@ -1,68 +1,75 @@
1
- import path from 'path'
2
- import fs from 'fs-extra'
3
- import { fileURLToPath } from 'url'
4
-
5
- const sanitize = (s) => (s || '').replace(/^[/\\]+/, '')
6
- const dirOf = (url) => path.dirname(fileURLToPath(url))
7
-
8
- function candidates(moduleUrl) {
9
- const dir = dirOf(moduleUrl)
10
- return [
11
- path.join(dir, '../../'),
12
- path.join(dir, '../../../../')
13
- ]
14
- }
15
-
16
- function resolveProjectRoot(moduleUrl) {
17
- const initCwd = process.env.INIT_CWD
18
- if (initCwd && fs.pathExistsSync(initCwd)) {
19
- return initCwd
20
- }
21
-
22
- const cwd = process.cwd()
23
- if (cwd && fs.pathExistsSync(cwd)) {
24
- return cwd
25
- }
26
-
27
- const dirs = candidates(moduleUrl)
28
- for (const root of dirs) {
29
- const hasSrc = fs.pathExistsSync(path.join(root, 'src'))
30
- const hasApi = fs.pathExistsSync(path.join(root, 'api'))
31
- if (hasSrc || hasApi) return root
32
- }
33
- return dirs[1]
34
- }
35
-
36
- function joinProject(moduleUrl, ...segments) {
37
- const root = resolveProjectRoot(moduleUrl)
38
- const clean = segments.map(sanitize)
39
- return path.join(root, ...clean)
40
- }
41
-
42
- export function getProjectRoot(moduleUrl) {
43
- return resolveProjectRoot(moduleUrl)
44
- }
45
-
46
- export function getPath(moduleUrl, folder, ...segments) {
47
- return joinProject(moduleUrl, folder, ...segments)
48
- }
49
-
50
- export function getSrcPath(moduleUrl, ...segments) {
51
- return joinProject(moduleUrl, 'src', ...segments)
52
- }
53
-
54
- export function getApiPath(moduleUrl, ...segments) {
55
- return joinProject(moduleUrl, 'api', ...segments)
56
- }
57
-
58
- export function getDistPath(moduleUrl, ...segments) {
59
- return joinProject(moduleUrl, 'dist', ...segments)
60
- }
61
-
62
- export function getConfigPath(moduleUrl) {
63
- return joinProject(moduleUrl, 'src', 'sliceConfig.json')
64
- }
65
-
66
- export function getComponentsJsPath(moduleUrl) {
67
- return joinProject(moduleUrl, 'src', 'Components', 'components.js')
68
- }
1
+ import path from 'path'
2
+ import fs from 'fs-extra'
3
+ import { fileURLToPath } from 'url'
4
+
5
+ const sanitize = (s) => (s || '').replace(/^[/\\]+/, '')
6
+ const dirOf = (url) => path.dirname(fileURLToPath(url))
7
+
8
+ function candidates(moduleUrl) {
9
+ const dir = dirOf(moduleUrl)
10
+ return [
11
+ path.join(dir, '../../'),
12
+ path.join(dir, '../../../../')
13
+ ]
14
+ }
15
+
16
+ function resolveProjectRoot(moduleUrl) {
17
+ const initCwd = process.env.INIT_CWD
18
+ if (initCwd && fs.pathExistsSync(initCwd)) {
19
+ return initCwd
20
+ }
21
+
22
+ const cwd = process.cwd()
23
+ if (cwd && fs.pathExistsSync(cwd)) {
24
+ return cwd
25
+ }
26
+
27
+ const dirs = candidates(moduleUrl)
28
+ for (const root of dirs) {
29
+ const hasSrc = fs.pathExistsSync(path.join(root, 'src'))
30
+ const hasApi = fs.pathExistsSync(path.join(root, 'api'))
31
+ if (hasSrc || hasApi) return root
32
+ }
33
+ return dirs[1]
34
+ }
35
+
36
+ function joinProject(moduleUrl, ...segments) {
37
+ const root = resolveProjectRoot(moduleUrl)
38
+ const clean = segments.map(sanitize)
39
+ return path.join(root, ...clean)
40
+ }
41
+
42
+ export function getProjectRoot(moduleUrl) {
43
+ return resolveProjectRoot(moduleUrl)
44
+ }
45
+
46
+ export function getPath(moduleUrl, folder, ...segments) {
47
+ return joinProject(moduleUrl, folder, ...segments)
48
+ }
49
+
50
+ export function getSrcPath(moduleUrl, ...segments) {
51
+ return joinProject(moduleUrl, 'src', ...segments)
52
+ }
53
+
54
+ export function getApiPath(moduleUrl, ...segments) {
55
+ return joinProject(moduleUrl, 'api', ...segments)
56
+ }
57
+
58
+ export function getDistPath(moduleUrl, ...segments) {
59
+ return joinProject(moduleUrl, 'dist', ...segments)
60
+ }
61
+
62
+ export function getConfigPath(moduleUrl, root) {
63
+ if (root) return path.join(root, 'src', 'sliceConfig.json')
64
+ return joinProject(moduleUrl, 'src', 'sliceConfig.json')
65
+ }
66
+
67
+ export function getComponentsJsPath(moduleUrl, root) {
68
+ if (root) return path.join(root, 'src', 'Components', 'components.js')
69
+ return joinProject(moduleUrl, 'src', 'Components', 'components.js')
70
+ }
71
+
72
+ export function joinRoot(root, ...segments) {
73
+ const clean = segments.map(sanitize)
74
+ return path.join(root, ...clean)
75
+ }