@openparachute/vault 0.6.0-rc.1 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.parachute/module.json +14 -3
- package/README.md +32 -7
- package/core/src/content-range.test.ts +374 -0
- package/core/src/content-range.ts +185 -0
- package/core/src/core.test.ts +279 -26
- package/core/src/expand-visibility.test.ts +102 -0
- package/core/src/expand.ts +31 -3
- package/core/src/indexed-fields.ts +1 -1
- package/core/src/link-count.test.ts +301 -0
- package/core/src/links.ts +172 -22
- package/core/src/mcp.ts +254 -34
- package/core/src/notes.ts +172 -48
- package/core/src/obsidian-alignment.test.ts +375 -0
- package/core/src/obsidian.ts +234 -14
- package/core/src/portable-md.test.ts +40 -0
- package/core/src/portable-md.ts +142 -16
- package/core/src/query-perf-routing.test.ts +208 -0
- package/core/src/schema.ts +87 -11
- package/core/src/store.ts +69 -22
- package/core/src/tag-expand-axis.test.ts +301 -0
- package/core/src/tag-hierarchy.ts +80 -0
- package/core/src/tag-schemas.ts +61 -46
- package/core/src/triggers-store.test.ts +100 -0
- package/core/src/triggers-store.ts +165 -0
- package/core/src/types.ts +68 -4
- package/core/src/vault-projection.ts +20 -0
- package/core/src/wikilinks.ts +2 -2
- package/package.json +2 -3
- package/src/admin-spa.test.ts +100 -10
- package/src/admin-spa.ts +48 -3
- package/src/auth-hub-jwt.test.ts +8 -1
- package/src/auth-status.ts +2 -2
- package/src/auth.test.ts +39 -3
- package/src/auth.ts +31 -2
- package/src/auto-transcribe.test.ts +51 -0
- package/src/auto-transcribe.ts +24 -6
- package/src/autostart.test.ts +75 -0
- package/src/autostart.ts +84 -0
- package/src/cli.ts +434 -140
- package/src/config.test.ts +109 -0
- package/src/config.ts +157 -10
- package/src/content-range-routes.test.ts +178 -0
- package/src/export-watch.test.ts +23 -0
- package/src/export-watch.ts +14 -0
- package/src/git-preflight.test.ts +70 -0
- package/src/git-preflight.ts +68 -0
- package/src/github-device-flow.test.ts +265 -6
- package/src/github-device-flow.ts +297 -45
- package/src/hub-jwt.test.ts +75 -2
- package/src/hub-jwt.ts +43 -6
- package/src/init-summary.test.ts +120 -5
- package/src/init-summary.ts +67 -25
- package/src/live-match.test.ts +198 -0
- package/src/live-match.ts +310 -0
- package/src/mcp-install.test.ts +93 -0
- package/src/mcp-install.ts +106 -0
- package/src/mcp-tools.ts +80 -7
- package/src/mirror-config.test.ts +14 -0
- package/src/mirror-config.ts +11 -0
- package/src/mirror-credentials.test.ts +20 -0
- package/src/mirror-credentials.ts +6 -2
- package/src/mirror-import.test.ts +110 -0
- package/src/mirror-import.ts +71 -13
- package/src/mirror-manager.test.ts +51 -0
- package/src/mirror-manager.ts +73 -11
- package/src/mirror-routes.test.ts +1331 -110
- package/src/mirror-routes.ts +787 -30
- package/src/oauth-discovery.test.ts +55 -0
- package/src/oauth-discovery.ts +24 -5
- package/src/routes.ts +763 -122
- package/src/routing.test.ts +451 -5
- package/src/routing.ts +121 -5
- package/src/scopes.ts +1 -1
- package/src/server.ts +66 -4
- package/src/storage.test.ts +162 -0
- package/src/subscribe.test.ts +588 -0
- package/src/subscribe.ts +248 -0
- package/src/subscriptions.ts +295 -0
- package/src/tag-expand-routes.test.ts +45 -0
- package/src/tag-scope.ts +68 -1
- package/src/token-store.ts +7 -7
- package/src/transcription-worker.test.ts +471 -5
- package/src/transcription-worker.ts +212 -44
- package/src/triggers-api.test.ts +533 -0
- package/src/triggers-api.ts +295 -0
- package/src/triggers.ts +93 -7
- package/src/usage.test.ts +362 -0
- package/src/usage.ts +318 -0
- package/src/vault-create.test.ts +340 -12
- package/src/vault-name.test.ts +61 -3
- package/src/vault-name.ts +62 -14
- package/src/vault-remove.test.ts +187 -0
- package/src/vault-store.ts +10 -3
- package/src/vault.test.ts +1353 -62
- package/web/ui/dist/assets/index-BPgyIjR7.js +61 -0
- package/web/ui/dist/assets/index-J0pVP7I-.css +1 -0
- package/web/ui/dist/index.html +2 -2
- package/web/ui/dist/assets/index-DBe8Xiah.css +0 -1
- package/web/ui/dist/assets/index-DDRo6F4u.js +0 -60
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for `parachute-vault remove <name> --yes` — the cmdRemove
|
|
3
|
+
* hygiene from the 2026-06-09 hub-module-boundary migration (B1's CLI-side
|
|
4
|
+
* improvements):
|
|
5
|
+
*
|
|
6
|
+
* 1. services.json refresh — remove re-runs `selfRegister` (the same
|
|
7
|
+
* refresh cmdCreate does, #208) so the deleted vault's `/vault/<name>`
|
|
8
|
+
* path drops out of the parachute-vault row immediately instead of
|
|
9
|
+
* going stale until the next server boot.
|
|
10
|
+
* 2. last-vault marker — removing the LAST vault writes
|
|
11
|
+
* `auto_create: false` into the global config so the server boot's
|
|
12
|
+
* auto-create-default does NOT silently resurrect a fresh `default`
|
|
13
|
+
* (with fresh credentials). Fresh installs (no config.yaml at all)
|
|
14
|
+
* still auto-create — the Docker first-run path is preserved.
|
|
15
|
+
*
|
|
16
|
+
* Spawns the CLI in a temp PARACHUTE_HOME (never the operator's real
|
|
17
|
+
* ~/.parachute). In-process `readGlobalConfig` reads resolve PARACHUTE_HOME
|
|
18
|
+
* per call, so pointing the env var at the temp home inside a test is safe.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
|
22
|
+
import { resolve } from "path";
|
|
23
|
+
import { mkdtempSync, rmSync, existsSync, readFileSync } from "fs";
|
|
24
|
+
import { tmpdir } from "os";
|
|
25
|
+
import { join } from "path";
|
|
26
|
+
import { bootAutoCreateAllowed, readGlobalConfig } from "./config.ts";
|
|
27
|
+
import type { GlobalConfig } from "./config.ts";
|
|
28
|
+
|
|
29
|
+
const CLI = resolve(import.meta.dir, "cli.ts");
|
|
30
|
+
|
|
31
|
+
function runCli(
|
|
32
|
+
args: string[],
|
|
33
|
+
env: Record<string, string>,
|
|
34
|
+
): { exitCode: number; stdout: string; stderr: string } {
|
|
35
|
+
// Hermetic: don't inherit the dev/CI box's PARACHUTE_HUB_ORIGIN (same
|
|
36
|
+
// posture as vault-create.test.ts — a leaked origin flips guidance copy).
|
|
37
|
+
const baseEnv: Record<string, string | undefined> = { ...process.env };
|
|
38
|
+
delete baseEnv.PARACHUTE_HUB_ORIGIN;
|
|
39
|
+
const proc = Bun.spawnSync({
|
|
40
|
+
cmd: ["bun", CLI, ...args],
|
|
41
|
+
stdout: "pipe",
|
|
42
|
+
stderr: "pipe",
|
|
43
|
+
env: { ...baseEnv, ...env },
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
exitCode: proc.exitCode ?? -1,
|
|
47
|
+
stdout: new TextDecoder().decode(proc.stdout),
|
|
48
|
+
stderr: new TextDecoder().decode(proc.stderr),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Read the global config from the test home via the real parser. The
|
|
53
|
+
* config path resolves `process.env.PARACHUTE_HOME` per call, so a scoped
|
|
54
|
+
* override + restore is all it takes — no module re-import dance. */
|
|
55
|
+
function readGlobalConfigFrom(home: string): GlobalConfig {
|
|
56
|
+
const prior = process.env.PARACHUTE_HOME;
|
|
57
|
+
process.env.PARACHUTE_HOME = home;
|
|
58
|
+
try {
|
|
59
|
+
return readGlobalConfig();
|
|
60
|
+
} finally {
|
|
61
|
+
if (prior === undefined) delete process.env.PARACHUTE_HOME;
|
|
62
|
+
else process.env.PARACHUTE_HOME = prior;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function readServices(home: string): { name: string; paths: string[] }[] {
|
|
67
|
+
const raw = readFileSync(join(home, "services.json"), "utf-8");
|
|
68
|
+
return JSON.parse(raw).services;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let home: string;
|
|
72
|
+
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
home = mkdtempSync(join(tmpdir(), "vault-remove-test-"));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
rmSync(home, { recursive: true, force: true });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("vault remove — services.json refresh", () => {
|
|
82
|
+
test("removing a vault drops its /vault/<name> path immediately", () => {
|
|
83
|
+
runCli(["create", "alpha", "--json"], { PARACHUTE_HOME: home });
|
|
84
|
+
runCli(["create", "beta", "--json"], { PARACHUTE_HOME: home });
|
|
85
|
+
expect(readServices(home).find((s) => s.name === "parachute-vault")!.paths).toEqual([
|
|
86
|
+
"/vault/alpha",
|
|
87
|
+
"/vault/beta",
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
const { exitCode, stdout } = runCli(["remove", "beta", "--yes"], {
|
|
91
|
+
PARACHUTE_HOME: home,
|
|
92
|
+
});
|
|
93
|
+
expect(exitCode).toBe(0);
|
|
94
|
+
expect(stdout).toContain('Vault "beta" removed.');
|
|
95
|
+
|
|
96
|
+
// The stale-until-next-boot path is gone NOW — the hub's well-known
|
|
97
|
+
// fan-out stops advertising the deleted vault without a restart.
|
|
98
|
+
const vault = readServices(home).find((s) => s.name === "parachute-vault");
|
|
99
|
+
expect(vault).toBeDefined();
|
|
100
|
+
expect(vault!.paths).toEqual(["/vault/alpha"]);
|
|
101
|
+
// The vault data itself is gone too.
|
|
102
|
+
expect(existsSync(join(home, "vault", "data", "beta"))).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("removing the DEFAULT vault promotes the survivor and reorders paths", () => {
|
|
106
|
+
runCli(["create", "alpha", "--json"], { PARACHUTE_HOME: home }); // default
|
|
107
|
+
runCli(["create", "beta", "--json"], { PARACHUTE_HOME: home });
|
|
108
|
+
|
|
109
|
+
const { exitCode, stdout } = runCli(["remove", "alpha", "--yes"], {
|
|
110
|
+
PARACHUTE_HOME: home,
|
|
111
|
+
});
|
|
112
|
+
expect(exitCode).toBe(0);
|
|
113
|
+
expect(stdout).toContain('Default vault is now "beta".');
|
|
114
|
+
|
|
115
|
+
const config = readGlobalConfigFrom(home);
|
|
116
|
+
expect(config.default_vault).toBe("beta");
|
|
117
|
+
// selfRegister ran AFTER the default promotion, so paths[0] is the new
|
|
118
|
+
// default (the hub reads paths[0] as the canonical mount).
|
|
119
|
+
const vault = readServices(home).find((s) => s.name === "parachute-vault");
|
|
120
|
+
expect(vault!.paths).toEqual(["/vault/beta"]);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("vault remove — last-vault auto_create marker", () => {
|
|
125
|
+
test("removing the last vault writes auto_create: false + boot honors it", () => {
|
|
126
|
+
runCli(["create", "solo", "--json"], { PARACHUTE_HOME: home });
|
|
127
|
+
|
|
128
|
+
const { exitCode, stdout } = runCli(["remove", "solo", "--yes"], {
|
|
129
|
+
PARACHUTE_HOME: home,
|
|
130
|
+
});
|
|
131
|
+
expect(exitCode).toBe(0);
|
|
132
|
+
expect(stdout).toContain("auto_create: false");
|
|
133
|
+
|
|
134
|
+
// The marker is in the YAML and the real parser reads it back.
|
|
135
|
+
const yaml = readFileSync(join(home, "vault", "config.yaml"), "utf-8");
|
|
136
|
+
expect(yaml).toMatch(/^auto_create: false$/m);
|
|
137
|
+
const config = readGlobalConfigFrom(home);
|
|
138
|
+
expect(config.auto_create).toBe(false);
|
|
139
|
+
|
|
140
|
+
// The boot gate (server.ts auto-create branch) honors the marker — no
|
|
141
|
+
// resurrection of a freshly-credentialed "default".
|
|
142
|
+
expect(bootAutoCreateAllowed(config)).toBe(false);
|
|
143
|
+
|
|
144
|
+
// services.json was still refreshed: with zero vaults the row falls back
|
|
145
|
+
// to the manifest's canonical paths — the SAME row a subsequent boot's
|
|
146
|
+
// selfRegister writes, so CLI-remove and boot agree on the zero-vault
|
|
147
|
+
// registration shape (and the hub still sees the module as installed).
|
|
148
|
+
const vault = readServices(home).find((s) => s.name === "parachute-vault");
|
|
149
|
+
expect(vault).toBeDefined();
|
|
150
|
+
expect(vault!.paths).toEqual(["/vault/default"]);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("removing a NON-last vault does not write the marker", () => {
|
|
154
|
+
runCli(["create", "alpha", "--json"], { PARACHUTE_HOME: home });
|
|
155
|
+
runCli(["create", "beta", "--json"], { PARACHUTE_HOME: home });
|
|
156
|
+
runCli(["remove", "beta", "--yes"], { PARACHUTE_HOME: home });
|
|
157
|
+
|
|
158
|
+
const yaml = readFileSync(join(home, "vault", "config.yaml"), "utf-8");
|
|
159
|
+
expect(yaml).not.toContain("auto_create");
|
|
160
|
+
const config = readGlobalConfigFrom(home);
|
|
161
|
+
expect(config.auto_create).toBeUndefined();
|
|
162
|
+
expect(bootAutoCreateAllowed(config)).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("fresh install (no config.yaml at all) still allows boot auto-create", () => {
|
|
166
|
+
// Docker first-run: PARACHUTE_HOME exists but no vault/config.yaml has
|
|
167
|
+
// ever been written. readGlobalConfig returns defaults — no marker —
|
|
168
|
+
// and the boot auto-create proceeds.
|
|
169
|
+
expect(existsSync(join(home, "vault", "config.yaml"))).toBe(false);
|
|
170
|
+
const config = readGlobalConfigFrom(home);
|
|
171
|
+
expect(config.auto_create).toBeUndefined();
|
|
172
|
+
expect(bootAutoCreateAllowed(config)).toBe(true);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("remove without --yes is a dry-run: no marker, no services change", () => {
|
|
176
|
+
runCli(["create", "solo", "--json"], { PARACHUTE_HOME: home });
|
|
177
|
+
const before = readServices(home).find((s) => s.name === "parachute-vault")!.paths;
|
|
178
|
+
|
|
179
|
+
const { exitCode, stdout } = runCli(["remove", "solo"], { PARACHUTE_HOME: home });
|
|
180
|
+
expect(exitCode).toBe(0);
|
|
181
|
+
expect(stdout).toContain("To confirm");
|
|
182
|
+
|
|
183
|
+
expect(existsSync(join(home, "vault", "data", "solo"))).toBe(true);
|
|
184
|
+
expect(readGlobalConfigFrom(home).auto_create).toBeUndefined();
|
|
185
|
+
expect(readServices(home).find((s) => s.name === "parachute-vault")!.paths).toEqual(before);
|
|
186
|
+
});
|
|
187
|
+
});
|
package/src/vault-store.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { SqliteStore } from "../core/src/store.ts";
|
|
9
9
|
import { defaultHookRegistry } from "../core/src/hooks.ts";
|
|
10
|
+
import type { Store } from "../core/src/types.ts";
|
|
10
11
|
import { openVaultDb } from "./db.ts";
|
|
11
12
|
|
|
12
13
|
export { SqliteStore as BunStore };
|
|
@@ -33,9 +34,15 @@ export function getVaultStore(name: string): SqliteStore {
|
|
|
33
34
|
return store;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Look up the vault name for a previously-opened store. Accepts the `Store`
|
|
39
|
+
* interface (not just the concrete `SqliteStore`) so hook handlers — which
|
|
40
|
+
* receive `Store` from the dispatcher — can resolve the vault. The WeakMap is
|
|
41
|
+
* keyed on the concrete instance the dispatcher passes through unchanged, so
|
|
42
|
+
* the lookup still hits.
|
|
43
|
+
*/
|
|
44
|
+
export function getVaultNameForStore(store: Store): string | undefined {
|
|
45
|
+
return storeToVault.get(store as SqliteStore);
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
/**
|