Template tests / tests (pull_request) Failing after 33s
Phase 3 of the improvement plan (PR 9 of the sequence): the dnf/Fedora packaging half, in separate Linux-specific files, mirroring the apt/.deb work. - packaging/linux/common/stage-runtime.sh: shared runtime-only payload staging extracted from the .deb builder so the two package formats never drift. It copies app code + a fixed Electron runtime + production npm deps (npm ls --omit=dev), guards against dev-dep leaks, and fails without node_modules. The .deb builder now delegates to it. - packaging/linux/fedora/stepforge.spec: runtime Requires (nss, nspr, gtk3, …), Recommends (xinput, portal, pipewire), %license, and a %post that makes chrome-sandbox setuid and refreshes desktop/MIME/icon caches. No compilation — it packages the prebuilt BuildRoot. - packaging/linux/fedora/package.sh: stages the shared payload, substitutes version/maintainer into the spec, and runs rpmbuild against the prebuilt BuildRoot with detected arch. Requires rpmbuild + node_modules; emits an .rpm + sha256. - scripts/linux/dnf/install-runtime-deps.sh and install-build-deps.sh: separate runtime vs build dependency sets for dnf (runtime installs no build tools). - docs/linux/dnf.md: Fedora/RHEL install + build guide, distinct from apt.md. Tests: packaging-linux.test.js gains Fedora/dnf structural checks (spec Requires + MPL-2.0 + %license + sandbox setup, builder shares staging + requires rpmbuild, dnf build/runtime dep separation, shared staging never copies the whole dev tree). tests/integration/linux/package-rpm.test.sh builds a real .rpm and asserts runtime-only contents (honest skip when rpmbuild is absent, as on this apt host). Verified: 13 packaging unit tests pass; the refactored .deb builder still produces a valid runtime-only package (integration test green); the .rpm integration test skips cleanly where rpmbuild is unavailable. Co-Authored-By: Claude Fable 5 <[email protected]>
145 lines
6.2 KiB
JavaScript
145 lines
6.2 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const ROOT = path.resolve(__dirname, '..', '..');
|
|
const read = (rel) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
|
|
const exists = (rel) => fs.existsSync(path.join(ROOT, rel));
|
|
|
|
// These are structural checks that run in the normal (cross-platform) unit
|
|
// suite so the Linux packaging config can't rot silently; the actual .deb
|
|
// build/inspection lives in tests/integration/linux/package-deb.test.sh.
|
|
|
|
test('Linux packaging files exist in their expected separate locations', () => {
|
|
for (const f of [
|
|
'packaging/linux/debian/package.sh',
|
|
'packaging/linux/debian/control.in',
|
|
'packaging/linux/common/stepforge.desktop',
|
|
'packaging/linux/common/stepforge-mime.xml',
|
|
'packaging/linux/common/launcher.sh',
|
|
'scripts/linux/apt/install-runtime-deps.sh',
|
|
'scripts/linux/apt/install-build-deps.sh',
|
|
'docs/linux/apt.md',
|
|
'tests/integration/linux/package-deb.test.sh',
|
|
]) {
|
|
assert.ok(exists(f), `expected ${f} to exist`);
|
|
}
|
|
});
|
|
|
|
test('the old non-production package-linux.sh is gone', () => {
|
|
assert.equal(exists('scripts/package-linux.sh'), false);
|
|
});
|
|
|
|
test('Fedora/dnf packaging files exist in their own separate locations', () => {
|
|
for (const f of [
|
|
'packaging/linux/fedora/package.sh',
|
|
'packaging/linux/fedora/stepforge.spec',
|
|
'packaging/linux/common/stage-runtime.sh',
|
|
'scripts/linux/dnf/install-runtime-deps.sh',
|
|
'scripts/linux/dnf/install-build-deps.sh',
|
|
'docs/linux/dnf.md',
|
|
'tests/integration/linux/package-rpm.test.sh',
|
|
]) {
|
|
assert.ok(exists(f), `expected ${f} to exist`);
|
|
}
|
|
});
|
|
|
|
test('the RPM spec declares runtime Requires, license, and MPL-2.0', () => {
|
|
const spec = read('packaging/linux/fedora/stepforge.spec');
|
|
assert.match(spec, /^License:\s+MPL-2\.0$/m);
|
|
assert.match(spec, /^Requires:\s+nss$/m);
|
|
assert.match(spec, /%license/);
|
|
assert.match(spec, /chrome-sandbox/); // %post makes the sandbox helper setuid
|
|
assert.match(spec, /@VERSION@/);
|
|
assert.doesNotMatch(spec, /fully offline/i);
|
|
});
|
|
|
|
test('the rpm builder shares staging and requires rpmbuild + node_modules', () => {
|
|
const script = read('packaging/linux/fedora/package.sh');
|
|
assert.match(script, /stage-runtime\.sh/);
|
|
assert.match(script, /rpmbuild/);
|
|
assert.match(script, /rpm --eval '%\{_arch\}'|uname -m/); // arch detected
|
|
assert.doesNotMatch(script, /cp -a "\$ROOT_DIR\/node_modules" /);
|
|
});
|
|
|
|
test('dnf setup scripts target dnf and keep build vs runtime deps separate', () => {
|
|
const runtime = read('scripts/linux/dnf/install-runtime-deps.sh');
|
|
const build = read('scripts/linux/dnf/install-build-deps.sh');
|
|
assert.match(runtime, /dnf install/);
|
|
assert.match(runtime, /nss/);
|
|
assert.doesNotMatch(runtime, /rpm-build|rpmdevtools/, 'runtime script must not install build tools');
|
|
assert.match(build, /rpm-build/);
|
|
});
|
|
|
|
test('the shared staging never copies the whole dev node_modules', () => {
|
|
const staging = read('packaging/linux/common/stage-runtime.sh');
|
|
assert.match(staging, /npm ls --omit=dev/);
|
|
assert.match(staging, /electron-builder/); // leak guard
|
|
assert.doesNotMatch(staging, /cp -a "\$ROOT_DIR\/node_modules" "\$APP_DIR/);
|
|
assert.match(staging, /node_modules\/electron\/dist/); // requires the runtime
|
|
});
|
|
|
|
test('the desktop entry is valid and app-branded', () => {
|
|
const desktop = read('packaging/linux/common/stepforge.desktop');
|
|
assert.match(desktop, /^\[Desktop Entry\]/);
|
|
assert.match(desktop, /^Type=Application$/m);
|
|
assert.match(desktop, /^Exec=stepforge %U$/m);
|
|
assert.match(desktop, /^Icon=stepforge$/m);
|
|
assert.match(desktop, /^Categories=.+;$/m);
|
|
assert.match(desktop, /MimeType=application\/x-stepforge-guide/);
|
|
});
|
|
|
|
test('the control template declares runtime deps, no hardcoded arch, real maintainer slot', () => {
|
|
const control = read('packaging/linux/debian/control.in');
|
|
assert.match(control, /^Architecture: @ARCH@$/m, 'arch must be templated, not hardcoded');
|
|
assert.match(control, /^Depends:.*libnss3/m);
|
|
assert.match(control, /@VERSION@/);
|
|
assert.match(control, /@MAINTAINER@/);
|
|
// The false "fully offline" wording must not reappear here.
|
|
assert.doesNotMatch(control, /fully offline/i);
|
|
});
|
|
|
|
test('the launcher refuses an unsandboxed launch unless explicitly opted in', () => {
|
|
const launcher = read('packaging/linux/common/launcher.sh');
|
|
assert.match(launcher, /STEPFORGE_ALLOW_NO_SANDBOX/);
|
|
// It must not unconditionally exec with --no-sandbox.
|
|
assert.doesNotMatch(launcher, /^exec .*--no-sandbox/m);
|
|
// It never installs anything at runtime.
|
|
assert.doesNotMatch(launcher, /npm (install|ci|rebuild)/);
|
|
});
|
|
|
|
test('the deb builder detects arch and delegates to shared staging', () => {
|
|
const script = read('packaging/linux/debian/package.sh');
|
|
assert.match(script, /stage-runtime\.sh/); // runtime-only staging is shared
|
|
assert.match(script, /dpkg --print-architecture/); // arch detected, not hardcoded
|
|
assert.doesNotMatch(script, /cp -a "\$ROOT_DIR\/node_modules" /); // never copy the whole dev tree
|
|
});
|
|
|
|
test('apt setup scripts target apt and keep build vs runtime deps separate', () => {
|
|
const runtime = read('scripts/linux/apt/install-runtime-deps.sh');
|
|
const build = read('scripts/linux/apt/install-build-deps.sh');
|
|
assert.match(runtime, /apt-get/);
|
|
assert.match(runtime, /libnss3/);
|
|
assert.doesNotMatch(runtime, /dpkg-dev|fakeroot/, 'runtime script must not install build tools');
|
|
assert.match(build, /dpkg-dev/);
|
|
assert.match(build, /fakeroot/);
|
|
});
|
|
|
|
test('an original icon set is generated (not a placeholder/third-party asset)', () => {
|
|
assert.ok(exists('packaging/assets/stepforge.svg'));
|
|
assert.ok(exists('scripts/make-icons.js'));
|
|
// The generated PNGs are committed for packaging.
|
|
for (const size of [16, 48, 256]) {
|
|
assert.ok(exists(`packaging/assets/icons/stepforge-${size}.png`), `icon ${size} missing`);
|
|
}
|
|
// Regenerate the 256px icon and confirm the generator is deterministic and
|
|
// produces a valid PNG (starts with the PNG signature).
|
|
const { renderIcon } = require('../../scripts/make-icons');
|
|
const { encodePng } = require('../../core/png');
|
|
const png = encodePng(renderIcon(256));
|
|
assert.deepEqual([...png.subarray(0, 4)], [0x89, 0x50, 0x4e, 0x47]);
|
|
});
|