create-geonosis 2.13.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 +23 -0
- package/bin/create-geonosis.mjs +1 -1
- 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-LUUI74BB.js +0 -1224
package/dist/index.d.ts
CHANGED
|
@@ -1,170 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
type
|
|
7
|
-
type
|
|
8
|
-
backend: Backend;
|
|
9
|
-
brand?: string;
|
|
10
|
-
dir: string;
|
|
11
|
-
/** The domains this repo composes at its composition root. Empty until the flag names one. */
|
|
12
|
-
domains: string[];
|
|
13
|
-
/** The kit read from a workspace instead of the registry — a proof aid, never the default. */
|
|
14
|
-
kit?: string;
|
|
15
|
-
marketplace?: string;
|
|
16
|
-
/** The Medusa range a `--backend medusa` tree declares. `MEDUSA_RANGE` unless one is named. */
|
|
17
|
-
medusa?: string;
|
|
18
|
-
name: string;
|
|
19
|
-
runtime: Runtime;
|
|
20
|
-
scope: string;
|
|
21
|
-
themekit?: string;
|
|
22
|
-
ui: Ui;
|
|
23
|
-
};
|
|
24
|
-
declare const parseAnswers: (argv: readonly string[], cwd: string) => Answers;
|
|
25
|
-
declare const wantsInstall: (argv: readonly string[]) => boolean;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* What an existing repo never receives: whatever the scaffold has learned since it was run.
|
|
29
|
-
*
|
|
30
|
-
* The comparison is over SHAPE and not over content, because a repo that has been worked in has
|
|
31
|
-
* legitimately rewritten every file the scaffold gave it. What it cannot have legitimately done is
|
|
32
|
-
* lose a file the scaffold now writes, or miss a declaration a new gate reads — so a missing path is
|
|
33
|
-
* drift and a changed value never is.
|
|
34
|
-
*/
|
|
35
|
-
type DriftKind = 'key' | 'missing' | 'unreadable';
|
|
36
|
-
type Drift = {
|
|
37
|
-
/** The key path inside the file, or `''` when the fact is about the whole file. */
|
|
38
|
-
at: string;
|
|
39
|
-
file: string;
|
|
40
|
-
kind: DriftKind;
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* The drift between a tree and what the scaffold would write into it today. Reads only.
|
|
44
|
-
*
|
|
45
|
-
* Nothing in this module opens a file for writing, and nothing calls `writeScaffold`. That is the
|
|
46
|
-
* whole contract of `--check`: it is run against a repo somebody is working in.
|
|
47
|
-
*/
|
|
48
|
-
declare const checkScaffold: (answers: Answers) => Drift[];
|
|
49
|
-
declare const describeDrift: (drift: readonly Drift[]) => string[];
|
|
50
|
-
|
|
51
|
-
/** Where the composition root lives, relative to the workspace that composes: the app. */
|
|
52
|
-
declare const PLATFORM_FILE = "src/platform.ts";
|
|
53
|
-
/**
|
|
54
|
-
* A domain the kit ships, and the two lines composing it takes.
|
|
55
|
-
*
|
|
56
|
-
* A domain is added here and nowhere else: the flag's vocabulary, the app's dependencies and the
|
|
57
|
-
* generated file all read this table, so a domain that ships without a way to compose it cannot be
|
|
58
|
-
* offered by name. `version` is the floor package's OWN line — the method's number says nothing
|
|
59
|
-
* about it (D-053) — and `tooling/floor-scaffold-versions.test.ts` holds each against the workspace.
|
|
60
|
-
*/
|
|
61
|
-
type DomainScaffold = {
|
|
62
|
-
/** Where the package sits in the kit's tree, for `--kit`'s workspace links. */
|
|
63
|
-
at: string;
|
|
64
|
-
/** What the scope field is called, and the type it is annotated with. */
|
|
65
|
-
field: string;
|
|
66
|
-
/** Everything the generated file needs above the composition itself. */
|
|
67
|
-
declarations: readonly string[];
|
|
68
|
-
fieldType: string;
|
|
69
|
-
imports: readonly string[];
|
|
70
|
-
name: string;
|
|
71
|
-
/** How the store is built, once the tenant's transaction is already open. */
|
|
72
|
-
built: readonly string[];
|
|
73
|
-
version: string;
|
|
74
|
-
};
|
|
75
|
-
declare const DOMAINS: readonly DomainScaffold[];
|
|
76
|
-
declare const DOMAIN_NAMES: readonly string[];
|
|
77
|
-
/** The two foundations a composition root is built from, whichever domains it composes. */
|
|
78
|
-
declare const FOUNDATIONS: readonly {
|
|
79
|
-
at: string;
|
|
80
|
-
name: string;
|
|
81
|
-
version: string;
|
|
82
|
-
}[];
|
|
83
|
-
declare const domainsNamed: (named: readonly string[]) => DomainScaffold[];
|
|
84
|
-
/** What the composing workspace declares once a domain is named. */
|
|
85
|
-
declare const floorDependencies: (named: readonly string[]) => Record<string, string>;
|
|
86
|
-
/** Every kit directory a `--kit` link has to reach for the domains this repo named. */
|
|
87
|
-
declare const floorDirectories: (named: readonly string[]) => string[];
|
|
88
|
-
/**
|
|
89
|
-
* The composition root, in the two lifetimes it has.
|
|
90
|
-
*
|
|
91
|
-
* DEFINITIONS are what this deployment IS — wired once per isolate, before anybody has asked
|
|
92
|
-
* anything. SCOPE is who is asking, bound per request. Keeping them apart is what lets the session
|
|
93
|
-
* come first: the stores are built INSIDE the tenant's transaction, so a caller cannot hold one
|
|
94
|
-
* that was made without a session to run in (D-014 — structural, never an assertion).
|
|
95
|
-
*/
|
|
96
|
-
declare const platformSource: (named: readonly string[]) => string;
|
|
97
|
-
|
|
98
|
-
type Workspace = {
|
|
99
|
-
dir: string;
|
|
100
|
-
/** The files under this workspace, keyed by path relative to the workspace. */
|
|
101
|
-
sources: Record<string, string>;
|
|
102
|
-
name: string;
|
|
103
|
-
/** Packages outside the workspace this one declares, spec and all. */
|
|
104
|
-
packages?: Record<string, string>;
|
|
105
|
-
tag: string;
|
|
106
|
-
mayDependOn: string[];
|
|
107
|
-
};
|
|
108
|
-
/**
|
|
109
|
-
* The workspaces the answers imply, and the edge each one is allowed. Two at minimum — a pure-logic
|
|
110
|
-
* package and the app that composes it — because a wall needs something on the other side of it: a
|
|
111
|
-
* repo with one tag has no edge to violate, and a `layer-walls` that cannot fire is the inert rule
|
|
112
|
-
* this kit exists to catch.
|
|
113
|
-
*/
|
|
114
|
-
declare const workspacesOf: (answers: Answers) => Workspace[];
|
|
115
|
-
|
|
116
|
-
declare const CORPUS_DIR = "lint-corpus";
|
|
117
|
-
/**
|
|
118
|
-
* A reach corpus a shipped one cannot be.
|
|
119
|
-
*
|
|
120
|
-
* `geonosis-doctor` asks whether every enabled rule can fire at all, and it asks the corpus the
|
|
121
|
-
* PLUGIN ships. That cannot answer for a rule whose reach is the repo's own vocabulary: its layer
|
|
122
|
-
* names, its brand, its plugin package root, the `within` its sagas live in. The plugin's corpus
|
|
123
|
-
* says "acme" and `layers/core`, so it is no evidence either way about a repo that says anything
|
|
124
|
-
* else — and those rules would read as firing nowhere.
|
|
125
|
-
*
|
|
126
|
-
* So the repo ships its own, in ITS vocabulary. The corpus is copied to a temp root and linted with
|
|
127
|
-
* `--no-ignore`, so the fixture paths mirror the repo's real ones and the layer patterns match.
|
|
128
|
-
*/
|
|
129
|
-
declare const corpusFor: (answers: Answers, workspaces: Workspace[]) => Record<string, string>;
|
|
130
|
-
|
|
131
|
-
declare const filesFor: (answers: Answers) => Record<string, string>;
|
|
132
|
-
declare const presetsChosenBy: (answers: Answers) => string[];
|
|
133
|
-
|
|
134
|
-
/** The kit packages a scaffolded repo RESOLVES, and therefore the ones `--kit` has to link. */
|
|
135
|
-
declare const KIT_PACKAGES: string[];
|
|
136
|
-
/**
|
|
137
|
-
* Every file, and never over the top of anything. A scaffold that overwrote a tree is a scaffold
|
|
138
|
-
* nobody dares run in the wrong shell, and the tree it lands in is usually somebody's work.
|
|
139
|
-
*/
|
|
140
|
-
declare const writeScaffold: (answers: Answers) => string[];
|
|
141
|
-
type Step = {
|
|
142
|
-
code: number;
|
|
143
|
-
output: string;
|
|
144
|
-
what: string;
|
|
145
|
-
};
|
|
146
|
-
/**
|
|
147
|
-
* The kit read from a workspace instead of the registry — a proof aid, and only that.
|
|
148
|
-
*
|
|
149
|
-
* It is done by hand, after the install, because neither package manager can do it: pnpm resolves
|
|
150
|
-
* `link:` happily, and bun refuses BOTH `link:` and `file:` here, because it follows into the target
|
|
151
|
-
* and tries to resolve the `workspace:*` specifiers the kit's own packages use. Symlinking the
|
|
152
|
-
* directories and writing the bin shims is the one form of the answer that is true on both.
|
|
153
|
-
*
|
|
154
|
-
* The manifests are then told what is on disk, because `geonosis-doctor` FAILS a config whose plugin
|
|
155
|
-
* no manifest declares — and it should. A link is a declaration it accepts and can verify.
|
|
156
|
-
*/
|
|
157
|
-
declare const linkKit: (answers: Answers, kit: string) => string[];
|
|
158
|
-
/**
|
|
159
|
-
* What has to happen after the files exist, in the order it has to happen in.
|
|
160
|
-
*
|
|
161
|
-
* The two `sync` calls are why the scaffold does not compute a boundaries map or a layer list of
|
|
162
|
-
* its own: the edges table is the ONE place the graph is written, and the ledger is the one thing
|
|
163
|
-
* that reads it. A second generator here would be a second answer to the same question.
|
|
164
|
-
*/
|
|
165
|
-
declare const finish: ({ answers, install }: {
|
|
166
|
-
answers: Answers;
|
|
167
|
-
install: boolean;
|
|
168
|
-
}) => Step[];
|
|
169
|
-
|
|
170
|
-
export { type Answers, BACKENDS, type Backend, CORPUS_DIR, DOMAINS, DOMAIN_NAMES, type DomainScaffold, type Drift, type DriftKind, FOUNDATIONS, KIT_PACKAGES, PLATFORM_FILE, RUNTIMES, type Runtime, type Step, UIS, type Ui, type Workspace, checkScaffold, corpusFor, describeDrift, domainsNamed, filesFor, finish, floorDependencies, floorDirectories, linkKit, parseAnswers, platformSource, presetsChosenBy, wantsInstall, workspacesOf, writeScaffold };
|
|
1
|
+
export { type Answers, type Backend, BACKENDS, parseAnswers, type Runtime, RUNTIMES, type Ui, UIS, wantsInstall } from './answers.js';
|
|
2
|
+
export { checkScaffold, describeDrift, type Drift, type DriftKind } from './check.js';
|
|
3
|
+
export { DOMAIN_NAMES, DOMAINS, type DomainScaffold, domainsNamed, floorDependencies, floorDirectories, FOUNDATIONS, PLATFORM_FILE, platformSource } from './composition.js';
|
|
4
|
+
export { CORPUS_DIR, corpusFor } from './corpus.js';
|
|
5
|
+
export { filesFor, presetsChosenBy } from './files.js';
|
|
6
|
+
export { type Workspace, workspacesOf } from './layout.js';
|
|
7
|
+
export { finish, KIT_PACKAGES, linkKit, type Step, writeScaffold } from './scaffold.js';
|
package/dist/index.js
CHANGED
|
@@ -1,29 +1,26 @@
|
|
|
1
|
+
// src/index.ts
|
|
1
2
|
import {
|
|
2
3
|
BACKENDS,
|
|
3
|
-
|
|
4
|
-
DOMAINS,
|
|
5
|
-
DOMAIN_NAMES,
|
|
6
|
-
FOUNDATIONS,
|
|
7
|
-
KIT_PACKAGES,
|
|
8
|
-
PLATFORM_FILE,
|
|
4
|
+
parseAnswers,
|
|
9
5
|
RUNTIMES,
|
|
10
6
|
UIS,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
wantsInstall
|
|
8
|
+
} from "./answers.js";
|
|
9
|
+
import { checkScaffold, describeDrift } from "./check.js";
|
|
10
|
+
import {
|
|
11
|
+
DOMAIN_NAMES,
|
|
12
|
+
DOMAINS,
|
|
14
13
|
domainsNamed,
|
|
15
|
-
filesFor,
|
|
16
|
-
finish,
|
|
17
14
|
floorDependencies,
|
|
18
15
|
floorDirectories,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
platformSource
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
} from "./
|
|
16
|
+
FOUNDATIONS,
|
|
17
|
+
PLATFORM_FILE,
|
|
18
|
+
platformSource
|
|
19
|
+
} from "./composition.js";
|
|
20
|
+
import { CORPUS_DIR, corpusFor } from "./corpus.js";
|
|
21
|
+
import { filesFor, presetsChosenBy } from "./files.js";
|
|
22
|
+
import { workspacesOf } from "./layout.js";
|
|
23
|
+
import { finish, KIT_PACKAGES, linkKit, writeScaffold } from "./scaffold.js";
|
|
27
24
|
export {
|
|
28
25
|
BACKENDS,
|
|
29
26
|
CORPUS_DIR,
|
package/dist/layout.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Answers } from './answers.js';
|
|
2
|
+
export type Workspace = {
|
|
3
|
+
dir: string;
|
|
4
|
+
/** The files under this workspace, keyed by path relative to the workspace. */
|
|
5
|
+
sources: Record<string, string>;
|
|
6
|
+
name: string;
|
|
7
|
+
/** Packages outside the workspace this one declares, spec and all. */
|
|
8
|
+
packages?: Record<string, string>;
|
|
9
|
+
tag: string;
|
|
10
|
+
mayDependOn: string[];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The workspaces the answers imply, and the edge each one is allowed. Two at minimum — a pure-logic
|
|
14
|
+
* package and the app that composes it — because a wall needs something on the other side of it: a
|
|
15
|
+
* repo with one tag has no edge to violate, and a `layer-walls` that cannot fire is the inert rule
|
|
16
|
+
* this kit exists to catch.
|
|
17
|
+
*/
|
|
18
|
+
export declare const workspacesOf: (answers: Answers) => Workspace[];
|
package/dist/layout.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// src/layout.ts
|
|
2
|
+
import { MEDUSA_PACKAGES, MEDUSA_RANGE } from "./answers.js";
|
|
3
|
+
import { floorDependencies, PLATFORM_FILE, platformSource } from "./composition.js";
|
|
4
|
+
var CORE = `export type Money = { amount: number; currency: string }
|
|
5
|
+
|
|
6
|
+
export const totalOf = (lines: readonly Money[], currency: string): Money => ({
|
|
7
|
+
amount: lines.filter((one) => one.currency === currency).reduce((sum, one) => sum + one.amount, 0),
|
|
8
|
+
currency,
|
|
9
|
+
})
|
|
10
|
+
`;
|
|
11
|
+
var CORE_TEST = (scope) => `import { describe, expect, it } from 'vitest'
|
|
12
|
+
|
|
13
|
+
import { totalOf } from './money'
|
|
14
|
+
|
|
15
|
+
describe('totalOf', () => {
|
|
16
|
+
it('adds the lines that share the currency', () => {
|
|
17
|
+
expect(totalOf([{ amount: 2, currency: 'EUR' }, { amount: 3, currency: 'EUR' }], 'EUR')).toEqual(
|
|
18
|
+
{ amount: 5, currency: 'EUR' },
|
|
19
|
+
)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('ignores the ones that do not — ${scope}/core never guesses a rate', () => {
|
|
23
|
+
expect(totalOf([{ amount: 2, currency: 'EUR' }, { amount: 9, currency: 'USD' }], 'EUR').amount)
|
|
24
|
+
.toBe(2)
|
|
25
|
+
})
|
|
26
|
+
})
|
|
27
|
+
`;
|
|
28
|
+
var BUTTON = `import type { ComponentProps } from 'react'
|
|
29
|
+
|
|
30
|
+
export function Button(props: ComponentProps<'button'>) {
|
|
31
|
+
return <button type="button" {...props} />
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
var FIELD = (scope) => `import { Button } from '${scope}/ui/atoms/button'
|
|
35
|
+
|
|
36
|
+
export function Field({ label, onSubmit }: { label: string; onSubmit: () => void }) {
|
|
37
|
+
return (
|
|
38
|
+
<label>
|
|
39
|
+
{label}
|
|
40
|
+
<Button onClick={onSubmit}>Go</Button>
|
|
41
|
+
</label>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
var UI_TEST = `import { describe, expect, it } from 'vitest'
|
|
46
|
+
|
|
47
|
+
import { Button } from './atoms/button'
|
|
48
|
+
|
|
49
|
+
describe('Button', () => {
|
|
50
|
+
it('is a component, so a tier above can compose it', () => {
|
|
51
|
+
expect(typeof Button).toBe('function')
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
`;
|
|
55
|
+
var MEDUSA_SUBSCRIBER = `export const config = {
|
|
56
|
+
context: { subscriberId: 'order-placed' },
|
|
57
|
+
event: 'order.placed',
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default async function orderPlaced({ data }: { data: { id: string } }): Promise<string> {
|
|
61
|
+
return data.id
|
|
62
|
+
}
|
|
63
|
+
`;
|
|
64
|
+
var SAGAFLOW_STEP = `export const step = <Input, Output>(
|
|
65
|
+
name: string,
|
|
66
|
+
body: (input: Input) => Promise<Output>,
|
|
67
|
+
undo: (output: Output) => Promise<void>,
|
|
68
|
+
) => ({ body, name, undo })
|
|
69
|
+
`;
|
|
70
|
+
var SAGAFLOW_FLOW = `import { step } from './step'
|
|
71
|
+
|
|
72
|
+
export const reserve = step(
|
|
73
|
+
'reserve',
|
|
74
|
+
async (input: { id: string }) => ({ held: input.id }),
|
|
75
|
+
async () => undefined,
|
|
76
|
+
)
|
|
77
|
+
`;
|
|
78
|
+
var BACKEND_TEST = `import { describe, expect, it } from 'vitest'
|
|
79
|
+
|
|
80
|
+
import { handler } from './handler'
|
|
81
|
+
|
|
82
|
+
describe('the backend seam', () => {
|
|
83
|
+
it('answers with what it was asked about', async () => {
|
|
84
|
+
expect(await handler({ id: 'ord_1' })).toBe('ord_1')
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
`;
|
|
88
|
+
var HANDLER = `export const handler = async ({ id }: { id: string }): Promise<string> => id
|
|
89
|
+
`;
|
|
90
|
+
var APP = (scope, ui) => ui ? `import { totalOf } from '${scope}/core/money'
|
|
91
|
+
import { Button } from '${scope}/ui/atoms/button'
|
|
92
|
+
|
|
93
|
+
export const boot = () => ({ button: Button, total: totalOf([], 'EUR') })
|
|
94
|
+
` : `import { totalOf } from '${scope}/core/money'
|
|
95
|
+
|
|
96
|
+
export const boot = () => ({ total: totalOf([], 'EUR') })
|
|
97
|
+
`;
|
|
98
|
+
var APP_TEST = `import { describe, expect, it } from 'vitest'
|
|
99
|
+
|
|
100
|
+
import { boot } from './boot'
|
|
101
|
+
|
|
102
|
+
describe('boot', () => {
|
|
103
|
+
it('starts with an empty total rather than a guess', () => {
|
|
104
|
+
expect(boot().total.amount).toBe(0)
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
`;
|
|
108
|
+
var workspacesOf = (answers) => {
|
|
109
|
+
const { scope, ui } = answers;
|
|
110
|
+
const list = [
|
|
111
|
+
{
|
|
112
|
+
dir: "packages/core",
|
|
113
|
+
mayDependOn: [],
|
|
114
|
+
name: `${scope}/core`,
|
|
115
|
+
sources: { "src/money.ts": CORE, "src/money.test.ts": CORE_TEST(scope) },
|
|
116
|
+
tag: "core"
|
|
117
|
+
}
|
|
118
|
+
];
|
|
119
|
+
if (ui !== "none") {
|
|
120
|
+
list.push({
|
|
121
|
+
dir: "packages/ui",
|
|
122
|
+
mayDependOn: ["core"],
|
|
123
|
+
name: `${scope}/ui`,
|
|
124
|
+
sources: {
|
|
125
|
+
"src/atoms/button.tsx": BUTTON,
|
|
126
|
+
"src/button.test.ts": UI_TEST,
|
|
127
|
+
...ui === "tiered-shadcn" ? { "src/molecules/field.tsx": FIELD(scope) } : {}
|
|
128
|
+
},
|
|
129
|
+
tag: "ui"
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
if (answers.backend !== "none") {
|
|
133
|
+
list.push({
|
|
134
|
+
dir: "packages/backend",
|
|
135
|
+
mayDependOn: ["core"],
|
|
136
|
+
name: `${scope}/backend`,
|
|
137
|
+
...answers.backend === "medusa" ? {
|
|
138
|
+
packages: Object.fromEntries(MEDUSA_PACKAGES.map((one) => [one, answers.medusa ?? MEDUSA_RANGE]))
|
|
139
|
+
} : {},
|
|
140
|
+
sources: {
|
|
141
|
+
"src/handler.ts": HANDLER,
|
|
142
|
+
"src/handler.test.ts": BACKEND_TEST,
|
|
143
|
+
...answers.backend === "medusa" ? { "src/subscribers/order-placed.ts": MEDUSA_SUBSCRIBER } : { "src/workflows/step.ts": SAGAFLOW_STEP, "src/workflows/reserve.ts": SAGAFLOW_FLOW }
|
|
144
|
+
},
|
|
145
|
+
tag: "backend"
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
list.push({
|
|
149
|
+
dir: "apps/web",
|
|
150
|
+
mayDependOn: list.map((one) => one.tag),
|
|
151
|
+
name: `${scope}/web`,
|
|
152
|
+
packages: floorDependencies(answers.domains),
|
|
153
|
+
sources: {
|
|
154
|
+
"src/boot.ts": APP(scope, ui !== "none"),
|
|
155
|
+
"src/boot.test.ts": APP_TEST,
|
|
156
|
+
...answers.domains.length === 0 ? {} : { [PLATFORM_FILE]: platformSource(answers.domains) }
|
|
157
|
+
},
|
|
158
|
+
tag: "app"
|
|
159
|
+
});
|
|
160
|
+
return list;
|
|
161
|
+
};
|
|
162
|
+
export {
|
|
163
|
+
workspacesOf
|
|
164
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Answers } from './answers.js';
|
|
2
|
+
/** The kit packages a scaffolded repo RESOLVES, and therefore the ones `--kit` has to link. */
|
|
3
|
+
export declare const KIT_PACKAGES: string[];
|
|
4
|
+
/**
|
|
5
|
+
* Every file, and never over the top of anything. A scaffold that overwrote a tree is a scaffold
|
|
6
|
+
* nobody dares run in the wrong shell, and the tree it lands in is usually somebody's work.
|
|
7
|
+
*/
|
|
8
|
+
export declare const writeScaffold: (answers: Answers) => string[];
|
|
9
|
+
export type Step = {
|
|
10
|
+
code: number;
|
|
11
|
+
output: string;
|
|
12
|
+
what: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The kit read from a workspace instead of the registry — a proof aid, and only that.
|
|
16
|
+
*
|
|
17
|
+
* It is done by hand, after the install, because neither package manager can do it: pnpm resolves
|
|
18
|
+
* `link:` happily, and bun refuses BOTH `link:` and `file:` here, because it follows into the target
|
|
19
|
+
* and tries to resolve the `workspace:*` specifiers the kit's own packages use. Symlinking the
|
|
20
|
+
* directories and writing the bin shims is the one form of the answer that is true on both.
|
|
21
|
+
*
|
|
22
|
+
* The manifests are then told what is on disk, because `geonosis-doctor` FAILS a config whose plugin
|
|
23
|
+
* no manifest declares — and it should. A link is a declaration it accepts and can verify.
|
|
24
|
+
*/
|
|
25
|
+
export declare const linkKit: (answers: Answers, kit: string) => string[];
|
|
26
|
+
/**
|
|
27
|
+
* What has to happen after the files exist, in the order it has to happen in.
|
|
28
|
+
*
|
|
29
|
+
* The two `sync` calls are why the scaffold does not compute a boundaries map or a layer list of
|
|
30
|
+
* its own: the edges table is the ONE place the graph is written, and the ledger is the one thing
|
|
31
|
+
* that reads it. A second generator here would be a second answer to the same question.
|
|
32
|
+
*/
|
|
33
|
+
export declare const finish: ({ answers, install }: {
|
|
34
|
+
answers: Answers;
|
|
35
|
+
install: boolean;
|
|
36
|
+
}) => Step[];
|
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// src/scaffold.ts
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import {
|
|
4
|
+
chmodSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
symlinkSync,
|
|
10
|
+
writeFileSync
|
|
11
|
+
} from "node:fs";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { floorDirectories } from "./composition.js";
|
|
14
|
+
import { filesFor, GRAPH_SYNCS, kitDependencies } from "./files.js";
|
|
15
|
+
import { workspacesOf } from "./layout.js";
|
|
16
|
+
var KIT_PACKAGES = [
|
|
17
|
+
"cli",
|
|
18
|
+
"doctor",
|
|
19
|
+
"ledger",
|
|
20
|
+
"lint-parity",
|
|
21
|
+
"oxlint-plugin-biological-architecture",
|
|
22
|
+
"ratchet",
|
|
23
|
+
"themekit",
|
|
24
|
+
"verify"
|
|
25
|
+
];
|
|
26
|
+
var write = (dir, file, body) => {
|
|
27
|
+
const path = join(dir, file);
|
|
28
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
29
|
+
writeFileSync(path, body);
|
|
30
|
+
};
|
|
31
|
+
var readJson = (path) => JSON.parse(readFileSync(path, "utf8"));
|
|
32
|
+
var writeScaffold = (answers) => {
|
|
33
|
+
if (existsSync(answers.dir) && readdirSync(answers.dir).length > 0) {
|
|
34
|
+
throw new Error(`${answers.dir} is not empty — nothing was written`);
|
|
35
|
+
}
|
|
36
|
+
const files = filesFor(answers);
|
|
37
|
+
mkdirSync(answers.dir, { recursive: true });
|
|
38
|
+
for (const [file, body] of Object.entries(files))
|
|
39
|
+
write(answers.dir, file, body);
|
|
40
|
+
return Object.keys(files).toSorted();
|
|
41
|
+
};
|
|
42
|
+
var run = (what, dir, command, args) => {
|
|
43
|
+
const done = spawnSync(command, args, { cwd: dir, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
44
|
+
return {
|
|
45
|
+
code: done.error === undefined ? done.status ?? 2 : 2,
|
|
46
|
+
output: `${done.stdout ?? ""}${done.stderr ?? ""}${done.error === undefined ? "" : done.error.message}`,
|
|
47
|
+
what
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
var binsOf = (dir) => {
|
|
51
|
+
const manifest = readJson(join(dir, "package.json"));
|
|
52
|
+
if (typeof manifest.bin === "string")
|
|
53
|
+
return [[manifest.name ?? "", manifest.bin]];
|
|
54
|
+
return Object.entries(manifest.bin ?? {});
|
|
55
|
+
};
|
|
56
|
+
var repoint = (path, at, owed) => {
|
|
57
|
+
if (!existsSync(path))
|
|
58
|
+
return [];
|
|
59
|
+
const manifest = readJson(path);
|
|
60
|
+
const moved = [];
|
|
61
|
+
for (const block of ["dependencies", "devDependencies"]) {
|
|
62
|
+
const declared = manifest[block];
|
|
63
|
+
if (declared === undefined)
|
|
64
|
+
continue;
|
|
65
|
+
for (const name of Object.keys(declared)) {
|
|
66
|
+
const from = at.get(name);
|
|
67
|
+
if (from === undefined)
|
|
68
|
+
continue;
|
|
69
|
+
declared[name] = `link:${from}`;
|
|
70
|
+
moved.push(name);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const name of owed) {
|
|
74
|
+
const from = at.get(name);
|
|
75
|
+
if (from === undefined || moved.includes(name))
|
|
76
|
+
continue;
|
|
77
|
+
manifest.dependencies = { ...manifest.dependencies, [name]: `link:${from}` };
|
|
78
|
+
moved.push(name);
|
|
79
|
+
}
|
|
80
|
+
if (moved.length > 0)
|
|
81
|
+
writeFileSync(path, `${JSON.stringify(manifest, undefined, 2)}
|
|
82
|
+
`);
|
|
83
|
+
return moved;
|
|
84
|
+
};
|
|
85
|
+
var linkKit = (answers, kit) => {
|
|
86
|
+
const modules = join(answers.dir, "node_modules");
|
|
87
|
+
const scope = join(modules, "@geonosis");
|
|
88
|
+
const bin = join(modules, ".bin");
|
|
89
|
+
mkdirSync(scope, { recursive: true });
|
|
90
|
+
mkdirSync(bin, { recursive: true });
|
|
91
|
+
const linked = [];
|
|
92
|
+
const at = new Map;
|
|
93
|
+
const manifestPath = join(answers.dir, "package.json");
|
|
94
|
+
const manifest = readJson(manifestPath);
|
|
95
|
+
for (const relative of [
|
|
96
|
+
...KIT_PACKAGES.map((name) => `packages/${name}`),
|
|
97
|
+
...floorDirectories(answers.domains)
|
|
98
|
+
]) {
|
|
99
|
+
const from = join(kit, relative);
|
|
100
|
+
if (!existsSync(from))
|
|
101
|
+
continue;
|
|
102
|
+
const declared = readJson(join(from, "package.json"));
|
|
103
|
+
if (declared.name === undefined)
|
|
104
|
+
continue;
|
|
105
|
+
const to = join(scope, declared.name.replace("@geonosis/", ""));
|
|
106
|
+
if (!existsSync(to))
|
|
107
|
+
symlinkSync(from, to);
|
|
108
|
+
for (const [called, entry] of binsOf(from)) {
|
|
109
|
+
const shim = join(bin, called);
|
|
110
|
+
writeFileSync(shim, `#!/bin/sh
|
|
111
|
+
exec node "${join(from, entry)}" "$@"
|
|
112
|
+
`);
|
|
113
|
+
chmodSync(shim, 493);
|
|
114
|
+
}
|
|
115
|
+
at.set(declared.name, from);
|
|
116
|
+
linked.push(relative);
|
|
117
|
+
}
|
|
118
|
+
const owned = new Set(workspacesOf(answers).flatMap((workspace) => repoint(join(answers.dir, workspace.dir, "package.json"), at, Object.keys(workspace.packages ?? {}))));
|
|
119
|
+
const rootDeclares = new Set(Object.keys(kitDependencies(answers)));
|
|
120
|
+
for (const [name, from] of at) {
|
|
121
|
+
if (!owned.has(name) && rootDeclares.has(name)) {
|
|
122
|
+
manifest.devDependencies = { ...manifest.devDependencies, [name]: `link:${from}` };
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
writeFileSync(manifestPath, `${JSON.stringify(manifest, undefined, 2)}
|
|
126
|
+
`);
|
|
127
|
+
return linked;
|
|
128
|
+
};
|
|
129
|
+
var installer = (answers) => answers.runtime === "bun" ? ["bun", ["install"]] : ["pnpm", ["install"]];
|
|
130
|
+
var finish = ({ answers, install }) => {
|
|
131
|
+
const steps = [];
|
|
132
|
+
if (install) {
|
|
133
|
+
const [command, args] = installer(answers);
|
|
134
|
+
const offline = answers.runtime === "pnpm" ? [...args, "--offline"] : args;
|
|
135
|
+
const first = run(`${command} install (offline)`, answers.dir, command, offline);
|
|
136
|
+
steps.push(first);
|
|
137
|
+
if (first.code !== 0) {
|
|
138
|
+
steps.push(run(`${command} install (network)`, answers.dir, command, args));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (answers.kit !== undefined) {
|
|
142
|
+
const linked = linkKit(answers, answers.kit);
|
|
143
|
+
steps.push({
|
|
144
|
+
code: 0,
|
|
145
|
+
output: `${linked.join(", ")}
|
|
146
|
+
`,
|
|
147
|
+
what: `link the kit from ${answers.kit}`
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
const ledger = join(answers.dir, "node_modules/.bin/geonosis-ledger");
|
|
151
|
+
if (existsSync(ledger)) {
|
|
152
|
+
for (const { target, write: into } of GRAPH_SYNCS) {
|
|
153
|
+
steps.push(run(`sync the edges table into ${into}`, answers.dir, ledger, [
|
|
154
|
+
"sync",
|
|
155
|
+
"--target",
|
|
156
|
+
target,
|
|
157
|
+
"--write",
|
|
158
|
+
into
|
|
159
|
+
]));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const oxfmt = join(answers.dir, "node_modules/.bin/oxfmt");
|
|
163
|
+
if (existsSync(oxfmt)) {
|
|
164
|
+
steps.push(run("format", answers.dir, oxfmt, ["--config", ".oxfmtrc.json", "."]));
|
|
165
|
+
}
|
|
166
|
+
return steps;
|
|
167
|
+
};
|
|
168
|
+
export {
|
|
169
|
+
KIT_PACKAGES,
|
|
170
|
+
finish,
|
|
171
|
+
linkKit,
|
|
172
|
+
writeScaffold
|
|
173
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-geonosis",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.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": [
|
|
@@ -35,16 +35,17 @@
|
|
|
35
35
|
"dist"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@geonosis/cli": "
|
|
38
|
+
"@geonosis/cli": "3.0.0"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
|
41
|
-
"node": ">=22"
|
|
41
|
+
"node": ">=22",
|
|
42
|
+
"bun": "^1.4.2"
|
|
42
43
|
},
|
|
43
44
|
"publishConfig": {
|
|
44
45
|
"access": "public"
|
|
45
46
|
},
|
|
46
47
|
"scripts": {
|
|
47
|
-
"build": "
|
|
48
|
+
"build": "bun ../../tooling/build-step.ts bun ../../tooling/bundle.ts",
|
|
48
49
|
"typecheck": "tsc --noEmit",
|
|
49
50
|
"lint": "oxlint --config ../../.oxlintrc.json .",
|
|
50
51
|
"test:unit": "vitest run --project unit --root ../.. --passWithNoTests \"$PWD\"",
|