@fairgarden/distribution 0.1.0-0.canary.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.
Files changed (82) hide show
  1. package/Readme.md +97 -0
  2. package/dist/add-module.d.ts +62 -0
  3. package/dist/add-module.d.ts.map +1 -0
  4. package/dist/add-module.js +279 -0
  5. package/dist/add-module.js.map +1 -0
  6. package/dist/calver.d.ts +65 -0
  7. package/dist/calver.d.ts.map +1 -0
  8. package/dist/calver.js +115 -0
  9. package/dist/calver.js.map +1 -0
  10. package/dist/canary.d.ts +58 -0
  11. package/dist/canary.d.ts.map +1 -0
  12. package/dist/canary.js +130 -0
  13. package/dist/canary.js.map +1 -0
  14. package/dist/changelog.d.ts +111 -0
  15. package/dist/changelog.d.ts.map +1 -0
  16. package/dist/changelog.js +285 -0
  17. package/dist/changelog.js.map +1 -0
  18. package/dist/cli.d.ts +3 -0
  19. package/dist/cli.d.ts.map +1 -0
  20. package/dist/cli.js +1076 -0
  21. package/dist/cli.js.map +1 -0
  22. package/dist/config-edit.d.ts +18 -0
  23. package/dist/config-edit.d.ts.map +1 -0
  24. package/dist/config-edit.js +166 -0
  25. package/dist/config-edit.js.map +1 -0
  26. package/dist/extends.d.ts +60 -0
  27. package/dist/extends.d.ts.map +1 -0
  28. package/dist/extends.js +173 -0
  29. package/dist/extends.js.map +1 -0
  30. package/dist/extract.d.ts +31 -0
  31. package/dist/extract.d.ts.map +1 -0
  32. package/dist/extract.js +177 -0
  33. package/dist/extract.js.map +1 -0
  34. package/dist/forks.d.ts +116 -0
  35. package/dist/forks.d.ts.map +1 -0
  36. package/dist/forks.js +357 -0
  37. package/dist/forks.js.map +1 -0
  38. package/dist/git-url.d.ts +19 -0
  39. package/dist/git-url.d.ts.map +1 -0
  40. package/dist/git-url.js +55 -0
  41. package/dist/git-url.js.map +1 -0
  42. package/dist/index.d.ts +21 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +11 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/inherit.d.ts +18 -0
  47. package/dist/inherit.d.ts.map +1 -0
  48. package/dist/inherit.js +84 -0
  49. package/dist/inherit.js.map +1 -0
  50. package/dist/manifest.d.ts +43 -0
  51. package/dist/manifest.d.ts.map +1 -0
  52. package/dist/manifest.js +64 -0
  53. package/dist/manifest.js.map +1 -0
  54. package/dist/overrides.d.ts +32 -0
  55. package/dist/overrides.d.ts.map +1 -0
  56. package/dist/overrides.js +81 -0
  57. package/dist/overrides.js.map +1 -0
  58. package/dist/policy.d.ts +86 -0
  59. package/dist/policy.d.ts.map +1 -0
  60. package/dist/policy.js +269 -0
  61. package/dist/policy.js.map +1 -0
  62. package/dist/readme.d.ts +60 -0
  63. package/dist/readme.d.ts.map +1 -0
  64. package/dist/readme.js +304 -0
  65. package/dist/readme.js.map +1 -0
  66. package/dist/release.d.ts +164 -0
  67. package/dist/release.d.ts.map +1 -0
  68. package/dist/release.js +454 -0
  69. package/dist/release.js.map +1 -0
  70. package/dist/scaffold.d.ts +45 -0
  71. package/dist/scaffold.d.ts.map +1 -0
  72. package/dist/scaffold.js +492 -0
  73. package/dist/scaffold.js.map +1 -0
  74. package/dist/submodules.d.ts +144 -0
  75. package/dist/submodules.d.ts.map +1 -0
  76. package/dist/submodules.js +315 -0
  77. package/dist/submodules.js.map +1 -0
  78. package/dist/workflows.d.ts +102 -0
  79. package/dist/workflows.d.ts.map +1 -0
  80. package/dist/workflows.js +710 -0
  81. package/dist/workflows.js.map +1 -0
  82. package/package.json +54 -0
package/dist/policy.js ADDED
@@ -0,0 +1,269 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { createHash } from 'node:crypto';
3
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
4
+ import { createRequire } from 'node:module';
5
+ import path from 'node:path';
6
+ /**
7
+ * A distribution's policy: the organization's rules for every service it
8
+ * ships, kept in `policies/` beside `apps/` and `packages/`, and marked by the
9
+ * `.manifest` that says whose they are.
10
+ *
11
+ * It is built in layers — each module's own `policies/`, then the policy of
12
+ * any distribution this one extends, then its own — into one bundle, named
13
+ * after the distribution's version and what is in it. `policies/` is a
14
+ * workspace package, so turbo builds it once, and every service's build
15
+ * takes a copy (`fg-dist policy use`) to run. Upgrading a module brings its
16
+ * rules; the organization's stay as they were.
17
+ *
18
+ * Compiling is `@fairgarden/policy`'s: this finds the layers and hands them
19
+ * to whichever `fg-policy` the distribution installed.
20
+ */
21
+ export const POLICIES = 'policies';
22
+ /** Where the bundle is built, inside `policies/`. */
23
+ export const BUILT = path.join('dist', 'policies.tar.gz');
24
+ /** Where a service runs it from, inside the service; `@fairgarden/policy` looks there. */
25
+ export const USED = path.join('.policy', 'policies.tar.gz');
26
+ /** What marks a project as running the policy, in its build script. */
27
+ const USES = 'fg-dist policy use';
28
+ const readJson = (file) => JSON.parse(readFileSync(file, 'utf8'));
29
+ /** A `policies/` with a `.manifest` is an organization's; a module's own has none. */
30
+ const isOrganizations = (dir) => existsSync(path.join(dir, '.manifest'));
31
+ /** The published policy of what `root` extends, and of what that extends, the furthest first. */
32
+ const parentsOf = (root, seen = new Set()) => {
33
+ const parent = readJson(path.join(root, 'package.json')).distribution?.extends;
34
+ if (!parent || seen.has(parent))
35
+ return [];
36
+ seen.add(parent);
37
+ let parentRoot;
38
+ try {
39
+ parentRoot = path.dirname(createRequire(path.join(root, 'package.json')).resolve(`${parent}/package.json`));
40
+ }
41
+ catch {
42
+ // Not installed: `fg-dist check` says so; there is nothing to build on.
43
+ return [];
44
+ }
45
+ const dir = path.join(parentRoot, POLICIES);
46
+ return [...parentsOf(parentRoot, seen), ...(isOrganizations(dir) ? [dir] : [])];
47
+ };
48
+ const describe = (root) => {
49
+ const bases = [];
50
+ for (const group of ['apps', 'packages']) {
51
+ const entries = existsSync(path.join(root, group)) ? readdirSync(path.join(root, group)).sort() : [];
52
+ for (const name of entries) {
53
+ const dir = path.join(root, group, name, POLICIES);
54
+ if (existsSync(dir) && statSync(dir).isDirectory() && !isOrganizations(dir))
55
+ bases.push(dir);
56
+ }
57
+ }
58
+ const dir = path.join(root, POLICIES);
59
+ return {
60
+ root,
61
+ dir,
62
+ version: readJson(path.join(root, 'package.json')).version,
63
+ bases,
64
+ parents: parentsOf(root),
65
+ built: path.join(dir, BUILT),
66
+ };
67
+ };
68
+ /**
69
+ * The distribution around `from`, if it has a policy: the nearest directory
70
+ * above with an organization's `policies/`. Found on disk rather than by
71
+ * git, which a build host may not have, and which stops at a submodule.
72
+ */
73
+ export const findPolicy = (from) => {
74
+ for (let dir = path.resolve(from);; dir = path.dirname(dir)) {
75
+ if (path.basename(dir) === POLICIES && isOrganizations(dir))
76
+ return describe(path.dirname(dir));
77
+ if (isOrganizations(path.join(dir, POLICIES)))
78
+ return describe(dir);
79
+ if (path.dirname(dir) === dir)
80
+ return undefined;
81
+ }
82
+ };
83
+ /** Where a package is installed, seen from `base`, whether or not it exports its package.json. */
84
+ const packageDir = (base, name) => {
85
+ const require = createRequire(path.join(base, 'package.json'));
86
+ try {
87
+ return path.dirname(require.resolve(`${name}/package.json`));
88
+ }
89
+ catch {
90
+ // Not exported: find it from its entry point instead.
91
+ }
92
+ try {
93
+ for (let dir = path.dirname(require.resolve(name)); path.dirname(dir) !== dir; dir = path.dirname(dir)) {
94
+ const file = path.join(dir, 'package.json');
95
+ if (existsSync(file) && readJson(file).name === name)
96
+ return dir;
97
+ }
98
+ }
99
+ catch {
100
+ // Not installed here.
101
+ }
102
+ return undefined;
103
+ };
104
+ /** The `fg-policy` the distribution installed, from `from` or its root. */
105
+ const fgPolicy = (policy, from) => {
106
+ for (const base of [from, policy.dir, policy.root]) {
107
+ const dir = packageDir(base, '@fairgarden/policy');
108
+ if (!dir)
109
+ continue;
110
+ const { bin } = readJson(path.join(dir, 'package.json'));
111
+ return path.join(dir, bin['fg-policy']);
112
+ }
113
+ throw new Error('@fairgarden/policy is not installed here; add it to policies/package.json.');
114
+ };
115
+ const layerArgs = (policy) => {
116
+ const relative = (dir) => path.relative(policy.root, dir);
117
+ return [
118
+ '--dir',
119
+ relative(policy.dir),
120
+ ...policy.bases.flatMap((dir) => ['--base', relative(dir)]),
121
+ ...policy.parents.flatMap((dir) => ['--parent', relative(dir)]),
122
+ ];
123
+ };
124
+ const run = (policy, from, args) => {
125
+ const result = spawnSync(process.execPath, [fgPolicy(policy, from), ...args], { cwd: policy.root, stdio: 'inherit' });
126
+ if (result.error)
127
+ throw result.error;
128
+ return result.status ?? 1;
129
+ };
130
+ /**
131
+ * What the bundle is built from, as a digest: every rule, setting and
132
+ * manifest in every layer, and the version it is named after. A bundle built
133
+ * from anything else — a rule since changed, added or deleted — is out of date.
134
+ */
135
+ export const inputsDigest = (policy) => {
136
+ const hash = createHash('sha256').update(`${policy.version ?? ''}\0`);
137
+ for (const dir of [...policy.bases, ...policy.parents, policy.dir]) {
138
+ const files = readdirSync(dir, { recursive: true, encoding: 'utf8' })
139
+ .filter((file) => !file.split(path.sep).some((part) => part === 'node_modules' || part === 'dist'))
140
+ .filter((file) => /(\.rego|data\.json|\.manifest)$/.test(file))
141
+ .sort();
142
+ for (const file of files) {
143
+ hash.update(`${path.relative(policy.root, path.join(dir, file)).split(path.sep).join('/')}\0`);
144
+ hash.update(readFileSync(path.join(dir, file))).update('\0');
145
+ }
146
+ }
147
+ return hash.digest('hex');
148
+ };
149
+ /** Beside the bundle: the digest of what it was built from. */
150
+ const inputsFile = (built) => `${built}.inputs`;
151
+ /** Build the bundle, named after the distribution's version. */
152
+ export const buildPolicy = (policy, from = policy.root, out = policy.built) => {
153
+ const digest = inputsDigest(policy);
154
+ const status = run(policy, from, [
155
+ 'build',
156
+ ...layerArgs(policy),
157
+ ...(policy.version ? ['--release', policy.version] : []),
158
+ '--out',
159
+ out,
160
+ ]);
161
+ if (status === 0)
162
+ writeFileSync(inputsFile(out), `${digest}\n`);
163
+ return status;
164
+ };
165
+ /** Each module's tests on its own rules, then the organization's on them all. */
166
+ export const testPolicy = (policy, from = policy.root) => run(policy, from, ['test', ...layerArgs(policy)]);
167
+ /**
168
+ * Put the distribution's policy beside the service in `project`, for it to
169
+ * run: the bundle turbo already built, or one built now if there is none, or
170
+ * it was built from anything other than the rules as they are. Outside a
171
+ * distribution, nothing — and no copy left from before: the service's
172
+ * built-in rules decide.
173
+ */
174
+ export const usePolicy = (project) => {
175
+ const used = path.join(project, USED);
176
+ const policy = findPolicy(project);
177
+ if (!policy) {
178
+ rmSync(used, { force: true });
179
+ return { status: 0 };
180
+ }
181
+ const built = existsSync(inputsFile(policy.built)) ? readFileSync(inputsFile(policy.built), 'utf8').trim() : undefined;
182
+ if (!existsSync(policy.built) || built !== inputsDigest(policy)) {
183
+ const status = buildPolicy(policy, project);
184
+ if (status !== 0)
185
+ return { policy, status };
186
+ }
187
+ mkdirSync(path.dirname(used), { recursive: true });
188
+ // Whole or not at all, for a server reading it meanwhile.
189
+ const partial = `${used}.${process.pid}.partial`;
190
+ copyFileSync(policy.built, partial);
191
+ renameSync(partial, used);
192
+ return { policy, status: 0 };
193
+ };
194
+ /** `list` with `entry` at the end, unless it has it already. */
195
+ const including = (list, entry) => list?.includes(entry) ? list : [...(list ?? []), entry];
196
+ /**
197
+ * What turbo needs, so the policy is built once however many services run
198
+ * it: the policy's own tasks, which read every module's rules as well as its
199
+ * own, and each project whose build uses it waiting for it to be built.
200
+ *
201
+ * A project's task is the generic one plus the policy, unless `existing`
202
+ * already has one for it: that is kept as it is — a monolith's waits for its
203
+ * apps' libraries rather than their builds, say — and only gains the policy.
204
+ */
205
+ export const turboTasks = (policy, generic, genericDev = { persistent: true, cache: false }, existing = {}) => {
206
+ const name = readJson(path.join(policy.dir, 'package.json')).name;
207
+ if (!name)
208
+ throw new Error(`${path.join(POLICIES, 'package.json')} needs a name.`);
209
+ const inputs = [
210
+ '$TURBO_DEFAULT$',
211
+ '$TURBO_ROOT$/apps/*/policies/**',
212
+ '$TURBO_ROOT$/packages/*/policies/**',
213
+ // The distribution's version names every revision.
214
+ '$TURBO_ROOT$/package.json',
215
+ // What it extends is installed, so its policy changes with the lockfile.
216
+ ...(policy.parents.length > 0 ? ['$TURBO_ROOT$/pnpm-lock.yaml'] : []),
217
+ ];
218
+ // Not hashed: which opa compiles it changes nothing, and the commit is only
219
+ // where a revision was first built — the same rules are the same revision.
220
+ // A signing key is hashed, so adding one does not bring back an unsigned build.
221
+ const passThroughEnv = ['OPA', 'VERCEL_GIT_COMMIT_SHA', 'GITHUB_SHA'];
222
+ const tasks = {
223
+ [`${name}#build`]: { dependsOn: ['^build'], inputs, outputs: ['dist/**'], env: ['FG_POLICY_SIGNING_KEY'], passThroughEnv },
224
+ [`${name}#test`]: { dependsOn: ['^build'], inputs, passThroughEnv: ['OPA'] },
225
+ };
226
+ for (const group of ['apps', 'packages']) {
227
+ const entries = existsSync(path.join(policy.root, group)) ? readdirSync(path.join(policy.root, group)).sort() : [];
228
+ for (const entry of entries) {
229
+ const file = path.join(policy.root, group, entry, 'package.json');
230
+ if (!existsSync(file))
231
+ continue;
232
+ const pkg = readJson(file);
233
+ if (!pkg.name)
234
+ continue;
235
+ if (pkg.scripts?.build?.includes(USES)) {
236
+ const base = existing[`${pkg.name}#build`] ?? generic;
237
+ tasks[`${pkg.name}#build`] = {
238
+ ...base,
239
+ dependsOn: including(base.dependsOn, `${name}#build`),
240
+ // The copy it runs is part of what it built: a cached build brings it back.
241
+ outputs: including(base.outputs, `${path.dirname(USED)}/**`),
242
+ };
243
+ }
244
+ // A fresh checkout has built neither the policy nor the tools that build it.
245
+ if (pkg.scripts?.dev?.includes(USES)) {
246
+ const base = existing[`${pkg.name}#dev`] ?? genericDev;
247
+ tasks[`${pkg.name}#dev`] = { ...base, dependsOn: including(base.dependsOn, `${name}#build`) };
248
+ }
249
+ }
250
+ }
251
+ return tasks;
252
+ };
253
+ /**
254
+ * Write the policy's tasks into turbo.json, or with `check`, say which are
255
+ * missing or out of date. Returns what differed.
256
+ */
257
+ export const setupTurbo = (policy, { check = false } = {}) => {
258
+ const file = path.join(policy.root, 'turbo.json');
259
+ const turbo = readJson(file);
260
+ const tasks = turbo.tasks ?? {};
261
+ const wanted = turboTasks(policy, tasks.build ?? { dependsOn: ['^build'] }, tasks.dev, tasks);
262
+ const differ = Object.keys(wanted).filter((key) => JSON.stringify(tasks[key]) !== JSON.stringify(wanted[key]));
263
+ if (!check && differ.length > 0) {
264
+ turbo.tasks = { ...tasks, ...wanted };
265
+ writeFileSync(file, `${JSON.stringify(turbo, null, 2)}\n`);
266
+ }
267
+ return differ;
268
+ };
269
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Replace the block a marker delimits, or add one.
3
+ *
4
+ * A new block goes after the first heading, which is where someone looking for
5
+ * a version would look first.
6
+ */
7
+ export declare const updateSection: (source: string, marker: string, body: string,
8
+ /**
9
+ * Marker whose block this one belongs under, when it has to be inserted.
10
+ * Without it a new block goes straight after the first heading, which means
11
+ * whichever block is written last ends up on top.
12
+ */
13
+ after?: string) => string;
14
+ export interface ModuleVersion {
15
+ name: string;
16
+ /** The version its own package.json declares. */
17
+ version: string;
18
+ relativePath: string;
19
+ /** The commit the distribution pins. */
20
+ pinned: string;
21
+ /** The version tag at that commit, or the newest one behind it. */
22
+ tag: string | undefined;
23
+ /** Whether the tag is the pinned commit rather than an ancestor of it. */
24
+ released: boolean;
25
+ /** Where the repository can be browsed, when it has a web address. */
26
+ url: string | undefined;
27
+ /** For a fork, where the repository it was forked from can be browsed, or its url. */
28
+ upstream: string | undefined;
29
+ /** What the module is, from its own package.json. */
30
+ description: string | undefined;
31
+ }
32
+ /** What a distribution ships, read from the checkouts. */
33
+ export declare const moduleVersions: (root: string) => Promise<ModuleVersion[]>;
34
+ /**
35
+ * How to release this module, written into the module's own readme.
36
+ *
37
+ * The same words in every module, with this one's version and branch names
38
+ * filled in — so "how do I release this" is answered where someone is already
39
+ * standing, rather than in a document they have to know exists.
40
+ */
41
+ export declare const releasingSection: (name: string, version: string) => string;
42
+ export interface ReadmeUpdate {
43
+ file: string;
44
+ changed: boolean;
45
+ }
46
+ export interface ReadmeResult {
47
+ updates: ReadmeUpdate[];
48
+ modules: ModuleVersion[];
49
+ /** What was found here: a distribution listing modules, or a module itself. */
50
+ kind: 'distribution' | 'module';
51
+ }
52
+ /**
53
+ * Write the versions into the distribution's readme and each module's.
54
+ *
55
+ * Idempotent: running it again when nothing has moved rewrites nothing.
56
+ */
57
+ export declare const writeReadmes: (root: string, { check }?: {
58
+ check?: boolean;
59
+ }) => Promise<ReadmeResult>;
60
+ //# sourceMappingURL=readme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readme.d.ts","sourceRoot":"","sources":["../src/readme.ts"],"names":[],"mappings":"AA2CA;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,EACd,QAAQ,MAAM,EACd,MAAM,MAAM;AACZ;;;;GAIG;AACH,QAAQ,MAAM,KACb,MA2BF,CAAA;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,MAAM,CAAA;IACpB,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAA;IACd,mEAAmE;IACnE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;IACvB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAA;IACjB,sEAAsE;IACtE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;IACvB,sFAAsF;IACtF,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B,qDAAqD;IACrD,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;CAChC;AAqCD,0DAA0D;AAC1D,eAAO,MAAM,cAAc,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,aAAa,EAAE,CAS1E,CAAA;AAuED;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,EAAE,SAAS,MAAM,KAAG,MA4DhE,CAAA;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,OAAO,EAAE,aAAa,EAAE,CAAA;IACxB,+EAA+E;IAC/E,IAAI,EAAE,cAAc,GAAG,QAAQ,CAAA;CAChC;AA8BD;;;;GAIG;AACH,eAAO,MAAM,YAAY,GACvB,MAAM,MAAM,EACZ,YAAmB;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,KAC1C,OAAO,CAAC,YAAY,CA6FtB,CAAA"}
package/dist/readme.js ADDED
@@ -0,0 +1,304 @@
1
+ import { readFile, readdir, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import semver from 'semver';
4
+ import { fromMarkdown } from 'mdast-util-from-markdown';
5
+ import { inspect, isDistribution, submodules, uninitialised, } from "./submodules.js";
6
+ import { readDistribution } from "./extends.js";
7
+ import { toHttps } from "./git-url.js";
8
+ const open = (marker) => `<!-- fg:${marker} -->`;
9
+ const close = (marker) => `<!-- /fg:${marker} -->`;
10
+ const offsets = (node) => [
11
+ node.position?.start.offset ?? 0,
12
+ node.position?.end.offset ?? 0,
13
+ ];
14
+ /**
15
+ * Replace the block a marker delimits, or add one.
16
+ *
17
+ * A new block goes after the first heading, which is where someone looking for
18
+ * a version would look first.
19
+ */
20
+ export const updateSection = (source, marker, body,
21
+ /**
22
+ * Marker whose block this one belongs under, when it has to be inserted.
23
+ * Without it a new block goes straight after the first heading, which means
24
+ * whichever block is written last ends up on top.
25
+ */
26
+ after) => {
27
+ const tree = fromMarkdown(source);
28
+ const comments = tree.children.filter((node) => node.type === 'html' && typeof node.value === 'string');
29
+ const start = comments.find((node) => node.value?.trim() === open(marker));
30
+ const end = comments.find((node) => node.value?.trim() === close(marker));
31
+ if (start && end) {
32
+ const [, from] = offsets(start);
33
+ const [to] = offsets(end);
34
+ return `${source.slice(0, from)}\n\n${body}\n\n${source.slice(to)}`;
35
+ }
36
+ const block = `${open(marker)}\n\n${body}\n\n${close(marker)}`;
37
+ // Under the block it belongs to if that is already here, otherwise under the
38
+ // heading — the top of the file is where a reader starts.
39
+ const anchor = (after && comments.find((node) => node.value?.trim() === close(after))) ??
40
+ tree.children.find((node) => node.type === 'heading');
41
+ if (!anchor)
42
+ return `${block}\n\n${source}`;
43
+ const [, at] = offsets(anchor);
44
+ return `${source.slice(0, at)}\n\n${block}${source.slice(at)}`;
45
+ };
46
+ /**
47
+ * A remote as somewhere to browse, or undefined when there is nowhere.
48
+ *
49
+ * A local path is a real remote and has no web address, so linking to one
50
+ * would be worse than not linking at all.
51
+ */
52
+ const browsableAt = (remote) => {
53
+ const https = toHttps(remote);
54
+ if (!/^https?:\/\//i.test(https))
55
+ return undefined;
56
+ return https.replace(/\.git$/, '');
57
+ };
58
+ const link = (text, href) => href ? `[${text}](${href})` : text;
59
+ /**
60
+ * What the distribution is actually pinned at.
61
+ *
62
+ * A tagged commit is a released version and links to the tag. Anything else is
63
+ * a commit after the last release, which is worth saying: the version in the
64
+ * manifest is not what is being shipped.
65
+ */
66
+ const pinnedAs = (module) => {
67
+ const short = module.pinned.slice(0, 7);
68
+ if (module.released && module.tag) {
69
+ return link(module.tag, module.url && `${module.url}/tree/${module.tag}`);
70
+ }
71
+ const commit = link(short, module.url && `${module.url}/commit/${module.pinned}`);
72
+ return module.tag
73
+ ? `${commit} — unreleased, after ${module.tag}`
74
+ : `${commit} — untagged`;
75
+ };
76
+ /** What a distribution ships, read from the checkouts. */
77
+ export const moduleVersions = async (root) => {
78
+ const found = [];
79
+ for (const submodule of submodules(root)) {
80
+ const manifest = await readManifest(submodule);
81
+ if (manifest)
82
+ found.push(manifest);
83
+ }
84
+ return found.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
85
+ };
86
+ const readManifest = async (submodule) => {
87
+ try {
88
+ const pkg = JSON.parse(await readFile(path.join(submodule.path, 'package.json'), 'utf8'));
89
+ if (!pkg.name || !pkg.version)
90
+ return undefined;
91
+ // Never fetches: this reads what is checked out, and writing a readme
92
+ // should not depend on the network.
93
+ const state = inspect(submodule, { fetch: false });
94
+ return {
95
+ name: pkg.name,
96
+ version: pkg.version,
97
+ description: typeof pkg.description === 'string' ? pkg.description : undefined,
98
+ relativePath: submodule.relativePath,
99
+ pinned: state.head,
100
+ tag: state.current,
101
+ released: state.exact,
102
+ url: browsableAt(submodule.url),
103
+ upstream: submodule.upstream && (browsableAt(submodule.upstream) ?? submodule.upstream),
104
+ };
105
+ }
106
+ catch {
107
+ return undefined;
108
+ }
109
+ };
110
+ const modulesTable = (modules) => {
111
+ const rows = [
112
+ '| Module | Version | Pinned at | Path |',
113
+ '| --- | --- | --- | --- |',
114
+ ...modules.map((module) => {
115
+ // Linked by name, so the table is also the index of where each lives.
116
+ const name = link(`\`${module.name}\``, module.url);
117
+ // An empty cell is honest: it shows which modules have yet to say what
118
+ // they are, rather than hiding it.
119
+ const what = module.description ? `<br>${module.description}` : '';
120
+ // A fork is shipped from somewhere other than where the module lives.
121
+ const fork = module.upstream
122
+ ? `<br>Fork of ${/^https?:/.test(module.upstream) ? link(module.upstream.replace(/^https?:\/\//, ''), module.upstream) : module.upstream}`
123
+ : '';
124
+ return `| ${name}${what}${fork} | ${module.version} | ${pinnedAs(module)} | \`${module.relativePath}\` |`;
125
+ }),
126
+ ];
127
+ // Only worth explaining when something is actually unreleased.
128
+ if (modules.some((module) => !module.released)) {
129
+ rows.push('', 'A module pinned at a commit rather than a tag is being shipped ahead of', 'its last release, so its stated version is not what is deployed.');
130
+ }
131
+ return rows.join('\n');
132
+ };
133
+ /**
134
+ * A module states its own version and nothing else.
135
+ *
136
+ * Naming a distribution here would be a claim the module cannot make: the same
137
+ * module is shipped in several, which is the point of extending one. The
138
+ * distribution's readme is where the relationship belongs.
139
+ */
140
+ const versionLine = (module) => `Version **${module.version}**`;
141
+ /**
142
+ * How to release this module, written into the module's own readme.
143
+ *
144
+ * The same words in every module, with this one's version and branch names
145
+ * filled in — so "how do I release this" is answered where someone is already
146
+ * standing, rather than in a document they have to know exists.
147
+ */
148
+ export const releasingSection = (name, version) => {
149
+ const parsed = semver.parse(version);
150
+ // A prerelease has no line to maintain — there is no 2.0.0-alpha.0 that
151
+ // someone is still running and needs a fix for — so `release` only moves it
152
+ // forward, and saying otherwise here would contradict the command.
153
+ const prerelease = (parsed?.prerelease.length ?? 0) > 0;
154
+ const after = (bump) => (parsed && semver.inc(version, bump)) ?? '…';
155
+ const moveOn = prerelease
156
+ ? [
157
+ '2. **Move it on.** `pnpm release` — opens a pull request bumping this branch',
158
+ ` to \`${(parsed && semver.inc(version, 'prerelease')) ?? '…'}\`, or \`pnpm release --id rc\` to change`,
159
+ ' identifier. A prerelease gets no maintenance branch; there is no released',
160
+ ' line behind it yet.',
161
+ ]
162
+ : [
163
+ '2. **Decide which way main moves on.** `pnpm release` on its own prints the',
164
+ ' three and stops — it will not choose for you:',
165
+ '',
166
+ ' | | Next | Leaves behind |',
167
+ ' | --- | --- | --- |',
168
+ ` | \`pnpm release --patch\` | \`${after('patch')}\` | nothing; this branch is the line |`,
169
+ ` | \`pnpm release --minor\` | \`${after('minor')}\` | \`v${parsed?.major ?? 'x'}-${parsed?.minor ?? 'y'}\` at \`${after('patch')}\` |`,
170
+ ` | \`pnpm release --major\` | \`${after('major')}\` | \`v${parsed?.major ?? 'x'}-${parsed?.minor ?? 'y'}\` at \`${after('patch')}\` |`,
171
+ '',
172
+ ' The branch it leaves behind is where fixes to what you just released go.',
173
+ '3. **Fixing an older release.** Land it on main first, cherry-pick it onto that',
174
+ " release's `v<major>-<minor>` branch, then publish from there under its own",
175
+ ' dist tag — never as `latest` unless that line is still the newest.',
176
+ ];
177
+ const early = prerelease
178
+ ? []
179
+ : [
180
+ '',
181
+ '### Starting the next line early',
182
+ '',
183
+ `\`pnpm exec fg-dist prerelease --major\` cuts \`v${(parsed?.major ?? 0) + 1}\` at`,
184
+ `\`${(parsed && semver.inc(version, 'premajor', 'alpha')) ?? '…'}\` and leaves main exactly where it is, so the next`,
185
+ 'line can be worked on while this one goes on shipping. Release from that branch',
186
+ 'under a dist tag of its own — `next`, say — so `latest` goes on meaning the line',
187
+ 'main is shipping. `--minor` does the same for the next minor.',
188
+ ];
189
+ return [
190
+ '## Releasing',
191
+ '',
192
+ `This module releases on its own. \`${version}\` is what main is working towards,`,
193
+ 'not what is published — the version here is always the next one.',
194
+ '',
195
+ '1. **Publish it.** Run the *Publish* workflow from the Actions tab, picking the',
196
+ ' dist tag. It refuses if that version is already on npm.',
197
+ ...moveOn,
198
+ ...early,
199
+ '',
200
+ `Every push to main publishes \`${name}@canary\`. A canary is not a release and`,
201
+ 'carries no promise; it is there so main can be tried without a checkout.',
202
+ ].join('\n');
203
+ };
204
+ /** The name and version of the repository this is run in. */
205
+ const ownManifest = async (root) => {
206
+ try {
207
+ const pkg = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8'));
208
+ if (!pkg.name || !pkg.version)
209
+ return undefined;
210
+ return { name: pkg.name, version: pkg.version };
211
+ }
212
+ catch {
213
+ return undefined;
214
+ }
215
+ };
216
+ /**
217
+ * The readme this repository has, or what to call one if it has none.
218
+ *
219
+ * Hard-coding `Readme.md` writes a second file next to an existing `README.md`
220
+ * on Linux and edits the real one on macOS, so the same command passes locally
221
+ * and fails in CI.
222
+ */
223
+ const readmeIn = async (dir) => {
224
+ const found = (await readdir(dir).catch(() => []))
225
+ .filter((entry) => /^readme\.md$/i.test(entry))
226
+ .sort();
227
+ return path.join(dir, found[0] ?? 'Readme.md');
228
+ };
229
+ /**
230
+ * Write the versions into the distribution's readme and each module's.
231
+ *
232
+ * Idempotent: running it again when nothing has moved rewrites nothing.
233
+ */
234
+ export const writeReadmes = async (root, { check = false } = {}) => {
235
+ const modules = await moduleVersions(root);
236
+ // Read from .gitmodules rather than from the checkouts: a clone made without
237
+ // --recurse-submodules has none of them, and calling that a module would
238
+ // write a module's readme over the distribution's.
239
+ const kind = isDistribution(root) ? 'distribution' : 'module';
240
+ if (kind === 'distribution') {
241
+ const absent = uninitialised(root);
242
+ if (absent.length > 0) {
243
+ throw new Error(`These submodules are not checked out, so their versions cannot be read: ` +
244
+ `${absent.join(', ')}.\nRun \`git submodule update --init\` first.`);
245
+ }
246
+ }
247
+ const planned = [];
248
+ if (kind === 'module') {
249
+ const own = await ownManifest(root);
250
+ if (!own) {
251
+ throw new Error(`${root} has no submodules and no package.json version, so there is ` +
252
+ 'nothing to record.');
253
+ }
254
+ const file = await readmeIn(root);
255
+ planned.push({ file, marker: 'version', body: `Version **${own.version}**`, heading: own.name }, {
256
+ file,
257
+ marker: 'releasing',
258
+ body: releasingSection(own.name, own.version),
259
+ heading: own.name,
260
+ after: 'version',
261
+ });
262
+ }
263
+ else {
264
+ const distribution = readDistribution(root);
265
+ planned.push({
266
+ file: await readmeIn(root),
267
+ marker: 'modules',
268
+ body: modulesTable(modules),
269
+ heading: distribution.name,
270
+ });
271
+ for (const module of modules) {
272
+ const file = await readmeIn(path.join(root, module.relativePath));
273
+ planned.push({ file, marker: 'version', body: versionLine(module), heading: module.name }, {
274
+ file,
275
+ marker: 'releasing',
276
+ body: releasingSection(module.name, module.version),
277
+ heading: module.name,
278
+ after: 'version',
279
+ });
280
+ }
281
+ }
282
+ // Grouped, because a file carries more than one block and reporting it once
283
+ // per block doubles both the listing and the count of what changed.
284
+ const byFile = new Map();
285
+ for (const entry of planned) {
286
+ byFile.set(entry.file, [...(byFile.get(entry.file) ?? []), entry]);
287
+ }
288
+ const updates = [];
289
+ for (const [file, blocks] of byFile) {
290
+ const original = await readFile(file, 'utf8').catch(() => undefined);
291
+ // Applied in order to the same text: a block that has to be inserted is
292
+ // placed relative to the ones before it.
293
+ let updated = original ?? `# ${blocks[0].heading}\n`;
294
+ for (const { marker, body, after } of blocks) {
295
+ updated = updateSection(updated, marker, body, after);
296
+ }
297
+ const changed = updated !== original;
298
+ if (changed && !check)
299
+ await writeFile(file, updated);
300
+ updates.push({ file, changed });
301
+ }
302
+ return { updates, modules, kind };
303
+ };
304
+ //# sourceMappingURL=readme.js.map