mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-28 14:18:21 +00:00
476 lines
23 KiB
TypeScript
476 lines
23 KiB
TypeScript
import * as assert from 'assert';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import * as http from 'http';
|
|
import { createHash } from 'crypto';
|
|
import { Worker } from 'worker_threads';
|
|
import * as zlib from 'zlib';
|
|
import { execFileSync } from 'child_process';
|
|
import { AssetStore, assetServers, defaultCacheDir } from '../../src/tools/assets';
|
|
import { EXCLUDED_PLATFORM_MODULES, UNSUPPORTED_PLATFORMS } from '../../src/tools/exclusions';
|
|
import { needsSdcc4, AssetManifest, PackInfo, isUnreviewedFile, makePack, packForFile, packsForPlatform, readPack } from '../../src/tools/assetpacks';
|
|
import { listPackFiles } from '../scripts/assetpack';
|
|
import { findRootDir } from '../src/projectinfo';
|
|
import { Rpc } from '../src/rpc';
|
|
import type { BuildOutcome } from '../src/buildcore';
|
|
import type { EmuStatus } from '../src/emuworker';
|
|
|
|
const ROOT = findRootDir(__dirname);
|
|
const OUT = path.join(__dirname, '..');
|
|
|
|
function tmpdir(): string {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), '8bws-assets-'));
|
|
}
|
|
|
|
async function pack(name: string, files: { [path: string]: string }): Promise<[PackInfo, Uint8Array]> {
|
|
var enc = new TextEncoder();
|
|
var data = await makePack(Object.keys(files).map(p => ({ path: p, data: enc.encode(files[p]) })), 5);
|
|
var sha256 = createHash('sha256').update(data).digest('hex');
|
|
return [{ file: `8bitworkshop-${name}-test-${sha256.slice(0, 8)}.tar.br`, size: data.length, sha256, count: Object.keys(files).length }, data];
|
|
}
|
|
|
|
describe('extension asset packs', function () {
|
|
|
|
it('puts the Verilog toolchain in its own pack', function () {
|
|
assert.equal(packForFile('src/worker/wasm/verilator_bin.wasm'), 'verilog');
|
|
assert.equal(packForFile('src/worker/fs/fsSilice.data'), 'verilog');
|
|
assert.equal(packForFile('presets/verilog/ball_absolute.v'), 'base');
|
|
assert.deepEqual(packsForPlatform('verilog-vga'), ['base', 'verilog']);
|
|
assert.deepEqual(packsForPlatform('nes'), ['base']);
|
|
assert.deepEqual(packsForPlatform(undefined), ['base']);
|
|
});
|
|
|
|
it('puts the lesser-used toolchains in the extra pack, fetched by tool', function () {
|
|
for (var f of ['src/worker/wasm/oscar64.wasm', 'src/worker/fs/cc7800-fs.zip']) {
|
|
assert.equal(packForFile(f), 'extra', f);
|
|
}
|
|
assert.equal(packForFile('src/worker/wasm/cmoc.wasm'), 'base');
|
|
var all = Object.values(listPackFiles(ROOT)).flat();
|
|
assert.ok(!all.some(f => /^src\/worker\/.*(dialogc|armips|inform|yasm|dialog-fs|arm-tcc|smlrc|arm32)/.test(f)), 'unused toolchains are not packed');
|
|
assert.equal(packForFile('src/worker/wasm/sdcc4.wasm'), 'extra');
|
|
assert.equal(packForFile('src/worker/fs/sdcc-fs.zip'), 'extra');
|
|
assert.deepEqual(packsForPlatform('c64', 'oscar64'), ['base', 'extra']);
|
|
assert.deepEqual(packsForPlatform('verilog', 'cc2600'), ['base', 'verilog', 'extra']);
|
|
assert.deepEqual(packsForPlatform('nes', 'cc65'), ['base', 'cc65']);
|
|
// SDCC 4.x: by directive, or the 6502 backend; 3.x stays in base
|
|
assert.deepEqual(packsForPlatform('gb', 'sdcc', 'int x;'), ['base', 'sdcc']);
|
|
assert.deepEqual(packsForPlatform('gb', 'sdcc', '//#tooldef c sdcc=4\nint x;'), ['base', 'sdcc', 'extra']);
|
|
assert.ok(needsSdcc4('coleco', 'sdcc', '//#tooldef sdcc=4'));
|
|
assert.ok(!needsSdcc4('coleco', 'sdcc', '//#tooldef c sdcc=3'));
|
|
assert.ok(!needsSdcc4('coleco', 'cc65', '//#tooldef c sdcc=4'));
|
|
assert.ok(needsSdcc4('c64', 'sdcc', 'int x;'), 'mos6502 is 4.x only');
|
|
assert.ok(!needsSdcc4('c64', 'cc65', 'int x;'));
|
|
});
|
|
|
|
it('puts cc65 and SDCC 3.x in their own packs, with the tools that hand off to them', function () {
|
|
for (var f of ['src/worker/wasm/cc65.wasm', 'src/worker/wasm/ca65.wasm', 'src/worker/wasm/ld65.wasm', 'src/worker/fs/cc65-fs-nes.zip',
|
|
'src/worker/wasm/fastbasic-int.js', 'src/worker/wasm/fastbasic-int.wasm',
|
|
'src/worker/wasm/fastbasic-fp.js', 'src/worker/wasm/fastbasic-fp.wasm']) {
|
|
assert.equal(packForFile(f), 'cc65', f);
|
|
}
|
|
for (var f of ['src/worker/wasm/sdcc.wasm', 'src/worker/wasm/sdasgb.js', 'src/worker/wasm/sdldz80.wasm', 'src/worker/fs/fssdcc.data', 'src/worker/fs/fssdcc.js.metadata']) {
|
|
assert.equal(packForFile(f), 'sdcc', f);
|
|
}
|
|
// shared by 6809 and SDCC, so it stays in base
|
|
assert.equal(packForFile('src/worker/asmjs/mcpp.js'), 'base');
|
|
// fastbasic and ECS assemble with ca65
|
|
assert.deepEqual(packsForPlatform('atari8-800', 'fastbasic'), ['base', 'cc65']);
|
|
assert.deepEqual(packsForPlatform('nes', 'ecs'), ['base', 'cc65']);
|
|
assert.deepEqual(packsForPlatform('c64', 'ca65'), ['base', 'cc65']);
|
|
assert.deepEqual(packsForPlatform('zx', 'sdasz80'), ['base', 'sdcc']);
|
|
// no build tool, just an emulator
|
|
assert.deepEqual(packsForPlatform('nes'), ['base']);
|
|
assert.deepEqual(packsForPlatform('vcs', 'dasm'), ['base']);
|
|
});
|
|
|
|
it('lists the tracked toolchain files, with symlinked directories copied', function () {
|
|
var lists = listPackFiles(ROOT);
|
|
assert.ok(lists.cc65.includes('src/worker/wasm/cc65.wasm'));
|
|
assert.ok(lists.sdcc.includes('src/worker/wasm/sdcc.wasm'));
|
|
assert.ok(lists.base.includes('presets/nes/hello.c'));
|
|
assert.ok(lists.base.some(f => f.startsWith('presets/msx-libcv/')), 'symlinked preset dir');
|
|
assert.ok(!lists.base.includes('presets/msx-libcv'));
|
|
assert.deepEqual(lists.verilog.filter(f => f.startsWith('src/')).sort(), [
|
|
'src/worker/fs/fsSilice.data', 'src/worker/fs/fsSilice.js', 'src/worker/fs/fsSilice.js.metadata',
|
|
'src/worker/wasm/silice.js', 'src/worker/wasm/silice.wasm',
|
|
'src/worker/wasm/verilator_bin.js', 'src/worker/wasm/verilator_bin.wasm',
|
|
]);
|
|
assert.ok(lists.verilog.includes('node_modules/binaryen/index.js'));
|
|
for (var f of Object.values(lists).flat()) {
|
|
assert.ok(fs.statSync(path.join(ROOT, f)).isFile(), f);
|
|
assert.ok(!f.endsWith('~'), `backup file ${f}`);
|
|
}
|
|
});
|
|
|
|
it('leaves out tools whose license is not cleared', function () {
|
|
assert.ok(isUnreviewedFile('src/worker/wasm/nesasm.wasm'));
|
|
assert.ok(isUnreviewedFile('src/worker/wasm/merlin32.js'));
|
|
assert.ok(isUnreviewedFile('src/worker/asmjs/xasm6809.js'));
|
|
assert.ok(!isUnreviewedFile('src/worker/wasm/cc65.wasm'));
|
|
var lists = listPackFiles(ROOT);
|
|
for (var f of Object.values(lists).flat()) {
|
|
assert.ok(!isUnreviewedFile(f), `unreviewed file shipped: ${f}`);
|
|
}
|
|
});
|
|
|
|
it('packs the same files to the same bytes', async function () {
|
|
var [a] = await pack('base', { 'a.txt': 'x', 'b/c.txt': 'y' });
|
|
var [b] = await pack('base', { 'a.txt': 'x', 'b/c.txt': 'y' });
|
|
assert.equal(a.sha256, b.sha256);
|
|
});
|
|
|
|
it('round-trips files through a pack', async function () {
|
|
var long = 'presets/' + 'x'.repeat(60) + '/' + 'y'.repeat(60) + '.c';
|
|
var files = [
|
|
{ path: 'src/worker/wasm/a.wasm', data: new Uint8Array([0, 1, 2, 255]) },
|
|
{ path: 'empty.txt', data: new Uint8Array(0) },
|
|
{ path: 'block.bin', data: new Uint8Array(512).fill(7) },
|
|
{ path: long, data: new TextEncoder().encode('long name') },
|
|
];
|
|
var out = await readPack(await makePack(files, 5));
|
|
assert.deepEqual(out.map(f => [f.path, Array.from(f.data)]), files.map(f => [f.path, Array.from(f.data)]));
|
|
});
|
|
|
|
it('writes tar that the tar command reads', async function () {
|
|
var dir = tmpdir();
|
|
var tar = zlib.brotliDecompressSync(await makePack([
|
|
{ path: 'a/b.txt', data: new TextEncoder().encode('hello') },
|
|
{ path: 'c.bin', data: new Uint8Array(1000).fill(1) },
|
|
], 5));
|
|
fs.writeFileSync(path.join(dir, 'p.tar'), tar);
|
|
execFileSync('tar', ['-xf', 'p.tar'], { cwd: dir });
|
|
assert.equal(fs.readFileSync(path.join(dir, 'a/b.txt'), 'utf-8'), 'hello');
|
|
assert.equal(fs.statSync(path.join(dir, 'c.bin')).size, 1000);
|
|
fs.rmSync(dir, { recursive: true });
|
|
});
|
|
});
|
|
|
|
describe('8bws toolchain cache', function () {
|
|
it('defaults to the user cache directory, per OS', function () {
|
|
assert.equal(defaultCacheDir({}, 'linux', '/home/u'), path.join('/home/u', '.cache', '8bitworkshop'));
|
|
assert.equal(defaultCacheDir({ XDG_CACHE_HOME: '/x' }, 'linux', '/home/u'), path.join('/x', '8bitworkshop'));
|
|
assert.equal(defaultCacheDir({}, 'darwin', '/Users/u'), path.join('/Users/u', 'Library', 'Caches', '8bitworkshop'));
|
|
assert.equal(defaultCacheDir({ LOCALAPPDATA: 'C:\\L' }, 'win32', 'C:\\u'), path.join('C:\\L', '8bitworkshop', 'Cache'));
|
|
});
|
|
|
|
it('lets $EIGHTBITWORKSHOP_TOOLCHAINS override it', function () {
|
|
assert.equal(defaultCacheDir({ EIGHTBITWORKSHOP_TOOLCHAINS: '/t' }, 'darwin', '/Users/u'), '/t');
|
|
});
|
|
});
|
|
|
|
describe('extension AssetStore', function () {
|
|
var server: http.Server;
|
|
var url: string;
|
|
var served: { [file: string]: Uint8Array };
|
|
var hits: string[];
|
|
var manifest: AssetManifest;
|
|
var cache: string;
|
|
|
|
before(async function () {
|
|
var [base, baseZip] = await pack('base', { 'src/worker/wasm/a.wasm': 'AAA', 'presets/nes/hello.c': 'main' });
|
|
var [verilog, verilogZip] = await pack('verilog', { 'src/worker/wasm/verilator_bin.wasm': 'VVV' });
|
|
manifest = { ideVersion: 'test', version: 'test', packs: { base, verilog } };
|
|
served = { [base.file]: baseZip, [verilog.file]: verilogZip };
|
|
server = http.createServer((req, res) => {
|
|
var name = path.posix.basename(req.url || '');
|
|
hits.push(name);
|
|
if (served[name] && req.url === '/assets/' + name) res.end(served[name]);
|
|
else { res.statusCode = 404; res.end(); }
|
|
});
|
|
await new Promise<void>(r => server.listen(0, '127.0.0.1', r));
|
|
url = `http://127.0.0.1:${(server.address() as any).port}/assets/`;
|
|
});
|
|
|
|
after(function () {
|
|
server.close();
|
|
});
|
|
|
|
beforeEach(function () {
|
|
hits = [];
|
|
cache = tmpdir();
|
|
});
|
|
|
|
afterEach(function () {
|
|
fs.rmSync(cache, { recursive: true, force: true });
|
|
});
|
|
|
|
it('downloads a pack once and unpacks it', async function () {
|
|
var store = new AssetStore(cache, manifest, [url]);
|
|
var progress: number[] = [];
|
|
var root = await store.ensure(['base'], (pack, n) => progress.push(n));
|
|
assert.equal(fs.readFileSync(path.join(root, 'src/worker/wasm/a.wasm'), 'utf-8'), 'AAA');
|
|
assert.ok(store.has('base'));
|
|
assert.ok(!store.has('verilog'));
|
|
assert.ok(progress.length > 0);
|
|
// a second store (a new window) finds it installed
|
|
var again = new AssetStore(cache, manifest, [url]);
|
|
assert.equal(await again.ensure(['base']), root);
|
|
assert.deepEqual(hits, [manifest.packs.base.file]);
|
|
});
|
|
|
|
it('adds the verilog pack to the same root', async function () {
|
|
var store = new AssetStore(cache, manifest, [url]);
|
|
var root = await store.ensure(['base']);
|
|
assert.equal(await store.ensure(['base', 'verilog']), root);
|
|
assert.ok(fs.existsSync(path.join(root, 'src/worker/wasm/a.wasm')));
|
|
assert.ok(fs.existsSync(path.join(root, 'src/worker/wasm/verilator_bin.wasm')));
|
|
});
|
|
|
|
it('shares one download between concurrent callers', async function () {
|
|
var store = new AssetStore(cache, manifest, [url]);
|
|
var [a, b] = await Promise.all([store.ensure(['base']), store.ensure(['base'])]);
|
|
assert.equal(a, b);
|
|
assert.equal(hits.length, 1);
|
|
});
|
|
|
|
it('skips a server with the wrong file and tries the next', async function () {
|
|
var bad = tmpdir();
|
|
fs.writeFileSync(path.join(bad, manifest.packs.base.file), 'not a pack');
|
|
var store = new AssetStore(cache, manifest, [bad, 'http://127.0.0.1:1/nothing/', url]);
|
|
var root = await store.ensure(['base']);
|
|
assert.ok(fs.existsSync(path.join(root, 'presets/nes/hello.c')));
|
|
fs.rmSync(bad, { recursive: true });
|
|
});
|
|
|
|
it('reads a pack from a local directory or file: URL', async function () {
|
|
var dir = tmpdir();
|
|
fs.writeFileSync(path.join(dir, manifest.packs.base.file), served[manifest.packs.base.file]);
|
|
var store = new AssetStore(cache, manifest, ['file://' + dir]);
|
|
await store.ensure(['base']);
|
|
assert.ok(store.has('base'));
|
|
assert.deepEqual(hits, []);
|
|
fs.rmSync(dir, { recursive: true });
|
|
});
|
|
|
|
it('fails when no server has a good copy, and installs nothing', async function () {
|
|
var store = new AssetStore(cache, manifest, [url + 'missing/']);
|
|
await assert.rejects(store.ensure(['base']), /Cannot download toolchain pack.*HTTP 404/);
|
|
assert.ok(!store.has('base'));
|
|
});
|
|
|
|
it('removes roots from older builds', async function () {
|
|
var old = path.join(cache, '0123456789ab');
|
|
fs.mkdirSync(old);
|
|
var store = new AssetStore(cache, manifest, [url]);
|
|
await store.ensure(['base']);
|
|
assert.ok(!fs.existsSync(old));
|
|
});
|
|
});
|
|
|
|
// Builds and runs from the real packs (npm run assets), not the repo.
|
|
describe('extension toolchains from packs', function () {
|
|
this.timeout(120000);
|
|
var manifestFile = path.join(OUT, 'assets.json');
|
|
var cache: string;
|
|
var root: string;
|
|
|
|
before(async function () {
|
|
if (!fs.existsSync(manifestFile)) return this.skip();
|
|
var manifest: AssetManifest = JSON.parse(fs.readFileSync(manifestFile, 'utf-8'));
|
|
cache = tmpdir();
|
|
var store = new AssetStore(cache, manifest, [path.join(OUT, 'assets')]);
|
|
root = await store.ensure(["base", "cc65", "verilog"]);
|
|
});
|
|
|
|
after(function () {
|
|
if (cache) fs.rmSync(cache, { recursive: true, force: true });
|
|
});
|
|
|
|
function start(script: string, handlers = {}): [Worker, Rpc] {
|
|
var worker = new Worker(path.join(OUT, script), { workerData: { rootDir: root } });
|
|
return [worker, new Rpc(worker, handlers)];
|
|
}
|
|
|
|
async function build(platform: string, dir: string, main: string): Promise<BuildOutcome> {
|
|
var [worker, rpc] = start('buildworker.js', {
|
|
readFile: (id: number, rel: string) => {
|
|
var p = path.join(root, 'presets', dir, rel);
|
|
return fs.existsSync(p) ? new Uint8Array(fs.readFileSync(p)) : null;
|
|
},
|
|
});
|
|
try {
|
|
var mainText = fs.readFileSync(path.join(root, 'presets', dir, main), 'utf-8');
|
|
return await rpc.call<BuildOutcome>('build', { buildId: 1, platform, mainPath: main, mainText });
|
|
} finally {
|
|
await worker.terminate();
|
|
}
|
|
}
|
|
|
|
it('builds NES C', async function () {
|
|
var r = await build('nes', 'nes', 'hello.c');
|
|
assert.deepEqual(r.diagnostics, []);
|
|
assert.ok(r.success && r.output);
|
|
});
|
|
|
|
it('builds Verilog and loads binaryen from the pack', async function () {
|
|
var r = await build('verilog', 'verilog', 'ball_absolute.v');
|
|
assert.deepEqual(r.diagnostics, []);
|
|
assert.ok(r.success && r.output);
|
|
// loading the platform imports binaryen, which only the pack root has.
|
|
// (Running frames is a separate, pre-existing Verilog failure.)
|
|
var [worker, rpc] = start('emuworker.js');
|
|
try {
|
|
var s = await rpc.call<EmuStatus>('start', 'verilog', r.output);
|
|
assert.equal(s.platform, 'verilog');
|
|
} finally {
|
|
await worker.terminate();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('8bws bundle with an empty cache', function () {
|
|
this.timeout(120000);
|
|
var cli = path.join(OUT, '8bws.js');
|
|
var home: string;
|
|
var work: string;
|
|
|
|
before(function () {
|
|
if (!fs.existsSync(path.join(OUT, 'assets.json')) || !fs.existsSync(cli)) return this.skip();
|
|
home = tmpdir();
|
|
work = tmpdir();
|
|
for (var f of ['presets/nes/hello.c', 'presets/verilog/ball_absolute.v', 'presets/verilog/hvsync_generator.v']) {
|
|
fs.copyFileSync(path.join(ROOT, f), path.join(work, path.basename(f)));
|
|
}
|
|
});
|
|
|
|
after(function () {
|
|
for (var d of [home, work]) if (d) fs.rmSync(d, { recursive: true, force: true });
|
|
});
|
|
|
|
function run(...args: string[]): string {
|
|
var env: NodeJS.ProcessEnv = { ...process.env, HOME: home, USERPROFILE: home, LOCALAPPDATA: home, XDG_CACHE_HOME: path.join(home, '.cache') };
|
|
delete env.EIGHTBITWORKSHOP_ROOT;
|
|
delete env.EIGHTBITWORKSHOP_TOOLCHAINS;
|
|
return execFileSync(process.execPath, [cli, ...args], { cwd: work, env, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
}
|
|
|
|
function installed(home: string): string[] {
|
|
var found: string[] = [];
|
|
(function walk(dir: string) {
|
|
for (var e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
if (e.name === '.packs') found.push(...fs.readdirSync(path.join(dir, e.name)).map(n => n.split('-')[0]));
|
|
else if (e.isDirectory() && dir.split(path.sep).length - home.split(path.sep).length < 4) walk(path.join(dir, e.name));
|
|
}
|
|
})(home);
|
|
return found.sort();
|
|
}
|
|
|
|
it('unpacks only base and cc65 for a NES build, into the user cache', function () {
|
|
run('build', '--check', 'hello.c');
|
|
assert.deepEqual(installed(home), ['base', 'cc65']);
|
|
});
|
|
|
|
it('unpacks sdcc, not cc65, for a Game Boy build', function () {
|
|
var h = fs.mkdtempSync(path.join(os.tmpdir(), '8bws-gb-'));
|
|
try {
|
|
fs.copyFileSync(path.join(ROOT, 'presets/gb/clock.c'), path.join(work, 'clock.c'));
|
|
var env: NodeJS.ProcessEnv = { ...process.env, HOME: h, USERPROFILE: h, LOCALAPPDATA: h, XDG_CACHE_HOME: path.join(h, '.cache') };
|
|
delete env.EIGHTBITWORKSHOP_ROOT;
|
|
delete env.EIGHTBITWORKSHOP_TOOLCHAINS;
|
|
execFileSync(process.execPath, [cli, 'build', '--check', '-p', 'gb', 'clock.c'], { cwd: work, env, stdio: 'pipe' });
|
|
assert.deepEqual(installed(h), ['base', 'sdcc']);
|
|
} finally {
|
|
fs.rmSync(h, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it('lists only the tools and platforms the packs can build', function () {
|
|
var tools: string[] = JSON.parse(run('list-tools', '--json')).data.tools;
|
|
assert.ok(tools.includes('cc65') && tools.includes('dasm'));
|
|
for (var t of ['dialog', 'armtcc', 'inform', 'nesasm', 'merlin32', 'xasm6809']) assert.ok(!tools.includes(t), `${t} listed`);
|
|
var platforms = Object.keys(JSON.parse(run('list-platforms', '--json')).data.platforms);
|
|
assert.ok(platforms.includes('nes'));
|
|
for (var p of UNSUPPORTED_PLATFORMS) assert.ok(!platforms.includes(p), `${p} listed`);
|
|
});
|
|
|
|
it('refuses an unsupported platform up front, and leaves the emulator out of the bundle', function () {
|
|
assert.throws(() => run('build', '--check', '-p', 'x86', 'hello.c'), /isn't in this install/);
|
|
assert.ok(UNSUPPORTED_PLATFORMS.includes('vectrex'));
|
|
for (var id of EXCLUDED_PLATFORM_MODULES) {
|
|
assert.ok(!new RegExp(`^// \\.\\./.*src/platform/${id}\\.ts$`, 'm').test(fs.readFileSync(cli, 'utf-8')), `${id} compiled into the bundle`);
|
|
}
|
|
});
|
|
|
|
it('fetches the Verilog pack when a Verilog build needs it', function () {
|
|
run('build', '--check', '-p', 'verilog', 'ball_absolute.v');
|
|
assert.deepEqual(installed(home), ['base', 'cc65', 'verilog']);
|
|
});
|
|
});
|
|
|
|
describe('asset servers', function () {
|
|
it('try the version\'s GitHub Release first, then the Pages fallbacks', function () {
|
|
var urls = assetServers({ ideVersion: '4.0.0', version: '0.1.2', packs: {} });
|
|
assert.equal(urls[0], 'https://github.com/8bitworkshop/8bitworkshop/releases/download/cli-v0.1.2/');
|
|
assert.ok(urls.length > 1 && urls.slice(1).every(u => !u.includes('/releases/')));
|
|
});
|
|
});
|
|
|
|
describe('8bws npm package', function () {
|
|
this.timeout(120000);
|
|
var dir: string;
|
|
var home: string;
|
|
var work: string;
|
|
|
|
before(function () {
|
|
if (!fs.existsSync(path.join(OUT, 'assets.json')) || !fs.existsSync(path.join(OUT, '8bws.js'))) return this.skip();
|
|
dir = path.join(tmpdir(), 'pkg');
|
|
home = tmpdir();
|
|
work = tmpdir();
|
|
execFileSync(process.execPath, [path.join(OUT, '..', 'scripts', 'stage-npm.mjs'), '--out', dir], { stdio: 'pipe' });
|
|
fs.copyFileSync(path.join(ROOT, 'presets/nes/hello.c'), path.join(work, 'hello.c'));
|
|
});
|
|
|
|
after(function () {
|
|
for (var d of [dir && path.dirname(dir), home, work]) if (d) fs.rmSync(d, { recursive: true, force: true });
|
|
});
|
|
|
|
it('has a package.json that matches the extension version, and a bin with a shebang', function () {
|
|
var pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
|
|
assert.equal(pkg.version, JSON.parse(fs.readFileSync(path.join(OUT, '..', 'package.json'), 'utf-8')).version);
|
|
assert.deepEqual(pkg.bin, { '8bws': '8bws.js' });
|
|
assert.ok(fs.readFileSync(path.join(dir, '8bws.js'), 'utf-8').startsWith('#!/usr/bin/env node\n'));
|
|
});
|
|
|
|
it('is scoped @8bws/8bws by default and takes --name', function () {
|
|
var pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
|
|
assert.equal(pkg.name, '@8bws/8bws');
|
|
var other = path.join(tmpdir(), 'named');
|
|
execFileSync(process.execPath, [path.join(OUT, '..', 'scripts', 'stage-npm.mjs'), '--out', other, '--name', '@acme/tool'], { stdio: 'pipe' });
|
|
assert.equal(JSON.parse(fs.readFileSync(path.join(other, 'package.json'), 'utf-8')).name, '@acme/tool');
|
|
assert.ok(fs.readFileSync(path.join(other, 'README.md'), 'utf-8').includes('npx @acme/tool build'));
|
|
assert.ok(fs.readFileSync(path.join(dir, 'README.md'), 'utf-8').includes('npx @8bws/8bws build'));
|
|
fs.rmSync(path.dirname(other), { recursive: true, force: true });
|
|
});
|
|
|
|
it('stages the CLI, manifest and licenses, and lists exactly those to publish', function () {
|
|
var expected = ['8bws.js', 'LICENSE', 'README.md', 'THIRD-PARTY-NOTICES.md', 'assets.json', 'package.json', 'presets.json'];
|
|
assert.deepEqual(fs.readdirSync(dir).sort(), expected);
|
|
var pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
|
|
assert.deepEqual([...pkg.files, 'package.json'].sort(), expected);
|
|
});
|
|
|
|
it('builds from an empty cache, fetching packs from $EIGHTBITWORKSHOP_ASSETS', function () {
|
|
var env: NodeJS.ProcessEnv = { ...process.env, HOME: home, USERPROFILE: home, LOCALAPPDATA: home, XDG_CACHE_HOME: path.join(home, '.cache'),
|
|
EIGHTBITWORKSHOP_ASSETS: path.join(OUT, 'assets') };
|
|
delete env.EIGHTBITWORKSHOP_ROOT;
|
|
delete env.EIGHTBITWORKSHOP_TOOLCHAINS;
|
|
var out = execFileSync(process.execPath, [path.join(dir, '8bws.js'), 'build', '--check', '--json', 'hello.c'], { cwd: work, env, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
assert.equal(JSON.parse(out).data.platform, 'nes');
|
|
});
|
|
|
|
it('lists presets and copies one that then builds', function () {
|
|
var env: NodeJS.ProcessEnv = { ...process.env, HOME: home, USERPROFILE: home, LOCALAPPDATA: home, XDG_CACHE_HOME: path.join(home, '.cache'),
|
|
EIGHTBITWORKSHOP_ASSETS: path.join(OUT, 'assets') };
|
|
delete env.EIGHTBITWORKSHOP_ROOT;
|
|
delete env.EIGHTBITWORKSHOP_TOOLCHAINS;
|
|
var cli = (cwd: string, ...args: string[]) => JSON.parse(execFileSync(process.execPath, [path.join(dir, '8bws.js'), ...args, '--json'], { cwd, env, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] })).data;
|
|
assert.ok(cli(work, 'presets').platforms.some((p: any) => p.id === 'nes'));
|
|
assert.ok(cli(work, 'presets', 'nes').templates.some((t: any) => t.id === 'aputest.c'));
|
|
var made = cli(work, 'new', 'nes/aputest.c', 'game');
|
|
assert.equal(made.main, 'aputest.c');
|
|
assert.ok(fs.existsSync(path.join(work, 'game', 'aputest.c')));
|
|
assert.equal(cli(path.join(work, 'game'), 'build', '--check', '-p', 'nes', 'aputest.c').platform, 'nes');
|
|
});
|
|
});
|