@angelmsger/fx 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/README.md +7 -8
- package/defaults/tools.yaml +9 -0
- package/dist/adapters/ci/github-actions/ci.js +95 -0
- package/dist/adapters/ci/github-actions/ci.js.map +1 -0
- package/dist/adapters/ci/github-actions/provider.js +33 -0
- package/dist/adapters/ci/github-actions/provider.js.map +1 -0
- package/dist/adapters/code-host/github/provider.js +207 -0
- package/dist/adapters/code-host/github/provider.js.map +1 -0
- package/dist/adapters/github/gh.js +103 -0
- package/dist/adapters/github/gh.js.map +1 -0
- package/dist/adapters/process/launch.js +55 -0
- package/dist/adapters/process/launch.js.map +1 -0
- package/dist/adapters/process/node-process.js +3 -3
- package/dist/adapters/process/node-process.js.map +1 -1
- package/dist/adapters/process/powershell.js +13 -2
- package/dist/adapters/process/powershell.js.map +1 -1
- package/dist/adapters/process/stdio-bridge.js +2 -2
- package/dist/adapters/process/stdio-bridge.js.map +1 -1
- package/dist/application/docs/derive.js +14 -5
- package/dist/application/docs/derive.js.map +1 -1
- package/dist/application/import.js +2 -1
- package/dist/application/import.js.map +1 -1
- package/dist/application/login/collect.js +12 -6
- package/dist/application/login/collect.js.map +1 -1
- package/dist/application/release/ci.js +15 -4
- package/dist/application/release/ci.js.map +1 -1
- package/dist/application/release/errors.js +4 -0
- package/dist/application/release/errors.js.map +1 -1
- package/dist/application/release/facts.js +24 -3
- package/dist/application/release/facts.js.map +1 -1
- package/dist/application/release/index.js +14 -5
- package/dist/application/release/index.js.map +1 -1
- package/dist/application/release/repo.js +109 -0
- package/dist/application/release/repo.js.map +1 -0
- package/dist/application/sync/checks.js +11 -0
- package/dist/application/sync/checks.js.map +1 -1
- package/dist/application/sync/stages.js +11 -1
- package/dist/application/sync/stages.js.map +1 -1
- package/dist/application/workspace/go-work.js +77 -0
- package/dist/application/workspace/go-work.js.map +1 -0
- package/dist/application/workspace/resources.js +9 -0
- package/dist/application/workspace/resources.js.map +1 -1
- package/dist/application/workspace/root-entries.js +3 -0
- package/dist/application/workspace/root-entries.js.map +1 -1
- package/dist/cli/base.js +3 -1
- package/dist/cli/base.js.map +1 -1
- package/dist/cli/commands/aliases.js +1 -0
- package/dist/cli/commands/aliases.js.map +1 -1
- package/dist/cli/commands/index.js +3 -3
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/{release.js → release/check.js} +14 -13
- package/dist/cli/commands/release/check.js.map +1 -0
- package/dist/cli/commands/self/info.js +2 -2
- package/dist/cli/commands/self/info.js.map +1 -1
- package/dist/cli/flags.js +2 -2
- package/dist/cli/flags.js.map +1 -1
- package/dist/cli/help.js +2 -8
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/release-view.js +105 -17
- package/dist/cli/release-view.js.map +1 -1
- package/dist/cli/render/report.js +1 -1
- package/dist/cli/render/report.js.map +1 -1
- package/dist/cli/route/index.js +13 -1
- package/dist/cli/route/index.js.map +1 -1
- package/dist/composition/bundled.js +4 -1
- package/dist/composition/bundled.js.map +1 -1
- package/dist/composition/registry.js +4 -0
- package/dist/composition/registry.js.map +1 -1
- package/dist/domain/manifest/files.js +8 -7
- package/dist/domain/manifest/files.js.map +1 -1
- package/dist/domain/manifest/lint.js +4 -3
- package/dist/domain/manifest/lint.js.map +1 -1
- package/dist/domain/manifest/workspace.schema.js +9 -0
- package/dist/domain/manifest/workspace.schema.js.map +1 -1
- package/dist/domain/release/ci.js +11 -8
- package/dist/domain/release/ci.js.map +1 -1
- package/dist/domain/release/deps.js +3 -1
- package/dist/domain/release/deps.js.map +1 -1
- package/dist/domain/release/errors.js +8 -8
- package/dist/domain/release/errors.js.map +1 -1
- package/dist/domain/release/merge.js +26 -13
- package/dist/domain/release/merge.js.map +1 -1
- package/dist/domain/release/policy.js +2 -0
- package/dist/domain/release/policy.js.map +1 -1
- package/dist/domain/release/types.js +2 -1
- package/dist/domain/release/types.js.map +1 -1
- package/dist/domain/release/version.js +133 -0
- package/dist/domain/release/version.js.map +1 -0
- package/dist/domain/release/window.js +1 -1
- package/dist/domain/release/window.js.map +1 -1
- package/dist/domain/setup/planner.js +7 -5
- package/dist/domain/setup/planner.js.map +1 -1
- package/dist/domain/workspace/commits.js +4 -1
- package/dist/domain/workspace/commits.js.map +1 -1
- package/dist/domain/workspace/go-work.js +75 -0
- package/dist/domain/workspace/go-work.js.map +1 -0
- package/dist/domain/workspace/ref.js +3 -0
- package/dist/domain/workspace/ref.js.map +1 -1
- package/package.json +4 -1
- package/skills/fx-workspace/SKILL.md +1 -1
- package/skills/fx-workspace/references/commands.md +33 -31
- package/skills/fx-workspace/references/workspace-tasks.md +6 -5
- package/dist/cli/commands/release.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,62 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## 0.11.0 (2026-10-12)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Keep a `go.work` at the workspace root when the manifest sets `go.workspace: true`.
|
|
10
|
+
`fx init` and `fx sync` list every selected checkout that has a `go.mod` at the
|
|
11
|
+
newest `go` version they require, so each module builds against its siblings'
|
|
12
|
+
checkouts. A repository not cloned yet is left out, fx replaces or removes only
|
|
13
|
+
the `go.work` it wrote, and `fx sync --only go` refreshes it alone. `go.work` and
|
|
14
|
+
`go.work.sum` no longer count as stray root entries in such a workspace.
|
|
15
|
+
- Bind a workspace to GitHub. The `github` code-host provider and the
|
|
16
|
+
`github-actions` ci provider both drive the GitHub CLI `gh`, which joins the
|
|
17
|
+
tool catalog with one sign-in per host for both. `fx release check` reads
|
|
18
|
+
branches, commits, files and the last workflow run of a branch from GitHub just
|
|
19
|
+
as it does from Bitbucket and Jenkins; a ci environment can narrow the runs it
|
|
20
|
+
counts to one branch and one trigger. Opening pull requests through fx is not
|
|
21
|
+
available on GitHub yet.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Rename `fx release` to `fx release check`, so the name says it checks a release
|
|
26
|
+
rather than making one. `fx release` keeps working as a compatibility name.
|
|
27
|
+
- Say what each release would ship. `fx release check` opens each repository with
|
|
28
|
+
the version its target branch declares, the head commits of both branches and how
|
|
29
|
+
many commits on the source branch are not merged into the target yet, by how many
|
|
30
|
+
people; repositories whose checks all passed close the output one line each with the
|
|
31
|
+
same. JSON reports gain `repos[]` with these facts, summaries included; every
|
|
32
|
+
existing field is unchanged.
|
|
33
|
+
- Name the commit a CI build is judged against, say "not merged into main" and "not
|
|
34
|
+
merged back into dev" in the merge check, and use each platform's own name for the
|
|
35
|
+
job or project in CI and error messages. CI evidence gains `head`.
|
|
36
|
+
- Show a repository's `release.note` in the release report, as the manifest schema
|
|
37
|
+
describes.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- Resolve every relative link in the source AGENTS.md that names a file of the
|
|
42
|
+
manifest source, such as `CONTRIBUTING.md`, in the guide derived at the
|
|
43
|
+
workspace root. Only links into `README.md`, `docs/` and `skills/` resolved
|
|
44
|
+
before, so the others pointed at files the root does not have.
|
|
45
|
+
- Stop `fx manifest lint` from asking for a code-host provider when every
|
|
46
|
+
repository names its `upstream`, since such a workspace reaches no code host.
|
|
47
|
+
- Pass every argument intact to a companion that Windows starts through a batch
|
|
48
|
+
file (`.cmd`, as npm, Scoop and Chocolatey install them). cmd.exe no longer
|
|
49
|
+
interprets characters such as `&`, `|`, `%` or `^` in an argument.
|
|
50
|
+
- Read a local Git remote given as a Windows path, such as `C:\host\fx\api.git`,
|
|
51
|
+
as project and name, so import, adoption, forks and `fx source push` work with it.
|
|
52
|
+
Manifest documents are named with forward slashes in stdin bundles and source
|
|
53
|
+
contributions on every platform, and import writes repository paths with them.
|
|
54
|
+
- Stop the PowerShell hook from writing task results into the agent's context,
|
|
55
|
+
passing fx its input with a byte order mark, and writing progress records to
|
|
56
|
+
stderr.
|
|
57
|
+
- Link source files with forward slashes in guides derived on Windows, and report
|
|
58
|
+
the bundled tools catalog in `fx self info` as a file path, also when the install
|
|
59
|
+
path contains spaces.
|
|
60
|
+
|
|
5
61
|
## 0.10.1 (2026-10-12)
|
|
6
62
|
|
|
7
63
|
### Added
|
package/README.md
CHANGED
|
@@ -118,7 +118,7 @@ For everyday updates use `fx sync`; `fx status` shows repository state. To work
|
|
|
118
118
|
| List capabilities | `fx can` |
|
|
119
119
|
| Check a capability | `fx can <capability> --env <environment>` |
|
|
120
120
|
| Refresh guides and Skills | `fx sync --only docs,skills` |
|
|
121
|
-
|
|
|
121
|
+
| Check what is ready to release | `fx release check` |
|
|
122
122
|
|
|
123
123
|
Use `repo list`, `env list`, `agent list` and `tool list` to discover valid names.
|
|
124
124
|
Use `repo select` and `env select` to choose an exact set, `--all` to follow the
|
|
@@ -126,7 +126,8 @@ team's full set including future additions, or `--none` to select nothing.
|
|
|
126
126
|
`pick/unpick` add or remove individual names. First-time setup can limit cloning
|
|
127
127
|
with `fx init ./work --from <manifest-repository> --repos example-api,example-web --dry-run`.
|
|
128
128
|
`where` prints coordinates and commands; it does not execute those commands.
|
|
129
|
-
`release`
|
|
129
|
+
`release check` shows what each repository would release and whether it is ready; it
|
|
130
|
+
never publishes or deploys.
|
|
130
131
|
The [CLI reference](docs/cli/README.md) and `fx <command> --help` own exact syntax.
|
|
131
132
|
|
|
132
133
|
## For coding agents
|
|
@@ -143,12 +144,10 @@ pending writes; inspect them before retrying.
|
|
|
143
144
|
step fails. Read those results alongside stderr errors before retrying; the
|
|
144
145
|
[user guide](docs/user-guide.md) describes the result and recovery fields.
|
|
145
146
|
|
|
146
|
-
`init`, `sync`, `doctor` and `release` summarize JSON by default
|
|
147
|
-
for
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
separate from diagnostic stderr. Do not repeat a write just to obtain a different
|
|
151
|
-
view of its output.
|
|
147
|
+
`init`, `sync`, `doctor` and `release check` summarize JSON by default; the
|
|
148
|
+
[user guide](docs/user-guide.md#for-coding-agents) explains the full and projected
|
|
149
|
+
views. Keep stdout separate from diagnostic stderr. Do not repeat a write just to
|
|
150
|
+
obtain a different view of its output.
|
|
152
151
|
|
|
153
152
|
For `init`, `sync` and `doctor`, read `preparation.state` and the scoped blockers.
|
|
154
153
|
Incomplete preparation exits 12; continuation is `fx init --resume --workspace <name>`.
|
package/defaults/tools.yaml
CHANGED
|
@@ -46,6 +46,15 @@ tools:
|
|
|
46
46
|
desc: Issues, comments, transitions and worklogs in Jira
|
|
47
47
|
install: [{ via: npm, spec: '@angelmsger/jira-cli' }]
|
|
48
48
|
docs: https://github.com/AngelMsger/jira-cli
|
|
49
|
+
- name: gh
|
|
50
|
+
min_version: '2.50.0'
|
|
51
|
+
desc: Repositories, branches and Actions runs on GitHub
|
|
52
|
+
install:
|
|
53
|
+
- { via: brew, spec: gh }
|
|
54
|
+
- { via: manual }
|
|
55
|
+
skill: { command: [] }
|
|
56
|
+
auth: { command: [auth, status] }
|
|
57
|
+
docs: https://cli.github.com/
|
|
49
58
|
- name: sentry
|
|
50
59
|
min_version: '0.44.1'
|
|
51
60
|
desc: Issues, events and releases in Sentry
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// GitHub Actions through gh: a job is one workflow of one repository, narrowed to the
|
|
2
|
+
// runs of one branch and one trigger when the coordinate names them. One workflow often
|
|
3
|
+
// runs on pushes, pull requests and by hand, and only the coordinate knows which of
|
|
4
|
+
// those builds what ships. A workflow that is not there is an answer (undefined), a
|
|
5
|
+
// workflow that cannot be read is an error.
|
|
6
|
+
import { EXIT, objectOf, queryFailed, stringAt, unreadableError } from '../../family/cli.js';
|
|
7
|
+
import { BINARY, GhCli, qualifiedRepository } from '../../github/gh.js';
|
|
8
|
+
import { TemplatePlatform } from '../../platform/template-platform.js';
|
|
9
|
+
export const ID = 'github-actions';
|
|
10
|
+
const QUERY = { timeoutMs: 15_000 };
|
|
11
|
+
const RUNS = ['run', 'list', '--repo', '{repository}', '--workflow', '{workflow}'];
|
|
12
|
+
const FIELDS = 'number,status,conclusion,startedAt,createdAt,url';
|
|
13
|
+
// A run still on its way, by gh's status; a finished run says how it ended instead.
|
|
14
|
+
const PENDING = new Set(['queued', 'in_progress', 'waiting', 'requested', 'pending']);
|
|
15
|
+
const CONCLUSIONS = {
|
|
16
|
+
success: 'success',
|
|
17
|
+
failure: 'failure',
|
|
18
|
+
timed_out: 'failure',
|
|
19
|
+
startup_failure: 'failure',
|
|
20
|
+
cancelled: 'aborted',
|
|
21
|
+
skipped: 'not_built',
|
|
22
|
+
};
|
|
23
|
+
export class GitHubActionsCi {
|
|
24
|
+
process;
|
|
25
|
+
id = ID;
|
|
26
|
+
capability = 'ci';
|
|
27
|
+
platform = new TemplatePlatform({
|
|
28
|
+
id: ID,
|
|
29
|
+
capability: 'ci',
|
|
30
|
+
label: 'GitHub Actions workflow {workflow} in {repository}',
|
|
31
|
+
optionalFlags: { branch: '--branch', event: '--event' },
|
|
32
|
+
command: [BINARY, ...RUNS],
|
|
33
|
+
probe: { label: 'workflow {workflow} in {repository}', command: [BINARY, ...RUNS] },
|
|
34
|
+
});
|
|
35
|
+
constructor(process) {
|
|
36
|
+
this.process = process;
|
|
37
|
+
}
|
|
38
|
+
locate(vars) {
|
|
39
|
+
return this.platform.locate(qualified(vars));
|
|
40
|
+
}
|
|
41
|
+
probe(vars) {
|
|
42
|
+
return this.platform.probe(qualified(vars));
|
|
43
|
+
}
|
|
44
|
+
async lastBuild(vars) {
|
|
45
|
+
const { repository = '', workflow = '', branch, event } = qualified(vars);
|
|
46
|
+
const what = `the last run of ${workflow} in ${repository}${branch ? ` on ${branch}` : ''}`;
|
|
47
|
+
const call = await new GhCli(this.process).call([
|
|
48
|
+
'run',
|
|
49
|
+
'list',
|
|
50
|
+
'--repo',
|
|
51
|
+
repository,
|
|
52
|
+
'--workflow',
|
|
53
|
+
workflow,
|
|
54
|
+
...(branch ? ['--branch', branch] : []),
|
|
55
|
+
...(event ? ['--event', event] : []),
|
|
56
|
+
'--limit',
|
|
57
|
+
'1',
|
|
58
|
+
'--json',
|
|
59
|
+
FIELDS,
|
|
60
|
+
], QUERY);
|
|
61
|
+
if (call.code === EXIT.not_found)
|
|
62
|
+
return undefined;
|
|
63
|
+
if (call.code !== 0 || !Array.isArray(call.data))
|
|
64
|
+
throw queryFailed(BINARY, call, `read ${what}`);
|
|
65
|
+
// A workflow that never ran on that branch has no build to judge.
|
|
66
|
+
if (call.data.length === 0)
|
|
67
|
+
return undefined;
|
|
68
|
+
const run = objectOf(call.data[0]);
|
|
69
|
+
if (run === undefined || typeof run['number'] !== 'number')
|
|
70
|
+
throw unreadableError(BINARY, what);
|
|
71
|
+
return {
|
|
72
|
+
number: run['number'],
|
|
73
|
+
status: statusOf(run),
|
|
74
|
+
startedAt: stringAt(run, 'startedAt') ?? stringAt(run, 'createdAt') ?? null,
|
|
75
|
+
url: stringAt(run, 'url') ?? null,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Every command, run or printed, names the repository's host, so GH_HOST cannot send
|
|
80
|
+
// it elsewhere.
|
|
81
|
+
function qualified(vars) {
|
|
82
|
+
const { repository } = vars;
|
|
83
|
+
return repository === undefined
|
|
84
|
+
? vars
|
|
85
|
+
: { ...vars, repository: qualifiedRepository(repository).repository };
|
|
86
|
+
}
|
|
87
|
+
function statusOf(run) {
|
|
88
|
+
const status = stringAt(run, 'status') ?? '';
|
|
89
|
+
if (PENDING.has(status))
|
|
90
|
+
return 'building';
|
|
91
|
+
if (status !== 'completed')
|
|
92
|
+
return 'unknown';
|
|
93
|
+
return CONCLUSIONS[stringAt(run, 'conclusion') ?? ''] ?? 'unknown';
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=ci.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci.js","sourceRoot":"","sources":["../../../../src/adapters/ci/github-actions/ci.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// GitHub Actions through gh: one sign-in, shared with the github code host; each
|
|
2
|
+
// environment names the workflow, branch and trigger that build it.
|
|
3
|
+
import { BINARY, GH_RECIPES, qualifiedRepository } from '../../github/gh.js';
|
|
4
|
+
import { GitHubActionsCi, ID } from './ci.js';
|
|
5
|
+
export const githubActionsProvider = {
|
|
6
|
+
id: ID,
|
|
7
|
+
capability: 'ci',
|
|
8
|
+
tools: [BINARY],
|
|
9
|
+
recipes: GH_RECIPES,
|
|
10
|
+
vars: [
|
|
11
|
+
{
|
|
12
|
+
name: 'repository',
|
|
13
|
+
desc: '[HOST/]OWNER/NAME of the repository the workflows live in (github.com without a host), may use {repo}',
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
envVars: [
|
|
18
|
+
{ name: 'workflow', desc: 'workflow file name or name, e.g. ci.yml', required: true },
|
|
19
|
+
{
|
|
20
|
+
name: 'branch',
|
|
21
|
+
desc: 'only runs of this branch; without it, the latest run on any branch',
|
|
22
|
+
required: false,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'event',
|
|
26
|
+
desc: 'only runs this trigger started, e.g. push; without it, any trigger',
|
|
27
|
+
required: false,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
recipeVars: (vars) => ({ hostname: qualifiedRepository(vars['repository'] ?? '').hostname }),
|
|
31
|
+
create: (deps) => new GitHubActionsCi(deps.process),
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../../src/adapters/ci/github-actions/provider.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,oEAAoE;AAIpE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE7E,OAAO,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE9C,MAAM,CAAC,MAAM,qBAAqB,GAA2B;IAC3D,EAAE,EAAE,EAAE;IACN,UAAU,EAAE,IAAI;IAChB,KAAK,EAAE,CAAC,MAAM,CAAC;IACf,OAAO,EAAE,UAAU;IACnB,IAAI,EAAE;QACJ;YACE,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,uGAAuG;YAC7G,QAAQ,EAAE,IAAI;SACf;KACF;IACD,OAAO,EAAE;QACP,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,yCAAyC,EAAE,QAAQ,EAAE,IAAI,EAAE;QACrF;YACE,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,oEAAoE;YAC1E,QAAQ,EAAE,KAAK;SAChB;QACD;YACE,IAAI,EAAE,OAAO;YACb,IAAI,EAAE,oEAAoE;YAC1E,QAAQ,EAAE,KAAK;SAChB;KACF;IACD,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,mBAAmB,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5F,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;CACpD,CAAC"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// GitHub as the code host, through gh. Branches, ranges, commits and files come from
|
|
2
|
+
// the REST API by way of `gh api`, so gh holds the credential and fx never sees it.
|
|
3
|
+
// Pull requests are not wired yet: contribution workflows report this host as
|
|
4
|
+
// unsupported instead of half-working.
|
|
5
|
+
import { DevkitError } from '../../../domain/errors.js';
|
|
6
|
+
import { repoUrl } from '../../../domain/workspace/ref.js';
|
|
7
|
+
import { EXIT, objectOf, queryFailed, stringAt, unreadableError } from '../../family/cli.js';
|
|
8
|
+
import { BINARY, DEFAULT_HOSTNAME, GH_RECIPES, GhCli, jsonLines, } from '../../github/gh.js';
|
|
9
|
+
const QUERY = { timeoutMs: 15_000 };
|
|
10
|
+
// The fields of a commit the release check reads, so a commit's diff never crosses the pipe.
|
|
11
|
+
const COMMIT = '{sha, author: .commit.author.name, date: .commit.committer.date, message: .commit.message, parents: [.parents[].sha]}';
|
|
12
|
+
export class GitHubCodeHost {
|
|
13
|
+
vars;
|
|
14
|
+
supportsContributions = false;
|
|
15
|
+
gh;
|
|
16
|
+
constructor(deps, vars) {
|
|
17
|
+
this.vars = vars;
|
|
18
|
+
this.gh = new GhCli(deps.process);
|
|
19
|
+
}
|
|
20
|
+
async repositoryIdentity(_ref) {
|
|
21
|
+
throw contributionUnsupported();
|
|
22
|
+
}
|
|
23
|
+
async pullRequest(_input) {
|
|
24
|
+
throw contributionUnsupported();
|
|
25
|
+
}
|
|
26
|
+
async createPullRequest(_input) {
|
|
27
|
+
throw contributionUnsupported();
|
|
28
|
+
}
|
|
29
|
+
async whoami() {
|
|
30
|
+
const call = await this.api('user', ['--jq', '.login'], { timeoutMs: 5000 });
|
|
31
|
+
const user = call.stdout.trim();
|
|
32
|
+
if (call.code !== 0 || user === '')
|
|
33
|
+
throw queryFailed(BINARY, call, 'tell who is signed in');
|
|
34
|
+
return { user };
|
|
35
|
+
}
|
|
36
|
+
async canRead(ref) {
|
|
37
|
+
const call = await this.api(`repos/${slug(ref)}`, ['--jq', '.full_name'], { timeoutMs: 5000 });
|
|
38
|
+
if (call.code === 0)
|
|
39
|
+
return 'readable';
|
|
40
|
+
if (call.code === EXIT.permission)
|
|
41
|
+
return 'forbidden';
|
|
42
|
+
// GitHub answers 404 for a private repository the person cannot see, too.
|
|
43
|
+
if (call.code === EXIT.not_found)
|
|
44
|
+
return 'missing';
|
|
45
|
+
return 'unknown';
|
|
46
|
+
}
|
|
47
|
+
async fork(ref) {
|
|
48
|
+
const { user } = await this.whoami();
|
|
49
|
+
const url = this.forkUrl(user, ref);
|
|
50
|
+
const existing = await this.api(`repos/${user}/${ref.name}`, ['--jq', '{fork, parent: .parent.full_name, source: .source.full_name}'], QUERY);
|
|
51
|
+
if (existing.code === 0) {
|
|
52
|
+
// Being a fork does not say of which repository: only one forked from this
|
|
53
|
+
// upstream, or from a fork whose network starts at it, can carry its pull requests.
|
|
54
|
+
const found = objectOf(existing.data) ?? {};
|
|
55
|
+
const of = [stringAt(found, 'parent'), stringAt(found, 'source')];
|
|
56
|
+
if (of.some((name) => name?.toLowerCase() === slug(ref).toLowerCase()))
|
|
57
|
+
return { url, created: false };
|
|
58
|
+
// A repository of that name that is not this fork would make gh pick another name.
|
|
59
|
+
throw forkFailed(ref, found['fork'] === true
|
|
60
|
+
? `${user}/${ref.name} is a fork of ${of[0] ?? 'another repository'}`
|
|
61
|
+
: `${user}/${ref.name} exists and is not a fork`);
|
|
62
|
+
}
|
|
63
|
+
const call = await this.gh.call(['repo', 'fork', `${this.hostname}/${slug(ref)}`, '--clone=false', '--remote=false'], QUERY);
|
|
64
|
+
if (call.code !== 0)
|
|
65
|
+
throw forkFailed(ref, call.error?.message ?? `exit ${call.code}`);
|
|
66
|
+
return { url, created: true };
|
|
67
|
+
}
|
|
68
|
+
async branches(ref) {
|
|
69
|
+
const [list, repo] = await Promise.all([
|
|
70
|
+
this.api(`repos/${slug(ref)}/branches?per_page=100`, [
|
|
71
|
+
'--paginate',
|
|
72
|
+
'--jq',
|
|
73
|
+
'.[] | {name, sha: .commit.sha}',
|
|
74
|
+
]),
|
|
75
|
+
this.api(`repos/${slug(ref)}`, ['--jq', '.default_branch']),
|
|
76
|
+
]);
|
|
77
|
+
if (list.code !== 0)
|
|
78
|
+
throw queryFailed(BINARY, list, `list the branches of ${slug(ref)}`);
|
|
79
|
+
if (repo.code !== 0)
|
|
80
|
+
throw queryFailed(BINARY, repo, `read the default branch of ${slug(ref)}`);
|
|
81
|
+
const defaultBranch = repo.stdout.trim();
|
|
82
|
+
return records(list, `the branches of ${slug(ref)}`).map((item) => {
|
|
83
|
+
const name = stringAt(item, 'name');
|
|
84
|
+
const sha = stringAt(item, 'sha');
|
|
85
|
+
if (name === undefined || sha === undefined)
|
|
86
|
+
throw unreadableError(BINARY, `a branch of ${slug(ref)}`);
|
|
87
|
+
return { name, sha, default: name === defaultBranch };
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// Every page, checked against the total GitHub states: a list shorter than the range
|
|
91
|
+
// would read as branches that agree.
|
|
92
|
+
async compare(ref, range) {
|
|
93
|
+
const what = `${range.from}..${range.to} of ${slug(ref)}`;
|
|
94
|
+
const call = await this.api(`repos/${slug(ref)}/compare/${refPath(range.from)}...${refPath(range.to)}?per_page=100`, ['--paginate', '--jq', `{total: .total_commits, commits: [.commits[] | ${COMMIT}]}`]);
|
|
95
|
+
if (call.code !== 0)
|
|
96
|
+
throw queryFailed(BINARY, call, `compare ${what}`);
|
|
97
|
+
const pages = records(call, what);
|
|
98
|
+
const commits = pages.flatMap((page) => Array.isArray(page['commits']) ? page['commits'].map(objectOf) : [undefined]);
|
|
99
|
+
const total = pages[0]?.['total'];
|
|
100
|
+
if (typeof total !== 'number' || commits.length !== total)
|
|
101
|
+
throw unreadableError(BINARY, `an incomplete list of the commits in ${what}`);
|
|
102
|
+
return commits.map((item) => {
|
|
103
|
+
const commit = item === undefined ? undefined : commitOf(item);
|
|
104
|
+
if (commit === undefined)
|
|
105
|
+
throw unreadableError(BINARY, `a commit of ${what}`);
|
|
106
|
+
return commit;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async commit(ref, sha) {
|
|
110
|
+
const call = await this.api(`repos/${slug(ref)}/commits/${refPath(sha)}`, ['--jq', COMMIT]);
|
|
111
|
+
const commit = call.code === 0 ? commitOf(objectOf(call.data) ?? {}) : undefined;
|
|
112
|
+
if (commit === undefined)
|
|
113
|
+
throw queryFailed(BINARY, call, `read commit ${sha} of ${slug(ref)}`);
|
|
114
|
+
return commit;
|
|
115
|
+
}
|
|
116
|
+
async readFile(ref, path, at) {
|
|
117
|
+
const call = await this.api(`repos/${slug(ref)}/contents/${refPath(path)}?ref=${encodeURIComponent(at)}`, ['-H', 'Accept: application/vnd.github.raw']);
|
|
118
|
+
if (call.code === 0)
|
|
119
|
+
return call.stdout;
|
|
120
|
+
if (call.code === EXIT.not_found)
|
|
121
|
+
return undefined;
|
|
122
|
+
throw queryFailed(BINARY, call, `read ${path} at ${at} of ${slug(ref)}`);
|
|
123
|
+
}
|
|
124
|
+
upstreamUrl(ref) {
|
|
125
|
+
return repoUrl(this.host, ref.project, ref.name);
|
|
126
|
+
}
|
|
127
|
+
forkUrl(user, ref) {
|
|
128
|
+
return repoUrl(this.host, user, ref.name);
|
|
129
|
+
}
|
|
130
|
+
webUrl(ref) {
|
|
131
|
+
return `https://${this.hostname}/${slug(ref)}`;
|
|
132
|
+
}
|
|
133
|
+
// gh signs in per host and would otherwise pick one from GH_HOST or its configuration,
|
|
134
|
+
// so every call names the host the binding names, github.com included.
|
|
135
|
+
api(path, args, options = QUERY) {
|
|
136
|
+
return this.gh.call(['api', '--hostname', this.hostname, ...args, path], options);
|
|
137
|
+
}
|
|
138
|
+
get host() {
|
|
139
|
+
return this.vars['host'] ?? '';
|
|
140
|
+
}
|
|
141
|
+
get hostname() {
|
|
142
|
+
return hostnameOf(this.host);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export const githubProvider = {
|
|
146
|
+
id: 'github',
|
|
147
|
+
capability: 'code-host',
|
|
148
|
+
tools: [BINARY],
|
|
149
|
+
recipes: GH_RECIPES,
|
|
150
|
+
vars: [
|
|
151
|
+
{
|
|
152
|
+
name: 'host',
|
|
153
|
+
desc: 'git clone prefix, e.g. https://github.com, or an ssh:// prefix for SSH clones',
|
|
154
|
+
required: true,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
recipeVars: (vars) => ({ hostname: hostnameOf(vars['host'] ?? '') }),
|
|
158
|
+
create: (deps, vars) => new GitHubCodeHost(deps, vars),
|
|
159
|
+
};
|
|
160
|
+
// An https:// or an ssh:// clone prefix names its host; anything else means github.com.
|
|
161
|
+
function hostnameOf(host) {
|
|
162
|
+
try {
|
|
163
|
+
return new URL(host).hostname || DEFAULT_HOSTNAME;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return DEFAULT_HOSTNAME;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function records(call, what) {
|
|
170
|
+
const rows = jsonLines(call.stdout)?.map(objectOf);
|
|
171
|
+
if (rows === undefined || rows.some((row) => row === undefined))
|
|
172
|
+
throw unreadableError(BINARY, what);
|
|
173
|
+
return rows;
|
|
174
|
+
}
|
|
175
|
+
function slug(ref) {
|
|
176
|
+
return `${ref.project}/${ref.name}`;
|
|
177
|
+
}
|
|
178
|
+
// A branch or path keeps its slashes; anything else that means something in a URL is escaped.
|
|
179
|
+
function refPath(value) {
|
|
180
|
+
return value.split('/').map(encodeURIComponent).join('/');
|
|
181
|
+
}
|
|
182
|
+
function commitOf(item) {
|
|
183
|
+
const sha = stringAt(item, 'sha');
|
|
184
|
+
const date = stringAt(item, 'date');
|
|
185
|
+
if (sha === undefined || date === undefined)
|
|
186
|
+
return undefined;
|
|
187
|
+
const parents = Array.isArray(item['parents'])
|
|
188
|
+
? item['parents'].filter((p) => typeof p === 'string')
|
|
189
|
+
: [];
|
|
190
|
+
return {
|
|
191
|
+
sha,
|
|
192
|
+
author: stringAt(item, 'author') ?? '',
|
|
193
|
+
date,
|
|
194
|
+
message: stringAt(item, 'message') ?? '',
|
|
195
|
+
parents,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function forkFailed(ref, reason) {
|
|
199
|
+
return new DevkitError('permission', 'FORK_FAILED', `${BINARY} could not fork ${slug(ref)}: ${reason}`, {
|
|
200
|
+
hint: 'Forking needs read access to the repository and a free name in your account.',
|
|
201
|
+
nextSteps: [`${BINARY} repo view ${slug(ref)}`, 'fx repo list --unreadable'],
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function contributionUnsupported() {
|
|
205
|
+
return new DevkitError('config', 'SOURCE_CONTRIBUTION_UNSUPPORTED', 'The github code host does not open pull requests through fx yet', { nextSteps: ['Open the pull request with gh pr create.'] });
|
|
206
|
+
}
|
|
207
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../../src/adapters/code-host/github/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// gh, the GitHub CLI, serves two providers: the github code host and GitHub Actions
|
|
2
|
+
// ci. This is the one place that runs it. gh reports a failed request as an HTTP
|
|
3
|
+
// status on stderr, so a failure is translated into the family's exit categories and
|
|
4
|
+
// classified by the same `queryFailed` every companion's failures go through.
|
|
5
|
+
import { stderrReason } from '../../domain/toolchain/stderr.js';
|
|
6
|
+
import { EXIT, toolMissing } from '../family/cli.js';
|
|
7
|
+
export const BINARY = 'gh';
|
|
8
|
+
export const DEFAULT_HOSTNAME = 'github.com';
|
|
9
|
+
// gh signs in per host, so the step is per host too: a binding names its host through
|
|
10
|
+
// `hostname` (see `recipeVars`), the same host from both providers is planned once, and
|
|
11
|
+
// two hosts are two steps. Only the active account of that host is checked, the one gh
|
|
12
|
+
// will use.
|
|
13
|
+
export const GH_RECIPES = [
|
|
14
|
+
{
|
|
15
|
+
id: 'login-{hostname}',
|
|
16
|
+
tool: BINARY,
|
|
17
|
+
perEnv: false,
|
|
18
|
+
kind: 'run',
|
|
19
|
+
phase: 'authenticate',
|
|
20
|
+
title: 'Log in to GitHub at {hostname}',
|
|
21
|
+
instructions: 'Signs gh in through the browser or with a token. fx reads repositories and Actions runs with that sign-in; private repositories need the repo scope gh asks for by default.',
|
|
22
|
+
run: ['auth', 'login', '--hostname', '{hostname}'],
|
|
23
|
+
verify: { command: ['auth', 'status', '--active', '--hostname', '{hostname}'] },
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
// gh's own [HOST/]OWNER/NAME. Left without a host, gh would pick one from GH_HOST or
|
|
27
|
+
// its configuration, so a bare OWNER/NAME is spelled out as github.com's.
|
|
28
|
+
export function qualifiedRepository(repository) {
|
|
29
|
+
const parts = repository.split('/');
|
|
30
|
+
if (parts.length > 2)
|
|
31
|
+
return { hostname: parts[0] ?? DEFAULT_HOSTNAME, repository };
|
|
32
|
+
return { hostname: DEFAULT_HOSTNAME, repository: `${DEFAULT_HOSTNAME}/${repository}` };
|
|
33
|
+
}
|
|
34
|
+
export class GhCli {
|
|
35
|
+
process;
|
|
36
|
+
constructor(process) {
|
|
37
|
+
this.process = process;
|
|
38
|
+
}
|
|
39
|
+
async call(args, options = {}) {
|
|
40
|
+
const result = await this.process.run([BINARY, ...args], {
|
|
41
|
+
timeoutMs: options.timeoutMs,
|
|
42
|
+
diagnostic: { phase: `${BINARY} ${args.slice(0, 2).join(' ')}`.trim() },
|
|
43
|
+
});
|
|
44
|
+
if (result.notStarted === true || result.code === EXIT.missing_binary)
|
|
45
|
+
throw toolMissing(BINARY);
|
|
46
|
+
if (result.code === 0)
|
|
47
|
+
return { code: 0, stdout: result.stdout, data: parse(result.stdout), error: undefined };
|
|
48
|
+
const status = httpStatus(result.stderr);
|
|
49
|
+
const message = stderrReason(result.stderr, result.code).replace(/^gh: /, '');
|
|
50
|
+
return {
|
|
51
|
+
code: exitOf(result.code, status, message),
|
|
52
|
+
stdout: result.stdout,
|
|
53
|
+
data: undefined,
|
|
54
|
+
error: { message, ...(status === undefined ? {} : { http_status: status }) },
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// `--paginate --jq` prints one JSON value per line. A line that does not parse makes
|
|
59
|
+
// the whole answer unreadable rather than shorter.
|
|
60
|
+
export function jsonLines(text) {
|
|
61
|
+
const values = [];
|
|
62
|
+
for (const line of text.split('\n')) {
|
|
63
|
+
if (line.trim() === '')
|
|
64
|
+
continue;
|
|
65
|
+
const value = parse(line);
|
|
66
|
+
if (value === undefined)
|
|
67
|
+
return undefined;
|
|
68
|
+
values.push(value);
|
|
69
|
+
}
|
|
70
|
+
return values;
|
|
71
|
+
}
|
|
72
|
+
// `gh api` ends its message with "(HTTP 404)"; other commands open it with "HTTP 404:".
|
|
73
|
+
function httpStatus(stderr) {
|
|
74
|
+
const match = /\(HTTP (\d{3})\)|^HTTP (\d{3}):/m.exec(stderr);
|
|
75
|
+
const status = match?.[1] ?? match?.[2];
|
|
76
|
+
return status === undefined ? undefined : Number(status);
|
|
77
|
+
}
|
|
78
|
+
// gh's own exit 4 means it holds no credential at all. Any other failure is a plain
|
|
79
|
+
// failure: gh's remaining codes (cancelled, pending checks) would read as categories.
|
|
80
|
+
function exitOf(code, status, message) {
|
|
81
|
+
if (code === 4 || status === 401)
|
|
82
|
+
return EXIT.auth;
|
|
83
|
+
if (status === 429 || (status === 403 && /rate limit/i.test(message)))
|
|
84
|
+
return EXIT.rate_limit;
|
|
85
|
+
if (status === 403)
|
|
86
|
+
return EXIT.permission;
|
|
87
|
+
if (status === 404)
|
|
88
|
+
return EXIT.not_found;
|
|
89
|
+
if (status !== undefined && status >= 500)
|
|
90
|
+
return EXIT.server;
|
|
91
|
+
if (/error connecting to/i.test(message))
|
|
92
|
+
return EXIT.network;
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
function parse(text) {
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(text);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=gh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gh.js","sourceRoot":"","sources":["../../../src/adapters/github/gh.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Start an executable that findExecutable resolved. A batch file runs under cmd.exe,
|
|
2
|
+
// which reads the command line first. The batch then reads each argument itself
|
|
3
|
+
// (`%~1`, as mvn.cmd does) or hands them all on (`%*`, as the shims npm, Scoop and
|
|
4
|
+
// Chocolatey install do), and cmd reads that line again. Caret escaping throughout,
|
|
5
|
+
// as cross-spawn applies it, suits only one of those readers, so fx starts every batch
|
|
6
|
+
// file itself and leaves everything else to cross-spawn.
|
|
7
|
+
import { spawn as spawnNode } from 'node:child_process';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import spawn from 'cross-spawn';
|
|
10
|
+
const BATCH = /\.(?:bat|cmd)$/i;
|
|
11
|
+
// What cmd acts on outside quotes.
|
|
12
|
+
const SPECIAL = /[&|<>^()!]/;
|
|
13
|
+
export function launch(executable, args, options) {
|
|
14
|
+
if (process.platform !== 'win32' || !BATCH.test(executable))
|
|
15
|
+
return spawn(executable, [...args], options);
|
|
16
|
+
// /v:off keeps !name! literal whatever delayed expansion the registry turns on.
|
|
17
|
+
return spawnNode(process.env['comspec'] ?? 'cmd.exe', ['/d', '/v:off', '/s', '/c', `"${batchLine([path.normalize(executable), ...args])}"`], { ...options, windowsVerbatimArguments: true });
|
|
18
|
+
}
|
|
19
|
+
// Every argument is quoted for the program at the end of the chain, and cmd leaves
|
|
20
|
+
// whatever sits inside quotes alone, so an argument without a quote of its own reaches
|
|
21
|
+
// `%~1` unchanged. A quote inside an argument turns cmd's quoting off until the next
|
|
22
|
+
// one; there, cmd's special characters are escaped for both of its reads. Each percent
|
|
23
|
+
// sign is followed by an empty expansion, so no %name% survives to expand.
|
|
24
|
+
function batchLine(vector) {
|
|
25
|
+
let line = '';
|
|
26
|
+
let quoted = false;
|
|
27
|
+
for (const char of vector.map(programArgument).join(' ')) {
|
|
28
|
+
if (char === '"')
|
|
29
|
+
quoted = !quoted;
|
|
30
|
+
if (char === '%')
|
|
31
|
+
line += '%%cd:~,%';
|
|
32
|
+
else if (!quoted && SPECIAL.test(char))
|
|
33
|
+
line += '^^^' + char;
|
|
34
|
+
else
|
|
35
|
+
line += char;
|
|
36
|
+
}
|
|
37
|
+
return line;
|
|
38
|
+
}
|
|
39
|
+
// The program's own reading of its command line: backslashes are literal except in
|
|
40
|
+
// a run before a quote, so such a run is doubled, before an inner quote and before
|
|
41
|
+
// the closing one.
|
|
42
|
+
function programArgument(arg) {
|
|
43
|
+
let text = '"';
|
|
44
|
+
let slashes = 0;
|
|
45
|
+
for (const char of arg) {
|
|
46
|
+
if (char === '\\') {
|
|
47
|
+
slashes += 1;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
text += '\\'.repeat(char === '"' ? slashes * 2 + 1 : slashes) + char;
|
|
51
|
+
slashes = 0;
|
|
52
|
+
}
|
|
53
|
+
return text + '\\'.repeat(slashes * 2) + '"';
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=launch.js.map
|