create-geonosis 2.12.0 → 3.0.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.
- package/CHANGELOG.md +30 -0
- package/bin/create-geonosis.mjs +7 -192
- package/dist/adopt.js +144 -0
- package/dist/answers.d.ts +42 -0
- package/dist/answers.js +111 -0
- package/dist/check.d.ts +24 -0
- package/dist/check.js +62 -0
- package/dist/composition.d.ts +46 -0
- package/dist/composition.js +137 -0
- package/dist/corpus.d.ts +16 -0
- package/dist/corpus.js +46 -0
- package/dist/create-geonosis-cli.js +41 -229
- package/dist/files.d.ts +18 -0
- package/dist/files.js +497 -0
- package/dist/floor-versions.d.ts +29 -0
- package/dist/floor-versions.js +35 -0
- package/dist/index.d.ts +7 -170
- package/dist/index.js +16 -19
- package/dist/layout.d.ts +18 -0
- package/dist/layout.js +164 -0
- package/dist/scaffold.d.ts +36 -0
- package/dist/scaffold.js +173 -0
- package/package.json +5 -4
- package/dist/chunk-4IX6PJ7R.js +0 -1200
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Where the composition root lives, relative to the workspace that composes: the app. */
|
|
2
|
+
export declare const PLATFORM_FILE = "src/platform.ts";
|
|
3
|
+
/**
|
|
4
|
+
* A domain the kit ships, and the two lines composing it takes.
|
|
5
|
+
*
|
|
6
|
+
* A domain is added here and nowhere else: the flag's vocabulary, the app's dependencies and the
|
|
7
|
+
* generated file all read this table, so a domain that ships without a way to compose it cannot be
|
|
8
|
+
* offered by name. `version` is the floor package's OWN line — the method's number says nothing
|
|
9
|
+
* about it (D-053) — and `tooling/floor-scaffold-versions.test.ts` holds each against the workspace.
|
|
10
|
+
*/
|
|
11
|
+
export type DomainScaffold = {
|
|
12
|
+
/** Where the package sits in the kit's tree, for `--kit`'s workspace links. */
|
|
13
|
+
at: string;
|
|
14
|
+
/** What the scope field is called, and the type it is annotated with. */
|
|
15
|
+
field: string;
|
|
16
|
+
/** Everything the generated file needs above the composition itself. */
|
|
17
|
+
declarations: readonly string[];
|
|
18
|
+
fieldType: string;
|
|
19
|
+
imports: readonly string[];
|
|
20
|
+
name: string;
|
|
21
|
+
/** How the store is built, once the tenant's transaction is already open. */
|
|
22
|
+
built: readonly string[];
|
|
23
|
+
version: string;
|
|
24
|
+
};
|
|
25
|
+
export declare const DOMAINS: readonly DomainScaffold[];
|
|
26
|
+
export declare const DOMAIN_NAMES: readonly string[];
|
|
27
|
+
/** The two foundations a composition root is built from, whichever domains it composes. */
|
|
28
|
+
export declare const FOUNDATIONS: readonly {
|
|
29
|
+
at: string;
|
|
30
|
+
name: string;
|
|
31
|
+
version: string;
|
|
32
|
+
}[];
|
|
33
|
+
export declare const domainsNamed: (named: readonly string[]) => DomainScaffold[];
|
|
34
|
+
/** What the composing workspace declares once a domain is named. */
|
|
35
|
+
export declare const floorDependencies: (named: readonly string[]) => Record<string, string>;
|
|
36
|
+
/** Every kit directory a `--kit` link has to reach for the domains this repo named. */
|
|
37
|
+
export declare const floorDirectories: (named: readonly string[]) => string[];
|
|
38
|
+
/**
|
|
39
|
+
* The composition root, in the two lifetimes it has.
|
|
40
|
+
*
|
|
41
|
+
* DEFINITIONS are what this deployment IS — wired once per isolate, before anybody has asked
|
|
42
|
+
* anything. SCOPE is who is asking, bound per request. Keeping them apart is what lets the session
|
|
43
|
+
* come first: the stores are built INSIDE the tenant's transaction, so a caller cannot hold one
|
|
44
|
+
* that was made without a session to run in (D-014 — structural, never an assertion).
|
|
45
|
+
*/
|
|
46
|
+
export declare const platformSource: (named: readonly string[]) => string;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/composition.ts
|
|
2
|
+
import { FLOOR_VERSIONS } from "./floor-versions.js";
|
|
3
|
+
var PLATFORM_FILE = "src/platform.ts";
|
|
4
|
+
var SEARCH = {
|
|
5
|
+
at: "domains/search",
|
|
6
|
+
built: [
|
|
7
|
+
" search: pgFtsSearchIndex({",
|
|
8
|
+
" executor: tx,",
|
|
9
|
+
" nowMs: definitions.nowMs,",
|
|
10
|
+
" projections: SEARCH_PROJECTIONS,",
|
|
11
|
+
" }),"
|
|
12
|
+
],
|
|
13
|
+
declarations: [
|
|
14
|
+
"/** How each kind this repo indexes becomes searchable text — declared once, never per call. */",
|
|
15
|
+
"const SEARCH_PROJECTIONS: SearchProjections<'note'> = {",
|
|
16
|
+
" note: {",
|
|
17
|
+
" body: (fields) => fields.notes ?? '',",
|
|
18
|
+
" title: (fields) => fields.heading ?? '',",
|
|
19
|
+
" },",
|
|
20
|
+
"}"
|
|
21
|
+
],
|
|
22
|
+
field: "search",
|
|
23
|
+
fieldType: "SearchIndex<'note'>",
|
|
24
|
+
imports: [
|
|
25
|
+
"import { pgFtsSearchIndex, type SearchIndex, type SearchProjections } from '@geonosis/search'"
|
|
26
|
+
],
|
|
27
|
+
name: "@geonosis/search",
|
|
28
|
+
version: FLOOR_VERSIONS["@geonosis/search"]
|
|
29
|
+
};
|
|
30
|
+
var DOCUMENT = {
|
|
31
|
+
at: "domains/document",
|
|
32
|
+
built: [" document: DOCUMENT_ENTITIES,"],
|
|
33
|
+
declarations: [
|
|
34
|
+
"/** Every `Document.extend` this repo declares, by the name its rows carry — grow this map. */",
|
|
35
|
+
"const DOCUMENT_ENTITIES = documentEntities({})"
|
|
36
|
+
],
|
|
37
|
+
field: "document",
|
|
38
|
+
fieldType: "typeof DOCUMENT_ENTITIES",
|
|
39
|
+
imports: ["import { documentEntities } from '@geonosis/document'"],
|
|
40
|
+
name: "@geonosis/document",
|
|
41
|
+
version: FLOOR_VERSIONS["@geonosis/document"]
|
|
42
|
+
};
|
|
43
|
+
var DOMAINS = [SEARCH, DOCUMENT];
|
|
44
|
+
var DOMAIN_NAMES = DOMAINS.map((one) => one.field);
|
|
45
|
+
var FOUNDATIONS = [
|
|
46
|
+
{ at: "foundations/db", name: "@geonosis/db", version: FLOOR_VERSIONS["@geonosis/db"] },
|
|
47
|
+
{
|
|
48
|
+
at: "foundations/events",
|
|
49
|
+
name: "@geonosis/events",
|
|
50
|
+
version: FLOOR_VERSIONS["@geonosis/events"]
|
|
51
|
+
}
|
|
52
|
+
];
|
|
53
|
+
var domainsNamed = (named) => named.map((one) => {
|
|
54
|
+
const found = DOMAINS.find((domain) => domain.field === one);
|
|
55
|
+
if (found === undefined) {
|
|
56
|
+
throw new Error(`--domains "${one}" is not a domain this kit ships — it ships ${DOMAIN_NAMES.join(", ")}. A domain nothing publishes cannot be composed, and a scaffold that wrote the import anyway would leave a repo that does not install.`);
|
|
57
|
+
}
|
|
58
|
+
return found;
|
|
59
|
+
});
|
|
60
|
+
var floorDependencies = (named) => named.length === 0 ? {} : Object.fromEntries([...FOUNDATIONS, ...domainsNamed(named)].map((one) => [one.name, `^${one.version}`]).toSorted(([a], [b]) => String(a).localeCompare(String(b), "en-US")));
|
|
61
|
+
var floorDirectories = (named) => named.length === 0 ? [] : [...FOUNDATIONS, ...domainsNamed(named)].map((one) => one.at);
|
|
62
|
+
var platformSource = (named) => {
|
|
63
|
+
const domains = domainsNamed(named);
|
|
64
|
+
return [
|
|
65
|
+
"import { createSessionSeam, type Executor, type SessionSeam } from '@geonosis/db'",
|
|
66
|
+
"import { createEmitDoor, type EmitDoor, type Extensions } from '@geonosis/events'",
|
|
67
|
+
...domains.flatMap((one) => one.imports),
|
|
68
|
+
"",
|
|
69
|
+
"/** Every name a foundation cannot pick for this repo, answered here and only here. */",
|
|
70
|
+
"export type PlatformConfig = {",
|
|
71
|
+
" newId: () => string",
|
|
72
|
+
" nowMs: () => number",
|
|
73
|
+
" /** What maintenance sets to see every tenant, through a lever no request path reaches. */",
|
|
74
|
+
" opsSetting: string",
|
|
75
|
+
" /** The producer every envelope names (D-057) — spelled here, never at an emit call. */",
|
|
76
|
+
" source: string",
|
|
77
|
+
" /** What a transaction sets to name its tenant, and what the RLS policies read. */",
|
|
78
|
+
" tenantSetting: string",
|
|
79
|
+
"}",
|
|
80
|
+
"",
|
|
81
|
+
"export type Definitions = {",
|
|
82
|
+
" emit: EmitDoor<Extensions>",
|
|
83
|
+
" nowMs: () => number",
|
|
84
|
+
" session: SessionSeam",
|
|
85
|
+
"}",
|
|
86
|
+
"",
|
|
87
|
+
"/** Once per isolate: what this platform is, before anybody has asked it anything. */",
|
|
88
|
+
"export const definitionsOf = (config: PlatformConfig): Definitions => ({",
|
|
89
|
+
" emit: createEmitDoor({",
|
|
90
|
+
" extensions: [],",
|
|
91
|
+
" newId: config.newId,",
|
|
92
|
+
" now: config.nowMs,",
|
|
93
|
+
" source: config.source,",
|
|
94
|
+
" }),",
|
|
95
|
+
" nowMs: config.nowMs,",
|
|
96
|
+
" session: createSessionSeam({",
|
|
97
|
+
" settings: { opsSetting: config.opsSetting, tenantSetting: config.tenantSetting },",
|
|
98
|
+
" }),",
|
|
99
|
+
"})",
|
|
100
|
+
"",
|
|
101
|
+
...domains.flatMap((one) => [...one.declarations, ""]),
|
|
102
|
+
"/** Who is asking, and the connection they may ask on. */",
|
|
103
|
+
"export type Caller = { connection: Executor; tenantId: string }",
|
|
104
|
+
"",
|
|
105
|
+
"/** Every domain this repo composed, built for one caller. */",
|
|
106
|
+
"export type Scope = {",
|
|
107
|
+
...domains.map((one) => ` ${one.field}: ${one.fieldType}`),
|
|
108
|
+
"}",
|
|
109
|
+
"",
|
|
110
|
+
"/**",
|
|
111
|
+
" * Per request. The tenant session opens FIRST and the stores are built inside it, so a store",
|
|
112
|
+
" * that could run outside the transaction that named its tenant does not exist to be held.",
|
|
113
|
+
" */",
|
|
114
|
+
"export const inScope = async <Result>(",
|
|
115
|
+
" definitions: Definitions,",
|
|
116
|
+
" caller: Caller,",
|
|
117
|
+
" run: (scope: Scope) => Promise<Result>,",
|
|
118
|
+
"): Promise<Result> =>",
|
|
119
|
+
" definitions.session.inTenant(caller.connection, caller.tenantId, async (tx) =>",
|
|
120
|
+
" run({",
|
|
121
|
+
...domains.flatMap((one) => [...one.built]),
|
|
122
|
+
" }),",
|
|
123
|
+
" )",
|
|
124
|
+
""
|
|
125
|
+
].join(`
|
|
126
|
+
`);
|
|
127
|
+
};
|
|
128
|
+
export {
|
|
129
|
+
DOMAINS,
|
|
130
|
+
DOMAIN_NAMES,
|
|
131
|
+
FOUNDATIONS,
|
|
132
|
+
PLATFORM_FILE,
|
|
133
|
+
domainsNamed,
|
|
134
|
+
floorDependencies,
|
|
135
|
+
floorDirectories,
|
|
136
|
+
platformSource
|
|
137
|
+
};
|
package/dist/corpus.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Answers } from './answers.js';
|
|
2
|
+
import type { Workspace } from './layout.js';
|
|
3
|
+
export declare const CORPUS_DIR = "lint-corpus";
|
|
4
|
+
/**
|
|
5
|
+
* A reach corpus a shipped one cannot be.
|
|
6
|
+
*
|
|
7
|
+
* `geonosis-doctor` asks whether every enabled rule can fire at all, and it asks the corpus the
|
|
8
|
+
* PLUGIN ships. That cannot answer for a rule whose reach is the repo's own vocabulary: its layer
|
|
9
|
+
* names, its brand, its plugin package root, the `within` its sagas live in. The plugin's corpus
|
|
10
|
+
* says "acme" and `layers/core`, so it is no evidence either way about a repo that says anything
|
|
11
|
+
* else — and those rules would read as firing nowhere.
|
|
12
|
+
*
|
|
13
|
+
* So the repo ships its own, in ITS vocabulary. The corpus is copied to a temp root and linted with
|
|
14
|
+
* `--no-ignore`, so the fixture paths mirror the repo's real ones and the layer patterns match.
|
|
15
|
+
*/
|
|
16
|
+
export declare const corpusFor: (answers: Answers, workspaces: Workspace[]) => Record<string, string>;
|
package/dist/corpus.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// src/corpus.ts
|
|
2
|
+
var CORPUS_DIR = "lint-corpus";
|
|
3
|
+
var PREFIX = "biological-architecture";
|
|
4
|
+
var corpusFor = (answers, workspaces) => {
|
|
5
|
+
const files = {};
|
|
6
|
+
const rules = [];
|
|
7
|
+
const walled = workspaces.find((one) => one.mayDependOn.length === 0);
|
|
8
|
+
const forbidden = workspaces.find((one) => one.tag === "app");
|
|
9
|
+
if (walled !== undefined && forbidden !== undefined) {
|
|
10
|
+
rules.push(`${PREFIX}/layer-walls`);
|
|
11
|
+
files[`${CORPUS_DIR}/${walled.dir}/src/bad-reaches-up.ts`] = `// BREACH: the ${walled.tag} layer may import nothing, and this reaches ${forbidden.tag}.
|
|
12
|
+
import { boot } from '${forbidden.name}/boot'
|
|
13
|
+
|
|
14
|
+
export const reachesUp = () => boot
|
|
15
|
+
`;
|
|
16
|
+
}
|
|
17
|
+
if (answers.brand !== undefined) {
|
|
18
|
+
rules.push(`${PREFIX}/no-brand-names`);
|
|
19
|
+
files[`${CORPUS_DIR}/brand/bad-brand.ts`] = `// BREACH: the brand name spelled into source, which is what the rule is configured to find.
|
|
20
|
+
export const FOOTER_NOTE = 'Powered by ${answers.brand}'
|
|
21
|
+
`;
|
|
22
|
+
}
|
|
23
|
+
if (answers.backend === "medusa") {
|
|
24
|
+
rules.push(`${PREFIX}/plugin-route-namespaced`);
|
|
25
|
+
files[`${CORPUS_DIR}/packages/medusa-plugins/example/src/api/store/route.ts`] = `// BREACH: a store route with no namespace under it, in a plugin package.
|
|
26
|
+
export async function GET(req: unknown, res: { json: (body: unknown) => void }) {
|
|
27
|
+
res.json({ ok: true, req })
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
}
|
|
31
|
+
if (answers.backend === "sagaflow-cf") {
|
|
32
|
+
rules.push(`${PREFIX}/step-opens-its-own-cell`);
|
|
33
|
+
files[`${CORPUS_DIR}/packages/backend/src/workflows/bad-engine-step.ts`] = `// BREACH: a saga inside the configured \`within\` reaching the engine's own step.
|
|
34
|
+
import { step } from 'sagaflow-js'
|
|
35
|
+
|
|
36
|
+
export const issue = step('issue', async () => ({ ok: true }))
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
39
|
+
files[`${CORPUS_DIR}/manifest.json`] = `${JSON.stringify({ plugin: PREFIX, rules }, undefined, 2)}
|
|
40
|
+
`;
|
|
41
|
+
return files;
|
|
42
|
+
};
|
|
43
|
+
export {
|
|
44
|
+
CORPUS_DIR,
|
|
45
|
+
corpusFor
|
|
46
|
+
};
|
|
@@ -1,199 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BACKENDS,
|
|
3
|
-
DOMAIN_NAMES,
|
|
4
|
-
MEDUSA_RANGE,
|
|
5
|
-
PLATFORM_FILE,
|
|
6
|
-
UIS,
|
|
7
|
-
checkScaffold,
|
|
8
|
-
describeDrift,
|
|
9
|
-
finish,
|
|
10
|
-
parseAnswers,
|
|
11
|
-
parseArgs,
|
|
12
|
-
scopeOf,
|
|
13
|
-
wantsInstall,
|
|
14
|
-
writeScaffold
|
|
15
|
-
} from "./chunk-4IX6PJ7R.js";
|
|
16
|
-
|
|
17
|
-
// src/create-geonosis-cli.ts
|
|
18
|
-
import { existsSync as existsSync2 } from "fs";
|
|
19
|
-
|
|
20
|
-
// src/adopt.ts
|
|
21
|
-
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
22
|
-
import { basename, join, resolve } from "path";
|
|
23
|
-
import { countersFor, rulesFor, runtimeOf, tiersFor } from "@geonosis/cli";
|
|
24
|
-
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.";
|
|
25
|
-
var LOCKFILES = ["bun.lock", "bun.lockb", "pnpm-lock.yaml"];
|
|
26
|
-
var MEDUSA = "@medusajs/";
|
|
27
|
-
var WORKERS = ["wrangler", "@cloudflare/workers-types", "sagaflow-js"];
|
|
28
|
-
var readJson = (at) => {
|
|
29
|
-
if (!existsSync(at)) return void 0;
|
|
30
|
-
try {
|
|
31
|
-
return JSON.parse(readFileSync(at, "utf8"));
|
|
32
|
-
} catch {
|
|
33
|
-
return void 0;
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
var manifestsIn = (dir) => {
|
|
37
|
-
const found = [readJson(join(dir, "package.json"))];
|
|
38
|
-
for (const group of ["apps", "packages"]) {
|
|
39
|
-
const at = join(dir, group);
|
|
40
|
-
if (!existsSync(at)) continue;
|
|
41
|
-
for (const entry of readdirSync(at, { withFileTypes: true })) {
|
|
42
|
-
if (entry.isDirectory()) found.push(readJson(join(at, entry.name, "package.json")));
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return found.filter((one) => one !== void 0);
|
|
46
|
-
};
|
|
47
|
-
var dependencyNames = (manifests) => manifests.flatMap((one) => [
|
|
48
|
-
...Object.keys(one.dependencies ?? {}),
|
|
49
|
-
...Object.keys(one.devDependencies ?? {})
|
|
50
|
-
]);
|
|
51
|
-
var backendOf = (manifests) => {
|
|
52
|
-
const names = dependencyNames(manifests);
|
|
53
|
-
if (names.some((one) => one.startsWith(MEDUSA))) return "medusa";
|
|
54
|
-
if (names.some((one) => WORKERS.includes(one))) return "sagaflow-cf";
|
|
55
|
-
return "none";
|
|
56
|
-
};
|
|
57
|
-
var chosen = (flag, given, allowed) => {
|
|
58
|
-
if (given === void 0) return void 0;
|
|
59
|
-
if (!allowed.includes(given)) {
|
|
60
|
-
throw new Error(`${flag} "${given}" is not one of ${allowed.join(", ")}`);
|
|
61
|
-
}
|
|
62
|
-
return given;
|
|
63
|
-
};
|
|
64
|
-
var uiOf = (dir, given) => {
|
|
65
|
-
if (given !== void 0) return given;
|
|
66
|
-
if (!existsSync(join(dir, "packages/ui"))) return "none";
|
|
67
|
-
throw new Error(
|
|
68
|
-
`${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("|")}.`
|
|
69
|
-
);
|
|
70
|
-
};
|
|
71
|
-
var doorFor = (block) => `@geonosis/${block}`;
|
|
72
|
-
var KIT_OWNED_BLOCKS = ["ledger", "review", "testbed", "verify", "walk"];
|
|
73
|
-
var planAdoption = ({ backend, dir, presets, runtime, ui }) => {
|
|
74
|
-
const at = resolve(dir);
|
|
75
|
-
if (!existsSync(at))
|
|
76
|
-
throw new Error(`${at} is not there \u2014 --plan reads a repo, it never makes one`);
|
|
77
|
-
const manifests = manifestsIn(at);
|
|
78
|
-
const chosenRuntime = chosen("--runtime", runtime, ["bun", "pnpm"]) ?? runtimeOf(at);
|
|
79
|
-
if (chosenRuntime === void 0) {
|
|
80
|
-
throw new Error(
|
|
81
|
-
`${at} has none of ${LOCKFILES.join(", ")}, so nothing there says which package manager it runs \u2014 name --runtime pnpm|bun.`
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
const answers = {
|
|
85
|
-
backend: chosen("--backend", backend, BACKENDS) ?? backendOf(manifests),
|
|
86
|
-
dir: at,
|
|
87
|
-
domains: [],
|
|
88
|
-
name: basename(at),
|
|
89
|
-
runtime: chosenRuntime,
|
|
90
|
-
scope: scopeOf(basename(at)),
|
|
91
|
-
ui: uiOf(at, chosen("--ui", ui, UIS))
|
|
92
|
-
};
|
|
93
|
-
const drift = checkScaffold(answers);
|
|
94
|
-
const enabled = new Set(
|
|
95
|
-
Object.keys(
|
|
96
|
-
readJson(join(at, ".oxlintrc.json"))?.rules ?? {}
|
|
97
|
-
)
|
|
98
|
-
);
|
|
99
|
-
const config = readJson(join(at, "geonosis.json")) ?? {};
|
|
100
|
-
const declaredTiers = config["verify"] ?? {};
|
|
101
|
-
const declaredCounters = new Set(
|
|
102
|
-
(readJson(
|
|
103
|
-
join(at, "geonosis.ratchet.json")
|
|
104
|
-
)?.counters ?? []).map((one) => one.key ?? one.counter ?? "")
|
|
105
|
-
);
|
|
106
|
-
const declaredPackages = new Set(dependencyNames(manifests));
|
|
107
|
-
const { unanswered } = rulesFor(presets);
|
|
108
|
-
const tiers = tiersFor({
|
|
109
|
-
rules: {},
|
|
110
|
-
runtime: chosenRuntime,
|
|
111
|
-
scripts: Object.keys(
|
|
112
|
-
readJson(join(at, "package.json"))?.scripts ?? {}
|
|
113
|
-
)
|
|
114
|
-
});
|
|
115
|
-
return {
|
|
116
|
-
backend: answers.backend,
|
|
117
|
-
counters: countersFor(chosenRuntime, "LAW.md").map((one) => {
|
|
118
|
-
const key = typeof one["key"] === "string" ? one["key"] : String(one["counter"]);
|
|
119
|
-
return { declared: declaredCounters.has(key), key };
|
|
120
|
-
}),
|
|
121
|
-
dir: at,
|
|
122
|
-
doors: KIT_OWNED_BLOCKS.map((block) => ({
|
|
123
|
-
block,
|
|
124
|
-
declared: declaredPackages.has(doorFor(block)),
|
|
125
|
-
package: doorFor(block)
|
|
126
|
-
})),
|
|
127
|
-
files: drift.filter((one) => one.kind === "missing"),
|
|
128
|
-
keys: drift.filter((one) => one.kind === "key"),
|
|
129
|
-
presets: presets.map((name) => {
|
|
130
|
-
const brings = Object.keys(rulesFor([name]).rules);
|
|
131
|
-
return {
|
|
132
|
-
brings: brings.length,
|
|
133
|
-
enabled: brings.filter((id) => enabled.has(id)),
|
|
134
|
-
name
|
|
135
|
-
};
|
|
136
|
-
}),
|
|
137
|
-
runtime: chosenRuntime,
|
|
138
|
-
tiers: Object.entries(tiers).map(([name, steps]) => ({
|
|
139
|
-
declared: Object.hasOwn(declaredTiers, name),
|
|
140
|
-
name,
|
|
141
|
-
steps
|
|
142
|
-
})),
|
|
143
|
-
ui: answers.ui,
|
|
144
|
-
unanswered,
|
|
145
|
-
unreadable: drift.filter((one) => one.kind === "unreadable")
|
|
146
|
-
};
|
|
147
|
-
};
|
|
148
|
-
var section = (title, lines) => lines.length === 0 ? [`${title} \u2014 none`, ""] : [`${title} (${lines.length})`, ...lines, ""];
|
|
149
|
-
var formatPlan = (plan) => [
|
|
150
|
-
`adoption plan for ${plan.dir}`,
|
|
151
|
-
` runtime ${plan.runtime} \xB7 backend ${plan.backend} \xB7 ui ${plan.ui}`,
|
|
152
|
-
"",
|
|
153
|
-
...section(
|
|
154
|
-
"FILES the scaffold writes and this repo has none of",
|
|
155
|
-
plan.files.map((one) => ` ${one.file}`)
|
|
156
|
-
),
|
|
157
|
-
...section(
|
|
158
|
-
"KEYS the scaffold declares inside a file this repo has",
|
|
159
|
-
plan.keys.map((one) => ` ${one.file} \u2192 ${one.at}`)
|
|
160
|
-
),
|
|
161
|
-
...section(
|
|
162
|
-
"RULES the presets bring",
|
|
163
|
-
plan.presets.map(
|
|
164
|
-
(one) => ` ${one.name}: ${one.brings} rules, ${one.enabled.length} already enabled here`
|
|
165
|
-
)
|
|
166
|
-
),
|
|
167
|
-
...section(
|
|
168
|
-
"RULES left out until somebody answers their option",
|
|
169
|
-
plan.unanswered.map(([rule, option]) => ` ${rule} needs \`${option}\``)
|
|
170
|
-
),
|
|
171
|
-
...section(
|
|
172
|
-
"COUNTERS the ratchet would run",
|
|
173
|
-
plan.counters.map((one) => ` ${one.key}${one.declared ? " \u2014 declared" : ""}`)
|
|
174
|
-
),
|
|
175
|
-
...section(
|
|
176
|
-
"TIERS",
|
|
177
|
-
plan.tiers.map((one) => ` ${one.name}${one.declared ? " \u2014 declared" : ""}`)
|
|
178
|
-
),
|
|
179
|
-
...section(
|
|
180
|
-
"DOORS a block needs declared",
|
|
181
|
-
plan.doors.map(
|
|
182
|
-
(one) => ` ${one.package} for "${one.block}"${one.declared ? " \u2014 declared" : ""}`
|
|
183
|
-
)
|
|
184
|
-
),
|
|
185
|
-
...plan.unreadable.length === 0 ? [] : section(
|
|
186
|
-
"FILES this check could not read as JSON",
|
|
187
|
-
plan.unreadable.map((one) => ` ${one.file}`)
|
|
188
|
-
)
|
|
189
|
-
].join("\n");
|
|
190
|
-
|
|
191
1
|
// src/create-geonosis-cli.ts
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { ADOPT_USAGE, formatPlan, planAdoption } from "./adopt.js";
|
|
4
|
+
import { MEDUSA_RANGE, parseAnswers, parseArgs, wantsInstall } from "./answers.js";
|
|
5
|
+
import { checkScaffold, describeDrift } from "./check.js";
|
|
6
|
+
import { DOMAIN_NAMES, PLATFORM_FILE } from "./composition.js";
|
|
7
|
+
import { finish, writeScaffold } from "./scaffold.js";
|
|
192
8
|
var USAGE = `create-geonosis <dir> --runtime pnpm|bun --backend medusa|sagaflow-cf|none --ui tiered-shadcn|atoms-only|none
|
|
193
9
|
[--medusa <range>]
|
|
194
10
|
[--domains <a,b>] [--brand <name>] [--themekit <name>] [--marketplace <path>] [--kit <path>] [--no-install]
|
|
195
11
|
[--check]
|
|
196
|
-
create-geonosis --plan <dir> [--presets <a,b>] [--runtime
|
|
12
|
+
create-geonosis --plan <dir> [--presets <a,b>] [--runtime …] [--backend …] [--ui …]
|
|
197
13
|
|
|
198
14
|
Writes a repo that passes its own gates on day one: a workspace with one catalog of shared versions,
|
|
199
15
|
an exports map and no barrel in every package, typecheck + lint + test scripts in every workspace,
|
|
@@ -204,7 +20,7 @@ skills lock, and a reach corpus in this repo's own vocabulary so the doctor can
|
|
|
204
20
|
There is no default for --runtime, --backend or --ui. Each one decides the shape of the tree, and a
|
|
205
21
|
default borrowed from another repo would be that repo's stack hardcoded into this one.
|
|
206
22
|
|
|
207
|
-
--domains the domains this repo composes
|
|
23
|
+
--domains the domains this repo composes — ${DOMAIN_NAMES.join(", ")}. Each one writes its
|
|
208
24
|
store into the app's ${PLATFORM_FILE}: the composition root, in two lifetimes, with
|
|
209
25
|
the tenant session opened before any store is built. A name nothing ships is refused.
|
|
210
26
|
--medusa <range>
|
|
@@ -221,19 +37,19 @@ default borrowed from another repo would be that repo's stack hardcoded into thi
|
|
|
221
37
|
preset brings against the ones already enabled, the rules left out until somebody
|
|
222
38
|
answers their option, the counters, the tiers and the doors a block needs
|
|
223
39
|
declared. The runtime and the backend are read off the lockfile and the manifests;
|
|
224
|
-
a flag overrides one. Exit 0
|
|
40
|
+
a flag overrides one. Exit 0 — it is a report.
|
|
225
41
|
--check say what an EXISTING repo is missing of what the scaffold writes today, and WRITE
|
|
226
42
|
NOTHING. Files it has never had, and keys inside the JSON it has. Contents and
|
|
227
43
|
values are never compared: a repo that has been worked in has rewritten them, and
|
|
228
44
|
it was entitled to. Exit 1 when anything drifted.
|
|
229
45
|
|
|
230
|
-
Exit: 0 written (or, under --check, no drift)
|
|
231
|
-
answer that is not one) or drift found
|
|
46
|
+
Exit: 0 written (or, under --check, no drift) · 1 refused (a directory with something in it, an
|
|
47
|
+
answer that is not one) or drift found · 2 a run that could not be made at all.`;
|
|
232
48
|
var CONFIG_SHAPE = `create-geonosis reads no configuration file.
|
|
233
49
|
|
|
234
50
|
It WRITES them: geonosis.json, .oxlintrc.json, geonosis.ratchet.json and gate-baseline.json, from
|
|
235
51
|
the answers given on the command line. Every answer is a flag, and there is no default for the three
|
|
236
|
-
that decide the shape of the tree
|
|
52
|
+
that decide the shape of the tree — see create-geonosis --help.
|
|
237
53
|
|
|
238
54
|
For the shape of what it wrote, ask the bin that reads it: geonosis-<tool> --print-config-shape.`;
|
|
239
55
|
var main = () => {
|
|
@@ -250,61 +66,57 @@ var main = () => {
|
|
|
250
66
|
}
|
|
251
67
|
if (argv.includes("--plan")) {
|
|
252
68
|
const { dir, flags } = parseArgs(argv);
|
|
253
|
-
if (dir ===
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
)}
|
|
264
|
-
`
|
|
265
|
-
);
|
|
69
|
+
if (dir === undefined)
|
|
70
|
+
throw new Error(`--plan needs a directory — ${ADOPT_USAGE}`);
|
|
71
|
+
process.stdout.write(`${formatPlan(planAdoption({
|
|
72
|
+
...flags["--backend"] === undefined ? {} : { backend: flags["--backend"] },
|
|
73
|
+
dir,
|
|
74
|
+
presets: (flags["--presets"] ?? "frontend,tooling").split(",").map((one) => one.trim()).filter((one) => one !== ""),
|
|
75
|
+
...flags["--runtime"] === undefined ? {} : { runtime: flags["--runtime"] },
|
|
76
|
+
...flags["--ui"] === undefined ? {} : { ui: flags["--ui"] }
|
|
77
|
+
}))}
|
|
78
|
+
`);
|
|
266
79
|
return 0;
|
|
267
80
|
}
|
|
268
81
|
const answers = parseAnswers(argv, process.cwd());
|
|
269
82
|
if (argv.includes("--check")) {
|
|
270
|
-
if (!
|
|
271
|
-
throw new Error(`${answers.dir} is not there
|
|
83
|
+
if (!existsSync(answers.dir)) {
|
|
84
|
+
throw new Error(`${answers.dir} is not there — --check reads a repo, it never makes one`);
|
|
272
85
|
}
|
|
273
86
|
const drift = checkScaffold(answers);
|
|
274
87
|
if (drift.length === 0) {
|
|
275
|
-
process.stdout.write(`create-geonosis --check ${answers.dir}
|
|
88
|
+
process.stdout.write(`create-geonosis --check ${answers.dir} — no drift
|
|
276
89
|
`);
|
|
277
90
|
return 0;
|
|
278
91
|
}
|
|
279
|
-
for (const line of describeDrift(drift))
|
|
92
|
+
for (const line of describeDrift(drift))
|
|
93
|
+
process.stderr.write(` ${line}
|
|
94
|
+
`);
|
|
95
|
+
process.stderr.write(`create-geonosis --check ${answers.dir} — ${drift.length} drift, nothing written
|
|
280
96
|
`);
|
|
281
|
-
process.stderr.write(
|
|
282
|
-
`create-geonosis --check ${answers.dir} \u2014 ${drift.length} drift, nothing written
|
|
283
|
-
`
|
|
284
|
-
);
|
|
285
97
|
return 1;
|
|
286
98
|
}
|
|
287
99
|
const written = writeScaffold(answers);
|
|
288
|
-
process.stdout.write(`create-geonosis
|
|
100
|
+
process.stdout.write(`create-geonosis — ${written.length} file(s) into ${answers.dir}
|
|
289
101
|
`);
|
|
290
102
|
const install = wantsInstall(argv);
|
|
291
103
|
for (const step of finish({ answers, install })) {
|
|
292
104
|
process.stdout.write(` ${step.code === 0 ? "ok " : "FAIL"} ${step.what}
|
|
293
105
|
`);
|
|
294
|
-
if (step.code !== 0)
|
|
106
|
+
if (step.code !== 0)
|
|
107
|
+
process.stdout.write(`${step.output}
|
|
295
108
|
`);
|
|
296
109
|
}
|
|
297
110
|
if (!install) {
|
|
298
|
-
process.stdout.write(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
);
|
|
111
|
+
process.stdout.write([
|
|
112
|
+
"",
|
|
113
|
+
" Nothing was installed, so the edges table has not reached the gates yet. Run:",
|
|
114
|
+
` cd ${answers.dir} && ${answers.runtime} install`,
|
|
115
|
+
" geonosis sync --target turbo --into turbo.json",
|
|
116
|
+
" geonosis sync --target layer-walls --into .oxlintrc.json",
|
|
117
|
+
""
|
|
118
|
+
].join(`
|
|
119
|
+
`));
|
|
308
120
|
}
|
|
309
121
|
return 0;
|
|
310
122
|
};
|
package/dist/files.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Answers } from './answers.js';
|
|
2
|
+
declare const presetsFor: (answers: Answers) => string[];
|
|
3
|
+
/** The kit a scaffolded repo runs on, at the version this scaffold was published at. */
|
|
4
|
+
export declare const kitDependencies: (answers: Answers) => Record<string, string>;
|
|
5
|
+
/**
|
|
6
|
+
* The two files the edges table generates, named once. `scaffold` runs them when it makes the tree
|
|
7
|
+
* and the `sync:graph` script runs them when the table changes; a second spelling of the pair is a
|
|
8
|
+
* second answer to where the graph comes from.
|
|
9
|
+
*/
|
|
10
|
+
export declare const GRAPH_SYNCS: {
|
|
11
|
+
target: string;
|
|
12
|
+
write: string;
|
|
13
|
+
}[];
|
|
14
|
+
/** What the root can be asked to run — and therefore what a verify tier may name (`tiersFor`). */
|
|
15
|
+
export declare const HOOK_INSTALLER = "scripts/install-hooks.mjs";
|
|
16
|
+
export declare const filesFor: (answers: Answers) => Record<string, string>;
|
|
17
|
+
export declare const presetsChosenBy: typeof presetsFor;
|
|
18
|
+
export {};
|