first cycle lab release

This commit is contained in:
2026-10-04 00:30:09 +07:00
parent 85f6897365
commit d134daafd8
92 changed files with 5623 additions and 9 deletions
+165
View File
@@ -0,0 +1,165 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import crypto from 'node:crypto';
import { execFileSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
import { parse, stringify } from 'smol-toml';
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
import { build } from '../platform/build.mjs';
import {fixture,commit} from './helpers/build-fixture.mjs';
const repository=fileURLToPath(new URL('../',import.meta.url));
const sha=file=>crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
const git=(root,args)=>execFileSync('git',args,{cwd:root,stdio:'pipe'});
function refresh(root) {
const directory = path.join(root, 'pack');
const pack = parse(fs.readFileSync(path.join(directory, 'pack.toml'), 'utf8'));
const index = parse(fs.readFileSync(path.join(directory, pack.index.file), 'utf8'));
for (const entry of index.files) entry.hash = sha(path.join(directory, entry.file));
fs.writeFileSync(path.join(directory, pack.index.file), stringify(index));
pack.index.hash = sha(path.join(directory, pack.index.file));
fs.writeFileSync(path.join(directory, 'pack.toml'), stringify(pack));
}
const perform = (root, options = {}) => build({ root, log: () => {}, ...options });
const leftovers = root => fs.existsSync(path.join(root, 'dist')) ? fs.readdirSync(path.join(root, 'dist')).filter(file => file.startsWith('.build-')) : [];
test('full clean release has portable Compose, complete checksums, ownership and no secrets/target', t => {
const root = fixture(t);
const configFile = path.join(root, 'platform/config.yaml');
const config = parseYaml(fs.readFileSync(configFile, 'utf8'));
config.server.memory = '3G'; config.server.port = 25570;
config.server.host = 'private-deployment-host'; config.server.directory = '/private/remote/path';
config.build.output_directory = './releases';
fs.writeFileSync(configFile, stringifyYaml(config));
fs.writeFileSync(path.join(root, 'server/.env'), 'PRIVATE_TOKEN=secret-local-value\nMC_EULA=TRUE\n');
commit(root);
const release = perform(root, { releaseOnly: true });
assert.equal(path.basename(path.dirname(release)), 'releases');
const manifest = JSON.parse(fs.readFileSync(path.join(release, 'manifest.json')));
assert.equal(manifest.git.dirty, false);
assert.equal(manifest.git.commit, git(root, ['rev-parse', 'HEAD']).toString().trim());
assert.equal(manifest.server.memory, '3G');
assert.equal(manifest.server.port, 25570);
assert(manifest.managed_files.client.some(file => /MouseTweaks/.test(file.path)));
assert(!manifest.managed_files.server.some(file => /MouseTweaks/.test(file.path)));
const serverMarker = manifest.managed_files.server.find(file => file.path === 'config/spike-marker.txt');
assert.equal(serverMarker.owner, 'server');
assert.equal(serverMarker.overrides, 'pack');
assert.equal(serverMarker.hashes.sha256, sha(path.join(root, 'server/config/spike-marker.txt')));
for (const file of manifest.release_files) assert.equal(file.sha256, sha(path.join(release, file.path)));
const checksumLines = fs.readFileSync(path.join(release, 'SHA256SUMS'), 'utf8').trim().split('\n');
assert.equal(checksumLines.length, manifest.release_files.length + 1);
assert(checksumLines.some(line => line.endsWith(' manifest.json')));
execFileSync('sha256sum', ['-c', 'SHA256SUMS'], { cwd: release });
const composeText = fs.readFileSync(path.join(release, 'server/compose.yaml'), 'utf8');
const compose = parseYaml(composeText).services.minecraft;
assert.equal(compose.image, manifest.server.image);
assert.equal(compose.environment.MEMORY, '${MC_MEMORY:-3G}');
assert.equal(compose.ports[0], '${MC_BIND_ADDRESS:-127.0.0.1}:${MC_PORT:-25570}:25565');
assert.equal(compose.environment.MODRINTH_MODPACK, `/modpacks/${manifest.artifact}`);
assert(compose.volumes.every(volume => volume.source.startsWith('${MC_')));
for (const file of ['manifest.json', 'server/compose.yaml', 'server/.env.example']) {
const text = fs.readFileSync(path.join(release, file), 'utf8');
for (const secret of ['secret-local-value', 'private-deployment-host', '/private/remote/path', root]) assert(!text.includes(secret));
}
assert.match(fs.readFileSync(path.join(release, 'server/.env.example'), 'utf8'), /MC_EULA=FALSE/);
assert.deepEqual(leftovers(root), []);
});
test('development build records dirty state and leaves source unchanged', t => {
const root = fixture(t);
fs.writeFileSync(path.join(root, 'local-note.txt'), 'dirty\n');
const before = sha(path.join(root, 'pack/pack.toml'));
const release = perform(root);
assert.equal(JSON.parse(fs.readFileSync(path.join(release, 'manifest.json'))).git.dirty, true);
assert.equal(sha(path.join(root, 'pack/pack.toml')), before);
});
test('existing immutable release is never overwritten', t => {
const root = fixture(t), release = perform(root);
const before = sha(path.join(release, 'SHA256SUMS'));
assert.throws(() => perform(root), /already exists/);
assert.equal(sha(path.join(release, 'SHA256SUMS')), before);
});
test('stale metadata is rejected before export', t => {
const root = fixture(t);
fs.appendFileSync(path.join(root, 'pack/config/spike-marker.txt'), 'stale\n');
assert.throws(() => perform(root), /stale indexed file/);
});
test('refresh changes in temporary copy fail safely without changing source', t => {
const root = fixture(t), index = path.join(root, 'pack/index.toml'), before = sha(index);
fs.writeFileSync(path.join(root, '.tools/state.json'), '{"stale":true}');
assert.throws(() => perform(root), /Stale source/);
assert.equal(sha(index), before);
assert.deepEqual(fs.readdirSync(path.join(root, 'dist')), ['.gitkeep']);
});
test('failed export leaves no partial release or build lock', t => {
const root = fixture(t);
fs.writeFileSync(path.join(root, '.tools/state.json'), '{"exportFailure":true}');
assert.throws(() => perform(root), /Simulated export failure/);
assert.deepEqual(fs.readdirSync(path.join(root, 'dist')), ['.gitkeep']);
});
test('unverified loader/image combination is rejected', t => {
const root = fixture(t), file = path.join(root, 'pack/pack.toml');
const pack = parse(fs.readFileSync(file, 'utf8'));
pack.versions.neoforge = '21.1.251';
fs.writeFileSync(file, stringify(pack));
assert.throws(() => perform(root), /not been verified/);
});
test('duplicate mod definitions fail despite a consistent index', t => {
const root = fixture(t), directory = path.join(root, 'pack');
const indexFile = path.join(directory, 'index.toml'), index = parse(fs.readFileSync(indexFile, 'utf8'));
const entry = index.files.find(file => file.file.includes('mouse-tweaks'));
fs.copyFileSync(path.join(directory, entry.file), path.join(directory, 'mods/duplicate.pw.toml'));
index.files.push({ ...entry, file: 'mods/duplicate.pw.toml' });
fs.writeFileSync(indexFile, stringify(index));
refresh(root);
assert.throws(() => perform(root), /Duplicate mod target/);
});
test('invalid/duplicate config keys and protected output paths are rejected', t => {
const root = fixture(t), file = path.join(root, 'platform/config.yaml');
fs.appendFileSync(file, '\nschema_version: 1\n');
assert.throws(() => perform(root), /unique/);
fs.copyFileSync(path.join(repository, 'platform/config.yaml'), file);
const config = parseYaml(fs.readFileSync(file, 'utf8'));
config.build.output_directory = './pack/output';
fs.writeFileSync(file, stringifyYaml(config));
assert.throws(() => perform(root), /overlaps/);
config.build.output_directory = '../outside';
fs.writeFileSync(file, stringifyYaml(config));
assert.throws(() => perform(root), /inside repository/);
});
test('release mode rejects dirty worktree', t => {
const root = fixture(t);
fs.writeFileSync(path.join(root, 'untracked.txt'), 'dirty');
assert.throws(() => perform(root, { releaseOnly: true }), /clean Git worktree/);
});
test('tracked known secrets are rejected', t => {
const root = fixture(t);
fs.writeFileSync(path.join(root, 'server/.env'), 'PRIVATE_TOKEN=x');
git(root, ['add', '-f', 'server/.env']);
commit(root);
assert.throws(() => perform(root), /secret path is tracked/);
});
test('tampered tool and source symlinks fail validation', t => {
const root = fixture(t);
const marker = path.join(root, 'pack/config/spike-marker.txt');
fs.unlinkSync(marker); fs.symlinkSync('/tmp/nonexistent-mcpack-marker', marker);
assert.throws(() => perform(root), /regular files/);
fs.unlinkSync(marker); fs.copyFileSync(path.join(repository, 'pack/config/spike-marker.txt'), marker);
fs.appendFileSync(path.join(root, '.tools/packwiz/packwiz'), '\n// tampered\n');
assert.throws(() => perform(root), /checksum mismatch/);
});