@homepages/template-kit 0.3.0 → 0.4.1

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 (109) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +39 -4
  3. package/dist/asset-modules.d.ts +81 -0
  4. package/dist/cli/check/config.js +5 -1
  5. package/dist/cli/check/css.js +27 -13
  6. package/dist/cli/check/loader.js +4 -0
  7. package/dist/cli/check/section-assets.js +58 -0
  8. package/dist/cli/check/stages/size.js +124 -8
  9. package/dist/cli/check/stages/tree.js +26 -27
  10. package/dist/cli/dev/build-css.js +91 -0
  11. package/dist/cli/dev/catalog.js +35 -0
  12. package/dist/cli/dev/compose-template.js +38 -0
  13. package/dist/cli/dev/content-override.js +58 -0
  14. package/dist/cli/dev/discover.js +35 -0
  15. package/dist/cli/dev/fill-state.js +75 -0
  16. package/dist/cli/dev/index.js +32 -0
  17. package/dist/cli/dev/inspect.js +38 -0
  18. package/dist/cli/dev/island-bootstrap.js +38 -0
  19. package/dist/cli/dev/island-map.js +35 -0
  20. package/dist/cli/dev/island-transform.js +31 -0
  21. package/dist/cli/dev/manifest-instances.js +31 -0
  22. package/dist/cli/dev/render-section.js +24 -0
  23. package/dist/cli/dev/screenshot-target.js +24 -0
  24. package/dist/cli/dev/section-page.js +54 -0
  25. package/dist/cli/dev/server.js +480 -0
  26. package/dist/cli/dev/slot-schema.js +12 -0
  27. package/dist/cli/dev/structure-summary.js +118 -0
  28. package/dist/cli/dev/tailwind.js +32 -0
  29. package/dist/cli/dev/vite-server.js +33 -0
  30. package/dist/cli/dev/workspace.js +63 -0
  31. package/dist/cli/link/index.js +74 -0
  32. package/dist/cli/link/overlay.js +59 -0
  33. package/dist/cli/link/watch.js +25 -0
  34. package/dist/cli/new/emit.js +48 -0
  35. package/dist/cli/new/index.js +69 -0
  36. package/dist/cli/new/scaffold/section/_Renderer.tsx +20 -0
  37. package/dist/cli/new/scaffold/section/_fill-spec.ts +18 -0
  38. package/dist/cli/new/scaffold/section/_fixtures.ts +13 -0
  39. package/dist/cli/new/scaffold/section/_schema.ts +24 -0
  40. package/dist/cli/new/scaffold/template/_manifest.json +9 -0
  41. package/dist/cli/new/scaffold/template/sections/hero/ExpandableText.tsx +45 -0
  42. package/dist/cli/new/scaffold/template/sections/hero/Renderer.tsx +40 -0
  43. package/dist/cli/new/scaffold/template/sections/hero/components/Feature.tsx +14 -0
  44. package/dist/cli/new/scaffold/template/sections/hero/fill-spec.ts +29 -0
  45. package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +41 -0
  46. package/dist/cli/new/scaffold/template/sections/hero/schema.ts +60 -0
  47. package/dist/cli/new/scaffold/template/theme.css +24 -0
  48. package/dist/cli/new/scaffold/template/theme.ts +27 -0
  49. package/dist/cli/new/scaffold-assets.js +20 -0
  50. package/dist/cli/pack/collect.js +44 -0
  51. package/dist/cli/pack/guards.js +57 -0
  52. package/dist/cli/pack/index.js +66 -0
  53. package/dist/cli/pack/manifest.js +15 -0
  54. package/dist/cli/pack/zip.js +86 -0
  55. package/dist/cli.js +80 -14
  56. package/dist/dev-client/assets/index-DfEfrp6P.css +1 -0
  57. package/dist/dev-client/assets/index-bpMP0b0W.js +50 -0
  58. package/dist/dev-client/index.html +13 -0
  59. package/dist/eslint/is-client-file.d.ts +4 -0
  60. package/dist/index.d.ts +1 -0
  61. package/dist/islands/detect.d.ts +16 -0
  62. package/dist/islands/detect.js +22 -0
  63. package/dist/islands/discover.d.ts +5 -0
  64. package/dist/islands/discover.js +19 -0
  65. package/dist/islands/esbuild-plugin.d.ts +13 -0
  66. package/dist/islands/esbuild-plugin.js +39 -0
  67. package/dist/islands/wrap.d.ts +12 -0
  68. package/dist/islands/wrap.js +48 -0
  69. package/dist/package.js +1 -1
  70. package/dist/ssr.d.ts +31 -0
  71. package/dist/ssr.js +46 -0
  72. package/docs/INDEX.md +9 -1
  73. package/docs/assets.md +128 -0
  74. package/docs/check.md +6 -3
  75. package/docs/dev.md +187 -0
  76. package/docs/islands.md +5 -2
  77. package/docs/llms.txt +41 -3
  78. package/docs/new.md +27 -0
  79. package/docs/overview.md +57 -0
  80. package/docs/pack.md +46 -0
  81. package/docs/primitives.md +5 -6
  82. package/docs/quickstart.md +102 -0
  83. package/docs/recipes/INDEX.md +27 -0
  84. package/docs/recipes/bind-property-fact.md +58 -0
  85. package/docs/recipes/collection-slot.md +100 -0
  86. package/docs/recipes/fill-spec-decision.md +66 -0
  87. package/docs/recipes/fixture-states.md +68 -0
  88. package/docs/recipes/image-slot-crop.md +98 -0
  89. package/docs/recipes/interactive-island.md +100 -0
  90. package/docs/recipes/organize-section-folder.md +72 -0
  91. package/docs/recipes/prepare-submission.md +55 -0
  92. package/docs/recipes/second-template.md +49 -0
  93. package/docs/recipes/select-slot.md +59 -0
  94. package/docs/recipes/static-asset.md +103 -0
  95. package/docs/recipes/third-party-package.md +90 -0
  96. package/docs/rules/INDEX.md +4 -2
  97. package/docs/rules/bundle-binary-asset.md +33 -52
  98. package/docs/rules/bundle-incomplete.md +2 -2
  99. package/docs/rules/css-reason.md +3 -3
  100. package/docs/rules/no-bare-css-import.md +8 -8
  101. package/docs/rules/no-css-import-from-render-path.md +7 -8
  102. package/docs/rules/size-assets.md +88 -0
  103. package/docs/rules/size-island-bundle.md +123 -0
  104. package/docs/rules/size-section-css.md +19 -14
  105. package/docs/schema-system.md +28 -28
  106. package/docs/theme-and-css.md +2 -2
  107. package/docs/vocabulary.md +98 -0
  108. package/package.json +12 -3
  109. package/tsconfig.json +1 -1
@@ -0,0 +1,24 @@
1
+ /* Generated by `template-kit theme` from theme.ts — do not edit.
2
+ Re-run `template-kit theme` after changing theme.ts. */
3
+ @theme inline {
4
+ --color-accent: var(--tr-color-accent);
5
+ --color-background: var(--tr-color-background);
6
+ --color-ink: var(--tr-color-ink);
7
+ --font-body: var(--tr-font-body);
8
+ --text-base: var(--tr-text-base);
9
+ }
10
+
11
+ :root {
12
+ --tr-color-accent: #2f6df6;
13
+ --tr-color-background: #ffffff;
14
+ --tr-color-ink: #1a1d21;
15
+ --tr-font-body: system-ui, -apple-system, sans-serif;
16
+ --tr-text-base: 1rem;
17
+ }
18
+
19
+ body {
20
+ font-family: var(--tr-font-body);
21
+ color: var(--tr-color-ink);
22
+ background: var(--tr-color-background);
23
+ font-size: var(--tr-text-base);
24
+ }
@@ -0,0 +1,27 @@
1
+ import type { TokenTheme } from "@homepages/template-kit";
2
+
3
+ // A template's design tokens. `template-kit theme` compiles this to theme.css;
4
+ // `template-kit dev` reads it directly. The `document` refs must name declared
5
+ // tokens (color/background → colors, font → fonts, fontSize → type).
6
+ export const theme: TokenTheme = {
7
+ colors: {
8
+ ink: "#1a1d21",
9
+ background: "#ffffff",
10
+ accent: "#2f6df6",
11
+ },
12
+ fonts: {
13
+ body: "system-ui, -apple-system, sans-serif",
14
+ },
15
+ type: {
16
+ base: "1rem",
17
+ },
18
+ radii: {},
19
+ shadows: {},
20
+ layout: {},
21
+ document: {
22
+ font: "body",
23
+ color: "ink",
24
+ background: "background",
25
+ fontSize: "base",
26
+ },
27
+ };
@@ -0,0 +1,20 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ //#region src/cli/new/scaffold-assets.ts
6
+ const here = dirname(fileURLToPath(import.meta.url));
7
+ function assetsRoot() {
8
+ const dist = join(here, "scaffold");
9
+ if (existsSync(dist)) return dist;
10
+ return join(here, "..", "..", "..", "create-homepages-workspace", "scaffold");
11
+ }
12
+ function templateAssetsDir() {
13
+ return join(assetsRoot(), "template");
14
+ }
15
+ function sectionAssetsDir() {
16
+ return join(assetsRoot(), "section");
17
+ }
18
+
19
+ //#endregion
20
+ export { sectionAssetsDir, templateAssetsDir };
@@ -0,0 +1,44 @@
1
+ import { join, relative, sep } from "node:path";
2
+ import { createHash } from "node:crypto";
3
+ import { readFile, readdir } from "node:fs/promises";
4
+
5
+ //#region src/cli/pack/collect.ts
6
+ const toPosix = (p) => p.split(sep).join("/");
7
+ async function walk(dir, out) {
8
+ for (const entry of await readdir(dir, { withFileTypes: true })) if (entry.isDirectory()) {
9
+ if (entry.name === "node_modules") continue;
10
+ await walk(join(dir, entry.name), out);
11
+ } else if (entry.isFile()) out.push(join(dir, entry.name));
12
+ }
13
+ /**
14
+ * Reads a template's regular files into sorted, hashable entries.
15
+ *
16
+ * Only real files and directories are walked: `node_modules` is skipped at any
17
+ * depth, and symlinks are omitted (a `Dirent` is neither `isFile()` nor
18
+ * `isDirectory()`), so a symlinked source file will not appear in the pack.
19
+ * Templates are authored as plain trees, so this is a non-issue in practice —
20
+ * but a submission that relies on a symlinked file would pack without it.
21
+ */
22
+ async function collectTemplateFiles(workspaceRoot, templateDir) {
23
+ const absPaths = [];
24
+ await walk(templateDir, absPaths);
25
+ const files = await Promise.all(absPaths.map(async (abs) => ({
26
+ archivePath: toPosix(relative(workspaceRoot, abs)),
27
+ templatePath: toPosix(relative(templateDir, abs)),
28
+ bytes: await readFile(abs)
29
+ })));
30
+ files.sort((a, b) => a.archivePath < b.archivePath ? -1 : a.archivePath > b.archivePath ? 1 : 0);
31
+ return files;
32
+ }
33
+ function contentHash(files) {
34
+ const ordered = [...files].sort((a, b) => a.templatePath < b.templatePath ? -1 : a.templatePath > b.templatePath ? 1 : 0);
35
+ const top = createHash("sha256");
36
+ for (const file of ordered) {
37
+ const inner = createHash("sha256").update(file.bytes).digest("hex");
38
+ top.update(file.templatePath).update("\0").update(inner).update("\n");
39
+ }
40
+ return `sha256:${top.digest("hex")}`;
41
+ }
42
+
43
+ //#endregion
44
+ export { collectTemplateFiles, contentHash };
@@ -0,0 +1,57 @@
1
+ import { readLinkMarker } from "../link/overlay.js";
2
+ import { z } from "zod";
3
+ import { join } from "node:path";
4
+ import { readFile, stat } from "node:fs/promises";
5
+
6
+ //#region src/cli/pack/guards.ts
7
+ const depsSchema = z.object({
8
+ dependencies: z.record(z.string(), z.string()).optional(),
9
+ devDependencies: z.record(z.string(), z.string()).optional(),
10
+ optionalDependencies: z.record(z.string(), z.string()).optional()
11
+ }).passthrough();
12
+ async function isFile(p) {
13
+ try {
14
+ return (await stat(p)).isFile();
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+ function localProtocolDeps(pkg) {
20
+ const offenders = [];
21
+ for (const block of [
22
+ pkg.dependencies,
23
+ pkg.devDependencies,
24
+ pkg.optionalDependencies
25
+ ]) for (const [name, spec] of Object.entries(block ?? {})) if (spec.startsWith("file:") || spec.startsWith("link:")) offenders.push(`${name} (${spec})`);
26
+ return offenders;
27
+ }
28
+ async function preflightGuards(workspaceRoot) {
29
+ const refusals = [];
30
+ if (await readLinkMarker(workspaceRoot) !== void 0) refusals.push({
31
+ reason: "a linked kit overlay is active in this workspace.",
32
+ fix: "Run `template-kit unlink` to restore the published kit, then pack."
33
+ });
34
+ if (!await isFile(join(workspaceRoot, "package-lock.json"))) refusals.push({
35
+ reason: "missing lockfile (package-lock.json) at workspace root.",
36
+ fix: "Run npm install and commit the lockfile — a submission without one cannot be reproduced."
37
+ });
38
+ let pkgJson;
39
+ try {
40
+ pkgJson = JSON.parse(await readFile(join(workspaceRoot, "package.json"), "utf8"));
41
+ } catch {
42
+ refusals.push({
43
+ reason: "package.json at the workspace root is missing or not valid JSON.",
44
+ fix: "Ensure the workspace root has a well-formed package.json before packing."
45
+ });
46
+ return refusals;
47
+ }
48
+ const parsed = depsSchema.safeParse(pkgJson);
49
+ if (parsed.success) for (const dep of localProtocolDeps(parsed.data)) refusals.push({
50
+ reason: `dependency ${dep} uses a local file:/link: protocol.`,
51
+ fix: "Depend on published versions only — a submission must install from the registry."
52
+ });
53
+ return refusals;
54
+ }
55
+
56
+ //#endregion
57
+ export { preflightGuards };
@@ -0,0 +1,66 @@
1
+ import { findWorkspaceRoot, selectTemplates, templateFromCwd } from "../check/workspace.js";
2
+ import { runCheck } from "../check/index.js";
3
+ import { collectTemplateFiles, contentHash } from "./collect.js";
4
+ import { preflightGuards } from "./guards.js";
5
+ import { buildPackManifest } from "./manifest.js";
6
+ import { buildZip } from "./zip.js";
7
+ import { join } from "node:path";
8
+ import { parseArgs } from "node:util";
9
+ import { readFile, writeFile } from "node:fs/promises";
10
+
11
+ //#region src/cli/pack/index.ts
12
+ function refusal(lines) {
13
+ return {
14
+ code: 1,
15
+ output: `template-kit pack: cannot pack.\n${lines.join("\n")}\n`
16
+ };
17
+ }
18
+ async function runPack(argv, cwd) {
19
+ const { positionals } = parseArgs({
20
+ args: argv,
21
+ allowPositionals: true
22
+ });
23
+ const root = await findWorkspaceRoot(cwd);
24
+ const template = (await selectTemplates(root, positionals[0] ?? templateFromCwd(root, cwd), false, "pack"))[0];
25
+ if (template === void 0) return refusal([" this workspace has no template to pack: templates/ holds no template folder."]);
26
+ const preflight = await preflightGuards(root);
27
+ if (preflight.length > 0) return refusal(preflight.map((r) => ` ${r.reason}\n fix: ${r.fix}`));
28
+ const { code: checkCode, output: checkOut } = await runCheck([template.key], cwd);
29
+ if (checkCode !== 0) return {
30
+ code: 1,
31
+ output: `template-kit pack: \`check\` must pass before packing.\n\n${checkOut}`
32
+ };
33
+ const files = await collectTemplateFiles(root, template.dir);
34
+ const hash = contentHash(files);
35
+ const manifest = buildPackManifest({
36
+ template: template.key,
37
+ contentHash: hash,
38
+ packedAt: (/* @__PURE__ */ new Date()).toISOString()
39
+ });
40
+ const entries = [
41
+ {
42
+ path: "pack-manifest.json",
43
+ bytes: Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8")
44
+ },
45
+ {
46
+ path: "package.json",
47
+ bytes: await readFile(join(root, "package.json"))
48
+ },
49
+ {
50
+ path: "package-lock.json",
51
+ bytes: await readFile(join(root, "package-lock.json"))
52
+ },
53
+ ...files.map((f) => ({
54
+ path: f.archivePath,
55
+ bytes: f.bytes
56
+ }))
57
+ ];
58
+ await writeFile(join(root, `${template.key}.zip`), buildZip(entries));
59
+ return {
60
+ code: 0,
61
+ output: `template-kit pack\n wrote ${template.key}.zip\n ${hash}\n kit ${manifest.kitVersion}`
62
+ };
63
+ }
64
+
65
+ //#endregion
66
+ export { runPack };
@@ -0,0 +1,15 @@
1
+ import { KIT_VERSION } from "../../index.js";
2
+
3
+ //#region src/cli/pack/manifest.ts
4
+ function buildPackManifest(input) {
5
+ return {
6
+ kitVersion: KIT_VERSION,
7
+ template: input.template,
8
+ contentHash: input.contentHash,
9
+ packedAt: input.packedAt,
10
+ check: { ok: true }
11
+ };
12
+ }
13
+
14
+ //#endregion
15
+ export { buildPackManifest };
@@ -0,0 +1,86 @@
1
+ import { deflateRawSync } from "node:zlib";
2
+
3
+ //#region src/cli/pack/zip.ts
4
+ const DOS_DATE = 33;
5
+ const DOS_TIME = 0;
6
+ const CRC_TABLE = (() => {
7
+ const table = /* @__PURE__ */ new Uint32Array(256);
8
+ for (let n = 0; n < 256; n++) {
9
+ let c = n;
10
+ for (let k = 0; k < 8; k++) c = (c & 1) !== 0 ? 3988292384 ^ c >>> 1 : c >>> 1;
11
+ table[n] = c >>> 0;
12
+ }
13
+ return table;
14
+ })();
15
+ function crc32(bytes) {
16
+ let c = 4294967295;
17
+ for (const byte of bytes) c = CRC_TABLE[(c ^ byte) & 255] ^ c >>> 8;
18
+ return (c ^ 4294967295) >>> 0;
19
+ }
20
+ /** A standard DEFLATE zip built from `entries`, in order. No zip64 — pack inputs
21
+ * are source files, far under the 4 GiB per-entry limit. */
22
+ function buildZip(entries) {
23
+ const locals = [];
24
+ const centrals = [];
25
+ let offset = 0;
26
+ for (const entry of entries) {
27
+ const name = Buffer.from(entry.path, "utf8");
28
+ const source = Buffer.from(entry.bytes);
29
+ const crc = crc32(source);
30
+ const compressed = deflateRawSync(source);
31
+ const local = Buffer.alloc(30 + name.length);
32
+ local.writeUInt32LE(67324752, 0);
33
+ local.writeUInt16LE(20, 4);
34
+ local.writeUInt16LE(2048, 6);
35
+ local.writeUInt16LE(8, 8);
36
+ local.writeUInt16LE(DOS_TIME, 10);
37
+ local.writeUInt16LE(DOS_DATE, 12);
38
+ local.writeUInt32LE(crc, 14);
39
+ local.writeUInt32LE(compressed.length, 18);
40
+ local.writeUInt32LE(source.length, 22);
41
+ local.writeUInt16LE(name.length, 26);
42
+ local.writeUInt16LE(0, 28);
43
+ name.copy(local, 30);
44
+ locals.push(local, compressed);
45
+ const central = Buffer.alloc(46 + name.length);
46
+ central.writeUInt32LE(33639248, 0);
47
+ central.writeUInt16LE(788, 4);
48
+ central.writeUInt16LE(20, 6);
49
+ central.writeUInt16LE(2048, 8);
50
+ central.writeUInt16LE(8, 10);
51
+ central.writeUInt16LE(DOS_TIME, 12);
52
+ central.writeUInt16LE(DOS_DATE, 14);
53
+ central.writeUInt32LE(crc, 16);
54
+ central.writeUInt32LE(compressed.length, 20);
55
+ central.writeUInt32LE(source.length, 24);
56
+ central.writeUInt16LE(name.length, 28);
57
+ central.writeUInt16LE(0, 30);
58
+ central.writeUInt16LE(0, 32);
59
+ central.writeUInt16LE(0, 34);
60
+ central.writeUInt16LE(0, 36);
61
+ central.writeUInt32LE(33188 << 16 >>> 0, 38);
62
+ central.writeUInt32LE(offset, 42);
63
+ name.copy(central, 46);
64
+ centrals.push(central);
65
+ offset += local.length + compressed.length;
66
+ }
67
+ const localData = Buffer.concat(locals);
68
+ const centralDir = Buffer.concat(centrals);
69
+ const eocd = Buffer.alloc(22);
70
+ eocd.writeUInt32LE(101010256, 0);
71
+ eocd.writeUInt16LE(0, 4);
72
+ eocd.writeUInt16LE(0, 6);
73
+ eocd.writeUInt16LE(entries.length, 8);
74
+ eocd.writeUInt16LE(entries.length, 10);
75
+ eocd.writeUInt32LE(centralDir.length, 12);
76
+ eocd.writeUInt32LE(localData.length, 16);
77
+ eocd.writeUInt16LE(0, 20);
78
+ return Buffer.concat([
79
+ localData,
80
+ centralDir,
81
+ eocd
82
+ ]);
83
+ }
84
+
85
+ //#endregion
86
+ export { buildZip };
package/dist/cli.js CHANGED
@@ -1,6 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { KIT_VERSION } from "./index.js";
3
3
  import { runCheck } from "./cli/check/index.js";
4
+ import { runDev } from "./cli/dev/index.js";
5
+ import { rebuildOverlay, runLinkOnce, runStatus, runUnlink } from "./cli/link/index.js";
6
+ import { watchAndRebuild } from "./cli/link/watch.js";
7
+ import { runNew, runNewSection } from "./cli/new/index.js";
8
+ import { runPack } from "./cli/pack/index.js";
4
9
  import { runTheme } from "./cli/theme/index.js";
5
10
 
6
11
  //#region src/cli/index.ts
@@ -10,25 +15,21 @@ import { runTheme } from "./cli/theme/index.js";
10
15
  * printed to template authors outside HomePages. The command → issue map is in
11
16
  * CLAUDE.md, which does not ship in the tarball.
12
17
  */
13
- const NOT_IMPLEMENTED = /* @__PURE__ */ new Set([
14
- "dev",
15
- "pack",
16
- "link",
17
- "unlink",
18
- "status"
19
- ]);
18
+ const NOT_IMPLEMENTED = /* @__PURE__ */ new Set();
20
19
  const USAGE = `template-kit ${KIT_VERSION}
21
20
 
22
21
  Usage: template-kit <command>
23
22
 
24
23
  Commands:
25
- dev Local preview server (not implemented yet)
26
- check Typecheck, lint, and validate a template
27
- theme Generate a template's theme CSS from its theme.ts
28
- pack Build the submission artifact (not implemented yet)
29
- link Overlay a local kit build (not implemented yet)
30
- unlink Remove the overlay (not implemented yet)
31
- status Show overlay state (not implemented yet)
24
+ dev Start the local preview server
25
+ check Typecheck, lint, and validate a template
26
+ new Add a template to the workspace
27
+ new-section Add a section to a template
28
+ theme Generate a template's theme CSS from its theme.ts
29
+ pack Build a template's submission zip
30
+ link Overlay a local kit build into a consumer (kit devs)
31
+ unlink Restore the registry install (kit devs)
32
+ status Show overlay state (kit devs)
32
33
 
33
34
  Options:
34
35
  -v, --version Print the kit version
@@ -52,6 +53,12 @@ async function main(argv) {
52
53
  process.stderr.write(`template-kit check: ${cause instanceof Error ? cause.message : String(cause)}\n`);
53
54
  return 1;
54
55
  }
56
+ if (cmd === "dev") try {
57
+ return await runDev(argv.slice(1), process.cwd());
58
+ } catch (cause) {
59
+ process.stderr.write(`template-kit dev: ${cause instanceof Error ? cause.message : String(cause)}\n`);
60
+ return 1;
61
+ }
55
62
  if (cmd === "theme") try {
56
63
  const { code, output } = await runTheme(argv.slice(1), process.cwd());
57
64
  process.stdout.write(output.endsWith("\n") ? output : `${output}\n`);
@@ -60,6 +67,65 @@ async function main(argv) {
60
67
  process.stderr.write(`template-kit theme: ${cause instanceof Error ? cause.message : String(cause)}\n`);
61
68
  return 1;
62
69
  }
70
+ if (cmd === "pack") try {
71
+ const { code, output } = await runPack(argv.slice(1), process.cwd());
72
+ process.stdout.write(output.endsWith("\n") ? output : `${output}\n`);
73
+ return code;
74
+ } catch (cause) {
75
+ process.stderr.write(`template-kit pack: ${cause instanceof Error ? cause.message : String(cause)}\n`);
76
+ return 1;
77
+ }
78
+ if (cmd === "status" || cmd === "unlink") {
79
+ const run = cmd === "status" ? runStatus : runUnlink;
80
+ try {
81
+ const { code, output } = await run(argv.slice(1), process.cwd());
82
+ process.stdout.write(output.endsWith("\n") ? output : `${output}\n`);
83
+ return code;
84
+ } catch (cause) {
85
+ process.stderr.write(`template-kit ${cmd}: ${cause instanceof Error ? cause.message : String(cause)}\n`);
86
+ return 1;
87
+ }
88
+ }
89
+ if (cmd === "link") try {
90
+ const result = await runLinkOnce(argv.slice(1), process.cwd());
91
+ process.stdout.write(`${result.output}\n`);
92
+ const { kitSrcDir, kitRoot, workspaceRoot } = result.watch;
93
+ process.stdout.write(" watching for kit changes — Ctrl-C to stop\n");
94
+ let watchFailed = false;
95
+ await new Promise((resolvePromise) => {
96
+ const stop = watchAndRebuild(kitSrcDir, () => {
97
+ rebuildOverlay(kitRoot, workspaceRoot).then(() => process.stdout.write(" rebuilt + recopied\n"), (err) => process.stderr.write(` rebuild failed: ${err instanceof Error ? err.message : String(err)}\n`));
98
+ }, (err) => {
99
+ watchFailed = true;
100
+ process.stderr.write(` watch error — stopping: ${err instanceof Error ? err.message : String(err)}\n`);
101
+ resolvePromise();
102
+ });
103
+ process.on("SIGINT", () => {
104
+ stop();
105
+ resolvePromise();
106
+ });
107
+ });
108
+ return watchFailed ? 1 : 0;
109
+ } catch (cause) {
110
+ process.stderr.write(`template-kit link: ${cause instanceof Error ? cause.message : String(cause)}\n`);
111
+ return 1;
112
+ }
113
+ if (cmd === "new") try {
114
+ const { code, output } = await runNew(argv.slice(1), process.cwd());
115
+ process.stdout.write(output.endsWith("\n") ? output : `${output}\n`);
116
+ return code;
117
+ } catch (cause) {
118
+ process.stderr.write(`template-kit new: ${cause instanceof Error ? cause.message : String(cause)}\n`);
119
+ return 1;
120
+ }
121
+ if (cmd === "new-section") try {
122
+ const { code, output } = await runNewSection(argv.slice(1), process.cwd());
123
+ process.stdout.write(output.endsWith("\n") ? output : `${output}\n`);
124
+ return code;
125
+ } catch (cause) {
126
+ process.stderr.write(`template-kit new-section: ${cause instanceof Error ? cause.message : String(cause)}\n`);
127
+ return 1;
128
+ }
63
129
  if (NOT_IMPLEMENTED.has(cmd)) {
64
130
  process.stderr.write(`template-kit ${cmd}: not implemented yet.\nThis 0.0.x release ships the package skeleton only.\n`);
65
131
  return 1;
@@ -0,0 +1 @@
1
+ *{box-sizing:border-box}html,body,#root{margin:0;padding:0;height:100%;background:#0f1115;color:#e6e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,Helvetica,Arial,sans-serif}a{color:inherit;text-decoration:none}button,select,input,textarea{font:inherit;color:inherit;background:#1a1d24;border:1px solid #2c313a;border-radius:4px;padding:6px 10px}button{cursor:pointer}button:hover{background:#232831}button:disabled{opacity:.4;cursor:default}.empty{padding:40px;color:#9ca0a8;text-align:center}.boot-error{padding:24px;color:#ff8b8b;font:13px/1.5 ui-monospace,Menlo,monospace;white-space:pre-wrap}.canvas-mirror{display:flex;flex-direction:column;height:100%;min-height:0}.canvas-toolbar{display:flex;gap:8px;padding:6px 10px;border-bottom:1px solid #e2e5ea;background:#fafbfc}.canvas-toolbar button{font:12px system-ui,sans-serif;padding:3px 8px;border:1px solid #cbd2dc;border-radius:5px;background:#fff;cursor:pointer}.canvas-iframe{flex:1 1 auto;width:100%;border:0;background:#fff;min-height:0}.home{max-width:880px;margin:0 auto;padding:40px 24px}.home-header{margin-bottom:28px}.home-title{font-size:15px;font-weight:600}.home-tagline{font-size:13px;color:#9ca0a8;margin-top:4px}.home-template{display:inline-flex;align-items:center;gap:8px;font-size:12px;color:#9ca0a8;margin-bottom:24px}.home-template select{min-width:160px}.home-tools{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}.tool-card{display:flex;flex-direction:column;gap:12px;background:#14171c;border:1px solid #21252d;border-radius:8px;padding:20px}.tool-card h2{font-size:14px;font-weight:600;margin:0}.tool-card p{font-size:13px;color:#9ca0a8;margin:0;line-height:1.5}.tool-card .controls{display:flex;flex-direction:column;gap:8px}.tool-card label{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:12px;color:#9ca0a8}.tool-card label select{min-width:140px}.open-btn{margin-top:auto;text-align:center;padding:8px 12px;border:1px solid #2c313a;border-radius:6px;background:#1a1d24;color:inherit;cursor:pointer}.open-btn:hover{background:#232831}.open-btn:disabled{opacity:.4;cursor:default}.home-hint{font-size:12px;color:#6b7280}.home-link{font-weight:600;color:#1a1d24}.canvas-toolbar .spacer{flex:1}.canvas-mirror__stage{display:flex;min-height:0;flex:1}.canvas-mirror__stage .canvas-iframe{flex:1}.instance-panel{width:340px;flex:none;overflow-y:auto;border-left:1px solid #e5e7eb;padding:12px;background:#fff;font:13px system-ui,sans-serif;display:flex;flex-direction:column;gap:10px}.instance-panel__head{display:flex;flex-direction:column;gap:8px}.instance-panel__slot{display:flex;align-items:center;gap:8px}.instance-panel__type{color:#6b7280}.instance-panel__fill{margin-left:auto;padding:1px 7px;border-radius:999px;font-size:11px}.instance-panel__fill--filled{background:#dcfce7;color:#166534}.instance-panel__fill--empty{background:#f3f4f6;color:#6b7280}.instance-panel__fill--missing{background:#fee2e2;color:#991b1b}.instance-panel__fixture{display:flex;flex-direction:column;gap:3px;color:#6b7280}.instance-panel__json{width:100%;min-height:180px;font:12px ui-monospace,monospace;resize:vertical}.instance-panel__error{color:#b91c1c;white-space:pre-wrap}.instance-panel__actions{display:flex;gap:8px}.instance-panel__note{color:#9ca3af;font-size:11px}