tina4-nodejs 3.13.46 → 3.13.47
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/CLAUDE.md +1 -1
- package/package.json +1 -1
- package/packages/core/src/scss.ts +23 -0
- package/packages/orm/src/migration.ts +117 -34
package/CLAUDE.md
CHANGED
package/package.json
CHANGED
|
@@ -122,6 +122,9 @@ function compileString(
|
|
|
122
122
|
// 5. Resolve @include
|
|
123
123
|
scss = resolveIncludes(scss, mixins);
|
|
124
124
|
|
|
125
|
+
// 5.5. Resolve #{ ... } interpolation (before $var substitution + nesting).
|
|
126
|
+
scss = resolveInterpolation(scss, variables);
|
|
127
|
+
|
|
125
128
|
// 6. Substitute variables
|
|
126
129
|
scss = substituteVariables(scss, variables);
|
|
127
130
|
|
|
@@ -182,6 +185,26 @@ function substituteVariables(scss: string, variables: Record<string, string>): s
|
|
|
182
185
|
return scss;
|
|
183
186
|
}
|
|
184
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Resolve SCSS `#{ ... }` interpolation. Each `#{ expr }` is replaced by its
|
|
190
|
+
* resolved inner text: a `$variable` inside the braces resolves to its value,
|
|
191
|
+
* anything else is inlined verbatim (trimmed). This lets a value carry a
|
|
192
|
+
* variable inside a string context plain `$var` substitution can't reach —
|
|
193
|
+
* e.g. `calc(100% - #{$gap})` → `calc(100% - 20px)` — and lets a variable
|
|
194
|
+
* appear in a selector (`.icon-#{$name}` → `.icon-home`). Run BEFORE nested
|
|
195
|
+
* rule flattening so the literal braces never confuse the block matcher.
|
|
196
|
+
*/
|
|
197
|
+
function resolveInterpolation(scss: string, variables: Record<string, string>): string {
|
|
198
|
+
const sorted = Object.keys(variables).sort((a, b) => b.length - a.length);
|
|
199
|
+
return scss.replace(/#\{([^{}]*)\}/g, (_m, inner: string) => {
|
|
200
|
+
let resolved = inner.trim();
|
|
201
|
+
for (const name of sorted) {
|
|
202
|
+
resolved = resolved.replaceAll(`$${name}`, variables[name]);
|
|
203
|
+
}
|
|
204
|
+
return resolved;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
185
208
|
// ── Mixins ───────────────────────────────────────────────────────
|
|
186
209
|
|
|
187
210
|
function extractMixins(
|
|
@@ -569,54 +569,137 @@ export function normalizeQuotes(sql: string): string {
|
|
|
569
569
|
}
|
|
570
570
|
|
|
571
571
|
/**
|
|
572
|
-
* Split SQL text into individual statements
|
|
572
|
+
* Split SQL text into individual statements with a single-pass, quote- and
|
|
573
|
+
* comment-aware scanner. The split decision is made character by character so
|
|
574
|
+
* the delimiter only ever fires in real statement position.
|
|
573
575
|
*
|
|
574
|
-
*
|
|
575
|
-
*
|
|
576
|
+
* This is the fix for issue #54: the old implementation split on `delimiter`
|
|
577
|
+
* BEFORE stripping `-- …` line comments, so a `;` inside a line comment
|
|
578
|
+
* fragmented one statement into several broken pieces. A scanner that knows
|
|
579
|
+
* where it is (code / comment / string) cannot make that mistake.
|
|
580
|
+
*
|
|
581
|
+
* Handled, in priority order, only when NOT already inside a stored-proc block:
|
|
582
|
+
* - `$$ … $$` and `// … //` stored-proc blocks are kept intact (inner `;` never
|
|
583
|
+
* splits). A `//` preceded by `:` is a URL scheme (`https://…`), not a delimiter.
|
|
584
|
+
* - `/* … */` block comments are stripped.
|
|
585
|
+
* - `-- …` line comments are stripped to end of line (the newline is kept).
|
|
586
|
+
* - `'…'` single-quoted strings and `"…"` double-quoted identifiers are copied
|
|
587
|
+
* verbatim, honouring the SQL doubled-quote escape (`''` / `""`); a `;`, `--`
|
|
588
|
+
* or `/*` inside a literal is data, not a delimiter or comment.
|
|
589
|
+
* Mirrors the tina4-python `_split_statements` / tina4-php scanner (parity).
|
|
576
590
|
*/
|
|
577
591
|
export function splitStatements(sql: string, delimiter = ";"): string[] {
|
|
578
592
|
// Normalize smart/curly quotes to straight ASCII first, so SQL pasted from
|
|
579
|
-
// an editor/doc (which converts " → “ ” and ' → ‘ ’) actually runs.
|
|
580
|
-
// Python's _split_statements applying _normalize_quotes as its first line.
|
|
593
|
+
// an editor/doc (which converts " → “ ” and ' → ‘ ’) actually runs.
|
|
581
594
|
sql = normalizeQuotes(sql);
|
|
582
595
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
596
|
+
const statements: string[] = [];
|
|
597
|
+
let current = "";
|
|
598
|
+
const n = sql.length;
|
|
599
|
+
const dlen = delimiter.length;
|
|
600
|
+
let i = 0;
|
|
601
|
+
let inDollarBlock = false;
|
|
602
|
+
let inSlashBlock = false;
|
|
603
|
+
|
|
604
|
+
while (i < n) {
|
|
605
|
+
const ch = sql[i];
|
|
606
|
+
|
|
607
|
+
// $$ … $$ stored-proc block (toggle).
|
|
608
|
+
if (!inSlashBlock && ch === "$" && i + 1 < n && sql[i + 1] === "$") {
|
|
609
|
+
current += "$$";
|
|
610
|
+
i += 2;
|
|
611
|
+
inDollarBlock = !inDollarBlock;
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// // … // stored-proc block (toggle) — but NOT a `://` URL scheme.
|
|
616
|
+
if (
|
|
617
|
+
!inDollarBlock && ch === "/" && i + 1 < n && sql[i + 1] === "/" &&
|
|
618
|
+
!(i > 0 && sql[i - 1] === ":")
|
|
619
|
+
) {
|
|
620
|
+
current += "//";
|
|
621
|
+
i += 2;
|
|
622
|
+
inSlashBlock = !inSlashBlock;
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
589
625
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
processed = processed.replace(/(?<!:)\/\/([\s\S]*?)(?<!:)\/\//g, saveBlock);
|
|
626
|
+
// Inside a stored-proc block: consume verbatim (inner ; never splits).
|
|
627
|
+
if (inDollarBlock || inSlashBlock) {
|
|
628
|
+
current += ch;
|
|
629
|
+
i += 1;
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
597
632
|
|
|
598
|
-
|
|
599
|
-
|
|
633
|
+
// Block comment /* … */ — stripped.
|
|
634
|
+
if (ch === "/" && i + 1 < n && sql[i + 1] === "*") {
|
|
635
|
+
const end = sql.indexOf("*/", i + 2);
|
|
636
|
+
i = end !== -1 ? end + 2 : n;
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
600
639
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
// Remove inline comments
|
|
608
|
-
const commentPos = line.indexOf("--");
|
|
609
|
-
lines.push(commentPos >= 0 ? line.slice(0, commentPos) : line);
|
|
640
|
+
// Line comment -- … — stripped to end of line; the newline is left for the
|
|
641
|
+
// next iteration so line structure (and NEXT-line boundaries) survive.
|
|
642
|
+
if (ch === "-" && i + 1 < n && sql[i + 1] === "-") {
|
|
643
|
+
const end = sql.indexOf("\n", i + 2);
|
|
644
|
+
i = end !== -1 ? end : n;
|
|
645
|
+
continue;
|
|
610
646
|
}
|
|
611
|
-
let cleaned = lines.join("\n").trim();
|
|
612
647
|
|
|
613
|
-
//
|
|
614
|
-
|
|
615
|
-
|
|
648
|
+
// Single-quoted string literal — '' escapes a quote. Copied verbatim.
|
|
649
|
+
if (ch === "'") {
|
|
650
|
+
current += "'";
|
|
651
|
+
i += 1;
|
|
652
|
+
while (i < n) {
|
|
653
|
+
if (sql[i] === "'" && i + 1 < n && sql[i + 1] === "'") {
|
|
654
|
+
current += "''";
|
|
655
|
+
i += 2;
|
|
656
|
+
} else if (sql[i] === "'") {
|
|
657
|
+
current += "'";
|
|
658
|
+
i += 1;
|
|
659
|
+
break;
|
|
660
|
+
} else {
|
|
661
|
+
current += sql[i];
|
|
662
|
+
i += 1;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Double-quoted identifier — "" escapes a quote. Same verbatim handling.
|
|
669
|
+
if (ch === '"') {
|
|
670
|
+
current += '"';
|
|
671
|
+
i += 1;
|
|
672
|
+
while (i < n) {
|
|
673
|
+
if (sql[i] === '"' && i + 1 < n && sql[i + 1] === '"') {
|
|
674
|
+
current += '""';
|
|
675
|
+
i += 2;
|
|
676
|
+
} else if (sql[i] === '"') {
|
|
677
|
+
current += '"';
|
|
678
|
+
i += 1;
|
|
679
|
+
break;
|
|
680
|
+
} else {
|
|
681
|
+
current += sql[i];
|
|
682
|
+
i += 1;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
continue;
|
|
616
686
|
}
|
|
617
687
|
|
|
618
|
-
|
|
688
|
+
// Statement delimiter — only reached outside blocks/comments/strings.
|
|
689
|
+
if (dlen > 0 && sql.startsWith(delimiter, i)) {
|
|
690
|
+
const stmt = current.trim();
|
|
691
|
+
if (stmt) statements.push(stmt);
|
|
692
|
+
current = "";
|
|
693
|
+
i += dlen;
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
current += ch;
|
|
698
|
+
i += 1;
|
|
619
699
|
}
|
|
700
|
+
|
|
701
|
+
const stmt = current.trim();
|
|
702
|
+
if (stmt) statements.push(stmt);
|
|
620
703
|
return statements;
|
|
621
704
|
}
|
|
622
705
|
|