@spexcode/session-selflaunch 0.6.8 → 0.7.0-next.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/dist/locality.d.ts +12 -1
- package/dist/locality.js +62 -10
- package/package.json +3 -3
package/dist/locality.d.ts
CHANGED
|
@@ -10,11 +10,22 @@ export interface FilesystemClassification {
|
|
|
10
10
|
export declare function classifyFilesystemType(type: number | bigint): FilesystemClassification;
|
|
11
11
|
export interface LocalityDetector {
|
|
12
12
|
readonly platform: string;
|
|
13
|
-
|
|
13
|
+
readonly classify?: (parentPath: string) => FilesystemClassification;
|
|
14
14
|
}
|
|
15
|
+
export declare const linuxLocalityDetector: (statfsType: (parentPath: string) => number | bigint) => LocalityDetector;
|
|
16
|
+
interface DarwinMountEntry {
|
|
17
|
+
readonly mountPoint: string;
|
|
18
|
+
readonly fstype: string;
|
|
19
|
+
readonly flags: ReadonlySet<string>;
|
|
20
|
+
}
|
|
21
|
+
export declare function parseDarwinMountTable(output: string): DarwinMountEntry[];
|
|
22
|
+
export declare function classifyDarwinMount(mountTable: string, resolvedPath: string): FilesystemClassification;
|
|
23
|
+
export declare const darwinLocalityDetector: (readMountTable: () => string) => LocalityDetector;
|
|
15
24
|
export declare function requireLocalDatabasePathWithDetector(databasePath: string, options: {
|
|
16
25
|
assumeLocal?: boolean;
|
|
17
26
|
}, detector: LocalityDetector): string;
|
|
27
|
+
export declare function localityDetectorForPlatform(platform: string): LocalityDetector;
|
|
18
28
|
export declare function requireLocalDatabasePath(databasePath: string, options?: {
|
|
19
29
|
assumeLocal?: boolean;
|
|
20
30
|
}): string;
|
|
31
|
+
export {};
|
package/dist/locality.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { realpathSync, statSync, statfsSync } from 'node:fs';
|
|
2
3
|
import { isAbsolute, dirname } from 'node:path';
|
|
3
4
|
import { DatabasePathError } from './path.js';
|
|
4
5
|
export class LocalityError extends Error {
|
|
@@ -44,6 +45,54 @@ export function classifyFilesystemType(type) {
|
|
|
44
45
|
return { locality: 'local', name: local.name };
|
|
45
46
|
return { locality: 'undetermined', name: `0x${magic.toString(16)}` };
|
|
46
47
|
}
|
|
48
|
+
export const linuxLocalityDetector = (statfsType) => ({
|
|
49
|
+
platform: 'linux',
|
|
50
|
+
classify: parentPath => classifyFilesystemType(statfsType(parentPath)),
|
|
51
|
+
});
|
|
52
|
+
// Darwin filesystem type names that are network transports even when the mount table omits MNT_LOCAL. `local`
|
|
53
|
+
// wins when present (it is the kernel's own verdict); this list only names the refusal, so the operator reads
|
|
54
|
+
// "smbfs" instead of a bare "undetermined".
|
|
55
|
+
const DARWIN_NETWORK_FILESYSTEM_NAMES = new Set(['nfs', 'smbfs', 'afpfs', 'webdav', 'cifs', 'ftp']);
|
|
56
|
+
// `/sbin/mount` prints one `<device> on <mount point> (<fstype>, <flag>, …)` line per mount. The mount point may
|
|
57
|
+
// contain spaces (`/Volumes/My Disk`); the parenthesised list is always last and never contains `)`.
|
|
58
|
+
const DARWIN_MOUNT_LINE = /^.+ on (.+) \(([^)]*)\)$/;
|
|
59
|
+
export function parseDarwinMountTable(output) {
|
|
60
|
+
const entries = [];
|
|
61
|
+
for (const line of output.split('\n')) {
|
|
62
|
+
const match = DARWIN_MOUNT_LINE.exec(line.trim());
|
|
63
|
+
if (!match)
|
|
64
|
+
continue;
|
|
65
|
+
const [fstype = '', ...flags] = match[2].split(',').map(part => part.trim()).filter(Boolean);
|
|
66
|
+
entries.push({ mountPoint: match[1], fstype, flags: new Set(flags) });
|
|
67
|
+
}
|
|
68
|
+
return entries;
|
|
69
|
+
}
|
|
70
|
+
const mountPointCovers = (mountPoint, path) => mountPoint === '/' || path === mountPoint || path.startsWith(mountPoint.endsWith('/') ? mountPoint : `${mountPoint}/`);
|
|
71
|
+
export function classifyDarwinMount(mountTable, resolvedPath) {
|
|
72
|
+
let entry;
|
|
73
|
+
for (const candidate of parseDarwinMountTable(mountTable)) {
|
|
74
|
+
if (!mountPointCovers(candidate.mountPoint, resolvedPath))
|
|
75
|
+
continue;
|
|
76
|
+
if (!entry || candidate.mountPoint.length > entry.mountPoint.length)
|
|
77
|
+
entry = candidate;
|
|
78
|
+
}
|
|
79
|
+
if (!entry)
|
|
80
|
+
return { locality: 'undetermined', name: 'no mount table entry' };
|
|
81
|
+
if (entry.flags.has('local'))
|
|
82
|
+
return { locality: 'local', name: entry.fstype };
|
|
83
|
+
if (DARWIN_NETWORK_FILESYSTEM_NAMES.has(entry.fstype))
|
|
84
|
+
return { locality: 'network', name: entry.fstype };
|
|
85
|
+
return { locality: 'undetermined', name: entry.fstype };
|
|
86
|
+
}
|
|
87
|
+
export const darwinLocalityDetector = (readMountTable) => ({
|
|
88
|
+
platform: 'darwin',
|
|
89
|
+
classify: parentPath => {
|
|
90
|
+
// `mount` never fails for a missing directory, so the parent's absence must be raised here for the resolver
|
|
91
|
+
// to keep its actionable PROTOCOL_PATH_PARENT_MISSING error.
|
|
92
|
+
statSync(parentPath);
|
|
93
|
+
return classifyDarwinMount(readMountTable(), realpathSync(parentPath));
|
|
94
|
+
},
|
|
95
|
+
});
|
|
47
96
|
export function requireLocalDatabasePathWithDetector(databasePath, options, detector) {
|
|
48
97
|
if (!isAbsolute(databasePath)) {
|
|
49
98
|
throw new DatabasePathError('PROTOCOL_PATH_NOT_ABSOLUTE', 'databasePath must be absolute before locality detection');
|
|
@@ -51,12 +100,12 @@ export function requireLocalDatabasePathWithDetector(databasePath, options, dete
|
|
|
51
100
|
if (options.assumeLocal)
|
|
52
101
|
return databasePath;
|
|
53
102
|
const parent = dirname(databasePath);
|
|
54
|
-
if (detector.
|
|
103
|
+
if (!detector.classify) {
|
|
55
104
|
throw new LocalityError('LOCALITY_DETECTOR_UNAVAILABLE', `no filesystem locality detector for platform ${detector.platform}; pass --assume-local-storage only after auditing ${parent}`);
|
|
56
105
|
}
|
|
57
|
-
let
|
|
106
|
+
let classification;
|
|
58
107
|
try {
|
|
59
|
-
|
|
108
|
+
classification = detector.classify(parent);
|
|
60
109
|
}
|
|
61
110
|
catch (error) {
|
|
62
111
|
// @@@missing-parent - Preserve the actionable path error without pretending locality was established.
|
|
@@ -65,7 +114,6 @@ export function requireLocalDatabasePathWithDetector(databasePath, options, dete
|
|
|
65
114
|
}
|
|
66
115
|
throw new LocalityError('LOCALITY_PROBE_FAILED', `could not determine the filesystem of ${parent}`, error);
|
|
67
116
|
}
|
|
68
|
-
const classification = classifyFilesystemType(type);
|
|
69
117
|
if (classification.locality === 'network') {
|
|
70
118
|
throw new LocalityError('LOCALITY_NETWORK_FILESYSTEM', `${parent} is ${classification.name}; advisory locking is not admitted there`);
|
|
71
119
|
}
|
|
@@ -74,10 +122,14 @@ export function requireLocalDatabasePathWithDetector(databasePath, options, dete
|
|
|
74
122
|
}
|
|
75
123
|
return databasePath;
|
|
76
124
|
}
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
125
|
+
const readDarwinMountTable = () => execFileSync('/sbin/mount', [], { encoding: 'utf8', timeout: 5_000, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
126
|
+
export function localityDetectorForPlatform(platform) {
|
|
127
|
+
if (platform === 'linux')
|
|
128
|
+
return linuxLocalityDetector(parentPath => statfsSync(parentPath).type);
|
|
129
|
+
if (platform === 'darwin')
|
|
130
|
+
return darwinLocalityDetector(readDarwinMountTable);
|
|
131
|
+
return { platform };
|
|
132
|
+
}
|
|
81
133
|
export function requireLocalDatabasePath(databasePath, options = {}) {
|
|
82
|
-
return requireLocalDatabasePathWithDetector(databasePath, options,
|
|
134
|
+
return requireLocalDatabasePathWithDetector(databasePath, options, localityDetectorForPlatform(process.platform));
|
|
83
135
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spexcode/session-selflaunch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0-next.1",
|
|
4
4
|
"publishConfig": { "access": "public" },
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "A fail-closed self-launch adopter for the SpexCode session protocol.",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"test": "npm run build && tsx --import ../../scripts/test-home.mjs --test src/*.test.ts"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@spexcode/session-protocol": "0.
|
|
28
|
-
"@spexcode/session-runtime": "0.
|
|
27
|
+
"@spexcode/session-protocol": "0.7.0-next.1",
|
|
28
|
+
"@spexcode/session-runtime": "0.7.0-next.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^20.16.0",
|