@push.rocks/smartdb 5.0.5 → 5.2.0

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.
Files changed (40) hide show
  1. package/.smartconfig.json +26 -5
  2. package/dist_rust/rustdb_linux_amd64 +0 -0
  3. package/dist_rust/rustdb_linux_amd64.tsrust-build.json +14 -0
  4. package/dist_rust/rustdb_linux_arm64 +0 -0
  5. package/dist_rust/rustdb_linux_arm64.tsrust-build.json +14 -0
  6. package/dist_rust/rustdb_macos_amd64 +0 -0
  7. package/dist_rust/rustdb_macos_amd64.tsrust-build.json +14 -0
  8. package/dist_rust/rustdb_macos_arm64 +0 -0
  9. package/dist_rust/rustdb_macos_arm64.tsrust-build.json +14 -0
  10. package/dist_ts/00_commitinfo_data.js +1 -1
  11. package/dist_ts/index.d.ts +2 -1
  12. package/dist_ts/index.js +2 -1
  13. package/dist_ts/ts_local/classes.localsmartdb.d.ts +10 -1
  14. package/dist_ts/ts_local/classes.localsmartdb.js +133 -2
  15. package/dist_ts/ts_local/index.d.ts +2 -0
  16. package/dist_ts/ts_local/index.js +2 -1
  17. package/dist_ts/ts_smartdb/index.d.ts +2 -0
  18. package/dist_ts/ts_smartdb/index.js +2 -1
  19. package/dist_ts/ts_smartdb/plugins.d.ts +3 -1
  20. package/dist_ts/ts_smartdb/plugins.js +4 -2
  21. package/dist_ts/ts_smartdb/rust-db-bridge.d.ts +3 -0
  22. package/dist_ts/ts_smartdb/rust-db-bridge.js +8 -1
  23. package/dist_ts/ts_smartdb/server/SmartdbServer.js +4 -1
  24. package/dist_ts/ts_smartdb/storage-root-relocation.d.ts +27 -0
  25. package/dist_ts/ts_smartdb/storage-root-relocation.js +211 -0
  26. package/package.json +9 -4
  27. package/readme.hints.md +11 -1
  28. package/readme.md +74 -2
  29. package/readme.plan.md +1 -1
  30. package/scripts/release-artifacts.ts +626 -0
  31. package/third-party-notices.md +1 -1
  32. package/ts/00_commitinfo_data.ts +1 -1
  33. package/ts/index.ts +7 -0
  34. package/ts/ts_local/classes.localsmartdb.ts +151 -1
  35. package/ts/ts_local/index.ts +9 -0
  36. package/ts/ts_smartdb/index.ts +9 -0
  37. package/ts/ts_smartdb/plugins.ts +3 -1
  38. package/ts/ts_smartdb/rust-db-bridge.ts +26 -0
  39. package/ts/ts_smartdb/server/SmartdbServer.ts +3 -0
  40. package/ts/ts_smartdb/storage-root-relocation.ts +277 -0
@@ -0,0 +1,626 @@
1
+ import * as childProcess from 'node:child_process';
2
+ import * as crypto from 'node:crypto';
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+
6
+ type TReleaseArtifactMode = 'check' | 'build';
7
+
8
+ interface IReleaseArtifactEnvironment {
9
+ macosSsh: string;
10
+ macosTempRoot: string;
11
+ }
12
+
13
+ const provenanceSuffix = '.tsrust-build.json';
14
+ const defaultCommandTimeoutMs = 60 * 60 * 1000;
15
+ const capabilityCommandTimeoutMs = 5 * 60 * 1000;
16
+ const forceKillDelayMs = 10 * 1000;
17
+ const maximumCapturedOutputBytes = 16 * 1024 * 1024;
18
+ const sshConnectionArgs = [
19
+ '-o',
20
+ 'ServerAliveInterval=15',
21
+ '-o',
22
+ 'ServerAliveCountMax=2',
23
+ ];
24
+
25
+ type TForwardedSignal = 'SIGINT' | 'SIGTERM' | 'SIGHUP';
26
+
27
+ const shellQuote = (valueArg: string): string => `'${valueArg.replace(/'/g, `'"'"'`)}'`;
28
+
29
+ const executeCommand = (
30
+ commandArg: string,
31
+ argsArg: string[],
32
+ optionsArg: {
33
+ cwd?: string;
34
+ capture?: boolean;
35
+ timeoutMs?: number;
36
+ heartbeatStdin?: boolean;
37
+ } = {},
38
+ ): Promise<string> =>
39
+ new Promise((resolveArg, rejectArg) => {
40
+ const capture = optionsArg.capture === true;
41
+ const child = childProcess.spawn(commandArg, argsArg, {
42
+ cwd: optionsArg.cwd || process.cwd(),
43
+ env: process.env,
44
+ stdio: capture
45
+ ? ['ignore', 'pipe', 'pipe']
46
+ : optionsArg.heartbeatStdin
47
+ ? ['pipe', 'inherit', 'inherit']
48
+ : 'inherit',
49
+ detached: true,
50
+ });
51
+ let stdout = '';
52
+ let stderr = '';
53
+ let capturedOutputBytes = 0;
54
+ let timedOut = false;
55
+ let outputLimitExceeded = false;
56
+ let forwardedSignal: TForwardedSignal | undefined;
57
+ let forwardedSignalCount = 0;
58
+ let settled = false;
59
+ let forceKillTimer: NodeJS.Timeout | undefined;
60
+ let heartbeatTimer: NodeJS.Timeout | undefined;
61
+
62
+ const killProcessGroup = (signalArg: NodeJS.Signals): void => {
63
+ if (!child.pid) return;
64
+ try {
65
+ process.kill(-child.pid, signalArg);
66
+ } catch {
67
+ try {
68
+ child.kill(signalArg);
69
+ } catch {
70
+ // The process may already have exited.
71
+ }
72
+ }
73
+ };
74
+
75
+ const terminateProcessGroup = (signalArg: NodeJS.Signals): void => {
76
+ killProcessGroup(signalArg);
77
+ if (!forceKillTimer) {
78
+ forceKillTimer = setTimeout(
79
+ () => killProcessGroup('SIGKILL'),
80
+ forceKillDelayMs,
81
+ );
82
+ }
83
+ };
84
+
85
+ const processGroupExists = (): boolean => {
86
+ if (!child.pid) return false;
87
+ try {
88
+ process.kill(-child.pid, 0);
89
+ return true;
90
+ } catch (error) {
91
+ if ((error as NodeJS.ErrnoException).code === 'ESRCH') return false;
92
+ throw error;
93
+ }
94
+ };
95
+
96
+ const waitForProcessGroupExit = async (timeoutMsArg: number): Promise<boolean> => {
97
+ const deadline = Date.now() + timeoutMsArg;
98
+ while (processGroupExists() && Date.now() < deadline) {
99
+ await new Promise((resolveArg) => setTimeout(resolveArg, 100));
100
+ }
101
+ return !processGroupExists();
102
+ };
103
+
104
+ const ensureProcessGroupStopped = async (): Promise<void> => {
105
+ if (!processGroupExists()) return;
106
+ terminateProcessGroup('SIGTERM');
107
+ if (!(await waitForProcessGroupExit(forceKillDelayMs + 1000))) {
108
+ killProcessGroup('SIGKILL');
109
+ if (!(await waitForProcessGroupExit(2000))) {
110
+ throw new Error(`${commandArg} left a live descendant process group`);
111
+ }
112
+ }
113
+ };
114
+
115
+ const signalHandlers = new Map<TForwardedSignal, () => void>();
116
+ for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP'] as TForwardedSignal[]) {
117
+ const handler = (): void => {
118
+ forwardedSignal = signal;
119
+ forwardedSignalCount += 1;
120
+ if (forwardedSignalCount === 1) terminateProcessGroup(signal);
121
+ else killProcessGroup('SIGKILL');
122
+ };
123
+ signalHandlers.set(signal, handler);
124
+ process.on(signal, handler);
125
+ }
126
+
127
+ const timeout = setTimeout(() => {
128
+ timedOut = true;
129
+ terminateProcessGroup('SIGTERM');
130
+ }, optionsArg.timeoutMs || defaultCommandTimeoutMs);
131
+
132
+ if (optionsArg.heartbeatStdin) {
133
+ child.stdin?.on('error', () => {
134
+ // Process close handles a lost stdin channel.
135
+ });
136
+ const sendHeartbeat = (): void => {
137
+ if (!child.stdin?.destroyed) child.stdin?.write('heartbeat\n');
138
+ };
139
+ sendHeartbeat();
140
+ heartbeatTimer = setInterval(sendHeartbeat, 1000);
141
+ }
142
+
143
+ const cleanup = (): void => {
144
+ clearTimeout(timeout);
145
+ if (forceKillTimer) clearTimeout(forceKillTimer);
146
+ if (heartbeatTimer) clearInterval(heartbeatTimer);
147
+ child.stdin?.end();
148
+ for (const [signal, handler] of signalHandlers) {
149
+ process.off(signal, handler);
150
+ }
151
+ };
152
+
153
+ const captureChunk = (targetArg: 'stdout' | 'stderr', chunkArg: Buffer): void => {
154
+ capturedOutputBytes += chunkArg.length;
155
+ if (capturedOutputBytes > maximumCapturedOutputBytes) {
156
+ outputLimitExceeded = true;
157
+ terminateProcessGroup('SIGTERM');
158
+ return;
159
+ }
160
+ if (targetArg === 'stdout') stdout += chunkArg.toString('utf8');
161
+ else stderr += chunkArg.toString('utf8');
162
+ };
163
+ child.stdout?.on('data', (chunkArg: Buffer) => captureChunk('stdout', chunkArg));
164
+ child.stderr?.on('data', (chunkArg: Buffer) => captureChunk('stderr', chunkArg));
165
+
166
+ child.once('error', (errorArg) => {
167
+ if (settled) return;
168
+ settled = true;
169
+ cleanup();
170
+ rejectArg(errorArg);
171
+ });
172
+ child.once('close', (codeArg, signalArg) => {
173
+ if (settled) return;
174
+ settled = true;
175
+ void (async () => {
176
+ try {
177
+ await ensureProcessGroupStopped();
178
+ cleanup();
179
+ if (codeArg !== 0 && capture) {
180
+ if (stdout) process.stdout.write(stdout);
181
+ if (stderr) process.stderr.write(stderr);
182
+ }
183
+ if (timedOut) {
184
+ rejectArg(new Error(`${commandArg} exceeded its command timeout`));
185
+ } else if (outputLimitExceeded) {
186
+ rejectArg(new Error(`${commandArg} exceeded the captured output limit`));
187
+ } else if (forwardedSignal) {
188
+ rejectArg(new Error(`${commandArg} was interrupted by ${forwardedSignal}`));
189
+ } else if (codeArg !== 0) {
190
+ rejectArg(
191
+ new Error(
192
+ `${commandArg} exited with ${signalArg ? `signal ${signalArg}` : `status ${codeArg}`}`,
193
+ ),
194
+ );
195
+ } else {
196
+ resolveArg(stdout.trim());
197
+ }
198
+ } catch (error) {
199
+ cleanup();
200
+ rejectArg(error);
201
+ }
202
+ })();
203
+ });
204
+ });
205
+
206
+ const runCommand = async (
207
+ commandArg: string,
208
+ argsArg: string[],
209
+ cwdArg: string = process.cwd(),
210
+ timeoutMsArg: number = defaultCommandTimeoutMs,
211
+ heartbeatStdinArg: boolean = false,
212
+ ): Promise<void> => {
213
+ await executeCommand(commandArg, argsArg, {
214
+ cwd: cwdArg,
215
+ timeoutMs: timeoutMsArg,
216
+ heartbeatStdin: heartbeatStdinArg,
217
+ });
218
+ };
219
+
220
+ const captureCommand = async (
221
+ commandArg: string,
222
+ argsArg: string[],
223
+ cwdArg: string = process.cwd(),
224
+ ): Promise<string> => {
225
+ return executeCommand(commandArg, argsArg, {
226
+ cwd: cwdArg,
227
+ capture: true,
228
+ timeoutMs: capabilityCommandTimeoutMs,
229
+ });
230
+ };
231
+
232
+ const loadEnvironment = (): IReleaseArtifactEnvironment => {
233
+ const macosSsh = process.env.SMARTDB_RELEASE_MACOS_SSH?.trim() || '';
234
+ const macosTempRoot = (process.env.SMARTDB_RELEASE_MACOS_TEMP_ROOT?.trim() || '').replace(
235
+ /\/+$/,
236
+ '',
237
+ );
238
+
239
+ if (!/^[A-Za-z0-9._-]+@[A-Za-z0-9.-]+$/.test(macosSsh)) {
240
+ throw new Error(
241
+ 'SMARTDB_RELEASE_MACOS_SSH must use the user@host form without SSH options',
242
+ );
243
+ }
244
+ if (
245
+ !/^\/[A-Za-z0-9._/-]+$/.test(macosTempRoot) ||
246
+ path.posix.normalize(macosTempRoot) !== macosTempRoot ||
247
+ macosTempRoot.split('/').filter(Boolean).length < 2
248
+ ) {
249
+ throw new Error(
250
+ 'SMARTDB_RELEASE_MACOS_TEMP_ROOT must be a non-root absolute path without spaces',
251
+ );
252
+ }
253
+ return { macosSsh, macosTempRoot };
254
+ };
255
+
256
+ const runRemoteLoginScript = async (
257
+ environmentArg: IReleaseArtifactEnvironment,
258
+ scriptArg: string,
259
+ timeoutMsArg: number = defaultCommandTimeoutMs,
260
+ ): Promise<void> => {
261
+ const ownedRemoteScript = [
262
+ 'set -u',
263
+ 'exec 3<&0',
264
+ 'child_pid=',
265
+ 'watchdog_pid=',
266
+ 'kill_tree() {',
267
+ ' local parent_pid children child_pid_to_kill',
268
+ ' parent_pid="$1"',
269
+ ' kill -STOP "$parent_pid" 2>/dev/null || return 0',
270
+ ' children="$(/usr/bin/pgrep -P "$parent_pid" 2>/dev/null || true)"',
271
+ ' for child_pid_to_kill in $children; do kill_tree "$child_pid_to_kill"; done',
272
+ ' kill -KILL "$parent_pid" 2>/dev/null || true',
273
+ '}',
274
+ 'terminate_owned() { test -z "$child_pid" || kill_tree "$child_pid"; test -z "$watchdog_pid" || kill -KILL "$watchdog_pid" 2>/dev/null || true; }',
275
+ 'watch_channel() { while IFS= read -r -t 45 ignored_line; do :; done; test -z "$child_pid" || kill_tree "$child_pid"; }',
276
+ 'trap terminate_owned EXIT HUP INT TERM',
277
+ `/bin/zsh -lc ${shellQuote(scriptArg)} </dev/null &`,
278
+ 'child_pid=$!',
279
+ 'watch_channel <&3 &',
280
+ 'watchdog_pid=$!',
281
+ 'wait "$child_pid"',
282
+ 'exit_status=$?',
283
+ 'child_pid=',
284
+ 'kill -KILL "$watchdog_pid" 2>/dev/null || true',
285
+ 'wait "$watchdog_pid" 2>/dev/null || true',
286
+ 'watchdog_pid=',
287
+ 'exec 3<&-',
288
+ 'trap - EXIT HUP INT TERM',
289
+ 'exit "$exit_status"',
290
+ ].join('\n');
291
+ await runCommand(
292
+ 'ssh',
293
+ [
294
+ ...sshConnectionArgs,
295
+ environmentArg.macosSsh,
296
+ `zsh -lic ${shellQuote(`/bin/sh -c ${shellQuote(ownedRemoteScript)}`)}`,
297
+ ],
298
+ process.cwd(),
299
+ timeoutMsArg,
300
+ true,
301
+ );
302
+ };
303
+
304
+ const checkBuilders = async (
305
+ environmentArg: IReleaseArtifactEnvironment,
306
+ ): Promise<void> => {
307
+ if (process.platform !== 'linux' || process.arch !== 'x64') {
308
+ throw new Error('SmartDB release artifact assembly requires a Linux amd64 coordinator');
309
+ }
310
+
311
+ await runCommand('git', ['--version'], process.cwd(), capabilityCommandTimeoutMs);
312
+ await runCommand('pnpm', ['--version'], process.cwd(), capabilityCommandTimeoutMs);
313
+ await captureCommand('/bin/sh', ['-c', 'command -v scp >/dev/null']);
314
+ const linuxProbeParent = path.join(process.cwd(), '.nogit', 'smartdb-release-probe');
315
+ fs.mkdirSync(linuxProbeParent, { recursive: true, mode: 0o700 });
316
+ const linuxProbeDirectory = fs.mkdtempSync(path.join(linuxProbeParent, 'arm64-'));
317
+ try {
318
+ const sourcePath = path.join(linuxProbeDirectory, 'probe.c');
319
+ const binaryPath = path.join(linuxProbeDirectory, 'probe');
320
+ fs.writeFileSync(sourcePath, 'int main(void) { return 0; }\n');
321
+ await runCommand(
322
+ 'aarch64-linux-gnu-gcc',
323
+ ['-static', '-O2', sourcePath, '-o', binaryPath],
324
+ process.cwd(),
325
+ capabilityCommandTimeoutMs,
326
+ );
327
+ await runCommand(binaryPath, [], process.cwd(), capabilityCommandTimeoutMs);
328
+ } finally {
329
+ fs.rmSync(linuxProbeDirectory, { recursive: true, force: true });
330
+ }
331
+
332
+ const probeToken = crypto.randomUUID();
333
+ const probePath = `${environmentArg.macosTempRoot}/smartdb-release-check.${probeToken}`;
334
+ const remoteCheckScript = [
335
+ 'set -eu',
336
+ 'test "$(uname -s)" = "Darwin"',
337
+ 'test "$(uname -m)" = "arm64"',
338
+ 'command -v git >/dev/null',
339
+ 'command -v pnpm >/dev/null',
340
+ 'command -v codesign >/dev/null',
341
+ 'command -v cmp >/dev/null',
342
+ `root=${shellQuote(environmentArg.macosTempRoot)}`,
343
+ `probe=${shellQuote(probePath)}`,
344
+ `test "$probe" = "$root/smartdb-release-check.${probeToken}"`,
345
+ 'mkdir -p "$root"',
346
+ 'test "$(cd "$root" && pwd -P)" = "$root"',
347
+ 'created=0',
348
+ `cleanup_probe() { if [ "$created" = 1 ]; then test ! -f "$probe/.smartdb-release-owner" || test "$(cat "$probe/.smartdb-release-owner")" = ${shellQuote(probeToken)} || return; rm -f "$probe/.smartdb-release-owner"; rmdir "$probe" 2>/dev/null || true; fi; }`,
349
+ 'trap cleanup_probe EXIT HUP INT TERM',
350
+ 'mkdir -m 700 "$probe"',
351
+ 'created=1',
352
+ `printf '%s\\n' ${shellQuote(probeToken)} > "$probe/.smartdb-release-owner"`,
353
+ 'rm -f "$probe/.smartdb-release-owner"',
354
+ 'rmdir "$probe"',
355
+ 'trap - EXIT HUP INT TERM',
356
+ '/usr/bin/arch -x86_64 /usr/bin/true',
357
+ ].join('\n');
358
+ await runRemoteLoginScript(
359
+ environmentArg,
360
+ remoteCheckScript,
361
+ capabilityCommandTimeoutMs,
362
+ );
363
+ console.log('All SmartDB release builders can execute their assigned architectures.');
364
+ };
365
+
366
+ const assertArtifactSet = (directoryArg: string, targetsArg: string[]): void => {
367
+ const expectedNames = targetsArg.flatMap((targetArg) => [
368
+ `rustdb_${targetArg}`,
369
+ `rustdb_${targetArg}${provenanceSuffix}`,
370
+ ]);
371
+ const actualNames = fs.readdirSync(directoryArg).sort();
372
+ if (JSON.stringify(actualNames) !== JSON.stringify(expectedNames.sort())) {
373
+ throw new Error(
374
+ `Unexpected release artifact set in ${directoryArg}: ${actualNames.join(', ')}`,
375
+ );
376
+ }
377
+ for (const target of targetsArg) {
378
+ const binaryPath = path.join(directoryArg, `rustdb_${target}`);
379
+ const stat = fs.lstatSync(binaryPath);
380
+ if (!stat.isFile() || (stat.mode & 0o111) === 0) {
381
+ throw new Error(`Release artifact is not an executable regular file: ${binaryPath}`);
382
+ }
383
+ }
384
+ };
385
+
386
+ const remoteWorkspacePath = (
387
+ environmentArg: IReleaseArtifactEnvironment,
388
+ gitCommitArg: string,
389
+ ownerTokenArg: string,
390
+ ): string =>
391
+ `${environmentArg.macosTempRoot}/smartdb-release.${gitCommitArg}.${ownerTokenArg}`;
392
+
393
+ const assertOwnedRemoteWorkspacePath = (
394
+ environmentArg: IReleaseArtifactEnvironment,
395
+ remoteDirectoryArg: string,
396
+ gitCommitArg: string,
397
+ ownerTokenArg: string,
398
+ ): void => {
399
+ const expectedDirectory = remoteWorkspacePath(
400
+ environmentArg,
401
+ gitCommitArg,
402
+ ownerTokenArg,
403
+ );
404
+ if (
405
+ remoteDirectoryArg !== expectedDirectory ||
406
+ path.posix.dirname(remoteDirectoryArg) !== environmentArg.macosTempRoot ||
407
+ path.posix.normalize(remoteDirectoryArg) !== remoteDirectoryArg
408
+ ) {
409
+ throw new Error(`Refusing unsafe macOS release workspace path: ${remoteDirectoryArg}`);
410
+ }
411
+ };
412
+
413
+ const createRemoteWorkspace = async (
414
+ environmentArg: IReleaseArtifactEnvironment,
415
+ remoteDirectoryArg: string,
416
+ gitCommitArg: string,
417
+ ownerTokenArg: string,
418
+ ): Promise<void> => {
419
+ assertOwnedRemoteWorkspacePath(
420
+ environmentArg,
421
+ remoteDirectoryArg,
422
+ gitCommitArg,
423
+ ownerTokenArg,
424
+ );
425
+ const createScript = [
426
+ 'set -eu',
427
+ `root=${shellQuote(environmentArg.macosTempRoot)}`,
428
+ `directory=${shellQuote(remoteDirectoryArg)}`,
429
+ `test "$directory" = "$root/smartdb-release.${gitCommitArg}.${ownerTokenArg}"`,
430
+ 'mkdir -p "$root"',
431
+ 'test "$(cd "$root" && pwd -P)" = "$root"',
432
+ 'created=0',
433
+ `cleanup_unowned() { if [ "$created" = 1 ]; then test ! -f "$directory/.smartdb-release-owner" || test "$(cat "$directory/.smartdb-release-owner")" = ${shellQuote(ownerTokenArg)} || return; rm -f "$directory/.smartdb-release-owner"; rmdir "$directory" 2>/dev/null || true; fi; }`,
434
+ 'trap cleanup_unowned EXIT HUP INT TERM',
435
+ 'mkdir -m 700 "$directory"',
436
+ 'created=1',
437
+ `printf '%s\\n' ${shellQuote(ownerTokenArg)} > "$directory/.smartdb-release-owner"`,
438
+ 'trap - EXIT HUP INT TERM',
439
+ ].join('\n');
440
+ await runCommand(
441
+ 'ssh',
442
+ [...sshConnectionArgs, environmentArg.macosSsh, createScript],
443
+ process.cwd(),
444
+ capabilityCommandTimeoutMs,
445
+ );
446
+ };
447
+
448
+ const cleanupRemoteWorkspace = async (
449
+ environmentArg: IReleaseArtifactEnvironment,
450
+ remoteDirectoryArg: string,
451
+ gitCommitArg: string,
452
+ ownerTokenArg: string,
453
+ ): Promise<void> => {
454
+ assertOwnedRemoteWorkspacePath(
455
+ environmentArg,
456
+ remoteDirectoryArg,
457
+ gitCommitArg,
458
+ ownerTokenArg,
459
+ );
460
+ const cleanupScript = [
461
+ 'set -eu',
462
+ `root=${shellQuote(environmentArg.macosTempRoot)}`,
463
+ `directory=${shellQuote(remoteDirectoryArg)}`,
464
+ `test "$directory" = "$root/smartdb-release.${gitCommitArg}.${ownerTokenArg}"`,
465
+ 'test "$(cd "$root" && pwd -P)" = "$root"',
466
+ 'test -f "$directory/.smartdb-release-owner"',
467
+ `test "$(cat "$directory/.smartdb-release-owner")" = ${shellQuote(ownerTokenArg)}`,
468
+ 'rm -rf "$directory"',
469
+ ].join('\n');
470
+ await runCommand('ssh', [...sshConnectionArgs, environmentArg.macosSsh, cleanupScript]);
471
+ };
472
+
473
+ const buildArtifacts = async (
474
+ environmentArg: IReleaseArtifactEnvironment,
475
+ ): Promise<void> => {
476
+ await checkBuilders(environmentArg);
477
+
478
+ const workspace = process.cwd();
479
+ const branch = await captureCommand('git', ['branch', '--show-current'], workspace);
480
+ const gitCommit = await captureCommand('git', ['rev-parse', 'HEAD'], workspace);
481
+ const gitStatus = await captureCommand(
482
+ 'git',
483
+ ['status', '--porcelain=v1', '--untracked-files=all'],
484
+ workspace,
485
+ );
486
+ const packageJson = JSON.parse(
487
+ fs.readFileSync(path.join(workspace, 'package.json'), 'utf8'),
488
+ ) as { version?: string };
489
+ const expectedTag = `v${packageJson.version || ''}`;
490
+ const tagsAtHead = (
491
+ await captureCommand('git', ['tag', '--points-at', 'HEAD'], workspace)
492
+ ).split(/\r?\n/);
493
+ if (branch !== 'main') throw new Error('SmartDB releases must be built from main');
494
+ if (gitStatus) throw new Error('SmartDB release artifact build requires a clean worktree');
495
+ if (!packageJson.version || !tagsAtHead.includes(expectedTag)) {
496
+ throw new Error(`SmartDB release artifact build requires ${expectedTag} at HEAD`);
497
+ }
498
+
499
+ const releaseParent = path.join(workspace, '.nogit', 'smartdb-release');
500
+ fs.mkdirSync(releaseParent, { recursive: true, mode: 0o700 });
501
+ const localDirectory = fs.mkdtempSync(
502
+ path.join(releaseParent, `${gitCommit.slice(0, 12)}-`),
503
+ );
504
+ const linuxDirectory = path.join(localDirectory, 'linux');
505
+ const macosDirectory = path.join(localDirectory, 'macos');
506
+ const bundlePath = path.join(localDirectory, 'source.bundle');
507
+ const ownerToken = crypto.randomUUID();
508
+ let remoteDirectory: string | undefined;
509
+ let completed = false;
510
+
511
+ try {
512
+ await runCommand('pnpm', ['exec', 'tsbundle'], workspace);
513
+ await runCommand('pnpm', ['exec', 'tsbuild', 'tsfolders'], workspace);
514
+ await runCommand('pnpm', ['exec', 'tsrust'], workspace);
515
+
516
+ fs.cpSync(path.join(workspace, 'dist_rust'), linuxDirectory, {
517
+ recursive: true,
518
+ preserveTimestamps: true,
519
+ });
520
+ assertArtifactSet(linuxDirectory, ['linux_amd64', 'linux_arm64']);
521
+ await runCommand(path.join(linuxDirectory, 'rustdb_linux_amd64'), ['--version'], workspace);
522
+ await runCommand(
523
+ path.join(linuxDirectory, 'rustdb_linux_arm64'),
524
+ ['--version'],
525
+ workspace,
526
+ );
527
+
528
+ await runCommand('git', ['bundle', 'create', bundlePath, 'HEAD'], workspace);
529
+ remoteDirectory = remoteWorkspacePath(environmentArg, gitCommit, ownerToken);
530
+ await createRemoteWorkspace(
531
+ environmentArg,
532
+ remoteDirectory,
533
+ gitCommit,
534
+ ownerToken,
535
+ );
536
+ await runCommand('scp', [
537
+ '-p',
538
+ ...sshConnectionArgs,
539
+ bundlePath,
540
+ `${environmentArg.macosSsh}:${remoteDirectory}/source.bundle`,
541
+ ]);
542
+
543
+ const remoteWorkspace = `${remoteDirectory}/workspace`;
544
+ const remoteBuildScript = [
545
+ 'set -eu',
546
+ `bundle=${shellQuote(`${remoteDirectory}/source.bundle`)}`,
547
+ `workspace=${shellQuote(remoteWorkspace)}`,
548
+ `expected_commit=${shellQuote(gitCommit)}`,
549
+ 'git clone --quiet "$bundle" "$workspace"',
550
+ 'cd "$workspace"',
551
+ 'test "$(git rev-parse HEAD)" = "$expected_commit"',
552
+ 'test -z "$(git status --porcelain=v1 --untracked-files=all)"',
553
+ 'pnpm install --frozen-lockfile',
554
+ 'pnpm exec tsrust',
555
+ 'cmp .nogit/tsrust-target/aarch64-apple-darwin/release/rustdb dist_rust/rustdb_macos_arm64',
556
+ 'cmp .nogit/tsrust-target/x86_64-apple-darwin/release/rustdb dist_rust/rustdb_macos_amd64',
557
+ 'codesign --verify --deep --strict dist_rust/rustdb_macos_arm64',
558
+ 'codesign --verify --deep --strict dist_rust/rustdb_macos_amd64',
559
+ '/usr/bin/arch -arm64 dist_rust/rustdb_macos_arm64 --version',
560
+ '/usr/bin/arch -x86_64 dist_rust/rustdb_macos_amd64 --version',
561
+ 'test "$(git rev-parse HEAD)" = "$expected_commit"',
562
+ 'test -z "$(git status --porcelain=v1 --untracked-files=all)"',
563
+ ].join('\n');
564
+ await runRemoteLoginScript(environmentArg, remoteBuildScript);
565
+ await runCommand('scp', [
566
+ '-pr',
567
+ ...sshConnectionArgs,
568
+ `${environmentArg.macosSsh}:${remoteWorkspace}/dist_rust`,
569
+ macosDirectory,
570
+ ]);
571
+ assertArtifactSet(macosDirectory, ['macos_amd64', 'macos_arm64']);
572
+
573
+ await runCommand(
574
+ 'pnpm',
575
+ ['exec', 'tsrust', 'assemble', linuxDirectory, macosDirectory],
576
+ workspace,
577
+ );
578
+ assertArtifactSet(path.join(workspace, 'dist_rust'), [
579
+ 'linux_amd64',
580
+ 'linux_arm64',
581
+ 'macos_amd64',
582
+ 'macos_arm64',
583
+ ]);
584
+ for (const target of ['linux_amd64', 'linux_arm64', 'macos_amd64', 'macos_arm64']) {
585
+ await runCommand(
586
+ 'pnpm',
587
+ ['exec', 'tsrust', 'inspect', `dist_rust/rustdb_${target}`],
588
+ workspace,
589
+ );
590
+ }
591
+
592
+ await cleanupRemoteWorkspace(
593
+ environmentArg,
594
+ remoteDirectory,
595
+ gitCommit,
596
+ ownerToken,
597
+ );
598
+ remoteDirectory = undefined;
599
+ fs.rmSync(localDirectory, { recursive: true });
600
+ completed = true;
601
+ console.log(`Assembled and qualified SmartDB release artifacts for ${expectedTag}.`);
602
+ } finally {
603
+ if (!completed) {
604
+ console.error(`Retained local release diagnostics at ${localDirectory}`);
605
+ if (remoteDirectory) {
606
+ console.error(`Retained macOS release diagnostics at ${remoteDirectory}`);
607
+ }
608
+ }
609
+ }
610
+ };
611
+
612
+ try {
613
+ const mode = process.argv.find((argumentArg) =>
614
+ ['check', 'build'].includes(argumentArg),
615
+ ) as TReleaseArtifactMode | undefined;
616
+ if (!mode) throw new Error('Usage: release-artifacts.ts <check|build>');
617
+ const environment = loadEnvironment();
618
+ if (mode === 'check') {
619
+ await checkBuilders(environment);
620
+ } else {
621
+ await buildArtifacts(environment);
622
+ }
623
+ } catch (error) {
624
+ console.error(error);
625
+ process.exitCode = 1;
626
+ }
@@ -1,6 +1,6 @@
1
1
  # Third-Party Notices
2
2
 
3
- The statically linked Rust executables distributed with this package include
3
+ The Rust executables distributed with this package include
4
4
  third-party components. This document covers the complete third-party package
5
5
  set recorded by `rust/Cargo.lock`, including target-inactive and compile-time
6
6
  components. The nine `rustdb*` workspace crates are covered by the package's
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartdb',
6
- version: '5.0.5',
6
+ version: '5.2.0',
7
7
  description: 'A MongoDB-compatible embedded database server with wire protocol support, backed by a high-performance Rust engine.'
8
8
  }
package/ts/index.ts CHANGED
@@ -14,6 +14,13 @@ export type {
14
14
  ILocalSmartDbOfflinePhysicalNamespaceInspectionResult,
15
15
  ILocalSmartDbOfflineStringValueInspection,
16
16
  TLocalSmartDbOfflineStringValueInspectionResult,
17
+ ILocalSmartDbStoppedStorageRootRelocationInput,
18
+ ILocalSmartDbStoppedStorageRootRelocationReceipt,
19
+ TLocalSmartDbStorageRootRelocationErrorCode,
20
+ } from './ts_local/index.js';
21
+ export {
22
+ LocalSmartDbStorageRootRelocationError,
23
+ localSmartDbStorageRootRelocationErrorCodes,
17
24
  } from './ts_local/index.js';
18
25
 
19
26
  // Export migration