@fairgarden/distribution 0.1.0-alpha.0 → 0.1.0-alpha.2

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 (63) hide show
  1. package/Readme.md +6 -3
  2. package/dist/add-module.d.ts +4 -0
  3. package/dist/add-module.d.ts.map +1 -1
  4. package/dist/add-module.js +29 -0
  5. package/dist/add-module.js.map +1 -1
  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.map +1 -1
  11. package/dist/canary.js +15 -2
  12. package/dist/canary.js.map +1 -1
  13. package/dist/changelog.d.ts +111 -0
  14. package/dist/changelog.d.ts.map +1 -0
  15. package/dist/changelog.js +285 -0
  16. package/dist/changelog.js.map +1 -0
  17. package/dist/cli.js +341 -12
  18. package/dist/cli.js.map +1 -1
  19. package/dist/extends.d.ts +3 -2
  20. package/dist/extends.d.ts.map +1 -1
  21. package/dist/extends.js +12 -2
  22. package/dist/extends.js.map +1 -1
  23. package/dist/forks.d.ts +116 -0
  24. package/dist/forks.d.ts.map +1 -0
  25. package/dist/forks.js +357 -0
  26. package/dist/forks.js.map +1 -0
  27. package/dist/index.d.ts +9 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +4 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/inherit.d.ts +18 -0
  32. package/dist/inherit.d.ts.map +1 -0
  33. package/dist/inherit.js +84 -0
  34. package/dist/inherit.js.map +1 -0
  35. package/dist/manifest.d.ts +43 -0
  36. package/dist/manifest.d.ts.map +1 -0
  37. package/dist/manifest.js +64 -0
  38. package/dist/manifest.js.map +1 -0
  39. package/dist/policy.d.ts +86 -0
  40. package/dist/policy.d.ts.map +1 -0
  41. package/dist/policy.js +269 -0
  42. package/dist/policy.js.map +1 -0
  43. package/dist/readme.d.ts +2 -0
  44. package/dist/readme.d.ts.map +1 -1
  45. package/dist/readme.js +6 -1
  46. package/dist/readme.js.map +1 -1
  47. package/dist/release.d.ts +24 -0
  48. package/dist/release.d.ts.map +1 -1
  49. package/dist/release.js +51 -6
  50. package/dist/release.js.map +1 -1
  51. package/dist/scaffold.d.ts +4 -5
  52. package/dist/scaffold.d.ts.map +1 -1
  53. package/dist/scaffold.js +167 -64
  54. package/dist/scaffold.js.map +1 -1
  55. package/dist/submodules.d.ts +49 -0
  56. package/dist/submodules.d.ts.map +1 -1
  57. package/dist/submodules.js +94 -4
  58. package/dist/submodules.js.map +1 -1
  59. package/dist/workflows.d.ts +46 -1
  60. package/dist/workflows.d.ts.map +1 -1
  61. package/dist/workflows.js +372 -20
  62. package/dist/workflows.js.map +1 -1
  63. package/package.json +14 -12
package/dist/cli.js CHANGED
@@ -1,18 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  import path from 'node:path';
3
3
  import { execFileSync } from 'node:child_process';
4
- import { mkdir } from 'node:fs/promises';
4
+ 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 { writeWorkflows } from "./workflows.js";
8
+ import { writeDistributionChangelogCheck, writeDistributionWorkflow, writeWorkflows } from "./workflows.js";
9
9
  import { writeOverrides } from "./overrides.js";
10
- import { checkReleasable, planRelease, readRelease, release, reportToActions as reportRelease, startPrerelease, } from "./release.js";
10
+ import { checkReleasable, planRelease, readRelease, release, releaseDistribution, reportToActions as reportRelease, startPrerelease, } from "./release.js";
11
11
  import { reportToActions as reportCanary, stampCanary } from "./canary.js";
12
12
  import { describeRemote, isSsh, toHttps } from "./git-url.js";
13
13
  import { distributionRepo, isEmpty, moduleRepo, monolithRepo, write, } from "./scaffold.js";
14
14
  import { describeViolations, inspectExtends } from "./extends.js";
15
- import { inspect, isPublic, moveTo, repositoryRoot, setUrl, submodules, target, } from "./submodules.js";
15
+ import { contribute, forkModule, integrate, unforkModule } from "./forks.js";
16
+ import { inherit } from "./inherit.js";
17
+ import { bumpEntry, checkChangelog, crossedReleases, CHANGELOG, moduleLabel, noteInDistribution, notesFor, pullRequestFromActions, } from "./changelog.js";
18
+ import { buildPolicy, findPolicy, POLICIES, setupTurbo, testPolicy, usePolicy } from "./policy.js";
19
+ import { stampModules } from "./manifest.js";
20
+ import { containsAll, inspect, isDistribution, isPublic, moveTo, repositoryRoot, setUrl, submodules, target, } from "./submodules.js";
16
21
  // Piping into `head` closes stdout early; that is ordinary use, not a failure.
17
22
  for (const stream of [process.stdout, process.stderr]) {
18
23
  stream.on('error', (error) => {
@@ -29,14 +34,25 @@ Commands:
29
34
  extract <path> Turn a directory here into its own repository and submodule
30
35
  use-https [name...] Rewrite submodule urls from ssh to https
31
36
  readme Record versions in the readmes; run in a distribution or a module
32
- workflows Give every module a publishing workflow
37
+ workflows [name...] Give every module a publishing workflow and a changelog check;
38
+ --distribution, this one a publishing workflow too
33
39
  overrides Point the workspace at this distribution's own checkouts
34
40
  canary Stamp a canary version into the manifest, for CI
35
41
  release Move a module on from the release it just published
36
42
  prerelease Start the next line on a branch, leaving main where it is
37
43
  check Fail when this ships anything older than what it extends
44
+ inherit Add the modules the distribution this extends ships, and this does not
38
45
  sync Report which modules have newer versions available
39
- bump [name...] Move modules to their newest non-major version
46
+ bump [name...] Move modules to their newest non-major version; a fork merges it
47
+ fork <name> <url> Check a module out from your fork, keeping where it came from
48
+ contribute <name> Push the module's branch and open a pull request upstream
49
+ unfork <name> Go back to the upstream a fork came from, once it has the fork's work
50
+ changelog check Fail a pull request that adds no changelog line linking itself (CI)
51
+ changelog notes [v] Print a version's changelog section, for its release notes
52
+ policy build Build the organization's policy: policies/, on every module's own
53
+ policy test Test each module's rules, then the organization's on top of them
54
+ policy use Put the built policy beside this service, for it to run
55
+ policy setup Have turbo build the policy once, before each service that uses it
40
56
 
41
57
  Options:
42
58
  --cwd <dir> Repository directory (default: the working directory)
@@ -44,6 +60,7 @@ Options:
44
60
  --minor Release the next minor, or branch one (release, prerelease)
45
61
  --patch Release the next patch, staying on this line (release)
46
62
  --id <name> Prerelease identifier: alpha, beta, rc (release, prerelease)
63
+ --stable A distribution's release leaves its prerelease stages (release)
47
64
  --no-fetch Use the refs already fetched (sync, bump)
48
65
  --dry-run Report what would change without changing it
49
66
  --check Verify instead of writing (readme, release, overrides)
@@ -58,7 +75,13 @@ Options:
58
75
  --no-verify Skip checking whether the https urls can be cloned
59
76
  --no-tag Do not tag the extracted module with its declared version
60
77
  --force Overwrite workflows that are already there (workflows)
61
- --no-push Leave the release branches local, and open no pull request
78
+ --distribution Publish the distribution itself, for others to extend (workflows)
79
+ --no-push Push nothing and open no pull request (release, fork, contribute, bump)
80
+ --to <ref> Upstream tag, branch or commit to go back to (unfork)
81
+ --base <branch> Branch the pull request goes into (contribute); what it merges into (changelog check)
82
+ --pr <number> The pull request to check, outside GitHub Actions (changelog check)
83
+ --repo <owner/name> The repository its link names, outside GitHub Actions (changelog check)
84
+ --out <file> Where policy build writes the bundle (default: policies/dist/)
62
85
  `;
63
86
  const parseArgs = (argv) => {
64
87
  const args = {
@@ -82,6 +105,13 @@ const parseArgs = (argv) => {
82
105
  push: true,
83
106
  bump: undefined,
84
107
  id: undefined,
108
+ out: undefined,
109
+ to: undefined,
110
+ base: undefined,
111
+ distribution: false,
112
+ stable: false,
113
+ pr: undefined,
114
+ repo: undefined,
85
115
  };
86
116
  for (let index = 0; index < argv.length; index += 1) {
87
117
  const arg = argv[index];
@@ -143,6 +173,27 @@ const parseArgs = (argv) => {
143
173
  else if (arg === '--no-push') {
144
174
  args.push = false;
145
175
  }
176
+ else if (arg === '--pr') {
177
+ args.pr = Number(argv[++index]);
178
+ }
179
+ else if (arg === '--repo') {
180
+ args.repo = argv[++index];
181
+ }
182
+ else if (arg === '--stable') {
183
+ args.stable = true;
184
+ }
185
+ else if (arg === '--distribution') {
186
+ args.distribution = true;
187
+ }
188
+ else if (arg === '--to') {
189
+ args.to = argv[++index];
190
+ }
191
+ else if (arg === '--base') {
192
+ args.base = argv[++index];
193
+ }
194
+ else if (arg === '--out') {
195
+ args.out = path.resolve(argv[++index] ?? '.');
196
+ }
146
197
  else if (!arg.startsWith('-')) {
147
198
  if (args.command)
148
199
  args.names.push(arg);
@@ -156,6 +207,19 @@ const parseArgs = (argv) => {
156
207
  return args;
157
208
  };
158
209
  /** One line per module, aligned, saying what is available. */
210
+ /**
211
+ * Where a fork could go back to upstream, when upstream has taken what it
212
+ * added: a release past the pinned one, or its branch. Not a fork that has
213
+ * added nothing yet, which is where every fork starts.
214
+ */
215
+ const canReturn = (state) => {
216
+ const merged = state.fork?.merged;
217
+ if (!merged)
218
+ return undefined;
219
+ if (merged === state.current && state.fork?.ahead === 0)
220
+ return undefined;
221
+ return merged.replace(/^upstream\//, '');
222
+ };
159
223
  const describeState = (state) => {
160
224
  const { current, available, untagged, dirty } = state;
161
225
  const parts = [];
@@ -175,6 +239,18 @@ const describeState = (state) => {
175
239
  }
176
240
  if (untagged > 0)
177
241
  parts.push(`${untagged} untagged commit${untagged === 1 ? '' : 's'}`);
242
+ if (state.fork) {
243
+ const { upstream, ahead } = state.fork;
244
+ const from = `fork of ${describeRemote(upstream)}`;
245
+ const returnTo = canReturn(state);
246
+ parts.push(returnTo
247
+ ? `${from}, all in upstream ${returnTo}`
248
+ : ahead === 0
249
+ ? `${from}, nothing of its own`
250
+ : `${from}, ${ahead} commit${ahead === 1 ? '' : 's'} not upstream`);
251
+ }
252
+ if (state.pushed === false)
253
+ parts.push('not pushed');
178
254
  if (dirty)
179
255
  parts.push('uncommitted changes');
180
256
  if (state.fetched === false)
@@ -259,7 +335,9 @@ const main = async () => {
259
335
  }
260
336
  process.stdout.write(kind === 'module'
261
337
  ? 'Next: pnpm install, then commit and push so a distribution can add it.\n'
262
- : 'Next: pnpm install, then `fg-dist add-module <url>` to ship a module.\n');
338
+ : args.extends && kind === 'distribution'
339
+ ? `Next: pnpm install, then \`fg-dist inherit\` to ship what ${args.extends} ships.\n`
340
+ : 'Next: pnpm install, then `fg-dist add-module <url>` to ship a module.\n');
263
341
  return 0;
264
342
  }
265
343
  if (args.command === 'add-module') {
@@ -318,6 +396,9 @@ const main = async () => {
318
396
  else if (result.monolithConfig) {
319
397
  process.stdout.write(`Already mounted at /${result.mount}.\n`);
320
398
  }
399
+ if (result.turbo) {
400
+ process.stdout.write("turbo.json: the monolith's build waits for its libraries, not its own build\n");
401
+ }
321
402
  process.stdout.write(result.monolithPackageJson
322
403
  ? '\nRun pnpm install and `fg-monolith merge-package-json`.\n'
323
404
  : '\nRun pnpm install.\n');
@@ -355,10 +436,34 @@ const main = async () => {
355
436
  const root = repositoryRoot(args.cwd);
356
437
  if (!root)
357
438
  throw new Error(`${args.cwd} is not inside a git repository.`);
439
+ if (args.distribution) {
440
+ const update = await writeDistributionWorkflow(root, { force: args.force, dryRun: args.dryRun });
441
+ if (update.skipped) {
442
+ process.stdout.write('The distribution has a publishing workflow already. Pass --force to overwrite it.\n');
443
+ return 0;
444
+ }
445
+ process.stdout.write(`${update.files.join(', ')}\n`);
446
+ if (update.unprivated) {
447
+ process.stdout.write('\nTook "private": true off package.json — a private package cannot publish.\n');
448
+ }
449
+ if (update.missingAccess) {
450
+ process.stdout.write('Nothing says whether it publishes publicly. For others to extend it, add\n' +
451
+ ' "publishConfig": { "access": "public" }\n');
452
+ }
453
+ process.stdout.write('\nIt publishes the manifest, recording every module it ships, and its policy. ' +
454
+ 'Version it with `fg-dist release`, then run the workflow.\n');
455
+ return 0;
456
+ }
358
457
  const updates = await writeWorkflows(root, {
359
458
  force: args.force,
360
459
  dryRun: args.dryRun,
460
+ names: args.names,
361
461
  });
462
+ // The distribution checks its own changelog too, for policy changes —
463
+ // unless only some modules were named.
464
+ if (isDistribution(root) && args.names.length === 0) {
465
+ updates.push(await writeDistributionChangelogCheck(root, { force: args.force, dryRun: args.dryRun }));
466
+ }
362
467
  if (updates.length === 0) {
363
468
  process.stdout.write('No modules here. Run this in a distribution, which is where the modules are.\n');
364
469
  return 0;
@@ -453,11 +558,35 @@ const main = async () => {
453
558
  throw new Error(`${args.cwd} is not inside a git repository.`);
454
559
  if (args.check) {
455
560
  const releasable = await checkReleasable(root);
561
+ // A distribution records what it ships in what it publishes.
562
+ if (isDistribution(root))
563
+ await stampModules(root, { version: releasable.version });
456
564
  await reportRelease(releasable);
457
565
  process.stdout.write(`${releasable.name}@${releasable.version} is not on npm; ready to release as ` +
458
566
  `${releasable.tag}.\n`);
459
567
  return 0;
460
568
  }
569
+ if (isDistribution(root)) {
570
+ if (args.bump) {
571
+ process.stderr.write("A distribution's version is its month and its release: move it with " +
572
+ '--id <alpha|beta|rc> or --stable, or neither to carry on as it is.\n');
573
+ return 1;
574
+ }
575
+ const next = await releaseDistribution(root, {
576
+ dryRun: args.dryRun,
577
+ id: args.id,
578
+ stable: args.stable,
579
+ });
580
+ const verb = args.dryRun ? 'Would move' : 'Moved';
581
+ process.stdout.write(next.version === next.from
582
+ ? `${next.name} ${next.from} is not published yet; it is what the publish workflow releases next.\n`
583
+ : `${next.published ? `${next.from} is published. ` : `${next.from} was never published. `}` +
584
+ `${verb} ${next.name} to ${next.version}.\n`);
585
+ if (!args.dryRun && next.version !== next.from) {
586
+ process.stdout.write(`Commit it, then run the publish workflow, which publishes it and tags v${next.version}.\n`);
587
+ }
588
+ return 0;
589
+ }
461
590
  // Which way main moves on is a judgement about what changed, so it is asked
462
591
  // for rather than guessed at. Showing all three is the cheapest way to ask.
463
592
  if (!args.bump) {
@@ -540,6 +669,38 @@ const main = async () => {
540
669
  : '\nLeft local. Push it when you are ready.\n');
541
670
  return 0;
542
671
  }
672
+ if (args.command === 'policy') {
673
+ const [action] = args.names;
674
+ if (action === 'use') {
675
+ const { policy, status } = usePolicy(args.cwd);
676
+ process.stdout.write(policy
677
+ ? status === 0
678
+ ? `Using ${path.relative(args.cwd, policy.built) || policy.built}.\n`
679
+ : ''
680
+ : 'No organization policy here: the built-in rules decide.\n');
681
+ return status;
682
+ }
683
+ const policy = findPolicy(args.cwd);
684
+ if (!policy) {
685
+ process.stderr.write(`There is no organization policy here: no ${POLICIES}/.manifest above ${args.cwd}.\n`);
686
+ return 1;
687
+ }
688
+ if (action === 'build')
689
+ return buildPolicy(policy, args.cwd, args.out);
690
+ if (action === 'test')
691
+ return testPolicy(policy, args.cwd);
692
+ if (action === 'setup') {
693
+ const differ = setupTurbo(policy, { check: args.check });
694
+ if (differ.length === 0) {
695
+ process.stdout.write('turbo.json builds the policy once, before each service that uses it.\n');
696
+ return 0;
697
+ }
698
+ process.stdout.write(`${args.check ? 'turbo.json is missing' : 'Wrote'} ${differ.join(', ')}${args.check ? '; run fg-dist policy setup.' : ' into turbo.json.'}\n`);
699
+ return args.check ? 1 : 0;
700
+ }
701
+ process.stderr.write('Usage: fg-dist policy <build|test|use|setup>\n');
702
+ return 1;
703
+ }
543
704
  if (args.command === 'check') {
544
705
  const root = repositoryRoot(args.cwd);
545
706
  if (!root)
@@ -665,6 +826,25 @@ const main = async () => {
665
826
  process.stdout.write('Run `fg-dist use-https` to rewrite them. They also have to be ' +
666
827
  'readable without credentials, which means public.\n');
667
828
  }
829
+ // A pin the remote does not have breaks every other clone, a build host's first.
830
+ const unpushed = states.filter((state) => state.pushed === false);
831
+ if (unpushed.length > 0) {
832
+ process.stdout.write(`\n${unpushed.length} module(s) are pinned at a commit their remote does not have, ` +
833
+ 'so no other clone can check them out:\n');
834
+ for (const state of unpushed) {
835
+ process.stdout.write(` ${state.submodule.relativePath} ${state.head.slice(0, 7)}\n`);
836
+ }
837
+ process.stdout.write('Push them — `fg-dist contribute <name>` does, for a change on its way upstream.\n');
838
+ }
839
+ // A fork whose work upstream has taken can go back to upstream.
840
+ const returnable = states.filter((state) => canReturn(state));
841
+ if (returnable.length > 0) {
842
+ process.stdout.write('\nUpstream has everything these forks add:\n');
843
+ for (const state of returnable) {
844
+ process.stdout.write(` ${state.submodule.relativePath} in ${canReturn(state)}\n`);
845
+ }
846
+ process.stdout.write('Keep them, or `fg-dist unfork <name>` to go back to upstream.\n');
847
+ }
668
848
  // An extension may move ahead of what it extends, never behind it.
669
849
  const extendsReport = inspectExtends(root);
670
850
  if (extendsReport.violations.length > 0) {
@@ -700,7 +880,7 @@ const main = async () => {
700
880
  return 0;
701
881
  }
702
882
  if (args.command === 'bump') {
703
- const { modules } = modulesFor(args.cwd, args.names);
883
+ const { root, modules } = modulesFor(args.cwd, args.names);
704
884
  const states = modules.map((module) => inspect(module, { fetch: args.fetch }));
705
885
  const moves = states
706
886
  .map((state) => ({ state, to: target(state, { major: args.major }) }))
@@ -721,18 +901,167 @@ const main = async () => {
721
901
  .join('\n')}\n`);
722
902
  return 1;
723
903
  }
904
+ let failed = 0;
724
905
  for (const { state, to } of moves) {
725
906
  const from = state.current ?? state.head.slice(0, 7);
907
+ const line = `${state.submodule.relativePath} ${from} -> ${to}`;
726
908
  if (args.dryRun) {
727
- process.stdout.write(`${state.submodule.relativePath} ${from} -> ${to}\n`);
909
+ const how = !state.fork
910
+ ? ''
911
+ : containsAll(state.submodule.path, `refs/tags/${to}`, state.head)
912
+ ? ' (fork: upstream has all of it; would move to it)'
913
+ : ' (fork: would merge it in)';
914
+ process.stdout.write(`${line}${how}\n`);
915
+ continue;
916
+ }
917
+ if (!state.fork) {
918
+ moveTo(state.submodule, to);
919
+ noteInDistribution(root, bumpEntry(moduleLabel(state.submodule.path).name, from, to, state.submodule.url, crossedReleases(state.available, to)));
920
+ process.stdout.write(`${line}\n`);
728
921
  continue;
729
922
  }
730
- moveTo(state.submodule, to);
731
- process.stdout.write(`${state.submodule.relativePath} ${from} -> ${to}\n`);
923
+ // A fork takes upstream's release by merging it, keeping its own work on top.
924
+ try {
925
+ const done = integrate(root, state, to, { push: args.push });
926
+ process.stdout.write(done.how === 'moved'
927
+ ? `${line} (fork: upstream has all of it; \`fg-dist unfork ${state.submodule.name}\` goes back)\n`
928
+ : `${line} (fork: merged${done.pushed ? `, pushed to ${done.pushed}` : ', not pushed'})\n`);
929
+ }
930
+ catch (error) {
931
+ failed += 1;
932
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
933
+ }
732
934
  }
733
935
  process.stdout.write(args.dryRun
734
936
  ? '\nDry run; nothing moved.\n'
735
937
  : '\nSubmodule pointers moved. Commit them to record the new versions.\n');
938
+ return failed > 0 ? 1 : 0;
939
+ }
940
+ if (args.command === 'inherit') {
941
+ const root = repositoryRoot(args.cwd);
942
+ if (!root)
943
+ throw new Error(`${args.cwd} is not inside a git repository.`);
944
+ const result = await inherit(root, { dryRun: args.dryRun });
945
+ const width = pad(result.added.map((module) => module.path));
946
+ for (const module of result.added) {
947
+ process.stdout.write(`${module.path.padEnd(width)} ${module.name} ${module.version} at ${module.commit.slice(0, 7)}, ` +
948
+ `from ${describeRemote(module.repository)}` +
949
+ `${module.upstream ? ` (a fork of ${describeRemote(module.upstream)})` : ''}\n`);
950
+ }
951
+ for (const module of result.blocked) {
952
+ process.stderr.write(`${module.path} has something in it already, so ${module.name} was not added there.\n`);
953
+ }
954
+ if (result.added.length === 0 && result.blocked.length === 0) {
955
+ process.stdout.write(`Ships everything ${result.parent}@${result.parentVersion} ships already.\n`);
956
+ return 0;
957
+ }
958
+ process.stdout.write(args.dryRun
959
+ ? '\nDry run; nothing added.\n'
960
+ : `\nShips what ${result.parent}@${result.parentVersion} ships. Run pnpm install, then commit.\n`);
961
+ return result.blocked.length > 0 ? 1 : 0;
962
+ }
963
+ if (args.command === 'changelog') {
964
+ const [action, version] = args.names;
965
+ const root = repositoryRoot(args.cwd) ?? args.cwd;
966
+ if (action === 'check') {
967
+ const actions = pullRequestFromActions();
968
+ const pullRequest = args.pr ?? actions.pullRequest;
969
+ const base = args.base ?? actions.base;
970
+ const repository = args.repo ?? actions.repository;
971
+ if (!pullRequest || !base || !repository) {
972
+ process.stderr.write('changelog check runs on a pull request: in GitHub Actions, or given ' +
973
+ '--pr <number> --base <ref> --repo <owner/name>.\n');
974
+ return 1;
975
+ }
976
+ const result = checkChangelog(root, {
977
+ pullRequest,
978
+ base,
979
+ repository,
980
+ distribution: isDistribution(root),
981
+ labels: actions.labels,
982
+ server: actions.server,
983
+ });
984
+ (result.ok ? process.stdout : process.stderr).write(`${result.message}\n`);
985
+ return result.ok ? 0 : 1;
986
+ }
987
+ if (action === 'notes') {
988
+ const wanted = version ?? JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8')).version;
989
+ const text = await readFile(path.join(root, CHANGELOG), 'utf8').catch(() => '');
990
+ const notes = notesFor(text, wanted);
991
+ if (!notes) {
992
+ process.stderr.write(`${CHANGELOG} says nothing for ${wanted}.\n`);
993
+ return 1;
994
+ }
995
+ process.stdout.write(notes);
996
+ return 0;
997
+ }
998
+ process.stderr.write('changelog needs `check` or `notes`.\n');
999
+ return 1;
1000
+ }
1001
+ if (args.command === 'fork') {
1002
+ const [name, url] = args.names;
1003
+ if (!name || !url) {
1004
+ process.stderr.write('fork needs a module and the url of your fork: fg-dist fork <name> <url>\n');
1005
+ return 1;
1006
+ }
1007
+ const { root, modules } = modulesFor(args.cwd, [name]);
1008
+ const result = forkModule(root, modules[0], url, { ssh: args.ssh, push: args.push });
1009
+ process.stdout.write(`${modules[0].relativePath} is checked out from ${result.url}\n` +
1010
+ `forked from ${result.upstream}, which .gitmodules records as its upstream\n`);
1011
+ if (result.rewritten) {
1012
+ process.stdout.write(`(rewritten from ${url}; a submodule is cloned without an SSH key)\n`);
1013
+ }
1014
+ process.stdout.write(result.branch
1015
+ ? `Pinned commit pushed to ${result.branch}, where everything this distribution pins in the fork lives.\n`
1016
+ : 'Nothing pushed. The pinned commit has to be on the fork before anyone else can check it out.\n');
1017
+ process.stdout.write(`\nNext: make the change on a branch in ${modules[0].relativePath}, then ` +
1018
+ `\`fg-dist contribute ${modules[0].name}\`.\nCommit .gitmodules and ${modules[0].relativePath} here.\n`);
1019
+ return 0;
1020
+ }
1021
+ if (args.command === 'contribute') {
1022
+ const [name] = args.names;
1023
+ if (!name) {
1024
+ process.stderr.write('contribute needs a module: fg-dist contribute <name>\n');
1025
+ return 1;
1026
+ }
1027
+ const { root, modules } = modulesFor(args.cwd, [name]);
1028
+ const result = contribute(root, modules[0], { push: args.push, base: args.base });
1029
+ if (!args.push) {
1030
+ process.stdout.write(`Would push ${result.branch} and open a pull request into ${describeRemote(result.repository)} ${result.base}.\n`);
1031
+ return 0;
1032
+ }
1033
+ process.stdout.write(`Pushed ${result.branch}.\n`);
1034
+ if (result.pinnedOn)
1035
+ process.stdout.write(`Pinned commit kept on ${result.pinnedOn}, which outlives the pull request's branch.\n`);
1036
+ if (result.pullRequest) {
1037
+ process.stdout.write(`Opened ${result.pullRequest}\n`);
1038
+ }
1039
+ else if (result.compare) {
1040
+ process.stdout.write(`Open the pull request at\n ${result.compare}\n`);
1041
+ }
1042
+ else {
1043
+ process.stdout.write(`Open a pull request from ${result.branch} into ${result.base} on ${describeRemote(result.repository)}.\n`);
1044
+ }
1045
+ if (result.changelog) {
1046
+ process.stdout.write(`\nIts changelog check wants a line in ${result.changelog.file}, under ${result.changelog.version}. ` +
1047
+ 'The words are yours:\n' +
1048
+ ` ${result.changelog.line}\n` +
1049
+ 'Commit it on the branch and push, then run this again to keep the pin current.\n');
1050
+ }
1051
+ process.stdout.write(`\nCommit ${modules[0].relativePath} here to ship the change meanwhile.\n`);
1052
+ return 0;
1053
+ }
1054
+ if (args.command === 'unfork') {
1055
+ const [name] = args.names;
1056
+ if (!name) {
1057
+ process.stderr.write('unfork needs a module: fg-dist unfork <name>\n');
1058
+ return 1;
1059
+ }
1060
+ const { root, modules } = modulesFor(args.cwd, [name]);
1061
+ const state = inspect(modules[0], { fetch: args.fetch });
1062
+ const result = unforkModule(root, state, { to: args.to, major: args.major, force: args.force });
1063
+ process.stdout.write(`${modules[0].relativePath} is checked out from ${result.url} again, at ${result.to}.\n` +
1064
+ `Commit .gitmodules and ${modules[0].relativePath} here. The fork itself is left as it is.\n`);
736
1065
  return 0;
737
1066
  }
738
1067
  process.stderr.write(`Unknown command: ${args.command}\n\n${USAGE}`);