@getvitops/create 0.2.0 → 0.3.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 ADDED
@@ -0,0 +1,59 @@
1
+ # @getvitops/create
2
+
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 44de07f: Shared toolchain version + changelogs that reach consumers.
8
+ - **`@getvitops/astro` now shares the toolchain version** (`core`/`generator`/`utils`/`cli`/`vite`),
9
+ so it moves from its own `0.4.x` line onto the group's. The number changes; the package does not —
10
+ install it at the same version as `@getvitops/cli`. It was already being bumped on every toolchain
11
+ release by its dependency updates, and it depends on core, generator, utils _and_ vite, so a
12
+ separate version line cost the same churn while leaving the compatible pairing implicit. The
13
+ lockstep is load-bearing: the generator ships a snapshot of core's CSS + web-component bundles
14
+ while the Astro integration copies the _installed_ core's bundles, so mismatched versions can leave
15
+ the CSS and the components disagreeing.
16
+
17
+ - **Every package now ships its `CHANGELOG.md` in the published tarball.** npm does not include
18
+ changelogs by default, so none of this history previously reached anyone who installed the
19
+ packages. Per-package history now reads from `node_modules/@getvitops/<pkg>/CHANGELOG.md`;
20
+ curated toolchain-level release notes live in the repo's root `CHANGELOG.md`.
21
+
22
+ ## 0.3.0
23
+
24
+ ### Minor Changes
25
+
26
+ - emdash template: hosting is now a configurable seam — astro.config resolves
27
+ adapter + database/storage via `vitopsHosting()` from `@getvitops/emdash`
28
+ (^0.2.0), documented in the new README "Hosting" section. New
29
+ `pnpm run init:hosting` provisions the platform: on cloudflare it creates the
30
+ isolated dev D1 + R2 via wrangler and sets the `DEV_D1_ID` /
31
+ `CLOUDFLARE_ACCOUNT_ID` repo secrets via `gh` (idempotent, `--dry-run`;
32
+ `--target node` prints the Node-host switch steps). Also preconfigures
33
+ permission allows for the Astro/EmDash docs MCP tools in `.claude/settings.json`.
34
+
35
+ ## 0.2.0
36
+
37
+ ### Minor Changes
38
+
39
+ - emdash template: `pnpm run init:github` — one-time GitHub bootstrap via the
40
+ `gh` CLI. Creates a repo named after the project, makes the initial commit if
41
+ needed, pushes `main` + `dev`, sets `dev` as the default branch, and prints
42
+ the remaining manual steps (dev D1/R2, secrets, branch protection).
43
+ Idempotent, with `--dry-run` and `--public` flags.
44
+
45
+ ## 0.1.0
46
+
47
+ ### Minor Changes
48
+
49
+ - New `@getvitops/create` package: `vp create` org templates for the Vitops
50
+ design system. First template: `emdash` — an EmDash CMS website on Cloudflare
51
+ Workers (D1 + R2) with `@getvitops/astro` (design-system CSS, favicons,
52
+ web-component runtime), `@getvitops/emdash` (editor blocks), and
53
+ `@getvitops/cli` pre-wired, plus a seed with a `pages` collection, menus, and
54
+ starter content — home, about, terms, privacy, 404 (published via the setup
55
+ wizard's "include sample content" option). Ships a dev → main promotion flow
56
+ (auto-deploying `dev`/`main` workflows, a manual promote workflow + matching
57
+ `promote` agent skill, and an isolated dev worker config), the Astro + EmDash
58
+ docs MCP servers in `.mcp.json`, and a Claude Code web hook that provisions
59
+ Node 24 + pnpm. Scaffold with `vp create @getvitops:emdash`.
package/README.md CHANGED
@@ -43,6 +43,12 @@ with every relevant `@getvitops/*` package pre-wired:
43
43
  - **GitHub bootstrap** — `pnpm run init:github` creates a same-named GitHub
44
44
  repo via the `gh` CLI, pushes `main` + `dev`, and makes `dev` the default
45
45
  branch (idempotent; `--dry-run` / `--public` flags).
46
+ - **Hosting seam** — astro.config resolves its adapter + EmDash
47
+ database/storage through `vitopsHosting()` from `@getvitops/emdash`
48
+ (`cloudflare` default; `node` for VPS/docker/k8s after installing
49
+ `@astrojs/node` + `better-sqlite3`), and `pnpm run init:hosting` provisions
50
+ the platform (cloudflare: dev D1 + R2 created via wrangler, `DEV_D1_ID` /
51
+ `CLOUDFLARE_ACCOUNT_ID` repo secrets set via `gh`; idempotent, `--dry-run`).
46
52
  - **dev → main promotion flow** — GitHub Actions deploying `dev` to an
47
53
  isolated `-dev` worker (`dev.<domain>` with a custom domain; own D1/R2 via
48
54
  `scripts/build-dev-wrangler.mjs`, which hard-fails without `DEV_D1_ID`) and
@@ -66,3 +72,10 @@ provisions the D1/R2 resources on first deploy).
66
72
 
67
73
  Template dependencies must use concrete npm ranges (no `workspace:*` /
68
74
  `catalog:` — the scaffolded project lives outside this monorepo).
75
+
76
+ ## Changelog
77
+
78
+ This package's history: [`CHANGELOG.md`](./CHANGELOG.md) (shipped in the npm tarball, so it
79
+ also reads from `node_modules/@getvitops/create/CHANGELOG.md`). This package versions
80
+ independently of the `@getvitops` toolchain (`core`/`generator`/`utils`/`cli`/`vite`/`astro`,
81
+ which share one version).
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@getvitops/create",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Project templates for the Vitops design system — scaffold wired-up sites with `vp create @getvitops`.",
5
5
  "license": "MIT",
6
6
  "files": [
7
- "templates"
7
+ "templates",
8
+ "CHANGELOG.md"
8
9
  ],
9
10
  "type": "module",
10
11
  "publishConfig": {
@@ -1,4 +1,7 @@
1
1
  {
2
+ "permissions": {
3
+ "allow": ["mcp__astro-docs__search_astro_docs", "mcp__emdash-docs__search_docs"]
4
+ },
2
5
  "hooks": {
3
6
  "SessionStart": [
4
7
  {
@@ -22,6 +22,11 @@ styled by the Vitops design system.
22
22
  full generated class-vocabulary reference to this file.
23
23
  - The Worker cron trigger in `wrangler.jsonc` drives scheduled publishing —
24
24
  don't remove it.
25
+ - Hosting (adapter + EmDash database/storage) comes from `vitopsHosting()`
26
+ (`@getvitops/emdash`) in astro.config.mjs — never hard-code an adapter or
27
+ d1/r2/sqlite there. Switch targets via `vitopsHosting({ target })` or the
28
+ `HOSTING` env var (see README "Hosting"); the `node` target needs
29
+ `@astrojs/node` + `better-sqlite3` installed first.
25
30
 
26
31
  ## Branch model
27
32
 
@@ -49,4 +54,6 @@ searching them over pre-trained knowledge for Astro/EmDash questions.
49
54
  - `pnpm deploy:dev` — build + deploy the isolated dev worker (needs `DEV_D1_ID`)
50
55
  - `pnpm run init:github` — one-time: create + push the GitHub repo (main + dev,
51
56
  dev default). Only run when the user asks to set up GitHub.
57
+ - `pnpm run init:hosting` — one-time: provision the hosting platform (dev
58
+ D1/R2 + repo secrets on cloudflare). Only run when the user asks for it.
52
59
  - `npx vitops validate design-system.json` — schema-check the design config
@@ -74,14 +74,15 @@ One-time setup:
74
74
  pushes `main` + `dev`, and makes `dev` the default branch. Idempotent;
75
75
  `-- --dry-run` previews. Then branch-protect `main` (require the promotion
76
76
  PR + review).
77
- 2. Create the isolated dev resources:
78
- `wrangler d1 create my-emdash-site-dev` and
79
- `wrangler r2 bucket create my-emdash-site-media-dev`.
80
- 3. Add repo secrets: `gh secret set CLOUDFLARE_API_TOKEN`,
81
- `gh secret set CLOUDFLARE_ACCOUNT_ID`, and `gh secret set DEV_D1_ID`
82
- (the id printed by `d1 create`). Optionally `PROMOTE_TOKEN` (a PAT allowed
83
- to bypass protection) for hands-off promotion. The first workflow runs
84
- fail until these exist.
77
+ 2. `pnpm run init:hosting` — provisions the hosting platform (after
78
+ `npx wrangler login`): creates the isolated dev D1 + R2 and sets the
79
+ `DEV_D1_ID` / `CLOUDFLARE_ACCOUNT_ID` repo secrets. Idempotent;
80
+ `-- --dry-run` previews.
81
+ 3. Create a Cloudflare API token (Workers + D1 + R2 edit —
82
+ <https://dash.cloudflare.com/profile/api-tokens>; tokens can't be created
83
+ via CLI) and `gh secret set CLOUDFLARE_API_TOKEN`. Optionally
84
+ `PROMOTE_TOKEN` (a PAT allowed to bypass protection) for hands-off
85
+ promotion. The first workflow runs fail until the secrets exist.
85
86
 
86
87
  `pnpm deploy:dev` reproduces the dev deploy locally: it rewrites the
87
88
  adapter-generated wrangler config (`scripts/build-dev-wrangler.mjs`) so the
@@ -89,6 +90,30 @@ dev worker never touches prod data — it hard-fails without `DEV_D1_ID`,
89
90
  on purpose. Schema/content changes live in the EmDash database, not git, so
90
91
  promote them separately (via the prod admin or its MCP server).
91
92
 
93
+ ## Hosting
94
+
95
+ The adapter and EmDash database/storage come from one call in
96
+ `astro.config.mjs` — `vitopsHosting()` from `@getvitops/emdash` — so hosting
97
+ is a configurable dimension of the project, not a rewrite:
98
+
99
+ - **`cloudflare`** (default) — Workers + D1 + R2. `wrangler.jsonc`,
100
+ `src/worker.ts` (fetch + scheduled handlers), and the deploy workflows
101
+ belong to this target.
102
+ - **`node`** — `@astrojs/node` (standalone) + SQLite file + local uploads,
103
+ for a VPS / docker-compose / k8s host. Not installed by default; to switch:
104
+ `pnpm add @astrojs/node better-sqlite3`, then `vitopsHosting({ target:
105
+ 'node' })` (or set `HOSTING=node` — the env var wins). Run the built site
106
+ with `node ./dist/server/entry.mjs`; data lives in `./data/` (gitignored) —
107
+ persist it as a volume. Scheduled publishing runs in-process on Node, so
108
+ the worker/cron pieces aren't needed. For production, move storage to
109
+ S3-compatible and/or the database to Postgres via `vitopsHosting({ node:
110
+ { database, storage } })`. CI deploy for Node hosts is TBD — the shipped
111
+ workflows are Cloudflare-specific.
112
+
113
+ Moving an existing site between targets (either direction) is a data
114
+ migration: content lives in the database (D1 export / EmDash seed round-trip)
115
+ and media in the storage backend (bucket/directory copy).
116
+
92
117
  ## AI tooling
93
118
 
94
119
  - **MCP servers** (`.mcp.json`): the Astro docs (`mcp.docs.astro.build`) and
@@ -12,6 +12,9 @@ worker-configuration.d.ts
12
12
  # wrangler local state (local D1/R2 data lives here during `astro dev`)
13
13
  .wrangler/
14
14
 
15
+ # node hosting target: SQLite db + local uploads
16
+ data/
17
+
15
18
  # generated by the getvitops() integration on dev/build
16
19
  src/styles/
17
20
  public/vitops/
@@ -1,14 +1,21 @@
1
- import cloudflare from '@astrojs/cloudflare';
2
1
  import react from '@astrojs/react';
3
- import { d1, r2 } from '@emdash-cms/cloudflare';
4
2
  import getvitops from '@getvitops/astro';
5
- import { vitopsEmdash } from '@getvitops/emdash';
3
+ import { vitopsEmdash, vitopsHosting } from '@getvitops/emdash';
6
4
  import { defineConfig } from 'astro/config';
7
5
  import emdash from 'emdash/astro';
8
6
 
7
+ // Hosting seam: adapter + EmDash database/storage for the configured target.
8
+ // Default 'cloudflare' (Workers + D1 + R2 — see wrangler.jsonc / src/worker.ts).
9
+ // To move to a Node host (VPS / docker-compose / k8s) later:
10
+ // pnpm add @astrojs/node better-sqlite3
11
+ // then pass { target: 'node' } here (or set the HOSTING env var, which wins).
12
+ // Content lives in the database and media in storage, so switching hosts is a
13
+ // data migration, not a rewrite — see README "Hosting".
14
+ const { adapter, database, storage } = await vitopsHosting();
15
+
9
16
  export default defineConfig({
10
17
  output: 'server',
11
- adapter: cloudflare(),
18
+ adapter,
12
19
  image: {
13
20
  layout: 'constrained',
14
21
  responsiveStyles: true,
@@ -17,8 +24,8 @@ export default defineConfig({
17
24
  // The EmDash admin UI (/_emdash/admin) is a React app.
18
25
  react(),
19
26
  emdash({
20
- database: d1({ binding: 'DB', session: 'auto' }),
21
- storage: r2({ binding: 'MEDIA' }),
27
+ database,
28
+ storage,
22
29
  // Vitops design-system blocks (image compare, copy snippet, banner,
23
30
  // disclosure, carousel) in the editor slash menu. Default 'integration'
24
31
  // script delivery: Layout.astro renders <Head /> from @getvitops/astro,
@@ -10,6 +10,7 @@
10
10
  "deploy": "astro build && wrangler deploy",
11
11
  "deploy:dev": "astro build && node scripts/build-dev-wrangler.mjs && wrangler deploy --config dist/server/wrangler.dev.json",
12
12
  "init:github": "node scripts/init-github.mjs",
13
+ "init:hosting": "node scripts/init-hosting.mjs",
13
14
  "typecheck": "astro check",
14
15
  "build:css": "vitops generate -i design-system.json -f tailwind -o src/styles",
15
16
  "prepare": "vp config"
@@ -19,7 +20,7 @@
19
20
  "@astrojs/react": "^6.0.0",
20
21
  "@emdash-cms/cloudflare": "^0.31.0",
21
22
  "@getvitops/astro": "^0.4.0",
22
- "@getvitops/emdash": "^0.1.0",
23
+ "@getvitops/emdash": "^0.2.0",
23
24
  "astro": "^7.1.2",
24
25
  "emdash": "^0.31.0",
25
26
  "react": "^19.2.4",
@@ -30,6 +31,7 @@
30
31
  "@cloudflare/workers-types": "^4.20260305.1",
31
32
  "@getvitops/cli": "^0.4.0",
32
33
  "typescript": "^6.0.3",
34
+ "vite": "catalog:",
33
35
  "vite-plus": "catalog:",
34
36
  "wrangler": "^4.111.0"
35
37
  },
@@ -40,7 +42,7 @@
40
42
  "onFail": "download"
41
43
  }
42
44
  },
43
- "packageManager": "pnpm@11.9.0",
45
+ "packageManager": "pnpm@11.17.0",
44
46
  "emdash": {
45
47
  "seed": "seed/seed.json"
46
48
  }
@@ -0,0 +1,150 @@
1
+ // One-time hosting-platform setup for the target configured in
2
+ // astro.config.mjs (vitopsHosting from @getvitops/emdash).
3
+ //
4
+ // pnpm run init:hosting # cloudflare (the default target)
5
+ // pnpm run init:hosting -- --dry-run
6
+ // pnpm run init:hosting -- --target node
7
+ //
8
+ // cloudflare: creates the isolated DEV resources (D1 database + R2 bucket —
9
+ // prod ones are auto-provisioned by wrangler on the first deploy) and, when a
10
+ // GitHub repo is wired up (see init-github.mjs), sets the repo secrets the
11
+ // deploy workflows need. The Cloudflare API token cannot be created via CLI,
12
+ // so that one secret stays manual.
13
+ //
14
+ // Idempotent: safe to re-run — existing resources and secrets are just
15
+ // updated/skipped. Requires wrangler auth (`npx wrangler login`).
16
+
17
+ import { execSync, spawnSync } from 'node:child_process';
18
+ import { readFileSync } from 'node:fs';
19
+
20
+ const args = process.argv.slice(2);
21
+ const dryRun = args.includes('--dry-run');
22
+ const targetFlag = args[args.indexOf('--target') + 1];
23
+ const target = (args.includes('--target') && targetFlag) || process.env.HOSTING || 'cloudflare';
24
+
25
+ const name = JSON.parse(readFileSync('package.json', 'utf8')).name.replace(/^@[^/]+\//, '');
26
+
27
+ function quiet(cmd) {
28
+ try {
29
+ return execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ function run(cmd, { capture = false } = {}) {
36
+ if (dryRun) {
37
+ console.log(`[dry-run] ${cmd}`);
38
+ return '';
39
+ }
40
+ console.log(`$ ${cmd}`);
41
+ try {
42
+ if (capture) {
43
+ const out = execSync(cmd, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] });
44
+ process.stdout.write(out);
45
+ return out;
46
+ }
47
+ execSync(cmd, { stdio: 'inherit' });
48
+ return '';
49
+ } catch {
50
+ console.error(
51
+ `\nERROR: \`${cmd}\` failed (see output above). Fix and re-run — the script is idempotent.`,
52
+ );
53
+ process.exit(1);
54
+ }
55
+ }
56
+
57
+ if (target === 'node') {
58
+ console.log(`Hosting target 'node' has no cloud resources to provision. To switch this
59
+ project to a Node host (VPS / docker-compose / k8s):
60
+
61
+ 1. pnpm add @astrojs/node better-sqlite3
62
+ 2. In astro.config.mjs: vitopsHosting({ target: 'node' }) (or set HOSTING=node)
63
+ 3. Build and run: pnpm build && node ./dist/server/entry.mjs
64
+ Data lives in ./data/ (SQLite db + uploads) — persist it as a volume.
65
+ 4. CI deploy for VPS/docker/k8s is not scaffolded yet — the deploy-*.yml
66
+ workflows are Cloudflare-specific; replace or disable them.
67
+
68
+ For production on Node, move storage to S3-compatible and/or the database to
69
+ Postgres via vitopsHosting({ node: { database, storage } }) — see the
70
+ @getvitops/emdash README.`);
71
+ process.exit(0);
72
+ }
73
+
74
+ if (target !== 'cloudflare') {
75
+ console.error(`ERROR: unknown hosting target '${target}'. Valid targets: cloudflare, node.`);
76
+ process.exit(1);
77
+ }
78
+
79
+ // --- Preflight: wrangler auth ------------------------------------------------
80
+ const whoami = spawnSync('npx', ['wrangler', 'whoami'], { encoding: 'utf8' });
81
+ if (whoami.status !== 0) {
82
+ console.error('ERROR: wrangler is not logged in. Run `npx wrangler login` first.');
83
+ process.exit(1);
84
+ }
85
+ // "Account Name | <32-hex account id>" table row in `wrangler whoami` output.
86
+ const accountId = (whoami.stdout.match(/\b([0-9a-f]{32})\b/) ?? [])[1] ?? null;
87
+
88
+ // --- Dev D1 database ---------------------------------------------------------
89
+ const devDb = `${name}-dev`;
90
+ let devDbId = null;
91
+ const d1List = quiet('npx wrangler d1 list --json');
92
+ if (d1List) {
93
+ devDbId = JSON.parse(d1List).find((d) => d.name === devDb)?.uuid ?? null;
94
+ }
95
+ if (devDbId) {
96
+ console.log(`D1 database ${devDb} already exists (${devDbId}).`);
97
+ } else {
98
+ const out = run(`npx wrangler d1 create ${devDb}`, { capture: true });
99
+ devDbId = (out.match(/"database_id":\s*"([0-9a-f-]{36})"/) ?? [])[1] ?? null;
100
+ if (!dryRun && !devDbId) {
101
+ // Output format drifts across wrangler versions — the list is authoritative.
102
+ const relist = quiet('npx wrangler d1 list --json');
103
+ devDbId = relist ? (JSON.parse(relist).find((d) => d.name === devDb)?.uuid ?? null) : null;
104
+ }
105
+ if (!dryRun && !devDbId) {
106
+ console.error('ERROR: could not determine the database_id of the created dev database.');
107
+ process.exit(1);
108
+ }
109
+ }
110
+
111
+ // --- Dev R2 bucket -----------------------------------------------------------
112
+ const devBucket = `${name}-media-dev`;
113
+ const bucketList = quiet('npx wrangler r2 bucket list');
114
+ if (bucketList && new RegExp(`name:\\s+${devBucket}\\s*$`, 'm').test(bucketList)) {
115
+ console.log(`R2 bucket ${devBucket} already exists.`);
116
+ } else {
117
+ run(`npx wrangler r2 bucket create ${devBucket}`);
118
+ }
119
+
120
+ // --- GitHub repo secrets (when the repo is wired up) -------------------------
121
+ const hasGh = spawnSync('gh', ['auth', 'status'], { encoding: 'utf8' }).status === 0;
122
+ const hasOrigin = quiet('git remote get-url origin') !== null;
123
+ if (hasGh && hasOrigin) {
124
+ if (devDbId) run(`gh secret set DEV_D1_ID --body ${devDbId}`);
125
+ if (accountId) run(`gh secret set CLOUDFLARE_ACCOUNT_ID --body ${accountId}`);
126
+ } else {
127
+ console.log(
128
+ '\nSkipping GitHub secrets (no gh auth or no origin remote — run init:github first).',
129
+ );
130
+ }
131
+
132
+ console.log(`
133
+ Cloudflare dev environment ready:
134
+ D1: ${devDb}${devDbId ? ` (${devDbId})` : ''}
135
+ R2: ${devBucket}
136
+ ${
137
+ hasGh && hasOrigin
138
+ ? ''
139
+ : ` Set repo secrets once GitHub is wired up:
140
+ gh secret set DEV_D1_ID --body ${devDbId ?? '<id>'}
141
+ gh secret set CLOUDFLARE_ACCOUNT_ID --body ${accountId ?? '<account id>'}
142
+ `
143
+ }
144
+ Still manual (CLI can't create API tokens):
145
+ 1. Create a token with Workers + D1 + R2 edit permissions:
146
+ https://dash.cloudflare.com/profile/api-tokens
147
+ 2. gh secret set CLOUDFLARE_API_TOKEN
148
+
149
+ Prod D1/R2 are provisioned automatically by wrangler on the first \`pnpm deploy\`.
150
+ Local \`pnpm deploy:dev\` runs need: export DEV_D1_ID=${devDbId ?? '<id>'}`);