isotone 1.1.1 → 1.1.3
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/AGENTS.md +5 -1
- package/CHANGELOG.md +16 -0
- package/README.md +3 -2
- package/dist/chunk-BDTB3DFR.js +100 -0
- package/dist/chunk-BDTB3DFR.js.map +1 -0
- package/dist/{chunk-TGAU3WTF.js → chunk-BRBXIGZP.js} +56 -12
- package/dist/chunk-BRBXIGZP.js.map +1 -0
- package/dist/{chunk-FKEKBZ3E.js → chunk-K5RAC2BT.js} +94 -6
- package/dist/chunk-K5RAC2BT.js.map +1 -0
- package/dist/{chunk-X6V2AKJL.js → chunk-LXW6WFOC.js} +33 -6
- package/dist/chunk-LXW6WFOC.js.map +1 -0
- package/dist/{chunk-PUXQGUMQ.js → chunk-RKZWO37H.js} +20 -5
- package/dist/chunk-RKZWO37H.js.map +1 -0
- package/dist/{chunk-DTAWRQCP.js → chunk-YTEUMO3O.js} +2 -2
- package/dist/{chunk-DTAWRQCP.js.map → chunk-YTEUMO3O.js.map} +1 -1
- package/dist/cli.js +46 -7
- package/dist/cli.js.map +1 -1
- package/dist/core.d.ts +44 -9
- package/dist/core.js +4 -2
- package/dist/index.js +49 -41
- package/dist/index.js.map +1 -1
- package/dist/isotone.global.js +20 -20
- package/dist/serve.js +2 -2
- package/dist/server.js +3 -3
- package/dist/vite-plugin.js +29 -113
- package/dist/vite-plugin.js.map +1 -1
- package/docs/variants.md +37 -2
- package/package.json +1 -1
- package/dist/chunk-FKEKBZ3E.js.map +0 -1
- package/dist/chunk-PUXQGUMQ.js.map +0 -1
- package/dist/chunk-TGAU3WTF.js.map +0 -1
- package/dist/chunk-X6V2AKJL.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -20,7 +20,8 @@ reviewer's panel until you run `npx isotone --done <id>`.
|
|
|
20
20
|
Run `npx isotone --json` in the project under review. When several agents share a
|
|
21
21
|
dev server, list only your page's notes with `npx isotone <route> --json`, and run
|
|
22
22
|
`npx isotone --working <id>` on a note when you start it so the reviewer sees it in progress. Each note includes a route,
|
|
23
|
-
viewport, pinned elements
|
|
23
|
+
viewport, pinned elements (a selector matching only that element, plus its React
|
|
24
|
+
components, labelled ancestors, attributes and page position), optional variant or host context, and a `doneCommand`
|
|
24
25
|
for open notes. Reproduce the request at that route and viewport, make the source
|
|
25
26
|
change, verify it, then run the note's `doneCommand`. A unique ID prefix works.
|
|
26
27
|
Use `--all` or `--days 30` when the note is not in the default listing.
|
|
@@ -42,6 +43,9 @@ npx isotone variant path <id> <file>
|
|
|
42
43
|
Edit the copied file. `--new` starts a file the project does not have yet.
|
|
43
44
|
Where you can, copy only files that export components. A copied file that
|
|
44
45
|
exports a hook, helper or constant object makes every switch reload the page.
|
|
46
|
+
Describe a variant with `variant new --description`, not a comment in the copy,
|
|
47
|
+
because `promote` lands the copy as it stands. `variant add` warns when a file
|
|
48
|
+
will reload on every switch or when another group already overrides it.
|
|
45
49
|
Use `--group` for alternatives of the same decision and scope them to the
|
|
46
50
|
note's route. `variant use <id>` previews one; promote it only
|
|
47
51
|
when the reviewer chooses it. See [variants](docs/variants.md) for stale-file
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.1.3
|
|
6
|
+
|
|
7
|
+
- fix: a failed variant switch shows the server's reason, naming the variants, their groups and the shared file
|
|
8
|
+
- fix: deleting an active variant's folder by hand no longer makes every later switch fail with a 404
|
|
9
|
+
- fix: `variant sync` keeps working on files git does not track, where a second sync used to fail and leave the file stale
|
|
10
|
+
- fix: `variant promote` leaves the other variants in its group current
|
|
11
|
+
- fix: adding a file to or removing one from a variant now reaches server-rendered modules, which caused stale HTML and hydration mismatches, and reaches whole-file switching too
|
|
12
|
+
- fix: pinning an element could highlight a different one and move the composer to it, because the saved path also matched earlier elements. A path now matches only the pinned element
|
|
13
|
+
- feat: pinned elements carry their React components, labelled or headed ancestors, attributes and page position, so an agent can tell which element a note means
|
|
14
|
+
- feat: select knobs, which also set `data-knob-<id>` on `<html>`, and an `isotone:knob` event for code that draws for itself
|
|
15
|
+
- feat: `variant add` warns about exports that force a reload and about files another group already overrides
|
|
16
|
+
|
|
17
|
+
## 1.1.2
|
|
18
|
+
|
|
19
|
+
- feat: the shortcut opens feedback in select mode, and toggles select mode once it's open. Escape closes the panel; the shortcut no longer does
|
|
20
|
+
|
|
5
21
|
## 1.1.1
|
|
6
22
|
|
|
7
23
|
- feat: open feedback in select mode, and write the note next to the pinned element
|
package/README.md
CHANGED
|
@@ -68,8 +68,9 @@ required. Coming from the previous package? See the [migration guide](docs/migra
|
|
|
68
68
|
## Keys
|
|
69
69
|
|
|
70
70
|
**Enter** sends a note and **Shift + Enter** adds a newline. Click a saved note
|
|
71
|
-
to edit it. Opening Feedback with the mouse
|
|
72
|
-
|
|
71
|
+
to edit it. Opening Feedback with the mouse or **⌘/Ctrl + Shift + .** starts
|
|
72
|
+
select mode. With Feedback open, the same shortcut starts select mode again, or
|
|
73
|
+
finishes it if it's on. In select mode, click to pin or unpin, hold
|
|
73
74
|
**Shift** to use the page normally, and press **Enter** to finish or **Escape**
|
|
74
75
|
to clear what you pinned. After a pin, the note field opens next to the element
|
|
75
76
|
you clicked, and you can start typing right away. Once you're done selecting,
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// src/vite/exports.ts
|
|
2
|
+
var ROUTE_EXPORTS = /* @__PURE__ */ new Set([
|
|
3
|
+
"loader",
|
|
4
|
+
"action",
|
|
5
|
+
"clientLoader",
|
|
6
|
+
"clientAction",
|
|
7
|
+
"headers",
|
|
8
|
+
"links",
|
|
9
|
+
"meta",
|
|
10
|
+
"handle",
|
|
11
|
+
"shouldRevalidate",
|
|
12
|
+
"middleware",
|
|
13
|
+
"clientMiddleware",
|
|
14
|
+
"unstable_middleware",
|
|
15
|
+
"unstable_clientMiddleware",
|
|
16
|
+
"HydrateFallback"
|
|
17
|
+
]);
|
|
18
|
+
var IDENT = /^[A-Za-z_$][\w$]*$/;
|
|
19
|
+
function scanExports(code) {
|
|
20
|
+
const names = /* @__PURE__ */ new Set();
|
|
21
|
+
const lines = code.split("\n");
|
|
22
|
+
for (let i = 0; i < lines.length; i++) {
|
|
23
|
+
const line = (lines[i] ?? "").replace(/^(?:\/\*.*?\*\/\s*)+/, "");
|
|
24
|
+
if (!line.startsWith("export")) continue;
|
|
25
|
+
const rest = line.slice("export".length);
|
|
26
|
+
if (rest !== "" && !/^[\s{*]/.test(rest)) continue;
|
|
27
|
+
const body = rest.trim();
|
|
28
|
+
if (body.startsWith("{") || body.startsWith("type {")) {
|
|
29
|
+
if (body.startsWith("type")) continue;
|
|
30
|
+
let clause = body;
|
|
31
|
+
while (!clause.includes("}") && i + 1 < lines.length) clause += ` ${lines[++i]}`;
|
|
32
|
+
const inner = clause.slice(clause.indexOf("{") + 1, clause.indexOf("}"));
|
|
33
|
+
for (const raw of inner.split(",")) {
|
|
34
|
+
const spec = raw.replace(/\/\/.*$/, "").trim();
|
|
35
|
+
if (!spec || spec.startsWith("type ")) continue;
|
|
36
|
+
const as = spec.split(/\s+as\s+/);
|
|
37
|
+
const name = (as[1] ?? as[0] ?? "").trim().replace(/^["']|["']$/g, "");
|
|
38
|
+
if (!name) return null;
|
|
39
|
+
names.add(name);
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (/^type\s*\*/.test(body)) continue;
|
|
44
|
+
const star = body.match(/^\*\s+as\s+([A-Za-z_$][\w$]*)/);
|
|
45
|
+
if (star?.[1]) {
|
|
46
|
+
names.add(star[1]);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (body.startsWith("*")) return null;
|
|
50
|
+
if (/^default\b/.test(body)) {
|
|
51
|
+
names.add("default");
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (/^(?:declare\s|type\s|interface\s)/.test(body)) continue;
|
|
55
|
+
if (/^(?:=|import\s|namespace\s|module\s|as\s)/.test(body)) return null;
|
|
56
|
+
const decl = body.match(
|
|
57
|
+
/^(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)|^(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)|^(?:const\s+)?enum\s+([A-Za-z_$][\w$]*)/
|
|
58
|
+
);
|
|
59
|
+
const declared = decl?.[1] ?? decl?.[2] ?? decl?.[3];
|
|
60
|
+
if (declared) {
|
|
61
|
+
names.add(declared);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const variable = body.match(/^(?:const|let|var)\s+(.*)$/);
|
|
65
|
+
if (variable) {
|
|
66
|
+
const target = (variable[1] ?? "").trim();
|
|
67
|
+
if (target.startsWith("{") || target.startsWith("[")) return null;
|
|
68
|
+
const name = target.match(/^([A-Za-z_$][\w$]*)/)?.[1];
|
|
69
|
+
if (!name) return null;
|
|
70
|
+
if (hasSecondDeclarator(target)) return null;
|
|
71
|
+
names.add(name);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
for (const name of names) if (name !== "default" && !IDENT.test(name)) return null;
|
|
77
|
+
return [...names];
|
|
78
|
+
}
|
|
79
|
+
function isRouteModule(names) {
|
|
80
|
+
return names.includes("default") && names.some((name) => ROUTE_EXPORTS.has(name));
|
|
81
|
+
}
|
|
82
|
+
function hasSecondDeclarator(declaration) {
|
|
83
|
+
const text = declaration.replace(/\/\/.*$/, "").replace(/'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*`/g, '""');
|
|
84
|
+
let depth = 0;
|
|
85
|
+
let initialized = false;
|
|
86
|
+
for (let i = 0; i < text.length; i++) {
|
|
87
|
+
const ch = text[i];
|
|
88
|
+
if (ch === "(" || ch === "[" || ch === "{" || ch === "<") depth++;
|
|
89
|
+
else if (ch === ")" || ch === "]" || ch === "}" || ch === ">" && text[i - 1] !== "=") depth = Math.max(0, depth - 1);
|
|
90
|
+
else if (depth === 0 && ch === "=" && text[i + 1] !== "=" && text[i + 1] !== ">" && text[i - 1] !== "=") initialized = true;
|
|
91
|
+
else if (depth === 0 && ch === "," && initialized) return true;
|
|
92
|
+
}
|
|
93
|
+
return depth === 0 && /,\s*$/.test(text);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export {
|
|
97
|
+
scanExports,
|
|
98
|
+
isRouteModule
|
|
99
|
+
};
|
|
100
|
+
//# sourceMappingURL=chunk-BDTB3DFR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/vite/exports.ts"],"sourcesContent":["/**\n * The runtime names a source file exports, read from its text without a\n * parser. Live variants need them to write a dispatcher module whose\n * `export` statements cover every version of a file; anything this cannot\n * read with certainty answers `null`, and the plugin falls back to swapping\n * that file whole.\n *\n * Only statements that start a line are read. Formatted code always puts a\n * top-level `export` at the start of its line, while one inside a template\n * string or a comment almost never is, and a stray hit there only adds a\n * name whose value is `undefined`. Type-only exports are skipped: they do\n * not exist at runtime.\n */\n\n/** Exports whose names are only meaningful to a framework that reads the\n * module itself (React Router and Remix route modules). Such a file is\n * transformed by the framework's own plugin, which a dispatcher would hide\n * the real exports from. */\nconst ROUTE_EXPORTS = new Set([\n 'loader', 'action', 'clientLoader', 'clientAction', 'headers', 'links',\n 'meta', 'handle', 'shouldRevalidate', 'middleware', 'clientMiddleware',\n 'unstable_middleware', 'unstable_clientMiddleware', 'HydrateFallback',\n]);\n\nconst IDENT = /^[A-Za-z_$][\\w$]*$/;\n\nexport function scanExports(code: string): string[] | null {\n const names = new Set<string>();\n const lines = code.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n // A one-line block comment in front (`/** Doc */ export …`) is skipped.\n const line = (lines[i] ?? '').replace(/^(?:\\/\\*.*?\\*\\/\\s*)+/, '');\n if (!line.startsWith('export')) continue;\n const rest = line.slice('export'.length);\n if (rest !== '' && !/^[\\s{*]/.test(rest)) continue; // `exported = 1`\n const body = rest.trim();\n\n // `export { a, b as c } [from '…']`, possibly over several lines.\n if (body.startsWith('{') || body.startsWith('type {')) {\n if (body.startsWith('type')) continue;\n let clause = body;\n while (!clause.includes('}') && i + 1 < lines.length) clause += ` ${lines[++i]}`;\n const inner = clause.slice(clause.indexOf('{') + 1, clause.indexOf('}'));\n for (const raw of inner.split(',')) {\n const spec = raw.replace(/\\/\\/.*$/, '').trim();\n if (!spec || spec.startsWith('type ')) continue;\n const as = spec.split(/\\s+as\\s+/);\n const name = (as[1] ?? as[0] ?? '').trim().replace(/^[\"']|[\"']$/g, '');\n if (!name) return null;\n names.add(name);\n }\n continue;\n }\n\n if (/^type\\s*\\*/.test(body)) continue;\n // `export * as ns from '…'` names one binding; a bare `export *`\n // re-exports names this file cannot see.\n const star = body.match(/^\\*\\s+as\\s+([A-Za-z_$][\\w$]*)/);\n if (star?.[1]) { names.add(star[1]); continue; }\n if (body.startsWith('*')) return null;\n\n if (/^default\\b/.test(body)) { names.add('default'); continue; }\n if (/^(?:declare\\s|type\\s|interface\\s)/.test(body)) continue;\n if (/^(?:=|import\\s|namespace\\s|module\\s|as\\s)/.test(body)) return null;\n\n const decl = body.match(\n /^(?:async\\s+)?function\\s*\\*?\\s*([A-Za-z_$][\\w$]*)|^(?:abstract\\s+)?class\\s+([A-Za-z_$][\\w$]*)|^(?:const\\s+)?enum\\s+([A-Za-z_$][\\w$]*)/,\n );\n const declared = decl?.[1] ?? decl?.[2] ?? decl?.[3];\n if (declared) { names.add(declared); continue; }\n\n const variable = body.match(/^(?:const|let|var)\\s+(.*)$/);\n if (variable) {\n const target = (variable[1] ?? '').trim();\n // Destructuring declares names that need a real parser to list.\n if (target.startsWith('{') || target.startsWith('[')) return null;\n const name = target.match(/^([A-Za-z_$][\\w$]*)/)?.[1];\n if (!name) return null;\n // Several declarators (`export const a = 1, b = 2`, possibly over\n // lines) need a parser to list; serve such a file whole.\n if (hasSecondDeclarator(target)) return null;\n names.add(name);\n continue;\n }\n return null;\n }\n for (const name of names) if (name !== 'default' && !IDENT.test(name)) return null;\n return [...names];\n}\n\n/** True for a module a route framework reads export by export. */\nexport function isRouteModule(names: string[]): boolean {\n return names.includes('default') && names.some((name) => ROUTE_EXPORTS.has(name));\n}\n\n/**\n * Whether a declaration line goes on to a second declarator: a comma outside\n * brackets after its initializer starts, or a line that ends in one. Strings\n * are blanked first so a comma inside one does not count.\n */\nfunction hasSecondDeclarator(declaration: string): boolean {\n const text = declaration\n .replace(/\\/\\/.*$/, '')\n .replace(/'(?:[^'\\\\]|\\\\.)*'|\"(?:[^\"\\\\]|\\\\.)*\"|`(?:[^`\\\\]|\\\\.)*`/g, '\"\"');\n let depth = 0;\n let initialized = false;\n for (let i = 0; i < text.length; i++) {\n const ch = text[i];\n if (ch === '(' || ch === '[' || ch === '{' || ch === '<') depth++;\n else if (ch === ')' || ch === ']' || ch === '}' || (ch === '>' && text[i - 1] !== '=')) depth = Math.max(0, depth - 1);\n else if (depth === 0 && ch === '=' && text[i + 1] !== '=' && text[i + 1] !== '>' && text[i - 1] !== '=') initialized = true;\n else if (depth === 0 && ch === ',' && initialized) return true;\n }\n return depth === 0 && /,\\s*$/.test(text);\n}\n"],"mappings":";AAkBA,IAAM,gBAAgB,oBAAI,IAAI;AAAA,EAC5B;AAAA,EAAU;AAAA,EAAU;AAAA,EAAgB;AAAA,EAAgB;AAAA,EAAW;AAAA,EAC/D;AAAA,EAAQ;AAAA,EAAU;AAAA,EAAoB;AAAA,EAAc;AAAA,EACpD;AAAA,EAAuB;AAAA,EAA6B;AACtD,CAAC;AAED,IAAM,QAAQ;AAEP,SAAS,YAAY,MAA+B;AACzD,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AAErC,UAAM,QAAQ,MAAM,CAAC,KAAK,IAAI,QAAQ,wBAAwB,EAAE;AAChE,QAAI,CAAC,KAAK,WAAW,QAAQ,EAAG;AAChC,UAAM,OAAO,KAAK,MAAM,SAAS,MAAM;AACvC,QAAI,SAAS,MAAM,CAAC,UAAU,KAAK,IAAI,EAAG;AAC1C,UAAM,OAAO,KAAK,KAAK;AAGvB,QAAI,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,QAAQ,GAAG;AACrD,UAAI,KAAK,WAAW,MAAM,EAAG;AAC7B,UAAI,SAAS;AACb,aAAO,CAAC,OAAO,SAAS,GAAG,KAAK,IAAI,IAAI,MAAM,OAAQ,WAAU,IAAI,MAAM,EAAE,CAAC,CAAC;AAC9E,YAAM,QAAQ,OAAO,MAAM,OAAO,QAAQ,GAAG,IAAI,GAAG,OAAO,QAAQ,GAAG,CAAC;AACvE,iBAAW,OAAO,MAAM,MAAM,GAAG,GAAG;AAClC,cAAM,OAAO,IAAI,QAAQ,WAAW,EAAE,EAAE,KAAK;AAC7C,YAAI,CAAC,QAAQ,KAAK,WAAW,OAAO,EAAG;AACvC,cAAM,KAAK,KAAK,MAAM,UAAU;AAChC,cAAM,QAAQ,GAAG,CAAC,KAAK,GAAG,CAAC,KAAK,IAAI,KAAK,EAAE,QAAQ,gBAAgB,EAAE;AACrE,YAAI,CAAC,KAAM,QAAO;AAClB,cAAM,IAAI,IAAI;AAAA,MAChB;AACA;AAAA,IACF;AAEA,QAAI,aAAa,KAAK,IAAI,EAAG;AAG7B,UAAM,OAAO,KAAK,MAAM,+BAA+B;AACvD,QAAI,OAAO,CAAC,GAAG;AAAE,YAAM,IAAI,KAAK,CAAC,CAAC;AAAG;AAAA,IAAU;AAC/C,QAAI,KAAK,WAAW,GAAG,EAAG,QAAO;AAEjC,QAAI,aAAa,KAAK,IAAI,GAAG;AAAE,YAAM,IAAI,SAAS;AAAG;AAAA,IAAU;AAC/D,QAAI,oCAAoC,KAAK,IAAI,EAAG;AACpD,QAAI,4CAA4C,KAAK,IAAI,EAAG,QAAO;AAEnE,UAAM,OAAO,KAAK;AAAA,MAChB;AAAA,IACF;AACA,UAAM,WAAW,OAAO,CAAC,KAAK,OAAO,CAAC,KAAK,OAAO,CAAC;AACnD,QAAI,UAAU;AAAE,YAAM,IAAI,QAAQ;AAAG;AAAA,IAAU;AAE/C,UAAM,WAAW,KAAK,MAAM,4BAA4B;AACxD,QAAI,UAAU;AACZ,YAAM,UAAU,SAAS,CAAC,KAAK,IAAI,KAAK;AAExC,UAAI,OAAO,WAAW,GAAG,KAAK,OAAO,WAAW,GAAG,EAAG,QAAO;AAC7D,YAAM,OAAO,OAAO,MAAM,qBAAqB,IAAI,CAAC;AACpD,UAAI,CAAC,KAAM,QAAO;AAGlB,UAAI,oBAAoB,MAAM,EAAG,QAAO;AACxC,YAAM,IAAI,IAAI;AACd;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,aAAW,QAAQ,MAAO,KAAI,SAAS,aAAa,CAAC,MAAM,KAAK,IAAI,EAAG,QAAO;AAC9E,SAAO,CAAC,GAAG,KAAK;AAClB;AAGO,SAAS,cAAc,OAA0B;AACtD,SAAO,MAAM,SAAS,SAAS,KAAK,MAAM,KAAK,CAAC,SAAS,cAAc,IAAI,IAAI,CAAC;AAClF;AAOA,SAAS,oBAAoB,aAA8B;AACzD,QAAM,OAAO,YACV,QAAQ,WAAW,EAAE,EACrB,QAAQ,0DAA0D,IAAI;AACzE,MAAI,QAAQ;AACZ,MAAI,cAAc;AAClB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,KAAK,KAAK,CAAC;AACjB,QAAI,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,OAAO,IAAK;AAAA,aACjD,OAAO,OAAO,OAAO,OAAO,OAAO,OAAQ,OAAO,OAAO,KAAK,IAAI,CAAC,MAAM,IAAM,SAAQ,KAAK,IAAI,GAAG,QAAQ,CAAC;AAAA,aAC5G,UAAU,KAAK,OAAO,OAAO,KAAK,IAAI,CAAC,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,OAAO,KAAK,IAAI,CAAC,MAAM,IAAK,eAAc;AAAA,aAC9G,UAAU,KAAK,OAAO,OAAO,YAAa,QAAO;AAAA,EAC5D;AACA,SAAO,UAAU,KAAK,QAAQ,KAAK,IAAI;AACzC;","names":[]}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
-
OVERLAY_GROUP_ID
|
|
3
|
-
|
|
2
|
+
OVERLAY_GROUP_ID,
|
|
3
|
+
overlayGroups
|
|
4
|
+
} from "./chunk-RKZWO37H.js";
|
|
4
5
|
import {
|
|
5
6
|
ensureFileIgnored,
|
|
6
7
|
withFileLock,
|
|
7
8
|
writeJson
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-LXW6WFOC.js";
|
|
9
10
|
|
|
10
11
|
// src/variants-store.ts
|
|
11
12
|
import { execFileSync } from "child_process";
|
|
@@ -247,19 +248,39 @@ function promoteVariant(opts, id, { force = false } = {}) {
|
|
|
247
248
|
);
|
|
248
249
|
}
|
|
249
250
|
}
|
|
251
|
+
const siblings = overlayGroups(readVariants(opts)).find((g) => g.members.some((m) => m.id === id))?.members.filter((m) => m.id !== id) ?? [];
|
|
250
252
|
const files = [];
|
|
253
|
+
const landed = /* @__PURE__ */ new Map();
|
|
251
254
|
for (const key of Object.keys(manifest.files)) {
|
|
252
255
|
const overrideAbs = join(vDir, key);
|
|
253
256
|
const destAbs = join(root, key);
|
|
257
|
+
const before = existsSync(destAbs) ? currentBlob(destAbs) : null;
|
|
254
258
|
mkdirSync(dirname(destAbs), { recursive: true });
|
|
255
259
|
writeFileSync(destAbs, readFileSync(overrideAbs));
|
|
260
|
+
landed.set(key, { before, after: blobOf(destAbs) });
|
|
256
261
|
files.push(key);
|
|
257
262
|
}
|
|
258
263
|
rmSync(vDir, { recursive: true, force: true });
|
|
259
264
|
if (active.includes(id)) writeActive(opts, active.filter((a) => a !== id));
|
|
260
|
-
return { files };
|
|
265
|
+
return { files, rebased: rebaseSiblings(opts, siblings, landed) };
|
|
261
266
|
});
|
|
262
267
|
}
|
|
268
|
+
function rebaseSiblings(opts, siblings, landed) {
|
|
269
|
+
const rebased = [];
|
|
270
|
+
for (const sibling of siblings) {
|
|
271
|
+
let moved = false;
|
|
272
|
+
for (const [key, entry] of Object.entries(sibling.files)) {
|
|
273
|
+
const change = landed.get(key);
|
|
274
|
+
if (!change?.after || !(entry.added || entry.base !== null && entry.base === change.before)) continue;
|
|
275
|
+
sibling.files[key] = { base: change.after };
|
|
276
|
+
moved = true;
|
|
277
|
+
}
|
|
278
|
+
if (!moved) continue;
|
|
279
|
+
writeVariant(opts, sibling);
|
|
280
|
+
rebased.push(sibling.id);
|
|
281
|
+
}
|
|
282
|
+
return rebased;
|
|
283
|
+
}
|
|
263
284
|
function dropVariant(opts, id) {
|
|
264
285
|
mutateVariants(opts, () => {
|
|
265
286
|
if (!isValidVariantId(id)) throw new Error(`Invalid variant id "${id}"`);
|
|
@@ -268,11 +289,13 @@ function dropVariant(opts, id) {
|
|
|
268
289
|
if (active.includes(id)) writeActive(opts, active.filter((a) => a !== id));
|
|
269
290
|
});
|
|
270
291
|
}
|
|
292
|
+
var GIT_QUIET = ["ignore", "pipe", "ignore"];
|
|
271
293
|
function blobOf(absPath) {
|
|
272
294
|
try {
|
|
273
295
|
return execFileSync("git", ["hash-object", "--no-filters", "-w", absPath], {
|
|
274
296
|
cwd: dirname(absPath),
|
|
275
|
-
encoding: "utf8"
|
|
297
|
+
encoding: "utf8",
|
|
298
|
+
stdio: GIT_QUIET
|
|
276
299
|
}).trim();
|
|
277
300
|
} catch {
|
|
278
301
|
return null;
|
|
@@ -282,7 +305,8 @@ function currentBlob(absPath) {
|
|
|
282
305
|
try {
|
|
283
306
|
return execFileSync("git", ["hash-object", "--no-filters", absPath], {
|
|
284
307
|
cwd: dirname(absPath),
|
|
285
|
-
encoding: "utf8"
|
|
308
|
+
encoding: "utf8",
|
|
309
|
+
stdio: GIT_QUIET
|
|
286
310
|
}).trim();
|
|
287
311
|
} catch {
|
|
288
312
|
return null;
|
|
@@ -299,7 +323,7 @@ function hashFile(file, base) {
|
|
|
299
323
|
}
|
|
300
324
|
function catFile(cwd, hash) {
|
|
301
325
|
try {
|
|
302
|
-
return execFileSync("git", ["cat-file", "-p", hash], { cwd, encoding: "utf8" });
|
|
326
|
+
return execFileSync("git", ["cat-file", "-p", hash], { cwd, encoding: "utf8", stdio: GIT_QUIET });
|
|
303
327
|
} catch {
|
|
304
328
|
return null;
|
|
305
329
|
}
|
|
@@ -321,7 +345,7 @@ function mergeFile(overrideAbs, baseAbs, currentAbs) {
|
|
|
321
345
|
baseAbs,
|
|
322
346
|
currentAbs
|
|
323
347
|
],
|
|
324
|
-
{ encoding: "utf8" }
|
|
348
|
+
{ encoding: "utf8", stdio: GIT_QUIET }
|
|
325
349
|
);
|
|
326
350
|
return { status: 0, stdout };
|
|
327
351
|
} catch (err) {
|
|
@@ -332,6 +356,9 @@ function mergeFile(overrideAbs, baseAbs, currentAbs) {
|
|
|
332
356
|
function isConflictStatus(status) {
|
|
333
357
|
return status !== null && status >= 1 && status <= 127;
|
|
334
358
|
}
|
|
359
|
+
function recordBase(absPath, hash) {
|
|
360
|
+
return blobOf(absPath) ?? hash;
|
|
361
|
+
}
|
|
335
362
|
function syncVariant(opts, id) {
|
|
336
363
|
return mutateVariants(opts, () => {
|
|
337
364
|
const manifest = readVariant(opts, id);
|
|
@@ -365,12 +392,12 @@ function syncVariant(opts, id) {
|
|
|
365
392
|
const result = mergeFile(overrideAbs, baseTmp, currentAbs);
|
|
366
393
|
if (result.status === 0) {
|
|
367
394
|
writeFileSync(overrideAbs, result.stdout);
|
|
368
|
-
entry.base = currentHash;
|
|
395
|
+
entry.base = recordBase(currentAbs, currentHash);
|
|
369
396
|
dirty = true;
|
|
370
397
|
merged.push(file);
|
|
371
398
|
} else if (isConflictStatus(result.status)) {
|
|
372
399
|
writeFileSync(overrideAbs, result.stdout);
|
|
373
|
-
entry.base = currentHash;
|
|
400
|
+
entry.base = recordBase(currentAbs, currentHash);
|
|
374
401
|
dirty = true;
|
|
375
402
|
conflicts.push(file);
|
|
376
403
|
} else {
|
|
@@ -417,6 +444,13 @@ function activeConflict(opts, ids) {
|
|
|
417
444
|
}
|
|
418
445
|
return null;
|
|
419
446
|
}
|
|
447
|
+
function crossGroupOverlaps(opts, id) {
|
|
448
|
+
const variants = readVariants(opts);
|
|
449
|
+
const self = variants.find((v) => v.id === id);
|
|
450
|
+
if (!self) return [];
|
|
451
|
+
const siblings = overlayGroups(variants).find((g) => g.members.some((m) => m.id === id))?.members ?? [];
|
|
452
|
+
return variants.flatMap((other) => other.id === id || siblings.includes(other) ? [] : Object.keys(other.files).filter((file) => Object.hasOwn(self.files, file)).map((file) => ({ file, other: other.id })));
|
|
453
|
+
}
|
|
420
454
|
function readKnobs(opts) {
|
|
421
455
|
const file = resolve(opts.root, KNOBS_FILE);
|
|
422
456
|
if (!existsSync(file)) return [];
|
|
@@ -445,6 +479,10 @@ function readKnobs(opts) {
|
|
|
445
479
|
const hex = (value) => typeof value === "string" && /^#(?:[a-f0-9]{3}|[a-f0-9]{6})$/i.test(value);
|
|
446
480
|
return hex(c.defaultColor) && Array.isArray(c.swatches) && c.swatches.every((s) => s && typeof s.id === "string" && typeof s.label === "string" && hex(s.value));
|
|
447
481
|
}
|
|
482
|
+
if (a.kind === "select") {
|
|
483
|
+
const s = a;
|
|
484
|
+
return Array.isArray(s.options) && s.options.length > 0 && s.options.every((o) => o && typeof o.id === "string" && typeof o.label === "string") && s.options.some((o) => o.id === s.defaultOption);
|
|
485
|
+
}
|
|
448
486
|
return false;
|
|
449
487
|
}).filter((axis) => {
|
|
450
488
|
if (seen.has(axis.id)) return false;
|
|
@@ -514,8 +552,13 @@ function variantsResponse(opts, req) {
|
|
|
514
552
|
const conflict = activeConflict(opts, active);
|
|
515
553
|
if (conflict) {
|
|
516
554
|
const [a, b, file] = conflict;
|
|
555
|
+
const groups = overlayGroups(readVariants(opts));
|
|
556
|
+
const named = (id) => {
|
|
557
|
+
const label = groups.find((group) => group.members.some((member) => member.id === id))?.label;
|
|
558
|
+
return label ? `"${id}" (${label})` : `"${id}"`;
|
|
559
|
+
};
|
|
517
560
|
return json(409, {
|
|
518
|
-
error:
|
|
561
|
+
error: `${named(a)} and ${named(b)} both override ${file}; only one can be active`,
|
|
519
562
|
conflict
|
|
520
563
|
});
|
|
521
564
|
}
|
|
@@ -542,8 +585,9 @@ export {
|
|
|
542
585
|
syncVariant,
|
|
543
586
|
resolveOverride,
|
|
544
587
|
overrideSourcePath,
|
|
588
|
+
crossGroupOverlaps,
|
|
545
589
|
readActive,
|
|
546
590
|
writeActive,
|
|
547
591
|
variantsResponse
|
|
548
592
|
};
|
|
549
|
-
//# sourceMappingURL=chunk-
|
|
593
|
+
//# sourceMappingURL=chunk-BRBXIGZP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/variants-store.ts"],"sourcesContent":["/**\n * isotone — overlay variants store (node only).\n *\n * An overlay variant is a directory of alternative source files under\n * `.isotone/variants/<id>/`, mirrored at their repo-relative paths, plus\n * a `variant.json` manifest recording which git blob each override was made\n * from. That blob is the three-way merge base: when the real file moves on,\n * `syncVariant` diffs the override against what it started from and what\n * the file is now, the same way a branch rebases. Everything here is\n * synchronous and framework-free so the CLI and the Vite plugin (a\n * different owner) can both sit on top of it.\n */\nimport { execFileSync } from 'node:child_process';\nimport { createHash, randomUUID } from 'node:crypto';\nimport {\n existsSync,\n mkdirSync,\n lstatSync,\n readdirSync,\n readFileSync,\n realpathSync,\n rmSync,\n writeFileSync,\n} from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';\n\nimport type { IterationElement } from './core/index';\nimport {\n type ColorVariantAxis,\n type KnobAxis,\n OVERLAY_GROUP_ID,\n overlayGroups,\n type OverlayVariant,\n type OverlayVariantStatus,\n type RangeVariantAxis,\n type SelectVariantAxis,\n} from './core/variants';\nimport { ensureFileIgnored, type NotesRequest, type NotesResponse } from './store';\nimport { withFileLock, writeJson } from './file-store';\n\n/** Default location of the variants tree, relative to the project root. */\nexport const DEFAULT_VARIANTS_DIR = '.isotone/variants';\n\n/** Relative to root. Holds `{ \"active\": [\"<id>\", ...] }`, at most one id per\n * group. The Vite plugin keeps this out of git; nothing here assumes that. */\nexport const ACTIVE_FILE = '.isotone/active.json';\n\n/** Relative to root. A JSON array of {@link KnobAxis}; committed, since the\n * defaults in it are part of the design. Missing or malformed reads as none. */\nexport const KNOBS_FILE = '.isotone/knobs.json';\n\nexport interface VariantsStoreOptions {\n /** Project root: Vite's root or the CLI's cwd. Absolute. */\n root: string;\n /** Variants directory, relative to root or absolute. Default {@link DEFAULT_VARIANTS_DIR}. */\n dir?: string;\n}\n\ninterface ActiveFileShape {\n active: string[];\n}\n\n/** Agents in separate processes may edit the same manifest. Keep their\n * read-modify-write operations together, including promotion and removal. */\nfunction mutateVariants<T>(opts: VariantsStoreOptions, change: () => T): T {\n return withFileLock(join(variantsDir(opts), '.mutation'), change);\n}\n\n/* ── paths ────────────────────────────────────────────────────────────── */\n\nexport function variantsDir(opts: VariantsStoreOptions): string {\n const dir = opts.dir ?? DEFAULT_VARIANTS_DIR;\n return isAbsolute(dir) ? dir : resolve(opts.root, dir);\n}\n\nexport function variantDir(opts: VariantsStoreOptions, id: string): string {\n if (!isValidVariantId(id)) throw new Error(`Invalid variant id \"${id}\"`);\n return join(variantsDir(opts), id);\n}\n\nexport function isValidVariantId(id: string): boolean {\n return /^[a-z0-9][a-z0-9-]{0,63}$/.test(id);\n}\n\n/** POSIX-ify a path node gave us as platform-native (a no-op on POSIX,\n * where `sep` already is `/`; keeps manifest keys portable regardless). */\nfunction toPosixPath(p: string): string {\n return p.split(sep).join('/');\n}\n\n/** `relative()` puts you outside the base with a leading `..` (or, on a\n * different drive on Windows, an absolute path back out) — either way the\n * caller asked for something outside the tree we're allowed to touch. */\nfunction isEscaping(rel: string): boolean {\n return rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel);\n}\n\n/** Check existing ancestors too: a lexical child may be a symlink outside\n * the tree, including a not-yet-created file beneath a symlinked directory. */\nfunction staysInside(base: string, file: string): boolean {\n const canonical = (path: string): string => {\n let existing = resolve(path);\n while (dirname(existing) !== existing) {\n try { lstatSync(existing); break; } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n existing = dirname(existing);\n }\n }\n return resolve(realpathSync(existing), relative(existing, resolve(path)));\n };\n return !isEscaping(relative(canonical(base), canonical(file)));\n}\n\n/**\n * Resolve `file` (repo-relative, or absolute inside root) against `root`\n * and return both the absolute path and its POSIX repo-relative manifest\n * key. Throws for anything that escapes root, so every caller that takes a\n * user-supplied path gets the same guard.\n */\nfunction repoRelativeKey(root: string, file: string): { abs: string; key: string } {\n const abs = isAbsolute(file) ? resolve(file) : resolve(root, file);\n const rel = relative(root, abs);\n if (!rel || isEscaping(rel) || !isSafeManifestKey(toPosixPath(rel)) || !staysInside(root, abs)) {\n throw new Error(`\"${file}\" escapes the project root`);\n }\n return { abs, key: toPosixPath(rel) };\n}\n\n/* ── manifest read/write ──────────────────────────────────────────────── */\n\n/**\n * A manifest key is joined onto the variant dir and onto the project root,\n * so a key that climbs out of either (`../../x`, an absolute path, or a\n * backslash that Windows would read as a separator) would let a hand-edited\n * or checked-in variant.json reach files outside both trees. Such a manifest\n * is not a variant at all.\n */\nfunction isSafeManifestKey(key: string): boolean {\n if (!key || isAbsolute(key) || /[\\\\:\\0]/.test(key) || key === 'variant.json') return false;\n return !key.split('/').some((segment) => !segment || segment === '.' || segment === '..' || segment === '.git');\n}\n\nfunction isPlausibleVariant(v: unknown): v is OverlayVariant {\n if (!v || typeof v !== 'object' || Array.isArray(v)) return false;\n const o = v as Record<string, unknown>;\n return (\n typeof o.id === 'string' &&\n typeof o.label === 'string' &&\n typeof o.createdAt === 'string' &&\n (o.route === undefined || typeof o.route === 'string') &&\n (o.description === undefined || typeof o.description === 'string') &&\n (o.group === undefined || (typeof o.group === 'string' && OVERLAY_GROUP_ID.test(o.group))) &&\n (o.groupLabel === undefined || typeof o.groupLabel === 'string') &&\n !!o.files &&\n typeof o.files === 'object' &&\n !Array.isArray(o.files) &&\n Object.entries(o.files).every(([key, entry]) => isSafeManifestKey(key) && entry && typeof entry === 'object' &&\n ('base' in entry) && (entry.base === null || (typeof entry.base === 'string' && /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/.test(entry.base))) &&\n (!('added' in entry) || entry.added === true))\n );\n}\n\nexport function readVariant(opts: VariantsStoreOptions, id: string): OverlayVariant | null {\n // An id that is not a valid directory name (`..`, a path with a slash)\n // would resolve to somewhere outside the variants dir; there is no\n // variant there by definition.\n if (!isValidVariantId(id)) return null;\n const file = join(variantDir(opts, id), 'variant.json');\n if (!existsSync(file)) return null;\n try {\n const data = JSON.parse(readFileSync(file, 'utf8'));\n const dir = variantDir(opts, id);\n return isPlausibleVariant(data) && data.id === id && staysInside(variantsDir(opts), file) &&\n Object.keys(data.files).every((key) => staysInside(opts.root, join(opts.root, key)) && staysInside(dir, join(dir, key))) ? data : null;\n } catch {\n // Junk directory: unreadable or not JSON. listVariants skips it.\n return null;\n }\n}\n\nexport function writeVariant(opts: VariantsStoreOptions, v: OverlayVariant): void {\n if (!isPlausibleVariant(v)) throw new Error('Invalid variant manifest');\n const dir = variantDir(opts, v.id);\n if (!staysInside(variantsDir(opts), dir)) throw new Error('Variant directory escapes the variants tree');\n mkdirSync(dir, { recursive: true });\n writeJson(join(dir, 'variant.json'), v);\n}\n\n/** Whether a tracked file has drifted from what its override was derived\n * from. `base: null` (never hashed, or the manifest was hand-edited to\n * simulate an unreachable blob) counts as stale too: there's nothing to\n * compare against, so we can't claim it's unchanged. */\nfunction isStale(base: string | null, currentHash: string | null): boolean {\n return base === null || currentHash === null || currentHash !== base;\n}\n\n/** Every valid manifest, oldest first, without the per-file status that\n * {@link listVariants} hashes for. */\nexport function readVariants(opts: VariantsStoreOptions): OverlayVariant[] {\n const dir = variantsDir(opts);\n if (!existsSync(dir)) return [];\n return readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => readVariant(opts, e.name))\n .filter((v): v is OverlayVariant => v !== null)\n .sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n}\n\nexport function listVariants(opts: VariantsStoreOptions): OverlayVariantStatus[] {\n const dir = variantsDir(opts);\n if (!existsSync(dir)) return [];\n const root = resolve(opts.root);\n const ids = readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n\n const statuses: OverlayVariantStatus[] = [];\n const hashes = new Map<string, string | null>();\n for (const id of ids) {\n const manifest = readVariant(opts, id);\n if (!manifest) continue; // no manifest, or invalid JSON: not a variant\n const stale: string[] = [];\n const missing: string[] = [];\n for (const [file, entry] of Object.entries(manifest.files)) {\n const overrideAbs = join(dir, id, file);\n if (!existsSync(overrideAbs)) {\n missing.push(file);\n continue;\n }\n // A file the variant adds is current for as long as the project still\n // lacks it; once a real one appears, the two need a person.\n if (entry.added) {\n if (existsSync(join(root, file))) stale.push(file);\n continue;\n }\n const hashKey = `${entry.base?.length}:${file}`;\n if (!hashes.has(hashKey)) hashes.set(hashKey, hashFile(join(root, file), entry.base));\n if (isStale(entry.base, hashes.get(hashKey) ?? null)) stale.push(file);\n }\n statuses.push({ ...manifest, stale, missing });\n }\n statuses.sort((a, b) => a.createdAt.localeCompare(b.createdAt));\n return statuses;\n}\n\nexport function createVariant(\n opts: VariantsStoreOptions,\n input: {\n id: string;\n label?: string;\n route?: string;\n element?: IterationElement | null;\n note?: string;\n description?: string;\n group?: string;\n groupLabel?: string;\n },\n): OverlayVariant {\n return mutateVariants(opts, () => {\n if (!isValidVariantId(input.id)) {\n throw new Error(`Invalid variant id \"${input.id}\"`);\n }\n if (input.group !== undefined) assertGroupId(input.group);\n if (readVariant(opts, input.id)) {\n throw new Error(`Variant \"${input.id}\" already exists`);\n }\n const variant: OverlayVariant = {\n id: input.id,\n label: input.label || input.id,\n createdAt: new Date().toISOString(),\n files: {},\n ...(input.route !== undefined ? { route: input.route } : {}),\n ...(input.element !== undefined ? { element: input.element } : {}),\n ...(input.note !== undefined ? { note: input.note } : {}),\n ...(input.description !== undefined ? { description: input.description } : {}),\n ...(input.group !== undefined ? { group: input.group } : {}),\n ...(input.groupLabel !== undefined ? { groupLabel: input.groupLabel } : {}),\n };\n writeVariant(opts, variant);\n return variant;\n });\n}\n\nfunction assertGroupId(group: string): void {\n if (!OVERLAY_GROUP_ID.test(group)) {\n throw new Error(`Invalid group id \"${group}\" (lowercase letters, digits and hyphens)`);\n }\n}\n\n/** Put an existing variant into a group, so two variants made separately\n * become alternatives of one decision. `groupLabel` is left as it was\n * unless given. */\nexport function setVariantGroup(\n opts: VariantsStoreOptions,\n id: string,\n group: string,\n groupLabel?: string,\n): OverlayVariant {\n return mutateVariants(opts, () => {\n const manifest = readVariant(opts, id);\n if (!manifest) throw new Error(`No variant \"${id}\"`);\n assertGroupId(group);\n manifest.group = group;\n if (groupLabel !== undefined) manifest.groupLabel = groupLabel;\n writeVariant(opts, manifest);\n return manifest;\n });\n}\n\n/**\n * Copy a real file into the variant. With `create`, the file is one the\n * project does not have yet: the variant gets an empty file at that path,\n * and the plugin resolves imports of it while the variant is loaded.\n */\nexport function addVariantFile(\n opts: VariantsStoreOptions,\n id: string,\n file: string,\n { create = false }: { create?: boolean } = {},\n): OverlayVariant {\n return mutateVariants(opts, () => {\n const manifest = readVariant(opts, id);\n if (!manifest) throw new Error(`No variant \"${id}\"`);\n const root = resolve(opts.root);\n const { abs: sourceAbs, key } = repoRelativeKey(root, file);\n // Idempotent: a file already tracked keeps its override and its base\n // untouched. Re-adding is a no-op, not a refresh (sync is how you refresh).\n if (Object.hasOwn(manifest.files, key)) return manifest;\n const destAbs = join(variantDir(opts, id), key);\n if (!staysInside(variantDir(opts, id), destAbs)) throw new Error('Override path escapes the variant directory');\n if (create) {\n if (existsSync(sourceAbs)) throw new Error(`\"${file}\" already exists; add it without --new`);\n mkdirSync(dirname(destAbs), { recursive: true });\n if (!existsSync(destAbs)) writeFileSync(destAbs, '');\n manifest.files = { ...manifest.files, [key]: { base: null, added: true } };\n writeVariant(opts, manifest);\n return manifest;\n }\n if (!existsSync(sourceAbs)) throw new Error(`\"${file}\" does not exist. Use --new for a file the variant adds.`);\n mkdirSync(dirname(destAbs), { recursive: true });\n writeFileSync(destAbs, readFileSync(sourceAbs));\n manifest.files = { ...manifest.files, [key]: { base: blobOf(sourceAbs) } };\n writeVariant(opts, manifest);\n return manifest;\n });\n}\n\nexport function removeVariantFile(\n opts: VariantsStoreOptions,\n id: string,\n file: string,\n): OverlayVariant {\n return mutateVariants(opts, () => {\n const manifest = readVariant(opts, id);\n if (!manifest) throw new Error(`No variant \"${id}\"`);\n const root = resolve(opts.root);\n const { key } = repoRelativeKey(root, file);\n if (Object.hasOwn(manifest.files, key)) {\n delete manifest.files[key];\n rmSync(join(variantDir(opts, id), key), { force: true });\n writeVariant(opts, manifest);\n }\n return manifest;\n });\n}\n\n/** The marker `git merge-file` leaves at the top of a conflicted hunk, with\n * the label `syncVariant` gives the override side. */\nconst CONFLICT_MARKER = '<<<<<<< variant';\n\n/**\n * Land a variant: copy every override over its real file, then delete the\n * variant. Refuses when an override would silently discard work: a file\n * whose real source moved on since the override was made (run `syncVariant`\n * first) or an override still carrying merge conflict markers. `force`\n * skips both checks for the case where the reviewer has decided the\n * override wins as-is.\n */\nexport function promoteVariant(\n opts: VariantsStoreOptions,\n id: string,\n { force = false }: { force?: boolean } = {},\n): { files: string[]; rebased: string[] } {\n return mutateVariants(opts, () => {\n // Checked before the manifest read so the rmSync below can never be\n // handed a directory the id escaped into.\n if (!isValidVariantId(id)) throw new Error(`Invalid variant id \"${id}\"`);\n const manifest = readVariant(opts, id);\n if (!manifest) throw new Error(`No variant \"${id}\"`);\n const active = readActive(opts);\n const root = resolve(opts.root);\n const vDir = variantDir(opts, id);\n const missing = Object.keys(manifest.files).filter((key) => !existsSync(join(vDir, key)));\n if (missing.length) throw new Error(`\"${id}\" has missing overrides: ${missing.join(', ')}. Restore or remove them before promoting.`);\n if (!force) {\n const status = listVariants(opts).find((v) => v.id === id);\n if (status?.stale.length) {\n // A null base is stale by definition, but the file did not necessarily\n // change: there was simply no git to record what it looked like.\n const reasons = status.stale.map((file) =>\n manifest.files[file]?.base === null\n ? `no base recorded for ${file} (not a git repo when the override was made)`\n : `${file} changed since the override was made`,\n );\n throw new Error(\n `\"${id}\" is stale (${reasons.join('; ')}). Run \\`variant sync ${id}\\` first, or promote with --force.`,\n );\n }\n const conflicted = Object.keys(manifest.files).filter((key) => {\n const overrideAbs = join(vDir, key);\n return existsSync(overrideAbs) && readFileSync(overrideAbs, 'utf8').includes(CONFLICT_MARKER);\n });\n if (conflicted.length) {\n throw new Error(\n `\"${id}\" has unresolved conflict markers in ${conflicted.join(', ')}. Resolve them, or promote with --force.`,\n );\n }\n }\n const siblings = overlayGroups(readVariants(opts)).find((g) => g.members.some((m) => m.id === id))\n ?.members.filter((m) => m.id !== id) ?? [];\n const files: string[] = [];\n const landed = new Map<string, { before: string | null; after: string | null }>();\n for (const key of Object.keys(manifest.files)) {\n const overrideAbs = join(vDir, key);\n const destAbs = join(root, key);\n const before = existsSync(destAbs) ? currentBlob(destAbs) : null;\n mkdirSync(dirname(destAbs), { recursive: true });\n writeFileSync(destAbs, readFileSync(overrideAbs));\n landed.set(key, { before, after: blobOf(destAbs) });\n files.push(key);\n }\n rmSync(vDir, { recursive: true, force: true });\n if (active.includes(id)) writeActive(opts, active.filter((a) => a !== id));\n return { files, rebased: rebaseSiblings(opts, siblings, landed) };\n });\n}\n\n/**\n * A sibling is another answer to the decision the promote just settled, so the\n * promoted file is what it now differs from: reading it as out of date would\n * only send someone to merge two alternatives into each other. Only a copy made\n * from the version the promote replaced moves. One that was already behind\n * stays stale, because the real file changed in ways nobody has looked at.\n */\nfunction rebaseSiblings(\n opts: VariantsStoreOptions,\n siblings: OverlayVariant[],\n landed: Map<string, { before: string | null; after: string | null }>,\n): string[] {\n const rebased: string[] = [];\n for (const sibling of siblings) {\n let moved = false;\n for (const [key, entry] of Object.entries(sibling.files)) {\n const change = landed.get(key);\n if (!change?.after || !(entry.added || (entry.base !== null && entry.base === change.before))) continue;\n sibling.files[key] = { base: change.after };\n moved = true;\n }\n if (!moved) continue;\n writeVariant(opts, sibling);\n rebased.push(sibling.id);\n }\n return rebased;\n}\n\nexport function dropVariant(opts: VariantsStoreOptions, id: string): void {\n mutateVariants(opts, () => {\n // `variant drop ..` would otherwise resolve to the variants dir's parent\n // and rmSync it, recursively.\n if (!isValidVariantId(id)) throw new Error(`Invalid variant id \"${id}\"`);\n const active = readActive(opts);\n rmSync(variantDir(opts, id), { recursive: true, force: true });\n if (active.includes(id)) writeActive(opts, active.filter((a) => a !== id));\n });\n}\n\n/* ── git blob hashing ─────────────────────────────────────────────────── */\n\n/** A failed lookup is an answer here (`null`), not something to print. */\nconst GIT_QUIET: ['ignore', 'pipe', 'ignore'] = ['ignore', 'pipe', 'ignore'];\n\n/**\n * `git hash-object` looks up the repository from its cwd, not from the\n * path it's hashing, so every call here runs with cwd set to the file's own\n * directory — always inside the right repo regardless of where the caller's\n * process happens to be.\n */\nexport function blobOf(absPath: string): string | null {\n try {\n return execFileSync('git', ['hash-object', '--no-filters', '-w', absPath], {\n cwd: dirname(absPath),\n encoding: 'utf8',\n stdio: GIT_QUIET,\n }).trim();\n } catch {\n return null;\n }\n}\n\nexport function currentBlob(absPath: string): string | null {\n try {\n return execFileSync('git', ['hash-object', '--no-filters', absPath], {\n cwd: dirname(absPath),\n encoding: 'utf8',\n stdio: GIT_QUIET,\n }).trim();\n } catch {\n return null;\n }\n}\n\n/** Git blob hashing is just a header and the file bytes. Existing bases tell\n * us the repository's hash format, avoiding a process per file on every poll. */\nfunction hashFile(file: string, base: string | null): string | null {\n if (base === null) return null;\n try {\n const contents = readFileSync(file);\n return createHash(base.length === 64 ? 'sha256' : 'sha1')\n .update(`blob ${contents.length}\\0`).update(contents).digest('hex');\n } catch { return null; }\n}\n\nfunction catFile(cwd: string, hash: string): string | null {\n try {\n return execFileSync('git', ['cat-file', '-p', hash], { cwd, encoding: 'utf8', stdio: GIT_QUIET });\n } catch {\n return null; // the blob isn't reachable (gc'd, or never written)\n }\n}\n\n/** Runs `git merge-file -p <override> <base> <current>`, capturing stdout\n * whether it exits clean (0) or with conflict markers. execFileSync throws\n * on a nonzero exit, but the thrown error still carries stdout and the exit\n * status, so both are read from there. `status` is null when git never\n * ran to completion (killed by a signal, or not found). */\nfunction mergeFile(overrideAbs: string, baseAbs: string, currentAbs: string): {\n status: number | null;\n stdout: string;\n} {\n try {\n const stdout = execFileSync(\n 'git',\n [\n 'merge-file',\n '-p',\n '-L',\n 'variant',\n '-L',\n 'base',\n '-L',\n 'default',\n overrideAbs,\n baseAbs,\n currentAbs,\n ],\n { encoding: 'utf8', stdio: GIT_QUIET },\n );\n return { status: 0, stdout };\n } catch (err) {\n const e = err as { status?: number | null; stdout?: string };\n return { status: e.status ?? null, stdout: e.stdout ?? '' };\n }\n}\n\n/** `git merge-file` exits with the number of conflicts it left, so 1..127\n * is a conflicted merge whose stdout still carries the whole file (with\n * markers). 128 and up, or no status at all, is git failing outright. */\nfunction isConflictStatus(status: number | null): boolean {\n return status !== null && status >= 1 && status <= 127;\n}\n\n/** The base a later sync merges from has to be in git's object store. A file\n * git does not track yet is in there only if this writes it. */\nfunction recordBase(absPath: string, hash: string): string {\n return blobOf(absPath) ?? hash;\n}\n\nexport function syncVariant(\n opts: VariantsStoreOptions,\n id: string,\n): { merged: string[]; conflicts: string[]; unchanged: string[] } {\n return mutateVariants(opts, () => {\n const manifest = readVariant(opts, id);\n if (!manifest) throw new Error(`No variant \"${id}\"`);\n const root = resolve(opts.root);\n const vDir = variantDir(opts, id);\n\n const merged: string[] = [];\n const conflicts: string[] = [];\n const unchanged: string[] = [];\n let dirty = false;\n\n for (const [file, entry] of Object.entries(manifest.files)) {\n const currentAbs = join(root, file);\n const overrideAbs = join(vDir, file);\n if (entry.added) {\n // Nothing to merge into; a real file that appeared meanwhile is a\n // conflict for a person to settle.\n (existsSync(currentAbs) ? conflicts : unchanged).push(file);\n continue;\n }\n const currentHash = currentBlob(currentAbs);\n\n if (!isStale(entry.base, currentHash)) {\n unchanged.push(file);\n continue;\n }\n\n // No base to diff against (never hashed, or the blob is unreachable), or\n // no current file to merge into: nothing to three-way merge, so the\n // override is left as-is and flagged for a person to sort out.\n const baseContent = entry.base !== null ? catFile(dirname(currentAbs), entry.base) : null;\n if (baseContent === null || currentHash === null) {\n conflicts.push(file);\n continue;\n }\n\n const baseTmp = join(tmpdir(), `isotone-base-${randomUUID()}`);\n writeFileSync(baseTmp, baseContent);\n try {\n const result = mergeFile(overrideAbs, baseTmp, currentAbs);\n if (result.status === 0) {\n writeFileSync(overrideAbs, result.stdout);\n entry.base = recordBase(currentAbs, currentHash);\n dirty = true;\n merged.push(file);\n } else if (isConflictStatus(result.status)) {\n // The markers go into the override for a person to resolve, and the\n // base advances anyway, the way git records a conflicted merge:\n // the current file is now what the override was reconciled against,\n // so resolving the markers is all that stands between it and\n // promote. Left behind, the base would make the resolved file read\n // as stale and a second sync would re-conflict the same hunks; the\n // marker check in promoteVariant is the guard that remains.\n writeFileSync(overrideAbs, result.stdout);\n entry.base = recordBase(currentAbs, currentHash);\n dirty = true;\n conflicts.push(file);\n } else {\n // Git failed outright; its empty stdout must not replace the\n // override, and the base stays where it was.\n conflicts.push(file);\n }\n } finally {\n rmSync(baseTmp, { force: true });\n }\n }\n\n if (dirty) writeVariant(opts, manifest);\n return { merged, conflicts, unchanged };\n });\n}\n\n/* ── active variants / vite load() lookup ─────────────────────────── */\n\n/**\n * The override that answers for `absSourcePath` under the active set, or\n * `null` when the real file stands. The first active variant whose manifest\n * names the file wins; `readActive` and the endpoint keep the set free of\n * two variants over one file, so in practice there is only ever one.\n */\nexport function resolveOverride(\n opts: VariantsStoreOptions,\n active: string[],\n absSourcePath: string,\n): string | null {\n if (active.length === 0) return null;\n const root = resolve(opts.root);\n const relToRoot = relative(root, resolve(absSourcePath));\n if (isEscaping(relToRoot)) return null;\n const key = toPosixPath(relToRoot);\n for (const id of active) {\n const manifest = readVariant(opts, id);\n if (!manifest || !Object.hasOwn(manifest.files, key)) continue;\n const overrideAbs = join(variantDir(opts, id), key);\n if (existsSync(overrideAbs)) return overrideAbs;\n }\n return null;\n}\n\nexport function overrideSourcePath(\n opts: VariantsStoreOptions,\n absOverridePath: string,\n): { id: string; file: string } | null {\n const rel = relative(variantsDir(opts), resolve(absOverridePath));\n if (!rel || isEscaping(rel)) return null;\n const [id, ...rest] = toPosixPath(rel).split('/');\n if (!id || rest.length === 0) return null;\n return { id, file: rest.join('/') };\n}\n\n/**\n * Two active variants over the same file, as `[first, second, file]`, or\n * `null` when the set is consistent. `load` can only serve one copy of a\n * file, so such a set is refused by the endpoint and trimmed by\n * {@link readActive}. Ids without a manifest are skipped; the callers\n * check existence themselves.\n */\nexport function activeConflict(\n opts: VariantsStoreOptions,\n ids: string[],\n): [string, string, string] | null {\n const owner = new Map<string, string>();\n for (const id of ids) {\n const manifest = readVariant(opts, id);\n for (const file of Object.keys(manifest?.files ?? {})) {\n const first = owner.get(file);\n if (first !== undefined && first !== id) return [first, id, file];\n owner.set(file, id);\n }\n }\n return null;\n}\n\n/**\n * Variants in another group that override one of this variant's files. Groups\n * are independent selectors, so picking both of these fails at activation.\n */\nexport function crossGroupOverlaps(opts: VariantsStoreOptions, id: string): { file: string; other: string }[] {\n const variants = readVariants(opts);\n const self = variants.find((v) => v.id === id);\n if (!self) return [];\n const siblings = overlayGroups(variants).find((g) => g.members.some((m) => m.id === id))?.members ?? [];\n return variants.flatMap((other) => other.id === id || siblings.includes(other) ? [] :\n Object.keys(other.files).filter((file) => Object.hasOwn(self.files, file)).map((file) => ({ file, other: other.id })));\n}\n\n/**\n * The active set, cleaned: ids whose variant is gone (dropped or promoted\n * since the file was written) are dropped, and of two variants over one\n * file the later one is dropped, so `load` never has to choose. The file's\n * older shape, one id or `null`, still reads.\n */\n/** The host's knobs, dropping entries that are not a usable axis. */\nexport function readKnobs(opts: VariantsStoreOptions): KnobAxis[] {\n const file = resolve(opts.root, KNOBS_FILE);\n if (!existsSync(file)) return [];\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(file, 'utf8'));\n } catch {\n return [];\n }\n if (!Array.isArray(parsed)) return [];\n const seen = new Set<string>();\n return parsed.filter((k): k is KnobAxis => {\n if (!k || typeof k !== 'object') return false;\n const a = k as Partial<KnobAxis>;\n if (typeof a.id !== 'string' || !isValidVariantId(a.id) || typeof a.label !== 'string') return false;\n if (a.route !== undefined && typeof a.route !== 'string' &&\n (!Array.isArray(a.route) || !a.route.every((route) => typeof route === 'string'))) return false;\n if (a.showWhen !== undefined && (!a.showWhen || typeof a.showWhen.axis !== 'string' ||\n (a.showWhen.equals !== undefined && typeof a.showWhen.equals !== 'string') ||\n (a.showWhen.not !== undefined && typeof a.showWhen.not !== 'string'))) return false;\n if (a.section !== undefined && typeof a.section !== 'string') return false;\n if (a.kind === 'range') {\n const { min, max, step, defaultValue } = a as Partial<RangeVariantAxis>;\n if (typeof min !== 'number' || typeof max !== 'number' || typeof step !== 'number' || typeof defaultValue !== 'number') return false;\n return [min, max, step, defaultValue, max - min].every(Number.isFinite) &&\n min < max && step > 0 && defaultValue >= min && defaultValue <= max &&\n (a.unit === undefined || typeof a.unit === 'string');\n }\n if (a.kind === 'color') {\n const c = a as Partial<ColorVariantAxis>;\n const hex = (value: unknown) => typeof value === 'string' && /^#(?:[a-f0-9]{3}|[a-f0-9]{6})$/i.test(value);\n return hex(c.defaultColor) && Array.isArray(c.swatches) && c.swatches.every((s) =>\n s && typeof s.id === 'string' && typeof s.label === 'string' && hex(s.value));\n }\n if (a.kind === 'select') {\n const s = a as Partial<SelectVariantAxis>;\n return Array.isArray(s.options) && s.options.length > 0 &&\n s.options.every((o) => o && typeof o.id === 'string' && typeof o.label === 'string') &&\n s.options.some((o) => o.id === s.defaultOption);\n }\n return false;\n }).filter((axis) => {\n if (seen.has(axis.id)) return false;\n seen.add(axis.id);\n return true;\n });\n}\n\nexport function readActive(opts: VariantsStoreOptions): string[] {\n const file = join(resolve(opts.root), ACTIVE_FILE);\n if (!existsSync(file)) return [];\n let raw: unknown;\n try {\n raw = (JSON.parse(readFileSync(file, 'utf8')) as { active?: unknown }).active;\n } catch {\n return [];\n }\n const ids = Array.isArray(raw) ? raw : typeof raw === 'string' ? [raw] : [];\n const kept: string[] = [];\n const seen = new Set<string>();\n const owner = new Set<string>();\n for (const id of ids) {\n if (typeof id !== 'string' || seen.has(id)) continue;\n seen.add(id);\n const manifest = readVariant(opts, id);\n if (!manifest) continue;\n const files = Object.keys(manifest.files);\n if (files.some((f) => owner.has(f))) continue;\n for (const f of files) owner.add(f);\n kept.push(id);\n }\n return kept;\n}\n\nexport function writeActive(opts: VariantsStoreOptions, ids: string[]): void {\n const file = join(resolve(opts.root), ACTIVE_FILE);\n mkdirSync(dirname(file), { recursive: true });\n const active = Array.from(new Set(ids));\n writeJson(file, { active } satisfies ActiveFileShape);\n // Which variants a machine is looking at is local state, never a commit;\n // the CLI writes this too, so the exclusion cannot live in the plugin alone.\n ensureFileIgnored(file);\n}\n\n/* ── the endpoint handler ─────────────────────────────────────────────── */\n\n/**\n * The transport-agnostic core of the variants endpoint, mirroring\n * {@link import('./store').notesResponse}. `req.url` is the path relative\n * to wherever the caller mounted `VARIANTS_ENDPOINT` (\"/\", \"/active\"), so\n * this function never needs to know the mount point.\n */\nexport function variantsResponse(opts: VariantsStoreOptions, req: NotesRequest): NotesResponse {\n const method = req.method ?? '';\n const path = (req.url ?? '/').split('?')[0] || '/';\n const json = (status: number, body: unknown): NotesResponse => ({\n status,\n headers: { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' },\n body,\n });\n\n if (method === 'GET' && path === '/') {\n return json(200, { variants: listVariants(opts), active: readActive(opts), knobs: readKnobs(opts) });\n }\n\n if (method === 'POST' && path === '/active') {\n const body = req.body;\n if (body === null || typeof body !== 'object' || Array.isArray(body)) {\n return json(400, { error: 'body must be a JSON object' });\n }\n const raw = (body as { active?: unknown }).active;\n if (!Array.isArray(raw) || !raw.every((id) => typeof id === 'string')) {\n return json(400, { error: 'active must be an array of variant ids' });\n }\n const active = Array.from(new Set(raw as string[]));\n for (const id of active) {\n // Malformed, not merely unknown: a 404 would imply the shape was fine.\n if (!isValidVariantId(id)) return json(400, { error: `invalid variant id \"${id}\"` });\n }\n for (const id of active) {\n if (!readVariant(opts, id)) return json(404, { error: `unknown variant \"${id}\"` });\n }\n const conflict = activeConflict(opts, active);\n if (conflict) {\n const [a, b, file] = conflict;\n const groups = overlayGroups(readVariants(opts));\n const named = (id: string) => {\n const label = groups.find((group) => group.members.some((member) => member.id === id))?.label;\n return label ? `\"${id}\" (${label})` : `\"${id}\"`;\n };\n return json(409, {\n error: `${named(a)} and ${named(b)} both override ${file}; only one can be active`,\n conflict,\n });\n }\n writeActive(opts, active);\n return json(200, { active });\n }\n\n return json(405, { error: 'method not allowed' });\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
readBrowserStorage,
|
|
4
4
|
request,
|
|
5
5
|
writeBrowserStorage
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-RKZWO37H.js";
|
|
7
7
|
import {
|
|
8
8
|
MAX_FEEDBACK_LENGTH
|
|
9
9
|
} from "./chunk-A4DX2V4C.js";
|
|
@@ -29,11 +29,17 @@ function describeIterationElement(el, maxDepth = 4) {
|
|
|
29
29
|
const parts = [];
|
|
30
30
|
let node = el;
|
|
31
31
|
let depth = 0;
|
|
32
|
-
|
|
32
|
+
const only = el.isOnlyMatch ? (path) => el.isOnlyMatch?.(path.join(" > ")) === true : null;
|
|
33
|
+
let settled = false;
|
|
34
|
+
while (node && node.tagName !== "BODY" && (only || depth < maxDepth)) {
|
|
33
35
|
if (node.id) {
|
|
34
36
|
const id = Array.from(node.id, (c) => `\\${c.codePointAt(0)?.toString(16)} `).join("");
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
const segment = `#${/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(node.id) ? node.id : id}`;
|
|
38
|
+
if (!only || only([segment, ...parts])) {
|
|
39
|
+
parts.unshift(segment);
|
|
40
|
+
settled = true;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
37
43
|
}
|
|
38
44
|
let part = node.tagName.toLowerCase();
|
|
39
45
|
const parent = node.parentElement;
|
|
@@ -44,11 +50,84 @@ function describeIterationElement(el, maxDepth = 4) {
|
|
|
44
50
|
parts.unshift(part);
|
|
45
51
|
node = parent;
|
|
46
52
|
depth++;
|
|
53
|
+
if (only && depth >= maxDepth && only(parts)) {
|
|
54
|
+
settled = true;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
47
57
|
}
|
|
58
|
+
if (only && !settled && !only(parts)) parts.unshift("body");
|
|
48
59
|
return {
|
|
49
60
|
tag: el.tagName.toLowerCase(),
|
|
50
61
|
text: (el.textContent ?? "").trim().replace(/\s+/g, " ").slice(0, 80),
|
|
51
|
-
selector: parts.join(" > ")
|
|
62
|
+
selector: parts.join(" > "),
|
|
63
|
+
...el.context?.()
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
var IDENTIFYING_ATTRIBUTES = ["role", "type", "name", "href", "aria-label", "title", "alt", "placeholder"];
|
|
67
|
+
function reactContext(el) {
|
|
68
|
+
const key = Object.keys(el).find((name) => name.startsWith("__reactFiber$"));
|
|
69
|
+
let fiber = key ? el[key] : null;
|
|
70
|
+
const names = [];
|
|
71
|
+
let source;
|
|
72
|
+
while (fiber && names.length < 5) {
|
|
73
|
+
if (!source && fiber._debugSource?.fileName) {
|
|
74
|
+
source = `${fiber._debugSource.fileName}:${fiber._debugSource.lineNumber ?? 0}`;
|
|
75
|
+
}
|
|
76
|
+
const type = fiber.type;
|
|
77
|
+
const named = type && typeof type !== "string" ? type.render ?? type.type ?? type : null;
|
|
78
|
+
const name = named?.displayName ?? named?.name;
|
|
79
|
+
if (name && /^[A-Z]/.test(name) && names[0] !== name) names.unshift(name);
|
|
80
|
+
fiber = fiber.return;
|
|
81
|
+
}
|
|
82
|
+
return { ...names.length ? { component: names } : {}, ...source ? { source } : {} };
|
|
83
|
+
}
|
|
84
|
+
function elementContext(el) {
|
|
85
|
+
const attributes = {};
|
|
86
|
+
for (const name of IDENTIFYING_ATTRIBUTES) {
|
|
87
|
+
const value = el.getAttribute(name);
|
|
88
|
+
if (value) attributes[name] = value;
|
|
89
|
+
}
|
|
90
|
+
for (const { name, value } of Array.from(el.attributes).filter((a) => a.name.startsWith("data-")).slice(0, 6)) {
|
|
91
|
+
if (value) attributes[name] = value;
|
|
92
|
+
}
|
|
93
|
+
const className = el.getAttribute("class");
|
|
94
|
+
if (className) attributes.class = className;
|
|
95
|
+
const within = [];
|
|
96
|
+
let near;
|
|
97
|
+
const ownText = renderedText(el).trim();
|
|
98
|
+
for (let up = el.parentElement; up && up.tagName !== "BODY"; up = up.parentElement) {
|
|
99
|
+
if (!ownText && !near) {
|
|
100
|
+
const text = renderedText(up).trim().replace(/\s+/g, " ");
|
|
101
|
+
if (text) near = text.slice(0, 80);
|
|
102
|
+
}
|
|
103
|
+
if (within.length === 4) continue;
|
|
104
|
+
const tag = up.tagName.toLowerCase();
|
|
105
|
+
const label = up.getAttribute("aria-label");
|
|
106
|
+
const test = up.getAttribute("data-testid");
|
|
107
|
+
let heading = null;
|
|
108
|
+
try {
|
|
109
|
+
heading = Array.from(up.querySelectorAll(":scope > :is(h1,h2,h3,h4,h5,h6), :scope > * > :is(h1,h2,h3,h4,h5,h6)")).filter((candidate) => candidate.compareDocumentPosition(el) & 4).pop() ?? null;
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
if (label) within.unshift(`<${tag}> labelled "${label}"`);
|
|
113
|
+
else if (test) within.unshift(`<${tag} data-testid="${test}">`);
|
|
114
|
+
else if (heading && heading !== el) within.unshift(`<${tag}> headed "${(heading.textContent ?? "").trim().replace(/\s+/g, " ").slice(0, 60)}"`);
|
|
115
|
+
}
|
|
116
|
+
const box = el.getBoundingClientRect();
|
|
117
|
+
const view = el.ownerDocument?.defaultView;
|
|
118
|
+
return {
|
|
119
|
+
...Object.keys(attributes).length ? { attributes } : {},
|
|
120
|
+
...reactContext(el),
|
|
121
|
+
...within.length ? { within } : {},
|
|
122
|
+
...near ? { near } : {},
|
|
123
|
+
...box.width || box.height ? {
|
|
124
|
+
rect: {
|
|
125
|
+
x: Math.round(box.left + (view?.scrollX ?? 0)),
|
|
126
|
+
y: Math.round(box.top + (view?.scrollY ?? 0)),
|
|
127
|
+
w: Math.round(box.width),
|
|
128
|
+
h: Math.round(box.height)
|
|
129
|
+
}
|
|
130
|
+
} : {}
|
|
52
131
|
};
|
|
53
132
|
}
|
|
54
133
|
function renderedText(el) {
|
|
@@ -95,6 +174,15 @@ function elementAsNode(el) {
|
|
|
95
174
|
if (root === null) {
|
|
96
175
|
throw new Error("unreachable: a real element always maps to a view");
|
|
97
176
|
}
|
|
177
|
+
root.isOnlyMatch = (selector) => {
|
|
178
|
+
try {
|
|
179
|
+
const found = el.ownerDocument.querySelectorAll(selector);
|
|
180
|
+
return found.length === 1 && found[0] === el;
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
root.context = () => elementContext(el);
|
|
98
186
|
return root;
|
|
99
187
|
}
|
|
100
188
|
var DEFAULT_HIDDEN = ["/login", "/logout", "/register", "/error", "/_error"];
|
|
@@ -370,4 +458,4 @@ export {
|
|
|
370
458
|
isRouteVisible,
|
|
371
459
|
createIterationStore
|
|
372
460
|
};
|
|
373
|
-
//# sourceMappingURL=chunk-
|
|
461
|
+
//# sourceMappingURL=chunk-K5RAC2BT.js.map
|