hivecast-sdk/packages/cli/tests/integration/fp30-install-source-resolution.spec.ts

612 lines
19 KiB
TypeScript

import { afterEach, describe, it } from 'node:test';
import { strict as assert } from 'node:assert';
import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import * as fs from 'node:fs';
import * as http from 'node:http';
import * as os from 'node:os';
import * as path from 'node:path';
import { runMxCli, parseLastJson, assertExitCode } from '../helpers/cli-harness.js';
import { createMatrixPackage } from '../helpers/package-fixtures.js';
async function startNpmRegistryFixture(params: {
readonly packageName: string;
readonly version: string;
readonly tarballPath: string;
readonly expectedToken?: string;
}): Promise<{
readonly registryUrl: string;
readonly metadataRequests: () => number;
readonly tarballRequests: () => number;
readonly authorizationHeaders: () => readonly string[];
close(): Promise<void>;
}> {
const tarball = fs.readFileSync(params.tarballPath);
const shasum = createHash('sha1').update(tarball).digest('hex');
const integrity = `sha512-${createHash('sha512').update(tarball).digest('base64')}`;
let metadataRequestCount = 0;
let tarballRequestCount = 0;
const authorizationHeaders: string[] = [];
const server = http.createServer((request, response) => {
const requestUrl = new URL(request.url ?? '/', 'http://127.0.0.1');
const decodedPath = decodeURIComponent(requestUrl.pathname);
const authorization = request.headers.authorization;
if (typeof authorization === 'string') {
authorizationHeaders.push(authorization);
} else if (Array.isArray(authorization)) {
authorizationHeaders.push(...authorization);
}
if (decodedPath === `/${params.packageName}`) {
metadataRequestCount += 1;
const registryUrl = `http://127.0.0.1:${addressPort(server)}/`;
const metadata = {
name: params.packageName,
'dist-tags': { latest: params.version },
versions: {
[params.version]: {
name: params.packageName,
version: params.version,
dist: {
tarball: new URL('tarballs/package.tgz', registryUrl).toString(),
shasum,
integrity,
},
},
},
};
response.writeHead(200, { 'content-type': 'application/json' });
response.end(JSON.stringify(metadata));
return;
}
if (decodedPath === '/tarballs/package.tgz') {
tarballRequestCount += 1;
response.writeHead(200, {
'content-type': 'application/octet-stream',
'content-length': String(tarball.byteLength),
});
response.end(tarball);
return;
}
response.writeHead(404, { 'content-type': 'application/json' });
response.end(JSON.stringify({ error: `Not found: ${decodedPath}` }));
});
await new Promise<void>((resolve, reject) => {
server.once('error', reject);
server.listen(0, '127.0.0.1', () => {
server.off('error', reject);
resolve();
});
});
return {
registryUrl: `http://127.0.0.1:${addressPort(server)}/`,
metadataRequests: () => metadataRequestCount,
tarballRequests: () => tarballRequestCount,
authorizationHeaders: () => [...authorizationHeaders],
async close(): Promise<void> {
await new Promise<void>((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
},
};
}
function addressPort(server: http.Server): number {
const address = server.address();
if (!address || typeof address === 'string') {
throw new Error('Registry fixture is not listening on a TCP port');
}
return address.port;
}
function npmPackPackage(packageDir: string, outDir: string): string {
fs.mkdirSync(outDir, { recursive: true });
const result = spawnSync('npm', ['pack', packageDir, '--pack-destination', outDir], {
encoding: 'utf8',
});
assert.equal(
result.status,
0,
`npm pack failed\nstdout:\n${result.stdout ?? ''}\nstderr:\n${result.stderr ?? ''}`,
);
const fileName = result.stdout.trim().split(/\r?\n/).filter(Boolean).pop();
assert.equal(Boolean(fileName), true);
return path.join(outDir, fileName!);
}
describe('FP-30 mx install source resolution', () => {
const tempDirs: string[] = [];
const closeFns: Array<() => Promise<void>> = [];
const restoreEnvFns: Array<() => void> = [];
afterEach(async () => {
while (restoreEnvFns.length > 0) {
const restore = restoreEnvFns.pop();
if (restore) {
restore();
}
}
while (closeFns.length > 0) {
const close = closeFns.pop();
if (close) {
await close();
}
}
while (tempDirs.length > 0) {
const dir = tempDirs.pop();
if (dir) {
fs.rmSync(dir, { recursive: true, force: true });
}
}
});
function useMatrixHome(matrixHome: string): void {
const previousMatrixHome = process.env.MATRIX_HOME;
const previousCliConfig = process.env.MATRIX_CLI_CONFIG;
const previousPackageRegistry = process.env.MATRIX_PACKAGE_REGISTRY;
const previousRegistryUrl = process.env.MATRIX_REGISTRY_URL;
process.env.MATRIX_HOME = matrixHome;
delete process.env.MATRIX_CLI_CONFIG;
delete process.env.MATRIX_PACKAGE_REGISTRY;
delete process.env.MATRIX_REGISTRY_URL;
restoreEnvFns.push(() => {
restoreEnv('MATRIX_HOME', previousMatrixHome);
restoreEnv('MATRIX_CLI_CONFIG', previousCliConfig);
restoreEnv('MATRIX_PACKAGE_REGISTRY', previousPackageRegistry);
restoreEnv('MATRIX_REGISTRY_URL', previousRegistryUrl);
});
}
function restoreEnv(key: string, value: string | undefined): void {
if (value === undefined) {
delete process.env[key];
return;
}
process.env[key] = value;
}
it('installs package from local directory, tarball, and registry name', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-install-sources-'));
tempDirs.push(root);
const workspaceDir = path.join(root, 'workspace');
fs.mkdirSync(workspaceDir, { recursive: true });
const packagesDir = path.join(root, '.matrix', 'packages');
const registryDir = path.join(root, '.matrix', 'registry');
const credentialsFile = path.join(root, '.matrix', 'credentials.json');
const fixture = createMatrixPackage(path.join(root, 'source'), {
packageName: '@acme/install-source',
version: '1.0.0',
componentType: 'InstallSourceActor',
mount: 'install.source',
});
const installFromDirectory = await runMxCli([
'node',
'mx',
'install',
fixture.packageDir,
'--packages-dir',
packagesDir,
'--json',
], { cwd: workspaceDir });
assertExitCode(installFromDirectory, 0);
const installedFromDir = parseLastJson(installFromDirectory.logs) as { targetDir: string };
assert.equal(fs.existsSync(installedFromDir.targetDir), true);
const uninstallAfterDirectory = await runMxCli([
'node',
'mx',
'uninstall',
fixture.packageName,
'--packages-dir',
packagesDir,
'--json',
], { cwd: workspaceDir });
assertExitCode(uninstallAfterDirectory, 0);
const packResult = await runMxCli([
'node',
'mx',
'pack',
'--out-dir',
path.join(root, 'packed'),
'--json',
], { cwd: fixture.packageDir });
assertExitCode(packResult, 0);
const packPayload = parseLastJson(packResult.logs) as { tarballPath: string };
assert.equal(fs.existsSync(packPayload.tarballPath), true);
const installFromTarball = await runMxCli([
'node',
'mx',
'install',
packPayload.tarballPath,
'--packages-dir',
packagesDir,
'--json',
], { cwd: workspaceDir });
assertExitCode(installFromTarball, 0);
const installedFromTarball = parseLastJson(installFromTarball.logs) as { targetDir: string };
assert.equal(fs.existsSync(installedFromTarball.targetDir), true);
const uninstallAfterTarball = await runMxCli([
'node',
'mx',
'uninstall',
fixture.packageName,
'--packages-dir',
packagesDir,
'--json',
], { cwd: workspaceDir });
assertExitCode(uninstallAfterTarball, 0);
const loginResult = await runMxCli([
'node',
'mx',
'login',
'--username',
'alice',
'--token',
'token-install',
'--credentials-file',
credentialsFile,
'--json',
], { cwd: workspaceDir });
assertExitCode(loginResult, 0);
const publishResult = await runMxCli([
'node',
'mx',
'publish',
'--registry-dir',
registryDir,
'--credentials-file',
credentialsFile,
'--json',
], { cwd: fixture.packageDir });
assertExitCode(publishResult, 0);
const installFromRegistry = await runMxCli([
'node',
'mx',
'install',
`${fixture.packageName}@${fixture.version}`,
'--packages-dir',
packagesDir,
'--registry-dir',
registryDir,
'--json',
], { cwd: workspaceDir });
assertExitCode(installFromRegistry, 0);
const installedFromRegistry = parseLastJson(installFromRegistry.logs) as { source: string; targetDir: string };
assert.equal(installedFromRegistry.source, `${fixture.packageName}@${fixture.version}`);
assert.equal(fs.existsSync(installedFromRegistry.targetDir), true);
});
it('installs package from an npm-compatible registry URL with stored registry credentials', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-install-npm-registry-'));
tempDirs.push(root);
const workspaceDir = path.join(root, 'workspace');
fs.mkdirSync(workspaceDir, { recursive: true });
const packagesDir = path.join(root, '.matrix', 'packages');
const credentialsFile = path.join(root, '.matrix', 'credentials.json');
const fixture = createMatrixPackage(path.join(root, 'source'), {
packageName: '@acme/remote-install',
version: '1.2.3',
componentType: 'RemoteInstallActor',
mount: 'remote.install',
});
fs.mkdirSync(path.join(fixture.packageDir, 'vendor', 'runtime-helper'), { recursive: true });
fs.writeFileSync(
path.join(fixture.packageDir, 'vendor', 'runtime-helper', 'package.json'),
JSON.stringify({
name: '@acme/runtime-helper',
version: '1.0.0',
type: 'module',
main: 'index.js',
}, null, 2),
'utf8',
);
fs.writeFileSync(
path.join(fixture.packageDir, 'vendor', 'runtime-helper', 'index.js'),
'export const helper = true;\n',
'utf8',
);
const packageJsonPath = path.join(fixture.packageDir, 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as Record<string, unknown>;
packageJson.dependencies = {
'@acme/runtime-helper': 'file:./vendor/runtime-helper',
};
packageJson.devDependencies = {
'@open-matrix/core': '*',
};
packageJson.peerDependencies = {
'@open-matrix/core': '*',
};
packageJson.peerDependenciesMeta = {
'@open-matrix/core': { optional: true },
};
fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, 'utf8');
const packResult = await runMxCli([
'node',
'mx',
'pack',
'--out-dir',
path.join(root, 'packed'),
'--json',
], { cwd: fixture.packageDir });
assertExitCode(packResult, 0);
const packPayload = parseLastJson(packResult.logs) as { tarballPath: string };
const registry = await startNpmRegistryFixture({
packageName: fixture.packageName,
version: fixture.version,
tarballPath: packPayload.tarballPath,
expectedToken: 'token-remote-install',
});
closeFns.push(registry.close);
const loginResult = await runMxCli([
'node',
'mx',
'login',
'--username',
'alice',
'--token',
'token-remote-install',
'--registry',
registry.registryUrl,
'--credentials-file',
credentialsFile,
'--json',
], { cwd: workspaceDir });
assertExitCode(loginResult, 0);
const installFromRemoteRegistry = await runMxCli([
'node',
'mx',
'install',
`${fixture.packageName}@${fixture.version}`,
'--packages-dir',
packagesDir,
'--registry',
registry.registryUrl,
'--credentials-file',
credentialsFile,
'--json',
], { cwd: workspaceDir });
assertExitCode(installFromRemoteRegistry, 0);
const installed = parseLastJson(installFromRemoteRegistry.logs) as {
source: string;
targetDir: string;
version: string;
};
assert.equal(installed.source, `${fixture.packageName}@${fixture.version}`);
assert.equal(installed.version, fixture.version);
assert.equal(fs.existsSync(installed.targetDir), true);
assert.equal(
fs.existsSync(path.join(installed.targetDir, 'node_modules', '@acme', 'runtime-helper', 'package.json')),
true,
);
assert.equal(
fs.existsSync(path.join(installed.targetDir, 'node_modules', '@open-matrix', 'core', 'package.json')),
false,
);
assert.equal(registry.metadataRequests() > 0, true);
assert.equal(registry.tarballRequests() > 0, true);
assert.equal(
registry.authorizationHeaders().some((header) => header.includes('token-remote-install')),
true,
);
});
it('installs package names from the configured npm registry without a per-command registry flag', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-install-config-registry-'));
tempDirs.push(root);
const workspaceDir = path.join(root, 'workspace');
fs.mkdirSync(workspaceDir, { recursive: true });
useMatrixHome(path.join(root, '.matrix'));
const packagesDir = path.join(root, '.matrix', 'packages');
const credentialsFile = path.join(root, '.matrix', 'credentials.json');
const fixture = createMatrixPackage(path.join(root, 'source'), {
packageName: '@acme/configured-remote-install',
version: '1.2.3',
componentType: 'ConfiguredRemoteInstallActor',
mount: 'configured.remote.install',
});
const packResult = await runMxCli([
'node',
'mx',
'pack',
'--out-dir',
path.join(root, 'packed'),
'--json',
], { cwd: fixture.packageDir });
assertExitCode(packResult, 0);
const packPayload = parseLastJson(packResult.logs) as { tarballPath: string };
const registry = await startNpmRegistryFixture({
packageName: fixture.packageName,
version: fixture.version,
tarballPath: packPayload.tarballPath,
expectedToken: 'token-configured-install',
});
closeFns.push(registry.close);
const setConfig = await runMxCli([
'node',
'mx',
'config',
'set',
'registry',
registry.registryUrl,
'--json',
], { cwd: workspaceDir });
assertExitCode(setConfig, 0);
const loginResult = await runMxCli([
'node',
'mx',
'login',
'--username',
'alice',
'--token',
'token-configured-install',
'--credentials-file',
credentialsFile,
'--json',
], { cwd: workspaceDir });
assertExitCode(loginResult, 0);
const installFromConfiguredRegistry = await runMxCli([
'node',
'mx',
'install',
`${fixture.packageName}@${fixture.version}`,
'--packages-dir',
packagesDir,
'--credentials-file',
credentialsFile,
'--json',
], { cwd: workspaceDir });
assertExitCode(installFromConfiguredRegistry, 0);
const installed = parseLastJson(installFromConfiguredRegistry.logs) as {
source: string;
targetDir: string;
version: string;
};
assert.equal(installed.source, `${fixture.packageName}@${fixture.version}`);
assert.equal(installed.version, fixture.version);
assert.equal(fs.existsSync(installed.targetDir), true);
assert.equal(registry.metadataRequests() > 0, true);
assert.equal(registry.tarballRequests() > 0, true);
assert.equal(
registry.authorizationHeaders().some((header) => header.includes('token-configured-install')),
true,
);
});
it('rejects npm registry packages whose matrix.json and package.json versions disagree', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-install-version-mismatch-'));
tempDirs.push(root);
const workspaceDir = path.join(root, 'workspace');
fs.mkdirSync(workspaceDir, { recursive: true });
const packagesDir = path.join(root, '.matrix', 'packages');
const fixture = createMatrixPackage(path.join(root, 'source'), {
packageName: '@acme/version-mismatch',
version: '1.0.0',
componentType: 'VersionMismatchActor',
mount: 'version.mismatch',
});
fs.writeFileSync(
path.join(fixture.packageDir, 'package.json'),
JSON.stringify({
name: fixture.packageName,
version: '9.9.9',
type: 'module',
main: 'src/index.ts',
}, null, 2),
'utf8',
);
const tarballPath = npmPackPackage(fixture.packageDir, path.join(root, 'packed'));
const registry = await startNpmRegistryFixture({
packageName: fixture.packageName,
version: '9.9.9',
tarballPath,
});
closeFns.push(registry.close);
const installResult = await runMxCli([
'node',
'mx',
'install',
`${fixture.packageName}@9.9.9`,
'--packages-dir',
packagesDir,
'--registry',
registry.registryUrl,
'--json',
], { cwd: workspaceDir });
assertExitCode(installResult, 1);
assert.equal(
installResult.errors.some((line) => line.includes('MX_PACKAGE_VERSION_MISMATCH')),
true,
);
});
it('normalizes whitespace-padded registry specifiers before install', async () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-install-specifier-trim-'));
tempDirs.push(root);
const workspaceDir = path.join(root, 'workspace');
fs.mkdirSync(workspaceDir, { recursive: true });
const packagesDir = path.join(root, '.matrix', 'packages');
const registryDir = path.join(root, '.matrix', 'registry');
const credentialsFile = path.join(root, '.matrix', 'credentials.json');
const fixture = createMatrixPackage(path.join(root, 'source'), {
packageName: '@acme/install-source',
version: '1.0.0',
componentType: 'InstallSourceActor',
mount: 'install.source',
});
const loginResult = await runMxCli([
'node',
'mx',
'login',
'--username',
'alice',
'--token',
'token-install',
'--credentials-file',
credentialsFile,
'--json',
], { cwd: workspaceDir });
assertExitCode(loginResult, 0);
const publishResult = await runMxCli([
'node',
'mx',
'publish',
'--registry-dir',
registryDir,
'--credentials-file',
credentialsFile,
'--json',
], { cwd: fixture.packageDir });
assertExitCode(publishResult, 0);
const installFromRegistry = await runMxCli([
'node',
'mx',
'install',
` ${fixture.packageName}@${fixture.version} `,
'--packages-dir',
packagesDir,
'--registry-dir',
registryDir,
'--json',
], { cwd: workspaceDir });
assertExitCode(installFromRegistry, 0);
const installedFromRegistry = parseLastJson(installFromRegistry.logs) as { source: string; targetDir: string };
assert.equal(installedFromRegistry.source, `${fixture.packageName}@${fixture.version}`);
assert.equal(fs.existsSync(installedFromRegistry.targetDir), true);
});
});