Files
orca/config/scripts/build-plugin-declarative-runtime.mjs
T
Neil 531658f1d1 Plugin test kit: orca/testing, orca plugin test and orca plugin check (#36969)
* feat(plugins): orca/testing kit, `orca plugin test` and `orca plugin check`

- `orca/testing` mounts a components panel in plain Node (node:test, vitest, bun) with the real
  runtime and Orca's own tree validator: find by text/role/label/testId, press/type/submit/toggle/
  select that resolve once the panel settles, tree() snapshots, errors (with stacks) and warnings,
  persisted state (with stored() for migrations), emit/setVisible/rerender, and unmount running
  setup cleanup. It re-exports node:test and strict assert so tests type-check without @types/node.
- mockHost refuses what Orca refuses with the same codes (surface, capability from the manifest,
  params, a gesture method without a press), checks configured results against the host API, and
  records calls.
- `orca plugin test [dir]` discovers *.test.ts(x) and runs them with node:test through Node module
  hooks that resolve `orca` like the guest's import map and transpile/resolve sources exactly as
  Orca's build-less loader, or with the plugin's own vitest via a generated config.
- `orca plugin check [dir]` validates, then renders each components panel once and reports what it
  threw (file:line), layouts Orca would refuse, and props it would drop.
- The kit, hooks and register entry ship minified beside orca-runtime.mjs and are unpacked from asar;
  typings land in .orca/types/orca-testing. The components template and hello-tsx get sample tests.
- ui.error carries an optional stack; a new runtime starts every atom from its initial value; the
  hydration fallback timer is cleared once hydrated.

* plugins: run plugin test/check on a system Node or Orca's own, and warn they are unsandboxed

* test(plugins): run the shipped example plugin tests from the unit suite
2026-11-01 02:42:12 -08:00

104 lines
3.3 KiB
JavaScript

// Bundle the files Orca ships for components panels into resources/plugins/declarative/:
// the guest `orca` module, the `orca/testing` kit, and the Node hooks `orca plugin test` runs with.
// `--check` fails when a checked-in file is stale instead of rewriting it.
import { readFileSync, writeFileSync } from 'node:fs'
import path from 'node:path'
import { build } from 'esbuild'
const ROOT = path.resolve(import.meta.dirname, '..', '..')
const OUTPUT_DIR = path.join(ROOT, 'resources', 'plugins', 'declarative')
const DECLARATIVE = path.join(ROOT, 'src', 'shared', 'plugins', 'declarative')
const TESTING = path.join(ROOT, 'src', 'main', 'plugins', 'testing')
export const DECLARATIVE_RUNTIME_OUTPUT = path.join(OUTPUT_DIR, 'orca-runtime.mjs')
/** The kit and the panel must share one runtime instance, so the kit imports the shipped file. */
const runtimeAsSibling = {
name: 'orca-runtime-as-sibling',
setup(builder) {
builder.onResolve({ filter: /runtime\/orca-module$/ }, () => ({
path: './orca-runtime.mjs',
external: true
}))
}
}
const BUNDLES = [
{
file: 'orca-runtime.mjs',
entry: path.join(DECLARATIVE, 'runtime', 'orca-module.ts'),
source: 'src/shared/plugins/declarative/runtime/',
platform: 'browser'
},
{
file: 'orca-testing.mjs',
entry: path.join(DECLARATIVE, 'testing', 'orca-testing.ts'),
source: 'src/shared/plugins/declarative/testing/',
platform: 'node',
plugins: [runtimeAsSibling]
},
{
file: 'orca-test-hooks.mjs',
entry: path.join(TESTING, 'plugin-test-hooks.ts'),
source: 'src/main/plugins/testing/',
platform: 'node'
},
{
file: 'orca-test-register.mjs',
entry: path.join(TESTING, 'plugin-test-register.ts'),
source: 'src/main/plugins/testing/',
platform: 'node'
}
]
async function bundle({ file, entry, source, platform, plugins = [] }) {
const result = await build({
entryPoints: [entry],
bundle: true,
format: 'esm',
platform,
target: platform === 'node' ? 'node20' : 'es2022',
charset: 'utf8',
legalComments: 'none',
// The Node files bundle zod and sucrase; minified they stay small in Git and on disk.
minify: platform === 'node',
plugins,
banner: {
js:
`// Generated by config/scripts/build-plugin-declarative-runtime.mjs from\n` +
`// ${source}. Do not edit.`
},
write: false,
logLevel: 'silent'
})
return { output: path.join(OUTPUT_DIR, file), text: result.outputFiles[0].text }
}
/** Every shipped bundle, as `{ output, text }`. */
export function bundleDeclarativeFiles() {
return Promise.all(BUNDLES.map(bundle))
}
export async function bundleDeclarativeRuntime() {
return (await bundle(BUNDLES[0])).text
}
async function main() {
const bundles = await bundleDeclarativeFiles()
const check = process.argv.includes('--check')
for (const { output, text } of bundles) {
const relativeOutput = path.relative(ROOT, output)
if (!check) {
writeFileSync(output, text)
console.log(`wrote ${relativeOutput}`)
} else if (readFileSync(output, 'utf8') !== text) {
console.error(`${relativeOutput} is stale; run pnpm generate:plugin-declarative-runtime`)
process.exitCode = 1
}
}
}
if (process.argv[1] && path.resolve(process.argv[1]) === import.meta.filename) {
await main()
}