590 lines
20 KiB
TypeScript
590 lines
20 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';
|
||
|
|
import { createMatrixPackage } from '../helpers/package-fixtures.js';
|
||
|
|
|
||
|
|
interface INpmPublishRegistryFixture {
|
||
|
|
readonly registryUrl: string;
|
||
|
|
readonly close: () => Promise<void>;
|
||
|
|
readonly publishRequests: () => number;
|
||
|
|
readonly tarballRequests: () => number;
|
||
|
|
readonly authorizationHeaders: () => readonly string[];
|
||
|
|
}
|
||
|
|
|
||
|
|
interface IPublishedPackage {
|
||
|
|
readonly name: string;
|
||
|
|
readonly version: string;
|
||
|
|
readonly fileName: string;
|
||
|
|
readonly tarball: Buffer;
|
||
|
|
}
|
||
|
|
|
||
|
|
function asRecord(value: unknown): Record<string, unknown> | null {
|
||
|
|
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
||
|
|
? value as Record<string, unknown>
|
||
|
|
: null;
|
||
|
|
}
|
||
|
|
|
||
|
|
function readString(value: unknown): string | null {
|
||
|
|
return typeof value === 'string' && value.trim().length > 0 ? value : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function readRequestBody(req: http.IncomingMessage): Promise<string> {
|
||
|
|
const chunks: Buffer[] = [];
|
||
|
|
for await (const chunk of req) {
|
||
|
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
||
|
|
}
|
||
|
|
return Buffer.concat(chunks).toString('utf8');
|
||
|
|
}
|
||
|
|
|
||
|
|
async function startNpmPublishRegistryFixture(): Promise<INpmPublishRegistryFixture> {
|
||
|
|
let publishRequests = 0;
|
||
|
|
let tarballRequests = 0;
|
||
|
|
let published: IPublishedPackage | null = null;
|
||
|
|
const authorizationHeaders: string[] = [];
|
||
|
|
|
||
|
|
const server = http.createServer((req, res) => {
|
||
|
|
void (async () => {
|
||
|
|
const auth = req.headers.authorization;
|
||
|
|
if (typeof auth === 'string') {
|
||
|
|
authorizationHeaders.push(auth);
|
||
|
|
}
|
||
|
|
const registryUrl = `http://127.0.0.1:${addressPort(server)}/`;
|
||
|
|
const url = new URL(req.url ?? '/', registryUrl);
|
||
|
|
if (req.method === 'PUT') {
|
||
|
|
publishRequests += 1;
|
||
|
|
const body = asRecord(JSON.parse(await readRequestBody(req)) as unknown);
|
||
|
|
const packageName = readString(body?.name);
|
||
|
|
const distTags = asRecord(body?.['dist-tags']);
|
||
|
|
const version = readString(distTags?.latest);
|
||
|
|
const attachments = asRecord(body?._attachments);
|
||
|
|
const attachmentEntry = attachments ? Object.entries(attachments)[0] : undefined;
|
||
|
|
const attachment = asRecord(attachmentEntry?.[1]);
|
||
|
|
const data = readString(attachment?.data);
|
||
|
|
if (!packageName || !version || !attachmentEntry || !data) {
|
||
|
|
res.writeHead(400, { 'content-type': 'application/json' });
|
||
|
|
res.end(JSON.stringify({ error: 'invalid publish payload' }));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
published = {
|
||
|
|
name: packageName,
|
||
|
|
version,
|
||
|
|
fileName: attachmentEntry[0],
|
||
|
|
tarball: Buffer.from(data, 'base64'),
|
||
|
|
};
|
||
|
|
res.writeHead(201, { 'content-type': 'application/json' });
|
||
|
|
res.end(JSON.stringify({ ok: true }));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (req.method === 'GET' && url.pathname.endsWith('.tgz')) {
|
||
|
|
tarballRequests += 1;
|
||
|
|
if (!published) {
|
||
|
|
res.writeHead(404, { 'content-type': 'application/json' });
|
||
|
|
res.end(JSON.stringify({ error: 'no tarball published' }));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
res.writeHead(200, { 'content-type': 'application/octet-stream' });
|
||
|
|
res.end(published.tarball);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const decodedPath = decodeURIComponent(url.pathname);
|
||
|
|
if (req.method === 'GET' && published && decodedPath === `/${published.name}`) {
|
||
|
|
res.writeHead(200, { 'content-type': 'application/json' });
|
||
|
|
res.end(JSON.stringify({
|
||
|
|
name: published.name,
|
||
|
|
'dist-tags': { latest: published.version },
|
||
|
|
versions: {
|
||
|
|
[published.version]: {
|
||
|
|
name: published.name,
|
||
|
|
version: published.version,
|
||
|
|
dist: {
|
||
|
|
tarball: new URL(`tarballs/${published.fileName}`, registryUrl).toString(),
|
||
|
|
},
|
||
|
|
},
|
||
|
|
},
|
||
|
|
}));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
res.writeHead(404, { 'content-type': 'application/json' });
|
||
|
|
res.end(JSON.stringify({ error: 'not found', path: url.pathname }));
|
||
|
|
})().catch((error: unknown) => {
|
||
|
|
res.writeHead(500, { 'content-type': 'application/json' });
|
||
|
|
res.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
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)}/`,
|
||
|
|
close: async () => {
|
||
|
|
await new Promise<void>((resolve, reject) => {
|
||
|
|
server.close((error) => (error ? reject(error) : resolve()));
|
||
|
|
});
|
||
|
|
},
|
||
|
|
publishRequests: () => publishRequests,
|
||
|
|
tarballRequests: () => tarballRequests,
|
||
|
|
authorizationHeaders: () => authorizationHeaders,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('FP-30 mx auth + publish integration', { concurrency: false }, () => {
|
||
|
|
const tempDirs: string[] = [];
|
||
|
|
const restoreEnvFns: Array<() => void> = [];
|
||
|
|
|
||
|
|
afterEach(() => {
|
||
|
|
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 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;
|
||
|
|
const previousNpmUserConfig = process.env.NPM_CONFIG_USERCONFIG;
|
||
|
|
const previousNodeAuthToken = process.env.NODE_AUTH_TOKEN;
|
||
|
|
const previousNpmToken = process.env.NPM_TOKEN;
|
||
|
|
process.env.MATRIX_HOME = matrixHome;
|
||
|
|
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('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('requires login before publish and enables registry install after publish', async () => {
|
||
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-auth-publish-'));
|
||
|
|
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 homeDir = path.join(root, 'home');
|
||
|
|
const previousHome = process.env.HOME;
|
||
|
|
fs.mkdirSync(homeDir, { recursive: true });
|
||
|
|
process.env.HOME = homeDir;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const fixture = createMatrixPackage(path.join(root, 'source'), {
|
||
|
|
packageName: '@acme/fp30-auth-publish',
|
||
|
|
version: '1.0.0',
|
||
|
|
componentType: 'AuthPublishActor',
|
||
|
|
mount: 'auth.publish',
|
||
|
|
});
|
||
|
|
|
||
|
|
const publishWithoutLogin = await runMxCli([
|
||
|
|
'node',
|
||
|
|
'mx',
|
||
|
|
'publish',
|
||
|
|
'--registry-dir',
|
||
|
|
registryDir,
|
||
|
|
'--credentials-file',
|
||
|
|
credentialsFile,
|
||
|
|
'--json',
|
||
|
|
], { cwd: fixture.packageDir });
|
||
|
|
assertExitCode(publishWithoutLogin, 1);
|
||
|
|
assert.equal(publishWithoutLogin.errors.some((line) => line.includes('MX_AUTH_REQUIRED')), true);
|
||
|
|
|
||
|
|
const loginResult = await runMxCli([
|
||
|
|
'node',
|
||
|
|
'mx',
|
||
|
|
'login',
|
||
|
|
'--username',
|
||
|
|
'alice',
|
||
|
|
'--token',
|
||
|
|
'token-auth-publish',
|
||
|
|
'--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 payload = parseLastJson(installFromRegistry.logs) as { targetDir: string };
|
||
|
|
assert.equal(fs.existsSync(payload.targetDir), true);
|
||
|
|
} finally {
|
||
|
|
restoreEnv('HOME', previousHome);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('publishes to an npm registry API and installs from the published tarball', async () => {
|
||
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-npm-publish-'));
|
||
|
|
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 registry = await startNpmPublishRegistryFixture();
|
||
|
|
|
||
|
|
try {
|
||
|
|
const fixture = createMatrixPackage(path.join(root, 'source'), {
|
||
|
|
packageName: '@acme/fp30-npm-publish',
|
||
|
|
version: '1.0.0',
|
||
|
|
componentType: 'NpmPublishActor',
|
||
|
|
mount: 'npm.publish',
|
||
|
|
});
|
||
|
|
|
||
|
|
const loginResult = await runMxCli([
|
||
|
|
'node',
|
||
|
|
'mx',
|
||
|
|
'login',
|
||
|
|
'--username',
|
||
|
|
'alice',
|
||
|
|
'--token',
|
||
|
|
'token-npm-publish',
|
||
|
|
'--registry',
|
||
|
|
registry.registryUrl,
|
||
|
|
'--credentials-file',
|
||
|
|
credentialsFile,
|
||
|
|
'--json',
|
||
|
|
], { cwd: workspaceDir });
|
||
|
|
assertExitCode(loginResult, 0);
|
||
|
|
|
||
|
|
const publishResult = await runMxCli([
|
||
|
|
'node',
|
||
|
|
'mx',
|
||
|
|
'publish',
|
||
|
|
'--registry',
|
||
|
|
registry.registryUrl,
|
||
|
|
'--credentials-file',
|
||
|
|
credentialsFile,
|
||
|
|
'--json',
|
||
|
|
], { cwd: fixture.packageDir });
|
||
|
|
assertExitCode(publishResult, 0);
|
||
|
|
const publishPayload = parseLastJson(publishResult.logs) as {
|
||
|
|
packageName: string;
|
||
|
|
version: string;
|
||
|
|
registryUrl?: string;
|
||
|
|
};
|
||
|
|
assert.equal(publishPayload.packageName, fixture.packageName);
|
||
|
|
assert.equal(publishPayload.version, fixture.version);
|
||
|
|
assert.equal(publishPayload.registryUrl, registry.registryUrl);
|
||
|
|
assert.equal(registry.publishRequests(), 1);
|
||
|
|
assert.equal(
|
||
|
|
registry.authorizationHeaders().some((header) => header.includes('token-npm-publish')),
|
||
|
|
true
|
||
|
|
);
|
||
|
|
|
||
|
|
const installResult = await runMxCli([
|
||
|
|
'node',
|
||
|
|
'mx',
|
||
|
|
'install',
|
||
|
|
`${fixture.packageName}@${fixture.version}`,
|
||
|
|
'--packages-dir',
|
||
|
|
packagesDir,
|
||
|
|
'--registry',
|
||
|
|
registry.registryUrl,
|
||
|
|
'--credentials-file',
|
||
|
|
credentialsFile,
|
||
|
|
'--json',
|
||
|
|
], { cwd: workspaceDir });
|
||
|
|
assertExitCode(installResult, 0);
|
||
|
|
const installPayload = parseLastJson(installResult.logs) as { targetDir: string };
|
||
|
|
assert.equal(fs.existsSync(path.join(installPayload.targetDir, 'matrix.json')), true);
|
||
|
|
assert.equal(registry.tarballRequests(), 1);
|
||
|
|
} finally {
|
||
|
|
await registry.close();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('keeps Matrix scoped packages on the requested npm registry API', async () => {
|
||
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-scoped-npm-publish-'));
|
||
|
|
tempDirs.push(root);
|
||
|
|
const workspaceDir = path.join(root, 'workspace');
|
||
|
|
const homeDir = path.join(root, 'home');
|
||
|
|
fs.mkdirSync(workspaceDir, { recursive: true });
|
||
|
|
fs.mkdirSync(homeDir, { recursive: true });
|
||
|
|
const packagesDir = path.join(root, '.matrix', 'packages');
|
||
|
|
const credentialsFile = path.join(root, '.matrix', 'credentials.json');
|
||
|
|
const registry = await startNpmPublishRegistryFixture();
|
||
|
|
const previousHome = process.env.HOME;
|
||
|
|
const previousGlobalConfig = process.env.NPM_CONFIG_GLOBALCONFIG;
|
||
|
|
const wrongRegistry = 'http://127.0.0.1:9/wrong/npm/';
|
||
|
|
const poisonedGlobalConfig = path.join(root, 'poisoned-global-npmrc');
|
||
|
|
|
||
|
|
fs.writeFileSync(
|
||
|
|
path.join(homeDir, '.npmrc'),
|
||
|
|
[
|
||
|
|
`@matrix:registry=${wrongRegistry}`,
|
||
|
|
`@open-matrix:registry=${wrongRegistry}`,
|
||
|
|
`@omega:registry=${wrongRegistry}`,
|
||
|
|
'',
|
||
|
|
].join('\n'),
|
||
|
|
'utf8',
|
||
|
|
);
|
||
|
|
fs.writeFileSync(
|
||
|
|
poisonedGlobalConfig,
|
||
|
|
[
|
||
|
|
`@matrix:registry=${wrongRegistry}`,
|
||
|
|
`@open-matrix:registry=${wrongRegistry}`,
|
||
|
|
`@omega:registry=${wrongRegistry}`,
|
||
|
|
'',
|
||
|
|
].join('\n'),
|
||
|
|
'utf8',
|
||
|
|
);
|
||
|
|
process.env.HOME = homeDir;
|
||
|
|
process.env.NPM_CONFIG_GLOBALCONFIG = poisonedGlobalConfig;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const fixture = createMatrixPackage(path.join(root, 'source'), {
|
||
|
|
packageName: '@matrix/fp30-scoped-npm-publish',
|
||
|
|
version: '1.0.0',
|
||
|
|
componentType: 'ScopedNpmPublishActor',
|
||
|
|
mount: 'scoped.npm.publish',
|
||
|
|
});
|
||
|
|
|
||
|
|
const loginResult = await runMxCli([
|
||
|
|
'node',
|
||
|
|
'mx',
|
||
|
|
'login',
|
||
|
|
'--username',
|
||
|
|
'alice',
|
||
|
|
'--token',
|
||
|
|
'token-scoped-npm-publish',
|
||
|
|
'--registry',
|
||
|
|
registry.registryUrl,
|
||
|
|
'--credentials-file',
|
||
|
|
credentialsFile,
|
||
|
|
'--json',
|
||
|
|
], { cwd: workspaceDir });
|
||
|
|
assertExitCode(loginResult, 0);
|
||
|
|
|
||
|
|
const publishResult = await runMxCli([
|
||
|
|
'node',
|
||
|
|
'mx',
|
||
|
|
'publish',
|
||
|
|
'--registry',
|
||
|
|
registry.registryUrl,
|
||
|
|
'--credentials-file',
|
||
|
|
credentialsFile,
|
||
|
|
'--json',
|
||
|
|
], { cwd: fixture.packageDir });
|
||
|
|
assertExitCode(publishResult, 0);
|
||
|
|
assert.equal(registry.publishRequests(), 1);
|
||
|
|
assert.equal(
|
||
|
|
registry.authorizationHeaders().some((header) => header.includes('token-scoped-npm-publish')),
|
||
|
|
true,
|
||
|
|
);
|
||
|
|
|
||
|
|
const installResult = await runMxCli([
|
||
|
|
'node',
|
||
|
|
'mx',
|
||
|
|
'install',
|
||
|
|
`${fixture.packageName}@${fixture.version}`,
|
||
|
|
'--packages-dir',
|
||
|
|
packagesDir,
|
||
|
|
'--registry',
|
||
|
|
registry.registryUrl,
|
||
|
|
'--credentials-file',
|
||
|
|
credentialsFile,
|
||
|
|
'--json',
|
||
|
|
], { cwd: workspaceDir });
|
||
|
|
assertExitCode(installResult, 0);
|
||
|
|
const installPayload = parseLastJson(installResult.logs) as { targetDir: string };
|
||
|
|
assert.equal(fs.existsSync(path.join(installPayload.targetDir, 'matrix.json')), true);
|
||
|
|
assert.equal(registry.tarballRequests(), 1);
|
||
|
|
} finally {
|
||
|
|
restoreEnv('HOME', previousHome);
|
||
|
|
restoreEnv('NPM_CONFIG_GLOBALCONFIG', previousGlobalConfig);
|
||
|
|
await registry.close();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('publishes to the configured npm registry API without a per-command registry flag', async () => {
|
||
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-configured-npm-publish-'));
|
||
|
|
tempDirs.push(root);
|
||
|
|
useMatrixHome(path.join(root, '.matrix'));
|
||
|
|
const workspaceDir = path.join(root, 'workspace');
|
||
|
|
fs.mkdirSync(workspaceDir, { recursive: true });
|
||
|
|
const credentialsFile = path.join(root, '.matrix', 'credentials.json');
|
||
|
|
const registry = await startNpmPublishRegistryFixture();
|
||
|
|
|
||
|
|
try {
|
||
|
|
const fixture = createMatrixPackage(path.join(root, 'source'), {
|
||
|
|
packageName: '@acme/fp30-configured-npm-publish',
|
||
|
|
version: '1.0.0',
|
||
|
|
componentType: 'ConfiguredNpmPublishActor',
|
||
|
|
mount: 'configured.npm.publish',
|
||
|
|
});
|
||
|
|
|
||
|
|
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-npm-publish',
|
||
|
|
'--credentials-file',
|
||
|
|
credentialsFile,
|
||
|
|
'--json',
|
||
|
|
], { cwd: workspaceDir });
|
||
|
|
assertExitCode(loginResult, 0);
|
||
|
|
|
||
|
|
const publishResult = await runMxCli([
|
||
|
|
'node',
|
||
|
|
'mx',
|
||
|
|
'publish',
|
||
|
|
'--credentials-file',
|
||
|
|
credentialsFile,
|
||
|
|
'--json',
|
||
|
|
], { cwd: fixture.packageDir });
|
||
|
|
assertExitCode(publishResult, 0);
|
||
|
|
const publishPayload = parseLastJson(publishResult.logs) as {
|
||
|
|
packageName: string;
|
||
|
|
version: string;
|
||
|
|
registryUrl?: string;
|
||
|
|
};
|
||
|
|
assert.equal(publishPayload.packageName, fixture.packageName);
|
||
|
|
assert.equal(publishPayload.version, fixture.version);
|
||
|
|
assert.equal(publishPayload.registryUrl, registry.registryUrl);
|
||
|
|
assert.equal(registry.publishRequests(), 1);
|
||
|
|
assert.equal(
|
||
|
|
registry.authorizationHeaders().some((header) => header.includes('token-configured-npm-publish')),
|
||
|
|
true,
|
||
|
|
);
|
||
|
|
} finally {
|
||
|
|
await registry.close();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
it('publishes to an npm registry API using an npmrc token', async () => {
|
||
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'mx-fp30-npmrc-publish-'));
|
||
|
|
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/fp30-npmrc-publish',
|
||
|
|
version: '1.0.0',
|
||
|
|
componentType: 'NpmrcPublishActor',
|
||
|
|
mount: 'npmrc.publish',
|
||
|
|
});
|
||
|
|
const registry = await startNpmPublishRegistryFixture();
|
||
|
|
const npmrcPath = path.join(root, '.npmrc');
|
||
|
|
const registryKey = registry.registryUrl.replace(/^https?:/, '');
|
||
|
|
const previousNpmUserConfig = process.env.NPM_CONFIG_USERCONFIG;
|
||
|
|
restoreEnvFns.push(() => restoreEnv('NPM_CONFIG_USERCONFIG', previousNpmUserConfig));
|
||
|
|
fs.writeFileSync(
|
||
|
|
npmrcPath,
|
||
|
|
`${registryKey}:_authToken=npmrc-token-publish\n`,
|
||
|
|
'utf8',
|
||
|
|
);
|
||
|
|
process.env.NPM_CONFIG_USERCONFIG = npmrcPath;
|
||
|
|
try {
|
||
|
|
const publishResult = await runMxCli([
|
||
|
|
'node',
|
||
|
|
'mx',
|
||
|
|
'publish',
|
||
|
|
'--registry',
|
||
|
|
registry.registryUrl,
|
||
|
|
'--json',
|
||
|
|
], { cwd: fixture.packageDir });
|
||
|
|
assertExitCode(publishResult, 0);
|
||
|
|
|
||
|
|
const installResult = await runMxCli([
|
||
|
|
'node',
|
||
|
|
'mx',
|
||
|
|
'install',
|
||
|
|
`${fixture.packageName}@${fixture.version}`,
|
||
|
|
'--packages-dir',
|
||
|
|
packagesDir,
|
||
|
|
'--registry',
|
||
|
|
registry.registryUrl,
|
||
|
|
'--json',
|
||
|
|
], { cwd: workspaceDir });
|
||
|
|
assertExitCode(installResult, 0);
|
||
|
|
|
||
|
|
assert.equal(registry.publishRequests(), 1);
|
||
|
|
assert.equal(registry.tarballRequests(), 1);
|
||
|
|
assert.equal(registry.authorizationHeaders().includes('Bearer npmrc-token-publish'), true);
|
||
|
|
} finally {
|
||
|
|
await registry.close();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|