@kuratchi/js 0.0.15 → 0.0.17
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/README.md +160 -1
- package/dist/cli.js +78 -45
- package/dist/compiler/api-route-pipeline.d.ts +8 -0
- package/dist/compiler/api-route-pipeline.js +23 -0
- package/dist/compiler/asset-pipeline.d.ts +7 -0
- package/dist/compiler/asset-pipeline.js +33 -0
- package/dist/compiler/client-module-pipeline.d.ts +25 -0
- package/dist/compiler/client-module-pipeline.js +257 -0
- package/dist/compiler/compiler-shared.d.ts +73 -0
- package/dist/compiler/compiler-shared.js +4 -0
- package/dist/compiler/component-pipeline.d.ts +15 -0
- package/dist/compiler/component-pipeline.js +158 -0
- package/dist/compiler/config-reading.d.ts +12 -0
- package/dist/compiler/config-reading.js +380 -0
- package/dist/compiler/convention-discovery.d.ts +9 -0
- package/dist/compiler/convention-discovery.js +83 -0
- package/dist/compiler/durable-object-pipeline.d.ts +9 -0
- package/dist/compiler/durable-object-pipeline.js +255 -0
- package/dist/compiler/error-page-pipeline.d.ts +1 -0
- package/dist/compiler/error-page-pipeline.js +16 -0
- package/dist/compiler/import-linking.d.ts +36 -0
- package/dist/compiler/import-linking.js +140 -0
- package/dist/compiler/index.d.ts +7 -7
- package/dist/compiler/index.js +181 -3321
- package/dist/compiler/layout-pipeline.d.ts +31 -0
- package/dist/compiler/layout-pipeline.js +155 -0
- package/dist/compiler/page-route-pipeline.d.ts +16 -0
- package/dist/compiler/page-route-pipeline.js +62 -0
- package/dist/compiler/parser.d.ts +4 -0
- package/dist/compiler/parser.js +436 -55
- package/dist/compiler/root-layout-pipeline.d.ts +10 -0
- package/dist/compiler/root-layout-pipeline.js +532 -0
- package/dist/compiler/route-discovery.d.ts +7 -0
- package/dist/compiler/route-discovery.js +87 -0
- package/dist/compiler/route-pipeline.d.ts +57 -0
- package/dist/compiler/route-pipeline.js +291 -0
- package/dist/compiler/route-state-pipeline.d.ts +26 -0
- package/dist/compiler/route-state-pipeline.js +139 -0
- package/dist/compiler/routes-module-feature-blocks.d.ts +2 -0
- package/dist/compiler/routes-module-feature-blocks.js +330 -0
- package/dist/compiler/routes-module-pipeline.d.ts +2 -0
- package/dist/compiler/routes-module-pipeline.js +6 -0
- package/dist/compiler/routes-module-runtime-shell.d.ts +2 -0
- package/dist/compiler/routes-module-runtime-shell.js +91 -0
- package/dist/compiler/routes-module-types.d.ts +45 -0
- package/dist/compiler/routes-module-types.js +1 -0
- package/dist/compiler/script-transform.d.ts +16 -0
- package/dist/compiler/script-transform.js +218 -0
- package/dist/compiler/server-module-pipeline.d.ts +13 -0
- package/dist/compiler/server-module-pipeline.js +124 -0
- package/dist/compiler/template.d.ts +13 -1
- package/dist/compiler/template.js +337 -71
- package/dist/compiler/worker-output-pipeline.d.ts +13 -0
- package/dist/compiler/worker-output-pipeline.js +37 -0
- package/dist/compiler/wrangler-sync.d.ts +14 -0
- package/dist/compiler/wrangler-sync.js +185 -0
- package/dist/runtime/app.js +15 -3
- package/dist/runtime/context.d.ts +4 -0
- package/dist/runtime/context.js +40 -2
- package/dist/runtime/do.js +21 -6
- package/dist/runtime/generated-worker.d.ts +55 -0
- package/dist/runtime/generated-worker.js +543 -0
- package/dist/runtime/index.d.ts +4 -1
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/router.d.ts +6 -1
- package/dist/runtime/router.js +125 -31
- package/dist/runtime/security.d.ts +101 -0
- package/dist/runtime/security.js +298 -0
- package/dist/runtime/types.d.ts +29 -2
- package/package.json +5 -1
package/dist/compiler/parser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
|
+
import { collectReferencedIdentifiers, parseNamedImportBindings } from './import-linking.js';
|
|
2
3
|
function getTopLevelImportStatements(source) {
|
|
3
4
|
const sourceFile = ts.createSourceFile('kuratchi-script.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
4
5
|
const imports = [];
|
|
@@ -29,6 +30,23 @@ export function stripTopLevelImports(source) {
|
|
|
29
30
|
function hasReactiveLabel(scriptBody) {
|
|
30
31
|
return /\$\s*:/.test(scriptBody);
|
|
31
32
|
}
|
|
33
|
+
const TEMPLATE_JS_CONTROL_PATTERNS = [
|
|
34
|
+
/^\s*for\s*\(/,
|
|
35
|
+
/^\s*if\s*\(/,
|
|
36
|
+
/^\s*switch\s*\(/,
|
|
37
|
+
/^\s*case\s+.+:\s*$/,
|
|
38
|
+
/^\s*default\s*:\s*$/,
|
|
39
|
+
/^\s*break\s*;\s*$/,
|
|
40
|
+
/^\s*continue\s*;\s*$/,
|
|
41
|
+
/^\s*\}\s*else\s*if\s*\(/,
|
|
42
|
+
/^\s*\}\s*else\s*\{?\s*$/,
|
|
43
|
+
/^\s*\}\s*$/,
|
|
44
|
+
/^\s*\w[\w.]*\s*(\+\+|--)\s*;\s*$/,
|
|
45
|
+
/^\s*(let|const|var)\s+/,
|
|
46
|
+
];
|
|
47
|
+
function isTemplateJsControlLine(line) {
|
|
48
|
+
return TEMPLATE_JS_CONTROL_PATTERNS.some((pattern) => pattern.test(line));
|
|
49
|
+
}
|
|
32
50
|
function splitTopLevel(input, delimiter) {
|
|
33
51
|
const parts = [];
|
|
34
52
|
let start = 0;
|
|
@@ -271,6 +289,286 @@ function extractReturnObjectKeys(body) {
|
|
|
271
289
|
}
|
|
272
290
|
return keys;
|
|
273
291
|
}
|
|
292
|
+
function findTemplateTagEnd(source, start) {
|
|
293
|
+
let quote = null;
|
|
294
|
+
let escaped = false;
|
|
295
|
+
let braceDepth = 0;
|
|
296
|
+
for (let i = start; i < source.length; i++) {
|
|
297
|
+
const ch = source[i];
|
|
298
|
+
if (quote) {
|
|
299
|
+
if (escaped) {
|
|
300
|
+
escaped = false;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (ch === '\\') {
|
|
304
|
+
escaped = true;
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (ch === quote)
|
|
308
|
+
quote = null;
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
312
|
+
quote = ch;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (ch === '{') {
|
|
316
|
+
braceDepth++;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (ch === '}') {
|
|
320
|
+
braceDepth = Math.max(0, braceDepth - 1);
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (ch === '>' && braceDepth === 0)
|
|
324
|
+
return i;
|
|
325
|
+
}
|
|
326
|
+
return -1;
|
|
327
|
+
}
|
|
328
|
+
function parseTemplateTagAttributes(source) {
|
|
329
|
+
const attrs = new Map();
|
|
330
|
+
let i = 0;
|
|
331
|
+
while (i < source.length) {
|
|
332
|
+
while (i < source.length && /\s/.test(source[i]))
|
|
333
|
+
i++;
|
|
334
|
+
if (i >= source.length)
|
|
335
|
+
break;
|
|
336
|
+
const nameStart = i;
|
|
337
|
+
while (i < source.length && /[^\s=/>]/.test(source[i]))
|
|
338
|
+
i++;
|
|
339
|
+
const name = source.slice(nameStart, i);
|
|
340
|
+
if (!name)
|
|
341
|
+
break;
|
|
342
|
+
while (i < source.length && /\s/.test(source[i]))
|
|
343
|
+
i++;
|
|
344
|
+
if (source[i] !== '=') {
|
|
345
|
+
attrs.set(name, '');
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
i++;
|
|
349
|
+
while (i < source.length && /\s/.test(source[i]))
|
|
350
|
+
i++;
|
|
351
|
+
if (i >= source.length) {
|
|
352
|
+
attrs.set(name, '');
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
if (source[i] === '"' || source[i] === "'") {
|
|
356
|
+
const quote = source[i];
|
|
357
|
+
const valueStart = i;
|
|
358
|
+
i++;
|
|
359
|
+
while (i < source.length) {
|
|
360
|
+
if (source[i] === '\\') {
|
|
361
|
+
i += 2;
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
if (source[i] === quote) {
|
|
365
|
+
i++;
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
i++;
|
|
369
|
+
}
|
|
370
|
+
attrs.set(name, source.slice(valueStart, i));
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (source[i] === '{') {
|
|
374
|
+
const valueStart = i;
|
|
375
|
+
const closeIdx = findMatchingToken(source, i, '{', '}');
|
|
376
|
+
if (closeIdx === -1) {
|
|
377
|
+
attrs.set(name, source.slice(valueStart));
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
i = closeIdx + 1;
|
|
381
|
+
attrs.set(name, source.slice(valueStart, i));
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const valueStart = i;
|
|
385
|
+
while (i < source.length && /[^\s>]/.test(source[i]))
|
|
386
|
+
i++;
|
|
387
|
+
attrs.set(name, source.slice(valueStart, i));
|
|
388
|
+
}
|
|
389
|
+
return attrs;
|
|
390
|
+
}
|
|
391
|
+
function tokenizeTemplateTags(template) {
|
|
392
|
+
const tags = [];
|
|
393
|
+
let cursor = 0;
|
|
394
|
+
while (cursor < template.length) {
|
|
395
|
+
const lt = template.indexOf('<', cursor);
|
|
396
|
+
if (lt === -1)
|
|
397
|
+
break;
|
|
398
|
+
if (template.startsWith('<!--', lt)) {
|
|
399
|
+
const commentEnd = template.indexOf('-->', lt + 4);
|
|
400
|
+
if (commentEnd === -1)
|
|
401
|
+
break;
|
|
402
|
+
cursor = commentEnd + 3;
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
const tagEnd = findTemplateTagEnd(template, lt + 1);
|
|
406
|
+
if (tagEnd === -1)
|
|
407
|
+
break;
|
|
408
|
+
const raw = template.slice(lt + 1, tagEnd).trim();
|
|
409
|
+
cursor = tagEnd + 1;
|
|
410
|
+
if (!raw || raw.startsWith('!'))
|
|
411
|
+
continue;
|
|
412
|
+
const closing = raw.startsWith('/');
|
|
413
|
+
const normalized = closing ? raw.slice(1).trim() : raw;
|
|
414
|
+
const nameMatch = normalized.match(/^([A-Za-z][\w:-]*)/);
|
|
415
|
+
if (!nameMatch)
|
|
416
|
+
continue;
|
|
417
|
+
const name = nameMatch[1];
|
|
418
|
+
const attrSource = normalized.slice(name.length).replace(/\/\s*$/, '').trim();
|
|
419
|
+
tags.push({
|
|
420
|
+
name,
|
|
421
|
+
attrs: closing ? new Map() : parseTemplateTagAttributes(attrSource),
|
|
422
|
+
closing,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
return tags;
|
|
426
|
+
}
|
|
427
|
+
function extractBracedAttributeExpression(value) {
|
|
428
|
+
if (!value)
|
|
429
|
+
return null;
|
|
430
|
+
const trimmed = value.trim();
|
|
431
|
+
if (!trimmed.startsWith('{') || !trimmed.endsWith('}'))
|
|
432
|
+
return null;
|
|
433
|
+
return trimmed.slice(1, -1).trim();
|
|
434
|
+
}
|
|
435
|
+
function extractAttributeText(value) {
|
|
436
|
+
if (!value)
|
|
437
|
+
return null;
|
|
438
|
+
const trimmed = value.trim();
|
|
439
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
440
|
+
return trimmed.slice(1, -1);
|
|
441
|
+
}
|
|
442
|
+
const braced = extractBracedAttributeExpression(trimmed);
|
|
443
|
+
if (braced && /^[A-Za-z_$][\w$]*$/.test(braced))
|
|
444
|
+
return braced;
|
|
445
|
+
return trimmed || null;
|
|
446
|
+
}
|
|
447
|
+
function extractCallExpression(value) {
|
|
448
|
+
const expr = extractBracedAttributeExpression(value);
|
|
449
|
+
if (!expr)
|
|
450
|
+
return null;
|
|
451
|
+
const match = expr.match(/^([A-Za-z_$][\w$]*)\(([\s\S]*)\)$/);
|
|
452
|
+
if (!match)
|
|
453
|
+
return null;
|
|
454
|
+
return {
|
|
455
|
+
fnName: match[1],
|
|
456
|
+
argsExpr: (match[2] || '').trim(),
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function extractTopLevelImportNames(source) {
|
|
460
|
+
const sourceFile = ts.createSourceFile('kuratchi-inline-client.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
461
|
+
const names = [];
|
|
462
|
+
for (const statement of sourceFile.statements) {
|
|
463
|
+
if (!ts.isImportDeclaration(statement))
|
|
464
|
+
continue;
|
|
465
|
+
const clause = statement.importClause;
|
|
466
|
+
if (!clause)
|
|
467
|
+
continue;
|
|
468
|
+
if (clause.name)
|
|
469
|
+
pushIdentifier(clause.name.text, names);
|
|
470
|
+
if (!clause.namedBindings)
|
|
471
|
+
continue;
|
|
472
|
+
if (ts.isNamedImports(clause.namedBindings)) {
|
|
473
|
+
for (const element of clause.namedBindings.elements) {
|
|
474
|
+
pushIdentifier(element.name.text, names);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else if (ts.isNamespaceImport(clause.namedBindings)) {
|
|
478
|
+
pushIdentifier(clause.namedBindings.name.text, names);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return names;
|
|
482
|
+
}
|
|
483
|
+
function extractImportModuleSpecifier(source) {
|
|
484
|
+
const sourceFile = ts.createSourceFile('kuratchi-import-spec.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
485
|
+
const statement = sourceFile.statements.find(ts.isImportDeclaration);
|
|
486
|
+
if (!statement || !ts.isStringLiteral(statement.moduleSpecifier))
|
|
487
|
+
return null;
|
|
488
|
+
return statement.moduleSpecifier.text;
|
|
489
|
+
}
|
|
490
|
+
function isExecutableTemplateScript(attrs) {
|
|
491
|
+
if (/\bsrc\s*=/i.test(attrs))
|
|
492
|
+
return false;
|
|
493
|
+
const typeMatch = attrs.match(/\btype\s*=\s*(['"])(.*?)\1/i);
|
|
494
|
+
const type = typeMatch?.[2]?.trim().toLowerCase();
|
|
495
|
+
if (!type)
|
|
496
|
+
return true;
|
|
497
|
+
return type === 'module' || type === 'text/javascript' || type === 'application/javascript';
|
|
498
|
+
}
|
|
499
|
+
function collectTemplateClientDeclaredNames(template) {
|
|
500
|
+
const declared = new Set();
|
|
501
|
+
const scriptRegex = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
502
|
+
let match;
|
|
503
|
+
while ((match = scriptRegex.exec(template)) !== null) {
|
|
504
|
+
const attrs = match[1] || '';
|
|
505
|
+
const body = match[2] || '';
|
|
506
|
+
if (!isExecutableTemplateScript(attrs))
|
|
507
|
+
continue;
|
|
508
|
+
// TypeScript source works directly for reference collection
|
|
509
|
+
for (const name of extractTopLevelImportNames(body))
|
|
510
|
+
declared.add(name);
|
|
511
|
+
for (const name of extractTopLevelDataVars(body))
|
|
512
|
+
declared.add(name);
|
|
513
|
+
for (const name of extractTopLevelFunctionNames(body))
|
|
514
|
+
declared.add(name);
|
|
515
|
+
}
|
|
516
|
+
return Array.from(declared);
|
|
517
|
+
}
|
|
518
|
+
function stripLeadingTopLevelScriptBlock(template) {
|
|
519
|
+
return template.replace(/^(\s*)<script(\s[^>]*)?\s*>[\s\S]*?<\/script>\s*/i, '');
|
|
520
|
+
}
|
|
521
|
+
function stripTemplateRawBlocks(template) {
|
|
522
|
+
return template
|
|
523
|
+
.replace(/<script\b[\s\S]*?<\/script>/gi, '')
|
|
524
|
+
.replace(/<style\b[\s\S]*?<\/style>/gi, '');
|
|
525
|
+
}
|
|
526
|
+
function extractBraceExpressions(line) {
|
|
527
|
+
const expressions = [];
|
|
528
|
+
let cursor = 0;
|
|
529
|
+
while (cursor < line.length) {
|
|
530
|
+
const openIdx = line.indexOf('{', cursor);
|
|
531
|
+
if (openIdx === -1)
|
|
532
|
+
break;
|
|
533
|
+
const closeIdx = findMatchingToken(line, openIdx, '{', '}');
|
|
534
|
+
if (closeIdx === -1)
|
|
535
|
+
break;
|
|
536
|
+
const expression = line.slice(openIdx + 1, closeIdx).trim();
|
|
537
|
+
const beforeBrace = line.slice(0, openIdx);
|
|
538
|
+
const charBefore = openIdx > 0 ? line[openIdx - 1] : '';
|
|
539
|
+
let attrName = null;
|
|
540
|
+
if (charBefore === '=') {
|
|
541
|
+
const attrMatch = beforeBrace.match(/([\w-]+)=$/);
|
|
542
|
+
attrName = attrMatch ? attrMatch[1] : null;
|
|
543
|
+
}
|
|
544
|
+
expressions.push({ expression, attrName });
|
|
545
|
+
cursor = closeIdx + 1;
|
|
546
|
+
}
|
|
547
|
+
return expressions;
|
|
548
|
+
}
|
|
549
|
+
function collectServerTemplateReferences(template) {
|
|
550
|
+
const refs = new Set();
|
|
551
|
+
const stripped = stripTemplateRawBlocks(template);
|
|
552
|
+
const lines = stripped.split('\n');
|
|
553
|
+
for (const line of lines) {
|
|
554
|
+
const trimmed = line.trim();
|
|
555
|
+
if (!trimmed)
|
|
556
|
+
continue;
|
|
557
|
+
if (isTemplateJsControlLine(trimmed)) {
|
|
558
|
+
for (const ref of collectReferencedIdentifiers(trimmed))
|
|
559
|
+
refs.add(ref);
|
|
560
|
+
}
|
|
561
|
+
for (const entry of extractBraceExpressions(line)) {
|
|
562
|
+
if (!entry.expression)
|
|
563
|
+
continue;
|
|
564
|
+
if (entry.attrName && /^on[A-Za-z]+$/i.test(entry.attrName))
|
|
565
|
+
continue;
|
|
566
|
+
for (const ref of collectReferencedIdentifiers(entry.expression))
|
|
567
|
+
refs.add(ref);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return refs;
|
|
571
|
+
}
|
|
274
572
|
function extractExplicitLoad(scriptBody) {
|
|
275
573
|
let i = 0;
|
|
276
574
|
let depthParen = 0;
|
|
@@ -685,12 +983,15 @@ export function parseFile(source, options = {}) {
|
|
|
685
983
|
// Extract all imports from script
|
|
686
984
|
const serverImports = [];
|
|
687
985
|
const clientImports = [];
|
|
986
|
+
const routeClientImports = [];
|
|
987
|
+
const routeClientImportBindings = [];
|
|
688
988
|
const componentImports = {};
|
|
689
989
|
const workerEnvAliases = [];
|
|
690
990
|
const devAliases = [];
|
|
691
991
|
if (script) {
|
|
692
992
|
for (const statement of getTopLevelImportStatements(script)) {
|
|
693
993
|
const line = statement.text.trim();
|
|
994
|
+
const moduleSpecifier = extractImportModuleSpecifier(line);
|
|
694
995
|
// Check for component imports: import Name from '$lib/file.html' or '@kuratchi/ui/file.html'
|
|
695
996
|
const libMatch = line.match(/import\s+([A-Za-z_$][\w$]*)\s+from\s+['"]\$lib\/([^'"]+\.html)['"]/s);
|
|
696
997
|
const pkgMatch = !libMatch ? line.match(/import\s+([A-Za-z_$][\w$]*)\s+from\s+['"](@[^/'"]+\/[^/'"]+)\/([^'"]+\.html)['"]/s) : null;
|
|
@@ -714,6 +1015,14 @@ export function parseFile(source, options = {}) {
|
|
|
714
1015
|
}
|
|
715
1016
|
continue;
|
|
716
1017
|
}
|
|
1018
|
+
if (moduleSpecifier?.startsWith('$client/')) {
|
|
1019
|
+
routeClientImports.push(line);
|
|
1020
|
+
for (const binding of extractTopLevelImportNames(line)) {
|
|
1021
|
+
if (!routeClientImportBindings.includes(binding))
|
|
1022
|
+
routeClientImportBindings.push(binding);
|
|
1023
|
+
}
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
717
1026
|
serverImports.push(line);
|
|
718
1027
|
}
|
|
719
1028
|
for (const alias of extractCloudflareEnvAliases(line)) {
|
|
@@ -757,11 +1066,22 @@ export function parseFile(source, options = {}) {
|
|
|
757
1066
|
if (workerEnvAliases.length > 0 && kind !== 'route') {
|
|
758
1067
|
throw buildEnvAccessError(kind, options.filePath, `Imported env from cloudflare:workers in a ${kind} script.`);
|
|
759
1068
|
}
|
|
760
|
-
|
|
761
|
-
const
|
|
1069
|
+
// TypeScript source works directly for reference collection
|
|
1070
|
+
const serverScriptRefs = new Set(collectReferencedIdentifiers(scriptBody));
|
|
1071
|
+
if (loadFunction) {
|
|
1072
|
+
for (const ref of collectReferencedIdentifiers(loadFunction))
|
|
1073
|
+
serverScriptRefs.add(ref);
|
|
1074
|
+
}
|
|
1075
|
+
const leakedClientScriptBindings = routeClientImportBindings.filter((name) => serverScriptRefs.has(name));
|
|
1076
|
+
if (leakedClientScriptBindings.length > 0) {
|
|
1077
|
+
throw new Error(`[kuratchi compiler] ${options.filePath || kind}\n` +
|
|
1078
|
+
`Top-level $client imports cannot be used in server route code: ${leakedClientScriptBindings.join(', ')}.\n` +
|
|
1079
|
+
`Move this usage to a client event handler or client bridge code, or import from $shared instead.`);
|
|
1080
|
+
}
|
|
1081
|
+
const topLevelVars = extractTopLevelDataVars(scriptBody);
|
|
762
1082
|
for (const v of topLevelVars)
|
|
763
1083
|
dataVars.push(v);
|
|
764
|
-
const topLevelFns = extractTopLevelFunctionNames(
|
|
1084
|
+
const topLevelFns = extractTopLevelFunctionNames(scriptBody);
|
|
765
1085
|
for (const fn of topLevelFns) {
|
|
766
1086
|
if (!dataVars.includes(fn))
|
|
767
1087
|
dataVars.push(fn);
|
|
@@ -772,15 +1092,8 @@ export function parseFile(source, options = {}) {
|
|
|
772
1092
|
}
|
|
773
1093
|
// Server import named bindings are also data vars (available in templates)
|
|
774
1094
|
for (const line of serverImports) {
|
|
775
|
-
const
|
|
776
|
-
|
|
777
|
-
continue;
|
|
778
|
-
for (const part of namesMatch[1].split(',')) {
|
|
779
|
-
const trimmed = part.trim();
|
|
780
|
-
if (!trimmed)
|
|
781
|
-
continue;
|
|
782
|
-
const segments = trimmed.split(/\s+as\s+/);
|
|
783
|
-
const localName = (segments[1] || segments[0]).trim();
|
|
1095
|
+
for (const binding of parseNamedImportBindings(line)) {
|
|
1096
|
+
const localName = binding.local.trim();
|
|
784
1097
|
if (localName && !dataVars.includes(localName))
|
|
785
1098
|
dataVars.push(localName);
|
|
786
1099
|
}
|
|
@@ -791,74 +1104,140 @@ export function parseFile(source, options = {}) {
|
|
|
791
1104
|
// This prevents commented-out code (<!-- ... -->) from being parsed as live
|
|
792
1105
|
// action expressions, which would cause false "Invalid action expression" errors.
|
|
793
1106
|
const templateWithoutComments = template.replace(/<!--[\s\S]*?-->/g, '');
|
|
794
|
-
|
|
1107
|
+
const templateTags = tokenizeTemplateTags(templateWithoutComments);
|
|
795
1108
|
const actionFunctions = [];
|
|
1109
|
+
const pollFunctions = [];
|
|
1110
|
+
const dataGetQueries = [];
|
|
1111
|
+
for (const tag of templateTags) {
|
|
1112
|
+
if (tag.closing)
|
|
1113
|
+
continue;
|
|
1114
|
+
const actionExpr = extractBracedAttributeExpression(tag.attrs.get('action'));
|
|
1115
|
+
if (actionExpr && /^[A-Za-z_$][\w$]*$/.test(actionExpr) && !actionFunctions.includes(actionExpr)) {
|
|
1116
|
+
actionFunctions.push(actionExpr);
|
|
1117
|
+
}
|
|
1118
|
+
for (const [attrName, attrValue] of tag.attrs.entries()) {
|
|
1119
|
+
if (/^on[A-Za-z]+$/i.test(attrName)) {
|
|
1120
|
+
const actionCall = extractCallExpression(attrValue);
|
|
1121
|
+
if (actionCall && !actionFunctions.includes(actionCall.fnName))
|
|
1122
|
+
actionFunctions.push(actionCall.fnName);
|
|
1123
|
+
}
|
|
1124
|
+
if (/^data-(post|put|patch|delete)$/.test(attrName)) {
|
|
1125
|
+
const methodCall = extractCallExpression(attrValue);
|
|
1126
|
+
if (methodCall && !actionFunctions.includes(methodCall.fnName))
|
|
1127
|
+
actionFunctions.push(methodCall.fnName);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
const pollCall = extractCallExpression(tag.attrs.get('data-poll'));
|
|
1131
|
+
if (pollCall && !pollFunctions.includes(pollCall.fnName)) {
|
|
1132
|
+
pollFunctions.push(pollCall.fnName);
|
|
1133
|
+
}
|
|
1134
|
+
const getCall = extractCallExpression(tag.attrs.get('data-get'));
|
|
1135
|
+
const asName = extractAttributeText(tag.attrs.get('data-as'));
|
|
1136
|
+
if (!getCall || !asName || !/^[A-Za-z_$][\w$]*$/.test(asName))
|
|
1137
|
+
continue;
|
|
1138
|
+
const key = (extractAttributeText(tag.attrs.get('data-key')) || asName).trim();
|
|
1139
|
+
if (!pollFunctions.includes(getCall.fnName))
|
|
1140
|
+
pollFunctions.push(getCall.fnName);
|
|
1141
|
+
if (!dataVars.includes(asName))
|
|
1142
|
+
dataVars.push(asName);
|
|
1143
|
+
const exists = dataGetQueries.some((q) => q.asName === asName);
|
|
1144
|
+
if (!exists)
|
|
1145
|
+
dataGetQueries.push({ fnName: getCall.fnName, argsExpr: getCall.argsExpr, asName, key });
|
|
1146
|
+
}
|
|
1147
|
+
for (const clientBinding of routeClientImportBindings) {
|
|
1148
|
+
const idx = actionFunctions.indexOf(clientBinding);
|
|
1149
|
+
if (idx !== -1)
|
|
1150
|
+
actionFunctions.splice(idx, 1);
|
|
1151
|
+
}
|
|
1152
|
+
const templateTemplateScriptSource = clientScript ? stripLeadingTopLevelScriptBlock(template) : template;
|
|
1153
|
+
const templateClientDeclaredNames = collectTemplateClientDeclaredNames(templateTemplateScriptSource);
|
|
1154
|
+
const serverTemplateRefs = collectServerTemplateReferences(template);
|
|
1155
|
+
const leakedRouteClientTemplateBindings = routeClientImportBindings.filter((name) => serverTemplateRefs.has(name));
|
|
1156
|
+
if (leakedRouteClientTemplateBindings.length > 0) {
|
|
1157
|
+
throw new Error(`[kuratchi compiler] ${options.filePath || kind}\n` +
|
|
1158
|
+
`Top-level $client imports cannot be used in server-rendered template output: ${leakedRouteClientTemplateBindings.join(', ')}.\n` +
|
|
1159
|
+
`Use $shared for portable helpers or move this usage into a client event handler.`);
|
|
1160
|
+
}
|
|
1161
|
+
if (templateClientDeclaredNames.length > 0) {
|
|
1162
|
+
const leakedNames = templateClientDeclaredNames.filter((name) => serverTemplateRefs.has(name));
|
|
1163
|
+
if (leakedNames.length > 0) {
|
|
1164
|
+
throw new Error(`[kuratchi compiler] ${options.filePath || kind}\n` +
|
|
1165
|
+
`Client template <script> bindings cannot be used in server-rendered template output: ${leakedNames.join(', ')}.\n` +
|
|
1166
|
+
`Move shared/pure helpers into the top route <script> or a $shared module.`);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
/*
|
|
1170
|
+
// Extract action functions referenced in template: action={fnName}
|
|
1171
|
+
const actionFunctions: string[] = [];
|
|
796
1172
|
const actionRegex = /action=\{(\w+)\}/g;
|
|
797
1173
|
let am;
|
|
798
1174
|
while ((am = actionRegex.exec(templateWithoutComments)) !== null) {
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1175
|
+
if (!actionFunctions.includes(am[1])) {
|
|
1176
|
+
actionFunctions.push(am[1]);
|
|
1177
|
+
}
|
|
802
1178
|
}
|
|
803
1179
|
// Also collect onX={fnName(...)} candidates (e.g. onclick, onClick, onChange)
|
|
804
1180
|
// — the compiler will filter these against actual imports to determine server actions.
|
|
805
1181
|
const eventActionRegex = /on[A-Za-z]+\s*=\{(\w+)\s*\(/g;
|
|
806
1182
|
let em;
|
|
807
1183
|
while ((em = eventActionRegex.exec(templateWithoutComments)) !== null) {
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1184
|
+
if (!actionFunctions.includes(em[1])) {
|
|
1185
|
+
actionFunctions.push(em[1]);
|
|
1186
|
+
}
|
|
811
1187
|
}
|
|
812
1188
|
// Collect method-style action attributes: data-post/data-put/data-patch/data-delete
|
|
813
1189
|
const methodActionRegex = /data-(?:post|put|patch|delete)\s*=\{(\w+)\s*\(/g;
|
|
814
1190
|
let mm;
|
|
815
1191
|
while ((mm = methodActionRegex.exec(templateWithoutComments)) !== null) {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
1192
|
+
if (!actionFunctions.includes(mm[1])) {
|
|
1193
|
+
actionFunctions.push(mm[1]);
|
|
1194
|
+
}
|
|
819
1195
|
}
|
|
1196
|
+
|
|
820
1197
|
// Extract poll functions referenced in template: data-poll={fnName(args)}
|
|
821
|
-
const pollFunctions = [];
|
|
1198
|
+
const pollFunctions: string[] = [];
|
|
822
1199
|
const pollRegex = /data-poll=\{(\w+)\s*\(/g;
|
|
823
1200
|
let pm;
|
|
824
1201
|
while ((pm = pollRegex.exec(templateWithoutComments)) !== null) {
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1202
|
+
if (!pollFunctions.includes(pm[1])) {
|
|
1203
|
+
pollFunctions.push(pm[1]);
|
|
1204
|
+
}
|
|
828
1205
|
}
|
|
1206
|
+
|
|
829
1207
|
// Extract query blocks: tags that include both data-get={fn(args)} and data-as=...
|
|
830
|
-
const dataGetQueries = [];
|
|
1208
|
+
const dataGetQueries: Array<{ fnName: string; argsExpr: string; asName: string; key?: string }> = [];
|
|
831
1209
|
const tagRegex = /<[^>]+>/g;
|
|
832
1210
|
let tm;
|
|
833
1211
|
while ((tm = tagRegex.exec(templateWithoutComments)) !== null) {
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1212
|
+
const tag = tm[0];
|
|
1213
|
+
const getMatch = tag.match(/\bdata-get=\{([\s\S]*?)\}/);
|
|
1214
|
+
if (!getMatch) continue;
|
|
1215
|
+
const call = getMatch[1].trim();
|
|
1216
|
+
const callMatch = call.match(/^([A-Za-z_$][\w$]*)\(([\s\S]*)\)$/);
|
|
1217
|
+
if (!callMatch) continue;
|
|
1218
|
+
const fnName = callMatch[1];
|
|
1219
|
+
const argsExpr = (callMatch[2] || '').trim();
|
|
1220
|
+
|
|
1221
|
+
const asMatch =
|
|
1222
|
+
tag.match(/\bdata-as="([A-Za-z_$][\w$]*)"/) ||
|
|
1223
|
+
tag.match(/\bdata-as='([A-Za-z_$][\w$]*)'/) ||
|
|
1224
|
+
tag.match(/\bdata-as=\{([A-Za-z_$][\w$]*)\}/);
|
|
1225
|
+
if (!asMatch) continue;
|
|
1226
|
+
const asName = asMatch[1];
|
|
1227
|
+
|
|
1228
|
+
const keyMatch =
|
|
1229
|
+
tag.match(/\bdata-key="([^"]+)"/) ||
|
|
1230
|
+
tag.match(/\bdata-key='([^']+)'/) ||
|
|
1231
|
+
tag.match(/\bdata-key=\{([^}]+)\}/);
|
|
1232
|
+
const key = keyMatch?.[1]?.trim() || asName;
|
|
1233
|
+
|
|
1234
|
+
if (!pollFunctions.includes(fnName)) pollFunctions.push(fnName);
|
|
1235
|
+
if (!dataVars.includes(asName)) dataVars.push(asName);
|
|
1236
|
+
|
|
1237
|
+
const exists = dataGetQueries.some((q) => q.asName === asName);
|
|
1238
|
+
if (!exists) dataGetQueries.push({ fnName, argsExpr, asName, key });
|
|
861
1239
|
}
|
|
1240
|
+
*/
|
|
862
1241
|
return {
|
|
863
1242
|
script,
|
|
864
1243
|
loadFunction,
|
|
@@ -871,9 +1250,11 @@ export function parseFile(source, options = {}) {
|
|
|
871
1250
|
pollFunctions,
|
|
872
1251
|
dataGetQueries,
|
|
873
1252
|
clientImports,
|
|
1253
|
+
routeClientImports,
|
|
1254
|
+
routeClientImportBindings,
|
|
874
1255
|
loadReturnVars,
|
|
875
1256
|
workerEnvAliases,
|
|
876
1257
|
devAliases,
|
|
877
1258
|
};
|
|
878
1259
|
}
|
|
879
|
-
|
|
1260
|
+
// TypeScript transpilation removed — wrangler's esbuild handles it
|