create-kedge 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/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # create-kedge
2
+
3
+ A new [Kedge](https://www.npmjs.com/package/@kedge/cli) store: an open-source (MIT)
4
+ headless commerce backend that runs on Cloudflare Workers and D1, in your own
5
+ Cloudflare account.
6
+
7
+ ```sh
8
+ mkdir my-store && cd my-store
9
+ npm create kedge@latest
10
+ ```
11
+
12
+ Run it in an empty folder, or name a new one: `npm create kedge@latest my-store`.
13
+ The store's name comes from the folder (`--name` chooses another). It needs Node 24
14
+ or newer and pnpm 10 or newer (`npm install --global pnpm`), and checks both
15
+ before it writes anything.
16
+
17
+ ## With a coding agent
18
+
19
+ Give your agent this page and ask: *"Make me a Kedge store and place a test
20
+ order."* The store it makes carries the `kedge-start` skill (in `.claude/skills/`
21
+ and `.agents/skills/`), which holds the steps below. A command that needs you
22
+ stops with exit code 3 and one line starting `for you:`; your agent passes it on.
23
+
24
+ ## The first run
25
+
26
+ **On this machine** (no Cloudflare account needed):
27
+
28
+ 1. `pnpm kedge check --fast`
29
+ 2. `pnpm kedge dev` (keep it running): the store at `http://localhost:8787`, its
30
+ admin at `/app/`.
31
+ 3. `pnpm kedge staff invite-owner you@example.com`: open the link it prints, set a
32
+ password and an authenticator, and you're the store's Owner.
33
+ 4. `pnpm kedge seed --sample apparel --url http://localhost:8787`
34
+ 5. `pnpm kedge smoke --url http://localhost:8787`: a test order, placed.
35
+
36
+ **On Cloudflare** (a Workers Paid account):
37
+
38
+ 1. Commit the store: `git add -A && git commit -m "A new Kedge store"`.
39
+ 2. Give the CLI credentials: `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` in
40
+ `.env.local`, or `npx wrangler login`. `pnpm kedge doctor --remote` checks them.
41
+ 3. `pnpm kedge deploy --jurisdiction <eu|us|fedramp|none>`: where the store's data
42
+ lives, chosen once.
43
+ 4. `pnpm kedge staff invite-owner you@example.com --remote`: your invite.
44
+ 5. `pnpm kedge seed --sample apparel --url <store> --live` and
45
+ `pnpm kedge smoke --url <store> --live`: sample products and a test order on the
46
+ live store.
47
+
48
+ Every command takes `--json` for a single result object on stdout. The installed
49
+ `@kedge/docs` holds the guides for extending the store, matched to its version.
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "create-kedge",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/chandeldivyam/Kedge.git",
8
8
  "directory": "packages/create-kedge"
9
9
  },
10
+ "engines": {
11
+ "node": ">=24"
12
+ },
10
13
  "type": "module",
11
14
  "description": "npx create-kedge my-store: a new Kedge store from the starter.",
12
15
  "bin": {
package/src/create.mjs CHANGED
@@ -7,8 +7,26 @@ import { randomBytes } from "node:crypto";
7
7
  import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
8
8
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
9
9
 
10
- /** A store's name: it becomes Worker, database and queue names, so Cloudflare's rules apply. */
10
+ /**
11
+ * Kedge's rule for a store's name, not Cloudflare's: with its six-character
12
+ * suffix it becomes the Worker's, the databases' and the queue's names, and a
13
+ * preview's Worker adds more, all inside Cloudflare's 63 characters for a
14
+ * workers.dev name (letters, digits and dashes, no dash first or last:
15
+ * https://developers.cloudflare.com/workers/configuration/routing/workers-dev/#limitations).
16
+ */
11
17
  export const NAME = /^[a-z][a-z0-9-]{0,38}[a-z0-9]$/;
18
+
19
+ /** A store name from any folder's name (p0.10.1 §2): always one that meets NAME, so a folder is never refused. */
20
+ export function storeNameOf(folder) {
21
+ let s = folder
22
+ .toLowerCase()
23
+ .replace(/[^a-z0-9]+/g, "-")
24
+ .replace(/^-+|-+$/g, "");
25
+ if (!s) return "my-store";
26
+ if (!/^[a-z]/.test(s)) s = `store-${s}`;
27
+ s = s.slice(0, 40).replace(/-+$/, "");
28
+ return s.length < 2 ? "my-store" : s;
29
+ }
12
30
  /** The starter's placeholder for the store's Cloudflare names. */
13
31
  export const PLACEHOLDER = "my-store";
14
32
 
@@ -71,26 +89,70 @@ export function write(dir, files) {
71
89
  }
72
90
  }
73
91
 
92
+ /**
93
+ * The oldest Node and pnpm a store is tested on (p0.10 §3): `kedge generate`
94
+ * reads with `node:sqlite` (D-039), and the template's pnpm-workspace.yaml
95
+ * relies on `allowBuilds` to let workerd and esbuild fetch their binaries. The
96
+ * clean-machine run is what proves these; every package's `engines.node` says the same.
97
+ */
98
+ export const MIN_NODE = 24;
99
+ /**
100
+ * pnpm 10 runs the store's pinned pnpm (`packageManager`, pnpm@11.11.0 in the
101
+ * starter) by itself: measured 2026-11-01, `npx pnpm@10 --version` in such a
102
+ * folder answers 11.11.0 (p0.10.1 §1, 3).
103
+ */
104
+ export const MIN_PNPM = 10;
105
+
106
+ /** What this machine lacks, with the one command that fixes it; null when nothing. */
107
+ export function environmentProblem({ node, pnpm, install }) {
108
+ const major = (v) => Number(/^v?(\d+)\./.exec(v ?? "")?.[1] ?? 0);
109
+ if (major(node) < MIN_NODE)
110
+ return `Kedge needs Node ${MIN_NODE} or newer, and this is ${node}: install it from https://nodejs.org (or \`nvm install ${MIN_NODE}\`), then run this again`;
111
+ if (!install) return null;
112
+ if (!pnpm)
113
+ return `Kedge stores install with pnpm, which isn't on the path: run \`npm install --global pnpm\`, then run this again`;
114
+ if (major(pnpm) < MIN_PNPM)
115
+ return `Kedge needs pnpm ${MIN_PNPM} or newer, and this is ${pnpm}: run \`npm install --global pnpm\`, then run this again`;
116
+ return null;
117
+ }
118
+
74
119
  const USAGE = `usage: create-kedge <directory> [--name <name>] [--from-packs <dir>] [--no-install] [--no-git]`;
75
120
 
76
- export function main(argv, { template, run = spawnSync, log = console.log, error = console.error } = {}) {
121
+ export function main(
122
+ argv,
123
+ { template, run = spawnSync, log = console.log, error = console.error, node = process.versions.node } = {},
124
+ ) {
77
125
  const flags = new Set(argv.filter((a) => a.startsWith("--no-")));
78
126
  const value = (f) => {
79
127
  const i = argv.indexOf(f);
80
128
  return i >= 0 ? argv[i + 1] : undefined;
81
129
  };
82
130
  const positional = argv.filter((a, i) => !a.startsWith("--") && !["--name", "--from-packs"].includes(argv[i - 1]));
83
- const target = positional[0];
84
- if (!target) {
85
- error(USAGE);
131
+ // No directory: the folder it's run in, as `npm create kedge@latest` alone leaves it (p0.10.1 §1, 1).
132
+ const target = positional[0] ?? ".";
133
+ if (positional[0] === undefined && readdirSync(resolve(target)).length > 0) {
134
+ error(`this folder isn't empty: run it in an empty one, or name a new one: npm create kedge@latest my-store`);
86
135
  return 2;
87
136
  }
137
+ // Before anything is written: a half-made store on a machine that can't install it helps no one.
138
+ const pnpm = flags.has("--no-install") ? null : run("pnpm", ["--version"], { encoding: "utf8" });
139
+ const problem = environmentProblem({
140
+ node,
141
+ pnpm: pnpm?.status === 0 ? String(pnpm.stdout ?? "").trim() : null,
142
+ install: !flags.has("--no-install"),
143
+ });
144
+ if (problem) {
145
+ error(problem);
146
+ return 1;
147
+ }
88
148
  const dir = resolve(target);
89
149
  if (existsSync(dir) && readdirSync(dir).length > 0) {
90
150
  error(`${dir} isn't empty`);
91
151
  return 1;
92
152
  }
93
- const name = value("--name") ?? basename(dir).toLowerCase();
153
+ const chosen = value("--name");
154
+ const name = chosen ?? storeNameOf(basename(dir));
155
+ if (!chosen) log(`store name ${name}, from the folder; --name to choose another`);
94
156
  const fromPacks = value("--from-packs");
95
157
  const t = JSON.parse(readFileSync(template, "utf8"));
96
158
  let files;
@@ -118,6 +180,6 @@ export function main(argv, { template, run = spawnSync, log = console.log, error
118
180
  return g.status ?? 1;
119
181
  }
120
182
  }
121
- log(`next: cd ${target} && pnpm kedge check`);
183
+ log(`next: ${target === "." ? "" : `cd ${target} && `}pnpm kedge check`);
122
184
  return 0;
123
185
  }