193 lines
6.4 KiB
TypeScript
193 lines
6.4 KiB
TypeScript
|
|
import { afterEach, describe, it } from 'node:test';
|
||
|
|
import { strict as assert } from 'node:assert';
|
||
|
|
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';
|
||
|
|
|
||
|
|
function addressPort(server: http.Server): number {
|
||
|
|
const address = server.address();
|
||
|
|
if (!address || typeof address === 'string') {
|
||
|
|
throw new Error('Fixture registry did not bind a TCP port');
|
||
|
|
}
|
||
|
|
return address.port;
|
||
|
|
}
|
||
|
|
|
||
|
|
function asRecord(value: unknown): Record<string, unknown> {
|
||
|
|
assert.ok(value !== null && typeof value === 'object' && !Array.isArray(value));
|
||
|
|
return value as Record<string, unknown>;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function startRegistryFixture(): Promise<{ readonly registryUrl: string; readonly close: () => Promise<void> }> {
|
||
|
|
const server = http.createServer((req, res) => {
|
||
|
|
const registryUrl = `http://127.0.0.1:${addressPort(server)}/api/packages/open-matrix/npm/`;
|
||
|
|
const url = new URL(req.url ?? '/', registryUrl);
|
||
|
|
if (req.method === 'GET' && url.pathname.endsWith('/-/whoami')) {
|
||
|
|
res.writeHead(200, { 'content-type': 'application/json' });
|
||
|
|
res.end(JSON.stringify({ username: 'alice' }));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
res.writeHead(200, { 'content-type': 'application/json' });
|
||
|
|
res.end(JSON.stringify({ ok: true }));
|
||
|
|
});
|
||
|
|
|
||
|
|
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)}/api/packages/open-matrix/npm/`,
|
||
|
|
close: async () => {
|
||
|
|
await new Promise<void>((resolve, reject) => {
|
||
|
|
server.close((error) => (error ? reject(error) : resolve()));
|
||
|
|
});
|
||
|
|
},
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('FP-30 mx registry commands', { concurrency: false }, () => {
|
||
|
|
const tempDirs: string[] = [];
|
||
|
|
const restoreEnvFns: Array<() => void> = [];
|
||
|
|
const fixtureClosers: Array<() => Promise<void>> = [];
|
||
|
|
|
||
|
|
afterEach(async () => {
|
||
|
|
while (fixtureClosers.length > 0) {
|
||
|
|
const close = fixtureClosers.pop();
|
||
|
|
if (close) {
|
||
|
|
await close();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
while (restoreEnvFns.length > 0) {
|
||
|
|
const restore = restoreEnvFns.pop();
|
||
|
|
if (restore) {
|
||
|
|
restore();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
while (tempDirs.length > 0) {
|
||
|
|
const dir = tempDirs.pop();
|
||
|
|
if (dir) {
|
||
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
function useCleanEnv(root: string): void {
|
||
|
|
const previousHome = process.env.HOME;
|
||
|
|
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;
|
||
|
|
const previousNpmUserConfig = process.env.NPM_CONFIG_USERCONFIG;
|
||
|
|
const previousNodeAuthToken = process.env.NODE_AUTH_TOKEN;
|
||
|
|
const previousNpmToken = process.env.NPM_TOKEN;
|
||
|
|
const home = path.join(root, 'home');
|
||
|
|
fs.mkdirSync(home, { recursive: true });
|
||
|
|
process.env.HOME = home;
|
||
|
|
process.env.MATRIX_HOME = path.join(root, '.matrix');
|
||
|
|
delete process.env.MATRIX_CLI_CONFIG;
|
||
|
|
delete process.env.MATRIX_PACKAGE_REGISTRY;
|
||
|
|
delete process.env.MATRIX_REGISTRY_URL;
|
||
|
|
delete process.env.NPM_CONFIG_USERCONFIG;
|
||
|
|
delete process.env.NODE_AUTH_TOKEN;
|
||
|
|
delete process.env.NPM_TOKEN;
|
||
|
|
restoreEnvFns.push(() => {
|
||
|
|
restoreEnv('HOME', previousHome);
|
||
|
|
restoreEnv('MATRIX_HOME', previousMatrixHome);
|
||
|
|
restoreEnv('MATRIX_CLI_CONFIG', previousCliConfig);
|
||
|
|
restoreEnv('MATRIX_PACKAGE_REGISTRY', previousPackageRegistry);
|
||
|
|
restoreEnv('MATRIX_REGISTRY_URL', previousRegistryUrl);
|
||
|
|
restoreEnv('NPM_CONFIG_USERCONFIG', previousNpmUserConfig);
|
||
|
|
restoreEnv('NODE_AUTH_TOKEN', previousNodeAuthToken);
|
||
|
|
restoreEnv('NPM_TOKEN', previousNpmToken);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function restoreEnv(key: string, value: string | undefined): void {
|
||
|
|
if (value === undefined) {
|
||
|
|
delete process.env[key];
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
process.env[key] = value;
|
||
|
|
}
|
||
|
|
|
||
|
|
it('uses a local Gitea registry profile and diagnoses scope config without reading poisoned user npmrc', async () => {
|
||
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-registry-command-'));
|
||
|
|
tempDirs.push(root);
|
||
|
|
useCleanEnv(root);
|
||
|
|
fs.writeFileSync(path.join(process.env.HOME!, '.npmrc'), [
|
||
|
|
'@matrix:registry=https://wrong.example.invalid/',
|
||
|
|
'@open-matrix:registry=https://wrong.example.invalid/',
|
||
|
|
'@omega:registry=https://wrong.example.invalid/',
|
||
|
|
'',
|
||
|
|
].join('\n'), 'utf8');
|
||
|
|
const fixture = await startRegistryFixture();
|
||
|
|
fixtureClosers.push(fixture.close);
|
||
|
|
const credentialsFile = path.join(root, 'credentials.json');
|
||
|
|
|
||
|
|
const useResult = await runMxCli([
|
||
|
|
'node',
|
||
|
|
'mx',
|
||
|
|
'registry',
|
||
|
|
'use',
|
||
|
|
'local-gitea',
|
||
|
|
'--registry',
|
||
|
|
fixture.registryUrl,
|
||
|
|
'--json',
|
||
|
|
], { cwd: root });
|
||
|
|
assertExitCode(useResult, 0);
|
||
|
|
assert.equal((parseLastJson(useResult.logs) as Record<string, unknown>).registry, fixture.registryUrl);
|
||
|
|
|
||
|
|
const loginResult = await runMxCli([
|
||
|
|
'node',
|
||
|
|
'mx',
|
||
|
|
'registry',
|
||
|
|
'login',
|
||
|
|
'--registry',
|
||
|
|
fixture.registryUrl,
|
||
|
|
'--username',
|
||
|
|
'alice',
|
||
|
|
'--token',
|
||
|
|
'token-registry-command',
|
||
|
|
'--credentials-file',
|
||
|
|
credentialsFile,
|
||
|
|
'--json',
|
||
|
|
], { cwd: root });
|
||
|
|
assertExitCode(loginResult, 0);
|
||
|
|
|
||
|
|
const doctorResult = await runMxCli([
|
||
|
|
'node',
|
||
|
|
'mx',
|
||
|
|
'registry',
|
||
|
|
'doctor',
|
||
|
|
'--registry',
|
||
|
|
fixture.registryUrl,
|
||
|
|
'--credentials-file',
|
||
|
|
credentialsFile,
|
||
|
|
'--timeout-ms',
|
||
|
|
'5000',
|
||
|
|
'--json',
|
||
|
|
], { cwd: root });
|
||
|
|
assertExitCode(doctorResult, 0);
|
||
|
|
const doctor = asRecord(parseLastJson(doctorResult.logs));
|
||
|
|
const registry = asRecord(doctor.registry);
|
||
|
|
const credentials = asRecord(doctor.credentials);
|
||
|
|
const npm = asRecord(doctor.npm);
|
||
|
|
assert.equal(doctor.ok, true);
|
||
|
|
assert.equal(registry.url, fixture.registryUrl);
|
||
|
|
assert.equal(credentials.found, true);
|
||
|
|
assert.equal(credentials.authOk, true);
|
||
|
|
assert.equal(npm.allScopesMatch, true);
|
||
|
|
assert.equal(npm.poisonedUserConfigIgnored, true);
|
||
|
|
assert.deepEqual(npm.scopeResolution, {
|
||
|
|
'@open-matrix': fixture.registryUrl,
|
||
|
|
'@matrix': fixture.registryUrl,
|
||
|
|
'@omega': fixture.registryUrl,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|