@jxsuite/create 1.2.2 → 1.3.2

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/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # `@jxsuite/create`
2
+
3
+ > Project scaffolder for Jx — the package behind `bun create @jxsuite`.
4
+
5
+ ## Overview
6
+
7
+ `@jxsuite/create` turns an empty directory into a working Jx project. It is two things at once: an
8
+ interactive CLI, and the library whose `generateProject()` is the single generation engine shared by
9
+ every creation surface in the monorepo — the CLI imports it directly, and the dev server's Studio
10
+ API and the packaged desktop backend dynamic-import it (`packages/server/src/studio-api.ts`,
11
+ `packages/desktop/src/project-session.ts`).
12
+
13
+ The package publishes **raw TypeScript**: there is no build step, the `create-jxsuite` bin points at
14
+ `./index.ts`, and every `exports` target is a `.ts` or `.json` source file. Its only runtime
15
+ dependency is `@jxsuite/starters`.
16
+
17
+ ## CLI
18
+
19
+ ```sh
20
+ bun create @jxsuite <directory> [--template <id>]
21
+ ```
22
+
23
+ The first argument that is not part of `--template` is the destination directory; it is required (a
24
+ bare invocation prints usage and exits 1). `--template <id>` and `--template=<id>` are both accepted,
25
+ and no other flag is recognized — an unrecognized one is taken as the destination.
26
+
27
+ Prompts, in order — everything except the destination is asked interactively:
28
+
29
+ | Prompt | Default when blank |
30
+ | ---------------------------------------- | ----------------------------------------------------- |
31
+ | `Project name (<dir>): ` | basename of the destination directory |
32
+ | `Description: ` | `""` |
33
+ | `Production URL (https://example.com): ` | `""` — `generateProject` writes `https://example.com` |
34
+ | `Template [1]: ` | Blank — skipped entirely when `--template` is given |
35
+ | `Adapter [1]: ` | `static` |
36
+
37
+ The template menu numbers the four built-in templates 1-4, then the starters from
38
+ `@jxsuite/starters` 5 and up. The adapter menu is `1) static`, `2) cloudflare-pages`, `3) node`,
39
+ `4) bun`, `5) cloudflare-workers`; any unrecognized answer to either prompt falls back to the
40
+ default.
41
+
42
+ ## Templates and starters
43
+
44
+ Two disjoint id namespaces feed `--template`. A **built-in template id wins**; otherwise a starter id
45
+ is matched; an id in neither namespace **silently falls back to blank** rather than erroring.
46
+
47
+ | Built-in id | What it produces |
48
+ | --------------- | ------------------------------------------------------------------------------------- |
49
+ | `blank` | The shared skeleton, desktop-first `$media` |
50
+ | `desktop-first` | Byte-identical to `blank` |
51
+ | `mobile-first` | Same files, mobile-first `$media` (min-width queries, 375px base) |
52
+ | `mobile-app` | Mobile-first `$media`, `viewport-fit=cover`, a `theme-color` meta, and a file overlay |
53
+
54
+ `template/` (singular) is the shared skeleton copied on every non-starter scaffold — `gitignore` →
55
+ `.gitignore`, `layouts/`, `pages/`. `templates/` (plural) holds per-template overlay trees copied
56
+ **after** the skeleton with `force: true`, and today contains exactly one: `mobile-app/`, which
57
+ replaces `layouts/base.json` with an app shell (bottom nav bar, safe-area insets) and
58
+ `pages/index.md` with an app-flavored home page, and adds `pages/explore.md` and
59
+ `pages/profile.md`. So `blank`, `desktop-first` and `mobile-first` differ only in the generated
60
+ `project.json` — zero file differences. Breakpoint maps live in `templates.ts`
61
+ (`mediaForTemplate`).
62
+
63
+ Starter ids come from [`@jxsuite/starters`](../starters/README.md) — its `registry.json` is the
64
+ current list. A starter clone copies the starter tree verbatim except `node_modules`, `dist`,
65
+ `.cache`, `.jx-cache`, `.git`, and `images.json`, then re-stamps `project.json` in place: `name`
66
+ always, `url` only when non-empty, `build.adapter` only when the adapter is not `static`, and the
67
+ `<meta name="description">` content only when a description was given. The starter's design tokens,
68
+ content collections, image pipeline, and the rest of `$head` survive; its `package.json` does not —
69
+ it is rebuilt from scratch (see below).
70
+
71
+ ## What gets generated
72
+
73
+ `project.json` leads with `$schema: "./project.schema.json"` and carries `$head`, `$media`, `build`
74
+ (`{ outDir: "./dist", trailingSlash: "always" }`, plus `adapter` when it is not `static`),
75
+ `content: {}`, `defaults: { lang: "en", layout: "./layouts/base.json" }`,
76
+ `extensions: ["@jxsuite/parser"]`, `name`, `style`, and `url`.
77
+
78
+ `package.json` is `private: true`, MIT, with `build`/`dev`/`preview` scripts calling the `jx` CLI. Its
79
+ name is the project name lowercased with non-alphanumerics collapsed to hyphens. `@jxsuite/parser` is
80
+ a dependency; `@jxsuite/compiler`, `@jxsuite/runtime` and `@jxsuite/server` are devDependencies. Any
81
+ non-static adapter adds `hono: "^4"`; the two Cloudflare adapters additionally add `wrangler: "^4"`, a
82
+ `deploy` script (`wrangler deploy` or `wrangler pages deploy dist`), and a generated `wrangler.jsonc`.
83
+
84
+ A blank scaffold also creates empty `components/`, `public/`, and `content/` directories.
85
+
86
+ ## Programmatic API
87
+
88
+ ```ts
89
+ import { generateProject } from "@jxsuite/create/generate";
90
+
91
+ await generateProject("/abs/path/to/my-site", {
92
+ name: "My Site",
93
+ description: "",
94
+ url: "https://example.com",
95
+ adapter: "static", // | "cloudflare-pages" | "cloudflare-workers" | "node" | "bun"
96
+ starter: "blank", // a starter id overrides `template` outright
97
+ template: "blank", // | "desktop-first" | "mobile-first" | "mobile-app"
98
+ });
99
+ ```
100
+
101
+ `design` (optional) applies a colors/fonts/logo/breakpoints quickstart on top of the scaffold — see
102
+ `DesignOptions` in `generate.ts`.
103
+
104
+ | Export | Description |
105
+ | ---------------------------------------- | --------------------------------------------------------------------------------------------- |
106
+ | `@jxsuite/create/generate` | `generateProject`, `ProjectOptions`, `DesignOptions` |
107
+ | `@jxsuite/create/templates` | `TEMPLATES`, `listTemplates`, `isTemplateId`, `mediaForTemplate` |
108
+ | `@jxsuite/create/scaffold` | `adapterNeedsWrangler`, `buildWranglerJsonc`, `updateWranglerConfig`, `applyBindingFragments` |
109
+ | `@jxsuite/create/template-versions.json` | The `@jxsuite/*` semver ranges stamped into scaffolded projects |
110
+
111
+ `scaffold.ts` has **zero node imports** by design, so it runs in a browser and against a Git Data
112
+ API. `@jxsuite/studio` imports `updateWranglerConfig` for its Cloudflare Pages publish flow, and
113
+ `@jxsuite/compiler` imports `applyBindingFragments` for `jx db push` — adding `node:fs` there breaks
114
+ both. `isTemplateId` is used server-side as request validation: the Studio API rejects an unknown
115
+ template with a 400 before generating.
116
+
117
+ ## Dependency ranges
118
+
119
+ Every `@jxsuite/*` range a scaffold emits — including a starter clone's rebuilt `package.json` —
120
+ comes from `template-versions.json`, never from a literal in code. A test asserts exactly that, so a
121
+ future hardcoded range fails there.
122
+
123
+ **Never hand-edit that file.** In the normal case release-please rewrites it inside the release
124
+ commit (each of the four owning packages declares a jsonpath into it via `extra-files` in
125
+ `release-please-config.json`). The gate is `bun run templates:check`, which requires every range to
126
+ be exactly `^<workspace version>` across both shipping surfaces (this file and
127
+ `packages/starters/sites/*/package.json`) and runs on every PR; `bun run templates:sync` is the fixer
128
+ for drift that predates release-please. The gate exists because these ranges once named versions
129
+ that were never published, so `bun install` in a fresh scaffold could not resolve them at all.
130
+
131
+ ## Surprises worth knowing
132
+
133
+ - **Generation refuses a non-empty destination** — `Directory "<path>" is not empty`. An existing but
134
+ empty directory is fine.
135
+ - **`$schema` points at a file this package does not write.** `project.schema.json` comes from
136
+ `jx schema` (specs/extensions.md §5.2), so a fresh scaffold has a dangling reference until that
137
+ runs.
138
+ - **A starter's `package.json` is rebuilt, not copied** — anything its manifest declared beyond
139
+ name/scripts/deps is lost by design.
140
+ - **The design quickstart is best-effort on a starter**: an override only lands on a key the
141
+ starter's style already declares, and `--color-primary-hover` is deliberately left alone. Two of
142
+ them fall back rather than give up — `text` and `bodyFont` try `--color-text-primary` and
143
+ `--font-body` first, then the plain `color` / `fontFamily` keys. On a blank scaffold values are
144
+ written directly. A non-empty `design.media` replaces `$media` wholesale;
145
+ an empty one is treated as absent.
146
+ - **`design.logo.name` is the only untrusted-input path to the filesystem.** It is flattened to its
147
+ basename and must match `/\.(svg|png|jpe?g|webp|gif|ico)$/i` or generation throws. That is a
148
+ security boundary, not a convenience check.
149
+ - **`index.ts` exports `ready`, not a top-level await.** Bun's test runtime drops a
150
+ dynamically-imported module's top-level-await continuation, so the interactive run is a promise
151
+ tests can await; the no-arg usage guard stays at module scope so a bare invocation still exits
152
+ during evaluation.
153
+ - **`template/` and `templates/` resolve from `import.meta.dirname`**, which is why the desktop build
154
+ stages them to exact paths and `packages/desktop/scripts/verify-bundle.ts` fails the packaged build
155
+ when they are missing.
156
+ - **`generate.ts` imports `@jxsuite/starters` lazily**, inside the starter path only, so a blank
157
+ scaffold never loads it; the CLI entry imports `listStarters` eagerly, because the menu needs it.
158
+ An unknown starter id passed programmatically throws `Unknown starter: "<id>"`.
159
+
160
+ ## Testing
161
+
162
+ ```sh
163
+ bun test --isolate # from packages/create
164
+ bun test --isolate --coverage
165
+ ```
166
+
167
+ Per-file coverage thresholds live in `packages/create/bunfig.toml`.
168
+
169
+ ## Docs
170
+
171
+ User-facing documentation for this surface is `docs/framework/build/cli.md`, whose `code:`
172
+ frontmatter already lists `index.ts`, `cli-args.ts`, `templates.ts` and `generate.ts` — a behavior
173
+ change here trips `bun run docs:sync`.
174
+
175
+ ## License
176
+
177
+ MIT
package/generate.ts CHANGED
@@ -10,6 +10,8 @@ import { existsSync } from "node:fs";
10
10
  import { mediaForTemplate } from "./templates";
11
11
  import type { TemplateId } from "./templates";
12
12
 
13
+ import TEMPLATE_VERSIONS from "./template-versions.json";
14
+
13
15
  const __dirname = import.meta.dirname;
14
16
  const TEMPLATE_DIR = join(__dirname, "template");
15
17
  const TEMPLATE_OVERLAYS_DIR = join(__dirname, "templates");
@@ -393,14 +395,20 @@ function buildPackageJson({
393
395
 
394
396
  // Projects own their extension dependencies (specs/extensions.md §3) — the parser extension is
395
397
  // A runtime dependency, resolved project-first by every host.
398
+ //
399
+ // The ranges come from ./template-versions.json, which is GENERATED and CI-verified
400
+ // (`bun run templates:check`) and rewritten by release-please inside the release commit itself
401
+ // (release-please-config.json extra-files). Hardcoding them here is what left every scaffolded
402
+ // Project asking for a compiler version that was never published. Never hand-edit the
403
+ // Map; `bun run templates:sync` is the fixer.
396
404
  const dependencies: Record<string, string> = {
397
- "@jxsuite/parser": "^0.35.1",
405
+ "@jxsuite/parser": TEMPLATE_VERSIONS.parser,
398
406
  };
399
407
  const devDependencies: Record<string, string> = {
400
- "@jxsuite/compiler": "^0.19.0",
401
- "@jxsuite/runtime": "^0.19.0",
408
+ "@jxsuite/compiler": TEMPLATE_VERSIONS.compiler,
409
+ "@jxsuite/runtime": TEMPLATE_VERSIONS.runtime,
402
410
  // The `dev` script spawns @jxsuite/server's dev entry (see jx dev in the compiler CLI)
403
- "@jxsuite/server": "^1.0.0",
411
+ "@jxsuite/server": TEMPLATE_VERSIONS.server,
404
412
  };
405
413
 
406
414
  const scripts: Record<string, string> = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/create",
3
- "version": "1.2.2",
3
+ "version": "1.3.2",
4
4
  "description": "Scaffold a new Jx project",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -17,6 +17,7 @@
17
17
  "cli-args.ts",
18
18
  "templates.ts",
19
19
  "scaffold.ts",
20
+ "template-versions.json",
20
21
  "template/",
21
22
  "templates/"
22
23
  ],
@@ -25,7 +26,8 @@
25
26
  ".": "./index.ts",
26
27
  "./generate": "./generate.ts",
27
28
  "./templates": "./templates.ts",
28
- "./scaffold": "./scaffold.ts"
29
+ "./scaffold": "./scaffold.ts",
30
+ "./template-versions.json": "./template-versions.json"
29
31
  },
30
32
  "publishConfig": {
31
33
  "provenance": true
@@ -35,6 +37,6 @@
35
37
  "test:coverage": "bun test --isolate --coverage"
36
38
  },
37
39
  "dependencies": {
38
- "@jxsuite/starters": "^1.2.2"
40
+ "@jxsuite/starters": "^1.5.0"
39
41
  }
40
42
  }
@@ -0,0 +1,6 @@
1
+ {
2
+ "parser": "^1.5.2",
3
+ "compiler": "^2.0.2",
4
+ "runtime": "^2.0.2",
5
+ "server": "^2.2.1"
6
+ }
package/templates.ts CHANGED
@@ -29,7 +29,7 @@ export const TEMPLATES: readonly TemplateMeta[] = [
29
29
  name: "Mobile First",
30
30
  },
31
31
  {
32
- description: "A mobile-first app shell with a fixed bottom navigation and scrollable content.",
32
+ description: "A mobile-first app shell: a scrolling content area above a bottom nav bar.",
33
33
  id: "mobile-app",
34
34
  name: "Mobile App",
35
35
  },