fix(where): Differentiate ephemeral state, clarify tests
MODIFY
packages/where/README.md
packages/where/README.md
@@ -4,3 +4,12 @@ This package provides a utility for finding the user files and Unix domain
socket or Windows named pipe for the Endo daemon.
The Endo user directory stores the per-user runtime data for Endo,
including logs and other application storage.
+
+Endo attempts to use or infer [Cross-desktop XDG conventions][XDG] paths in
+every meaningful way.
+Windows named pipes do not appear to fit this model.
+Otherwise falls back to the native conventions on Windows and Mac/Darwin.
+On Windows, Endo does not use separate state and cache directories and does not
+yet sync state between home directories.
+
+[XDG]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html

MODIFY
packages/where/index.d.ts
packages/where/index.d.ts
-export { whereEndoState, whereEndoSock, whereEndoCache } from './types.js';
+export {
+ whereEndoState,
+ whereEndoEphemeralState,
+ whereEndoSock,
+ whereEndoCache,
+} from './types.js';

MODIFY
packages/where/index.js
packages/where/index.js
// @ts-check
-/* Infers the rendezvous path for the endo.sock file and apps from the platform
- * and environment.
- */
-
const { raw } = String;
/**
+ * Returns a path for local Endo application user data on Windows.
+ *
* @param {{[name: string]: string}} env
*/
const whereEndoHomeWindows = env => {
// Favoring local app data over roaming app data since I don't expect to be
// able to listen on one host and connect on another.
+ // TODO support roaming data for shared content addressable state and
+ // find a suitable mechanism for merging state that may change independently
+ // on separate roaming hosts.
if (env.LOCALAPPDATA !== undefined) {
return `${env.LOCALAPPDATA}\\Endo`;
}
if (env.APPDATA !== undefined) {
- return `${env.APPDATA}\\Endo`;
+ return `${env.APPDATA}\\Local\\Endo`;
}
if (env.USERPROFILE !== undefined) {
- return `${env.USERPROFILE}\\AppData\\Endo`;
+ return `${env.USERPROFILE}\\AppData\\Local\\Endo`;
}
if (env.HOMEDRIVE !== undefined && env.HOMEPATH !== undefined) {
- return `${env.HOMEDRIVE}${env.HOMEPATH}\\AppData\\Endo`;
+ return `${env.HOMEDRIVE}${env.HOMEPATH}\\AppData\\Local\\Endo`;
}
return 'Endo';
};
/**
+ * Returns the most suitable path for Endo state with this platform and
+ * environment.
+ * Endo uses the state directory for saved files including applications,
+ * durable capabilities, and the user's pet names for them.
+ * Endo also logs here, per XDG's preference to persist logs even when caches
+ * are purged.
+ *
* @type {typeof import('./types.js').whereEndoState}
*/
export const whereEndoState = (platform, env) => {
@@ -46,6 +54,37 @@ export const whereEndoState = (platform, env) => {
};
/**
+ * Returns the most suitable location for storing state that ideally does not
+ * persist between restarts or reboots, specifically PID files.
+ * This should be coincident with the directory containing UNIX domain sockets,
+ * but is not suitable for Windows named pipes.
+ *
+ * @type {typeof import('./types.js').whereEndoEphemeralState}
+ */
+export const whereEndoEphemeralState = (platform, env) => {
+ if (env.XDG_RUNTIME_DIR !== undefined) {
+ return `${env.XDG_RUNTIME_DIR}/endo`;
+ } else if (platform === 'win32') {
+ return whereEndoHomeWindows(env);
+ } else if (platform === 'darwin') {
+ if (env.HOME !== undefined) {
+ return `${env.HOME}/Library/Application Support/Endo`;
+ }
+ }
+ if (env.USER !== undefined) {
+ // The XDG specification says that in this case, we should fall through to
+ // system specific behavior, but we are not in a position to assume which
+ // system we're using, other than it is probably a variety of Linux,
+ // in which case /tmp and /run might be viable base paths.
+ return `/tmp/endo-${env.USER}`;
+ }
+ return '/tmp/endo';
+};
+
+/**
+ * Returns the most suitable path for the Endo UNIX domain socket or Windows
+ * named pipe.
+ *
* @type {typeof import('./types.js').whereEndoSock}
*/
export const whereEndoSock = (platform, env) => {
@@ -69,6 +108,8 @@ export const whereEndoSock = (platform, env) => {
};
/**
+ * Returns the most suitable path for Endo caches.
+ *
* @type {typeof import('./types.js').whereEndoCache}
*/
export const whereEndoCache = (platform, env) => {

MODIFY
packages/where/test/test-where-endo-cache.js
packages/where/test/test-where-endo-cache.js
@@ -5,21 +5,23 @@ test('windows', t => {
t.is(
whereEndoCache('win32', {
LOCALAPPDATA: 'C:\\Users\\Alice\\AppData\\Local',
- APPDATA: 'C:\\Users\\Alice\\AppData',
- USERPROFILE: 'C:\\Users\\Alice',
- HOMEDRIVE: 'C:\\',
- HOMEPATH: 'Users\\Alice',
+ APPDATA: 'IGNOREME',
+ USERPROFILE: 'IGNOREME',
+ HOMEDRIVE: 'IGNOREME',
+ HOMEPATH: 'IGNOREME',
}),
'C:\\Users\\Alice\\AppData\\Local\\Endo',
+ 'LOCALAPPDATA has highest precedence for locating the Endo cache on Windows.',
);
t.is(
whereEndoCache('win32', {
APPDATA: 'C:\\Users\\Alice\\AppData',
- USERPROFILE: 'C:\\Users\\Alice',
- HOMEDRIVE: 'C:\\',
- HOMEPATH: 'Users\\Alice',
+ USERPROFILE: 'IGNOREME',
+ HOMEDRIVE: 'IGNOREME',
+ HOMEPATH: 'IGNOREME',
}),
- 'C:\\Users\\Alice\\AppData\\Endo',
+ 'C:\\Users\\Alice\\AppData\\Local\\Endo',
+ 'Infer LOCALAPPDATA from APPDATA',
);
t.is(
whereEndoCache('win32', {
@@ -27,43 +29,70 @@ test('windows', t => {
HOMEDRIVE: 'C:\\',
HOMEPATH: 'Users\\Alice',
}),
- 'C:\\Users\\Alice\\AppData\\Endo',
+ 'C:\\Users\\Alice\\AppData\\Local\\Endo',
+ 'Infer LOCALAPPDATA from USERPROFILE',
);
t.is(
whereEndoCache('win32', {
HOMEDRIVE: 'C:\\',
HOMEPATH: 'Users\\Alice',
}),
- 'C:\\Users\\Alice\\AppData\\Endo',
+ 'C:\\Users\\Alice\\AppData\\Local\\Endo',
+ 'Infer LOCALAPPDATA from HOMEDRIVE and HOMEPATH',
+ );
+ t.is(
+ whereEndoCache('win32', {}),
+ 'Endo',
+ 'Under duress, fall back to a relative path, just Endo',
);
- t.is(whereEndoCache('win32', {}), 'Endo');
});
test('darwin', t => {
+ t.is(
+ whereEndoCache('darwin', {
+ XDG_CACHE_HOME: '/Users/alice/.config',
+ HOME: 'IGNOREME',
+ }),
+ '/Users/alice/.config/endo',
+ 'Prioritize XDG environment if provided, even on a Mac',
+ );
t.is(
whereEndoCache('darwin', {
HOME: '/Users/alice',
}),
'/Users/alice/Library/Caches/Endo',
+ 'In absence of XDG environment, use conventional Mac cache location',
+ );
+ t.is(
+ whereEndoCache('darwin', {}),
+ 'endo/cache',
+ 'Under duress, fall back to a relative path, just endo/cache',
);
- t.is(whereEndoCache('darwin', {}), 'endo/cache');
});
test('linux', t => {
t.is(
whereEndoCache('linux', {
XDG_CACHE_HOME: '/var/cache/users/alice',
- USER: 'alice',
- HOME: '/Users/alice',
+ USER: 'IGNOREME',
+ HOME: 'IGNOREME',
}),
'/var/cache/users/alice/endo',
+ 'Prioritize XDG environment location for caches',
);
t.is(
whereEndoCache('linux', {
- USER: 'alice',
+ USER: 'IGNOREME',
HOME: '/Users/alice',
}),
'/Users/alice/.cache/endo',
+ 'Infer the conventional XDG environment from the user HOME',
+ );
+ t.is(
+ whereEndoCache('linux', {
+ USER: 'IGNOREME',
+ }),
+ 'endo/cache',
+ 'Under duress, do not attempt to infer whether /users/ or /home/ is a correct USER prefix for HOME, fall through to a relative path',
);
- t.is(whereEndoCache('linux', {}), 'endo/cache');
});

MODIFY
packages/where/test/test-where-endo-sock.js
packages/where/test/test-where-endo-sock.js
@@ -4,44 +4,61 @@ import { whereEndoSock } from '../index.js';
test('windows', t => {
t.is(
whereEndoSock('win32', {
- XDG_RUNTIME_DIR: 'IGNORE_ME',
+ XDG_RUNTIME_DIR: 'IGNOREME', // Not necessarily suitable for named pipes
USERNAME: 'alice',
}),
'\\\\?\\pipe\\alice-Endo\\endo.pipe',
+ 'Use Windows named pipe namespace, scoped to the user when possible',
+ );
+ t.is(
+ whereEndoSock('win32', {}),
+ '\\\\?\\pipe\\Endo\\endo.pipe',
+ 'Under duress, fall back to a location shared by all users',
);
- t.is(whereEndoSock('win32', {}), '\\\\?\\pipe\\Endo\\endo.pipe');
});
test('darwin', t => {
t.is(
whereEndoSock('darwin', {
XDG_RUNTIME_DIR: '/var/run/user/alice',
- USER: 'alice',
+ USER: 'IGNOREME',
}),
'/var/run/user/alice/endo/endo.sock',
+ 'Favor XDG over local conventions, even on a Mac',
);
t.is(
whereEndoSock('darwin', {
HOME: '/Users/alice',
}),
'/Users/alice/Library/Application Support/Endo/endo.sock',
+ 'Infer Darwin/Mac conventional socket location from HOME',
+ );
+ t.is(
+ whereEndoSock('darwin', {}),
+ 'endo/endo.sock',
+ 'Under duress, fall through to a relative path',
);
- t.is(whereEndoSock('darwin', {}), 'endo/endo.sock');
});
test('linux', t => {
t.is(
whereEndoSock('linux', {
XDG_RUNTIME_DIR: '/var/run/user/alice',
- USER: 'alice',
+ USER: 'IGNOREME',
}),
'/var/run/user/alice/endo/endo.sock',
+ 'XDG takes precedence over USER on Linux',
);
t.is(
whereEndoSock('linux', {
USER: 'alice',
}),
'/tmp/endo-alice/endo.sock',
+ 'Under duress, assume the host has a /tmp file system and scope the UNIX domain socket to the user by name',
+ );
+ t.is(
+ whereEndoSock('linux', {}),
+ 'endo/endo.sock',
+ 'Under extreme duress, just use a relative path for the socket',
);
- t.is(whereEndoSock('linux', {}), 'endo/endo.sock');
});

MODIFY
packages/where/test/test-where-endo-state.js
packages/where/test/test-where-endo-state.js
@@ -5,64 +5,92 @@ test('windows', t => {
t.is(
whereEndoState('win32', {
LOCALAPPDATA: 'C:\\Users\\Alice\\AppData\\Local',
- APPDATA: 'C:\\Users\\Alice\\AppData',
- USERPROFILE: 'C:\\Users\\Alice',
- HOMEDRIVE: 'C:\\',
- HOMEPATH: 'Users\\Alice',
+ APPDATA: 'IGNOREME',
+ USERPROFILE: 'IGNOREME',
+ HOMEDRIVE: 'IGNOREME',
+ HOMEPATH: 'IGNOREME',
}),
'C:\\Users\\Alice\\AppData\\Local\\Endo',
+ 'Use LOCALAPPDATA for Endo state if available',
);
t.is(
whereEndoState('win32', {
APPDATA: 'C:\\Users\\Alice\\AppData',
- USERPROFILE: 'C:\\Users\\Alice',
- HOMEDRIVE: 'C:\\',
- HOMEPATH: 'Users\\Alice',
+ USERPROFILE: 'IGNOREME',
+ HOMEDRIVE: 'IGNOREME',
+ HOMEPATH: 'IGNOREME',
}),
- 'C:\\Users\\Alice\\AppData\\Endo',
+ 'C:\\Users\\Alice\\AppData\\Local\\Endo',
+ 'Infer LOCALAPPDATA from APPDATA if necessary and possible',
);
t.is(
whereEndoState('win32', {
USERPROFILE: 'C:\\Users\\Alice',
- HOMEDRIVE: 'C:\\',
- HOMEPATH: 'Users\\Alice',
+ HOMEDRIVE: 'IGNOREME',
+ HOMEPATH: 'IGNOREME',
}),
- 'C:\\Users\\Alice\\AppData\\Endo',
+ 'C:\\Users\\Alice\\AppData\\Local\\Endo',
+ 'Infer LOCALAPPDATA from USERPROFILE if necessary and possible',
);
t.is(
whereEndoState('win32', {
HOMEDRIVE: 'C:\\',
HOMEPATH: 'Users\\Alice',
}),
- 'C:\\Users\\Alice\\AppData\\Endo',
+ 'C:\\Users\\Alice\\AppData\\Local\\Endo',
+ 'Infer LOCALAPPDATA from HOMEDRIVE and HOMEPATH if necessary and possible',
+ );
+ t.is(
+ whereEndoState('win32', {}),
+ 'Endo',
+ 'Under duress, just use a relative path',
);
- t.is(whereEndoState('win32', {}), 'Endo');
});
test('darwin', t => {
+ t.is(
+ whereEndoState('darwin', {
+ XDG_STATE_HOME: '/Users/alice/.local/state',
+ XDG_CONFIG_HOME: 'IGNOREME',
+ HOME: 'IGNOREME',
+ }),
+ '/Users/alice/.local/state/endo',
+ 'Favor XDG state home over Darwin conventions if provided by the user',
+ );
t.is(
whereEndoState('darwin', {
HOME: '/Users/alice',
}),
'/Users/alice/Library/Application Support/Endo',
+ 'Use the Mac/Darwin conventional location for Application user data',
+ );
+ t.is(
+ whereEndoState('darwin', {}),
+ 'endo/state',
+ 'Under duress, fall back to a relative path for state',
);
- t.is(whereEndoState('darwin', {}), 'endo/state');
});
test('linux', t => {
t.is(
whereEndoState('linux', {
- XDG_CONFIG_HOME: '/Users/alice/.config2',
XDG_STATE_HOME: '/Users/alice/.local/state',
- HOME: '/Users/alice',
+ XDG_CONFIG_HOME: 'IGNOREME',
+ HOME: 'IGNOREME',
}),
'/Users/alice/.local/state/endo',
+ 'Use XDG state home if provided by the user',
);
t.is(
whereEndoState('linux', {
HOME: '/Users/alice',
}),
'/Users/alice/.local/state/endo',
+ 'Infer XDG state home from HOME on Linux',
+ );
+ t.is(
+ whereEndoState('linux', {}),
+ 'endo/state',
+ 'For lack of any useful environment information, fall back to a relative path',
);
- t.is(whereEndoState('linux', {}), 'endo/state');
});

MODIFY
packages/where/types.d.ts
packages/where/types.d.ts
export function whereEndoState(
platform: string,
- env: { [name: string]: string },
+ env: { [name: string]: string | undefined },
+): string;
+export function whereEndoEphemeralState(
+ platform: string,
+ env: { [name: string]: string | undefined },
): string;
export function whereEndoSock(
platform: string,
- env: { [name: string]: string },
+ env: { [name: string]: string | undefined },
): string;
export function whereEndoCache(
platform: string,
- env: { [name: string]: string },
+ env: { [name: string]: string | undefined },
): string;

fix(where): Differentiate ephemeral state, clarify tests