pulse-js-framework 1.10.0 → 1.10.3
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/compiler/parser/_extract.js +393 -0
- package/compiler/parser/blocks.js +361 -0
- package/compiler/parser/core.js +306 -0
- package/compiler/parser/expressions.js +386 -0
- package/compiler/parser/imports.js +108 -0
- package/compiler/parser/index.js +47 -0
- package/compiler/parser/state.js +155 -0
- package/compiler/parser/style.js +445 -0
- package/compiler/parser/view.js +632 -0
- package/compiler/parser.js +15 -2372
- package/compiler/parser.js.original +2376 -0
- package/package.json +2 -1
- package/runtime/a11y/announcements.js +213 -0
- package/runtime/a11y/contrast.js +125 -0
- package/runtime/a11y/focus.js +412 -0
- package/runtime/a11y/index.js +35 -0
- package/runtime/a11y/preferences.js +121 -0
- package/runtime/a11y/utils.js +164 -0
- package/runtime/a11y/validation.js +258 -0
- package/runtime/a11y/widgets.js +545 -0
- package/runtime/a11y.js +15 -1840
- package/runtime/a11y.js.original +1844 -0
- package/runtime/graphql/cache.js +69 -0
- package/runtime/graphql/client.js +563 -0
- package/runtime/graphql/hooks.js +492 -0
- package/runtime/graphql/index.js +62 -0
- package/runtime/graphql/subscriptions.js +241 -0
- package/runtime/graphql.js +12 -1322
- package/runtime/graphql.js.original +1326 -0
- package/runtime/router/core.js +956 -0
- package/runtime/router/guards.js +90 -0
- package/runtime/router/history.js +204 -0
- package/runtime/router/index.js +36 -0
- package/runtime/router/lazy.js +180 -0
- package/runtime/router/utils.js +226 -0
- package/runtime/router.js +12 -1600
- package/runtime/router.js.original +1605 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parser extraction script - splits parser.js into sub-modules
|
|
3
|
+
* Run: node compiler/parser/_extract.js
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const source = fs.readFileSync(path.join(__dirname, '..', 'parser.js.original'), 'utf-8');
|
|
11
|
+
const lines = source.split('\n');
|
|
12
|
+
|
|
13
|
+
// Helper: extract lines (1-indexed, inclusive)
|
|
14
|
+
function extract(start, end) {
|
|
15
|
+
return lines.slice(start - 1, end).join('\n');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Helper: extract a method body and convert to prototype assignment
|
|
19
|
+
function extractMethod(methodName, start, end) {
|
|
20
|
+
const methodLines = lines.slice(start - 1, end);
|
|
21
|
+
const body = [];
|
|
22
|
+
|
|
23
|
+
// Find the method signature line
|
|
24
|
+
let sigIdx = 0;
|
|
25
|
+
for (let i = 0; i < methodLines.length; i++) {
|
|
26
|
+
if (methodLines[i].match(new RegExp(`^\\s+${methodName}\\s*\\(`)) ||
|
|
27
|
+
methodLines[i].match(new RegExp(`^\\s+async\\s+${methodName}\\s*\\(`))) {
|
|
28
|
+
sigIdx = i;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Find where JSDoc starts (look backwards from signature)
|
|
34
|
+
let docStart = sigIdx;
|
|
35
|
+
for (let i = sigIdx - 1; i >= 0; i--) {
|
|
36
|
+
if (methodLines[i].trim().startsWith('/**') || methodLines[i].trim().startsWith('*')) {
|
|
37
|
+
docStart = i;
|
|
38
|
+
} else if (methodLines[i].trim() === '') {
|
|
39
|
+
// Skip blank lines before JSDoc
|
|
40
|
+
if (docStart < sigIdx) break;
|
|
41
|
+
} else {
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Extract JSDoc (if any) - keep indentation as-is
|
|
47
|
+
const jsdocLines = methodLines.slice(docStart, sigIdx);
|
|
48
|
+
|
|
49
|
+
// Extract method signature
|
|
50
|
+
const sigLine = methodLines[sigIdx];
|
|
51
|
+
const isAsync = sigLine.includes('async');
|
|
52
|
+
const paramsMatch = sigLine.match(/\(([^)]*)\)/);
|
|
53
|
+
const params = paramsMatch ? paramsMatch[1] : '';
|
|
54
|
+
|
|
55
|
+
// Extract body (everything after the signature line until the closing brace)
|
|
56
|
+
const bodyLines = methodLines.slice(sigIdx + 1, methodLines.length);
|
|
57
|
+
|
|
58
|
+
// Build prototype assignment
|
|
59
|
+
const jsdoc = jsdocLines.length > 0 ? jsdocLines.join('\n') + '\n' : '';
|
|
60
|
+
const asyncPrefix = isAsync ? 'async ' : '';
|
|
61
|
+
|
|
62
|
+
return `${jsdoc} ${asyncPrefix}${methodName}(${params}) {\n${bodyLines.join('\n')}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Define module structure with method line ranges
|
|
66
|
+
const modules = {
|
|
67
|
+
// core.js: lines 1-290 (NodeType, ASTNode, Parser class with core methods + parse())
|
|
68
|
+
'core.js': {
|
|
69
|
+
header: `/**
|
|
70
|
+
* Pulse Parser - Core
|
|
71
|
+
*
|
|
72
|
+
* Parser class, AST node types, and infrastructure methods
|
|
73
|
+
*
|
|
74
|
+
* @module pulse-js-framework/compiler/parser/core
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
import { TokenType } from '../lexer.js';
|
|
78
|
+
import { ParserError, SUGGESTIONS, getDocsUrl } from '../../runtime/errors.js';
|
|
79
|
+
`,
|
|
80
|
+
content: extract(10, 290),
|
|
81
|
+
// We'll close the class later after all methods are extracted
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
// imports.js: lines 300-380
|
|
85
|
+
'imports.js': {
|
|
86
|
+
header: `/**
|
|
87
|
+
* Pulse Parser - Import Parsing
|
|
88
|
+
*
|
|
89
|
+
* Import, page, and route declaration parsing
|
|
90
|
+
*
|
|
91
|
+
* @module pulse-js-framework/compiler/parser/imports
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
import { TokenType } from '../lexer.js';
|
|
95
|
+
import { Parser, NodeType, ASTNode } from './core.js';
|
|
96
|
+
`,
|
|
97
|
+
methods: [
|
|
98
|
+
{ name: 'parseImportDeclaration', start: 292, end: 362 },
|
|
99
|
+
{ name: 'parsePageDeclaration', start: 364, end: 371 },
|
|
100
|
+
{ name: 'parseRouteDeclaration', start: 373, end: 380 },
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
// state.js: lines 389-513
|
|
105
|
+
'state.js': {
|
|
106
|
+
header: `/**
|
|
107
|
+
* Pulse Parser - State & Props
|
|
108
|
+
*
|
|
109
|
+
* Props block, state block, and value/literal parsing
|
|
110
|
+
*
|
|
111
|
+
* @module pulse-js-framework/compiler/parser/state
|
|
112
|
+
*/
|
|
113
|
+
|
|
114
|
+
import { TokenType } from '../lexer.js';
|
|
115
|
+
import { Parser, NodeType, ASTNode } from './core.js';
|
|
116
|
+
`,
|
|
117
|
+
methods: [
|
|
118
|
+
{ name: 'parsePropsBlock', start: 382, end: 400 },
|
|
119
|
+
{ name: 'parsePropsProperty', start: 402, end: 410 },
|
|
120
|
+
{ name: 'parseStateBlock', start: 412, end: 426 },
|
|
121
|
+
{ name: 'parseStateProperty', start: 428, end: 436 },
|
|
122
|
+
{ name: 'tryParseLiteral', start: 438, end: 455 },
|
|
123
|
+
{ name: 'parseValue', start: 457, end: 472 },
|
|
124
|
+
{ name: 'parseObjectLiteral', start: 474, end: 494 },
|
|
125
|
+
{ name: 'parseArrayLiteral', start: 496, end: 513 },
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
// view.js: lines 518-735 + directives 740-1133
|
|
130
|
+
'view.js': {
|
|
131
|
+
header: `/**
|
|
132
|
+
* Pulse Parser - View & Directives
|
|
133
|
+
*
|
|
134
|
+
* View block, elements, text nodes, and all directive parsing
|
|
135
|
+
*
|
|
136
|
+
* @module pulse-js-framework/compiler/parser/view
|
|
137
|
+
*/
|
|
138
|
+
|
|
139
|
+
import { TokenType } from '../lexer.js';
|
|
140
|
+
import { Parser, NodeType, ASTNode } from './core.js';
|
|
141
|
+
`,
|
|
142
|
+
methods: [
|
|
143
|
+
{ name: 'parseViewBlock', start: 515, end: 529 },
|
|
144
|
+
{ name: 'parseViewChild', start: 531, end: 554 },
|
|
145
|
+
{ name: 'parseSlotElement', start: 556, end: 588 },
|
|
146
|
+
{ name: 'parseElement', start: 590, end: 652 },
|
|
147
|
+
{ name: 'parseComponentProp', start: 654, end: 678 },
|
|
148
|
+
{ name: 'couldBeElement', start: 680, end: 688 },
|
|
149
|
+
{ name: 'parseTextNode', start: 690, end: 697 },
|
|
150
|
+
{ name: 'parseInterpolatedString', start: 699, end: 735 },
|
|
151
|
+
// Directives (tightly coupled with view)
|
|
152
|
+
{ name: 'parseDirective', start: 737, end: 821 },
|
|
153
|
+
{ name: 'parseInlineDirective', start: 823, end: 869 },
|
|
154
|
+
{ name: 'parseIfDirective', start: 871, end: 952 },
|
|
155
|
+
{ name: 'parseEachDirective', start: 954, end: 989 },
|
|
156
|
+
{ name: 'parseEventDirective', start: 991, end: 1011 },
|
|
157
|
+
{ name: 'parseModelDirective', start: 1013, end: 1024 },
|
|
158
|
+
{ name: 'parseA11yDirective', start: 1026, end: 1066 },
|
|
159
|
+
{ name: 'parseLiveDirective', start: 1068, end: 1083 },
|
|
160
|
+
{ name: 'parseFocusTrapDirective', start: 1085, end: 1124 },
|
|
161
|
+
{ name: 'parseSrOnlyDirective', start: 1126, end: 1133 },
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// expressions.js: lines 1138-1500
|
|
166
|
+
'expressions.js': {
|
|
167
|
+
header: `/**
|
|
168
|
+
* Pulse Parser - Expression Parsing
|
|
169
|
+
*
|
|
170
|
+
* Expression parsing with precedence climbing algorithm
|
|
171
|
+
*
|
|
172
|
+
* @module pulse-js-framework/compiler/parser/expressions
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
import { TokenType } from '../lexer.js';
|
|
176
|
+
import { Parser, NodeType, ASTNode } from './core.js';
|
|
177
|
+
`,
|
|
178
|
+
methods: [
|
|
179
|
+
{ name: 'parseExpression', start: 1135, end: 1140 },
|
|
180
|
+
{ name: 'parseAssignmentExpression', start: 1142, end: 1171 },
|
|
181
|
+
{ name: 'parseConditionalExpression', start: 1173, end: 1188 },
|
|
182
|
+
// BINARY_OPS static property needs special handling
|
|
183
|
+
{ name: 'parseBinaryExpr', start: 1202, end: 1220 },
|
|
184
|
+
{ name: 'parseOrExpression', start: 1222, end: 1223 },
|
|
185
|
+
{ name: 'parseUnaryExpression', start: 1225, end: 1241 },
|
|
186
|
+
{ name: 'parsePostfixExpression', start: 1243, end: 1259 },
|
|
187
|
+
{ name: 'parsePrimaryExpression', start: 1261, end: 1326 },
|
|
188
|
+
{ name: 'tryParseArrowFunction', start: 1328, end: 1351 },
|
|
189
|
+
{ name: 'parseArrowFunction', start: 1353, end: 1394 },
|
|
190
|
+
{ name: 'parseArrayLiteralExpr', start: 1396, end: 1419 },
|
|
191
|
+
{ name: 'parseObjectLiteralExpr', start: 1421, end: 1456 },
|
|
192
|
+
{ name: 'parseIdentifierOrExpression', start: 1458, end: 1500 },
|
|
193
|
+
],
|
|
194
|
+
extraContent: extract(1190, 1200) // BINARY_OPS static property
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
// style.js: lines 1584-2359
|
|
198
|
+
'style.js': {
|
|
199
|
+
header: `/**
|
|
200
|
+
* Pulse Parser - Style Block
|
|
201
|
+
*
|
|
202
|
+
* CSS parsing with preprocessor support
|
|
203
|
+
*
|
|
204
|
+
* @module pulse-js-framework/compiler/parser/style
|
|
205
|
+
*/
|
|
206
|
+
|
|
207
|
+
import { TokenType } from '../lexer.js';
|
|
208
|
+
import { Parser, NodeType, ASTNode } from './core.js';
|
|
209
|
+
`,
|
|
210
|
+
methods: [
|
|
211
|
+
{ name: 'parseStyleBlock', start: 1581, end: 1637 },
|
|
212
|
+
{ name: 'reconstructCSS', start: 1639, end: 1663 },
|
|
213
|
+
{ name: 'parseStyleRule', start: 1665, end: 1770 },
|
|
214
|
+
{ name: 'isNestedRule', start: 1772, end: 1822 },
|
|
215
|
+
{ name: 'parseStyleProperty', start: 1824, end: 2047 },
|
|
216
|
+
{ name: 'isPropertyStart', start: 2341, end: 2359 },
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
// blocks.js: lines 1505-1579 + 2063-2336
|
|
221
|
+
'blocks.js': {
|
|
222
|
+
header: `/**
|
|
223
|
+
* Pulse Parser - Block Parsing
|
|
224
|
+
*
|
|
225
|
+
* Actions, router, store blocks, and function/guard parsing
|
|
226
|
+
*
|
|
227
|
+
* @module pulse-js-framework/compiler/parser/blocks
|
|
228
|
+
*/
|
|
229
|
+
|
|
230
|
+
import { TokenType } from '../lexer.js';
|
|
231
|
+
import { Parser, NodeType, ASTNode } from './core.js';
|
|
232
|
+
`,
|
|
233
|
+
methods: [
|
|
234
|
+
{ name: 'parseActionsBlock', start: 1502, end: 1516 },
|
|
235
|
+
{ name: 'parseFunctionDeclaration', start: 1518, end: 1555 },
|
|
236
|
+
{ name: 'parseFunctionBody', start: 1557, end: 1579 },
|
|
237
|
+
{ name: 'parseRouterBlock', start: 2049, end: 2110 },
|
|
238
|
+
{ name: 'parseRoutesBlock', start: 2112, end: 2133 },
|
|
239
|
+
{ name: 'parseGuardHook', start: 2135, end: 2159 },
|
|
240
|
+
{ name: 'parseStoreBlock', start: 2161, end: 2237 },
|
|
241
|
+
{ name: 'parseGettersBlock', start: 2239, end: 2256 },
|
|
242
|
+
{ name: 'parseGetterDeclaration', start: 2258, end: 2270 },
|
|
243
|
+
{ name: 'parseLinkDirective', start: 2272, end: 2304 },
|
|
244
|
+
{ name: 'parseOutletDirective', start: 2306, end: 2319 },
|
|
245
|
+
{ name: 'parseNavigateDirective', start: 2321, end: 2336 },
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// Process core.js specially (it keeps the class definition open for prototype extensions)
|
|
251
|
+
const coreContent = modules['core.js'].content;
|
|
252
|
+
// The core.js content includes lines 10-290, which ends with parse() method
|
|
253
|
+
// We need to close the class properly
|
|
254
|
+
const coreFile = modules['core.js'].header + '\n' + coreContent + '\n}\n';
|
|
255
|
+
fs.writeFileSync(path.join(__dirname, 'core.js'), coreFile);
|
|
256
|
+
console.log('✓ core.js written');
|
|
257
|
+
|
|
258
|
+
// Process each sub-module
|
|
259
|
+
for (const [filename, config] of Object.entries(modules)) {
|
|
260
|
+
if (filename === 'core.js') continue;
|
|
261
|
+
|
|
262
|
+
let content = config.header + '\n';
|
|
263
|
+
|
|
264
|
+
// Add extra content (like BINARY_OPS)
|
|
265
|
+
if (config.extraContent) {
|
|
266
|
+
content += '// Static properties\n';
|
|
267
|
+
content += config.extraContent + '\n\n';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Extract each method and convert to prototype assignment
|
|
271
|
+
for (const method of config.methods) {
|
|
272
|
+
const methodLines = lines.slice(method.start - 1, method.end);
|
|
273
|
+
|
|
274
|
+
// Find the actual method signature
|
|
275
|
+
let sigIdx = -1;
|
|
276
|
+
for (let i = 0; i < methodLines.length; i++) {
|
|
277
|
+
const line = methodLines[i].trimStart();
|
|
278
|
+
if (line.startsWith(`${method.name}(`) || line.startsWith(`async ${method.name}(`)) {
|
|
279
|
+
sigIdx = i;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (sigIdx === -1) {
|
|
285
|
+
console.error(` ✗ Could not find method signature for ${method.name} in lines ${method.start}-${method.end}`);
|
|
286
|
+
// Try broader search
|
|
287
|
+
for (let i = 0; i < methodLines.length; i++) {
|
|
288
|
+
if (methodLines[i].includes(method.name)) {
|
|
289
|
+
console.error(` Found "${methodLines[i].trim()}" at offset ${i} (line ${method.start + i})`);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Get JSDoc (lines before signature)
|
|
296
|
+
let docStart = sigIdx;
|
|
297
|
+
for (let i = sigIdx - 1; i >= 0; i--) {
|
|
298
|
+
const trimmed = methodLines[i].trim();
|
|
299
|
+
if (trimmed.startsWith('/**') || trimmed.startsWith('*') || trimmed.startsWith('*/')) {
|
|
300
|
+
docStart = i;
|
|
301
|
+
} else if (trimmed === '') {
|
|
302
|
+
continue; // skip blank lines between JSDoc blocks
|
|
303
|
+
} else {
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Build JSDoc as standalone comment
|
|
309
|
+
const jsdocLines = methodLines.slice(docStart, sigIdx);
|
|
310
|
+
const jsdoc = jsdocLines.map(l => l.replace(/^ /, '')).join('\n');
|
|
311
|
+
|
|
312
|
+
// Parse method signature
|
|
313
|
+
const sigLine = methodLines[sigIdx];
|
|
314
|
+
const isAsync = sigLine.trimStart().startsWith('async');
|
|
315
|
+
|
|
316
|
+
// Extract parameter string
|
|
317
|
+
const paramMatch = sigLine.match(/\(([^)]*)\)/);
|
|
318
|
+
const params = paramMatch ? paramMatch[1] : '';
|
|
319
|
+
|
|
320
|
+
// Extract body (everything from line after signature to end, minus last closing brace)
|
|
321
|
+
const bodyLines = methodLines.slice(sigIdx + 1);
|
|
322
|
+
|
|
323
|
+
// Remove the last closing brace (which was the method's closing brace)
|
|
324
|
+
let bodyStr = bodyLines.join('\n');
|
|
325
|
+
// Find last } and remove it
|
|
326
|
+
const lastBraceIdx = bodyStr.lastIndexOf('}');
|
|
327
|
+
if (lastBraceIdx >= 0) {
|
|
328
|
+
bodyStr = bodyStr.substring(0, lastBraceIdx) + bodyStr.substring(lastBraceIdx + 1);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Build prototype assignment
|
|
332
|
+
if (jsdoc.trim()) {
|
|
333
|
+
content += `${jsdoc}\n`;
|
|
334
|
+
}
|
|
335
|
+
content += `Parser.prototype.${method.name} = ${isAsync ? 'async ' : ''}function(${params}) {\n`;
|
|
336
|
+
content += bodyStr;
|
|
337
|
+
content += '};\n\n';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
fs.writeFileSync(path.join(__dirname, filename), content);
|
|
341
|
+
console.log(`✓ ${filename} written (${config.methods.length} methods)`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Create index.js barrel export
|
|
345
|
+
const indexContent = `/**
|
|
346
|
+
* Pulse Parser - Main Entry Point
|
|
347
|
+
*
|
|
348
|
+
* Barrel export for all parser modules.
|
|
349
|
+
* Sub-modules extend Parser.prototype with their methods.
|
|
350
|
+
*
|
|
351
|
+
* @module pulse-js-framework/compiler/parser
|
|
352
|
+
*/
|
|
353
|
+
|
|
354
|
+
import { tokenize } from '../lexer.js';
|
|
355
|
+
|
|
356
|
+
// Core must be imported first (defines the Parser class)
|
|
357
|
+
export { NodeType, ASTNode, Parser } from './core.js';
|
|
358
|
+
import { Parser } from './core.js';
|
|
359
|
+
|
|
360
|
+
// Each sub-module extends Parser.prototype with its methods
|
|
361
|
+
import './imports.js';
|
|
362
|
+
import './state.js';
|
|
363
|
+
import './view.js';
|
|
364
|
+
import './expressions.js';
|
|
365
|
+
import './style.js';
|
|
366
|
+
import './blocks.js';
|
|
367
|
+
|
|
368
|
+
// Re-export NodeType for convenience
|
|
369
|
+
export { NodeType } from './core.js';
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Parse a .pulse source string into an AST
|
|
373
|
+
* @param {string} source - Source code
|
|
374
|
+
* @returns {ASTNode} Program AST node
|
|
375
|
+
*/
|
|
376
|
+
export function parse(source) {
|
|
377
|
+
const tokens = tokenize(source);
|
|
378
|
+
const parser = new Parser(tokens);
|
|
379
|
+
return parser.parse();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export default {
|
|
383
|
+
NodeType: (await import('./core.js')).NodeType,
|
|
384
|
+
ASTNode: (await import('./core.js')).ASTNode,
|
|
385
|
+
Parser,
|
|
386
|
+
parse
|
|
387
|
+
};
|
|
388
|
+
`;
|
|
389
|
+
|
|
390
|
+
fs.writeFileSync(path.join(__dirname, 'index.js'), indexContent);
|
|
391
|
+
console.log('✓ index.js written');
|
|
392
|
+
|
|
393
|
+
console.log('\nExtraction complete!');
|