diff --git a/packages/cli/src/runtime-install.spec.ts b/packages/cli/src/runtime-install.spec.ts index 883f1116..df0ee9fb 100644 --- a/packages/cli/src/runtime-install.spec.ts +++ b/packages/cli/src/runtime-install.spec.ts @@ -25,6 +25,18 @@ const ephemeral: RuntimePaths = { serviceTemplate: join(npxCli, 'packaging/systemd/codor.service'), }; +const dlxCli = join( + HOME, + '.local/share/pnpm/pnpm-cache/dlx/abcd1234ef567890/1706000000000/node_modules/@richhardry/codor/node_modules/@codor/cli', +); +const dlxEphemeral: RuntimePaths = { + root: dlxCli, + layout: 'installed-package', + cliEntrypoint: join(dlxCli, 'dist/index.js'), + staticRoot: join(dlxCli, 'runtime/web'), + serviceTemplate: join(dlxCli, 'packaging/systemd/codor.service'), +}; + const checkoutRoot = join(HOME, 'git/codor'); const checkout: RuntimePaths = { root: checkoutRoot, @@ -95,8 +107,9 @@ function fakeIo(options: { existing?: string; failCopy?: boolean; incompleteCopy // harn:assume setup-installs-durable-per-user-runtime-atomically ref=durable-runtime-install-regression describe('isEphemeralRuntime', () => { - it('flags npx cache and temp paths, not stable locations', () => { + it('flags npx cache, pnpm dlx cache, and temp paths, not stable locations', () => { expect(isEphemeralRuntime(join(HOME, '.npm/_npx/abcd1234'))).toBe(true); + expect(isEphemeralRuntime(join(HOME, '.local/share/pnpm/pnpm-cache/dlx/abcd1234ef567890/1706000000000'))).toBe(true); expect(isEphemeralRuntime(join(tmpdir(), 'x'))).toBe(true); expect(isEphemeralRuntime('/opt/codor')).toBe(false); expect(isEphemeralRuntime(checkoutRoot)).toBe(false); @@ -117,6 +130,17 @@ describe('installDurableRuntime', () => { expect(result.runtime.cliEntrypoint).not.toContain('_npx'); }); + it('stages, validates, and swaps an ephemeral pnpm dlx runtime into ~/.codor/runtime', () => { + const { io, moves } = fakeIo(); + const result = installDurableRuntime({ runtime: dlxEphemeral, dataDir: DATA, version: '0.10.0', io }); + expect(result.action).toBe('installed'); + expect(result.location).toBe(LOCATION); + expect(moves).toContainEqual([`${LOCATION}.staging`, LOCATION]); + // Rooted at the durable location, not the dlx cache the source runtime lived in. + expect(result.runtime.cliEntrypoint).toContain(LOCATION); + expect(result.runtime.cliEntrypoint).not.toContain('dlx'); + }); + it('uses a source checkout in place without copying', () => { const { io, copies } = fakeIo(); const result = installDurableRuntime({ runtime: checkout, dataDir: DATA, version: '0.10.0', io }); diff --git a/packages/cli/src/runtime-install.ts b/packages/cli/src/runtime-install.ts index 7fe57cda..43993fb5 100644 --- a/packages/cli/src/runtime-install.ts +++ b/packages/cli/src/runtime-install.ts @@ -52,10 +52,15 @@ export function durableRuntimeLocation(dataDir: string): string { return join(dataDir, 'runtime'); } -/** A path is ephemeral when it lives in an npx cache or the OS temp directory. */ +/** A path is ephemeral when it lives in an npx cache, a pnpm dlx cache, or the OS temp directory. */ export function isEphemeralRuntime(path: string): boolean { const temp = tmpdir(); - return path.includes(`${sep}_npx${sep}`) || path === temp || path.startsWith(temp + sep); + return ( + path.includes(`${sep}_npx${sep}`) || + path.includes(`${sep}dlx${sep}`) || + path === temp || + path.startsWith(temp + sep) + ); } function installedWrapperPackageJson(location: string): string { @@ -65,7 +70,8 @@ function installedWrapperPackageJson(location: string): string { // harn:assume setup-installs-durable-per-user-runtime-atomically ref=durable-runtime-install /** The self-contained module tree the running CLI resolves against, and whether * it is already durable (a source checkout or a stable install) or ephemeral - * (an npx cache / temp dir that must be copied before a service points at it). */ + * (an npx cache, a pnpm dlx cache, or a temp dir that must be copied before a + * service points at it). */ export function resolveInstallSource(runtime: RuntimePaths): { installRoot: string; nodeModules: string; durable: boolean } { if (runtime.layout === 'source-checkout') { return { installRoot: runtime.root, nodeModules: join(runtime.root, 'node_modules'), durable: true };