mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 15:05:25 +00:00
vscode: bundle CLI
This commit is contained in:
1 parent
c912c65e0d
commit
65acaa91b1
16 files changed
+293
-13
No files matched your search
@@ -13,6 +13,7 @@
|
||||
!out/extension.js
|
||||
!out/buildworker.js
|
||||
!out/emuworker.js
|
||||
!out/8bws.js
|
||||
!out/presets.json
|
||||
!out/assets.json
|
||||
!out/assets/8bitworkshop-base-*.tar.br
|
||||
|
||||
@@ -17,5 +17,9 @@ First preview.
|
||||
frame, and can step back.
|
||||
- `.s`, `.asm`, `.inc` and `.a` files in a project get the highlighting
|
||||
for the platform's CPU. `.acme` files get 6502 highlighting.
|
||||
- An `8bws` command in the integrated terminal builds and runs programs
|
||||
from the command line, for scripts and AI agents.
|
||||
- New Project writes a README with build instructions and the link that
|
||||
platform detection reads.
|
||||
- The platform list shows the 8bitworkshop book for each platform that
|
||||
has one, with a button to open it.
|
||||
@@ -50,6 +50,10 @@ To look at the included examples without copying them, run **8bitworkshop: Open
|
||||
|
||||
To use code you already have, open its folder: the extension detects the appropriate platform and main file.
|
||||
|
||||
To build from the command line, or let an AI agent build and test your
|
||||
program, use the `8bws` command in VS Code's integrated terminal. See
|
||||
[Building from the terminal](docs/building.md#building-from-the-terminal).
|
||||
|
||||
## Settings
|
||||
|
||||
Project settings, saved in `.vscode/settings.json`:
|
||||
@@ -71,6 +75,7 @@ Your local user settings:
|
||||
| `8bitworkshop.exportRom` | Write each successful build's ROM to the export folder |
|
||||
| `8bitworkshop.toolchainPath` | Use a local 8bitworkshop checkout instead of the included toolchains |
|
||||
| `8bitworkshop.assetUrl` | Server or local directory to download toolchains from, tried before the default servers |
|
||||
| `8bitworkshop.terminalCommand` | Add the `8bws` command to the integrated terminal (on by default) |
|
||||
|
||||
## Commands
|
||||
|
||||
|
||||
@@ -56,6 +56,33 @@ then writes it to the `8bitworkshop.exportRomPath` folder (`bin` by
|
||||
default), next to the main file, with the platform's usual extension
|
||||
such as `.nes` or `.a26`.
|
||||
|
||||
## Building from the terminal
|
||||
|
||||
VS Code's integrated terminal has an `8bws` command that builds and runs
|
||||
programs with the same toolchains and emulators as the extension. It
|
||||
needs no Node.js install. Scripts and AI agents that run in the
|
||||
terminal, such as Claude Code, can use it to check their work:
|
||||
|
||||
```bash
|
||||
8bws build game.c -o game.nes # compile to a ROM
|
||||
8bws build --check game.c # just report errors
|
||||
8bws run game.c --frames 120 --png screen.png
|
||||
8bws run game.c -e "run 60; screen" # a run script; see 8bws help
|
||||
8bws help
|
||||
```
|
||||
|
||||
Add `--json` for machine-readable output on stdout. Without `--platform`,
|
||||
`8bws` uses [platform detection](detection.md#from-the-command-line); the
|
||||
README that **New Project** writes names the platform, so a new project
|
||||
needs no `--platform`. Library files that aren't in your folder come from
|
||||
the examples, as they do in the editor.
|
||||
|
||||
The command is a launcher script in the extension's storage folder, which
|
||||
the extension adds to `PATH` for each new terminal. Terminals opened
|
||||
before the extension started don't have it; open a new one. To turn it
|
||||
off, set `8bitworkshop.terminalCommand` to `false`. The first run in a
|
||||
while may take a few seconds while it unpacks the toolchains.
|
||||
|
||||
## Where included files come from
|
||||
|
||||
8bitworkshop looks for files your program includes or links in two
|
||||
|
||||
@@ -8,7 +8,9 @@ your choices.
|
||||
|
||||
A folder becomes an 8bitworkshop project in one of these ways:
|
||||
|
||||
- **New Project** or **Copy to Workspace** creates it.
|
||||
- **New Project** or **Copy to Workspace** creates it. It also writes a
|
||||
`README.md`, unless the folder has one, with the 8bitworkshop link
|
||||
below and how to build from the terminal.
|
||||
- You choose **Set as Main File** on a file, or run a file that has no
|
||||
project yet, and confirm its platform.
|
||||
- You accept a detected platform when you open a folder. See
|
||||
|
||||
@@ -268,6 +268,11 @@
|
||||
],
|
||||
"markdownDescription": "When a successful build restarts the running game."
|
||||
},
|
||||
"8bitworkshop.terminalCommand": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"markdownDescription": "Add the `8bws` command to the integrated terminal, to build and run programs from the command line or an AI agent. Open a new terminal after changing this."
|
||||
},
|
||||
"8bitworkshop.exportRom": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
// node scripts/build.mjs --watch # rebuild on change
|
||||
//
|
||||
// extension.js stays small; builds and emulation run in worker threads
|
||||
// (buildworker.js, emuworker.js) that start on first use. presetindex.js
|
||||
// (buildworker.js, emuworker.js) that start on first use. 8bws.js is the
|
||||
// command line tool for the integrated terminal (src/cli.ts). presetindex.js
|
||||
// writes out/presets.json and syntaxes.js writes out/syntaxes/ (npm run
|
||||
// build runs both).
|
||||
import esbuild from 'esbuild';
|
||||
@@ -51,6 +52,7 @@ const ctx = await esbuild.context({
|
||||
extension: 'src/extension.ts',
|
||||
buildworker: 'src/buildworker.ts',
|
||||
emuworker: 'src/emuworker.ts',
|
||||
'8bws': 'src/cli.ts',
|
||||
presetindex: 'scripts/presetindex.ts',
|
||||
assetpack: 'scripts/assetpack.ts',
|
||||
syntaxes: 'scripts/syntaxes.ts',
|
||||
|
||||
@@ -9,6 +9,9 @@ import { createHash } from 'crypto';
|
||||
import { AssetManifest, PackInfo, readPack } from './assetpacks';
|
||||
|
||||
/** Reports download progress: bytes so far of `total`. */
|
||||
/** The 8bitworkshop asset servers, tried in order after 8bitworkshop.assetUrl. */
|
||||
export const ASSET_URLS = ['https://sehugg.github.io/8bitworkshop/vscode/', 'https://8bitworkshop.com/vscode/'];
|
||||
|
||||
export type ProgressFn = (pack: string, received: number, total: number) => void;
|
||||
|
||||
export class AssetStore {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// cli - the 8bws command in the integrated terminal (out/8bws.js). The
|
||||
// launcher script from terminalcli.ts runs it with VS Code's own Node. It
|
||||
// unpacks the bundled toolchains if the extension hasn't yet, then runs the
|
||||
// repo's CLI (src/tools/8bws.ts) against them.
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { ASSET_URLS, AssetStore } from './assets';
|
||||
import type { AssetManifest } from './assetpacks';
|
||||
|
||||
/** The asset root: toolchainPath or a checkout, else the extension's store. */
|
||||
async function toolchainRoot(): Promise<string> {
|
||||
var cache = process.env.EIGHTBITWORKSHOP_TOOLCHAINS;
|
||||
if (!cache) throw new Error('Set EIGHTBITWORKSHOP_ROOT or EIGHTBITWORKSHOP_TOOLCHAINS; the 8bws launcher from VS Code sets them.');
|
||||
var manifest: AssetManifest = JSON.parse(fs.readFileSync(path.join(__dirname, 'assets.json'), 'utf-8'));
|
||||
var urls = [path.join(__dirname, 'assets')];
|
||||
if (process.env.EIGHTBITWORKSHOP_ASSET_URL) urls.push(process.env.EIGHTBITWORKSHOP_ASSET_URL);
|
||||
urls.push(...ASSET_URLS);
|
||||
// stderr, so --json output stays clean
|
||||
var store = new AssetStore(cache, manifest, urls, msg => process.stderr.write(msg + '\n'));
|
||||
return store.ensure(Object.keys(manifest.packs));
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (!process.env.EIGHTBITWORKSHOP_ROOT) process.env.EIGHTBITWORKSHOP_ROOT = await toolchainRoot();
|
||||
// 8bws runs as it loads, so load it only once the root is set
|
||||
await import('../../src/tools/8bws');
|
||||
}
|
||||
|
||||
main().catch(e => {
|
||||
process.stderr.write(`8bws: ${e && e.message || e}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -20,7 +20,9 @@ import { PRESET_SCHEME, PresetFileSystem, Templates } from './templates';
|
||||
import { chooseForFile, detectDirectoryAt, detectionSummary, describeDetection, describeFinding, dontAskKey, FolderFinding, platformName, scanFolder } from './detection';
|
||||
import { Detection, classifyFinding, isBuildableSource } from '../../src/common/detect';
|
||||
import { TOOL_META } from '../../src/common/toolmeta';
|
||||
import { AssetStore } from './assets';
|
||||
import { ASSET_URLS, AssetStore } from './assets';
|
||||
import { projectReadme, writeLauncher } from './terminalcli';
|
||||
import type { PlatformInfo } from './presettypes';
|
||||
import { AssetManifest, packsForPlatform } from './assetpacks';
|
||||
import { ErrorTelemetry } from './telemetry';
|
||||
|
||||
@@ -72,6 +74,10 @@ export function activate(ctx: vscode.ExtensionContext) {
|
||||
templates = new Templates(ctx.extensionPath, () => toolchainRoot());
|
||||
scheduler = new BuildScheduler(reason => autoBuild(reason));
|
||||
ctx.subscriptions.push(output, telemetry, diagnostics, status, scope);
|
||||
setupTerminalCommand();
|
||||
ctx.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => {
|
||||
if (['terminalCommand', 'toolchainPath', 'assetUrl'].some(k => e.affectsConfiguration(`${CONFIG}.${k}`))) setupTerminalCommand();
|
||||
}));
|
||||
|
||||
ctx.subscriptions.push(vscode.workspace.registerFileSystemProvider(PRESET_SCHEME, new PresetFileSystem(templates),
|
||||
{ isCaseSensitive: true, isReadonly: PresetFileSystem.readonlyMessage() }));
|
||||
@@ -160,14 +166,16 @@ function config(uri?: vscode.Uri) {
|
||||
|
||||
////// toolchains
|
||||
|
||||
// asset servers, tried in order (8bitworkshop.assetUrl goes first)
|
||||
const ASSET_URLS = ['https://sehugg.github.io/8bitworkshop/vscode/', 'https://8bitworkshop.com/vscode/'];
|
||||
|
||||
/** A local copy (toolchainPath, or the repo in development), if there is one. */
|
||||
function localRoot(): string | null {
|
||||
return config().get<string>('toolchainPath') || findRootDir(context.extensionPath);
|
||||
}
|
||||
|
||||
/** Where downloaded and unpacked toolchains live. */
|
||||
function toolchainCacheDir(): string {
|
||||
return path.join(context.globalStorageUri.fsPath, 'toolchains');
|
||||
}
|
||||
|
||||
/** Toolchain packs shipped inside the extension (see .vscodeignore). */
|
||||
function bundledAssetsDir(): string {
|
||||
return path.join(context.extensionPath, 'out', 'assets');
|
||||
@@ -203,7 +211,7 @@ function getAssets(): AssetStore {
|
||||
if (Object.keys(manifest.packs).some(p => hasBundledPack(manifest, p))) urls.push(bundledAssetsDir());
|
||||
if (custom) urls.push(custom);
|
||||
urls.push(...ASSET_URLS);
|
||||
assets = new AssetStore(path.join(context.globalStorageUri.fsPath, 'toolchains'), manifest,
|
||||
assets = new AssetStore(toolchainCacheDir(), manifest,
|
||||
urls, msg => output.appendLine(msg));
|
||||
}
|
||||
return assets;
|
||||
@@ -231,6 +239,34 @@ async function installPacks(packs: string[]): Promise<string> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Put 8bws on the integrated terminal's PATH (8bitworkshop.terminalCommand):
|
||||
* a launcher in global storage runs out/8bws.js with VS Code's Node. It is
|
||||
* rewritten on each activation, since VS Code's and the extension's paths
|
||||
* change with updates.
|
||||
*/
|
||||
function setupTerminalCommand() {
|
||||
var env = context.environmentVariableCollection;
|
||||
env.clear();
|
||||
var script = path.join(context.extensionPath, 'out', '8bws.js');
|
||||
if (!config().get<boolean>('terminalCommand') || !fs.existsSync(script)) return;
|
||||
var vars: { [name: string]: string } = {};
|
||||
var local = localRoot();
|
||||
if (local) vars.EIGHTBITWORKSHOP_ROOT = local;
|
||||
else vars.EIGHTBITWORKSHOP_TOOLCHAINS = toolchainCacheDir();
|
||||
var custom = config().get<string>('assetUrl');
|
||||
if (custom) vars.EIGHTBITWORKSHOP_ASSET_URL = custom;
|
||||
var bin = path.join(context.globalStorageUri.fsPath, 'bin');
|
||||
try {
|
||||
writeLauncher(bin, { node: process.execPath, script, env: vars });
|
||||
} catch (e) {
|
||||
output.appendLine(`Cannot write the 8bws launcher to ${bin}: ${e && e.message || e}`);
|
||||
return;
|
||||
}
|
||||
env.description = 'Adds the 8bws command, to build and run 8bitworkshop programs from the terminal.';
|
||||
env.prepend('PATH', bin + path.delimiter);
|
||||
}
|
||||
|
||||
async function prepareToolchains() {
|
||||
if (localRoot()) {
|
||||
vscode.window.showInformationMessage(`8bitworkshop: using the toolchains in ${localRoot()}.`);
|
||||
@@ -985,6 +1021,7 @@ async function createFromTemplate(platformId: string, templateId: string, replac
|
||||
}
|
||||
var main = await templates.copy(platform, t, dest.dir, withLibraries);
|
||||
if (!main) return;
|
||||
await writeReadme(dest.dir, platform, path.relative(dest.dir.fsPath, main.fsPath).split(path.sep).join('/'));
|
||||
var settings = { platform: platform.id, mainFile: main.fsPath };
|
||||
if (dest.open) {
|
||||
await writeFolderSettings(dest.dir, { platform: platform.id, mainFile: path.basename(main.fsPath) });
|
||||
@@ -996,6 +1033,19 @@ async function createFromTemplate(platformId: string, templateId: string, replac
|
||||
await openAndBuild(main.fsPath, false);
|
||||
}
|
||||
|
||||
/** A README with build instructions, unless the folder has one. */
|
||||
async function writeReadme(dir: vscode.Uri, platform: PlatformInfo, mainFile: string) {
|
||||
var readme = vscode.Uri.joinPath(dir, 'README.md');
|
||||
try {
|
||||
await vscode.workspace.fs.stat(readme);
|
||||
return;
|
||||
} catch (e) {
|
||||
// none yet
|
||||
}
|
||||
var text = projectReadme(path.basename(dir.fsPath), platform, mainFile);
|
||||
await vscode.workspace.fs.writeFile(readme, new TextEncoder().encode(text));
|
||||
}
|
||||
|
||||
async function closeEditor(uri: vscode.Uri) {
|
||||
for (var group of vscode.window.tabGroups.all) {
|
||||
for (var tab of group.tabs) {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// terminalcli - the launcher script that puts 8bws on the integrated
|
||||
// terminal's PATH. It runs out/8bws.js (cli.ts) with VS Code's own Node, so
|
||||
// users need no Node install. No vscode import, so tests can drive it.
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface LauncherOptions {
|
||||
/** VS Code's executable; ELECTRON_RUN_AS_NODE makes it plain Node */
|
||||
node: string;
|
||||
/** out/8bws.js */
|
||||
script: string;
|
||||
/** EIGHTBITWORKSHOP_* variables for cli.ts */
|
||||
env: { [name: string]: string };
|
||||
}
|
||||
|
||||
function shQuote(s: string): string {
|
||||
return `'${s.replace(/'/g, `'\\''`)}'`;
|
||||
}
|
||||
|
||||
/** The launcher files for `platform`: a shell script, plus a .cmd on Windows. */
|
||||
export function launcherFiles(opts: LauncherOptions, platform: string = process.platform): { name: string, text: string }[] {
|
||||
var env = { ELECTRON_RUN_AS_NODE: '1', ...opts.env };
|
||||
var sh = [
|
||||
'#!/bin/sh',
|
||||
'# 8bitworkshop command line tool, written by the VS Code extension.',
|
||||
...Object.entries(env).map(([k, v]) => `export ${k}=${shQuote(v)}`),
|
||||
`exec ${shQuote(opts.node)} ${shQuote(opts.script)} "$@"`,
|
||||
'',
|
||||
].join('\n');
|
||||
var files = [{ name: '8bws', text: sh }];
|
||||
if (platform === 'win32') {
|
||||
files.push({
|
||||
name: '8bws.cmd', text: [
|
||||
'@echo off',
|
||||
'rem 8bitworkshop command line tool, written by the VS Code extension.',
|
||||
'setlocal',
|
||||
...Object.entries(env).map(([k, v]) => `set "${k}=${v}"`),
|
||||
`"${opts.node}" "${opts.script}" %*`,
|
||||
'',
|
||||
].join('\r\n'),
|
||||
});
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
/** Write the launcher into `binDir`, touching only files that changed. */
|
||||
export function writeLauncher(binDir: string, opts: LauncherOptions, platform: string = process.platform) {
|
||||
fs.mkdirSync(binDir, { recursive: true });
|
||||
for (var f of launcherFiles(opts, platform)) {
|
||||
var file = path.join(binDir, f.name);
|
||||
var old = fs.existsSync(file) ? fs.readFileSync(file, 'utf-8') : null;
|
||||
if (old !== f.text) fs.writeFileSync(file, f.text);
|
||||
fs.chmodSync(file, 0o755);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* README.md for a new project. The 8bitworkshop link names the platform and
|
||||
* main file, which detection reads (parseReadmeBadge in src/common/detect.ts),
|
||||
* so `8bws build` needs no --platform.
|
||||
*/
|
||||
export function projectReadme(name: string, platform: { id: string, name: string, romext?: string }, mainFile: string): string {
|
||||
var base = path.basename(mainFile, path.extname(mainFile));
|
||||
var rom = base + (platform.romext || '.bin');
|
||||
var link = `https://8bitworkshop.com/redir.html?platform=${encodeURIComponent(platform.id)}&file=${encodeURIComponent(mainFile)}`;
|
||||
return `# ${name}
|
||||
|
||||
A ${platform.name} program made with [8bitworkshop](${link}).
|
||||
|
||||
## Build and run
|
||||
|
||||
In VS Code with the 8bitworkshop extension, open \`${mainFile}\` and click **Run**.
|
||||
|
||||
From VS Code's integrated terminal, in this folder:
|
||||
|
||||
\`\`\`sh
|
||||
# compile to a ROM
|
||||
8bws build ${mainFile} -o ${rom}
|
||||
# run without a window for 120 frames, and save a screenshot
|
||||
8bws run ${mainFile} --frames 120 --png screen.png
|
||||
# all commands and options
|
||||
8bws help
|
||||
\`\`\`
|
||||
|
||||
Add \`--json\` for machine-readable output.
|
||||
`;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import * as assert from 'assert';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { launcherFiles, projectReadme, writeLauncher } from '../src/terminalcli';
|
||||
import { parseReadmeBadge } from '../../src/common/detect';
|
||||
|
||||
describe('terminalcli', () => {
|
||||
var opts = { node: "/Apps/Code's Helper", script: '/ext/out/8bws.js', env: { EIGHTBITWORKSHOP_TOOLCHAINS: '/store/toolchains' } };
|
||||
|
||||
it('quotes paths in the shell launcher', () => {
|
||||
var files = launcherFiles(opts, 'darwin');
|
||||
assert.deepStrictEqual(files.map(f => f.name), ['8bws']);
|
||||
var sh = files[0].text;
|
||||
assert.match(sh, /^#!\/bin\/sh\n/);
|
||||
assert.ok(sh.includes(`export ELECTRON_RUN_AS_NODE='1'`));
|
||||
assert.ok(sh.includes(`export EIGHTBITWORKSHOP_TOOLCHAINS='/store/toolchains'`));
|
||||
assert.ok(sh.includes(`exec '/Apps/Code'\\''s Helper' '/ext/out/8bws.js' "$@"`));
|
||||
});
|
||||
|
||||
it('adds a .cmd launcher on Windows', () => {
|
||||
var cmd = launcherFiles(opts, 'win32').find(f => f.name === '8bws.cmd');
|
||||
assert.ok(cmd);
|
||||
assert.ok(cmd.text.includes(`"/Apps/Code's Helper" "/ext/out/8bws.js" %*`));
|
||||
assert.ok(cmd.text.includes('set "ELECTRON_RUN_AS_NODE=1"'));
|
||||
});
|
||||
|
||||
it('writes an executable launcher, rewriting it when paths change', () => {
|
||||
var dir = fs.mkdtempSync(path.join(os.tmpdir(), '8bws-launcher-'));
|
||||
try {
|
||||
writeLauncher(dir, opts, 'linux');
|
||||
var file = path.join(dir, '8bws');
|
||||
assert.ok(fs.statSync(file).mode & 0o100);
|
||||
writeLauncher(dir, { ...opts, script: '/ext2/out/8bws.js' }, 'linux');
|
||||
assert.ok(fs.readFileSync(file, 'utf-8').includes('/ext2/out/8bws.js'));
|
||||
} finally {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('writes a README that detection reads', () => {
|
||||
var readme = projectReadme('my-game', { id: 'nes', name: 'NES', romext: '.nes' }, 'game.c');
|
||||
assert.deepStrictEqual(parseReadmeBadge(readme), { platform: 'nes', mainFile: 'game.c' });
|
||||
assert.ok(readme.includes('8bws build game.c -o game.nes'));
|
||||
assert.ok(projectReadme('x', { id: 'c64', name: 'C64' }, 'a.c').includes('-o a.bin'));
|
||||
});
|
||||
});
|
||||
+2
-1
@@ -23,6 +23,7 @@ import type { BuildInfo } from './debugservice';
|
||||
import { parseSymbolFile } from '../common/symbols/symbolfile';
|
||||
import { romBytes, type CompileResult } from './testlib';
|
||||
import { ROM_PLATFORMS } from '../common/detect';
|
||||
import { toolRoot } from './toolroot';
|
||||
|
||||
interface Args {
|
||||
[key: string]: string | true | string[];
|
||||
@@ -349,7 +350,7 @@ async function writeScreenshot(video: ReturnType<EmuTarget['getVideo']>, pngFile
|
||||
// platform detection
|
||||
|
||||
function presetsDir(): string | null {
|
||||
for (const dir of [path.resolve('presets'), path.resolve(__dirname, '../../presets')]) {
|
||||
for (const dir of [path.resolve(toolRoot(), 'presets'), path.resolve(__dirname, '../../presets')]) {
|
||||
if (fs.existsSync(dir)) return dir;
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { EmuCore } from "../common/emucore";
|
||||
import { PLATFORMS } from "../common/emu";
|
||||
import { getRootBasePlatform } from "../common/util";
|
||||
import { importPlatform } from "../platform/_index";
|
||||
import { toolRoot } from "./toolroot";
|
||||
import { mockAudio, mockDOM, mockFetch, mockGlobals, mockScripts, resetScripts } from "./nodemock";
|
||||
|
||||
export { EmuCore as EmuTarget, DEFAULT_MAX_FRAMES } from "../common/emucore";
|
||||
@@ -14,7 +15,7 @@ type EmuTarget = EmuCore;
|
||||
|
||||
/** Load a platform module by ID (e.g. "nes", "c64.wasm", "atari8-5200"). */
|
||||
export async function loadPlatform(platformId: string): Promise<EmuTarget> {
|
||||
installNodeMocks();
|
||||
installNodeMocks(toolRoot());
|
||||
// a headless host may load more than one platform (or the same one twice)
|
||||
// in a process; scripts kept global state, so evaluate them fresh each time
|
||||
resetScripts();
|
||||
|
||||
@@ -12,6 +12,7 @@ import { FileProvider, buildWorkerMessage, resolveDependencies } from "../common
|
||||
import { setupNodeEnvironment, handleMessage, store } from "../worker/workerlib";
|
||||
import { PLATFORM_PARAMS } from "../worker/platforms";
|
||||
import { TOOLS } from "../worker/workertools";
|
||||
import { toolRoot } from "./toolroot";
|
||||
|
||||
export { store, PLATFORM_PARAMS, TOOLS };
|
||||
|
||||
@@ -52,7 +53,7 @@ let initialized = false;
|
||||
*/
|
||||
export async function initialize(): Promise<void> {
|
||||
if (initialized) return;
|
||||
setupNodeEnvironment();
|
||||
setupNodeEnvironment(toolRoot());
|
||||
// The worker tools are already registered through the import chain:
|
||||
// workerlib -> workertools -> tools/* and builder -> TOOLS
|
||||
// No need to load the esbuild bundle.
|
||||
@@ -110,7 +111,7 @@ export async function compile(options: CompileOptions): Promise<CompileResult> {
|
||||
export async function compileFile(tool: string, platform: string, presetPath: string): Promise<CompileResult> {
|
||||
await initialize();
|
||||
|
||||
var code = fs.readFileSync('presets/' + platform + '/' + presetPath, 'utf-8');
|
||||
var code = fs.readFileSync(path.join(toolRoot(), 'presets', platform, presetPath), 'utf-8');
|
||||
return compile({
|
||||
tool: tool,
|
||||
platform: platform,
|
||||
@@ -121,14 +122,14 @@ export async function compileFile(tool: string, platform: string, presetPath: st
|
||||
|
||||
/**
|
||||
* Reads files for a build from the source file's directory, then
|
||||
* presets/<base platform>, then the current directory.
|
||||
* presets/<base platform> under the tool root, then the current directory.
|
||||
*/
|
||||
export class NodeFileProvider implements FileProvider {
|
||||
constructor(readonly sourceDir: string, readonly platform: string) { }
|
||||
async readFile(filePath: string): Promise<FileData | null> {
|
||||
var searchPaths = [];
|
||||
if (this.sourceDir) searchPaths.push(path.resolve(this.sourceDir, filePath));
|
||||
searchPaths.push(path.resolve('presets', getBasePlatform(this.platform), filePath));
|
||||
searchPaths.push(path.resolve(toolRoot(), 'presets', getBasePlatform(this.platform), filePath));
|
||||
searchPaths.push(path.resolve(filePath));
|
||||
for (var p of searchPaths) {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// toolroot - where the CLI finds toolchains, presets and BIOS images: a
|
||||
// directory laid out like the repo (src/worker/..., presets/, res/).
|
||||
|
||||
/**
|
||||
* $EIGHTBITWORKSHOP_ROOT when set (the VS Code extension's launcher points it
|
||||
* at the extension's toolchains), else the current directory (a checkout).
|
||||
*/
|
||||
export function toolRoot(): string {
|
||||
return process.env.EIGHTBITWORKSHOP_ROOT || process.cwd();
|
||||
}
|
||||
Reference in new issue
Block a user