@saastro/studio 0.1.7 → 0.3.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-D7L6LF5V.js +62 -0
- package/dist/chunk-D7L6LF5V.js.map +1 -0
- package/dist/{chunk-5LJXT5GH.js → chunk-LKNB6JHI.js} +334 -65
- package/dist/chunk-LKNB6JHI.js.map +1 -0
- package/dist/{chunk-OCA3YUNT.js → chunk-OG4676FV.js} +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/integration/index.d.ts +1 -1
- package/dist/integration/index.js +3 -3
- package/dist/middleware/index.js +5 -3
- package/dist/middleware/index.js.map +1 -1
- package/dist/types-DlrT0PDc.d.ts +19 -0
- package/dist/vite/index.d.ts +1 -1
- package/dist/vite/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-3LBSAZCN.js +0 -17
- package/dist/chunk-3LBSAZCN.js.map +0 -1
- package/dist/chunk-5LJXT5GH.js.map +0 -1
- package/dist/types-JUOAAaUW.d.ts +0 -11
- /package/dist/{chunk-OCA3YUNT.js.map → chunk-OG4676FV.js.map} +0 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// src/vite/meta-tag.ts
|
|
2
|
+
var STUDIO_PROTOCOL_VERSION = "2";
|
|
3
|
+
var PACKAGE_VERSION = "0.3.0".length > 0 ? "0.3.0" : "0.0.0+unknown";
|
|
4
|
+
var META_TAGS = [
|
|
5
|
+
`<meta name="saastro:studio-version" content="${STUDIO_PROTOCOL_VERSION}">`,
|
|
6
|
+
`<meta name="saastro:studio-package" content="${PACKAGE_VERSION}">`
|
|
7
|
+
].join("\n ");
|
|
8
|
+
function injectVersionMetaTag(html) {
|
|
9
|
+
if (html.includes("saastro:studio-version")) return html;
|
|
10
|
+
return html.replace(/<head([^>]*)>/i, `<head$1>
|
|
11
|
+
${META_TAGS}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// src/visibility-css.ts
|
|
15
|
+
import { readFileSync } from "fs";
|
|
16
|
+
import { resolve } from "path";
|
|
17
|
+
var CONFIG_FILENAME = "studio.config.json";
|
|
18
|
+
var STYLE_ID = "saastro-studio-hidden";
|
|
19
|
+
function readVisibilityConfig(projectRoot = ".") {
|
|
20
|
+
try {
|
|
21
|
+
const raw = readFileSync(resolve(projectRoot, CONFIG_FILENAME), "utf8");
|
|
22
|
+
if (!raw.trim()) return {};
|
|
23
|
+
const parsed = JSON.parse(raw);
|
|
24
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
25
|
+
return {};
|
|
26
|
+
} catch {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function collectHiddenFieldPrefixes(config) {
|
|
31
|
+
const set = /* @__PURE__ */ new Set();
|
|
32
|
+
for (const prefixes of Object.values(config.hidden ?? {})) {
|
|
33
|
+
if (!Array.isArray(prefixes)) continue;
|
|
34
|
+
for (const p of prefixes) {
|
|
35
|
+
if (typeof p === "string" && p.length > 0) set.add(p);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return Array.from(set).sort();
|
|
39
|
+
}
|
|
40
|
+
function buildVisibilityStyleTag(config) {
|
|
41
|
+
const prefixes = collectHiddenFieldPrefixes(config);
|
|
42
|
+
if (prefixes.length === 0) return "";
|
|
43
|
+
const rules = prefixes.map((fp) => {
|
|
44
|
+
const safe = `sec:${fp}`.replace(/["\\]/g, "\\$&");
|
|
45
|
+
return `[data-saastro="${safe}"]{display:none !important}`;
|
|
46
|
+
}).join("");
|
|
47
|
+
return `<style id="${STYLE_ID}">${rules}</style>`;
|
|
48
|
+
}
|
|
49
|
+
function injectVisibilityStyle(html, projectRoot = ".") {
|
|
50
|
+
if (html.includes(`id="${STYLE_ID}"`)) return html;
|
|
51
|
+
const config = readVisibilityConfig(projectRoot);
|
|
52
|
+
const tag = buildVisibilityStyleTag(config);
|
|
53
|
+
if (!tag) return html;
|
|
54
|
+
return html.replace(/<head([^>]*)>/i, `<head$1>
|
|
55
|
+
${tag}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export {
|
|
59
|
+
injectVersionMetaTag,
|
|
60
|
+
injectVisibilityStyle
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=chunk-D7L6LF5V.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vite/meta-tag.ts","../src/visibility-css.ts"],"sourcesContent":["/**\n * Injects two meta tags into the HTML <head>:\n * - <meta name=\"saastro:studio-version\" content=\"2\"> — protocol version\n * - <meta name=\"saastro:studio-package\" content=\"X.Y.Z\"> — npm package version\n *\n * B8 — spec v5.3 §2 bumps the postMessage protocol number to \"2\". The\n * `studio-version` field reads as the PROTOCOL version (the contract Hub\n * relies on when wiring overlay <-> host messages), not the npm package\n * version of `@saastro/studio`. The package version still ships in a\n * separate `saastro:studio-package` tag for telemetry.\n *\n * The package version comes from a tsup `define` substitution\n * (`__SAASTRO_STUDIO_VERSION__`) injected at build time. The previous\n * implementation used `createRequire(import.meta.url)` to read\n * `package.json` at runtime, but tsup's chunked output places dist\n * files at `dist/chunk-XXX.js` and `../../package.json` resolves\n * OUTSIDE the package — the require always threw and the catch fell\n * back to a hardcoded `'0.1.0'`. Build-time substitution is bullet-\n * proof regardless of how tsup chunks the bundle.\n *\n * This is separate from the auto-wrap transform hook —\n * transformIndexHtml runs on rendered HTML output, not on .astro\n * source files.\n */\n\n/**\n * postMessage protocol version. Bumped to \"2\" in spec v5.3 to add\n * `studio:sections:enumerate` + `studio:select:section` exchanges.\n * Increment when adding/changing the iframe⇄host wire format.\n */\nconst STUDIO_PROTOCOL_VERSION = '2'\n\ndeclare const __SAASTRO_STUDIO_VERSION__: string | undefined\n\nconst PACKAGE_VERSION =\n typeof __SAASTRO_STUDIO_VERSION__ === 'string' && __SAASTRO_STUDIO_VERSION__.length > 0\n ? __SAASTRO_STUDIO_VERSION__\n : '0.0.0+unknown'\n\nconst META_TAGS = [\n `<meta name=\"saastro:studio-version\" content=\"${STUDIO_PROTOCOL_VERSION}\">`,\n `<meta name=\"saastro:studio-package\" content=\"${PACKAGE_VERSION}\">`,\n].join('\\n ')\n\n/**\n * Inject the two studio meta tags into the HTML head. Idempotent —\n * skips injection if the protocol-version tag is already present\n * (matches both the new shape and the legacy single-tag shape, so\n * customer sites with old metadata aren't double-tagged on rebuild).\n */\nexport function injectVersionMetaTag(html: string): string {\n if (html.includes('saastro:studio-version')) return html\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n ${META_TAGS}`)\n}\n","/**\n * Read `studio.config.json` from a project root and build the\n * `<style>` block that hides sections flagged as `hidden`.\n *\n * The config shape is the one written by the Studio Hub at publish time:\n *\n * {\n * \"hidden\": {\n * \"<pageFile>\": [\"<fieldPrefix>\", ...]\n * }\n * }\n *\n * The CSS targets the `data-saastro=\"sec:<fieldPrefix>\"` markers the\n * auto-wrap plugin emits — so hiding is \"exactly the section root,\n * nothing more\". Selectors are flattened across all `pageFile` entries:\n * Studio's `fieldPrefix` naming convention is `<sectionName>.<pageName>`\n * which keeps them unique site-wide, so this loses no precision.\n *\n * Both the Vite `transformIndexHtml` hook (static-build sites) and the\n * Astro middleware (SSR routes) call into the same helpers so the\n * injected CSS is identical regardless of render mode.\n */\n\nimport { readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nconst CONFIG_FILENAME = 'studio.config.json'\nconst STYLE_ID = 'saastro-studio-hidden'\n\ninterface StudioConfig {\n hidden?: Record<string, string[]>\n}\n\n/**\n * Read studio.config.json from `projectRoot`. Returns an empty config\n * if the file is missing or malformed — a publish that hasn't written\n * the config yet behaves like \"nothing hidden\".\n *\n * Defaulting `projectRoot` to `'.'` lets callers omit it when they\n * cannot easily determine the resolved Vite root — `node:path.resolve`\n * resolves relative to the current working directory, which is the\n * project root in every realistic deployment of the middleware.\n */\nexport function readVisibilityConfig(projectRoot: string = '.'): StudioConfig {\n try {\n const raw = readFileSync(resolve(projectRoot, CONFIG_FILENAME), 'utf8')\n if (!raw.trim()) return {}\n const parsed = JSON.parse(raw) as unknown\n if (parsed && typeof parsed === 'object') return parsed as StudioConfig\n return {}\n } catch {\n return {}\n }\n}\n\n/**\n * Collect every fieldPrefix flagged as hidden across all pageFile\n * entries. Deduplicates so a prefix that appears under multiple\n * pageFiles only emits one selector.\n */\nexport function collectHiddenFieldPrefixes(config: StudioConfig): string[] {\n const set = new Set<string>()\n for (const prefixes of Object.values(config.hidden ?? {})) {\n if (!Array.isArray(prefixes)) continue\n for (const p of prefixes) {\n if (typeof p === 'string' && p.length > 0) set.add(p)\n }\n }\n return Array.from(set).sort()\n}\n\n/**\n * Build the `<style>` block. Returns an empty string when there's\n * nothing to hide so callers can no-op without conditional branching.\n */\nexport function buildVisibilityStyleTag(config: StudioConfig): string {\n const prefixes = collectHiddenFieldPrefixes(config)\n if (prefixes.length === 0) return ''\n const rules = prefixes\n .map(fp => {\n // CSS attribute-selector grammar: only `\\` and `\"` need escaping\n // inside `[attr=\"...\"]`. fieldPrefix is conventionally\n // `[a-zA-Z0-9._-]+` so this is belt-and-braces.\n const safe = `sec:${fp}`.replace(/[\"\\\\]/g, '\\\\$&')\n return `[data-saastro=\"${safe}\"]{display:none !important}`\n })\n .join('')\n return `<style id=\"${STYLE_ID}\">${rules}</style>`\n}\n\n/**\n * Inject the visibility `<style>` into an HTML document's `<head>`.\n * Idempotent: a page that already carries a `${STYLE_ID}` style is\n * returned unchanged.\n */\nexport function injectVisibilityStyle(html: string, projectRoot: string = '.'): string {\n if (html.includes(`id=\"${STYLE_ID}\"`)) return html\n const config = readVisibilityConfig(projectRoot)\n const tag = buildVisibilityStyleTag(config)\n if (!tag) return html\n return html.replace(/<head([^>]*)>/i, `<head$1>\\n ${tag}`)\n}\n"],"mappings":";AA8BA,IAAM,0BAA0B;AAIhC,IAAM,kBAC8C,QAA2B,SAAS,IAClF,UACA;AAEN,IAAM,YAAY;AAAA,EAChB,gDAAgD,uBAAuB;AAAA,EACvE,gDAAgD,eAAe;AACjE,EAAE,KAAK,MAAM;AAQN,SAAS,qBAAqB,MAAsB;AACzD,MAAI,KAAK,SAAS,wBAAwB,EAAG,QAAO;AACpD,SAAO,KAAK,QAAQ,kBAAkB;AAAA,IAAe,SAAS,EAAE;AAClE;;;AC9BA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AAExB,IAAM,kBAAkB;AACxB,IAAM,WAAW;AAgBV,SAAS,qBAAqB,cAAsB,KAAmB;AAC5E,MAAI;AACF,UAAM,MAAM,aAAa,QAAQ,aAAa,eAAe,GAAG,MAAM;AACtE,QAAI,CAAC,IAAI,KAAK,EAAG,QAAO,CAAC;AACzB,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,UAAU,OAAO,WAAW,SAAU,QAAO;AACjD,WAAO,CAAC;AAAA,EACV,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAOO,SAAS,2BAA2B,QAAgC;AACzE,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,YAAY,OAAO,OAAO,OAAO,UAAU,CAAC,CAAC,GAAG;AACzD,QAAI,CAAC,MAAM,QAAQ,QAAQ,EAAG;AAC9B,eAAW,KAAK,UAAU;AACxB,UAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAG,KAAI,IAAI,CAAC;AAAA,IACtD;AAAA,EACF;AACA,SAAO,MAAM,KAAK,GAAG,EAAE,KAAK;AAC9B;AAMO,SAAS,wBAAwB,QAA8B;AACpE,QAAM,WAAW,2BAA2B,MAAM;AAClD,MAAI,SAAS,WAAW,EAAG,QAAO;AAClC,QAAM,QAAQ,SACX,IAAI,QAAM;AAIT,UAAM,OAAO,OAAO,EAAE,GAAG,QAAQ,UAAU,MAAM;AACjD,WAAO,kBAAkB,IAAI;AAAA,EAC/B,CAAC,EACA,KAAK,EAAE;AACV,SAAO,cAAc,QAAQ,KAAK,KAAK;AACzC;AAOO,SAAS,sBAAsB,MAAc,cAAsB,KAAa;AACrF,MAAI,KAAK,SAAS,OAAO,QAAQ,GAAG,EAAG,QAAO;AAC9C,QAAM,SAAS,qBAAqB,WAAW;AAC/C,QAAM,MAAM,wBAAwB,MAAM;AAC1C,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,KAAK,QAAQ,kBAAkB;AAAA,IAAe,GAAG,EAAE;AAC5D;","names":[]}
|
|
@@ -1,33 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
|
-
injectVersionMetaTag
|
|
3
|
-
|
|
2
|
+
injectVersionMetaTag,
|
|
3
|
+
injectVisibilityStyle
|
|
4
|
+
} from "./chunk-D7L6LF5V.js";
|
|
4
5
|
|
|
5
6
|
// src/vite/index.ts
|
|
6
7
|
import { promises as fs } from "fs";
|
|
7
8
|
|
|
8
|
-
// src/vite/auto-wrap.ts
|
|
9
|
-
var FRONTMATTER_RE = /^\s*---\s*\n([\s\S]*?)\n---\s*\n/;
|
|
10
|
-
var SAASTRO_ATTR_RE = /data-saastro/;
|
|
11
|
-
var FIELD_PREFIX_REFERENCE_RE = /\bfieldPrefix\b/;
|
|
12
|
-
var FIRST_OPEN_TAG_RE = /<([a-zA-Z][a-zA-Z0-9]*)\b([^<>]*?)\s*(\/?)>/;
|
|
13
|
-
var INJECTION = ' {...(fieldPrefix ? { "data-saastro": `sec:${fieldPrefix}`, draggable: "true" } : {})}';
|
|
14
|
-
function transformAstroSource(source) {
|
|
15
|
-
const fm = FRONTMATTER_RE.exec(source);
|
|
16
|
-
if (!fm) return source;
|
|
17
|
-
if (!FIELD_PREFIX_REFERENCE_RE.test(fm[1])) return source;
|
|
18
|
-
const template = source.slice(fm[0].length);
|
|
19
|
-
const tagMatch = FIRST_OPEN_TAG_RE.exec(template);
|
|
20
|
-
if (!tagMatch) return source;
|
|
21
|
-
const tagName = tagMatch[1];
|
|
22
|
-
if (tagName[0] !== tagName[0].toLowerCase()) return source;
|
|
23
|
-
const existingAttrs = tagMatch[2];
|
|
24
|
-
if (SAASTRO_ATTR_RE.test(existingAttrs)) return source;
|
|
25
|
-
const selfClose = tagMatch[3];
|
|
26
|
-
const replacement = `<${tagName}${existingAttrs}${INJECTION}${selfClose ? " " + selfClose : ""}>`;
|
|
27
|
-
const templateInjected = template.slice(0, tagMatch.index) + replacement + template.slice(tagMatch.index + tagMatch[0].length);
|
|
28
|
-
return source.slice(0, fm[0].length) + templateInjected;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
9
|
// ../studio-core/dist/component-schema/index.js
|
|
32
10
|
import {
|
|
33
11
|
Project
|
|
@@ -47,11 +25,135 @@ var STUDIO_DIRECTIVE_KINDS = /* @__PURE__ */ new Set([
|
|
|
47
25
|
var STUDIO_DIRECTIVE_RE = /@studio:([A-Za-z][A-Za-z0-9-]*)/g;
|
|
48
26
|
var IMAGE_NAME_HINTS = /(^|_)(image|img|avatar|logo|icon|thumbnail|photo|picture|cover|background)($|_)/i;
|
|
49
27
|
var IMAGE_SUFFIX_HINTS = /_(url|src)$/i;
|
|
50
|
-
var
|
|
28
|
+
var FRONTMATTER_RE = /^\s*---\s*\n([\s\S]*?)\n---/;
|
|
51
29
|
function extractFrontmatter(astroSource) {
|
|
52
|
-
const m =
|
|
30
|
+
const m = FRONTMATTER_RE.exec(astroSource);
|
|
53
31
|
return m ? m[1] ?? "" : null;
|
|
54
32
|
}
|
|
33
|
+
function extractPropsTypeBlock(tsxSource) {
|
|
34
|
+
const interfaceRe = /\b(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)\s*\{/g;
|
|
35
|
+
const typeRe = /\b(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=\s*\{/g;
|
|
36
|
+
let match;
|
|
37
|
+
while ((match = interfaceRe.exec(tsxSource)) !== null) {
|
|
38
|
+
const name = match[1];
|
|
39
|
+
if (name === "Props" || name.endsWith("Props")) {
|
|
40
|
+
const braceOpenIdx = tsxSource.indexOf("{", match.index);
|
|
41
|
+
if (braceOpenIdx === -1) continue;
|
|
42
|
+
const close = readBalancedBrace(tsxSource, braceOpenIdx);
|
|
43
|
+
if (close === null) continue;
|
|
44
|
+
const block = tsxSource.slice(match.index, close + 1);
|
|
45
|
+
return normalizeInterfaceName(block, name);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
while ((match = typeRe.exec(tsxSource)) !== null) {
|
|
49
|
+
const name = match[1];
|
|
50
|
+
if (name === "Props" || name.endsWith("Props")) {
|
|
51
|
+
const braceOpenIdx = tsxSource.indexOf("{", match.index);
|
|
52
|
+
if (braceOpenIdx === -1) continue;
|
|
53
|
+
const close = readBalancedBrace(tsxSource, braceOpenIdx);
|
|
54
|
+
if (close === null) continue;
|
|
55
|
+
const block = tsxSource.slice(match.index, close + 1);
|
|
56
|
+
return normalizeTypeAliasName(block, name);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function normalizeInterfaceName(block, name) {
|
|
62
|
+
if (name === "Props") return block;
|
|
63
|
+
return block.replace(/^(\s*(?:export\s+)?interface\s+)[A-Za-z_$][\w$]*/, "$1Props");
|
|
64
|
+
}
|
|
65
|
+
function normalizeTypeAliasName(block, name) {
|
|
66
|
+
if (name === "Props") return block;
|
|
67
|
+
return block.replace(/^(\s*(?:export\s+)?type\s+)[A-Za-z_$][\w$]*/, "$1Props");
|
|
68
|
+
}
|
|
69
|
+
function readBalancedBrace(s, open) {
|
|
70
|
+
if (s[open] !== "{") return null;
|
|
71
|
+
let depth = 0;
|
|
72
|
+
let i = open;
|
|
73
|
+
while (i < s.length) {
|
|
74
|
+
const ch = s[i];
|
|
75
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
76
|
+
i = skipStringLiteral(s, i) + 1;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (ch === "/" && s[i + 1] === "/") {
|
|
80
|
+
const eol = s.indexOf("\n", i + 2);
|
|
81
|
+
i = eol === -1 ? s.length : eol + 1;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (ch === "/" && s[i + 1] === "*") {
|
|
85
|
+
const end = s.indexOf("*/", i + 2);
|
|
86
|
+
i = end === -1 ? s.length : end + 2;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (ch === "{") depth++;
|
|
90
|
+
else if (ch === "}") {
|
|
91
|
+
depth--;
|
|
92
|
+
if (depth === 0) return i;
|
|
93
|
+
}
|
|
94
|
+
i++;
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
function skipStringLiteral(s, start) {
|
|
99
|
+
const quote = s[start];
|
|
100
|
+
let i = start + 1;
|
|
101
|
+
while (i < s.length) {
|
|
102
|
+
const ch = s[i];
|
|
103
|
+
if (ch === "\\") {
|
|
104
|
+
i += 2;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (quote === "`" && ch === "$" && s[i + 1] === "{") {
|
|
108
|
+
const close = readBalancedBrace(s, i + 1);
|
|
109
|
+
if (close === null) return s.length - 1;
|
|
110
|
+
i = close + 1;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (ch === quote) return i;
|
|
114
|
+
i++;
|
|
115
|
+
}
|
|
116
|
+
return s.length - 1;
|
|
117
|
+
}
|
|
118
|
+
function extractAllTypeBlocks(tsxSource) {
|
|
119
|
+
const blocks = [];
|
|
120
|
+
const seen = /* @__PURE__ */ new Set();
|
|
121
|
+
const decl = /\b(?:export\s+)?(?:interface|type)\s+[A-Za-z_$][\w$]*/g;
|
|
122
|
+
let m;
|
|
123
|
+
while ((m = decl.exec(tsxSource)) !== null) {
|
|
124
|
+
const start = m.index;
|
|
125
|
+
if (seen.has(start)) continue;
|
|
126
|
+
seen.add(start);
|
|
127
|
+
const isType = /\btype\b/.test(m[0]);
|
|
128
|
+
const braceOpen = tsxSource.indexOf("{", start);
|
|
129
|
+
if (braceOpen < 0) continue;
|
|
130
|
+
const between = tsxSource.slice(start + m[0].length, braceOpen);
|
|
131
|
+
if (!/^[\s=]*(?:extends\s+[^{]+)?$/.test(between)) continue;
|
|
132
|
+
const close = readBalancedBrace(tsxSource, braceOpen);
|
|
133
|
+
if (close === null) continue;
|
|
134
|
+
let block = tsxSource.slice(start, close + 1);
|
|
135
|
+
if (isType) {
|
|
136
|
+
if (!block.trimEnd().endsWith(";")) block = block + ";";
|
|
137
|
+
}
|
|
138
|
+
blocks.push(block);
|
|
139
|
+
decl.lastIndex = close + 1;
|
|
140
|
+
}
|
|
141
|
+
return blocks;
|
|
142
|
+
}
|
|
143
|
+
function extractComponentSchemaFromTsx(tsxSource, options = {}) {
|
|
144
|
+
const propsBlock = extractPropsTypeBlock(tsxSource);
|
|
145
|
+
if (!propsBlock) return null;
|
|
146
|
+
const allBlocks = extractAllTypeBlocks(tsxSource);
|
|
147
|
+
const siblings = allBlocks.filter((b) => !looksLikeProps(b));
|
|
148
|
+
const synthetic = `---
|
|
149
|
+
` + [...siblings, propsBlock].join("\n\n") + `
|
|
150
|
+
---
|
|
151
|
+
`;
|
|
152
|
+
return extractComponentSchema(synthetic, options);
|
|
153
|
+
}
|
|
154
|
+
function looksLikeProps(block) {
|
|
155
|
+
return /^\s*(?:export\s+)?(?:interface|type)\s+(?:Props\b|[A-Za-z_$][\w$]*Props\b)/.test(block);
|
|
156
|
+
}
|
|
55
157
|
function extractComponentSchema(astroSource, options = {}) {
|
|
56
158
|
const frontmatter = extractFrontmatter(astroSource);
|
|
57
159
|
if (frontmatter == null) return null;
|
|
@@ -385,6 +487,29 @@ function parseLiteral(rhs) {
|
|
|
385
487
|
return trimmed;
|
|
386
488
|
}
|
|
387
489
|
|
|
490
|
+
// src/vite/auto-wrap.ts
|
|
491
|
+
var FRONTMATTER_RE2 = /^\s*---\s*\n([\s\S]*?)\n---\s*\n/;
|
|
492
|
+
var SAASTRO_ATTR_RE = /data-saastro/;
|
|
493
|
+
var FIELD_PREFIX_REFERENCE_RE = /\bfieldPrefix\b/;
|
|
494
|
+
var FIRST_OPEN_TAG_RE = /<([a-zA-Z][a-zA-Z0-9]*)\b([^<>]*?)\s*(\/?)>/;
|
|
495
|
+
var INJECTION = ' {...(fieldPrefix ? { "data-saastro": `sec:${fieldPrefix}`, draggable: "true" } : {})}';
|
|
496
|
+
function transformAstroSource(source) {
|
|
497
|
+
const fm = FRONTMATTER_RE2.exec(source);
|
|
498
|
+
if (!fm) return source;
|
|
499
|
+
if (!FIELD_PREFIX_REFERENCE_RE.test(fm[1])) return source;
|
|
500
|
+
const template = source.slice(fm[0].length);
|
|
501
|
+
const tagMatch = FIRST_OPEN_TAG_RE.exec(template);
|
|
502
|
+
if (!tagMatch) return source;
|
|
503
|
+
const tagName = tagMatch[1];
|
|
504
|
+
if (tagName[0] !== tagName[0].toLowerCase()) return source;
|
|
505
|
+
const existingAttrs = tagMatch[2];
|
|
506
|
+
if (SAASTRO_ATTR_RE.test(existingAttrs)) return source;
|
|
507
|
+
const selfClose = tagMatch[3];
|
|
508
|
+
const replacement = `<${tagName}${existingAttrs}${INJECTION}${selfClose ? " " + selfClose : ""}>`;
|
|
509
|
+
const templateInjected = template.slice(0, tagMatch.index) + replacement + template.slice(tagMatch.index + tagMatch[0].length);
|
|
510
|
+
return source.slice(0, fm[0].length) + templateInjected;
|
|
511
|
+
}
|
|
512
|
+
|
|
388
513
|
// src/vite/extract-props-schema.ts
|
|
389
514
|
var CACHE_MAX = 500;
|
|
390
515
|
var cache = /* @__PURE__ */ new Map();
|
|
@@ -502,6 +627,11 @@ function instrumentFields(source, schema, _options = {}) {
|
|
|
502
627
|
const transformed = transformTemplate(head, schema.fields);
|
|
503
628
|
return source.slice(0, templateStart) + transformed + tail;
|
|
504
629
|
}
|
|
630
|
+
function instrumentJsxFields(source, schema, _options = {}) {
|
|
631
|
+
if (!schema.fields.length) return source;
|
|
632
|
+
if (!/<[A-Za-z]/.test(source)) return source;
|
|
633
|
+
return transformTemplate(source, schema.fields);
|
|
634
|
+
}
|
|
505
635
|
function transformTemplate(template, fields) {
|
|
506
636
|
let out = template;
|
|
507
637
|
for (const field of fields) {
|
|
@@ -761,14 +891,81 @@ function stampArrayItem(jsx, fieldName, paramName, subFields) {
|
|
|
761
891
|
return out;
|
|
762
892
|
}
|
|
763
893
|
function stampOuterElement(jsx, valueExpr) {
|
|
764
|
-
const
|
|
765
|
-
if (
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const
|
|
770
|
-
const
|
|
771
|
-
return jsx.slice(0,
|
|
894
|
+
const start = jsx.search(/<[A-Za-z]/);
|
|
895
|
+
if (start < 0) return jsx;
|
|
896
|
+
const tagInfo = readOpeningTag(jsx, start);
|
|
897
|
+
if (!tagInfo) return jsx;
|
|
898
|
+
if (/data-saastro-field/.test(tagInfo.attrs)) return jsx;
|
|
899
|
+
const inject = ` data-saastro-field=${valueExpr}`;
|
|
900
|
+
const insertAt = start + 1 + tagInfo.tag.length;
|
|
901
|
+
return jsx.slice(0, insertAt) + inject + jsx.slice(insertAt);
|
|
902
|
+
}
|
|
903
|
+
function readOpeningTag(s, start) {
|
|
904
|
+
if (s[start] !== "<") return null;
|
|
905
|
+
const nameMatch = /^[A-Za-z][A-Za-z0-9]*/.exec(s.slice(start + 1));
|
|
906
|
+
if (!nameMatch) return null;
|
|
907
|
+
const tag = nameMatch[0];
|
|
908
|
+
let i = start + 1 + tag.length;
|
|
909
|
+
const attrsStart = i;
|
|
910
|
+
let inString = null;
|
|
911
|
+
let braceDepth = 0;
|
|
912
|
+
while (i < s.length) {
|
|
913
|
+
const ch = s[i];
|
|
914
|
+
if (inString) {
|
|
915
|
+
if (ch === "\\") {
|
|
916
|
+
i += 2;
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
if (inString === "`" && ch === "$" && s[i + 1] === "{") {
|
|
920
|
+
const close = readBalanced(s, i + 1, "{", "}");
|
|
921
|
+
if (close == null) return null;
|
|
922
|
+
i = close + 1;
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
if (ch === inString) inString = null;
|
|
926
|
+
i++;
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
if (braceDepth > 0) {
|
|
930
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
931
|
+
inString = ch;
|
|
932
|
+
i++;
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
if (ch === "{") braceDepth++;
|
|
936
|
+
else if (ch === "}") braceDepth--;
|
|
937
|
+
i++;
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
941
|
+
inString = ch;
|
|
942
|
+
i++;
|
|
943
|
+
continue;
|
|
944
|
+
}
|
|
945
|
+
if (ch === "{") {
|
|
946
|
+
braceDepth = 1;
|
|
947
|
+
i++;
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
if (ch === "/" && s[i + 1] === ">") {
|
|
951
|
+
return {
|
|
952
|
+
tag,
|
|
953
|
+
attrs: s.slice(attrsStart, i),
|
|
954
|
+
end: i + 2,
|
|
955
|
+
selfClose: true
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
if (ch === ">") {
|
|
959
|
+
return {
|
|
960
|
+
tag,
|
|
961
|
+
attrs: s.slice(attrsStart, i),
|
|
962
|
+
end: i + 1,
|
|
963
|
+
selfClose: false
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
i++;
|
|
967
|
+
}
|
|
968
|
+
return null;
|
|
772
969
|
}
|
|
773
970
|
function transformScalarField(template, name) {
|
|
774
971
|
return transformDottedReference(template, name, null, JSON.stringify(name));
|
|
@@ -873,18 +1070,48 @@ function isInsideTag(s, pos) {
|
|
|
873
1070
|
return false;
|
|
874
1071
|
}
|
|
875
1072
|
function findEnclosingElement(s, pos) {
|
|
876
|
-
const tagRe = /<(\/?)([A-Za-z][A-Za-z0-9]*)\b([^<>]*?)(\/?)>/g;
|
|
877
1073
|
const tags = [];
|
|
878
|
-
let
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1074
|
+
for (let scan = 0; scan < s.length; ) {
|
|
1075
|
+
const lt = s.indexOf("<", scan);
|
|
1076
|
+
if (lt < 0) break;
|
|
1077
|
+
const next = s[lt + 1];
|
|
1078
|
+
if (next === "/") {
|
|
1079
|
+
const nm = /^[A-Za-z][A-Za-z0-9]*/.exec(s.slice(lt + 2));
|
|
1080
|
+
if (!nm) {
|
|
1081
|
+
scan = lt + 1;
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
const gt = s.indexOf(">", lt + 2 + nm[0].length);
|
|
1085
|
+
if (gt < 0) break;
|
|
1086
|
+
tags.push({
|
|
1087
|
+
isClose: true,
|
|
1088
|
+
selfClose: false,
|
|
1089
|
+
tagName: nm[0],
|
|
1090
|
+
attrs: "",
|
|
1091
|
+
start: lt,
|
|
1092
|
+
end: gt + 1
|
|
1093
|
+
});
|
|
1094
|
+
scan = gt + 1;
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
if (typeof next === "string" && /[A-Za-z]/.test(next)) {
|
|
1098
|
+
const ot = readOpeningTag(s, lt);
|
|
1099
|
+
if (!ot) {
|
|
1100
|
+
scan = lt + 1;
|
|
1101
|
+
continue;
|
|
1102
|
+
}
|
|
1103
|
+
tags.push({
|
|
1104
|
+
isClose: false,
|
|
1105
|
+
selfClose: ot.selfClose,
|
|
1106
|
+
tagName: ot.tag,
|
|
1107
|
+
attrs: ot.attrs,
|
|
1108
|
+
start: lt,
|
|
1109
|
+
end: ot.end
|
|
1110
|
+
});
|
|
1111
|
+
scan = ot.end;
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
scan = lt + 1;
|
|
888
1115
|
}
|
|
889
1116
|
const stack = [];
|
|
890
1117
|
let bestOpen = null;
|
|
@@ -940,42 +1167,84 @@ function isMainAstroModule(id) {
|
|
|
940
1167
|
if (query && /[?&]astro([&=]|$)/.test(query)) return false;
|
|
941
1168
|
return true;
|
|
942
1169
|
}
|
|
1170
|
+
function isReactComponentModule(id) {
|
|
1171
|
+
const { path, query } = splitId(id);
|
|
1172
|
+
if (query) return false;
|
|
1173
|
+
if (!/\.(tsx|jsx)$/.test(path)) return false;
|
|
1174
|
+
if (/\.(test|spec|stories)\.[tj]sx$/.test(path)) return false;
|
|
1175
|
+
return true;
|
|
1176
|
+
}
|
|
943
1177
|
function studioAutoWrap(opts = {}) {
|
|
944
1178
|
const enabled = opts.autoWrap === true;
|
|
945
|
-
const include = opts.include ??
|
|
1179
|
+
const include = opts.include ?? defaultInclude;
|
|
1180
|
+
const instrumentTsx = opts.instrumentTsx !== false;
|
|
1181
|
+
let projectRoot;
|
|
946
1182
|
return {
|
|
947
1183
|
name: "@saastro/studio:auto-wrap",
|
|
948
1184
|
enforce: "pre",
|
|
1185
|
+
configResolved(config) {
|
|
1186
|
+
projectRoot = config.root;
|
|
1187
|
+
},
|
|
949
1188
|
async load(id) {
|
|
950
1189
|
if (!enabled) return null;
|
|
951
|
-
if (!isMainAstroModule(id)) return null;
|
|
952
1190
|
const { path } = splitId(id);
|
|
953
|
-
if (
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
code = await fs.readFile(path, "utf8");
|
|
957
|
-
} catch {
|
|
958
|
-
return null;
|
|
1191
|
+
if (isMainAstroModule(id)) {
|
|
1192
|
+
if (!include(path)) return null;
|
|
1193
|
+
return loadAstro(path);
|
|
959
1194
|
}
|
|
960
|
-
if (
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
let next = wrapped;
|
|
964
|
-
if (schema) {
|
|
965
|
-
next = injectSchema(next, schema);
|
|
966
|
-
next = instrumentFields(next, schema, { componentPath: path });
|
|
1195
|
+
if (instrumentTsx && isReactComponentModule(id)) {
|
|
1196
|
+
if (!include(path)) return null;
|
|
1197
|
+
return loadTsx(path);
|
|
967
1198
|
}
|
|
968
|
-
|
|
969
|
-
return { code: next, map: null };
|
|
1199
|
+
return null;
|
|
970
1200
|
},
|
|
971
1201
|
transformIndexHtml(html) {
|
|
972
1202
|
if (!enabled) return html;
|
|
973
|
-
|
|
1203
|
+
let next = injectVersionMetaTag(html);
|
|
1204
|
+
next = injectVisibilityStyle(next, projectRoot);
|
|
1205
|
+
return next;
|
|
974
1206
|
}
|
|
975
1207
|
};
|
|
976
1208
|
}
|
|
1209
|
+
var DEFAULT_INCLUDE_EXTS = /\.(astro|tsx|jsx)$/;
|
|
1210
|
+
function defaultInclude(id) {
|
|
1211
|
+
return DEFAULT_INCLUDE_EXTS.test(id);
|
|
1212
|
+
}
|
|
1213
|
+
async function loadAstro(path) {
|
|
1214
|
+
let code;
|
|
1215
|
+
try {
|
|
1216
|
+
code = await fs.readFile(path, "utf8");
|
|
1217
|
+
} catch {
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
if (!hasFieldPrefixReference(code)) return null;
|
|
1221
|
+
const wrapped = transformAstroSource(code);
|
|
1222
|
+
const schema = extractPropsSchema(code, { componentPath: path });
|
|
1223
|
+
let next = wrapped;
|
|
1224
|
+
if (schema) {
|
|
1225
|
+
next = injectSchema(next, schema);
|
|
1226
|
+
next = instrumentFields(next, schema, { componentPath: path });
|
|
1227
|
+
}
|
|
1228
|
+
if (next === code) return null;
|
|
1229
|
+
return { code: next, map: null };
|
|
1230
|
+
}
|
|
1231
|
+
async function loadTsx(path) {
|
|
1232
|
+
let code;
|
|
1233
|
+
try {
|
|
1234
|
+
code = await fs.readFile(path, "utf8");
|
|
1235
|
+
} catch {
|
|
1236
|
+
return null;
|
|
1237
|
+
}
|
|
1238
|
+
if (!code.includes(".map(")) return null;
|
|
1239
|
+
if (extractPropsTypeBlock(code) === null) return null;
|
|
1240
|
+
const schema = extractComponentSchemaFromTsx(code, { componentPath: path });
|
|
1241
|
+
if (!schema || !schema.fields.length) return null;
|
|
1242
|
+
const next = instrumentJsxFields(code, schema, { componentPath: path });
|
|
1243
|
+
if (next === code) return null;
|
|
1244
|
+
return { code: next, map: null };
|
|
1245
|
+
}
|
|
977
1246
|
|
|
978
1247
|
export {
|
|
979
1248
|
studioAutoWrap
|
|
980
1249
|
};
|
|
981
|
-
//# sourceMappingURL=chunk-
|
|
1250
|
+
//# sourceMappingURL=chunk-LKNB6JHI.js.map
|