my-durable 0.0.5 → 0.0.7
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.
|
@@ -11,6 +11,7 @@ import { chmodSync, closeSync, fsyncSync, openSync, readFileSync, readdirSync, r
|
|
|
11
11
|
import { dirname, join } from "node:path";
|
|
12
12
|
import { randomBytes } from "node:crypto";
|
|
13
13
|
import { ensurePrivateDir } from "../../dpi-sdk/src/node/private-dir.js";
|
|
14
|
+
import { fsyncReadOnlySupported } from "../../dpi-sdk/src/node/registry-migration.js";
|
|
14
15
|
|
|
15
16
|
export { ensurePrivateDir };
|
|
16
17
|
|
|
@@ -32,12 +33,15 @@ export function replaceFileAtomic(path: string, contents: string): void {
|
|
|
32
33
|
closeSync(fd);
|
|
33
34
|
fd = undefined;
|
|
34
35
|
renameSync(temp, path);
|
|
35
|
-
// fsync the directory so the rename itself is durable
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
// fsync the directory so the rename itself is durable (POSIX only: Windows
|
|
37
|
+
// directory fds cannot be fsynced — EPERM; NTFS journaling carries it there).
|
|
38
|
+
if (fsyncReadOnlySupported) {
|
|
39
|
+
const dirFd = openSync(dir, "r");
|
|
40
|
+
try {
|
|
41
|
+
fsyncSync(dirFd);
|
|
42
|
+
} finally {
|
|
43
|
+
closeSync(dirFd);
|
|
44
|
+
}
|
|
41
45
|
}
|
|
42
46
|
} catch (e) {
|
|
43
47
|
if (fd !== undefined) {
|
|
@@ -388,7 +388,15 @@ function preflightV0(db: DatabaseSync): void {
|
|
|
388
388
|
}
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
/**
|
|
392
|
+
* Windows cannot fsync read-only or directory handles (FlushFileBuffers needs a write
|
|
393
|
+
* handle; directory handles cannot be flushed at all — EPERM, errno -4048). POSIX-only
|
|
394
|
+
* durability steps are skipped there: NTFS journaling plus the atomic rename carry it.
|
|
395
|
+
*/
|
|
396
|
+
export const fsyncReadOnlySupported = process.platform !== "win32";
|
|
397
|
+
|
|
391
398
|
function syncFile(file: string): void {
|
|
399
|
+
if (!fsyncReadOnlySupported) return;
|
|
392
400
|
const fd = openSync(file, "r");
|
|
393
401
|
try {
|
|
394
402
|
fsyncSync(fd);
|
|
@@ -405,6 +413,7 @@ function syncFile(file: string): void {
|
|
|
405
413
|
* Exported for a focused unit test — the fsync itself is not observable from JS.
|
|
406
414
|
*/
|
|
407
415
|
export function syncDirectory(dir: string): void {
|
|
416
|
+
if (!fsyncReadOnlySupported) return; // Windows: directory fds cannot be fsynced (EPERM)
|
|
408
417
|
const fd = openSync(dir, "r");
|
|
409
418
|
try {
|
|
410
419
|
if (!fstatSync(fd).isDirectory()) throw new Error(`not a directory: ${dir}`);
|
package/apps/dpi-tui/src/main.ts
CHANGED
|
@@ -147,7 +147,27 @@ if (startup.kind === "welcome") {
|
|
|
147
147
|
await start(device, client, info);
|
|
148
148
|
} else {
|
|
149
149
|
const known = registry.list().map((d) => d.id);
|
|
150
|
-
if (!deviceRef
|
|
150
|
+
if (!deviceRef && (values.pair !== undefined || values.hub !== undefined)) {
|
|
151
|
+
// Fresh-install flow: an explicit --pair (a pasted code, pair URL or offer link IS
|
|
152
|
+
// the whole configuration) must never die on usage text — pair this machine's LOCAL
|
|
153
|
+
// backend first, then the app opens on it. Mirrors the welcome screen's [p] action.
|
|
154
|
+
const { applyBackendEnv } = await import("./welcome.ts");
|
|
155
|
+
applyBackendEnv();
|
|
156
|
+
const { LocalPairing } = await import("./pairing.ts");
|
|
157
|
+
try {
|
|
158
|
+
LocalPairing.fromEnv();
|
|
159
|
+
} catch (e) {
|
|
160
|
+
fail((e as Error).message);
|
|
161
|
+
}
|
|
162
|
+
const port = Number(process.env.DPI_PORT ?? "4780");
|
|
163
|
+
const url = `http://127.0.0.1:${port}`;
|
|
164
|
+
const device: DeviceRef = { id: "local", name: "This machine", url, token: process.env.DPI_TOKEN ?? "" };
|
|
165
|
+
const client = new DpiClient(url, device.token);
|
|
166
|
+
const info = await client.device({ timeoutMs: 8000 }).catch((e: unknown) =>
|
|
167
|
+
fail(`no local dpi-backend on ${url} — start one with: dpi device (${(e as Error).message})`),
|
|
168
|
+
);
|
|
169
|
+
await start(device, client, info);
|
|
170
|
+
} else if (!deviceRef) fail(`${USAGE}\n\nKnown devices: ${known.join(", ") || "none"}`);
|
|
151
171
|
const row = registry.get(deviceRef);
|
|
152
172
|
if (!row) fail(`Unknown device "${deviceRef}". Known: ${known.join(", ") || "none (add one to dpi-network.yaml)"}`);
|
|
153
173
|
if (!row.token) fail(`No token for ${row.id}. Run: just net register ${row.id}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "my-durable",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Durable Pi — the durable agent network: hub web UI, device backend daemon, TUI admin and the shared SDK, packaged as one runnable monorepo install",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|