Releases on git.crt.fyi from a workflow: a v* tag push runs .forgejo/workflows/release.yml (tag = package.json version and every version copy, the full CI, Linux AppImage + deb and Windows NSIS on the VDS runners, privacy scan with the private terms, one Forgejo release with short CHANGELOG-summary notes, the installers and SHA256SUMS, older release entries deleted, tags kept); workflow_dispatch dry run makes a draft test-<sha> and deletes it; desktop.yml folded in, ci.yml and release.yml share .forgejo/actions/linux-setup and ci-run; tools/forgejo-release.ts (notes, upsert, prune, delete; uploads over 100 MB go to the origin because Cloudflare refuses them); desktop-release.ts upload-forgejo adds the notarized DMG and updater bundle from the Mac; docs/desktop-release.md release order
This commit is contained in:
parent
e5f148e534
commit
2590b2e828
11 changed files with 871 additions and 229 deletions
|
|
@ -12,7 +12,7 @@
|
|||
// never run, Rosetta), the native self-test (sidecar handshake) from the mounted DMG and from the unpacked updater bundle, the privacy scan, minisign signatures with
|
||||
// the updater key (~/.storebound-keys/updater.key), SHA256SUMS, latest.json and evidence.json.
|
||||
// bun tools/desktop-release.ts add --out <dir> [--commit <sha>] [--run <url>] <files...> [--code-signed]
|
||||
// Adds Windows / Linux installers built by the Forgejo workflow (.forgejo/workflows/desktop.yml) to the version
|
||||
// Adds Windows / Linux installers built by the Forgejo workflow (.forgejo/workflows/release.yml) to the version
|
||||
// folder: privacy scan, minisign signature here (the key never goes to a runner), latest.json, SHA256SUMS and
|
||||
// evidence-<linux|windows>-<version>.json (the run and commit they came from; a commit other than the one of the
|
||||
// folder's other evidence is refused: every artifact of a version comes from one commit).
|
||||
|
|
@ -21,6 +21,10 @@
|
|||
// script there (tmp -> mv, latest.json last). Without --yes it only prints the plan (dry run).
|
||||
// The folder's version must be package.json's: the npm CLI and the app are released together, the app first,
|
||||
// then `npm publish` of the same version.
|
||||
// bun tools/desktop-release.ts upload-forgejo --out <dir> [--tag v<version>] [--yes]
|
||||
// After `mac` (signed, notarized): attaches the DMG and the updater bundle with their minisign signatures to the
|
||||
// Forgejo release of the tag that the release workflow made (.forgejo/workflows/release.yml); SHA256SUMS is merged.
|
||||
// Without --yes only the plan. Token: FORGEJO_TOKEN or the git credential helper (tools/forgejo-release.ts).
|
||||
// bun tools/desktop-release.ts verify --out <dir>
|
||||
// The checks of publish without uploading.
|
||||
// bun tools/desktop-release.ts exec -- <command...>
|
||||
|
|
@ -36,6 +40,7 @@ import { type Download, type DownloadKey, type Manifest, manifestProblems } from
|
|||
import { verify as verifySig } from '../cli/lib/minisign';
|
||||
import { buildOne, OUT_DIR as SIDECAR_DIR } from './build-sidecars';
|
||||
import { versions } from './desktop-version';
|
||||
import { upsert as forgejoUpsert } from './forgejo-release';
|
||||
import { scan } from './privacy-scan';
|
||||
|
||||
export const ROOT = resolve(import.meta.dir, '..');
|
||||
|
|
@ -213,7 +218,7 @@ function gitState(): { commit: string; dirty: boolean } {
|
|||
|
||||
function args(argv: string[]) {
|
||||
const get = (n: string) => { const i = argv.indexOf(`--${n}`); return i >= 0 ? argv[i + 1] : undefined; };
|
||||
return { get, has: (n: string) => argv.includes(`--${n}`), rest: argv.filter((a, i) => !a.startsWith('--') && !(i > 0 && argv[i - 1]!.startsWith('--') && !['--allow-dirty', '--no-notarize', '--yes', '--code-signed'].includes(argv[i - 1]!))) };
|
||||
return { get, has: (n: string) => argv.includes(`--${n}`), rest: argv.filter((a, i) => !a.startsWith('--') && !(i > 0 && argv[i - 1]!.startsWith('--') && !['--allow-dirty', '--no-notarize', '--yes', '--code-signed', '--allow-unsigned'].includes(argv[i - 1]!))) };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
|
@ -673,6 +678,31 @@ function publish(argv: string[]): void {
|
|||
say(`next, the same release: npm publish of storebound ${v} (CONTRIBUTING.md). \`npx storebound desktop\` of ${v} installs or updates to this app.`);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// upload-forgejo (the Mac's files join the release the workflow made)
|
||||
|
||||
/** The files of a version folder that go to the Forgejo release from the Mac: the DMG and the updater bundle, each
|
||||
* with its .sig (pure). Linux and Windows files come from the workflow itself. */
|
||||
export function forgejoMacFiles(names: string[], version: string): string[] {
|
||||
return names.filter(n => n.startsWith(`${PRODUCT}_${version}_universal`) && /\.(dmg|app\.tar\.gz)(\.sig)?$/.test(n)).sort();
|
||||
}
|
||||
|
||||
async function uploadForgejo(argv: string[]): Promise<void> {
|
||||
const a = args(argv);
|
||||
const out = resolve(a.get('out') ?? fail('--out <version folder> is required'));
|
||||
const { manifest } = verifyFolder(out);
|
||||
const v = manifest.version;
|
||||
const together = releaseTogetherProblems(v, versions().version);
|
||||
if (together.length) fail(together.join('; '));
|
||||
const tag = a.get('tag') ?? `v${v}`;
|
||||
if (tag !== `v${v}`) fail(`--tag ${tag} is not v${v}: the folder is version ${v}`);
|
||||
const files = forgejoMacFiles(readdirSync(out), v);
|
||||
if (!files.some(f => f.endsWith('.dmg'))) fail(`${out}: no DMG of ${v} (run mac first)`);
|
||||
if (files.some(f => f.includes('-unsigned')) && !a.has('allow-unsigned')) fail('the DMG is unsigned (-unsigned): only a notarized build goes to the release (--allow-unsigned for a preview)');
|
||||
await forgejoUpsert({ tag, files: files.map(f => join(out, f)), create: false, yes: a.has('yes') });
|
||||
if (a.has('yes')) say(`next: publish --out ${out.replace(homedir(), '~')} --host <user@host> (storebound.dev), then npm publish of ${v}`);
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
const argv = process.argv.slice(2);
|
||||
const cmd = argv[0];
|
||||
|
|
@ -680,6 +710,7 @@ if (import.meta.main) {
|
|||
else if (cmd === 'add') add(argv);
|
||||
else if (cmd === 'verify') verifyFolder(resolve(args(argv).get('out') ?? fail('--out <version folder>')));
|
||||
else if (cmd === 'publish') publish(argv.slice(1));
|
||||
else if (cmd === 'upload-forgejo') await uploadForgejo(argv.slice(1));
|
||||
else if (cmd === 'exec') {
|
||||
// `exec -- <command...>`: runs a build command with the path-free environment (the Forgejo workflow uses it)
|
||||
const cmdline = argv.slice(argv.indexOf('--') + 1);
|
||||
|
|
@ -687,6 +718,6 @@ if (import.meta.main) {
|
|||
const r = spawnSync(cmdline[0]!, cmdline.slice(1), { stdio: 'inherit', env: { ...process.env, ...pathFreeEnv(ROOT, process.env) }, shell: process.platform === 'win32' });
|
||||
process.exit(r.status ?? 1);
|
||||
}
|
||||
else { console.log(readFileSync(import.meta.path, 'utf8').split('\n').filter(l => l.startsWith('//')).slice(0, 26).map(l => l.slice(3)).join('\n')); process.exit(cmd ? 2 : 0); }
|
||||
else { console.log(readFileSync(import.meta.path, 'utf8').split('\n').filter(l => l.startsWith('//')).slice(0, 30).map(l => l.slice(3)).join('\n')); process.exit(cmd ? 2 : 0); }
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue