feedback5 R6: add a player package tool with a lite dry-run
tools/package.mjs packs git-tracked runtime files and production dependencies. --lite leaves out local art, fonts and the local asset manifest. --dry-run lists the files and the uncompressed sizes and does not write a zip.
This commit is contained in:
1 parent
014259b75f
commit
b6c9918c46
3 files changed
+501
-1
No files matched your search
@@ -0,0 +1,155 @@
|
||||
// Player-package file selection. Dry-run only: no zip, no npm ci.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import {
|
||||
extraPaths,
|
||||
isExcluded,
|
||||
packageOutIsUnsafe,
|
||||
selectTracked,
|
||||
textHasHomePath,
|
||||
trackedFiles,
|
||||
} from '../tools/package.mjs';
|
||||
|
||||
const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
|
||||
const SAMPLE = [
|
||||
'server/index.js',
|
||||
'server/sim/rng.js',
|
||||
'shared/constants.js',
|
||||
'public/index.html',
|
||||
'public/js/main.js',
|
||||
'public/vendor/preact.mjs',
|
||||
'public/dev/game-mock.html',
|
||||
'public/assets/board.png',
|
||||
'public/fonts/fonts.css',
|
||||
'data/chess.json',
|
||||
'data/local-assets.json',
|
||||
'tools/setup.mjs',
|
||||
'tools/vendor.mjs',
|
||||
'tools/fetch-assets.mjs',
|
||||
'tools/assets/plan.mjs',
|
||||
'tools/local-extract/extract.py',
|
||||
'tools/golden.mjs',
|
||||
'tools/check-imports.mjs',
|
||||
'scripts/start.sh',
|
||||
'docs/PLAYING.md',
|
||||
'docs/DEPLOY.md',
|
||||
'docs/WINDOWS.md',
|
||||
'docs/ASSETS.md',
|
||||
'docs/img/combat.jpg',
|
||||
'docs/DESIGN.md',
|
||||
'docs/SIM.md',
|
||||
'docs/research/03-operators.json',
|
||||
'docs/research/10-networking-hosting.md',
|
||||
'test/version.test.js',
|
||||
'package.json',
|
||||
'package-lock.json',
|
||||
'LICENSE',
|
||||
'NOTICE.md',
|
||||
'THIRD-PARTY-NOTICES.md',
|
||||
'README.md',
|
||||
'CHANGELOG.md',
|
||||
'Dockerfile',
|
||||
'.dockerignore',
|
||||
'AGENTS.md',
|
||||
'.env',
|
||||
'.env.local',
|
||||
'server/.env.production',
|
||||
'pv/clip.mp4',
|
||||
'3,9,11回合情况/note.txt',
|
||||
'handoff/HANDOFF.md',
|
||||
'node_modules/ws/index.js',
|
||||
'.cache/x',
|
||||
'eslint.config.js',
|
||||
'jsconfig.json',
|
||||
'types/core.js',
|
||||
];
|
||||
|
||||
test('selectTracked keeps runtime files and drops the rest', () => {
|
||||
const got = selectTracked(SAMPLE);
|
||||
assert.deepEqual(got, selectTracked(SAMPLE, { lite: true }));
|
||||
for (const rel of [
|
||||
'server/index.js', 'tools/setup.mjs', 'tools/vendor.mjs', 'docs/PLAYING.md',
|
||||
'docs/DEPLOY.md', 'public/index.html', 'data/chess.json', 'package.json',
|
||||
'Dockerfile', 'scripts/start.sh', 'tools/assets/plan.mjs', 'tools/local-extract/extract.py',
|
||||
]) assert.ok(got.includes(rel), rel);
|
||||
for (const rel of [
|
||||
'tools/golden.mjs', 'tools/check-imports.mjs', 'test/version.test.js', 'public/dev/game-mock.html',
|
||||
'public/assets/board.png', 'public/fonts/fonts.css', 'docs/research/10-networking-hosting.md',
|
||||
'docs/research/03-operators.json', 'docs/DESIGN.md', 'docs/SIM.md', 'AGENTS.md', '.env',
|
||||
'.env.local', 'server/.env.production', 'pv/clip.mp4', '3,9,11回合情况/note.txt',
|
||||
'handoff/HANDOFF.md', 'node_modules/ws/index.js', 'data/local-assets.json', 'eslint.config.js',
|
||||
'types/core.js',
|
||||
]) assert.ok(!got.includes(rel), rel);
|
||||
assert.equal(isExcluded('docs/research/10-networking-hosting.md'), true);
|
||||
});
|
||||
|
||||
test('lite extras skip art, fonts and the local manifest', () => {
|
||||
assert.deepEqual(extraPaths({ lite: true, hasLocalManifest: true }), ['public/vendor']);
|
||||
assert.deepEqual(extraPaths({ lite: false, hasLocalManifest: true }), [
|
||||
'public/vendor', 'public/assets', 'public/fonts', 'data/local-assets.json',
|
||||
]);
|
||||
assert.deepEqual(extraPaths({ lite: false, hasLocalManifest: false }), [
|
||||
'public/vendor', 'public/assets', 'public/fonts',
|
||||
]);
|
||||
});
|
||||
|
||||
test('home-directory text and unsafe output directories', () => {
|
||||
assert.equal(textHasHomePath('see /Users/example/game'), 'mac-users');
|
||||
assert.equal(textHasHomePath('C:\\Users\\example\\game'), 'win-users');
|
||||
assert.equal(textHasHomePath('prefix /home/example/game'), 'posix-home');
|
||||
assert.equal(textHasHomePath('https://github.com/example/game'), null);
|
||||
assert.equal(packageOutIsUnsafe(ROOT, ROOT), true);
|
||||
assert.equal(packageOutIsUnsafe(path.dirname(ROOT), ROOT), true);
|
||||
assert.equal(packageOutIsUnsafe(path.join(ROOT, 'dist-pack'), ROOT), false);
|
||||
});
|
||||
|
||||
test('real git ls-files: runtime in, dev and private out', () => {
|
||||
const got = new Set(selectTracked(trackedFiles(ROOT)));
|
||||
assert.ok(got.has('server/index.js'));
|
||||
assert.ok(got.has('tools/setup.mjs'));
|
||||
assert.ok(got.has('docs/PLAYING.md'));
|
||||
assert.ok(got.has('package-lock.json'));
|
||||
assert.equal(got.has('tools/golden.mjs'), false);
|
||||
assert.equal(got.has('tools/package.mjs'), false);
|
||||
assert.equal(got.has('test/golden.test.js'), false);
|
||||
assert.equal(got.has('docs/DESIGN.md'), false);
|
||||
assert.equal(got.has('eslint.config.js'), false);
|
||||
for (const rel of got) {
|
||||
assert.equal(rel.startsWith('docs/research/'), false, rel);
|
||||
assert.equal(rel.startsWith('test/'), false, rel);
|
||||
assert.equal(rel.startsWith('public/dev/'), false, rel);
|
||||
assert.equal(rel.startsWith('handoff/'), false, rel);
|
||||
}
|
||||
});
|
||||
|
||||
test('dry-run lists the lite set and prints both size estimates', () => {
|
||||
const lite = spawnSync(process.execPath, ['tools/package.mjs', '--dry-run', '--lite'], {
|
||||
cwd: ROOT, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024,
|
||||
});
|
||||
assert.equal(lite.status, 0, lite.stderr);
|
||||
assert.match(lite.stdout, /^mode: lite/m);
|
||||
assert.match(lite.stdout, /^dry-run: yes/m);
|
||||
assert.match(lite.stdout, /^file server\/index\.js$/m);
|
||||
assert.match(lite.stdout, /^file tools\/setup\.mjs$/m);
|
||||
assert.match(lite.stdout, /^file docs\/PLAYING\.md$/m);
|
||||
assert.match(lite.stdout, /^extra public\/vendor$/m);
|
||||
assert.doesNotMatch(lite.stdout, /^file tools\/golden\.mjs$/m);
|
||||
assert.doesNotMatch(lite.stdout, /^file test\//m);
|
||||
assert.doesNotMatch(lite.stdout, /^extra public\/assets$/m);
|
||||
assert.match(lite.stdout, /^uncompressed lite: \d+/m);
|
||||
assert.match(lite.stdout, /^uncompressed full estimate: \d+/m);
|
||||
assert.match(lite.stdout, /puppeteer-core/);
|
||||
const full = spawnSync(process.execPath, ['tools/package.mjs', '--dry-run'], {
|
||||
cwd: ROOT, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024,
|
||||
});
|
||||
assert.equal(full.status, 0, full.stderr);
|
||||
assert.match(full.stdout, /^mode: full/m);
|
||||
assert.match(full.stdout, /^extra public\/assets$/m);
|
||||
assert.match(full.stdout, /^extra public\/fonts$/m);
|
||||
const assets = Number((full.stdout.match(/^assets bytes: (\d+)/m) || [])[1]);
|
||||
assert.ok(assets > 300 * 1048576, `assets bytes ${assets}`);
|
||||
});
|
||||
Reference in new issue
Block a user