88 lines
4.1 KiB
JavaScript
Executable File
88 lines
4.1 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
// Runs the checks of the toolkit with Docker, on the engine of the docker CLI's context: builds test.Dockerfile from
|
|
// the files of the checkout git knows, then runs the tests against a scratch PostgreSQL on a private network.
|
|
|
|
import { spawnSync } from 'node:child_process';
|
|
import { resolve } from 'node:path';
|
|
import { parseArgs } from 'node:util';
|
|
|
|
const NAME = 'sk-react-toolkit-test';
|
|
const POSTGRES_IMAGE = 'postgres:18';
|
|
|
|
const usage = `Usage: test-container.mjs [-h|--help] [--package <folder>]
|
|
|
|
Builds test.Dockerfile, which runs the typecheck, the lint and the package build, and runs the
|
|
tests in a container with a scratch PostgreSQL. The build context is the files git tracks and the
|
|
untracked files it does not ignore.
|
|
|
|
--package <folder> write the packed toolkit, the .tgz file npm pack makes, to the folder instead
|
|
of running the tests`;
|
|
|
|
const { values } = parseArgs({
|
|
options: {
|
|
package: { type: 'string' },
|
|
help: { type: 'boolean', short: 'h', default: false },
|
|
},
|
|
});
|
|
|
|
if (values.help) {
|
|
console.log(usage);
|
|
process.exit(0);
|
|
}
|
|
|
|
function fail(message) {
|
|
console.error(`test-container: ${message}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
// Runs docker, failing with its message when it cannot start; answers the result.
|
|
function docker(args, options = {}) {
|
|
const result = spawnSync('docker', args, { stdio: 'inherit', ...options });
|
|
if (result.error) fail(`docker: ${result.error.message}`);
|
|
return result;
|
|
}
|
|
|
|
const files = spawnSync('git', ['ls-files', '-z', '--cached', '--others', '--exclude-standard'], { cwd: import.meta.dirname, maxBuffer: 1 << 26 });
|
|
if (files.status !== 0) fail(`git ls-files failed: ${files.stderr}`);
|
|
// macOS tar adds extended attributes and AppleDouble files, which a Linux engine refuses.
|
|
const macOptions = process.platform === 'darwin' ? ['--no-xattrs', '--no-mac-metadata'] : [];
|
|
const context = spawnSync('tar', [...macOptions, '--null', '-T', '-', '-cf', '-'], { cwd: import.meta.dirname, input: files.stdout, maxBuffer: 1 << 30 });
|
|
if (context.status !== 0) fail(`tar failed: ${context.stderr}`);
|
|
|
|
if (values.package !== undefined) {
|
|
const output = `type=local,dest=${resolve(values.package)}`;
|
|
const built = docker(['build', '-f', 'test.Dockerfile', '--target', 'package', '--output', output, '-'], { input: context.stdout, stdio: ['pipe', 'inherit', 'inherit'] });
|
|
process.exit(built.status ?? 1);
|
|
}
|
|
|
|
const built = docker(['build', '-f', 'test.Dockerfile', '--target', 'check', '-t', NAME, '-'], { input: context.stdout, stdio: ['pipe', 'inherit', 'inherit'] });
|
|
if (built.status !== 0) process.exit(built.status ?? 1);
|
|
|
|
// Ctrl-C stops the running docker command; the cleanup below still runs.
|
|
process.on('SIGINT', () => {});
|
|
|
|
const network = `${NAME}-${process.pid}`;
|
|
const database = `${NAME}-pg-${process.pid}`;
|
|
let status = 1;
|
|
try {
|
|
if (docker(['network', 'create', network], { stdio: 'ignore' }).status !== 0) throw new Error(`cannot create the network ${network}`);
|
|
const started = docker(['run', '-d', '--rm', '--name', database, '--network', network, '-e', 'POSTGRES_PASSWORD=test', POSTGRES_IMAGE], { stdio: ['ignore', 'ignore', 'inherit'] });
|
|
if (started.status !== 0) throw new Error(`cannot start ${POSTGRES_IMAGE}`);
|
|
// The server answers on TCP once its initialization has finished.
|
|
let ready = false;
|
|
for (let attempt = 0; attempt < 60 && !ready; attempt++) {
|
|
ready = docker(['exec', database, 'pg_isready', '-q', '-h', '127.0.0.1', '-U', 'postgres'], { stdio: 'ignore' }).status === 0;
|
|
if (!ready) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000);
|
|
}
|
|
if (!ready) throw new Error('PostgreSQL did not start within 60 seconds');
|
|
const environment = ['PGHOST=' + database, 'PGUSER=postgres', 'PGPASSWORD=test', 'PGDATABASE=postgres'].flatMap((variable) => ['-e', variable]);
|
|
status = docker(['run', '--rm', '--network', network, ...environment, NAME]).status ?? 1;
|
|
} catch (error) {
|
|
console.error(`test-container: ${error.message}`);
|
|
} finally {
|
|
docker(['rm', '-f', database], { stdio: 'ignore' });
|
|
docker(['network', 'rm', network], { stdio: 'ignore' });
|
|
}
|
|
process.exit(status);
|