From fbf416dbf426adcfddc55de04e68fe5f899d5265 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Mon, 27 Jul 2026 11:15:26 -0400 Subject: [PATCH 1/5] ENG-1767 Setup Linear release for Obsidian Add a manual stable_obsidian_release.yaml workflow that publishes a stable Obsidian version and syncs/completes the matching Linear release. publish.ts now writes the resolved version back into local package.json/manifest.json (previously only the mirror-repo copy was updated, so the local version field was permanently stale) so the workflow's guard step has an accurate baseline to compare against. Co-Authored-By: Claude Sonnet 5 --- .../workflows/stable_obsidian_release.yaml | 91 +++++++++++++++++++ apps/obsidian/README.md | 10 ++ apps/obsidian/manifest.json | 2 +- apps/obsidian/package.json | 4 +- apps/obsidian/scripts/publish.ts | 18 ++++ 5 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/stable_obsidian_release.yaml diff --git a/.github/workflows/stable_obsidian_release.yaml b/.github/workflows/stable_obsidian_release.yaml new file mode 100644 index 000000000..c6f22a059 --- /dev/null +++ b/.github/workflows/stable_obsidian_release.yaml @@ -0,0 +1,91 @@ +name: Stable Obsidian Release +on: + workflow_dispatch: + inputs: + version: + description: "Stable version to release, e.g. 1.6.0. Must match an existing Linear release version." + required: true + type: string + +permissions: + contents: write + +env: + OBSIDIAN_PLUGIN_REPO_TOKEN: ${{ secrets.OBSIDIAN_PLUGIN_REPO_TOKEN }} + SUPABASE_USE_DB: production + SUPABASE_URL: ${{ secrets.SUPABASE_URL }} + SUPABASE_PUBLISHABLE_KEY: ${{ secrets.SUPABASE_PUBLISHABLE_KEY }} + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Validate version format + run: | + if ! [[ "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "::error::Version '${{ inputs.version }}' is not a valid stable version (expected x.y.z)" + exit 1 + fi + + - name: Install pnpm + uses: pnpm/action-setup@v6 + with: + version: 10.15.1 + run_install: false + + - name: Setup Node.js environment + uses: actions/setup-node@v6 + with: + node-version: "22" + cache: "pnpm" + + - name: Install Dependencies + run: pnpm install --frozen-lockfile + + - name: Guard against re-releasing an already-shipped version + run: | + CURRENT_VERSION=$(node -p "require('./apps/obsidian/package.json').version") + CURRENT_BASE="${CURRENT_VERSION%%-*}" + INPUT_VERSION="${{ inputs.version }}" + HIGHEST=$(printf '%s\n%s\n' "$CURRENT_BASE" "$INPUT_VERSION" | sort -V | tail -n1) + if [ "$HIGHEST" != "$INPUT_VERSION" ]; then + echo "::error::Requested version $INPUT_VERSION is behind the current version $CURRENT_VERSION." + exit 1 + fi + if [ "$CURRENT_BASE" == "$INPUT_VERSION" ] && [[ "$CURRENT_VERSION" != *-beta.* ]]; then + echo "::error::Version $INPUT_VERSION has already been released (current version is $CURRENT_VERSION)." + exit 1 + fi + + - name: Publish stable release + run: cd apps/obsidian && npx tsx scripts/publish.ts --version ${{ inputs.version }} + + - name: Commit version bump + run: | + if ! git diff --quiet -- apps/obsidian/package.json apps/obsidian/manifest.json; then + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add apps/obsidian/package.json apps/obsidian/manifest.json + git commit -m "chore: release obsidian ${{ inputs.version }} [skip ci]" + git push + fi + + - name: Sync Linear release + uses: linear/linear-release-action@v0 + with: + access_key: ${{ secrets.LINEAR_RELEASES_ACCESS_KEY }} + command: sync + version: ${{ inputs.version }} + include_paths: "apps/obsidian/**,packages/database/**,packages/utils/**" + + - name: Complete Linear release + uses: linear/linear-release-action@v0 + with: + access_key: ${{ secrets.LINEAR_RELEASES_ACCESS_KEY }} + command: complete + version: ${{ inputs.version }} diff --git a/apps/obsidian/README.md b/apps/obsidian/README.md index 7e51fe49f..ee8e02767 100644 --- a/apps/obsidian/README.md +++ b/apps/obsidian/README.md @@ -141,3 +141,13 @@ For more information about Discourse Graphs, check out [our website](https://dis 1. Join our growing community of academics, researchers, and thinkers on [Slack 💬](https://join.slack.com/t/discoursegraphs/shared_invite/zt-37xklatti-cpEjgPQC0YyKYQWPNgAkEg) 2. Are you a lab or researcher interested in piloting the plugin with some guidance from the team? Send us [an email](mailto:discoursegraphs@homeworld.bio) or DM on Slack! 3. Discourse Graphs is [open source](https://en.wikipedia.org/wiki/Open_source) and open to contributions. If you have an idea for an improvement or identify a bug open an issue here on the repo to start the conversation. + +# Release process + +To ship an official stable release: + +1. Merge a PR to `main` that bumps the version in `apps/obsidian/package.json` and `apps/obsidian/manifest.json` to the target stable version, e.g. `1.6.0`. +2. Make sure a Linear release with that exact version already exists. +3. From the Actions tab, manually run **Stable Obsidian Release** (`.github/workflows/stable_obsidian_release.yaml`) with the `version` input set to the same version. + +That single run publishes the stable GitHub release, pushes it to the mirror repo, and syncs and completes the matching Linear release. diff --git a/apps/obsidian/manifest.json b/apps/obsidian/manifest.json index b05fa71f9..8038f67ab 100644 --- a/apps/obsidian/manifest.json +++ b/apps/obsidian/manifest.json @@ -1,7 +1,7 @@ { "id": "@discourse-graph/obsidian", "name": "Discourse Graph", - "version": "0.1.0", + "version": "1.5.1", "minAppVersion": "1.7.0", "description": "Add semantic structure to your notes with the Discourse Graph protocol.", "author": "Discourse Graphs", diff --git a/apps/obsidian/package.json b/apps/obsidian/package.json index 1b715d839..d9c5f6b9c 100644 --- a/apps/obsidian/package.json +++ b/apps/obsidian/package.json @@ -1,6 +1,6 @@ { "name": "@discourse-graphs/obsidian", - "version": "0.1.0", + "version": "1.5.1", "description": "Discourse Graph Plugin for obsidian.md", "main": "dist/main.js", "private": true, @@ -9,7 +9,7 @@ "build": "tsx scripts/build.ts", "lint": "eslint .", "lint:fix": "eslint . --fix", - "publish": "tsx scripts/publish.ts --version 0.1.0", + "publish": "tsx scripts/publish.ts", "check-types": "tsc --noEmit --skipLibCheck" }, "keywords": [], diff --git a/apps/obsidian/scripts/publish.ts b/apps/obsidian/scripts/publish.ts index c60bbad8b..7faa41b7a 100644 --- a/apps/obsidian/scripts/publish.ts +++ b/apps/obsidian/scripts/publish.ts @@ -402,6 +402,23 @@ const sanitizePackageJsonForMirror = (tempDir: string): void => { } }; +const updateLocalVersion = (obsidianDir: string, version: string): void => { + const packageJsonPath = path.join(obsidianDir, "package.json"); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); + packageJson.version = version; + fs.writeFileSync( + packageJsonPath, + JSON.stringify(packageJson, null, 2) + "\n", + ); + + const manifestPath = path.join(obsidianDir, "manifest.json"); + const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")); + manifest.version = version; + fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n"); + + log(`Updated local package.json and manifest.json to version ${version}`); +}; + const updateMainBranch = async ( tempDir: string, version: string, @@ -710,6 +727,7 @@ const publish = async (config: PublishConfig): Promise => { if (isExternal) { updateManifest(tempDir, version); await updateMainBranch(tempDir, version); + updateLocalVersion(obsidianDir, version); } else { log("Skipping main branch update for internal or pre-release"); } From d69b78332b3d7fc897dcca245375a96eaeabf3a4 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Mon, 27 Jul 2026 11:20:41 -0400 Subject: [PATCH 2/5] ENG-1767 Bump obsidian baseline version to 1.5.2 A new stable 1.5.2 release was just cut manually, so update the one-time baseline correction to match instead of the now-stale 1.5.1. Co-Authored-By: Claude Sonnet 5 --- apps/obsidian/manifest.json | 2 +- apps/obsidian/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/obsidian/manifest.json b/apps/obsidian/manifest.json index 8038f67ab..142ec82d9 100644 --- a/apps/obsidian/manifest.json +++ b/apps/obsidian/manifest.json @@ -1,7 +1,7 @@ { "id": "@discourse-graph/obsidian", "name": "Discourse Graph", - "version": "1.5.1", + "version": "1.5.2", "minAppVersion": "1.7.0", "description": "Add semantic structure to your notes with the Discourse Graph protocol.", "author": "Discourse Graphs", diff --git a/apps/obsidian/package.json b/apps/obsidian/package.json index d9c5f6b9c..d7b167bc9 100644 --- a/apps/obsidian/package.json +++ b/apps/obsidian/package.json @@ -1,6 +1,6 @@ { "name": "@discourse-graphs/obsidian", - "version": "1.5.1", + "version": "1.5.2", "description": "Discourse Graph Plugin for obsidian.md", "main": "dist/main.js", "private": true, From 375e4338fcfab59dc8cd7a207164f21a0b38703d Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Mon, 27 Jul 2026 11:25:00 -0400 Subject: [PATCH 3/5] ENG-1767 Drop unusable SUPABASE_USE_DB=production from stable workflow packages/database/src/dbDotEnv.mjs unconditionally overrides SUPABASE_USE_DB to "implicit" (or "none") whenever running under GitHub Actions with GITHUB_TEST != "test", regardless of what the variable is set to. So SUPABASE_USE_DB=production was dead in CI. SUPABASE_URL/SUPABASE_PUBLISHABLE_KEY alone are enough to trigger the implicit-mode fallback, same as roam-release.yaml/roam-main.yaml. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/stable_obsidian_release.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/stable_obsidian_release.yaml b/.github/workflows/stable_obsidian_release.yaml index c6f22a059..07e7b3f67 100644 --- a/.github/workflows/stable_obsidian_release.yaml +++ b/.github/workflows/stable_obsidian_release.yaml @@ -12,7 +12,6 @@ permissions: env: OBSIDIAN_PLUGIN_REPO_TOKEN: ${{ secrets.OBSIDIAN_PLUGIN_REPO_TOKEN }} - SUPABASE_USE_DB: production SUPABASE_URL: ${{ secrets.SUPABASE_URL }} SUPABASE_PUBLISHABLE_KEY: ${{ secrets.SUPABASE_PUBLISHABLE_KEY }} From ea8818f83cfae365fd02f60913d61c663e81a465 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Mon, 27 Jul 2026 11:37:33 -0400 Subject: [PATCH 4/5] ENG-1767 Drop the version-format and re-release guard steps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both steps contradicted the documented release process: README step 1 tells maintainers to bump package.json to the target version before dispatching, but the guard rejected exactly that state as "already released" (flagged independently by Devin and Graphite review). roam-release.yaml has no equivalent safeguard at all — it trusts package.json outright. Do the same here: devs are expected to pass a sane version, so drop both checks rather than patch the guard to special-case the pre-bumped state. Co-Authored-By: Claude Sonnet 5 --- .../workflows/stable_obsidian_release.yaml | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/.github/workflows/stable_obsidian_release.yaml b/.github/workflows/stable_obsidian_release.yaml index 07e7b3f67..38c271f64 100644 --- a/.github/workflows/stable_obsidian_release.yaml +++ b/.github/workflows/stable_obsidian_release.yaml @@ -24,13 +24,6 @@ jobs: with: fetch-depth: 0 - - name: Validate version format - run: | - if ! [[ "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "::error::Version '${{ inputs.version }}' is not a valid stable version (expected x.y.z)" - exit 1 - fi - - name: Install pnpm uses: pnpm/action-setup@v6 with: @@ -46,21 +39,6 @@ jobs: - name: Install Dependencies run: pnpm install --frozen-lockfile - - name: Guard against re-releasing an already-shipped version - run: | - CURRENT_VERSION=$(node -p "require('./apps/obsidian/package.json').version") - CURRENT_BASE="${CURRENT_VERSION%%-*}" - INPUT_VERSION="${{ inputs.version }}" - HIGHEST=$(printf '%s\n%s\n' "$CURRENT_BASE" "$INPUT_VERSION" | sort -V | tail -n1) - if [ "$HIGHEST" != "$INPUT_VERSION" ]; then - echo "::error::Requested version $INPUT_VERSION is behind the current version $CURRENT_VERSION." - exit 1 - fi - if [ "$CURRENT_BASE" == "$INPUT_VERSION" ] && [[ "$CURRENT_VERSION" != *-beta.* ]]; then - echo "::error::Version $INPUT_VERSION has already been released (current version is $CURRENT_VERSION)." - exit 1 - fi - - name: Publish stable release run: cd apps/obsidian && npx tsx scripts/publish.ts --version ${{ inputs.version }} From 2a38b23d5789f1fdc53cec037448358e1f0f87d9 Mon Sep 17 00:00:00 2001 From: Trang Doan Date: Wed, 29 Jul 2026 16:11:40 -0400 Subject: [PATCH 5/5] ENG-1767 Make createGithubRelease idempotent on re-run Skip release creation if a release for the tag already exists, so retrying after a transient Linear API failure doesn't abort at the duplicate-release 422. Co-Authored-By: Claude Sonnet 4.6 --- apps/obsidian/scripts/publish.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/obsidian/scripts/publish.ts b/apps/obsidian/scripts/publish.ts index 7faa41b7a..7ea88d1a0 100644 --- a/apps/obsidian/scripts/publish.ts +++ b/apps/obsidian/scripts/publish.ts @@ -630,6 +630,21 @@ const createGithubRelease = async ({ const releaseTempDir = path.join(os.tmpdir(), "temp-obsidian-release-assets"); + const existingRelease = await octokit + .request("GET /repos/{owner}/{repo}/releases/tags/{tag}", { + owner, + repo, + tag: tagName, + }) + .catch((err: { status?: number }) => + err.status === 404 ? null : Promise.reject(err), + ); + + if (existingRelease) { + log(`GitHub release for ${tagName} already exists, skipping creation.`); + return; + } + try { if (fs.existsSync(releaseTempDir)) { fs.rmSync(releaseTempDir, { recursive: true });