@fairgarden/distribution 0.1.0-alpha.3 → 0.1.0-alpha.5

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Connor Davis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/Readme.md CHANGED
@@ -2,29 +2,10 @@
2
2
 
3
3
  <!-- fg:version -->
4
4
 
5
- Version **0.1.0-alpha.3**
5
+ Version **0.1.0-alpha.5**
6
6
 
7
7
  <!-- /fg:version -->
8
8
 
9
- <!-- fg:releasing -->
10
-
11
- ## Releasing
12
-
13
- This module releases on its own. `0.1.0-alpha.3` is what main is working towards,
14
- not what is published — the version here is always the next one.
15
-
16
- 1. **Publish it.** Run the *Publish* workflow from the Actions tab, picking the
17
- dist tag. It refuses if that version is already on npm.
18
- 2. **Move it on.** `pnpm release` — opens a pull request bumping this branch
19
- to `0.1.0-alpha.4`, or `pnpm release --id rc` to change
20
- identifier. A prerelease gets no maintenance branch; there is no released
21
- line behind it yet.
22
-
23
- Every push to main publishes `@fairgarden/distribution@canary`. A canary is not a release and
24
- carries no promise; it is there so main can be tried without a checkout.
25
-
26
- <!-- /fg:releasing -->
27
-
28
9
  Ship a set of versioned modules together.
29
10
 
30
11
  End users do not care about semver — they want to know how old their copy is,
@@ -95,3 +76,30 @@ fg-dist prerelease --major # start the next line on a branch, leaving main alo
95
76
 
96
77
  Mounting a distribution's apps into one Next deployment is a separate concern,
97
78
  handled by `@fairgarden/monolith`.
79
+
80
+ <!-- fg:releasing -->
81
+
82
+ ## Releasing
83
+
84
+ This module releases on its own. `0.1.0-alpha.5` is what main is working towards,
85
+ not what is published — the version here is always the next one. Its release
86
+ notes are the top section of `CHANGELOG.md`, where every pull request adds a
87
+ line linking itself.
88
+
89
+ 1. **Publish it.** Run the *Publish* workflow from the Actions tab, picking the
90
+ dist tag. It refuses if that version is already on npm. Once it is out, open
91
+ pull requests are held — their changelog check fails — so nothing is noted
92
+ under a version that has already shipped.
93
+ 2. **Move it on.** `pnpm release` opens a pull request moving main to
94
+ `0.1.0-alpha.6` and starting its section of the changelog, or
95
+ `pnpm release --id rc` to change identifier. Merging it lifts the hold. A
96
+ prerelease gets no maintenance branch; there is no released line behind it
97
+ yet.
98
+
99
+ A held pull request goes on once it is brought up to date with main and its
100
+ line is moved into the new version's section.
101
+
102
+ Every push to main publishes `@fairgarden/distribution@canary`. A canary is not a release and
103
+ carries no promise; it is there so main can be tried without a checkout.
104
+
105
+ <!-- /fg:releasing -->
package/dist/cli.js CHANGED
@@ -5,7 +5,8 @@ import { mkdir, readFile } from 'node:fs/promises';
5
5
  import { addModule } from "./add-module.js";
6
6
  import { extractModule } from "./extract.js";
7
7
  import { writeReadmes } from "./readme.js";
8
- import { writeDistributionChangelogCheck, writeDistributionWorkflow, writeWorkflows } from "./workflows.js";
8
+ import { verify } from "./verify.js";
9
+ import { toolRelease, writeDistributionChecks, writeDistributionWorkflow, writeWorkflows } from "./workflows.js";
9
10
  import { writeOverrides } from "./overrides.js";
10
11
  import { checkReleasable, planRelease, readRelease, release, releaseDistribution, reportToActions as reportRelease, startPrerelease, } from "./release.js";
11
12
  import { reportToActions as reportCanary, stampCanary } from "./canary.js";
@@ -27,6 +28,7 @@ for (const stream of [process.stdout, process.stderr]) {
27
28
  });
28
29
  }
29
30
  const USAGE = `Usage: fg-dist <command> [options]
31
+ pnpm dist <command> [options], in a repository fg-dist set up
30
32
 
31
33
  Commands:
32
34
  init <kind> [dir] Scaffold a repository; kind is "distribution", "monolith" or "module"
@@ -41,6 +43,7 @@ Commands:
41
43
  release Move a module on from the release it just published
42
44
  prerelease Start the next line on a branch, leaving main where it is
43
45
  check Fail when this ships anything older than what it extends
46
+ verify Fail when a file fg-dist writes no longer says what is true (CI)
44
47
  inherit Add the modules the distribution this extends ships, and this does not
45
48
  sync Report which modules have newer versions available
46
49
  bump [name...] Move modules to their newest non-major version; a fork merges it
@@ -73,6 +76,7 @@ Options:
73
76
  --no-git Skip "git init" when scaffolding
74
77
  --url <git-url> Remote the scaffolded repository will live at (init)
75
78
  --extends <name> Distribution the scaffolded one extends (init distribution)
79
+ --copyright <holder> Who holds the copyright on its MIT license (init; default: git user.name)
76
80
  --separate Scaffold a distribution whose apps deploy separately
77
81
  --ssh Record a submodule's URL as given, without rewriting it
78
82
  --no-verify Skip checking whether the https urls can be cloned
@@ -116,6 +120,7 @@ const parseArgs = (argv) => {
116
120
  pr: undefined,
117
121
  repo: undefined,
118
122
  direct: false,
123
+ copyright: undefined,
119
124
  };
120
125
  for (let index = 0; index < argv.length; index += 1) {
121
126
  const arg = argv[index];
@@ -177,6 +182,9 @@ const parseArgs = (argv) => {
177
182
  else if (arg === '--no-push') {
178
183
  args.push = false;
179
184
  }
185
+ else if (arg === '--copyright') {
186
+ args.copyright = argv[++index];
187
+ }
180
188
  else if (arg === '--direct') {
181
189
  args.direct = true;
182
190
  }
@@ -305,6 +313,28 @@ const modulesFor = (cwd, names) => {
305
313
  }
306
314
  return { root, modules };
307
315
  };
316
+ /**
317
+ * Bring a distribution's readme up to what a command just changed.
318
+ *
319
+ * Its table names every module's version and pin, so a command that moves one
320
+ * leaves it stale — and \`verify\` fails the pull request that forgot. Only the
321
+ * distribution's own: a module's readme is the module's, and writing into one
322
+ * would leave its submodule dirty.
323
+ */
324
+ const refreshReadme = async (root) => {
325
+ if (!isDistribution(root))
326
+ return;
327
+ try {
328
+ const { updates } = await writeReadmes(root, { modules: false });
329
+ for (const update of updates.filter((each) => each.changed)) {
330
+ process.stdout.write(`${path.relative(root, update.file)} lists what it ships now.\n`);
331
+ }
332
+ }
333
+ catch (error) {
334
+ process.stderr.write(`The readme was not updated: ${error instanceof Error ? error.message : String(error)}\n` +
335
+ 'Run `pnpm dist readme` once that is sorted.\n');
336
+ }
337
+ };
308
338
  const pad = (values) => values.reduce((widest, value) => Math.max(widest, value.length), 0);
309
339
  const main = async () => {
310
340
  const args = parseArgs(process.argv.slice(2));
@@ -328,11 +358,22 @@ const main = async () => {
328
358
  // A module is consumed as a submodule, so record the URL it will be
329
359
  // cloned from in the form a keyless clone can use.
330
360
  const origin = args.url ? (args.ssh ? args.url : toHttps(args.url)) : undefined;
361
+ // Who holds the copyright on the MIT license it starts under: as given, or
362
+ // whoever git says is making it.
363
+ let copyright = args.copyright;
364
+ if (!copyright) {
365
+ try {
366
+ copyright = execFileSync('git', ['config', 'user.name'], { encoding: 'utf8' }).trim() || undefined;
367
+ }
368
+ catch {
369
+ copyright = undefined;
370
+ }
371
+ }
331
372
  const files = kind === 'distribution'
332
- ? distributionRepo(name, origin, args.extends, { monolith: !args.separate })
373
+ ? distributionRepo(name, origin, args.extends, { monolith: !args.separate, copyright })
333
374
  : kind === 'monolith'
334
- ? monolithRepo(name, origin)
335
- : moduleRepo(name, origin);
375
+ ? monolithRepo(name, origin, { copyright })
376
+ : moduleRepo(name, origin, { copyright });
336
377
  const written = await write(target, files);
337
378
  if (args.git) {
338
379
  try {
@@ -363,8 +404,8 @@ const main = async () => {
363
404
  process.stdout.write(kind === 'module'
364
405
  ? 'Next: pnpm install, then commit and push so a distribution can add it.\n'
365
406
  : args.extends && kind === 'distribution'
366
- ? `Next: pnpm install, then \`fg-dist inherit\` to ship what ${args.extends} ships.\n`
367
- : 'Next: pnpm install, then `fg-dist add-module <url>` to ship a module.\n');
407
+ ? `Next: pnpm install, then \`pnpm dist inherit\` to ship what ${args.extends} ships.\n`
408
+ : 'Next: pnpm install, then `pnpm dist add-module <url>` to ship a module.\n');
368
409
  return 0;
369
410
  }
370
411
  if (args.command === 'add-module') {
@@ -401,7 +442,7 @@ const main = async () => {
401
442
  else if (result.overridesError) {
402
443
  process.stderr.write(`\nThe module is added, but the workspace overrides were not updated:\n` +
403
444
  ` ${result.overridesError}\n` +
404
- 'Run `fg-dist overrides` once that is sorted.\n');
445
+ 'Run `pnpm dist overrides` once that is sorted.\n');
405
446
  }
406
447
  if (result.mounted && result.monolithConfig) {
407
448
  process.stdout.write(`Mounted at /${result.mount} in ${path.relative(args.cwd, result.monolithConfig)}\n`);
@@ -426,6 +467,9 @@ const main = async () => {
426
467
  if (result.turbo) {
427
468
  process.stdout.write("turbo.json: the monolith's build waits for its libraries, not its own build\n");
428
469
  }
470
+ const root = repositoryRoot(args.cwd);
471
+ if (root)
472
+ await refreshReadme(root);
429
473
  process.stdout.write(result.monolithPackageJson
430
474
  ? '\nRun pnpm install and `fg-monolith merge-package-json`.\n'
431
475
  : '\nRun pnpm install.\n');
@@ -451,7 +495,7 @@ const main = async () => {
451
495
  if (args.check) {
452
496
  process.stderr.write(`\n${changed.length} readme(s) are out of date:\n${changed
453
497
  .map((update) => ` ${path.relative(root, update.file)}`)
454
- .join('\n')}\nRun \`fg-dist readme\` to update them.\n`);
498
+ .join('\n')}\nRun \`pnpm dist readme\` to update them.\n`);
455
499
  return 1;
456
500
  }
457
501
  process.stdout.write(`\nUpdated:\n${changed
@@ -478,7 +522,7 @@ const main = async () => {
478
522
  ' "publishConfig": { "access": "public" }\n');
479
523
  }
480
524
  process.stdout.write('\nIt publishes the manifest, recording every module it ships, and its policy. ' +
481
- 'Version it with `fg-dist release`, then run the workflow.\n');
525
+ 'Version it with `pnpm release`, then run the workflow.\n');
482
526
  return 0;
483
527
  }
484
528
  const updates = await writeWorkflows(root, {
@@ -486,10 +530,10 @@ const main = async () => {
486
530
  dryRun: args.dryRun,
487
531
  names: args.names,
488
532
  });
489
- // The distribution checks its own changelog too, for policy changes —
490
- // unless only some modules were named.
533
+ // The distribution has its own checks too — the changelog for policy
534
+ // changes, and what fg-dist writes — unless only some modules were named.
491
535
  if (isDistribution(root) && args.names.length === 0) {
492
- updates.push(await writeDistributionChangelogCheck(root, { force: args.force, dryRun: args.dryRun }));
536
+ updates.push(await writeDistributionChecks(root, { force: args.force, dryRun: args.dryRun }));
493
537
  }
494
538
  if (updates.length === 0) {
495
539
  process.stdout.write('No modules here. Run this in a distribution, which is where the modules are.\n');
@@ -501,6 +545,14 @@ const main = async () => {
501
545
  ? `${update.relativePath} has one already\n`
502
546
  : `${update.relativePath} ${update.files.join(', ')}\n`);
503
547
  }
548
+ // A module's CI installs fg-dist from npm, so what these workflows run has
549
+ // to be in a release it can install.
550
+ const tool = await toolRelease();
551
+ if (tool && tool.floor !== tool.own) {
552
+ process.stderr.write(`\nfg-dist ${tool.own} is not released, so modules are given ^${tool.floor}, the newest that is.\n` +
553
+ `Whatever these workflows run that is new since then fails in the modules' CI until\n` +
554
+ `${tool.own} is published. Publish it, then run this again to move them up to it.\n`);
555
+ }
504
556
  if (written.length === 0) {
505
557
  process.stdout.write('\nEvery module already publishes. Pass --force to overwrite the workflows.\n');
506
558
  return 0;
@@ -564,7 +616,7 @@ const main = async () => {
564
616
  }
565
617
  if (args.check) {
566
618
  process.stderr.write(`\n${path.relative(root, file)} does not match the modules here.\n` +
567
- 'Run `fg-dist overrides` to update it.\n');
619
+ 'Run `pnpm dist overrides` to update it.\n');
568
620
  return 1;
569
621
  }
570
622
  process.stdout.write(`\nUpdated ${path.relative(root, file)}. Run \`pnpm install\` to take it up.\n`);
@@ -633,7 +685,7 @@ const main = async () => {
633
685
  ? `${plan.maintenance.branch} keeps ${released.split('.').slice(0, 2).join('.')}.x\n`
634
686
  : 'stays on this line; nothing branches off\n'));
635
687
  }
636
- process.stdout.write(`\nOr \`fg-dist prerelease --major\` to start the next line beside main, ` +
688
+ process.stdout.write(`\nOr \`pnpm dist prerelease --major\` to start the next line beside main, ` +
637
689
  'without\nmoving main off this one.\n');
638
690
  return 1;
639
691
  }
@@ -735,7 +787,7 @@ const main = async () => {
735
787
  process.stdout.write('turbo.json builds the policy once, before each service that uses it.\n');
736
788
  return 0;
737
789
  }
738
- process.stdout.write(`${args.check ? 'turbo.json is missing' : 'Wrote'} ${differ.join(', ')}${args.check ? '; run fg-dist policy setup.' : ' into turbo.json.'}\n`);
790
+ process.stdout.write(`${args.check ? 'turbo.json is missing' : 'Wrote'} ${differ.join(', ')}${args.check ? '; run pnpm dist policy setup.' : ' into turbo.json.'}\n`);
739
791
  return args.check ? 1 : 0;
740
792
  }
741
793
  process.stderr.write('Usage: fg-dist policy <build|test|use|setup>\n');
@@ -763,6 +815,23 @@ const main = async () => {
763
815
  `than ${report.parent?.name}@${report.parent?.version}.\n`);
764
816
  return 0;
765
817
  }
818
+ if (args.command === 'verify') {
819
+ const root = repositoryRoot(args.cwd);
820
+ if (!root)
821
+ throw new Error(`${args.cwd} is not inside a git repository.`);
822
+ const checks = await verify(root);
823
+ const width = pad(checks.map((check) => check.what));
824
+ for (const check of checks) {
825
+ if (check.ok) {
826
+ process.stdout.write(`ok ${check.what}\n`);
827
+ continue;
828
+ }
829
+ const problem = (check.problem ?? '').split('\n').join(`\n ${' '.repeat(width)} `);
830
+ process.stderr.write(`stale ${check.what.padEnd(width)} ${problem}\n` +
831
+ (check.fix ? ` ${' '.repeat(width)} Run \`${check.fix}\`.\n` : ''));
832
+ }
833
+ return checks.every((check) => check.ok) ? 0 : 1;
834
+ }
766
835
  if (args.command === 'extract') {
767
836
  const [target] = args.names;
768
837
  if (!target) {
@@ -791,8 +860,11 @@ const main = async () => {
791
860
  if (result.overridesError) {
792
861
  process.stderr.write(`\nThe module is extracted, but the workspace overrides were not updated:\n` +
793
862
  ` ${result.overridesError}\n` +
794
- 'Run `fg-dist overrides` once that is sorted.\n');
863
+ 'Run `pnpm dist overrides` once that is sorted.\n');
795
864
  }
865
+ const root = repositoryRoot(args.cwd);
866
+ if (root)
867
+ await refreshReadme(root);
796
868
  process.stdout.write(`origin is ${result.url}\n`);
797
869
  if (result.rewritten) {
798
870
  process.stdout.write(`(rewritten from ${args.url}; a submodule is cloned without an SSH key)\n`);
@@ -863,7 +935,7 @@ const main = async () => {
863
935
  process.stdout.write(` ${state.submodule.relativePath} ${state.submodule.url}` +
864
936
  ` -> ${toHttps(state.submodule.url)}\n`);
865
937
  }
866
- process.stdout.write('Run `fg-dist use-https` to rewrite them. They also have to be ' +
938
+ process.stdout.write('Run `pnpm dist use-https` to rewrite them. They also have to be ' +
867
939
  'readable without credentials, which means public.\n');
868
940
  }
869
941
  // A pin the remote does not have breaks every other clone, a build host's first.
@@ -874,7 +946,7 @@ const main = async () => {
874
946
  for (const state of unpushed) {
875
947
  process.stdout.write(` ${state.submodule.relativePath} ${state.head.slice(0, 7)}\n`);
876
948
  }
877
- process.stdout.write('Push them — `fg-dist contribute <name>` does, for a change on its way upstream.\n');
949
+ process.stdout.write('Push them — `pnpm dist contribute <name>` does, for a change on its way upstream.\n');
878
950
  }
879
951
  // A fork whose work upstream has taken can go back to upstream.
880
952
  const returnable = states.filter((state) => canReturn(state));
@@ -883,7 +955,7 @@ const main = async () => {
883
955
  for (const state of returnable) {
884
956
  process.stdout.write(` ${state.submodule.relativePath} in ${canReturn(state)}\n`);
885
957
  }
886
- process.stdout.write('Keep them, or `fg-dist unfork <name>` to go back to upstream.\n');
958
+ process.stdout.write('Keep them, or `pnpm dist unfork <name>` to go back to upstream.\n');
887
959
  }
888
960
  // An extension may move ahead of what it extends, never behind it.
889
961
  const extendsReport = inspectExtends(root);
@@ -913,9 +985,9 @@ const main = async () => {
913
985
  // A major is only held back when it is newer than the non-major target.
914
986
  const held = upgradable.filter((state) => state.upgrades.major !== undefined);
915
987
  process.stdout.write(`\n${upgradable.length} module(s) have newer versions. `);
916
- process.stdout.write('Run `fg-dist bump` to take them');
988
+ process.stdout.write('Run `pnpm dist bump` to take them');
917
989
  process.stdout.write(held.length > 0
918
- ? `, or \`fg-dist bump --major\` to include the ${held.length} major upgrade(s).\n`
990
+ ? `, or \`pnpm dist bump --major\` to include the ${held.length} major upgrade(s).\n`
919
991
  : '.\n');
920
992
  return 0;
921
993
  }
@@ -964,7 +1036,7 @@ const main = async () => {
964
1036
  try {
965
1037
  const done = integrate(root, state, to, { push: args.push });
966
1038
  process.stdout.write(done.how === 'moved'
967
- ? `${line} (fork: upstream has all of it; \`fg-dist unfork ${state.submodule.name}\` goes back)\n`
1039
+ ? `${line} (fork: upstream has all of it; \`pnpm dist unfork ${state.submodule.name}\` goes back)\n`
968
1040
  : `${line} (fork: merged${done.pushed ? `, pushed to ${done.pushed}` : ', not pushed'})\n`);
969
1041
  }
970
1042
  catch (error) {
@@ -972,6 +1044,8 @@ const main = async () => {
972
1044
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
973
1045
  }
974
1046
  }
1047
+ if (!args.dryRun)
1048
+ await refreshReadme(root);
975
1049
  process.stdout.write(args.dryRun
976
1050
  ? '\nDry run; nothing moved.\n'
977
1051
  : '\nSubmodule pointers moved. Commit them to record the new versions.\n');
@@ -991,10 +1065,23 @@ const main = async () => {
991
1065
  for (const module of result.blocked) {
992
1066
  process.stderr.write(`${module.path} has something in it already, so ${module.name} was not added there.\n`);
993
1067
  }
1068
+ // The parent's notice goes with what is built on it.
1069
+ const license = result.license;
1070
+ if (license.how === 'merged') {
1071
+ process.stdout.write(`LICENSE carries ${result.parent}'s copyright too: ${license.lines.join('; ')}\n`);
1072
+ }
1073
+ else if (license.how === 'copied') {
1074
+ process.stdout.write(`${license.file} keeps ${result.parent}'s license, which is not this one's.\n`);
1075
+ }
1076
+ else if (license.how === 'none') {
1077
+ process.stdout.write(`${result.parent} publishes no license to carry.\n`);
1078
+ }
994
1079
  if (result.added.length === 0 && result.blocked.length === 0) {
995
1080
  process.stdout.write(`Ships everything ${result.parent}@${result.parentVersion} ships already.\n`);
996
1081
  return 0;
997
1082
  }
1083
+ if (!args.dryRun && result.added.length > 0)
1084
+ await refreshReadme(root);
998
1085
  process.stdout.write(args.dryRun
999
1086
  ? '\nDry run; nothing added.\n'
1000
1087
  : `\nShips what ${result.parent}@${result.parentVersion} ships. Run pnpm install, then commit.\n`);
@@ -1058,11 +1145,12 @@ const main = async () => {
1058
1145
  if (args.command === 'fork') {
1059
1146
  const [name, url] = args.names;
1060
1147
  if (!name || !url) {
1061
- process.stderr.write('fork needs a module and the url of your fork: fg-dist fork <name> <url>\n');
1148
+ process.stderr.write('fork needs a module and the url of your fork: pnpm dist fork <name> <url>\n');
1062
1149
  return 1;
1063
1150
  }
1064
1151
  const { root, modules } = modulesFor(args.cwd, [name]);
1065
1152
  const result = forkModule(root, modules[0], url, { ssh: args.ssh, push: args.push });
1153
+ await refreshReadme(root);
1066
1154
  process.stdout.write(`${modules[0].relativePath} is checked out from ${result.url}\n` +
1067
1155
  `forked from ${result.upstream}, which .gitmodules records as its upstream\n`);
1068
1156
  if (result.rewritten) {
@@ -1072,13 +1160,13 @@ const main = async () => {
1072
1160
  ? `Pinned commit pushed to ${result.branch}, where everything this distribution pins in the fork lives.\n`
1073
1161
  : 'Nothing pushed. The pinned commit has to be on the fork before anyone else can check it out.\n');
1074
1162
  process.stdout.write(`\nNext: make the change on a branch in ${modules[0].relativePath}, then ` +
1075
- `\`fg-dist contribute ${modules[0].name}\`.\nCommit .gitmodules and ${modules[0].relativePath} here.\n`);
1163
+ `\`pnpm dist contribute ${modules[0].name}\`.\nCommit .gitmodules and ${modules[0].relativePath} here.\n`);
1076
1164
  return 0;
1077
1165
  }
1078
1166
  if (args.command === 'contribute') {
1079
1167
  const [name] = args.names;
1080
1168
  if (!name) {
1081
- process.stderr.write('contribute needs a module: fg-dist contribute <name>\n');
1169
+ process.stderr.write('contribute needs a module: pnpm dist contribute <name>\n');
1082
1170
  return 1;
1083
1171
  }
1084
1172
  const { root, modules } = modulesFor(args.cwd, [name]);
@@ -1087,6 +1175,7 @@ const main = async () => {
1087
1175
  process.stdout.write(`Would push ${result.branch} and open a pull request into ${describeRemote(result.repository)} ${result.base}.\n`);
1088
1176
  return 0;
1089
1177
  }
1178
+ await refreshReadme(root);
1090
1179
  process.stdout.write(`Pushed ${result.branch}.\n`);
1091
1180
  if (result.pinnedOn)
1092
1181
  process.stdout.write(`Pinned commit kept on ${result.pinnedOn}, which outlives the pull request's branch.\n`);
@@ -1111,12 +1200,13 @@ const main = async () => {
1111
1200
  if (args.command === 'unfork') {
1112
1201
  const [name] = args.names;
1113
1202
  if (!name) {
1114
- process.stderr.write('unfork needs a module: fg-dist unfork <name>\n');
1203
+ process.stderr.write('unfork needs a module: pnpm dist unfork <name>\n');
1115
1204
  return 1;
1116
1205
  }
1117
1206
  const { root, modules } = modulesFor(args.cwd, [name]);
1118
1207
  const state = inspect(modules[0], { fetch: args.fetch });
1119
1208
  const result = unforkModule(root, state, { to: args.to, major: args.major, force: args.force });
1209
+ await refreshReadme(root);
1120
1210
  process.stdout.write(`${modules[0].relativePath} is checked out from ${result.url} again, at ${result.to}.\n` +
1121
1211
  `Commit .gitmodules and ${modules[0].relativePath} here. The fork itself is left as it is.\n`);
1122
1212
  return 0;