diff --git a/README.md b/README.md index 28e9ad0..60e36a5 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,10 @@ bun run build # bundles src/ -> dist/index.cjs (committed; CI verifies it is `dist/index.cjs` is committed because a composite action runs straight from the checked-out repo. Rebuild it whenever `src/` changes. +Other tools can drive Blockbench the same way by importing from +`animated-java-ci/lib` (see [`src/lib.ts`](src/lib.ts)). Bun runs it straight +from source, so it needs no build. + ## License MIT diff --git a/dist/index.cjs b/dist/index.cjs index c9e68c9..a3a6c98 100644 --- a/dist/index.cjs +++ b/dist/index.cjs @@ -11546,7 +11546,7 @@ function oneLine(message) { // src/blockbench.ts var delay = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms)); -var ENVIRONMENT = "animated-java-ci"; +var DEFAULT_ENVIRONMENT = "animated-java-ci"; function getFreePort() { return new Promise((resolve2, reject) => { const server = (0, import_node_net.createServer)(); @@ -11567,12 +11567,12 @@ function numericSemver(a, b2) { } return 0; } -async function ensurePortable(eb, blockbenchVersion) { - if (await eb.environmentExists(ENVIRONMENT) !== "env") { +async function ensurePortable(eb, environment, blockbenchVersion) { + if (await eb.environmentExists(environment) !== "env") { info(`Provisioning Blockbench (${blockbenchVersion}) via envbench...`); let lastPercent = -1; await eb.createEnvironment( - ENVIRONMENT, + environment, { blockbenchVersion, force: true }, { onDownloadStart: (version2) => info(`Downloading Blockbench ${version2}`), @@ -11586,7 +11586,7 @@ async function ensurePortable(eb, blockbenchVersion) { } ); } - const env = await eb.getEnvironment(ENVIRONMENT); + const env = await eb.getEnvironment(environment); let version = ""; try { version = await eb.resolveVersion(env.blockbench_version); @@ -11604,10 +11604,10 @@ async function ensurePortable(eb, blockbenchVersion) { } return portable; } -async function launchBlockbench(blockbenchVersion) { +async function launchBlockbench(blockbenchVersion, environment = DEFAULT_ENVIRONMENT) { const eb = new se(); await eb.ensureStorageFolder(); - const userDataDir = (0, import_node_path2.join)(eb.storageDir, ENVIRONMENT); + const userDataDir = (0, import_node_path2.join)(eb.storageDir, environment); await killByUserData(userDataDir); for (const lock of ["SingletonLock", "SingletonSocket", "SingletonCookie"]) { try { @@ -11615,10 +11615,10 @@ async function launchBlockbench(blockbenchVersion) { } catch { } } - await ensurePortable(eb, blockbenchVersion); + await ensurePortable(eb, environment, blockbenchVersion); grantFsPermission(userDataDir, "animated_java"); const debugPort = await getFreePort(); - const child = await eb.launch(ENVIRONMENT, { + const child = await eb.launch(environment, { extraArgs: [ `--remote-debugging-port=${debugPort}`, "--remote-allow-origins=*", @@ -12017,7 +12017,7 @@ async function rendererLoadBlueprint(blueprintPath, fileName, content) { } }; } -async function rendererExport() { +async function rendererExport(options) { const g2 = globalThis; const aj = g2.window && g2.window.AnimatedJava || g2.AnimatedJava; if (!aj || typeof aj.exportProject !== "function") { @@ -12043,7 +12043,7 @@ async function rendererExport() { let ok = false; let thrown = null; try { - ok = await aj.exportProject(); + ok = await aj.exportProject(options || void 0); } catch (e) { thrown = e && (e.stack || e.message) || String(e); } finally { @@ -12134,7 +12134,7 @@ async function exportBlueprint(bridge, blueprintPath, config) { try { result = await bridge.evaluate( rendererExport, - [], + [{ forceSave: false }], config.exportTimeout * 1e3 ); } catch (e) { diff --git a/package.json b/package.json index 9009ced..66ba931 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "url": "https://github.com/Animated-Java/animated-java-ci" }, "type": "module", + "exports": { + "./lib": "./src/lib.ts" + }, "scripts": { "build": "esbuild src/main.ts --bundle --platform=node --target=node20 --format=cjs --outfile=dist/index.cjs --banner:js=\"/* Generated by \\`bun run build\\` - edit src/ instead. */\"", "typecheck": "tsc --noEmit", diff --git a/src/blockbench.ts b/src/blockbench.ts index 413f7cc..271f636 100644 --- a/src/blockbench.ts +++ b/src/blockbench.ts @@ -9,8 +9,8 @@ import * as log from './log' const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) -/** Fixed envbench environment name - one isolated Blockbench install for the action. */ -const ENVIRONMENT = 'animated-java-ci' +/** Default envbench environment name - one isolated Blockbench install for the action. */ +const DEFAULT_ENVIRONMENT = 'animated-java-ci' export interface RunningBlockbench { debugPort: number @@ -46,12 +46,16 @@ function numericSemver(a: string, b: string): number { * Ensure the isolated Blockbench environment exists (downloading Blockbench on * first use) and return the path to its portable executable. */ -async function ensurePortable(eb: Envbench, blockbenchVersion: string): Promise { - if ((await eb.environmentExists(ENVIRONMENT)) !== 'env') { +async function ensurePortable( + eb: Envbench, + environment: string, + blockbenchVersion: string +): Promise { + if ((await eb.environmentExists(environment)) !== 'env') { log.info(`Provisioning Blockbench (${blockbenchVersion}) via envbench...`) let lastPercent = -1 await eb.createEnvironment( - ENVIRONMENT, + environment, { blockbenchVersion: blockbenchVersion as NamedBlockbenchVersion, force: true }, { onDownloadStart: version => log.info(`Downloading Blockbench ${version}`), @@ -66,7 +70,7 @@ async function ensurePortable(eb: Envbench, blockbenchVersion: string): Promise< ) } - const env = await eb.getEnvironment(ENVIRONMENT) + const env = await eb.getEnvironment(environment) let version: ResolvedBlockbenchVersion | '' = '' try { version = await eb.resolveVersion(env.blockbench_version) @@ -91,11 +95,16 @@ async function ensurePortable(eb: Envbench, blockbenchVersion: string): Promise< * Launch Blockbench with the Chrome DevTools Protocol enabled and wait until its * endpoint answers. The caller is expected to already be running under * `xvfb-run` (the action wrapper does this) so no window appears. + * + * `environment` names the isolated envbench install, so separate tools don't share one. */ -export async function launchBlockbench(blockbenchVersion: string): Promise { +export async function launchBlockbench( + blockbenchVersion: string, + environment = DEFAULT_ENVIRONMENT +): Promise { const eb = new Envbench() await eb.ensureStorageFolder() - const userDataDir = join(eb.storageDir, ENVIRONMENT) + const userDataDir = join(eb.storageDir, environment) // Clear a stale Chromium singleton lock a previously hard-killed run may have left. await killByUserData(userDataDir) @@ -107,11 +116,11 @@ export async function launchBlockbench(blockbenchVersion: string): Promise( rendererExport, - [], + [{ forceSave: false }], config.exportTimeout * 1000 ) } catch (e) { diff --git a/src/lib.ts b/src/lib.ts new file mode 100644 index 0000000..21195ff --- /dev/null +++ b/src/lib.ts @@ -0,0 +1,16 @@ +/** + * Library entry for other tools that drive Blockbench headlessly + * (e.g. animated-java-gametest). The action itself starts at `main.ts`. + */ +export { launchBlockbench, type RunningBlockbench } from './blockbench' +export { RendererBridge } from './cdp' +export { + type ExportOptions, + type ExportResult, + type LoadResult, + type PluginLoadResult, + type RendererBlueprintSettings, + rendererExport, + rendererLoadBlueprint, + rendererLoadPlugin, +} from './renderer' diff --git a/src/renderer.ts b/src/renderer.ts index 6c1efa6..9f785f0 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -32,6 +32,11 @@ export interface ExportResult { messages: string[] } +export interface ExportOptions { + /** Save the blueprint after exporting. Animated Java defaults this to `true`. */ + forceSave?: boolean +} + export interface PluginLoadResult { ok: boolean error?: string | null @@ -150,7 +155,7 @@ export async function rendererLoadBlueprint( } } -export async function rendererExport(): Promise { +export async function rendererExport(options: ExportOptions | null): Promise { const g = globalThis as any const aj = (g.window && g.window.AnimatedJava) || g.AnimatedJava if (!aj || typeof aj.exportProject !== 'function') { @@ -184,7 +189,7 @@ export async function rendererExport(): Promise { let ok = false let thrown: string | null = null try { - ok = await aj.exportProject() + ok = await aj.exportProject(options || undefined) } catch (e: any) { thrown = (e && (e.stack || e.message)) || String(e) } finally { diff --git a/test/lib.test.ts b/test/lib.test.ts new file mode 100644 index 0000000..52f0934 --- /dev/null +++ b/test/lib.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, it } from 'bun:test' +import * as lib from '../src/lib' + +// animated-java-gametest imports these; a rename here breaks it silently otherwise. +describe('lib', () => { + it('exports the Blockbench driver', () => { + expect(typeof lib.launchBlockbench).toBe('function') + expect(typeof lib.RendererBridge.attach).toBe('function') + expect(typeof lib.rendererLoadPlugin).toBe('function') + expect(typeof lib.rendererLoadBlueprint).toBe('function') + expect(typeof lib.rendererExport).toBe('function') + }) +})