@autono/open-pages 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/{build-CCoxDxL-.js → build-Cpod-K9R.js} +9 -4
  2. package/dist/cli/bin.js +5 -5
  3. package/dist/{config-D_U_aCQz.js → config-CYLBzMMb.js} +1077 -18
  4. package/dist/{config-p-TWNXbv.d.ts → config-IkwICvOe.d.ts} +2 -1
  5. package/dist/{dev-CTrB263u.js → dev-B7x-G-wk.js} +1 -1
  6. package/dist/{export-enKL9R7x.js → export-BxMksWEc.js} +74 -12
  7. package/dist/index.d.ts +23 -3
  8. package/dist/locale/index.d.ts +1 -1
  9. package/dist/locale/index.js +96 -0
  10. package/dist/{preview-Dmzyd1cf.js → preview-CMv1lGOC.js} +1 -1
  11. package/dist/{types-DDdUFUIS.d.ts → types-B-9WXL93.d.ts} +24 -0
  12. package/dist/vite/index.d.ts +1 -1
  13. package/dist/vite/index.js +1 -1
  14. package/package.json +1 -1
  15. package/skills/apply-comments/SKILL.md +6 -4
  16. package/skills/create-email/SKILL.md +166 -0
  17. package/skills/create-theme/SKILL.md +31 -7
  18. package/skills/create-theme/references/email-theme-from-css.mjs +185 -0
  19. package/skills/create-theme/references/email-theme.md +48 -0
  20. package/skills/current-page/SKILL.md +3 -3
  21. package/src/app/app.tsx +4 -0
  22. package/src/app/components/command/home-command-menu.tsx +19 -2
  23. package/src/app/components/selection-note.tsx +71 -0
  24. package/src/app/components/sidebar/folder-item.tsx +9 -2
  25. package/src/app/components/sidebar/sidebar.tsx +10 -0
  26. package/src/app/frame/email-inspect.ts +17 -0
  27. package/src/app/lib/emails.ts +17 -0
  28. package/src/app/lib/frame.ts +15 -4
  29. package/src/app/lib/page-thumb.tsx +11 -3
  30. package/src/app/lib/sdk.ts +22 -0
  31. package/src/app/routes/email.tsx +424 -0
  32. package/src/app/routes/emails.tsx +94 -0
  33. package/src/app/routes/home-shell.tsx +21 -2
  34. package/src/app/routes/page.tsx +9 -50
  35. package/src/app/virtual.d.ts +10 -0
  36. package/src/locale/en.ts +25 -0
  37. package/src/locale/ja.ts +25 -0
  38. package/src/locale/types.ts +24 -0
  39. package/src/locale/zh-cn.ts +25 -0
  40. package/src/locale/zh-tw.ts +25 -0
  41. package/workspace/components.json +3 -1
  42. package/workspace/styles/globals.css +8 -2
  43. package/dist/open-pages-plugin-C8cFhMB9.js +0 -662
  44. package/dist/version-DZrXLoAD.js +0 -42
@@ -1,662 +0,0 @@
1
- import { createRequire } from "node:module";
2
- import path from "node:path";
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
- import fs from "node:fs/promises";
5
- import { loadConfigFromFile, normalizePath } from "vite";
6
- import { parse } from "@babel/parser";
7
- import fg from "fast-glob";
8
- //#region src/editing/page-ops.ts
9
- const PAGE_ID_RE = /^[a-z0-9_-]+$/i;
10
- function validatePageName(v) {
11
- if (typeof v !== "string") return null;
12
- const trimmed = v.trim();
13
- if (trimmed.length < 1 || trimmed.length > 80) return null;
14
- return trimmed;
15
- }
16
- function unwrapExpression(node) {
17
- let current = node;
18
- while (current && (current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression")) current = current.expression;
19
- return current;
20
- }
21
- function readMetaTitleInSource(source) {
22
- let ast;
23
- try {
24
- ast = parse(source, {
25
- sourceType: "module",
26
- plugins: ["typescript", "jsx"],
27
- errorRecovery: true
28
- });
29
- } catch {
30
- return { kind: "unsupported" };
31
- }
32
- const body = ast.program?.body ?? [];
33
- for (const stmt of body) {
34
- if (stmt.type !== "ExportNamedDeclaration") continue;
35
- const decl = stmt.declaration;
36
- if (!decl || decl.type !== "VariableDeclaration") continue;
37
- const declarations = decl.declarations ?? [];
38
- for (const d of declarations) {
39
- const id = d.id;
40
- if (!id || id.type !== "Identifier" || id.name !== "meta") continue;
41
- const init = unwrapExpression(d.init);
42
- if (!init || init.type !== "ObjectExpression") return { kind: "unsupported" };
43
- const properties = init.properties ?? [];
44
- for (const property of properties) {
45
- if (property.type !== "ObjectProperty" || property.computed) continue;
46
- const key = property.key;
47
- if ((key?.type === "Identifier" ? key.name : key?.type === "StringLiteral" ? key.value : void 0) !== "title") continue;
48
- const value = property.value;
49
- if (value?.type === "StringLiteral" && typeof value.value === "string") return {
50
- kind: "found",
51
- title: value.value
52
- };
53
- if (value?.type === "TemplateLiteral") {
54
- const expressions = value.expressions ?? [];
55
- const firstValue = (value.quasis ?? [])[0]?.value;
56
- const cooked = firstValue?.cooked;
57
- const raw = firstValue?.raw;
58
- if (expressions.length === 0 && typeof (cooked ?? raw) === "string") return {
59
- kind: "found",
60
- title: cooked ?? raw
61
- };
62
- }
63
- return { kind: "unsupported" };
64
- }
65
- return { kind: "missing" };
66
- }
67
- }
68
- return { kind: "missing" };
69
- }
70
- async function rmPageDir(pagesRoot, pageId) {
71
- if (!PAGE_ID_RE.test(pageId)) return false;
72
- const dir = path.resolve(pagesRoot, pageId);
73
- if (!dir.startsWith(pagesRoot + path.sep)) return false;
74
- try {
75
- await fs.rm(dir, {
76
- recursive: true,
77
- force: true
78
- });
79
- return true;
80
- } catch {
81
- return false;
82
- }
83
- }
84
- async function duplicatePageDir(pagesRoot, pageId, desiredId) {
85
- if (!PAGE_ID_RE.test(pageId)) return {
86
- ok: false,
87
- status: 400,
88
- error: "invalid pageId"
89
- };
90
- const root = path.resolve(pagesRoot);
91
- const srcDir = path.resolve(root, pageId);
92
- if (!srcDir.startsWith(root + path.sep)) return {
93
- ok: false,
94
- status: 400,
95
- error: "invalid pageId"
96
- };
97
- try {
98
- await fs.access(path.join(srcDir, "index.tsx"));
99
- } catch {
100
- return {
101
- ok: false,
102
- status: 404,
103
- error: "page not found"
104
- };
105
- }
106
- let newId;
107
- if (desiredId !== void 0) {
108
- if (!PAGE_ID_RE.test(desiredId)) return {
109
- ok: false,
110
- status: 400,
111
- error: "invalid newId"
112
- };
113
- newId = desiredId;
114
- const dstDir = path.resolve(root, newId);
115
- if (!dstDir.startsWith(root + path.sep)) return {
116
- ok: false,
117
- status: 400,
118
- error: "invalid newId"
119
- };
120
- try {
121
- await fs.access(dstDir);
122
- return {
123
- ok: false,
124
- status: 409,
125
- error: "page already exists"
126
- };
127
- } catch {}
128
- } else {
129
- let suffix = 1;
130
- while (true) {
131
- newId = suffix === 1 ? `${pageId}-copy` : `${pageId}-copy-${suffix}`;
132
- try {
133
- await fs.access(path.resolve(root, newId));
134
- suffix++;
135
- } catch {
136
- break;
137
- }
138
- }
139
- }
140
- const dstDir = path.resolve(root, newId);
141
- if (!dstDir.startsWith(root + path.sep)) return {
142
- ok: false,
143
- status: 400,
144
- error: "invalid newId"
145
- };
146
- const srcEntry = path.join(srcDir, "index.tsx");
147
- let copiedEntrySource;
148
- try {
149
- const source = await fs.readFile(srcEntry, "utf8");
150
- const metaTitle = readMetaTitleInSource(source);
151
- if (metaTitle.kind === "unsupported") return {
152
- ok: false,
153
- status: 422,
154
- error: "could not update copied page title"
155
- };
156
- const updated = updateMetaTitleInSource(source, `${metaTitle.kind === "found" ? metaTitle.title : pageId} (copy)`);
157
- if (updated === null) return {
158
- ok: false,
159
- status: 422,
160
- error: "could not update copied page title"
161
- };
162
- copiedEntrySource = updated;
163
- } catch {
164
- return {
165
- ok: false,
166
- status: 404,
167
- error: "page not found"
168
- };
169
- }
170
- try {
171
- await fs.cp(srcDir, dstDir, {
172
- recursive: true,
173
- errorOnExist: true,
174
- force: false
175
- });
176
- await fs.writeFile(path.join(dstDir, "index.tsx"), copiedEntrySource, "utf8");
177
- return {
178
- ok: true,
179
- pageId: newId
180
- };
181
- } catch (err) {
182
- if (err.code === "EEXIST") return {
183
- ok: false,
184
- status: 409,
185
- error: "page already exists"
186
- };
187
- return {
188
- ok: false,
189
- status: 500,
190
- error: String(err.message ?? err)
191
- };
192
- }
193
- }
194
- function resolvePageEntry(pagesRoot, pageId) {
195
- if (!PAGE_ID_RE.test(pageId)) return null;
196
- const dir = path.resolve(pagesRoot, pageId);
197
- if (!dir.startsWith(pagesRoot + path.sep)) return null;
198
- return path.join(dir, "index.tsx");
199
- }
200
- function escapeSingleQuoted(s) {
201
- return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
202
- }
203
- /**
204
- * Rewrite (or insert) the `title` field in the page module's `export const meta`.
205
- *
206
- * Strategy:
207
- * 1. Find `export const meta` and brace-match its object literal.
208
- * 2. If the object already has a `title: '...'` entry, replace the literal.
209
- * 3. If the object exists but has no title, inject a new `title: '...'` line
210
- * as the first property (preserving the author's surrounding indentation).
211
- * 4. If there is no `meta` export at all, insert a fresh one right before
212
- * `export default`.
213
- *
214
- * Returns the rewritten source, or `null` if the file shape was too surprising
215
- * to touch safely (e.g. `export default` missing when we'd need to inject meta).
216
- */
217
- function updateMetaTitleInSource(source, title) {
218
- const newLiteral = `'${escapeSingleQuoted(title)}'`;
219
- const metaStart = source.search(/export\s+const\s+meta\b/);
220
- if (metaStart !== -1) {
221
- const eqIdx = source.indexOf("=", metaStart);
222
- if (eqIdx === -1) return null;
223
- const openBrace = source.indexOf("{", eqIdx);
224
- if (openBrace === -1) return null;
225
- let depth = 0;
226
- let closeBrace = -1;
227
- for (let i = openBrace; i < source.length; i++) {
228
- const ch = source[i];
229
- if (ch === "{") depth++;
230
- else if (ch === "}") {
231
- depth--;
232
- if (depth === 0) {
233
- closeBrace = i;
234
- break;
235
- }
236
- }
237
- }
238
- if (closeBrace === -1) return null;
239
- const body = source.slice(openBrace + 1, closeBrace);
240
- const titleRe = /(^|[\s,{])(title\s*:\s*)(['"`])((?:\\.|(?!\3).)*)\3/;
241
- const match = body.match(titleRe);
242
- if (match) {
243
- const newBody = body.replace(titleRe, `${match[1]}${match[2]}${newLiteral}`);
244
- return source.slice(0, openBrace + 1) + newBody + source.slice(closeBrace);
245
- }
246
- const firstIndentMatch = body.match(/\n([ \t]+)\S/);
247
- const insertion = `\n${firstIndentMatch ? firstIndentMatch[1] : " "}title: ${newLiteral}${body.replace(/^\s*\n?/, "").trim().length > 0 ? "," : ""}`;
248
- return source.slice(0, openBrace + 1) + insertion + body + source.slice(closeBrace);
249
- }
250
- const exportDefaultIdx = source.search(/export\s+default\b/);
251
- if (exportDefaultIdx === -1) return null;
252
- const insertion = `export const meta: PageMeta = { title: ${newLiteral} };\n\n`;
253
- return source.slice(0, exportDefaultIdx) + insertion + source.slice(exportDefaultIdx);
254
- }
255
- //#endregion
256
- //#region src/vite/open-pages-plugin.ts
257
- const CONFIG_FILE = "open-pages.config.ts";
258
- const PAGES_VMOD = "virtual:open-pages/pages";
259
- const CONFIG_VMOD = "virtual:open-pages/config";
260
- const FOLDERS_VMOD = "virtual:open-pages/folders";
261
- const PAGES_CSS_VMOD = "virtual:open-pages/pages.css";
262
- const WORKSPACE_SOURCE_DIRS = [
263
- "ui",
264
- "lib",
265
- "hooks"
266
- ];
267
- const ENTRY_GLOB = "*/index.{tsx,jsx,ts,js,html}";
268
- const REACT_ENTRY_RE = /^index\.(tsx|jsx|ts|js)$/;
269
- async function readFoldersManifest(file) {
270
- try {
271
- const raw = await fs.readFile(file, "utf8");
272
- const parsed = JSON.parse(raw);
273
- return {
274
- folders: Array.isArray(parsed.folders) ? parsed.folders : [],
275
- assignments: parsed.assignments && typeof parsed.assignments === "object" ? parsed.assignments : {}
276
- };
277
- } catch (err) {
278
- if (err.code === "ENOENT") return {
279
- folders: [],
280
- assignments: {}
281
- };
282
- throw err;
283
- }
284
- }
285
- function resolved(id) {
286
- return `\0${id}`;
287
- }
288
- function invalidatePagesModule(server) {
289
- const mod = server.moduleGraph.getModuleById(resolved(PAGES_VMOD));
290
- if (mod) server.moduleGraph.invalidateModule(mod);
291
- server.ws.send({ type: "full-reload" });
292
- }
293
- /**
294
- * Every `pages/<id>/index.*` entry. A folder with both a React entry and an
295
- * `index.html` is a React page — the HTML is then a plain asset of the page.
296
- */
297
- async function findPages(userCwd, pagesDir) {
298
- const abs = path.resolve(userCwd, pagesDir);
299
- if (!existsSync(abs)) return [];
300
- const hits = await fg(ENTRY_GLOB, {
301
- cwd: abs,
302
- absolute: true,
303
- onlyFiles: true
304
- });
305
- const byId = /* @__PURE__ */ new Map();
306
- for (const file of hits.sort()) {
307
- const [id, name] = path.relative(abs, file).split(path.sep);
308
- const kind = REACT_ENTRY_RE.test(name) ? "react" : "html";
309
- const existing = byId.get(id);
310
- if (!existing || existing.kind === "html" && kind === "react") byId.set(id, {
311
- id,
312
- kind,
313
- file
314
- });
315
- }
316
- return [...byId.values()].sort((a, b) => a.id.localeCompare(b.id));
317
- }
318
- const META_THEME_RE = /(?:^|[\s,{])theme\s*:\s*['"]([^'"]+)['"]/;
319
- const META_CREATED_AT_RE = /(?:^|[\s,{])createdAt\s*:\s*['"]([^'"]+)['"]/;
320
- const META_TITLE_RE = /(?:^|[\s,{])title\s*:\s*['"]([^'"]+)['"]/;
321
- const HTML_TITLE_RE = /<title[^>]*>([^<]*)<\/title>/i;
322
- const EMPTY_META = {
323
- title: null,
324
- theme: null,
325
- createdAt: null
326
- };
327
- function extractMeta(src) {
328
- const metaStart = src.search(/export\s+const\s+meta\b/);
329
- if (metaStart === -1) return EMPTY_META;
330
- const eqIdx = src.indexOf("=", metaStart);
331
- if (eqIdx === -1) return EMPTY_META;
332
- const openBrace = src.indexOf("{", eqIdx);
333
- if (openBrace === -1) return EMPTY_META;
334
- let depth = 0;
335
- let closeBrace = -1;
336
- for (let i = openBrace; i < src.length; i++) {
337
- const ch = src[i];
338
- if (ch === "{") depth++;
339
- else if (ch === "}") {
340
- depth--;
341
- if (depth === 0) {
342
- closeBrace = i;
343
- break;
344
- }
345
- }
346
- }
347
- if (closeBrace === -1) return EMPTY_META;
348
- const body = src.slice(openBrace + 1, closeBrace);
349
- return {
350
- title: body.match(META_TITLE_RE)?.[1] ?? null,
351
- theme: body.match(META_THEME_RE)?.[1] ?? null,
352
- createdAt: body.match(META_CREATED_AT_RE)?.[1] ?? null
353
- };
354
- }
355
- function extractHtmlMeta(src) {
356
- return {
357
- title: (src.match(HTML_TITLE_RE)?.[1]?.trim() ?? null) || null,
358
- theme: null,
359
- createdAt: null
360
- };
361
- }
362
- async function readPageMeta(entry) {
363
- try {
364
- const src = await fs.readFile(entry.file, "utf8");
365
- return entry.kind === "html" ? extractHtmlMeta(src) : extractMeta(src);
366
- } catch {
367
- return EMPTY_META;
368
- }
369
- }
370
- function parseCreatedAtMs(iso) {
371
- if (!iso) return null;
372
- const ms = Date.parse(iso);
373
- return Number.isFinite(ms) ? ms : null;
374
- }
375
- const warnedInvalidPageIds = /* @__PURE__ */ new Set();
376
- async function generatePagesModule(entries, isDev) {
377
- const scanned = await Promise.all(entries.map(async (entry) => {
378
- const importPath = isDev ? `@fs/${normalizePath(entry.file).replace(/^\/+/, "")}` : entry.file;
379
- const meta = await readPageMeta(entry);
380
- return {
381
- ...entry,
382
- importPath,
383
- meta,
384
- createdAt: parseCreatedAtMs(meta.createdAt)
385
- };
386
- }));
387
- const valid = scanned.filter((e) => PAGE_ID_RE.test(e.id));
388
- const ignored = scanned.filter((e) => !PAGE_ID_RE.test(e.id)).map((e) => e.id);
389
- const kinds = {};
390
- const themesMap = {};
391
- const createdAtMap = {};
392
- for (const e of valid) {
393
- kinds[e.id] = e.kind;
394
- if (e.meta.theme) themesMap[e.id] = e.meta.theme;
395
- if (e.createdAt !== null) createdAtMap[e.id] = e.createdAt;
396
- }
397
- const importTokens = JSON.stringify(Object.fromEntries(valid.filter((e) => e.kind === "react").map((e) => [e.id, 0])));
398
- const devRuntime = isDev ? `
399
- const pageImportTokens = ${importTokens};
400
- if (import.meta.hot) {
401
- import.meta.hot.on('open-pages:page-changed', (data) => {
402
- const ids = Array.isArray(data?.pageIds) ? data.pageIds : data?.pageId ? [data.pageId] : [];
403
- const token = Date.now();
404
- for (const id of ids) {
405
- if (Object.prototype.hasOwnProperty.call(pageImportTokens, id)) pageImportTokens[id] = token;
406
- }
407
- });
408
- }
409
- ` : "";
410
- const cases = valid.map((e) => {
411
- if (e.kind === "html") {
412
- const mod = { meta: { title: e.meta.title ?? e.id } };
413
- return ` case ${JSON.stringify(e.id)}: return Promise.resolve(${JSON.stringify(mod)});`;
414
- }
415
- const importExpr = isDev ? `import(/* @vite-ignore */ import.meta.env.BASE_URL + ${JSON.stringify(`${e.importPath}?t=`)} + pageImportTokens[${JSON.stringify(e.id)}])` : `import(${JSON.stringify(e.importPath)})`;
416
- return ` case ${JSON.stringify(e.id)}: return ${importExpr};`;
417
- }).join("\n");
418
- return {
419
- code: `// virtual:open-pages/pages — generated
420
- export const pageIds = ${JSON.stringify(valid.map((e) => e.id))};
421
- export const pageKinds = ${JSON.stringify(kinds)};
422
- export const pageThemes = ${JSON.stringify(themesMap)};
423
- export const pageCreatedAt = ${JSON.stringify(createdAtMap)};
424
- ${devRuntime}
425
-
426
- export async function loadPage(id) {
427
- switch (id) {
428
- ${cases}
429
- default: throw new Error('Page not found: ' + id);
430
- }
431
- }
432
- `,
433
- ignored
434
- };
435
- }
436
- /**
437
- * Tailwind for page source. The workspace UI has its own stylesheet rooted
438
- * at the core app; pages live in the user's project, so they get a generated
439
- * stylesheet whose `@source` points at their directories. Written under
440
- * node_modules so Tailwind resolves `tailwindcss` from the workspace.
441
- */
442
- function pagesCssFile(userCwd) {
443
- return path.join(userCwd, "node_modules", ".open-pages", "pages.css");
444
- }
445
- /**
446
- * The workspace's own stylesheet when it has one (the shadcn `tailwind.css`
447
- * entry from components.json, default `styles/globals.css`) — it carries the
448
- * theme tokens every installed component reads. Falls back to the generated
449
- * file for workspaces scaffolded without one.
450
- */
451
- function workspaceCssFile(userCwd) {
452
- let rel = "styles/globals.css";
453
- try {
454
- const cfg = JSON.parse(readFileSync(path.join(userCwd, "components.json"), "utf8"));
455
- if (typeof cfg.tailwind?.css === "string" && cfg.tailwind.css) rel = cfg.tailwind.css;
456
- } catch {}
457
- const abs = path.resolve(userCwd, rel);
458
- return existsSync(abs) ? abs : null;
459
- }
460
- const TAILWIND_CSS = normalizePath(createRequire(import.meta.url).resolve("tailwindcss/index.css"));
461
- function writePagesCss(userCwd, sources) {
462
- const file = pagesCssFile(userCwd);
463
- const lines = [`@import ${JSON.stringify(TAILWIND_CSS)} source(none);`];
464
- for (const dir of sources) lines.push(`@source ${JSON.stringify(normalizePath(dir))};`);
465
- mkdirSync(path.dirname(file), { recursive: true });
466
- writeFileSync(file, `${lines.join("\n")}\n`, "utf8");
467
- return file;
468
- }
469
- function rewriteHtmlUrls(html, pageDir) {
470
- const prefix = `/@fs/${normalizePath(pageDir).replace(/^\/+/, "")}/`;
471
- return html.replace(/(\s(?:src|href)=)(["'])(?!\/|[a-z][a-z0-9+.-]*:|#|\2)(?:\.\/)?([^"']+)\2/gi, (_m, attr, quote, rel) => `${attr}${quote}${prefix}${rel}${quote}`);
472
- }
473
- function pagesSignature(entries) {
474
- return entries.map((e) => `${e.id}:${e.kind}:${e.file}`).join(",");
475
- }
476
- function openPagesPlugin(opts) {
477
- const { userCwd, config, coreVersion } = opts;
478
- const pagesDir = config.pagesDir ?? "pages";
479
- const themesDir = config.themesDir ?? "themes";
480
- const pagesRoot = path.resolve(userCwd, pagesDir);
481
- const themesRoot = path.resolve(userCwd, themesDir);
482
- const foldersManifestPath = path.join(pagesRoot, ".folders.json");
483
- let isDev = false;
484
- let cssFile = "";
485
- let generatedSignature = "";
486
- const pageIdForEntry = (p) => {
487
- const rel = path.relative(pagesRoot, p);
488
- if (rel.startsWith("..") || path.isAbsolute(rel)) return null;
489
- const parts = rel.split(path.sep);
490
- if (parts.length !== 2) return null;
491
- if (REACT_ENTRY_RE.test(parts[1])) return {
492
- id: parts[0],
493
- kind: "react"
494
- };
495
- if (parts[1] === "index.html") return {
496
- id: parts[0],
497
- kind: "html"
498
- };
499
- return null;
500
- };
501
- let pageChangeTimer = null;
502
- const pendingPageChanges = /* @__PURE__ */ new Set();
503
- const queuePageChanged = (server, id) => {
504
- pendingPageChanges.add(id);
505
- if (pageChangeTimer) clearTimeout(pageChangeTimer);
506
- pageChangeTimer = setTimeout(() => {
507
- pageChangeTimer = null;
508
- const mod = server.moduleGraph.getModuleById(resolved(PAGES_VMOD));
509
- if (mod) server.moduleGraph.invalidateModule(mod);
510
- const pageIds = Array.from(pendingPageChanges);
511
- pendingPageChanges.clear();
512
- server.ws.send({
513
- type: "custom",
514
- event: "open-pages:page-changed",
515
- data: { pageIds }
516
- });
517
- }, 100);
518
- };
519
- return {
520
- name: "open-pages",
521
- config(_c, env) {
522
- isDev = env.command === "serve";
523
- cssFile = workspaceCssFile(userCwd) ?? writePagesCss(userCwd, [
524
- pagesRoot,
525
- themesRoot,
526
- ...WORKSPACE_SOURCE_DIRS.map((d) => path.join(userCwd, d))
527
- ]);
528
- return { server: { fs: { allow: [userCwd] } } };
529
- },
530
- resolveId(id) {
531
- if (id === PAGES_VMOD) return resolved(PAGES_VMOD);
532
- if (id === CONFIG_VMOD) return resolved(CONFIG_VMOD);
533
- if (id === FOLDERS_VMOD) return resolved(FOLDERS_VMOD);
534
- if (id === PAGES_CSS_VMOD) return cssFile;
535
- return null;
536
- },
537
- async load(id) {
538
- if (id === resolved(PAGES_VMOD)) {
539
- const entries = await findPages(userCwd, pagesDir);
540
- generatedSignature = pagesSignature(entries);
541
- const { code, ignored } = await generatePagesModule(entries, isDev);
542
- for (const pageId of ignored) {
543
- if (warnedInvalidPageIds.has(pageId)) continue;
544
- warnedInvalidPageIds.add(pageId);
545
- this.warn(`Ignoring page folder "${pageId}": page ids must match ${PAGE_ID_RE} (lowercase/uppercase letters, digits, "-", "_"). Rename the folder under "${pagesDir}/" to a kebab-case id so it appears in the browser and can be moved into folders.`);
546
- }
547
- return code;
548
- }
549
- if (id === resolved(CONFIG_VMOD)) {
550
- const userBuild = config.build ?? {};
551
- const buildResolved = isDev ? {
552
- showPageBrowser: true,
553
- showPageUi: true
554
- } : {
555
- showPageBrowser: userBuild.showPageBrowser ?? true,
556
- showPageUi: userBuild.showPageUi ?? true
557
- };
558
- const resolvedConfig = {
559
- ...config,
560
- build: buildResolved,
561
- version: coreVersion
562
- };
563
- return `export default ${JSON.stringify(resolvedConfig)};\n`;
564
- }
565
- if (id === resolved(FOLDERS_VMOD)) {
566
- const manifest = await readFoldersManifest(foldersManifestPath);
567
- return `export default ${JSON.stringify(manifest)};\n`;
568
- }
569
- return null;
570
- },
571
- handleHotUpdate(ctx) {
572
- const entry = pageIdForEntry(ctx.file);
573
- if (!entry) return;
574
- queuePageChanged(ctx.server, entry.id);
575
- return [];
576
- },
577
- configureServer(server) {
578
- let reloadTimer = null;
579
- const reload = () => {
580
- if (reloadTimer) clearTimeout(reloadTimer);
581
- reloadTimer = setTimeout(async () => {
582
- reloadTimer = null;
583
- try {
584
- if (pagesSignature(await findPages(userCwd, pagesDir)) === generatedSignature) return;
585
- } catch (err) {
586
- server.config.logger.error(`[open-pages] failed to rescan pages: ${err}`, { error: err instanceof Error ? err : void 0 });
587
- }
588
- invalidatePagesModule(server);
589
- }, 150);
590
- };
591
- const isPageDir = (p) => path.dirname(p) === pagesRoot;
592
- if (existsSync(pagesRoot)) server.watcher.add(pagesRoot);
593
- server.watcher.on("add", (p) => {
594
- if (pageIdForEntry(p)) reload();
595
- });
596
- server.watcher.on("unlink", (p) => {
597
- if (pageIdForEntry(p)) reload();
598
- });
599
- server.watcher.on("addDir", (p) => {
600
- if (isPageDir(p)) reload();
601
- });
602
- server.watcher.on("unlinkDir", (p) => {
603
- if (isPageDir(p)) reload();
604
- });
605
- server.watcher.on("change", (p) => {
606
- const entry = pageIdForEntry(p);
607
- if (entry?.kind === "html") queuePageChanged(server, entry.id);
608
- });
609
- let foldersTimer = null;
610
- const invalidateFolders = () => {
611
- if (foldersTimer) clearTimeout(foldersTimer);
612
- foldersTimer = setTimeout(() => {
613
- foldersTimer = null;
614
- const mod = server.moduleGraph.getModuleById(resolved(FOLDERS_VMOD));
615
- if (mod) server.moduleGraph.invalidateModule(mod);
616
- }, 100);
617
- };
618
- server.watcher.add(foldersManifestPath);
619
- server.watcher.on("change", (p) => {
620
- if (p === foldersManifestPath) invalidateFolders();
621
- });
622
- server.watcher.on("add", (p) => {
623
- if (p === foldersManifestPath) invalidateFolders();
624
- });
625
- server.watcher.on("unlink", (p) => {
626
- if (p === foldersManifestPath) invalidateFolders();
627
- });
628
- server.middlewares.use("/__page", async (req, res, next) => {
629
- const m = new URL(req.url ?? "/", "http://local").pathname.match(/^\/([^/]+)\/(?:index\.html)?$/);
630
- if (!m || (req.method ?? "GET") !== "GET") return next();
631
- const pageId = decodeURIComponent(m[1]);
632
- if (!PAGE_ID_RE.test(pageId)) return next();
633
- const pageDir = path.join(pagesRoot, pageId);
634
- const file = path.join(pageDir, "index.html");
635
- let raw;
636
- try {
637
- raw = await fs.readFile(file, "utf8");
638
- } catch {
639
- return next();
640
- }
641
- try {
642
- const html = await server.transformIndexHtml(`/__page/${pageId}/index.html`, rewriteHtmlUrls(raw, pageDir), req.originalUrl);
643
- res.statusCode = 200;
644
- res.setHeader("content-type", "text/html");
645
- res.end(html);
646
- } catch (err) {
647
- next(err);
648
- }
649
- });
650
- }
651
- };
652
- }
653
- async function loadUserConfig(userCwd) {
654
- const file = path.join(userCwd, CONFIG_FILE);
655
- if (!existsSync(file)) return {};
656
- return (await loadConfigFromFile({
657
- command: "serve",
658
- mode: "development"
659
- }, file, userCwd, "silent"))?.config ?? {};
660
- }
661
- //#endregion
662
- export { openPagesPlugin as a, resolvePageEntry as c, validatePageName as d, loadUserConfig as i, rmPageDir as l, findPages as n, PAGE_ID_RE as o, invalidatePagesModule as r, duplicatePageDir as s, extractMeta as t, updateMetaTitleInSource as u };
@@ -1,42 +0,0 @@
1
- import "node:module";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { existsSync, readFileSync } from "node:fs";
5
- //#region \0rolldown/runtime.js
6
- var __defProp = Object.defineProperty;
7
- var __exportAll = (all, no_symbols) => {
8
- let target = {};
9
- for (var name in all) __defProp(target, name, {
10
- get: all[name],
11
- enumerable: true
12
- });
13
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
14
- return target;
15
- };
16
- //#endregion
17
- //#region src/vite/version.ts
18
- var version_exports = /* @__PURE__ */ __exportAll({
19
- APP_ROOT: () => APP_ROOT,
20
- PKG_ROOT: () => PKG_ROOT,
21
- readCoreVersion: () => readCoreVersion
22
- });
23
- function findPackageRoot(fromFile) {
24
- let dir = path.dirname(fromFile);
25
- while (dir !== path.dirname(dir)) {
26
- if (existsSync(path.join(dir, "package.json"))) return dir;
27
- dir = path.dirname(dir);
28
- }
29
- throw new Error(`Could not find package.json walking up from ${fromFile}`);
30
- }
31
- const PKG_ROOT = findPackageRoot(fileURLToPath(import.meta.url));
32
- const APP_ROOT = path.join(PKG_ROOT, "src", "app");
33
- function readCoreVersion() {
34
- try {
35
- const raw = readFileSync(path.join(PKG_ROOT, "package.json"), "utf8");
36
- return JSON.parse(raw).version ?? "0.0.0";
37
- } catch {
38
- return "0.0.0";
39
- }
40
- }
41
- //#endregion
42
- export { version_exports as i, PKG_ROOT as n, readCoreVersion as r, APP_ROOT as t };