mirror of
https://github.com/murashit/codex-panel.git
synced 2026-07-22 06:57:10 +00:00
Add Jujutsu release workflow
This commit is contained in:
parent
d59a307f8a
commit
ecfea49b3a
3 changed files with 64 additions and 21 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,4 +1,5 @@
|
|||
node_modules/
|
||||
.jj/
|
||||
main.js
|
||||
data.json
|
||||
*.log
|
||||
|
|
|
|||
|
|
@ -2,19 +2,22 @@
|
|||
|
||||
GitHub Releases attach only `main.js`, `manifest.json`, and `styles.css` as Obsidian install assets. `LICENSE` and `NOTICE` are kept in the repository and source archives for license distribution.
|
||||
|
||||
Release work is Jujutsu-first in a colocated Git repository; Git is still used to push the tag that triggers the GitHub Release workflow. If the checkout has not been initialized for Jujutsu yet, run `jj git init --colocate` and `jj bookmark track main --remote=origin` once.
|
||||
|
||||
Create a release by preparing the next version, editing the generated release notes, committing the release changes, then running the preflight before pushing the matching tag:
|
||||
|
||||
```sh
|
||||
npm run release:prepare -- X.Y.Z
|
||||
# Edit .github/release-notes/X.Y.Z.md.
|
||||
git status --short
|
||||
git add package.json package-lock.json manifest.json versions.json .github/release-notes/X.Y.Z.md
|
||||
git commit -m "Bump version to X.Y.Z"
|
||||
jj status
|
||||
jj commit -m "Bump version to X.Y.Z"
|
||||
jj bookmark move main --to @-
|
||||
npm run release:preflight
|
||||
git tag X.Y.Z
|
||||
git push origin main X.Y.Z
|
||||
jj tag set X.Y.Z -r main
|
||||
jj git push --remote origin --bookmark main
|
||||
git push origin X.Y.Z
|
||||
```
|
||||
|
||||
`release:prepare` updates the version files and creates a `## Changes` release notes template. `release:preflight` verifies the local Git state, release metadata, lockfile, and full build once after the release commit is on `main`.
|
||||
`release:prepare` updates the version files and creates a `## Changes` release notes template. `release:preflight` verifies the local Jujutsu/Git state, release metadata, lockfile, and full build once after the release commit is on `main`.
|
||||
|
||||
The release workflow runs `npm ci`, `npm run release:check`, `npm run check`, attaches the install assets, and generates GitHub artifact attestations for them. The release notes file is required and must contain a single `## Changes` section. If a tag-triggered release fails before creating the GitHub Release, fix the commit, move the local tag with `git tag -f X.Y.Z`, then update the remote tag with `git push --force origin X.Y.Z`.
|
||||
The release workflow runs `npm ci`, `npm run release:check`, `npm run check`, attaches the install assets, and generates GitHub artifact attestations for them. The release notes file is required and must contain a single `## Changes` section. If a tag-triggered release fails before creating the GitHub Release, fix the commit, move the local tag with `jj tag set --allow-move -r main X.Y.Z`, then update the remote tag with `git push --force origin X.Y.Z`.
|
||||
|
|
|
|||
|
|
@ -31,25 +31,64 @@ function maybeRun(command, args) {
|
|||
return result.status === 0 ? result.stdout.trim() : null;
|
||||
}
|
||||
|
||||
const branch = run("git", ["branch", "--show-current"], { capture: true });
|
||||
if (branch !== "main") fail(`release must be prepared from main, got ${branch || "(detached HEAD)"}`);
|
||||
function assertGitReleaseState(packageVersion) {
|
||||
const branch = run("git", ["branch", "--show-current"], { capture: true });
|
||||
if (branch !== "main") fail(`release must be prepared from main, got ${branch || "(detached HEAD)"}`);
|
||||
|
||||
const status = run("git", ["status", "--short"], { capture: true });
|
||||
if (status) fail(`working tree must be clean before release preflight\n${status}`);
|
||||
const status = run("git", ["status", "--short"], { capture: true });
|
||||
if (status) fail(`working tree must be clean before release preflight\n${status}`);
|
||||
|
||||
const upstream = maybeRun("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
|
||||
if (!upstream) fail("main must have an upstream tracking branch");
|
||||
const upstream = maybeRun("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]);
|
||||
if (!upstream) fail("main must have an upstream tracking branch");
|
||||
|
||||
const upstreamAncestor = spawnSync("git", ["merge-base", "--is-ancestor", upstream, "HEAD"], {
|
||||
encoding: "utf8",
|
||||
stdio: "pipe",
|
||||
shell: false,
|
||||
});
|
||||
if (upstreamAncestor.status !== 0) fail(`HEAD must contain ${upstream}; pull or rebase before tagging`);
|
||||
const upstreamAncestor = spawnSync("git", ["merge-base", "--is-ancestor", upstream, "HEAD"], {
|
||||
encoding: "utf8",
|
||||
stdio: "pipe",
|
||||
shell: false,
|
||||
});
|
||||
if (upstreamAncestor.status !== 0) fail(`HEAD must contain ${upstream}; pull or rebase before tagging`);
|
||||
|
||||
const existingTag = run("git", ["tag", "--list", packageVersion], { capture: true });
|
||||
if (existingTag) fail(`local tag ${packageVersion} already exists`);
|
||||
}
|
||||
|
||||
function jjCommitId(revision) {
|
||||
return run("jj", ["log", "-r", revision, "--no-graph", "-T", 'commit_id ++ "\\n"'], { capture: true });
|
||||
}
|
||||
|
||||
function jjRevisionCount(revision) {
|
||||
return Number(run("jj", ["log", "-r", revision, "--count"], { capture: true }));
|
||||
}
|
||||
|
||||
function assertJujutsuReleaseState(packageVersion) {
|
||||
const workingCopyDiff = run("jj", ["diff", "--summary", "-r", "@"], { capture: true });
|
||||
if (workingCopyDiff) fail(`Jujutsu working-copy commit must be empty before release preflight\n${workingCopyDiff}`);
|
||||
|
||||
const mainCommit = jjCommitId("main");
|
||||
const workingCopyParent = jjCommitId("@-");
|
||||
if (mainCommit !== workingCopyParent) {
|
||||
fail("main bookmark must point to the release commit and be the parent of the empty working-copy commit");
|
||||
}
|
||||
|
||||
const existingTag = run("jj", ["tag", "list", packageVersion], { capture: true });
|
||||
if (existingTag) fail(`local tag ${packageVersion} already exists`);
|
||||
|
||||
if (jjRevisionCount("main@origin & ::main") !== 1) {
|
||||
fail("main@origin must be an ancestor of main; fetch or rebase before tagging");
|
||||
}
|
||||
|
||||
const unpushedCommits = jjRevisionCount("main@origin..main");
|
||||
if (unpushedCommits !== 1) {
|
||||
fail(`main must be exactly one release commit ahead of main@origin, got ${unpushedCommits}`);
|
||||
}
|
||||
}
|
||||
|
||||
const packageVersion = run("node", ["-p", "require('./package.json').version"], { capture: true });
|
||||
const existingTag = run("git", ["tag", "--list", packageVersion], { capture: true });
|
||||
if (existingTag) fail(`local tag ${packageVersion} already exists`);
|
||||
if (maybeRun("jj", ["root"])) {
|
||||
assertJujutsuReleaseState(packageVersion);
|
||||
} else {
|
||||
assertGitReleaseState(packageVersion);
|
||||
}
|
||||
|
||||
run("npm", ["run", "release:check"]);
|
||||
run("npm", ["ci", "--dry-run"]);
|
||||
|
|
|
|||
Loading…
Reference in a new issue