@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
@@ -0,0 +1,177 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { readFile, readdir, writeFile } from 'node:fs/promises';
3
+ import { existsSync, statSync } from 'node:fs';
4
+ import path from 'node:path';
5
+ import { writeOverrides } from "./overrides.js";
6
+ import semver from 'semver';
7
+ import { repositoryRoot, submodules } from "./submodules.js";
8
+ import { toHttps } from "./git-url.js";
9
+ /**
10
+ * Turn a directory that grew inside a distribution into a module of its own.
11
+ *
12
+ * An app or package is easier to get working in place, where the workspace
13
+ * already resolves it and there is one repository to run. Once it works it
14
+ * wants its own history and its own version, which is what this does: the
15
+ * directory becomes a repository, and the distribution picks it back up as a
16
+ * submodule. Nothing is re-cloned, so it works before anything is pushed.
17
+ */
18
+ const git = (cwd, args) => {
19
+ try {
20
+ return execFileSync('git', args, {
21
+ cwd,
22
+ encoding: 'utf8',
23
+ stdio: ['ignore', 'pipe', 'pipe'],
24
+ }).trim();
25
+ }
26
+ catch (cause) {
27
+ const stderr = cause instanceof Error && 'stderr' in cause ? String(cause.stderr).trim() : '';
28
+ throw new Error(`git ${args[0]} failed${stderr ? `: ${stderr}` : ''}`, { cause });
29
+ }
30
+ };
31
+ const quiet = (cwd, args) => {
32
+ try {
33
+ return execFileSync('git', args, {
34
+ cwd,
35
+ encoding: 'utf8',
36
+ stdio: ['ignore', 'pipe', 'ignore'],
37
+ }).trim();
38
+ }
39
+ catch {
40
+ return undefined;
41
+ }
42
+ };
43
+ const isDirectory = (candidate) => {
44
+ try {
45
+ return statSync(candidate).isDirectory();
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ };
51
+ const readManifest = async (root) => {
52
+ try {
53
+ return JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8'));
54
+ }
55
+ catch {
56
+ return {};
57
+ }
58
+ };
59
+ export const extractModule = async (cwd, target, options) => {
60
+ const root = repositoryRoot(cwd);
61
+ if (!root)
62
+ throw new Error(`${cwd} is not inside a git repository.`);
63
+ const full = path.resolve(cwd, target);
64
+ const relativePath = path.relative(root, full);
65
+ if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
66
+ throw new Error(`${target} is outside ${root}.`);
67
+ }
68
+ if (!isDirectory(full)) {
69
+ throw new Error(`${relativePath} is not a directory.`);
70
+ }
71
+ if (submodules(root).some((module) => module.relativePath === relativePath)) {
72
+ throw new Error(`${relativePath} is already a submodule.`);
73
+ }
74
+ const recorded = options.ssh ? options.url : toHttps(options.url);
75
+ const { name, version } = await readManifest(full);
76
+ // An existing repository here is kept: it may already have the history.
77
+ // Looking for `.git` in the directory itself, because `rev-parse` walks up
78
+ // and would find the distribution's repository instead.
79
+ const alreadyRepository = existsSync(path.join(full, '.git'));
80
+ const initialised = !alreadyRepository;
81
+ if (initialised) {
82
+ git(full, ['init', '--quiet', '-b', 'main']);
83
+ }
84
+ // The distribution's .gitignore stops applying the moment this is its own
85
+ // repository, so without one of its own the first commit would carry
86
+ // node_modules and build output.
87
+ if (!existsSync(path.join(full, '.gitignore'))) {
88
+ await writeFile(path.join(full, '.gitignore'), 'node_modules\n.next\n.turbo\ndist\nout\nnext-env.d.ts\n*.tsbuildinfo\n');
89
+ }
90
+ // Nested packages — a docs site, most often — were members of the
91
+ // distribution's workspace. Out here nothing says so, and pnpm 10 links
92
+ // nothing across a workspace unless told to, so this repository needs the
93
+ // arrangement written down for itself.
94
+ const nested = await nestedPackages(full);
95
+ if (nested.length > 0 && !existsSync(path.join(full, 'pnpm-workspace.yaml'))) {
96
+ await writeFile(path.join(full, 'pnpm-workspace.yaml'), workspaceFile(nested));
97
+ }
98
+ // Commit whatever is there, so the submodule has something to point at.
99
+ if (quiet(full, ['status', '--porcelain']) !== '') {
100
+ git(full, ['add', '-A']);
101
+ git(full, ['commit', '--quiet', '-m', 'Initial commit']);
102
+ }
103
+ if (quiet(full, ['remote', 'get-url', 'origin']) === undefined) {
104
+ git(full, ['remote', 'add', 'origin', recorded]);
105
+ }
106
+ // A module is versioned by tags, so give it the one its manifest declares.
107
+ let tagged;
108
+ if (options.tag !== false && version && semver.valid(version)) {
109
+ const tag = `v${version}`;
110
+ if (quiet(full, ['rev-parse', '--verify', '--quiet', `refs/tags/${tag}`]) === undefined) {
111
+ quiet(full, ['tag', tag]);
112
+ tagged = tag;
113
+ }
114
+ }
115
+ // Untrack the files, then let git adopt the repository that is already here.
116
+ // `submodule add` clones only when the path is empty, so nothing is refetched
117
+ // and this works before the remote exists.
118
+ // Tolerant, so a retry after a half-finished run still works: the files may
119
+ // already be untracked.
120
+ quiet(root, ['rm', '-r', '--quiet', '--cached', relativePath]);
121
+ git(root, ['submodule', 'add', recorded, relativePath]);
122
+ // Now that it is a submodule, the distribution resolves it from the tree
123
+ // rather than through whatever range its dependents declare. The repository
124
+ // is made and the submodule added by this point, so a workspace this cannot
125
+ // edit is reported rather than failing a job that is otherwise done.
126
+ const overridesError = await writeOverrides(root).then(() => undefined, (error) => (error instanceof Error ? error.message : String(error)));
127
+ return {
128
+ overridesError,
129
+ relativePath,
130
+ packageName: name,
131
+ version,
132
+ url: recorded,
133
+ rewritten: recorded !== options.url,
134
+ initialised,
135
+ tagged,
136
+ };
137
+ };
138
+ /** Directories one level down that are packages in their own right. */
139
+ const nestedPackages = async (root) => {
140
+ const entries = await readdir(root, { withFileTypes: true }).catch(() => []);
141
+ return entries
142
+ .filter((entry) => entry.isDirectory() &&
143
+ entry.name !== 'node_modules' &&
144
+ !entry.name.startsWith('.') &&
145
+ existsSync(path.join(root, entry.name, 'package.json')))
146
+ .map((entry) => entry.name)
147
+ .sort();
148
+ };
149
+ /**
150
+ * A workspace covering this repository and the packages inside it.
151
+ *
152
+ * The settings are not decoration: pnpm 10 defaults `linkWorkspacePackages` to
153
+ * false, so without it a nested package resolves its dependency on this one
154
+ * from the registry rather than from the checkout it is sitting in.
155
+ */
156
+ export const workspaceFile = (nested) => [
157
+ '# For when this repository is cloned on its own.',
158
+ '#',
159
+ '# pnpm uses the nearest workspace file to where it is run, so inside a',
160
+ '# distribution this one applies to `pnpm install` run from THIS directory —',
161
+ '# which would build a second workspace with its own lockfile and without the',
162
+ '# distribution\'s overrides. Install from the distribution root instead.',
163
+ 'packages:',
164
+ ' # The workspace root — this module — is always a member; these are the',
165
+ ' # additions.',
166
+ ...nested.map((name) => ` - ${name}`),
167
+ '',
168
+ '# pnpm 10 defaults this to false, so without it the packages above resolve',
169
+ '# their dependency on this module from the registry instead of from the',
170
+ '# checkout next to them.',
171
+ 'linkWorkspacePackages: true',
172
+ '# Keep plain semver ranges in package.json: the range is what a published',
173
+ '# module is installed by, and what a distribution reads to resolve versions.',
174
+ 'saveWorkspaceProtocol: false',
175
+ '',
176
+ ].join('\n');
177
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ import { type Submodule, type SubmoduleState } from './submodules.ts';
2
+ /**
3
+ * The branch in a fork that holds everything this distribution pins, named
4
+ * after the distribution.
5
+ *
6
+ * Not the pull request's branch: GitHub deletes that once it is merged, and a
7
+ * pinned commit has to stay fetchable for as long as anything pins it. Not the
8
+ * fork's main either, which is upstream's to follow.
9
+ */
10
+ export declare const distributionBranch: (root: string) => string;
11
+ /** `https://github.com/acme/id.git` -> `acme/id`; undefined anywhere else. */
12
+ export declare const githubRepo: (url: string) => string | undefined;
13
+ export interface ForkOptions {
14
+ /** Record the url as given instead of rewriting it to https. */
15
+ ssh?: boolean;
16
+ /** Push the pinned commit to the fork, so other clones can check it out. */
17
+ push?: boolean;
18
+ }
19
+ export interface ForkResult {
20
+ /** The fork's url, as recorded in .gitmodules. */
21
+ url: string;
22
+ /** Where it was forked from. */
23
+ upstream: string;
24
+ /** Whether the url was rewritten from ssh. */
25
+ rewritten: boolean;
26
+ /** The branch the pinned commit was pushed to, when it was. */
27
+ branch: string | undefined;
28
+ }
29
+ /**
30
+ * Check a module out from a fork, keeping where it came from.
31
+ *
32
+ * The fork has to exist already — on GitHub, the Fork button — since creating
33
+ * repositories is not something to do behind anyone's back. Forking a fork
34
+ * keeps the original upstream.
35
+ */
36
+ export declare const forkModule: (root: string, submodule: Submodule, url: string, { ssh, push }?: ForkOptions) => ForkResult;
37
+ export interface ContributeOptions {
38
+ /** Push the branch and open the pull request. On by default. */
39
+ push?: boolean;
40
+ /** The branch the pull request goes into; defaults to the upstream's own. */
41
+ base?: string;
42
+ }
43
+ export interface ContributeResult {
44
+ branch: string;
45
+ /** The repository the pull request is against, and its branch. */
46
+ repository: string;
47
+ base: string;
48
+ /** The pull request, when `gh` opened one. */
49
+ pullRequest: string | undefined;
50
+ /** Where to open it by hand otherwise, on GitHub. */
51
+ compare: string | undefined;
52
+ /** The fork's distribution branch, which now holds the pinned commit too. */
53
+ pinnedOn: string | undefined;
54
+ /**
55
+ * The line the module's changelog check wants, when the pull request is
56
+ * open and has none yet: its link filled in, its words left to whoever made
57
+ * the change, and to review.
58
+ */
59
+ changelog?: {
60
+ file: string;
61
+ version: string;
62
+ line: string;
63
+ };
64
+ }
65
+ /**
66
+ * Offer what is on the checkout's branch upstream, as a pull request.
67
+ *
68
+ * For a fork the branch goes to the fork and the pull request to upstream; for
69
+ * a module you can push to, both go to its own repository. A fork also moves
70
+ * its distribution branch up to the commit, since that is what is pinned and
71
+ * the pull request's branch is gone once merged.
72
+ */
73
+ export declare const contribute: (root: string, submodule: Submodule, { push, base }?: ContributeOptions) => ContributeResult;
74
+ export interface Integration {
75
+ /** `moved` when upstream's release already had the fork's work, `merged` otherwise. */
76
+ how: 'moved' | 'merged';
77
+ /** What was pushed to the fork: the distribution branch, or the release's tag. */
78
+ pushed: string | undefined;
79
+ }
80
+ /**
81
+ * Take an upstream release into a fork: what `bump` does for one.
82
+ *
83
+ * When the release already has everything the fork adds, the fork is simply
84
+ * moved to it — upstream has taken the work, and the fork could be dropped.
85
+ * Otherwise the release is merged into the fork, keeping the fork's changes on
86
+ * top, and pushed to the distribution branch. A merge rather than a rebase, so
87
+ * every commit an older distribution pinned stays in the fork.
88
+ */
89
+ export declare const integrate: (root: string, state: SubmoduleState, tag: string, { push }?: {
90
+ push?: boolean;
91
+ }) => Integration;
92
+ export interface UnforkOptions {
93
+ /** What to go back to: an upstream tag, branch or commit. Chosen when omitted. */
94
+ to?: string;
95
+ /** Allow going back to a release with a newer major version. */
96
+ major?: boolean;
97
+ /** Go back to `to` even when it lacks some of what the fork adds. */
98
+ force?: boolean;
99
+ }
100
+ export interface UnforkResult {
101
+ /** The upstream url the module is checked out from again. */
102
+ url: string;
103
+ /** The release or ref it is pinned at. */
104
+ to: string;
105
+ }
106
+ /**
107
+ * Check a forked module out from its upstream again.
108
+ *
109
+ * Only to something that has everything the fork adds, unless forced: going
110
+ * back means the fork's commits stop shipping, and that should be because
111
+ * upstream has them, not by accident. Without `to`, the oldest upstream release
112
+ * from the pinned version on that has them is chosen — going back to upstream
113
+ * is not the moment to upgrade as well.
114
+ */
115
+ export declare const unforkModule: (root: string, state: SubmoduleState, { to, major, force }?: UnforkOptions) => UnforkResult;
116
+ //# sourceMappingURL=forks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forks.d.ts","sourceRoot":"","sources":["../src/forks.ts"],"names":[],"mappings":"AAkBA,OAAO,EAOL,KAAK,SAAS,EACd,KAAK,cAAc,EACpB,MAAM,iBAAiB,CAAA;AAqCxB;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MAUjD,CAAA;AAoBD,8EAA8E;AAC9E,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,SACgC,CAAA;AAYlF,MAAM,WAAW,WAAW;IAC1B,gEAAgE;IAChE,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,4EAA4E;IAC5E,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,UAAU;IACzB,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAA;IACX,gCAAgC;IAChC,QAAQ,EAAE,MAAM,CAAA;IAChB,8CAA8C;IAC9C,SAAS,EAAE,OAAO,CAAA;IAClB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CAC3B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,MAAM,EACZ,WAAW,SAAS,EACpB,KAAK,MAAM,EACX,gBAA8B,WAAgB,KAC7C,UAmCF,CAAA;AAED,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,8CAA8C;IAC9C,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;IAC/B,qDAAqD;IACrD,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5B;;;;OAIG;IACH,SAAS,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5D;AAwCD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,MAAM,EACZ,WAAW,SAAS,EACpB,iBAAuB,iBAAsB,KAC5C,gBAmFF,CAAA;AAED,MAAM,WAAW,WAAW;IAC1B,uFAAuF;IACvF,GAAG,EAAE,OAAO,GAAG,QAAQ,CAAA;IACvB,kFAAkF;IAClF,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CAC3B;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,GACpB,MAAM,MAAM,EACZ,OAAO,cAAc,EACrB,KAAK,MAAM,EACX,WAAiB;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,KACvC,WAuCF,CAAA;AAED,MAAM,WAAW,aAAa;IAC5B,kFAAkF;IAClF,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,gEAAgE;IAChE,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,qEAAqE;IACrE,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,GAAG,EAAE,MAAM,CAAA;IACX,0CAA0C;IAC1C,EAAE,EAAE,MAAM,CAAA;CACX;AAiBD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,GACvB,MAAM,MAAM,EACZ,OAAO,cAAc,EACrB,uBAAsC,aAAkB,KACvD,YAyEF,CAAA"}
package/dist/forks.js ADDED
@@ -0,0 +1,357 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import semver from 'semver';
5
+ import { CHANGELOG, aheadEntry, bumpEntry, crossedReleases, moduleLabel, noteInDistribution, releaseNotesLinks, repositoryLink, shown, topVersion, } from "./changelog.js";
6
+ import { existsSync } from 'node:fs';
7
+ import { describeRemote, toHttps } from "./git-url.js";
8
+ import { UPSTREAM, containsAll, ensureUpstreamRemote, gitmodulesSet, moveTo, setUrl, } from "./submodules.js";
9
+ /**
10
+ * Forking a module, from inside a distribution.
11
+ *
12
+ * A fork is the module checked out from somewhere you can push to, with the
13
+ * repository it came from recorded beside it. You fix or add something there,
14
+ * offer it upstream as a pull request, and meanwhile ship it. Updating works
15
+ * as it does for any module, except that a fork takes upstream's releases by
16
+ * merging them in. Once upstream has everything the fork adds, you can go back
17
+ * to upstream, or keep the fork.
18
+ */
19
+ /** Run git, throwing with what it said when it fails. */
20
+ const run = (cwd, args) => {
21
+ try {
22
+ return execFileSync('git', args, {
23
+ cwd,
24
+ encoding: 'utf8',
25
+ stdio: ['ignore', 'pipe', 'pipe'],
26
+ }).trim();
27
+ }
28
+ catch (cause) {
29
+ const stderr = cause instanceof Error && 'stderr' in cause ? String(cause.stderr).trim() : '';
30
+ throw new Error(`git ${args[0]} failed in ${cwd}${stderr ? `:\n${stderr}` : ''}`, { cause });
31
+ }
32
+ };
33
+ /** Run git, returning undefined rather than throwing when it fails. */
34
+ const attempt = (cwd, args) => {
35
+ try {
36
+ return run(cwd, args);
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ };
42
+ /**
43
+ * The branch in a fork that holds everything this distribution pins, named
44
+ * after the distribution.
45
+ *
46
+ * Not the pull request's branch: GitHub deletes that once it is merged, and a
47
+ * pinned commit has to stay fetchable for as long as anything pins it. Not the
48
+ * fork's main either, which is upstream's to follow.
49
+ */
50
+ export const distributionBranch = (root) => {
51
+ let name;
52
+ try {
53
+ name = JSON.parse(readFileSync(path.join(root, 'package.json'), 'utf8')).name;
54
+ }
55
+ catch {
56
+ name = undefined;
57
+ }
58
+ return (typeof name === 'string' ? name : path.basename(root))
59
+ .replace(/^@/, '')
60
+ .replace(/[^A-Za-z0-9._-]+/g, '-');
61
+ };
62
+ /**
63
+ * Put the checked-out commit on the fork's distribution branch, which only
64
+ * moves forward. `to` is the fork's url, or `origin` once it is the fork.
65
+ */
66
+ const pushPinned = (submodule, branch, to = 'origin') => {
67
+ try {
68
+ run(submodule.path, ['push', '--quiet', to, `HEAD:refs/heads/${branch}`]);
69
+ }
70
+ catch (cause) {
71
+ const fork = to === 'origin' ? submodule.url : to;
72
+ throw new Error(`${branch} on ${describeRemote(fork)} has commits that ${submodule.relativePath} does not. ` +
73
+ `Merge them in first, then run this again:\n` +
74
+ ` cd ${submodule.relativePath} && git fetch ${fork} ${branch} && git merge FETCH_HEAD`, { cause });
75
+ }
76
+ };
77
+ /** `https://github.com/acme/id.git` -> `acme/id`; undefined anywhere else. */
78
+ export const githubRepo = (url) => /^https:\/\/github\.com\/([^/]+\/[^/]+?)(?:\.git)?\/?$/i.exec(toHttps(url))?.[1];
79
+ /** The branch a remote's HEAD names, which is where pull requests go. */
80
+ const defaultBranch = (cwd, remote) => {
81
+ const read = () => attempt(cwd, ['symbolic-ref', '--short', `refs/remotes/${remote}/HEAD`])?.replace(`${remote}/`, '');
82
+ const known = read();
83
+ if (known)
84
+ return known;
85
+ attempt(cwd, ['remote', 'set-head', remote, '--auto']);
86
+ return read() ?? 'main';
87
+ };
88
+ /**
89
+ * Check a module out from a fork, keeping where it came from.
90
+ *
91
+ * The fork has to exist already — on GitHub, the Fork button — since creating
92
+ * repositories is not something to do behind anyone's back. Forking a fork
93
+ * keeps the original upstream.
94
+ */
95
+ export const forkModule = (root, submodule, url, { ssh = false, push = true } = {}) => {
96
+ const fork = ssh ? url : toHttps(url);
97
+ const upstream = submodule.upstream ?? submodule.url;
98
+ if (fork === submodule.url) {
99
+ throw new Error(`${submodule.relativePath} is already checked out from ${fork}.`);
100
+ }
101
+ if (fork === upstream) {
102
+ throw new Error(`${fork} is where ${submodule.relativePath} was forked from. ` +
103
+ `Run \`fg-dist unfork ${submodule.name}\` to go back to it.`);
104
+ }
105
+ // Before anything is written: a fork nobody can read is a pin nobody can fetch.
106
+ if (attempt(submodule.path, ['ls-remote', '--heads', fork]) === undefined) {
107
+ throw new Error(`Could not read ${fork}. Fork ${describeRemote(upstream)} there first, then run this again.`);
108
+ }
109
+ // Pushed before anything is recorded, so a refusal leaves the module as it was.
110
+ const branch = distributionBranch(root);
111
+ if (push)
112
+ pushPinned(submodule, branch, fork);
113
+ gitmodulesSet(root, submodule.configName, 'upstream', upstream);
114
+ setUrl(root, submodule, fork);
115
+ run(submodule.path, ['remote', 'set-url', 'origin', fork]);
116
+ ensureUpstreamRemote(submodule.path, upstream);
117
+ attempt(submodule.path, ['fetch', '--quiet', 'origin']);
118
+ attempt(submodule.path, ['fetch', '--quiet', '--tags', UPSTREAM]);
119
+ const { name } = moduleLabel(submodule.path);
120
+ noteInDistribution(root, `\`${name}\` forked to ${repositoryLink(fork)}, from ${repositoryLink(upstream)}`);
121
+ return { url: fork, upstream, rewritten: fork !== url, branch: push ? branch : undefined };
122
+ };
123
+ /** A pull request's title, as GitHub has it. */
124
+ const pullRequestTitle = (cwd, url) => {
125
+ try {
126
+ return (execFileSync('gh', ['pr', 'view', url, '--json', 'title', '--jq', '.title'], {
127
+ cwd,
128
+ encoding: 'utf8',
129
+ stdio: ['ignore', 'pipe', 'ignore'],
130
+ }).trim() || undefined);
131
+ }
132
+ catch {
133
+ return undefined;
134
+ }
135
+ };
136
+ /**
137
+ * The changelog line a pull request still needs, with its link: only the
138
+ * words are left, which are the author's to write. Undefined for a module
139
+ * without a changelog, or one that links the pull request already.
140
+ */
141
+ const changelogLine = (submodule, pullRequest) => {
142
+ const file = path.join(submodule.path, CHANGELOG);
143
+ if (!existsSync(file))
144
+ return undefined;
145
+ const text = readFileSync(file, 'utf8');
146
+ if (text.toLowerCase().includes(pullRequest.replace(/^https?:\/\//, '').toLowerCase()))
147
+ return undefined;
148
+ const version = topVersion(text);
149
+ const number = /\/pull\/(\d+)/.exec(pullRequest)?.[1];
150
+ if (!version || !number)
151
+ return undefined;
152
+ return {
153
+ file: path.join(submodule.relativePath, CHANGELOG),
154
+ version,
155
+ line: `- What this changes, for someone using it ([#${number}](${pullRequest}))`,
156
+ };
157
+ };
158
+ /**
159
+ * Offer what is on the checkout's branch upstream, as a pull request.
160
+ *
161
+ * For a fork the branch goes to the fork and the pull request to upstream; for
162
+ * a module you can push to, both go to its own repository. A fork also moves
163
+ * its distribution branch up to the commit, since that is what is pinned and
164
+ * the pull request's branch is gone once merged.
165
+ */
166
+ export const contribute = (root, submodule, { push = true, base } = {}) => {
167
+ const cwd = submodule.path;
168
+ const branch = attempt(cwd, ['symbolic-ref', '--short', '-q', 'HEAD']);
169
+ if (!branch) {
170
+ throw new Error(`${submodule.relativePath} is not on a branch. Start one for the change:\n` +
171
+ ` cd ${submodule.relativePath} && git switch -c <name>`);
172
+ }
173
+ if ((attempt(cwd, ['status', '--porcelain']) ?? '') !== '') {
174
+ throw new Error(`${submodule.relativePath} has uncommitted changes. Commit them first.`);
175
+ }
176
+ const fork = submodule.upstream;
177
+ const remote = fork ? UPSTREAM : 'origin';
178
+ const repository = fork ?? submodule.url;
179
+ if (fork) {
180
+ ensureUpstreamRemote(cwd, fork);
181
+ attempt(cwd, ['fetch', '--quiet', UPSTREAM]);
182
+ }
183
+ const into = base ?? defaultBranch(cwd, remote);
184
+ if (!fork && branch === into) {
185
+ throw new Error(`${submodule.relativePath} is on ${into} itself. A pull request needs a branch of its own:\n` +
186
+ ` cd ${submodule.relativePath} && git switch -c <name>`);
187
+ }
188
+ const upstreamRepo = githubRepo(repository);
189
+ const ownRepo = githubRepo(submodule.url);
190
+ const head = fork && ownRepo ? `${ownRepo.split('/')[0]}:${branch}` : branch;
191
+ const compare = upstreamRepo
192
+ ? `https://github.com/${upstreamRepo}/compare/${into}...${head}?expand=1`
193
+ : undefined;
194
+ if (!push) {
195
+ return { branch, repository, base: into, pullRequest: undefined, compare, pinnedOn: undefined };
196
+ }
197
+ run(cwd, ['push', '--quiet', '-u', 'origin', branch]);
198
+ const pinnedOn = fork ? distributionBranch(root) : undefined;
199
+ if (pinnedOn)
200
+ pushPinned(submodule, pinnedOn);
201
+ let pullRequest;
202
+ if (upstreamRepo) {
203
+ try {
204
+ const output = execFileSync('gh', ['pr', 'create', '--repo', upstreamRepo, '--base', into, '--head', head, '--fill'], { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
205
+ pullRequest = /https:\/\/\S+/.exec(output)?.[0];
206
+ }
207
+ catch {
208
+ // No gh, not signed in, or one already open: the compare link does the rest.
209
+ pullRequest = undefined;
210
+ }
211
+ }
212
+ // What the change is, in the distribution's changelog: the pull request's
213
+ // title, or without one, what its last commit says. Never the branch's name,
214
+ // which means nothing to anyone reading it.
215
+ const title = (pullRequest && pullRequestTitle(cwd, pullRequest)) ||
216
+ attempt(cwd, ['log', '-1', '--format=%s']) ||
217
+ branch;
218
+ // Linked where it can be read about: its pull request, or else its commit.
219
+ const { name } = moduleLabel(cwd);
220
+ const number = pullRequest && /\/pull\/(\d+)/.exec(pullRequest)?.[1];
221
+ const tip = attempt(cwd, ['rev-parse', 'HEAD']);
222
+ const link = pullRequest && upstreamRepo && number
223
+ ? { text: `${upstreamRepo}#${number}`, url: pullRequest }
224
+ : ownRepo && tip
225
+ ? { text: `${ownRepo}@${tip.slice(0, 7)}`, url: `https://github.com/${ownRepo}/commit/${tip}` }
226
+ : undefined;
227
+ noteInDistribution(root, aheadEntry(name, title, link));
228
+ // The module's own line is written by whoever made the change, and reviewed
229
+ // with it; only its link is known here.
230
+ const changelog = pullRequest ? changelogLine(submodule, pullRequest) : undefined;
231
+ return { branch, repository, base: into, pullRequest, compare, pinnedOn, changelog };
232
+ };
233
+ /**
234
+ * Take an upstream release into a fork: what `bump` does for one.
235
+ *
236
+ * When the release already has everything the fork adds, the fork is simply
237
+ * moved to it — upstream has taken the work, and the fork could be dropped.
238
+ * Otherwise the release is merged into the fork, keeping the fork's changes on
239
+ * top, and pushed to the distribution branch. A merge rather than a rebase, so
240
+ * every commit an older distribution pinned stays in the fork.
241
+ */
242
+ export const integrate = (root, state, tag, { push = true } = {}) => {
243
+ const { submodule } = state;
244
+ const cwd = submodule.path;
245
+ const upstream = submodule.upstream ?? submodule.url;
246
+ const { name } = moduleLabel(cwd);
247
+ const from = state.current ?? state.head.slice(0, 7);
248
+ const releases = crossedReleases(state.available, tag);
249
+ if (containsAll(cwd, `refs/tags/${tag}`, state.head)) {
250
+ moveTo(submodule, tag);
251
+ // The fork may not have the release yet, and it is what is pinned now.
252
+ if (push)
253
+ run(cwd, ['push', '--quiet', 'origin', `refs/tags/${tag}`]);
254
+ noteInDistribution(root, bumpEntry(name, from, tag, upstream, releases, 'which has everything the fork added'));
255
+ return { how: 'moved', pushed: push ? tag : undefined };
256
+ }
257
+ try {
258
+ run(cwd, [
259
+ 'merge',
260
+ '--no-edit',
261
+ '--quiet',
262
+ '-m',
263
+ `Merge ${tag} from ${describeRemote(upstream)}`,
264
+ `refs/tags/${tag}`,
265
+ ]);
266
+ }
267
+ catch (cause) {
268
+ attempt(cwd, ['merge', '--abort']);
269
+ throw new Error(`${tag} does not merge cleanly into ${submodule.relativePath}. Merge it by hand, ` +
270
+ `then push, and commit the new pin:\n cd ${submodule.relativePath} && git merge ${tag}`, { cause });
271
+ }
272
+ const branch = distributionBranch(root);
273
+ if (push)
274
+ pushPinned(submodule, branch);
275
+ noteInDistribution(root, bumpEntry(name, from, tag, upstream, releases, 'merged into the fork'));
276
+ return { how: 'merged', pushed: push ? branch : undefined };
277
+ };
278
+ /** Commits the fork has that `base` does not, for saying what would be lost. */
279
+ const notIn = (cwd, base) => (attempt(cwd, [
280
+ 'log',
281
+ '--no-merges',
282
+ '--cherry-pick',
283
+ '--right-only',
284
+ '--format=%h %s',
285
+ `${base}...HEAD`,
286
+ ]) ?? '')
287
+ .split('\n')
288
+ .filter(Boolean);
289
+ /**
290
+ * Check a forked module out from its upstream again.
291
+ *
292
+ * Only to something that has everything the fork adds, unless forced: going
293
+ * back means the fork's commits stop shipping, and that should be because
294
+ * upstream has them, not by accident. Without `to`, the oldest upstream release
295
+ * from the pinned version on that has them is chosen — going back to upstream
296
+ * is not the moment to upgrade as well.
297
+ */
298
+ export const unforkModule = (root, state, { to, major = false, force = false } = {}) => {
299
+ const { submodule } = state;
300
+ const cwd = submodule.path;
301
+ const upstream = submodule.upstream;
302
+ if (!upstream) {
303
+ throw new Error(`${submodule.relativePath} is not a fork; it is checked out from ${submodule.url}.`);
304
+ }
305
+ if (state.dirty) {
306
+ throw new Error(`${submodule.relativePath} has uncommitted changes. Commit or drop them first.`);
307
+ }
308
+ let target;
309
+ let label;
310
+ if (to) {
311
+ const ref = [`refs/tags/${to}`, `${UPSTREAM}/${to}`, to].find((candidate) => attempt(cwd, ['rev-parse', '--verify', '--quiet', `${candidate}^{commit}`]) !== undefined);
312
+ if (!ref)
313
+ throw new Error(`${to} is not a tag, upstream branch or commit in ${submodule.relativePath}.`);
314
+ const lost = containsAll(cwd, ref, state.head) ? [] : notIn(cwd, ref);
315
+ if (lost.length > 0 && !force) {
316
+ throw new Error(`${to} does not have everything the fork adds:\n${lost.map((line) => ` ${line}`).join('\n')}\n` +
317
+ 'Pass --force to go back anyway and stop shipping them.');
318
+ }
319
+ target = ref;
320
+ label = to;
321
+ }
322
+ else {
323
+ const merged = state.fork?.merged;
324
+ if (!merged) {
325
+ const tip = `${UPSTREAM}/HEAD`;
326
+ const lost = notIn(cwd, attempt(cwd, ['rev-parse', '--verify', '--quiet', tip]) ? tip : 'HEAD');
327
+ throw new Error(`Upstream has not taken everything ${submodule.relativePath} adds yet:\n` +
328
+ `${lost.map((line) => ` ${line}`).join('\n')}\n` +
329
+ 'Keep the fork until it has, or pass --to <tag> --force to go back without them.');
330
+ }
331
+ if (!semver.valid(merged)) {
332
+ throw new Error(`Only ${merged} has everything ${submodule.relativePath} adds, and it is not a release yet. ` +
333
+ `Wait for one, or pass --to ${merged.replace(`${UPSTREAM}/`, '')} to go back to the branch.`);
334
+ }
335
+ if (!major && state.current && semver.major(merged) !== semver.major(state.current)) {
336
+ throw new Error(`The first release with everything ${submodule.relativePath} adds is ${merged}, a major ` +
337
+ 'upgrade. Pass --major to go back to it.');
338
+ }
339
+ target = `refs/tags/${merged}`;
340
+ label = merged;
341
+ }
342
+ const lost = containsAll(cwd, target, state.head) ? [] : notIn(cwd, target);
343
+ run(cwd, ['checkout', '--quiet', `${target}^{commit}`]);
344
+ setUrl(root, submodule, upstream);
345
+ run(cwd, ['remote', 'set-url', 'origin', upstream]);
346
+ gitmodulesSet(root, submodule.configName, 'upstream', undefined);
347
+ attempt(cwd, ['remote', 'remove', UPSTREAM]);
348
+ const { name } = moduleLabel(cwd);
349
+ const notes = semver.valid(label) ? releaseNotesLinks(upstream, crossedReleases(state.available, label)) : '';
350
+ noteInDistribution(root, `\`${name}\` back to ${repositoryLink(upstream)} at ${shown(label)}, ` +
351
+ (lost.length > 0
352
+ ? `leaving behind ${lost.length} commit${lost.length === 1 ? '' : 's'} of the fork's that upstream does not have`
353
+ : 'which has everything the fork added') +
354
+ notes);
355
+ return { url: upstream, to: label };
356
+ };
357
+ //# sourceMappingURL=forks.js.map