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.
- package/AGENTS.md +247 -0
- package/LICENSE +21 -21
- package/client.js +663 -626
- package/commands/Print.js +163 -167
- package/commands/Validations.js +92 -103
- package/commands/build/build.js +40 -40
- package/commands/buildProduction/buildProduction.js +576 -579
- package/commands/bundle/bundle.js +234 -235
- package/commands/createComponent/VisualComponentTemplate.js +55 -55
- package/commands/createComponent/createComponent.js +124 -126
- package/commands/deleteComponent/deleteComponent.js +77 -77
- package/commands/doctor/doctor.js +366 -369
- package/commands/getComponent/getComponent.js +684 -747
- package/commands/init/init.js +269 -261
- package/commands/listComponents/listComponents.js +172 -175
- package/commands/startServer/startServer.js +261 -264
- package/commands/startServer/watchServer.js +79 -79
- package/commands/types/types.js +69 -27
- package/commands/utils/LocalCliDelegation.js +53 -53
- package/commands/utils/PathHelper.js +75 -68
- package/commands/utils/VersionChecker.js +167 -167
- package/commands/utils/bundling/BundleGenerator.js +2292 -2292
- package/commands/utils/bundling/DependencyAnalyzer.js +925 -933
- package/commands/utils/loadConfig.js +31 -0
- package/commands/utils/updateManager.js +452 -453
- package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +105 -105
- package/package.json +58 -46
- package/post.js +66 -65
- package/tests/bundle-generator.test.js +691 -708
- package/tests/bundle-v2-register-output.test.js +470 -470
- package/tests/client-launcher-contract.test.js +211 -211
- package/tests/client-update-flow-contract.test.js +272 -272
- package/tests/component-registry-parse.test.js +34 -0
- package/tests/dependency-analyzer.test.js +24 -24
- package/tests/fixtures/components.js +8 -0
- package/tests/fixtures/sliceConfig.json +74 -0
- package/tests/getcomponent.test.js +407 -0
- package/tests/helpers/setup.js +97 -0
- package/tests/init-command-contract.test.js +46 -0
- package/tests/local-cli-delegation.test.js +81 -79
- package/tests/path-helper.test.js +206 -0
- package/tests/types-breakage.test.js +491 -0
- package/tests/types-generator-errors.test.js +361 -0
- package/tests/types-generator.test.js +172 -184
- package/tests/update-manager-notifications.test.js +88 -88
- 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
|
-
*
|
|
7
|
-
* @param {ChildProcess} serverProcess -
|
|
8
|
-
* @returns {FSWatcher} -
|
|
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
|
-
/(^|[\/\\])\../, //
|
|
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
|
|
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
|
-
*
|
|
72
|
-
* @param {FSWatcher} watcher - Watcher
|
|
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
|
+
}
|
package/commands/types/types.js
CHANGED
|
@@ -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(
|
|
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 =
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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
|
|
384
|
-
|
|
385
|
-
|
|
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 =
|
|
393
|
-
|
|
394
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 === '
|
|
517
|
-
|
|
518
|
-
Print.
|
|
519
|
-
|
|
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
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
+
}
|