@homepages/create-workspace 0.9.0 → 0.10.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/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // `npm create @homepages/workspace <name>` — stamp a workspace container: shared
3
3
  // root context + one working starter template, then install and git-init.
4
4
  import { execFileSync } from "node:child_process";
5
- import { existsSync, readFileSync, realpathSync } from "node:fs";
5
+ import { existsSync, realpathSync } from "node:fs";
6
6
  import { mkdir } from "node:fs/promises";
7
7
  import { dirname, join, resolve } from "node:path";
8
8
  import { createInterface } from "node:readline/promises";
@@ -10,6 +10,14 @@ import { fileURLToPath } from "node:url";
10
10
  import { stampTree } from "./stamp.js";
11
11
  const ASSETS = join(dirname(fileURLToPath(import.meta.url)), "..", "scaffold");
12
12
  const NAME_RE = /^[a-z][a-z0-9-]*$/;
13
+ /**
14
+ * The kit's caret pin. The kit versions independently of this scaffolder, so
15
+ * it cannot be derived from this package's own version and is a committed
16
+ * constant — same reasoning as ESLINT_PLUGIN_VERSION below — bumped when the
17
+ * kit releases a new minor. A scaffolded workspace pinned to a stale range
18
+ * never picks up the kit's current contract.
19
+ */
20
+ const KIT_VERSION = "0.10.0";
13
21
  /**
14
22
  * The authoring ESLint preset's caret pin. Unlike the kit it cannot be derived from
15
23
  * this package's own version — @homepages/eslint-plugin-template versions
@@ -17,7 +25,7 @@ const NAME_RE = /^[a-z][a-z0-9-]*$/;
17
25
  * new minor. A scaffolded workspace whose config imports a preset it does not depend
18
26
  * on fails at `npm install`, not at lint time, so this is not optional.
19
27
  */
20
- const ESLINT_PLUGIN_VERSION = "0.1.0";
28
+ const ESLINT_PLUGIN_VERSION = "0.2.0";
21
29
  /**
22
30
  * The CLI's caret pin. Same reasoning as ESLINT_PLUGIN_VERSION: @homepages/template-cli
23
31
  * versions independently of the kit, so it cannot be derived from this package's own
@@ -27,14 +35,34 @@ const ESLINT_PLUGIN_VERSION = "0.1.0";
27
35
  * kit no longer carries a bin — so a workspace missing it fails on `npm run dev` with
28
36
  * "command not found", which reads as a broken scaffold rather than a missing pin.
29
37
  */
30
- const TEMPLATE_CLI_VERSION = "0.1.0";
38
+ const TEMPLATE_CLI_VERSION = "0.2.0";
39
+ /**
40
+ * The dev media pack's caret pin. Same committed-constant reasoning as the pins
41
+ * above — the pack versions independently of this scaffolder.
42
+ *
43
+ * The pack is what the `dev` server serves asset BYTES from: the kit carries only
44
+ * the manifest (copied into its dist at build time), so a workspace without this
45
+ * dependency has an index of media it cannot resolve a single pixel of, and every
46
+ * fixture image renders broken.
47
+ *
48
+ * It is a devDependency, not a dependency: media is dev-time authoring content, in
49
+ * the same category as vite and the rest of the toolchain, and nothing in a packed
50
+ * template references it.
51
+ *
52
+ * This range must cover the pack's actual published version on npm, or a
53
+ * scaffolded workspace outside this monorepo dies on `npm install` with a 404 —
54
+ * the drift guard in `src/index.test.ts` (the stamped dev-media range covers the
55
+ * pack's actual version) fails the moment this constant falls behind a real bump.
56
+ */
57
+ const DEV_MEDIA_VERSION = "0.0.1";
31
58
  export async function scaffoldWorkspace(name, targetDir, opts) {
32
59
  await mkdir(targetDir, { recursive: true });
33
60
  await stampTree(join(ASSETS, "workspace"), targetDir, {
34
61
  WORKSPACE_NAME: name,
35
- KIT_VERSION: opts.kitVersion,
62
+ KIT_VERSION: opts.kitVersion ?? KIT_VERSION,
36
63
  ESLINT_PLUGIN_VERSION: opts.eslintPluginVersion ?? ESLINT_PLUGIN_VERSION,
37
64
  TEMPLATE_CLI_VERSION: opts.templateCliVersion ?? TEMPLATE_CLI_VERSION,
65
+ DEV_MEDIA_VERSION: opts.devMediaVersion ?? DEV_MEDIA_VERSION,
38
66
  });
39
67
  await stampTree(join(ASSETS, "template"), join(targetDir, "templates", "starter"), {
40
68
  TEMPLATE_KEY: "starter",
@@ -45,17 +73,6 @@ export async function scaffoldWorkspace(name, targetDir, opts) {
45
73
  if (opts.install !== false)
46
74
  execFileSync("npm", ["install"], { cwd: targetDir, stdio: "inherit" });
47
75
  }
48
- /**
49
- * The kit version to caret-pin: this scaffolder is published version-synced
50
- * with the kit, so its own `version` IS the kit version. Read at runtime
51
- * (rather than a static JSON import) because `../package.json` sits outside
52
- * `rootDir: "src"` and trips TS6059 under a static import.
53
- */
54
- function kitVersion() {
55
- const raw = readFileSync(new URL("../package.json", import.meta.url), "utf8");
56
- const pkg = JSON.parse(raw);
57
- return typeof pkg.version === "string" ? pkg.version : "0.0.0";
58
- }
59
76
  async function promptName() {
60
77
  const rl = createInterface({ input: process.stdin, output: process.stdout });
61
78
  try {
@@ -77,7 +94,7 @@ async function main() {
77
94
  process.stderr.write(`create-workspace: ${name}/ already exists.\n`);
78
95
  return 1;
79
96
  }
80
- await scaffoldWorkspace(name, targetDir, { kitVersion: kitVersion() });
97
+ await scaffoldWorkspace(name, targetDir, {});
81
98
  process.stdout.write(`\nCreated ${name}/\n\n` +
82
99
  ` cd ${name}\n` +
83
100
  ` npm run dev # preview the starter (the island hydrates)\n` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homepages/create-workspace",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "Scaffold a HomePages template workspace.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -1,19 +1,21 @@
1
1
  import type { ReactNode } from "react";
2
2
 
3
- import { Section, Slot } from "@homepages/template-kit";
3
+ import { Section, slotMarkers } from "@homepages/template-kit";
4
4
 
5
- import type { Props } from "./schema";
5
+ import type { Props, schema } from "./schema";
6
6
 
7
- // {{SECTION_DISPLAY}} a pure, server-rendered function of its props. Wrap
8
- // every editable slot in <Slot id="…"> so the editor can resolve clicks. For
9
- // interactivity, add a "use client" island file beside this one.
7
+ const { slot } = slotMarkers<typeof schema>();
8
+
9
+ // {{SECTION_DISPLAY}} a pure, server-rendered function of its props. `slots.x` is your
10
+ // data; `{...slot.x}` tells the editor where you put it. For interactivity, add a
11
+ // "use client" island file beside this one.
10
12
  export default function {{SECTION_PASCAL}}Renderer({ slots, nav }: Props): ReactNode {
11
13
  return (
12
14
  <Section id={nav.selfAnchor} className="bg-background text-ink">
13
15
  <div className="mx-auto max-w-3xl px-6 py-12">
14
- <Slot id="headline" textLeaf>
15
- <h2 className="text-2xl font-semibold">{slots.headline}</h2>
16
- </Slot>
16
+ <h2 className="text-2xl font-semibold" {...slot.headline.text}>
17
+ {slots.headline}
18
+ </h2>
17
19
  </div>
18
20
  </Section>
19
21
  );
@@ -3,7 +3,7 @@
3
3
  "name": "{{TEMPLATE_NAME}}",
4
4
  "deliverable_type": "single_property_website",
5
5
  "sections": [
6
- { "section": "hero", "instance_id": "hero-1", "options": {}, "locked": [], "required": true, "reorderable": false }
6
+ { "section": "hero", "instance_id": "hero-1", "options": {}, "locked": [], "required": true }
7
7
  ],
8
8
  "reconcile": []
9
9
  }
@@ -1,35 +1,35 @@
1
1
  import type { ReactNode } from "react";
2
2
 
3
- import { Image, Section, Slot } from "@homepages/template-kit";
3
+ import { Image, Section, slotMarkers } from "@homepages/template-kit";
4
4
 
5
5
  import { Feature } from "./components/Feature";
6
6
  import ExpandableText from "./ExpandableText";
7
- import type { Props } from "./schema";
7
+ import type { Props, schema } from "./schema";
8
8
 
9
- // Hero a pure server-rendered function of its props: no fetches, no globals,
10
- // no state. Every editable slot's outer DOM is wrapped in <Slot id="…"> (or a
11
- // primitive with slotId="…") so the editor can map a click back to the slot.
12
- // The one interactive part is the <ExpandableText> island.
9
+ const { slot } = slotMarkers<typeof schema>();
10
+
11
+ // Hero a pure server-rendered function of its props: no fetches, no globals, no state.
12
+ // `slots.x` is your data; `{...slot.x}` tells the editor where you put it. The one
13
+ // interactive part is the <ExpandableText> island.
13
14
  export default function HeroRenderer({ slots, options, nav }: Props): ReactNode {
14
15
  const align = options.align === "left" ? "items-start text-left" : "items-center text-center";
15
16
  return (
16
17
  <Section id={nav.selfAnchor} className="bg-background text-ink">
17
18
  <Image
18
- slotId="hero_image"
19
- src={slots.hero_image.url}
20
- responsive={slots.hero_image.responsive}
21
- alt={slots.hero_image.alt}
19
+ {...slot.hero_image}
20
+ value={slots.hero_image}
21
+ frame={false}
22
22
  loading="eager"
23
23
  className="w-full"
24
24
  />
25
25
  <div className={`mx-auto flex max-w-3xl flex-col gap-4 px-6 py-12 ${align}`}>
26
- <Slot id="headline" textLeaf>
27
- <h1 className="text-3xl font-semibold">{slots.headline}</h1>
28
- </Slot>
29
- <Slot id="blurb">
26
+ <h1 className="text-3xl font-semibold" {...slot.headline.text}>
27
+ {slots.headline}
28
+ </h1>
29
+ <div {...slot.blurb}>
30
30
  {/* The island receives JSON-serializable props only. */}
31
31
  <ExpandableText text={slots.blurb} collapsedChars={140} moreLabel="Read more" />
32
- </Slot>
32
+ </div>
33
33
  <div className="mt-2 grid w-full grid-cols-2 gap-3">
34
34
  <Feature label="Status" value="For sale" />
35
35
  <Feature label="Type" value="Single-family" />
@@ -6,7 +6,7 @@ import { luxuryMultiUnit } from "@homepages/template-kit/fixtures";
6
6
  // needed for it. Add a `states` entry only for a designed scenario that
7
7
  // combines overrides at once (a content-length or option branch the section
8
8
  // renders differently).
9
- const heroImage = luxuryMultiUnit.photos[19]!; // exterior facade shot
9
+ const heroImage = luxuryMultiUnit.photos.find((p) => p.tags.room === "exterior-front")!;
10
10
 
11
11
  const fixtures: FixtureModule = {
12
12
  base: {
@@ -1,5 +1,5 @@
1
1
  <!-- Tool-agnostic mirror of CLAUDE.md — keep the two in sync. -->
2
-
2
+ <!-- BEGIN:homepages-kit -->
3
3
  # {{WORKSPACE_NAME}} — agent guide
4
4
 
5
5
  You are authoring HomePages marketing-section templates in this workspace.
@@ -15,8 +15,13 @@ You are authoring HomePages marketing-section templates in this workspace.
15
15
  All three are declared once, at this root, and every template shares them. Do not
16
16
  add per-template `package.json`, `tsconfig`, or `eslint` files — there is exactly
17
17
  one of each, at this root.
18
- - **The docs corpus is your whole universe:** `node_modules/@homepages/template-kit/guide/`
19
- (`llms.txt` is the router). Read it before authoring; do not guess the contract.
18
+ - **Read the docs corpus before authoring — your training data is outdated.** The
19
+ authoring contract changes with every kit minor, and the corpus installed in this
20
+ workspace is the version you are building against:
21
+ `node_modules/@homepages/template-kit/guide/` (`llms.txt` is the router). Grep it;
22
+ do not guess the contract, and do not rely on what you remember about it. The same
23
+ pages are browsable at https://docs.homepages.io, but the local copy is
24
+ authoritative — it matches your installed version.
20
25
 
21
26
  ## The section contract (reserved files)
22
27
 
@@ -46,3 +51,7 @@ islands — is yours to organize.
46
51
 
47
52
  Read the starter section (`templates/starter/sections/hero/`) — it is a working,
48
53
  annotated example of the whole contract.
54
+ <!-- END:homepages-kit -->
55
+
56
+ <!-- Your own notes belong outside the markers above; anything inside them may be
57
+ replaced when the kit updates this file. -->
@@ -1,3 +1,4 @@
1
+ <!-- BEGIN:homepages-kit -->
1
2
  # {{WORKSPACE_NAME}} — agent guide
2
3
 
3
4
  You are authoring HomePages marketing-section templates in this workspace.
@@ -13,8 +14,13 @@ You are authoring HomePages marketing-section templates in this workspace.
13
14
  All three are declared once, at this root, and every template shares them. Do not
14
15
  add per-template `package.json`, `tsconfig`, or `eslint` files — there is exactly
15
16
  one of each, at this root.
16
- - **The docs corpus is your whole universe:** `node_modules/@homepages/template-kit/guide/`
17
- (`llms.txt` is the router). Read it before authoring; do not guess the contract.
17
+ - **Read the docs corpus before authoring — your training data is outdated.** The
18
+ authoring contract changes with every kit minor, and the corpus installed in this
19
+ workspace is the version you are building against:
20
+ `node_modules/@homepages/template-kit/guide/` (`llms.txt` is the router). Grep it;
21
+ do not guess the contract, and do not rely on what you remember about it. The same
22
+ pages are browsable at https://docs.homepages.io, but the local copy is
23
+ authoritative — it matches your installed version.
18
24
 
19
25
  ## The section contract (reserved files)
20
26
 
@@ -44,3 +50,7 @@ islands — is yours to organize.
44
50
 
45
51
  Read the starter section (`templates/starter/sections/hero/`) — it is a working,
46
52
  annotated example of the whole contract.
53
+ <!-- END:homepages-kit -->
54
+
55
+ <!-- Your own notes belong outside the markers above; anything inside them may be
56
+ replaced when the kit updates this file. -->
@@ -13,6 +13,7 @@
13
13
  "react-dom": "^19.0.0"
14
14
  },
15
15
  "devDependencies": {
16
+ "@homepages/dev-media": "^{{DEV_MEDIA_VERSION}}",
16
17
  "@homepages/eslint-plugin-template": "^{{ESLINT_PLUGIN_VERSION}}",
17
18
  "@homepages/template-cli": "^{{TEMPLATE_CLI_VERSION}}",
18
19
  "@tailwindcss/cli": "^4.3.0",