slicejs-web-framework 3.4.4 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/Slice/Components/Structural/Controller/Controller.js +135 -0
  2. package/Slice/Components/Structural/EventManager/EventManager.js +289 -4
  3. package/Slice/Components/Structural/EventManager/EventManagerDebugger.js +368 -30
  4. package/Slice/Components/Structural/LeakInspector/LeakInspector.js +296 -0
  5. package/Slice/Slice.js +14 -1
  6. package/Slice/tests/event-emitters.test.js +79 -0
  7. package/Slice/tests/event-registry.test.js +141 -0
  8. package/Slice/tests/leak-orphans.test.js +131 -0
  9. package/api/index.js +35 -8
  10. package/package.json +1 -1
  11. package/sliceConfig.schema.json +10 -1
  12. package/src/sliceConfig.json +5 -1
  13. package/types/index.d.ts +12 -0
  14. package/slicejs-cli/.github/workflows/ci.yml +0 -43
  15. package/slicejs-cli/LICENSE +0 -21
  16. package/slicejs-cli/assets/Slice.js-logo.png +0 -0
  17. package/slicejs-cli/client.js +0 -740
  18. package/slicejs-cli/commands/Print.js +0 -164
  19. package/slicejs-cli/commands/Validations.js +0 -92
  20. package/slicejs-cli/commands/build/build.js +0 -40
  21. package/slicejs-cli/commands/buildProduction/buildProduction.js +0 -577
  22. package/slicejs-cli/commands/bundle/bundle.js +0 -236
  23. package/slicejs-cli/commands/createComponent/VisualComponentTemplate.js +0 -55
  24. package/slicejs-cli/commands/createComponent/createComponent.js +0 -128
  25. package/slicejs-cli/commands/deleteComponent/deleteComponent.js +0 -81
  26. package/slicejs-cli/commands/doctor/doctor.js +0 -516
  27. package/slicejs-cli/commands/getComponent/getComponent.js +0 -695
  28. package/slicejs-cli/commands/init/init.js +0 -473
  29. package/slicejs-cli/commands/listComponents/listComponents.js +0 -172
  30. package/slicejs-cli/commands/startServer/startServer.js +0 -240
  31. package/slicejs-cli/commands/startServer/watchServer.js +0 -66
  32. package/slicejs-cli/commands/types/types.js +0 -583
  33. package/slicejs-cli/commands/utils/LocalCliDelegation.js +0 -53
  34. package/slicejs-cli/commands/utils/PackageManager.js +0 -148
  35. package/slicejs-cli/commands/utils/PathHelper.js +0 -75
  36. package/slicejs-cli/commands/utils/VersionChecker.js +0 -169
  37. package/slicejs-cli/commands/utils/bundling/BundleGenerator.js +0 -2525
  38. package/slicejs-cli/commands/utils/bundling/DependencyAnalyzer.js +0 -933
  39. package/slicejs-cli/commands/utils/loadConfig.js +0 -31
  40. package/slicejs-cli/commands/utils/sliceScripts.js +0 -48
  41. package/slicejs-cli/commands/utils/updateManager.js +0 -471
  42. package/slicejs-cli/package.json +0 -71
  43. package/slicejs-cli/playwright.config.js +0 -51
  44. package/slicejs-cli/pnpm-lock.yaml +0 -1629
  45. package/slicejs-cli/tests/build-command-integration.test.js +0 -87
  46. package/slicejs-cli/tests/build-mangling-e2e.test.js +0 -290
  47. package/slicejs-cli/tests/build-production-e2e.test.js +0 -140
  48. package/slicejs-cli/tests/builder-edge-cases.test.js +0 -322
  49. package/slicejs-cli/tests/bundle-generate-e2e.test.js +0 -115
  50. package/slicejs-cli/tests/bundle-generator.test.js +0 -691
  51. package/slicejs-cli/tests/bundle-v2-register-output.test.js +0 -470
  52. package/slicejs-cli/tests/bundling-dependency-edges.test.js +0 -127
  53. package/slicejs-cli/tests/bundling-imports-unit.test.js +0 -267
  54. package/slicejs-cli/tests/client-launcher-contract.test.js +0 -211
  55. package/slicejs-cli/tests/commands-component-crud.test.js +0 -128
  56. package/slicejs-cli/tests/commands-doctor.test.js +0 -152
  57. package/slicejs-cli/tests/commands-version-checker.test.js +0 -37
  58. package/slicejs-cli/tests/component-registry-parse.test.js +0 -34
  59. package/slicejs-cli/tests/dependency-analyzer.test.js +0 -24
  60. package/slicejs-cli/tests/e2e/bundles.spec.js +0 -91
  61. package/slicejs-cli/tests/e2e/dependency-scenarios.spec.js +0 -56
  62. package/slicejs-cli/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +0 -136
  63. package/slicejs-cli/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +0 -149
  64. package/slicejs-cli/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +0 -45
  65. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.css +0 -106
  66. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.html +0 -5
  67. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.js +0 -158
  68. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Link/Link.js +0 -33
  69. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.css +0 -56
  70. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.html +0 -83
  71. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.js +0 -164
  72. package/slicejs-cli/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +0 -167
  73. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +0 -116
  74. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +0 -44
  75. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +0 -180
  76. package/slicejs-cli/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +0 -20
  77. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Route/Route.js +0 -181
  78. package/slicejs-cli/tests/e2e/fixtures/components/registry.json +0 -12
  79. package/slicejs-cli/tests/e2e/fixtures/vendor-components.mjs +0 -65
  80. package/slicejs-cli/tests/e2e/navigation.spec.js +0 -44
  81. package/slicejs-cli/tests/e2e/render.spec.js +0 -34
  82. package/slicejs-cli/tests/e2e/serve.mjs +0 -264
  83. package/slicejs-cli/tests/e2e/shared-deps.spec.js +0 -61
  84. package/slicejs-cli/tests/e2e/unminified.spec.js +0 -33
  85. package/slicejs-cli/tests/e2e-serve.test.js +0 -148
  86. package/slicejs-cli/tests/fixtures/components.js +0 -8
  87. package/slicejs-cli/tests/fixtures/sliceConfig.json +0 -74
  88. package/slicejs-cli/tests/getcomponent.test.js +0 -435
  89. package/slicejs-cli/tests/helpers/setup.js +0 -102
  90. package/slicejs-cli/tests/init-command-contract.test.js +0 -101
  91. package/slicejs-cli/tests/init-e2e.test.js +0 -82
  92. package/slicejs-cli/tests/init-project-isolation.test.js +0 -98
  93. package/slicejs-cli/tests/local-cli-delegation.test.js +0 -81
  94. package/slicejs-cli/tests/package-manager.test.js +0 -127
  95. package/slicejs-cli/tests/path-helper.test.js +0 -206
  96. package/slicejs-cli/tests/perf-budget.test.js +0 -84
  97. package/slicejs-cli/tests/postinstall-command.test.js +0 -103
  98. package/slicejs-cli/tests/property-mangling-regression.test.js +0 -182
  99. package/slicejs-cli/tests/types-breakage.test.js +0 -491
  100. package/slicejs-cli/tests/types-generator-errors.test.js +0 -361
  101. package/slicejs-cli/tests/types-generator.test.js +0 -348
@@ -1,583 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import { parse } from '@babel/parser';
4
- import traverse from '@babel/traverse';
5
-
6
- import Print from '../Print.js';
7
- import { getConfigPath, getComponentsJsPath, joinRoot } from '../utils/PathHelper.js';
8
-
9
- const TYPE_MAP = {
10
- string: 'string',
11
- number: 'number',
12
- boolean: 'boolean',
13
- object: 'Record<string, unknown>',
14
- array: 'unknown[]',
15
- function: '(...args: unknown[]) => unknown',
16
- any: 'unknown'
17
- };
18
-
19
- const literalValueFromAst = (node) => {
20
- if (!node) return undefined;
21
- if (node.type === 'StringLiteral' || node.type === 'NumericLiteral' || node.type === 'BooleanLiteral') {
22
- return node.value;
23
- }
24
- if (node.type === 'NullLiteral') {
25
- return null;
26
- }
27
- return undefined;
28
- };
29
-
30
- const keyNameFromAst = (keyNode) => {
31
- if (!keyNode) return null;
32
- if (keyNode.type === 'Identifier') return keyNode.name;
33
- if (keyNode.type === 'StringLiteral') return keyNode.value;
34
- return null;
35
- };
36
-
37
- const astNodeToValue = (node) => {
38
- if (!node) return undefined;
39
-
40
- const literal = literalValueFromAst(node);
41
- if (literal !== undefined || (node && node.type === 'NullLiteral')) {
42
- return literal;
43
- }
44
-
45
- if (node.type === 'ArrayExpression') {
46
- return (node.elements || [])
47
- .map((element) => astNodeToValue(element))
48
- .filter((value) => value !== undefined);
49
- }
50
-
51
- if (node.type === 'ObjectExpression') {
52
- const out = {};
53
- for (const property of node.properties || []) {
54
- if (property.type !== 'ObjectProperty') continue;
55
- const key = keyNameFromAst(property.key);
56
- if (!key) continue;
57
- const value = astNodeToValue(property.value);
58
- if (value !== undefined) {
59
- out[key] = value;
60
- }
61
- }
62
- return out;
63
- }
64
-
65
- return undefined;
66
- };
67
-
68
- const normalizePropConfig = (rawConfig) => {
69
- if (!rawConfig || typeof rawConfig !== 'object' || Array.isArray(rawConfig)) {
70
- return { type: 'any', required: false };
71
- }
72
-
73
- const config = {
74
- type: typeof rawConfig.type === 'string' ? rawConfig.type.toLowerCase() : 'any',
75
- required: rawConfig.required === true
76
- };
77
-
78
- if (Array.isArray(rawConfig.allowedValues) && rawConfig.allowedValues.length > 0) {
79
- config.allowedValues = rawConfig.allowedValues;
80
- }
81
-
82
- if (rawConfig.schema && typeof rawConfig.schema === 'object' && !Array.isArray(rawConfig.schema)) {
83
- const schema = {};
84
- for (const [name, nestedRaw] of Object.entries(rawConfig.schema)) {
85
- schema[name] = normalizePropConfig(nestedRaw);
86
- }
87
- config.schema = schema;
88
- }
89
-
90
- if (rawConfig.items && typeof rawConfig.items === 'object' && !Array.isArray(rawConfig.items)) {
91
- config.items = normalizePropConfig(rawConfig.items);
92
- }
93
-
94
- return config;
95
- };
96
-
97
- const extractStaticPropsFromObjectExpression = (objectExpressionNode) => {
98
- const raw = astNodeToValue(objectExpressionNode);
99
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
100
-
101
- const props = {};
102
- for (const [propName, rawConfig] of Object.entries(raw)) {
103
- props[propName] = normalizePropConfig(rawConfig);
104
- }
105
-
106
- return Object.keys(props).length > 0 ? props : null;
107
- };
108
-
109
- const sortKeys = (obj) => {
110
- return Object.keys(obj)
111
- .sort((a, b) => a.localeCompare(b))
112
- .reduce((acc, key) => {
113
- acc[key] = obj[key];
114
- return acc;
115
- }, {});
116
- };
117
-
118
- const parseComponentsRegistry = (content, filePath) => {
119
- const match = content.match(/const components = ({[\s\S]*?});/);
120
- if (!match) {
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}`);
127
- }
128
- };
129
-
130
- const extractStaticPropsFromSource = (source, filePath) => {
131
- let ast;
132
- try {
133
- ast = parse(source, {
134
- sourceType: 'module',
135
- plugins: ['classProperties']
136
- });
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}`);
141
- return null;
142
- }
143
-
144
- let staticPropsObject = null;
145
- traverse.default(ast, {
146
- ClassProperty(pathRef) {
147
- const node = pathRef.node;
148
- if (!node.static || !node.key || keyNameFromAst(node.key) !== 'props') return;
149
- if (node.value && node.value.type === 'ObjectExpression') {
150
- staticPropsObject = node.value;
151
- }
152
- }
153
- });
154
-
155
- if (!staticPropsObject) return null;
156
- return extractStaticPropsFromObjectExpression(staticPropsObject);
157
- };
158
-
159
- const typeFromProp = (propMeta) => {
160
- if (propMeta.type === 'object' && propMeta.schema && typeof propMeta.schema === 'object') {
161
- const schemaEntries = Object.entries(sortKeys(propMeta.schema));
162
- const inner = schemaEntries
163
- .map(([name, meta]) => {
164
- const optionalMark = meta.required ? '' : '?';
165
- return `${name}${optionalMark}: ${typeFromProp(meta)};`;
166
- })
167
- .join(' ');
168
- return `{ ${inner} }`;
169
- }
170
-
171
- if (propMeta.type === 'array' && propMeta.items && typeof propMeta.items === 'object') {
172
- return `${typeFromProp(propMeta.items)}[]`;
173
- }
174
-
175
- const allowedValues = Array.isArray(propMeta.allowedValues) ? propMeta.allowedValues : [];
176
-
177
- if (allowedValues.length > 0 && propMeta.type === 'string' && allowedValues.every((value) => typeof value === 'string')) {
178
- return allowedValues.map((value) => `'${String(value).replace(/'/g, "\\'")}'`).join(' | ');
179
- }
180
-
181
- if (allowedValues.length > 0 && propMeta.type === 'number' && allowedValues.every((value) => typeof value === 'number' && Number.isFinite(value))) {
182
- return allowedValues.join(' | ');
183
- }
184
-
185
- return TYPE_MAP[propMeta.type] || 'unknown';
186
- };
187
-
188
- const interfaceNameFor = (componentName) => `${componentName}Props`;
189
- const DYNAMIC_FALLBACK_PROP = '__dynamicPropsFallback';
190
-
191
- const DEFAULT_EDITOR_COMPILER_OPTIONS = {
192
- allowJs: true,
193
- checkJs: true,
194
- strict: false,
195
- noImplicitAny: false,
196
- strictNullChecks: false,
197
- maxNodeModuleJsDepth: 2
198
- };
199
-
200
- const DEFAULT_EDITOR_INCLUDE = ['src/Components/**/*.js', 'src/**/*.d.ts'];
201
- const DEFAULT_EDITOR_EXCLUDE = ['node_modules', 'dist', 'src/libs/**', 'tests/**'];
202
- const NOISY_INCLUDE_PATTERNS = new Set(['src/**/*.js', 'api/**/*.js', 'tests/**/*.js']);
203
-
204
- const readPublicFolderExcludes = async (projectRoot) => {
205
- const configPath = getConfigPath(import.meta.url, projectRoot);
206
- if (!(await fs.pathExists(configPath))) return [];
207
-
208
- try {
209
- const raw = await fs.readFile(configPath, 'utf8');
210
- const parsed = JSON.parse(raw);
211
- const folders = Array.isArray(parsed?.publicFolders) ? parsed.publicFolders : [];
212
- return folders
213
- .map((folder) => String(folder || '').trim())
214
- .filter(Boolean)
215
- .map((folder) => folder.replace(/^[/\\]+/, ''))
216
- .map((folder) => `src/${folder}/**`);
217
- } catch {
218
- return [];
219
- }
220
- };
221
-
222
- const collectJavaScriptFiles = async (dirPath) => {
223
- if (!(await fs.pathExists(dirPath))) return [];
224
- const entries = await fs.readdir(dirPath, { withFileTypes: true });
225
- const files = [];
226
-
227
- for (const entry of entries) {
228
- const fullPath = path.join(dirPath, entry.name);
229
- if (entry.isDirectory()) {
230
- const nested = await collectJavaScriptFiles(fullPath);
231
- files.push(...nested);
232
- } else if (entry.isFile() && entry.name.toLowerCase().endsWith('.js')) {
233
- files.push(fullPath);
234
- }
235
- }
236
-
237
- return files;
238
- };
239
-
240
- const ensureNoCheckInPublicVendorFiles = async (projectRoot) => {
241
- const configPath = getConfigPath(import.meta.url, projectRoot);
242
- if (!(await fs.pathExists(configPath))) return { updatedFiles: 0, scannedFiles: 0 };
243
-
244
- let parsed;
245
- try {
246
- parsed = JSON.parse(await fs.readFile(configPath, 'utf8'));
247
- } catch {
248
- return { updatedFiles: 0, scannedFiles: 0 };
249
- }
250
-
251
- const publicFolders = Array.isArray(parsed?.publicFolders) ? parsed.publicFolders : [];
252
- const candidateDirs = publicFolders
253
- .map((folder) => String(folder || '').trim())
254
- .filter(Boolean)
255
- .map((folder) => folder.replace(/^[/\\]+/, ''))
256
- .map((folder) => joinRoot(projectRoot, 'src', folder));
257
-
258
- const uniqueDirs = Array.from(new Set(candidateDirs));
259
- let scannedFiles = 0;
260
- let updatedFiles = 0;
261
-
262
- for (const dirPath of uniqueDirs) {
263
- const jsFiles = await collectJavaScriptFiles(dirPath);
264
- for (const filePath of jsFiles) {
265
- scannedFiles += 1;
266
- const raw = await fs.readFile(filePath, 'utf8');
267
- if (raw.startsWith('// @ts-nocheck')) {
268
- continue;
269
- }
270
-
271
- await fs.writeFile(filePath, `// @ts-nocheck\n${raw}`, 'utf8');
272
- updatedFiles += 1;
273
- }
274
- }
275
-
276
- return { updatedFiles, scannedFiles };
277
- };
278
-
279
- const generateDeclarationContent = (componentPropsMap) => {
280
- const componentsSorted = sortKeys(componentPropsMap);
281
- const lines = [];
282
-
283
- lines.push('/* Auto-generated by slice types generate. Do not edit manually. */');
284
- lines.push('');
285
-
286
- for (const [componentName, props] of Object.entries(componentsSorted)) {
287
- lines.push(`export interface ${interfaceNameFor(componentName)} {`);
288
- lines.push(' [key: string]: unknown;');
289
- const sortedProps = sortKeys(props);
290
- const isDynamicFallback = Object.keys(sortedProps).length === 1 && sortedProps[DYNAMIC_FALLBACK_PROP];
291
- if (!isDynamicFallback) {
292
- for (const [propName, propMeta] of Object.entries(sortedProps)) {
293
- const optionalMark = propMeta.required ? '' : '?';
294
- lines.push(` ${propName}${optionalMark}: ${typeFromProp(propMeta)};`);
295
- }
296
- }
297
- lines.push('}');
298
- lines.push('');
299
- }
300
-
301
- lines.push('export interface SliceComponentPropsMap {');
302
- for (const componentName of Object.keys(componentsSorted)) {
303
- lines.push(` ${componentName}: ${interfaceNameFor(componentName)};`);
304
- }
305
- lines.push('}');
306
- lines.push('');
307
- lines.push('export type SliceComponentName = keyof SliceComponentPropsMap;');
308
- lines.push('export type SliceDynamicElement = HTMLElement & Record<string, any>;');
309
- lines.push('');
310
- lines.push('declare global {');
311
- lines.push(' const slice: SliceBuildApi & Record<string, any>;');
312
- lines.push('');
313
- lines.push(' interface Event {');
314
- lines.push(' detail: any;');
315
- lines.push(' key: any;');
316
- lines.push(' request: any;');
317
- lines.push(' waitUntil: any;');
318
- lines.push(' respondWith: any;');
319
- lines.push(' target: any;');
320
- lines.push(' currentTarget: any;');
321
- lines.push(' }');
322
- lines.push('');
323
- lines.push(' interface Element {');
324
- lines.push(' querySelector<E extends Element = HTMLElement>(selectors: string): E | null;');
325
- lines.push(' querySelectorAll<E extends Element = HTMLElement>(selectors: string): NodeListOf<E>;');
326
- lines.push(' }');
327
- lines.push(' interface HTMLElement {');
328
- lines.push(' [key: string]: any;');
329
- lines.push(' }');
330
- lines.push(' interface EventTarget {');
331
- lines.push(' [key: string]: any;');
332
- lines.push(' }');
333
- lines.push('');
334
- lines.push(' interface SliceBuildApi {');
335
- lines.push(' build<K extends SliceComponentName>(');
336
- lines.push(' name: K,');
337
- lines.push(' props?: SliceComponentPropsMap[K]');
338
- lines.push(' ): Promise<SliceDynamicElement | null>;');
339
- lines.push(' }');
340
- lines.push('}');
341
- lines.push('');
342
- lines.push("declare module 'slicejs-web-framework' {");
343
- lines.push(' interface SliceApi {');
344
- lines.push(' build<K extends SliceComponentName>(');
345
- lines.push(' name: K,');
346
- lines.push(' props?: SliceComponentPropsMap[K]');
347
- lines.push(' ): Promise<SliceDynamicElement | null>;');
348
- lines.push(' getComponent<K extends SliceComponentName>(');
349
- lines.push(' componentSliceId: K | `${K}-${string}`');
350
- lines.push(' ): SliceDynamicElement | undefined;');
351
- lines.push(' getComponent<T extends SliceDynamicElement = SliceDynamicElement>(');
352
- lines.push(' componentSliceId: string');
353
- lines.push(' ): T | undefined;');
354
- lines.push(' }');
355
- lines.push('}');
356
- lines.push('');
357
- lines.push('export {};');
358
- lines.push('');
359
-
360
- return lines.join('\n');
361
- };
362
-
363
- const readCategoryPathFromConfig = async (configPath, category) => {
364
- const raw = await fs.readFile(configPath, 'utf8');
365
- const config = JSON.parse(raw);
366
- const categoryEntry = config?.paths?.components?.[category];
367
- if (!categoryEntry || !categoryEntry.path) {
368
- return null;
369
- }
370
- return categoryEntry.path.replace(/^[/\\]+/, '');
371
- };
372
-
373
- const loadComponentStaticProps = async ({ projectRoot, registryMap }) => {
374
- const configPath = getConfigPath(import.meta.url, projectRoot);
375
- const categoryPathCache = new Map();
376
- const componentPropsMap = {};
377
- let skippedCount = 0;
378
- let processedCount = 0;
379
-
380
- for (const [componentName, category] of Object.entries(sortKeys(registryMap))) {
381
- if (!categoryPathCache.has(category)) {
382
- const categoryPath = await readCategoryPathFromConfig(configPath, category);
383
- categoryPathCache.set(category, categoryPath);
384
- }
385
-
386
- const categoryPath = categoryPathCache.get(category);
387
- if (!categoryPath) {
388
- skippedCount++;
389
- continue;
390
- }
391
-
392
- const componentFile = joinRoot(projectRoot, 'src', categoryPath, componentName, `${componentName}.js`);
393
- if (!(await fs.pathExists(componentFile))) {
394
- skippedCount++;
395
- continue;
396
- }
397
-
398
- let source;
399
- try {
400
- source = await fs.readFile(componentFile, 'utf8');
401
- } catch (readError) {
402
- Print.warning(`Cannot read ${componentFile}: ${readError.message}`);
403
- skippedCount++;
404
- continue;
405
- }
406
-
407
- const props = extractStaticPropsFromSource(source, componentFile);
408
- if (props) {
409
- componentPropsMap[componentName] = props;
410
- processedCount++;
411
- } else {
412
- componentPropsMap[componentName] = { [DYNAMIC_FALLBACK_PROP]: { type: 'any', required: false } };
413
- }
414
- }
415
-
416
- if (skippedCount > 0) {
417
- Print.info(`Skipped ${skippedCount} component(s) with missing or unreadable files`);
418
- }
419
-
420
- return componentPropsMap;
421
- };
422
-
423
- const generateTypesFile = async ({ projectRoot, outputPath }) => {
424
- const registryPath = getComponentsJsPath(import.meta.url, projectRoot);
425
- let registryContent;
426
- try {
427
- registryContent = await fs.readFile(registryPath, 'utf8');
428
- } catch (readError) {
429
- throw new Error(`Cannot read components registry at ${registryPath}: ${readError.message}`);
430
- }
431
- const registryMap = parseComponentsRegistry(registryContent, registryPath);
432
-
433
- const componentPropsMap = await loadComponentStaticProps({ projectRoot, registryMap });
434
-
435
- const declaration = generateDeclarationContent(componentPropsMap);
436
- await fs.ensureDir(path.dirname(outputPath));
437
- await fs.writeFile(outputPath, declaration, 'utf8');
438
-
439
- return {
440
- outputPath,
441
- componentsProcessed: Object.keys(componentPropsMap).length
442
- };
443
- };
444
-
445
- const toPosixRelative = (projectRoot, targetPath) => {
446
- const relative = path.relative(projectRoot, targetPath).replace(/\\/g, '/');
447
- return relative;
448
- };
449
-
450
- const ensureEditorConfigForTypes = async ({ projectRoot, outputPath }) => {
451
- try {
452
- const tsconfigPath = joinRoot(projectRoot, 'tsconfig.json');
453
- if (await fs.pathExists(tsconfigPath)) {
454
- return { mode: 'tsconfig_exists', filePath: tsconfigPath, includeAdded: false };
455
- }
456
-
457
- const jsconfigPath = joinRoot(projectRoot, 'jsconfig.json');
458
- const declarationGlob = (() => {
459
- const relative = toPosixRelative(projectRoot, outputPath);
460
- if (!relative) return 'src/**/*.d.ts';
461
- const idx = relative.lastIndexOf('/');
462
- if (idx === -1) return relative;
463
- const dir = relative.slice(0, idx);
464
- return `${dir}/**/*.d.ts`;
465
- })();
466
-
467
- const writeDefaultJsconfig = async () => {
468
- const jsconfig = {
469
- compilerOptions: { ...DEFAULT_EDITOR_COMPILER_OPTIONS },
470
- include: [...DEFAULT_EDITOR_INCLUDE, declarationGlob],
471
- exclude: [...DEFAULT_EDITOR_EXCLUDE]
472
- };
473
- await fs.writeFile(jsconfigPath, `${JSON.stringify(jsconfig, null, 2)}\n`, 'utf8');
474
- };
475
-
476
- if (!(await fs.pathExists(jsconfigPath))) {
477
- await writeDefaultJsconfig();
478
- return { mode: 'created_jsconfig', filePath: jsconfigPath, includeAdded: true };
479
- }
480
-
481
- let jsconfigRaw;
482
- try {
483
- jsconfigRaw = await fs.readFile(jsconfigPath, 'utf8');
484
- } catch {
485
- // Don't fail — fall back to writing the default options.
486
- await writeDefaultJsconfig();
487
- return { mode: 'reset_jsconfig', reason: 'unreadable', filePath: jsconfigPath, includeAdded: true };
488
- }
489
-
490
- let parsed;
491
- try {
492
- parsed = JSON.parse(jsconfigRaw);
493
- } catch {
494
- // The jsconfig was edited into invalid JSON (a typo, comments, trailing commas...).
495
- // Don't fail — just write the default options so editor IntelliSense keeps working.
496
- await writeDefaultJsconfig();
497
- return { mode: 'reset_jsconfig', reason: 'invalid_json', filePath: jsconfigPath, includeAdded: true };
498
- }
499
-
500
- const include = Array.isArray(parsed.include) ? parsed.include : [];
501
- const needsInclude = !include.includes(declarationGlob);
502
- const hasAllDefaultInclude = DEFAULT_EDITOR_INCLUDE.every((entry) => include.includes(entry));
503
- const hasNoisyInclude = include.some((entry) => NOISY_INCLUDE_PATTERNS.has(entry));
504
- const exclude = Array.isArray(parsed.exclude) ? parsed.exclude : [];
505
- const publicFolderExcludes = await readPublicFolderExcludes(projectRoot);
506
- const desiredExcludes = Array.from(new Set([...DEFAULT_EDITOR_EXCLUDE, ...publicFolderExcludes]));
507
- const hasAllDefaultExclude = desiredExcludes.every((entry) => exclude.includes(entry));
508
-
509
- const compilerOptions = parsed && typeof parsed.compilerOptions === 'object' && parsed.compilerOptions !== null
510
- ? { ...parsed.compilerOptions }
511
- : {};
512
-
513
- let compilerOptionsChanged = false;
514
- for (const [key, value] of Object.entries(DEFAULT_EDITOR_COMPILER_OPTIONS)) {
515
- if (compilerOptions[key] === undefined) {
516
- compilerOptions[key] = value;
517
- compilerOptionsChanged = true;
518
- }
519
- }
520
-
521
- if (!needsInclude && !compilerOptionsChanged && hasAllDefaultInclude && hasAllDefaultExclude && !hasNoisyInclude) {
522
- return { mode: 'jsconfig_already_has_include', filePath: jsconfigPath, includeAdded: false };
523
- }
524
-
525
- parsed.compilerOptions = compilerOptions;
526
- parsed.include = needsInclude ? [...include, declarationGlob] : include;
527
- const includeSet = new Set(
528
- (Array.isArray(parsed.include) ? parsed.include : []).filter((entry) => !NOISY_INCLUDE_PATTERNS.has(entry))
529
- );
530
- DEFAULT_EDITOR_INCLUDE.forEach((entry) => includeSet.add(entry));
531
- parsed.include = Array.from(includeSet);
532
-
533
- const excludeSet = new Set(Array.isArray(parsed.exclude) ? parsed.exclude : []);
534
- desiredExcludes.forEach((entry) => excludeSet.add(entry));
535
- parsed.exclude = Array.from(excludeSet);
536
- await fs.writeFile(jsconfigPath, `${JSON.stringify(parsed, null, 2)}\n`, 'utf8');
537
- return { mode: 'updated_jsconfig', filePath: jsconfigPath, includeAdded: needsInclude };
538
- } catch (error) {
539
- return {
540
- mode: 'editor_config_error',
541
- filePath: projectRoot,
542
- includeAdded: false,
543
- errorMessage: error?.message || 'Unknown editor config setup error'
544
- };
545
- }
546
- };
547
-
548
- const runGenerateTypes = async ({ projectRoot, outputPath }) => {
549
- const result = await generateTypesFile({ projectRoot, outputPath });
550
- const editorConfig = await ensureEditorConfigForTypes({ projectRoot, outputPath });
551
- const publicVendorSuppression = await ensureNoCheckInPublicVendorFiles(projectRoot);
552
- Print.success(`Generated TypeScript declarations at ${result.outputPath}`);
553
- Print.info(`Components with static props: ${result.componentsProcessed}`);
554
- if (editorConfig.mode === 'created_jsconfig') {
555
- Print.info(`Created jsconfig.json and included declaration glob for editor IntelliSense.`);
556
- } else if (editorConfig.mode === 'updated_jsconfig') {
557
- Print.info(`Updated jsconfig.json include list for declaration IntelliSense.`);
558
- } else if (editorConfig.mode === 'jsconfig_already_has_include') {
559
- Print.info(`jsconfig.json already includes declaration glob. Editor IntelliSense should pick generated types.`);
560
- } else if (editorConfig.mode === 'tsconfig_exists') {
561
- Print.info(`tsconfig.json detected. Types declaration is generated; ensure include covers ${toPosixRelative(projectRoot, outputPath)}.`);
562
- } else if (editorConfig.mode === 'reset_jsconfig') {
563
- const why = editorConfig.reason === 'invalid_json' ? 'contained invalid JSON' : 'could not be read';
564
- Print.warning(`jsconfig.json ${why}; wrote the default options so editor IntelliSense keeps working.`);
565
- Print.info(`Review ${editorConfig.filePath} if you had custom settings there.`);
566
- } else if (editorConfig.mode === 'editor_config_error') {
567
- Print.warning(`Unexpected editor config setup error: ${editorConfig.errorMessage}`);
568
- }
569
- if (publicVendorSuppression.updatedFiles > 0) {
570
- Print.info(`Added // @ts-nocheck to ${publicVendorSuppression.updatedFiles} vendor JS files from publicFolders.`);
571
- }
572
- return result;
573
- };
574
-
575
- export {
576
- ensureNoCheckInPublicVendorFiles,
577
- ensureEditorConfigForTypes,
578
- extractStaticPropsFromSource,
579
- generateDeclarationContent,
580
- generateTypesFile,
581
- parseComponentsRegistry,
582
- runGenerateTypes
583
- };
@@ -1,53 +0,0 @@
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
- }