mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
Add release skill
This commit is contained in:
97
.opencode/skill/release/SKILL.md
Normal file
97
.opencode/skill/release/SKILL.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# release
|
||||
|
||||
Create a human-friendly **unsigned macOS DMG** release for OpenWork (or similar Tauri apps), and publish it on GitHub.
|
||||
|
||||
This skill is intentionally lightweight: it’s mostly a checklist + a couple of sanity scripts.
|
||||
|
||||
## What this skill is for
|
||||
|
||||
- You have a Tauri app.
|
||||
- You want to publish a **DMG** on GitHub Releases.
|
||||
- You are **not** code signing / notarizing yet (so macOS will warn users).
|
||||
|
||||
## Prereqs
|
||||
|
||||
- `pnpm`
|
||||
- Rust toolchain (`cargo`, `rustc`)
|
||||
- `gh` authenticated (`gh auth status`)
|
||||
- macOS tools: `codesign`, `spctl`, `hdiutil`
|
||||
|
||||
## Release checklist (recommended)
|
||||
|
||||
### 1) Clean working tree
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
### 2) Bump version everywhere
|
||||
|
||||
- `package.json` (`version`)
|
||||
- `src-tauri/tauri.conf.json` (`version`)
|
||||
- `src-tauri/Cargo.toml` (`version`)
|
||||
|
||||
### 3) Validate builds
|
||||
|
||||
```bash
|
||||
pnpm typecheck
|
||||
pnpm build:web
|
||||
cargo check --manifest-path src-tauri/Cargo.toml
|
||||
```
|
||||
|
||||
### 4) Build DMG
|
||||
|
||||
```bash
|
||||
pnpm tauri build --bundles dmg
|
||||
```
|
||||
|
||||
This should produce something like:
|
||||
|
||||
- `src-tauri/target/release/bundle/dmg/OpenWork_<version>_aarch64.dmg`
|
||||
|
||||
### 5) Verify “unsigned” state
|
||||
|
||||
Unsigned here means: **not Developer ID signed / not notarized**.
|
||||
|
||||
Quick checks:
|
||||
|
||||
```bash
|
||||
# mount the dmg read-only
|
||||
hdiutil attach -nobrowse -readonly "src-tauri/target/release/bundle/dmg/<DMG_NAME>.dmg"
|
||||
|
||||
# verify signature details (expect ad-hoc or not notarized)
|
||||
codesign -dv --verbose=4 "/Volumes/<VOLUME>/<APP>.app"
|
||||
|
||||
# gatekeeper assessment (expect rejected)
|
||||
spctl -a -vv "/Volumes/<VOLUME>/<APP>.app" || true
|
||||
|
||||
# unmount
|
||||
hdiutil detach "/Volumes/<VOLUME>"
|
||||
```
|
||||
|
||||
### 6) Tag + push
|
||||
|
||||
```bash
|
||||
git commit -am "Prepare vX.Y.Z release"
|
||||
git tag -a vX.Y.Z -m "OpenWork vX.Y.Z"
|
||||
git push
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
|
||||
### 7) Create / update GitHub Release
|
||||
|
||||
```bash
|
||||
gh release create vX.Y.Z \
|
||||
--title "OpenWork vX.Y.Z" \
|
||||
--notes "<human summary>"
|
||||
|
||||
gh release upload vX.Y.Z "src-tauri/target/release/bundle/dmg/<DMG_NAME>.dmg" --clobber
|
||||
```
|
||||
|
||||
## Local helper scripts
|
||||
|
||||
- `bun .opencode/skill/release/first-call.ts` checks prerequisites and prints the current version.
|
||||
|
||||
## Notes
|
||||
|
||||
- If you later add signing/notarization, this skill should be updated to include that flow.
|
||||
37
.opencode/skill/release/client.ts
Normal file
37
.opencode/skill/release/client.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
export async function run(
|
||||
command: string,
|
||||
args: string[],
|
||||
options?: { cwd?: string; allowFailure?: boolean },
|
||||
): Promise<{ ok: boolean; code: number; stdout: string; stderr: string }> {
|
||||
const cwd = options?.cwd;
|
||||
|
||||
const child = spawn(command, args, {
|
||||
cwd,
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
child.stdout.setEncoding("utf8");
|
||||
child.stderr.setEncoding("utf8");
|
||||
|
||||
child.stdout.on("data", (d) => (stdout += d));
|
||||
child.stderr.on("data", (d) => (stderr += d));
|
||||
|
||||
const code = await new Promise<number>((resolve) => {
|
||||
child.on("close", (c) => resolve(c ?? -1));
|
||||
});
|
||||
|
||||
const ok = code === 0;
|
||||
if (!ok && !options?.allowFailure) {
|
||||
throw new Error(
|
||||
`Command failed (${code}): ${command} ${args.join(" ")}\n${stderr || stdout}`,
|
||||
);
|
||||
}
|
||||
|
||||
return { ok, code, stdout, stderr };
|
||||
}
|
||||
35
.opencode/skill/release/first-call.ts
Normal file
35
.opencode/skill/release/first-call.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { loadEnv } from "./load-env";
|
||||
import { run } from "./client";
|
||||
|
||||
async function main() {
|
||||
await loadEnv();
|
||||
|
||||
await run("gh", ["auth", "status"], { allowFailure: false });
|
||||
|
||||
const pkgRaw = await readFile("package.json", "utf8");
|
||||
const pkg = JSON.parse(pkgRaw) as { name?: string; version?: string };
|
||||
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
ok: true,
|
||||
package: pkg.name ?? null,
|
||||
version: pkg.version ?? null,
|
||||
next: [
|
||||
"pnpm typecheck",
|
||||
"pnpm tauri build --bundles dmg",
|
||||
"gh release upload vX.Y.Z <dmg> --clobber",
|
||||
],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
console.error(message);
|
||||
process.exit(1);
|
||||
});
|
||||
28
.opencode/skill/release/load-env.ts
Normal file
28
.opencode/skill/release/load-env.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { run } from "./client";
|
||||
|
||||
const REQUIRED = [
|
||||
"pnpm",
|
||||
"cargo",
|
||||
"gh",
|
||||
"hdiutil",
|
||||
"codesign",
|
||||
"spctl",
|
||||
];
|
||||
|
||||
export async function loadEnv() {
|
||||
const missing: string[] = [];
|
||||
|
||||
for (const bin of REQUIRED) {
|
||||
try {
|
||||
await run("/usr/bin/env", ["bash", "-lc", `command -v ${bin}`], { allowFailure: false });
|
||||
} catch {
|
||||
missing.push(bin);
|
||||
}
|
||||
}
|
||||
|
||||
if (missing.length) {
|
||||
throw new Error(`Missing required tools: ${missing.join(", ")}`);
|
||||
}
|
||||
|
||||
return { ok: true as const };
|
||||
}
|
||||
Reference in New Issue
Block a user