@saastro/studio 0.8.0 → 0.10.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.
Files changed (41) hide show
  1. package/dist/{chunk-FGYTDBZU.js → chunk-3VK5GQVH.js} +1323 -508
  2. package/dist/chunk-3VK5GQVH.js.map +1 -0
  3. package/dist/{chunk-PKDMKMD6.js → chunk-7V3SGN3T.js} +19 -2
  4. package/dist/chunk-7V3SGN3T.js.map +1 -0
  5. package/dist/{chunk-7U2GMTVW.js → chunk-IKMPDZYE.js} +17 -4
  6. package/dist/chunk-IKMPDZYE.js.map +1 -0
  7. package/dist/{chunk-JMSDBWL2.js → chunk-N2PHBHNZ.js} +2 -2
  8. package/dist/chunk-OI6N7H4Y.js +24 -0
  9. package/dist/chunk-OI6N7H4Y.js.map +1 -0
  10. package/dist/chunk-TCKD2I2B.js +443 -0
  11. package/dist/chunk-TCKD2I2B.js.map +1 -0
  12. package/dist/doctor.d.ts +35 -3
  13. package/dist/doctor.js +96 -10
  14. package/dist/doctor.js.map +1 -1
  15. package/dist/editable-array-item-Cko68alJ.d.ts +33 -0
  16. package/dist/index.d.ts +4 -32
  17. package/dist/index.js +9 -22
  18. package/dist/index.js.map +1 -1
  19. package/dist/integration/index.js +4 -4
  20. package/dist/markers.d.ts +20 -0
  21. package/dist/markers.js +23 -0
  22. package/dist/markers.js.map +1 -0
  23. package/dist/middleware/index.js +3 -1
  24. package/dist/middleware/index.js.map +1 -1
  25. package/dist/richtext/index.d.ts +96 -17
  26. package/dist/richtext/index.js +23 -1
  27. package/dist/vite/index.js +3 -3
  28. package/package.json +9 -5
  29. package/src/RichText.astro +45 -22
  30. package/dist/chunk-4L5C226B.js +0 -71
  31. package/dist/chunk-4L5C226B.js.map +0 -1
  32. package/dist/chunk-5QZN4IGF.js +0 -71
  33. package/dist/chunk-5QZN4IGF.js.map +0 -1
  34. package/dist/chunk-7U2GMTVW.js.map +0 -1
  35. package/dist/chunk-FGYTDBZU.js.map +0 -1
  36. package/dist/chunk-K7DTYPRE.js +0 -1348
  37. package/dist/chunk-K7DTYPRE.js.map +0 -1
  38. package/dist/chunk-PKDMKMD6.js.map +0 -1
  39. package/dist/chunk-S2IKJ3QM.js +0 -28
  40. package/dist/chunk-S2IKJ3QM.js.map +0 -1
  41. /package/dist/{chunk-JMSDBWL2.js.map → chunk-N2PHBHNZ.js.map} +0 -0
@@ -1,1348 +0,0 @@
1
- import {
2
- buildVisibilityStyleTag,
3
- injectVersionMetaTag,
4
- injectVisibilityStyle,
5
- readVisibilityConfigSync
6
- } from "./chunk-4L5C226B.js";
7
- import {
8
- hasFrontmatterFieldPrefix,
9
- resolvePageKey,
10
- transformAstroPage,
11
- transformAstroSource
12
- } from "./chunk-PKDMKMD6.js";
13
-
14
- // src/vite/index.ts
15
- import { promises as fs } from "node:fs";
16
-
17
- // ../studio-core/dist/component-schema/index.js
18
- import {
19
- Project
20
- } from "ts-morph";
21
- import { Project as Project2 } from "ts-morph";
22
- var STUDIO_DIRECTIVE_KINDS = /* @__PURE__ */ new Set([
23
- "string",
24
- "textarea",
25
- "image-url",
26
- "enum",
27
- "number",
28
- "boolean",
29
- "date",
30
- "array",
31
- "object",
32
- "json"
33
- ]);
34
- var STUDIO_DIRECTIVE_RE = /@studio:([A-Za-z][A-Za-z0-9-]*)/g;
35
- var IMAGE_NAME_HINTS = /(^|_)(image|img|avatar|logo|icon|thumbnail|photo|picture|cover|background)($|_)/i;
36
- var IMAGE_SUFFIX_HINTS = /_(url|src)$/i;
37
- var DATE_NAME_HINTS = /(^|_)date($|_)|date$|_at$|At$|^(published|updated|created|expires?|start|end|due)(date|at)?$/i;
38
- var FRONTMATTER_RE = /^\s*---\s*\n([\s\S]*?)\n---/;
39
- function extractFrontmatter(astroSource) {
40
- const m = FRONTMATTER_RE.exec(astroSource);
41
- return m ? m[1] ?? "" : null;
42
- }
43
- function extractPropsTypeBlock(tsxSource) {
44
- const interfaceRe = /\b(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)\s*\{/g;
45
- const typeRe = /\b(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=\s*\{/g;
46
- let match;
47
- while ((match = interfaceRe.exec(tsxSource)) !== null) {
48
- const name = match[1];
49
- if (name === "Props" || name.endsWith("Props")) {
50
- const braceOpenIdx = tsxSource.indexOf("{", match.index);
51
- if (braceOpenIdx === -1) continue;
52
- const close = readBalancedBrace(tsxSource, braceOpenIdx);
53
- if (close === null) continue;
54
- const block = tsxSource.slice(match.index, close + 1);
55
- return normalizeInterfaceName(block, name);
56
- }
57
- }
58
- while ((match = typeRe.exec(tsxSource)) !== null) {
59
- const name = match[1];
60
- if (name === "Props" || name.endsWith("Props")) {
61
- const braceOpenIdx = tsxSource.indexOf("{", match.index);
62
- if (braceOpenIdx === -1) continue;
63
- const close = readBalancedBrace(tsxSource, braceOpenIdx);
64
- if (close === null) continue;
65
- const block = tsxSource.slice(match.index, close + 1);
66
- return normalizeTypeAliasName(block, name);
67
- }
68
- }
69
- return null;
70
- }
71
- function normalizeInterfaceName(block, name) {
72
- if (name === "Props") return block;
73
- return block.replace(/^(\s*(?:export\s+)?interface\s+)[A-Za-z_$][\w$]*/, "$1Props");
74
- }
75
- function normalizeTypeAliasName(block, name) {
76
- if (name === "Props") return block;
77
- return block.replace(/^(\s*(?:export\s+)?type\s+)[A-Za-z_$][\w$]*/, "$1Props");
78
- }
79
- function readBalancedBrace(s, open) {
80
- if (s[open] !== "{") return null;
81
- let depth = 0;
82
- let i = open;
83
- while (i < s.length) {
84
- const ch = s[i];
85
- if (ch === '"' || ch === "'" || ch === "`") {
86
- i = skipStringLiteral(s, i) + 1;
87
- continue;
88
- }
89
- if (ch === "/" && s[i + 1] === "/") {
90
- const eol = s.indexOf("\n", i + 2);
91
- i = eol === -1 ? s.length : eol + 1;
92
- continue;
93
- }
94
- if (ch === "/" && s[i + 1] === "*") {
95
- const end = s.indexOf("*/", i + 2);
96
- i = end === -1 ? s.length : end + 2;
97
- continue;
98
- }
99
- if (ch === "{") depth++;
100
- else if (ch === "}") {
101
- depth--;
102
- if (depth === 0) return i;
103
- }
104
- i++;
105
- }
106
- return null;
107
- }
108
- function skipStringLiteral(s, start) {
109
- const quote = s[start];
110
- let i = start + 1;
111
- while (i < s.length) {
112
- const ch = s[i];
113
- if (ch === "\\") {
114
- i += 2;
115
- continue;
116
- }
117
- if (quote === "`" && ch === "$" && s[i + 1] === "{") {
118
- const close = readBalancedBrace(s, i + 1);
119
- if (close === null) return s.length - 1;
120
- i = close + 1;
121
- continue;
122
- }
123
- if (ch === quote) return i;
124
- i++;
125
- }
126
- return s.length - 1;
127
- }
128
- function extractAllTypeBlocks(tsxSource) {
129
- const blocks = [];
130
- const seen = /* @__PURE__ */ new Set();
131
- const decl = /\b(?:export\s+)?(?:interface|type)\s+[A-Za-z_$][\w$]*/g;
132
- let m;
133
- while ((m = decl.exec(tsxSource)) !== null) {
134
- const start = m.index;
135
- if (seen.has(start)) continue;
136
- seen.add(start);
137
- const isType = /\btype\b/.test(m[0]);
138
- const braceOpen = tsxSource.indexOf("{", start);
139
- if (braceOpen < 0) continue;
140
- const between = tsxSource.slice(start + m[0].length, braceOpen);
141
- if (!/^[\s=]*(?:extends\s+[^{]+)?$/.test(between)) continue;
142
- const close = readBalancedBrace(tsxSource, braceOpen);
143
- if (close === null) continue;
144
- let block = tsxSource.slice(start, close + 1);
145
- if (isType) {
146
- if (!block.trimEnd().endsWith(";")) block = block + ";";
147
- }
148
- blocks.push(block);
149
- decl.lastIndex = close + 1;
150
- }
151
- return blocks;
152
- }
153
- function extractComponentSchemaFromTsx(tsxSource, options = {}) {
154
- const propsBlock = extractPropsTypeBlock(tsxSource);
155
- if (!propsBlock) return null;
156
- const allBlocks = extractAllTypeBlocks(tsxSource);
157
- const siblings = allBlocks.filter((b) => !looksLikeProps(b));
158
- const synthetic = `---
159
- ` + [...siblings, propsBlock].join("\n\n") + `
160
- ---
161
- `;
162
- return extractComponentSchema(synthetic, options);
163
- }
164
- function looksLikeProps(block) {
165
- return /^\s*(?:export\s+)?(?:interface|type)\s+(?:Props\b|[A-Za-z_$][\w$]*Props\b)/.test(block);
166
- }
167
- function extractComponentSchema(astroSource, options = {}) {
168
- const frontmatter = extractFrontmatter(astroSource);
169
- if (frontmatter == null) return null;
170
- const project = options.project ?? createProject();
171
- const virtualName = options.componentPath ? `${options.componentPath}.frontmatter.ts` : "component.frontmatter.ts";
172
- let sourceFile;
173
- try {
174
- sourceFile = project.createSourceFile(virtualName, frontmatter, {
175
- overwrite: true
176
- });
177
- } catch {
178
- return null;
179
- }
180
- let propsDecl = sourceFile.getInterface("Props");
181
- if (!propsDecl) {
182
- propsDecl = sourceFile.getTypeAlias("Props");
183
- }
184
- if (!propsDecl) {
185
- sourceFile.delete();
186
- return null;
187
- }
188
- const propsType = propsDecl.getType();
189
- const fields = [];
190
- for (const prop of propsType.getProperties()) {
191
- const field = describeProperty(prop, propsDecl);
192
- if (field) fields.push(field);
193
- }
194
- applyDefaultsFromDestructuring(frontmatter, fields);
195
- sourceFile.delete();
196
- return {
197
- fields,
198
- componentPath: options.componentPath,
199
- version: 1
200
- };
201
- }
202
- function createProject() {
203
- return new Project({
204
- useInMemoryFileSystem: true,
205
- skipFileDependencyResolution: true,
206
- compilerOptions: {
207
- strict: true,
208
- // Match the customer's typical Astro tsconfig — ESNext + node16
209
- // resolution covers most setups. Misses are non-fatal: unresolved
210
- // imports surface as `'json'` kind with a description.
211
- target: 99,
212
- module: 199,
213
- moduleResolution: 100,
214
- allowSyntheticDefaultImports: true,
215
- esModuleInterop: true,
216
- skipLibCheck: true
217
- }
218
- });
219
- }
220
- function describeProperty(prop, parent) {
221
- const name = prop.getName();
222
- const declarations = prop.getDeclarations();
223
- const decl = declarations[0];
224
- if (!decl) return null;
225
- const isQuestionToken = decl.hasQuestionToken?.() ?? false;
226
- const propsSourceFile = parent.getSourceFile();
227
- const type = prop.getTypeAtLocation(propsSourceFile);
228
- const optional = isQuestionToken || typeIncludes(type, (t) => t.isUndefined());
229
- const directive = readStudioDirective(decl);
230
- if (isBooleanType(type)) {
231
- return applyDirective({ name, kind: "boolean", optional }, directive, type);
232
- }
233
- const nonNullishParts = type.isUnion() ? type.getUnionTypes().filter((t) => !t.isUndefined() && !t.isNull()) : [type];
234
- if (nonNullishParts.length >= 1 && nonNullishParts.every((t) => t.isStringLiteral())) {
235
- const enumValues = nonNullishParts.map(
236
- (t) => String(t.getLiteralValue())
237
- );
238
- if (enumValues.length === 1) {
239
- const f = describeType(name, nonNullishParts[0]);
240
- return applyDirective(
241
- { ...f, optional: f.optional || optional },
242
- directive,
243
- type
244
- );
245
- }
246
- return applyDirective(
247
- { name, kind: "enum", optional, enumValues },
248
- directive,
249
- type
250
- );
251
- }
252
- if (nonNullishParts.length === 1) {
253
- const f = describeType(name, nonNullishParts[0]);
254
- return applyDirective(
255
- { ...f, optional: f.optional || optional },
256
- directive,
257
- type
258
- );
259
- }
260
- return applyDirective(
261
- {
262
- name,
263
- kind: "json",
264
- optional,
265
- description: `unparsed: ${type.getText()}`
266
- },
267
- directive,
268
- type
269
- );
270
- }
271
- function readStudioDirective(decl) {
272
- const sources = [];
273
- const jsDocable = decl;
274
- if (typeof jsDocable.getJsDocs === "function") {
275
- for (const doc of jsDocable.getJsDocs()) {
276
- sources.push(doc.getText());
277
- }
278
- }
279
- if (sources.length === 0) {
280
- const ranges = decl.getLeadingCommentRanges?.() ?? [];
281
- for (const range of ranges) {
282
- const text = range.getText();
283
- if (text.includes("@studio:")) sources.push(text);
284
- }
285
- }
286
- for (const source of sources) {
287
- STUDIO_DIRECTIVE_RE.lastIndex = 0;
288
- let match;
289
- while ((match = STUDIO_DIRECTIVE_RE.exec(source)) !== null) {
290
- const raw = match[1];
291
- if (!raw) continue;
292
- const kind = raw === "image" ? "image-url" : raw;
293
- if (STUDIO_DIRECTIVE_KINDS.has(kind)) return kind;
294
- }
295
- }
296
- return null;
297
- }
298
- function applyDirective(field, directive, type) {
299
- if (!directive) return field;
300
- if (!isDirectiveCompatible(directive, type)) return field;
301
- if (field.kind === directive) return field;
302
- return { ...field, kind: directive };
303
- }
304
- function isDirectiveCompatible(directive, type) {
305
- if (directive === "json") return true;
306
- const stripped = stripUndefined(type);
307
- const nonNullishParts = type.isUnion() ? type.getUnionTypes().filter((t) => !t.isUndefined() && !t.isNull()) : [stripped];
308
- switch (directive) {
309
- case "string":
310
- case "textarea":
311
- case "image-url":
312
- return nonNullishParts.every((t) => t.isString() || t.isStringLiteral());
313
- case "date":
314
- return nonNullishParts.every(
315
- (t) => t.isString() || t.isStringLiteral() || (t.getSymbol() ?? t.getAliasSymbol())?.getName() === "Date"
316
- );
317
- case "enum":
318
- return nonNullishParts.every((t) => t.isString() || t.isStringLiteral());
319
- case "number":
320
- return nonNullishParts.every((t) => t.isNumber() || t.isNumberLiteral());
321
- case "boolean":
322
- return isBooleanType(type);
323
- case "array":
324
- return nonNullishParts.every((t) => t.isArray());
325
- case "object":
326
- return nonNullishParts.every((t) => isDescendableObject(t));
327
- default:
328
- return false;
329
- }
330
- }
331
- var NON_DESCENDABLE_TYPE_NAMES = /* @__PURE__ */ new Set([
332
- "RegExp",
333
- "Date",
334
- "Map",
335
- "Set",
336
- "WeakMap",
337
- "WeakSet",
338
- "Promise",
339
- "Error",
340
- "URL",
341
- "URLSearchParams",
342
- "ArrayBuffer",
343
- "Uint8Array"
344
- ]);
345
- function isCallable(type) {
346
- return type.getCallSignatures().length > 0;
347
- }
348
- function isDescendableObject(type) {
349
- if (!type.isObject() && !type.isInterface()) return false;
350
- if (isCallable(type)) return false;
351
- const symbol = type.getSymbol() ?? type.getAliasSymbol();
352
- const symbolName = symbol?.getName();
353
- if (symbolName && NON_DESCENDABLE_TYPE_NAMES.has(symbolName)) return false;
354
- return type.getProperties().length > 0;
355
- }
356
- var MAX_DESCENT_DEPTH = 8;
357
- function describeType(name, type, depth = 0) {
358
- if (type.isStringLiteral()) {
359
- return { name, kind: "string", optional: false };
360
- }
361
- if (type.isUnion() && type.getUnionTypes().every((t) => t.isStringLiteral())) {
362
- const values = type.getUnionTypes().map((t) => String(t.getLiteralValue()));
363
- return { name, kind: "enum", optional: false, enumValues: values };
364
- }
365
- if (type.isString()) {
366
- return { name, kind: pickStringKind(name), optional: false };
367
- }
368
- if (type.isNumber()) return { name, kind: "number", optional: false };
369
- if (isBooleanType(type)) return { name, kind: "boolean", optional: false };
370
- if ((type.getSymbol() ?? type.getAliasSymbol())?.getName() === "Date") {
371
- return { name, kind: "date", optional: false };
372
- }
373
- if (depth >= MAX_DESCENT_DEPTH) {
374
- return {
375
- name,
376
- kind: "json",
377
- optional: false,
378
- description: `unparsed: ${type.getText()} (depth ${depth})`
379
- };
380
- }
381
- if (type.isArray()) {
382
- const elementType = type.getArrayElementType();
383
- if (elementType) {
384
- const itemDescriptor = describeAnyType("__item__", elementType, depth + 1);
385
- return { name, kind: "array", optional: false, items: itemDescriptor };
386
- }
387
- return { name, kind: "array", optional: false };
388
- }
389
- if (isDescendableObject(type)) {
390
- const fields = [];
391
- for (const sub of type.getProperties()) {
392
- const subDecl = sub.getDeclarations()[0];
393
- if (!subDecl) continue;
394
- const subType = sub.getTypeAtLocation(subDecl);
395
- const subOptional = (subDecl.hasQuestionToken?.() ?? false) || typeIncludes(subType, (t) => t.isUndefined());
396
- const sf = describeAnyType(sub.getName(), subType, depth + 1);
397
- const subDirective = readStudioDirective(subDecl);
398
- fields.push(
399
- applyDirective(
400
- { ...sf, optional: sf.optional || subOptional },
401
- subDirective,
402
- subType
403
- )
404
- );
405
- }
406
- if (fields.length > 0) {
407
- return { name, kind: "object", optional: false, fields };
408
- }
409
- }
410
- return {
411
- name,
412
- kind: "json",
413
- optional: false,
414
- description: `unparsed: ${type.getText()}`
415
- };
416
- }
417
- function describeAnyType(name, type, depth = 0) {
418
- const optional = typeIncludes(type, (t) => t.isUndefined());
419
- if (isBooleanType(type)) return { name, kind: "boolean", optional };
420
- const nonNullishParts = type.isUnion() ? type.getUnionTypes().filter((t) => !t.isUndefined() && !t.isNull()) : [type];
421
- if (nonNullishParts.length >= 1 && nonNullishParts.every((t) => t.isStringLiteral())) {
422
- if (nonNullishParts.length === 1) {
423
- return { ...describeType(name, nonNullishParts[0], depth), optional };
424
- }
425
- return {
426
- name,
427
- kind: "enum",
428
- optional,
429
- enumValues: nonNullishParts.map((t) => String(t.getLiteralValue()))
430
- };
431
- }
432
- if (nonNullishParts.length === 1) {
433
- const f = describeType(name, nonNullishParts[0], depth);
434
- return { ...f, optional: f.optional || optional };
435
- }
436
- return {
437
- name,
438
- kind: "json",
439
- optional,
440
- description: `unparsed: ${type.getText()}`
441
- };
442
- }
443
- function pickStringKind(name) {
444
- if (IMAGE_NAME_HINTS.test(name) || IMAGE_SUFFIX_HINTS.test(name)) {
445
- return "image-url";
446
- }
447
- if (DATE_NAME_HINTS.test(name)) {
448
- return "date";
449
- }
450
- if (/description|body|content|copy|summary/i.test(name)) {
451
- return "textarea";
452
- }
453
- return "string";
454
- }
455
- function typeIncludes(type, predicate) {
456
- if (predicate(type)) return true;
457
- if (type.isUnion()) {
458
- return type.getUnionTypes().some(predicate);
459
- }
460
- return false;
461
- }
462
- function stripUndefined(type) {
463
- if (!type.isUnion()) return type;
464
- const remaining = type.getUnionTypes().filter((t) => !t.isUndefined() && !t.isNull());
465
- return remaining[0] ?? type;
466
- }
467
- function isBooleanType(type) {
468
- if (type.isBoolean() || type.isBooleanLiteral()) return true;
469
- const parts = type.isUnion() ? type.getUnionTypes().filter((t) => !t.isUndefined() && !t.isNull()) : [type];
470
- return parts.length > 0 && parts.every((t) => t.isBoolean() || t.isBooleanLiteral());
471
- }
472
- function applyDefaultsFromDestructuring(frontmatter, fields) {
473
- const match = /const\s*\{([\s\S]*?)\}\s*=\s*Astro\.props/.exec(frontmatter);
474
- if (!match) return;
475
- const body = match[1] ?? "";
476
- const entries = splitTopLevel(body, ",");
477
- for (const entry of entries) {
478
- const eq = entry.indexOf("=");
479
- if (eq < 0) continue;
480
- const lhs = entry.slice(0, eq).trim();
481
- const rhs = entry.slice(eq + 1).trim();
482
- if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(lhs)) continue;
483
- const target = fields.find((f) => f.name === lhs);
484
- if (!target) continue;
485
- target.defaultValue = parseLiteral(rhs);
486
- }
487
- }
488
- function splitTopLevel(s, sep) {
489
- const out = [];
490
- let depth = 0;
491
- let current = "";
492
- for (const ch of s) {
493
- if (ch === "{" || ch === "[" || ch === "(") depth++;
494
- else if (ch === "}" || ch === "]" || ch === ")") depth--;
495
- if (ch === sep && depth === 0) {
496
- out.push(current);
497
- current = "";
498
- } else {
499
- current += ch;
500
- }
501
- }
502
- if (current.trim()) out.push(current);
503
- return out;
504
- }
505
- function parseLiteral(rhs) {
506
- const trimmed = rhs.trim();
507
- if (trimmed === "true") return true;
508
- if (trimmed === "false") return false;
509
- if (trimmed === "null") return null;
510
- if (/^-?\d+(\.\d+)?$/.test(trimmed)) return Number(trimmed);
511
- const quoted = /^(['"`])([\s\S]*)\1$/.exec(trimmed);
512
- if (quoted) return quoted[2];
513
- return trimmed;
514
- }
515
-
516
- // src/vite/extract-props-schema.ts
517
- var CACHE_MAX = 500;
518
- var cache = /* @__PURE__ */ new Map();
519
- function hashSource(source) {
520
- let h = 5381;
521
- for (let i = 0; i < source.length; i++) {
522
- h = (h << 5) + h + source.charCodeAt(i) | 0;
523
- }
524
- return (h >>> 0).toString(16);
525
- }
526
- function extractPropsSchema(source, options = {}) {
527
- const key = `${options.componentPath ?? ""} ${hashSource(source)}`;
528
- if (cache.has(key)) {
529
- const cached = cache.get(key) ?? null;
530
- cache.delete(key);
531
- cache.set(key, cached);
532
- return cached;
533
- }
534
- const schema = extractComponentSchema(source, {
535
- componentPath: options.componentPath
536
- });
537
- if (cache.size >= CACHE_MAX) {
538
- const oldest = cache.keys().next().value;
539
- if (oldest !== void 0) cache.delete(oldest);
540
- }
541
- cache.set(key, schema);
542
- return schema;
543
- }
544
-
545
- // src/vite/inject-schema.ts
546
- var FRONTMATTER_RE2 = /^\s*---\s*\n([\s\S]*?)\n---\s*\n/;
547
- var SCHEMA_SCRIPT_MARKER = 'type="application/saastro-schema"';
548
- var FIRST_OPEN_TAG_RE = /<([a-zA-Z][a-zA-Z0-9]*)\b([^<>]*?)\s*(\/?)>/;
549
- function extractFieldPrefixDefault(frontmatter) {
550
- const destruct = /const\s*\{([\s\S]*?)\}\s*=\s*Astro\.props/.exec(frontmatter);
551
- if (!destruct) return null;
552
- const body = destruct[1] ?? "";
553
- const m = /\bfieldPrefix\s*=\s*(['"])([^'"\n]*)\1/.exec(body);
554
- return m ? m[2] ?? null : null;
555
- }
556
- function extractFieldPrefixFromDataAttr(template) {
557
- const m = /data-saastro=\{\s*`\s*sec:\$\{\s*fieldPrefix\s*\?\?\s*(['"])([^'"`]*)\1\s*\}/.exec(
558
- template
559
- );
560
- return m ? m[2] ?? null : null;
561
- }
562
- function findClosingTag(haystack, tagName, searchFrom) {
563
- const re = new RegExp(
564
- `<!--[\\s\\S]*?-->|<(/?)${tagName}\\b[^<>]*?(/?)>`,
565
- "gi"
566
- );
567
- re.lastIndex = searchFrom;
568
- let depth = 1;
569
- while (true) {
570
- const m = re.exec(haystack);
571
- if (!m) return -1;
572
- if (m[0].startsWith("<!--")) continue;
573
- const isClose = m[1] === "/";
574
- const isSelfClose = m[2] === "/";
575
- if (isClose) {
576
- depth--;
577
- if (depth === 0) return m.index;
578
- } else if (!isSelfClose) {
579
- depth++;
580
- }
581
- }
582
- }
583
- function encodeSchemaBody(schema) {
584
- return JSON.stringify(schema).replace(/<\//g, "<\\/");
585
- }
586
- function injectSchema(source, schema, options = {}) {
587
- if (source.includes(SCHEMA_SCRIPT_MARKER)) return source;
588
- const fm = FRONTMATTER_RE2.exec(source);
589
- if (!fm) return source;
590
- const frontmatter = fm[1] ?? "";
591
- const templateStart = fm[0].length;
592
- const template = source.slice(templateStart);
593
- const tagMatch = FIRST_OPEN_TAG_RE.exec(template);
594
- if (!tagMatch) return source;
595
- const tagName = tagMatch[1];
596
- if (tagName[0] !== tagName[0].toLowerCase()) return source;
597
- const isSelfClose = tagMatch[3] === "/";
598
- if (isSelfClose) return source;
599
- const openTagEnd = tagMatch.index + tagMatch[0].length;
600
- const closeIdx = findClosingTag(template, tagName, openTagEnd);
601
- if (closeIdx < 0) return source;
602
- const prefixDefault = options.prefixOverride ?? extractFieldPrefixDefault(frontmatter) ?? extractFieldPrefixFromDataAttr(template);
603
- const dataPrefixAttr = prefixDefault != null ? `data-prefix={fieldPrefix ?? ${JSON.stringify(prefixDefault)}}` : `data-prefix={fieldPrefix}`;
604
- const scriptTag = `<script type="application/saastro-schema" is:inline ${dataPrefixAttr}>` + encodeSchemaBody(schema) + `</script>`;
605
- const newTemplate = template.slice(0, closeIdx) + scriptTag + template.slice(closeIdx);
606
- return source.slice(0, templateStart) + newTemplate;
607
- }
608
-
609
- // src/vite/instrument-fields.ts
610
- var FRONTMATTER_RE3 = /^\s*---\s*\n[\s\S]*?\n---\s*\n/;
611
- function instrumentFields(source, schema, options = {}) {
612
- if (!schema.fields.length) return source;
613
- if (!source.includes("data-saastro=")) return source;
614
- const fmMatch = FRONTMATTER_RE3.exec(source);
615
- if (!fmMatch) return source;
616
- const templateStart = fmMatch[0].length;
617
- const template = source.slice(templateStart);
618
- const schemaScriptStart = template.indexOf(
619
- '<script type="application/saastro-schema"'
620
- );
621
- let head;
622
- let tail;
623
- if (schemaScriptStart >= 0) {
624
- head = template.slice(0, schemaScriptStart);
625
- tail = template.slice(schemaScriptStart);
626
- } else {
627
- head = template;
628
- tail = "";
629
- }
630
- const transformed = transformTemplate(
631
- head,
632
- schema.fields,
633
- options.componentPath,
634
- source
635
- );
636
- return source.slice(0, templateStart) + transformed + tail;
637
- }
638
- function instrumentJsxFields(source, schema, options = {}) {
639
- if (!schema.fields.length) return source;
640
- if (!/<[A-Za-z]/.test(source)) return source;
641
- return transformTemplate(source, schema.fields, options.componentPath, source);
642
- }
643
- function transformTemplate(template, fields, componentPath, source = template) {
644
- let out = template;
645
- for (const field of fields) {
646
- if (field.kind !== "array") continue;
647
- const res = transformArrayField(out, field, componentPath);
648
- out = res.out;
649
- if (!res.matched && hasMapReference(source, field.name) && !hasHandWrittenFieldMarker(template, field.name)) {
650
- warnUninstrumentedArray(componentPath, field.name);
651
- }
652
- }
653
- for (const field of fields) {
654
- if (field.kind === "array") continue;
655
- out = transformScalarField(out, field.name);
656
- }
657
- return out;
658
- }
659
- function hasHandWrittenFieldMarker(template, name) {
660
- const n = escapeReg(name);
661
- return new RegExp(
662
- "data-saastro(?:-field)?\\s*=\\s*\\{\\s*`[^`]*\\b" + n + "[[.]"
663
- ).test(template);
664
- }
665
- function warnUninstrumentedArray(componentPath, name, mapExpr = name) {
666
- const where = componentPath ? ` in ${componentPath}` : "";
667
- console.warn(
668
- `[saastro/studio] array prop "${name}"${where} was not auto-instrumented: no \`${mapExpr}.map(...)\` found in the template (it may be iterated via a derived variable, a chained method, or a destructured param). Its items won't be editable in the Studio until you hand-write data-saastro-field markers \u2014 e.g. <img src={item.image} data-saastro-field={\`${name}[\${i}].image\`} />.`
669
- );
670
- }
671
- function hasMapReference(source, name) {
672
- return new RegExp("\\b" + escapeReg(name) + "\\s*\\.\\s*map\\s*\\(").test(source);
673
- }
674
- function transformArrayField(template, field, componentPath) {
675
- const name = field.name;
676
- const re = mapCallAnchor(escapeReg(name));
677
- return rewriteMapCalls(
678
- template,
679
- re,
680
- "__i",
681
- (paramName) => `${name}.map((${paramName}, __i) => `,
682
- (innerJsx, paramName) => stampArrayItem(
683
- innerJsx,
684
- `${name}[\${__i}]`,
685
- paramName,
686
- field.items,
687
- 1,
688
- componentPath
689
- )
690
- );
691
- }
692
- function transformNestedArrayField(template, parentParam, field, parentPathPrefix, depth, componentPath) {
693
- const indexVar = `__i${depth + 1}`;
694
- const callee = `${parentParam}.${field.name}`;
695
- const re = mapCallAnchor(
696
- `${escapeReg(parentParam)}\\s*\\.\\s*${escapeReg(field.name)}`
697
- );
698
- return rewriteMapCalls(
699
- template,
700
- re,
701
- indexVar,
702
- (paramName) => `${callee}.map((${paramName}, ${indexVar}) => `,
703
- (innerJsx, paramName) => stampArrayItem(
704
- innerJsx,
705
- `${parentPathPrefix}.${field.name}[\${${indexVar}}]`,
706
- paramName,
707
- field.items,
708
- depth + 1,
709
- componentPath
710
- )
711
- );
712
- }
713
- function mapCallAnchor(calleePattern) {
714
- return new RegExp(
715
- `\\b${calleePattern}\\s*\\.\\s*map\\s*\\(\\s*\\(\\s*([A-Za-z_$][\\w$]*)\\s*(?:,\\s*([A-Za-z_$][\\w$]*)\\s*)?\\)\\s*=>\\s*`,
716
- "g"
717
- );
718
- }
719
- function rewriteMapCalls(template, re, indexVar, makeHeader, transformBody) {
720
- let result = "";
721
- let cursor = 0;
722
- let matchCount = 0;
723
- let m;
724
- while ((m = re.exec(template)) !== null) {
725
- matchCount++;
726
- const headerStart = m.index;
727
- const paramName = m[1];
728
- const existingIdx = m[2] ?? null;
729
- const arrowEnd = headerStart + m[0].length;
730
- const body = readArrowBody(template, arrowEnd);
731
- if (!body) continue;
732
- let innerJsx = body.bodyText;
733
- if (existingIdx && existingIdx !== indexVar) {
734
- const renameRe = new RegExp(`\\b${escapeReg(existingIdx)}\\b`, "g");
735
- innerJsx = innerJsx.replace(renameRe, indexVar);
736
- }
737
- const transformedJsx = transformBody(innerJsx, paramName);
738
- result += template.slice(cursor, headerStart) + makeHeader(paramName);
739
- if (body.parens) {
740
- result += "(" + transformedJsx + ")";
741
- } else {
742
- result += transformedJsx;
743
- }
744
- const afterBody = arrowEnd + body.bodyCloseRel;
745
- const callCloseAbsEnd = arrowEnd + body.totalEndRel;
746
- result += template.slice(afterBody, callCloseAbsEnd);
747
- cursor = callCloseAbsEnd;
748
- re.lastIndex = cursor;
749
- }
750
- result += template.slice(cursor);
751
- return { out: result, matched: matchCount > 0 };
752
- }
753
- function readArrowBody(template, start) {
754
- let i = start;
755
- while (i < template.length && /\s/.test(template[i])) i++;
756
- if (i >= template.length) return null;
757
- let bodyStart;
758
- let bodyEnd;
759
- let parens;
760
- if (template[i] === "(") {
761
- parens = true;
762
- const inner = readBalanced(template, i, "(", ")");
763
- if (inner == null) return null;
764
- bodyStart = i + 1;
765
- bodyEnd = inner;
766
- while (bodyStart < bodyEnd && /\s/.test(template[bodyStart])) bodyStart++;
767
- let trimEnd = bodyEnd;
768
- while (trimEnd > bodyStart && /\s/.test(template[trimEnd - 1])) trimEnd--;
769
- bodyEnd = trimEnd;
770
- let j = inner + 1;
771
- while (j < template.length && /\s/.test(template[j])) j++;
772
- if (template[j] !== ")") return null;
773
- j++;
774
- return {
775
- parens,
776
- bodyOffset: bodyStart - start,
777
- bodyText: template.slice(bodyStart, bodyEnd),
778
- bodyCloseRel: inner + 1 - start,
779
- totalEndRel: j - start
780
- };
781
- }
782
- if (template[i] === "<") {
783
- parens = false;
784
- const elEnd = readJsxElement(template, i);
785
- if (elEnd == null) return null;
786
- bodyStart = i;
787
- bodyEnd = elEnd;
788
- let j = bodyEnd;
789
- while (j < template.length && /\s/.test(template[j])) j++;
790
- if (template[j] !== ")") return null;
791
- j++;
792
- return {
793
- parens,
794
- bodyOffset: bodyStart - start,
795
- bodyText: template.slice(bodyStart, bodyEnd),
796
- bodyCloseRel: bodyEnd - start,
797
- totalEndRel: j - start
798
- };
799
- }
800
- return null;
801
- }
802
- function readBalanced(s, i, open, close) {
803
- if (s[i] !== open) return null;
804
- let depth = 0;
805
- let j = i;
806
- while (j < s.length) {
807
- const ch = s[j];
808
- if (ch === '"' || ch === "'" || ch === "`") {
809
- j = skipString(s, j) + 1;
810
- continue;
811
- }
812
- if (ch === open) depth++;
813
- else if (ch === close) {
814
- depth--;
815
- if (depth === 0) return j;
816
- }
817
- j++;
818
- }
819
- return null;
820
- }
821
- function skipString(s, i) {
822
- const quote = s[i];
823
- let j = i + 1;
824
- while (j < s.length) {
825
- const ch = s[j];
826
- if (ch === "\\") {
827
- j += 2;
828
- continue;
829
- }
830
- if (quote === "`" && ch === "$" && s[j + 1] === "{") {
831
- const close = readBalanced(s, j + 1, "{", "}");
832
- if (close == null) return s.length - 1;
833
- j = close + 1;
834
- continue;
835
- }
836
- if (ch === quote) return j;
837
- j++;
838
- }
839
- return s.length - 1;
840
- }
841
- function readJsxElement(s, start) {
842
- if (s[start] !== "<") return null;
843
- let i = start + 1;
844
- const nameMatch = /^[A-Za-z][A-Za-z0-9]*/.exec(s.slice(i));
845
- if (!nameMatch) return null;
846
- const tag = nameMatch[0];
847
- i += tag.length;
848
- let inString = null;
849
- let braceDepth = 0;
850
- while (i < s.length) {
851
- const ch = s[i];
852
- if (inString) {
853
- if (ch === "\\") {
854
- i += 2;
855
- continue;
856
- }
857
- if (inString === "`" && ch === "$" && s[i + 1] === "{") {
858
- const close = readBalanced(s, i + 1, "{", "}");
859
- if (close == null) return null;
860
- i = close + 1;
861
- continue;
862
- }
863
- if (ch === inString) inString = null;
864
- i++;
865
- continue;
866
- }
867
- if (braceDepth > 0) {
868
- if (ch === "{") braceDepth++;
869
- else if (ch === "}") braceDepth--;
870
- else if (ch === '"' || ch === "'" || ch === "`") inString = ch;
871
- i++;
872
- continue;
873
- }
874
- if (ch === '"' || ch === "'" || ch === "`") {
875
- inString = ch;
876
- i++;
877
- continue;
878
- }
879
- if (ch === "{") {
880
- braceDepth = 1;
881
- i++;
882
- continue;
883
- }
884
- if (ch === "/" && s[i + 1] === ">") {
885
- return i + 2;
886
- }
887
- if (ch === ">") {
888
- i++;
889
- let depth = 1;
890
- const re = new RegExp(`<\\/?${tag}\\b`, "g");
891
- re.lastIndex = i;
892
- while (true) {
893
- const m = re.exec(s);
894
- if (!m) return null;
895
- const isClose = s[m.index + 1] === "/";
896
- let k = m.index + (isClose ? 2 + tag.length : 1 + tag.length);
897
- let str = null;
898
- let bd = 0;
899
- while (k < s.length) {
900
- const c = s[k];
901
- if (str) {
902
- if (c === "\\") {
903
- k += 2;
904
- continue;
905
- }
906
- if (c === str) str = null;
907
- k++;
908
- continue;
909
- }
910
- if (bd > 0) {
911
- if (c === "{") bd++;
912
- else if (c === "}") bd--;
913
- k++;
914
- continue;
915
- }
916
- if (c === '"' || c === "'" || c === "`") {
917
- str = c;
918
- k++;
919
- continue;
920
- }
921
- if (c === "{") {
922
- bd = 1;
923
- k++;
924
- continue;
925
- }
926
- if (c === "/" && s[k + 1] === ">") {
927
- k += 2;
928
- break;
929
- }
930
- if (c === ">") {
931
- k++;
932
- break;
933
- }
934
- k++;
935
- }
936
- if (isClose) {
937
- depth--;
938
- if (depth === 0) return k;
939
- } else {
940
- if (s[k - 2] !== "/") depth++;
941
- }
942
- re.lastIndex = k;
943
- }
944
- }
945
- i++;
946
- }
947
- return null;
948
- }
949
- function stampArrayItem(jsx, pathPrefix, paramName, itemField, depth, componentPath) {
950
- let out = stampOuterElement(jsx, `{\`${pathPrefix}\`}`);
951
- const subFields = itemField?.fields ?? [];
952
- for (const sub of subFields) {
953
- if (sub.kind !== "array") continue;
954
- const res = transformNestedArrayField(
955
- out,
956
- paramName,
957
- sub,
958
- pathPrefix,
959
- depth,
960
- componentPath
961
- );
962
- out = res.out;
963
- const mapExpr = `${paramName}.${sub.name}`;
964
- if (!res.matched && hasMapReference(out, mapExpr) && !hasHandWrittenFieldMarker(out, sub.name)) {
965
- const displayPath = `${pathPrefix.replace(/\[\$\{__i\d*\}\]/g, "[]")}.${sub.name}`;
966
- warnUninstrumentedArray(componentPath, displayPath, mapExpr);
967
- }
968
- }
969
- for (const sub of subFields) {
970
- if (sub.kind === "array") continue;
971
- const propPath = `{\`${pathPrefix}.${sub.name}\`}`;
972
- out = transformDottedReference(out, paramName, sub.name, propPath);
973
- }
974
- return out;
975
- }
976
- function stampOuterElement(jsx, valueExpr) {
977
- const start = jsx.search(/<[A-Za-z]/);
978
- if (start < 0) return jsx;
979
- const tagInfo = readOpeningTag(jsx, start);
980
- if (!tagInfo) return jsx;
981
- if (/data-saastro-field/.test(tagInfo.attrs)) return jsx;
982
- const inject = ` data-saastro-field=${valueExpr}`;
983
- const insertAt = start + 1 + tagInfo.tag.length;
984
- return jsx.slice(0, insertAt) + inject + jsx.slice(insertAt);
985
- }
986
- function readOpeningTag(s, start) {
987
- if (s[start] !== "<") return null;
988
- const nameMatch = /^[A-Za-z][A-Za-z0-9]*/.exec(s.slice(start + 1));
989
- if (!nameMatch) return null;
990
- const tag = nameMatch[0];
991
- let i = start + 1 + tag.length;
992
- const attrsStart = i;
993
- let inString = null;
994
- let braceDepth = 0;
995
- while (i < s.length) {
996
- const ch = s[i];
997
- if (inString) {
998
- if (ch === "\\") {
999
- i += 2;
1000
- continue;
1001
- }
1002
- if (inString === "`" && ch === "$" && s[i + 1] === "{") {
1003
- const close = readBalanced(s, i + 1, "{", "}");
1004
- if (close == null) return null;
1005
- i = close + 1;
1006
- continue;
1007
- }
1008
- if (ch === inString) inString = null;
1009
- i++;
1010
- continue;
1011
- }
1012
- if (braceDepth > 0) {
1013
- if (ch === '"' || ch === "'" || ch === "`") {
1014
- inString = ch;
1015
- i++;
1016
- continue;
1017
- }
1018
- if (ch === "{") braceDepth++;
1019
- else if (ch === "}") braceDepth--;
1020
- i++;
1021
- continue;
1022
- }
1023
- if (ch === '"' || ch === "'" || ch === "`") {
1024
- inString = ch;
1025
- i++;
1026
- continue;
1027
- }
1028
- if (ch === "{") {
1029
- braceDepth = 1;
1030
- i++;
1031
- continue;
1032
- }
1033
- if (ch === "/" && s[i + 1] === ">") {
1034
- return {
1035
- tag,
1036
- attrs: s.slice(attrsStart, i),
1037
- end: i + 2,
1038
- selfClose: true
1039
- };
1040
- }
1041
- if (ch === ">") {
1042
- return {
1043
- tag,
1044
- attrs: s.slice(attrsStart, i),
1045
- end: i + 1,
1046
- selfClose: false
1047
- };
1048
- }
1049
- i++;
1050
- }
1051
- return null;
1052
- }
1053
- function transformScalarField(template, name) {
1054
- return transformDottedReference(template, name, null, JSON.stringify(name));
1055
- }
1056
- function transformDottedReference(template, paramOrName, subProp, valueAttr) {
1057
- const refPattern = subProp ? new RegExp(
1058
- `\\{\\s*${escapeReg(paramOrName)}\\s*\\.\\s*${escapeReg(subProp)}\\s*\\}`,
1059
- "g"
1060
- ) : new RegExp(`\\{\\s*${escapeReg(paramOrName)}\\s*\\}`, "g");
1061
- let out = template;
1062
- const imgSrcRef = subProp ? `${paramOrName}.${subProp}` : paramOrName;
1063
- out = stampImgSrc(out, imgSrcRef, valueAttr);
1064
- let result = "";
1065
- let cursor = 0;
1066
- let m;
1067
- refPattern.lastIndex = 0;
1068
- while ((m = refPattern.exec(out)) !== null) {
1069
- const refStart = m.index;
1070
- const refEnd = refStart + m[0].length;
1071
- if (isInsideTag(out, refStart)) {
1072
- result += out.slice(cursor, refEnd);
1073
- cursor = refEnd;
1074
- continue;
1075
- }
1076
- if (out[refEnd] === "." || out[refStart - 1] === ".") {
1077
- result += out.slice(cursor, refEnd);
1078
- cursor = refEnd;
1079
- continue;
1080
- }
1081
- const parent = findEnclosingElement(out, refStart);
1082
- if (!parent) {
1083
- result += out.slice(cursor, refEnd);
1084
- cursor = refEnd;
1085
- continue;
1086
- }
1087
- if (parent.attrs.includes("data-saastro=")) {
1088
- result += out.slice(cursor, refEnd);
1089
- cursor = refEnd;
1090
- continue;
1091
- }
1092
- if (parent.attrs.includes("data-saastro-field")) {
1093
- result += out.slice(cursor, refEnd);
1094
- cursor = refEnd;
1095
- continue;
1096
- }
1097
- const childContent = out.slice(parent.bodyStart, parent.bodyEnd);
1098
- const onlyChild = isOnlyMeaningfulExpression(childContent, refStart - parent.bodyStart, refEnd - parent.bodyStart);
1099
- if (onlyChild) {
1100
- const newOpen = injectAttr(parent.openTag, valueAttr);
1101
- const replaced = out.slice(0, parent.openStart) + newOpen + out.slice(parent.openEnd);
1102
- const delta = newOpen.length - parent.openTag.length;
1103
- result += replaced.slice(cursor, refEnd + delta);
1104
- cursor = refEnd + delta;
1105
- out = replaced;
1106
- refPattern.lastIndex = cursor;
1107
- } else {
1108
- const wrapped = `<span data-saastro-field=${valueAttr}>${m[0]}</span>`;
1109
- result += out.slice(cursor, refStart) + wrapped;
1110
- cursor = refEnd;
1111
- refPattern.lastIndex = cursor;
1112
- }
1113
- }
1114
- result += out.slice(cursor);
1115
- return result;
1116
- }
1117
- function stampImgSrc(template, name, valueAttr) {
1118
- const re = /<img\b([^<>]*?)\/?>/g;
1119
- let result = "";
1120
- let cursor = 0;
1121
- let m;
1122
- while ((m = re.exec(template)) !== null) {
1123
- const attrs = m[1];
1124
- if (/data-saastro-field/.test(attrs)) continue;
1125
- const srcMatch = /\bsrc\s*=\s*\{([\s\S]*?)\}/.exec(attrs);
1126
- if (!srcMatch) continue;
1127
- const srcExpr = srcMatch[1];
1128
- if (!new RegExp(`\\b${escapeReg(name)}\\b`).test(srcExpr)) continue;
1129
- const newOpen = injectAttr(m[0], valueAttr);
1130
- result += template.slice(cursor, m.index) + newOpen;
1131
- cursor = m.index + m[0].length;
1132
- re.lastIndex = cursor;
1133
- }
1134
- result += template.slice(cursor);
1135
- return result;
1136
- }
1137
- function escapeReg(s) {
1138
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1139
- }
1140
- function injectAttr(tag, valueAttr) {
1141
- if (/data-saastro-field/.test(tag)) return tag;
1142
- const m = /^<([A-Za-z][A-Za-z0-9]*)/.exec(tag);
1143
- if (!m) return tag;
1144
- const insertAt = m[0].length;
1145
- return tag.slice(0, insertAt) + ` data-saastro-field=${valueAttr}` + tag.slice(insertAt);
1146
- }
1147
- function isInsideTag(s, pos) {
1148
- for (let i = pos - 1; i >= 0; i--) {
1149
- const ch = s[i];
1150
- if (ch === ">") return false;
1151
- if (ch === "<") return true;
1152
- }
1153
- return false;
1154
- }
1155
- function findEnclosingElement(s, pos) {
1156
- const tags = [];
1157
- for (let scan = 0; scan < s.length; ) {
1158
- const lt = s.indexOf("<", scan);
1159
- if (lt < 0) break;
1160
- const next = s[lt + 1];
1161
- if (next === "/") {
1162
- const nm = /^[A-Za-z][A-Za-z0-9]*/.exec(s.slice(lt + 2));
1163
- if (!nm) {
1164
- scan = lt + 1;
1165
- continue;
1166
- }
1167
- const gt = s.indexOf(">", lt + 2 + nm[0].length);
1168
- if (gt < 0) break;
1169
- tags.push({
1170
- isClose: true,
1171
- selfClose: false,
1172
- tagName: nm[0],
1173
- attrs: "",
1174
- start: lt,
1175
- end: gt + 1
1176
- });
1177
- scan = gt + 1;
1178
- continue;
1179
- }
1180
- if (typeof next === "string" && /[A-Za-z]/.test(next)) {
1181
- const ot = readOpeningTag(s, lt);
1182
- if (!ot) {
1183
- scan = lt + 1;
1184
- continue;
1185
- }
1186
- tags.push({
1187
- isClose: false,
1188
- selfClose: ot.selfClose,
1189
- tagName: ot.tag,
1190
- attrs: ot.attrs,
1191
- start: lt,
1192
- end: ot.end
1193
- });
1194
- scan = ot.end;
1195
- continue;
1196
- }
1197
- scan = lt + 1;
1198
- }
1199
- const stack = [];
1200
- let bestOpen = null;
1201
- let bestClose = null;
1202
- for (const t of tags) {
1203
- if (t.selfClose) continue;
1204
- if (t.isClose) {
1205
- for (let i = stack.length - 1; i >= 0; i--) {
1206
- if (stack[i].tagName === t.tagName) {
1207
- const opener = stack[i];
1208
- stack.splice(i, stack.length - i);
1209
- if (opener.end <= pos && pos < t.start) {
1210
- if (!bestOpen || opener.end > bestOpen.end || opener.end === bestOpen.end && t.start < bestClose.start) {
1211
- bestOpen = opener;
1212
- bestClose = t;
1213
- }
1214
- }
1215
- break;
1216
- }
1217
- }
1218
- } else {
1219
- stack.push(t);
1220
- }
1221
- }
1222
- if (!bestOpen || !bestClose) return null;
1223
- return {
1224
- openStart: bestOpen.start,
1225
- openEnd: bestOpen.end,
1226
- openTag: s.slice(bestOpen.start, bestOpen.end),
1227
- attrs: bestOpen.attrs,
1228
- bodyStart: bestOpen.end,
1229
- bodyEnd: bestClose.start
1230
- };
1231
- }
1232
- function isOnlyMeaningfulExpression(body, relStart, relEnd) {
1233
- const before = body.slice(0, relStart);
1234
- const after = body.slice(relEnd);
1235
- return /^\s*$/.test(before) && /^\s*$/.test(after);
1236
- }
1237
-
1238
- // src/vite/index.ts
1239
- function splitId(id) {
1240
- const idx = id.indexOf("?");
1241
- return idx === -1 ? { path: id, query: "" } : { path: id.slice(0, idx), query: id.slice(idx) };
1242
- }
1243
- function isMainAstroModule(id) {
1244
- const { path, query } = splitId(id);
1245
- if (!path.endsWith(".astro")) return false;
1246
- if (query && /[?&]astro([&=]|$)/.test(query)) return false;
1247
- return true;
1248
- }
1249
- function isReactComponentModule(id) {
1250
- const { path, query } = splitId(id);
1251
- if (query) return false;
1252
- if (!/\.(tsx|jsx)$/.test(path)) return false;
1253
- if (/\.(test|spec|stories)\.[tj]sx$/.test(path)) return false;
1254
- return true;
1255
- }
1256
- function studioAutoWrap(opts = {}) {
1257
- const enabled = opts.autoWrap === true;
1258
- const include = opts.include ?? defaultInclude;
1259
- const instrumentTsx = opts.instrumentTsx !== false;
1260
- const autoWrapPages = opts.autoWrapPages === true;
1261
- let projectRoot;
1262
- return {
1263
- name: "@saastro/studio:auto-wrap",
1264
- enforce: "pre",
1265
- config() {
1266
- const config = readVisibilityConfigSync(process.cwd());
1267
- const tag = buildVisibilityStyleTag(config);
1268
- return {
1269
- define: {
1270
- __SAASTRO_STUDIO_HIDDEN_TAG__: JSON.stringify(tag)
1271
- }
1272
- };
1273
- },
1274
- configResolved(config) {
1275
- projectRoot = config.root;
1276
- },
1277
- async load(id) {
1278
- if (!enabled) return null;
1279
- const { path } = splitId(id);
1280
- if (isMainAstroModule(id)) {
1281
- if (!include(path)) return null;
1282
- return loadAstro(path, autoWrapPages);
1283
- }
1284
- if (instrumentTsx && isReactComponentModule(id)) {
1285
- if (!include(path)) return null;
1286
- return loadTsx(path);
1287
- }
1288
- return null;
1289
- },
1290
- transformIndexHtml(html) {
1291
- if (!enabled) return html;
1292
- let next = injectVersionMetaTag(html);
1293
- next = injectVisibilityStyle(next, projectRoot);
1294
- return next;
1295
- }
1296
- };
1297
- }
1298
- var DEFAULT_INCLUDE_EXTS = /\.(astro|tsx|jsx)$/;
1299
- function defaultInclude(id) {
1300
- return DEFAULT_INCLUDE_EXTS.test(id);
1301
- }
1302
- async function loadAstro(path, autoWrapPages) {
1303
- let code;
1304
- try {
1305
- code = await fs.readFile(path, "utf8");
1306
- } catch {
1307
- return null;
1308
- }
1309
- if (!hasFrontmatterFieldPrefix(code)) {
1310
- if (autoWrapPages) {
1311
- const key = resolvePageKey(code, path);
1312
- if (key) {
1313
- const pageWrapped = transformAstroPage(code, key);
1314
- if (pageWrapped !== code) return { code: pageWrapped, map: null };
1315
- }
1316
- }
1317
- return null;
1318
- }
1319
- const wrapped = transformAstroSource(code);
1320
- const schema = extractPropsSchema(code, { componentPath: path });
1321
- let next = wrapped;
1322
- if (schema) {
1323
- next = injectSchema(next, schema);
1324
- next = instrumentFields(next, schema, { componentPath: path });
1325
- }
1326
- if (next === code) return null;
1327
- return { code: next, map: null };
1328
- }
1329
- async function loadTsx(path) {
1330
- let code;
1331
- try {
1332
- code = await fs.readFile(path, "utf8");
1333
- } catch {
1334
- return null;
1335
- }
1336
- if (!code.includes(".map(")) return null;
1337
- if (extractPropsTypeBlock(code) === null) return null;
1338
- const schema = extractComponentSchemaFromTsx(code, { componentPath: path });
1339
- if (!schema || !schema.fields.length) return null;
1340
- const next = instrumentJsxFields(code, schema, { componentPath: path });
1341
- if (next === code) return null;
1342
- return { code: next, map: null };
1343
- }
1344
-
1345
- export {
1346
- studioAutoWrap
1347
- };
1348
- //# sourceMappingURL=chunk-K7DTYPRE.js.map