@saastro/studio 0.1.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.
- package/dist/chunk-QTXHBNZQ.js +959 -0
- package/dist/chunk-QTXHBNZQ.js.map +1 -0
- package/dist/chunk-RXPV6UUN.js +24 -0
- package/dist/chunk-RXPV6UUN.js.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/integration/index.d.ts +31 -0
- package/dist/integration/index.js +8 -0
- package/dist/integration/index.js.map +1 -0
- package/dist/types-JUOAAaUW.d.ts +11 -0
- package/dist/vite/index.d.ts +32 -0
- package/dist/vite/index.js +7 -0
- package/dist/vite/index.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,959 @@
|
|
|
1
|
+
// src/vite/auto-wrap.ts
|
|
2
|
+
var FRONTMATTER_RE = /^\s*---\s*\n([\s\S]*?)\n---\s*\n/;
|
|
3
|
+
var SAASTRO_ATTR_RE = /data-saastro/;
|
|
4
|
+
var FIELD_PREFIX_REFERENCE_RE = /\bfieldPrefix\b/;
|
|
5
|
+
var FIRST_OPEN_TAG_RE = /<([a-zA-Z][a-zA-Z0-9]*)\b([^<>]*?)\s*(\/?)>/;
|
|
6
|
+
var INJECTION = ' {...(fieldPrefix ? { "data-saastro": `sec:${fieldPrefix}`, draggable: "true" } : {})}';
|
|
7
|
+
function transformAstroSource(source) {
|
|
8
|
+
const fm = FRONTMATTER_RE.exec(source);
|
|
9
|
+
if (!fm) return source;
|
|
10
|
+
if (!FIELD_PREFIX_REFERENCE_RE.test(fm[1])) return source;
|
|
11
|
+
const template = source.slice(fm[0].length);
|
|
12
|
+
const tagMatch = FIRST_OPEN_TAG_RE.exec(template);
|
|
13
|
+
if (!tagMatch) return source;
|
|
14
|
+
const tagName = tagMatch[1];
|
|
15
|
+
if (tagName[0] !== tagName[0].toLowerCase()) return source;
|
|
16
|
+
const existingAttrs = tagMatch[2];
|
|
17
|
+
if (SAASTRO_ATTR_RE.test(existingAttrs)) return source;
|
|
18
|
+
const selfClose = tagMatch[3];
|
|
19
|
+
const replacement = `<${tagName}${existingAttrs}${INJECTION}${selfClose ? " " + selfClose : ""}>`;
|
|
20
|
+
const templateInjected = template.slice(0, tagMatch.index) + replacement + template.slice(tagMatch.index + tagMatch[0].length);
|
|
21
|
+
return source.slice(0, fm[0].length) + templateInjected;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ../studio-core/dist/component-schema/index.js
|
|
25
|
+
import {
|
|
26
|
+
Project
|
|
27
|
+
} from "ts-morph";
|
|
28
|
+
import { Project as Project2 } from "ts-morph";
|
|
29
|
+
var STUDIO_DIRECTIVE_KINDS = /* @__PURE__ */ new Set([
|
|
30
|
+
"string",
|
|
31
|
+
"textarea",
|
|
32
|
+
"image-url",
|
|
33
|
+
"enum",
|
|
34
|
+
"number",
|
|
35
|
+
"boolean",
|
|
36
|
+
"array",
|
|
37
|
+
"object",
|
|
38
|
+
"json"
|
|
39
|
+
]);
|
|
40
|
+
var STUDIO_DIRECTIVE_RE = /@studio:([A-Za-z][A-Za-z0-9-]*)/g;
|
|
41
|
+
var IMAGE_NAME_HINTS = /(^|_)(image|img|avatar|logo|icon|thumbnail|photo|picture|cover|background)($|_)/i;
|
|
42
|
+
var IMAGE_SUFFIX_HINTS = /_(url|src)$/i;
|
|
43
|
+
var FRONTMATTER_RE2 = /^\s*---\s*\n([\s\S]*?)\n---/;
|
|
44
|
+
function extractFrontmatter(astroSource) {
|
|
45
|
+
const m = FRONTMATTER_RE2.exec(astroSource);
|
|
46
|
+
return m ? m[1] ?? "" : null;
|
|
47
|
+
}
|
|
48
|
+
function extractComponentSchema(astroSource, options = {}) {
|
|
49
|
+
const frontmatter = extractFrontmatter(astroSource);
|
|
50
|
+
if (frontmatter == null) return null;
|
|
51
|
+
const project = options.project ?? createProject();
|
|
52
|
+
const virtualName = options.componentPath ? `${options.componentPath}.frontmatter.ts` : "component.frontmatter.ts";
|
|
53
|
+
let sourceFile;
|
|
54
|
+
try {
|
|
55
|
+
sourceFile = project.createSourceFile(virtualName, frontmatter, {
|
|
56
|
+
overwrite: true
|
|
57
|
+
});
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
let propsDecl = sourceFile.getInterface("Props");
|
|
62
|
+
if (!propsDecl) {
|
|
63
|
+
propsDecl = sourceFile.getTypeAlias("Props");
|
|
64
|
+
}
|
|
65
|
+
if (!propsDecl) {
|
|
66
|
+
sourceFile.delete();
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const propsType = propsDecl.getType();
|
|
70
|
+
const fields = [];
|
|
71
|
+
for (const prop of propsType.getProperties()) {
|
|
72
|
+
const field = describeProperty(prop, propsDecl);
|
|
73
|
+
if (field) fields.push(field);
|
|
74
|
+
}
|
|
75
|
+
applyDefaultsFromDestructuring(frontmatter, fields);
|
|
76
|
+
sourceFile.delete();
|
|
77
|
+
return {
|
|
78
|
+
fields,
|
|
79
|
+
componentPath: options.componentPath,
|
|
80
|
+
version: 1
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function createProject() {
|
|
84
|
+
return new Project({
|
|
85
|
+
useInMemoryFileSystem: true,
|
|
86
|
+
skipFileDependencyResolution: true,
|
|
87
|
+
compilerOptions: {
|
|
88
|
+
strict: true,
|
|
89
|
+
// Match the customer's typical Astro tsconfig — ESNext + node16
|
|
90
|
+
// resolution covers most setups. Misses are non-fatal: unresolved
|
|
91
|
+
// imports surface as `'json'` kind with a description.
|
|
92
|
+
target: 99,
|
|
93
|
+
module: 199,
|
|
94
|
+
moduleResolution: 100,
|
|
95
|
+
allowSyntheticDefaultImports: true,
|
|
96
|
+
esModuleInterop: true,
|
|
97
|
+
skipLibCheck: true
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function describeProperty(prop, parent) {
|
|
102
|
+
const name = prop.getName();
|
|
103
|
+
const declarations = prop.getDeclarations();
|
|
104
|
+
const decl = declarations[0];
|
|
105
|
+
if (!decl) return null;
|
|
106
|
+
const isQuestionToken = decl.hasQuestionToken?.() ?? false;
|
|
107
|
+
const propsSourceFile = parent.getSourceFile();
|
|
108
|
+
const type = prop.getTypeAtLocation(propsSourceFile);
|
|
109
|
+
const optional = isQuestionToken || typeIncludes(type, (t) => t.isUndefined());
|
|
110
|
+
const directive = readStudioDirective(decl);
|
|
111
|
+
if (type.isBoolean() || stripUndefined(type).isBoolean()) {
|
|
112
|
+
return applyDirective({ name, kind: "boolean", optional }, directive, type);
|
|
113
|
+
}
|
|
114
|
+
const nonNullishParts = type.isUnion() ? type.getUnionTypes().filter((t) => !t.isUndefined() && !t.isNull()) : [type];
|
|
115
|
+
if (nonNullishParts.length >= 1 && nonNullishParts.every((t) => t.isStringLiteral())) {
|
|
116
|
+
const enumValues = nonNullishParts.map(
|
|
117
|
+
(t) => String(t.getLiteralValue())
|
|
118
|
+
);
|
|
119
|
+
if (enumValues.length === 1) {
|
|
120
|
+
const f = describeType(name, nonNullishParts[0]);
|
|
121
|
+
return applyDirective(
|
|
122
|
+
{ ...f, optional: f.optional || optional },
|
|
123
|
+
directive,
|
|
124
|
+
type
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return applyDirective(
|
|
128
|
+
{ name, kind: "enum", optional, enumValues },
|
|
129
|
+
directive,
|
|
130
|
+
type
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
if (nonNullishParts.length === 1) {
|
|
134
|
+
const f = describeType(name, nonNullishParts[0]);
|
|
135
|
+
return applyDirective(
|
|
136
|
+
{ ...f, optional: f.optional || optional },
|
|
137
|
+
directive,
|
|
138
|
+
type
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return applyDirective(
|
|
142
|
+
{
|
|
143
|
+
name,
|
|
144
|
+
kind: "json",
|
|
145
|
+
optional,
|
|
146
|
+
description: `unparsed: ${type.getText()}`
|
|
147
|
+
},
|
|
148
|
+
directive,
|
|
149
|
+
type
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
function readStudioDirective(decl) {
|
|
153
|
+
const sources = [];
|
|
154
|
+
const jsDocable = decl;
|
|
155
|
+
if (typeof jsDocable.getJsDocs === "function") {
|
|
156
|
+
for (const doc of jsDocable.getJsDocs()) {
|
|
157
|
+
sources.push(doc.getText());
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (sources.length === 0) {
|
|
161
|
+
const ranges = decl.getLeadingCommentRanges?.() ?? [];
|
|
162
|
+
for (const range of ranges) {
|
|
163
|
+
const text = range.getText();
|
|
164
|
+
if (text.includes("@studio:")) sources.push(text);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const source of sources) {
|
|
168
|
+
STUDIO_DIRECTIVE_RE.lastIndex = 0;
|
|
169
|
+
let match;
|
|
170
|
+
while ((match = STUDIO_DIRECTIVE_RE.exec(source)) !== null) {
|
|
171
|
+
const raw = match[1];
|
|
172
|
+
if (!raw) continue;
|
|
173
|
+
const kind = raw === "image" ? "image-url" : raw;
|
|
174
|
+
if (STUDIO_DIRECTIVE_KINDS.has(kind)) return kind;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
function applyDirective(field, directive, type) {
|
|
180
|
+
if (!directive) return field;
|
|
181
|
+
if (!isDirectiveCompatible(directive, type)) return field;
|
|
182
|
+
if (field.kind === directive) return field;
|
|
183
|
+
return { ...field, kind: directive };
|
|
184
|
+
}
|
|
185
|
+
function isDirectiveCompatible(directive, type) {
|
|
186
|
+
if (directive === "json") return true;
|
|
187
|
+
const stripped = stripUndefined(type);
|
|
188
|
+
const nonNullishParts = type.isUnion() ? type.getUnionTypes().filter((t) => !t.isUndefined() && !t.isNull()) : [stripped];
|
|
189
|
+
switch (directive) {
|
|
190
|
+
case "string":
|
|
191
|
+
case "textarea":
|
|
192
|
+
case "image-url":
|
|
193
|
+
return nonNullishParts.every((t) => t.isString() || t.isStringLiteral());
|
|
194
|
+
case "enum":
|
|
195
|
+
return nonNullishParts.every((t) => t.isString() || t.isStringLiteral());
|
|
196
|
+
case "number":
|
|
197
|
+
return nonNullishParts.every((t) => t.isNumber() || t.isNumberLiteral());
|
|
198
|
+
case "boolean":
|
|
199
|
+
return type.isBoolean() || stripped.isBoolean();
|
|
200
|
+
case "array":
|
|
201
|
+
return nonNullishParts.every((t) => t.isArray());
|
|
202
|
+
case "object":
|
|
203
|
+
return nonNullishParts.every((t) => isDescendableObject(t));
|
|
204
|
+
default:
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
var NON_DESCENDABLE_TYPE_NAMES = /* @__PURE__ */ new Set([
|
|
209
|
+
"RegExp",
|
|
210
|
+
"Date",
|
|
211
|
+
"Map",
|
|
212
|
+
"Set",
|
|
213
|
+
"WeakMap",
|
|
214
|
+
"WeakSet",
|
|
215
|
+
"Promise",
|
|
216
|
+
"Error",
|
|
217
|
+
"URL",
|
|
218
|
+
"URLSearchParams",
|
|
219
|
+
"ArrayBuffer",
|
|
220
|
+
"Uint8Array"
|
|
221
|
+
]);
|
|
222
|
+
function isCallable(type) {
|
|
223
|
+
return type.getCallSignatures().length > 0;
|
|
224
|
+
}
|
|
225
|
+
function isDescendableObject(type) {
|
|
226
|
+
if (!type.isObject() && !type.isInterface()) return false;
|
|
227
|
+
if (isCallable(type)) return false;
|
|
228
|
+
const symbol = type.getSymbol() ?? type.getAliasSymbol();
|
|
229
|
+
const symbolName = symbol?.getName();
|
|
230
|
+
if (symbolName && NON_DESCENDABLE_TYPE_NAMES.has(symbolName)) return false;
|
|
231
|
+
return type.getProperties().length > 0;
|
|
232
|
+
}
|
|
233
|
+
function describeType(name, type) {
|
|
234
|
+
if (type.isStringLiteral()) {
|
|
235
|
+
return { name, kind: "string", optional: false };
|
|
236
|
+
}
|
|
237
|
+
if (type.isUnion() && type.getUnionTypes().every((t) => t.isStringLiteral())) {
|
|
238
|
+
const values = type.getUnionTypes().map((t) => String(t.getLiteralValue()));
|
|
239
|
+
return { name, kind: "enum", optional: false, enumValues: values };
|
|
240
|
+
}
|
|
241
|
+
if (type.isString()) {
|
|
242
|
+
return { name, kind: pickStringKind(name), optional: false };
|
|
243
|
+
}
|
|
244
|
+
if (type.isNumber()) return { name, kind: "number", optional: false };
|
|
245
|
+
if (type.isBoolean()) return { name, kind: "boolean", optional: false };
|
|
246
|
+
if (type.isArray()) {
|
|
247
|
+
const elementType = type.getArrayElementType();
|
|
248
|
+
if (elementType) {
|
|
249
|
+
const itemDescriptor = describeAnyType("__item__", elementType);
|
|
250
|
+
return { name, kind: "array", optional: false, items: itemDescriptor };
|
|
251
|
+
}
|
|
252
|
+
return { name, kind: "array", optional: false };
|
|
253
|
+
}
|
|
254
|
+
if (isDescendableObject(type)) {
|
|
255
|
+
const fields = [];
|
|
256
|
+
for (const sub of type.getProperties()) {
|
|
257
|
+
const subDecl = sub.getDeclarations()[0];
|
|
258
|
+
if (!subDecl) continue;
|
|
259
|
+
const subType = sub.getTypeAtLocation(subDecl);
|
|
260
|
+
const subOptional = (subDecl.hasQuestionToken?.() ?? false) || typeIncludes(subType, (t) => t.isUndefined());
|
|
261
|
+
const sf = describeAnyType(sub.getName(), subType);
|
|
262
|
+
const subDirective = readStudioDirective(subDecl);
|
|
263
|
+
fields.push(
|
|
264
|
+
applyDirective(
|
|
265
|
+
{ ...sf, optional: sf.optional || subOptional },
|
|
266
|
+
subDirective,
|
|
267
|
+
subType
|
|
268
|
+
)
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if (fields.length > 0) {
|
|
272
|
+
return { name, kind: "object", optional: false, fields };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
name,
|
|
277
|
+
kind: "json",
|
|
278
|
+
optional: false,
|
|
279
|
+
description: `unparsed: ${type.getText()}`
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function describeAnyType(name, type) {
|
|
283
|
+
const optional = typeIncludes(type, (t) => t.isUndefined());
|
|
284
|
+
const nonNullishParts = type.isUnion() ? type.getUnionTypes().filter((t) => !t.isUndefined() && !t.isNull()) : [type];
|
|
285
|
+
if (nonNullishParts.length >= 1 && nonNullishParts.every((t) => t.isStringLiteral())) {
|
|
286
|
+
if (nonNullishParts.length === 1) {
|
|
287
|
+
return { ...describeType(name, nonNullishParts[0]), optional };
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
name,
|
|
291
|
+
kind: "enum",
|
|
292
|
+
optional,
|
|
293
|
+
enumValues: nonNullishParts.map((t) => String(t.getLiteralValue()))
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
if (nonNullishParts.length === 1) {
|
|
297
|
+
const f = describeType(name, nonNullishParts[0]);
|
|
298
|
+
return { ...f, optional: f.optional || optional };
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
name,
|
|
302
|
+
kind: "json",
|
|
303
|
+
optional,
|
|
304
|
+
description: `unparsed: ${type.getText()}`
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function pickStringKind(name) {
|
|
308
|
+
if (IMAGE_NAME_HINTS.test(name) || IMAGE_SUFFIX_HINTS.test(name)) {
|
|
309
|
+
return "image-url";
|
|
310
|
+
}
|
|
311
|
+
if (/description|body|content|copy|summary/i.test(name)) {
|
|
312
|
+
return "textarea";
|
|
313
|
+
}
|
|
314
|
+
return "string";
|
|
315
|
+
}
|
|
316
|
+
function typeIncludes(type, predicate) {
|
|
317
|
+
if (predicate(type)) return true;
|
|
318
|
+
if (type.isUnion()) {
|
|
319
|
+
return type.getUnionTypes().some(predicate);
|
|
320
|
+
}
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
function stripUndefined(type) {
|
|
324
|
+
if (!type.isUnion()) return type;
|
|
325
|
+
const remaining = type.getUnionTypes().filter((t) => !t.isUndefined() && !t.isNull());
|
|
326
|
+
return remaining[0] ?? type;
|
|
327
|
+
}
|
|
328
|
+
function applyDefaultsFromDestructuring(frontmatter, fields) {
|
|
329
|
+
const match = /const\s*\{([\s\S]*?)\}\s*=\s*Astro\.props/.exec(frontmatter);
|
|
330
|
+
if (!match) return;
|
|
331
|
+
const body = match[1] ?? "";
|
|
332
|
+
const entries = splitTopLevel(body, ",");
|
|
333
|
+
for (const entry of entries) {
|
|
334
|
+
const eq = entry.indexOf("=");
|
|
335
|
+
if (eq < 0) continue;
|
|
336
|
+
const lhs = entry.slice(0, eq).trim();
|
|
337
|
+
const rhs = entry.slice(eq + 1).trim();
|
|
338
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(lhs)) continue;
|
|
339
|
+
const target = fields.find((f) => f.name === lhs);
|
|
340
|
+
if (!target) continue;
|
|
341
|
+
target.defaultValue = parseLiteral(rhs);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
function splitTopLevel(s, sep) {
|
|
345
|
+
const out = [];
|
|
346
|
+
let depth = 0;
|
|
347
|
+
let current = "";
|
|
348
|
+
for (const ch of s) {
|
|
349
|
+
if (ch === "{" || ch === "[" || ch === "(") depth++;
|
|
350
|
+
else if (ch === "}" || ch === "]" || ch === ")") depth--;
|
|
351
|
+
if (ch === sep && depth === 0) {
|
|
352
|
+
out.push(current);
|
|
353
|
+
current = "";
|
|
354
|
+
} else {
|
|
355
|
+
current += ch;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (current.trim()) out.push(current);
|
|
359
|
+
return out;
|
|
360
|
+
}
|
|
361
|
+
function parseLiteral(rhs) {
|
|
362
|
+
const trimmed = rhs.trim();
|
|
363
|
+
if (trimmed === "true") return true;
|
|
364
|
+
if (trimmed === "false") return false;
|
|
365
|
+
if (trimmed === "null") return null;
|
|
366
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed);
|
|
367
|
+
const quoted = /^(['"`])([\s\S]*)\1$/.exec(trimmed);
|
|
368
|
+
if (quoted) return quoted[2];
|
|
369
|
+
return trimmed;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// src/vite/extract-props-schema.ts
|
|
373
|
+
var CACHE_MAX = 500;
|
|
374
|
+
var cache = /* @__PURE__ */ new Map();
|
|
375
|
+
function hashSource(source) {
|
|
376
|
+
let h = 5381;
|
|
377
|
+
for (let i = 0; i < source.length; i++) {
|
|
378
|
+
h = (h << 5) + h + source.charCodeAt(i) | 0;
|
|
379
|
+
}
|
|
380
|
+
return (h >>> 0).toString(16);
|
|
381
|
+
}
|
|
382
|
+
function extractPropsSchema(source, options = {}) {
|
|
383
|
+
const key = `${options.componentPath ?? ""} ${hashSource(source)}`;
|
|
384
|
+
if (cache.has(key)) {
|
|
385
|
+
const cached = cache.get(key) ?? null;
|
|
386
|
+
cache.delete(key);
|
|
387
|
+
cache.set(key, cached);
|
|
388
|
+
return cached;
|
|
389
|
+
}
|
|
390
|
+
const schema = extractComponentSchema(source, {
|
|
391
|
+
componentPath: options.componentPath
|
|
392
|
+
});
|
|
393
|
+
if (cache.size >= CACHE_MAX) {
|
|
394
|
+
const oldest = cache.keys().next().value;
|
|
395
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
396
|
+
}
|
|
397
|
+
cache.set(key, schema);
|
|
398
|
+
return schema;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/vite/inject-schema.ts
|
|
402
|
+
var FRONTMATTER_RE3 = /^\s*---\s*\n([\s\S]*?)\n---\s*\n/;
|
|
403
|
+
var SCHEMA_SCRIPT_MARKER = 'type="application/saastro-schema"';
|
|
404
|
+
var FIRST_OPEN_TAG_RE2 = /<([a-zA-Z][a-zA-Z0-9]*)\b([^<>]*?)\s*(\/?)>/;
|
|
405
|
+
function extractFieldPrefixDefault(frontmatter) {
|
|
406
|
+
const destruct = /const\s*\{([\s\S]*?)\}\s*=\s*Astro\.props/.exec(frontmatter);
|
|
407
|
+
if (!destruct) return null;
|
|
408
|
+
const body = destruct[1] ?? "";
|
|
409
|
+
const m = /\bfieldPrefix\s*=\s*(['"])([^'"\n]*)\1/.exec(body);
|
|
410
|
+
return m ? m[2] ?? null : null;
|
|
411
|
+
}
|
|
412
|
+
function extractFieldPrefixFromDataAttr(template) {
|
|
413
|
+
const m = /data-saastro=\{\s*`\s*sec:\$\{\s*fieldPrefix\s*\?\?\s*(['"])([^'"`]*)\1\s*\}/.exec(
|
|
414
|
+
template
|
|
415
|
+
);
|
|
416
|
+
return m ? m[2] ?? null : null;
|
|
417
|
+
}
|
|
418
|
+
function findClosingTag(haystack, tagName, searchFrom) {
|
|
419
|
+
const re = new RegExp(
|
|
420
|
+
`<!--[\\s\\S]*?-->|<(/?)${tagName}\\b[^<>]*?(/?)>`,
|
|
421
|
+
"gi"
|
|
422
|
+
);
|
|
423
|
+
re.lastIndex = searchFrom;
|
|
424
|
+
let depth = 1;
|
|
425
|
+
while (true) {
|
|
426
|
+
const m = re.exec(haystack);
|
|
427
|
+
if (!m) return -1;
|
|
428
|
+
if (m[0].startsWith("<!--")) continue;
|
|
429
|
+
const isClose = m[1] === "/";
|
|
430
|
+
const isSelfClose = m[2] === "/";
|
|
431
|
+
if (isClose) {
|
|
432
|
+
depth--;
|
|
433
|
+
if (depth === 0) return m.index;
|
|
434
|
+
} else if (!isSelfClose) {
|
|
435
|
+
depth++;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function encodeSchemaBody(schema) {
|
|
440
|
+
return JSON.stringify(schema).replace(/<\//g, "<\\/");
|
|
441
|
+
}
|
|
442
|
+
function injectSchema(source, schema, options = {}) {
|
|
443
|
+
if (source.includes(SCHEMA_SCRIPT_MARKER)) return source;
|
|
444
|
+
const fm = FRONTMATTER_RE3.exec(source);
|
|
445
|
+
if (!fm) return source;
|
|
446
|
+
const frontmatter = fm[1] ?? "";
|
|
447
|
+
const templateStart = fm[0].length;
|
|
448
|
+
const template = source.slice(templateStart);
|
|
449
|
+
const tagMatch = FIRST_OPEN_TAG_RE2.exec(template);
|
|
450
|
+
if (!tagMatch) return source;
|
|
451
|
+
const tagName = tagMatch[1];
|
|
452
|
+
if (tagName[0] !== tagName[0].toLowerCase()) return source;
|
|
453
|
+
const isSelfClose = tagMatch[3] === "/";
|
|
454
|
+
if (isSelfClose) return source;
|
|
455
|
+
const openTagEnd = tagMatch.index + tagMatch[0].length;
|
|
456
|
+
const closeIdx = findClosingTag(template, tagName, openTagEnd);
|
|
457
|
+
if (closeIdx < 0) return source;
|
|
458
|
+
const prefixDefault = options.prefixOverride ?? extractFieldPrefixDefault(frontmatter) ?? extractFieldPrefixFromDataAttr(template);
|
|
459
|
+
const dataPrefixAttr = prefixDefault != null ? `data-prefix={fieldPrefix ?? ${JSON.stringify(prefixDefault)}}` : `data-prefix={fieldPrefix}`;
|
|
460
|
+
const scriptTag = `<script type="application/saastro-schema" is:inline ${dataPrefixAttr}>` + encodeSchemaBody(schema) + `</script>`;
|
|
461
|
+
const newTemplate = template.slice(0, closeIdx) + scriptTag + template.slice(closeIdx);
|
|
462
|
+
return source.slice(0, templateStart) + newTemplate;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// src/vite/instrument-fields.ts
|
|
466
|
+
var FRONTMATTER_RE4 = /^\s*---\s*\n[\s\S]*?\n---\s*\n/;
|
|
467
|
+
function instrumentFields(source, schema, _options = {}) {
|
|
468
|
+
if (!schema.fields.length) return source;
|
|
469
|
+
if (!source.includes("data-saastro=")) return source;
|
|
470
|
+
const fmMatch = FRONTMATTER_RE4.exec(source);
|
|
471
|
+
if (!fmMatch) return source;
|
|
472
|
+
const templateStart = fmMatch[0].length;
|
|
473
|
+
const template = source.slice(templateStart);
|
|
474
|
+
const schemaScriptStart = template.indexOf(
|
|
475
|
+
'<script type="application/saastro-schema"'
|
|
476
|
+
);
|
|
477
|
+
let head;
|
|
478
|
+
let tail;
|
|
479
|
+
if (schemaScriptStart >= 0) {
|
|
480
|
+
head = template.slice(0, schemaScriptStart);
|
|
481
|
+
tail = template.slice(schemaScriptStart);
|
|
482
|
+
} else {
|
|
483
|
+
head = template;
|
|
484
|
+
tail = "";
|
|
485
|
+
}
|
|
486
|
+
const transformed = transformTemplate(head, schema.fields);
|
|
487
|
+
return source.slice(0, templateStart) + transformed + tail;
|
|
488
|
+
}
|
|
489
|
+
function transformTemplate(template, fields) {
|
|
490
|
+
let out = template;
|
|
491
|
+
for (const field of fields) {
|
|
492
|
+
if (field.kind !== "array") continue;
|
|
493
|
+
out = transformArrayField(out, field);
|
|
494
|
+
}
|
|
495
|
+
for (const field of fields) {
|
|
496
|
+
if (field.kind === "array") continue;
|
|
497
|
+
out = transformScalarField(out, field.name);
|
|
498
|
+
}
|
|
499
|
+
return out;
|
|
500
|
+
}
|
|
501
|
+
function transformArrayField(template, field) {
|
|
502
|
+
const name = field.name;
|
|
503
|
+
const subFields = field.items?.fields ?? [];
|
|
504
|
+
const re = new RegExp(
|
|
505
|
+
`\\b${escapeReg(name)}\\s*\\.\\s*map\\s*\\(\\s*\\(\\s*([A-Za-z_$][\\w$]*)\\s*(?:,\\s*([A-Za-z_$][\\w$]*)\\s*)?\\)\\s*=>\\s*`,
|
|
506
|
+
"g"
|
|
507
|
+
);
|
|
508
|
+
let result = "";
|
|
509
|
+
let cursor = 0;
|
|
510
|
+
let m;
|
|
511
|
+
while ((m = re.exec(template)) !== null) {
|
|
512
|
+
const headerStart = m.index;
|
|
513
|
+
const paramName = m[1];
|
|
514
|
+
const existingIdx = m[2] ?? null;
|
|
515
|
+
const arrowEnd = headerStart + m[0].length;
|
|
516
|
+
const body = readArrowBody(template, arrowEnd);
|
|
517
|
+
if (!body) continue;
|
|
518
|
+
let innerJsx = body.bodyText;
|
|
519
|
+
if (existingIdx && existingIdx !== "__i") {
|
|
520
|
+
const renameRe = new RegExp(`\\b${escapeReg(existingIdx)}\\b`, "g");
|
|
521
|
+
innerJsx = innerJsx.replace(renameRe, "__i");
|
|
522
|
+
}
|
|
523
|
+
const transformedJsx = stampArrayItem(innerJsx, name, paramName, subFields);
|
|
524
|
+
const headerNew = `${name}.map((${paramName}, __i) => `;
|
|
525
|
+
result += template.slice(cursor, headerStart) + headerNew;
|
|
526
|
+
if (body.parens) {
|
|
527
|
+
result += "(" + transformedJsx + ")";
|
|
528
|
+
} else {
|
|
529
|
+
result += transformedJsx;
|
|
530
|
+
}
|
|
531
|
+
const bodyAbsEnd = arrowEnd + body.bodyOffset + body.bodyText.length;
|
|
532
|
+
const callCloseAbsEnd = arrowEnd + body.totalEndRel;
|
|
533
|
+
if (body.parens) {
|
|
534
|
+
const afterBodyParen = bodyAbsEnd + 1;
|
|
535
|
+
result += template.slice(afterBodyParen, callCloseAbsEnd);
|
|
536
|
+
} else {
|
|
537
|
+
result += template.slice(bodyAbsEnd, callCloseAbsEnd);
|
|
538
|
+
}
|
|
539
|
+
cursor = callCloseAbsEnd;
|
|
540
|
+
re.lastIndex = cursor;
|
|
541
|
+
}
|
|
542
|
+
result += template.slice(cursor);
|
|
543
|
+
return result;
|
|
544
|
+
}
|
|
545
|
+
function readArrowBody(template, start) {
|
|
546
|
+
let i = start;
|
|
547
|
+
while (i < template.length && /\s/.test(template[i])) i++;
|
|
548
|
+
if (i >= template.length) return null;
|
|
549
|
+
let bodyStart;
|
|
550
|
+
let bodyEnd;
|
|
551
|
+
let parens;
|
|
552
|
+
if (template[i] === "(") {
|
|
553
|
+
parens = true;
|
|
554
|
+
const inner = readBalanced(template, i, "(", ")");
|
|
555
|
+
if (inner == null) return null;
|
|
556
|
+
bodyStart = i + 1;
|
|
557
|
+
bodyEnd = inner;
|
|
558
|
+
while (bodyStart < bodyEnd && /\s/.test(template[bodyStart])) bodyStart++;
|
|
559
|
+
let trimEnd = bodyEnd;
|
|
560
|
+
while (trimEnd > bodyStart && /\s/.test(template[trimEnd - 1])) trimEnd--;
|
|
561
|
+
bodyEnd = trimEnd;
|
|
562
|
+
let j = inner + 1;
|
|
563
|
+
while (j < template.length && /\s/.test(template[j])) j++;
|
|
564
|
+
if (template[j] !== ")") return null;
|
|
565
|
+
j++;
|
|
566
|
+
return {
|
|
567
|
+
parens,
|
|
568
|
+
bodyOffset: bodyStart - start,
|
|
569
|
+
bodyText: template.slice(bodyStart, bodyEnd),
|
|
570
|
+
totalEndRel: j - start
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
if (template[i] === "<") {
|
|
574
|
+
parens = false;
|
|
575
|
+
const elEnd = readJsxElement(template, i);
|
|
576
|
+
if (elEnd == null) return null;
|
|
577
|
+
bodyStart = i;
|
|
578
|
+
bodyEnd = elEnd;
|
|
579
|
+
let j = bodyEnd;
|
|
580
|
+
while (j < template.length && /\s/.test(template[j])) j++;
|
|
581
|
+
if (template[j] !== ")") return null;
|
|
582
|
+
j++;
|
|
583
|
+
return {
|
|
584
|
+
parens,
|
|
585
|
+
bodyOffset: bodyStart - start,
|
|
586
|
+
bodyText: template.slice(bodyStart, bodyEnd),
|
|
587
|
+
totalEndRel: j - start
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
function readBalanced(s, i, open, close) {
|
|
593
|
+
if (s[i] !== open) return null;
|
|
594
|
+
let depth = 0;
|
|
595
|
+
let j = i;
|
|
596
|
+
while (j < s.length) {
|
|
597
|
+
const ch = s[j];
|
|
598
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
599
|
+
j = skipString(s, j) + 1;
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (ch === open) depth++;
|
|
603
|
+
else if (ch === close) {
|
|
604
|
+
depth--;
|
|
605
|
+
if (depth === 0) return j;
|
|
606
|
+
}
|
|
607
|
+
j++;
|
|
608
|
+
}
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
function skipString(s, i) {
|
|
612
|
+
const quote = s[i];
|
|
613
|
+
let j = i + 1;
|
|
614
|
+
while (j < s.length) {
|
|
615
|
+
const ch = s[j];
|
|
616
|
+
if (ch === "\\") {
|
|
617
|
+
j += 2;
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
if (quote === "`" && ch === "$" && s[j + 1] === "{") {
|
|
621
|
+
const close = readBalanced(s, j + 1, "{", "}");
|
|
622
|
+
if (close == null) return s.length - 1;
|
|
623
|
+
j = close + 1;
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
if (ch === quote) return j;
|
|
627
|
+
j++;
|
|
628
|
+
}
|
|
629
|
+
return s.length - 1;
|
|
630
|
+
}
|
|
631
|
+
function readJsxElement(s, start) {
|
|
632
|
+
if (s[start] !== "<") return null;
|
|
633
|
+
let i = start + 1;
|
|
634
|
+
const nameMatch = /^[A-Za-z][A-Za-z0-9]*/.exec(s.slice(i));
|
|
635
|
+
if (!nameMatch) return null;
|
|
636
|
+
const tag = nameMatch[0];
|
|
637
|
+
i += tag.length;
|
|
638
|
+
let inString = null;
|
|
639
|
+
let braceDepth = 0;
|
|
640
|
+
while (i < s.length) {
|
|
641
|
+
const ch = s[i];
|
|
642
|
+
if (inString) {
|
|
643
|
+
if (ch === "\\") {
|
|
644
|
+
i += 2;
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
if (inString === "`" && ch === "$" && s[i + 1] === "{") {
|
|
648
|
+
const close = readBalanced(s, i + 1, "{", "}");
|
|
649
|
+
if (close == null) return null;
|
|
650
|
+
i = close + 1;
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
if (ch === inString) inString = null;
|
|
654
|
+
i++;
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
if (braceDepth > 0) {
|
|
658
|
+
if (ch === "{") braceDepth++;
|
|
659
|
+
else if (ch === "}") braceDepth--;
|
|
660
|
+
else if (ch === '"' || ch === "'" || ch === "`") inString = ch;
|
|
661
|
+
i++;
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
665
|
+
inString = ch;
|
|
666
|
+
i++;
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (ch === "{") {
|
|
670
|
+
braceDepth = 1;
|
|
671
|
+
i++;
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (ch === "/" && s[i + 1] === ">") {
|
|
675
|
+
return i + 2;
|
|
676
|
+
}
|
|
677
|
+
if (ch === ">") {
|
|
678
|
+
i++;
|
|
679
|
+
let depth = 1;
|
|
680
|
+
const re = new RegExp(`<\\/?${tag}\\b`, "g");
|
|
681
|
+
re.lastIndex = i;
|
|
682
|
+
while (true) {
|
|
683
|
+
const m = re.exec(s);
|
|
684
|
+
if (!m) return null;
|
|
685
|
+
const isClose = s[m.index + 1] === "/";
|
|
686
|
+
let k = m.index + (isClose ? 2 + tag.length : 1 + tag.length);
|
|
687
|
+
let str = null;
|
|
688
|
+
let bd = 0;
|
|
689
|
+
while (k < s.length) {
|
|
690
|
+
const c = s[k];
|
|
691
|
+
if (str) {
|
|
692
|
+
if (c === "\\") {
|
|
693
|
+
k += 2;
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (c === str) str = null;
|
|
697
|
+
k++;
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
if (bd > 0) {
|
|
701
|
+
if (c === "{") bd++;
|
|
702
|
+
else if (c === "}") bd--;
|
|
703
|
+
k++;
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
707
|
+
str = c;
|
|
708
|
+
k++;
|
|
709
|
+
continue;
|
|
710
|
+
}
|
|
711
|
+
if (c === "{") {
|
|
712
|
+
bd = 1;
|
|
713
|
+
k++;
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
if (c === "/" && s[k + 1] === ">") {
|
|
717
|
+
k += 2;
|
|
718
|
+
break;
|
|
719
|
+
}
|
|
720
|
+
if (c === ">") {
|
|
721
|
+
k++;
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
k++;
|
|
725
|
+
}
|
|
726
|
+
if (isClose) {
|
|
727
|
+
depth--;
|
|
728
|
+
if (depth === 0) return k;
|
|
729
|
+
} else {
|
|
730
|
+
if (s[k - 2] !== "/") depth++;
|
|
731
|
+
}
|
|
732
|
+
re.lastIndex = k;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
i++;
|
|
736
|
+
}
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
function stampArrayItem(jsx, fieldName, paramName, subFields) {
|
|
740
|
+
let out = stampOuterElement(
|
|
741
|
+
jsx,
|
|
742
|
+
`{\`${fieldName}[\${__i}]\`}`
|
|
743
|
+
);
|
|
744
|
+
for (const sub of subFields) {
|
|
745
|
+
const propPath = `{\`${fieldName}[\${__i}].${sub.name}\`}`;
|
|
746
|
+
out = transformDottedReference(out, paramName, sub.name, propPath);
|
|
747
|
+
}
|
|
748
|
+
return out;
|
|
749
|
+
}
|
|
750
|
+
function stampOuterElement(jsx, valueExpr) {
|
|
751
|
+
const m = /<([A-Za-z][A-Za-z0-9]*)\b([^<>]*?)(\/?)>/.exec(jsx);
|
|
752
|
+
if (!m) return jsx;
|
|
753
|
+
if (/data-saastro-field/.test(m[2])) return jsx;
|
|
754
|
+
const tag = m[1];
|
|
755
|
+
const attrs = m[2];
|
|
756
|
+
const selfClose = m[3];
|
|
757
|
+
const replacement = `<${tag}${attrs} data-saastro-field=${valueExpr}${selfClose}>`;
|
|
758
|
+
return jsx.slice(0, m.index) + replacement + jsx.slice(m.index + m[0].length);
|
|
759
|
+
}
|
|
760
|
+
function transformScalarField(template, name) {
|
|
761
|
+
return transformDottedReference(template, name, null, JSON.stringify(name));
|
|
762
|
+
}
|
|
763
|
+
function transformDottedReference(template, paramOrName, subProp, valueAttr) {
|
|
764
|
+
const refPattern = subProp ? new RegExp(
|
|
765
|
+
`\\{\\s*${escapeReg(paramOrName)}\\s*\\.\\s*${escapeReg(subProp)}\\s*\\}`,
|
|
766
|
+
"g"
|
|
767
|
+
) : new RegExp(`\\{\\s*${escapeReg(paramOrName)}\\s*\\}`, "g");
|
|
768
|
+
let out = template;
|
|
769
|
+
if (!subProp) {
|
|
770
|
+
out = stampImgSrc(out, paramOrName, valueAttr);
|
|
771
|
+
}
|
|
772
|
+
let result = "";
|
|
773
|
+
let cursor = 0;
|
|
774
|
+
let m;
|
|
775
|
+
refPattern.lastIndex = 0;
|
|
776
|
+
while ((m = refPattern.exec(out)) !== null) {
|
|
777
|
+
const refStart = m.index;
|
|
778
|
+
const refEnd = refStart + m[0].length;
|
|
779
|
+
if (isInsideTag(out, refStart)) {
|
|
780
|
+
result += out.slice(cursor, refEnd);
|
|
781
|
+
cursor = refEnd;
|
|
782
|
+
continue;
|
|
783
|
+
}
|
|
784
|
+
if (out[refEnd] === "." || out[refStart - 1] === ".") {
|
|
785
|
+
result += out.slice(cursor, refEnd);
|
|
786
|
+
cursor = refEnd;
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
const parent = findEnclosingElement(out, refStart);
|
|
790
|
+
if (!parent) {
|
|
791
|
+
result += out.slice(cursor, refEnd);
|
|
792
|
+
cursor = refEnd;
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
if (parent.attrs.includes("data-saastro=")) {
|
|
796
|
+
result += out.slice(cursor, refEnd);
|
|
797
|
+
cursor = refEnd;
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
if (parent.attrs.includes("data-saastro-field")) {
|
|
801
|
+
result += out.slice(cursor, refEnd);
|
|
802
|
+
cursor = refEnd;
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
const childContent = out.slice(parent.bodyStart, parent.bodyEnd);
|
|
806
|
+
const onlyChild = isOnlyMeaningfulExpression(childContent, refStart - parent.bodyStart, refEnd - parent.bodyStart);
|
|
807
|
+
if (onlyChild) {
|
|
808
|
+
const newOpen = injectAttr(parent.openTag, valueAttr);
|
|
809
|
+
const replaced = out.slice(0, parent.openStart) + newOpen + out.slice(parent.openEnd);
|
|
810
|
+
const delta = newOpen.length - parent.openTag.length;
|
|
811
|
+
result += replaced.slice(cursor, refEnd + delta);
|
|
812
|
+
cursor = refEnd + delta;
|
|
813
|
+
out = replaced;
|
|
814
|
+
refPattern.lastIndex = cursor;
|
|
815
|
+
} else {
|
|
816
|
+
const wrapped = `<span data-saastro-field=${valueAttr}>${m[0]}</span>`;
|
|
817
|
+
result += out.slice(cursor, refStart) + wrapped;
|
|
818
|
+
cursor = refEnd;
|
|
819
|
+
refPattern.lastIndex = cursor;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
result += out.slice(cursor);
|
|
823
|
+
return result;
|
|
824
|
+
}
|
|
825
|
+
function stampImgSrc(template, name, valueAttr) {
|
|
826
|
+
const re = /<img\b([^<>]*?)\/?>/g;
|
|
827
|
+
let result = "";
|
|
828
|
+
let cursor = 0;
|
|
829
|
+
let m;
|
|
830
|
+
while ((m = re.exec(template)) !== null) {
|
|
831
|
+
const attrs = m[1];
|
|
832
|
+
if (/data-saastro-field/.test(attrs)) continue;
|
|
833
|
+
const srcMatch = /\bsrc\s*=\s*\{([\s\S]*?)\}/.exec(attrs);
|
|
834
|
+
if (!srcMatch) continue;
|
|
835
|
+
const srcExpr = srcMatch[1];
|
|
836
|
+
if (!new RegExp(`\\b${escapeReg(name)}\\b`).test(srcExpr)) continue;
|
|
837
|
+
const newOpen = injectAttr(m[0], valueAttr);
|
|
838
|
+
result += template.slice(cursor, m.index) + newOpen;
|
|
839
|
+
cursor = m.index + m[0].length;
|
|
840
|
+
re.lastIndex = cursor;
|
|
841
|
+
}
|
|
842
|
+
result += template.slice(cursor);
|
|
843
|
+
return result;
|
|
844
|
+
}
|
|
845
|
+
function escapeReg(s) {
|
|
846
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
847
|
+
}
|
|
848
|
+
function injectAttr(tag, valueAttr) {
|
|
849
|
+
if (/data-saastro-field/.test(tag)) return tag;
|
|
850
|
+
const m = /^<([A-Za-z][A-Za-z0-9]*)/.exec(tag);
|
|
851
|
+
if (!m) return tag;
|
|
852
|
+
const insertAt = m[0].length;
|
|
853
|
+
return tag.slice(0, insertAt) + ` data-saastro-field=${valueAttr}` + tag.slice(insertAt);
|
|
854
|
+
}
|
|
855
|
+
function isInsideTag(s, pos) {
|
|
856
|
+
for (let i = pos - 1; i >= 0; i--) {
|
|
857
|
+
const ch = s[i];
|
|
858
|
+
if (ch === ">") return false;
|
|
859
|
+
if (ch === "<") return true;
|
|
860
|
+
}
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
863
|
+
function findEnclosingElement(s, pos) {
|
|
864
|
+
const tagRe = /<(\/?)([A-Za-z][A-Za-z0-9]*)\b([^<>]*?)(\/?)>/g;
|
|
865
|
+
const tags = [];
|
|
866
|
+
let m;
|
|
867
|
+
while ((m = tagRe.exec(s)) !== null) {
|
|
868
|
+
tags.push({
|
|
869
|
+
isClose: m[1] === "/",
|
|
870
|
+
selfClose: m[4] === "/",
|
|
871
|
+
tagName: m[2],
|
|
872
|
+
attrs: m[3],
|
|
873
|
+
start: m.index,
|
|
874
|
+
end: m.index + m[0].length
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
const stack = [];
|
|
878
|
+
let bestOpen = null;
|
|
879
|
+
let bestClose = null;
|
|
880
|
+
for (const t of tags) {
|
|
881
|
+
if (t.selfClose) continue;
|
|
882
|
+
if (t.isClose) {
|
|
883
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
884
|
+
if (stack[i].tagName === t.tagName) {
|
|
885
|
+
const opener = stack[i];
|
|
886
|
+
stack.splice(i, stack.length - i);
|
|
887
|
+
if (opener.end <= pos && pos < t.start) {
|
|
888
|
+
if (!bestOpen || opener.end > bestOpen.end || opener.end === bestOpen.end && t.start < bestClose.start) {
|
|
889
|
+
bestOpen = opener;
|
|
890
|
+
bestClose = t;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
} else {
|
|
897
|
+
stack.push(t);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
if (!bestOpen || !bestClose) return null;
|
|
901
|
+
return {
|
|
902
|
+
openStart: bestOpen.start,
|
|
903
|
+
openEnd: bestOpen.end,
|
|
904
|
+
openTag: s.slice(bestOpen.start, bestOpen.end),
|
|
905
|
+
attrs: bestOpen.attrs,
|
|
906
|
+
bodyStart: bestOpen.end,
|
|
907
|
+
bodyEnd: bestClose.start
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
function isOnlyMeaningfulExpression(body, relStart, relEnd) {
|
|
911
|
+
const before = body.slice(0, relStart);
|
|
912
|
+
const after = body.slice(relEnd);
|
|
913
|
+
return /^\s*$/.test(before) && /^\s*$/.test(after);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// src/vite/meta-tag.ts
|
|
917
|
+
var STUDIO_PROTOCOL_VERSION = "2";
|
|
918
|
+
var PACKAGE_VERSION = "0.1.0".length > 0 ? "0.1.0" : "0.0.0+unknown";
|
|
919
|
+
var META_TAGS = [
|
|
920
|
+
`<meta name="saastro:studio-version" content="${STUDIO_PROTOCOL_VERSION}">`,
|
|
921
|
+
`<meta name="saastro:studio-package" content="${PACKAGE_VERSION}">`
|
|
922
|
+
].join("\n ");
|
|
923
|
+
function injectVersionMetaTag(html) {
|
|
924
|
+
if (html.includes("saastro:studio-version")) return html;
|
|
925
|
+
return html.replace(/<head([^>]*)>/i, `<head$1>
|
|
926
|
+
${META_TAGS}`);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// src/vite/index.ts
|
|
930
|
+
function studioAutoWrap(opts = {}) {
|
|
931
|
+
const enabled = opts.autoWrap === true;
|
|
932
|
+
const include = opts.include ?? ((id) => id.endsWith(".astro"));
|
|
933
|
+
return {
|
|
934
|
+
name: "@saastro/studio:auto-wrap",
|
|
935
|
+
enforce: "pre",
|
|
936
|
+
transform(code, id) {
|
|
937
|
+
if (!enabled) return null;
|
|
938
|
+
if (!include(id)) return null;
|
|
939
|
+
const wrapped = transformAstroSource(code);
|
|
940
|
+
if (wrapped === code) return null;
|
|
941
|
+
const schema = extractPropsSchema(code, { componentPath: id });
|
|
942
|
+
let next = schema ? injectSchema(wrapped, schema) : wrapped;
|
|
943
|
+
if (schema) {
|
|
944
|
+
next = instrumentFields(next, schema, { componentPath: id });
|
|
945
|
+
}
|
|
946
|
+
if (next === code) return null;
|
|
947
|
+
return { code: next, map: null };
|
|
948
|
+
},
|
|
949
|
+
transformIndexHtml(html) {
|
|
950
|
+
if (!enabled) return html;
|
|
951
|
+
return injectVersionMetaTag(html);
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
export {
|
|
957
|
+
studioAutoWrap
|
|
958
|
+
};
|
|
959
|
+
//# sourceMappingURL=chunk-QTXHBNZQ.js.map
|