Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions .github/workflows/obsidian-main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ concurrency:
cancel-in-progress: false

env:
OBSIDIAN_PLUGIN_REPO_TOKEN: ${{ secrets.OBSIDIAN_PLUGIN_REPO_TOKEN }}
# TODO(ENG-2099): REVERT BEFORE MERGE, restore secrets.OBSIDIAN_PLUGIN_REPO_TOKEN.
OBSIDIAN_PLUGIN_REPO_TOKEN: ${{ secrets.OBSIDIAN_TEST_REPO_TOKEN }}
# TODO(ENG-2099): REVERT BEFORE MERGE, restore DiscourseGraphs/discourse-graph-obsidian.
PUBLISH_REPO: DiscourseGraphs/discourse-graph-obsidian-test
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_PUBLISHABLE_KEY: ${{ secrets.SUPABASE_PUBLISHABLE_KEY }}
GH_TOKEN: ${{ github.token }}
Expand Down Expand Up @@ -48,17 +51,17 @@ jobs:

- name: Compute next beta version
id: version
env:
# Releases exist only in the publish repo, never in the monorepo, so the
# lookup needs an explicit --repo and a token that can read that repo.
GH_TOKEN: ${{ env.OBSIDIAN_PLUGIN_REPO_TOKEN }}
run: |
STABLE_VERSION=$(node -p "require('./apps/obsidian/package.json').version" | sed 's/-beta\.[0-9]*//')
LATEST_N=$(gh release list --limit 50 --json tagName --jq '.[].tagName' \
STABLE_VERSION=$(node -p "require('./apps/obsidian/package.json').version")
LATEST_N=$(gh release list --repo "$PUBLISH_REPO" --limit 100 --json tagName --jq '.[].tagName' \

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug C: the beta counter never incremented

gh release list ran without --repo, so it queried the monorepo, which has 0 releases and 0 tags. The lookup always came back empty and the code fell through to -beta.1 every time.

The failure mode was the quiet kind: the recomputed version already existed, createGithubRelease took its early return, and the run exited green having shipped nothing.

After the fix the runs produced beta.2 through beta.5, incrementing correctly.

| grep "^${STABLE_VERSION}-beta\." \
| sed "s/^${STABLE_VERSION}-beta\.//" \
| sort -n | tail -1)
if [ -n "$LATEST_N" ]; then
NEXT_BETA="${STABLE_VERSION}-beta.$((LATEST_N + 1))"
else
NEXT_BETA="${STABLE_VERSION}-beta.1"
fi
NEXT_BETA="${STABLE_VERSION}-beta.$(( ${LATEST_N:-0} + 1 ))"
echo "Stable version: $STABLE_VERSION, next beta: $NEXT_BETA"
echo "version=$NEXT_BETA" >> "$GITHUB_OUTPUT"

Expand All @@ -68,5 +71,5 @@ jobs:
- name: Sync Linear release
uses: linear/linear-release-action@v0
with:
access_key: ${{ secrets.OBSIDIAN_LINEAR_RELEASE_KEY }}
access_key: ${{ secrets.LINEAR_RELEASES_ACCESS_KEY_OBSIDIAN }}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug B: every run on main failed at the Linear step

This referenced secrets.OBSIDIAN_LINEAR_RELEASE_KEY, which does not exist. The real secret is LINEAR_RELEASES_ACCESS_KEY_OBSIDIAN, so the input resolved to an empty string and the action exited with access_key input is required.

The name was correct on eng-1767, which is why it passed there on 24 May, and it changed on merge. Since then: 6 failures, 3 cancelled, 0 successes.

Worth noting the step runs last, after publish has already written to the publish repo. Those red runs still changed production.

include_paths: "apps/obsidian/**,packages/database/**,packages/utils/**"
30 changes: 18 additions & 12 deletions .github/workflows/obsidian-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ permissions:

env:
VERSION: ${{ inputs.version }}
OBSIDIAN_PLUGIN_REPO_TOKEN: ${{ secrets.OBSIDIAN_PLUGIN_REPO_TOKEN }}
# TODO(ENG-2099): REVERT BEFORE MERGE, restore secrets.OBSIDIAN_PLUGIN_REPO_TOKEN.
OBSIDIAN_PLUGIN_REPO_TOKEN: ${{ secrets.OBSIDIAN_TEST_REPO_TOKEN }}
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_PUBLISHABLE_KEY: ${{ secrets.SUPABASE_PUBLISHABLE_KEY }}

Expand Down Expand Up @@ -50,27 +51,32 @@ jobs:
- name: Publish stable release
run: cd apps/obsidian && npx tsx scripts/publish.ts --version "$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 ${VERSION} [skip ci]"
git push
fi
# Disabled: the default-branch ruleset requires a pull request and does not

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug E: this step could never succeed on main

The Main Branch Protection ruleset requires a pull request on the default branch and does not list github-actions[bot] as a bypass actor, so the push is always rejected there.

The step sits after publishing, so a real stable release would create the GitHub release, push the publish repo, then fail here and never reach the Linear steps. Retrying does not help: updateMainBranch reports no changes, createGithubRelease skips the existing release, updateLocalVersion rewrites the same files, and the push is rejected again. The Linear half of the pipeline was unreachable for that version.

The two green runs on 24 May prove nothing. They ran on a branch, where git push targets the branch instead of main.

The step's own git diff --quiet guard is what makes disabling it safe: bump the version in the release PR and there is nothing for CI to push. I did not reproduce the rejection, since that needs a real stable release from main.

# grant github-actions[bot] a bypass, so this push is always rejected on main.
# Bump apps/obsidian/package.json and manifest.json in the release PR instead.
# To re-enable, add github-actions[bot] as a ruleset bypass actor.
#
# - 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 ${VERSION} [skip ci]"
# git push
# fi

- name: Sync Linear release
uses: linear/linear-release-action@v0
with:
access_key: ${{ secrets.OBSIDIAN_LINEAR_RELEASE_KEY }}
access_key: ${{ secrets.LINEAR_RELEASES_ACCESS_KEY_OBSIDIAN }}
command: sync
version: ${{ env.VERSION }}
include_paths: "apps/obsidian/**,packages/database/**,packages/utils/**"

- name: Complete Linear release
uses: linear/linear-release-action@v0
with:
access_key: ${{ secrets.OBSIDIAN_LINEAR_RELEASE_KEY }}
access_key: ${{ secrets.LINEAR_RELEASES_ACCESS_KEY_OBSIDIAN }}
command: complete
version: ${{ env.VERSION }}
2 changes: 1 addition & 1 deletion apps/obsidian/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "@discourse-graph/obsidian",
"name": "Discourse Graph",
"version": "1.5.2",
"version": "9.9.10",
"minAppVersion": "1.7.0",
"description": "Add semantic structure to your notes with the Discourse Graph protocol.",
"author": "Discourse Graphs",
Expand Down
2 changes: 1 addition & 1 deletion apps/obsidian/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@discourse-graphs/obsidian",
"version": "1.5.2",
"version": "9.9.10",
"description": "Discourse Graph Plugin for obsidian.md",
"main": "dist/main.js",
"private": true,
Expand Down
59 changes: 35 additions & 24 deletions apps/obsidian/scripts/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ const BLOB_UPLOAD_BATCH_SIZE = 10;
const MAX_GITHUB_RETRIES = 5;
const BASE_RETRY_DELAY_MS = 2_000;

const TARGET_REPO = "DiscourseGraphs/discourse-graph-obsidian";
// TODO(ENG-2099): REVERT BEFORE MERGE, restore discourse-graph-obsidian.
const TARGET_REPO = "DiscourseGraphs/discourse-graph-obsidian-test";
const OWNER = "DiscourseGraphs";
const REPO = "discourse-graph-obsidian";
const REPO = "discourse-graph-obsidian-test";

const log = (message: string): void => {
console.log(`[Obsidian Publisher] ${message}`);
Expand Down Expand Up @@ -215,23 +216,14 @@ const validateVersion = (version: string): void => {
};

const isExternalRelease = (version: string): boolean => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug A: -beta versions were published as official releases

This predicate returned true for beta as well as stable, and one boolean gated two separate decisions: the GitHub prerelease flag, and whether to push manifest.json to the publish repo's main branch. So a beta shipped as a full release, and it overwrote the manifest the Obsidian community store reads to pick a version. That is ENG-2106.

The publish repo's main still sits at 1.5.2-beta.1 today, with five Release v1.5.2-beta.1 commits, two of them landing after stable 1.5.3 shipped on 7 Aug.

Verified across fourteen version inputs, then in five beta runs: every release came out prerelease=true, and the test repo's main kept exactly one commit and one file throughout.

// External releases are:
// 1. Stable releases (x.y.z)
// 2. Beta releases (x.y.z-beta.n)

// Stable release pattern (x.y.z)
// The Obsidian community store reads manifest.json from the publish repo's
// main branch, so only a finished release may be external. Everything else
// ships as a GitHub pre-release and leaves that branch untouched.
const stablePattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
if (stablePattern.test(version)) {
return true;
}

// Beta release pattern (x.y.z-beta.n)
const betaPattern = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-beta(\.\d+)?$/;
if (betaPattern.test(version)) {
return true;
}

// Everything else (including alpha releases) is internal
return false;
};

Expand Down Expand Up @@ -302,17 +294,23 @@ const execCommand = async (
}
};

// Patterns match a whole path segment, never a substring of one, and globs are
// anchored to a single segment.
const segmentMatchesPattern = (segment: string, pattern: string): boolean => {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug D: source files were silently dropped from the publish repo

The old matcher used relativePath.includes(pattern) and turned globs into unanchored regexes, so any file whose name merely contained an excluded word was dropped. "out" ate src/components/DatacoreCallout.tsx. *.log became /.*.log/ and ate src/utils/nativeJsonFileDialogs.ts via "diaLOGs".

Confirmed on a real publish: 101 local source files, 99 published. After the fix, 101 of 101.

Before committing I diffed old against new across the whole apps/obsidian tree: exactly those two files flip to included, nothing flips the other way. Re-checked secrets against the enlarged 113-file set, since loosening an exclusion filter on a public repo is where a leak would hide. Nothing sensitive published.

if (!pattern.includes("*")) return segment === pattern;

const escaped = pattern
.split("*")
.map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
.join("[^/]*");
return new RegExp(`^${escaped}$`).test(segment);
};

const shouldExclude = (filePath: string, baseDir: string): boolean => {
const relativePath = path.relative(baseDir, filePath);
return EXCLUDE_PATTERNS.some((pattern) => {
if (pattern.includes("*")) {
const regex = new RegExp(pattern.replace(/\*/g, ".*"));
return regex.test(relativePath) || regex.test(path.basename(filePath));
}
return (
relativePath.includes(pattern) || path.basename(filePath) === pattern
);
});
const segments = path.relative(baseDir, filePath).split(path.sep);
return segments.some((segment) =>
EXCLUDE_PATTERNS.some((pattern) => segmentMatchesPattern(segment, pattern)),
);
};

const copyDirectory = ({
Expand Down Expand Up @@ -402,6 +400,18 @@ const sanitizePackageJsonForMirror = (tempDir: string): void => {
}
};

// updateLocalVersion runs after the publish-repo push, so the release version
// has to be written into the staged copy here as well.
const updateStagedPackageVersion = (tempDir: string, version: string): void => {

@trangdoan982 trangdoan982 Aug 22, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug E: the published package.json was permanently one release behind

updateManifest only rewrote manifest.json in the staging directory. package.json was copied at its pre-bump value, and updateLocalVersion runs after updateMainBranch has already pushed.

Result on both repos:

test repo, right after publishing 9.9.9:  manifest.json = 9.9.9         package.json = 1.5.2
publish repo:                             manifest.json = 1.5.2-beta.1  package.json = 1.5.2

Low user impact, since Obsidian reads manifest.json, but the published repo contradicted itself. Verified on the 9.9.10 publish: both files now read 9.9.10.

const packageJsonPath = path.join(tempDir, "package.json");
if (!fs.existsSync(packageJsonPath)) return;

const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
packageJson.version = version;
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
log(`Updated staged package.json version to ${version}`);
};

const updateLocalVersion = (obsidianDir: string, version: string): void => {
const packageJsonPath = path.join(obsidianDir, "package.json");
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
Expand Down Expand Up @@ -741,6 +751,7 @@ const publish = async (config: PublishConfig): Promise<void> => {

if (isExternal) {
updateManifest(tempDir, version);
updateStagedPackageVersion(tempDir, version);
await updateMainBranch(tempDir, version);
updateLocalVersion(obsidianDir, version);
} else {
Expand Down