@fzgame/cli 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../node_modules/.pnpm/tsup@8.5.1_@swc+core@1.16.1_@swc+helpers@0.5.15__jiti@2.7.0_postcss@8.5.26_tsx@4.23.13_typescript@5.9.3_yaml@2.9.0/node_modules/tsup/assets/cjs_shims.js","../src/env.ts","../src/program.ts","../src/dev.ts","../src/kit-path.ts","../src/pack.ts","../src/manifest.ts","../src/zip.ts","../src/init.ts","../src/publish.ts","../src/fetch.ts","../src/runner.ts","../src/runner-pipeline.ts","../src/version.ts","../src/cli.ts"],"sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\n/**\n * Load KEY=VALUE pairs from `<dir>/.env` into process.env.\n *\n * Variables already set in the environment win, so a shell export or a CI\n * secret is never overridden by a checked-in file. Missing file is not an error.\n */\nexport function loadEnvFile(dir: string = process.cwd()): boolean {\n let raw: string\n try {\n raw = readFileSync(join(dir, '.env'), 'utf8')\n } catch {\n return false\n }\n\n for (const line of raw.split('\\n')) {\n const match = /^\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=\\s*(.*?)\\s*$/.exec(line)\n if (!match) continue\n const key = match[1]!\n let value = match[2]!\n const quoted =\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n if (quoted) value = value.slice(1, -1)\n if (process.env[key] === undefined) process.env[key] = value\n }\n return true\n}\n","import { Command } from 'commander'\nimport { startDevServer } from './dev'\nimport { initGame } from './init'\nimport { buildGame } from './pack'\nimport { publishGame } from './publish'\nimport { startRunner } from './runner'\nimport { VERSION } from './version'\n\nconst DEFAULT_API = 'http://localhost:3020'\n\nexport function buildProgram() {\n const program = new Command()\n\n program\n .name('fzgame')\n .description('Build, publish, and develop FZ games')\n .version(VERSION)\n .addHelpText(\n 'after',\n `\nEnvironment (a .env file in the working directory is read too, shell values win):\n FZGAME_API GameHub API for publish and runner (default ${DEFAULT_API})\n FZGAME_SERVER Alias for FZGAME_API\n FZGAME_TOKEN Session token for publish\n FZGAME_SPACE_ID Space id, publishes a private game\n FZGAME_RUNNER_KEY Runner key from the BO receipt\n FZGAME_RUNNER_PASS Runner pass from the BO receipt`,\n )\n\n program\n .command('init [dir]')\n .description('Scaffold a game from the bundled click-counter example')\n .option('-f, --force', 'write into a directory that already has files')\n .action(async (dir: string | undefined, opts: { force?: boolean }) => {\n const result = await initGame(dir || '.', { force: opts.force })\n console.log(`Scaffolded ${result.files.length} files in ${result.dir}`)\n for (const file of result.files) console.log(` ${file}`)\n console.log('Next: fzgame dev')\n })\n\n program\n .command('build')\n .description('Validate fzgame.json and pack game/editor/handler into a zip')\n .option('--cwd <dir>', 'game root', process.cwd())\n .option('-o, --out <file>', 'output zip path')\n .action(async (opts: { cwd: string; out?: string }) => {\n const result = await buildGame(opts.cwd, opts.out)\n console.log(\n `Built ${result.manifest.slug}@${result.manifest.version} -> ${result.dest}`,\n )\n })\n\n program\n .command('publish')\n .description('Build and upload the zip to /games/publishing')\n .option('--cwd <dir>', 'game root', process.cwd())\n .option('--api <url>', 'GameHub API', process.env.FZGAME_API || DEFAULT_API)\n .option('--token <token>', 'session token', process.env.FZGAME_TOKEN)\n .option('--space <id>', 'space id for private games', process.env.FZGAME_SPACE_ID)\n .action(async (opts: { cwd: string; api: string; token?: string; space?: string }) => {\n if (!opts.token) {\n throw new Error('Pass --token or set FZGAME_TOKEN')\n }\n const game = await publishGame({\n cwd: opts.cwd,\n api: opts.api,\n token: opts.token,\n spaceId: opts.space,\n })\n console.log(`Published ${game.slug}@${game.version} (${game.id})`)\n if (game.entryPointUrl) console.log(game.entryPointUrl)\n })\n\n program\n .command('dev')\n .description('Serve the game locally with a mock runtime kit API')\n .option('--cwd <dir>', 'game root', process.cwd())\n .option('-p, --port <port>', 'port', '3400')\n .option('--host <host>', 'bind address (use 0.0.0.0 to expose on the LAN)', '127.0.0.1')\n .action(async (opts: { cwd: string; port: string; host: string }) => {\n const { port, manifest } = await startDevServer(opts.cwd, Number(opts.port), {\n host: opts.host,\n })\n const shown = opts.host === '0.0.0.0' || opts.host === '::' ? '127.0.0.1' : opts.host\n console.log(`${manifest.name} dev server http://${shown}:${port}`)\n if (shown !== opts.host) console.log(`Bound to ${opts.host}, reachable from the LAN`)\n })\n\n const runner = program.command('runner').description('Self-hosted GameHub CI runner')\n\n runner\n .command('start')\n .description('Connect to the API and run fetch → build → test → publish jobs')\n .option(\n '--server <url>',\n 'GameHub API',\n process.env.FZGAME_API || process.env.FZGAME_SERVER || DEFAULT_API,\n )\n .option('--key <key>', 'runner key from the BO receipt', process.env.FZGAME_RUNNER_KEY)\n .option('--pass <pass>', 'runner pass from the BO receipt', process.env.FZGAME_RUNNER_PASS)\n .action(async (opts: { server: string; key?: string; pass?: string }) => {\n await startRunner({\n server: opts.server,\n key: opts.key || '',\n pass: opts.pass || '',\n })\n })\n\n return program\n}\n","import { createReadStream } from 'node:fs'\nimport { readFile, stat } from 'node:fs/promises'\nimport { createServer, type IncomingMessage, type ServerResponse } from 'node:http'\nimport { extname, join, resolve, sep } from 'node:path'\nimport { KIT_HINT, resolveKitFile } from './kit-path'\nimport type { FzGameManifest } from './manifest'\nimport { loadManifest } from './pack'\n\nconst DEFAULT_HOST = '127.0.0.1'\n\nfunction applyCors(res: ServerResponse) {\n res.setHeader('access-control-allow-origin', '*')\n res.setHeader('access-control-allow-headers', '*')\n res.setHeader('access-control-allow-methods', 'GET,POST,PUT,OPTIONS')\n}\n\nconst MIME: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'application/javascript; charset=utf-8',\n '.mjs': 'application/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n}\n\ntype Session = {\n id: string\n token: string\n status: string\n score: number | null\n result: unknown\n metadata: { snapshot?: unknown }\n gameInstanceId: string\n}\n\nexport type DevServerOptions = {\n /** Bind address. Defaults to loopback: the mock runtime has no auth. */\n host?: string\n}\n\nexport async function startDevServer(\n root: string,\n port: number,\n options: DevServerOptions = {},\n) {\n const host = options.host || DEFAULT_HOST\n const manifest = await loadManifest(root)\n const sessions = new Map<string, Session>()\n\n const server = createServer(async (req, res) => {\n applyCors(res)\n applySecurityHeaders(res)\n if (req.method === 'OPTIONS') {\n res.writeHead(204)\n res.end()\n return\n }\n try {\n const url = new URL(req.url || '/', `http://127.0.0.1:${port}`)\n if (url.pathname === '/health') {\n res.writeHead(200, { 'content-type': 'application/json' })\n res.end(JSON.stringify({ ok: true, name: manifest.name, port }))\n return\n }\n if (\n url.pathname.startsWith('/games/runtime') ||\n url.pathname.startsWith('/pub/game-instances')\n ) {\n await handleRuntime(req, res, url, sessions, manifest)\n return\n }\n if (url.pathname === '/@fzgame/kit.js') {\n await serveKit(res)\n return\n }\n if (url.pathname === '/' || url.pathname === '/index.html') {\n res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' })\n res.end(launcherHtml(requestOrigin(req, host, port), manifest.name))\n return\n }\n await serveStatic(root, url.pathname, res)\n } catch (error) {\n res.writeHead(500, { 'content-type': 'application/json' })\n res.end(JSON.stringify({ message: (error as Error).message }))\n }\n })\n\n await new Promise<void>((resolveReady, reject) => {\n server.once('error', (error: NodeJS.ErrnoException) => {\n reject(\n error.code === 'EADDRINUSE'\n ? new Error(`Port ${port} is already in use. Pass --port to pick another one.`)\n : new Error(`Cannot serve on ${host}:${port} (${error.code || error.message}).`),\n )\n })\n server.listen(port, host, () => resolveReady())\n })\n const address = server.address()\n return {\n server,\n host,\n port: typeof address === 'object' && address ? address.port : port,\n manifest,\n }\n}\n\nfunction applySecurityHeaders(res: ServerResponse) {\n // Allow cross-origin requests\n res.setHeader('Access-Control-Allow-Origin', '*')\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE')\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With')\n\n // Any parent may embed the game, so the portal origin works without a proxy.\n res.setHeader('Content-Security-Policy', 'frame-ancestors *')\n\n // Remove restrictive framing header if set by default\n res.removeHeader('X-Frame-Options')\n}\n\nasync function serveKit(res: ServerResponse) {\n try {\n const body = await readFile(resolveKitFile())\n res.writeHead(200, { 'content-type': 'application/javascript; charset=utf-8' })\n res.end(body)\n } catch (error) {\n res.writeHead(404, { 'content-type': 'text/plain' })\n res.end(`Cannot serve @fzgame/kit: ${(error as Error).message}\\n${KIT_HINT}`)\n }\n}\n\nasync function serveStatic(root: string, pathname: string, res: ServerResponse) {\n const rel = decodeURIComponent(pathname).replace(/^\\//, '') || 'index.html'\n const abs = resolve(root, rel)\n const rootAbs = resolve(root)\n if (abs !== rootAbs && !abs.startsWith(rootAbs + sep)) {\n res.writeHead(403)\n res.end('Forbidden')\n return\n }\n try {\n const info = await stat(abs)\n const file = info.isDirectory() ? join(abs, 'index.html') : abs\n res.writeHead(200, {\n 'content-type': MIME[extname(file)] || 'application/octet-stream',\n })\n createReadStream(file).pipe(res)\n } catch {\n res.writeHead(404)\n res.end('Not found')\n }\n}\n\nasync function handleRuntime(\n req: IncomingMessage,\n res: ServerResponse,\n url: URL,\n sessions: Map<string, Session>,\n manifest: FzGameManifest,\n) {\n res.setHeader('content-type', 'application/json')\n res.setHeader('access-control-allow-origin', '*')\n res.setHeader('access-control-allow-headers', 'content-type')\n res.setHeader('access-control-allow-methods', 'GET,POST,PUT,OPTIONS')\n if (req.method === 'OPTIONS') {\n res.writeHead(204)\n res.end()\n return\n }\n\n if (\n req.method === 'GET' &&\n (url.pathname.startsWith('/pub/game-instances/') ||\n url.pathname.startsWith('/games/runtime/instances/'))\n ) {\n res.end(\n JSON.stringify({\n id: url.pathname.split('/').pop(),\n name: manifest.name,\n status: 'PUBLISHED',\n overrides: {},\n game: { name: manifest.name, configSchema: manifest.configSchema ?? {} },\n }),\n )\n return\n }\n\n if (req.method === 'POST' && url.pathname === '/games/runtime/sessions') {\n const body = await readJson(req)\n const existing = [...sessions.values()].find(\n (s) => s.gameInstanceId === body.gameInstanceId && s.status !== 'FINISHED',\n )\n if (existing) {\n res.end(JSON.stringify(existing))\n return\n }\n const session: Session = {\n id: `dev-${sessions.size + 1}`,\n token: `dev-token-${sessions.size + 1}`,\n status: 'CREATED',\n score: null,\n result: null,\n metadata: { snapshot: {} },\n gameInstanceId: String(body.gameInstanceId || 'dev-instance'),\n }\n sessions.set(session.token, session)\n res.end(JSON.stringify(session))\n return\n }\n\n const emitMatch = url.pathname.match(/^\\/games\\/runtime\\/sessions\\/([^/]+)\\/emit$/)\n if (req.method === 'POST' && emitMatch) {\n const session = sessions.get(emitMatch[1]!)\n if (!session) {\n res.writeHead(404)\n res.end(JSON.stringify({ message: 'Session not found' }))\n return\n }\n const body = await readJson(req)\n applyDevEmit(\n session,\n String(body.type || ''),\n body.payload as Record<string, unknown> | undefined,\n )\n res.end(JSON.stringify(session))\n return\n }\n\n const snapMatch = url.pathname.match(/^\\/games\\/runtime\\/sessions\\/([^/]+)\\/snapshot$/)\n if (req.method === 'PUT' && snapMatch) {\n const session = sessions.get(snapMatch[1]!)\n if (!session) {\n res.writeHead(404)\n res.end(JSON.stringify({ message: 'Session not found' }))\n return\n }\n const body = await readJson(req)\n session.metadata.snapshot = body.state ?? body\n res.end(JSON.stringify(session))\n return\n }\n\n const getMatch = url.pathname.match(/^\\/games\\/runtime\\/sessions\\/([^/]+)$/)\n if (req.method === 'GET' && getMatch) {\n const session = sessions.get(getMatch[1]!)\n if (!session) {\n res.writeHead(404)\n res.end(JSON.stringify({ message: 'Session not found' }))\n return\n }\n res.end(JSON.stringify(session))\n return\n }\n\n res.writeHead(404)\n res.end(JSON.stringify({ message: 'Not found' }))\n}\n\nfunction applyDevEmit(session: Session, type: string, payload?: Record<string, unknown>) {\n if (type === 'ready') session.status = 'READY'\n if (type === 'start') session.status = 'PLAYING'\n if (type === 'pause') session.status = 'PAUSED'\n if (type === 'finish') {\n session.status = 'FINISHED'\n if (typeof payload?.score === 'number') session.score = payload.score\n if (payload?.result) session.result = payload.result\n }\n if (type === 'abort') session.status = 'ABORTED'\n if (type === 'score' && typeof payload?.score === 'number') session.score = payload.score\n if (type === 'snapshot') session.metadata.snapshot = payload?.state ?? payload\n}\n\nfunction readJson(req: IncomingMessage): Promise<Record<string, unknown>> {\n return new Promise((resolveJson, reject) => {\n const chunks: Buffer[] = []\n req.on('data', (c) => chunks.push(c as Buffer))\n req.on('end', () => {\n if (chunks.length === 0) return resolveJson({})\n try {\n resolveJson(JSON.parse(Buffer.concat(chunks).toString('utf8')))\n } catch (error) {\n reject(error)\n }\n })\n req.on('error', reject)\n })\n}\n\nfunction requestOrigin(req: IncomingMessage, host: string, port: number) {\n if (req.headers.host) return `http://${req.headers.host}`\n const shown = host === '0.0.0.0' || host === '::' ? DEFAULT_HOST : host\n return `http://${shown}:${port}`\n}\n\nfunction launcherHtml(origin: string, name: string) {\n const kit = `kit=${encodeURIComponent('/@fzgame/kit.js')}`\n const play = `/game/index.html?gameInstanceId=dev-instance&playerToken=dev-player&apiUrl=${encodeURIComponent(origin)}&${kit}`\n const preview = `/game/index.html?preview=true&${kit}`\n const editor = `/editor/index.html?gameInstanceId=dev-instance&apiUrl=${encodeURIComponent(origin)}&${kit}`\n return `<!doctype html>\n<html>\n <head><meta charset=\"utf-8\"><title>${name} — fzgame dev</title></head>\n <body style=\"font-family:system-ui;padding:24px\">\n <h1>${name}</h1>\n <p>Local game developer mode.</p>\n <p><a href=\"${play}\">Open game</a></p>\n <p><a href=\"${preview}\">Open preview</a> (no play session; pass <code>gameSetting</code> JSON to try config)</p>\n <p><a href=\"${editor}\">Open editor</a></p>\n </body>\n</html>`\n}\n","import { readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { dirname, join } from 'node:path'\n\nconst require = createRequire(import.meta.url)\n\nexport const KIT_HINT = 'Run `pnpm --filter @fzgame/kit build` (monorepo) or `npm install` (installed CLI)'\n\ntype ExportTarget = string | { import?: ExportTarget; require?: ExportTarget; default?: ExportTarget }\n\ntype KitManifest = {\n module?: string\n main?: string\n exports?: Record<string, ExportTarget>\n}\n\n/**\n * The ESM file behind an export condition, which may be a bare path or a tree\n * of conditions (`{ import: { types, default } }`).\n */\nfunction resolveTarget(target: ExportTarget | undefined): string | undefined {\n if (typeof target === 'string') return target\n if (!target) return undefined\n return resolveTarget(target.import) ?? resolveTarget(target.default)\n}\n\n/**\n * Absolute path of the kit's browser (ESM) bundle, the file the game loads as\n * `/@fzgame/kit.js` in dev and as `game/vendor/kit.js` in a packed zip.\n *\n * The kit's own manifest decides the filename, so changing its build layout\n * cannot silently ship a zip with no vendor script.\n */\nexport function resolveKitFile(): string {\n const manifestPath = require.resolve('@fzgame/kit/package.json')\n const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as KitManifest\n const entry =\n manifest.module ?? resolveTarget(manifest.exports?.['.']) ?? manifest.main ?? './dist/index.js'\n return join(dirname(manifestPath), entry)\n}\n","import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises'\nimport { dirname, join, relative } from 'node:path'\nimport { KIT_HINT, resolveKitFile } from './kit-path'\nimport { parseFzGameManifest, type FzGameManifest } from './manifest'\nimport { buildZip } from './zip'\n\nexport type PackOptions = {\n /** Kit bundle to vendor. Defaults to the installed `@fzgame/kit`; `null` skips vendoring. */\n kitFile?: string | null\n}\n\nexport async function loadManifest(root: string): Promise<FzGameManifest> {\n const manifestPath = join(root, 'fzgame.json')\n let text: string\n try {\n text = await readFile(manifestPath, 'utf8')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new Error(`No fzgame.json in ${root}. Run fzgame dev from a game root.`)\n }\n throw error\n }\n try {\n return parseFzGameManifest(JSON.parse(text))\n } catch (error) {\n throw new Error(`Invalid fzgame.json in ${root}: ${(error as Error).message}`)\n }\n}\n\nexport async function collectGameFiles(\n root: string,\n options: PackOptions = {},\n): Promise<Record<string, Buffer>> {\n const manifest = await loadManifest(root)\n const files: Record<string, Buffer> = {\n 'fzgame.json': await readFile(join(root, 'fzgame.json')),\n }\n\n for (const dir of ['game', 'editor', 'handler']) {\n const abs = join(root, dir)\n if (!(await exists(abs))) {\n throw new Error(`Missing required directory: ${dir}/`)\n }\n await walk(abs, async (file) => {\n const rel = relative(root, file).replace(/\\\\/g, '/')\n files[rel] = await readFile(file)\n })\n }\n\n for (const path of [manifest.entry.game, manifest.entry.editor, manifest.entry.handler]) {\n if (!files[path]) throw new Error(`Missing required entrypoint: ${path}`)\n }\n\n await vendorKit(files, options)\n return files\n}\n\nexport async function buildGame(root: string, outFile?: string, options: PackOptions = {}) {\n const manifest = await loadManifest(root)\n const files = await collectGameFiles(root, options)\n const zip = buildZip(files)\n const dest = outFile || join(root, 'dist', `${manifest.slug}-v${manifest.version}.zip`)\n await mkdir(dirname(dest), { recursive: true })\n await writeFile(dest, zip)\n return { dest, manifest, files: Object.keys(files) }\n}\n\nasync function vendorKit(files: Record<string, Buffer>, options: PackOptions) {\n if (files['game/vendor/kit.js']) return\n if (options.kitFile === null) return\n\n let kitFile: string\n if (options.kitFile) {\n kitFile = options.kitFile\n } else {\n try {\n kitFile = resolveKitFile()\n } catch (error) {\n throw new Error(\n `Cannot vendor the game runtime: @fzgame/kit is not installed (${(error as Error).message}). ${KIT_HINT}`,\n )\n }\n }\n\n if (!(await exists(kitFile))) {\n throw new Error(`Cannot vendor the game runtime: ${kitFile} does not exist. ${KIT_HINT}`)\n }\n files['game/vendor/kit.js'] = await readFile(kitFile)\n}\n\nasync function walk(dir: string, visit: (file: string) => Promise<void>) {\n const entries = await readdir(dir, { withFileTypes: true })\n for (const entry of entries) {\n if (entry.name === '.DS_Store') continue\n const abs = join(dir, entry.name)\n if (entry.isDirectory()) await walk(abs, visit)\n else await visit(abs)\n }\n}\n\nasync function exists(path: string) {\n try {\n await stat(path)\n return true\n } catch {\n return false\n }\n}\n","export type FzGameEntry = {\n game: string\n editor: string\n handler: string\n}\n\nexport type FzGameInfo = {\n name: string\n slug: string\n version: number\n image?: string\n description?: string\n author?: string\n gitUrl?: string\n}\n\nexport type FzGameManifest = {\n info: FzGameInfo\n entry: FzGameEntry\n formSchema?: Record<string, unknown>\n formUiSchema?: Record<string, unknown>\n resultSchema?: Record<string, unknown>\n snapshotSchema?: Record<string, unknown>\n gameDataSchema?: Record<string, unknown>\n name: string\n slug: string\n version: number\n description?: string\n configSchema?: Record<string, unknown>\n}\n\nconst SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\n\nexport const DEFAULT_ENTRY: FzGameEntry = {\n game: 'game/index.html',\n editor: 'editor/index.html',\n handler: 'handler/index.js',\n}\n\nexport function readFormSchema(\n schema: Record<string, unknown> | null | undefined,\n): Record<string, unknown> {\n if (!schema) return {}\n const form = schema.formSchema\n if (form && typeof form === 'object' && !Array.isArray(form)) {\n return form as Record<string, unknown>\n }\n return schema\n}\n\nexport function toStoredConfigSchema(\n manifest: Pick<\n FzGameManifest,\n | 'formSchema'\n | 'formUiSchema'\n | 'resultSchema'\n | 'snapshotSchema'\n | 'gameDataSchema'\n | 'info'\n >,\n): Record<string, unknown> {\n return {\n formSchema: manifest.formSchema ?? { type: 'object', properties: {} },\n ...(manifest.formUiSchema ? { formUiSchema: manifest.formUiSchema } : {}),\n ...(manifest.resultSchema ? { resultSchema: manifest.resultSchema } : {}),\n ...(manifest.snapshotSchema ? { snapshotSchema: manifest.snapshotSchema } : {}),\n ...(manifest.gameDataSchema ? { gameDataSchema: manifest.gameDataSchema } : {}),\n ...(manifest.info.author ? { author: manifest.info.author } : {}),\n ...(manifest.info.gitUrl ? { git_url: manifest.info.gitUrl } : {}),\n ...(manifest.info.image ? { image: manifest.info.image } : {}),\n }\n}\n\nexport function parseFzGameManifest(input: unknown): FzGameManifest {\n if (!input || typeof input !== 'object' || Array.isArray(input)) {\n throw new Error('fzgame.json must be an object')\n }\n const raw = input as Record<string, unknown>\n const info = parseInfo(raw)\n const formSchema =\n readObject(raw.formSchema, 'formSchema') ??\n readObject(raw.configSchema, 'configSchema')\n const manifest: FzGameManifest = {\n info,\n entry: parseEntry(raw.entry),\n formSchema,\n formUiSchema: readObject(raw.formUiSchema, 'formUiSchema'),\n resultSchema: readObject(raw.resultSchema, 'resultSchema'),\n snapshotSchema: readObject(raw.snapshotSchema, 'snapshotSchema'),\n gameDataSchema: readObject(raw.gameDataSchema, 'gameDataSchema'),\n name: info.name,\n slug: info.slug,\n version: info.version,\n description: info.description,\n }\n manifest.configSchema = toStoredConfigSchema(manifest)\n return manifest\n}\n\nfunction parseInfo(raw: Record<string, unknown>): FzGameInfo {\n const nested =\n raw.info && typeof raw.info === 'object' && !Array.isArray(raw.info)\n ? (raw.info as Record<string, unknown>)\n : {}\n const name = pickString(nested.name, raw.name)\n const slug = pickString(nested.slug, raw.slug)\n if (!name || name.trim().length < 2) {\n throw new Error('fzgame.json must include a name (min 2 characters)')\n }\n if (!slug || !SLUG_RE.test(slug)) {\n throw new Error(\n 'fzgame.json slug must be kebab-case (lowercase letters, digits, hyphens)',\n )\n }\n const versionRaw = nested.version ?? raw.version\n const version =\n versionRaw === undefined || versionRaw === null ? 1 : versionRaw\n if (typeof version !== 'number' || !Number.isInteger(version) || version < 1) {\n throw new Error('fzgame.json version must be a positive integer')\n }\n const gitUrl = optionalString(nested.git_url ?? nested.gitUrl)\n if (gitUrl && !/^https?:\\/\\//i.test(gitUrl)) {\n throw new Error('fzgame.json info.git_url must be an http(s) URL')\n }\n const image = optionalString(nested.image)\n const description = optionalString(nested.description ?? raw.description)\n const author = optionalString(nested.author)\n return {\n name: name.trim(),\n slug,\n version,\n ...(image ? { image } : {}),\n ...(description ? { description } : {}),\n ...(author ? { author } : {}),\n ...(gitUrl ? { gitUrl } : {}),\n }\n}\n\nfunction parseEntry(input: unknown): FzGameEntry {\n if (input === undefined || input === null) return { ...DEFAULT_ENTRY }\n if (typeof input !== 'object' || Array.isArray(input)) {\n throw new Error('fzgame.json entry must be an object')\n }\n const raw = input as Record<string, unknown>\n return {\n game: readPath(raw.game, DEFAULT_ENTRY.game),\n editor: readPath(raw.editor, DEFAULT_ENTRY.editor),\n handler: readPath(raw.handler, DEFAULT_ENTRY.handler),\n }\n}\n\nfunction readPath(value: unknown, fallback: string): string {\n if (value === undefined || value === null) return fallback\n if (typeof value !== 'string' || !value.trim()) {\n throw new Error('fzgame.json entry paths must be relative')\n }\n const path = value.replace(/\\\\/g, '/').replace(/^\\.\\//, '')\n if (path.startsWith('/') || path.includes('..')) {\n throw new Error('fzgame.json entry paths must be relative without ..')\n }\n return path\n}\n\nfunction readObject(value: unknown, field: string): Record<string, unknown> | undefined {\n if (value === undefined || value === null) return undefined\n if (typeof value !== 'object' || Array.isArray(value)) {\n throw new Error(`fzgame.json ${field} must be a JSON Schema object`)\n }\n return value as Record<string, unknown>\n}\n\nfunction pickString(...values: unknown[]): string | undefined {\n for (const value of values) {\n if (typeof value === 'string' && value.trim()) return value\n }\n return undefined\n}\n\nfunction optionalString(value: unknown): string | undefined {\n if (value === undefined || value === null || value === '') return undefined\n return String(value)\n}\n","import { deflateRawSync } from 'node:zlib'\n\n// Fixed 1980-01-01 00:00 DOS timestamp: byte-identical archives for identical\n// input, with month and day fields that real extractors accept.\nconst DOS_TIME = 0\nconst DOS_DATE = 0x0021\n\nconst CRC_TABLE = new Uint32Array(256)\nfor (let n = 0; n < 256; n++) {\n let c = n\n for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1\n CRC_TABLE[n] = c\n}\n\nfunction crc32(buf: Buffer): number {\n let c = 0xffffffff\n for (let i = 0; i < buf.length; i++) {\n c = CRC_TABLE[(c ^ buf[i]!) & 0xff]! ^ (c >>> 8)\n }\n return (c ^ 0xffffffff) >>> 0\n}\n\nexport function buildZip(files: Record<string, Buffer | string>): Buffer {\n const records: Array<{\n name: Buffer\n data: Buffer\n compressed: Buffer\n crc: number\n offset: number\n }> = []\n const parts: Buffer[] = []\n let offset = 0\n\n for (const [name, content] of Object.entries(files)) {\n const data = Buffer.isBuffer(content) ? content : Buffer.from(content)\n const compressed = deflateRawSync(data)\n const crc = crc32(data)\n const nameBuf = Buffer.from(name.replace(/\\\\/g, '/'), 'utf8')\n const local = Buffer.alloc(30)\n local.writeUInt32LE(0x04034b50, 0)\n local.writeUInt16LE(20, 4)\n local.writeUInt16LE(8, 8)\n local.writeUInt16LE(DOS_TIME, 10)\n local.writeUInt16LE(DOS_DATE, 12)\n local.writeUInt32LE(crc, 14)\n local.writeUInt32LE(compressed.length, 18)\n local.writeUInt32LE(data.length, 22)\n local.writeUInt16LE(nameBuf.length, 26)\n parts.push(local, nameBuf, compressed)\n records.push({ name: nameBuf, data, compressed, crc, offset })\n offset += local.length + nameBuf.length + compressed.length\n }\n\n const centralStart = offset\n for (const record of records) {\n const central = Buffer.alloc(46)\n central.writeUInt32LE(0x02014b50, 0)\n central.writeUInt16LE(20, 4)\n central.writeUInt16LE(20, 6)\n central.writeUInt16LE(8, 10)\n central.writeUInt16LE(DOS_TIME, 12)\n central.writeUInt16LE(DOS_DATE, 14)\n central.writeUInt32LE(record.crc, 16)\n central.writeUInt32LE(record.compressed.length, 20)\n central.writeUInt32LE(record.data.length, 24)\n central.writeUInt16LE(record.name.length, 28)\n central.writeUInt32LE(record.offset, 42)\n parts.push(central, record.name)\n offset += central.length + record.name.length\n }\n\n const eocd = Buffer.alloc(22)\n eocd.writeUInt32LE(0x06054b50, 0)\n eocd.writeUInt16LE(records.length, 8)\n eocd.writeUInt16LE(records.length, 10)\n eocd.writeUInt32LE(offset - centralStart, 12)\n eocd.writeUInt32LE(centralStart, 16)\n parts.push(eocd)\n return Buffer.concat(parts)\n}\n","import { cp, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises'\nimport { dirname, join, relative, resolve, sep } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst here = dirname(fileURLToPath(import.meta.url))\nconst EXAMPLE_DIR = join(here, '..', 'example', 'click-counter')\n\nexport type InitOptions = { force?: boolean }\n\nexport async function initGame(targetDir: string, options: InitOptions = {}) {\n const dest = resolve(targetDir)\n const template = await stat(EXAMPLE_DIR).then(\n (info) => info.isDirectory(),\n () => false,\n )\n if (!template) {\n throw new Error(\n `Game template missing at ${EXAMPLE_DIR}. Reinstall @fzgame/cli, the \"example\" folder must ship with it.`,\n )\n }\n\n await mkdir(dest, { recursive: true })\n const existing = (await readdir(dest)).filter((name) => name !== '.DS_Store' && name !== '.git')\n if (existing.length > 0 && !options.force) {\n throw new Error(\n `${dest} is not empty (${existing.slice(0, 5).join(', ')}${existing.length > 5 ? ', ...' : ''}). Pass --force to write into it.`,\n )\n }\n\n const hasGitignore = await stat(join(dest, '.gitignore')).then(\n () => true,\n () => false,\n )\n await cp(EXAMPLE_DIR, dest, {\n recursive: true,\n filter: (source) => keep(source) && !(hasGitignore && source.endsWith(`${sep}.gitignore`)),\n })\n await ensureGitignore(dest)\n\n const files: string[] = []\n await walk(dest, (file) => {\n files.push(relative(dest, file).split(sep).join('/'))\n })\n files.sort()\n return { dir: dest, files }\n}\n\n/** Skip build output and installed dependencies so the scaffold stays a source tree. */\nfunction keep(source: string) {\n const name = source.split(sep).pop() ?? ''\n return name !== 'dist' && name !== 'node_modules' && name !== '.DS_Store'\n}\n\nasync function ensureGitignore(dest: string) {\n const path = join(dest, '.gitignore')\n let current = ''\n try {\n current = await readFile(path, 'utf8')\n } catch {\n /* no .gitignore yet */\n }\n if (/(^|\\n)\\s*dist\\/?\\s*(\\n|$)/.test(current)) return\n const next = current && !current.endsWith('\\n') ? `${current}\\n` : current\n await writeFile(path, `${next}dist/\\n`)\n}\n\nasync function walk(dir: string, visit: (file: string) => void) {\n for (const entry of await readdir(dir, { withFileTypes: true })) {\n if (entry.name === 'node_modules' || entry.name === '.git') continue\n const abs = join(dir, entry.name)\n if (entry.isDirectory()) await walk(abs, visit)\n else visit(abs)\n }\n}\n","import { readFile } from 'node:fs/promises'\nimport { buildGame } from './pack'\nimport { request } from './fetch'\n\nexport type PublishOptions = {\n cwd: string\n api: string\n token: string\n spaceId?: string\n}\n\nconst UPLOAD_TIMEOUT_MS = 120_000\n\nexport async function publishGame(options: PublishOptions) {\n const { dest, manifest } = await buildGame(options.cwd)\n const zip = await readFile(dest)\n const api = options.api.replace(/\\/$/, '')\n const path = options.spaceId ? '/portal/games/publishing' : '/games/publishing'\n const url = `${api}${path}`\n const form = new FormData()\n form.append(\n 'file',\n new Blob([new Uint8Array(zip)], { type: 'application/zip' }),\n `${manifest.slug}.zip`,\n )\n if (options.spaceId) form.set('spaceId', options.spaceId)\n\n const res = await request(url, {\n method: 'POST',\n headers: {\n authorization: `Bearer ${options.token}`,\n cookie: `better-auth.session_token=${options.token}`,\n ...(options.spaceId ? { 'x-space-id': options.spaceId } : {}),\n },\n body: form,\n timeoutMs: UPLOAD_TIMEOUT_MS,\n })\n\n const text = await res.text()\n if (!res.ok) {\n throw new Error(`Publish failed (${res.status}): ${text.slice(0, 500)}`)\n }\n const contentType = res.headers.get('content-type') || ''\n if (!contentType.includes('json')) {\n throw new Error(\n `Publish returned ${res.status} ${contentType || 'without a content type'} instead of JSON: ${text.slice(0, 200)}`,\n )\n }\n try {\n return JSON.parse(text) as {\n id: string\n slug: string\n version: number\n entryPointUrl?: string\n }\n } catch {\n throw new Error(`Publish returned invalid JSON: ${text.slice(0, 200)}`)\n }\n}\n","const DEFAULT_TIMEOUT_MS = 30_000\n\nexport type RequestOptions = RequestInit & { timeoutMs?: number }\n\n/**\n * fetch with a hard deadline and an error message that names the URL.\n *\n * undici's defaults bound connection and idle time, so a wedged API or proxy\n * can otherwise hold a CLI or a runner forever with nothing printed.\n */\nexport async function request(url: string, options: RequestOptions = {}): Promise<Response> {\n const { timeoutMs = DEFAULT_TIMEOUT_MS, ...init } = options\n try {\n return await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) })\n } catch (error) {\n throw new Error(`${url} ${describeFailure(error, timeoutMs)}`)\n }\n}\n\nfunction describeFailure(error: unknown, timeoutMs: number) {\n const name = (error as Error)?.name\n if (name === 'TimeoutError' || name === 'AbortError') {\n return `did not respond within ${Math.round(timeoutMs / 1000)}s`\n }\n // fetch reports every transport problem as \"fetch failed\"; the reason is on the cause.\n const cause = (error as { cause?: unknown })?.cause\n const detail =\n cause instanceof AggregateError\n ? cause.errors.map(codeOf).join(', ')\n : codeOf(cause) ?? (error as Error)?.message ?? String(error)\n return `failed: ${detail}`\n}\n\nfunction codeOf(error: unknown) {\n if (error instanceof Error) return (error as NodeJS.ErrnoException).code ?? error.message\n return String(error)\n}\n","import { spawn } from 'node:child_process'\nimport { mkdtemp, readdir, readFile, rm } from 'node:fs/promises'\nimport { hostname } from 'node:os'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport { request, type RequestOptions } from './fetch'\nimport { buildGame } from './pack'\nimport {\n buildCommand,\n installCommand,\n planPipeline,\n testCommand,\n} from './runner-pipeline'\nimport { VERSION } from './version'\n\nexport type RunnerStartOptions = {\n server: string\n key: string\n pass: string\n}\n\ntype RunnerJob = {\n deploymentId: string\n owner: string\n repo: string\n commitSha: string\n branch?: string | null\n rootDirectory?: string\n cloneToken: string\n}\n\nconst EXAMPLE =\n 'fzgame runner start --server http://localhost:3020 --key <key> --pass <pass>'\n\nconst CLAIM_TIMEOUT_MS = 30_000\nconst IDLE_SLEEP_MS = 2000\nconst MIN_BACKOFF_MS = 2000\nconst MAX_BACKOFF_MS = 30_000\nconst UPLOAD_TIMEOUT_MS = 120_000\nconst LOG_FLUSH_MS = 250\nconst LOG_MAX_CHARS = 8000\n\nexport async function startRunner(opts: RunnerStartOptions) {\n const server = (opts.server || '').replace(/\\/$/, '')\n const key = opts.key || ''\n const pass = opts.pass || ''\n if (!server || !key || !pass) {\n throw new Error(`Pass --server, --key, and --pass. Example: ${EXAMPLE}`)\n }\n\n const headers = {\n 'x-runner-key': key,\n 'x-runner-pass': pass,\n }\n\n const connected = await api(server, '/internal/ci-runners/connect', {\n method: 'POST',\n headers: { ...headers, 'Content-Type': 'application/json' },\n body: JSON.stringify({\n key,\n pass,\n hostname: hostname(),\n version: VERSION,\n }),\n })\n console.log(`Runner ${connected.name || key} connected to ${server}`)\n console.log('Waiting for queued git deploys (fetch → build → test → publish)')\n\n let stopping = false\n const beat = setInterval(() => {\n api(server, '/internal/ci-runners/heartbeat', {\n method: 'POST',\n headers,\n }).catch(() => undefined)\n }, 15_000)\n\n const stop = () => {\n if (stopping) return\n stopping = true\n clearInterval(beat)\n api(server, '/internal/ci-runners/disconnect', {\n method: 'POST',\n headers,\n })\n .catch(() => undefined)\n .finally(() => process.exit(0))\n }\n process.on('SIGINT', stop)\n process.on('SIGTERM', stop)\n\n let backoff = MIN_BACKOFF_MS\n const retry = async (message: string) => {\n console.error(`${message}; retrying in ${Math.round(backoff / 1000)}s`)\n await sleep(backoff)\n backoff = Math.min(backoff * 2, MAX_BACKOFF_MS)\n }\n\n while (!stopping) {\n let res: Response\n try {\n res = await request(`${server}/internal/ci-runners/jobs/next`, {\n headers,\n timeoutMs: CLAIM_TIMEOUT_MS,\n })\n } catch (error) {\n await retry(`claim failed: ${(error as Error).message}`)\n continue\n }\n backoff = MIN_BACKOFF_MS\n\n if (res.status === 204) {\n await sleep(IDLE_SLEEP_MS)\n continue\n }\n const text = await res.text()\n if (!res.ok) {\n await retry(`claim failed (${res.status}): ${text.slice(0, 200)}`)\n continue\n }\n if (!text) {\n await sleep(IDLE_SLEEP_MS)\n continue\n }\n\n let job: RunnerJob\n try {\n job = JSON.parse(text) as RunnerJob\n } catch {\n await retry(`claim returned invalid JSON: ${text.slice(0, 200)}`)\n continue\n }\n\n console.log(`Job ${job.deploymentId} ${job.owner}/${job.repo}@${job.commitSha.slice(0, 7)}`)\n try {\n await runJob(server, headers, job)\n console.log(`Job ${job.deploymentId} ready`)\n } catch (err) {\n const message = (err as Error).message\n console.error(`Job ${job.deploymentId} failed: ${message}`)\n await api(server, `/internal/ci-runners/jobs/${job.deploymentId}/fail`, {\n method: 'POST',\n headers: { ...headers, 'Content-Type': 'application/json' },\n body: JSON.stringify({ message }),\n }).catch(() => undefined)\n }\n }\n}\n\nasync function runJob(\n server: string,\n headers: Record<string, string>,\n job: RunnerJob,\n) {\n const work = await mkdtemp(join(tmpdir(), 'fzgame-run-'))\n\n // Build output arrives in many small chunks; coalesce them so a chatty build\n // does not turn into one HTTP request per chunk.\n let pending = ''\n let flushTimer: NodeJS.Timeout | undefined\n const flushLogs = async () => {\n if (flushTimer) {\n clearTimeout(flushTimer)\n flushTimer = undefined\n }\n if (!pending) return\n const chunk = pending\n pending = ''\n await api(server, `/internal/ci-runners/jobs/${job.deploymentId}/logs`, {\n method: 'POST',\n headers: { ...headers, 'Content-Type': 'application/json' },\n body: JSON.stringify({ chunk }),\n }).catch(() => undefined)\n }\n const log = (chunk: string) => {\n process.stdout.write(chunk)\n pending += chunk\n if (pending.length >= LOG_MAX_CHARS) {\n void flushLogs()\n return\n }\n if (!flushTimer) {\n flushTimer = setTimeout(() => void flushLogs(), LOG_FLUSH_MS)\n flushTimer.unref()\n }\n }\n const stage = async (name: string, status: string, error?: string) => {\n await flushLogs()\n await api(server, `/internal/ci-runners/jobs/${job.deploymentId}/stage`, {\n method: 'POST',\n headers: { ...headers, 'Content-Type': 'application/json' },\n body: JSON.stringify({ name, status, error }),\n })\n }\n\n try {\n await stage('fetch', 'running')\n log(`fetch ${job.owner}/${job.repo} ${job.commitSha}\\n`)\n await runCmd('git', ['init'], work, log)\n const origin = `https://x-access-token:${job.cloneToken}@github.com/${job.owner}/${job.repo}.git`\n await runCmd('git', ['remote', 'add', 'origin', origin], work, () => undefined)\n await runCmd('git', ['fetch', '--depth', '1', 'origin', job.commitSha], work, log)\n await runCmd('git', ['checkout', '--force', 'FETCH_HEAD'], work, log)\n await stage('fetch', 'ok')\n\n const packRoot = job.rootDirectory ? join(work, job.rootDirectory) : work\n const files = await readdir(packRoot)\n const scripts = await readScripts(packRoot)\n const planned = planPipeline(scripts)\n\n await stage('build', 'running')\n const install = installCommand(files)\n if (install) await runCmd(install[0], install[1], packRoot, log)\n const build = buildCommand(files, scripts)\n if (build) await runCmd(build[0], build[1], packRoot, log)\n await stage('build', 'ok')\n\n if (planned.includes('test')) {\n await stage('test', 'running')\n const test = testCommand(files, scripts)\n if (test) await runCmd(test[0], test[1], packRoot, log)\n await stage('test', 'ok')\n } else {\n await stage('test', 'skipped')\n }\n\n await stage('publish', 'running')\n const packed = await buildGame(packRoot)\n log(`packed ${packed.dest}\\n`)\n const zip = await readFile(packed.dest)\n const form = new FormData()\n form.append(\n 'file',\n new Blob([new Uint8Array(zip)], { type: 'application/zip' }),\n 'game.zip',\n )\n const published = await request(\n `${server}/internal/ci-runners/jobs/${job.deploymentId}/artifact`,\n { method: 'POST', headers, body: form, timeoutMs: UPLOAD_TIMEOUT_MS },\n )\n if (!published.ok) {\n throw new Error(`publish failed (${published.status}): ${await published.text()}`)\n }\n await stage('publish', 'ok')\n } finally {\n await flushLogs()\n await rm(work, { recursive: true, force: true })\n }\n}\n\nasync function readScripts(root: string) {\n try {\n const raw = JSON.parse(await readFile(join(root, 'package.json'), 'utf8')) as {\n scripts?: { build?: string; test?: string }\n }\n return raw.scripts ?? {}\n } catch {\n return null\n }\n}\n\nfunction runCmd(\n cmd: string,\n args: string[],\n cwd: string,\n onLog: (chunk: string) => void,\n) {\n return new Promise<void>((resolve, reject) => {\n // pnpm/npm/yarn are .cmd shims on Windows and only resolve through cmd.exe.\n const useShell = process.platform === 'win32'\n const child = spawn(cmd, useShell ? args.map(quoteForShell) : args, {\n cwd,\n env: process.env,\n shell: useShell,\n })\n child.stdout.on('data', (buf: Buffer) => onLog(buf.toString()))\n child.stderr.on('data', (buf: Buffer) => onLog(buf.toString()))\n child.on('error', reject)\n child.on('close', (code, signal) => {\n if (code === 0) resolve()\n else if (signal) reject(new Error(`${cmd} ${args.join(' ')} killed by ${signal}`))\n else reject(new Error(`${cmd} ${args.join(' ')} exited ${code}`))\n })\n })\n}\n\nfunction quoteForShell(arg: string) {\n return /[\\s\"&|<>^()]/.test(arg) ? `\"${arg.replace(/\"/g, '\"\"')}\"` : arg\n}\n\nasync function api(server: string, path: string, init: RequestOptions) {\n const res = await request(`${server}${path}`, init)\n const text = await res.text()\n if (!res.ok) throw new Error(`${path} failed (${res.status}): ${text.slice(0, 500)}`)\n if (!text) return {} as { name?: string }\n try {\n return JSON.parse(text) as { name?: string }\n } catch {\n throw new Error(`${path} returned invalid JSON: ${text.slice(0, 200)}`)\n }\n}\n\nfunction sleep(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n","export type PipelineStageName = 'fetch' | 'build' | 'test' | 'publish'\n\nexport function planPipeline(\n scripts: { build?: string; test?: string } | null,\n): PipelineStageName[] {\n const stages: PipelineStageName[] = ['fetch', 'build']\n if (scripts?.test) stages.push('test')\n stages.push('publish')\n return stages\n}\n\nexport function installCommand(\n files: string[],\n): [string, string[]] | null {\n if (!files.includes('package.json')) return null\n if (files.includes('pnpm-lock.yaml')) {\n return ['pnpm', ['install', '--frozen-lockfile']]\n }\n if (files.includes('yarn.lock')) {\n return ['yarn', ['install', '--frozen-lockfile']]\n }\n if (files.includes('package-lock.json')) return ['npm', ['ci']]\n return ['npm', ['install']]\n}\n\nexport function buildCommand(\n files: string[],\n scripts: { build?: string } | null,\n): [string, string[]] | null {\n if (!scripts?.build) return null\n const [manager] = installCommand(files.includes('package.json') ? files : ['package.json', ...files]) ?? [\n 'npm',\n [],\n ]\n return [manager, ['run', 'build']]\n}\n\nexport function testCommand(\n files: string[],\n scripts: { test?: string } | null,\n): [string, string[]] | null {\n if (!scripts?.test) return null\n const [manager] = installCommand(files.includes('package.json') ? files : ['package.json', ...files]) ?? [\n 'npm',\n [],\n ]\n return [manager, ['run', 'test']]\n}\n","import { readFileSync } from 'node:fs'\nimport { dirname, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\n// Resolves to the package root from both src/ (vitest) and dist/ (the CJS bundle).\nconst here = dirname(fileURLToPath(import.meta.url))\n\nfunction readVersion(): string {\n try {\n const raw = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8')) as {\n version?: string\n }\n return raw.version ?? '0.0.0'\n } catch {\n return '0.0.0'\n }\n}\n\nexport const VERSION = readVersion()\n","import { loadEnvFile } from './env'\nimport { buildProgram } from './program'\n\nloadEnvFile()\n\nbuildProgram()\n .parseAsync(process.argv)\n .catch((error: unknown) => {\n console.error(`error: ${error instanceof Error ? error.message : String(error)}`)\n process.exit(1)\n })\n"],"mappings":";;;;AAKA,IAAM,mBAAmB,MACvB,OAAO,aAAa,cAChB,IAAI,IAAI,QAAQ,UAAU,EAAE,EAAE,OAC7B,SAAS,iBAAiB,SAAS,cAAc,QAAQ,YAAY,MAAM,WAC1E,SAAS,cAAc,MACvB,IAAI,IAAI,WAAW,SAAS,OAAO,EAAE;AAEtC,IAAM,gBAAgC,iCAAiB;;;ACZ9D,qBAA6B;AAC7B,uBAAqB;AAQd,SAAS,YAAY,MAAc,QAAQ,IAAI,GAAY;AAChE,MAAI;AACJ,MAAI;AACF,cAAM,iCAAa,uBAAK,KAAK,MAAM,GAAG,MAAM;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,aAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,UAAM,QAAQ,6DAA6D,KAAK,IAAI;AACpF,QAAI,CAAC,MAAO;AACZ,UAAM,MAAM,MAAM,CAAC;AACnB,QAAI,QAAQ,MAAM,CAAC;AACnB,UAAM,SACH,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG,KAC3C,MAAM,WAAW,GAAG,KAAK,MAAM,SAAS,GAAG;AAC9C,QAAI,OAAQ,SAAQ,MAAM,MAAM,GAAG,EAAE;AACrC,QAAI,QAAQ,IAAI,GAAG,MAAM,OAAW,SAAQ,IAAI,GAAG,IAAI;AAAA,EACzD;AACA,SAAO;AACT;;;AC7BA,uBAAwB;;;ACAxB,IAAAA,kBAAiC;AACjC,IAAAC,mBAA+B;AAC/B,uBAAwE;AACxE,IAAAC,oBAA4C;;;ACH5C,IAAAC,kBAA6B;AAC7B,yBAA8B;AAC9B,IAAAC,oBAA8B;AAE9B,IAAMC,eAAU,kCAAc,aAAe;AAEtC,IAAM,WAAW;AAcxB,SAAS,cAAc,QAAsD;AAC3E,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,cAAc,OAAO,MAAM,KAAK,cAAc,OAAO,OAAO;AACrE;AASO,SAAS,iBAAyB;AACvC,QAAM,eAAeA,SAAQ,QAAQ,0BAA0B;AAC/D,QAAM,WAAW,KAAK,UAAM,8BAAa,cAAc,MAAM,CAAC;AAC9D,QAAM,QACJ,SAAS,UAAU,cAAc,SAAS,UAAU,GAAG,CAAC,KAAK,SAAS,QAAQ;AAChF,aAAO,4BAAK,2BAAQ,YAAY,GAAG,KAAK;AAC1C;;;ACvCA,sBAA0D;AAC1D,IAAAC,oBAAwC;;;AC8BxC,IAAM,UAAU;AAET,IAAM,gBAA6B;AAAA,EACxC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,SAAS;AACX;AAaO,SAAS,qBACd,UASyB;AACzB,SAAO;AAAA,IACL,YAAY,SAAS,cAAc,EAAE,MAAM,UAAU,YAAY,CAAC,EAAE;AAAA,IACpE,GAAI,SAAS,eAAe,EAAE,cAAc,SAAS,aAAa,IAAI,CAAC;AAAA,IACvE,GAAI,SAAS,eAAe,EAAE,cAAc,SAAS,aAAa,IAAI,CAAC;AAAA,IACvE,GAAI,SAAS,iBAAiB,EAAE,gBAAgB,SAAS,eAAe,IAAI,CAAC;AAAA,IAC7E,GAAI,SAAS,iBAAiB,EAAE,gBAAgB,SAAS,eAAe,IAAI,CAAC;AAAA,IAC7E,GAAI,SAAS,KAAK,SAAS,EAAE,QAAQ,SAAS,KAAK,OAAO,IAAI,CAAC;AAAA,IAC/D,GAAI,SAAS,KAAK,SAAS,EAAE,SAAS,SAAS,KAAK,OAAO,IAAI,CAAC;AAAA,IAChE,GAAI,SAAS,KAAK,QAAQ,EAAE,OAAO,SAAS,KAAK,MAAM,IAAI,CAAC;AAAA,EAC9D;AACF;AAEO,SAAS,oBAAoB,OAAgC;AAClE,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AAC/D,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AACA,QAAM,MAAM;AACZ,QAAM,OAAO,UAAU,GAAG;AAC1B,QAAM,aACJ,WAAW,IAAI,YAAY,YAAY,KACvC,WAAW,IAAI,cAAc,cAAc;AAC7C,QAAM,WAA2B;AAAA,IAC/B;AAAA,IACA,OAAO,WAAW,IAAI,KAAK;AAAA,IAC3B;AAAA,IACA,cAAc,WAAW,IAAI,cAAc,cAAc;AAAA,IACzD,cAAc,WAAW,IAAI,cAAc,cAAc;AAAA,IACzD,gBAAgB,WAAW,IAAI,gBAAgB,gBAAgB;AAAA,IAC/D,gBAAgB,WAAW,IAAI,gBAAgB,gBAAgB;AAAA,IAC/D,MAAM,KAAK;AAAA,IACX,MAAM,KAAK;AAAA,IACX,SAAS,KAAK;AAAA,IACd,aAAa,KAAK;AAAA,EACpB;AACA,WAAS,eAAe,qBAAqB,QAAQ;AACrD,SAAO;AACT;AAEA,SAAS,UAAU,KAA0C;AAC3D,QAAM,SACJ,IAAI,QAAQ,OAAO,IAAI,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,IAAI,IAC9D,IAAI,OACL,CAAC;AACP,QAAM,OAAO,WAAW,OAAO,MAAM,IAAI,IAAI;AAC7C,QAAM,OAAO,WAAW,OAAO,MAAM,IAAI,IAAI;AAC7C,MAAI,CAAC,QAAQ,KAAK,KAAK,EAAE,SAAS,GAAG;AACnC,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AACA,MAAI,CAAC,QAAQ,CAAC,QAAQ,KAAK,IAAI,GAAG;AAChC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,aAAa,OAAO,WAAW,IAAI;AACzC,QAAM,UACJ,eAAe,UAAa,eAAe,OAAO,IAAI;AACxD,MAAI,OAAO,YAAY,YAAY,CAAC,OAAO,UAAU,OAAO,KAAK,UAAU,GAAG;AAC5E,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AACA,QAAM,SAAS,eAAe,OAAO,WAAW,OAAO,MAAM;AAC7D,MAAI,UAAU,CAAC,gBAAgB,KAAK,MAAM,GAAG;AAC3C,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AACA,QAAM,QAAQ,eAAe,OAAO,KAAK;AACzC,QAAM,cAAc,eAAe,OAAO,eAAe,IAAI,WAAW;AACxE,QAAM,SAAS,eAAe,OAAO,MAAM;AAC3C,SAAO;AAAA,IACL,MAAM,KAAK,KAAK;AAAA,IAChB;AAAA,IACA;AAAA,IACA,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,IACzB,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;AAAA,IACrC,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,IAC3B,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,EAC7B;AACF;AAEA,SAAS,WAAW,OAA6B;AAC/C,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO,EAAE,GAAG,cAAc;AACrE,MAAI,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AACrD,UAAM,IAAI,MAAM,qCAAqC;AAAA,EACvD;AACA,QAAM,MAAM;AACZ,SAAO;AAAA,IACL,MAAM,SAAS,IAAI,MAAM,cAAc,IAAI;AAAA,IAC3C,QAAQ,SAAS,IAAI,QAAQ,cAAc,MAAM;AAAA,IACjD,SAAS,SAAS,IAAI,SAAS,cAAc,OAAO;AAAA,EACtD;AACF;AAEA,SAAS,SAAS,OAAgB,UAA0B;AAC1D,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,KAAK,GAAG;AAC9C,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,QAAM,OAAO,MAAM,QAAQ,OAAO,GAAG,EAAE,QAAQ,SAAS,EAAE;AAC1D,MAAI,KAAK,WAAW,GAAG,KAAK,KAAK,SAAS,IAAI,GAAG;AAC/C,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AACA,SAAO;AACT;AAEA,SAAS,WAAW,OAAgB,OAAoD;AACtF,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AACrD,UAAM,IAAI,MAAM,eAAe,KAAK,+BAA+B;AAAA,EACrE;AACA,SAAO;AACT;AAEA,SAAS,cAAc,QAAuC;AAC5D,aAAW,SAAS,QAAQ;AAC1B,QAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAG,QAAO;AAAA,EACxD;AACA,SAAO;AACT;AAEA,SAAS,eAAe,OAAoC;AAC1D,MAAI,UAAU,UAAa,UAAU,QAAQ,UAAU,GAAI,QAAO;AAClE,SAAO,OAAO,KAAK;AACrB;;;ACrLA,uBAA+B;AAI/B,IAAM,WAAW;AACjB,IAAM,WAAW;AAEjB,IAAM,YAAY,IAAI,YAAY,GAAG;AACrC,SAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,GAAG,IAAK,KAAI,IAAI,IAAI,aAAc,MAAM,IAAK,MAAM;AACvE,YAAU,CAAC,IAAI;AACjB;AAEA,SAAS,MAAM,KAAqB;AAClC,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,QAAI,WAAW,IAAI,IAAI,CAAC,KAAM,GAAI,IAAM,MAAM;AAAA,EAChD;AACA,UAAQ,IAAI,gBAAgB;AAC9B;AAEO,SAAS,SAAS,OAAgD;AACvE,QAAM,UAMD,CAAC;AACN,QAAM,QAAkB,CAAC;AACzB,MAAI,SAAS;AAEb,aAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,KAAK,GAAG;AACnD,UAAM,OAAO,OAAO,SAAS,OAAO,IAAI,UAAU,OAAO,KAAK,OAAO;AACrE,UAAM,iBAAa,iCAAe,IAAI;AACtC,UAAM,MAAM,MAAM,IAAI;AACtB,UAAM,UAAU,OAAO,KAAK,KAAK,QAAQ,OAAO,GAAG,GAAG,MAAM;AAC5D,UAAM,QAAQ,OAAO,MAAM,EAAE;AAC7B,UAAM,cAAc,UAAY,CAAC;AACjC,UAAM,cAAc,IAAI,CAAC;AACzB,UAAM,cAAc,GAAG,CAAC;AACxB,UAAM,cAAc,UAAU,EAAE;AAChC,UAAM,cAAc,UAAU,EAAE;AAChC,UAAM,cAAc,KAAK,EAAE;AAC3B,UAAM,cAAc,WAAW,QAAQ,EAAE;AACzC,UAAM,cAAc,KAAK,QAAQ,EAAE;AACnC,UAAM,cAAc,QAAQ,QAAQ,EAAE;AACtC,UAAM,KAAK,OAAO,SAAS,UAAU;AACrC,YAAQ,KAAK,EAAE,MAAM,SAAS,MAAM,YAAY,KAAK,OAAO,CAAC;AAC7D,cAAU,MAAM,SAAS,QAAQ,SAAS,WAAW;AAAA,EACvD;AAEA,QAAM,eAAe;AACrB,aAAW,UAAU,SAAS;AAC5B,UAAM,UAAU,OAAO,MAAM,EAAE;AAC/B,YAAQ,cAAc,UAAY,CAAC;AACnC,YAAQ,cAAc,IAAI,CAAC;AAC3B,YAAQ,cAAc,IAAI,CAAC;AAC3B,YAAQ,cAAc,GAAG,EAAE;AAC3B,YAAQ,cAAc,UAAU,EAAE;AAClC,YAAQ,cAAc,UAAU,EAAE;AAClC,YAAQ,cAAc,OAAO,KAAK,EAAE;AACpC,YAAQ,cAAc,OAAO,WAAW,QAAQ,EAAE;AAClD,YAAQ,cAAc,OAAO,KAAK,QAAQ,EAAE;AAC5C,YAAQ,cAAc,OAAO,KAAK,QAAQ,EAAE;AAC5C,YAAQ,cAAc,OAAO,QAAQ,EAAE;AACvC,UAAM,KAAK,SAAS,OAAO,IAAI;AAC/B,cAAU,QAAQ,SAAS,OAAO,KAAK;AAAA,EACzC;AAEA,QAAM,OAAO,OAAO,MAAM,EAAE;AAC5B,OAAK,cAAc,WAAY,CAAC;AAChC,OAAK,cAAc,QAAQ,QAAQ,CAAC;AACpC,OAAK,cAAc,QAAQ,QAAQ,EAAE;AACrC,OAAK,cAAc,SAAS,cAAc,EAAE;AAC5C,OAAK,cAAc,cAAc,EAAE;AACnC,QAAM,KAAK,IAAI;AACf,SAAO,OAAO,OAAO,KAAK;AAC5B;;;AFpEA,eAAsB,aAAa,MAAuC;AACxE,QAAM,mBAAe,wBAAK,MAAM,aAAa;AAC7C,MAAI;AACJ,MAAI;AACF,WAAO,UAAM,0BAAS,cAAc,MAAM;AAAA,EAC5C,SAAS,OAAO;AACd,QAAK,MAAgC,SAAS,UAAU;AACtD,YAAM,IAAI,MAAM,qBAAqB,IAAI,oCAAoC;AAAA,IAC/E;AACA,UAAM;AAAA,EACR;AACA,MAAI;AACF,WAAO,oBAAoB,KAAK,MAAM,IAAI,CAAC;AAAA,EAC7C,SAAS,OAAO;AACd,UAAM,IAAI,MAAM,0BAA0B,IAAI,KAAM,MAAgB,OAAO,EAAE;AAAA,EAC/E;AACF;AAEA,eAAsB,iBACpB,MACA,UAAuB,CAAC,GACS;AACjC,QAAM,WAAW,MAAM,aAAa,IAAI;AACxC,QAAM,QAAgC;AAAA,IACpC,eAAe,UAAM,8BAAS,wBAAK,MAAM,aAAa,CAAC;AAAA,EACzD;AAEA,aAAW,OAAO,CAAC,QAAQ,UAAU,SAAS,GAAG;AAC/C,UAAM,UAAM,wBAAK,MAAM,GAAG;AAC1B,QAAI,CAAE,MAAM,OAAO,GAAG,GAAI;AACxB,YAAM,IAAI,MAAM,+BAA+B,GAAG,GAAG;AAAA,IACvD;AACA,UAAM,KAAK,KAAK,OAAO,SAAS;AAC9B,YAAM,UAAM,4BAAS,MAAM,IAAI,EAAE,QAAQ,OAAO,GAAG;AACnD,YAAM,GAAG,IAAI,UAAM,0BAAS,IAAI;AAAA,IAClC,CAAC;AAAA,EACH;AAEA,aAAW,QAAQ,CAAC,SAAS,MAAM,MAAM,SAAS,MAAM,QAAQ,SAAS,MAAM,OAAO,GAAG;AACvF,QAAI,CAAC,MAAM,IAAI,EAAG,OAAM,IAAI,MAAM,gCAAgC,IAAI,EAAE;AAAA,EAC1E;AAEA,QAAM,UAAU,OAAO,OAAO;AAC9B,SAAO;AACT;AAEA,eAAsB,UAAU,MAAc,SAAkB,UAAuB,CAAC,GAAG;AACzF,QAAM,WAAW,MAAM,aAAa,IAAI;AACxC,QAAM,QAAQ,MAAM,iBAAiB,MAAM,OAAO;AAClD,QAAM,MAAM,SAAS,KAAK;AAC1B,QAAM,OAAO,eAAW,wBAAK,MAAM,QAAQ,GAAG,SAAS,IAAI,KAAK,SAAS,OAAO,MAAM;AACtF,YAAM,2BAAM,2BAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,YAAM,2BAAU,MAAM,GAAG;AACzB,SAAO,EAAE,MAAM,UAAU,OAAO,OAAO,KAAK,KAAK,EAAE;AACrD;AAEA,eAAe,UAAU,OAA+B,SAAsB;AAC5E,MAAI,MAAM,oBAAoB,EAAG;AACjC,MAAI,QAAQ,YAAY,KAAM;AAE9B,MAAI;AACJ,MAAI,QAAQ,SAAS;AACnB,cAAU,QAAQ;AAAA,EACpB,OAAO;AACL,QAAI;AACF,gBAAU,eAAe;AAAA,IAC3B,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR,iEAAkE,MAAgB,OAAO,MAAM,QAAQ;AAAA,MACzG;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAE,MAAM,OAAO,OAAO,GAAI;AAC5B,UAAM,IAAI,MAAM,mCAAmC,OAAO,oBAAoB,QAAQ,EAAE;AAAA,EAC1F;AACA,QAAM,oBAAoB,IAAI,UAAM,0BAAS,OAAO;AACtD;AAEA,eAAe,KAAK,KAAa,OAAwC;AACvE,QAAM,UAAU,UAAM,yBAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAC1D,aAAW,SAAS,SAAS;AAC3B,QAAI,MAAM,SAAS,YAAa;AAChC,UAAM,UAAM,wBAAK,KAAK,MAAM,IAAI;AAChC,QAAI,MAAM,YAAY,EAAG,OAAM,KAAK,KAAK,KAAK;AAAA,QACzC,OAAM,MAAM,GAAG;AAAA,EACtB;AACF;AAEA,eAAe,OAAO,MAAc;AAClC,MAAI;AACF,cAAM,sBAAK,IAAI;AACf,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AFnGA,IAAM,eAAe;AAErB,SAAS,UAAU,KAAqB;AACtC,MAAI,UAAU,+BAA+B,GAAG;AAChD,MAAI,UAAU,gCAAgC,GAAG;AACjD,MAAI,UAAU,gCAAgC,sBAAsB;AACtE;AAEA,IAAM,OAA+B;AAAA,EACnC,SAAS;AAAA,EACT,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AACV;AAiBA,eAAsB,eACpB,MACA,MACA,UAA4B,CAAC,GAC7B;AACA,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,WAAW,MAAM,aAAa,IAAI;AACxC,QAAM,WAAW,oBAAI,IAAqB;AAE1C,QAAM,aAAS,+BAAa,OAAO,KAAK,QAAQ;AAC9C,cAAU,GAAG;AACb,yBAAqB,GAAG;AACxB,QAAI,IAAI,WAAW,WAAW;AAC5B,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI;AACR;AAAA,IACF;AACA,QAAI;AACF,YAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,oBAAoB,IAAI,EAAE;AAC9D,UAAI,IAAI,aAAa,WAAW;AAC9B,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI,IAAI,KAAK,UAAU,EAAE,IAAI,MAAM,MAAM,SAAS,MAAM,KAAK,CAAC,CAAC;AAC/D;AAAA,MACF;AACA,UACE,IAAI,SAAS,WAAW,gBAAgB,KACxC,IAAI,SAAS,WAAW,qBAAqB,GAC7C;AACA,cAAM,cAAc,KAAK,KAAK,KAAK,UAAU,QAAQ;AACrD;AAAA,MACF;AACA,UAAI,IAAI,aAAa,mBAAmB;AACtC,cAAM,SAAS,GAAG;AAClB;AAAA,MACF;AACA,UAAI,IAAI,aAAa,OAAO,IAAI,aAAa,eAAe;AAC1D,YAAI,UAAU,KAAK,EAAE,gBAAgB,2BAA2B,CAAC;AACjE,YAAI,IAAI,aAAa,cAAc,KAAK,MAAM,IAAI,GAAG,SAAS,IAAI,CAAC;AACnE;AAAA,MACF;AACA,YAAM,YAAY,MAAM,IAAI,UAAU,GAAG;AAAA,IAC3C,SAAS,OAAO;AACd,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,EAAE,SAAU,MAAgB,QAAQ,CAAC,CAAC;AAAA,IAC/D;AAAA,EACF,CAAC;AAED,QAAM,IAAI,QAAc,CAAC,cAAc,WAAW;AAChD,WAAO,KAAK,SAAS,CAAC,UAAiC;AACrD;AAAA,QACE,MAAM,SAAS,eACX,IAAI,MAAM,QAAQ,IAAI,sDAAsD,IAC5E,IAAI,MAAM,mBAAmB,IAAI,IAAI,IAAI,KAAK,MAAM,QAAQ,MAAM,OAAO,IAAI;AAAA,MACnF;AAAA,IACF,CAAC;AACD,WAAO,OAAO,MAAM,MAAM,MAAM,aAAa,CAAC;AAAA,EAChD,CAAC;AACD,QAAM,UAAU,OAAO,QAAQ;AAC/B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM,OAAO,YAAY,YAAY,UAAU,QAAQ,OAAO;AAAA,IAC9D;AAAA,EACF;AACF;AAEA,SAAS,qBAAqB,KAAqB;AAEjD,MAAI,UAAU,+BAA+B,GAAG;AAChD,MAAI,UAAU,gCAAgC,wCAAwC;AACtF,MAAI,UAAU,gCAAgC,+CAA+C;AAG7F,MAAI,UAAU,2BAA2B,mBAAmB;AAG5D,MAAI,aAAa,iBAAiB;AACpC;AAEA,eAAe,SAAS,KAAqB;AAC3C,MAAI;AACF,UAAM,OAAO,UAAM,2BAAS,eAAe,CAAC;AAC5C,QAAI,UAAU,KAAK,EAAE,gBAAgB,wCAAwC,CAAC;AAC9E,QAAI,IAAI,IAAI;AAAA,EACd,SAAS,OAAO;AACd,QAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,QAAI,IAAI,6BAA8B,MAAgB,OAAO;AAAA,EAAK,QAAQ,EAAE;AAAA,EAC9E;AACF;AAEA,eAAe,YAAY,MAAc,UAAkB,KAAqB;AAC9E,QAAM,MAAM,mBAAmB,QAAQ,EAAE,QAAQ,OAAO,EAAE,KAAK;AAC/D,QAAM,UAAM,2BAAQ,MAAM,GAAG;AAC7B,QAAM,cAAU,2BAAQ,IAAI;AAC5B,MAAI,QAAQ,WAAW,CAAC,IAAI,WAAW,UAAU,qBAAG,GAAG;AACrD,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI,WAAW;AACnB;AAAA,EACF;AACA,MAAI;AACF,UAAM,OAAO,UAAM,uBAAK,GAAG;AAC3B,UAAM,OAAO,KAAK,YAAY,QAAI,wBAAK,KAAK,YAAY,IAAI;AAC5D,QAAI,UAAU,KAAK;AAAA,MACjB,gBAAgB,SAAK,2BAAQ,IAAI,CAAC,KAAK;AAAA,IACzC,CAAC;AACD,0CAAiB,IAAI,EAAE,KAAK,GAAG;AAAA,EACjC,QAAQ;AACN,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI,WAAW;AAAA,EACrB;AACF;AAEA,eAAe,cACb,KACA,KACA,KACA,UACA,UACA;AACA,MAAI,UAAU,gBAAgB,kBAAkB;AAChD,MAAI,UAAU,+BAA+B,GAAG;AAChD,MAAI,UAAU,gCAAgC,cAAc;AAC5D,MAAI,UAAU,gCAAgC,sBAAsB;AACpE,MAAI,IAAI,WAAW,WAAW;AAC5B,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI;AACR;AAAA,EACF;AAEA,MACE,IAAI,WAAW,UACd,IAAI,SAAS,WAAW,sBAAsB,KAC7C,IAAI,SAAS,WAAW,2BAA2B,IACrD;AACA,QAAI;AAAA,MACF,KAAK,UAAU;AAAA,QACb,IAAI,IAAI,SAAS,MAAM,GAAG,EAAE,IAAI;AAAA,QAChC,MAAM,SAAS;AAAA,QACf,QAAQ;AAAA,QACR,WAAW,CAAC;AAAA,QACZ,MAAM,EAAE,MAAM,SAAS,MAAM,cAAc,SAAS,gBAAgB,CAAC,EAAE;AAAA,MACzE,CAAC;AAAA,IACH;AACA;AAAA,EACF;AAEA,MAAI,IAAI,WAAW,UAAU,IAAI,aAAa,2BAA2B;AACvE,UAAM,OAAO,MAAM,SAAS,GAAG;AAC/B,UAAM,WAAW,CAAC,GAAG,SAAS,OAAO,CAAC,EAAE;AAAA,MACtC,CAAC,MAAM,EAAE,mBAAmB,KAAK,kBAAkB,EAAE,WAAW;AAAA,IAClE;AACA,QAAI,UAAU;AACZ,UAAI,IAAI,KAAK,UAAU,QAAQ,CAAC;AAChC;AAAA,IACF;AACA,UAAM,UAAmB;AAAA,MACvB,IAAI,OAAO,SAAS,OAAO,CAAC;AAAA,MAC5B,OAAO,aAAa,SAAS,OAAO,CAAC;AAAA,MACrC,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,UAAU,EAAE,UAAU,CAAC,EAAE;AAAA,MACzB,gBAAgB,OAAO,KAAK,kBAAkB,cAAc;AAAA,IAC9D;AACA,aAAS,IAAI,QAAQ,OAAO,OAAO;AACnC,QAAI,IAAI,KAAK,UAAU,OAAO,CAAC;AAC/B;AAAA,EACF;AAEA,QAAM,YAAY,IAAI,SAAS,MAAM,6CAA6C;AAClF,MAAI,IAAI,WAAW,UAAU,WAAW;AACtC,UAAM,UAAU,SAAS,IAAI,UAAU,CAAC,CAAE;AAC1C,QAAI,CAAC,SAAS;AACZ,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,KAAK,UAAU,EAAE,SAAS,oBAAoB,CAAC,CAAC;AACxD;AAAA,IACF;AACA,UAAM,OAAO,MAAM,SAAS,GAAG;AAC/B;AAAA,MACE;AAAA,MACA,OAAO,KAAK,QAAQ,EAAE;AAAA,MACtB,KAAK;AAAA,IACP;AACA,QAAI,IAAI,KAAK,UAAU,OAAO,CAAC;AAC/B;AAAA,EACF;AAEA,QAAM,YAAY,IAAI,SAAS,MAAM,iDAAiD;AACtF,MAAI,IAAI,WAAW,SAAS,WAAW;AACrC,UAAM,UAAU,SAAS,IAAI,UAAU,CAAC,CAAE;AAC1C,QAAI,CAAC,SAAS;AACZ,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,KAAK,UAAU,EAAE,SAAS,oBAAoB,CAAC,CAAC;AACxD;AAAA,IACF;AACA,UAAM,OAAO,MAAM,SAAS,GAAG;AAC/B,YAAQ,SAAS,WAAW,KAAK,SAAS;AAC1C,QAAI,IAAI,KAAK,UAAU,OAAO,CAAC;AAC/B;AAAA,EACF;AAEA,QAAM,WAAW,IAAI,SAAS,MAAM,uCAAuC;AAC3E,MAAI,IAAI,WAAW,SAAS,UAAU;AACpC,UAAM,UAAU,SAAS,IAAI,SAAS,CAAC,CAAE;AACzC,QAAI,CAAC,SAAS;AACZ,UAAI,UAAU,GAAG;AACjB,UAAI,IAAI,KAAK,UAAU,EAAE,SAAS,oBAAoB,CAAC,CAAC;AACxD;AAAA,IACF;AACA,QAAI,IAAI,KAAK,UAAU,OAAO,CAAC;AAC/B;AAAA,EACF;AAEA,MAAI,UAAU,GAAG;AACjB,MAAI,IAAI,KAAK,UAAU,EAAE,SAAS,YAAY,CAAC,CAAC;AAClD;AAEA,SAAS,aAAa,SAAkB,MAAc,SAAmC;AACvF,MAAI,SAAS,QAAS,SAAQ,SAAS;AACvC,MAAI,SAAS,QAAS,SAAQ,SAAS;AACvC,MAAI,SAAS,QAAS,SAAQ,SAAS;AACvC,MAAI,SAAS,UAAU;AACrB,YAAQ,SAAS;AACjB,QAAI,OAAO,SAAS,UAAU,SAAU,SAAQ,QAAQ,QAAQ;AAChE,QAAI,SAAS,OAAQ,SAAQ,SAAS,QAAQ;AAAA,EAChD;AACA,MAAI,SAAS,QAAS,SAAQ,SAAS;AACvC,MAAI,SAAS,WAAW,OAAO,SAAS,UAAU,SAAU,SAAQ,QAAQ,QAAQ;AACpF,MAAI,SAAS,WAAY,SAAQ,SAAS,WAAW,SAAS,SAAS;AACzE;AAEA,SAAS,SAAS,KAAwD;AACxE,SAAO,IAAI,QAAQ,CAAC,aAAa,WAAW;AAC1C,UAAM,SAAmB,CAAC;AAC1B,QAAI,GAAG,QAAQ,CAAC,MAAM,OAAO,KAAK,CAAW,CAAC;AAC9C,QAAI,GAAG,OAAO,MAAM;AAClB,UAAI,OAAO,WAAW,EAAG,QAAO,YAAY,CAAC,CAAC;AAC9C,UAAI;AACF,oBAAY,KAAK,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM,CAAC,CAAC;AAAA,MAChE,SAAS,OAAO;AACd,eAAO,KAAK;AAAA,MACd;AAAA,IACF,CAAC;AACD,QAAI,GAAG,SAAS,MAAM;AAAA,EACxB,CAAC;AACH;AAEA,SAAS,cAAc,KAAsB,MAAc,MAAc;AACvE,MAAI,IAAI,QAAQ,KAAM,QAAO,UAAU,IAAI,QAAQ,IAAI;AACvD,QAAM,QAAQ,SAAS,aAAa,SAAS,OAAO,eAAe;AACnE,SAAO,UAAU,KAAK,IAAI,IAAI;AAChC;AAEA,SAAS,aAAa,QAAgB,MAAc;AAClD,QAAM,MAAM,OAAO,mBAAmB,iBAAiB,CAAC;AACxD,QAAM,OAAO,8EAA8E,mBAAmB,MAAM,CAAC,IAAI,GAAG;AAC5H,QAAM,UAAU,iCAAiC,GAAG;AACpD,QAAM,SAAS,yDAAyD,mBAAmB,MAAM,CAAC,IAAI,GAAG;AACzG,SAAO;AAAA;AAAA,uCAE8B,IAAI;AAAA;AAAA,UAEjC,IAAI;AAAA;AAAA,kBAEI,IAAI;AAAA,kBACJ,OAAO;AAAA,kBACP,MAAM;AAAA;AAAA;AAGxB;;;AKtTA,IAAAC,mBAA8D;AAC9D,IAAAC,oBAAsD;AACtD,sBAA8B;AAE9B,IAAM,WAAO,+BAAQ,+BAAc,aAAe,CAAC;AACnD,IAAM,kBAAc,wBAAK,MAAM,MAAM,WAAW,eAAe;AAI/D,eAAsB,SAAS,WAAmB,UAAuB,CAAC,GAAG;AAC3E,QAAM,WAAO,2BAAQ,SAAS;AAC9B,QAAM,WAAW,UAAM,uBAAK,WAAW,EAAE;AAAA,IACvC,CAAC,SAAS,KAAK,YAAY;AAAA,IAC3B,MAAM;AAAA,EACR;AACA,MAAI,CAAC,UAAU;AACb,UAAM,IAAI;AAAA,MACR,4BAA4B,WAAW;AAAA,IACzC;AAAA,EACF;AAEA,YAAM,wBAAM,MAAM,EAAE,WAAW,KAAK,CAAC;AACrC,QAAM,YAAY,UAAM,0BAAQ,IAAI,GAAG,OAAO,CAAC,SAAS,SAAS,eAAe,SAAS,MAAM;AAC/F,MAAI,SAAS,SAAS,KAAK,CAAC,QAAQ,OAAO;AACzC,UAAM,IAAI;AAAA,MACR,GAAG,IAAI,kBAAkB,SAAS,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,GAAG,SAAS,SAAS,IAAI,UAAU,EAAE;AAAA,IAC/F;AAAA,EACF;AAEA,QAAM,eAAe,UAAM,2BAAK,wBAAK,MAAM,YAAY,CAAC,EAAE;AAAA,IACxD,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AACA,YAAM,qBAAG,aAAa,MAAM;AAAA,IAC1B,WAAW;AAAA,IACX,QAAQ,CAAC,WAAW,KAAK,MAAM,KAAK,EAAE,gBAAgB,OAAO,SAAS,GAAG,qBAAG,YAAY;AAAA,EAC1F,CAAC;AACD,QAAM,gBAAgB,IAAI;AAE1B,QAAM,QAAkB,CAAC;AACzB,QAAMC,MAAK,MAAM,CAAC,SAAS;AACzB,UAAM,SAAK,4BAAS,MAAM,IAAI,EAAE,MAAM,qBAAG,EAAE,KAAK,GAAG,CAAC;AAAA,EACtD,CAAC;AACD,QAAM,KAAK;AACX,SAAO,EAAE,KAAK,MAAM,MAAM;AAC5B;AAGA,SAAS,KAAK,QAAgB;AAC5B,QAAM,OAAO,OAAO,MAAM,qBAAG,EAAE,IAAI,KAAK;AACxC,SAAO,SAAS,UAAU,SAAS,kBAAkB,SAAS;AAChE;AAEA,eAAe,gBAAgB,MAAc;AAC3C,QAAM,WAAO,wBAAK,MAAM,YAAY;AACpC,MAAI,UAAU;AACd,MAAI;AACF,cAAU,UAAM,2BAAS,MAAM,MAAM;AAAA,EACvC,QAAQ;AAAA,EAER;AACA,MAAI,4BAA4B,KAAK,OAAO,EAAG;AAC/C,QAAM,OAAO,WAAW,CAAC,QAAQ,SAAS,IAAI,IAAI,GAAG,OAAO;AAAA,IAAO;AACnE,YAAM,4BAAU,MAAM,GAAG,IAAI;AAAA,CAAS;AACxC;AAEA,eAAeA,MAAK,KAAa,OAA+B;AAC9D,aAAW,SAAS,UAAM,0BAAQ,KAAK,EAAE,eAAe,KAAK,CAAC,GAAG;AAC/D,QAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS,OAAQ;AAC5D,UAAM,UAAM,wBAAK,KAAK,MAAM,IAAI;AAChC,QAAI,MAAM,YAAY,EAAG,OAAMA,MAAK,KAAK,KAAK;AAAA,QACzC,OAAM,GAAG;AAAA,EAChB;AACF;;;ACzEA,IAAAC,mBAAyB;;;ACAzB,IAAM,qBAAqB;AAU3B,eAAsB,QAAQ,KAAa,UAA0B,CAAC,GAAsB;AAC1F,QAAM,EAAE,YAAY,oBAAoB,GAAG,KAAK,IAAI;AACpD,MAAI;AACF,WAAO,MAAM,MAAM,KAAK,EAAE,GAAG,MAAM,QAAQ,YAAY,QAAQ,SAAS,EAAE,CAAC;AAAA,EAC7E,SAAS,OAAO;AACd,UAAM,IAAI,MAAM,GAAG,GAAG,IAAI,gBAAgB,OAAO,SAAS,CAAC,EAAE;AAAA,EAC/D;AACF;AAEA,SAAS,gBAAgB,OAAgB,WAAmB;AAC1D,QAAM,OAAQ,OAAiB;AAC/B,MAAI,SAAS,kBAAkB,SAAS,cAAc;AACpD,WAAO,0BAA0B,KAAK,MAAM,YAAY,GAAI,CAAC;AAAA,EAC/D;AAEA,QAAM,QAAS,OAA+B;AAC9C,QAAM,SACJ,iBAAiB,iBACb,MAAM,OAAO,IAAI,MAAM,EAAE,KAAK,IAAI,IAClC,OAAO,KAAK,KAAM,OAAiB,WAAW,OAAO,KAAK;AAChE,SAAO,WAAW,MAAM;AAC1B;AAEA,SAAS,OAAO,OAAgB;AAC9B,MAAI,iBAAiB,MAAO,QAAQ,MAAgC,QAAQ,MAAM;AAClF,SAAO,OAAO,KAAK;AACrB;;;ADzBA,IAAM,oBAAoB;AAE1B,eAAsB,YAAY,SAAyB;AACzD,QAAM,EAAE,MAAM,SAAS,IAAI,MAAM,UAAU,QAAQ,GAAG;AACtD,QAAM,MAAM,UAAM,2BAAS,IAAI;AAC/B,QAAMC,OAAM,QAAQ,IAAI,QAAQ,OAAO,EAAE;AACzC,QAAM,OAAO,QAAQ,UAAU,6BAA6B;AAC5D,QAAM,MAAM,GAAGA,IAAG,GAAG,IAAI;AACzB,QAAM,OAAO,IAAI,SAAS;AAC1B,OAAK;AAAA,IACH;AAAA,IACA,IAAI,KAAK,CAAC,IAAI,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAAA,IAC3D,GAAG,SAAS,IAAI;AAAA,EAClB;AACA,MAAI,QAAQ,QAAS,MAAK,IAAI,WAAW,QAAQ,OAAO;AAExD,QAAM,MAAM,MAAM,QAAQ,KAAK;AAAA,IAC7B,QAAQ;AAAA,IACR,SAAS;AAAA,MACP,eAAe,UAAU,QAAQ,KAAK;AAAA,MACtC,QAAQ,6BAA6B,QAAQ,KAAK;AAAA,MAClD,GAAI,QAAQ,UAAU,EAAE,cAAc,QAAQ,QAAQ,IAAI,CAAC;AAAA,IAC7D;AAAA,IACA,MAAM;AAAA,IACN,WAAW;AAAA,EACb,CAAC;AAED,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,mBAAmB,IAAI,MAAM,MAAM,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EACzE;AACA,QAAM,cAAc,IAAI,QAAQ,IAAI,cAAc,KAAK;AACvD,MAAI,CAAC,YAAY,SAAS,MAAM,GAAG;AACjC,UAAM,IAAI;AAAA,MACR,oBAAoB,IAAI,MAAM,IAAI,eAAe,wBAAwB,qBAAqB,KAAK,MAAM,GAAG,GAAG,CAAC;AAAA,IAClH;AAAA,EACF;AACA,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EAMxB,QAAQ;AACN,UAAM,IAAI,MAAM,kCAAkC,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EACxE;AACF;;;AE1DA,gCAAsB;AACtB,IAAAC,mBAA+C;AAC/C,qBAAyB;AACzB,IAAAC,kBAAuB;AACvB,IAAAC,oBAAqB;;;ACFd,SAAS,aACd,SACqB;AACrB,QAAM,SAA8B,CAAC,SAAS,OAAO;AACrD,MAAI,SAAS,KAAM,QAAO,KAAK,MAAM;AACrC,SAAO,KAAK,SAAS;AACrB,SAAO;AACT;AAEO,SAAS,eACd,OAC2B;AAC3B,MAAI,CAAC,MAAM,SAAS,cAAc,EAAG,QAAO;AAC5C,MAAI,MAAM,SAAS,gBAAgB,GAAG;AACpC,WAAO,CAAC,QAAQ,CAAC,WAAW,mBAAmB,CAAC;AAAA,EAClD;AACA,MAAI,MAAM,SAAS,WAAW,GAAG;AAC/B,WAAO,CAAC,QAAQ,CAAC,WAAW,mBAAmB,CAAC;AAAA,EAClD;AACA,MAAI,MAAM,SAAS,mBAAmB,EAAG,QAAO,CAAC,OAAO,CAAC,IAAI,CAAC;AAC9D,SAAO,CAAC,OAAO,CAAC,SAAS,CAAC;AAC5B;AAEO,SAAS,aACd,OACA,SAC2B;AAC3B,MAAI,CAAC,SAAS,MAAO,QAAO;AAC5B,QAAM,CAAC,OAAO,IAAI,eAAe,MAAM,SAAS,cAAc,IAAI,QAAQ,CAAC,gBAAgB,GAAG,KAAK,CAAC,KAAK;AAAA,IACvG;AAAA,IACA,CAAC;AAAA,EACH;AACA,SAAO,CAAC,SAAS,CAAC,OAAO,OAAO,CAAC;AACnC;AAEO,SAAS,YACd,OACA,SAC2B;AAC3B,MAAI,CAAC,SAAS,KAAM,QAAO;AAC3B,QAAM,CAAC,OAAO,IAAI,eAAe,MAAM,SAAS,cAAc,IAAI,QAAQ,CAAC,gBAAgB,GAAG,KAAK,CAAC,KAAK;AAAA,IACvG;AAAA,IACA,CAAC;AAAA,EACH;AACA,SAAO,CAAC,SAAS,CAAC,OAAO,MAAM,CAAC;AAClC;;;AC/CA,IAAAC,kBAA6B;AAC7B,IAAAC,oBAA8B;AAC9B,IAAAC,mBAA8B;AAG9B,IAAMC,YAAO,+BAAQ,gCAAc,aAAe,CAAC;AAEnD,SAAS,cAAsB;AAC7B,MAAI;AACF,UAAM,MAAM,KAAK,UAAM,kCAAa,wBAAKA,OAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAG7E,WAAO,IAAI,WAAW;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,IAAM,UAAU,YAAY;;;AFanC,IAAM,UACJ;AAEF,IAAM,mBAAmB;AACzB,IAAM,gBAAgB;AACtB,IAAM,iBAAiB;AACvB,IAAM,iBAAiB;AACvB,IAAMC,qBAAoB;AAC1B,IAAM,eAAe;AACrB,IAAM,gBAAgB;AAEtB,eAAsB,YAAY,MAA0B;AAC1D,QAAM,UAAU,KAAK,UAAU,IAAI,QAAQ,OAAO,EAAE;AACpD,QAAM,MAAM,KAAK,OAAO;AACxB,QAAM,OAAO,KAAK,QAAQ;AAC1B,MAAI,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM;AAC5B,UAAM,IAAI,MAAM,8CAA8C,OAAO,EAAE;AAAA,EACzE;AAEA,QAAM,UAAU;AAAA,IACd,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,EACnB;AAEA,QAAM,YAAY,MAAM,IAAI,QAAQ,gCAAgC;AAAA,IAClE,QAAQ;AAAA,IACR,SAAS,EAAE,GAAG,SAAS,gBAAgB,mBAAmB;AAAA,IAC1D,MAAM,KAAK,UAAU;AAAA,MACnB;AAAA,MACA;AAAA,MACA,cAAU,yBAAS;AAAA,MACnB,SAAS;AAAA,IACX,CAAC;AAAA,EACH,CAAC;AACD,UAAQ,IAAI,UAAU,UAAU,QAAQ,GAAG,iBAAiB,MAAM,EAAE;AACpE,UAAQ,IAAI,gFAAiE;AAE7E,MAAI,WAAW;AACf,QAAM,OAAO,YAAY,MAAM;AAC7B,QAAI,QAAQ,kCAAkC;AAAA,MAC5C,QAAQ;AAAA,MACR;AAAA,IACF,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,EAC1B,GAAG,IAAM;AAET,QAAM,OAAO,MAAM;AACjB,QAAI,SAAU;AACd,eAAW;AACX,kBAAc,IAAI;AAClB,QAAI,QAAQ,mCAAmC;AAAA,MAC7C,QAAQ;AAAA,MACR;AAAA,IACF,CAAC,EACE,MAAM,MAAM,MAAS,EACrB,QAAQ,MAAM,QAAQ,KAAK,CAAC,CAAC;AAAA,EAClC;AACA,UAAQ,GAAG,UAAU,IAAI;AACzB,UAAQ,GAAG,WAAW,IAAI;AAE1B,MAAI,UAAU;AACd,QAAM,QAAQ,OAAO,YAAoB;AACvC,YAAQ,MAAM,GAAG,OAAO,iBAAiB,KAAK,MAAM,UAAU,GAAI,CAAC,GAAG;AACtE,UAAM,MAAM,OAAO;AACnB,cAAU,KAAK,IAAI,UAAU,GAAG,cAAc;AAAA,EAChD;AAEA,SAAO,CAAC,UAAU;AAChB,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,QAAQ,GAAG,MAAM,kCAAkC;AAAA,QAC7D;AAAA,QACA,WAAW;AAAA,MACb,CAAC;AAAA,IACH,SAAS,OAAO;AACd,YAAM,MAAM,iBAAkB,MAAgB,OAAO,EAAE;AACvD;AAAA,IACF;AACA,cAAU;AAEV,QAAI,IAAI,WAAW,KAAK;AACtB,YAAM,MAAM,aAAa;AACzB;AAAA,IACF;AACA,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,MAAM,iBAAiB,IAAI,MAAM,MAAM,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AACjE;AAAA,IACF;AACA,QAAI,CAAC,MAAM;AACT,YAAM,MAAM,aAAa;AACzB;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,YAAM,KAAK,MAAM,IAAI;AAAA,IACvB,QAAQ;AACN,YAAM,MAAM,gCAAgC,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAChE;AAAA,IACF;AAEA,YAAQ,IAAI,OAAO,IAAI,YAAY,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,IAAI,UAAU,MAAM,GAAG,CAAC,CAAC,EAAE;AAC3F,QAAI;AACF,YAAM,OAAO,QAAQ,SAAS,GAAG;AACjC,cAAQ,IAAI,OAAO,IAAI,YAAY,QAAQ;AAAA,IAC7C,SAAS,KAAK;AACZ,YAAM,UAAW,IAAc;AAC/B,cAAQ,MAAM,OAAO,IAAI,YAAY,YAAY,OAAO,EAAE;AAC1D,YAAM,IAAI,QAAQ,6BAA6B,IAAI,YAAY,SAAS;AAAA,QACtE,QAAQ;AAAA,QACR,SAAS,EAAE,GAAG,SAAS,gBAAgB,mBAAmB;AAAA,QAC1D,MAAM,KAAK,UAAU,EAAE,QAAQ,CAAC;AAAA,MAClC,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,IAC1B;AAAA,EACF;AACF;AAEA,eAAe,OACb,QACA,SACA,KACA;AACA,QAAM,OAAO,UAAM,8BAAQ,4BAAK,wBAAO,GAAG,aAAa,CAAC;AAIxD,MAAI,UAAU;AACd,MAAI;AACJ,QAAM,YAAY,YAAY;AAC5B,QAAI,YAAY;AACd,mBAAa,UAAU;AACvB,mBAAa;AAAA,IACf;AACA,QAAI,CAAC,QAAS;AACd,UAAM,QAAQ;AACd,cAAU;AACV,UAAM,IAAI,QAAQ,6BAA6B,IAAI,YAAY,SAAS;AAAA,MACtE,QAAQ;AAAA,MACR,SAAS,EAAE,GAAG,SAAS,gBAAgB,mBAAmB;AAAA,MAC1D,MAAM,KAAK,UAAU,EAAE,MAAM,CAAC;AAAA,IAChC,CAAC,EAAE,MAAM,MAAM,MAAS;AAAA,EAC1B;AACA,QAAM,MAAM,CAAC,UAAkB;AAC7B,YAAQ,OAAO,MAAM,KAAK;AAC1B,eAAW;AACX,QAAI,QAAQ,UAAU,eAAe;AACnC,WAAK,UAAU;AACf;AAAA,IACF;AACA,QAAI,CAAC,YAAY;AACf,mBAAa,WAAW,MAAM,KAAK,UAAU,GAAG,YAAY;AAC5D,iBAAW,MAAM;AAAA,IACnB;AAAA,EACF;AACA,QAAM,QAAQ,OAAO,MAAc,QAAgB,UAAmB;AACpE,UAAM,UAAU;AAChB,UAAM,IAAI,QAAQ,6BAA6B,IAAI,YAAY,UAAU;AAAA,MACvE,QAAQ;AAAA,MACR,SAAS,EAAE,GAAG,SAAS,gBAAgB,mBAAmB;AAAA,MAC1D,MAAM,KAAK,UAAU,EAAE,MAAM,QAAQ,MAAM,CAAC;AAAA,IAC9C,CAAC;AAAA,EACH;AAEA,MAAI;AACF,UAAM,MAAM,SAAS,SAAS;AAC9B,QAAI,SAAS,IAAI,KAAK,IAAI,IAAI,IAAI,IAAI,IAAI,SAAS;AAAA,CAAI;AACvD,UAAM,OAAO,OAAO,CAAC,MAAM,GAAG,MAAM,GAAG;AACvC,UAAM,SAAS,0BAA0B,IAAI,UAAU,eAAe,IAAI,KAAK,IAAI,IAAI,IAAI;AAC3F,UAAM,OAAO,OAAO,CAAC,UAAU,OAAO,UAAU,MAAM,GAAG,MAAM,MAAM,MAAS;AAC9E,UAAM,OAAO,OAAO,CAAC,SAAS,WAAW,KAAK,UAAU,IAAI,SAAS,GAAG,MAAM,GAAG;AACjF,UAAM,OAAO,OAAO,CAAC,YAAY,WAAW,YAAY,GAAG,MAAM,GAAG;AACpE,UAAM,MAAM,SAAS,IAAI;AAEzB,UAAM,WAAW,IAAI,oBAAgB,wBAAK,MAAM,IAAI,aAAa,IAAI;AACrE,UAAM,QAAQ,UAAM,0BAAQ,QAAQ;AACpC,UAAM,UAAU,MAAM,YAAY,QAAQ;AAC1C,UAAM,UAAU,aAAa,OAAO;AAEpC,UAAM,MAAM,SAAS,SAAS;AAC9B,UAAM,UAAU,eAAe,KAAK;AACpC,QAAI,QAAS,OAAM,OAAO,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,UAAU,GAAG;AAC/D,UAAM,QAAQ,aAAa,OAAO,OAAO;AACzC,QAAI,MAAO,OAAM,OAAO,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,UAAU,GAAG;AACzD,UAAM,MAAM,SAAS,IAAI;AAEzB,QAAI,QAAQ,SAAS,MAAM,GAAG;AAC5B,YAAM,MAAM,QAAQ,SAAS;AAC7B,YAAM,OAAO,YAAY,OAAO,OAAO;AACvC,UAAI,KAAM,OAAM,OAAO,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,UAAU,GAAG;AACtD,YAAM,MAAM,QAAQ,IAAI;AAAA,IAC1B,OAAO;AACL,YAAM,MAAM,QAAQ,SAAS;AAAA,IAC/B;AAEA,UAAM,MAAM,WAAW,SAAS;AAChC,UAAM,SAAS,MAAM,UAAU,QAAQ;AACvC,QAAI,UAAU,OAAO,IAAI;AAAA,CAAI;AAC7B,UAAM,MAAM,UAAM,2BAAS,OAAO,IAAI;AACtC,UAAM,OAAO,IAAI,SAAS;AAC1B,SAAK;AAAA,MACH;AAAA,MACA,IAAI,KAAK,CAAC,IAAI,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAAA,MAC3D;AAAA,IACF;AACA,UAAM,YAAY,MAAM;AAAA,MACtB,GAAG,MAAM,6BAA6B,IAAI,YAAY;AAAA,MACtD,EAAE,QAAQ,QAAQ,SAAS,MAAM,MAAM,WAAWA,mBAAkB;AAAA,IACtE;AACA,QAAI,CAAC,UAAU,IAAI;AACjB,YAAM,IAAI,MAAM,mBAAmB,UAAU,MAAM,MAAM,MAAM,UAAU,KAAK,CAAC,EAAE;AAAA,IACnF;AACA,UAAM,MAAM,WAAW,IAAI;AAAA,EAC7B,UAAE;AACA,UAAM,UAAU;AAChB,cAAM,qBAAG,MAAM,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EACjD;AACF;AAEA,eAAe,YAAY,MAAc;AACvC,MAAI;AACF,UAAM,MAAM,KAAK,MAAM,UAAM,+BAAS,wBAAK,MAAM,cAAc,GAAG,MAAM,CAAC;AAGzE,WAAO,IAAI,WAAW,CAAC;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,OACP,KACA,MACA,KACA,OACA;AACA,SAAO,IAAI,QAAc,CAACC,UAAS,WAAW;AAE5C,UAAM,WAAW,QAAQ,aAAa;AACtC,UAAM,YAAQ,iCAAM,KAAK,WAAW,KAAK,IAAI,aAAa,IAAI,MAAM;AAAA,MAClE;AAAA,MACA,KAAK,QAAQ;AAAA,MACb,OAAO;AAAA,IACT,CAAC;AACD,UAAM,OAAO,GAAG,QAAQ,CAAC,QAAgB,MAAM,IAAI,SAAS,CAAC,CAAC;AAC9D,UAAM,OAAO,GAAG,QAAQ,CAAC,QAAgB,MAAM,IAAI,SAAS,CAAC,CAAC;AAC9D,UAAM,GAAG,SAAS,MAAM;AACxB,UAAM,GAAG,SAAS,CAAC,MAAM,WAAW;AAClC,UAAI,SAAS,EAAG,CAAAA,SAAQ;AAAA,eACf,OAAQ,QAAO,IAAI,MAAM,GAAG,GAAG,IAAI,KAAK,KAAK,GAAG,CAAC,cAAc,MAAM,EAAE,CAAC;AAAA,UAC5E,QAAO,IAAI,MAAM,GAAG,GAAG,IAAI,KAAK,KAAK,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC;AAAA,IAClE,CAAC;AAAA,EACH,CAAC;AACH;AAEA,SAAS,cAAc,KAAa;AAClC,SAAO,eAAe,KAAK,GAAG,IAAI,IAAI,IAAI,QAAQ,MAAM,IAAI,CAAC,MAAM;AACrE;AAEA,eAAe,IAAI,QAAgB,MAAc,MAAsB;AACrE,QAAM,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,IAAI,IAAI,IAAI;AAClD,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,CAAC,IAAI,GAAI,OAAM,IAAI,MAAM,GAAG,IAAI,YAAY,IAAI,MAAM,MAAM,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AACpF,MAAI,CAAC,KAAM,QAAO,CAAC;AACnB,MAAI;AACF,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB,QAAQ;AACN,UAAM,IAAI,MAAM,GAAG,IAAI,2BAA2B,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,EACxE;AACF;AAEA,SAAS,MAAM,IAAY;AACzB,SAAO,IAAI,QAAQ,CAACA,aAAY,WAAWA,UAAS,EAAE,CAAC;AACzD;;;ATvSA,IAAM,cAAc;AAEb,SAAS,eAAe;AAC7B,QAAM,UAAU,IAAI,yBAAQ;AAE5B,UACG,KAAK,QAAQ,EACb,YAAY,sCAAsC,EAClD,QAAQ,OAAO,EACf;AAAA,IACC;AAAA,IACA;AAAA;AAAA,kEAE4D,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMzE;AAEF,UACG,QAAQ,YAAY,EACpB,YAAY,wDAAwD,EACpE,OAAO,eAAe,+CAA+C,EACrE,OAAO,OAAO,KAAyB,SAA8B;AACpE,UAAM,SAAS,MAAM,SAAS,OAAO,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;AAC/D,YAAQ,IAAI,cAAc,OAAO,MAAM,MAAM,aAAa,OAAO,GAAG,EAAE;AACtE,eAAW,QAAQ,OAAO,MAAO,SAAQ,IAAI,KAAK,IAAI,EAAE;AACxD,YAAQ,IAAI,kBAAkB;AAAA,EAChC,CAAC;AAEH,UACG,QAAQ,OAAO,EACf,YAAY,8DAA8D,EAC1E,OAAO,eAAe,aAAa,QAAQ,IAAI,CAAC,EAChD,OAAO,oBAAoB,iBAAiB,EAC5C,OAAO,OAAO,SAAwC;AACrD,UAAM,SAAS,MAAM,UAAU,KAAK,KAAK,KAAK,GAAG;AACjD,YAAQ;AAAA,MACN,SAAS,OAAO,SAAS,IAAI,IAAI,OAAO,SAAS,OAAO,OAAO,OAAO,IAAI;AAAA,IAC5E;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,SAAS,EACjB,YAAY,+CAA+C,EAC3D,OAAO,eAAe,aAAa,QAAQ,IAAI,CAAC,EAChD,OAAO,eAAe,eAAe,QAAQ,IAAI,cAAc,WAAW,EAC1E,OAAO,mBAAmB,iBAAiB,QAAQ,IAAI,YAAY,EACnE,OAAO,gBAAgB,8BAA8B,QAAQ,IAAI,eAAe,EAChF,OAAO,OAAO,SAAuE;AACpF,QAAI,CAAC,KAAK,OAAO;AACf,YAAM,IAAI,MAAM,kCAAkC;AAAA,IACpD;AACA,UAAM,OAAO,MAAM,YAAY;AAAA,MAC7B,KAAK,KAAK;AAAA,MACV,KAAK,KAAK;AAAA,MACV,OAAO,KAAK;AAAA,MACZ,SAAS,KAAK;AAAA,IAChB,CAAC;AACD,YAAQ,IAAI,aAAa,KAAK,IAAI,IAAI,KAAK,OAAO,KAAK,KAAK,EAAE,GAAG;AACjE,QAAI,KAAK,cAAe,SAAQ,IAAI,KAAK,aAAa;AAAA,EACxD,CAAC;AAEH,UACG,QAAQ,KAAK,EACb,YAAY,oDAAoD,EAChE,OAAO,eAAe,aAAa,QAAQ,IAAI,CAAC,EAChD,OAAO,qBAAqB,QAAQ,MAAM,EAC1C,OAAO,iBAAiB,mDAAmD,WAAW,EACtF,OAAO,OAAO,SAAsD;AACnE,UAAM,EAAE,MAAM,SAAS,IAAI,MAAM,eAAe,KAAK,KAAK,OAAO,KAAK,IAAI,GAAG;AAAA,MAC3E,MAAM,KAAK;AAAA,IACb,CAAC;AACD,UAAM,QAAQ,KAAK,SAAS,aAAa,KAAK,SAAS,OAAO,cAAc,KAAK;AACjF,YAAQ,IAAI,GAAG,SAAS,IAAI,sBAAsB,KAAK,IAAI,IAAI,EAAE;AACjE,QAAI,UAAU,KAAK,KAAM,SAAQ,IAAI,YAAY,KAAK,IAAI,0BAA0B;AAAA,EACtF,CAAC;AAEH,QAAM,SAAS,QAAQ,QAAQ,QAAQ,EAAE,YAAY,+BAA+B;AAEpF,SACG,QAAQ,OAAO,EACf,YAAY,+EAAgE,EAC5E;AAAA,IACC;AAAA,IACA;AAAA,IACA,QAAQ,IAAI,cAAc,QAAQ,IAAI,iBAAiB;AAAA,EACzD,EACC,OAAO,eAAe,kCAAkC,QAAQ,IAAI,iBAAiB,EACrF,OAAO,iBAAiB,mCAAmC,QAAQ,IAAI,kBAAkB,EACzF,OAAO,OAAO,SAA0D;AACvE,UAAM,YAAY;AAAA,MAChB,QAAQ,KAAK;AAAA,MACb,KAAK,KAAK,OAAO;AAAA,MACjB,MAAM,KAAK,QAAQ;AAAA,IACrB,CAAC;AAAA,EACH,CAAC;AAEH,SAAO;AACT;;;AY1GA,YAAY;AAEZ,aAAa,EACV,WAAW,QAAQ,IAAI,EACvB,MAAM,CAAC,UAAmB;AACzB,UAAQ,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE;AAChF,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["import_node_fs","import_promises","import_node_path","import_node_fs","import_node_path","require","import_node_path","import_promises","import_node_path","walk","import_promises","api","import_promises","import_node_os","import_node_path","import_node_fs","import_node_path","import_node_url","here","UPLOAD_TIMEOUT_MS","resolve"]}
@@ -0,0 +1,11 @@
1
+ # Click Counter
2
+
3
+ Example FZ game used by the local-dev guide in [../../README.md](../../README.md). `fzgame init` copies this folder, build output excluded, into a new game.
4
+
5
+ ```bash
6
+ fzgame dev --cwd .
7
+ ```
8
+
9
+ From a monorepo checkout, `pnpm --filter @fzgame/kit build && node ../../dist/cli.cjs dev --cwd .`.
10
+
11
+ Open [http://127.0.0.1:3400](http://127.0.0.1:3400).
@@ -0,0 +1,68 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Click Counter editor</title>
7
+ <style>
8
+ body { font-family: system-ui, sans-serif; padding: 24px; }
9
+ label { display: block; margin: 12px 0 4px; }
10
+ input { width: 240px; padding: 8px; }
11
+ button { margin-top: 16px; padding: 8px 16px; }
12
+ </style>
13
+ </head>
14
+ <body>
15
+ <h1>Click Counter</h1>
16
+ <form id="form">
17
+ <label for="goal">Goal</label>
18
+ <input id="goal" name="goal" type="number" min="1" value="10" />
19
+ <label for="accent">Accent</label>
20
+ <input id="accent" name="accent" type="color" value="#228be6" />
21
+ <label for="label">Button label</label>
22
+ <input id="label" name="label" type="text" value="Click me" />
23
+ <div>
24
+ <button type="submit">Save</button>
25
+ </div>
26
+ </form>
27
+ <script type="module">
28
+ const form = document.getElementById('form')
29
+ const params = new URLSearchParams(location.search)
30
+ const kitUrl = params.get('kit') || '../game/vendor/kit.js'
31
+ let postEditorSave = (config) => {
32
+ parent.postMessage({ source: 'fzgame-editor', type: 'save', config }, '*')
33
+ }
34
+ try {
35
+ const kit = await import(kitUrl)
36
+ if (kit.postEditorSave) postEditorSave = kit.postEditorSave
37
+ if (kit.onHostInit) {
38
+ kit.onHostInit((payload) => {
39
+ const config = payload.config || {}
40
+ if (config.goal) form.goal.value = config.goal
41
+ if (config.accent) form.accent.value = config.accent
42
+ if (config.label) form.label.value = config.label
43
+ })
44
+ }
45
+ } catch {
46
+ /* vendor kit optional in source tree */
47
+ }
48
+
49
+ window.addEventListener('message', (event) => {
50
+ const data = event.data
51
+ if (!data || data.source !== 'fzgame-host' || data.type !== 'init') return
52
+ const config = data.config || {}
53
+ if (config.goal) form.goal.value = config.goal
54
+ if (config.accent) form.accent.value = config.accent
55
+ if (config.label) form.label.value = config.label
56
+ })
57
+ parent.postMessage({ source: 'fzgame-editor', type: 'ready' }, '*')
58
+ form.addEventListener('submit', (event) => {
59
+ event.preventDefault()
60
+ postEditorSave({
61
+ goal: Number(form.goal.value),
62
+ accent: form.accent.value,
63
+ label: form.label.value,
64
+ })
65
+ })
66
+ </script>
67
+ </body>
68
+ </html>
@@ -0,0 +1,65 @@
1
+ {
2
+ "info": {
3
+ "name": "Click Counter",
4
+ "slug": "click-counter",
5
+ "version": 1,
6
+ "image": "",
7
+ "description": "Click the button to score. Configure a goal and accent in the editor.",
8
+ "author": "FZGame",
9
+ "git_url": "https://github.com/fzgame/click-counter"
10
+ },
11
+ "entry": {
12
+ "game": "game/index.html",
13
+ "editor": "editor/index.html",
14
+ "handler": "handler/index.js"
15
+ },
16
+ "formSchema": {
17
+ "type": "object",
18
+ "title": "Click Counter",
19
+ "required": ["goal"],
20
+ "properties": {
21
+ "goal": {
22
+ "type": "integer",
23
+ "title": "Goal",
24
+ "default": 10,
25
+ "minimum": 1
26
+ },
27
+ "accent": {
28
+ "type": "string",
29
+ "title": "Accent",
30
+ "default": "#228be6"
31
+ },
32
+ "label": {
33
+ "type": "string",
34
+ "title": "Button label",
35
+ "default": "Click me"
36
+ }
37
+ }
38
+ },
39
+ "formUiSchema": {
40
+ "accent": { "ui:widget": "color" }
41
+ },
42
+ "resultSchema": {
43
+ "type": "object",
44
+ "required": ["clicks", "reachedGoal"],
45
+ "properties": {
46
+ "clicks": { "type": "integer", "minimum": 0 },
47
+ "reachedGoal": { "type": "boolean" }
48
+ }
49
+ },
50
+ "snapshotSchema": {
51
+ "type": "object",
52
+ "required": ["clicks"],
53
+ "properties": {
54
+ "clicks": { "type": "integer", "minimum": 0, "default": 0 }
55
+ }
56
+ },
57
+ "gameDataSchema": {
58
+ "type": "object",
59
+ "properties": {
60
+ "goal": { "type": "integer" },
61
+ "accent": { "type": "string" },
62
+ "label": { "type": "string" }
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,75 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Click Counter</title>
7
+ <style>
8
+ body {
9
+ font-family: system-ui, sans-serif;
10
+ display: grid;
11
+ place-items: center;
12
+ min-height: 100vh;
13
+ margin: 0;
14
+ background: #f8f9fa;
15
+ }
16
+ button {
17
+ font-size: 24px;
18
+ padding: 16px 28px;
19
+ border: 0;
20
+ border-radius: 12px;
21
+ color: white;
22
+ cursor: pointer;
23
+ }
24
+ p { color: #495057; }
25
+ </style>
26
+ </head>
27
+ <body>
28
+ <main>
29
+ <p id="status">Loading…</p>
30
+ <button id="click">Click me</button>
31
+ <p id="score">0 / 10</p>
32
+ </main>
33
+ <script type="module">
34
+ const params = new URLSearchParams(location.search)
35
+ const kitUrl = params.get('kit') || './vendor/kit.js'
36
+ const { createKit } = await import(kitUrl)
37
+ const kit = createKit()
38
+ await kit.load()
39
+ await kit.connect()
40
+ await kit.emit('ready')
41
+ await kit.emit('start')
42
+
43
+ const config = kit.config()
44
+ const goal = Number(config.goal ?? 10)
45
+ const button = document.getElementById('click')
46
+ const scoreEl = document.getElementById('score')
47
+ const statusEl = document.getElementById('status')
48
+ button.textContent = String(config.label ?? 'Click me')
49
+ button.style.background = String(config.accent ?? '#228be6')
50
+ statusEl.textContent = kit.snapshot.status
51
+
52
+ let clicks = Number(kit.snapshot.state?.clicks ?? 0)
53
+ const render = () => {
54
+ scoreEl.textContent = `${clicks} / ${goal}`
55
+ statusEl.textContent = kit.snapshot.status
56
+ }
57
+ render()
58
+
59
+ button.addEventListener('click', async () => {
60
+ clicks += 1
61
+ await kit.setState({ clicks })
62
+ await kit.emit('score', { score: clicks })
63
+ render()
64
+ if (clicks >= goal) {
65
+ await kit.emit('finish', {
66
+ score: clicks,
67
+ result: { clicks, reachedGoal: true },
68
+ })
69
+ button.disabled = true
70
+ render()
71
+ }
72
+ })
73
+ </script>
74
+ </body>
75
+ </html>
@@ -0,0 +1,13 @@
1
+ export default {
2
+ async fetch(request) {
3
+ const url = new URL(request.url)
4
+ if (url.pathname.endsWith('/health')) {
5
+ return new Response(JSON.stringify({ ok: true, game: 'click-counter' }), {
6
+ headers: { 'content-type': 'application/json' },
7
+ })
8
+ }
9
+ return new Response(JSON.stringify({ ok: true }), {
10
+ headers: { 'content-type': 'application/json' },
11
+ })
12
+ },
13
+ }
package/package.json CHANGED
@@ -1,6 +1,63 @@
1
1
  {
2
2
  "name": "@fzgame/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "CLI to build, publish, and develop FZ games",
5
+ "license": "ISC",
6
+ "author": "Fanzeal <developer@fanzeal.com>",
7
+ "type": "module",
8
+ "bin": {
9
+ "fzgame": "dist/cli.cjs"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "example/click-counter",
14
+ "src",
15
+ "!src/**/*.spec.ts"
16
+ ],
17
+ "directories": {
18
+ "example": "example"
19
+ },
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://bitbucket.org/fanzeal-intl/fz-gamehub.git",
29
+ "directory": "packages/fzgame-cli"
30
+ },
31
+ "homepage": "https://bitbucket.org/fanzeal-intl/fz-gamehub/src/main/packages/fzgame-cli",
32
+ "bugs": {
33
+ "url": "https://bitbucket.org/fanzeal-intl/fz-gamehub/issues"
34
+ },
35
+ "keywords": [
36
+ "fanzeal",
37
+ "gamehub",
38
+ "fzgame",
39
+ "cli",
40
+ "game",
41
+ "publish"
42
+ ],
43
+ "scripts": {
44
+ "build": "tsup",
45
+ "check-types": "tsc --noEmit",
46
+ "test": "vitest",
47
+ "test:run": "vitest run",
48
+ "smoke": "node scripts/smoke-npm-pack.mjs",
49
+ "prepack": "pnpm build",
50
+ "prepublishOnly": "pnpm test:run && pnpm build"
51
+ },
52
+ "dependencies": {
53
+ "@fzgame/kit": "workspace:^",
54
+ "commander": "^12.0.0"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^22.15.3",
58
+ "tsup": "^8.5.1",
59
+ "typescript": "^5.9.2",
60
+ "vitest": "^4.1.3"
61
+ },
62
+ "main": "index.js"
63
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { loadEnvFile } from './env'
2
+ import { buildProgram } from './program'
3
+
4
+ loadEnvFile()
5
+
6
+ buildProgram()
7
+ .parseAsync(process.argv)
8
+ .catch((error: unknown) => {
9
+ console.error(`error: ${error instanceof Error ? error.message : String(error)}`)
10
+ process.exit(1)
11
+ })