slicejs-cli 3.1.0 โ†’ 3.3.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 (43) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
  3. package/.github/pull_request_template.md +22 -0
  4. package/.github/workflows/docs-render-cicd.yml +65 -0
  5. package/CODE_OF_CONDUCT.md +126 -0
  6. package/ECOSYSTEM.md +9 -0
  7. package/LICENSE +21 -0
  8. package/README.md +104 -308
  9. package/client.js +644 -557
  10. package/commands/Print.js +167 -167
  11. package/commands/Validations.js +103 -103
  12. package/commands/build/build.js +40 -40
  13. package/commands/buildProduction/buildProduction.js +579 -579
  14. package/commands/bundle/bundle.js +235 -235
  15. package/commands/createComponent/VisualComponentTemplate.js +55 -55
  16. package/commands/createComponent/createComponent.js +126 -126
  17. package/commands/deleteComponent/deleteComponent.js +77 -77
  18. package/commands/doctor/doctor.js +369 -369
  19. package/commands/getComponent/getComponent.js +747 -747
  20. package/commands/init/init.js +261 -261
  21. package/commands/listComponents/listComponents.js +175 -175
  22. package/commands/startServer/startServer.js +264 -264
  23. package/commands/startServer/watchServer.js +79 -79
  24. package/commands/types/types.js +538 -0
  25. package/commands/utils/LocalCliDelegation.js +53 -53
  26. package/commands/utils/PathHelper.js +68 -68
  27. package/commands/utils/VersionChecker.js +167 -167
  28. package/commands/utils/bundling/BundleGenerator.js +2292 -2292
  29. package/commands/utils/bundling/DependencyAnalyzer.js +933 -933
  30. package/commands/utils/updateManager.js +453 -453
  31. package/docs/superpowers/specs/2026-05-10-pwa-generate-design.md +182 -0
  32. package/package.json +46 -46
  33. package/post.js +65 -25
  34. package/tests/bundle-generator.test.js +708 -708
  35. package/tests/bundle-v2-register-output.test.js +470 -470
  36. package/tests/client-launcher-contract.test.js +211 -211
  37. package/tests/client-update-flow-contract.test.js +272 -272
  38. package/tests/dependency-analyzer.test.js +24 -24
  39. package/tests/local-cli-delegation.test.js +79 -79
  40. package/tests/postinstall-command.test.js +72 -0
  41. package/tests/types-generator.test.js +356 -0
  42. package/tests/update-manager-notifications.test.js +88 -88
  43. package/refactor.md +0 -271
@@ -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
+ * 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
+ }
@@ -0,0 +1,538 @@
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
+
8
+ const TYPE_MAP = {
9
+ string: 'string',
10
+ number: 'number',
11
+ boolean: 'boolean',
12
+ object: 'Record<string, unknown>',
13
+ array: 'unknown[]',
14
+ function: '(...args: unknown[]) => unknown',
15
+ any: 'unknown'
16
+ };
17
+
18
+ const literalValueFromAst = (node) => {
19
+ if (!node) return undefined;
20
+ if (node.type === 'StringLiteral' || node.type === 'NumericLiteral' || node.type === 'BooleanLiteral') {
21
+ return node.value;
22
+ }
23
+ if (node.type === 'NullLiteral') {
24
+ return null;
25
+ }
26
+ return undefined;
27
+ };
28
+
29
+ const keyNameFromAst = (keyNode) => {
30
+ if (!keyNode) return null;
31
+ if (keyNode.type === 'Identifier') return keyNode.name;
32
+ if (keyNode.type === 'StringLiteral') return keyNode.value;
33
+ return null;
34
+ };
35
+
36
+ const astNodeToValue = (node) => {
37
+ if (!node) return undefined;
38
+
39
+ const literal = literalValueFromAst(node);
40
+ if (literal !== undefined || (node && node.type === 'NullLiteral')) {
41
+ return literal;
42
+ }
43
+
44
+ if (node.type === 'ArrayExpression') {
45
+ return (node.elements || [])
46
+ .map((element) => astNodeToValue(element))
47
+ .filter((value) => value !== undefined);
48
+ }
49
+
50
+ if (node.type === 'ObjectExpression') {
51
+ const out = {};
52
+ for (const property of node.properties || []) {
53
+ if (property.type !== 'ObjectProperty') continue;
54
+ const key = keyNameFromAst(property.key);
55
+ if (!key) continue;
56
+ const value = astNodeToValue(property.value);
57
+ if (value !== undefined) {
58
+ out[key] = value;
59
+ }
60
+ }
61
+ return out;
62
+ }
63
+
64
+ return undefined;
65
+ };
66
+
67
+ const normalizePropConfig = (rawConfig) => {
68
+ if (!rawConfig || typeof rawConfig !== 'object' || Array.isArray(rawConfig)) {
69
+ return { type: 'any', required: false };
70
+ }
71
+
72
+ const config = {
73
+ type: typeof rawConfig.type === 'string' ? rawConfig.type.toLowerCase() : 'any',
74
+ required: rawConfig.required === true
75
+ };
76
+
77
+ if (Array.isArray(rawConfig.allowedValues) && rawConfig.allowedValues.length > 0) {
78
+ config.allowedValues = rawConfig.allowedValues;
79
+ }
80
+
81
+ if (rawConfig.schema && typeof rawConfig.schema === 'object' && !Array.isArray(rawConfig.schema)) {
82
+ const schema = {};
83
+ for (const [name, nestedRaw] of Object.entries(rawConfig.schema)) {
84
+ schema[name] = normalizePropConfig(nestedRaw);
85
+ }
86
+ config.schema = schema;
87
+ }
88
+
89
+ if (rawConfig.items && typeof rawConfig.items === 'object' && !Array.isArray(rawConfig.items)) {
90
+ config.items = normalizePropConfig(rawConfig.items);
91
+ }
92
+
93
+ return config;
94
+ };
95
+
96
+ const extractStaticPropsFromObjectExpression = (objectExpressionNode) => {
97
+ const raw = astNodeToValue(objectExpressionNode);
98
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
99
+
100
+ const props = {};
101
+ for (const [propName, rawConfig] of Object.entries(raw)) {
102
+ props[propName] = normalizePropConfig(rawConfig);
103
+ }
104
+
105
+ return Object.keys(props).length > 0 ? props : null;
106
+ };
107
+
108
+ const sortKeys = (obj) => {
109
+ return Object.keys(obj)
110
+ .sort((a, b) => a.localeCompare(b))
111
+ .reduce((acc, key) => {
112
+ acc[key] = obj[key];
113
+ return acc;
114
+ }, {});
115
+ };
116
+
117
+ const parseComponentsRegistry = (content) => {
118
+ const match = content.match(/const components = ({[\s\S]*?});/);
119
+ if (!match) {
120
+ throw new Error('Invalid components.js format. Expected: const components = { ... };');
121
+ }
122
+ return JSON.parse(match[1]);
123
+ };
124
+
125
+ const extractStaticPropsFromSource = (source) => {
126
+ let ast;
127
+ try {
128
+ ast = parse(source, {
129
+ sourceType: 'module',
130
+ plugins: ['classProperties']
131
+ });
132
+ } catch {
133
+ return null;
134
+ }
135
+
136
+ let staticPropsObject = null;
137
+ traverse.default(ast, {
138
+ ClassProperty(pathRef) {
139
+ const node = pathRef.node;
140
+ if (!node.static || !node.key || keyNameFromAst(node.key) !== 'props') return;
141
+ if (node.value && node.value.type === 'ObjectExpression') {
142
+ staticPropsObject = node.value;
143
+ }
144
+ }
145
+ });
146
+
147
+ if (!staticPropsObject) return null;
148
+ return extractStaticPropsFromObjectExpression(staticPropsObject);
149
+ };
150
+
151
+ const typeFromProp = (propMeta) => {
152
+ if (propMeta.type === 'object' && propMeta.schema && typeof propMeta.schema === 'object') {
153
+ const schemaEntries = Object.entries(sortKeys(propMeta.schema));
154
+ const inner = schemaEntries
155
+ .map(([name, meta]) => {
156
+ const optionalMark = meta.required ? '' : '?';
157
+ return `${name}${optionalMark}: ${typeFromProp(meta)};`;
158
+ })
159
+ .join(' ');
160
+ return `{ ${inner} }`;
161
+ }
162
+
163
+ if (propMeta.type === 'array' && propMeta.items && typeof propMeta.items === 'object') {
164
+ return `${typeFromProp(propMeta.items)}[]`;
165
+ }
166
+
167
+ const allowedValues = Array.isArray(propMeta.allowedValues) ? propMeta.allowedValues : [];
168
+
169
+ if (allowedValues.length > 0 && propMeta.type === 'string' && allowedValues.every((value) => typeof value === 'string')) {
170
+ return allowedValues.map((value) => `'${String(value).replace(/'/g, "\\'")}'`).join(' | ');
171
+ }
172
+
173
+ if (allowedValues.length > 0 && propMeta.type === 'number' && allowedValues.every((value) => typeof value === 'number' && Number.isFinite(value))) {
174
+ return allowedValues.join(' | ');
175
+ }
176
+
177
+ return TYPE_MAP[propMeta.type] || 'unknown';
178
+ };
179
+
180
+ const interfaceNameFor = (componentName) => `${componentName}Props`;
181
+ const DYNAMIC_FALLBACK_PROP = '__dynamicPropsFallback';
182
+
183
+ const DEFAULT_EDITOR_COMPILER_OPTIONS = {
184
+ allowJs: true,
185
+ checkJs: true,
186
+ strict: false,
187
+ noImplicitAny: false,
188
+ strictNullChecks: false,
189
+ maxNodeModuleJsDepth: 2
190
+ };
191
+
192
+ const DEFAULT_EDITOR_INCLUDE = ['src/Components/**/*.js', 'src/**/*.d.ts'];
193
+ const DEFAULT_EDITOR_EXCLUDE = ['node_modules', 'dist', 'src/libs/**', 'tests/**'];
194
+ const NOISY_INCLUDE_PATTERNS = new Set(['src/**/*.js', 'api/**/*.js', 'tests/**/*.js']);
195
+
196
+ const readPublicFolderExcludes = async (projectRoot) => {
197
+ const configPath = path.join(projectRoot, 'src', 'sliceConfig.json');
198
+ if (!(await fs.pathExists(configPath))) return [];
199
+
200
+ try {
201
+ const raw = await fs.readFile(configPath, 'utf8');
202
+ const parsed = JSON.parse(raw);
203
+ const folders = Array.isArray(parsed?.publicFolders) ? parsed.publicFolders : [];
204
+ return folders
205
+ .map((folder) => String(folder || '').trim())
206
+ .filter(Boolean)
207
+ .map((folder) => folder.replace(/^[/\\]+/, ''))
208
+ .map((folder) => `src/${folder}/**`);
209
+ } catch {
210
+ return [];
211
+ }
212
+ };
213
+
214
+ const collectJavaScriptFiles = async (dirPath) => {
215
+ if (!(await fs.pathExists(dirPath))) return [];
216
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
217
+ const files = [];
218
+
219
+ for (const entry of entries) {
220
+ const fullPath = path.join(dirPath, entry.name);
221
+ if (entry.isDirectory()) {
222
+ const nested = await collectJavaScriptFiles(fullPath);
223
+ files.push(...nested);
224
+ } else if (entry.isFile() && entry.name.toLowerCase().endsWith('.js')) {
225
+ files.push(fullPath);
226
+ }
227
+ }
228
+
229
+ return files;
230
+ };
231
+
232
+ const ensureNoCheckInPublicVendorFiles = async (projectRoot) => {
233
+ const configPath = path.join(projectRoot, 'src', 'sliceConfig.json');
234
+ if (!(await fs.pathExists(configPath))) return { updatedFiles: 0, scannedFiles: 0 };
235
+
236
+ let parsed;
237
+ try {
238
+ parsed = JSON.parse(await fs.readFile(configPath, 'utf8'));
239
+ } catch {
240
+ return { updatedFiles: 0, scannedFiles: 0 };
241
+ }
242
+
243
+ const publicFolders = Array.isArray(parsed?.publicFolders) ? parsed.publicFolders : [];
244
+ const candidateDirs = publicFolders
245
+ .map((folder) => String(folder || '').trim())
246
+ .filter(Boolean)
247
+ .map((folder) => folder.replace(/^[/\\]+/, ''))
248
+ .map((folder) => path.join(projectRoot, 'src', folder));
249
+
250
+ const uniqueDirs = Array.from(new Set(candidateDirs));
251
+ let scannedFiles = 0;
252
+ let updatedFiles = 0;
253
+
254
+ for (const dirPath of uniqueDirs) {
255
+ const jsFiles = await collectJavaScriptFiles(dirPath);
256
+ for (const filePath of jsFiles) {
257
+ scannedFiles += 1;
258
+ const raw = await fs.readFile(filePath, 'utf8');
259
+ if (raw.startsWith('// @ts-nocheck')) {
260
+ continue;
261
+ }
262
+
263
+ await fs.writeFile(filePath, `// @ts-nocheck\n${raw}`, 'utf8');
264
+ updatedFiles += 1;
265
+ }
266
+ }
267
+
268
+ return { updatedFiles, scannedFiles };
269
+ };
270
+
271
+ const generateDeclarationContent = (componentPropsMap) => {
272
+ const componentsSorted = sortKeys(componentPropsMap);
273
+ const lines = [];
274
+
275
+ lines.push('/* Auto-generated by slice types generate. Do not edit manually. */');
276
+ lines.push('');
277
+
278
+ for (const [componentName, props] of Object.entries(componentsSorted)) {
279
+ lines.push(`export interface ${interfaceNameFor(componentName)} {`);
280
+ lines.push(' [key: string]: unknown;');
281
+ const sortedProps = sortKeys(props);
282
+ const isDynamicFallback = Object.keys(sortedProps).length === 1 && sortedProps[DYNAMIC_FALLBACK_PROP];
283
+ if (!isDynamicFallback) {
284
+ for (const [propName, propMeta] of Object.entries(sortedProps)) {
285
+ const optionalMark = propMeta.required ? '' : '?';
286
+ lines.push(` ${propName}${optionalMark}: ${typeFromProp(propMeta)};`);
287
+ }
288
+ }
289
+ lines.push('}');
290
+ lines.push('');
291
+ }
292
+
293
+ lines.push('export interface SliceComponentPropsMap {');
294
+ for (const componentName of Object.keys(componentsSorted)) {
295
+ lines.push(` ${componentName}: ${interfaceNameFor(componentName)};`);
296
+ }
297
+ lines.push('}');
298
+ lines.push('');
299
+ lines.push('export type SliceComponentName = keyof SliceComponentPropsMap;');
300
+ lines.push('export type SliceDynamicElement = HTMLElement & Record<string, any>;');
301
+ lines.push('');
302
+ lines.push('declare global {');
303
+ lines.push(' const slice: SliceBuildApi & Record<string, any>;');
304
+ lines.push('');
305
+ lines.push(' interface Event {');
306
+ lines.push(' detail: any;');
307
+ lines.push(' key: any;');
308
+ lines.push(' request: any;');
309
+ lines.push(' waitUntil: any;');
310
+ lines.push(' respondWith: any;');
311
+ lines.push(' target: any;');
312
+ lines.push(' currentTarget: any;');
313
+ lines.push(' }');
314
+ lines.push('');
315
+ lines.push(' interface Element {');
316
+ lines.push(' querySelector<E extends Element = HTMLElement>(selectors: string): E | null;');
317
+ lines.push(' querySelectorAll<E extends Element = HTMLElement>(selectors: string): NodeListOf<E>;');
318
+ lines.push(' }');
319
+ lines.push(' interface HTMLElement {');
320
+ lines.push(' [key: string]: any;');
321
+ lines.push(' }');
322
+ lines.push(' interface EventTarget {');
323
+ lines.push(' [key: string]: any;');
324
+ lines.push(' }');
325
+ lines.push('');
326
+ lines.push(' interface SliceBuildApi {');
327
+ lines.push(' build<K extends SliceComponentName>(');
328
+ lines.push(' name: K,');
329
+ lines.push(' props?: SliceComponentPropsMap[K]');
330
+ lines.push(' ): Promise<SliceDynamicElement | null>;');
331
+ lines.push(' }');
332
+ lines.push('}');
333
+ lines.push('');
334
+ lines.push("declare module 'slicejs-web-framework' {");
335
+ lines.push(' interface SliceApi {');
336
+ lines.push(' build<K extends SliceComponentName>(');
337
+ lines.push(' name: K,');
338
+ lines.push(' props?: SliceComponentPropsMap[K]');
339
+ lines.push(' ): Promise<SliceDynamicElement | null>;');
340
+ lines.push(' getComponent<T extends SliceDynamicElement = SliceDynamicElement>(');
341
+ lines.push(' componentSliceId: string');
342
+ lines.push(' ): T | undefined;');
343
+ lines.push(' }');
344
+ lines.push('}');
345
+ lines.push('');
346
+ lines.push('export {};');
347
+ lines.push('');
348
+
349
+ return lines.join('\n');
350
+ };
351
+
352
+ const readCategoryPathFromConfig = async (configPath, category) => {
353
+ const raw = await fs.readFile(configPath, 'utf8');
354
+ const config = JSON.parse(raw);
355
+ const categoryEntry = config?.paths?.components?.[category];
356
+ if (!categoryEntry || !categoryEntry.path) {
357
+ return null;
358
+ }
359
+ return categoryEntry.path.replace(/^[/\\]+/, '');
360
+ };
361
+
362
+ const loadComponentStaticProps = async ({ projectRoot, registryMap }) => {
363
+ const configPath = path.join(projectRoot, 'src', 'sliceConfig.json');
364
+ const categoryPathCache = new Map();
365
+ const componentPropsMap = {};
366
+
367
+ for (const [componentName, category] of Object.entries(sortKeys(registryMap))) {
368
+ if (!categoryPathCache.has(category)) {
369
+ const categoryPath = await readCategoryPathFromConfig(configPath, category);
370
+ categoryPathCache.set(category, categoryPath);
371
+ }
372
+
373
+ const categoryPath = categoryPathCache.get(category);
374
+ if (!categoryPath) {
375
+ continue;
376
+ }
377
+
378
+ const componentFile = path.join(projectRoot, 'src', categoryPath, componentName, `${componentName}.js`);
379
+ if (!(await fs.pathExists(componentFile))) {
380
+ continue;
381
+ }
382
+
383
+ const source = await fs.readFile(componentFile, 'utf8');
384
+ const props = extractStaticPropsFromSource(source);
385
+ componentPropsMap[componentName] = props || { [DYNAMIC_FALLBACK_PROP]: { type: 'any', required: false } };
386
+ }
387
+
388
+ return componentPropsMap;
389
+ };
390
+
391
+ 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);
395
+
396
+ const componentPropsMap = await loadComponentStaticProps({ projectRoot, registryMap });
397
+
398
+ const declaration = generateDeclarationContent(componentPropsMap);
399
+ await fs.ensureDir(path.dirname(outputPath));
400
+ await fs.writeFile(outputPath, declaration, 'utf8');
401
+
402
+ return {
403
+ outputPath,
404
+ componentsProcessed: Object.keys(componentPropsMap).length
405
+ };
406
+ };
407
+
408
+ const toPosixRelative = (projectRoot, targetPath) => {
409
+ const relative = path.relative(projectRoot, targetPath).replace(/\\/g, '/');
410
+ return relative;
411
+ };
412
+
413
+ const ensureEditorConfigForTypes = async ({ projectRoot, outputPath }) => {
414
+ try {
415
+ const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
416
+ if (await fs.pathExists(tsconfigPath)) {
417
+ return { mode: 'tsconfig_exists', filePath: tsconfigPath, includeAdded: false };
418
+ }
419
+
420
+ const jsconfigPath = path.join(projectRoot, 'jsconfig.json');
421
+ const declarationGlob = (() => {
422
+ const relative = toPosixRelative(projectRoot, outputPath);
423
+ if (!relative) return 'src/**/*.d.ts';
424
+ const idx = relative.lastIndexOf('/');
425
+ if (idx === -1) return relative;
426
+ const dir = relative.slice(0, idx);
427
+ return `${dir}/**/*.d.ts`;
428
+ })();
429
+
430
+ if (!(await fs.pathExists(jsconfigPath))) {
431
+ const jsconfig = {
432
+ compilerOptions: { ...DEFAULT_EDITOR_COMPILER_OPTIONS },
433
+ include: [...DEFAULT_EDITOR_INCLUDE, declarationGlob],
434
+ exclude: [...DEFAULT_EDITOR_EXCLUDE]
435
+ };
436
+ await fs.writeFile(jsconfigPath, `${JSON.stringify(jsconfig, null, 2)}\n`, 'utf8');
437
+ return { mode: 'created_jsconfig', filePath: jsconfigPath, includeAdded: true };
438
+ }
439
+
440
+ let jsconfigRaw;
441
+ try {
442
+ jsconfigRaw = await fs.readFile(jsconfigPath, 'utf8');
443
+ } catch {
444
+ return { mode: 'jsconfig_unreadable', filePath: jsconfigPath, includeAdded: false };
445
+ }
446
+
447
+ let parsed;
448
+ try {
449
+ parsed = JSON.parse(jsconfigRaw);
450
+ } catch {
451
+ return { mode: 'jsconfig_invalid_json', filePath: jsconfigPath, includeAdded: false };
452
+ }
453
+
454
+ const include = Array.isArray(parsed.include) ? parsed.include : [];
455
+ const needsInclude = !include.includes(declarationGlob);
456
+ const hasAllDefaultInclude = DEFAULT_EDITOR_INCLUDE.every((entry) => include.includes(entry));
457
+ const hasNoisyInclude = include.some((entry) => NOISY_INCLUDE_PATTERNS.has(entry));
458
+ const exclude = Array.isArray(parsed.exclude) ? parsed.exclude : [];
459
+ const publicFolderExcludes = await readPublicFolderExcludes(projectRoot);
460
+ const desiredExcludes = Array.from(new Set([...DEFAULT_EDITOR_EXCLUDE, ...publicFolderExcludes]));
461
+ const hasAllDefaultExclude = desiredExcludes.every((entry) => exclude.includes(entry));
462
+
463
+ const compilerOptions = parsed && typeof parsed.compilerOptions === 'object' && parsed.compilerOptions !== null
464
+ ? { ...parsed.compilerOptions }
465
+ : {};
466
+
467
+ let compilerOptionsChanged = false;
468
+ for (const [key, value] of Object.entries(DEFAULT_EDITOR_COMPILER_OPTIONS)) {
469
+ if (compilerOptions[key] === undefined) {
470
+ compilerOptions[key] = value;
471
+ compilerOptionsChanged = true;
472
+ }
473
+ }
474
+
475
+ if (!needsInclude && !compilerOptionsChanged && hasAllDefaultInclude && hasAllDefaultExclude && !hasNoisyInclude) {
476
+ return { mode: 'jsconfig_already_has_include', filePath: jsconfigPath, includeAdded: false };
477
+ }
478
+
479
+ parsed.compilerOptions = compilerOptions;
480
+ parsed.include = needsInclude ? [...include, declarationGlob] : include;
481
+ const includeSet = new Set(
482
+ (Array.isArray(parsed.include) ? parsed.include : []).filter((entry) => !NOISY_INCLUDE_PATTERNS.has(entry))
483
+ );
484
+ DEFAULT_EDITOR_INCLUDE.forEach((entry) => includeSet.add(entry));
485
+ parsed.include = Array.from(includeSet);
486
+
487
+ const excludeSet = new Set(Array.isArray(parsed.exclude) ? parsed.exclude : []);
488
+ desiredExcludes.forEach((entry) => excludeSet.add(entry));
489
+ parsed.exclude = Array.from(excludeSet);
490
+ await fs.writeFile(jsconfigPath, `${JSON.stringify(parsed, null, 2)}\n`, 'utf8');
491
+ return { mode: 'updated_jsconfig', filePath: jsconfigPath, includeAdded: needsInclude };
492
+ } catch (error) {
493
+ return {
494
+ mode: 'editor_config_error',
495
+ filePath: projectRoot,
496
+ includeAdded: false,
497
+ errorMessage: error?.message || 'Unknown editor config setup error'
498
+ };
499
+ }
500
+ };
501
+
502
+ const runGenerateTypes = async ({ projectRoot, outputPath }) => {
503
+ const result = await generateTypesFile({ projectRoot, outputPath });
504
+ const editorConfig = await ensureEditorConfigForTypes({ projectRoot, outputPath });
505
+ const publicVendorSuppression = await ensureNoCheckInPublicVendorFiles(projectRoot);
506
+ Print.success(`Generated TypeScript declarations at ${result.outputPath}`);
507
+ Print.info(`Components with static props: ${result.componentsProcessed}`);
508
+ if (editorConfig.mode === 'created_jsconfig') {
509
+ Print.info(`Created jsconfig.json and included declaration glob for editor IntelliSense.`);
510
+ } else if (editorConfig.mode === 'updated_jsconfig') {
511
+ Print.info(`Updated jsconfig.json include list for declaration IntelliSense.`);
512
+ } else if (editorConfig.mode === 'jsconfig_already_has_include') {
513
+ Print.info(`jsconfig.json already includes declaration glob. Editor IntelliSense should pick generated types.`);
514
+ } else if (editorConfig.mode === 'tsconfig_exists') {
515
+ 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.`);
522
+ } else if (editorConfig.mode === 'editor_config_error') {
523
+ Print.warning(`Unexpected editor config setup error: ${editorConfig.errorMessage}`);
524
+ }
525
+ if (publicVendorSuppression.updatedFiles > 0) {
526
+ Print.info(`Added // @ts-nocheck to ${publicVendorSuppression.updatedFiles} vendor JS files from publicFolders.`);
527
+ }
528
+ return result;
529
+ };
530
+
531
+ export {
532
+ ensureNoCheckInPublicVendorFiles,
533
+ ensureEditorConfigForTypes,
534
+ extractStaticPropsFromSource,
535
+ generateDeclarationContent,
536
+ generateTypesFile,
537
+ runGenerateTypes
538
+ };