@homepages/template-kit 0.9.0-dev-20260719205406 → 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 (146) hide show
  1. package/CHANGELOG.md +60 -59
  2. package/README.md +14 -4
  3. package/dist/contracts/fill-treatments.d.ts +23 -0
  4. package/dist/fixtures/sample-property.d.ts +4 -7
  5. package/dist/fixtures/sample-property.js +41 -97
  6. package/dist/fixtures/scenario.d.ts +1 -1
  7. package/dist/fixtures/scenarios/for-rent-condo.js +24 -44
  8. package/dist/fixtures/scenarios/luxury-multi-unit.js +1 -1
  9. package/dist/fixtures/scenarios/sparse-single-family.js +14 -26
  10. package/dist/fixtures.d.ts +2 -2
  11. package/dist/fixtures.js +1 -2
  12. package/dist/index.d.ts +2 -2
  13. package/dist/index.js +2 -2
  14. package/dist/islands/discover.js +1 -1
  15. package/dist/islands/esbuild-plugin.js +1 -1
  16. package/dist/islands/wrap.d.ts +9 -1
  17. package/dist/manifest.json +940 -0
  18. package/dist/media/pack.d.ts +49 -0
  19. package/dist/media/pack.js +69 -0
  20. package/dist/media/resolve-fixture-media.d.ts +22 -0
  21. package/dist/media/resolve-fixture-media.js +73 -0
  22. package/dist/media/resolve-media.js +195 -0
  23. package/dist/package.js +1 -1
  24. package/dist/schema/property-facts.d.ts +2 -2
  25. package/dist/schema/resolve-section-ref.d.ts +4 -0
  26. package/dist/schema/rows.d.ts +9 -0
  27. package/dist/schema/section-nav.d.ts +5 -1
  28. package/dist/schema/slot-types.d.ts +1 -1
  29. package/dist/ssr.d.ts +19 -2
  30. package/dist/ssr.js +32 -13
  31. package/guide/INDEX.md +5 -0
  32. package/guide/check.md +1 -0
  33. package/guide/dev.md +1 -0
  34. package/guide/llms.txt +11 -3
  35. package/guide/recipes/image-slot-crop.md +6 -6
  36. package/guide/recipes/pick-a-scenario.md +13 -5
  37. package/guide/schema-system.md +36 -12
  38. package/guide/upgrading.md +12 -8
  39. package/package.json +4 -16
  40. package/dist/canvas-core/chrome-css.d.ts +0 -24
  41. package/dist/canvas-core/chrome-css.js +0 -403
  42. package/dist/canvas-core/chrome.d.ts +0 -49
  43. package/dist/canvas-core/chrome.js +0 -147
  44. package/dist/canvas-core/frame.d.ts +0 -20
  45. package/dist/canvas-core/frame.js +0 -23
  46. package/dist/canvas-core/hidden-tabs.d.ts +0 -16
  47. package/dist/canvas-core/hidden-tabs.js +0 -84
  48. package/dist/canvas-core/resolve-target.js +0 -55
  49. package/dist/canvas-core/scroll.d.ts +0 -10
  50. package/dist/canvas-core/scroll.js +0 -15
  51. package/dist/canvas-core/section-dom.d.ts +0 -6
  52. package/dist/canvas-core/section-dom.js +0 -43
  53. package/dist/canvas-core/srcdoc.d.ts +0 -8
  54. package/dist/canvas-core/srcdoc.js +0 -77
  55. package/dist/canvas-core/types.d.ts +0 -76
  56. package/dist/canvas-core.d.ts +0 -9
  57. package/dist/canvas-core.js +0 -9
  58. package/dist/cli/check/config.js +0 -41
  59. package/dist/cli/check/css.js +0 -131
  60. package/dist/cli/check/diagnostics.js +0 -37
  61. package/dist/cli/check/index.js +0 -94
  62. package/dist/cli/check/loader.js +0 -149
  63. package/dist/cli/check/paths.js +0 -15
  64. package/dist/cli/check/relativize.js +0 -18
  65. package/dist/cli/check/render-invariants.js +0 -24
  66. package/dist/cli/check/resolve-tool.js +0 -38
  67. package/dist/cli/check/section-assets.js +0 -58
  68. package/dist/cli/check/stages/deps.js +0 -337
  69. package/dist/cli/check/stages/lint.js +0 -46
  70. package/dist/cli/check/stages/render.js +0 -97
  71. package/dist/cli/check/stages/size.js +0 -274
  72. package/dist/cli/check/stages/tree.js +0 -206
  73. package/dist/cli/check/stages/typecheck.js +0 -46
  74. package/dist/cli/check/stages/validate/catalog-exhaustiveness.js +0 -18
  75. package/dist/cli/check/stages/validate/facts.js +0 -19
  76. package/dist/cli/check/stages/validate/formatter-contract.js +0 -14
  77. package/dist/cli/check/stages/validate/group-contract.js +0 -21
  78. package/dist/cli/check/stages/validate/index.js +0 -66
  79. package/dist/cli/check/stages/validate/list-scalar-contract.js +0 -22
  80. package/dist/cli/check/stages/validate/nav-contract.js +0 -92
  81. package/dist/cli/check/stages/validate/nested-slot-contract.js +0 -12
  82. package/dist/cli/check/stages/validate/object-list-contract.js +0 -31
  83. package/dist/cli/check/stages/validate/orchestrator.js +0 -241
  84. package/dist/cli/check/stages/validate/produced-by-contract.js +0 -18
  85. package/dist/cli/check/stages/validate/row-contract.js +0 -31
  86. package/dist/cli/check/stages/validate/select-contract.js +0 -13
  87. package/dist/cli/check/stages/validate/source-contract.js +0 -21
  88. package/dist/cli/check/stages/validate/variant-contract.js +0 -15
  89. package/dist/cli/check/workspace.js +0 -99
  90. package/dist/cli/dev/build-css.js +0 -97
  91. package/dist/cli/dev/catalog.js +0 -35
  92. package/dist/cli/dev/compose-template.js +0 -39
  93. package/dist/cli/dev/content-override.js +0 -58
  94. package/dist/cli/dev/diagnostics.js +0 -68
  95. package/dist/cli/dev/discover.js +0 -35
  96. package/dist/cli/dev/fill-state.js +0 -77
  97. package/dist/cli/dev/index.js +0 -42
  98. package/dist/cli/dev/inspect.js +0 -38
  99. package/dist/cli/dev/island-bootstrap.js +0 -38
  100. package/dist/cli/dev/island-canvas-bootstrap.js +0 -204
  101. package/dist/cli/dev/island-map.js +0 -35
  102. package/dist/cli/dev/island-transform.js +0 -31
  103. package/dist/cli/dev/manifest-instances.js +0 -31
  104. package/dist/cli/dev/render-section.js +0 -24
  105. package/dist/cli/dev/screenshot-target.js +0 -24
  106. package/dist/cli/dev/section-page.js +0 -91
  107. package/dist/cli/dev/server.js +0 -510
  108. package/dist/cli/dev/slot-schema.js +0 -12
  109. package/dist/cli/dev/structure-summary.js +0 -119
  110. package/dist/cli/dev/tailwind.js +0 -32
  111. package/dist/cli/dev/tsconfig-paths-plugin.js +0 -90
  112. package/dist/cli/dev/vite-server.js +0 -39
  113. package/dist/cli/dev/workspace.js +0 -63
  114. package/dist/cli/link/index.js +0 -74
  115. package/dist/cli/link/overlay.js +0 -59
  116. package/dist/cli/link/watch.js +0 -25
  117. package/dist/cli/new/emit.js +0 -48
  118. package/dist/cli/new/index.js +0 -69
  119. package/dist/cli/new/scaffold/section/_Renderer.tsx +0 -20
  120. package/dist/cli/new/scaffold/section/_fill-spec.ts +0 -18
  121. package/dist/cli/new/scaffold/section/_fixtures.ts +0 -13
  122. package/dist/cli/new/scaffold/section/_schema.ts +0 -24
  123. package/dist/cli/new/scaffold/template/_manifest.json +0 -9
  124. package/dist/cli/new/scaffold/template/sections/hero/ExpandableText.tsx +0 -45
  125. package/dist/cli/new/scaffold/template/sections/hero/Renderer.tsx +0 -40
  126. package/dist/cli/new/scaffold/template/sections/hero/components/Feature.tsx +0 -14
  127. package/dist/cli/new/scaffold/template/sections/hero/fill-spec.ts +0 -29
  128. package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +0 -41
  129. package/dist/cli/new/scaffold/template/sections/hero/schema.ts +0 -60
  130. package/dist/cli/new/scaffold/template/theme.css +0 -24
  131. package/dist/cli/new/scaffold/template/theme.ts +0 -27
  132. package/dist/cli/new/scaffold-assets.js +0 -20
  133. package/dist/cli/pack/collect.js +0 -44
  134. package/dist/cli/pack/guards.js +0 -57
  135. package/dist/cli/pack/index.js +0 -66
  136. package/dist/cli/pack/manifest.js +0 -15
  137. package/dist/cli/pack/zip.js +0 -86
  138. package/dist/cli/theme/generate.js +0 -43
  139. package/dist/cli/theme/index.js +0 -33
  140. package/dist/cli/theme/load-theme.js +0 -69
  141. package/dist/cli.d.ts +0 -1
  142. package/dist/cli.js +0 -138
  143. package/dist/dev-client/assets/index-CJa9G1Na.css +0 -1
  144. package/dist/dev-client/assets/index-C_1atZs4.js +0 -450
  145. package/dist/dev-client/index.html +0 -13
  146. package/dist/tokens.css +0 -21
@@ -1,510 +0,0 @@
1
- import { TokenThemeSchema } from "../../design-system/theme.js";
2
- import { resolveFromWorkspace } from "../check/resolve-tool.js";
3
- import { loadSectionBundle } from "../../ssr.js";
4
- import { assembleTemplateCss } from "./build-css.js";
5
- import { discoverSections } from "./discover.js";
6
- import { buildCatalog } from "./catalog.js";
7
- import { buildSectionIslandMap } from "./island-map.js";
8
- import { findManifestInstance, resolveManifestInstances } from "./manifest-instances.js";
9
- import { decodeUrlOverride, deepMerge, parseContentOverride } from "./content-override.js";
10
- import { projectSlotSchema } from "./slot-schema.js";
11
- import { renderSection } from "./render-section.js";
12
- import { wrapSectionHtml } from "./section-page.js";
13
- import { composeTemplatePage } from "./compose-template.js";
14
- import { runDiagnostics } from "./diagnostics.js";
15
- import { collectRenderedSlotIds, computeSlotFills } from "./fill-state.js";
16
- import { summarizeHtml } from "./structure-summary.js";
17
- import { inspectSection } from "./inspect.js";
18
- import { writeIslandBootstrap } from "./island-bootstrap.js";
19
- import { writeIslandCanvasBootstrap } from "./island-canvas-bootstrap.js";
20
- import { resolveScreenshotPath } from "./screenshot-target.js";
21
- import { createDevViteServer } from "./vite-server.js";
22
- import { resolveWorkspace } from "./workspace.js";
23
- import { readFile } from "node:fs/promises";
24
- import { dirname, extname, join, resolve, sep } from "node:path";
25
- import { fileURLToPath } from "node:url";
26
- import { existsSync } from "node:fs";
27
- import { createServer } from "node:http";
28
-
29
- //#region src/cli/dev/server.ts
30
- const BASE_CSS_PATH_BUILT = fileURLToPath(new URL("../../base.css", import.meta.url));
31
- const BASE_CSS_PATH_SOURCE = fileURLToPath(new URL("../../css/base.css", import.meta.url));
32
- const BASE_CSS_PATH = existsSync(BASE_CSS_PATH_BUILT) ? BASE_CSS_PATH_BUILT : BASE_CSS_PATH_SOURCE;
33
- const DEV_CLIENT_DIR = join(findPackageRoot(fileURLToPath(import.meta.url)), "dist", "dev-client");
34
- const ASSET_MIME = {
35
- ".js": "text/javascript; charset=utf-8",
36
- ".mjs": "text/javascript; charset=utf-8",
37
- ".css": "text/css; charset=utf-8",
38
- ".html": "text/html; charset=utf-8",
39
- ".json": "application/json; charset=utf-8",
40
- ".map": "application/json; charset=utf-8",
41
- ".svg": "image/svg+xml",
42
- ".ico": "image/x-icon",
43
- ".png": "image/png",
44
- ".jpg": "image/jpeg",
45
- ".jpeg": "image/jpeg",
46
- ".gif": "image/gif",
47
- ".webp": "image/webp",
48
- ".avif": "image/avif",
49
- ".woff": "font/woff",
50
- ".woff2": "font/woff2",
51
- ".ttf": "font/ttf",
52
- ".otf": "font/otf"
53
- };
54
- function findPackageRoot(fromPath) {
55
- let dir = dirname(fromPath);
56
- for (;;) {
57
- if (existsSync(join(dir, "package.json"))) return dir;
58
- const parent = dirname(dir);
59
- if (parent === dir) throw new Error("template-kit: could not locate the package root");
60
- dir = parent;
61
- }
62
- }
63
- async function startDevServer(opts) {
64
- const workspace = await resolveWorkspace(opts.cwd);
65
- if (workspace.templates.length === 0) throw new Error(`no templates found under ${workspace.templatesDir}`);
66
- const vite = await createDevViteServer(workspace.root, { hmrPort: opts.port + 100 });
67
- const hmr = createHmrBus();
68
- const cssCache = /* @__PURE__ */ new Map();
69
- const sectionAssets = /* @__PURE__ */ new Map();
70
- const islandBootstrapUrl = await writeIslandBootstrap(workspace.root);
71
- const canvasIslandBootstrapUrl = await writeIslandCanvasBootstrap(workspace.root);
72
- vite.watcher.on("change", () => {
73
- vite.moduleGraph.invalidateAll();
74
- cssCache.clear();
75
- sectionAssets.clear();
76
- hmr.broadcast("section:changed", "{}");
77
- });
78
- const ctx = {
79
- workspace,
80
- vite,
81
- hmr,
82
- port: opts.port,
83
- cssCache,
84
- sectionAssets,
85
- islandBootstrapUrl,
86
- canvasIslandBootstrapUrl,
87
- diagnosticsCmd: opts.diagnosticsCmd ?? null
88
- };
89
- const server = createServer((req, res) => {
90
- handle(req, res, ctx).catch((err) => {
91
- const error = err instanceof Error ? err : new Error(String(err));
92
- vite.ssrFixStacktrace(error);
93
- res.statusCode = 500;
94
- res.setHeader("content-type", "text/plain");
95
- res.end(`dev server error: ${error.message}`);
96
- });
97
- });
98
- await new Promise((resolve) => server.listen(opts.port, resolve));
99
- return {
100
- url: `http://localhost:${opts.port}`,
101
- port: opts.port,
102
- workspace,
103
- close: async () => {
104
- hmr.closeAll();
105
- await new Promise((r) => server.close(() => r()));
106
- await vite.close();
107
- }
108
- };
109
- }
110
- async function handle(req, res, ctx) {
111
- const url = new URL(req.url ?? "/", "http://localhost");
112
- const path = url.pathname;
113
- if (path === "/" || path.startsWith("/canvas/")) return sendSpaShell(res);
114
- if (path.startsWith("/dev-client/")) return sendDevClientAsset(res, path);
115
- if (path === "/api/ready") return sendJson(res, {
116
- ready: true,
117
- port: ctx.port
118
- });
119
- if (path === "/api/catalog.json") return sendJson(res, await buildCatalog(ctx.vite, ctx.workspace));
120
- if (path === "/runtime/base.css") return sendCss(res, await readFile(BASE_CSS_PATH, "utf8"));
121
- if (path === "/runtime/template.css") return sendCss(res, await templateCss(ctx, url.searchParams.get("template")));
122
- if (path.startsWith("/section-assets/")) return sendSectionAsset(ctx, path, res);
123
- if (path === "/__hmr") return ctx.hmr.attach(res);
124
- if (path === "/api/inspect") return sendInspect(ctx, url, res);
125
- if (path === "/api/diagnostics") return sendDiagnostics(ctx, url, res);
126
- if (path === "/api/screenshot") return sendScreenshot(ctx, url, res);
127
- if (path === "/api/render") return sendRenderPayload(ctx, url, req, res);
128
- if (path === "/api/template-schema") return sendTemplateSchema(ctx, url, res);
129
- const templateKey = matchTemplatePath(path);
130
- if (templateKey) return sendTemplatePage(ctx, templateKey, url.searchParams.get("canvas") === "1", res);
131
- const match = matchSection(path);
132
- if (match) return sendHtml(res, await renderSectionPage(ctx, match, url.searchParams.get("overrides") ?? void 0));
133
- ctx.vite.middlewares(req, res, () => {
134
- res.statusCode = 404;
135
- res.setHeader("content-type", "text/plain");
136
- res.end("Not found");
137
- });
138
- }
139
- function matchSection(path) {
140
- const [head, template, section, fixture] = path.split("/").filter(Boolean);
141
- if (head === "sections" && template && section && fixture) return {
142
- template: decodeURIComponent(template),
143
- section: decodeURIComponent(section),
144
- fixture: decodeURIComponent(fixture)
145
- };
146
- return null;
147
- }
148
- function matchTemplatePath(path) {
149
- const [head, key, ...rest] = path.split("/").filter(Boolean);
150
- if (head === "template" && key && rest.length === 0) return decodeURIComponent(key);
151
- return null;
152
- }
153
- async function sendTemplatePage(ctx, templateKey, canvas, res) {
154
- const template = ctx.workspace.templates.find((t) => t.key === templateKey);
155
- if (!template) {
156
- res.statusCode = 404;
157
- res.setHeader("content-type", "text/plain");
158
- res.end(`unknown template "${templateKey}"`);
159
- return;
160
- }
161
- sendHtml(res, await composeTemplatePage(ctx.vite, {
162
- workspace: ctx.workspace,
163
- template,
164
- islandBootstrapUrl: ctx.islandBootstrapUrl,
165
- canvasIslandBootstrapUrl: ctx.canvasIslandBootstrapUrl,
166
- canvas
167
- }));
168
- }
169
- const INITIAL_CSS_REV = "0";
170
- async function sendTemplateSchema(ctx, url, res) {
171
- const templateKey = url.searchParams.get("template");
172
- if (!templateKey) {
173
- res.statusCode = 400;
174
- res.setHeader("content-type", "text/plain");
175
- res.end("template-schema requires a template query param");
176
- return;
177
- }
178
- const template = ctx.workspace.templates.find((t) => t.key === templateKey);
179
- if (!template) {
180
- res.statusCode = 404;
181
- res.setHeader("content-type", "text/plain");
182
- res.end(`unknown template "${templateKey}"`);
183
- return;
184
- }
185
- const load = (dir, file) => ctx.vite.ssrLoadModule(join(dir, file));
186
- const instances = await resolveManifestInstances(template);
187
- const out = [];
188
- const islands = {};
189
- for (const inst of instances) {
190
- const { schemaSlots } = await loadSectionBundle(inst.sectionDir, load);
191
- out.push({
192
- instanceId: inst.instanceId,
193
- section: inst.section,
194
- fixture: inst.fixture,
195
- slotSchema: projectSlotSchema(schemaSlots)
196
- });
197
- Object.assign(islands, await buildSectionIslandMap(ctx.workspace.root, ctx.workspace.templatesDir, inst.sectionDir));
198
- }
199
- sendJson(res, {
200
- template: templateKey,
201
- instances: out,
202
- islands,
203
- bootstrapUrl: ctx.canvasIslandBootstrapUrl,
204
- cssRev: INITIAL_CSS_REV
205
- });
206
- }
207
- async function findSection(ctx, match) {
208
- const template = ctx.workspace.templates.find((t) => t.key === match.template);
209
- if (!template) throw new Error(`unknown template "${match.template}"`);
210
- const section = (await discoverSections(template.dir)).find((s) => s.name === match.section);
211
- if (!section) throw new Error(`unknown section "${match.section}" in template "${match.template}"`);
212
- return section;
213
- }
214
- async function renderSectionPage(ctx, match, overrides) {
215
- const section = await findSection(ctx, match);
216
- const { html } = await renderSection(ctx.vite, {
217
- sectionDir: section.dir,
218
- fixture: match.fixture,
219
- override: decodeUrlOverride(overrides)
220
- });
221
- const islands = await buildSectionIslandMap(ctx.workspace.root, ctx.workspace.templatesDir, section.dir);
222
- return wrapSectionHtml({
223
- sectionHtml: html,
224
- cssRev: "0",
225
- template: match.template,
226
- section: match.section,
227
- islands,
228
- bootstrapUrl: ctx.islandBootstrapUrl
229
- });
230
- }
231
- async function sendInspect(ctx, url, res) {
232
- const template = url.searchParams.get("template");
233
- const section = url.searchParams.get("section");
234
- const fixture = url.searchParams.get("fixture");
235
- if (!template || !section || !fixture) {
236
- res.statusCode = 400;
237
- res.setHeader("content-type", "text/plain");
238
- res.end("inspect requires template, section, and fixture query params");
239
- return;
240
- }
241
- const info = await findSection(ctx, {
242
- template,
243
- section,
244
- fixture
245
- });
246
- sendJson(res, await inspectSection(ctx.vite, {
247
- template,
248
- section,
249
- sectionDir: info.dir,
250
- fixture,
251
- override: decodeUrlOverride(url.searchParams.get("overrides") ?? void 0),
252
- withOutline: url.searchParams.get("format") === "text"
253
- }));
254
- }
255
- async function sendDiagnostics(ctx, url, res) {
256
- const template = url.searchParams.get("template");
257
- const section = url.searchParams.get("section");
258
- if (!template || !section) {
259
- res.statusCode = 400;
260
- res.setHeader("content-type", "text/plain");
261
- res.end("diagnostics requires template and section query params");
262
- return;
263
- }
264
- sendJson(res, await runDiagnostics(ctx.diagnosticsCmd, {
265
- template,
266
- section
267
- }));
268
- }
269
- const MAX_POST_OVERRIDE_BYTES = 1024 * 1024;
270
- async function readJsonBody(req, maxBytes) {
271
- const chunks = [];
272
- let size = 0;
273
- for await (const chunk of req) {
274
- const buf = chunk;
275
- size += buf.length;
276
- if (size > maxBytes) throw new Error(`request body exceeds ${maxBytes} bytes`);
277
- chunks.push(buf);
278
- }
279
- if (size === 0) return void 0;
280
- return JSON.parse(Buffer.concat(chunks).toString("utf8"));
281
- }
282
- async function resolveRenderInputs(ctx, params) {
283
- const template = ctx.workspace.templates.find((t) => t.key === params.template);
284
- if (!template) return { error: `unknown template "${params.template}"` };
285
- if (params.instanceId) {
286
- const inst = await findManifestInstance(template, params.instanceId);
287
- if (!inst) return { error: `unknown instance "${params.instanceId}"` };
288
- const override = deepMerge(Object.keys(inst.options).length > 0 ? { options: inst.options } : {}, params.override ?? {});
289
- return {
290
- sectionDir: inst.sectionDir,
291
- fixture: params.fixture ?? inst.fixture,
292
- override
293
- };
294
- }
295
- if (!params.section || !params.fixture) return { error: "render requires section+fixture or instanceId" };
296
- return {
297
- sectionDir: (await findSection(ctx, {
298
- template: params.template,
299
- section: params.section,
300
- fixture: params.fixture
301
- })).dir,
302
- fixture: params.fixture,
303
- override: params.override
304
- };
305
- }
306
- async function sendRenderPayload(ctx, url, req, res) {
307
- let template;
308
- let section = null;
309
- let fixture;
310
- let instanceId = null;
311
- let override;
312
- if (req.method === "POST") {
313
- const body = await readJsonBody(req, MAX_POST_OVERRIDE_BYTES) ?? {};
314
- template = body.template ?? null;
315
- section = body.section ?? null;
316
- fixture = body.fixture ?? null;
317
- instanceId = body.instanceId ?? null;
318
- override = parseContentOverride(body.override);
319
- } else {
320
- template = url.searchParams.get("template");
321
- section = url.searchParams.get("section");
322
- fixture = url.searchParams.get("fixture");
323
- override = decodeUrlOverride(url.searchParams.get("overrides") ?? void 0);
324
- }
325
- if (!template) {
326
- res.statusCode = 400;
327
- res.setHeader("content-type", "text/plain");
328
- res.end("render requires template");
329
- return;
330
- }
331
- const inputs = await resolveRenderInputs(ctx, {
332
- template,
333
- section,
334
- fixture,
335
- instanceId,
336
- override
337
- });
338
- if ("error" in inputs) {
339
- res.statusCode = 400;
340
- res.setHeader("content-type", "text/plain");
341
- res.end(inputs.error);
342
- return;
343
- }
344
- const { html, resolvedProps, slotSchema, schemaSlots } = await renderSection(ctx.vite, {
345
- sectionDir: inputs.sectionDir,
346
- fixture: inputs.fixture,
347
- override: inputs.override
348
- });
349
- const { tree } = summarizeHtml(html);
350
- sendJson(res, {
351
- html,
352
- resolvedProps,
353
- slotTree: tree,
354
- islandMap: await buildSectionIslandMap(ctx.workspace.root, ctx.workspace.templatesDir, inputs.sectionDir),
355
- slotSchema,
356
- slotFills: computeSlotFills({
357
- declaredSlots: schemaSlots,
358
- resolvedSlots: resolvedProps.slots,
359
- renderedSlotIds: collectRenderedSlotIds(tree),
360
- slotLines: {}
361
- })
362
- });
363
- }
364
- async function sendScreenshot(ctx, url, res) {
365
- const target = resolveScreenshotPath({
366
- template: url.searchParams.get("template") ?? "",
367
- section: url.searchParams.get("section"),
368
- fixture: url.searchParams.get("fixture")
369
- });
370
- if (!target.ok) {
371
- res.statusCode = 400;
372
- res.setHeader("content-type", "text/plain");
373
- res.end(target.error);
374
- return;
375
- }
376
- const widthRaw = Number(url.searchParams.get("width"));
377
- const width = Number.isFinite(widthRaw) && widthRaw > 0 ? Math.min(2400, Math.max(200, widthRaw)) : 1280;
378
- let pw;
379
- try {
380
- pw = await resolveFromWorkspace(ctx.workspace.root, "@playwright/test");
381
- } catch {
382
- res.statusCode = 501;
383
- res.setHeader("content-type", "text/plain");
384
- res.end("screenshots need @playwright/test: npm i -D @playwright/test && npx playwright install chromium");
385
- return;
386
- }
387
- const chromium = pw.chromium ?? pw.default.chromium;
388
- let browser;
389
- try {
390
- browser = await chromium.launch();
391
- } catch (err) {
392
- res.statusCode = 501;
393
- res.setHeader("content-type", "text/plain");
394
- res.end(`chromium is not available — run: npx playwright install chromium\n(${err instanceof Error ? err.message : String(err)})`);
395
- return;
396
- }
397
- try {
398
- const page = await browser.newPage({
399
- viewport: {
400
- width,
401
- height: 900
402
- },
403
- deviceScaleFactor: 2
404
- });
405
- const targetUrl = `http://localhost:${ctx.port}${target.path}`;
406
- await page.goto(targetUrl, { waitUntil: "load" });
407
- const png = await page.screenshot({ fullPage: true });
408
- res.setHeader("content-type", "image/png");
409
- res.end(png);
410
- } finally {
411
- await browser.close();
412
- }
413
- }
414
- async function templateCss(ctx, templateKey) {
415
- const template = ctx.workspace.templates.find((t) => t.key === templateKey) ?? ctx.workspace.templates[0];
416
- if (!template?.themePath) return "";
417
- const cached = ctx.cssCache.get(template.key);
418
- if (cached !== void 0) return cached;
419
- const mod = await ctx.vite.ssrLoadModule(template.themePath);
420
- const theme = Object.values(mod).find((v) => TokenThemeSchema.safeParse(v).success);
421
- if (!theme) throw new Error(`no TokenTheme export in ${template.themePath}`);
422
- const sectionDirs = (await discoverSections(template.dir)).map((s) => s.dir);
423
- const { css, assets } = await assembleTemplateCss(ctx.workspace.root, theme, sectionDirs);
424
- for (const [name, bytes] of assets) ctx.sectionAssets.set(name, bytes);
425
- ctx.cssCache.set(template.key, css);
426
- return css;
427
- }
428
- async function sendSpaShell(res) {
429
- try {
430
- sendHtml(res, await readFile(join(DEV_CLIENT_DIR, "index.html"), "utf8"));
431
- } catch {
432
- res.statusCode = 500;
433
- res.setHeader("content-type", "text/plain");
434
- res.end("dev UI is not built — run `npm run build` in @homepages/template-kit");
435
- }
436
- }
437
- async function sendDevClientAsset(res, path) {
438
- const rel = decodeURIComponent(path.slice(12));
439
- const filePath = resolve(DEV_CLIENT_DIR, rel);
440
- if (filePath !== DEV_CLIENT_DIR && !filePath.startsWith(DEV_CLIENT_DIR + sep)) {
441
- res.statusCode = 403;
442
- res.setHeader("content-type", "text/plain");
443
- res.end("forbidden");
444
- return;
445
- }
446
- try {
447
- const buf = await readFile(filePath);
448
- res.setHeader("content-type", ASSET_MIME[extname(filePath)] ?? "application/octet-stream");
449
- res.end(buf);
450
- } catch {
451
- res.statusCode = 404;
452
- res.setHeader("content-type", "text/plain");
453
- res.end("not found");
454
- }
455
- }
456
- function sendSectionAsset(ctx, path, res) {
457
- const filename = decodeURIComponent(path.slice(16));
458
- if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
459
- res.statusCode = 403;
460
- res.setHeader("content-type", "text/plain");
461
- res.end("forbidden");
462
- return;
463
- }
464
- const bytes = ctx.sectionAssets.get(filename);
465
- if (!bytes) {
466
- res.statusCode = 404;
467
- res.setHeader("content-type", "text/plain");
468
- res.end("not found");
469
- return;
470
- }
471
- res.setHeader("content-type", ASSET_MIME[extname(filename)] ?? "application/octet-stream");
472
- res.end(Buffer.from(bytes));
473
- }
474
- function createHmrBus() {
475
- const clients = /* @__PURE__ */ new Set();
476
- return {
477
- attach(res) {
478
- res.writeHead(200, {
479
- "content-type": "text/event-stream",
480
- "cache-control": "no-cache",
481
- connection: "keep-alive"
482
- });
483
- res.write(": hello\n\n");
484
- clients.add(res);
485
- res.on("close", () => clients.delete(res));
486
- },
487
- broadcast(event, data) {
488
- for (const res of clients) res.write(`event: ${event}\ndata: ${data}\n\n`);
489
- },
490
- closeAll() {
491
- for (const res of clients) res.end();
492
- clients.clear();
493
- }
494
- };
495
- }
496
- function sendJson(res, obj) {
497
- res.setHeader("content-type", "application/json");
498
- res.end(JSON.stringify(obj));
499
- }
500
- function sendCss(res, css) {
501
- res.setHeader("content-type", "text/css");
502
- res.end(css);
503
- }
504
- function sendHtml(res, html) {
505
- res.setHeader("content-type", "text/html; charset=utf-8");
506
- res.end(html);
507
- }
508
-
509
- //#endregion
510
- export { startDevServer };
@@ -1,12 +0,0 @@
1
- //#region src/cli/dev/slot-schema.ts
2
- function projectSlotSchema(slots) {
3
- const out = {};
4
- for (const [id, slot] of Object.entries(slots)) out[id] = {
5
- type: slot.type,
6
- editable_by_user: slot.editable_by_user === true
7
- };
8
- return out;
9
- }
10
-
11
- //#endregion
12
- export { projectSlotSchema };
@@ -1,119 +0,0 @@
1
- import { ATTR_SECTION_INSTANCE_ID, ATTR_SLOT_GROUP, ATTR_SLOT_ID, ATTR_SLOT_ITEM, ATTR_SLOT_TEXT_LEAF, ATTR_TR_ENHANCE } from "../../contracts/markers.js";
2
-
3
- //#region src/cli/dev/structure-summary.ts
4
- const VOID_TAGS = /* @__PURE__ */ new Set([
5
- "area",
6
- "base",
7
- "br",
8
- "col",
9
- "embed",
10
- "hr",
11
- "img",
12
- "input",
13
- "link",
14
- "meta",
15
- "param",
16
- "source",
17
- "track",
18
- "wbr"
19
- ]);
20
- const TAG_RE = /<(\/)?([a-zA-Z][a-zA-Z0-9-]*)((?:\s[^<>]*?)?)(\/?)>/g;
21
- const ATTR_RE = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*"([^"]*)")?/g;
22
- function parseAttrs(raw) {
23
- const out = {};
24
- ATTR_RE.lastIndex = 0;
25
- let m;
26
- while (m = ATTR_RE.exec(raw)) out[m[1]] = m[2] ?? "";
27
- return out;
28
- }
29
- function decode(s) {
30
- return s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&#39;/g, "'");
31
- }
32
- function parseHtml(html) {
33
- html = html.replace(/<!--[\s\S]*?-->/g, "");
34
- const root = {
35
- tag: "#root",
36
- classes: [],
37
- children: []
38
- };
39
- const stack = [root];
40
- let last = 0;
41
- TAG_RE.lastIndex = 0;
42
- let m;
43
- while (m = TAG_RE.exec(html)) {
44
- const between = decode(html.slice(last, m.index)).replace(/\s+/g, " ").trim();
45
- last = TAG_RE.lastIndex;
46
- if (between) {
47
- const top = stack[stack.length - 1];
48
- top.text = top.text ? top.text + " " + between : between;
49
- }
50
- const closing = m[1];
51
- const tag = m[2];
52
- const rawAttrs = m[3] ?? "";
53
- const selfClose = m[4] === "/";
54
- if (closing) {
55
- for (let i = stack.length - 1; i > 0; i--) if (stack[i].tag === tag) {
56
- stack.length = i;
57
- break;
58
- }
59
- continue;
60
- }
61
- const attrs = parseAttrs(rawAttrs);
62
- const node = {
63
- tag,
64
- classes: attrs.class ? attrs.class.split(/\s+/).filter(Boolean) : [],
65
- children: []
66
- };
67
- if (attrs["data-slot-id"]) node.slotId = attrs[ATTR_SLOT_ID];
68
- if (attrs["data-slot-group"]) node.slotGroup = attrs[ATTR_SLOT_GROUP];
69
- if ("data-slot-item" in attrs) {
70
- const idx = Number(attrs[ATTR_SLOT_ITEM]);
71
- if (Number.isInteger(idx)) node.slotItem = idx;
72
- }
73
- if ("data-slot-text-leaf" in attrs) node.textLeaf = true;
74
- if ("data-tr-enhance" in attrs) node.enhance = true;
75
- if (attrs["data-section-instance-id"]) node.sectionInstance = true;
76
- stack[stack.length - 1].children.push(node);
77
- if (!selfClose && !VOID_TAGS.has(tag)) stack.push(node);
78
- }
79
- return root;
80
- }
81
- function formatText(root, opts = {}) {
82
- const maxDepth = opts.maxDepth ?? 20;
83
- const textPreview = opts.textPreview ?? 50;
84
- const lines = [];
85
- function line(node, depth) {
86
- const cls = node.classes.length ? "." + node.classes.join(".") : "";
87
- const marks = [];
88
- if (node.sectionInstance) marks.push("section");
89
- if (node.slotId) marks.push("slot:" + node.slotId);
90
- if (node.slotGroup) marks.push("group:" + node.slotGroup);
91
- if (node.slotItem !== void 0) marks.push("item:" + node.slotItem);
92
- if (node.textLeaf) marks.push("leaf");
93
- if (node.enhance) marks.push("enhance");
94
- const markStr = marks.length ? " [" + marks.join("][") + "]" : "";
95
- let textStr = "";
96
- if (node.text && node.children.length === 0) textStr = " \"" + (node.text.length > textPreview ? node.text.slice(0, textPreview) + "…" : node.text) + "\"";
97
- lines.push(" ".repeat(depth) + node.tag + cls + markStr + textStr);
98
- }
99
- function walk(node, depth) {
100
- for (const c of node.children) {
101
- line(c, depth);
102
- if (depth + 1 > maxDepth) {
103
- if (c.children.length) lines.push(" ".repeat(depth + 1) + "…");
104
- } else walk(c, depth + 1);
105
- }
106
- }
107
- walk(root, 0);
108
- return lines.join("\n");
109
- }
110
- function summarizeHtml(html, opts = {}) {
111
- const tree = parseHtml(html);
112
- return {
113
- text: formatText(tree, opts),
114
- tree
115
- };
116
- }
117
-
118
- //#endregion
119
- export { formatText, parseHtml, summarizeHtml };
@@ -1,32 +0,0 @@
1
- import { promisify } from "node:util";
2
- import { dirname, join } from "node:path";
3
- import { pathToFileURL } from "node:url";
4
- import { createRequire } from "node:module";
5
- import { execFile } from "node:child_process";
6
-
7
- //#region src/cli/dev/tailwind.ts
8
- const exec = promisify(execFile);
9
- async function runTailwind(root, opts) {
10
- const require = createRequire(pathToFileURL(join(root, "package.json")));
11
- let cliPkgJson;
12
- try {
13
- cliPkgJson = require.resolve("@tailwindcss/cli/package.json");
14
- } catch {
15
- throw new Error("@tailwindcss/cli is not installed in this workspace. Install it: npm i -D @tailwindcss/cli");
16
- }
17
- const args = [
18
- join(dirname(cliPkgJson), "dist/index.mjs"),
19
- "-i",
20
- opts.input,
21
- "-o",
22
- opts.output
23
- ];
24
- if (opts.minify) args.push("--minify");
25
- await exec(process.execPath, args, {
26
- cwd: root,
27
- maxBuffer: 8 * 1024 * 1024
28
- });
29
- }
30
-
31
- //#endregion
32
- export { runTailwind };