wikimemory 0.2.4 → 0.2.5
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/dist/npm-cli/scripts/cli.js +2 -1
- package/dist/npm-cli/scripts/client-tools.js +11 -13
- package/dist/npm-cli/scripts/dev.js +7 -12
- package/dist/npm-cli/scripts/passkeys.js +2 -1
- package/dist/npm-cli/scripts/setup.js +9 -45
- package/dist/npm-cli/scripts/status.js +7 -1
- package/dist/npm-cli/scripts/subprocess.js +74 -0
- package/dist/npm-cli/scripts/uninstall.js +7 -25
- package/dist/npm-cli/scripts/upgrade.js +20 -29
- package/dist/npm-cli/src/version.js +1 -1
- package/package.json +1 -1
- package/release-manifest.json +1 -1
- package/src/version.ts +1 -1
|
@@ -5,6 +5,7 @@ import { WIKIMEMORY_VERSION } from "../src/version.js";
|
|
|
5
5
|
import { deploymentArguments, installArguments } from "./cli-options.js";
|
|
6
6
|
import { deploymentPaths } from "./deployment-record.js";
|
|
7
7
|
import { packageRoot } from "./package-root.js";
|
|
8
|
+
import { conciseError } from "./subprocess.js";
|
|
8
9
|
const [command, ...args] = process.argv.slice(2);
|
|
9
10
|
function usage() {
|
|
10
11
|
return `Wikimemory ${WIKIMEMORY_VERSION}\nPersonal, passkey-protected memory for Claude, Codex, and other MCP clients.\n\nUsage: wikimemory COMMAND [OPTIONS]\n\nStart a personal Cloudflare-hosted instance:\n npx --yes wikimemory install\n\nTry it locally without a Cloudflare deployment:\n npx --yes wikimemory dev\n\nCommands:\n wikimemory install [--deployment NAME] [installer options]\n wikimemory recover [--deployment NAME]\n wikimemory dev [wrangler dev options]\n wikimemory status [--deployment NAME]\n wikimemory upgrade [--deployment NAME] [--yes]\n wikimemory passkeys [--deployment NAME] list|add|revoke\n wikimemory connect [--deployment NAME] codex|claude\n wikimemory skills install codex|claude\n wikimemory uninstall [--deployment NAME] [--apply]\n wikimemory --version\n\nUse \`wikimemory COMMAND --help\` for command-specific options.`;
|
|
@@ -74,6 +75,6 @@ async function main() {
|
|
|
74
75
|
throw new Error(`Unknown command: ${command}`);
|
|
75
76
|
}
|
|
76
77
|
main().catch((error) => {
|
|
77
|
-
console.error(`Wikimemory failed: ${error
|
|
78
|
+
console.error(`Wikimemory failed: ${conciseError(error)}`);
|
|
78
79
|
process.exitCode = 1;
|
|
79
80
|
});
|
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { cp, mkdir } from "node:fs/promises";
|
|
3
2
|
import { homedir } from "node:os";
|
|
4
3
|
import { join } from "node:path";
|
|
5
4
|
import { deploymentRecordPath, readDeploymentRecord } from "./deployment-record.js";
|
|
5
|
+
import { commandFailureMessage, runCommand } from "./subprocess.js";
|
|
6
6
|
function client(value) {
|
|
7
7
|
if (value === "codex" || value === "claude")
|
|
8
8
|
return value;
|
|
9
9
|
throw new Error("Client must be codex or claude");
|
|
10
10
|
}
|
|
11
|
-
async function run(command, args) {
|
|
12
|
-
await
|
|
13
|
-
|
|
14
|
-
child.on("error", reject);
|
|
15
|
-
child.on("close", (code) => {
|
|
16
|
-
if (code === 0)
|
|
17
|
-
resolve();
|
|
18
|
-
else
|
|
19
|
-
reject(new Error(`${command} exited with status ${code ?? "unknown"}`));
|
|
20
|
-
});
|
|
11
|
+
async function run(command, args, interactive = false) {
|
|
12
|
+
const result = await runCommand(command, args, {
|
|
13
|
+
...(interactive ? { forwardLimitBytes: 4000, inheritStdin: true } : {})
|
|
21
14
|
});
|
|
15
|
+
if (result.exitCode !== 0)
|
|
16
|
+
throw new Error(commandFailureMessage(`${command} client configuration`, result));
|
|
22
17
|
}
|
|
23
18
|
export async function connectClient(deployment, selected) {
|
|
24
19
|
const target = client(selected);
|
|
@@ -26,7 +21,8 @@ export async function connectClient(deployment, selected) {
|
|
|
26
21
|
const endpoint = `${record.origin}/mcp`;
|
|
27
22
|
if (target === "codex") {
|
|
28
23
|
await run("codex", ["mcp", "add", deployment, "--url", endpoint]);
|
|
29
|
-
|
|
24
|
+
console.log("Complete authorization in your browser…");
|
|
25
|
+
await run("codex", ["mcp", "login", deployment, "--scopes", "memory:read,memory:write"], true);
|
|
30
26
|
}
|
|
31
27
|
else {
|
|
32
28
|
await run("claude", [
|
|
@@ -39,8 +35,10 @@ export async function connectClient(deployment, selected) {
|
|
|
39
35
|
deployment,
|
|
40
36
|
endpoint
|
|
41
37
|
]);
|
|
42
|
-
|
|
38
|
+
console.log("Complete authorization in your browser…");
|
|
39
|
+
await run("claude", ["mcp", "login", deployment], true);
|
|
43
40
|
}
|
|
41
|
+
console.log(`Connected ${target} to ${deployment}.`);
|
|
44
42
|
}
|
|
45
43
|
export async function installSkills(packageRoot, selected) {
|
|
46
44
|
const target = client(selected);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
2
|
import { join, resolve } from "node:path";
|
|
3
|
+
import { commandFailureMessage, runCommand } from "./subprocess.js";
|
|
4
4
|
export function localConfig(packageRoot) {
|
|
5
5
|
return `${JSON.stringify({
|
|
6
6
|
$schema: join(packageRoot, "node_modules", "wrangler", "config-schema.json"),
|
|
@@ -27,24 +27,19 @@ export function localConfig(packageRoot) {
|
|
|
27
27
|
}, null, 2)}\n`;
|
|
28
28
|
}
|
|
29
29
|
async function runWrangler(args, inherited = false) {
|
|
30
|
-
await
|
|
31
|
-
|
|
32
|
-
stdio: inherited ? "inherit" : ["ignore", "inherit", "inherit"]
|
|
33
|
-
});
|
|
34
|
-
child.on("error", reject);
|
|
35
|
-
child.on("close", (code) => {
|
|
36
|
-
if (code === 0)
|
|
37
|
-
resolvePromise();
|
|
38
|
-
else
|
|
39
|
-
reject(new Error(`Wrangler exited with status ${code ?? "unknown"}`));
|
|
40
|
-
});
|
|
30
|
+
const result = await runCommand("npx", ["wrangler", ...args], {
|
|
31
|
+
...(inherited ? { forwardLimitBytes: 8000, inheritStdin: true } : {})
|
|
41
32
|
});
|
|
33
|
+
if (result.exitCode !== 0)
|
|
34
|
+
throw new Error(commandFailureMessage("Wrangler", result));
|
|
42
35
|
}
|
|
43
36
|
export async function runDev(packageRoot, args) {
|
|
44
37
|
const stateDirectory = resolve(".wikimemory", "dev");
|
|
45
38
|
await mkdir(stateDirectory, { recursive: true });
|
|
46
39
|
const configPath = join(stateDirectory, "wrangler.jsonc");
|
|
47
40
|
await writeFile(configPath, localConfig(packageRoot), "utf8");
|
|
41
|
+
console.log("Preparing local database…");
|
|
48
42
|
await runWrangler(["d1", "migrations", "apply", "wikimemory", "--local", "--config", configPath]);
|
|
43
|
+
console.log("Starting local Wikimemory…");
|
|
49
44
|
await runWrangler(["dev", "--config", configPath, ...args], true);
|
|
50
45
|
}
|
|
@@ -7,6 +7,7 @@ import { pathToFileURL } from "node:url";
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { passkeyRuntime } from "./lifecycle-runtime.js";
|
|
9
9
|
import { configuredOrigin } from "./setup.js";
|
|
10
|
+
import { conciseError } from "./subprocess.js";
|
|
10
11
|
const PRODUCTION_CONFIG = passkeyRuntime.config;
|
|
11
12
|
const CLI_STATE = passkeyRuntime.client;
|
|
12
13
|
const CALLBACK = "http://127.0.0.1:45831/callback";
|
|
@@ -191,7 +192,7 @@ export async function runPasskeys(args) {
|
|
|
191
192
|
const entrypoint = process.argv[1];
|
|
192
193
|
if (entrypoint !== undefined && import.meta.url === pathToFileURL(entrypoint).href) {
|
|
193
194
|
runPasskeys(process.argv.slice(2)).catch((error) => {
|
|
194
|
-
console.error(`Passkey command failed: ${error
|
|
195
|
+
console.error(`Passkey command failed: ${conciseError(error)}`);
|
|
195
196
|
process.exitCode = 1;
|
|
196
197
|
});
|
|
197
198
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
2
|
import { constants } from "node:fs";
|
|
4
3
|
import { access, mkdtemp, readdir, readFile, rm, unlink, writeFile } from "node:fs/promises";
|
|
@@ -11,6 +10,8 @@ import { z } from "zod";
|
|
|
11
10
|
import { WIKIMEMORY_VERSION } from "../src/version.js";
|
|
12
11
|
import { deploymentRecordFromConfig, writeDeploymentRecord } from "./deployment-record.js";
|
|
13
12
|
import { installedRecordPath, packageRoot, setupRuntime } from "./lifecycle-runtime.js";
|
|
13
|
+
import { commandFailureMessage, conciseError, runCommand } from "./subprocess.js";
|
|
14
|
+
export { commandFailureMessage } from "./subprocess.js";
|
|
14
15
|
const PACKAGE_ROOT = packageRoot;
|
|
15
16
|
const CONFIG_PATH = setupRuntime.config;
|
|
16
17
|
const STATE_PATH = setupRuntime.progress;
|
|
@@ -55,16 +56,6 @@ function cloudflareOperation(args) {
|
|
|
55
56
|
return "KV namespace creation";
|
|
56
57
|
return `Cloudflare ${command}`;
|
|
57
58
|
}
|
|
58
|
-
function conciseDiagnostic(result) {
|
|
59
|
-
const raw = result.stderr.trim() || result.stdout.trim();
|
|
60
|
-
const plain = raw.replaceAll("\u001b", "").replaceAll(/\s+/gu, " ").trim();
|
|
61
|
-
if (plain === "")
|
|
62
|
-
return `process exited with status ${result.exitCode}`;
|
|
63
|
-
return plain.length > 600 ? `${plain.slice(0, 597)}...` : plain;
|
|
64
|
-
}
|
|
65
|
-
export function commandFailureMessage(operation, result) {
|
|
66
|
-
return `${operation} failed: ${conciseDiagnostic(result)}`;
|
|
67
|
-
}
|
|
68
59
|
async function sleep(milliseconds) {
|
|
69
60
|
await new Promise((resolve) => {
|
|
70
61
|
setTimeout(resolve, milliseconds);
|
|
@@ -146,44 +137,18 @@ async function exists(path) {
|
|
|
146
137
|
}
|
|
147
138
|
}
|
|
148
139
|
async function run(command, args, input, allowFailure = false) {
|
|
149
|
-
const result = await
|
|
150
|
-
const child = spawn(command, args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
151
|
-
let stdout = "";
|
|
152
|
-
let stderr = "";
|
|
153
|
-
child.stdout.setEncoding("utf8");
|
|
154
|
-
child.stderr.setEncoding("utf8");
|
|
155
|
-
child.stdout.on("data", (chunk) => {
|
|
156
|
-
stdout += chunk;
|
|
157
|
-
});
|
|
158
|
-
child.stderr.on("data", (chunk) => {
|
|
159
|
-
stderr += chunk;
|
|
160
|
-
});
|
|
161
|
-
child.on("error", (error) => {
|
|
162
|
-
reject(error);
|
|
163
|
-
});
|
|
164
|
-
child.on("close", (code) => {
|
|
165
|
-
resolve({ stdout, stderr, exitCode: code ?? -1 });
|
|
166
|
-
});
|
|
167
|
-
child.stdin.end(input === undefined ? undefined : `${input}\n`);
|
|
168
|
-
});
|
|
140
|
+
const result = await runCommand(command, args, { ...(input === undefined ? {} : { input }) });
|
|
169
141
|
if (result.exitCode !== 0 && !allowFailure)
|
|
170
142
|
throw new Error(commandFailureMessage(cloudflareOperation(args), result));
|
|
171
143
|
return result;
|
|
172
144
|
}
|
|
173
145
|
async function runInteractive(command, args) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
child.on("error", (error) => {
|
|
178
|
-
reject(error);
|
|
179
|
-
});
|
|
180
|
-
child.on("close", (code) => {
|
|
181
|
-
if (code === 0)
|
|
182
|
-
resolve();
|
|
183
|
-
else
|
|
184
|
-
reject(new Error(`${command} exited with status ${code ?? "unknown"}`));
|
|
185
|
-
});
|
|
146
|
+
const result = await runCommand(command, args, {
|
|
147
|
+
forwardLimitBytes: 4000,
|
|
148
|
+
inheritStdin: true
|
|
186
149
|
});
|
|
150
|
+
if (result.exitCode !== 0)
|
|
151
|
+
throw new Error(commandFailureMessage(cloudflareOperation(args), result));
|
|
187
152
|
}
|
|
188
153
|
export function initialConfig(options, account) {
|
|
189
154
|
return `${JSON.stringify({
|
|
@@ -504,7 +469,6 @@ async function finalizeDeployment(options) {
|
|
|
504
469
|
const deploymentRecord = deploymentRecordFromConfig(finalConfig, WIKIMEMORY_VERSION, options.kvName);
|
|
505
470
|
const recordPath = installedRecordPath(options.workerName);
|
|
506
471
|
await writeDeploymentRecord(deploymentRecord, recordPath);
|
|
507
|
-
console.log(`\nSaved deployment record: ${recordPath}`);
|
|
508
472
|
console.log(`\n${handoff(origin, secret.raw, options.workerName)}`);
|
|
509
473
|
}
|
|
510
474
|
async function freshDeployment(options) {
|
|
@@ -602,7 +566,7 @@ export async function runSetup(args) {
|
|
|
602
566
|
const entrypoint = process.argv[1];
|
|
603
567
|
if (entrypoint !== undefined && import.meta.url === pathToFileURL(entrypoint).href) {
|
|
604
568
|
runSetup(process.argv.slice(2)).catch((error) => {
|
|
605
|
-
console.error(`\nSetup failed: ${error
|
|
569
|
+
console.error(`\nSetup failed: ${conciseError(error)}`);
|
|
606
570
|
process.exitCode = 1;
|
|
607
571
|
});
|
|
608
572
|
}
|
|
@@ -45,5 +45,11 @@ export async function deploymentStatus(deployment) {
|
|
|
45
45
|
}
|
|
46
46
|
export async function runStatus(deployment) {
|
|
47
47
|
const status = await deploymentStatus(deployment);
|
|
48
|
-
console.log(
|
|
48
|
+
console.log(statusSummary(status));
|
|
49
|
+
}
|
|
50
|
+
export function statusSummary(status) {
|
|
51
|
+
const mismatch = status.recordedVersion === status.runningVersion
|
|
52
|
+
? ""
|
|
53
|
+
: `\nLocal record: ${status.recordedVersion} (run wikimemory upgrade to reconcile)`;
|
|
54
|
+
return `Wikimemory ${status.deployment}: ready\nURL: ${status.origin}\nVersion: ${status.runningVersion}\nDatabase: up to date.${mismatch}`;
|
|
49
55
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
import { stripVTControlCharacters } from "node:util";
|
|
4
|
+
export function boundedOutputChunk(chunk, remaining) {
|
|
5
|
+
const visible = chunk.slice(0, remaining);
|
|
6
|
+
return {
|
|
7
|
+
visible,
|
|
8
|
+
remaining: Math.max(0, remaining - visible.length),
|
|
9
|
+
truncated: visible.length < chunk.length
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function forwardBounded(destination, chunk, state) {
|
|
13
|
+
const limited = boundedOutputChunk(chunk, state.remaining);
|
|
14
|
+
if (limited.visible !== "")
|
|
15
|
+
destination.write(limited.visible);
|
|
16
|
+
state.remaining = limited.remaining;
|
|
17
|
+
if (limited.truncated && !state.suppressed) {
|
|
18
|
+
destination.write("\n… additional command output suppressed.\n");
|
|
19
|
+
state.suppressed = true;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function runCommand(command, args, options = {}) {
|
|
23
|
+
return await new Promise((resolve, reject) => {
|
|
24
|
+
const child = spawn(command, args, {
|
|
25
|
+
stdio: [options.inheritStdin === true ? "inherit" : "pipe", "pipe", "pipe"]
|
|
26
|
+
});
|
|
27
|
+
if (child.stdout === null || child.stderr === null) {
|
|
28
|
+
child.kill();
|
|
29
|
+
reject(new Error("Subprocess output pipes were not created"));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
let stdout = "";
|
|
33
|
+
let stderr = "";
|
|
34
|
+
const stdoutForward = { remaining: options.forwardLimitBytes ?? 0, suppressed: false };
|
|
35
|
+
const stderrForward = { remaining: options.forwardLimitBytes ?? 0, suppressed: false };
|
|
36
|
+
child.stdout.setEncoding("utf8");
|
|
37
|
+
child.stderr.setEncoding("utf8");
|
|
38
|
+
child.stdout.on("data", (chunk) => {
|
|
39
|
+
stdout += chunk;
|
|
40
|
+
if (options.forwardLimitBytes !== undefined)
|
|
41
|
+
forwardBounded(process.stdout, chunk, stdoutForward);
|
|
42
|
+
});
|
|
43
|
+
child.stderr.on("data", (chunk) => {
|
|
44
|
+
stderr += chunk;
|
|
45
|
+
if (options.forwardLimitBytes !== undefined)
|
|
46
|
+
forwardBounded(process.stderr, chunk, stderrForward);
|
|
47
|
+
});
|
|
48
|
+
child.on("error", reject);
|
|
49
|
+
child.on("close", (code) => {
|
|
50
|
+
resolve({ stdout, stderr, exitCode: code ?? -1 });
|
|
51
|
+
});
|
|
52
|
+
if (child.stdin !== null)
|
|
53
|
+
child.stdin.end(options.input === undefined ? undefined : `${options.input}\n`);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export function conciseDiagnostic(result, maximumLength = 600) {
|
|
57
|
+
const raw = result.stderr.trim() || result.stdout.trim();
|
|
58
|
+
const plain = stripVTControlCharacters(raw).replaceAll(/\s+/gu, " ").trim();
|
|
59
|
+
if (plain === "")
|
|
60
|
+
return `process exited with status ${result.exitCode}`;
|
|
61
|
+
if (plain.length <= maximumLength)
|
|
62
|
+
return plain;
|
|
63
|
+
return `${plain.slice(0, Math.max(0, maximumLength - 3))}...`;
|
|
64
|
+
}
|
|
65
|
+
export function commandFailureMessage(operation, result) {
|
|
66
|
+
return `${operation} failed: ${conciseDiagnostic(result)}`;
|
|
67
|
+
}
|
|
68
|
+
export function conciseError(error, maximumLength = 600) {
|
|
69
|
+
const raw = error instanceof Error ? error.message : "Unknown error";
|
|
70
|
+
const singleLine = stripVTControlCharacters(raw).replaceAll(/\s+/gu, " ").trim();
|
|
71
|
+
if (singleLine.length <= maximumLength)
|
|
72
|
+
return singleLine;
|
|
73
|
+
return `${singleLine.slice(0, Math.max(0, maximumLength - 3))}...`;
|
|
74
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
3
2
|
import process from "node:process";
|
|
4
3
|
import { createInterface } from "node:readline/promises";
|
|
5
4
|
import { pathToFileURL } from "node:url";
|
|
6
5
|
import { removePackagedDeploymentRecord, uninstallRuntime } from "./lifecycle-runtime.js";
|
|
7
6
|
import { bindingProperty, configValue, deploymentListIndicatesExisting } from "./setup.js";
|
|
7
|
+
import { commandFailureMessage, conciseError, runCommand as executeCommand } from "./subprocess.js";
|
|
8
8
|
const CONFIG_PATH = uninstallRuntime.config;
|
|
9
9
|
const STATE_PATH = uninstallRuntime.installProgress;
|
|
10
10
|
const UNINSTALL_STATE_PATH = uninstallRuntime.uninstallProgress;
|
|
@@ -54,30 +54,9 @@ export function clientRemovalInstructions(connectorName = "wikimemory") {
|
|
|
54
54
|
return `Client registrations are not removed automatically. Disconnect each configured client separately:\n\n codex mcp logout ${connectorName}\n codex mcp remove ${connectorName}\n\n claude mcp logout ${connectorName}\n claude mcp remove --scope user ${connectorName}\n\nFor Claude web or mobile, remove the custom connector from Settings > Connectors.`;
|
|
55
55
|
}
|
|
56
56
|
async function run(args, allowFailure = false) {
|
|
57
|
-
|
|
58
|
-
const result = await new Promise((resolve, reject) => {
|
|
59
|
-
const child = spawn("npx", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
60
|
-
let stdout = "";
|
|
61
|
-
let stderr = "";
|
|
62
|
-
child.stdout.setEncoding("utf8");
|
|
63
|
-
child.stderr.setEncoding("utf8");
|
|
64
|
-
child.stdout.on("data", (chunk) => {
|
|
65
|
-
stdout += chunk;
|
|
66
|
-
process.stdout.write(chunk);
|
|
67
|
-
});
|
|
68
|
-
child.stderr.on("data", (chunk) => {
|
|
69
|
-
stderr += chunk;
|
|
70
|
-
process.stderr.write(chunk);
|
|
71
|
-
});
|
|
72
|
-
child.on("error", (error) => {
|
|
73
|
-
reject(error);
|
|
74
|
-
});
|
|
75
|
-
child.on("close", (code) => {
|
|
76
|
-
resolve({ stdout, stderr, exitCode: code ?? -1 });
|
|
77
|
-
});
|
|
78
|
-
});
|
|
57
|
+
const result = await executeCommand("npx", args);
|
|
79
58
|
if (result.exitCode !== 0 && !allowFailure)
|
|
80
|
-
throw new Error(
|
|
59
|
+
throw new Error(commandFailureMessage("Cloudflare resource deletion", result));
|
|
81
60
|
return result;
|
|
82
61
|
}
|
|
83
62
|
async function requireExactName(workerName, supplied) {
|
|
@@ -145,6 +124,7 @@ export async function runUninstall(args, runCommand = run) {
|
|
|
145
124
|
const progress = await loadProgress(targets);
|
|
146
125
|
await saveProgress(progress);
|
|
147
126
|
if (!progress.workerDeleted) {
|
|
127
|
+
console.log(" Removing Worker…");
|
|
148
128
|
const probe = await runCommand([
|
|
149
129
|
"wrangler",
|
|
150
130
|
"deployments",
|
|
@@ -172,6 +152,7 @@ export async function runUninstall(args, runCommand = run) {
|
|
|
172
152
|
await saveProgress(progress);
|
|
173
153
|
}
|
|
174
154
|
if (!progress.kvDeleted) {
|
|
155
|
+
console.log(" Removing OAuth state…");
|
|
175
156
|
await runCommand([
|
|
176
157
|
"wrangler",
|
|
177
158
|
"kv",
|
|
@@ -187,6 +168,7 @@ export async function runUninstall(args, runCommand = run) {
|
|
|
187
168
|
await saveProgress(progress);
|
|
188
169
|
}
|
|
189
170
|
if (!progress.databaseDeleted) {
|
|
171
|
+
console.log(" Removing database…");
|
|
190
172
|
await runCommand([
|
|
191
173
|
"wrangler",
|
|
192
174
|
"d1",
|
|
@@ -214,7 +196,7 @@ export async function runUninstall(args, runCommand = run) {
|
|
|
214
196
|
const entrypoint = process.argv[1];
|
|
215
197
|
if (entrypoint !== undefined && import.meta.url === pathToFileURL(entrypoint).href) {
|
|
216
198
|
runUninstall(process.argv.slice(2)).catch((error) => {
|
|
217
|
-
console.error(`\nUninstall failed: ${error
|
|
199
|
+
console.error(`\nUninstall failed: ${conciseError(error)}`);
|
|
218
200
|
process.exitCode = 1;
|
|
219
201
|
});
|
|
220
202
|
}
|
|
@@ -7,6 +7,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
import { deploymentRecordPath, readDeploymentRecord, writeDeploymentRecord } from "./deployment-record.js";
|
|
9
9
|
import { packageRoot } from "./package-root.js";
|
|
10
|
+
import { commandFailureMessage, runCommand } from "./subprocess.js";
|
|
10
11
|
const PACKAGE_ROOT = packageRoot();
|
|
11
12
|
const DEPLOYMENT_NAME = /^[a-z0-9][a-z0-9-]{0,62}$/u;
|
|
12
13
|
const MIGRATION_NAME = /^\d{4}_[a-z0-9_]+\.sql$/u;
|
|
@@ -134,28 +135,14 @@ function migrationBundle(sql, migration) {
|
|
|
134
135
|
return `${sql.trimEnd()}\n\nINSERT INTO d1_migrations(name) VALUES ('${name}');\n`;
|
|
135
136
|
}
|
|
136
137
|
async function command(commandName, args) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
stdout += chunk;
|
|
146
|
-
process.stdout.write(chunk);
|
|
147
|
-
});
|
|
148
|
-
child.stderr.on("data", (chunk) => {
|
|
149
|
-
stderr += chunk;
|
|
150
|
-
process.stderr.write(chunk);
|
|
151
|
-
});
|
|
152
|
-
child.on("error", (error) => {
|
|
153
|
-
reject(error);
|
|
154
|
-
});
|
|
155
|
-
child.on("close", (code) => {
|
|
156
|
-
resolve({ stdout, stderr, exitCode: code ?? -1 });
|
|
157
|
-
});
|
|
158
|
-
});
|
|
138
|
+
return await runCommand(commandName, args);
|
|
139
|
+
}
|
|
140
|
+
export function upgradeSummary(record, currentVersion, targetVersion, databaseUpdates) {
|
|
141
|
+
const updates = databaseUpdates === 0 ? "none" : `${databaseUpdates} required`;
|
|
142
|
+
return `Upgrade Wikimemory “${record.workerName}” at ${record.origin}\n Version: ${currentVersion} -> ${targetVersion}\n Database updates: ${updates}`;
|
|
143
|
+
}
|
|
144
|
+
export function readySummary(version, alreadyCurrent = false) {
|
|
145
|
+
return `Wikimemory ${version} is ${alreadyCurrent ? "already " : ""}ready.\nDatabase: up to date.`;
|
|
159
146
|
}
|
|
160
147
|
function parsedJson(stdout) {
|
|
161
148
|
return JSON.parse(stdout);
|
|
@@ -264,7 +251,7 @@ export async function runUpgrade(args) {
|
|
|
264
251
|
command("npx", ["wrangler", "kv", "namespace", "list", ...common])
|
|
265
252
|
]);
|
|
266
253
|
if ([whoami, deployments, d1, kv].some((result) => result.exitCode !== 0))
|
|
267
|
-
throw new Error("Cloudflare target preflight
|
|
254
|
+
throw new Error(commandFailureMessage("Cloudflare target preflight", [whoami, deployments, d1, kv].find((result) => result.exitCode !== 0) ?? whoami));
|
|
268
255
|
const whoamiBody = z
|
|
269
256
|
.object({ accounts: z.array(z.object({ id: z.string() })) })
|
|
270
257
|
.parse(parsedJson(whoami.stdout));
|
|
@@ -292,7 +279,7 @@ export async function runUpgrade(args) {
|
|
|
292
279
|
"SELECT name FROM d1_migrations ORDER BY id"
|
|
293
280
|
]);
|
|
294
281
|
if (listed.exitCode !== 0)
|
|
295
|
-
throw new Error("
|
|
282
|
+
throw new Error(commandFailureMessage("Database version check", listed));
|
|
296
283
|
const query = z
|
|
297
284
|
.array(z.object({ results: z.array(z.object({ name: z.string() })) }))
|
|
298
285
|
.parse(parsedJson(listed.stdout));
|
|
@@ -308,10 +295,12 @@ export async function runUpgrade(args) {
|
|
|
308
295
|
if (deploymentIsCurrent(current, manifest, pending)) {
|
|
309
296
|
await verifyRelease(record.origin, manifest);
|
|
310
297
|
await writeDeploymentRecord({ ...record, installedVersion: manifest.version }, recordPath);
|
|
311
|
-
console.log(`\
|
|
298
|
+
console.log(`\n${readySummary(manifest.version, true)}`);
|
|
312
299
|
return;
|
|
313
300
|
}
|
|
314
|
-
await confirmUpgrade(`\
|
|
301
|
+
await confirmUpgrade(`\n${upgradeSummary(record, current, manifest.version, pending.length)}`, options.yes);
|
|
302
|
+
if (pending.length > 0)
|
|
303
|
+
console.log(" Updating database…");
|
|
315
304
|
for (const migration of pending) {
|
|
316
305
|
const bundlePath = join(temporary, migration.name);
|
|
317
306
|
const sql = await readFile(join(PACKAGE_ROOT, "migrations", migration.name), "utf8");
|
|
@@ -327,14 +316,16 @@ export async function runUpgrade(args) {
|
|
|
327
316
|
bundlePath
|
|
328
317
|
]);
|
|
329
318
|
if (result.exitCode !== 0)
|
|
330
|
-
throw new Error(
|
|
319
|
+
throw new Error(commandFailureMessage("Database update", result));
|
|
331
320
|
}
|
|
321
|
+
console.log(" Deploying Wikimemory…");
|
|
332
322
|
const deployed = await command("npx", ["wrangler", "deploy", "--strict", ...common]);
|
|
333
323
|
if (deployed.exitCode !== 0)
|
|
334
|
-
throw new Error("Worker deployment
|
|
324
|
+
throw new Error(commandFailureMessage("Worker deployment", deployed));
|
|
325
|
+
console.log(" Verifying deployment…");
|
|
335
326
|
await verifyRelease(record.origin, manifest);
|
|
336
327
|
await writeDeploymentRecord({ ...record, installedVersion: manifest.version }, recordPath);
|
|
337
|
-
console.log(`\
|
|
328
|
+
console.log(`\n${readySummary(manifest.version)}`);
|
|
338
329
|
}
|
|
339
330
|
finally {
|
|
340
331
|
await rm(temporary, { recursive: true, force: true });
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const WIKIMEMORY_VERSION = "0.2.
|
|
1
|
+
export const WIKIMEMORY_VERSION = "0.2.5";
|
|
2
2
|
export const LATEST_SCHEMA_VERSION = "0004_credential_bound_registration_tokens.sql";
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const WIKIMEMORY_VERSION = "0.2.
|
|
1
|
+
export const WIKIMEMORY_VERSION = "0.2.5";
|
|
2
2
|
export const LATEST_SCHEMA_VERSION = "0004_credential_bound_registration_tokens.sql";
|