slicejs-web-framework 3.4.4 → 3.5.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 (110) 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/Components/Structural/StylesManager/ThemeManager/ThemeManager.js +56 -0
  6. package/Slice/Slice.js +14 -1
  7. package/Slice/tests/event-emitters.test.js +79 -0
  8. package/Slice/tests/event-registry.test.js +141 -0
  9. package/Slice/tests/leak-orphans.test.js +131 -0
  10. package/api/{middleware → framework}/securityMiddleware.js +18 -74
  11. package/api/framework/server.js +333 -0
  12. package/api/index.js +5 -279
  13. package/api/tests/public-env-resolver.test.js +1 -1
  14. package/package.json +4 -2
  15. package/sliceConfig.schema.json +10 -1
  16. package/src/App/index.html +31 -1
  17. package/src/manifest.json +15 -0
  18. package/src/robots.txt +4 -0
  19. package/src/sitemap.xml +8 -0
  20. package/src/sliceConfig.json +5 -1
  21. package/types/index.d.ts +12 -0
  22. package/slicejs-cli/.github/workflows/ci.yml +0 -43
  23. package/slicejs-cli/LICENSE +0 -21
  24. package/slicejs-cli/assets/Slice.js-logo.png +0 -0
  25. package/slicejs-cli/client.js +0 -740
  26. package/slicejs-cli/commands/Print.js +0 -164
  27. package/slicejs-cli/commands/Validations.js +0 -92
  28. package/slicejs-cli/commands/build/build.js +0 -40
  29. package/slicejs-cli/commands/buildProduction/buildProduction.js +0 -577
  30. package/slicejs-cli/commands/bundle/bundle.js +0 -236
  31. package/slicejs-cli/commands/createComponent/VisualComponentTemplate.js +0 -55
  32. package/slicejs-cli/commands/createComponent/createComponent.js +0 -128
  33. package/slicejs-cli/commands/deleteComponent/deleteComponent.js +0 -81
  34. package/slicejs-cli/commands/doctor/doctor.js +0 -516
  35. package/slicejs-cli/commands/getComponent/getComponent.js +0 -695
  36. package/slicejs-cli/commands/init/init.js +0 -473
  37. package/slicejs-cli/commands/listComponents/listComponents.js +0 -172
  38. package/slicejs-cli/commands/startServer/startServer.js +0 -240
  39. package/slicejs-cli/commands/startServer/watchServer.js +0 -66
  40. package/slicejs-cli/commands/types/types.js +0 -583
  41. package/slicejs-cli/commands/utils/LocalCliDelegation.js +0 -53
  42. package/slicejs-cli/commands/utils/PackageManager.js +0 -148
  43. package/slicejs-cli/commands/utils/PathHelper.js +0 -75
  44. package/slicejs-cli/commands/utils/VersionChecker.js +0 -169
  45. package/slicejs-cli/commands/utils/bundling/BundleGenerator.js +0 -2525
  46. package/slicejs-cli/commands/utils/bundling/DependencyAnalyzer.js +0 -933
  47. package/slicejs-cli/commands/utils/loadConfig.js +0 -31
  48. package/slicejs-cli/commands/utils/sliceScripts.js +0 -48
  49. package/slicejs-cli/commands/utils/updateManager.js +0 -471
  50. package/slicejs-cli/package.json +0 -71
  51. package/slicejs-cli/playwright.config.js +0 -51
  52. package/slicejs-cli/pnpm-lock.yaml +0 -1629
  53. package/slicejs-cli/tests/build-command-integration.test.js +0 -87
  54. package/slicejs-cli/tests/build-mangling-e2e.test.js +0 -290
  55. package/slicejs-cli/tests/build-production-e2e.test.js +0 -140
  56. package/slicejs-cli/tests/builder-edge-cases.test.js +0 -322
  57. package/slicejs-cli/tests/bundle-generate-e2e.test.js +0 -115
  58. package/slicejs-cli/tests/bundle-generator.test.js +0 -691
  59. package/slicejs-cli/tests/bundle-v2-register-output.test.js +0 -470
  60. package/slicejs-cli/tests/bundling-dependency-edges.test.js +0 -127
  61. package/slicejs-cli/tests/bundling-imports-unit.test.js +0 -267
  62. package/slicejs-cli/tests/client-launcher-contract.test.js +0 -211
  63. package/slicejs-cli/tests/commands-component-crud.test.js +0 -128
  64. package/slicejs-cli/tests/commands-doctor.test.js +0 -152
  65. package/slicejs-cli/tests/commands-version-checker.test.js +0 -37
  66. package/slicejs-cli/tests/component-registry-parse.test.js +0 -34
  67. package/slicejs-cli/tests/dependency-analyzer.test.js +0 -24
  68. package/slicejs-cli/tests/e2e/bundles.spec.js +0 -91
  69. package/slicejs-cli/tests/e2e/dependency-scenarios.spec.js +0 -56
  70. package/slicejs-cli/tests/e2e/fixtures/components/Service/FetchManager/FetchManager.js +0 -136
  71. package/slicejs-cli/tests/e2e/fixtures/components/Service/IndexedDbManager/IndexedDbManager.js +0 -149
  72. package/slicejs-cli/tests/e2e/fixtures/components/Service/LocalStorageManager/LocalStorageManager.js +0 -45
  73. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.css +0 -106
  74. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.html +0 -5
  75. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Button/Button.js +0 -158
  76. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Link/Link.js +0 -33
  77. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.css +0 -56
  78. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.html +0 -83
  79. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Loading/Loading.js +0 -164
  80. package/slicejs-cli/tests/e2e/fixtures/components/Visual/MultiRoute/MultiRoute.js +0 -167
  81. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.css +0 -116
  82. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.html +0 -44
  83. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Navbar/Navbar.js +0 -180
  84. package/slicejs-cli/tests/e2e/fixtures/components/Visual/NotFound/NotFound.js +0 -20
  85. package/slicejs-cli/tests/e2e/fixtures/components/Visual/Route/Route.js +0 -181
  86. package/slicejs-cli/tests/e2e/fixtures/components/registry.json +0 -12
  87. package/slicejs-cli/tests/e2e/fixtures/vendor-components.mjs +0 -65
  88. package/slicejs-cli/tests/e2e/navigation.spec.js +0 -44
  89. package/slicejs-cli/tests/e2e/render.spec.js +0 -34
  90. package/slicejs-cli/tests/e2e/serve.mjs +0 -264
  91. package/slicejs-cli/tests/e2e/shared-deps.spec.js +0 -61
  92. package/slicejs-cli/tests/e2e/unminified.spec.js +0 -33
  93. package/slicejs-cli/tests/e2e-serve.test.js +0 -148
  94. package/slicejs-cli/tests/fixtures/components.js +0 -8
  95. package/slicejs-cli/tests/fixtures/sliceConfig.json +0 -74
  96. package/slicejs-cli/tests/getcomponent.test.js +0 -435
  97. package/slicejs-cli/tests/helpers/setup.js +0 -102
  98. package/slicejs-cli/tests/init-command-contract.test.js +0 -101
  99. package/slicejs-cli/tests/init-e2e.test.js +0 -82
  100. package/slicejs-cli/tests/init-project-isolation.test.js +0 -98
  101. package/slicejs-cli/tests/local-cli-delegation.test.js +0 -81
  102. package/slicejs-cli/tests/package-manager.test.js +0 -127
  103. package/slicejs-cli/tests/path-helper.test.js +0 -206
  104. package/slicejs-cli/tests/perf-budget.test.js +0 -84
  105. package/slicejs-cli/tests/postinstall-command.test.js +0 -103
  106. package/slicejs-cli/tests/property-mangling-regression.test.js +0 -182
  107. package/slicejs-cli/tests/types-breakage.test.js +0 -491
  108. package/slicejs-cli/tests/types-generator-errors.test.js +0 -361
  109. package/slicejs-cli/tests/types-generator.test.js +0 -348
  110. /package/api/{utils → framework}/publicEnvResolver.js +0 -0
@@ -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
- }