breakaway 1.4.0-main.26 → 1.4.0-main.28
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/package.json +1 -1
- package/scripts/lib/package-release.js +19 -0
- package/scripts/package-release.mjs +10 -2
- package/scripts/tasks/cli.js +15 -6
- package/scripts/tasks/pipeline.js +4 -1
- package/scripts/tasks.mjs +6 -2
- package/src/cli-version.js +2 -2
- package/template/pipeline/release.yml +71 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "breakaway",
|
|
3
|
-
"version": "1.4.0-main.
|
|
3
|
+
"version": "1.4.0-main.28",
|
|
4
4
|
"description": "The task board for you and your coding agents: a Cloudflare Worker, its web app, Taskwarrior sync, and the CLI (npx breakaway).",
|
|
5
5
|
"license": "FSL-1.1-Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -56,6 +56,25 @@ export function nextPrerelease(current, tags, prefix = 'v') {
|
|
|
56
56
|
return { version, base, tag: `${prefix}${version}` };
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* The version a stable release's pull request sets package.json to (BRK-118, WEB-39): the next minor or major after
|
|
61
|
+
* the stable, or null when there is nothing to set. A patch is null, since the pre-releases count patches by
|
|
62
|
+
* themselves, and so is a package.json already at or past the choice (main moved on before an older pre-release was
|
|
63
|
+
* released).
|
|
64
|
+
* @param {string} stable the version just released, like 1.3.0
|
|
65
|
+
* @param {string} next patch, minor, or major
|
|
66
|
+
* @param {string} current package.json's version on the default branch
|
|
67
|
+
* @returns {string | null}
|
|
68
|
+
*/
|
|
69
|
+
export function nextVersion(stable, next, current) {
|
|
70
|
+
const [maj, min] = parts(stable);
|
|
71
|
+
parts(current);
|
|
72
|
+
if (next === 'patch') return null;
|
|
73
|
+
if (next !== 'minor' && next !== 'major') throw new Error(`next is patch, minor, or major, not "${next}".`);
|
|
74
|
+
const version = next === 'major' ? `${maj + 1}.0.0` : `${maj}.${min + 1}.0`;
|
|
75
|
+
return compareVersions(current, version) >= 0 ? null : version;
|
|
76
|
+
}
|
|
77
|
+
|
|
59
78
|
/** The pre-release among `tags` (the tags on one commit), if one was already staged from it. */
|
|
60
79
|
export function prereleaseAmong(tags, prefix = 'v') {
|
|
61
80
|
const prerelease = prereleasePattern(prefix);
|
|
@@ -7,13 +7,15 @@
|
|
|
7
7
|
* exists=true when that commit already has a pre-release (a second run for one merge stages nothing)
|
|
8
8
|
* node scripts/package-release.mjs stable <pre-release tag> --prefix <tag prefix>
|
|
9
9
|
* the stable version and tag it becomes, and the commit it was staged from; stops if that stable exists
|
|
10
|
+
* node scripts/package-release.mjs next <stable> <patch|minor|major> --dir <path>
|
|
11
|
+
* the version to set <path>/package.json to after the stable, empty for none (WEB-39)
|
|
10
12
|
* Reads the tags with git, so run it in a checkout with its tags (fetch-depth: 0). Copied by `repos init`.
|
|
11
13
|
*/
|
|
12
14
|
import { execFileSync } from 'node:child_process';
|
|
13
15
|
import { readFileSync } from 'node:fs';
|
|
14
16
|
import { join } from 'node:path';
|
|
15
17
|
import { parseArgs } from 'node:util';
|
|
16
|
-
import { nextPrerelease, prereleaseAmong, stableOf } from './lib/package-release.js';
|
|
18
|
+
import { nextPrerelease, nextVersion, prereleaseAmong, stableOf } from './lib/package-release.js';
|
|
17
19
|
|
|
18
20
|
const { positionals, values: o } = parseArgs({
|
|
19
21
|
allowPositionals: true,
|
|
@@ -41,9 +43,15 @@ try {
|
|
|
41
43
|
throw new Error(`${o.prefix}${version} is already released. Pick a newer pre-release.`);
|
|
42
44
|
const [commit] = git('rev-list', '-n', '1', `refs/tags/${tag}`);
|
|
43
45
|
console.log(`version=${version}\ntag=${o.prefix}${version}\ncommit=${commit}`);
|
|
46
|
+
} else if (command === 'next' && tag) {
|
|
47
|
+
const current = JSON.parse(readFileSync(join(o.dir, 'package.json'), 'utf8')).version;
|
|
48
|
+
const version = nextVersion(tag, positionals[2] ?? 'patch', current);
|
|
49
|
+
if (!version && positionals[2] && positionals[2] !== 'patch')
|
|
50
|
+
console.error(`package.json already says ${current}, at or past the next ${positionals[2]} after ${tag}.`);
|
|
51
|
+
console.log(`version=${version ?? ''}`);
|
|
44
52
|
} else {
|
|
45
53
|
throw new Error(
|
|
46
|
-
'Usage: package-release.mjs prerelease --dir <path> --prefix <p> [--sha <commit>] | stable <tag> --prefix <p>',
|
|
54
|
+
'Usage: package-release.mjs prerelease --dir <path> --prefix <p> [--sha <commit>] | stable <tag> --prefix <p> | next <stable> <patch|minor|major> --dir <path>',
|
|
47
55
|
);
|
|
48
56
|
}
|
|
49
57
|
} catch (error) {
|
package/scripts/tasks/cli.js
CHANGED
|
@@ -106,18 +106,27 @@ export function pullAgentRequest(action, number, { repo = null, problem, note, f
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
|
-
* `npx breakaway github release <pre-release
|
|
110
|
-
* Release on the GitHub page does
|
|
111
|
-
*
|
|
109
|
+
* `npx breakaway github release <pre-release> [--next patch|minor|major]` (BRK-103, WEB-39): the owner releases a
|
|
110
|
+
* package's pre-release as its stable, as Release on the GitHub page does, with what the default branch works toward
|
|
111
|
+
* next. The board starts the repository's release.yml stable job, and npm waits for the owner's 2FA; it refuses an
|
|
112
|
+
* agent, so the request always says who asks, and refuses a pre-release whose stable is already out (409).
|
|
112
113
|
* @param {string | undefined} version the pre-release, like 1.4.0-main.5 (or its tag)
|
|
113
|
-
* @param {{ repo?: string | null, by?: string }} [options]
|
|
114
|
+
* @param {{ repo?: string | null, by?: string, next?: string | null }} [options]
|
|
114
115
|
* @returns {{ error?: string, request?: [string, string, Record<string, string>] }}
|
|
115
116
|
*/
|
|
116
|
-
export function packageReleaseRequest(version, { repo = null, by } = {}) {
|
|
117
|
+
export function packageReleaseRequest(version, { repo = null, by, next = null } = {}) {
|
|
117
118
|
const v = String(version ?? '').trim();
|
|
118
119
|
if (!/^(?:\S+@|v)?\d+\.\d+\.\d+-main\.\d+$/u.test(v))
|
|
119
120
|
return { error: 'say which pre-release: npx breakaway github release <version>, like 1.4.0-main.5' };
|
|
120
|
-
|
|
121
|
+
if (next !== null && next !== undefined && !['patch', 'minor', 'major'].includes(String(next)))
|
|
122
|
+
return { error: '--next is patch, minor, or major' };
|
|
123
|
+
return {
|
|
124
|
+
request: [
|
|
125
|
+
'POST',
|
|
126
|
+
'github/release',
|
|
127
|
+
{ version: v, ...(next ? { next: String(next) } : {}), ...(repo ? { repo } : {}), ...(by ? { by } : {}) },
|
|
128
|
+
],
|
|
129
|
+
};
|
|
121
130
|
}
|
|
122
131
|
|
|
123
132
|
export const REVIEW_VERDICTS = ['ready', 'follow-up', 'changes'];
|
|
@@ -576,7 +576,10 @@ export function lintWorkflow(text) {
|
|
|
576
576
|
if (!(need in doc.jobs)) problems.push(`${where} needs ${need}, which isn't a job`);
|
|
577
577
|
const { steps = [], ...rest } = job;
|
|
578
578
|
checkExpressions(stringsIn({ if: rest.if }), where, [], true);
|
|
579
|
-
checkExpressions(stringsIn({ ...rest, if: undefined }), where, [], false);
|
|
579
|
+
checkExpressions(stringsIn({ ...rest, if: undefined, outputs: undefined }), where, [], false);
|
|
580
|
+
// A job's outputs read its steps once they've all run.
|
|
581
|
+
const stepIds = Array.isArray(steps) ? steps.map((s) => s?.id).filter(Boolean) : [];
|
|
582
|
+
checkExpressions(stringsIn({ outputs: rest.outputs }), `${where}'s outputs`, stepIds, false);
|
|
580
583
|
if (stringsIn(job).some(({ text: value }) => /\bsecrets\./u.test(value)) && !job.environment)
|
|
581
584
|
problems.push(`${where} reads a secret outside an environment`);
|
|
582
585
|
if (!Array.isArray(steps) || !steps.length) {
|
package/scripts/tasks.mjs
CHANGED
|
@@ -181,7 +181,9 @@ Reading (list, next, claim, and add work in this checkout's repos
|
|
|
181
181
|
Review with an agent does; on a Dependabot one it tests the update, as Safe to merge? does
|
|
182
182
|
(owner) [--note <text>] [--repo <slug>] [--force]
|
|
183
183
|
github release <pre-release> release a package's pre-release (1.4.0-main.5) as its stable on latest, as Release on the
|
|
184
|
-
GitHub page does: the board starts release.yml's stable job, and npm waits for your 2FA (owner)
|
|
184
|
+
GitHub page does: the board starts release.yml's stable job, and npm waits for your 2FA (owner);
|
|
185
|
+
--next minor or major opens the pull request that sets the next version (patch, the default, counts
|
|
186
|
+
by itself). Refused once that stable is out [--next patch|minor|major] [--repo <slug>]
|
|
185
187
|
specs the repository's specs, newest first: each one's status and its tasks [--repo <slug>]
|
|
186
188
|
specs show <path> one spec: its status, last change, Markdown, and the tasks that link it [--repo <slug>]
|
|
187
189
|
github the checkout's repository on GitHub: open pull requests, checks, reviews, CI, deploys, alerts [--sync] [--repo <slug>]
|
|
@@ -1441,13 +1443,15 @@ const commands = {
|
|
|
1441
1443
|
const built = packageReleaseRequest(args[1], {
|
|
1442
1444
|
repo: (await checkoutRepo()).slug,
|
|
1443
1445
|
by: opts.as ?? setting('AGENT'),
|
|
1446
|
+
next: typeof opts.next === 'string' ? opts.next : null,
|
|
1444
1447
|
});
|
|
1445
1448
|
if (built.error || !built.request) fail(built.error ?? 'bad request');
|
|
1446
1449
|
const answer = await call(...built.request);
|
|
1450
|
+
const next = built.request[2].next;
|
|
1447
1451
|
print(
|
|
1448
1452
|
answer,
|
|
1449
1453
|
() =>
|
|
1450
|
-
`Started ${answer.workflow}'s stable job for ${args[1]}. It stages the stable on npm's latest, where it waits for your approval with 2FA
|
|
1454
|
+
`Started ${answer.workflow}'s stable job for ${args[1]}. It stages the stable on npm's latest, where it waits for your approval with 2FA.${next && next !== 'patch' ? ` Then a pull request sets package.json to the next ${next}.` : ''}`,
|
|
1451
1455
|
);
|
|
1452
1456
|
return;
|
|
1453
1457
|
}
|
package/src/cli-version.js
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* and how to update it. scripts/tasks/version.test.js fails when the copied files change and this doesn't:
|
|
5
5
|
* so it lives in the board's package (CLD-135) and the CLI imports it from here.
|
|
6
6
|
*/
|
|
7
|
-
export const CLI_VERSION =
|
|
8
|
-
export const CLI_FINGERPRINT = '
|
|
7
|
+
export const CLI_VERSION = 72;
|
|
8
|
+
export const CLI_FINGERPRINT = '393b3864ca66fb71';
|
|
@@ -19,6 +19,14 @@ on:
|
|
|
19
19
|
description: The pre-release to release as stable, like {{prefixName}}1.4.0-main.5
|
|
20
20
|
required: true
|
|
21
21
|
type: string
|
|
22
|
+
# Pre-releases count patches from the stable by themselves. With minor or major, once the stable is staged, the
|
|
23
|
+
# next-version job opens the pull request that sets package.json to the next minor or major after it.
|
|
24
|
+
next:
|
|
25
|
+
description: What {{branchName}} works toward next. Patch opens nothing; minor or major opens a pull request that sets package.json
|
|
26
|
+
required: false
|
|
27
|
+
default: patch
|
|
28
|
+
type: choice
|
|
29
|
+
options: [patch, minor, major]
|
|
22
30
|
|
|
23
31
|
permissions: {}
|
|
24
32
|
|
|
@@ -139,6 +147,8 @@ jobs:
|
|
|
139
147
|
permissions:
|
|
140
148
|
contents: write
|
|
141
149
|
id-token: write
|
|
150
|
+
outputs:
|
|
151
|
+
version: ${{ steps.version.outputs.version }}
|
|
142
152
|
env:
|
|
143
153
|
GITHUB_TOKEN: ${{ github.token }}
|
|
144
154
|
GH_TOKEN: ${{ github.token }}
|
|
@@ -210,3 +220,64 @@ jobs:
|
|
|
210
220
|
TAG: ${{ steps.version.outputs.tag }}
|
|
211
221
|
COMMIT: ${{ steps.version.outputs.commit }}
|
|
212
222
|
run: gh api -X POST "repos/$GITHUB_REPOSITORY/git/refs" -f "ref=refs/tags/$TAG" -f "sha=$COMMIT" > /dev/null
|
|
223
|
+
|
|
224
|
+
# With next minor or major, a pull request sets package.json to the next minor or major after the stable, so the next
|
|
225
|
+
# merge stages X.Y.0-main.1 instead of the stable's next patch. It runs after the stable is staged, in its own job with
|
|
226
|
+
# no environment and no secrets, and writes through GitHub's API, so no credentials are kept in a checkout. A pull
|
|
227
|
+
# request opened with GITHUB_TOKEN starts no workflows, so its checks run once the owner closes and reopens it, or
|
|
228
|
+
# pushes to it.
|
|
229
|
+
next-version:
|
|
230
|
+
name: next version
|
|
231
|
+
needs: stable
|
|
232
|
+
if: inputs.next == 'minor' || inputs.next == 'major'
|
|
233
|
+
runs-on: ubuntu-latest
|
|
234
|
+
timeout-minutes: 5
|
|
235
|
+
permissions:
|
|
236
|
+
contents: write
|
|
237
|
+
pull-requests: write
|
|
238
|
+
env:
|
|
239
|
+
GH_TOKEN: ${{ github.token }}
|
|
240
|
+
STABLE: ${{ needs.stable.outputs.version }}
|
|
241
|
+
NEXT: ${{ inputs.next }}
|
|
242
|
+
steps:
|
|
243
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
244
|
+
with:
|
|
245
|
+
ref: {{branchName}}
|
|
246
|
+
persist-credentials: false
|
|
247
|
+
- name: Open the pull request that sets the version
|
|
248
|
+
run: |
|
|
249
|
+
set -euo pipefail
|
|
250
|
+
version=$(node scripts/package-release.mjs next "$STABLE" "$NEXT" --dir "$DIRECTORY" | sed 's/^version=//')
|
|
251
|
+
if [ -z "$version" ]; then
|
|
252
|
+
echo "::notice title=No version to set::{{branchName}}'s package.json is already at or past the next $NEXT after $STABLE."
|
|
253
|
+
exit 0
|
|
254
|
+
fi
|
|
255
|
+
branch="next-version/$version"
|
|
256
|
+
if [ -n "$(gh pr list --repo "$GITHUB_REPOSITORY" --head "$branch" --state open --json number --jq '.[].number')" ]; then
|
|
257
|
+
echo "::notice title=Already open::A pull request from $branch sets $version."
|
|
258
|
+
exit 0
|
|
259
|
+
fi
|
|
260
|
+
file=package.json
|
|
261
|
+
if [ "$DIRECTORY" != . ]; then file="$DIRECTORY/package.json"; fi
|
|
262
|
+
# A run GitHub refused the pull request leaves its branch: open the pull request from it.
|
|
263
|
+
if ! gh api "repos/$GITHUB_REPOSITORY/git/ref/heads/$branch" >/dev/null 2>&1; then
|
|
264
|
+
base=$(git rev-parse HEAD)
|
|
265
|
+
blob=$(git rev-parse "HEAD:$file")
|
|
266
|
+
export V=$version F=$file
|
|
267
|
+
node -e "const f=process.env.F,p=JSON.parse(require('fs').readFileSync(f));p.version=process.env.V;require('fs').writeFileSync(f,JSON.stringify(p,null,2)+'\n')"
|
|
268
|
+
gh api -X POST "repos/$GITHUB_REPOSITORY/git/refs" -f "ref=refs/heads/$branch" -f "sha=$base" >/dev/null
|
|
269
|
+
gh api -X PUT "repos/$GITHUB_REPOSITORY/contents/$file" -f "branch=$branch" -f "sha=$blob" \
|
|
270
|
+
-f "message=Set $PACKAGE's version to $version" -f "content=$(base64 -w0 "$file")" >/dev/null
|
|
271
|
+
fi
|
|
272
|
+
printf '%s\n' \
|
|
273
|
+
"$PACKAGE@$STABLE is staged, and the Release workflow was run with next $NEXT: this sets $file's version to $version." \
|
|
274
|
+
"" \
|
|
275
|
+
"Once it merges, the next pre-release is $version-main.1 instead of $STABLE's next patch." \
|
|
276
|
+
"" \
|
|
277
|
+
"Opened with the workflow's token, which starts no workflows: close and reopen this pull request, or push to it, for its checks to run." \
|
|
278
|
+
> "$RUNNER_TEMP/body.md"
|
|
279
|
+
if ! gh pr create --repo "$GITHUB_REPOSITORY" --base {{branchName}} --head "$branch" \
|
|
280
|
+
--title "Set $PACKAGE's version to $version" --body-file "$RUNNER_TEMP/body.md"; then
|
|
281
|
+
echo "::error title=GitHub refused the pull request::$branch sets $version, but the repository doesn't let Actions open pull requests. Turn on Settings, Actions, General, Allow GitHub Actions to create and approve pull requests, then re-run this job, or open the pull request from $branch yourself."
|
|
282
|
+
exit 1
|
|
283
|
+
fi
|