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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ docs/site
# Obsidian E2E failure artifacts
.obsidian-e2e-artifacts

# Isolated per-worktree Obsidian E2E vaults (provisioned by scripts/obsidian-e2e-*)
# Isolated per-worktree Obsidian E2E vaults (provisioned by the obsidian-e2e runner)
.obsidian-e2e-vaults
23 changes: 15 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,24 +117,31 @@ npm run obsidian:e2e -- eval code='Boolean(app.plugins.plugins.podnotes)'
npm run obsidian:e2e -- dev:errors
```

- The four `provision:e2e-vault` / `start:e2e-obsidian` / `stop:e2e-obsidian` /
`obsidian:e2e` scripts run on the shared `obsidian-e2e` instance-runner bin,
configured by `obsidian-e2e.config.mjs` at the repo root (plugin id, the two
symlinked artifacts, the `data.json` seed, and the PodNotes ready probe).
- The wrapper links the worktree's own `main.js`/`manifest.json` (PodNotes injects
its CSS into the bundle, so there is no `styles.css` to link) and seeds a clean
`DEFAULT_SETTINGS`-shaped `data.json` on first provision; it never touches
`/Users/christian/Developer/dev_vault/dev`.
- `npm run provision:e2e-vault` and `npm run start:e2e-obsidian` expose the
provision/launch steps individually; both accept `--help`.
- Use `npm run start:e2e-obsidian -- --print-env` only when you need to export
`PODNOTES_E2E_VAULT` / `PODNOTES_E2E_VAULT_PATH` / `PODNOTES_E2E_OBSIDIAN_HOME`
for a separate process. The `obsidian` CLI routes by `$HOME` (it talks to
`$HOME/.obsidian-cli.sock`), so to point the Vitest `tests/e2e` suite at the
isolated instance you must remap `HOME` as well as the vault name — exporting
`PODNOTES_E2E_VAULT` alone leaves the suite talking to the shared `dev` vault:
the vault env for a separate process. `--print-env` emits export-only lines on
stdout (so `eval "$(...)"` is safe): the canonical `OBSIDIAN_E2E_VAULT` /
`OBSIDIAN_E2E_VAULT_PATH` / `OBSIDIAN_E2E_OBSIDIAN_HOME` names and, during the
migration, legacy `PODNOTES_E2E_*` aliases; `tests/e2e/harness.ts` reads the
canonical name first, then the alias. The `obsidian` CLI routes by `$HOME` (it
talks to `$HOME/.obsidian-cli.sock`), so to point the Vitest `tests/e2e` suite
at the isolated instance you must remap `HOME` as well as the vault name —
exporting the vault alone leaves the suite talking to the shared `dev` vault:

```bash
npm run build # required: provisioning links main.js
npm run build # required: provisioning links main.js
eval "$(npm run --silent start:e2e-obsidian -- --print-env)"
export HOME="$PODNOTES_E2E_OBSIDIAN_HOME" # required: re-point the CLI socket
PODNOTES_E2E_VAULT="$PODNOTES_E2E_VAULT" npm run test:e2e
export HOME="$OBSIDIAN_E2E_OBSIDIAN_HOME" # required: re-point the CLI socket
OBSIDIAN_E2E_VAULT="$OBSIDIAN_E2E_VAULT" npm run test:e2e
```

Build first so the instance loads the current bundle (provisioning also needs
Expand Down
126 changes: 126 additions & 0 deletions obsidian-e2e.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Consumer config for the shared obsidian-e2e instance runner. The four
// `provision:e2e-vault` / `start:e2e-obsidian` / `stop:e2e-obsidian` /
// `obsidian:e2e` scripts point at the `obsidian-e2e` bin, which reads this file
// from the worktree root. See the runner's README ("Instance Runner (CLI)") for
// the full schema.
//
// `defaultData` seeds a freshly provisioned vault's data.json. It mirrors
// DEFAULT_SETTINGS in src/constants.ts (plus the `schemaVersion: 2` persistence
// marker PodNotes writes alongside its settings) so a new vault loads with clean
// PodNotes state and the current on-disk schema. `currentEpisode` is omitted:
// DEFAULT_SETTINGS sets it to `undefined`, which JSON cannot represent and
// PodNotes treats as absent. Keep this in sync with constants.ts -
// scripts/e2e-config.test.ts fails if it drifts from DEFAULT_SETTINGS.
export default {
pluginId: "podnotes",
// PodNotes injects its CSS into main.js (svelte compilerOptions css:
// "injected"), so there is no styles.css artifact - only the manifest and the
// compiled bundle are symlinked into the vault.
pluginArtifacts: ["manifest.json", "main.js"],
defaultData: {
schemaVersion: 2,
savedFeeds: {},
podNotes: {},
defaultPlaybackRate: 1,
defaultVolume: 1,
hidePlayedEpisodes: false,
episodeListLimit: 10,
playedEpisodes: {},
favorites: {
icon: "lucide-star",
name: "Favorites",
shouldEpisodeRemoveAfterPlay: false,
shouldRepeat: false,
episodes: [],
},
queue: {
icon: "list-ordered",
name: "Queue",
shouldEpisodeRemoveAfterPlay: true,
shouldRepeat: false,
episodes: [],
},
autoQueue: true,
playlists: {},
skipBackwardLength: 15,
skipForwardLength: 15,
timestamp: {
template: "- {{time}} ",
offset: 0,
},
note: {
path: "PodNotes/{{podcast}}/{{title}}.md",
template:
"---\n" +
"type: podcastEpisode\n" +
'podcast: "{{podcastlink}}"\n' +
"date: {{date:YYYY-MM-DD}}\n" +
"tags:\n" +
" - podcastEpisode\n" +
"status:\n" +
"rating:\n" +
"favorite: false\n" +
"---\n" +
"# {{title}}\n\n" +
"![]({{artwork}})\n\n" +
"[Resume in PodNotes]({{episodelink}})\n\n" +
"{{url}}\n\n" +
"{{description}}\n",
},
feedNote: {
path: "PodNotes/Podcasts/{{podcast}}.md",
template:
"---\n" +
"type: podcast\n" +
'podcast: "{{podcast}}"\n' +
'image: "{{artwork}}"\n' +
'url: "{{url}}"\n' +
'feedUrl: "{{feedurl}}"\n' +
"tags:\n" +
" - podcast\n" +
"---\n" +
"# {{title}}\n" +
"{{author}}\n\n" +
"![]({{artwork}})\n\n" +
"{{description}}\n",
},
download: {
path: "PodNotes/{{podcast}}/{{title}}",
},
downloadedEpisodes: {},
localFiles: {
icon: "folder",
name: "Local Files",
shouldEpisodeRemoveAfterPlay: false,
shouldRepeat: false,
episodes: [],
},
openAISecretId: "",
deepgramSecretId: "",
transcript: {
path: "transcripts/{{podcast}}/{{title}}.md",
template: "# {{title}}\n\nPodcast: {{podcast}}\nDate: {{date}}\n\n{{transcript}}",
diarization: {
enabled: false,
provider: "openai",
speakerTemplate: "**{{speaker}}:** ",
},
},
feedCache: {
enabled: true,
ttlHours: 6,
},
},
buildCommand: "npm run build",
// Emit legacy PODNOTES_E2E_* env aliases alongside the canonical OBSIDIAN_E2E_*
// names while the harness and AGENTS.md playbooks migrate off them.
envPrefix: "PODNOTES",
// Confirm the PodNotes plugin instance is live in the target vault. The launcher
// waits for stdout to contain the match string; the code intentionally omits the
// literal "=> true" so an echoed command can't be mistaken for a positive result.
readyProbe: {
kind: "eval",
code: `Boolean(app.plugins.plugins["podnotes"])`,
match: "=> true",
},
};
2 changes: 1 addition & 1 deletion orca.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@
# of erroring.
scripts:
archive: |
command -v node >/dev/null 2>&1 && node scripts/stop-obsidian-e2e-instance.mjs --worktree "$ORCA_WORKTREE_PATH" || true
command -v node >/dev/null 2>&1 && npm exec obsidian-e2e -- stop --worktree "$ORCA_WORKTREE_PATH" || true
11 changes: 7 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
"release:plan": "node scripts/release-plan.mjs",
"test": "npm run check:a11y && vitest",
"test:e2e": "npm run build && vitest run --config vitest.e2e.config.ts",
"obsidian:e2e": "node scripts/obsidian-e2e-cli.mjs",
"provision:e2e-vault": "node scripts/provision-obsidian-e2e-vault.mjs",
"start:e2e-obsidian": "node scripts/start-obsidian-e2e-instance.mjs",
"stop:e2e-obsidian": "node scripts/stop-obsidian-e2e-instance.mjs",
"obsidian:e2e": "obsidian-e2e run",
"provision:e2e-vault": "obsidian-e2e provision",
"start:e2e-obsidian": "obsidian-e2e start",
"stop:e2e-obsidian": "obsidian-e2e stop",
"check:a11y": "svelte-check --tsconfig ./tsconfig.json --fail-on-warnings && svelte-check --tsconfig ./tsconfig.test.json --fail-on-warnings",
"docs:build": "mkdocs build -f docs/mkdocs.yml -d site",
"docs:deploy": "npm run docs:build && wrangler pages deploy docs/site --project-name podnotes --branch master",
"format": "oxfmt src tests scripts .github package.json manifest.json versions.json tsconfig.base.json tsconfig.json tsconfig.test.json .oxlintrc.json .oxfmtrc.json svelte.config.mjs vite.config.ts vitest.config.ts vitest.e2e.config.ts vitest.setup.ts wrangler.jsonc orca.yaml docs/mkdocs.yml"
"format": "oxfmt src tests scripts .github package.json manifest.json versions.json tsconfig.base.json tsconfig.json tsconfig.test.json .oxlintrc.json .oxfmtrc.json svelte.config.mjs vite.config.ts vitest.config.ts vitest.e2e.config.ts vitest.setup.ts obsidian-e2e.config.mjs wrangler.jsonc orca.yaml docs/mkdocs.yml"
},
"dependencies": {
"fuse.js": "^7.4.2",
Expand All @@ -41,7 +41,7 @@
"eslint-plugin-obsidianmd": "^0.4.1",
"jsdom": "^29.1.1",
"obsidian": "1.13.1",
"obsidian-e2e": "0.6.0",
"obsidian-e2e": "^0.7.0",
"oxfmt": "^0.57.0",
"oxlint": "^1.72.0",
"semantic-release": "^25.0.5",
Expand Down
18 changes: 18 additions & 0 deletions scripts/e2e-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DEFAULT_SETTINGS } from "src/constants";
import { describe, expect, it } from "vitest";
import config from "../obsidian-e2e.config.mjs";

describe("obsidian-e2e.config.mjs", () => {
it("seeds a defaultData that mirrors the real DEFAULT_SETTINGS", () => {
// JSON cannot represent currentEpisode: undefined, so compare the serialized
// forms - this is exactly what lands in a freshly provisioned vault's
// data.json. It fails if a setting is added to src/constants.ts without
// updating the runner config's defaultData seed. The `schemaVersion: 2`
// persistence marker PodNotes writes alongside its settings is seeded too, so
// a provisioned vault opens on the current on-disk schema.
expect(JSON.parse(JSON.stringify(config.defaultData))).toEqual({
schemaVersion: 2,
...JSON.parse(JSON.stringify(DEFAULT_SETTINGS)),
});
});
});
Loading