create-geonosis 2.1.0 → 2.3.0

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.
@@ -24,13 +24,13 @@ var SEARCH = {
24
24
  "import { pgFtsSearchIndex, type SearchIndex, type SearchProjections } from '@geonosis/search'"
25
25
  ],
26
26
  name: "@geonosis/search",
27
- version: "0.3.0"
27
+ version: "0.3.4"
28
28
  };
29
29
  var DOMAINS = [SEARCH];
30
30
  var DOMAIN_NAMES = DOMAINS.map((one) => one.field);
31
31
  var FOUNDATIONS = [
32
- { at: "foundations/db", name: "@geonosis/db", version: "0.3.0" },
33
- { at: "foundations/events", name: "@geonosis/events", version: "1.5.1" }
32
+ { at: "foundations/db", name: "@geonosis/db", version: "0.5.2" },
33
+ { at: "foundations/events", name: "@geonosis/events", version: "2.0.0" }
34
34
  ];
35
35
  var domainsNamed = (named) => named.map((one) => {
36
36
  const found = DOMAINS.find((domain) => domain.field === one);
@@ -116,17 +116,22 @@ import { basename, resolve } from "path";
116
116
  var RUNTIMES = ["bun", "pnpm"];
117
117
  var BACKENDS = ["medusa", "none", "sagaflow-cf"];
118
118
  var UIS = ["atoms-only", "none", "tiered-shadcn"];
119
+ var MEDUSA_RANGE = "^2.19.0";
120
+ var MEDUSA_PACKAGES = ["@medusajs/framework", "@medusajs/medusa"];
119
121
  var VALUED = /* @__PURE__ */ new Set([
120
122
  "--backend",
123
+ "--presets",
121
124
  "--brand",
122
125
  "--domains",
123
126
  "--kit",
124
127
  "--marketplace",
128
+ "--medusa",
125
129
  "--runtime",
126
130
  "--themekit",
127
131
  "--ui"
128
132
  ]);
129
- var SWITCHES = /* @__PURE__ */ new Set(["--check", "--help", "-h", "--no-install"]);
133
+ var SWITCHES = /* @__PURE__ */ new Set(["--check", "--help", "-h", "--no-install", "--plan"]);
134
+ var parseArgs = (argv) => read(argv);
130
135
  var read = (argv) => {
131
136
  const flags = {};
132
137
  let dir;
@@ -186,6 +191,7 @@ var parseAnswers = (argv, cwd) => {
186
191
  domains,
187
192
  ...flags["--kit"] === void 0 ? {} : { kit: resolve(cwd, flags["--kit"]) },
188
193
  ...flags["--marketplace"] === void 0 ? {} : { marketplace: flags["--marketplace"] },
194
+ ...flags["--medusa"] === void 0 ? {} : { medusa: flags["--medusa"] },
189
195
  name,
190
196
  runtime: choose("--runtime", flags["--runtime"], RUNTIMES),
191
197
  scope: scopeOf(name),
@@ -372,6 +378,11 @@ var workspacesOf = (answers) => {
372
378
  dir: "packages/backend",
373
379
  mayDependOn: ["core"],
374
380
  name: `${scope}/backend`,
381
+ ...answers.backend === "medusa" ? {
382
+ packages: Object.fromEntries(
383
+ MEDUSA_PACKAGES.map((one) => [one, answers.medusa ?? MEDUSA_RANGE])
384
+ )
385
+ } : {},
375
386
  sources: {
376
387
  "src/handler.ts": HANDLER,
377
388
  "src/handler.test.ts": BACKEND_TEST,
@@ -397,7 +408,7 @@ var workspacesOf = (answers) => {
397
408
 
398
409
  // src/files.ts
399
410
  import { countersFor, LAW_SOURCE, rulesFor, tiersFor } from "@geonosis/cli";
400
- var VERSION = "2.1.0";
411
+ var VERSION = "2.3.0";
401
412
  var SCHEMA = "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json";
402
413
  var CATALOG = {
403
414
  "@types/node": "^24.3.0",
@@ -435,6 +446,10 @@ var answersForRules = (answers) => ({
435
446
  "no-raw-html-atoms": [
436
447
  "error",
437
448
  [{ atom: "Button", element: "button", importPath: `${answers.scope}/ui/atoms/button` }]
449
+ ],
450
+ "no-runtime-dead-affordance": [
451
+ "error",
452
+ [{ atom: "Button", element: "button", importPath: `${answers.scope}/ui/atoms/button` }]
438
453
  ]
439
454
  },
440
455
  ...answers.backend === "medusa" ? {
@@ -497,9 +512,15 @@ var workspaceManifest = (workspace, answers, workspaces) => {
497
512
  return found === void 0 || found.name === workspace.name ? [] : [[found.name, "workspace:*"]];
498
513
  })
499
514
  ),
500
- // With --kit the floors are linked from a workspace AFTER the install, and the manifest is
501
- // rewritten to say so — the same reason the root's kit versions are left out.
502
- ...answers.kit === void 0 ? workspace.packages : {}
515
+ // With --kit the FLOORS are linked from a workspace AFTER the install, and the manifest is
516
+ // rewritten to say so — the same reason the root's kit versions are left out. Everything else
517
+ // a workspace declares is third-party and stays: an engine dropped here is an engine the
518
+ // doctor cannot find, and a rule that presumes it then reads as a rule with no finding.
519
+ ...Object.fromEntries(
520
+ Object.entries(workspace.packages ?? {}).filter(
521
+ ([name]) => answers.kit === void 0 || !name.startsWith("@geonosis/")
522
+ )
523
+ )
503
524
  }
504
525
  });
505
526
  };
@@ -519,8 +540,47 @@ var GRAPH_SYNCS = [
519
540
  var graphScript = GRAPH_SYNCS.map(
520
541
  ({ target, write: write2 }) => `geonosis-ledger sync --target ${target} --write ${write2}`
521
542
  ).join(" && ");
543
+ var HOOK_INSTALLER = "scripts/install-hooks.mjs";
544
+ var hookInstaller = () => [
545
+ "import { spawnSync } from 'node:child_process'",
546
+ "import { existsSync, lstatSync } from 'node:fs'",
547
+ "import { join, resolve } from 'node:path'",
548
+ "",
549
+ "const git = (...args) => {",
550
+ " const done = spawnSync('git', args, { encoding: 'utf8' })",
551
+ "",
552
+ " return done.status === 0 ? done.stdout.trim() : undefined",
553
+ "}",
554
+ "",
555
+ "const say = (line) => process.stdout.write(`install-hooks: ${line}\n`)",
556
+ "",
557
+ "const common = git('rev-parse', '--path-format=absolute', '--git-common-dir')",
558
+ "if (common === undefined) {",
559
+ " say('git says nothing about this directory, so there is no hooks path to install into.')",
560
+ " process.exit(0)",
561
+ "}",
562
+ "",
563
+ "// A worktree's own .git is a FILE; the hooks it would install into belong to the checkout the",
564
+ "// common dir names, and writing them from here changes the tree somebody else is in.",
565
+ "const here = resolve(process.cwd(), '.git')",
566
+ "if (existsSync(here) && lstatSync(here).isFile() && resolve(common) !== here) {",
567
+ " say(`this is a git worktree and its hooks path is shared with ${common} \u2014 install them there instead.`)",
568
+ " process.exit(0)",
569
+ "}",
570
+ "",
571
+ "const lefthook = join(process.cwd(), 'node_modules/.bin/lefthook')",
572
+ "if (!existsSync(lefthook)) {",
573
+ " say('lefthook is not installed here yet, so no hook was written.')",
574
+ " process.exit(0)",
575
+ "}",
576
+ "",
577
+ "const done = spawnSync(lefthook, ['install'], { stdio: 'inherit' })",
578
+ "process.exit(done.status ?? 0)",
579
+ ""
580
+ ].join("\n");
522
581
  var rootScripts = ({ runtime }) => ({
523
582
  doctor: "geonosis-doctor",
583
+ prepare: `node ${HOOK_INSTALLER}`,
524
584
  "format:check": "oxfmt --config .oxfmtrc.json --check .",
525
585
  format: "oxfmt --config .oxfmtrc.json .",
526
586
  lint: "oxlint --config .oxlintrc.json .",
@@ -732,7 +792,11 @@ var tsconfigBase = (answers) => json({
732
792
  });
733
793
  var filesFor = (answers) => {
734
794
  const workspaces = workspacesOf(answers);
735
- const counters = countersFor(answers.runtime, LAW_SOURCE);
795
+ const counters = countersFor(
796
+ answers.runtime,
797
+ LAW_SOURCE,
798
+ workspaces.map((one) => ({ dir: one.dir, name: one.name }))
799
+ );
736
800
  const { rules } = rulesFor(presetsFor(answers), answersForRules(answers));
737
801
  const files = {
738
802
  "package.json": rootManifest(answers),
@@ -809,6 +873,7 @@ var filesFor = (answers) => {
809
873
  )
810
874
  ),
811
875
  "skills-lock.json": json({ skills: {}, version: 1 }),
876
+ [HOOK_INSTALLER]: hookInstaller(),
812
877
  "lefthook.yml": [
813
878
  "# Staged scope only, so a commit never waits on a file it did not touch. When one fires, fix",
814
879
  "# or delete the code \u2014 never downgrade the rule to get past it.",
@@ -1084,6 +1149,8 @@ export {
1084
1149
  RUNTIMES,
1085
1150
  BACKENDS,
1086
1151
  UIS,
1152
+ MEDUSA_RANGE,
1153
+ parseArgs,
1087
1154
  parseAnswers,
1088
1155
  wantsInstall,
1089
1156
  CORPUS_DIR,
@@ -1,19 +1,198 @@
1
1
  import {
2
+ BACKENDS,
2
3
  DOMAIN_NAMES,
4
+ MEDUSA_RANGE,
3
5
  PLATFORM_FILE,
6
+ UIS,
4
7
  checkScaffold,
5
8
  describeDrift,
6
9
  finish,
7
10
  parseAnswers,
11
+ parseArgs,
8
12
  wantsInstall,
9
13
  writeScaffold
10
- } from "./chunk-NLXNF4TP.js";
14
+ } from "./chunk-UCGVZCVK.js";
15
+
16
+ // src/create-geonosis-cli.ts
17
+ import { existsSync as existsSync2 } from "fs";
18
+
19
+ // src/adopt.ts
20
+ import { existsSync, readdirSync, readFileSync } from "fs";
21
+ import { basename, join, resolve } from "path";
22
+ import { countersFor, rulesFor, runtimeOf, tiersFor } from "@geonosis/cli";
23
+ var ADOPT_USAGE = "create-geonosis --plan <dir> [--presets <a,b>] [--runtime pnpm|bun] [--backend medusa|sagaflow-cf|none] [--ui tiered-shadcn|atoms-only|none] \u2014 what adopting this repo would take, computed off the tree. Writes nothing.";
24
+ var LOCKFILES = ["bun.lock", "bun.lockb", "pnpm-lock.yaml"];
25
+ var MEDUSA = "@medusajs/";
26
+ var WORKERS = ["wrangler", "@cloudflare/workers-types", "sagaflow-js"];
27
+ var readJson = (at) => {
28
+ if (!existsSync(at)) return void 0;
29
+ try {
30
+ return JSON.parse(readFileSync(at, "utf8"));
31
+ } catch {
32
+ return void 0;
33
+ }
34
+ };
35
+ var manifestsIn = (dir) => {
36
+ const found = [readJson(join(dir, "package.json"))];
37
+ for (const group of ["apps", "packages"]) {
38
+ const at = join(dir, group);
39
+ if (!existsSync(at)) continue;
40
+ for (const entry of readdirSync(at, { withFileTypes: true })) {
41
+ if (entry.isDirectory()) found.push(readJson(join(at, entry.name, "package.json")));
42
+ }
43
+ }
44
+ return found.filter((one) => one !== void 0);
45
+ };
46
+ var dependencyNames = (manifests) => manifests.flatMap((one) => [
47
+ ...Object.keys(one.dependencies ?? {}),
48
+ ...Object.keys(one.devDependencies ?? {})
49
+ ]);
50
+ var backendOf = (manifests) => {
51
+ const names = dependencyNames(manifests);
52
+ if (names.some((one) => one.startsWith(MEDUSA))) return "medusa";
53
+ if (names.some((one) => WORKERS.includes(one))) return "sagaflow-cf";
54
+ return "none";
55
+ };
56
+ var chosen = (flag, given, allowed) => {
57
+ if (given === void 0) return void 0;
58
+ if (!allowed.includes(given)) {
59
+ throw new Error(`${flag} "${given}" is not one of ${allowed.join(", ")}`);
60
+ }
61
+ return given;
62
+ };
63
+ var uiOf = (dir, given) => {
64
+ if (given !== void 0) return given;
65
+ if (!existsSync(join(dir, "packages/ui"))) return "none";
66
+ throw new Error(
67
+ `${dir} has a packages/ui and no --ui was given \u2014 the tier decides which rules the plan proposes, and a directory listing cannot say which tier it is. Name --ui ${UIS.join("|")}.`
68
+ );
69
+ };
70
+ var doorFor = (block) => `@geonosis/${block}`;
71
+ var KIT_OWNED_BLOCKS = ["ledger", "review", "testbed", "verify", "walk"];
72
+ var planAdoption = ({ backend, dir, presets, runtime, ui }) => {
73
+ const at = resolve(dir);
74
+ if (!existsSync(at))
75
+ throw new Error(`${at} is not there \u2014 --plan reads a repo, it never makes one`);
76
+ const manifests = manifestsIn(at);
77
+ const chosenRuntime = chosen("--runtime", runtime, ["bun", "pnpm"]) ?? runtimeOf(at);
78
+ if (chosenRuntime === void 0) {
79
+ throw new Error(
80
+ `${at} has none of ${LOCKFILES.join(", ")}, so nothing there says which package manager it runs \u2014 name --runtime pnpm|bun.`
81
+ );
82
+ }
83
+ const answers = {
84
+ backend: chosen("--backend", backend, BACKENDS) ?? backendOf(manifests),
85
+ dir: at,
86
+ domains: [],
87
+ name: basename(at),
88
+ runtime: chosenRuntime,
89
+ scope: `@${basename(at).toLowerCase()}`,
90
+ ui: uiOf(at, chosen("--ui", ui, UIS))
91
+ };
92
+ const drift = checkScaffold(answers);
93
+ const enabled = new Set(
94
+ Object.keys(
95
+ readJson(join(at, ".oxlintrc.json"))?.rules ?? {}
96
+ )
97
+ );
98
+ const config = readJson(join(at, "geonosis.json")) ?? {};
99
+ const declaredTiers = config["verify"] ?? {};
100
+ const declaredCounters = new Set(
101
+ (readJson(
102
+ join(at, "geonosis.ratchet.json")
103
+ )?.counters ?? []).map((one) => one.key ?? one.counter ?? "")
104
+ );
105
+ const declaredPackages = new Set(dependencyNames(manifests));
106
+ const { unanswered } = rulesFor(presets);
107
+ const tiers = tiersFor({
108
+ rules: {},
109
+ runtime: chosenRuntime,
110
+ scripts: Object.keys(
111
+ readJson(join(at, "package.json"))?.scripts ?? {}
112
+ )
113
+ });
114
+ return {
115
+ backend: answers.backend,
116
+ counters: countersFor(chosenRuntime, "LAW.md").map((one) => {
117
+ const key = typeof one["key"] === "string" ? one["key"] : String(one["counter"]);
118
+ return { declared: declaredCounters.has(key), key };
119
+ }),
120
+ dir: at,
121
+ doors: [.../* @__PURE__ */ new Set([...KIT_OWNED_BLOCKS, ...Object.keys(config)])].filter((block) => KIT_OWNED_BLOCKS.includes(block)).toSorted().map((block) => ({
122
+ block,
123
+ declared: declaredPackages.has(doorFor(block)),
124
+ package: doorFor(block)
125
+ })),
126
+ files: drift.filter((one) => one.kind === "missing"),
127
+ keys: drift.filter((one) => one.kind === "key"),
128
+ presets: presets.map((name) => {
129
+ const brings = Object.keys(rulesFor([name]).rules);
130
+ return {
131
+ brings: brings.length,
132
+ enabled: brings.filter((id) => enabled.has(id)),
133
+ name
134
+ };
135
+ }),
136
+ runtime: chosenRuntime,
137
+ tiers: Object.entries(tiers).map(([name, steps]) => ({
138
+ declared: Object.hasOwn(declaredTiers, name),
139
+ name,
140
+ steps
141
+ })),
142
+ ui: answers.ui,
143
+ unanswered,
144
+ unreadable: drift.filter((one) => one.kind === "unreadable")
145
+ };
146
+ };
147
+ var section = (title, lines) => lines.length === 0 ? [`${title} \u2014 none`, ""] : [`${title} (${lines.length})`, ...lines, ""];
148
+ var formatPlan = (plan) => [
149
+ `adoption plan for ${plan.dir}`,
150
+ ` runtime ${plan.runtime} \xB7 backend ${plan.backend} \xB7 ui ${plan.ui}`,
151
+ "",
152
+ ...section(
153
+ "FILES the scaffold writes and this repo has none of",
154
+ plan.files.map((one) => ` ${one.file}`)
155
+ ),
156
+ ...section(
157
+ "KEYS the scaffold declares inside a file this repo has",
158
+ plan.keys.map((one) => ` ${one.file} \u2192 ${one.at}`)
159
+ ),
160
+ ...section(
161
+ "RULES the presets bring",
162
+ plan.presets.map(
163
+ (one) => ` ${one.name}: ${one.brings} rules, ${one.enabled.length} already enabled here`
164
+ )
165
+ ),
166
+ ...section(
167
+ "RULES left out until somebody answers their option",
168
+ plan.unanswered.map(([rule, option]) => ` ${rule} needs \`${option}\``)
169
+ ),
170
+ ...section(
171
+ "COUNTERS the ratchet would run",
172
+ plan.counters.map((one) => ` ${one.key}${one.declared ? " \u2014 declared" : ""}`)
173
+ ),
174
+ ...section(
175
+ "TIERS",
176
+ plan.tiers.map((one) => ` ${one.name}${one.declared ? " \u2014 declared" : ""}`)
177
+ ),
178
+ ...section(
179
+ "DOORS a block needs declared",
180
+ plan.doors.map(
181
+ (one) => ` ${one.package} for "${one.block}"${one.declared ? " \u2014 declared" : ""}`
182
+ )
183
+ ),
184
+ ...plan.unreadable.length === 0 ? [] : section(
185
+ "FILES this check could not read as JSON",
186
+ plan.unreadable.map((one) => ` ${one.file}`)
187
+ )
188
+ ].join("\n");
11
189
 
12
190
  // src/create-geonosis-cli.ts
13
- import { existsSync } from "fs";
14
191
  var USAGE = `create-geonosis <dir> --runtime pnpm|bun --backend medusa|sagaflow-cf|none --ui tiered-shadcn|atoms-only|none
192
+ [--medusa <range>]
15
193
  [--domains <a,b>] [--brand <name>] [--themekit <name>] [--marketplace <path>] [--kit <path>] [--no-install]
16
194
  [--check]
195
+ create-geonosis --plan <dir> [--presets <a,b>] [--runtime \u2026] [--backend \u2026] [--ui \u2026]
17
196
 
18
197
  Writes a repo that passes its own gates on day one: a workspace with one catalog of shared versions,
19
198
  an exports map and no barrel in every package, typecheck + lint + test scripts in every workspace,
@@ -27,10 +206,21 @@ default borrowed from another repo would be that repo's stack hardcoded into thi
27
206
  --domains the domains this repo composes \u2014 ${DOMAIN_NAMES.join(", ")}. Each one writes its
28
207
  store into the app's ${PLATFORM_FILE}: the composition root, in two lifetimes, with
29
208
  the tenant session opened before any store is built. A name nothing ships is refused.
209
+ --medusa <range>
210
+ the Medusa a --backend medusa tree declares. Defaults to ${MEDUSA_RANGE}, the version
211
+ the one Medusa consumer of this kit runs (measured 2026-09-02): the Medusa rules
212
+ the scaffold turns on presume the engine, and a tree that enables them while
213
+ declaring no @medusajs/* earns nine doctor WARNs for rules with no finding.
30
214
  --kit <path> read the kit from a workspace instead of the registry. A proof aid: it symlinks the
31
215
  packages after the install and rewrites the manifest to say so.
32
216
  --no-install write the files and stop. The gates cannot run until the two "geonosis sync
33
217
  --target" commands below have been run, and they are printed for you.
218
+ --plan what ADOPTING an existing repo would take, computed off its tree and written
219
+ nowhere: the files and keys it lacks of what the scaffold writes, the rules each
220
+ preset brings against the ones already enabled, the rules left out until somebody
221
+ answers their option, the counters, the tiers and the doors a block needs
222
+ declared. The runtime and the backend are read off the lockfile and the manifests;
223
+ a flag overrides one. Exit 0 \u2014 it is a report.
34
224
  --check say what an EXISTING repo is missing of what the scaffold writes today, and WRITE
35
225
  NOTHING. Files it has never had, and keys inside the JSON it has. Contents and
36
226
  values are never compared: a repo that has been worked in has rewritten them, and
@@ -57,9 +247,26 @@ var main = () => {
57
247
  `);
58
248
  return argv.length === 0 ? 2 : 0;
59
249
  }
250
+ if (argv.includes("--plan")) {
251
+ const { dir, flags } = parseArgs(argv);
252
+ if (dir === void 0) throw new Error(`--plan needs a directory \u2014 ${ADOPT_USAGE}`);
253
+ process.stdout.write(
254
+ `${formatPlan(
255
+ planAdoption({
256
+ ...flags["--backend"] === void 0 ? {} : { backend: flags["--backend"] },
257
+ dir,
258
+ presets: (flags["--presets"] ?? "frontend,tooling").split(",").map((one) => one.trim()).filter((one) => one !== ""),
259
+ ...flags["--runtime"] === void 0 ? {} : { runtime: flags["--runtime"] },
260
+ ...flags["--ui"] === void 0 ? {} : { ui: flags["--ui"] }
261
+ })
262
+ )}
263
+ `
264
+ );
265
+ return 0;
266
+ }
60
267
  const answers = parseAnswers(argv, process.cwd());
61
268
  if (argv.includes("--check")) {
62
- if (!existsSync(answers.dir)) {
269
+ if (!existsSync2(answers.dir)) {
63
270
  throw new Error(`${answers.dir} is not there \u2014 --check reads a repo, it never makes one`);
64
271
  }
65
272
  const drift = checkScaffold(answers);
package/dist/index.d.ts CHANGED
@@ -13,6 +13,8 @@ type Answers = {
13
13
  /** The kit read from a workspace instead of the registry — a proof aid, never the default. */
14
14
  kit?: string;
15
15
  marketplace?: string;
16
+ /** The Medusa range a `--backend medusa` tree declares. `MEDUSA_RANGE` unless one is named. */
17
+ medusa?: string;
16
18
  name: string;
17
19
  runtime: Runtime;
18
20
  scope: string;
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  wantsInstall,
24
24
  workspacesOf,
25
25
  writeScaffold
26
- } from "./chunk-NLXNF4TP.js";
26
+ } from "./chunk-UCGVZCVK.js";
27
27
  export {
28
28
  BACKENDS,
29
29
  CORPUS_DIR,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-geonosis",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "types": "./dist/index.d.ts",
5
5
  "description": "Scaffold a microcompany repo that passes its own geonosis gates on day one.",
6
6
  "keywords": [
@@ -34,7 +34,7 @@
34
34
  "dist"
35
35
  ],
36
36
  "dependencies": {
37
- "@geonosis/cli": "2.1.0"
37
+ "@geonosis/cli": "2.3.0"
38
38
  },
39
39
  "engines": {
40
40
  "node": ">=22"