Skip to content
Merged
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
44 changes: 28 additions & 16 deletions .githooks/sync-versions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function createFixtureRepo(): string {
})
const manifest = {
id: 'tnmso',
name: 'TNMSO',
name: 'TNMSO Preview',
version: initialVersion,
minAppVersion: '1.0.0',
isDesktopOnly: false
Expand Down Expand Up @@ -147,6 +147,25 @@ function createFixtureRepo(): string {
return rootDir
}

function preparePluginRelease(rootDir: string, nextVersion: string): void {
writeJson(join(rootDir, 'obsidian-plugin', 'package.json'), {
name: 'tnmso',
version: nextVersion,
private: true
})
writeJson(join(rootDir, 'obsidian-plugin', 'manifest.json'), {
id: 'tnmso',
name: 'TNMSO Preview',
version: nextVersion,
minAppVersion: '1.0.0',
isDesktopOnly: false
})
writeJson(join(rootDir, 'obsidian-plugin', 'versions.json'), {
'2026.10324.10015': '1.0.0',
[nextVersion]: '1.0.0'
})
}

function expectSharedVersionSurfaces(rootDir: string, nextVersion: string): void {
expect(JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf-8')) as {version: string}).toMatchObject({version: nextVersion})
expect(JSON.parse(readFileSync(join(rootDir, 'cli', 'package.json'), 'utf-8')) as {version: string, optionalDependencies: Record<string, string>}).toMatchObject({
Expand Down Expand Up @@ -198,6 +217,7 @@ describe('sync-versions hook', () => {
tempDirs.push(rootDir)

const nextVersion = '2026.10324.10314'
preparePluginRelease(rootDir, nextVersion)
writeJson(join(rootDir, 'cli', 'npm', 'darwin-arm64', 'package.json'), {
name: '@truenine/memory-sync-cli-darwin-arm64',
version: nextVersion
Expand All @@ -222,9 +242,6 @@ describe('sync-versions hook', () => {
'gui/src-tauri/tauri.conf.json',
'mcp/package.json',
'manifest.json',
'obsidian-plugin/manifest.json',
'obsidian-plugin/package.json',
'obsidian-plugin/versions.json',
'package.json',
'versions.json'
]))
Expand All @@ -235,6 +252,7 @@ describe('sync-versions hook', () => {
tempDirs.push(rootDir)

const nextVersion = '2026.10324.10316'
preparePluginRelease(rootDir, nextVersion)
writeJson(join(rootDir, 'gui', 'package.json'), {
name: '@truenine/memory-sync-gui',
version: nextVersion
Expand All @@ -259,9 +277,6 @@ describe('sync-versions hook', () => {
'gui/src-tauri/tauri.conf.json',
'mcp/package.json',
'manifest.json',
'obsidian-plugin/manifest.json',
'obsidian-plugin/package.json',
'obsidian-plugin/versions.json',
'package.json',
'versions.json'
]))
Expand All @@ -272,6 +287,7 @@ describe('sync-versions hook', () => {
tempDirs.push(rootDir)

const nextVersion = '2026.10324.10318'
preparePluginRelease(rootDir, nextVersion)
writeJson(join(rootDir, 'doc', 'package.json'), {
name: '@truenine/memory-sync-docs',
version: nextVersion,
Expand All @@ -286,23 +302,19 @@ describe('sync-versions hook', () => {
expectSharedVersionSurfaces(rootDir, nextVersion)
})

it('accepts the TNMSO package version as the staged system version source', () => {
it('requires TNMSO to be released before the system version advances', () => {
const rootDir = createFixtureRepo()
tempDirs.push(rootDir)

const nextVersion = '2026.10324.10319'
writeJson(join(rootDir, 'obsidian-plugin', 'package.json'), {
name: 'tnmso',
writeJson(join(rootDir, 'doc', 'package.json'), {
name: '@truenine/memory-sync-docs',
version: nextVersion,
private: true
})
runGit(rootDir, ['add', 'obsidian-plugin/package.json'])

const result = runSyncVersions({rootDir})
runGit(rootDir, ['add', 'doc/package.json'])

expect(result.targetVersion).toBe(nextVersion)
expect(result.versionSource).toBe('obsidian-plugin/package.json')
expectSharedVersionSurfaces(rootDir, nextVersion)
expect(() => runSyncVersions({rootDir})).toThrowError(/release TNMSO first/)
})

it('fails when staged package.json files propose conflicting versions', () => {
Expand Down
28 changes: 14 additions & 14 deletions .githooks/sync-versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const IGNORED_DIRECTORIES = new Set([
'coverage',
'dist',
'node_modules',
'obsidian-plugin',
'target',
])
const CALVER_VERSION_REGEX = /^\d{4}\.(?:0|1\d{2}(?:\d{2})?)\.(?:0|1\d{2}(?:\d{2}(?:\d{2})?)?)$/u
Expand Down Expand Up @@ -76,24 +77,22 @@ function syncObsidianReleaseMetadata(
const rootVersionsPath = resolve(rootDir, 'versions.json')
const manifest = readJsonFile(pluginManifestPath)
const minAppVersion = manifest.minAppVersion
const pluginVersion = manifest.version

if (manifest.id !== 'tnmso' || typeof minAppVersion !== 'string' || minAppVersion.trim() === '') {
throw new Error('TNMSO manifest must define id=tnmso and a non-empty minAppVersion')
}
if (pluginVersion !== targetVersion) {
throw new Error(`TNMSO submodule has version ${String(pluginVersion)}, expected ${targetVersion}; release TNMSO first`)
}

const updatedManifest = {...manifest, version: targetVersion}
writeJsonFileIfChanged(pluginManifestPath, updatedManifest, changedPaths)
writeJsonFileIfChanged(rootManifestPath, updatedManifest, changedPaths)

let versions: VersionedJson
try {
versions = readJsonFile(pluginVersionsPath)
} catch {
versions = {}
const versions = readJsonFile(pluginVersionsPath)
if (versions[targetVersion] !== minAppVersion) {
throw new Error(`TNMSO versions.json must map ${targetVersion} to ${minAppVersion}`)
}
const updatedVersions = {...versions, [targetVersion]: minAppVersion}
writeJsonFileIfChanged(pluginVersionsPath, updatedVersions, changedPaths)
writeJsonFileIfChanged(rootVersionsPath, updatedVersions, changedPaths)

writeJsonFileIfChanged(rootManifestPath, manifest, changedPaths)
writeJsonFileIfChanged(rootVersionsPath, versions, changedPaths)
}

function discoverFilesByName(baseDir: string, fileName: string): string[] {
Expand Down Expand Up @@ -375,6 +374,7 @@ function getStagedVersionCandidates(rootDir: string, rootVersion: string): Map<s
.map(line => line.trim())
.filter(line => line.length > 0)
.filter(filePath => {
if (filePath.startsWith('obsidian-plugin/')) return false
const name = basename(filePath)
return name === 'package.json' || name === 'Cargo.toml' || name === 'tauri.conf.json'
})
Expand Down Expand Up @@ -493,6 +493,8 @@ export function runSyncVersions(options: SyncVersionsOptions = {}): SyncVersions
const target = resolveTargetVersion(rootDir, currentRootVersion, options.requestedVersion)
const changedPaths = new Set<string>()

syncObsidianReleaseMetadata(rootDir, target.version, changedPaths)

syncJsonVersion(rootPackagePath, target.version, changedPaths)

const packageJsonPaths = discoverFilesByName(rootDir, 'package.json')
Expand Down Expand Up @@ -520,8 +522,6 @@ export function runSyncVersions(options: SyncVersionsOptions = {}): SyncVersions
syncJsonVersion(filePath, target.version, changedPaths)
}

syncObsidianReleaseMetadata(rootDir, target.version, changedPaths)

stageFiles(rootDir, [...changedPaths].sort())

return {
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
- 'pnpm-workspace.yaml'
- 'xtask/**'
obsidian:
- 'obsidian-plugin'
- 'obsidian-plugin/**'
- 'manifest.json'
- 'versions.json'
Expand All @@ -58,6 +59,8 @@ jobs:
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
with:
submodules: recursive

- uses: ./.github/actions/setup-bun
with:
Expand Down Expand Up @@ -185,6 +188,8 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
with:
submodules: recursive

- uses: ./.github/actions/setup-bun

Expand Down
47 changes: 5 additions & 42 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:
version: ${{ steps.release-version.outputs.version }}
steps:
- uses: actions/checkout@v6
with:
submodules: recursive

- uses: ./.github/actions/setup-node-pnpm
with:
Expand Down Expand Up @@ -66,7 +68,7 @@ jobs:
run: |
set -euo pipefail
git fetch --force --tags
for tag in "v${{ steps.release-version.outputs.version }}" "${{ steps.release-version.outputs.version }}"; do
for tag in "v${{ steps.release-version.outputs.version }}"; do
if git rev-parse --verify --quiet "refs/tags/${tag}" >/dev/null; then
tag_commit="$(git rev-list -n 1 "refs/tags/${tag}")"
if [[ "$tag_commit" != "${GITHUB_SHA}" ]]; then
Expand Down Expand Up @@ -442,6 +444,8 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
with:
submodules: recursive

- uses: ./.github/actions/setup-bun

Expand Down Expand Up @@ -529,44 +533,3 @@ jobs:
artifacts/obsidian/tnmso-*.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

create-obsidian-release:
needs: [validate-version, build-obsidian-plugin]
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- name: Download TNMSO artifacts
uses: actions/download-artifact@v8
with:
name: obsidian-plugin-release
path: artifacts/obsidian

- name: Verify Obsidian release files
shell: bash
run: |
set -euo pipefail
for file in main.js manifest.json styles.css "tnmso-${{ needs.validate-version.outputs.version }}.zip"; do
test -f "artifacts/obsidian/${file}" || {
echo "::error::Missing TNMSO release file: ${file}"
exit 1
}
done

- name: Publish Obsidian release
uses: softprops/action-gh-release@v3.0.0
with:
tag_name: ${{ needs.validate-version.outputs.version }}
target_commitish: ${{ github.sha }}
name: TNMSO ${{ needs.validate-version.outputs.version }}
body: |
TNMSO safely previews prompt-focused MDX files as Markdown in Obsidian without evaluating JavaScript.

Install `main.js`, `manifest.json`, and `styles.css` in `<vault>/.obsidian/plugins/tnmso/`, then reload Obsidian and enable TNMSO.
fail_on_unmatched_files: true
files: |
artifacts/obsidian/main.js
artifacts/obsidian/manifest.json
artifacts/obsidian/styles.css
artifacts/obsidian/tnmso-${{ needs.validate-version.outputs.version }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "obsidian-plugin"]
path = obsidian-plugin
url = https://github.com/TrueNine/tnmso.git
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ MCP server:
npm install -g @truenine/memory-sync-mcp
```

### TNMSO for Obsidian
### TNMSO Preview for Obsidian

TNMSO is the Obsidian plugin in [`obsidian-plugin/`](obsidian-plugin/README.md). Download `main.js`, `manifest.json`, and `styles.css` from the GitHub Release tagged with the exact CalVer version, then install them under `<vault>/.obsidian/plugins/tnmso/`. BRAT users can install `TrueNine/memory-sync` for beta testing.
TNMSO Preview is maintained in the [`obsidian-plugin/`](obsidian-plugin/README.md) submodule. Install it from the Obsidian community directory or download `main.js`, `manifest.json`, and `styles.css` from the matching [TNMSO Preview Release](https://github.com/TrueNine/tnmso/releases). BRAT users should install `TrueNine/tnmso`.

TNMSO shares the repository version. System releases use `v<version>` tags; Obsidian releases use the matching plain `<version>` tag because the community plugin updater requires the tag and manifest version to be identical.
TNMSO Preview shares the memory-sync CalVer version. The plugin is released first with the exact plain `<version>` tag, then memory-sync pins that release through the submodule and publishes its `v<version>` system release.

## Supported Tools

Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "tnmso",
"name": "TNMSO",
"name": "TNMSO Preview",
"version": "2026.10805.0",
"minAppVersion": "1.13.4",
"description": "Preview prompt-focused MDX files safely as Markdown.",
Expand Down
1 change: 1 addition & 0 deletions obsidian-plugin
Submodule obsidian-plugin added at eaeac8
11 changes: 0 additions & 11 deletions obsidian-plugin/RELEASING.md

This file was deleted.

Loading