@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.
- package/CHANGELOG.md +84 -0
- package/README.md +39 -4
- package/dist/asset-modules.d.ts +81 -0
- package/dist/cli/check/config.js +5 -1
- package/dist/cli/check/css.js +27 -13
- package/dist/cli/check/loader.js +4 -0
- package/dist/cli/check/section-assets.js +58 -0
- package/dist/cli/check/stages/size.js +124 -8
- package/dist/cli/check/stages/tree.js +26 -27
- package/dist/cli/dev/build-css.js +91 -0
- package/dist/cli/dev/catalog.js +35 -0
- package/dist/cli/dev/compose-template.js +38 -0
- package/dist/cli/dev/content-override.js +58 -0
- package/dist/cli/dev/discover.js +35 -0
- package/dist/cli/dev/fill-state.js +75 -0
- package/dist/cli/dev/index.js +32 -0
- package/dist/cli/dev/inspect.js +38 -0
- package/dist/cli/dev/island-bootstrap.js +38 -0
- package/dist/cli/dev/island-map.js +35 -0
- package/dist/cli/dev/island-transform.js +31 -0
- package/dist/cli/dev/manifest-instances.js +31 -0
- package/dist/cli/dev/render-section.js +24 -0
- package/dist/cli/dev/screenshot-target.js +24 -0
- package/dist/cli/dev/section-page.js +54 -0
- package/dist/cli/dev/server.js +480 -0
- package/dist/cli/dev/slot-schema.js +12 -0
- package/dist/cli/dev/structure-summary.js +118 -0
- package/dist/cli/dev/tailwind.js +32 -0
- package/dist/cli/dev/vite-server.js +33 -0
- package/dist/cli/dev/workspace.js +63 -0
- package/dist/cli/link/index.js +74 -0
- package/dist/cli/link/overlay.js +59 -0
- package/dist/cli/link/watch.js +25 -0
- package/dist/cli/new/emit.js +48 -0
- package/dist/cli/new/index.js +69 -0
- package/dist/cli/new/scaffold/section/_Renderer.tsx +20 -0
- package/dist/cli/new/scaffold/section/_fill-spec.ts +18 -0
- package/dist/cli/new/scaffold/section/_fixtures.ts +13 -0
- package/dist/cli/new/scaffold/section/_schema.ts +24 -0
- package/dist/cli/new/scaffold/template/_manifest.json +9 -0
- package/dist/cli/new/scaffold/template/sections/hero/ExpandableText.tsx +45 -0
- package/dist/cli/new/scaffold/template/sections/hero/Renderer.tsx +40 -0
- package/dist/cli/new/scaffold/template/sections/hero/components/Feature.tsx +14 -0
- package/dist/cli/new/scaffold/template/sections/hero/fill-spec.ts +29 -0
- package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +41 -0
- package/dist/cli/new/scaffold/template/sections/hero/schema.ts +60 -0
- package/dist/cli/new/scaffold/template/theme.css +24 -0
- package/dist/cli/new/scaffold/template/theme.ts +27 -0
- package/dist/cli/new/scaffold-assets.js +20 -0
- package/dist/cli/pack/collect.js +44 -0
- package/dist/cli/pack/guards.js +57 -0
- package/dist/cli/pack/index.js +66 -0
- package/dist/cli/pack/manifest.js +15 -0
- package/dist/cli/pack/zip.js +86 -0
- package/dist/cli.js +80 -14
- package/dist/dev-client/assets/index-DfEfrp6P.css +1 -0
- package/dist/dev-client/assets/index-bpMP0b0W.js +50 -0
- package/dist/dev-client/index.html +13 -0
- package/dist/eslint/is-client-file.d.ts +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/islands/detect.d.ts +16 -0
- package/dist/islands/detect.js +22 -0
- package/dist/islands/discover.d.ts +5 -0
- package/dist/islands/discover.js +19 -0
- package/dist/islands/esbuild-plugin.d.ts +13 -0
- package/dist/islands/esbuild-plugin.js +39 -0
- package/dist/islands/wrap.d.ts +12 -0
- package/dist/islands/wrap.js +48 -0
- package/dist/package.js +1 -1
- package/dist/ssr.d.ts +31 -0
- package/dist/ssr.js +46 -0
- package/docs/INDEX.md +9 -1
- package/docs/assets.md +128 -0
- package/docs/check.md +6 -3
- package/docs/dev.md +187 -0
- package/docs/islands.md +5 -2
- package/docs/llms.txt +41 -3
- package/docs/new.md +27 -0
- package/docs/overview.md +57 -0
- package/docs/pack.md +46 -0
- package/docs/primitives.md +5 -6
- package/docs/quickstart.md +102 -0
- package/docs/recipes/INDEX.md +27 -0
- package/docs/recipes/bind-property-fact.md +58 -0
- package/docs/recipes/collection-slot.md +100 -0
- package/docs/recipes/fill-spec-decision.md +66 -0
- package/docs/recipes/fixture-states.md +68 -0
- package/docs/recipes/image-slot-crop.md +98 -0
- package/docs/recipes/interactive-island.md +100 -0
- package/docs/recipes/organize-section-folder.md +72 -0
- package/docs/recipes/prepare-submission.md +55 -0
- package/docs/recipes/second-template.md +49 -0
- package/docs/recipes/select-slot.md +59 -0
- package/docs/recipes/static-asset.md +103 -0
- package/docs/recipes/third-party-package.md +90 -0
- package/docs/rules/INDEX.md +4 -2
- package/docs/rules/bundle-binary-asset.md +33 -52
- package/docs/rules/bundle-incomplete.md +2 -2
- package/docs/rules/css-reason.md +3 -3
- package/docs/rules/no-bare-css-import.md +8 -8
- package/docs/rules/no-css-import-from-render-path.md +7 -8
- package/docs/rules/size-assets.md +88 -0
- package/docs/rules/size-island-bundle.md +123 -0
- package/docs/rules/size-section-css.md +19 -14
- package/docs/schema-system.md +28 -28
- package/docs/theme-and-css.md +2 -2
- package/docs/vocabulary.md +98 -0
- package/package.json +12 -3
- 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
|
|
26
|
-
check
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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}
|