@marver-design/marver 0.17.0 → 0.19.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/CHANGELOG.md +96 -0
- package/README.md +3 -1
- package/dist/bake-kaf5kGZ7.mjs +747 -0
- package/dist/{build-D_g53Bp2.mjs → build-7ed5H2vT.mjs} +215 -13
- package/dist/cli.mjs +19 -11
- package/dist/{comments-oYcZ3cE-.mjs → comments-ClVgfQib.mjs} +1 -1
- package/dist/{daemon-Bbh_jmui.mjs → daemon-DbHvLQUL.mjs} +1 -1
- package/dist/{dev-LnIISva5.mjs → dev-D3mP2x27.mjs} +205 -10
- package/dist/{init-B7YhcN2o.mjs → init-BQYCS3EU.mjs} +2 -2
- package/dist/{manifest-CaslQIAO.mjs → manifest-B01PSyDc.mjs} +34 -7
- package/dist/{marver-id-gate-D6By7XHj.mjs → marver-id-gate-B_idGdHm.mjs} +1 -1
- package/dist/{plugin-D2msH1cj.mjs → plugin-DI-7NAnx.mjs} +108 -29
- package/dist/{poster-BEjUcQP3.mjs → poster-DNh6N27C.mjs} +1 -1
- package/dist/publish-bakes-Dp-ZFk3d.mjs +216 -0
- package/dist/{serve-Bcwfpvhl.mjs → serve-z5qtj_wJ.mjs} +3 -3
- package/dist/{share-Gqo_Ygqw.mjs → share--bdSc4G5.mjs} +1 -1
- package/dist/{shot-BzQ0PXKH.mjs → shot-BFEuYbaz.mjs} +1 -1
- package/dist/{shot-DlmTO8AF.mjs → shot-DMDvDbeP.mjs} +14 -7
- package/dist/{work-lzC-lPY0.mjs → work-0YopuMt9.mjs} +1 -1
- package/docs/publish.md +27 -5
- package/docs/sticky-notes.md +43 -0
- package/package.json +2 -1
- package/src/client/content/diagram.tsx +27 -1
- package/src/client/content/index.tsx +2 -2
- package/src/client/content/md.ts +48 -0
- package/src/client/frame-host/bridge.js +4 -9
- package/src/client/frame-host/main.tsx +20 -6
- package/src/client/shell/App.tsx +14 -63
- package/src/client/shell/Comments.tsx +77 -14
- package/src/client/shell/Play.tsx +6 -3
- package/src/client/shell/canvas/Canvas.tsx +12 -3
- package/src/client/shell/canvas/FrameNode.tsx +99 -89
- package/src/client/shell/canvas/Sticky.tsx +284 -0
- package/src/client/shell/canvas/admission.ts +70 -0
- package/src/client/shell/canvas/sleep.ts +194 -0
- package/src/client/shell/goto.ts +72 -0
- package/src/client/shell/notes.ts +151 -0
- package/src/client/shell/store.ts +36 -18
- package/src/client/shell/styles.css +92 -25
- package/src/client/shell/tidy.ts +20 -2
- package/src/shared/sleep-rule.ts +41 -0
- package/templates/AGENTS-embedded.md +15 -0
- package/templates/AGENTS-studio.md +15 -0
- package/templates/instructions/craft.md +9 -0
- package/templates/instructions/publish.md +62 -9
- package/templates/instructions/shape.md +62 -0
- package/src/client/frame-host/serialize.ts +0 -195
- package/src/client/shell/canvas/snapshots.ts +0 -233
|
@@ -1,12 +1,205 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-
|
|
4
|
-
import { realpathSync } from "node:fs";
|
|
1
|
+
import { i as PKG, r as NAME } from "./cli.mjs";
|
|
2
|
+
import { c as loadConfig, u as detectHost } from "./manifest-B01PSyDc.mjs";
|
|
3
|
+
import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-DI-7NAnx.mjs";
|
|
4
|
+
import { readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
5
5
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { createLogger, createServer, searchForWorkspaceRoot } from "vite";
|
|
7
|
+
import { createLogger, createServer, parseAst, searchForWorkspaceRoot } from "vite";
|
|
8
8
|
import react from "@vitejs/plugin-react";
|
|
9
9
|
import { createServer as createServer$1 } from "node:net";
|
|
10
|
+
//#region src/server/icons.ts
|
|
11
|
+
/**
|
|
12
|
+
* Icon barrels, unrolled.
|
|
13
|
+
*
|
|
14
|
+
* `import { Truck } from '@phosphor-icons/react'` makes Vite hand the frame the WHOLE prebundled
|
|
15
|
+
* package - 3000 icons, 6.5 MB of JavaScript - and every frame on a board is its own realm, so every
|
|
16
|
+
* frame parses and evaluates all of it: ~400 ms, the whole boot of a lo-fi frame (measured,
|
|
17
|
+
* research/hifi/frameres.ts: the scene import is 530 of a 550 ms boot; a board of 128 frames spends
|
|
18
|
+
* 50 s on it). The packages ship one module per icon and their barrel says which: this reads the
|
|
19
|
+
* INSTALLED barrel once (every alias, whatever the version), rewrites the import declarations of
|
|
20
|
+
* design sources to the per-icon modules, one line for one line, and names those modules to the
|
|
21
|
+
* dependency optimizer at server start so nothing is discovered - and reloaded for - on first use.
|
|
22
|
+
* A name the barrel does not map, a default or namespace import, a re-export: left as it is.
|
|
23
|
+
*/
|
|
24
|
+
const LIBS = [{
|
|
25
|
+
barrel: "@phosphor-icons/react",
|
|
26
|
+
spec: (rel) => {
|
|
27
|
+
const m = rel.match(/^\.\/csr\/([\w-]+)\.es\.js$/);
|
|
28
|
+
return m ? `@phosphor-icons/react/dist/csr/${m[1]}` : null;
|
|
29
|
+
}
|
|
30
|
+
}, {
|
|
31
|
+
barrel: "lucide-react",
|
|
32
|
+
spec: (rel) => {
|
|
33
|
+
const m = rel.match(/^\.\/icons\/([\w-]+\.m?js)$/);
|
|
34
|
+
return m ? `lucide-react/dist/esm/icons/${m[1]}` : null;
|
|
35
|
+
}
|
|
36
|
+
}];
|
|
37
|
+
/** The map read from a barrel's source: `import { A as x } from './f'` + `export { x as Name }`
|
|
38
|
+
* (phosphor) and `export { default as Name } from './f'` (lucide). */
|
|
39
|
+
function parseBarrel(source, lib) {
|
|
40
|
+
const map = /* @__PURE__ */ new Map();
|
|
41
|
+
const locals = /* @__PURE__ */ new Map();
|
|
42
|
+
for (const m of source.matchAll(/import\s*\{([^}]*)\}\s*from\s*["']([^"']+)["']/g)) {
|
|
43
|
+
const spec = lib.spec(m[2]);
|
|
44
|
+
if (!spec) continue;
|
|
45
|
+
for (const s of m[1].split(",")) {
|
|
46
|
+
const [name, local = name] = s.trim().split(/\s+as\s+/);
|
|
47
|
+
if (name) locals.set(local, {
|
|
48
|
+
spec,
|
|
49
|
+
name
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (const m of source.matchAll(/export\s*\{([^}]*)\}(?:\s*from\s*["']([^"']+)["'])?/g)) {
|
|
54
|
+
const spec = m[2] ? lib.spec(m[2]) : null;
|
|
55
|
+
if (m[2] && !spec) continue;
|
|
56
|
+
for (const s of m[1].split(",")) {
|
|
57
|
+
const [name, exported = name] = s.trim().split(/\s+as\s+/);
|
|
58
|
+
if (!name) continue;
|
|
59
|
+
if (spec) map.set(exported, {
|
|
60
|
+
spec,
|
|
61
|
+
name
|
|
62
|
+
});
|
|
63
|
+
else {
|
|
64
|
+
const l = locals.get(name);
|
|
65
|
+
if (l) map.set(exported, l);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return map;
|
|
70
|
+
}
|
|
71
|
+
const maps = /* @__PURE__ */ new Map();
|
|
72
|
+
/** The maps for every icon package installed under `root` (read once per server). */
|
|
73
|
+
function iconMaps(root) {
|
|
74
|
+
let m = maps.get(root);
|
|
75
|
+
if (m) return m;
|
|
76
|
+
m = /* @__PURE__ */ new Map();
|
|
77
|
+
for (const lib of LIBS) {
|
|
78
|
+
const dir = join(root, "node_modules", lib.barrel);
|
|
79
|
+
try {
|
|
80
|
+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
81
|
+
const dot = pkg.exports?.["."];
|
|
82
|
+
const entry = (typeof dot === "object" ? dot.import : typeof dot === "string" ? dot : void 0) ?? pkg.module;
|
|
83
|
+
if (!entry) continue;
|
|
84
|
+
const map = parseBarrel(readFileSync(join(dir, entry), "utf8"), lib);
|
|
85
|
+
if (map.size) m.set(lib.barrel, map);
|
|
86
|
+
} catch {}
|
|
87
|
+
}
|
|
88
|
+
maps.set(root, m);
|
|
89
|
+
return m;
|
|
90
|
+
}
|
|
91
|
+
/** The code with every mapped icon import unrolled and the per-icon specifiers it now imports;
|
|
92
|
+
* null when nothing changed. Real import declarations only (a template literal is content). */
|
|
93
|
+
function unrollIcons(code, maps) {
|
|
94
|
+
if (![...maps.keys()].some((b) => code.includes(b))) return null;
|
|
95
|
+
let ast;
|
|
96
|
+
try {
|
|
97
|
+
ast = parseAst(code);
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const edits = [];
|
|
102
|
+
const modules = [];
|
|
103
|
+
for (const node of ast.body) {
|
|
104
|
+
if (node.type !== "ImportDeclaration" || !node.source) continue;
|
|
105
|
+
const map = maps.get(node.source.value);
|
|
106
|
+
if (!map || !node.specifiers?.length) continue;
|
|
107
|
+
const lines = [], keep = [];
|
|
108
|
+
let icons = 0;
|
|
109
|
+
for (const s of node.specifiers) {
|
|
110
|
+
const imported = s.type === "ImportSpecifier" ? s.imported?.name ?? s.imported?.value : void 0;
|
|
111
|
+
const hit = imported ? map.get(imported) : void 0;
|
|
112
|
+
if (!hit) {
|
|
113
|
+
if (s.type !== "ImportSpecifier") {
|
|
114
|
+
lines.length = 0;
|
|
115
|
+
icons = 0;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
keep.push(imported === s.local.name ? imported : `${imported} as ${s.local.name}`);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
icons++;
|
|
122
|
+
modules.push(hit.spec);
|
|
123
|
+
lines.push(hit.name === "default" ? `import ${s.local.name} from '${hit.spec}';` : `import { ${hit.name === s.local.name ? hit.name : `${hit.name} as ${s.local.name}`} } from '${hit.spec}';`);
|
|
124
|
+
}
|
|
125
|
+
if (!icons) continue;
|
|
126
|
+
if (keep.length) lines.push(`import { ${keep.join(", ")} } from '${node.source.value}';`);
|
|
127
|
+
const original = code.slice(node.start, node.end);
|
|
128
|
+
edits.push({
|
|
129
|
+
start: node.start,
|
|
130
|
+
end: node.end,
|
|
131
|
+
text: lines.join(" ") + "\n".repeat((original.match(/\n/g) ?? []).length)
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (!edits.length) return null;
|
|
135
|
+
let out = "", at = 0;
|
|
136
|
+
for (const e of edits) {
|
|
137
|
+
out += code.slice(at, e.start) + e.text;
|
|
138
|
+
at = e.end;
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
code: out + code.slice(at),
|
|
142
|
+
modules
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** Every per-icon module the design sources import (for the optimizer, at server start). Raw
|
|
146
|
+
* TypeScript is scanned by a plain pattern; only names the barrel maps count. */
|
|
147
|
+
function iconModules(root) {
|
|
148
|
+
const m = iconMaps(root);
|
|
149
|
+
if (!m.size) return [];
|
|
150
|
+
const found = /* @__PURE__ */ new Set();
|
|
151
|
+
const walk = (dir) => {
|
|
152
|
+
let names;
|
|
153
|
+
try {
|
|
154
|
+
names = readdirSync(dir);
|
|
155
|
+
} catch {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
for (const n of names) {
|
|
159
|
+
if (n === "node_modules" || n.startsWith(".")) continue;
|
|
160
|
+
const p = join(dir, n);
|
|
161
|
+
let st;
|
|
162
|
+
try {
|
|
163
|
+
st = statSync(p);
|
|
164
|
+
} catch {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (st.isDirectory()) walk(p);
|
|
168
|
+
else if (/\.[jt]sx?$/.test(n)) {
|
|
169
|
+
const src = readFileSync(p, "utf8").replace(/\/\*[\s\S]*?\*\/|\/\/.*$/gm, (c) => " ".repeat(c.length));
|
|
170
|
+
for (const im of src.matchAll(/import\s*\{([^}]*)\}\s*from\s*['"]([^'"]+)['"]/g)) {
|
|
171
|
+
const map = m.get(im[2]);
|
|
172
|
+
if (!map) continue;
|
|
173
|
+
for (const s of im[1].split(",")) {
|
|
174
|
+
const name = s.trim().replace(/^type\s+/, "").split(/\s+as\s+/)[0];
|
|
175
|
+
const hit = map.get(name);
|
|
176
|
+
if (hit) found.add(hit.spec);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
walk(join(root, "design"));
|
|
183
|
+
return [...found].sort();
|
|
184
|
+
}
|
|
185
|
+
/** The Vite plugin: design sources only, after the TS/JSX transform (a normal plugin runs after
|
|
186
|
+
* the core ones), before import analysis resolves the specifiers. The rewrite keeps every line
|
|
187
|
+
* where it was, so the file's map still lands (no map of its own). */
|
|
188
|
+
function iconsPlugin(root) {
|
|
189
|
+
const design = join(root, "design") + "/";
|
|
190
|
+
return {
|
|
191
|
+
name: "marver:icons",
|
|
192
|
+
transform(code, id) {
|
|
193
|
+
if (!id.startsWith(design) || !/\.[jt]sx?(\?|$)/.test(id)) return;
|
|
194
|
+
const r = unrollIcons(code, iconMaps(root));
|
|
195
|
+
return r ? {
|
|
196
|
+
code: r.code,
|
|
197
|
+
map: null
|
|
198
|
+
} : void 0;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
//#endregion
|
|
10
203
|
//#region src/server/dev.ts
|
|
11
204
|
/** packageDir = the installed marver package root (dist/cli.js lives one level down). */
|
|
12
205
|
function packageDir() {
|
|
@@ -174,7 +367,7 @@ async function dev(root, portFlag) {
|
|
|
174
367
|
clientDir,
|
|
175
368
|
config,
|
|
176
369
|
detectedThemeCss: host.themeCss
|
|
177
|
-
}));
|
|
370
|
+
}), iconsPlugin(root));
|
|
178
371
|
const logger = createLogger("info");
|
|
179
372
|
const noise = (msg) => /Sourcemap for .*node_modules.*points to/.test(msg);
|
|
180
373
|
const warnBase = logger.warn.bind(logger);
|
|
@@ -244,7 +437,9 @@ async function dev(root, portFlag) {
|
|
|
244
437
|
"react/jsx-dev-runtime",
|
|
245
438
|
`${PKG} > marked`,
|
|
246
439
|
`${PKG} > mermaid`,
|
|
247
|
-
`${PKG} >
|
|
440
|
+
`${PKG} > roughjs`,
|
|
441
|
+
`${PKG} > html-to-image`,
|
|
442
|
+
...iconModules(root)
|
|
248
443
|
],
|
|
249
444
|
entries: [join(clientDir, "frame-host", "index.html"), "design/**/*.{tsx,jsx}"]
|
|
250
445
|
},
|
|
@@ -257,12 +452,12 @@ async function dev(root, portFlag) {
|
|
|
257
452
|
console.log(`\n ${NAME} · ${projectName} → http://localhost:${port}/\n`);
|
|
258
453
|
setTimeout(async () => {
|
|
259
454
|
try {
|
|
260
|
-
await (await import("./shot-
|
|
455
|
+
await (await import("./shot-DMDvDbeP.mjs")).sweepGhosts((m) => console.log(` ${m}`));
|
|
261
456
|
} catch {}
|
|
262
457
|
}, 0);
|
|
263
458
|
try {
|
|
264
459
|
const { staleManagedInstructions } = await import("./managed-HwHVNI3h.mjs");
|
|
265
|
-
const { installedVersion } = await import("./plugin-
|
|
460
|
+
const { installedVersion } = await import("./plugin-DI-7NAnx.mjs").then((n) => n.i);
|
|
266
461
|
const stale = staleManagedInstructions(root);
|
|
267
462
|
if (stale.length) {
|
|
268
463
|
const shown = stale.slice(0, 4).join(", ") + (stale.length > 4 ? `, +${stale.length - 4} more` : "");
|
|
@@ -300,7 +495,7 @@ async function dev(root, portFlag) {
|
|
|
300
495
|
});
|
|
301
496
|
}
|
|
302
497
|
if (config.jam) {
|
|
303
|
-
const { startJam } = await import("./daemon-
|
|
498
|
+
const { startJam } = await import("./daemon-DbHvLQUL.mjs");
|
|
304
499
|
const jam = startJam(root, config.jam, (m) => console.log(m), (board) => server.ws.send("sh:jam-comment", { board }));
|
|
305
500
|
if (jam) {
|
|
306
501
|
const close = server.close.bind(server);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { r as NAME } from "./cli.mjs";
|
|
2
|
+
import { d as readJson, l as detectAgent, o as writeManifest, r as scanFrames, s as DEFAULTS, u as detectHost } from "./manifest-B01PSyDc.mjs";
|
|
3
3
|
import { MANAGED_PREFIX, enumerateInstructionTemplates, hashBody, managedFile, pkgDir } from "./managed-HwHVNI3h.mjs";
|
|
4
4
|
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { basename, dirname, join, relative } from "node:path";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as PKG, n as CONTENT_WIDTH } from "./cli.mjs";
|
|
2
2
|
import { a as listBoardFiles, d as flatten, f as isBoardName, g as hash, m as readTitle, n as checkBoardsDir, p as readDescription, r as checkRealDirs, s as readRegistry, t as boardFields, u as buildTree } from "./boards-BmxcT3Lc.mjs";
|
|
3
3
|
import { accessSync, constants, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { delimiter, isAbsolute, join, relative, sep } from "node:path";
|
|
@@ -375,6 +375,23 @@ function validViewports(v) {
|
|
|
375
375
|
//#region src/server/manifest.ts
|
|
376
376
|
const FRAME_EXT = /\.(tsx|jsx|html)$/;
|
|
377
377
|
const RESERVED_SCENES = /* @__PURE__ */ new Set(["components", "screens"]);
|
|
378
|
+
/** Sticky notes (spec 18): the longest note the manifest carries - a note is an aside, not a spec. */
|
|
379
|
+
const NOTE_MAX = 2e4;
|
|
380
|
+
/** The note file beside a frame file: `cart.tsx` -> `cart.note.md`; a scene's is `_note.md`. */
|
|
381
|
+
const noteFileFor = (frameFile) => frameFile.replace(FRAME_EXT, ".note.md");
|
|
382
|
+
/** Is this path a note file - the dev watcher's "regenerate on change" predicate. */
|
|
383
|
+
const isNoteFile = (file) => /(^|[\\/])(_note|[^\\/]+\.note)\.md$/.test(file);
|
|
384
|
+
/** A note's text: BOM stripped, trimmed, capped at NOTE_MAX. Undefined when the file is missing,
|
|
385
|
+
* empty, or not a regular file (a symlink is never followed - the `_brief.md` rule). */
|
|
386
|
+
function readNote(abs) {
|
|
387
|
+
try {
|
|
388
|
+
if (!lstatSync(abs).isFile()) return void 0;
|
|
389
|
+
const text = readFileSync(abs, "utf8").replace(/^/, "").trim();
|
|
390
|
+
return text ? text.slice(0, NOTE_MAX) : void 0;
|
|
391
|
+
} catch {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
378
395
|
/** Extract `export const meta = {...}` with literal string values only. Anything else is silently omitted. */
|
|
379
396
|
function extractMeta(src) {
|
|
380
397
|
const m = /export\s+const\s+meta\s*=\s*\{([\s\S]*?)\}/.exec(src);
|
|
@@ -526,6 +543,10 @@ function sceneBrief(root, scene) {
|
|
|
526
543
|
...description ? { description } : {}
|
|
527
544
|
};
|
|
528
545
|
}
|
|
546
|
+
/** A scene's sticky note: `design/scenes/<scene>/_note.md` (spec 18). */
|
|
547
|
+
function sceneNote(root, scene) {
|
|
548
|
+
return scene ? readNote(join(root, "design", "scenes", scene, "_note.md")) : void 0;
|
|
549
|
+
}
|
|
529
550
|
/** Write a scene's title into its brief's front matter - only that line changes; the body,
|
|
530
551
|
* every other field and the file's own line endings are kept. No brief yet = a front-matter-only
|
|
531
552
|
* brief. An empty title removes the line, and the file when nothing else is in it. The write
|
|
@@ -649,6 +670,8 @@ function scanFrames(root, project) {
|
|
|
649
670
|
kind,
|
|
650
671
|
scene
|
|
651
672
|
};
|
|
673
|
+
const note = readNote(noteFileFor(abs));
|
|
674
|
+
if (note) entry.note = note;
|
|
652
675
|
if (kind === "tsx") {
|
|
653
676
|
const src = readFileSync(abs, "utf8");
|
|
654
677
|
const meta = extractMeta(src);
|
|
@@ -684,11 +707,15 @@ function scanFrames(root, project) {
|
|
|
684
707
|
inferVariantGroups(frames);
|
|
685
708
|
const sceneCounts = /* @__PURE__ */ new Map();
|
|
686
709
|
for (const f of frames) sceneCounts.set(f.scene, (sceneCounts.get(f.scene) ?? 0) + 1);
|
|
687
|
-
const scenes = [...sceneCounts.entries()].map(([name, n]) =>
|
|
688
|
-
name
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
710
|
+
const scenes = [...sceneCounts.entries()].map(([name, n]) => {
|
|
711
|
+
const note = sceneNote(root, name);
|
|
712
|
+
return {
|
|
713
|
+
name,
|
|
714
|
+
frames: n,
|
|
715
|
+
...sceneBrief(root, name),
|
|
716
|
+
...note ? { note } : {}
|
|
717
|
+
};
|
|
718
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
692
719
|
return {
|
|
693
720
|
...project && (project.name || project.description) ? { project: {
|
|
694
721
|
...project.name ? { name: project.name } : {},
|
|
@@ -779,4 +806,4 @@ function writeManifest(root, manifest) {
|
|
|
779
806
|
return true;
|
|
780
807
|
}
|
|
781
808
|
//#endregion
|
|
782
|
-
export {
|
|
809
|
+
export { toFrameId as a, loadConfig as c, readJson as d, setSceneTitle as i, detectAgent as l, isNoteFile as n, writeManifest as o, scanFrames as r, DEFAULTS as s, affectedFrameIds as t, detectHost as u };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as avatarSourceFor, n as attachAvatar, p as provisionFromMarverId } from "./auth-DdUKeVKt.mjs";
|
|
2
2
|
import { TransactionStore, browserBinding, verifyAssertion } from "./marver-id-CVCoaMGI.mjs";
|
|
3
|
-
import { t as poweredByUrl } from "./serve-
|
|
3
|
+
import { t as poweredByUrl } from "./serve-z5qtj_wJ.mjs";
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
5
5
|
import { isIP } from "node:net";
|
|
6
6
|
import { lookup } from "node:dns/promises";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { a as ROUTE, i as PKG, r as NAME } from "./cli.mjs";
|
|
3
3
|
import { a as listBoardFiles, c as BOARD_NAME, g as hash, h as validateWire, i as isRegularFile, l as FOLDERS_FILE, m as readTitle, n as checkBoardsDir, o as nodeExists, p as readDescription, s as readRegistry, t as boardFields } from "./boards-BmxcT3Lc.mjs";
|
|
4
4
|
import { n as localProfile, t as isConnected } from "./profile-BjAPAJSb.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { c as loadConfig, i as setSceneTitle, n as isNoteFile, o as writeManifest, r as scanFrames, t as affectedFrameIds } from "./manifest-B01PSyDc.mjs";
|
|
6
6
|
import { copyFileSync, existsSync, linkSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, watch, writeFileSync } from "node:fs";
|
|
7
7
|
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
@@ -422,7 +422,7 @@ function apiMiddleware(root, opts = {}) {
|
|
|
422
422
|
};
|
|
423
423
|
const asPng = url.searchParams.get("format") === "png";
|
|
424
424
|
const origin = opts.origin?.() ?? `http://${req.headers.host ?? "localhost"}`;
|
|
425
|
-
const { shootFrame } = await import("./shot-
|
|
425
|
+
const { shootFrame } = await import("./shot-DMDvDbeP.mjs");
|
|
426
426
|
const r = await shootFrame({
|
|
427
427
|
root,
|
|
428
428
|
viewports: opts.viewports ?? {},
|
|
@@ -472,7 +472,7 @@ function apiMiddleware(root, opts = {}) {
|
|
|
472
472
|
if (typeof theme !== "string" || !/^[a-z0-9-]+$/i.test(theme)) return json(res, 400, { error: "invalid theme" });
|
|
473
473
|
const scale = body.scale === void 0 ? void 0 : body.scale;
|
|
474
474
|
if (scale !== void 0 && !(Number.isInteger(scale) && scale >= 1 && scale <= 4)) return json(res, 400, { error: "invalid scale" });
|
|
475
|
-
const { resolveFrames, shootBatch } = await import("./shot-
|
|
475
|
+
const { resolveFrames, shootBatch } = await import("./shot-DMDvDbeP.mjs");
|
|
476
476
|
const sel = resolveFrames(root, body);
|
|
477
477
|
if (!sel.ok) return json(res, sel.status, { error: sel.error });
|
|
478
478
|
const origin = opts.origin?.() ?? `http://${req.headers.host ?? "localhost"}`;
|
|
@@ -489,11 +489,63 @@ function apiMiddleware(root, opts = {}) {
|
|
|
489
489
|
return json(res, 503, { error: err.message });
|
|
490
490
|
}
|
|
491
491
|
}
|
|
492
|
+
if (path === "bakes" && req.method === "POST") {
|
|
493
|
+
if (!ownerGated(req)) return json(res, 403, { error: "forbidden" });
|
|
494
|
+
const raw = await readBody(req);
|
|
495
|
+
if (raw == null) return json(res, 400, { error: "body too large or unreadable" });
|
|
496
|
+
let body;
|
|
497
|
+
try {
|
|
498
|
+
body = JSON.parse(raw);
|
|
499
|
+
} catch {
|
|
500
|
+
return json(res, 400, { error: "malformed JSON" });
|
|
501
|
+
}
|
|
502
|
+
if (!body || typeof body !== "object" || !Array.isArray(body.asks) || !body.asks.length || body.asks.length > 200) return json(res, 400, { error: "asks must list 1-200 frames" });
|
|
503
|
+
const { bakeBatch, ASK_MAX } = await import("./bake-kaf5kGZ7.mjs");
|
|
504
|
+
let manifest = {};
|
|
505
|
+
try {
|
|
506
|
+
manifest = JSON.parse(readFileSync(join(root, "design", "manifest.json"), "utf8"));
|
|
507
|
+
} catch {}
|
|
508
|
+
const frames = new Map((manifest.frames ?? []).map((f) => [f.id, f]));
|
|
509
|
+
const asks = [];
|
|
510
|
+
for (const a of body.asks) {
|
|
511
|
+
const frame = typeof a?.frame === "string" ? a.frame : "";
|
|
512
|
+
const theme = typeof a?.theme === "string" && /^[a-z0-9-]{1,32}$/i.test(a.theme) ? a.theme : "";
|
|
513
|
+
const w = Math.round(Number(a?.w)), h = Math.round(Number(a?.h));
|
|
514
|
+
if (!frames.has(frame) || !theme || !(w >= 120 && h >= 80 && w <= ASK_MAX.side && h <= ASK_MAX.side && w * h <= ASK_MAX.area)) return json(res, 400, { error: `invalid ask for "${frame}"` });
|
|
515
|
+
asks.push({
|
|
516
|
+
frame,
|
|
517
|
+
theme,
|
|
518
|
+
w,
|
|
519
|
+
h
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
const origin = opts.origin?.() ?? `http://${req.headers.host ?? "localhost"}`;
|
|
523
|
+
const { ROUTE } = await import("./cli.mjs").then((n) => n.t);
|
|
524
|
+
const gen = opts.bakeGen?.() ?? 0;
|
|
525
|
+
return json(res, 200, {
|
|
526
|
+
gen,
|
|
527
|
+
answers: await bakeBatch({
|
|
528
|
+
root,
|
|
529
|
+
gen,
|
|
530
|
+
asks,
|
|
531
|
+
live: () => opts.bakeGen?.() ?? 0,
|
|
532
|
+
log: (a) => {
|
|
533
|
+
if (a.ok) console.log(` bake: ${a.frame} ${a.theme} ${a.w}x${a.h} - ${a.targets.length} effects, ${a.rejected} stay live, ${a.ms} ms`);
|
|
534
|
+
else console.log(` bake: ${a.frame} ${a.theme} ${a.w}x${a.h} - stays live: ${a.error}`);
|
|
535
|
+
},
|
|
536
|
+
urlFor: (ask) => {
|
|
537
|
+
const f = frames.get(ask.frame);
|
|
538
|
+
return f.kind === "html" ? `${origin}/${f.file}?theme=${encodeURIComponent(ask.theme)}` : `${origin}${ROUTE}/frame/?id=${encodeURIComponent(ask.frame)}&theme=${encodeURIComponent(ask.theme)}`;
|
|
539
|
+
},
|
|
540
|
+
urlBase: `${ROUTE}/bakes`
|
|
541
|
+
})
|
|
542
|
+
});
|
|
543
|
+
}
|
|
492
544
|
if (path === "poster" && req.method === "GET") {
|
|
493
545
|
if (!ownerGated(req)) return json(res, 403, { error: "forbidden" });
|
|
494
546
|
const src = url.searchParams.get("src") ?? "";
|
|
495
|
-
const { ensurePoster, isLocalClip } = await import("./poster-
|
|
496
|
-
const { isLocalAssetRef } = await import("./build-
|
|
547
|
+
const { ensurePoster, isLocalClip } = await import("./poster-DNh6N27C.mjs");
|
|
548
|
+
const { isLocalAssetRef } = await import("./build-7ed5H2vT.mjs");
|
|
497
549
|
if (!isLocalAssetRef(src) || !isLocalClip(src)) return json(res, 400, { error: "src must be a clip under design/assets/" });
|
|
498
550
|
const r = await ensurePoster(join(root, "design", "assets"), src);
|
|
499
551
|
if (!r.ok) return json(res, r.error.includes("does not exist") ? 404 : 503, { error: r.error });
|
|
@@ -681,6 +733,24 @@ function routesMiddleware(server, clientDir) {
|
|
|
681
733
|
} catch {
|
|
682
734
|
return next();
|
|
683
735
|
}
|
|
736
|
+
} else if (path.startsWith(`/__mv/bakes/`)) {
|
|
737
|
+
const m = /^\/(\d+)\/([a-f0-9]{16})\/(\d+)\.png$/.exec(path.slice(`${ROUTE}/bakes`.length));
|
|
738
|
+
if (!m) {
|
|
739
|
+
res.statusCode = 404;
|
|
740
|
+
return res.end();
|
|
741
|
+
}
|
|
742
|
+
try {
|
|
743
|
+
const base = realpathSync(join(server.config.root, "design", ".local", "bakes"));
|
|
744
|
+
const file = realpathSync(join(base, m[1], m[2], `${m[3]}.png`));
|
|
745
|
+
if (!file.startsWith(base + sep)) throw new Error("outside the cache");
|
|
746
|
+
const png = readFileSync(file);
|
|
747
|
+
res.setHeader("content-type", "image/png");
|
|
748
|
+
res.setHeader("cache-control", "public, max-age=31536000, immutable");
|
|
749
|
+
return res.end(png);
|
|
750
|
+
} catch {
|
|
751
|
+
res.statusCode = 404;
|
|
752
|
+
return res.end();
|
|
753
|
+
}
|
|
684
754
|
} else if (path === `/__mv/bridge.js`) {
|
|
685
755
|
res.setHeader("content-type", "text/javascript");
|
|
686
756
|
return res.end(readFileSync(join(clientDir, "frame-host", "bridge.js"), "utf8"));
|
|
@@ -846,27 +916,19 @@ function marverPlugin(ctx) {
|
|
|
846
916
|
const bridge = "/@fs/" + join(clientDir, "frame-host", "bridge.js").split("\\").join("/") + "?html=1";
|
|
847
917
|
return {
|
|
848
918
|
html,
|
|
849
|
-
tags: [
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
919
|
+
tags: [{
|
|
920
|
+
tag: "script",
|
|
921
|
+
attrs: { type: "module" },
|
|
922
|
+
children: `import '${VIRTUAL_THEME}'`,
|
|
923
|
+
injectTo: "head-prepend"
|
|
924
|
+
}, {
|
|
925
|
+
tag: "script",
|
|
926
|
+
attrs: {
|
|
927
|
+
type: "module",
|
|
928
|
+
src: bridge
|
|
854
929
|
},
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
attrs: { type: "module" },
|
|
858
|
-
children: `import '${VIRTUAL_THEME}'`,
|
|
859
|
-
injectTo: "head-prepend"
|
|
860
|
-
},
|
|
861
|
-
{
|
|
862
|
-
tag: "script",
|
|
863
|
-
attrs: {
|
|
864
|
-
type: "module",
|
|
865
|
-
src: bridge
|
|
866
|
-
},
|
|
867
|
-
injectTo: "head-prepend"
|
|
868
|
-
}
|
|
869
|
-
]
|
|
930
|
+
injectTo: "head-prepend"
|
|
931
|
+
}]
|
|
870
932
|
};
|
|
871
933
|
}
|
|
872
934
|
},
|
|
@@ -888,6 +950,7 @@ function marverPlugin(ctx) {
|
|
|
888
950
|
return [];
|
|
889
951
|
},
|
|
890
952
|
configureServer(server) {
|
|
953
|
+
let bakeGen = Date.now();
|
|
891
954
|
server.middlewares.use((req, res, next) => {
|
|
892
955
|
if (req.url && req.url.includes("/frame-host/registry.ts")) {
|
|
893
956
|
const orig = res.setHeader.bind(res);
|
|
@@ -917,7 +980,8 @@ function marverPlugin(ctx) {
|
|
|
917
980
|
};
|
|
918
981
|
server.middlewares.use(apiMiddleware(root, {
|
|
919
982
|
viewports: config.viewports,
|
|
920
|
-
origin: listeningOrigin
|
|
983
|
+
origin: listeningOrigin,
|
|
984
|
+
bakeGen: () => bakeGen
|
|
921
985
|
}));
|
|
922
986
|
server.middlewares.use(routesMiddleware(server, clientDir));
|
|
923
987
|
devServer = server;
|
|
@@ -955,6 +1019,21 @@ function marverPlugin(ctx) {
|
|
|
955
1019
|
for (const mod of themeModules()) server.moduleGraph.invalidateModule(mod);
|
|
956
1020
|
pushTheme();
|
|
957
1021
|
};
|
|
1022
|
+
const localDir = join(root, "design", ".local"), boardsDirGen = join(root, "design", "boards"), manifestPath = join(root, "design", "manifest.json");
|
|
1023
|
+
const bumpGen = (f) => {
|
|
1024
|
+
if (f.startsWith(localDir) || f.startsWith(boardsDirGen) || f === manifestPath || f.includes("/node_modules/")) return;
|
|
1025
|
+
bakeGen++;
|
|
1026
|
+
prune();
|
|
1027
|
+
};
|
|
1028
|
+
const prune = () => setTimeout(async () => {
|
|
1029
|
+
try {
|
|
1030
|
+
(await import("./bake-kaf5kGZ7.mjs")).pruneBakes(root, bakeGen);
|
|
1031
|
+
} catch {}
|
|
1032
|
+
}, 500);
|
|
1033
|
+
prune();
|
|
1034
|
+
server.watcher.on("add", bumpGen);
|
|
1035
|
+
server.watcher.on("unlink", bumpGen);
|
|
1036
|
+
server.watcher.on("change", bumpGen);
|
|
958
1037
|
server.watcher.on("add", (f) => {
|
|
959
1038
|
if (inScope(f)) {
|
|
960
1039
|
regen();
|
|
@@ -967,7 +1046,7 @@ function marverPlugin(ctx) {
|
|
|
967
1046
|
rescanTheme();
|
|
968
1047
|
}
|
|
969
1048
|
});
|
|
970
|
-
server.watcher.on("change", (f) => inScope(f) && (/\.(tsx|jsx)$/.test(f) || f.endsWith("_brief.md")) && regen());
|
|
1049
|
+
server.watcher.on("change", (f) => inScope(f) && (/\.(tsx|jsx)$/.test(f) || f.endsWith("_brief.md") || isNoteFile(f)) && regen());
|
|
971
1050
|
const configFile = join(root, "design", "config.ts");
|
|
972
1051
|
server.watcher.on("change", (f) => {
|
|
973
1052
|
if (f !== configFile) return;
|
|
@@ -1087,7 +1166,7 @@ function marverPlugin(ctx) {
|
|
|
1087
1166
|
});
|
|
1088
1167
|
return;
|
|
1089
1168
|
}
|
|
1090
|
-
const { shootFrame, shootBatch, resolveFrames } = await import("./shot-
|
|
1169
|
+
const { shootFrame, shootBatch, resolveFrames } = await import("./shot-DMDvDbeP.mjs");
|
|
1091
1170
|
if (ways[0] === "frame") {
|
|
1092
1171
|
if (typeof spec.frame !== "string") {
|
|
1093
1172
|
write({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { capture, t as findChrome } from "./shot-
|
|
1
|
+
import { capture, t as findChrome } from "./shot-DMDvDbeP.mjs";
|
|
2
2
|
import { constants, copyFileSync, existsSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, join, sep } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|