@donut-games/cli 0.1.3 → 0.1.4

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.
package/dist/donut.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../../cli/src/index.ts","../../cli/src/commands/init.ts","../../cli/src/commands/template-copier.ts","../../cli/src/commands/dev.ts","../../cli/src/shared/monorepo-aliases.ts","../../cli/src/commands/validate.ts","../../cli/src/commands/build.ts","../../cli/src/commands/publish.ts","../../cli/src/commands/review.ts","../src/donut.ts"],"sourcesContent":["#!/usr/bin/env node\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { initializeProject } from './commands/init.js';\nimport { runDevServer } from './commands/dev.js';\nimport { runValidation, type ValidationError } from './commands/validate.js';\nimport { runBuild, BuildViolationError } from './commands/build.js';\nimport { runPublish } from './commands/publish.js';\nimport { runReview } from './commands/review.js';\n\nexport { runValidation } from './commands/validate.js';\nexport type {\n ValidationError,\n ValidationReport,\n RunValidationOptions,\n} from './commands/validate.js';\nexport { initializeProject } from './commands/init.js';\nexport type {\n RendererTarget,\n InitializeProjectOptions,\n InitializeProjectResult,\n} from './commands/init.js';\nexport { runBuild, BuildViolationError } from './commands/build.js';\nexport type {\n RunBuildOptions,\n BuildReport,\n SecurityViolation,\n} from './commands/build.js';\nexport type {\n BundleManifest,\n BundleManifestAssetEntry,\n} from './bundle/bundle-manifest.js';\nexport {\n runPublish,\n StubBundleUploader,\n scanBundleSourceForViolations,\n} from './commands/publish.js';\nexport type {\n RunPublishOptions,\n PublishReport,\n BundleUploader,\n BundleUploadParameters,\n PublishSecurityViolation,\n} from './commands/publish.js';\nexport {\n runReview,\n StubClaudeReviewClient,\n AnthropicClaudeReviewClient,\n createDefaultClaudeReviewClient,\n parseReviewFindingsFromText,\n REVIEW_PROMPT_TEXT,\n} from './commands/review.js';\nexport type {\n RunReviewOptions,\n ReviewReport,\n ReviewFinding,\n ReviewFindingSeverity,\n ClaudeReviewClient,\n ClaudeReviewRequest,\n ClaudeReviewResponse,\n} from './commands/review.js';\n\n/**\n * The top-level commander program for the `donut` CLI.\n * Exported so tests and other entry points can inspect or invoke it.\n */\nexport const program = new Command();\n\nprogram\n .name('donut')\n .description('Donut game engine command-line tooling')\n .version('0.1.0');\n\nprogram\n .command('init')\n .description('Scaffold a new Donut game project')\n .argument('<project-name>', 'name of the new project directory')\n .option('--2d', 'use the 2D (Pixi) renderer target', false)\n .option('--3d', 'use the 3D (Three) renderer target', false)\n .option('--skip-install', 'skip running pnpm/npm install after scaffolding', false)\n .action(async (projectName: string, options: Record<string, boolean>) => {\n const useThreeDimensional = options['3d'] === true;\n const rendererTarget = useThreeDimensional ? 'three-3d' : 'pixi-2d';\n try {\n await initializeProject({\n projectName,\n rendererTarget,\n targetDirectory: projectName,\n skipInstall: options.skipInstall === true,\n });\n } catch (error) {\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n });\n\nprogram\n .command('dev')\n .description('Run the project in a local development server')\n .option('--port <port>', 'port for the dev server', '5173')\n .option('--no-open', 'do not automatically open a browser window')\n .action(async (options: { port: string; open: boolean }) => {\n const parsedPort = Number.parseInt(options.port, 10);\n try {\n await runDevServer({\n projectDirectory: process.cwd(),\n port: Number.isFinite(parsedPort) ? parsedPort : 5173,\n open: options.open,\n });\n } catch (error) {\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n });\n\nprogram\n .command('validate')\n .description('Validate project structure, types, and asset references')\n .action(async () => {\n try {\n const report = await runValidation({ projectDirectory: process.cwd() });\n const formatFinding = (finding: ValidationError): string => {\n const locationParts: string[] = [finding.filePath];\n if (finding.line !== undefined) {\n locationParts.push(String(finding.line));\n if (finding.column !== undefined) {\n locationParts.push(String(finding.column));\n }\n }\n return `${locationParts.join(':')}: ${finding.code}: ${finding.message}`;\n };\n for (const warning of report.warnings) {\n console.warn(chalk.yellow(formatFinding(warning)));\n }\n for (const error of report.errors) {\n console.error(chalk.red(formatFinding(error)));\n }\n if (report.errors.length === 0 && report.warnings.length === 0) {\n console.log(chalk.green('donut validate: no problems found'));\n }\n process.exit(report.errors.length === 0 ? 0 : 1);\n } catch (error) {\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n });\n\nprogram\n .command('build')\n .description('Build a distributable .donut bundle for the project')\n .option('--output <dir>', 'output directory for the bundle', 'dist')\n .action(async (options: { output: string }) => {\n const projectDirectory = process.cwd();\n const outputDirectory = path.isAbsolute(options.output)\n ? options.output\n : path.join(projectDirectory, options.output);\n try {\n const report = await runBuild({ projectDirectory, outputDirectory });\n for (const warning of report.warnings) {\n console.warn(\n chalk.yellow(\n `${warning.filePath}:${warning.line}: warning: matched /${warning.pattern}/ (\"${warning.match}\")`,\n ),\n );\n }\n console.log(\n chalk.green(\n `donut build: wrote ${report.bundlePath} (${report.bundleSizeBytes} bytes) in ${report.buildTimeMilliseconds}ms`,\n ),\n );\n } catch (error) {\n if (error instanceof BuildViolationError) {\n console.error(chalk.red(error.message));\n for (const violation of error.violations) {\n console.error(\n chalk.red(\n ` ${violation.filePath}:${violation.line}: matched /${violation.pattern}/ (\"${violation.match}\")`,\n ),\n );\n }\n } else {\n console.error(chalk.red((error as Error).message));\n }\n process.exit(1);\n }\n });\n\nprogram\n .command('publish')\n .description('Publish a built .donut bundle (dry-run by default)')\n .option('--bundle <path>', 'explicit path to a .donut bundle to publish')\n .option('--auth-token <token>', 'bearer token for the upload endpoint')\n .action(async (options: { bundle?: string; authToken?: string }) => {\n try {\n const report = await runPublish({\n projectDirectory: process.cwd(),\n bundlePath: options.bundle,\n authToken: options.authToken,\n });\n if (report.success) {\n console.log(chalk.green(`donut publish: ${report.gameUrl}`));\n }\n } catch (error) {\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n });\n\nprogram\n .command('review')\n .description('Run AI-assisted security/style review on the project sources')\n .option('--bundle <path>', 'review the sources packaged into a built bundle')\n .action(async (options: { bundle?: string }) => {\n try {\n const report = await runReview({\n projectDirectory: process.cwd(),\n bundlePath: options.bundle,\n });\n if (report.overallPass === false) {\n process.exit(1);\n }\n } catch (error) {\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n });\n\n/**\n * True when this module is the main entry (not imported for tests).\n */\nconst isRunningAsMain =\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith('/donut') === true ||\n process.argv[1]?.endsWith('\\\\donut') === true ||\n process.argv[1]?.endsWith('cli/src/index.ts') === true ||\n process.argv[1]?.endsWith('cli/dist/index.js') === true;\n\nif (isRunningAsMain) {\n program.parseAsync(process.argv);\n}\n","import path from 'node:path';\nimport { spawn } from 'node:child_process';\nimport { createRequire } from 'node:module';\nimport { fileURLToPath } from 'node:url';\nimport fileSystemExtra from 'fs-extra';\nimport chalk from 'chalk';\n\nimport { copyTemplate, type TemplateTokens } from './template-copier.js';\n\n/**\n * Supported renderer targets for scaffolded projects.\n */\nexport type RendererTarget = 'pixi-2d' | 'three-3d';\n\n/**\n * Options accepted by {@link initializeProject}.\n */\nexport interface InitializeProjectOptions {\n /** Human-readable project name (also written into donut.json and package.json). */\n readonly projectName: string;\n /** Renderer stack the scaffold should wire up. */\n readonly rendererTarget: RendererTarget;\n /** Absolute or relative path to the directory to create. */\n readonly targetDirectory: string;\n /** Optional logger; defaults to console.log with chalk colors. */\n readonly log?: (message: string) => void;\n /** When true, skip the post-scaffold dependency install step. */\n readonly skipInstall?: boolean;\n /**\n * Absolute path to the template directory. When provided, skips the\n * built-in template resolution logic entirely. Used by\n * `@donut-games/create-game` to pass its own bundled template — the\n * template ships inside `create-game`, not inside `cli`, so Node's\n * require resolution cannot find it from `cli`'s perspective.\n */\n readonly templateDirectory?: string;\n}\n\n/**\n * Result of a successful scaffold.\n */\nexport interface InitializeProjectResult {\n readonly absoluteDirectory: string;\n readonly createdFiles: readonly string[];\n}\n\n/**\n * Shape of the relevant fields from `@donut-games/engine/package.json`.\n */\ninterface EnginePackageManifest {\n readonly version: string;\n readonly peerDependencies?: Record<string, string>;\n}\n\n/**\n * Resolve the engine, pixi.js, and three version strings by reading\n * `@donut-games/engine/package.json` at scaffold time. This is the single\n * source of truth — no hardcoded version constants exist in this module.\n *\n * Falls back to requiring the workspace package when the published path\n * cannot be resolved.\n */\nasync function resolveEngineVersions(): Promise<{\n readonly donutEngineVersion: string;\n readonly pixiVersion: string;\n readonly threeVersion: string;\n}> {\n const candidatePaths: string[] = [];\n\n // Try require.resolve — works when @donut-games/engine is a dependency\n // (create-game → cli → engine) or a workspace sibling.\n try {\n const nodeRequire = createRequire(import.meta.url);\n const engineManifestPath = nodeRequire.resolve(\n '@donut-games/engine/package.json',\n );\n candidatePaths.push(engineManifestPath);\n const manifest = (await fileSystemExtra.readJson(\n engineManifestPath,\n )) as EnginePackageManifest;\n return extractVersionsFromManifest(manifest);\n } catch {\n // fall through\n }\n\n // Walk up from this module looking for the monorepo's packages/donut-engine.\n const thisModuleDirectory = path.dirname(fileURLToPath(import.meta.url));\n let walkingDirectory = thisModuleDirectory;\n for (let walkDepth = 0; walkDepth < 8; walkDepth += 1) {\n const candidate = path.join(\n walkingDirectory,\n 'packages',\n 'donut-engine',\n 'package.json',\n );\n candidatePaths.push(candidate);\n if (await fileSystemExtra.pathExists(candidate)) {\n const manifest = (await fileSystemExtra.readJson(\n candidate,\n )) as EnginePackageManifest;\n return extractVersionsFromManifest(manifest);\n }\n const parentDirectory = path.dirname(walkingDirectory);\n if (parentDirectory === walkingDirectory) {\n break;\n }\n walkingDirectory = parentDirectory;\n }\n\n throw new Error(\n `Unable to resolve @donut-games/engine version metadata. Checked:\\n ${candidatePaths.join(\n '\\n ',\n )}`,\n );\n}\n\n/**\n * Extract the version strings from a parsed engine `package.json`.\n * Strips the leading `^` or `~` from peer dep ranges so the template's\n * `^{{pixiVersion}}` emits a clean `^8.0.0` rather than `^^8.0.0`.\n */\nfunction extractVersionsFromManifest(manifest: EnginePackageManifest): {\n readonly donutEngineVersion: string;\n readonly pixiVersion: string;\n readonly threeVersion: string;\n} {\n const peerDependencies = manifest.peerDependencies ?? {};\n const stripRange = (range: string): string =>\n range.replace(/^[\\^~>=<\\s]+/, '');\n\n return {\n donutEngineVersion: manifest.version,\n pixiVersion: stripRange(peerDependencies['pixi.js'] ?? '8.0.0'),\n threeVersion: stripRange(peerDependencies['three'] ?? '0.160.0'),\n };\n}\n\n/**\n * Scaffold a new Donut game project at {@link InitializeProjectOptions.targetDirectory}.\n * Throws if the target directory already exists and is non-empty.\n */\nexport async function initializeProject(\n options: InitializeProjectOptions,\n): Promise<InitializeProjectResult> {\n const { projectName, rendererTarget } = options;\n const log = options.log ?? ((message: string) => console.log(message));\n const absoluteDirectory = path.resolve(options.targetDirectory);\n\n if (await fileSystemExtra.pathExists(absoluteDirectory)) {\n const existingEntries = await fileSystemExtra.readdir(absoluteDirectory);\n if (existingEntries.length > 0) {\n throw new Error(\n `Target directory \"${absoluteDirectory}\" already exists and is not empty.`,\n );\n }\n }\n\n await fileSystemExtra.ensureDir(absoluteDirectory);\n\n log(chalk.cyan(`Scaffolding \"${projectName}\" (${rendererTarget}) at ${absoluteDirectory}`));\n\n const templateSourceDirectory = options.templateDirectory ?? await resolveTemplateDirectory();\n const engineVersions = await resolveEngineVersions();\n\n const templateTokens: TemplateTokens = {\n projectName,\n rendererTarget,\n donutEngineVersion: engineVersions.donutEngineVersion,\n pixiVersion: engineVersions.pixiVersion,\n threeVersion: engineVersions.threeVersion,\n };\n\n const copyResult = await copyTemplate({\n sourceDirectory: templateSourceDirectory,\n destinationDirectory: absoluteDirectory,\n variant: rendererTarget,\n tokens: templateTokens,\n logProgress: (relativePath) => {\n log(chalk.green(` created ${relativePath}`));\n },\n });\n\n if (options.skipInstall !== true) {\n await runDependencyInstall(absoluteDirectory, log);\n }\n\n log(chalk.cyan(`\\nDone. Next steps:`));\n log(chalk.white(` cd ${projectName}`));\n if (options.skipInstall === true) {\n log(chalk.white(` npm install`));\n }\n log(chalk.white(` npm run dev`));\n\n return { absoluteDirectory, createdFiles: copyResult.createdFiles };\n}\n\n/**\n * Resolve the `template/` directory that ships alongside\n * `@donut-games/create-game`. Tries candidate locations in order and returns\n * the first that exists on disk. Throws a descriptive error if none resolve.\n *\n * 1. `../template` relative to this module's URL — works when running from a\n * shipped `@donut-games/cli` dist where the template sits beside `dist/`.\n * 2. Resolving `@donut-games/create-game/package.json` via Node's require\n * machinery, then joining `template/` — works in the monorepo.\n * 3. A source-tree relative fallback — works when this file is executed\n * directly out of `src/` during development.\n */\nasync function resolveTemplateDirectory(): Promise<string> {\n const checkedPaths: string[] = [];\n\n const shippedCandidate = fileURLToPath(new URL('../template', import.meta.url));\n checkedPaths.push(shippedCandidate);\n if (await fileSystemExtra.pathExists(shippedCandidate)) {\n return shippedCandidate;\n }\n\n try {\n const nodeRequire = createRequire(import.meta.url);\n const createGameManifestPath = nodeRequire.resolve(\n '@donut-games/create-game/package.json',\n );\n const monorepoCandidate = path.join(\n path.dirname(createGameManifestPath),\n 'template',\n );\n checkedPaths.push(monorepoCandidate);\n if (await fileSystemExtra.pathExists(monorepoCandidate)) {\n return monorepoCandidate;\n }\n } catch {\n // fall through to the next candidate\n }\n\n const thisModuleDirectory = path.dirname(fileURLToPath(import.meta.url));\n let walkingDirectory = thisModuleDirectory;\n for (let walkDepth = 0; walkDepth < 8; walkDepth += 1) {\n const candidate = path.join(\n walkingDirectory,\n 'packages',\n 'create-donut-game',\n 'template',\n );\n checkedPaths.push(candidate);\n if (await fileSystemExtra.pathExists(candidate)) {\n return candidate;\n }\n const parentDirectory = path.dirname(walkingDirectory);\n if (parentDirectory === walkingDirectory) {\n break;\n }\n walkingDirectory = parentDirectory;\n }\n\n throw new Error(\n `Unable to locate the @donut-games/create-game template directory. Checked:\\n ${checkedPaths.join(\n '\\n ',\n )}`,\n );\n}\n\n/**\n * Run `pnpm install` (falling back to `npm install`) in the scaffolded\n * directory. A non-zero exit or a missing package manager prints a yellow\n * warning but never throws — init is best-effort on the install step.\n */\nasync function runDependencyInstall(\n workingDirectory: string,\n log: (message: string) => void,\n): Promise<void> {\n const candidatePackageManagers: readonly string[] = ['pnpm', 'npm'];\n for (const packageManager of candidatePackageManagers) {\n const exitCode = await spawnInstall(packageManager, workingDirectory);\n if (exitCode === 'binary-missing') {\n continue;\n }\n if (exitCode === 0) {\n return;\n }\n break;\n }\n log(\n chalk.yellow(\n '\\u26a0 dependency install failed, run `npm install` manually',\n ),\n );\n}\n\n/**\n * Spawn `<packageManager> install` with inherited stdio. Resolves with the\n * exit code, or the literal `'binary-missing'` when the binary cannot be found.\n */\nfunction spawnInstall(\n packageManager: string,\n workingDirectory: string,\n): Promise<number | 'binary-missing'> {\n return new Promise((resolve) => {\n const child = spawn(packageManager, ['install'], {\n cwd: workingDirectory,\n stdio: 'inherit',\n shell: false,\n });\n child.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'ENOENT') {\n resolve('binary-missing');\n return;\n }\n resolve(1);\n });\n child.on('exit', (code) => {\n resolve(code ?? 1);\n });\n });\n}\n","import path from 'node:path';\nimport fileSystemExtra from 'fs-extra';\n\n/**\n * Token values substituted into template files during {@link copyTemplate}.\n *\n * Every token maps to a single occurrence of `{{tokenName}}` in template\n * contents. Versions are resolved by the caller from the CLI package.json\n * so the scaffolder always ships alongside a known engine version.\n */\nexport interface TemplateTokens {\n readonly projectName: string;\n readonly rendererTarget: 'pixi-2d' | 'three-3d';\n readonly donutEngineVersion: string;\n readonly pixiVersion: string;\n readonly threeVersion: string;\n}\n\n/**\n * Options accepted by {@link copyTemplate}.\n */\nexport interface CopyTemplateOptions {\n /** Absolute path to the `template/` directory shipped by @donut-games/create-game. */\n readonly sourceDirectory: string;\n /** Absolute path to the new project directory to write into. */\n readonly destinationDirectory: string;\n /** Renderer variant overlay to apply on top of the `base/` layer. */\n readonly variant: 'pixi-2d' | 'three-3d';\n /** Token values substituted into `.tpl` files and file-name tokens. */\n readonly tokens: TemplateTokens;\n /**\n * Optional progress callback invoked once per written file with the file's\n * path relative to {@link CopyTemplateOptions.destinationDirectory}.\n */\n readonly logProgress?: (relativePath: string) => void;\n}\n\n/**\n * Result of {@link copyTemplate}.\n */\nexport interface CopyTemplateResult {\n /**\n * Every file written, relative to {@link CopyTemplateOptions.destinationDirectory}.\n * Deduplicated in the order each relative path was first written.\n */\n readonly createdFiles: readonly string[];\n}\n\nconst TEMPLATE_FILE_SUFFIX = '.tpl';\n\n/**\n * Copy the `base/` layer of {@link CopyTemplateOptions.sourceDirectory}, then\n * overlay the `<variant>/` layer (later writes win). Applies rename rules and\n * token substitution described in {@link applyRenameRules} and\n * {@link renderTemplate}.\n *\n * Returns the set of relative paths written. Duplicate paths (a base file\n * overwritten by a variant file) appear only once in the returned array, in\n * the order of first write.\n */\nexport async function copyTemplate(\n options: CopyTemplateOptions,\n): Promise<CopyTemplateResult> {\n const { sourceDirectory, destinationDirectory, variant, tokens, logProgress } = options;\n\n await fileSystemExtra.ensureDir(destinationDirectory);\n\n const writtenFilesInOrder: string[] = [];\n const seenRelativePaths = new Set<string>();\n\n const recordWrite = (relativePath: string): void => {\n if (seenRelativePaths.has(relativePath)) {\n return;\n }\n seenRelativePaths.add(relativePath);\n writtenFilesInOrder.push(relativePath);\n };\n\n const copyLayer = async (layerDirectory: string): Promise<void> => {\n if ((await fileSystemExtra.pathExists(layerDirectory)) === false) {\n return;\n }\n const layerEntries = await collectFilesRecursively(layerDirectory);\n for (const sourceRelativePath of layerEntries) {\n const destinationRelativePath = applyRenameRulesToPath(sourceRelativePath);\n const absoluteSourcePath = path.join(layerDirectory, sourceRelativePath);\n const absoluteDestinationPath = path.join(\n destinationDirectory,\n destinationRelativePath,\n );\n\n await fileSystemExtra.ensureDir(path.dirname(absoluteDestinationPath));\n\n if (destinationRelativePath.endsWith(TEMPLATE_FILE_SUFFIX)) {\n // Should not happen: applyRenameRulesToPath strips the suffix below.\n throw new Error(\n `Template suffix was not stripped for ${destinationRelativePath}`,\n );\n }\n\n if (sourceRelativePath.endsWith(TEMPLATE_FILE_SUFFIX)) {\n const rawContents = await fileSystemExtra.readFile(absoluteSourcePath, 'utf8');\n const renderedContents = renderTemplate(rawContents, tokens);\n await fileSystemExtra.writeFile(\n absoluteDestinationPath,\n renderedContents,\n 'utf8',\n );\n } else {\n await fileSystemExtra.copyFile(absoluteSourcePath, absoluteDestinationPath);\n }\n\n recordWrite(destinationRelativePath);\n if (logProgress !== undefined) {\n logProgress(destinationRelativePath);\n }\n }\n };\n\n await copyLayer(path.join(sourceDirectory, 'base'));\n await copyLayer(path.join(sourceDirectory, variant));\n\n return { createdFiles: writtenFilesInOrder };\n}\n\n/**\n * Apply filename rename rules to a single file name (no path separators).\n *\n * A leading `_` is stripped and replaced by `.` so template directories can\n * carry what would otherwise be dotfiles through npm packaging. Names not\n * starting with `_` are returned unchanged. A trailing `.tpl` suffix is NOT\n * handled here; callers that walk paths handle it separately.\n */\nexport function applyRenameRules(fileName: string): string {\n if (fileName.length > 0 && fileName.startsWith('_')) {\n return '.' + fileName.slice(1);\n }\n return fileName;\n}\n\n/**\n * Replace every known `{{tokenName}}` occurrence in `contents` with the\n * corresponding value from `tokens`. Unknown tokens (any `{{…}}` that does\n * not match a known token name) are left untouched so legitimate template\n * code containing braces is never damaged.\n */\nexport function renderTemplate(\n contents: string,\n tokens: TemplateTokens,\n): string {\n let rendered = contents;\n const orderedTokenNames: readonly (keyof TemplateTokens)[] = [\n 'projectName',\n 'rendererTarget',\n 'donutEngineVersion',\n 'pixiVersion',\n 'threeVersion',\n ];\n for (const tokenName of orderedTokenNames) {\n const placeholder = '{{' + tokenName + '}}';\n const tokenValue = tokens[tokenName];\n rendered = rendered.split(placeholder).join(tokenValue);\n }\n return rendered;\n}\n\n/**\n * Apply {@link applyRenameRules} to each segment of a relative path, and\n * strip the trailing `.tpl` suffix from the final segment when present. Path\n * separators are preserved so directory names are renamed consistently.\n */\nfunction applyRenameRulesToPath(relativePath: string): string {\n const segments = relativePath.split(path.sep);\n const renamedSegments = segments.map((segment) => applyRenameRules(segment));\n const lastSegmentIndex = renamedSegments.length - 1;\n const lastSegment = renamedSegments[lastSegmentIndex];\n if (lastSegment.endsWith(TEMPLATE_FILE_SUFFIX)) {\n renamedSegments[lastSegmentIndex] = lastSegment.slice(\n 0,\n lastSegment.length - TEMPLATE_FILE_SUFFIX.length,\n );\n }\n return renamedSegments.join(path.sep);\n}\n\n/**\n * Recursively walk `rootDirectory` and return every file's path relative to\n * `rootDirectory`, using the platform path separator. Directories themselves\n * are not returned; empty directories contribute nothing.\n */\nasync function collectFilesRecursively(\n rootDirectory: string,\n): Promise<readonly string[]> {\n const collectedPaths: string[] = [];\n\n const walkDirectory = async (absoluteDirectory: string): Promise<void> => {\n const directoryEntries = await fileSystemExtra.readdir(absoluteDirectory, {\n withFileTypes: true,\n });\n for (const directoryEntry of directoryEntries) {\n const absoluteEntryPath = path.join(absoluteDirectory, directoryEntry.name);\n if (directoryEntry.isDirectory()) {\n await walkDirectory(absoluteEntryPath);\n continue;\n }\n if (directoryEntry.isFile()) {\n const relativePath = path.relative(rootDirectory, absoluteEntryPath);\n collectedPaths.push(relativePath);\n }\n }\n };\n\n await walkDirectory(rootDirectory);\n collectedPaths.sort();\n return collectedPaths;\n}\n","import path from 'node:path';\nimport fileSystemExtra from 'fs-extra';\nimport chalk from 'chalk';\nimport { resolveMonorepoDevAliases } from '../shared/monorepo-aliases.js';\n\n/**\n * Options accepted by {@link runDevServer}.\n */\nexport interface RunDevServerOptions {\n /** Absolute path to the Donut project directory containing `donut.json`. */\n readonly projectDirectory: string;\n /** Port the Vite dev server should listen on. Defaults to 5173. */\n readonly port?: number;\n /** Whether Vite should open a browser window on start. Defaults to true. */\n readonly open?: boolean;\n /** Optional logger — defaults to `console.log`. Injected for tests. */\n readonly log?: (message: string) => void;\n /**\n * Optional hook to start Vite. Injected for tests so unit tests do not\n * need to bind to a network port. When omitted, the real Vite programmatic\n * API is loaded and used.\n */\n readonly startViteServer?: (viteConfig: Record<string, unknown>) => Promise<{\n listen(): Promise<unknown>;\n resolvedUrls?: { local?: readonly string[] };\n }>;\n}\n\n/**\n * Shape of the `donut.json` project manifest that this command needs.\n */\nexport interface DonutProjectManifest {\n readonly name: string;\n readonly rendererTarget: 'pixi-2d' | 'three-3d';\n readonly donutEngineVersion?: string;\n}\n\n/**\n * Reads and validates the `donut.json` manifest inside the given directory.\n *\n * @throws when the manifest is missing or malformed.\n */\nexport async function readProjectManifest(\n projectDirectory: string,\n): Promise<DonutProjectManifest> {\n const manifestPath = path.join(projectDirectory, 'donut.json');\n if (!(await fileSystemExtra.pathExists(manifestPath))) {\n throw new Error(\n `No donut.json found in ${projectDirectory}. Run 'donut init' first.`,\n );\n }\n const parsed = (await fileSystemExtra.readJson(manifestPath)) as Partial<DonutProjectManifest>;\n if (typeof parsed.name !== 'string' || parsed.name.length === 0) {\n throw new Error(`donut.json is missing required field 'name'.`);\n }\n if (parsed.rendererTarget !== 'pixi-2d' && parsed.rendererTarget !== 'three-3d') {\n throw new Error(\n `donut.json has invalid 'rendererTarget' — expected 'pixi-2d' or 'three-3d'.`,\n );\n }\n return {\n name: parsed.name,\n rendererTarget: parsed.rendererTarget,\n donutEngineVersion: parsed.donutEngineVersion,\n };\n}\n\n/**\n * Determines whether the project directory sits inside the donut-engine\n * monorepo by walking up looking for a `pnpm-workspace.yaml`. Returns the\n * absolute path to the monorepo root, or `undefined` if not found.\n */\nexport async function detectMonorepoRoot(\n projectDirectory: string,\n): Promise<string | undefined> {\n let currentDirectory = path.resolve(projectDirectory);\n for (let depth = 0; depth < 8; depth++) {\n const workspaceFile = path.join(currentDirectory, 'pnpm-workspace.yaml');\n if (await fileSystemExtra.pathExists(workspaceFile)) {\n return currentDirectory;\n }\n const parentDirectory = path.dirname(currentDirectory);\n if (parentDirectory === currentDirectory) {\n return undefined;\n }\n currentDirectory = parentDirectory;\n }\n return undefined;\n}\n\n/**\n * Writes the generated runtime entry module and `index.html` into\n * `<projectDirectory>/.donut/`. Returns the directory path that Vite should\n * use as its `root`.\n */\nexport async function writeDevEntryFiles(\n projectDirectory: string,\n manifest: DonutProjectManifest,\n): Promise<string> {\n const donutDirectory = path.join(projectDirectory, '.donut');\n await fileSystemExtra.ensureDir(donutDirectory);\n\n // Ensure .donut is git-ignored so generated artifacts don't leak in.\n const gitIgnorePath = path.join(donutDirectory, '.gitignore');\n await fileSystemExtra.writeFile(gitIgnorePath, '*\\n', 'utf8');\n\n const indexHtmlPath = path.join(donutDirectory, 'index.html');\n await fileSystemExtra.writeFile(indexHtmlPath, buildIndexHtml(manifest), 'utf8');\n\n const devEntryPath = path.join(donutDirectory, 'dev-entry.ts');\n await fileSystemExtra.writeFile(devEntryPath, buildDevEntrySource(manifest), 'utf8');\n\n return donutDirectory;\n}\n\n/**\n * Generates the HTML document served by the dev server.\n */\nfunction buildIndexHtml(manifest: DonutProjectManifest): string {\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${manifest.name} — Donut dev</title>\n <style>\n html, body { margin: 0; padding: 0; height: 100%; background: #0a0a12; overflow: hidden; }\n canvas#game-canvas { display: block; width: 100vw; height: 100vh; }\n </style>\n </head>\n <body>\n <canvas id=\"game-canvas\"></canvas>\n <script type=\"module\" src=\"./dev-entry.ts\"></script>\n </body>\n</html>\n`;\n}\n\n/**\n * Generates the TypeScript entry module served by Vite. Delegates the entire\n * runtime surface to `@donut/player`'s `startGame` — the same function used\n * by `pnpm --filter @donut/player dev` and (in a follow-up) by the hosted\n * bundle viewer on donut.games.\n *\n * The glob side-effect imports ensure every component/system file in the\n * project is pulled into the bundle, so HMR picks up edits to any file\n * even when nothing in the scene file references it directly.\n */\nfunction buildDevEntrySource(manifest: DonutProjectManifest): string {\n return `// AUTO-GENERATED by \\`donut dev\\`. Do not edit — regenerated every launch.\nimport { startGame } from '@donut/player';\nimport { createDefaultScene } from '../src/scenes/default-scene';\n\nconst discoveredComponentModules = import.meta.glob('../src/components/**/*.ts', { eager: true });\nconst discoveredSystemModules = import.meta.glob('../src/systems/**/*.ts', { eager: true });\n\nfor (const modulePath of Object.keys(discoveredComponentModules)) {\n console.log('[Donut dev] discovered component module:', modulePath);\n}\nfor (const modulePath of Object.keys(discoveredSystemModules)) {\n console.log('[Donut dev] discovered system module:', modulePath);\n}\n\nasync function bootstrapDevelopmentRuntime(): Promise<void> {\n const canvasElement = document.getElementById('game-canvas');\n if (!(canvasElement instanceof HTMLCanvasElement)) {\n throw new Error('[Donut dev] Missing <canvas id=\"game-canvas\">');\n }\n\n await startGame({\n canvas: canvasElement,\n rendererTarget: '${manifest.rendererTarget}',\n sceneFactory: createDefaultScene,\n enableDevelopmentBridge: true,\n enableDebugOverlay: true,\n });\n\n console.log('[Donut dev] booted renderer=${manifest.rendererTarget}');\n}\n\nbootstrapDevelopmentRuntime().catch((error) => {\n console.error('[Donut dev] bootstrap failed', error);\n});\n`;\n}\n\n/**\n * Builds the in-memory Vite config for serving the generated `.donut/` dir.\n * Exposed for tests.\n */\nexport async function buildViteConfig(parameters: {\n readonly projectDirectory: string;\n readonly donutRoot: string;\n readonly port: number;\n readonly open: boolean;\n}): Promise<Record<string, unknown>> {\n const { projectDirectory, donutRoot, port, open } = parameters;\n const monorepoRoot = await detectMonorepoRoot(projectDirectory);\n\n const resolveAlias =\n monorepoRoot !== undefined\n ? await resolveMonorepoDevAliases(monorepoRoot)\n : [];\n\n return {\n root: donutRoot,\n configFile: false,\n server: {\n port,\n open,\n strictPort: false,\n fs: {\n allow: [\n projectDirectory,\n ...(monorepoRoot !== undefined ? [monorepoRoot] : []),\n ],\n },\n },\n resolve: {\n alias: resolveAlias,\n },\n optimizeDeps: {\n // Let Vite figure out deps from the generated entry.\n },\n };\n}\n\n/**\n * Starts the Donut dev server for the given project directory.\n *\n * Reads `donut.json`, generates a runtime entry + HTML under\n * `<projectDirectory>/.donut/`, and launches Vite programmatically.\n */\nexport async function runDevServer(options: RunDevServerOptions): Promise<void> {\n const {\n projectDirectory,\n port = 5173,\n open = true,\n log = (message: string) => console.log(message),\n startViteServer,\n } = options;\n\n const manifest = await readProjectManifest(projectDirectory);\n log(chalk.cyan(`Donut dev — project '${manifest.name}' (${manifest.rendererTarget})`));\n\n const donutRoot = await writeDevEntryFiles(projectDirectory, manifest);\n const viteConfig = await buildViteConfig({ projectDirectory, donutRoot, port, open });\n\n const startServer =\n startViteServer ??\n (async (configuration: Record<string, unknown>) => {\n const viteModule = await import('vite');\n return viteModule.createServer(configuration as never);\n });\n\n const server = await startServer(viteConfig);\n await server.listen();\n const resolvedLocalUrl = server.resolvedUrls?.local?.[0];\n if (resolvedLocalUrl !== undefined) {\n log(chalk.green(`Dev server ready at ${resolvedLocalUrl}`));\n } else {\n log(chalk.green(`Dev server listening on port ${port}`));\n }\n}\n","import path from 'node:path';\nimport fileSystemExtra from 'fs-extra';\n\n/**\n * Names of the internal `@donut/<name>` workspace packages that get aliased\n * onto their source directories when running inside the monorepo.\n */\nconst INTERNAL_DONUT_PACKAGE_NAMES: readonly string[] = [\n 'core',\n 'math',\n 'pixi',\n 'three',\n 'ctrllr',\n 'player',\n];\n\n/**\n * Published `@donut-games/engine` subpaths to alias onto the umbrella package's\n * source barrels. The umbrella bare specifier (no subpath) gets mapped to\n * `src/index.ts`. The subpath specifiers map to `src/<subpath>/index.ts`.\n */\nconst PUBLISHED_ENGINE_SUBPATH_NAMES: readonly string[] = [\n 'math',\n 'core',\n 'ctrllr',\n 'pixi',\n 'three',\n 'player',\n];\n\n/**\n * Describes the exports object shape as it appears in\n * `packages/donut-engine/package.json`.\n */\ninterface DonutEnginePackageManifest {\n readonly exports?: Record<string, unknown>;\n}\n\n/**\n * Single Vite alias entry — `find` may be a string (prefix match) or a\n * regular expression (exact/anchored match). Array form is required because\n * Vite's string-prefix aliases match in array order and we need the more\n * specific `@donut-games/engine/<subpath>` entries to take priority over the\n * bare umbrella `@donut-games/engine`. With the record form, Vite would\n * iterate insertion order but still prefix-match the umbrella first against\n * `@donut-games/engine/core`, producing `src/index.ts/core` (ENOTDIR).\n */\nexport interface MonorepoDevAliasEntry {\n readonly find: string | RegExp;\n readonly replacement: string;\n}\n\n/**\n * Build the Vite `resolve.alias` array that redirects published and internal\n * Donut specifiers onto their in-monorepo source directories, so both `donut\n * dev` and `donut build` run directly against TypeScript sources without\n * needing a `tsup` rebuild of the umbrella package first.\n *\n * Returns an empty array when any expected source path is missing — callers\n * that depend on monorepo-relative aliasing should have already checked\n * `detectMonorepoRoot` before invoking this helper.\n *\n * Also runs a dev-time assertion: every subpath declared in the umbrella\n * package's `exports` field must have a matching alias entry. This catches\n * regressions where a new subpath is added to the umbrella without the\n * corresponding alias — otherwise HMR would silently fall through to the\n * compiled `dist/` copy, which would be stale during source-level editing.\n */\nexport async function resolveMonorepoDevAliases(\n monorepoRoot: string,\n): Promise<MonorepoDevAliasEntry[]> {\n const packagesDirectory = path.join(monorepoRoot, 'packages');\n const resolvedAliasEntries: MonorepoDevAliasEntry[] = [];\n\n // Published `@donut-games/engine/<subpath>` aliases come FIRST so Vite's\n // prefix matcher hits them before the umbrella bare specifier below.\n const umbrellaSourceDirectory = path.join(packagesDirectory, 'donut-engine', 'src');\n for (const subpathName of PUBLISHED_ENGINE_SUBPATH_NAMES) {\n const subpathBarrelPath = path.join(\n umbrellaSourceDirectory,\n subpathName,\n 'index.ts',\n );\n if (await fileSystemExtra.pathExists(subpathBarrelPath)) {\n resolvedAliasEntries.push({\n find: `@donut-games/engine/${subpathName}`,\n replacement: subpathBarrelPath,\n });\n }\n }\n\n // The bare umbrella specifier uses an anchored regex so it only matches\n // `@donut-games/engine` exactly — never `@donut-games/engine/core` etc.\n const umbrellaBarrelPath = path.join(umbrellaSourceDirectory, 'index.ts');\n if (await fileSystemExtra.pathExists(umbrellaBarrelPath)) {\n resolvedAliasEntries.push({\n find: /^@donut-games\\/engine$/,\n replacement: umbrellaBarrelPath,\n });\n }\n\n // Legacy internal `@donut/*` aliases — kept alongside the published\n // ones so any remaining monorepo consumers that still declare workspace\n // deps on the internal packages continue to resolve against source.\n for (const internalPackageName of INTERNAL_DONUT_PACKAGE_NAMES) {\n const internalSourceDirectory = path.join(\n packagesDirectory,\n internalPackageName,\n 'src',\n );\n if (await fileSystemExtra.pathExists(internalSourceDirectory)) {\n resolvedAliasEntries.push({\n find: `@donut/${internalPackageName}`,\n replacement: internalSourceDirectory,\n });\n }\n }\n\n await assertEveryPublishedSubpathHasAnAlias(monorepoRoot, resolvedAliasEntries);\n return resolvedAliasEntries;\n}\n\n/**\n * Walk the `exports` field of `packages/donut-engine/package.json` and\n * verify every subpath has a matching entry in the alias map. Throws when\n * a subpath is missing — this is a developer-visible fail-fast so nobody\n * accidentally ships a new subpath without the dev/build aliases.\n *\n * The `.` and `./package.json` entries are skipped because they either map\n * to the umbrella specifier (already covered) or don't need aliasing.\n */\nasync function assertEveryPublishedSubpathHasAnAlias(\n monorepoRoot: string,\n resolvedAliasEntries: readonly MonorepoDevAliasEntry[],\n): Promise<void> {\n const umbrellaPackageJsonPath = path.join(\n monorepoRoot,\n 'packages',\n 'donut-engine',\n 'package.json',\n );\n if (!(await fileSystemExtra.pathExists(umbrellaPackageJsonPath))) {\n return;\n }\n const parsedManifest = (await fileSystemExtra.readJson(\n umbrellaPackageJsonPath,\n )) as DonutEnginePackageManifest;\n const exportsField = parsedManifest.exports;\n if (exportsField === undefined || exportsField === null) {\n return;\n }\n\n const aliasedStringFinds = new Set(\n resolvedAliasEntries\n .map((entry) => entry.find)\n .filter((find): find is string => typeof find === 'string'),\n );\n\n const missingAliasSubpaths: string[] = [];\n for (const exportKey of Object.keys(exportsField)) {\n if (exportKey === '.' || exportKey === './package.json') {\n continue;\n }\n if (!exportKey.startsWith('./')) {\n continue;\n }\n const subpathName = exportKey.slice(2);\n const expectedAliasKey = `@donut-games/engine/${subpathName}`;\n if (!aliasedStringFinds.has(expectedAliasKey)) {\n missingAliasSubpaths.push(expectedAliasKey);\n }\n }\n\n if (missingAliasSubpaths.length > 0) {\n throw new Error(\n `resolveMonorepoDevAliases: missing Vite aliases for published subpaths — ` +\n `${missingAliasSubpaths.join(', ')}. Every subpath in ` +\n `packages/donut-engine/package.json \"exports\" must have a matching alias.`,\n );\n }\n}\n","import path from 'node:path';\nimport { spawn } from 'node:child_process';\nimport { createRequire } from 'node:module';\nimport { fileURLToPath } from 'node:url';\nimport fileSystemExtra from 'fs-extra';\n\n/**\n * A single validation finding emitted by {@link runValidation}.\n */\nexport interface ValidationError {\n /** Absolute or project-relative path of the offending file. */\n readonly filePath: string;\n /** 1-based line number when known. */\n readonly line?: number;\n /** 1-based column number when known. */\n readonly column?: number;\n /** Human-readable explanation of the problem. */\n readonly message: string;\n /** Stable machine-readable identifier (e.g. \"MANIFEST_INVALID\"). */\n readonly code: string;\n}\n\n/**\n * Aggregate report returned by {@link runValidation}.\n */\nexport interface ValidationReport {\n readonly errors: ValidationError[];\n readonly warnings: ValidationError[];\n}\n\n/**\n * Options accepted by {@link runValidation}.\n */\nexport interface RunValidationOptions {\n /** Absolute path to the project root to validate. */\n readonly projectDirectory: string;\n /** When true, skip the (slow) `tsc --noEmit` pass. Useful for tests. */\n readonly skipTypeCheck?: boolean;\n}\n\n/**\n * Parsed representation of the subset of `donut.json` fields that the\n * validator needs to route subsequent checks. Returned by\n * {@link validateManifest} when the manifest itself is structurally valid.\n */\nexport interface ParsedManifest {\n readonly name: string;\n readonly version: string;\n readonly rendererTarget: string;\n readonly donutEngineVersion: string;\n}\n\nconst REQUIRED_MANIFEST_FIELDS = ['name', 'version', 'rendererTarget', 'donutEngineVersion'] as const;\nconst ALLOWED_RENDERER_TARGETS = new Set(['pixi-2d', 'three-3d']);\n\n/**\n * Forbidden import prefixes inside `src/components/**` and `src/systems/**`.\n * Game logic must stay renderer-agnostic. The legacy `@donut/pixi` and\n * `@donut/three` entries remain during the transition to the published\n * `@donut-games/engine` surface so that in-monorepo projects keep failing\n * as expected.\n */\nconst GAME_LOGIC_FORBIDDEN_RENDERER_IMPORT_PREFIXES = [\n '@donut-games/engine/pixi',\n '@donut-games/engine/three',\n '@donut/pixi',\n '@donut/three',\n] as const;\n\n/**\n * Mapping from a project's `rendererTarget` to the renderer subpath that is\n * disallowed anywhere under `src/**` for that project. The other subpath is\n * the \"correct\" renderer for the target and remains allowed in scene files.\n */\nconst WRONG_RENDERER_FORBIDDEN_IMPORT_BY_TARGET: Record<string, string> = {\n 'pixi-2d': '@donut-games/engine/three',\n 'three-3d': '@donut-games/engine/pixi',\n};\n\n/**\n * Mapping from a project's `rendererTarget` to the renderer subpath that is\n * permitted for renderer-specific code in that project. Used only for\n * error-message construction.\n */\nconst WRONG_RENDERER_ALLOWED_IMPORT_BY_TARGET: Record<string, string> = {\n 'pixi-2d': '@donut-games/engine/pixi',\n 'three-3d': '@donut-games/engine/three',\n};\n\n/**\n * Run all validation checks against a Donut project directory and return a\n * structured {@link ValidationReport}. Never throws for validation problems;\n * only throws for unexpected I/O failures.\n */\nexport async function runValidation(\n options: RunValidationOptions,\n): Promise<ValidationReport> {\n const { projectDirectory } = options;\n const errors: ValidationError[] = [];\n const warnings: ValidationError[] = [];\n\n const parsedManifest = await validateManifest(projectDirectory, errors);\n\n if (options.skipTypeCheck !== true) {\n await validateTypes(projectDirectory, errors);\n }\n\n const rendererTarget = parsedManifest?.rendererTarget;\n await validateImportGraph(projectDirectory, rendererTarget, errors);\n await validateAssetReferences(projectDirectory, errors);\n\n return { errors, warnings };\n}\n\nasync function validateManifest(\n projectDirectory: string,\n errors: ValidationError[],\n): Promise<ParsedManifest | null> {\n const manifestPath = path.join(projectDirectory, 'donut.json');\n if (!(await fileSystemExtra.pathExists(manifestPath))) {\n errors.push({\n filePath: manifestPath,\n message: 'donut.json is missing at the project root',\n code: 'MANIFEST_INVALID',\n });\n return null;\n }\n let raw: string;\n try {\n raw = await fileSystemExtra.readFile(manifestPath, 'utf8');\n } catch (readError) {\n errors.push({\n filePath: manifestPath,\n message: `Unable to read donut.json: ${(readError as Error).message}`,\n code: 'MANIFEST_INVALID',\n });\n return null;\n }\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (parseError) {\n errors.push({\n filePath: manifestPath,\n message: `donut.json is not valid JSON: ${(parseError as Error).message}`,\n code: 'MANIFEST_INVALID',\n });\n return null;\n }\n let hasRequiredFieldError = false;\n for (const fieldName of REQUIRED_MANIFEST_FIELDS) {\n const value = parsed[fieldName];\n if (typeof value !== 'string' || value.trim().length === 0) {\n errors.push({\n filePath: manifestPath,\n message: `donut.json missing required string field \"${fieldName}\"`,\n code: 'MANIFEST_INVALID',\n });\n hasRequiredFieldError = true;\n }\n }\n const rendererTargetValue = parsed.rendererTarget;\n if (\n typeof rendererTargetValue === 'string' &&\n !ALLOWED_RENDERER_TARGETS.has(rendererTargetValue)\n ) {\n errors.push({\n filePath: manifestPath,\n message: `donut.json \"rendererTarget\" must be one of pixi-2d, three-3d (got \"${rendererTargetValue}\")`,\n code: 'MANIFEST_INVALID',\n });\n }\n if (hasRequiredFieldError) {\n return null;\n }\n return {\n name: parsed.name as string,\n version: parsed.version as string,\n rendererTarget: parsed.rendererTarget as string,\n donutEngineVersion: parsed.donutEngineVersion as string,\n };\n}\n\nasync function validateTypes(\n projectDirectory: string,\n errors: ValidationError[],\n): Promise<void> {\n const tsconfigPath = path.join(projectDirectory, 'tsconfig.json');\n if (!(await fileSystemExtra.pathExists(tsconfigPath))) {\n return;\n }\n const typeScriptCompilerPath = resolveTypeScriptCompiler(projectDirectory);\n if (typeScriptCompilerPath === undefined) {\n errors.push({\n filePath: tsconfigPath,\n message: 'Unable to locate a TypeScript compiler (tsc) to run the type-check',\n code: 'TYPE_ERROR',\n });\n return;\n }\n\n const { stdout, stderr } = await runProcess(\n process.execPath,\n [typeScriptCompilerPath, '--noEmit', '-p', tsconfigPath],\n projectDirectory,\n );\n const combined = `${stdout}\\n${stderr}`;\n const diagnosticLineRegex =\n /^([^\\s(][^(]*?)\\((\\d+),(\\d+)\\):\\s+error\\s+(TS\\d+):\\s+(.+)$/gm;\n let match: RegExpExecArray | null;\n while ((match = diagnosticLineRegex.exec(combined)) !== null) {\n const [, rawFilePath, lineText, columnText, typeScriptCode, messageText] = match;\n const absoluteFilePath = path.isAbsolute(rawFilePath)\n ? rawFilePath\n : path.join(projectDirectory, rawFilePath);\n errors.push({\n filePath: absoluteFilePath,\n line: Number.parseInt(lineText, 10),\n column: Number.parseInt(columnText, 10),\n message: `${typeScriptCode}: ${messageText.trim()}`,\n code: 'TYPE_ERROR',\n });\n }\n}\n\nfunction resolveTypeScriptCompiler(projectDirectory: string): string | undefined {\n const projectLocal = path.join(\n projectDirectory,\n 'node_modules',\n 'typescript',\n 'bin',\n 'tsc',\n );\n if (fileSystemExtra.existsSync(projectLocal)) {\n return projectLocal;\n }\n try {\n const requireFromHere = createRequire(import.meta.url);\n const typeScriptPackageJsonPath = requireFromHere.resolve('typescript/package.json');\n const typeScriptPackageDirectory = path.dirname(typeScriptPackageJsonPath);\n const fallback = path.join(typeScriptPackageDirectory, 'bin', 'tsc');\n if (fileSystemExtra.existsSync(fallback)) {\n return fallback;\n }\n } catch {\n // ignore\n }\n return undefined;\n}\n\nfunction runProcess(\n command: string,\n argumentList: string[],\n workingDirectory: string,\n): Promise<{ stdout: string; stderr: string; exitCode: number | null }> {\n return new Promise((resolve) => {\n const child = spawn(command, argumentList, {\n cwd: workingDirectory,\n env: process.env,\n });\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString('utf8');\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf8');\n });\n child.on('close', (exitCode) => {\n resolve({ stdout, stderr, exitCode });\n });\n child.on('error', () => {\n resolve({ stdout, stderr, exitCode: null });\n });\n });\n}\n\nasync function validateImportGraph(\n projectDirectory: string,\n rendererTarget: string | undefined,\n errors: ValidationError[],\n): Promise<void> {\n await validateGameLogicImports(projectDirectory, errors);\n await validateWrongRendererImports(projectDirectory, rendererTarget, errors);\n}\n\n/**\n * Game-logic rule: scan `src/components/**` and `src/systems/**` and flag any\n * renderer-specific import regardless of the project's `rendererTarget`.\n * Game logic must remain renderer-agnostic so components and systems are\n * portable across 2D and 3D projects.\n */\nasync function validateGameLogicImports(\n projectDirectory: string,\n errors: ValidationError[],\n): Promise<void> {\n const gameLogicDirectories = [\n path.join(projectDirectory, 'src', 'components'),\n path.join(projectDirectory, 'src', 'systems'),\n ];\n const importLineRegex = /^\\s*import\\s+[^;]*?from\\s+['\"]([^'\"]+)['\"]/;\n for (const directoryPath of gameLogicDirectories) {\n if (!(await fileSystemExtra.pathExists(directoryPath))) {\n continue;\n }\n const typeScriptFiles = await collectTypeScriptFiles(directoryPath);\n for (const filePath of typeScriptFiles) {\n const contents = await fileSystemExtra.readFile(filePath, 'utf8');\n const lines = contents.split(/\\r?\\n/);\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {\n const lineText = lines[lineIndex];\n const matched = importLineRegex.exec(lineText);\n if (matched === null) {\n continue;\n }\n const importedSpecifier = matched[1];\n if (\n GAME_LOGIC_FORBIDDEN_RENDERER_IMPORT_PREFIXES.some(\n (disallowedPrefix) =>\n importedSpecifier === disallowedPrefix ||\n importedSpecifier.startsWith(`${disallowedPrefix}/`),\n )\n ) {\n errors.push({\n filePath,\n line: lineIndex + 1,\n message: `Renderer import \"${importedSpecifier}\" is not allowed in game logic (components/systems must only import @donut-games/engine/core, @donut-games/engine/math, or relative paths)`,\n code: 'RENDERER_IMPORT_IN_GAME_LOGIC',\n });\n }\n }\n }\n }\n}\n\n/**\n * Wrong-renderer rule: scan the entire `src/**` tree and flag any import of\n * the renderer subpath that does not match the project's `rendererTarget`.\n * A `pixi-2d` project may not import `@donut-games/engine/three`, and a\n * `three-3d` project may not import `@donut-games/engine/pixi`.\n */\nasync function validateWrongRendererImports(\n projectDirectory: string,\n rendererTarget: string | undefined,\n errors: ValidationError[],\n): Promise<void> {\n if (rendererTarget === undefined) {\n return;\n }\n const wrongRendererImportSpecifierPrefix =\n WRONG_RENDERER_FORBIDDEN_IMPORT_BY_TARGET[rendererTarget];\n const allowedRendererImportSpecifierPrefix =\n WRONG_RENDERER_ALLOWED_IMPORT_BY_TARGET[rendererTarget];\n if (\n wrongRendererImportSpecifierPrefix === undefined ||\n allowedRendererImportSpecifierPrefix === undefined\n ) {\n return;\n }\n const sourceRoot = path.join(projectDirectory, 'src');\n if (!(await fileSystemExtra.pathExists(sourceRoot))) {\n return;\n }\n const importLineRegex = /^\\s*import\\s+[^;]*?from\\s+['\"]([^'\"]+)['\"]/;\n const typeScriptFiles = await collectTypeScriptFiles(sourceRoot);\n for (const filePath of typeScriptFiles) {\n const contents = await fileSystemExtra.readFile(filePath, 'utf8');\n const lines = contents.split(/\\r?\\n/);\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {\n const lineText = lines[lineIndex];\n const matched = importLineRegex.exec(lineText);\n if (matched === null) {\n continue;\n }\n const importedSpecifier = matched[1];\n if (\n importedSpecifier === wrongRendererImportSpecifierPrefix ||\n importedSpecifier.startsWith(`${wrongRendererImportSpecifierPrefix}/`)\n ) {\n errors.push({\n filePath,\n line: lineIndex + 1,\n message: `Renderer import \"${importedSpecifier}\" is not allowed in a \"${rendererTarget}\" project. This project's donut.json specifies rendererTarget: \"${rendererTarget}\", which only permits ${allowedRendererImportSpecifierPrefix} for renderer-specific code.`,\n code: 'WRONG_RENDERER_IMPORT',\n });\n }\n }\n }\n}\n\nasync function collectTypeScriptFiles(directoryPath: string): Promise<string[]> {\n const collected: string[] = [];\n const entries = await fileSystemExtra.readdir(directoryPath, { withFileTypes: true });\n for (const entry of entries) {\n const entryPath = path.join(directoryPath, entry.name);\n if (entry.isDirectory()) {\n collected.push(...(await collectTypeScriptFiles(entryPath)));\n } else if (entry.isFile() && entry.name.endsWith('.ts')) {\n collected.push(entryPath);\n }\n }\n return collected;\n}\n\nasync function validateAssetReferences(\n projectDirectory: string,\n errors: ValidationError[],\n): Promise<void> {\n const sourceRoot = path.join(projectDirectory, 'src');\n const assetsRoot = path.join(projectDirectory, 'assets');\n if (!(await fileSystemExtra.pathExists(sourceRoot))) {\n return;\n }\n const typeScriptFiles = await collectTypeScriptFiles(sourceRoot);\n const texturePathRegex = /texturePath\\s*(?::[^='\"]*)?=?\\s*['\"]([^'\"]*)['\"]/g;\n const assetsLiteralRegex = /['\"]assets\\/([^'\"]+)['\"]/g;\n\n for (const filePath of typeScriptFiles) {\n const contents = await fileSystemExtra.readFile(filePath, 'utf8');\n const lines = contents.split(/\\r?\\n/);\n const referencedAssets: { relativePath: string; line: number }[] = [];\n\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {\n const lineText = lines[lineIndex];\n texturePathRegex.lastIndex = 0;\n let textureMatch: RegExpExecArray | null;\n while ((textureMatch = texturePathRegex.exec(lineText)) !== null) {\n const referenced = textureMatch[1];\n if (referenced.length === 0) {\n continue;\n }\n referencedAssets.push({ relativePath: referenced, line: lineIndex + 1 });\n }\n assetsLiteralRegex.lastIndex = 0;\n let assetsMatch: RegExpExecArray | null;\n while ((assetsMatch = assetsLiteralRegex.exec(lineText)) !== null) {\n referencedAssets.push({\n relativePath: assetsMatch[1],\n line: lineIndex + 1,\n });\n }\n }\n\n for (const { relativePath, line } of referencedAssets) {\n const normalized = relativePath.startsWith('assets/')\n ? relativePath.slice('assets/'.length)\n : relativePath;\n const absoluteAssetPath = path.join(assetsRoot, normalized);\n if (!(await fileSystemExtra.pathExists(absoluteAssetPath))) {\n errors.push({\n filePath,\n line,\n message: `Referenced asset \"${relativePath}\" not found under assets/`,\n code: 'MISSING_ASSET',\n });\n }\n }\n }\n}\n\n/**\n * Resolve the directory where this module lives; useful for callers that\n * want to look up bundled assets relative to the compiled output.\n */\nexport function getValidateCommandDirectory(): string {\n return path.dirname(fileURLToPath(import.meta.url));\n}\n","import path from 'node:path';\nimport fileSystemExtra from 'fs-extra';\nimport chalk from 'chalk';\nimport JSZip from 'jszip';\nimport {\n readProjectManifest,\n detectMonorepoRoot,\n type DonutProjectManifest,\n} from './dev.js';\nimport { runValidation } from './validate.js';\nimport { resolveMonorepoDevAliases } from '../shared/monorepo-aliases.js';\nimport type {\n BundleManifest,\n BundleManifestAssetEntry,\n} from '../bundle/bundle-manifest.js';\n\n/**\n * A single security-scanner finding produced by {@link scanUserSourcesForForbiddenPatterns}.\n */\nexport interface SecurityViolation {\n /** Absolute path to the offending source file. */\n readonly filePath: string;\n /** 1-based line number. */\n readonly line: number;\n /** Regex source text that matched. */\n readonly pattern: string;\n /** The actual substring that matched. */\n readonly match: string;\n /** `error` fails the build; `warning` is informational. */\n readonly severity: 'error' | 'warning';\n}\n\n/**\n * Options accepted by {@link runBuild}.\n */\nexport interface RunBuildOptions {\n /** Absolute path to the Donut project directory. */\n readonly projectDirectory: string;\n /** Absolute path for produced bundle output; defaults to `<project>/dist`. */\n readonly outputDirectory?: string;\n /** Optional logger — defaults to `console.log`. */\n readonly log?: (message: string) => void;\n /**\n * Optional Vite-build hook. When omitted, the real programmatic `vite.build`\n * API is used. Tests inject a stub that writes a tiny `game.js` instead.\n */\n readonly runViteBuild?: (viteConfig: Record<string, unknown>) => Promise<void>;\n /**\n * Optional image optimizer override used for tests. When omitted, the\n * production helper {@link optimizeImageAssetIfPossible} is used which\n * dynamically imports `sharp` if installed.\n */\n readonly optimizeImageAsset?: ImageAssetOptimizer;\n}\n\n/**\n * Signature for the pluggable per-file image optimizer. Implementations\n * should return the optimized bytes, or `null` to signal pass-through copy.\n */\nexport type ImageAssetOptimizer = (\n sourcePath: string,\n extension: string,\n) => Promise<Buffer | null>;\n\n/**\n * Structured result of a build. Callers can present this to the user.\n */\nexport interface BuildReport {\n /** Absolute path to the emitted `.donut` file. */\n readonly bundlePath: string;\n /** Byte size of the emitted `.donut` file. */\n readonly bundleSizeBytes: number;\n /** Wall-clock duration of the build in milliseconds. */\n readonly buildTimeMilliseconds: number;\n /** Non-fatal findings (e.g. `window`/`document` usage). */\n readonly warnings: readonly SecurityViolation[];\n /** Fatal findings (empty on success; populated when build was aborted). */\n readonly violations: readonly SecurityViolation[];\n /** The generated manifest. */\n readonly manifest: BundleManifest;\n}\n\n/**\n * Thrown when the build is aborted due to fatal security violations or\n * validation errors. Consumers may read `.violations` to render diagnostics.\n */\nexport class BuildViolationError extends Error {\n public readonly violations: readonly SecurityViolation[];\n public constructor(message: string, violations: readonly SecurityViolation[]) {\n super(message);\n this.name = 'BuildViolationError';\n this.violations = violations;\n }\n}\n\n/**\n * Patterns searched for in user-authored game logic sources. The `severity`\n * field controls whether a match aborts the build or only warns.\n */\nconst FORBIDDEN_PATTERNS: ReadonlyArray<{\n readonly pattern: RegExp;\n readonly severity: 'error' | 'warning';\n}> = [\n { pattern: /\\bfetch\\s*\\(/g, severity: 'error' },\n { pattern: /\\blocalStorage\\b/g, severity: 'error' },\n { pattern: /\\bsessionStorage\\b/g, severity: 'error' },\n { pattern: /\\bXMLHttpRequest\\b/g, severity: 'error' },\n { pattern: /\\bWebSocket\\b/g, severity: 'error' },\n { pattern: /\\beval\\s*\\(/g, severity: 'error' },\n { pattern: /new\\s+Function\\s*\\(/g, severity: 'error' },\n { pattern: /\\bimport\\s*\\(/g, severity: 'error' },\n { pattern: /\\.innerHTML\\s*=/g, severity: 'error' },\n { pattern: /\\bwindow\\b/g, severity: 'warning' },\n { pattern: /\\bdocument\\b/g, severity: 'warning' },\n];\n\n/**\n * Recursively collect all `.ts` files under a directory.\n */\nasync function collectTypeScriptFilesRecursively(\n directoryPath: string,\n): Promise<string[]> {\n if (!(await fileSystemExtra.pathExists(directoryPath))) {\n return [];\n }\n const collected: string[] = [];\n const entries = await fileSystemExtra.readdir(directoryPath, { withFileTypes: true });\n for (const entry of entries) {\n const entryPath = path.join(directoryPath, entry.name);\n if (entry.isDirectory()) {\n collected.push(...(await collectTypeScriptFilesRecursively(entryPath)));\n } else if (entry.isFile() && entry.name.endsWith('.ts')) {\n collected.push(entryPath);\n }\n }\n return collected;\n}\n\n/**\n * Scan user-authored source files (components + systems only) for forbidden\n * runtime APIs. Produces structured findings — never throws for matches.\n */\nexport async function scanUserSourcesForForbiddenPatterns(\n projectDirectory: string,\n): Promise<SecurityViolation[]> {\n const directoriesToScan = [\n path.join(projectDirectory, 'src', 'components'),\n path.join(projectDirectory, 'src', 'systems'),\n ];\n const violations: SecurityViolation[] = [];\n for (const directoryPath of directoriesToScan) {\n const files = await collectTypeScriptFilesRecursively(directoryPath);\n for (const filePath of files) {\n const contents = await fileSystemExtra.readFile(filePath, 'utf8');\n const lines = contents.split(/\\r?\\n/);\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {\n const lineText = lines[lineIndex];\n for (const { pattern, severity } of FORBIDDEN_PATTERNS) {\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(lineText)) !== null) {\n violations.push({\n filePath,\n line: lineIndex + 1,\n pattern: pattern.source,\n match: match[0],\n severity,\n });\n }\n }\n }\n }\n }\n return violations;\n}\n\n/**\n * Discriminates which public-import surface the project is wired to. Drives\n * which bare specifiers get written into the generated build-entry module:\n *\n * - `'published'` — project declares `@donut-games/engine` as a dependency;\n * emit subpath imports like `@donut-games/engine/core`.\n * - `'workspace'` — project declares one or more internal `@donut/*` workspace\n * dependencies; emit legacy `@donut/core` etc. imports for back-compat with\n * in-repo smoketest and fixture projects.\n */\nexport type ProjectSourceKind = 'published' | 'workspace';\n\n/**\n * Inspect the project's `package.json` and decide which import surface the\n * generated build entry should target. Prefers `'published'` when both are\n * present (transitional projects), logs a warning in that case, and throws\n * when neither appears.\n */\nexport async function detectProjectSourceKind(\n projectDirectory: string,\n log: (message: string) => void,\n): Promise<ProjectSourceKind> {\n const projectPackageJsonPath = path.join(projectDirectory, 'package.json');\n if (!(await fileSystemExtra.pathExists(projectPackageJsonPath))) {\n throw new Error(\n `Cannot determine project source kind — no package.json found at ${projectPackageJsonPath}.`,\n );\n }\n const parsedManifest = (await fileSystemExtra.readJson(projectPackageJsonPath)) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const mergedDependencies: Record<string, string> = {\n ...(parsedManifest.dependencies ?? {}),\n ...(parsedManifest.devDependencies ?? {}),\n };\n\n const hasPublishedEngineDependency = '@donut-games/engine' in mergedDependencies;\n const hasAnyInternalWorkspaceDependency = Object.keys(mergedDependencies).some(\n (dependencyName) => dependencyName.startsWith('@donut/'),\n );\n\n if (hasPublishedEngineDependency && hasAnyInternalWorkspaceDependency) {\n log(\n chalk.yellow(\n '\\u26a0 Both @donut-games/engine and internal @donut/* workspace dependencies ' +\n 'are declared. Preferring published imports for the build entry.',\n ),\n );\n return 'published';\n }\n if (hasPublishedEngineDependency) {\n return 'published';\n }\n if (hasAnyInternalWorkspaceDependency) {\n return 'workspace';\n }\n\n throw new Error(\n `Cannot determine project source kind — package.json at ${projectPackageJsonPath} ` +\n `declares neither \"@donut-games/engine\" nor any internal \"@donut/*\" workspace dependency. ` +\n `Add \"@donut-games/engine\" to dependencies to build against the published engine surface.`,\n );\n}\n\n/**\n * Produce the auto-generated build entry file mirroring the dev entry but\n * structured as a library entry that exports `startGame` and also auto-starts\n * when `#game-canvas` is present in the DOM at load time.\n *\n * `projectSourceKind` decides whether the generated imports target the\n * published `@donut-games/engine/*` subpaths or the internal `@donut/*`\n * workspace specifiers.\n */\nfunction buildBuildEntrySource(\n manifest: DonutProjectManifest,\n projectSourceKind: ProjectSourceKind,\n): string {\n const usesPixi = manifest.rendererTarget.startsWith('pixi');\n const coreSpecifier =\n projectSourceKind === 'published' ? '@donut-games/engine/core' : '@donut/core';\n const ctrllrSpecifier =\n projectSourceKind === 'published' ? '@donut-games/engine/ctrllr' : '@donut/ctrllr';\n const pixiSpecifier =\n projectSourceKind === 'published' ? '@donut-games/engine/pixi' : '@donut/pixi';\n return `// AUTO-GENERATED by \\`donut build\\`. Do not edit.\nimport { World, SystemType, MotionSystem } from '${coreSpecifier}';\nimport { MockCtrllrManager, CtrllrInputSystem } from '${ctrllrSpecifier}';\n${usesPixi ? `import { PixiRendererAdapter, PixiRenderSystem } from '${pixiSpecifier}';\\n` : ''}import { createDefaultScene } from '../src/scenes/default-scene';\n\nconst discoveredComponentModules = import.meta.glob('../src/components/**/*.ts', { eager: true });\nconst discoveredSystemModules = import.meta.glob('../src/systems/**/*.ts', { eager: true });\nconst discoveredSceneModules = import.meta.glob('../src/scenes/**/*.ts', { eager: true });\nvoid discoveredComponentModules;\nvoid discoveredSystemModules;\nvoid discoveredSceneModules;\n\nexport interface StartGameOptions {\n readonly canvas: HTMLCanvasElement;\n}\n\nexport async function startGame(options: StartGameOptions): Promise<void> {\n const world = new World();\n world.addSystem(new CtrllrInputSystem(world));\n world.addSystem(new MotionSystem(world));\n\n ${usesPixi ? `const rendererAdapter = new PixiRendererAdapter({\n canvas: options.canvas,\n width: options.canvas.width || 800,\n height: options.canvas.height || 600,\n backgroundColor: 0x0a0a12,\n });\n await rendererAdapter.initialize();\n world.addSystem(new PixiRenderSystem(world, rendererAdapter));` : `console.warn('[Donut] three-3d renderer target is not yet implemented');`}\n\n const ctrllrManager = new MockCtrllrManager();\n\n createDefaultScene(world, ctrllrManager);\n\n let previousTimestamp: number | undefined;\n function gameLoop(currentTimestamp: number): void {\n if (previousTimestamp === undefined) previousTimestamp = currentTimestamp;\n const elapsedMilliseconds = currentTimestamp - previousTimestamp;\n previousTimestamp = currentTimestamp;\n world.update(SystemType.Update, elapsedMilliseconds);\n world.update(SystemType.Draw, elapsedMilliseconds);\n requestAnimationFrame(gameLoop);\n }\n requestAnimationFrame(gameLoop);\n}\n\n// Auto-start when a canvas with id=\"game-canvas\" is present.\nif (typeof document !== 'undefined') {\n const canvasElement = document.getElementById('game-canvas');\n if (canvasElement instanceof HTMLCanvasElement) {\n startGame({ canvas: canvasElement }).catch((error) => {\n console.error('[Donut] startGame failed', error);\n });\n }\n}\n`;\n}\n\n/**\n * Write the build-entry module into `<projectDirectory>/.donut/build-entry.ts`.\n */\nasync function writeBuildEntryFile(\n projectDirectory: string,\n manifest: DonutProjectManifest,\n projectSourceKind: ProjectSourceKind,\n): Promise<string> {\n const donutDirectory = path.join(projectDirectory, '.donut');\n await fileSystemExtra.ensureDir(donutDirectory);\n const gitIgnorePath = path.join(donutDirectory, '.gitignore');\n if (!(await fileSystemExtra.pathExists(gitIgnorePath))) {\n await fileSystemExtra.writeFile(gitIgnorePath, '*\\n', 'utf8');\n }\n const entryPath = path.join(donutDirectory, 'build-entry.ts');\n await fileSystemExtra.writeFile(\n entryPath,\n buildBuildEntrySource(manifest, projectSourceKind),\n 'utf8',\n );\n return entryPath;\n}\n\n/**\n * Build the Vite configuration object used by `donut build`.\n */\nexport async function buildViteBuildConfig(parameters: {\n readonly projectDirectory: string;\n readonly donutRoot: string;\n readonly buildEntryPath: string;\n readonly stagingDirectory: string;\n}): Promise<Record<string, unknown>> {\n const { projectDirectory, donutRoot, buildEntryPath, stagingDirectory } = parameters;\n const monorepoRoot = await detectMonorepoRoot(projectDirectory);\n const resolveAlias =\n monorepoRoot !== undefined\n ? await resolveMonorepoDevAliases(monorepoRoot)\n : [];\n return {\n root: donutRoot,\n configFile: false,\n logLevel: 'warn',\n resolve: { alias: resolveAlias },\n build: {\n outDir: stagingDirectory,\n emptyOutDir: true,\n minify: 'esbuild',\n lib: {\n entry: buildEntryPath,\n formats: ['es'],\n fileName: () => 'game.js',\n },\n rollupOptions: {\n output: {\n inlineDynamicImports: true,\n entryFileNames: 'game.js',\n },\n },\n },\n };\n}\n\n/**\n * Recursively copy project assets into the staging directory, returning the\n * manifest entries describing each copied file.\n */\nasync function copyAssetsAndCollectEntries(\n projectAssetsDirectory: string,\n stagingAssetsDirectory: string,\n imageAssetOptimizer: ImageAssetOptimizer,\n log: (message: string) => void,\n): Promise<BundleManifestAssetEntry[]> {\n let sharpUnavailableWarningEmitted = false;\n if (!(await fileSystemExtra.pathExists(projectAssetsDirectory))) {\n return [];\n }\n await fileSystemExtra.ensureDir(stagingAssetsDirectory);\n const entries: BundleManifestAssetEntry[] = [];\n\n async function walk(currentSource: string, currentDestination: string, relativePrefix: string): Promise<void> {\n const directoryEntries = await fileSystemExtra.readdir(currentSource, { withFileTypes: true });\n for (const directoryEntry of directoryEntries) {\n if (directoryEntry.name === '.gitkeep') {\n continue;\n }\n const sourcePath = path.join(currentSource, directoryEntry.name);\n const destinationPath = path.join(currentDestination, directoryEntry.name);\n const relativePath = relativePrefix === ''\n ? directoryEntry.name\n : `${relativePrefix}/${directoryEntry.name}`;\n if (directoryEntry.isDirectory()) {\n await fileSystemExtra.ensureDir(destinationPath);\n await walk(sourcePath, destinationPath, relativePath);\n } else if (directoryEntry.isFile()) {\n const extensionWithDot = path.extname(directoryEntry.name).toLowerCase();\n const extension = extensionWithDot.startsWith('.')\n ? extensionWithDot.slice(1)\n : extensionWithDot;\n const isOptimizableImage = ['png', 'jpg', 'jpeg', 'webp'].includes(extension);\n let wroteOptimizedOutput = false;\n if (isOptimizableImage) {\n const optimizedBuffer = await imageAssetOptimizer(sourcePath, extension);\n if (optimizedBuffer !== null) {\n await fileSystemExtra.writeFile(destinationPath, optimizedBuffer);\n wroteOptimizedOutput = true;\n } else if (!sharpUnavailableWarningEmitted) {\n log(chalk.yellow('\\u2139 sharp not installed \\u2014 assets copied uncompressed'));\n sharpUnavailableWarningEmitted = true;\n }\n }\n if (!wroteOptimizedOutput) {\n await fileSystemExtra.copy(sourcePath, destinationPath);\n }\n const stat = await fileSystemExtra.stat(destinationPath);\n entries.push({\n name: directoryEntry.name,\n path: `assets/${relativePath}`,\n type: extension,\n sizeBytes: stat.size,\n });\n }\n }\n }\n\n await walk(projectAssetsDirectory, stagingAssetsDirectory, '');\n return entries;\n}\n\n/**\n * Read engine package versions from the monorepo `packages/*` directory.\n * Returns a best-effort map — packages that cannot be resolved are omitted.\n */\nasync function collectRuntimeVersions(\n projectDirectory: string,\n): Promise<Record<string, string>> {\n const runtimeVersions: Record<string, string> = {};\n const monorepoRoot = await detectMonorepoRoot(projectDirectory);\n const donutPackageNames = ['core', 'math', 'pixi', 'three', 'ctrllr', 'player'];\n if (monorepoRoot !== undefined) {\n const packagesDirectory = path.join(monorepoRoot, 'packages');\n for (const packageName of donutPackageNames) {\n const packageJsonPath = path.join(packagesDirectory, packageName, 'package.json');\n if (await fileSystemExtra.pathExists(packageJsonPath)) {\n const parsed = (await fileSystemExtra.readJson(packageJsonPath)) as {\n name?: string;\n version?: string;\n };\n if (typeof parsed.name === 'string' && typeof parsed.version === 'string') {\n runtimeVersions[parsed.name] = parsed.version;\n }\n }\n }\n }\n return runtimeVersions;\n}\n\n/**\n * Programmatically invoke Vite's `build` API when no override is provided.\n */\nasync function runViteBuildProgrammatically(\n viteConfig: Record<string, unknown>,\n): Promise<void> {\n const viteModule = await import('vite');\n await viteModule.build(viteConfig as never);\n}\n\n/**\n * Entrypoint — validates, bundles, scans, and zips a Donut project into a\n * `.donut` archive. Throws {@link BuildViolationError} on fatal findings.\n */\nexport async function runBuild(options: RunBuildOptions): Promise<BuildReport> {\n const log = options.log ?? ((message: string) => console.log(message));\n const startTimeMilliseconds = Date.now();\n const projectDirectory = path.resolve(options.projectDirectory);\n const outputDirectory = path.resolve(\n options.outputDirectory ?? path.join(projectDirectory, 'dist'),\n );\n\n // Step (a): validate\n const validationReport = await runValidation({\n projectDirectory,\n skipTypeCheck: true,\n });\n if (validationReport.errors.length > 0) {\n const formattedErrors = validationReport.errors\n .map((validationError) => ` ${validationError.filePath}: ${validationError.message}`)\n .join('\\n');\n throw new BuildViolationError(\n `Validation failed with ${validationReport.errors.length} error(s):\\n${formattedErrors}`,\n [],\n );\n }\n\n // Step (e, pre-bundle): security scan on user sources only.\n const scanFindings = await scanUserSourcesForForbiddenPatterns(projectDirectory);\n const fatalViolations = scanFindings.filter((finding) => finding.severity === 'error');\n const warnings = scanFindings.filter((finding) => finding.severity === 'warning');\n if (fatalViolations.length > 0) {\n throw new BuildViolationError(\n `Security scan failed with ${fatalViolations.length} violation(s)`,\n fatalViolations,\n );\n }\n\n const donutManifest = await readProjectManifest(projectDirectory);\n log(`Donut build — project '${donutManifest.name}' (${donutManifest.rendererTarget})`);\n\n // Decide which public-import surface the generated build entry should target.\n const projectSourceKind = await detectProjectSourceKind(projectDirectory, log);\n\n // Step (b): write ephemeral entry\n const buildEntryPath = await writeBuildEntryFile(\n projectDirectory,\n donutManifest,\n projectSourceKind,\n );\n\n // Step (c): run vite build\n const stagingDirectory = path.join(outputDirectory, 'bundle-staging');\n await fileSystemExtra.ensureDir(outputDirectory);\n await fileSystemExtra.remove(stagingDirectory);\n await fileSystemExtra.ensureDir(stagingDirectory);\n\n const donutRoot = path.join(projectDirectory, '.donut');\n const viteConfig = await buildViteBuildConfig({\n projectDirectory,\n donutRoot,\n buildEntryPath,\n stagingDirectory,\n });\n const runVite = options.runViteBuild ?? runViteBuildProgrammatically;\n await runVite(viteConfig);\n\n const builtGameJsPath = path.join(stagingDirectory, 'game.js');\n if (!(await fileSystemExtra.pathExists(builtGameJsPath))) {\n throw new Error(\n `Expected Vite build to emit game.js at ${builtGameJsPath} — file not found.`,\n );\n }\n\n // Step (d): copy assets\n const projectAssetsDirectory = path.join(projectDirectory, 'assets');\n const stagingAssetsDirectory = path.join(stagingDirectory, 'assets');\n const imageAssetOptimizer = options.optimizeImageAsset ?? optimizeImageAssetIfPossible;\n const assetEntries = await copyAssetsAndCollectEntries(\n projectAssetsDirectory,\n stagingAssetsDirectory,\n imageAssetOptimizer,\n log,\n );\n\n // Step (f): build manifest.json\n const runtimeVersions = await collectRuntimeVersions(projectDirectory);\n\n // Step (g): zip into .donut — assemble archive, then patch final size.\n const gameJsContents = await fileSystemExtra.readFile(builtGameJsPath);\n const bundleFileName = `${donutManifest.name}-${(donutManifest as unknown as { version?: string }).version ?? '0.0.0'}.donut`;\n const bundlePath = path.join(outputDirectory, bundleFileName);\n\n const projectManifestVersion = await readProjectManifestVersion(projectDirectory);\n\n const partialManifest: Omit<BundleManifest, 'bundleSizeBytes'> = {\n name: donutManifest.name,\n version: projectManifestVersion,\n rendererTarget: donutManifest.rendererTarget,\n entryPoint: 'game.js',\n donutEngineVersion: donutManifest.donutEngineVersion ?? '0.0.0',\n runtimeVersions,\n assets: assetEntries,\n buildTimestamp: new Date().toISOString(),\n };\n\n const zipArchive = new JSZip();\n zipArchive.file('game.js', gameJsContents);\n for (const assetEntry of assetEntries) {\n const absoluteAssetPath = path.join(stagingDirectory, assetEntry.path);\n const assetBuffer = await fileSystemExtra.readFile(absoluteAssetPath);\n zipArchive.file(assetEntry.path, assetBuffer);\n }\n\n // Generating the zip after setting bundleSizeBytes can change manifest.json\n // text length (digit count), which changes zip size. To make the process\n // converge reliably, we pad manifest.json with trailing whitespace inside\n // a dedicated field so the rendered manifest text has a fixed length\n // regardless of the digit count of bundleSizeBytes.\n let candidateBundleSize = 0;\n let paddingLength = 8;\n let finalBuffer: Buffer = Buffer.alloc(0);\n let finalManifest: BundleManifest = { ...partialManifest, bundleSizeBytes: 0 };\n for (let iteration = 0; iteration < 16; iteration += 1) {\n finalManifest = { ...partialManifest, bundleSizeBytes: candidateBundleSize };\n const renderedManifest = renderManifestJsonWithPadding(finalManifest, paddingLength);\n // Store manifest.json uncompressed so its on-disk byte length is fully\n // determined by the rendered JSON text (no DEFLATE variability).\n zipArchive.file('manifest.json', renderedManifest, { compression: 'STORE' });\n finalBuffer = await zipArchive.generateAsync({\n type: 'nodebuffer',\n compression: 'DEFLATE',\n });\n if (finalBuffer.byteLength === candidateBundleSize) {\n break;\n }\n // If the number of digits in the new size differs from the previous\n // candidate, widen the padding so the manifest text length doesn't shrink.\n const previousDigits = String(candidateBundleSize).length;\n const newDigits = String(finalBuffer.byteLength).length;\n if (newDigits !== previousDigits) {\n paddingLength += Math.abs(newDigits - previousDigits) + 2;\n }\n candidateBundleSize = finalBuffer.byteLength;\n }\n await fileSystemExtra.writeFile(bundlePath, finalBuffer);\n const bundleStat = await fileSystemExtra.stat(bundlePath);\n if (bundleStat.size !== finalManifest.bundleSizeBytes) {\n throw new Error(\n `Internal build error: bundleSizeBytes (${finalManifest.bundleSizeBytes}) ` +\n `does not match on-disk archive size (${bundleStat.size}).`,\n );\n }\n\n const buildTimeMilliseconds = Date.now() - startTimeMilliseconds;\n return {\n bundlePath,\n bundleSizeBytes: bundleStat.size,\n buildTimeMilliseconds,\n warnings,\n violations: [],\n manifest: finalManifest,\n };\n}\n\n/**\n * Render manifest.json with a `_sizePadding` string field whose length we\n * control. This stabilizes the rendered JSON length across rewrites when the\n * digit count of `bundleSizeBytes` changes between iterations.\n */\nfunction renderManifestJsonWithPadding(\n manifest: BundleManifest,\n paddingLength: number,\n): string {\n const manifestWithPadding: BundleManifest & { _sizePadding: string } = {\n ...manifest,\n _sizePadding: ' '.repeat(Math.max(0, paddingLength)),\n };\n return JSON.stringify(manifestWithPadding, null, 2);\n}\n\n/**\n * Default image optimizer. Tries to dynamically import `sharp` and run a\n * format-appropriate compression pass. Returns `null` when sharp is not\n * installed or the file could not be processed — signalling the caller to\n * fall back to a pass-through copy.\n */\nexport async function optimizeImageAssetIfPossible(\n sourcePath: string,\n extension: string,\n): Promise<Buffer | null> {\n let sharpModule: { default: (input: string) => SharpPipeline } | undefined;\n try {\n sharpModule = (await import(/* @vite-ignore */ 'sharp' as string)) as unknown as {\n default: (input: string) => SharpPipeline;\n };\n } catch {\n return null;\n }\n try {\n const pipeline = sharpModule.default(sourcePath).rotate();\n let configured: SharpPipeline;\n if (extension === 'png') {\n configured = pipeline.png({ compressionLevel: 9 });\n } else if (extension === 'jpg' || extension === 'jpeg') {\n configured = pipeline.jpeg({ quality: 82, mozjpeg: true });\n } else if (extension === 'webp') {\n configured = pipeline.webp({ quality: 82 });\n } else {\n return null;\n }\n const output = await configured.toBuffer();\n return output;\n } catch {\n return null;\n }\n}\n\n/**\n * Minimal structural type describing the subset of sharp's fluent API we use.\n * Keeps sharp an optional dependency — no type import required.\n */\ninterface SharpPipeline {\n rotate(): SharpPipeline;\n png(options: { compressionLevel: number }): SharpPipeline;\n jpeg(options: { quality: number; mozjpeg: boolean }): SharpPipeline;\n webp(options: { quality: number }): SharpPipeline;\n toBuffer(): Promise<Buffer>;\n}\n\n/**\n * Read the `version` field from `donut.json` — defaults to `0.0.0` when absent.\n */\nasync function readProjectManifestVersion(projectDirectory: string): Promise<string> {\n const manifestPath = path.join(projectDirectory, 'donut.json');\n if (!(await fileSystemExtra.pathExists(manifestPath))) {\n return '0.0.0';\n }\n const raw = (await fileSystemExtra.readJson(manifestPath)) as { version?: unknown };\n return typeof raw.version === 'string' ? raw.version : '0.0.0';\n}\n","import path from 'node:path';\nimport fileSystemExtra from 'fs-extra';\nimport chalk from 'chalk';\nimport JsZip from 'jszip';\nimport * as acorn from 'acorn';\nimport * as acornWalk from 'acorn-walk';\nimport type { BundleManifest } from '../bundle/bundle-manifest.js';\n\n/**\n * Parameters accepted by {@link BundleUploader.upload}.\n */\nexport interface BundleUploadParameters {\n readonly bundleBuffer: Buffer;\n readonly manifest: BundleManifest;\n readonly authToken: string;\n}\n\n/**\n * Abstract transport that ships a validated `.donut` bundle somewhere.\n * Implementations must not mutate the buffer or manifest.\n */\nexport interface BundleUploader {\n upload(parameters: BundleUploadParameters): Promise<{ gameUrl: string }>;\n}\n\n/**\n * A single security violation discovered by the publish-time bundle scan.\n */\nexport interface PublishSecurityViolation {\n readonly pattern: string;\n readonly description: string;\n readonly line?: number;\n readonly column?: number;\n}\n\n/**\n * Options accepted by {@link runPublish}.\n */\nexport interface RunPublishOptions {\n /** Absolute path to the project root containing `dist/`. */\n readonly projectDirectory: string;\n /** Optional explicit path to a `.donut` bundle. When omitted, the latest bundle in `dist/` is picked. */\n readonly bundlePath?: string;\n /** Bearer token passed to the uploader. Defaults to a stub placeholder. */\n readonly authToken?: string;\n /** Uploader implementation. Defaults to {@link StubBundleUploader}. */\n readonly uploader?: BundleUploader;\n /** Optional logger hook; defaults to `console.log`. */\n readonly log?: (message: string) => void;\n}\n\n/**\n * Report returned by {@link runPublish} after a successful or stubbed publish.\n */\nexport interface PublishReport {\n readonly success: boolean;\n readonly gameUrl: string;\n readonly bundleSizeBytes: number;\n readonly warnings: readonly string[];\n readonly violations: readonly PublishSecurityViolation[];\n}\n\n/**\n * Default dry-run uploader that logs the intent and returns a placeholder URL.\n */\nexport class StubBundleUploader implements BundleUploader {\n public constructor(\n private readonly logger: (message: string) => void = (message) =>\n console.log(message),\n ) {}\n\n public async upload(\n parameters: BundleUploadParameters,\n ): Promise<{ gameUrl: string }> {\n const { manifest, bundleBuffer } = parameters;\n this.logger(\n chalk.yellow(\n `[StubBundleUploader] Would upload ${manifest.name}@${manifest.version} (${bundleBuffer.byteLength} bytes)`,\n ),\n );\n const gameUrl = `https://donut.games/${encodeURIComponent(\n manifest.name,\n )}/${encodeURIComponent(manifest.version)}`;\n return { gameUrl };\n }\n}\n\n/**\n * Identifier names whose invocation is forbidden inside a shipped bundle.\n */\nconst FORBIDDEN_CALL_IDENTIFIERS = new Set<string>([\n 'fetch',\n 'eval',\n 'Function',\n 'XMLHttpRequest',\n 'WebSocket',\n]);\n\n/**\n * Locate, validate, security-scan, and (stub-)upload a `.donut` bundle.\n */\nexport async function runPublish(\n options: RunPublishOptions,\n): Promise<PublishReport> {\n const log = options.log ?? ((message: string) => console.log(message));\n const uploader = options.uploader ?? new StubBundleUploader(log);\n const authToken = options.authToken ?? 'stub-auth-token';\n\n const bundlePath = await resolveBundlePath(\n options.projectDirectory,\n options.bundlePath,\n );\n log(chalk.cyan(`donut publish: using bundle ${bundlePath}`));\n\n const bundleBuffer = await fileSystemExtra.readFile(bundlePath);\n const zipArchive = await JsZip.loadAsync(bundleBuffer);\n\n const manifest = await readAndValidateManifest(zipArchive);\n await validateGameEntryExists(zipArchive);\n await validateAssetsExist(zipArchive, manifest);\n\n const gameSource = await readZipTextFile(zipArchive, 'game.js');\n const violations = scanBundleSourceForViolations(gameSource);\n if (violations.length > 0) {\n const formatted = violations\n .map(\n (violation) =>\n ` - [${violation.pattern}] ${violation.description}` +\n (violation.line !== undefined ? ` (line ${violation.line})` : ''),\n )\n .join('\\n');\n throw new Error(\n `Publish aborted: bundled game.js contains forbidden APIs:\\n${formatted}`,\n );\n }\n\n const warnings: string[] = [];\n const assetCount = manifest.assets.length;\n const bundleSizeBytes = bundleBuffer.byteLength;\n\n log(chalk.green('\\nBundle validated:'));\n log(` name: ${manifest.name}`);\n log(` version: ${manifest.version}`);\n log(` renderer: ${manifest.rendererTarget}`);\n log(` assets: ${assetCount}`);\n log(` bundle size: ${bundleSizeBytes} bytes`);\n log(chalk.yellow('\\nDRY RUN — no network request made'));\n\n const { gameUrl } = await uploader.upload({\n bundleBuffer,\n manifest,\n authToken,\n });\n log(chalk.cyan(`\\nPlaceholder game URL: ${gameUrl}`));\n\n return {\n success: true,\n gameUrl,\n bundleSizeBytes,\n warnings,\n violations,\n };\n}\n\nasync function resolveBundlePath(\n projectDirectory: string,\n explicitBundlePath: string | undefined,\n): Promise<string> {\n if (explicitBundlePath !== undefined) {\n const absoluteBundlePath = path.isAbsolute(explicitBundlePath)\n ? explicitBundlePath\n : path.join(projectDirectory, explicitBundlePath);\n if (!(await fileSystemExtra.pathExists(absoluteBundlePath))) {\n throw new Error(`Bundle not found at ${absoluteBundlePath}`);\n }\n return absoluteBundlePath;\n }\n const distDirectory = path.join(projectDirectory, 'dist');\n if (!(await fileSystemExtra.pathExists(distDirectory))) {\n throw new Error(\n `No bundle specified and no dist/ directory found at ${distDirectory}. Run \"donut build\" first.`,\n );\n }\n const entries = await fileSystemExtra.readdir(distDirectory);\n const donutBundles = entries.filter((entry) => entry.endsWith('.donut'));\n if (donutBundles.length === 0) {\n throw new Error(\n `No .donut bundles found in ${distDirectory}. Run \"donut build\" first.`,\n );\n }\n const statsByPath = await Promise.all(\n donutBundles.map(async (entry) => {\n const fullPath = path.join(distDirectory, entry);\n const stats = await fileSystemExtra.stat(fullPath);\n return { fullPath, modifiedTimeMilliseconds: stats.mtimeMs };\n }),\n );\n statsByPath.sort(\n (left, right) =>\n right.modifiedTimeMilliseconds - left.modifiedTimeMilliseconds,\n );\n return statsByPath[0].fullPath;\n}\n\nasync function readAndValidateManifest(\n zipArchive: JsZip,\n): Promise<BundleManifest> {\n const manifestFile = zipArchive.file('manifest.json');\n if (manifestFile === null) {\n throw new Error('Bundle is missing required /manifest.json');\n }\n const manifestText = await manifestFile.async('string');\n let parsed: unknown;\n try {\n parsed = JSON.parse(manifestText);\n } catch (parseError) {\n throw new Error(\n `Bundle manifest.json is not valid JSON: ${(parseError as Error).message}`,\n );\n }\n if (typeof parsed !== 'object' || parsed === null) {\n throw new Error('Bundle manifest.json is not an object');\n }\n const candidate = parsed as Record<string, unknown>;\n const requiredStringFields = [\n 'name',\n 'version',\n 'rendererTarget',\n 'entryPoint',\n 'donutEngineVersion',\n 'buildTimestamp',\n ] as const;\n for (const fieldName of requiredStringFields) {\n if (\n typeof candidate[fieldName] !== 'string' ||\n (candidate[fieldName] as string).length === 0\n ) {\n throw new Error(\n `Bundle manifest.json is missing required string field \"${fieldName}\"`,\n );\n }\n }\n const rendererTarget = candidate.rendererTarget;\n if (rendererTarget !== 'pixi-2d' && rendererTarget !== 'three-3d') {\n throw new Error(\n `Bundle manifest.json has invalid rendererTarget \"${String(rendererTarget)}\"`,\n );\n }\n if (candidate.entryPoint !== 'game.js') {\n throw new Error(\n `Bundle manifest.json entryPoint must be \"game.js\" (got \"${String(candidate.entryPoint)}\")`,\n );\n }\n if (typeof candidate.bundleSizeBytes !== 'number') {\n throw new Error(\n 'Bundle manifest.json is missing required number field \"bundleSizeBytes\"',\n );\n }\n if (\n typeof candidate.runtimeVersions !== 'object' ||\n candidate.runtimeVersions === null\n ) {\n throw new Error(\n 'Bundle manifest.json is missing required object field \"runtimeVersions\"',\n );\n }\n if (!Array.isArray(candidate.assets)) {\n throw new Error(\n 'Bundle manifest.json is missing required array field \"assets\"',\n );\n }\n return parsed as BundleManifest;\n}\n\nasync function validateGameEntryExists(zipArchive: JsZip): Promise<void> {\n const entry = zipArchive.file('game.js');\n if (entry === null) {\n throw new Error('Bundle is missing required /game.js');\n }\n}\n\nasync function validateAssetsExist(\n zipArchive: JsZip,\n manifest: BundleManifest,\n): Promise<void> {\n for (const asset of manifest.assets) {\n const entry = zipArchive.file(asset.path);\n if (entry === null) {\n throw new Error(\n `Bundle manifest declares asset \"${asset.path}\" but it is missing from the archive`,\n );\n }\n }\n}\n\nasync function readZipTextFile(\n zipArchive: JsZip,\n pathInZip: string,\n): Promise<string> {\n const entry = zipArchive.file(pathInZip);\n if (entry === null) {\n throw new Error(`Bundle is missing required file ${pathInZip}`);\n }\n return entry.async('string');\n}\n\n/**\n * AST-based security scan run on the already-built bundle. Flags forbidden\n * call expressions, dynamic code construction, and unsafe DOM sinks.\n */\nexport function scanBundleSourceForViolations(\n sourceText: string,\n): PublishSecurityViolation[] {\n const violations: PublishSecurityViolation[] = [];\n let ast: acorn.Node;\n try {\n ast = acorn.parse(sourceText, {\n ecmaVersion: 'latest',\n sourceType: 'module',\n locations: true,\n allowHashBang: true,\n });\n } catch (parseError) {\n // If we cannot parse it, we cannot trust it.\n violations.push({\n pattern: 'parse-error',\n description: `Bundle game.js failed to parse: ${(parseError as Error).message}`,\n });\n return violations;\n }\n\n // acorn-walk with TypeScript is permissive about node shapes, so we cast internally.\n acornWalk.simple(ast, {\n CallExpression(rawNode) {\n const node = rawNode as acorn.CallExpression & { loc?: acorn.SourceLocation };\n const callee = node.callee;\n if (callee.type === 'Identifier') {\n const identifierNode = callee as acorn.Identifier;\n if (FORBIDDEN_CALL_IDENTIFIERS.has(identifierNode.name)) {\n violations.push({\n pattern: identifierNode.name,\n description: `Forbidden call to ${identifierNode.name}(...)`,\n line: node.loc?.start.line,\n column: node.loc?.start.column,\n });\n }\n }\n },\n NewExpression(rawNode) {\n const node = rawNode as acorn.NewExpression & { loc?: acorn.SourceLocation };\n const callee = node.callee;\n if (callee.type === 'Identifier') {\n const identifierNode = callee as acorn.Identifier;\n if (FORBIDDEN_CALL_IDENTIFIERS.has(identifierNode.name)) {\n violations.push({\n pattern: identifierNode.name,\n description: `Forbidden construction of ${identifierNode.name}`,\n line: node.loc?.start.line,\n column: node.loc?.start.column,\n });\n }\n }\n },\n AssignmentExpression(rawNode) {\n const node = rawNode as acorn.AssignmentExpression & {\n loc?: acorn.SourceLocation;\n };\n if (node.left.type === 'MemberExpression') {\n const memberNode = node.left as acorn.MemberExpression;\n if (\n memberNode.property.type === 'Identifier' &&\n (memberNode.property as acorn.Identifier).name === 'innerHTML'\n ) {\n violations.push({\n pattern: 'innerHTML',\n description: 'Forbidden assignment to .innerHTML',\n line: node.loc?.start.line,\n column: node.loc?.start.column,\n });\n }\n }\n },\n ImportExpression(rawNode) {\n const node = rawNode as acorn.ImportExpression & {\n loc?: acorn.SourceLocation;\n };\n violations.push({\n pattern: 'dynamic-import',\n description: 'Forbidden dynamic import() expression',\n line: node.loc?.start.line,\n column: node.loc?.start.column,\n });\n },\n });\n\n return violations;\n}\n","import path from 'node:path';\nimport fileSystemExtra from 'fs-extra';\nimport chalk from 'chalk';\nimport JsZip from 'jszip';\n\n/**\n * Severity of a single AI-authored review finding.\n */\nexport type ReviewFindingSeverity = 'pass' | 'warning' | 'violation';\n\n/**\n * A single structured review finding produced by a {@link ClaudeReviewClient}.\n */\nexport interface ReviewFinding {\n readonly severity: ReviewFindingSeverity;\n readonly file?: string;\n readonly description: string;\n}\n\n/**\n * Parameters passed to a {@link ClaudeReviewClient.review} call.\n */\nexport interface ClaudeReviewRequest {\n readonly sourceText: string;\n readonly prompt: string;\n}\n\n/**\n * Response shape returned by a {@link ClaudeReviewClient}.\n */\nexport interface ClaudeReviewResponse {\n readonly findings: ReviewFinding[];\n readonly rawResponse: string;\n}\n\n/**\n * Pluggable abstraction over the Anthropic client so tests can inject a stub.\n */\nexport interface ClaudeReviewClient {\n review(request: ClaudeReviewRequest): Promise<ClaudeReviewResponse>;\n}\n\n/**\n * Aggregate report returned by {@link runReview}.\n */\nexport interface ReviewReport {\n readonly overallPass: boolean;\n readonly findings: ReviewFinding[];\n}\n\n/**\n * Options accepted by {@link runReview}.\n */\nexport interface RunReviewOptions {\n readonly projectDirectory: string;\n readonly bundlePath?: string;\n readonly claudeClient?: ClaudeReviewClient;\n readonly log?: (message: string) => void;\n}\n\n/**\n * System/user prompt injected into the Claude review call.\n * Kept as a top-level constant so tests can assert against it.\n */\nexport const REVIEW_PROMPT_TEXT =\n 'You are reviewing code for the Donut game engine. Games must only use the Donut framework API. ' +\n 'Check each file for: direct DOM access (document, window, innerHTML), network requests (fetch, XMLHttpRequest, WebSocket), ' +\n 'code execution (eval, new Function, dynamic import()), storage (localStorage, sessionStorage, indexedDB), ' +\n 'or any attempt to escape the game runtime. Respond with a JSON array of findings: ' +\n '[{ \"severity\": \"violation\"|\"warning\"|\"pass\", \"file\": \"path\", \"description\": \"what and why\" }]. ' +\n 'Return [{\"severity\":\"pass\",\"description\":\"clean\"}] if nothing is wrong.';\n\n/**\n * Stub client used when no ANTHROPIC_API_KEY is set. Always reports \"pass\".\n */\nexport class StubClaudeReviewClient implements ClaudeReviewClient {\n public constructor(\n private readonly logger: (message: string) => void = (message) =>\n console.log(message),\n ) {}\n\n public async review(\n _request: ClaudeReviewRequest,\n ): Promise<ClaudeReviewResponse> {\n this.logger(\n chalk.yellow('AI review skipped (no ANTHROPIC_API_KEY set)'),\n );\n const findings: ReviewFinding[] = [\n { severity: 'pass', description: 'clean' },\n ];\n return { findings, rawResponse: JSON.stringify(findings) };\n }\n}\n\n/**\n * Default client that talks to the live Anthropic API if an API key is set,\n * otherwise falls back to {@link StubClaudeReviewClient}.\n */\nexport function createDefaultClaudeReviewClient(\n logger: (message: string) => void = (message) => console.log(message),\n): ClaudeReviewClient {\n const apiKey = process.env.ANTHROPIC_API_KEY;\n if (apiKey === undefined || apiKey.length === 0) {\n return new StubClaudeReviewClient(logger);\n }\n return new AnthropicClaudeReviewClient(apiKey);\n}\n\n/**\n * Live implementation of {@link ClaudeReviewClient} backed by `@anthropic-ai/sdk`.\n */\nexport class AnthropicClaudeReviewClient implements ClaudeReviewClient {\n public constructor(private readonly apiKey: string) {}\n\n public async review(\n request: ClaudeReviewRequest,\n ): Promise<ClaudeReviewResponse> {\n const { default: AnthropicClient } = await import('@anthropic-ai/sdk');\n const client = new AnthropicClient({ apiKey: this.apiKey });\n const message = await client.messages.create({\n model: 'claude-opus-4-6',\n max_tokens: 4096,\n system: request.prompt,\n messages: [\n {\n role: 'user',\n content: request.sourceText,\n },\n ],\n });\n const firstTextBlock = message.content.find(\n (block): block is { type: 'text'; text: string } =>\n (block as { type: string }).type === 'text',\n );\n const rawResponse = firstTextBlock?.text ?? '';\n const findings = parseReviewFindingsFromText(rawResponse);\n return { findings, rawResponse };\n }\n}\n\n/**\n * Parse Claude's textual response into structured findings.\n * Tolerates prose wrapped around a JSON array.\n */\nexport function parseReviewFindingsFromText(text: string): ReviewFinding[] {\n const firstArrayStart = text.indexOf('[');\n const lastArrayEnd = text.lastIndexOf(']');\n if (firstArrayStart === -1 || lastArrayEnd === -1 || lastArrayEnd <= firstArrayStart) {\n return [\n {\n severity: 'warning',\n description: `Could not parse review findings as JSON array: ${text.slice(0, 200)}`,\n },\n ];\n }\n const candidate = text.slice(firstArrayStart, lastArrayEnd + 1);\n try {\n const parsed = JSON.parse(candidate) as unknown;\n if (!Array.isArray(parsed)) {\n throw new Error('Expected JSON array');\n }\n return parsed.map((entry) => {\n const record = entry as Record<string, unknown>;\n const severity = record.severity;\n const normalizedSeverity: ReviewFindingSeverity =\n severity === 'violation' || severity === 'warning' || severity === 'pass'\n ? severity\n : 'warning';\n return {\n severity: normalizedSeverity,\n file: typeof record.file === 'string' ? record.file : undefined,\n description:\n typeof record.description === 'string'\n ? record.description\n : 'no description',\n } satisfies ReviewFinding;\n });\n } catch (parseError) {\n return [\n {\n severity: 'warning',\n description: `Could not parse review findings: ${(parseError as Error).message}`,\n },\n ];\n }\n}\n\n/**\n * Orchestrate the AI-assisted review: gather sources, call the AI client,\n * and surface structured findings.\n */\nexport async function runReview(\n options: RunReviewOptions,\n): Promise<ReviewReport> {\n const log = options.log ?? ((message: string) => console.log(message));\n const claudeClient =\n options.claudeClient ?? createDefaultClaudeReviewClient(log);\n\n const sources = await collectReviewSources(\n options.projectDirectory,\n options.bundlePath,\n );\n if (sources.length === 0) {\n log(chalk.yellow('donut review: no source files found to review'));\n return {\n overallPass: true,\n findings: [\n { severity: 'pass', description: 'No reviewable sources found' },\n ],\n };\n }\n\n const sourceText = sources\n .map(\n (file) =>\n `// === FILE: ${file.displayPath} ===\\n${file.contents}\\n// === END FILE: ${file.displayPath} ===`,\n )\n .join('\\n\\n');\n\n log(chalk.cyan(`donut review: sending ${sources.length} file(s) for AI review`));\n\n const response = await claudeClient.review({\n sourceText,\n prompt: REVIEW_PROMPT_TEXT,\n });\n const hasViolation = response.findings.some(\n (finding) => finding.severity === 'violation',\n );\n\n for (const finding of response.findings) {\n const prefix = `[${finding.severity.toUpperCase()}]${\n finding.file !== undefined ? ` ${finding.file}` : ''\n }`;\n const line = `${prefix}: ${finding.description}`;\n if (finding.severity === 'violation') {\n log(chalk.red(line));\n } else if (finding.severity === 'warning') {\n log(chalk.yellow(line));\n } else {\n log(chalk.green(line));\n }\n }\n\n return {\n overallPass: hasViolation === false,\n findings: response.findings,\n };\n}\n\ninterface ReviewSourceFile {\n readonly displayPath: string;\n readonly contents: string;\n}\n\nasync function collectReviewSources(\n projectDirectory: string,\n bundlePath: string | undefined,\n): Promise<ReviewSourceFile[]> {\n if (bundlePath !== undefined) {\n const collected = await collectReviewSourcesFromBundle(\n projectDirectory,\n bundlePath,\n );\n if (collected.length > 0) {\n return collected;\n }\n }\n return collectReviewSourcesFromDisk(projectDirectory);\n}\n\nasync function collectReviewSourcesFromBundle(\n projectDirectory: string,\n bundlePath: string,\n): Promise<ReviewSourceFile[]> {\n const absoluteBundlePath = path.isAbsolute(bundlePath)\n ? bundlePath\n : path.join(projectDirectory, bundlePath);\n if (!(await fileSystemExtra.pathExists(absoluteBundlePath))) {\n throw new Error(`Bundle not found at ${absoluteBundlePath}`);\n }\n const buffer = await fileSystemExtra.readFile(absoluteBundlePath);\n const zipArchive = await JsZip.loadAsync(buffer);\n const sources: ReviewSourceFile[] = [];\n const entries: { path: string; file: JsZip.JSZipObject }[] = [];\n zipArchive.forEach((relativePath, file) => {\n if (\n relativePath.startsWith('src/') &&\n (relativePath.endsWith('.ts') || relativePath.endsWith('.js')) &&\n file.dir === false\n ) {\n entries.push({ path: relativePath, file });\n }\n });\n for (const entry of entries) {\n const contents = await entry.file.async('string');\n sources.push({ displayPath: entry.path, contents });\n }\n return sources;\n}\n\nasync function collectReviewSourcesFromDisk(\n projectDirectory: string,\n): Promise<ReviewSourceFile[]> {\n const componentsDirectory = path.join(\n projectDirectory,\n 'src',\n 'components',\n );\n const systemsDirectory = path.join(projectDirectory, 'src', 'systems');\n const sources: ReviewSourceFile[] = [];\n for (const directoryPath of [componentsDirectory, systemsDirectory]) {\n if (!(await fileSystemExtra.pathExists(directoryPath))) {\n continue;\n }\n const typeScriptFiles = await collectTypeScriptFilesRecursively(\n directoryPath,\n );\n for (const filePath of typeScriptFiles) {\n const contents = await fileSystemExtra.readFile(filePath, 'utf8');\n sources.push({\n displayPath: path.relative(projectDirectory, filePath),\n contents,\n });\n }\n }\n return sources;\n}\n\nasync function collectTypeScriptFilesRecursively(\n directoryPath: string,\n): Promise<string[]> {\n const collected: string[] = [];\n const entries = await fileSystemExtra.readdir(directoryPath, {\n withFileTypes: true,\n });\n for (const entry of entries) {\n const entryPath = path.join(directoryPath, entry.name);\n if (entry.isDirectory()) {\n collected.push(...(await collectTypeScriptFilesRecursively(entryPath)));\n } else if (entry.isFile() && entry.name.endsWith('.ts')) {\n collected.push(entryPath);\n }\n }\n return collected;\n}\n","// The real `#!/usr/bin/env node` shebang is injected at build time by the\n// `tsup` banner configuration — do not add one here as a string, or it will\n// appear mid-file after bundling.\n\n/**\n * `donut` bin entry. Forwards to the commander program defined in the\n * internal `@donut/cli` workspace package. The program's own command\n * definitions are wired up at import time; invoking `parseAsync` with the\n * current process arguments runs whichever subcommand the user requested.\n */\nimport { program as donutCliProgram } from '@donut/cli';\n\nawait donutCliProgram.parseAsync(process.argv);\n"],"mappings":"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","names":["path","chalk","path","fileSystemExtra","fileSystemExtra","path","path","fileSystemExtra","chalk","path","fileSystemExtra","path","fileSystemExtra","chalk","path","spawn","createRequire","fileURLToPath","fileSystemExtra","path","fileSystemExtra","chalk","fileSystemExtra","path","chalk","path","fileSystemExtra","chalk","path","fileSystemExtra","chalk","JsZip","collectTypeScriptFilesRecursively","chalk","path"]}
1
+ {"version":3,"sources":["../../cli/src/index.ts","../../cli/src/commands/init.ts","../../cli/src/commands/template-copier.ts","../../cli/src/commands/dev.ts","../../cli/src/shared/monorepo-aliases.ts","../../cli/src/commands/build.ts","../../cli/src/commands/validate.ts","../../cli/src/commands/publish.ts","../../cli/src/commands/review.ts","../src/donut.ts"],"sourcesContent":["#!/usr/bin/env node\nimport path from 'node:path';\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { initializeProject } from './commands/init.js';\nimport { runDevServer } from './commands/dev.js';\nimport { runValidation, type ValidationError } from './commands/validate.js';\nimport { runBuild, BuildViolationError } from './commands/build.js';\nimport { runPublish } from './commands/publish.js';\nimport { runReview } from './commands/review.js';\n\nexport { runValidation } from './commands/validate.js';\nexport type {\n ValidationError,\n ValidationReport,\n RunValidationOptions,\n} from './commands/validate.js';\nexport { initializeProject } from './commands/init.js';\nexport type {\n RendererTarget,\n InitializeProjectOptions,\n InitializeProjectResult,\n} from './commands/init.js';\nexport { runBuild, BuildViolationError } from './commands/build.js';\nexport type {\n RunBuildOptions,\n BuildReport,\n SecurityViolation,\n} from './commands/build.js';\nexport type {\n BundleManifest,\n BundleManifestAssetEntry,\n} from './bundle/bundle-manifest.js';\nexport {\n runPublish,\n StubBundleUploader,\n scanBundleSourceForViolations,\n} from './commands/publish.js';\nexport type {\n RunPublishOptions,\n PublishReport,\n BundleUploader,\n BundleUploadParameters,\n PublishSecurityViolation,\n} from './commands/publish.js';\nexport {\n runReview,\n StubClaudeReviewClient,\n AnthropicClaudeReviewClient,\n createDefaultClaudeReviewClient,\n parseReviewFindingsFromText,\n REVIEW_PROMPT_TEXT,\n} from './commands/review.js';\nexport type {\n RunReviewOptions,\n ReviewReport,\n ReviewFinding,\n ReviewFindingSeverity,\n ClaudeReviewClient,\n ClaudeReviewRequest,\n ClaudeReviewResponse,\n} from './commands/review.js';\n\n/**\n * The top-level commander program for the `donut` CLI.\n * Exported so tests and other entry points can inspect or invoke it.\n */\nexport const program = new Command();\n\nprogram\n .name('donut')\n .description('Donut game engine command-line tooling')\n .version('0.1.0');\n\nprogram\n .command('init')\n .description('Scaffold a new Donut game project')\n .argument('<project-name>', 'name of the new project directory')\n .option('--2d', 'use the 2D (Pixi) renderer target', false)\n .option('--3d', 'use the 3D (Three) renderer target', false)\n .option('--skip-install', 'skip running pnpm/npm install after scaffolding', false)\n .action(async (projectName: string, options: Record<string, boolean>) => {\n const useThreeDimensional = options['3d'] === true;\n const rendererTarget = useThreeDimensional ? 'three-3d' : 'pixi-2d';\n try {\n await initializeProject({\n projectName,\n rendererTarget,\n targetDirectory: projectName,\n skipInstall: options.skipInstall === true,\n });\n } catch (error) {\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n });\n\nprogram\n .command('dev')\n .description('Run the project in a local development server')\n .option('--port <port>', 'port for the dev server', '5173')\n .option('--no-open', 'do not automatically open a browser window')\n .action(async (options: { port: string; open: boolean }) => {\n const parsedPort = Number.parseInt(options.port, 10);\n try {\n await runDevServer({\n projectDirectory: process.cwd(),\n port: Number.isFinite(parsedPort) ? parsedPort : 5173,\n open: options.open,\n });\n } catch (error) {\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n });\n\nprogram\n .command('validate')\n .description('Validate project structure, types, and asset references')\n .action(async () => {\n try {\n const report = await runValidation({ projectDirectory: process.cwd() });\n const formatFinding = (finding: ValidationError): string => {\n const locationParts: string[] = [finding.filePath];\n if (finding.line !== undefined) {\n locationParts.push(String(finding.line));\n if (finding.column !== undefined) {\n locationParts.push(String(finding.column));\n }\n }\n return `${locationParts.join(':')}: ${finding.code}: ${finding.message}`;\n };\n for (const warning of report.warnings) {\n console.warn(chalk.yellow(formatFinding(warning)));\n }\n for (const error of report.errors) {\n console.error(chalk.red(formatFinding(error)));\n }\n if (report.errors.length === 0 && report.warnings.length === 0) {\n console.log(chalk.green('donut validate: no problems found'));\n }\n process.exit(report.errors.length === 0 ? 0 : 1);\n } catch (error) {\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n });\n\nprogram\n .command('build')\n .description('Build a distributable .donut bundle for the project')\n .option('--output <dir>', 'output directory for the bundle', 'dist')\n .action(async (options: { output: string }) => {\n const projectDirectory = process.cwd();\n const outputDirectory = path.isAbsolute(options.output)\n ? options.output\n : path.join(projectDirectory, options.output);\n try {\n const report = await runBuild({ projectDirectory, outputDirectory });\n for (const warning of report.warnings) {\n console.warn(\n chalk.yellow(\n `${warning.filePath}:${warning.line}: warning: matched /${warning.pattern}/ (\"${warning.match}\")`,\n ),\n );\n }\n console.log(\n chalk.green(\n `donut build: wrote ${report.bundlePath} (${report.bundleSizeBytes} bytes) in ${report.buildTimeMilliseconds}ms`,\n ),\n );\n } catch (error) {\n if (error instanceof BuildViolationError) {\n console.error(chalk.red(error.message));\n for (const violation of error.violations) {\n console.error(\n chalk.red(\n ` ${violation.filePath}:${violation.line}: matched /${violation.pattern}/ (\"${violation.match}\")`,\n ),\n );\n }\n } else {\n console.error(chalk.red((error as Error).message));\n }\n process.exit(1);\n }\n });\n\nprogram\n .command('publish')\n .description('Publish a built .donut bundle (dry-run by default)')\n .option('--bundle <path>', 'explicit path to a .donut bundle to publish')\n .option('--auth-token <token>', 'bearer token for the upload endpoint')\n .action(async (options: { bundle?: string; authToken?: string }) => {\n try {\n const report = await runPublish({\n projectDirectory: process.cwd(),\n bundlePath: options.bundle,\n authToken: options.authToken,\n });\n if (report.success) {\n console.log(chalk.green(`donut publish: ${report.gameUrl}`));\n }\n } catch (error) {\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n });\n\nprogram\n .command('review')\n .description('Run AI-assisted security/style review on the project sources')\n .option('--bundle <path>', 'review the sources packaged into a built bundle')\n .action(async (options: { bundle?: string }) => {\n try {\n const report = await runReview({\n projectDirectory: process.cwd(),\n bundlePath: options.bundle,\n });\n if (report.overallPass === false) {\n process.exit(1);\n }\n } catch (error) {\n console.error(chalk.red((error as Error).message));\n process.exit(1);\n }\n });\n\n/**\n * True when this module is the main entry (not imported for tests).\n */\nconst isRunningAsMain =\n import.meta.url === `file://${process.argv[1]}` ||\n process.argv[1]?.endsWith('/donut') === true ||\n process.argv[1]?.endsWith('\\\\donut') === true ||\n process.argv[1]?.endsWith('cli/src/index.ts') === true ||\n process.argv[1]?.endsWith('cli/dist/index.js') === true;\n\nif (isRunningAsMain) {\n program.parseAsync(process.argv);\n}\n","import path from 'node:path';\nimport { spawn } from 'node:child_process';\nimport { createRequire } from 'node:module';\nimport { fileURLToPath } from 'node:url';\nimport fileSystemExtra from 'fs-extra';\nimport chalk from 'chalk';\n\nimport { copyTemplate, type TemplateTokens } from './template-copier.js';\n\n/**\n * Supported renderer targets for scaffolded projects.\n */\nexport type RendererTarget = 'pixi-2d' | 'three-3d';\n\n/**\n * Options accepted by {@link initializeProject}.\n */\nexport interface InitializeProjectOptions {\n /** Human-readable project name (also written into donut.json and package.json). */\n readonly projectName: string;\n /** Renderer stack the scaffold should wire up. */\n readonly rendererTarget: RendererTarget;\n /** Absolute or relative path to the directory to create. */\n readonly targetDirectory: string;\n /** Optional logger; defaults to console.log with chalk colors. */\n readonly log?: (message: string) => void;\n /** When true, skip the post-scaffold dependency install step. */\n readonly skipInstall?: boolean;\n /**\n * Absolute path to the template directory. When provided, skips the\n * built-in template resolution logic entirely. Used by\n * `@donut-games/create-game` to pass its own bundled template — the\n * template ships inside `create-game`, not inside `cli`, so Node's\n * require resolution cannot find it from `cli`'s perspective.\n */\n readonly templateDirectory?: string;\n}\n\n/**\n * Result of a successful scaffold.\n */\nexport interface InitializeProjectResult {\n readonly absoluteDirectory: string;\n readonly createdFiles: readonly string[];\n}\n\n/**\n * Shape of the relevant fields from `@donut-games/engine/package.json`.\n */\ninterface EnginePackageManifest {\n readonly version: string;\n readonly peerDependencies?: Record<string, string>;\n}\n\n/**\n * Resolve the engine, pixi.js, and three version strings by reading\n * `@donut-games/engine/package.json` at scaffold time. This is the single\n * source of truth — no hardcoded version constants exist in this module.\n *\n * Falls back to requiring the workspace package when the published path\n * cannot be resolved.\n */\nasync function resolveEngineVersions(): Promise<{\n readonly donutEngineVersion: string;\n readonly pixiVersion: string;\n readonly threeVersion: string;\n}> {\n const candidatePaths: string[] = [];\n\n // Try require.resolve — works when @donut-games/engine is a dependency\n // (create-game → cli → engine) or a workspace sibling.\n try {\n const nodeRequire = createRequire(import.meta.url);\n const engineManifestPath = nodeRequire.resolve(\n '@donut-games/engine/package.json',\n );\n candidatePaths.push(engineManifestPath);\n const manifest = (await fileSystemExtra.readJson(\n engineManifestPath,\n )) as EnginePackageManifest;\n return extractVersionsFromManifest(manifest);\n } catch {\n // fall through\n }\n\n // Walk up from this module looking for the monorepo's packages/donut-engine.\n const thisModuleDirectory = path.dirname(fileURLToPath(import.meta.url));\n let walkingDirectory = thisModuleDirectory;\n for (let walkDepth = 0; walkDepth < 8; walkDepth += 1) {\n const candidate = path.join(\n walkingDirectory,\n 'packages',\n 'donut-engine',\n 'package.json',\n );\n candidatePaths.push(candidate);\n if (await fileSystemExtra.pathExists(candidate)) {\n const manifest = (await fileSystemExtra.readJson(\n candidate,\n )) as EnginePackageManifest;\n return extractVersionsFromManifest(manifest);\n }\n const parentDirectory = path.dirname(walkingDirectory);\n if (parentDirectory === walkingDirectory) {\n break;\n }\n walkingDirectory = parentDirectory;\n }\n\n throw new Error(\n `Unable to resolve @donut-games/engine version metadata. Checked:\\n ${candidatePaths.join(\n '\\n ',\n )}`,\n );\n}\n\n/**\n * Extract the version strings from a parsed engine `package.json`.\n * Strips the leading `^` or `~` from peer dep ranges so the template's\n * `^{{pixiVersion}}` emits a clean `^8.0.0` rather than `^^8.0.0`.\n */\nfunction extractVersionsFromManifest(manifest: EnginePackageManifest): {\n readonly donutEngineVersion: string;\n readonly pixiVersion: string;\n readonly threeVersion: string;\n} {\n const peerDependencies = manifest.peerDependencies ?? {};\n const stripRange = (range: string): string =>\n range.replace(/^[\\^~>=<\\s]+/, '');\n\n return {\n donutEngineVersion: manifest.version,\n pixiVersion: stripRange(peerDependencies['pixi.js'] ?? '8.0.0'),\n threeVersion: stripRange(peerDependencies['three'] ?? '0.160.0'),\n };\n}\n\n/**\n * Scaffold a new Donut game project at {@link InitializeProjectOptions.targetDirectory}.\n * Throws if the target directory already exists and is non-empty.\n */\nexport async function initializeProject(\n options: InitializeProjectOptions,\n): Promise<InitializeProjectResult> {\n const { projectName, rendererTarget } = options;\n const log = options.log ?? ((message: string) => console.log(message));\n const absoluteDirectory = path.resolve(options.targetDirectory);\n\n if (await fileSystemExtra.pathExists(absoluteDirectory)) {\n const existingEntries = await fileSystemExtra.readdir(absoluteDirectory);\n if (existingEntries.length > 0) {\n throw new Error(\n `Target directory \"${absoluteDirectory}\" already exists and is not empty.`,\n );\n }\n }\n\n await fileSystemExtra.ensureDir(absoluteDirectory);\n\n log(chalk.cyan(`Scaffolding \"${projectName}\" (${rendererTarget}) at ${absoluteDirectory}`));\n\n const templateSourceDirectory = options.templateDirectory ?? await resolveTemplateDirectory();\n const engineVersions = await resolveEngineVersions();\n\n const templateTokens: TemplateTokens = {\n projectName,\n rendererTarget,\n donutEngineVersion: engineVersions.donutEngineVersion,\n pixiVersion: engineVersions.pixiVersion,\n threeVersion: engineVersions.threeVersion,\n };\n\n const copyResult = await copyTemplate({\n sourceDirectory: templateSourceDirectory,\n destinationDirectory: absoluteDirectory,\n variant: rendererTarget,\n tokens: templateTokens,\n logProgress: (relativePath) => {\n log(chalk.green(` created ${relativePath}`));\n },\n });\n\n if (options.skipInstall !== true) {\n await runDependencyInstall(absoluteDirectory, log);\n }\n\n log(chalk.cyan(`\\nDone. Next steps:`));\n log(chalk.white(` cd ${projectName}`));\n if (options.skipInstall === true) {\n log(chalk.white(` npm install`));\n }\n log(chalk.white(` npm run dev`));\n\n return { absoluteDirectory, createdFiles: copyResult.createdFiles };\n}\n\n/**\n * Resolve the `template/` directory that ships alongside\n * `@donut-games/create-game`. Tries candidate locations in order and returns\n * the first that exists on disk. Throws a descriptive error if none resolve.\n *\n * 1. `../template` relative to this module's URL — works when running from a\n * shipped `@donut-games/cli` dist where the template sits beside `dist/`.\n * 2. Resolving `@donut-games/create-game/package.json` via Node's require\n * machinery, then joining `template/` — works in the monorepo.\n * 3. A source-tree relative fallback — works when this file is executed\n * directly out of `src/` during development.\n */\nasync function resolveTemplateDirectory(): Promise<string> {\n const checkedPaths: string[] = [];\n\n const shippedCandidate = fileURLToPath(new URL('../template', import.meta.url));\n checkedPaths.push(shippedCandidate);\n if (await fileSystemExtra.pathExists(shippedCandidate)) {\n return shippedCandidate;\n }\n\n try {\n const nodeRequire = createRequire(import.meta.url);\n const createGameManifestPath = nodeRequire.resolve(\n '@donut-games/create-game/package.json',\n );\n const monorepoCandidate = path.join(\n path.dirname(createGameManifestPath),\n 'template',\n );\n checkedPaths.push(monorepoCandidate);\n if (await fileSystemExtra.pathExists(monorepoCandidate)) {\n return monorepoCandidate;\n }\n } catch {\n // fall through to the next candidate\n }\n\n const thisModuleDirectory = path.dirname(fileURLToPath(import.meta.url));\n let walkingDirectory = thisModuleDirectory;\n for (let walkDepth = 0; walkDepth < 8; walkDepth += 1) {\n const candidate = path.join(\n walkingDirectory,\n 'packages',\n 'create-donut-game',\n 'template',\n );\n checkedPaths.push(candidate);\n if (await fileSystemExtra.pathExists(candidate)) {\n return candidate;\n }\n const parentDirectory = path.dirname(walkingDirectory);\n if (parentDirectory === walkingDirectory) {\n break;\n }\n walkingDirectory = parentDirectory;\n }\n\n throw new Error(\n `Unable to locate the @donut-games/create-game template directory. Checked:\\n ${checkedPaths.join(\n '\\n ',\n )}`,\n );\n}\n\n/**\n * Run `pnpm install` (falling back to `npm install`) in the scaffolded\n * directory. A non-zero exit or a missing package manager prints a yellow\n * warning but never throws — init is best-effort on the install step.\n */\nasync function runDependencyInstall(\n workingDirectory: string,\n log: (message: string) => void,\n): Promise<void> {\n const candidatePackageManagers: readonly string[] = ['pnpm', 'npm'];\n for (const packageManager of candidatePackageManagers) {\n const exitCode = await spawnInstall(packageManager, workingDirectory);\n if (exitCode === 'binary-missing') {\n continue;\n }\n if (exitCode === 0) {\n return;\n }\n break;\n }\n log(\n chalk.yellow(\n '\\u26a0 dependency install failed, run `npm install` manually',\n ),\n );\n}\n\n/**\n * Spawn `<packageManager> install` with inherited stdio. Resolves with the\n * exit code, or the literal `'binary-missing'` when the binary cannot be found.\n */\nfunction spawnInstall(\n packageManager: string,\n workingDirectory: string,\n): Promise<number | 'binary-missing'> {\n return new Promise((resolve) => {\n const child = spawn(packageManager, ['install'], {\n cwd: workingDirectory,\n stdio: 'inherit',\n shell: false,\n });\n child.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'ENOENT') {\n resolve('binary-missing');\n return;\n }\n resolve(1);\n });\n child.on('exit', (code) => {\n resolve(code ?? 1);\n });\n });\n}\n","import path from 'node:path';\nimport fileSystemExtra from 'fs-extra';\n\n/**\n * Token values substituted into template files during {@link copyTemplate}.\n *\n * Every token maps to a single occurrence of `{{tokenName}}` in template\n * contents. Versions are resolved by the caller from the CLI package.json\n * so the scaffolder always ships alongside a known engine version.\n */\nexport interface TemplateTokens {\n readonly projectName: string;\n readonly rendererTarget: 'pixi-2d' | 'three-3d';\n readonly donutEngineVersion: string;\n readonly pixiVersion: string;\n readonly threeVersion: string;\n}\n\n/**\n * Options accepted by {@link copyTemplate}.\n */\nexport interface CopyTemplateOptions {\n /** Absolute path to the `template/` directory shipped by @donut-games/create-game. */\n readonly sourceDirectory: string;\n /** Absolute path to the new project directory to write into. */\n readonly destinationDirectory: string;\n /** Renderer variant overlay to apply on top of the `base/` layer. */\n readonly variant: 'pixi-2d' | 'three-3d';\n /** Token values substituted into `.tpl` files and file-name tokens. */\n readonly tokens: TemplateTokens;\n /**\n * Optional progress callback invoked once per written file with the file's\n * path relative to {@link CopyTemplateOptions.destinationDirectory}.\n */\n readonly logProgress?: (relativePath: string) => void;\n}\n\n/**\n * Result of {@link copyTemplate}.\n */\nexport interface CopyTemplateResult {\n /**\n * Every file written, relative to {@link CopyTemplateOptions.destinationDirectory}.\n * Deduplicated in the order each relative path was first written.\n */\n readonly createdFiles: readonly string[];\n}\n\nconst TEMPLATE_FILE_SUFFIX = '.tpl';\n\n/**\n * Copy the `base/` layer of {@link CopyTemplateOptions.sourceDirectory}, then\n * overlay the `<variant>/` layer (later writes win). Applies rename rules and\n * token substitution described in {@link applyRenameRules} and\n * {@link renderTemplate}.\n *\n * Returns the set of relative paths written. Duplicate paths (a base file\n * overwritten by a variant file) appear only once in the returned array, in\n * the order of first write.\n */\nexport async function copyTemplate(\n options: CopyTemplateOptions,\n): Promise<CopyTemplateResult> {\n const { sourceDirectory, destinationDirectory, variant, tokens, logProgress } = options;\n\n await fileSystemExtra.ensureDir(destinationDirectory);\n\n const writtenFilesInOrder: string[] = [];\n const seenRelativePaths = new Set<string>();\n\n const recordWrite = (relativePath: string): void => {\n if (seenRelativePaths.has(relativePath)) {\n return;\n }\n seenRelativePaths.add(relativePath);\n writtenFilesInOrder.push(relativePath);\n };\n\n const copyLayer = async (layerDirectory: string): Promise<void> => {\n if ((await fileSystemExtra.pathExists(layerDirectory)) === false) {\n return;\n }\n const layerEntries = await collectFilesRecursively(layerDirectory);\n for (const sourceRelativePath of layerEntries) {\n const destinationRelativePath = applyRenameRulesToPath(sourceRelativePath);\n const absoluteSourcePath = path.join(layerDirectory, sourceRelativePath);\n const absoluteDestinationPath = path.join(\n destinationDirectory,\n destinationRelativePath,\n );\n\n await fileSystemExtra.ensureDir(path.dirname(absoluteDestinationPath));\n\n if (destinationRelativePath.endsWith(TEMPLATE_FILE_SUFFIX)) {\n // Should not happen: applyRenameRulesToPath strips the suffix below.\n throw new Error(\n `Template suffix was not stripped for ${destinationRelativePath}`,\n );\n }\n\n if (sourceRelativePath.endsWith(TEMPLATE_FILE_SUFFIX)) {\n const rawContents = await fileSystemExtra.readFile(absoluteSourcePath, 'utf8');\n const renderedContents = renderTemplate(rawContents, tokens);\n await fileSystemExtra.writeFile(\n absoluteDestinationPath,\n renderedContents,\n 'utf8',\n );\n } else {\n await fileSystemExtra.copyFile(absoluteSourcePath, absoluteDestinationPath);\n }\n\n recordWrite(destinationRelativePath);\n if (logProgress !== undefined) {\n logProgress(destinationRelativePath);\n }\n }\n };\n\n await copyLayer(path.join(sourceDirectory, 'base'));\n await copyLayer(path.join(sourceDirectory, variant));\n\n return { createdFiles: writtenFilesInOrder };\n}\n\n/**\n * Apply filename rename rules to a single file name (no path separators).\n *\n * A leading `_` is stripped and replaced by `.` so template directories can\n * carry what would otherwise be dotfiles through npm packaging. Names not\n * starting with `_` are returned unchanged. A trailing `.tpl` suffix is NOT\n * handled here; callers that walk paths handle it separately.\n */\nexport function applyRenameRules(fileName: string): string {\n if (fileName.length > 0 && fileName.startsWith('_')) {\n return '.' + fileName.slice(1);\n }\n return fileName;\n}\n\n/**\n * Replace every known `{{tokenName}}` occurrence in `contents` with the\n * corresponding value from `tokens`. Unknown tokens (any `{{…}}` that does\n * not match a known token name) are left untouched so legitimate template\n * code containing braces is never damaged.\n */\nexport function renderTemplate(\n contents: string,\n tokens: TemplateTokens,\n): string {\n let rendered = contents;\n const orderedTokenNames: readonly (keyof TemplateTokens)[] = [\n 'projectName',\n 'rendererTarget',\n 'donutEngineVersion',\n 'pixiVersion',\n 'threeVersion',\n ];\n for (const tokenName of orderedTokenNames) {\n const placeholder = '{{' + tokenName + '}}';\n const tokenValue = tokens[tokenName];\n rendered = rendered.split(placeholder).join(tokenValue);\n }\n return rendered;\n}\n\n/**\n * Apply {@link applyRenameRules} to each segment of a relative path, and\n * strip the trailing `.tpl` suffix from the final segment when present. Path\n * separators are preserved so directory names are renamed consistently.\n */\nfunction applyRenameRulesToPath(relativePath: string): string {\n const segments = relativePath.split(path.sep);\n const renamedSegments = segments.map((segment) => applyRenameRules(segment));\n const lastSegmentIndex = renamedSegments.length - 1;\n const lastSegment = renamedSegments[lastSegmentIndex];\n if (lastSegment.endsWith(TEMPLATE_FILE_SUFFIX)) {\n renamedSegments[lastSegmentIndex] = lastSegment.slice(\n 0,\n lastSegment.length - TEMPLATE_FILE_SUFFIX.length,\n );\n }\n return renamedSegments.join(path.sep);\n}\n\n/**\n * Recursively walk `rootDirectory` and return every file's path relative to\n * `rootDirectory`, using the platform path separator. Directories themselves\n * are not returned; empty directories contribute nothing.\n */\nasync function collectFilesRecursively(\n rootDirectory: string,\n): Promise<readonly string[]> {\n const collectedPaths: string[] = [];\n\n const walkDirectory = async (absoluteDirectory: string): Promise<void> => {\n const directoryEntries = await fileSystemExtra.readdir(absoluteDirectory, {\n withFileTypes: true,\n });\n for (const directoryEntry of directoryEntries) {\n const absoluteEntryPath = path.join(absoluteDirectory, directoryEntry.name);\n if (directoryEntry.isDirectory()) {\n await walkDirectory(absoluteEntryPath);\n continue;\n }\n if (directoryEntry.isFile()) {\n const relativePath = path.relative(rootDirectory, absoluteEntryPath);\n collectedPaths.push(relativePath);\n }\n }\n };\n\n await walkDirectory(rootDirectory);\n collectedPaths.sort();\n return collectedPaths;\n}\n","import path from 'node:path';\nimport fileSystemExtra from 'fs-extra';\nimport chalk from 'chalk';\nimport { resolveMonorepoDevAliases } from '../shared/monorepo-aliases.js';\nimport { detectProjectSourceKind, type ProjectSourceKind } from './build.js';\n\n/**\n * Options accepted by {@link runDevServer}.\n */\nexport interface RunDevServerOptions {\n /** Absolute path to the Donut project directory containing `donut.json`. */\n readonly projectDirectory: string;\n /** Port the Vite dev server should listen on. Defaults to 5173. */\n readonly port?: number;\n /** Whether Vite should open a browser window on start. Defaults to true. */\n readonly open?: boolean;\n /** Optional logger — defaults to `console.log`. Injected for tests. */\n readonly log?: (message: string) => void;\n /**\n * Optional hook to start Vite. Injected for tests so unit tests do not\n * need to bind to a network port. When omitted, the real Vite programmatic\n * API is loaded and used.\n */\n readonly startViteServer?: (viteConfig: Record<string, unknown>) => Promise<{\n listen(): Promise<unknown>;\n resolvedUrls?: { local?: readonly string[] };\n }>;\n}\n\n/**\n * Shape of the `donut.json` project manifest that this command needs.\n */\nexport interface DonutProjectManifest {\n readonly name: string;\n readonly rendererTarget: 'pixi-2d' | 'three-3d';\n readonly donutEngineVersion?: string;\n}\n\n/**\n * Reads and validates the `donut.json` manifest inside the given directory.\n *\n * @throws when the manifest is missing or malformed.\n */\nexport async function readProjectManifest(\n projectDirectory: string,\n): Promise<DonutProjectManifest> {\n const manifestPath = path.join(projectDirectory, 'donut.json');\n if (!(await fileSystemExtra.pathExists(manifestPath))) {\n throw new Error(\n `No donut.json found in ${projectDirectory}. Run 'donut init' first.`,\n );\n }\n const parsed = (await fileSystemExtra.readJson(manifestPath)) as Partial<DonutProjectManifest>;\n if (typeof parsed.name !== 'string' || parsed.name.length === 0) {\n throw new Error(`donut.json is missing required field 'name'.`);\n }\n if (parsed.rendererTarget !== 'pixi-2d' && parsed.rendererTarget !== 'three-3d') {\n throw new Error(\n `donut.json has invalid 'rendererTarget' — expected 'pixi-2d' or 'three-3d'.`,\n );\n }\n return {\n name: parsed.name,\n rendererTarget: parsed.rendererTarget,\n donutEngineVersion: parsed.donutEngineVersion,\n };\n}\n\n/**\n * Determines whether the project directory sits inside the donut-engine\n * monorepo by walking up looking for a `pnpm-workspace.yaml`. Returns the\n * absolute path to the monorepo root, or `undefined` if not found.\n */\nexport async function detectMonorepoRoot(\n projectDirectory: string,\n): Promise<string | undefined> {\n let currentDirectory = path.resolve(projectDirectory);\n for (let depth = 0; depth < 8; depth++) {\n const workspaceFile = path.join(currentDirectory, 'pnpm-workspace.yaml');\n if (await fileSystemExtra.pathExists(workspaceFile)) {\n return currentDirectory;\n }\n const parentDirectory = path.dirname(currentDirectory);\n if (parentDirectory === currentDirectory) {\n return undefined;\n }\n currentDirectory = parentDirectory;\n }\n return undefined;\n}\n\n/**\n * Writes the generated runtime entry module and `index.html` into\n * `<projectDirectory>/.donut/`. Returns the directory path that Vite should\n * use as its `root`.\n */\nexport async function writeDevEntryFiles(\n projectDirectory: string,\n manifest: DonutProjectManifest,\n projectSourceKind: ProjectSourceKind,\n): Promise<string> {\n const donutDirectory = path.join(projectDirectory, '.donut');\n await fileSystemExtra.ensureDir(donutDirectory);\n\n // Ensure .donut is git-ignored so generated artifacts don't leak in.\n const gitIgnorePath = path.join(donutDirectory, '.gitignore');\n await fileSystemExtra.writeFile(gitIgnorePath, '*\\n', 'utf8');\n\n const indexHtmlPath = path.join(donutDirectory, 'index.html');\n await fileSystemExtra.writeFile(indexHtmlPath, buildIndexHtml(manifest), 'utf8');\n\n const devEntryPath = path.join(donutDirectory, 'dev-entry.ts');\n await fileSystemExtra.writeFile(\n devEntryPath,\n buildDevEntrySource(manifest, projectSourceKind),\n 'utf8',\n );\n\n return donutDirectory;\n}\n\n/**\n * Generates the HTML document served by the dev server.\n */\nfunction buildIndexHtml(manifest: DonutProjectManifest): string {\n return `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>${manifest.name} — Donut dev</title>\n <style>\n html, body { margin: 0; padding: 0; height: 100%; background: #0a0a12; overflow: hidden; }\n canvas#game-canvas { display: block; width: 100vw; height: 100vh; }\n </style>\n </head>\n <body>\n <canvas id=\"game-canvas\"></canvas>\n <script type=\"module\" src=\"./dev-entry.ts\"></script>\n </body>\n</html>\n`;\n}\n\n/**\n * Generates the TypeScript entry module served by Vite. Delegates the entire\n * runtime surface to `@donut/player`'s `startGame` — the same function used\n * by `pnpm --filter @donut/player dev` and (in a follow-up) by the hosted\n * bundle viewer on donut.games.\n *\n * The glob side-effect imports ensure every component/system file in the\n * project is pulled into the bundle, so HMR picks up edits to any file\n * even when nothing in the scene file references it directly.\n */\nfunction buildDevEntrySource(\n manifest: DonutProjectManifest,\n projectSourceKind: ProjectSourceKind,\n): string {\n const playerImportSpecifier =\n projectSourceKind === 'published' ? '@donut-games/engine/player' : '@donut/player';\n return `// AUTO-GENERATED by \\`donut dev\\`. Do not edit — regenerated every launch.\nimport { startGame } from '${playerImportSpecifier}';\nimport { createDefaultScene } from '../src/scenes/default-scene';\n\nconst discoveredComponentModules = import.meta.glob('../src/components/**/*.ts', { eager: true });\nconst discoveredSystemModules = import.meta.glob('../src/systems/**/*.ts', { eager: true });\n\nfor (const modulePath of Object.keys(discoveredComponentModules)) {\n console.log('[Donut dev] discovered component module:', modulePath);\n}\nfor (const modulePath of Object.keys(discoveredSystemModules)) {\n console.log('[Donut dev] discovered system module:', modulePath);\n}\n\nasync function bootstrapDevelopmentRuntime(): Promise<void> {\n const canvasElement = document.getElementById('game-canvas');\n if (!(canvasElement instanceof HTMLCanvasElement)) {\n throw new Error('[Donut dev] Missing <canvas id=\"game-canvas\">');\n }\n\n await startGame({\n canvas: canvasElement,\n rendererTarget: '${manifest.rendererTarget}',\n sceneFactory: createDefaultScene,\n enableDevelopmentBridge: true,\n enableDebugOverlay: true,\n });\n\n console.log('[Donut dev] booted renderer=${manifest.rendererTarget}');\n}\n\nbootstrapDevelopmentRuntime().catch((error) => {\n console.error('[Donut dev] bootstrap failed', error);\n});\n`;\n}\n\n/**\n * Builds the in-memory Vite config for serving the generated `.donut/` dir.\n * Exposed for tests.\n */\nexport async function buildViteConfig(parameters: {\n readonly projectDirectory: string;\n readonly donutRoot: string;\n readonly port: number;\n readonly open: boolean;\n}): Promise<Record<string, unknown>> {\n const { projectDirectory, donutRoot, port, open } = parameters;\n const monorepoRoot = await detectMonorepoRoot(projectDirectory);\n\n const resolveAlias =\n monorepoRoot !== undefined\n ? await resolveMonorepoDevAliases(monorepoRoot)\n : [];\n\n return {\n root: donutRoot,\n configFile: false,\n server: {\n port,\n open,\n strictPort: false,\n fs: {\n allow: [\n projectDirectory,\n ...(monorepoRoot !== undefined ? [monorepoRoot] : []),\n ],\n },\n },\n resolve: {\n alias: resolveAlias,\n },\n optimizeDeps: {\n // Let Vite figure out deps from the generated entry.\n },\n };\n}\n\n/**\n * Starts the Donut dev server for the given project directory.\n *\n * Reads `donut.json`, generates a runtime entry + HTML under\n * `<projectDirectory>/.donut/`, and launches Vite programmatically.\n */\nexport async function runDevServer(options: RunDevServerOptions): Promise<void> {\n const {\n projectDirectory,\n port = 5173,\n open = true,\n log = (message: string) => console.log(message),\n startViteServer,\n } = options;\n\n const manifest = await readProjectManifest(projectDirectory);\n log(chalk.cyan(`Donut dev — project '${manifest.name}' (${manifest.rendererTarget})`));\n\n const projectSourceKind = await detectProjectSourceKind(projectDirectory, log);\n const donutRoot = await writeDevEntryFiles(projectDirectory, manifest, projectSourceKind);\n const viteConfig = await buildViteConfig({ projectDirectory, donutRoot, port, open });\n\n const startServer =\n startViteServer ??\n (async (configuration: Record<string, unknown>) => {\n const viteModule = await import('vite');\n return viteModule.createServer(configuration as never);\n });\n\n const server = await startServer(viteConfig);\n await server.listen();\n const resolvedLocalUrl = server.resolvedUrls?.local?.[0];\n if (resolvedLocalUrl !== undefined) {\n log(chalk.green(`Dev server ready at ${resolvedLocalUrl}`));\n } else {\n log(chalk.green(`Dev server listening on port ${port}`));\n }\n}\n","import path from 'node:path';\nimport fileSystemExtra from 'fs-extra';\n\n/**\n * Names of the internal `@donut/<name>` workspace packages that get aliased\n * onto their source directories when running inside the monorepo.\n */\nconst INTERNAL_DONUT_PACKAGE_NAMES: readonly string[] = [\n 'core',\n 'math',\n 'pixi',\n 'three',\n 'ctrllr',\n 'player',\n];\n\n/**\n * Published `@donut-games/engine` subpaths to alias onto the umbrella package's\n * source barrels. The umbrella bare specifier (no subpath) gets mapped to\n * `src/index.ts`. The subpath specifiers map to `src/<subpath>/index.ts`.\n */\nconst PUBLISHED_ENGINE_SUBPATH_NAMES: readonly string[] = [\n 'math',\n 'core',\n 'ctrllr',\n 'pixi',\n 'three',\n 'player',\n];\n\n/**\n * Describes the exports object shape as it appears in\n * `packages/donut-engine/package.json`.\n */\ninterface DonutEnginePackageManifest {\n readonly exports?: Record<string, unknown>;\n}\n\n/**\n * Single Vite alias entry — `find` may be a string (prefix match) or a\n * regular expression (exact/anchored match). Array form is required because\n * Vite's string-prefix aliases match in array order and we need the more\n * specific `@donut-games/engine/<subpath>` entries to take priority over the\n * bare umbrella `@donut-games/engine`. With the record form, Vite would\n * iterate insertion order but still prefix-match the umbrella first against\n * `@donut-games/engine/core`, producing `src/index.ts/core` (ENOTDIR).\n */\nexport interface MonorepoDevAliasEntry {\n readonly find: string | RegExp;\n readonly replacement: string;\n}\n\n/**\n * Build the Vite `resolve.alias` array that redirects published and internal\n * Donut specifiers onto their in-monorepo source directories, so both `donut\n * dev` and `donut build` run directly against TypeScript sources without\n * needing a `tsup` rebuild of the umbrella package first.\n *\n * Returns an empty array when any expected source path is missing — callers\n * that depend on monorepo-relative aliasing should have already checked\n * `detectMonorepoRoot` before invoking this helper.\n *\n * Also runs a dev-time assertion: every subpath declared in the umbrella\n * package's `exports` field must have a matching alias entry. This catches\n * regressions where a new subpath is added to the umbrella without the\n * corresponding alias — otherwise HMR would silently fall through to the\n * compiled `dist/` copy, which would be stale during source-level editing.\n */\nexport async function resolveMonorepoDevAliases(\n monorepoRoot: string,\n): Promise<MonorepoDevAliasEntry[]> {\n const packagesDirectory = path.join(monorepoRoot, 'packages');\n const resolvedAliasEntries: MonorepoDevAliasEntry[] = [];\n\n // Published `@donut-games/engine/<subpath>` aliases come FIRST so Vite's\n // prefix matcher hits them before the umbrella bare specifier below.\n const umbrellaSourceDirectory = path.join(packagesDirectory, 'donut-engine', 'src');\n for (const subpathName of PUBLISHED_ENGINE_SUBPATH_NAMES) {\n const subpathBarrelPath = path.join(\n umbrellaSourceDirectory,\n subpathName,\n 'index.ts',\n );\n if (await fileSystemExtra.pathExists(subpathBarrelPath)) {\n resolvedAliasEntries.push({\n find: `@donut-games/engine/${subpathName}`,\n replacement: subpathBarrelPath,\n });\n }\n }\n\n // The bare umbrella specifier uses an anchored regex so it only matches\n // `@donut-games/engine` exactly — never `@donut-games/engine/core` etc.\n const umbrellaBarrelPath = path.join(umbrellaSourceDirectory, 'index.ts');\n if (await fileSystemExtra.pathExists(umbrellaBarrelPath)) {\n resolvedAliasEntries.push({\n find: /^@donut-games\\/engine$/,\n replacement: umbrellaBarrelPath,\n });\n }\n\n // Legacy internal `@donut/*` aliases — kept alongside the published\n // ones so any remaining monorepo consumers that still declare workspace\n // deps on the internal packages continue to resolve against source.\n for (const internalPackageName of INTERNAL_DONUT_PACKAGE_NAMES) {\n const internalSourceDirectory = path.join(\n packagesDirectory,\n internalPackageName,\n 'src',\n );\n if (await fileSystemExtra.pathExists(internalSourceDirectory)) {\n resolvedAliasEntries.push({\n find: `@donut/${internalPackageName}`,\n replacement: internalSourceDirectory,\n });\n }\n }\n\n await assertEveryPublishedSubpathHasAnAlias(monorepoRoot, resolvedAliasEntries);\n return resolvedAliasEntries;\n}\n\n/**\n * Walk the `exports` field of `packages/donut-engine/package.json` and\n * verify every subpath has a matching entry in the alias map. Throws when\n * a subpath is missing — this is a developer-visible fail-fast so nobody\n * accidentally ships a new subpath without the dev/build aliases.\n *\n * The `.` and `./package.json` entries are skipped because they either map\n * to the umbrella specifier (already covered) or don't need aliasing.\n */\nasync function assertEveryPublishedSubpathHasAnAlias(\n monorepoRoot: string,\n resolvedAliasEntries: readonly MonorepoDevAliasEntry[],\n): Promise<void> {\n const umbrellaPackageJsonPath = path.join(\n monorepoRoot,\n 'packages',\n 'donut-engine',\n 'package.json',\n );\n if (!(await fileSystemExtra.pathExists(umbrellaPackageJsonPath))) {\n return;\n }\n const parsedManifest = (await fileSystemExtra.readJson(\n umbrellaPackageJsonPath,\n )) as DonutEnginePackageManifest;\n const exportsField = parsedManifest.exports;\n if (exportsField === undefined || exportsField === null) {\n return;\n }\n\n const aliasedStringFinds = new Set(\n resolvedAliasEntries\n .map((entry) => entry.find)\n .filter((find): find is string => typeof find === 'string'),\n );\n\n const missingAliasSubpaths: string[] = [];\n for (const exportKey of Object.keys(exportsField)) {\n if (exportKey === '.' || exportKey === './package.json') {\n continue;\n }\n if (!exportKey.startsWith('./')) {\n continue;\n }\n const subpathName = exportKey.slice(2);\n const expectedAliasKey = `@donut-games/engine/${subpathName}`;\n if (!aliasedStringFinds.has(expectedAliasKey)) {\n missingAliasSubpaths.push(expectedAliasKey);\n }\n }\n\n if (missingAliasSubpaths.length > 0) {\n throw new Error(\n `resolveMonorepoDevAliases: missing Vite aliases for published subpaths — ` +\n `${missingAliasSubpaths.join(', ')}. Every subpath in ` +\n `packages/donut-engine/package.json \"exports\" must have a matching alias.`,\n );\n }\n}\n","import path from 'node:path';\nimport fileSystemExtra from 'fs-extra';\nimport chalk from 'chalk';\nimport JSZip from 'jszip';\nimport {\n readProjectManifest,\n detectMonorepoRoot,\n type DonutProjectManifest,\n} from './dev.js';\nimport { runValidation } from './validate.js';\nimport { resolveMonorepoDevAliases } from '../shared/monorepo-aliases.js';\nimport type {\n BundleManifest,\n BundleManifestAssetEntry,\n} from '../bundle/bundle-manifest.js';\n\n/**\n * A single security-scanner finding produced by {@link scanUserSourcesForForbiddenPatterns}.\n */\nexport interface SecurityViolation {\n /** Absolute path to the offending source file. */\n readonly filePath: string;\n /** 1-based line number. */\n readonly line: number;\n /** Regex source text that matched. */\n readonly pattern: string;\n /** The actual substring that matched. */\n readonly match: string;\n /** `error` fails the build; `warning` is informational. */\n readonly severity: 'error' | 'warning';\n}\n\n/**\n * Options accepted by {@link runBuild}.\n */\nexport interface RunBuildOptions {\n /** Absolute path to the Donut project directory. */\n readonly projectDirectory: string;\n /** Absolute path for produced bundle output; defaults to `<project>/dist`. */\n readonly outputDirectory?: string;\n /** Optional logger — defaults to `console.log`. */\n readonly log?: (message: string) => void;\n /**\n * Optional Vite-build hook. When omitted, the real programmatic `vite.build`\n * API is used. Tests inject a stub that writes a tiny `game.js` instead.\n */\n readonly runViteBuild?: (viteConfig: Record<string, unknown>) => Promise<void>;\n /**\n * Optional image optimizer override used for tests. When omitted, the\n * production helper {@link optimizeImageAssetIfPossible} is used which\n * dynamically imports `sharp` if installed.\n */\n readonly optimizeImageAsset?: ImageAssetOptimizer;\n}\n\n/**\n * Signature for the pluggable per-file image optimizer. Implementations\n * should return the optimized bytes, or `null` to signal pass-through copy.\n */\nexport type ImageAssetOptimizer = (\n sourcePath: string,\n extension: string,\n) => Promise<Buffer | null>;\n\n/**\n * Structured result of a build. Callers can present this to the user.\n */\nexport interface BuildReport {\n /** Absolute path to the emitted `.donut` file. */\n readonly bundlePath: string;\n /** Byte size of the emitted `.donut` file. */\n readonly bundleSizeBytes: number;\n /** Wall-clock duration of the build in milliseconds. */\n readonly buildTimeMilliseconds: number;\n /** Non-fatal findings (e.g. `window`/`document` usage). */\n readonly warnings: readonly SecurityViolation[];\n /** Fatal findings (empty on success; populated when build was aborted). */\n readonly violations: readonly SecurityViolation[];\n /** The generated manifest. */\n readonly manifest: BundleManifest;\n}\n\n/**\n * Thrown when the build is aborted due to fatal security violations or\n * validation errors. Consumers may read `.violations` to render diagnostics.\n */\nexport class BuildViolationError extends Error {\n public readonly violations: readonly SecurityViolation[];\n public constructor(message: string, violations: readonly SecurityViolation[]) {\n super(message);\n this.name = 'BuildViolationError';\n this.violations = violations;\n }\n}\n\n/**\n * Patterns searched for in user-authored game logic sources. The `severity`\n * field controls whether a match aborts the build or only warns.\n */\nconst FORBIDDEN_PATTERNS: ReadonlyArray<{\n readonly pattern: RegExp;\n readonly severity: 'error' | 'warning';\n}> = [\n { pattern: /\\bfetch\\s*\\(/g, severity: 'error' },\n { pattern: /\\blocalStorage\\b/g, severity: 'error' },\n { pattern: /\\bsessionStorage\\b/g, severity: 'error' },\n { pattern: /\\bXMLHttpRequest\\b/g, severity: 'error' },\n { pattern: /\\bWebSocket\\b/g, severity: 'error' },\n { pattern: /\\beval\\s*\\(/g, severity: 'error' },\n { pattern: /new\\s+Function\\s*\\(/g, severity: 'error' },\n { pattern: /\\bimport\\s*\\(/g, severity: 'error' },\n { pattern: /\\.innerHTML\\s*=/g, severity: 'error' },\n { pattern: /\\bwindow\\b/g, severity: 'warning' },\n { pattern: /\\bdocument\\b/g, severity: 'warning' },\n];\n\n/**\n * Recursively collect all `.ts` files under a directory.\n */\nasync function collectTypeScriptFilesRecursively(\n directoryPath: string,\n): Promise<string[]> {\n if (!(await fileSystemExtra.pathExists(directoryPath))) {\n return [];\n }\n const collected: string[] = [];\n const entries = await fileSystemExtra.readdir(directoryPath, { withFileTypes: true });\n for (const entry of entries) {\n const entryPath = path.join(directoryPath, entry.name);\n if (entry.isDirectory()) {\n collected.push(...(await collectTypeScriptFilesRecursively(entryPath)));\n } else if (entry.isFile() && entry.name.endsWith('.ts')) {\n collected.push(entryPath);\n }\n }\n return collected;\n}\n\n/**\n * Scan user-authored source files (components + systems only) for forbidden\n * runtime APIs. Produces structured findings — never throws for matches.\n */\nexport async function scanUserSourcesForForbiddenPatterns(\n projectDirectory: string,\n): Promise<SecurityViolation[]> {\n const directoriesToScan = [\n path.join(projectDirectory, 'src', 'components'),\n path.join(projectDirectory, 'src', 'systems'),\n ];\n const violations: SecurityViolation[] = [];\n for (const directoryPath of directoriesToScan) {\n const files = await collectTypeScriptFilesRecursively(directoryPath);\n for (const filePath of files) {\n const contents = await fileSystemExtra.readFile(filePath, 'utf8');\n const lines = contents.split(/\\r?\\n/);\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {\n const lineText = lines[lineIndex];\n for (const { pattern, severity } of FORBIDDEN_PATTERNS) {\n pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = pattern.exec(lineText)) !== null) {\n violations.push({\n filePath,\n line: lineIndex + 1,\n pattern: pattern.source,\n match: match[0],\n severity,\n });\n }\n }\n }\n }\n }\n return violations;\n}\n\n/**\n * Discriminates which public-import surface the project is wired to. Drives\n * which bare specifiers get written into the generated build-entry module:\n *\n * - `'published'` — project declares `@donut-games/engine` as a dependency;\n * emit subpath imports like `@donut-games/engine/core`.\n * - `'workspace'` — project declares one or more internal `@donut/*` workspace\n * dependencies; emit legacy `@donut/core` etc. imports for back-compat with\n * in-repo smoketest and fixture projects.\n */\nexport type ProjectSourceKind = 'published' | 'workspace';\n\n/**\n * Inspect the project's `package.json` and decide which import surface the\n * generated build entry should target. Prefers `'published'` when both are\n * present (transitional projects), logs a warning in that case, and throws\n * when neither appears.\n */\nexport async function detectProjectSourceKind(\n projectDirectory: string,\n log: (message: string) => void,\n): Promise<ProjectSourceKind> {\n const projectPackageJsonPath = path.join(projectDirectory, 'package.json');\n if (!(await fileSystemExtra.pathExists(projectPackageJsonPath))) {\n throw new Error(\n `Cannot determine project source kind — no package.json found at ${projectPackageJsonPath}.`,\n );\n }\n const parsedManifest = (await fileSystemExtra.readJson(projectPackageJsonPath)) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const mergedDependencies: Record<string, string> = {\n ...(parsedManifest.dependencies ?? {}),\n ...(parsedManifest.devDependencies ?? {}),\n };\n\n const hasPublishedEngineDependency = '@donut-games/engine' in mergedDependencies;\n const hasAnyInternalWorkspaceDependency = Object.keys(mergedDependencies).some(\n (dependencyName) => dependencyName.startsWith('@donut/'),\n );\n\n if (hasPublishedEngineDependency && hasAnyInternalWorkspaceDependency) {\n log(\n chalk.yellow(\n '\\u26a0 Both @donut-games/engine and internal @donut/* workspace dependencies ' +\n 'are declared. Preferring published imports for the build entry.',\n ),\n );\n return 'published';\n }\n if (hasPublishedEngineDependency) {\n return 'published';\n }\n if (hasAnyInternalWorkspaceDependency) {\n return 'workspace';\n }\n\n throw new Error(\n `Cannot determine project source kind — package.json at ${projectPackageJsonPath} ` +\n `declares neither \"@donut-games/engine\" nor any internal \"@donut/*\" workspace dependency. ` +\n `Add \"@donut-games/engine\" to dependencies to build against the published engine surface.`,\n );\n}\n\n/**\n * Produce the auto-generated build entry file mirroring the dev entry but\n * structured as a library entry that exports `startGame` and also auto-starts\n * when `#game-canvas` is present in the DOM at load time.\n *\n * `projectSourceKind` decides whether the generated imports target the\n * published `@donut-games/engine/*` subpaths or the internal `@donut/*`\n * workspace specifiers.\n */\nfunction buildBuildEntrySource(\n manifest: DonutProjectManifest,\n projectSourceKind: ProjectSourceKind,\n): string {\n const usesPixi = manifest.rendererTarget.startsWith('pixi');\n const coreSpecifier =\n projectSourceKind === 'published' ? '@donut-games/engine/core' : '@donut/core';\n const ctrllrSpecifier =\n projectSourceKind === 'published' ? '@donut-games/engine/ctrllr' : '@donut/ctrllr';\n const pixiSpecifier =\n projectSourceKind === 'published' ? '@donut-games/engine/pixi' : '@donut/pixi';\n return `// AUTO-GENERATED by \\`donut build\\`. Do not edit.\nimport { World, SystemType, MotionSystem } from '${coreSpecifier}';\nimport { MockCtrllrManager, CtrllrInputSystem } from '${ctrllrSpecifier}';\n${usesPixi ? `import { PixiRendererAdapter, PixiRenderSystem } from '${pixiSpecifier}';\\n` : ''}import { createDefaultScene } from '../src/scenes/default-scene';\n\nconst discoveredComponentModules = import.meta.glob('../src/components/**/*.ts', { eager: true });\nconst discoveredSystemModules = import.meta.glob('../src/systems/**/*.ts', { eager: true });\nconst discoveredSceneModules = import.meta.glob('../src/scenes/**/*.ts', { eager: true });\nvoid discoveredComponentModules;\nvoid discoveredSystemModules;\nvoid discoveredSceneModules;\n\nexport interface StartGameOptions {\n readonly canvas: HTMLCanvasElement;\n}\n\nexport async function startGame(options: StartGameOptions): Promise<void> {\n const world = new World();\n world.addSystem(new CtrllrInputSystem(world));\n world.addSystem(new MotionSystem(world));\n\n ${usesPixi ? `const rendererAdapter = new PixiRendererAdapter({\n canvas: options.canvas,\n width: options.canvas.width || 800,\n height: options.canvas.height || 600,\n backgroundColor: 0x0a0a12,\n });\n await rendererAdapter.initialize();\n world.addSystem(new PixiRenderSystem(world, rendererAdapter));` : `console.warn('[Donut] three-3d renderer target is not yet implemented');`}\n\n const ctrllrManager = new MockCtrllrManager();\n\n createDefaultScene(world, ctrllrManager);\n\n let previousTimestamp: number | undefined;\n function gameLoop(currentTimestamp: number): void {\n if (previousTimestamp === undefined) previousTimestamp = currentTimestamp;\n const elapsedMilliseconds = currentTimestamp - previousTimestamp;\n previousTimestamp = currentTimestamp;\n world.update(SystemType.Update, elapsedMilliseconds);\n world.update(SystemType.Draw, elapsedMilliseconds);\n requestAnimationFrame(gameLoop);\n }\n requestAnimationFrame(gameLoop);\n}\n\n// Auto-start when a canvas with id=\"game-canvas\" is present.\nif (typeof document !== 'undefined') {\n const canvasElement = document.getElementById('game-canvas');\n if (canvasElement instanceof HTMLCanvasElement) {\n startGame({ canvas: canvasElement }).catch((error) => {\n console.error('[Donut] startGame failed', error);\n });\n }\n}\n`;\n}\n\n/**\n * Write the build-entry module into `<projectDirectory>/.donut/build-entry.ts`.\n */\nasync function writeBuildEntryFile(\n projectDirectory: string,\n manifest: DonutProjectManifest,\n projectSourceKind: ProjectSourceKind,\n): Promise<string> {\n const donutDirectory = path.join(projectDirectory, '.donut');\n await fileSystemExtra.ensureDir(donutDirectory);\n const gitIgnorePath = path.join(donutDirectory, '.gitignore');\n if (!(await fileSystemExtra.pathExists(gitIgnorePath))) {\n await fileSystemExtra.writeFile(gitIgnorePath, '*\\n', 'utf8');\n }\n const entryPath = path.join(donutDirectory, 'build-entry.ts');\n await fileSystemExtra.writeFile(\n entryPath,\n buildBuildEntrySource(manifest, projectSourceKind),\n 'utf8',\n );\n return entryPath;\n}\n\n/**\n * Build the Vite configuration object used by `donut build`.\n */\nexport async function buildViteBuildConfig(parameters: {\n readonly projectDirectory: string;\n readonly donutRoot: string;\n readonly buildEntryPath: string;\n readonly stagingDirectory: string;\n}): Promise<Record<string, unknown>> {\n const { projectDirectory, donutRoot, buildEntryPath, stagingDirectory } = parameters;\n const monorepoRoot = await detectMonorepoRoot(projectDirectory);\n const resolveAlias =\n monorepoRoot !== undefined\n ? await resolveMonorepoDevAliases(monorepoRoot)\n : [];\n return {\n root: donutRoot,\n configFile: false,\n logLevel: 'warn',\n resolve: { alias: resolveAlias },\n build: {\n outDir: stagingDirectory,\n emptyOutDir: true,\n minify: 'esbuild',\n lib: {\n entry: buildEntryPath,\n formats: ['es'],\n fileName: () => 'game.js',\n },\n rollupOptions: {\n output: {\n inlineDynamicImports: true,\n entryFileNames: 'game.js',\n },\n },\n },\n };\n}\n\n/**\n * Recursively copy project assets into the staging directory, returning the\n * manifest entries describing each copied file.\n */\nasync function copyAssetsAndCollectEntries(\n projectAssetsDirectory: string,\n stagingAssetsDirectory: string,\n imageAssetOptimizer: ImageAssetOptimizer,\n log: (message: string) => void,\n): Promise<BundleManifestAssetEntry[]> {\n let sharpUnavailableWarningEmitted = false;\n if (!(await fileSystemExtra.pathExists(projectAssetsDirectory))) {\n return [];\n }\n await fileSystemExtra.ensureDir(stagingAssetsDirectory);\n const entries: BundleManifestAssetEntry[] = [];\n\n async function walk(currentSource: string, currentDestination: string, relativePrefix: string): Promise<void> {\n const directoryEntries = await fileSystemExtra.readdir(currentSource, { withFileTypes: true });\n for (const directoryEntry of directoryEntries) {\n if (directoryEntry.name === '.gitkeep') {\n continue;\n }\n const sourcePath = path.join(currentSource, directoryEntry.name);\n const destinationPath = path.join(currentDestination, directoryEntry.name);\n const relativePath = relativePrefix === ''\n ? directoryEntry.name\n : `${relativePrefix}/${directoryEntry.name}`;\n if (directoryEntry.isDirectory()) {\n await fileSystemExtra.ensureDir(destinationPath);\n await walk(sourcePath, destinationPath, relativePath);\n } else if (directoryEntry.isFile()) {\n const extensionWithDot = path.extname(directoryEntry.name).toLowerCase();\n const extension = extensionWithDot.startsWith('.')\n ? extensionWithDot.slice(1)\n : extensionWithDot;\n const isOptimizableImage = ['png', 'jpg', 'jpeg', 'webp'].includes(extension);\n let wroteOptimizedOutput = false;\n if (isOptimizableImage) {\n const optimizedBuffer = await imageAssetOptimizer(sourcePath, extension);\n if (optimizedBuffer !== null) {\n await fileSystemExtra.writeFile(destinationPath, optimizedBuffer);\n wroteOptimizedOutput = true;\n } else if (!sharpUnavailableWarningEmitted) {\n log(chalk.yellow('\\u2139 sharp not installed \\u2014 assets copied uncompressed'));\n sharpUnavailableWarningEmitted = true;\n }\n }\n if (!wroteOptimizedOutput) {\n await fileSystemExtra.copy(sourcePath, destinationPath);\n }\n const stat = await fileSystemExtra.stat(destinationPath);\n entries.push({\n name: directoryEntry.name,\n path: `assets/${relativePath}`,\n type: extension,\n sizeBytes: stat.size,\n });\n }\n }\n }\n\n await walk(projectAssetsDirectory, stagingAssetsDirectory, '');\n return entries;\n}\n\n/**\n * Read engine package versions from the monorepo `packages/*` directory.\n * Returns a best-effort map — packages that cannot be resolved are omitted.\n */\nasync function collectRuntimeVersions(\n projectDirectory: string,\n): Promise<Record<string, string>> {\n const runtimeVersions: Record<string, string> = {};\n const monorepoRoot = await detectMonorepoRoot(projectDirectory);\n const donutPackageNames = ['core', 'math', 'pixi', 'three', 'ctrllr', 'player'];\n if (monorepoRoot !== undefined) {\n const packagesDirectory = path.join(monorepoRoot, 'packages');\n for (const packageName of donutPackageNames) {\n const packageJsonPath = path.join(packagesDirectory, packageName, 'package.json');\n if (await fileSystemExtra.pathExists(packageJsonPath)) {\n const parsed = (await fileSystemExtra.readJson(packageJsonPath)) as {\n name?: string;\n version?: string;\n };\n if (typeof parsed.name === 'string' && typeof parsed.version === 'string') {\n runtimeVersions[parsed.name] = parsed.version;\n }\n }\n }\n }\n return runtimeVersions;\n}\n\n/**\n * Programmatically invoke Vite's `build` API when no override is provided.\n */\nasync function runViteBuildProgrammatically(\n viteConfig: Record<string, unknown>,\n): Promise<void> {\n const viteModule = await import('vite');\n await viteModule.build(viteConfig as never);\n}\n\n/**\n * Entrypoint — validates, bundles, scans, and zips a Donut project into a\n * `.donut` archive. Throws {@link BuildViolationError} on fatal findings.\n */\nexport async function runBuild(options: RunBuildOptions): Promise<BuildReport> {\n const log = options.log ?? ((message: string) => console.log(message));\n const startTimeMilliseconds = Date.now();\n const projectDirectory = path.resolve(options.projectDirectory);\n const outputDirectory = path.resolve(\n options.outputDirectory ?? path.join(projectDirectory, 'dist'),\n );\n\n // Step (a): validate\n const validationReport = await runValidation({\n projectDirectory,\n skipTypeCheck: true,\n });\n if (validationReport.errors.length > 0) {\n const formattedErrors = validationReport.errors\n .map((validationError) => ` ${validationError.filePath}: ${validationError.message}`)\n .join('\\n');\n throw new BuildViolationError(\n `Validation failed with ${validationReport.errors.length} error(s):\\n${formattedErrors}`,\n [],\n );\n }\n\n // Step (e, pre-bundle): security scan on user sources only.\n const scanFindings = await scanUserSourcesForForbiddenPatterns(projectDirectory);\n const fatalViolations = scanFindings.filter((finding) => finding.severity === 'error');\n const warnings = scanFindings.filter((finding) => finding.severity === 'warning');\n if (fatalViolations.length > 0) {\n throw new BuildViolationError(\n `Security scan failed with ${fatalViolations.length} violation(s)`,\n fatalViolations,\n );\n }\n\n const donutManifest = await readProjectManifest(projectDirectory);\n log(`Donut build — project '${donutManifest.name}' (${donutManifest.rendererTarget})`);\n\n // Decide which public-import surface the generated build entry should target.\n const projectSourceKind = await detectProjectSourceKind(projectDirectory, log);\n\n // Step (b): write ephemeral entry\n const buildEntryPath = await writeBuildEntryFile(\n projectDirectory,\n donutManifest,\n projectSourceKind,\n );\n\n // Step (c): run vite build\n const stagingDirectory = path.join(outputDirectory, 'bundle-staging');\n await fileSystemExtra.ensureDir(outputDirectory);\n await fileSystemExtra.remove(stagingDirectory);\n await fileSystemExtra.ensureDir(stagingDirectory);\n\n const donutRoot = path.join(projectDirectory, '.donut');\n const viteConfig = await buildViteBuildConfig({\n projectDirectory,\n donutRoot,\n buildEntryPath,\n stagingDirectory,\n });\n const runVite = options.runViteBuild ?? runViteBuildProgrammatically;\n await runVite(viteConfig);\n\n const builtGameJsPath = path.join(stagingDirectory, 'game.js');\n if (!(await fileSystemExtra.pathExists(builtGameJsPath))) {\n throw new Error(\n `Expected Vite build to emit game.js at ${builtGameJsPath} — file not found.`,\n );\n }\n\n // Step (d): copy assets\n const projectAssetsDirectory = path.join(projectDirectory, 'assets');\n const stagingAssetsDirectory = path.join(stagingDirectory, 'assets');\n const imageAssetOptimizer = options.optimizeImageAsset ?? optimizeImageAssetIfPossible;\n const assetEntries = await copyAssetsAndCollectEntries(\n projectAssetsDirectory,\n stagingAssetsDirectory,\n imageAssetOptimizer,\n log,\n );\n\n // Step (f): build manifest.json\n const runtimeVersions = await collectRuntimeVersions(projectDirectory);\n\n // Step (g): zip into .donut — assemble archive, then patch final size.\n const gameJsContents = await fileSystemExtra.readFile(builtGameJsPath);\n const bundleFileName = `${donutManifest.name}-${(donutManifest as unknown as { version?: string }).version ?? '0.0.0'}.donut`;\n const bundlePath = path.join(outputDirectory, bundleFileName);\n\n const projectManifestVersion = await readProjectManifestVersion(projectDirectory);\n\n const partialManifest: Omit<BundleManifest, 'bundleSizeBytes'> = {\n name: donutManifest.name,\n version: projectManifestVersion,\n rendererTarget: donutManifest.rendererTarget,\n entryPoint: 'game.js',\n donutEngineVersion: donutManifest.donutEngineVersion ?? '0.0.0',\n runtimeVersions,\n assets: assetEntries,\n buildTimestamp: new Date().toISOString(),\n };\n\n const zipArchive = new JSZip();\n zipArchive.file('game.js', gameJsContents);\n for (const assetEntry of assetEntries) {\n const absoluteAssetPath = path.join(stagingDirectory, assetEntry.path);\n const assetBuffer = await fileSystemExtra.readFile(absoluteAssetPath);\n zipArchive.file(assetEntry.path, assetBuffer);\n }\n\n // Generating the zip after setting bundleSizeBytes can change manifest.json\n // text length (digit count), which changes zip size. To make the process\n // converge reliably, we pad manifest.json with trailing whitespace inside\n // a dedicated field so the rendered manifest text has a fixed length\n // regardless of the digit count of bundleSizeBytes.\n let candidateBundleSize = 0;\n let paddingLength = 8;\n let finalBuffer: Buffer = Buffer.alloc(0);\n let finalManifest: BundleManifest = { ...partialManifest, bundleSizeBytes: 0 };\n for (let iteration = 0; iteration < 16; iteration += 1) {\n finalManifest = { ...partialManifest, bundleSizeBytes: candidateBundleSize };\n const renderedManifest = renderManifestJsonWithPadding(finalManifest, paddingLength);\n // Store manifest.json uncompressed so its on-disk byte length is fully\n // determined by the rendered JSON text (no DEFLATE variability).\n zipArchive.file('manifest.json', renderedManifest, { compression: 'STORE' });\n finalBuffer = await zipArchive.generateAsync({\n type: 'nodebuffer',\n compression: 'DEFLATE',\n });\n if (finalBuffer.byteLength === candidateBundleSize) {\n break;\n }\n // If the number of digits in the new size differs from the previous\n // candidate, widen the padding so the manifest text length doesn't shrink.\n const previousDigits = String(candidateBundleSize).length;\n const newDigits = String(finalBuffer.byteLength).length;\n if (newDigits !== previousDigits) {\n paddingLength += Math.abs(newDigits - previousDigits) + 2;\n }\n candidateBundleSize = finalBuffer.byteLength;\n }\n await fileSystemExtra.writeFile(bundlePath, finalBuffer);\n const bundleStat = await fileSystemExtra.stat(bundlePath);\n if (bundleStat.size !== finalManifest.bundleSizeBytes) {\n throw new Error(\n `Internal build error: bundleSizeBytes (${finalManifest.bundleSizeBytes}) ` +\n `does not match on-disk archive size (${bundleStat.size}).`,\n );\n }\n\n const buildTimeMilliseconds = Date.now() - startTimeMilliseconds;\n return {\n bundlePath,\n bundleSizeBytes: bundleStat.size,\n buildTimeMilliseconds,\n warnings,\n violations: [],\n manifest: finalManifest,\n };\n}\n\n/**\n * Render manifest.json with a `_sizePadding` string field whose length we\n * control. This stabilizes the rendered JSON length across rewrites when the\n * digit count of `bundleSizeBytes` changes between iterations.\n */\nfunction renderManifestJsonWithPadding(\n manifest: BundleManifest,\n paddingLength: number,\n): string {\n const manifestWithPadding: BundleManifest & { _sizePadding: string } = {\n ...manifest,\n _sizePadding: ' '.repeat(Math.max(0, paddingLength)),\n };\n return JSON.stringify(manifestWithPadding, null, 2);\n}\n\n/**\n * Default image optimizer. Tries to dynamically import `sharp` and run a\n * format-appropriate compression pass. Returns `null` when sharp is not\n * installed or the file could not be processed — signalling the caller to\n * fall back to a pass-through copy.\n */\nexport async function optimizeImageAssetIfPossible(\n sourcePath: string,\n extension: string,\n): Promise<Buffer | null> {\n let sharpModule: { default: (input: string) => SharpPipeline } | undefined;\n try {\n sharpModule = (await import(/* @vite-ignore */ 'sharp' as string)) as unknown as {\n default: (input: string) => SharpPipeline;\n };\n } catch {\n return null;\n }\n try {\n const pipeline = sharpModule.default(sourcePath).rotate();\n let configured: SharpPipeline;\n if (extension === 'png') {\n configured = pipeline.png({ compressionLevel: 9 });\n } else if (extension === 'jpg' || extension === 'jpeg') {\n configured = pipeline.jpeg({ quality: 82, mozjpeg: true });\n } else if (extension === 'webp') {\n configured = pipeline.webp({ quality: 82 });\n } else {\n return null;\n }\n const output = await configured.toBuffer();\n return output;\n } catch {\n return null;\n }\n}\n\n/**\n * Minimal structural type describing the subset of sharp's fluent API we use.\n * Keeps sharp an optional dependency — no type import required.\n */\ninterface SharpPipeline {\n rotate(): SharpPipeline;\n png(options: { compressionLevel: number }): SharpPipeline;\n jpeg(options: { quality: number; mozjpeg: boolean }): SharpPipeline;\n webp(options: { quality: number }): SharpPipeline;\n toBuffer(): Promise<Buffer>;\n}\n\n/**\n * Read the `version` field from `donut.json` — defaults to `0.0.0` when absent.\n */\nasync function readProjectManifestVersion(projectDirectory: string): Promise<string> {\n const manifestPath = path.join(projectDirectory, 'donut.json');\n if (!(await fileSystemExtra.pathExists(manifestPath))) {\n return '0.0.0';\n }\n const raw = (await fileSystemExtra.readJson(manifestPath)) as { version?: unknown };\n return typeof raw.version === 'string' ? raw.version : '0.0.0';\n}\n","import path from 'node:path';\nimport { spawn } from 'node:child_process';\nimport { createRequire } from 'node:module';\nimport { fileURLToPath } from 'node:url';\nimport fileSystemExtra from 'fs-extra';\n\n/**\n * A single validation finding emitted by {@link runValidation}.\n */\nexport interface ValidationError {\n /** Absolute or project-relative path of the offending file. */\n readonly filePath: string;\n /** 1-based line number when known. */\n readonly line?: number;\n /** 1-based column number when known. */\n readonly column?: number;\n /** Human-readable explanation of the problem. */\n readonly message: string;\n /** Stable machine-readable identifier (e.g. \"MANIFEST_INVALID\"). */\n readonly code: string;\n}\n\n/**\n * Aggregate report returned by {@link runValidation}.\n */\nexport interface ValidationReport {\n readonly errors: ValidationError[];\n readonly warnings: ValidationError[];\n}\n\n/**\n * Options accepted by {@link runValidation}.\n */\nexport interface RunValidationOptions {\n /** Absolute path to the project root to validate. */\n readonly projectDirectory: string;\n /** When true, skip the (slow) `tsc --noEmit` pass. Useful for tests. */\n readonly skipTypeCheck?: boolean;\n}\n\n/**\n * Parsed representation of the subset of `donut.json` fields that the\n * validator needs to route subsequent checks. Returned by\n * {@link validateManifest} when the manifest itself is structurally valid.\n */\nexport interface ParsedManifest {\n readonly name: string;\n readonly version: string;\n readonly rendererTarget: string;\n readonly donutEngineVersion: string;\n}\n\nconst REQUIRED_MANIFEST_FIELDS = ['name', 'version', 'rendererTarget', 'donutEngineVersion'] as const;\nconst ALLOWED_RENDERER_TARGETS = new Set(['pixi-2d', 'three-3d']);\n\n/**\n * Forbidden import prefixes inside `src/components/**` and `src/systems/**`.\n * Game logic must stay renderer-agnostic. The legacy `@donut/pixi` and\n * `@donut/three` entries remain during the transition to the published\n * `@donut-games/engine` surface so that in-monorepo projects keep failing\n * as expected.\n */\nconst GAME_LOGIC_FORBIDDEN_RENDERER_IMPORT_PREFIXES = [\n '@donut-games/engine/pixi',\n '@donut-games/engine/three',\n '@donut/pixi',\n '@donut/three',\n] as const;\n\n/**\n * Mapping from a project's `rendererTarget` to the renderer subpath that is\n * disallowed anywhere under `src/**` for that project. The other subpath is\n * the \"correct\" renderer for the target and remains allowed in scene files.\n */\nconst WRONG_RENDERER_FORBIDDEN_IMPORT_BY_TARGET: Record<string, string> = {\n 'pixi-2d': '@donut-games/engine/three',\n 'three-3d': '@donut-games/engine/pixi',\n};\n\n/**\n * Mapping from a project's `rendererTarget` to the renderer subpath that is\n * permitted for renderer-specific code in that project. Used only for\n * error-message construction.\n */\nconst WRONG_RENDERER_ALLOWED_IMPORT_BY_TARGET: Record<string, string> = {\n 'pixi-2d': '@donut-games/engine/pixi',\n 'three-3d': '@donut-games/engine/three',\n};\n\n/**\n * Run all validation checks against a Donut project directory and return a\n * structured {@link ValidationReport}. Never throws for validation problems;\n * only throws for unexpected I/O failures.\n */\nexport async function runValidation(\n options: RunValidationOptions,\n): Promise<ValidationReport> {\n const { projectDirectory } = options;\n const errors: ValidationError[] = [];\n const warnings: ValidationError[] = [];\n\n const parsedManifest = await validateManifest(projectDirectory, errors);\n\n if (options.skipTypeCheck !== true) {\n await validateTypes(projectDirectory, errors);\n }\n\n const rendererTarget = parsedManifest?.rendererTarget;\n await validateImportGraph(projectDirectory, rendererTarget, errors);\n await validateAssetReferences(projectDirectory, errors);\n\n return { errors, warnings };\n}\n\nasync function validateManifest(\n projectDirectory: string,\n errors: ValidationError[],\n): Promise<ParsedManifest | null> {\n const manifestPath = path.join(projectDirectory, 'donut.json');\n if (!(await fileSystemExtra.pathExists(manifestPath))) {\n errors.push({\n filePath: manifestPath,\n message: 'donut.json is missing at the project root',\n code: 'MANIFEST_INVALID',\n });\n return null;\n }\n let raw: string;\n try {\n raw = await fileSystemExtra.readFile(manifestPath, 'utf8');\n } catch (readError) {\n errors.push({\n filePath: manifestPath,\n message: `Unable to read donut.json: ${(readError as Error).message}`,\n code: 'MANIFEST_INVALID',\n });\n return null;\n }\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch (parseError) {\n errors.push({\n filePath: manifestPath,\n message: `donut.json is not valid JSON: ${(parseError as Error).message}`,\n code: 'MANIFEST_INVALID',\n });\n return null;\n }\n let hasRequiredFieldError = false;\n for (const fieldName of REQUIRED_MANIFEST_FIELDS) {\n const value = parsed[fieldName];\n if (typeof value !== 'string' || value.trim().length === 0) {\n errors.push({\n filePath: manifestPath,\n message: `donut.json missing required string field \"${fieldName}\"`,\n code: 'MANIFEST_INVALID',\n });\n hasRequiredFieldError = true;\n }\n }\n const rendererTargetValue = parsed.rendererTarget;\n if (\n typeof rendererTargetValue === 'string' &&\n !ALLOWED_RENDERER_TARGETS.has(rendererTargetValue)\n ) {\n errors.push({\n filePath: manifestPath,\n message: `donut.json \"rendererTarget\" must be one of pixi-2d, three-3d (got \"${rendererTargetValue}\")`,\n code: 'MANIFEST_INVALID',\n });\n }\n if (hasRequiredFieldError) {\n return null;\n }\n return {\n name: parsed.name as string,\n version: parsed.version as string,\n rendererTarget: parsed.rendererTarget as string,\n donutEngineVersion: parsed.donutEngineVersion as string,\n };\n}\n\nasync function validateTypes(\n projectDirectory: string,\n errors: ValidationError[],\n): Promise<void> {\n const tsconfigPath = path.join(projectDirectory, 'tsconfig.json');\n if (!(await fileSystemExtra.pathExists(tsconfigPath))) {\n return;\n }\n const typeScriptCompilerPath = resolveTypeScriptCompiler(projectDirectory);\n if (typeScriptCompilerPath === undefined) {\n errors.push({\n filePath: tsconfigPath,\n message: 'Unable to locate a TypeScript compiler (tsc) to run the type-check',\n code: 'TYPE_ERROR',\n });\n return;\n }\n\n const { stdout, stderr } = await runProcess(\n process.execPath,\n [typeScriptCompilerPath, '--noEmit', '-p', tsconfigPath],\n projectDirectory,\n );\n const combined = `${stdout}\\n${stderr}`;\n const diagnosticLineRegex =\n /^([^\\s(][^(]*?)\\((\\d+),(\\d+)\\):\\s+error\\s+(TS\\d+):\\s+(.+)$/gm;\n let match: RegExpExecArray | null;\n while ((match = diagnosticLineRegex.exec(combined)) !== null) {\n const [, rawFilePath, lineText, columnText, typeScriptCode, messageText] = match;\n const absoluteFilePath = path.isAbsolute(rawFilePath)\n ? rawFilePath\n : path.join(projectDirectory, rawFilePath);\n errors.push({\n filePath: absoluteFilePath,\n line: Number.parseInt(lineText, 10),\n column: Number.parseInt(columnText, 10),\n message: `${typeScriptCode}: ${messageText.trim()}`,\n code: 'TYPE_ERROR',\n });\n }\n}\n\nfunction resolveTypeScriptCompiler(projectDirectory: string): string | undefined {\n const projectLocal = path.join(\n projectDirectory,\n 'node_modules',\n 'typescript',\n 'bin',\n 'tsc',\n );\n if (fileSystemExtra.existsSync(projectLocal)) {\n return projectLocal;\n }\n try {\n const requireFromHere = createRequire(import.meta.url);\n const typeScriptPackageJsonPath = requireFromHere.resolve('typescript/package.json');\n const typeScriptPackageDirectory = path.dirname(typeScriptPackageJsonPath);\n const fallback = path.join(typeScriptPackageDirectory, 'bin', 'tsc');\n if (fileSystemExtra.existsSync(fallback)) {\n return fallback;\n }\n } catch {\n // ignore\n }\n return undefined;\n}\n\nfunction runProcess(\n command: string,\n argumentList: string[],\n workingDirectory: string,\n): Promise<{ stdout: string; stderr: string; exitCode: number | null }> {\n return new Promise((resolve) => {\n const child = spawn(command, argumentList, {\n cwd: workingDirectory,\n env: process.env,\n });\n let stdout = '';\n let stderr = '';\n child.stdout.on('data', (chunk: Buffer) => {\n stdout += chunk.toString('utf8');\n });\n child.stderr.on('data', (chunk: Buffer) => {\n stderr += chunk.toString('utf8');\n });\n child.on('close', (exitCode) => {\n resolve({ stdout, stderr, exitCode });\n });\n child.on('error', () => {\n resolve({ stdout, stderr, exitCode: null });\n });\n });\n}\n\nasync function validateImportGraph(\n projectDirectory: string,\n rendererTarget: string | undefined,\n errors: ValidationError[],\n): Promise<void> {\n await validateGameLogicImports(projectDirectory, errors);\n await validateWrongRendererImports(projectDirectory, rendererTarget, errors);\n}\n\n/**\n * Game-logic rule: scan `src/components/**` and `src/systems/**` and flag any\n * renderer-specific import regardless of the project's `rendererTarget`.\n * Game logic must remain renderer-agnostic so components and systems are\n * portable across 2D and 3D projects.\n */\nasync function validateGameLogicImports(\n projectDirectory: string,\n errors: ValidationError[],\n): Promise<void> {\n const gameLogicDirectories = [\n path.join(projectDirectory, 'src', 'components'),\n path.join(projectDirectory, 'src', 'systems'),\n ];\n const importLineRegex = /^\\s*import\\s+[^;]*?from\\s+['\"]([^'\"]+)['\"]/;\n for (const directoryPath of gameLogicDirectories) {\n if (!(await fileSystemExtra.pathExists(directoryPath))) {\n continue;\n }\n const typeScriptFiles = await collectTypeScriptFiles(directoryPath);\n for (const filePath of typeScriptFiles) {\n const contents = await fileSystemExtra.readFile(filePath, 'utf8');\n const lines = contents.split(/\\r?\\n/);\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {\n const lineText = lines[lineIndex];\n const matched = importLineRegex.exec(lineText);\n if (matched === null) {\n continue;\n }\n const importedSpecifier = matched[1];\n if (\n GAME_LOGIC_FORBIDDEN_RENDERER_IMPORT_PREFIXES.some(\n (disallowedPrefix) =>\n importedSpecifier === disallowedPrefix ||\n importedSpecifier.startsWith(`${disallowedPrefix}/`),\n )\n ) {\n errors.push({\n filePath,\n line: lineIndex + 1,\n message: `Renderer import \"${importedSpecifier}\" is not allowed in game logic (components/systems must only import @donut-games/engine/core, @donut-games/engine/math, or relative paths)`,\n code: 'RENDERER_IMPORT_IN_GAME_LOGIC',\n });\n }\n }\n }\n }\n}\n\n/**\n * Wrong-renderer rule: scan the entire `src/**` tree and flag any import of\n * the renderer subpath that does not match the project's `rendererTarget`.\n * A `pixi-2d` project may not import `@donut-games/engine/three`, and a\n * `three-3d` project may not import `@donut-games/engine/pixi`.\n */\nasync function validateWrongRendererImports(\n projectDirectory: string,\n rendererTarget: string | undefined,\n errors: ValidationError[],\n): Promise<void> {\n if (rendererTarget === undefined) {\n return;\n }\n const wrongRendererImportSpecifierPrefix =\n WRONG_RENDERER_FORBIDDEN_IMPORT_BY_TARGET[rendererTarget];\n const allowedRendererImportSpecifierPrefix =\n WRONG_RENDERER_ALLOWED_IMPORT_BY_TARGET[rendererTarget];\n if (\n wrongRendererImportSpecifierPrefix === undefined ||\n allowedRendererImportSpecifierPrefix === undefined\n ) {\n return;\n }\n const sourceRoot = path.join(projectDirectory, 'src');\n if (!(await fileSystemExtra.pathExists(sourceRoot))) {\n return;\n }\n const importLineRegex = /^\\s*import\\s+[^;]*?from\\s+['\"]([^'\"]+)['\"]/;\n const typeScriptFiles = await collectTypeScriptFiles(sourceRoot);\n for (const filePath of typeScriptFiles) {\n const contents = await fileSystemExtra.readFile(filePath, 'utf8');\n const lines = contents.split(/\\r?\\n/);\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {\n const lineText = lines[lineIndex];\n const matched = importLineRegex.exec(lineText);\n if (matched === null) {\n continue;\n }\n const importedSpecifier = matched[1];\n if (\n importedSpecifier === wrongRendererImportSpecifierPrefix ||\n importedSpecifier.startsWith(`${wrongRendererImportSpecifierPrefix}/`)\n ) {\n errors.push({\n filePath,\n line: lineIndex + 1,\n message: `Renderer import \"${importedSpecifier}\" is not allowed in a \"${rendererTarget}\" project. This project's donut.json specifies rendererTarget: \"${rendererTarget}\", which only permits ${allowedRendererImportSpecifierPrefix} for renderer-specific code.`,\n code: 'WRONG_RENDERER_IMPORT',\n });\n }\n }\n }\n}\n\nasync function collectTypeScriptFiles(directoryPath: string): Promise<string[]> {\n const collected: string[] = [];\n const entries = await fileSystemExtra.readdir(directoryPath, { withFileTypes: true });\n for (const entry of entries) {\n const entryPath = path.join(directoryPath, entry.name);\n if (entry.isDirectory()) {\n collected.push(...(await collectTypeScriptFiles(entryPath)));\n } else if (entry.isFile() && entry.name.endsWith('.ts')) {\n collected.push(entryPath);\n }\n }\n return collected;\n}\n\nasync function validateAssetReferences(\n projectDirectory: string,\n errors: ValidationError[],\n): Promise<void> {\n const sourceRoot = path.join(projectDirectory, 'src');\n const assetsRoot = path.join(projectDirectory, 'assets');\n if (!(await fileSystemExtra.pathExists(sourceRoot))) {\n return;\n }\n const typeScriptFiles = await collectTypeScriptFiles(sourceRoot);\n const texturePathRegex = /texturePath\\s*(?::[^='\"]*)?=?\\s*['\"]([^'\"]*)['\"]/g;\n const assetsLiteralRegex = /['\"]assets\\/([^'\"]+)['\"]/g;\n\n for (const filePath of typeScriptFiles) {\n const contents = await fileSystemExtra.readFile(filePath, 'utf8');\n const lines = contents.split(/\\r?\\n/);\n const referencedAssets: { relativePath: string; line: number }[] = [];\n\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {\n const lineText = lines[lineIndex];\n texturePathRegex.lastIndex = 0;\n let textureMatch: RegExpExecArray | null;\n while ((textureMatch = texturePathRegex.exec(lineText)) !== null) {\n const referenced = textureMatch[1];\n if (referenced.length === 0) {\n continue;\n }\n referencedAssets.push({ relativePath: referenced, line: lineIndex + 1 });\n }\n assetsLiteralRegex.lastIndex = 0;\n let assetsMatch: RegExpExecArray | null;\n while ((assetsMatch = assetsLiteralRegex.exec(lineText)) !== null) {\n referencedAssets.push({\n relativePath: assetsMatch[1],\n line: lineIndex + 1,\n });\n }\n }\n\n for (const { relativePath, line } of referencedAssets) {\n const normalized = relativePath.startsWith('assets/')\n ? relativePath.slice('assets/'.length)\n : relativePath;\n const absoluteAssetPath = path.join(assetsRoot, normalized);\n if (!(await fileSystemExtra.pathExists(absoluteAssetPath))) {\n errors.push({\n filePath,\n line,\n message: `Referenced asset \"${relativePath}\" not found under assets/`,\n code: 'MISSING_ASSET',\n });\n }\n }\n }\n}\n\n/**\n * Resolve the directory where this module lives; useful for callers that\n * want to look up bundled assets relative to the compiled output.\n */\nexport function getValidateCommandDirectory(): string {\n return path.dirname(fileURLToPath(import.meta.url));\n}\n","import path from 'node:path';\nimport fileSystemExtra from 'fs-extra';\nimport chalk from 'chalk';\nimport JsZip from 'jszip';\nimport * as acorn from 'acorn';\nimport * as acornWalk from 'acorn-walk';\nimport type { BundleManifest } from '../bundle/bundle-manifest.js';\n\n/**\n * Parameters accepted by {@link BundleUploader.upload}.\n */\nexport interface BundleUploadParameters {\n readonly bundleBuffer: Buffer;\n readonly manifest: BundleManifest;\n readonly authToken: string;\n}\n\n/**\n * Abstract transport that ships a validated `.donut` bundle somewhere.\n * Implementations must not mutate the buffer or manifest.\n */\nexport interface BundleUploader {\n upload(parameters: BundleUploadParameters): Promise<{ gameUrl: string }>;\n}\n\n/**\n * A single security violation discovered by the publish-time bundle scan.\n */\nexport interface PublishSecurityViolation {\n readonly pattern: string;\n readonly description: string;\n readonly line?: number;\n readonly column?: number;\n}\n\n/**\n * Options accepted by {@link runPublish}.\n */\nexport interface RunPublishOptions {\n /** Absolute path to the project root containing `dist/`. */\n readonly projectDirectory: string;\n /** Optional explicit path to a `.donut` bundle. When omitted, the latest bundle in `dist/` is picked. */\n readonly bundlePath?: string;\n /** Bearer token passed to the uploader. Defaults to a stub placeholder. */\n readonly authToken?: string;\n /** Uploader implementation. Defaults to {@link StubBundleUploader}. */\n readonly uploader?: BundleUploader;\n /** Optional logger hook; defaults to `console.log`. */\n readonly log?: (message: string) => void;\n}\n\n/**\n * Report returned by {@link runPublish} after a successful or stubbed publish.\n */\nexport interface PublishReport {\n readonly success: boolean;\n readonly gameUrl: string;\n readonly bundleSizeBytes: number;\n readonly warnings: readonly string[];\n readonly violations: readonly PublishSecurityViolation[];\n}\n\n/**\n * Default dry-run uploader that logs the intent and returns a placeholder URL.\n */\nexport class StubBundleUploader implements BundleUploader {\n public constructor(\n private readonly logger: (message: string) => void = (message) =>\n console.log(message),\n ) {}\n\n public async upload(\n parameters: BundleUploadParameters,\n ): Promise<{ gameUrl: string }> {\n const { manifest, bundleBuffer } = parameters;\n this.logger(\n chalk.yellow(\n `[StubBundleUploader] Would upload ${manifest.name}@${manifest.version} (${bundleBuffer.byteLength} bytes)`,\n ),\n );\n const gameUrl = `https://donut.games/${encodeURIComponent(\n manifest.name,\n )}/${encodeURIComponent(manifest.version)}`;\n return { gameUrl };\n }\n}\n\n/**\n * Identifier names whose invocation is forbidden inside a shipped bundle.\n */\nconst FORBIDDEN_CALL_IDENTIFIERS = new Set<string>([\n 'fetch',\n 'eval',\n 'Function',\n 'XMLHttpRequest',\n 'WebSocket',\n]);\n\n/**\n * Locate, validate, security-scan, and (stub-)upload a `.donut` bundle.\n */\nexport async function runPublish(\n options: RunPublishOptions,\n): Promise<PublishReport> {\n const log = options.log ?? ((message: string) => console.log(message));\n const uploader = options.uploader ?? new StubBundleUploader(log);\n const authToken = options.authToken ?? 'stub-auth-token';\n\n const bundlePath = await resolveBundlePath(\n options.projectDirectory,\n options.bundlePath,\n );\n log(chalk.cyan(`donut publish: using bundle ${bundlePath}`));\n\n const bundleBuffer = await fileSystemExtra.readFile(bundlePath);\n const zipArchive = await JsZip.loadAsync(bundleBuffer);\n\n const manifest = await readAndValidateManifest(zipArchive);\n await validateGameEntryExists(zipArchive);\n await validateAssetsExist(zipArchive, manifest);\n\n const gameSource = await readZipTextFile(zipArchive, 'game.js');\n const violations = scanBundleSourceForViolations(gameSource);\n if (violations.length > 0) {\n const formatted = violations\n .map(\n (violation) =>\n ` - [${violation.pattern}] ${violation.description}` +\n (violation.line !== undefined ? ` (line ${violation.line})` : ''),\n )\n .join('\\n');\n throw new Error(\n `Publish aborted: bundled game.js contains forbidden APIs:\\n${formatted}`,\n );\n }\n\n const warnings: string[] = [];\n const assetCount = manifest.assets.length;\n const bundleSizeBytes = bundleBuffer.byteLength;\n\n log(chalk.green('\\nBundle validated:'));\n log(` name: ${manifest.name}`);\n log(` version: ${manifest.version}`);\n log(` renderer: ${manifest.rendererTarget}`);\n log(` assets: ${assetCount}`);\n log(` bundle size: ${bundleSizeBytes} bytes`);\n log(chalk.yellow('\\nDRY RUN — no network request made'));\n\n const { gameUrl } = await uploader.upload({\n bundleBuffer,\n manifest,\n authToken,\n });\n log(chalk.cyan(`\\nPlaceholder game URL: ${gameUrl}`));\n\n return {\n success: true,\n gameUrl,\n bundleSizeBytes,\n warnings,\n violations,\n };\n}\n\nasync function resolveBundlePath(\n projectDirectory: string,\n explicitBundlePath: string | undefined,\n): Promise<string> {\n if (explicitBundlePath !== undefined) {\n const absoluteBundlePath = path.isAbsolute(explicitBundlePath)\n ? explicitBundlePath\n : path.join(projectDirectory, explicitBundlePath);\n if (!(await fileSystemExtra.pathExists(absoluteBundlePath))) {\n throw new Error(`Bundle not found at ${absoluteBundlePath}`);\n }\n return absoluteBundlePath;\n }\n const distDirectory = path.join(projectDirectory, 'dist');\n if (!(await fileSystemExtra.pathExists(distDirectory))) {\n throw new Error(\n `No bundle specified and no dist/ directory found at ${distDirectory}. Run \"donut build\" first.`,\n );\n }\n const entries = await fileSystemExtra.readdir(distDirectory);\n const donutBundles = entries.filter((entry) => entry.endsWith('.donut'));\n if (donutBundles.length === 0) {\n throw new Error(\n `No .donut bundles found in ${distDirectory}. Run \"donut build\" first.`,\n );\n }\n const statsByPath = await Promise.all(\n donutBundles.map(async (entry) => {\n const fullPath = path.join(distDirectory, entry);\n const stats = await fileSystemExtra.stat(fullPath);\n return { fullPath, modifiedTimeMilliseconds: stats.mtimeMs };\n }),\n );\n statsByPath.sort(\n (left, right) =>\n right.modifiedTimeMilliseconds - left.modifiedTimeMilliseconds,\n );\n return statsByPath[0].fullPath;\n}\n\nasync function readAndValidateManifest(\n zipArchive: JsZip,\n): Promise<BundleManifest> {\n const manifestFile = zipArchive.file('manifest.json');\n if (manifestFile === null) {\n throw new Error('Bundle is missing required /manifest.json');\n }\n const manifestText = await manifestFile.async('string');\n let parsed: unknown;\n try {\n parsed = JSON.parse(manifestText);\n } catch (parseError) {\n throw new Error(\n `Bundle manifest.json is not valid JSON: ${(parseError as Error).message}`,\n );\n }\n if (typeof parsed !== 'object' || parsed === null) {\n throw new Error('Bundle manifest.json is not an object');\n }\n const candidate = parsed as Record<string, unknown>;\n const requiredStringFields = [\n 'name',\n 'version',\n 'rendererTarget',\n 'entryPoint',\n 'donutEngineVersion',\n 'buildTimestamp',\n ] as const;\n for (const fieldName of requiredStringFields) {\n if (\n typeof candidate[fieldName] !== 'string' ||\n (candidate[fieldName] as string).length === 0\n ) {\n throw new Error(\n `Bundle manifest.json is missing required string field \"${fieldName}\"`,\n );\n }\n }\n const rendererTarget = candidate.rendererTarget;\n if (rendererTarget !== 'pixi-2d' && rendererTarget !== 'three-3d') {\n throw new Error(\n `Bundle manifest.json has invalid rendererTarget \"${String(rendererTarget)}\"`,\n );\n }\n if (candidate.entryPoint !== 'game.js') {\n throw new Error(\n `Bundle manifest.json entryPoint must be \"game.js\" (got \"${String(candidate.entryPoint)}\")`,\n );\n }\n if (typeof candidate.bundleSizeBytes !== 'number') {\n throw new Error(\n 'Bundle manifest.json is missing required number field \"bundleSizeBytes\"',\n );\n }\n if (\n typeof candidate.runtimeVersions !== 'object' ||\n candidate.runtimeVersions === null\n ) {\n throw new Error(\n 'Bundle manifest.json is missing required object field \"runtimeVersions\"',\n );\n }\n if (!Array.isArray(candidate.assets)) {\n throw new Error(\n 'Bundle manifest.json is missing required array field \"assets\"',\n );\n }\n return parsed as BundleManifest;\n}\n\nasync function validateGameEntryExists(zipArchive: JsZip): Promise<void> {\n const entry = zipArchive.file('game.js');\n if (entry === null) {\n throw new Error('Bundle is missing required /game.js');\n }\n}\n\nasync function validateAssetsExist(\n zipArchive: JsZip,\n manifest: BundleManifest,\n): Promise<void> {\n for (const asset of manifest.assets) {\n const entry = zipArchive.file(asset.path);\n if (entry === null) {\n throw new Error(\n `Bundle manifest declares asset \"${asset.path}\" but it is missing from the archive`,\n );\n }\n }\n}\n\nasync function readZipTextFile(\n zipArchive: JsZip,\n pathInZip: string,\n): Promise<string> {\n const entry = zipArchive.file(pathInZip);\n if (entry === null) {\n throw new Error(`Bundle is missing required file ${pathInZip}`);\n }\n return entry.async('string');\n}\n\n/**\n * AST-based security scan run on the already-built bundle. Flags forbidden\n * call expressions, dynamic code construction, and unsafe DOM sinks.\n */\nexport function scanBundleSourceForViolations(\n sourceText: string,\n): PublishSecurityViolation[] {\n const violations: PublishSecurityViolation[] = [];\n let ast: acorn.Node;\n try {\n ast = acorn.parse(sourceText, {\n ecmaVersion: 'latest',\n sourceType: 'module',\n locations: true,\n allowHashBang: true,\n });\n } catch (parseError) {\n // If we cannot parse it, we cannot trust it.\n violations.push({\n pattern: 'parse-error',\n description: `Bundle game.js failed to parse: ${(parseError as Error).message}`,\n });\n return violations;\n }\n\n // acorn-walk with TypeScript is permissive about node shapes, so we cast internally.\n acornWalk.simple(ast, {\n CallExpression(rawNode) {\n const node = rawNode as acorn.CallExpression & { loc?: acorn.SourceLocation };\n const callee = node.callee;\n if (callee.type === 'Identifier') {\n const identifierNode = callee as acorn.Identifier;\n if (FORBIDDEN_CALL_IDENTIFIERS.has(identifierNode.name)) {\n violations.push({\n pattern: identifierNode.name,\n description: `Forbidden call to ${identifierNode.name}(...)`,\n line: node.loc?.start.line,\n column: node.loc?.start.column,\n });\n }\n }\n },\n NewExpression(rawNode) {\n const node = rawNode as acorn.NewExpression & { loc?: acorn.SourceLocation };\n const callee = node.callee;\n if (callee.type === 'Identifier') {\n const identifierNode = callee as acorn.Identifier;\n if (FORBIDDEN_CALL_IDENTIFIERS.has(identifierNode.name)) {\n violations.push({\n pattern: identifierNode.name,\n description: `Forbidden construction of ${identifierNode.name}`,\n line: node.loc?.start.line,\n column: node.loc?.start.column,\n });\n }\n }\n },\n AssignmentExpression(rawNode) {\n const node = rawNode as acorn.AssignmentExpression & {\n loc?: acorn.SourceLocation;\n };\n if (node.left.type === 'MemberExpression') {\n const memberNode = node.left as acorn.MemberExpression;\n if (\n memberNode.property.type === 'Identifier' &&\n (memberNode.property as acorn.Identifier).name === 'innerHTML'\n ) {\n violations.push({\n pattern: 'innerHTML',\n description: 'Forbidden assignment to .innerHTML',\n line: node.loc?.start.line,\n column: node.loc?.start.column,\n });\n }\n }\n },\n ImportExpression(rawNode) {\n const node = rawNode as acorn.ImportExpression & {\n loc?: acorn.SourceLocation;\n };\n violations.push({\n pattern: 'dynamic-import',\n description: 'Forbidden dynamic import() expression',\n line: node.loc?.start.line,\n column: node.loc?.start.column,\n });\n },\n });\n\n return violations;\n}\n","import path from 'node:path';\nimport fileSystemExtra from 'fs-extra';\nimport chalk from 'chalk';\nimport JsZip from 'jszip';\n\n/**\n * Severity of a single AI-authored review finding.\n */\nexport type ReviewFindingSeverity = 'pass' | 'warning' | 'violation';\n\n/**\n * A single structured review finding produced by a {@link ClaudeReviewClient}.\n */\nexport interface ReviewFinding {\n readonly severity: ReviewFindingSeverity;\n readonly file?: string;\n readonly description: string;\n}\n\n/**\n * Parameters passed to a {@link ClaudeReviewClient.review} call.\n */\nexport interface ClaudeReviewRequest {\n readonly sourceText: string;\n readonly prompt: string;\n}\n\n/**\n * Response shape returned by a {@link ClaudeReviewClient}.\n */\nexport interface ClaudeReviewResponse {\n readonly findings: ReviewFinding[];\n readonly rawResponse: string;\n}\n\n/**\n * Pluggable abstraction over the Anthropic client so tests can inject a stub.\n */\nexport interface ClaudeReviewClient {\n review(request: ClaudeReviewRequest): Promise<ClaudeReviewResponse>;\n}\n\n/**\n * Aggregate report returned by {@link runReview}.\n */\nexport interface ReviewReport {\n readonly overallPass: boolean;\n readonly findings: ReviewFinding[];\n}\n\n/**\n * Options accepted by {@link runReview}.\n */\nexport interface RunReviewOptions {\n readonly projectDirectory: string;\n readonly bundlePath?: string;\n readonly claudeClient?: ClaudeReviewClient;\n readonly log?: (message: string) => void;\n}\n\n/**\n * System/user prompt injected into the Claude review call.\n * Kept as a top-level constant so tests can assert against it.\n */\nexport const REVIEW_PROMPT_TEXT =\n 'You are reviewing code for the Donut game engine. Games must only use the Donut framework API. ' +\n 'Check each file for: direct DOM access (document, window, innerHTML), network requests (fetch, XMLHttpRequest, WebSocket), ' +\n 'code execution (eval, new Function, dynamic import()), storage (localStorage, sessionStorage, indexedDB), ' +\n 'or any attempt to escape the game runtime. Respond with a JSON array of findings: ' +\n '[{ \"severity\": \"violation\"|\"warning\"|\"pass\", \"file\": \"path\", \"description\": \"what and why\" }]. ' +\n 'Return [{\"severity\":\"pass\",\"description\":\"clean\"}] if nothing is wrong.';\n\n/**\n * Stub client used when no ANTHROPIC_API_KEY is set. Always reports \"pass\".\n */\nexport class StubClaudeReviewClient implements ClaudeReviewClient {\n public constructor(\n private readonly logger: (message: string) => void = (message) =>\n console.log(message),\n ) {}\n\n public async review(\n _request: ClaudeReviewRequest,\n ): Promise<ClaudeReviewResponse> {\n this.logger(\n chalk.yellow('AI review skipped (no ANTHROPIC_API_KEY set)'),\n );\n const findings: ReviewFinding[] = [\n { severity: 'pass', description: 'clean' },\n ];\n return { findings, rawResponse: JSON.stringify(findings) };\n }\n}\n\n/**\n * Default client that talks to the live Anthropic API if an API key is set,\n * otherwise falls back to {@link StubClaudeReviewClient}.\n */\nexport function createDefaultClaudeReviewClient(\n logger: (message: string) => void = (message) => console.log(message),\n): ClaudeReviewClient {\n const apiKey = process.env.ANTHROPIC_API_KEY;\n if (apiKey === undefined || apiKey.length === 0) {\n return new StubClaudeReviewClient(logger);\n }\n return new AnthropicClaudeReviewClient(apiKey);\n}\n\n/**\n * Live implementation of {@link ClaudeReviewClient} backed by `@anthropic-ai/sdk`.\n */\nexport class AnthropicClaudeReviewClient implements ClaudeReviewClient {\n public constructor(private readonly apiKey: string) {}\n\n public async review(\n request: ClaudeReviewRequest,\n ): Promise<ClaudeReviewResponse> {\n const { default: AnthropicClient } = await import('@anthropic-ai/sdk');\n const client = new AnthropicClient({ apiKey: this.apiKey });\n const message = await client.messages.create({\n model: 'claude-opus-4-6',\n max_tokens: 4096,\n system: request.prompt,\n messages: [\n {\n role: 'user',\n content: request.sourceText,\n },\n ],\n });\n const firstTextBlock = message.content.find(\n (block): block is { type: 'text'; text: string } =>\n (block as { type: string }).type === 'text',\n );\n const rawResponse = firstTextBlock?.text ?? '';\n const findings = parseReviewFindingsFromText(rawResponse);\n return { findings, rawResponse };\n }\n}\n\n/**\n * Parse Claude's textual response into structured findings.\n * Tolerates prose wrapped around a JSON array.\n */\nexport function parseReviewFindingsFromText(text: string): ReviewFinding[] {\n const firstArrayStart = text.indexOf('[');\n const lastArrayEnd = text.lastIndexOf(']');\n if (firstArrayStart === -1 || lastArrayEnd === -1 || lastArrayEnd <= firstArrayStart) {\n return [\n {\n severity: 'warning',\n description: `Could not parse review findings as JSON array: ${text.slice(0, 200)}`,\n },\n ];\n }\n const candidate = text.slice(firstArrayStart, lastArrayEnd + 1);\n try {\n const parsed = JSON.parse(candidate) as unknown;\n if (!Array.isArray(parsed)) {\n throw new Error('Expected JSON array');\n }\n return parsed.map((entry) => {\n const record = entry as Record<string, unknown>;\n const severity = record.severity;\n const normalizedSeverity: ReviewFindingSeverity =\n severity === 'violation' || severity === 'warning' || severity === 'pass'\n ? severity\n : 'warning';\n return {\n severity: normalizedSeverity,\n file: typeof record.file === 'string' ? record.file : undefined,\n description:\n typeof record.description === 'string'\n ? record.description\n : 'no description',\n } satisfies ReviewFinding;\n });\n } catch (parseError) {\n return [\n {\n severity: 'warning',\n description: `Could not parse review findings: ${(parseError as Error).message}`,\n },\n ];\n }\n}\n\n/**\n * Orchestrate the AI-assisted review: gather sources, call the AI client,\n * and surface structured findings.\n */\nexport async function runReview(\n options: RunReviewOptions,\n): Promise<ReviewReport> {\n const log = options.log ?? ((message: string) => console.log(message));\n const claudeClient =\n options.claudeClient ?? createDefaultClaudeReviewClient(log);\n\n const sources = await collectReviewSources(\n options.projectDirectory,\n options.bundlePath,\n );\n if (sources.length === 0) {\n log(chalk.yellow('donut review: no source files found to review'));\n return {\n overallPass: true,\n findings: [\n { severity: 'pass', description: 'No reviewable sources found' },\n ],\n };\n }\n\n const sourceText = sources\n .map(\n (file) =>\n `// === FILE: ${file.displayPath} ===\\n${file.contents}\\n// === END FILE: ${file.displayPath} ===`,\n )\n .join('\\n\\n');\n\n log(chalk.cyan(`donut review: sending ${sources.length} file(s) for AI review`));\n\n const response = await claudeClient.review({\n sourceText,\n prompt: REVIEW_PROMPT_TEXT,\n });\n const hasViolation = response.findings.some(\n (finding) => finding.severity === 'violation',\n );\n\n for (const finding of response.findings) {\n const prefix = `[${finding.severity.toUpperCase()}]${\n finding.file !== undefined ? ` ${finding.file}` : ''\n }`;\n const line = `${prefix}: ${finding.description}`;\n if (finding.severity === 'violation') {\n log(chalk.red(line));\n } else if (finding.severity === 'warning') {\n log(chalk.yellow(line));\n } else {\n log(chalk.green(line));\n }\n }\n\n return {\n overallPass: hasViolation === false,\n findings: response.findings,\n };\n}\n\ninterface ReviewSourceFile {\n readonly displayPath: string;\n readonly contents: string;\n}\n\nasync function collectReviewSources(\n projectDirectory: string,\n bundlePath: string | undefined,\n): Promise<ReviewSourceFile[]> {\n if (bundlePath !== undefined) {\n const collected = await collectReviewSourcesFromBundle(\n projectDirectory,\n bundlePath,\n );\n if (collected.length > 0) {\n return collected;\n }\n }\n return collectReviewSourcesFromDisk(projectDirectory);\n}\n\nasync function collectReviewSourcesFromBundle(\n projectDirectory: string,\n bundlePath: string,\n): Promise<ReviewSourceFile[]> {\n const absoluteBundlePath = path.isAbsolute(bundlePath)\n ? bundlePath\n : path.join(projectDirectory, bundlePath);\n if (!(await fileSystemExtra.pathExists(absoluteBundlePath))) {\n throw new Error(`Bundle not found at ${absoluteBundlePath}`);\n }\n const buffer = await fileSystemExtra.readFile(absoluteBundlePath);\n const zipArchive = await JsZip.loadAsync(buffer);\n const sources: ReviewSourceFile[] = [];\n const entries: { path: string; file: JsZip.JSZipObject }[] = [];\n zipArchive.forEach((relativePath, file) => {\n if (\n relativePath.startsWith('src/') &&\n (relativePath.endsWith('.ts') || relativePath.endsWith('.js')) &&\n file.dir === false\n ) {\n entries.push({ path: relativePath, file });\n }\n });\n for (const entry of entries) {\n const contents = await entry.file.async('string');\n sources.push({ displayPath: entry.path, contents });\n }\n return sources;\n}\n\nasync function collectReviewSourcesFromDisk(\n projectDirectory: string,\n): Promise<ReviewSourceFile[]> {\n const componentsDirectory = path.join(\n projectDirectory,\n 'src',\n 'components',\n );\n const systemsDirectory = path.join(projectDirectory, 'src', 'systems');\n const sources: ReviewSourceFile[] = [];\n for (const directoryPath of [componentsDirectory, systemsDirectory]) {\n if (!(await fileSystemExtra.pathExists(directoryPath))) {\n continue;\n }\n const typeScriptFiles = await collectTypeScriptFilesRecursively(\n directoryPath,\n );\n for (const filePath of typeScriptFiles) {\n const contents = await fileSystemExtra.readFile(filePath, 'utf8');\n sources.push({\n displayPath: path.relative(projectDirectory, filePath),\n contents,\n });\n }\n }\n return sources;\n}\n\nasync function collectTypeScriptFilesRecursively(\n directoryPath: string,\n): Promise<string[]> {\n const collected: string[] = [];\n const entries = await fileSystemExtra.readdir(directoryPath, {\n withFileTypes: true,\n });\n for (const entry of entries) {\n const entryPath = path.join(directoryPath, entry.name);\n if (entry.isDirectory()) {\n collected.push(...(await collectTypeScriptFilesRecursively(entryPath)));\n } else if (entry.isFile() && entry.name.endsWith('.ts')) {\n collected.push(entryPath);\n }\n }\n return collected;\n}\n","// The real `#!/usr/bin/env node` shebang is injected at build time by the\n// `tsup` banner configuration — do not add one here as a string, or it will\n// appear mid-file after bundling.\n\n/**\n * `donut` bin entry. Forwards to the commander program defined in the\n * internal `@donut/cli` workspace package. The program's own command\n * definitions are wired up at import time; invoking `parseAsync` with the\n * current process arguments runs whichever subcommand the user requested.\n */\nimport { program as donutCliProgram } from '@donut/cli';\n\nawait donutCliProgram.parseAsync(process.argv);\n"],"mappings":";;;AACA,OAAOA,WAAU;AACjB,SAAS,eAAe;AACxB,OAAOC,YAAW;;;ACHlB,OAAOC,WAAU;AACjB,SAAS,aAAa;AACtB,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,OAAOC,sBAAqB;AAC5B,OAAO,WAAW;;;ACLlB,OAAO,UAAU;AACjB,OAAO,qBAAqB;AA+C5B,IAAM,uBAAuB;AAY7B,eAAsB,aACpB,SAA4B;AAE5B,QAAM,EAAE,iBAAiB,sBAAsB,SAAS,QAAQ,YAAW,IAAK;AAEhF,QAAM,gBAAgB,UAAU,oBAAoB;AAEpD,QAAM,sBAAgC,CAAA;AACtC,QAAM,oBAAoB,oBAAI,IAAG;AAEjC,QAAM,cAAc,CAAC,iBAA8B;AACjD,QAAI,kBAAkB,IAAI,YAAY,GAAG;AACvC;IACF;AACA,sBAAkB,IAAI,YAAY;AAClC,wBAAoB,KAAK,YAAY;EACvC;AAEA,QAAM,YAAY,OAAO,mBAAyC;AAChE,QAAK,MAAM,gBAAgB,WAAW,cAAc,MAAO,OAAO;AAChE;IACF;AACA,UAAM,eAAe,MAAM,wBAAwB,cAAc;AACjE,eAAW,sBAAsB,cAAc;AAC7C,YAAM,0BAA0B,uBAAuB,kBAAkB;AACzE,YAAM,qBAAqB,KAAK,KAAK,gBAAgB,kBAAkB;AACvE,YAAM,0BAA0B,KAAK,KACnC,sBACA,uBAAuB;AAGzB,YAAM,gBAAgB,UAAU,KAAK,QAAQ,uBAAuB,CAAC;AAErE,UAAI,wBAAwB,SAAS,oBAAoB,GAAG;AAE1D,cAAM,IAAI,MACR,wCAAwC,uBAAuB,EAAE;MAErE;AAEA,UAAI,mBAAmB,SAAS,oBAAoB,GAAG;AACrD,cAAM,cAAc,MAAM,gBAAgB,SAAS,oBAAoB,MAAM;AAC7E,cAAM,mBAAmB,eAAe,aAAa,MAAM;AAC3D,cAAM,gBAAgB,UACpB,yBACA,kBACA,MAAM;MAEV,OAAO;AACL,cAAM,gBAAgB,SAAS,oBAAoB,uBAAuB;MAC5E;AAEA,kBAAY,uBAAuB;AACnC,UAAI,gBAAgB,QAAW;AAC7B,oBAAY,uBAAuB;MACrC;IACF;EACF;AAEA,QAAM,UAAU,KAAK,KAAK,iBAAiB,MAAM,CAAC;AAClD,QAAM,UAAU,KAAK,KAAK,iBAAiB,OAAO,CAAC;AAEnD,SAAO,EAAE,cAAc,oBAAmB;AAC5C;AAUM,SAAU,iBAAiB,UAAgB;AAC/C,MAAI,SAAS,SAAS,KAAK,SAAS,WAAW,GAAG,GAAG;AACnD,WAAO,MAAM,SAAS,MAAM,CAAC;EAC/B;AACA,SAAO;AACT;AAQM,SAAU,eACd,UACA,QAAsB;AAEtB,MAAI,WAAW;AACf,QAAM,oBAAuD;IAC3D;IACA;IACA;IACA;IACA;;AAEF,aAAW,aAAa,mBAAmB;AACzC,UAAM,cAAc,OAAO,YAAY;AACvC,UAAM,aAAa,OAAO,SAAS;AACnC,eAAW,SAAS,MAAM,WAAW,EAAE,KAAK,UAAU;EACxD;AACA,SAAO;AACT;AAOA,SAAS,uBAAuB,cAAoB;AAClD,QAAM,WAAW,aAAa,MAAM,KAAK,GAAG;AAC5C,QAAM,kBAAkB,SAAS,IAAI,CAAC,YAAY,iBAAiB,OAAO,CAAC;AAC3E,QAAM,mBAAmB,gBAAgB,SAAS;AAClD,QAAM,cAAc,gBAAgB,gBAAgB;AACpD,MAAI,YAAY,SAAS,oBAAoB,GAAG;AAC9C,oBAAgB,gBAAgB,IAAI,YAAY,MAC9C,GACA,YAAY,SAAS,qBAAqB,MAAM;EAEpD;AACA,SAAO,gBAAgB,KAAK,KAAK,GAAG;AACtC;AAOA,eAAe,wBACb,eAAqB;AAErB,QAAM,iBAA2B,CAAA;AAEjC,QAAM,gBAAgB,OAAO,sBAA4C;AACvE,UAAM,mBAAmB,MAAM,gBAAgB,QAAQ,mBAAmB;MACxE,eAAe;KAChB;AACD,eAAW,kBAAkB,kBAAkB;AAC7C,YAAM,oBAAoB,KAAK,KAAK,mBAAmB,eAAe,IAAI;AAC1E,UAAI,eAAe,YAAW,GAAI;AAChC,cAAM,cAAc,iBAAiB;AACrC;MACF;AACA,UAAI,eAAe,OAAM,GAAI;AAC3B,cAAM,eAAe,KAAK,SAAS,eAAe,iBAAiB;AACnE,uBAAe,KAAK,YAAY;MAClC;IACF;EACF;AAEA,QAAM,cAAc,aAAa;AACjC,iBAAe,KAAI;AACnB,SAAO;AACT;;;ADzJA,eAAe,wBAAqB;AAKlC,QAAM,iBAA2B,CAAA;AAIjC,MAAI;AACF,UAAM,cAAc,cAAc,YAAY,GAAG;AACjD,UAAM,qBAAqB,YAAY,QACrC,kCAAkC;AAEpC,mBAAe,KAAK,kBAAkB;AACtC,UAAM,WAAY,MAAMC,iBAAgB,SACtC,kBAAkB;AAEpB,WAAO,4BAA4B,QAAQ;EAC7C,QAAQ;EAER;AAGA,QAAM,sBAAsBC,MAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AACvE,MAAI,mBAAmB;AACvB,WAAS,YAAY,GAAG,YAAY,GAAG,aAAa,GAAG;AACrD,UAAM,YAAYA,MAAK,KACrB,kBACA,YACA,gBACA,cAAc;AAEhB,mBAAe,KAAK,SAAS;AAC7B,QAAI,MAAMD,iBAAgB,WAAW,SAAS,GAAG;AAC/C,YAAM,WAAY,MAAMA,iBAAgB,SACtC,SAAS;AAEX,aAAO,4BAA4B,QAAQ;IAC7C;AACA,UAAM,kBAAkBC,MAAK,QAAQ,gBAAgB;AACrD,QAAI,oBAAoB,kBAAkB;AACxC;IACF;AACA,uBAAmB;EACrB;AAEA,QAAM,IAAI,MACR;IAAuE,eAAe,KACpF,MAAM,CACP,EAAE;AAEP;AAOA,SAAS,4BAA4B,UAA+B;AAKlE,QAAM,mBAAmB,SAAS,oBAAoB,CAAA;AACtD,QAAM,aAAa,CAAC,UAClB,MAAM,QAAQ,gBAAgB,EAAE;AAElC,SAAO;IACL,oBAAoB,SAAS;IAC7B,aAAa,WAAW,iBAAiB,SAAS,KAAK,OAAO;IAC9D,cAAc,WAAW,iBAAiB,OAAO,KAAK,SAAS;;AAEnE;AAMA,eAAsB,kBACpB,SAAiC;AAEjC,QAAM,EAAE,aAAa,eAAc,IAAK;AACxC,QAAM,MAAM,QAAQ,QAAQ,CAAC,YAAoB,QAAQ,IAAI,OAAO;AACpE,QAAM,oBAAoBA,MAAK,QAAQ,QAAQ,eAAe;AAE9D,MAAI,MAAMD,iBAAgB,WAAW,iBAAiB,GAAG;AACvD,UAAM,kBAAkB,MAAMA,iBAAgB,QAAQ,iBAAiB;AACvE,QAAI,gBAAgB,SAAS,GAAG;AAC9B,YAAM,IAAI,MACR,qBAAqB,iBAAiB,oCAAoC;IAE9E;EACF;AAEA,QAAMA,iBAAgB,UAAU,iBAAiB;AAEjD,MAAI,MAAM,KAAK,gBAAgB,WAAW,MAAM,cAAc,QAAQ,iBAAiB,EAAE,CAAC;AAE1F,QAAM,0BAA0B,QAAQ,qBAAqB,MAAM,yBAAwB;AAC3F,QAAM,iBAAiB,MAAM,sBAAqB;AAElD,QAAM,iBAAiC;IACrC;IACA;IACA,oBAAoB,eAAe;IACnC,aAAa,eAAe;IAC5B,cAAc,eAAe;;AAG/B,QAAM,aAAa,MAAM,aAAa;IACpC,iBAAiB;IACjB,sBAAsB;IACtB,SAAS;IACT,QAAQ;IACR,aAAa,CAAC,iBAAgB;AAC5B,UAAI,MAAM,MAAM,cAAc,YAAY,EAAE,CAAC;IAC/C;GACD;AAED,MAAI,QAAQ,gBAAgB,MAAM;AAChC,UAAM,qBAAqB,mBAAmB,GAAG;EACnD;AAEA,MAAI,MAAM,KAAK;kBAAqB,CAAC;AACrC,MAAI,MAAM,MAAM,QAAQ,WAAW,EAAE,CAAC;AACtC,MAAI,QAAQ,gBAAgB,MAAM;AAChC,QAAI,MAAM,MAAM,eAAe,CAAC;EAClC;AACA,MAAI,MAAM,MAAM,eAAe,CAAC;AAEhC,SAAO,EAAE,mBAAmB,cAAc,WAAW,aAAY;AACnE;AAcA,eAAe,2BAAwB;AACrC,QAAM,eAAyB,CAAA;AAE/B,QAAM,mBAAmB,cAAc,IAAI,IAAI,eAAe,YAAY,GAAG,CAAC;AAC9E,eAAa,KAAK,gBAAgB;AAClC,MAAI,MAAMA,iBAAgB,WAAW,gBAAgB,GAAG;AACtD,WAAO;EACT;AAEA,MAAI;AACF,UAAM,cAAc,cAAc,YAAY,GAAG;AACjD,UAAM,yBAAyB,YAAY,QACzC,uCAAuC;AAEzC,UAAM,oBAAoBC,MAAK,KAC7BA,MAAK,QAAQ,sBAAsB,GACnC,UAAU;AAEZ,iBAAa,KAAK,iBAAiB;AACnC,QAAI,MAAMD,iBAAgB,WAAW,iBAAiB,GAAG;AACvD,aAAO;IACT;EACF,QAAQ;EAER;AAEA,QAAM,sBAAsBC,MAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AACvE,MAAI,mBAAmB;AACvB,WAAS,YAAY,GAAG,YAAY,GAAG,aAAa,GAAG;AACrD,UAAM,YAAYA,MAAK,KACrB,kBACA,YACA,qBACA,UAAU;AAEZ,iBAAa,KAAK,SAAS;AAC3B,QAAI,MAAMD,iBAAgB,WAAW,SAAS,GAAG;AAC/C,aAAO;IACT;AACA,UAAM,kBAAkBC,MAAK,QAAQ,gBAAgB;AACrD,QAAI,oBAAoB,kBAAkB;AACxC;IACF;AACA,uBAAmB;EACrB;AAEA,QAAM,IAAI,MACR;IAAiF,aAAa,KAC5F,MAAM,CACP,EAAE;AAEP;AAOA,eAAe,qBACb,kBACA,KAA8B;AAE9B,QAAM,2BAA8C,CAAC,QAAQ,KAAK;AAClE,aAAW,kBAAkB,0BAA0B;AACrD,UAAM,WAAW,MAAM,aAAa,gBAAgB,gBAAgB;AACpE,QAAI,aAAa,kBAAkB;AACjC;IACF;AACA,QAAI,aAAa,GAAG;AAClB;IACF;AACA;EACF;AACA,MACE,MAAM,OACJ,8DAA8D,CAC/D;AAEL;AAMA,SAAS,aACP,gBACA,kBAAwB;AAExB,SAAO,IAAI,QAAQ,CAAC,YAAW;AAC7B,UAAM,QAAQ,MAAM,gBAAgB,CAAC,SAAS,GAAG;MAC/C,KAAK;MACL,OAAO;MACP,OAAO;KACR;AACD,UAAM,GAAG,SAAS,CAAC,UAAgC;AACjD,UAAI,MAAM,SAAS,UAAU;AAC3B,gBAAQ,gBAAgB;AACxB;MACF;AACA,cAAQ,CAAC;IACX,CAAC;AACD,UAAM,GAAG,QAAQ,CAAC,SAAQ;AACxB,cAAQ,QAAQ,CAAC;IACnB,CAAC;EACH,CAAC;AACH;;;AEzTA,OAAOC,WAAU;AACjB,OAAOC,sBAAqB;AAC5B,OAAOC,YAAW;;;ACFlB,OAAOC,WAAU;AACjB,OAAOC,sBAAqB;AAM5B,IAAM,+BAAkD;EACtD;EACA;EACA;EACA;EACA;EACA;;AAQF,IAAM,iCAAoD;EACxD;EACA;EACA;EACA;EACA;EACA;;AAyCF,eAAsB,0BACpB,cAAoB;AAEpB,QAAM,oBAAoBD,MAAK,KAAK,cAAc,UAAU;AAC5D,QAAM,uBAAgD,CAAA;AAItD,QAAM,0BAA0BA,MAAK,KAAK,mBAAmB,gBAAgB,KAAK;AAClF,aAAW,eAAe,gCAAgC;AACxD,UAAM,oBAAoBA,MAAK,KAC7B,yBACA,aACA,UAAU;AAEZ,QAAI,MAAMC,iBAAgB,WAAW,iBAAiB,GAAG;AACvD,2BAAqB,KAAK;QACxB,MAAM,uBAAuB,WAAW;QACxC,aAAa;OACd;IACH;EACF;AAIA,QAAM,qBAAqBD,MAAK,KAAK,yBAAyB,UAAU;AACxE,MAAI,MAAMC,iBAAgB,WAAW,kBAAkB,GAAG;AACxD,yBAAqB,KAAK;MACxB,MAAM;MACN,aAAa;KACd;EACH;AAKA,aAAW,uBAAuB,8BAA8B;AAC9D,UAAM,0BAA0BD,MAAK,KACnC,mBACA,qBACA,KAAK;AAEP,QAAI,MAAMC,iBAAgB,WAAW,uBAAuB,GAAG;AAC7D,2BAAqB,KAAK;QACxB,MAAM,UAAU,mBAAmB;QACnC,aAAa;OACd;IACH;EACF;AAEA,QAAM,sCAAsC,cAAc,oBAAoB;AAC9E,SAAO;AACT;AAWA,eAAe,sCACb,cACA,sBAAsD;AAEtD,QAAM,0BAA0BD,MAAK,KACnC,cACA,YACA,gBACA,cAAc;AAEhB,MAAI,CAAE,MAAMC,iBAAgB,WAAW,uBAAuB,GAAI;AAChE;EACF;AACA,QAAM,iBAAkB,MAAMA,iBAAgB,SAC5C,uBAAuB;AAEzB,QAAM,eAAe,eAAe;AACpC,MAAI,iBAAiB,UAAa,iBAAiB,MAAM;AACvD;EACF;AAEA,QAAM,qBAAqB,IAAI,IAC7B,qBACG,IAAI,CAAC,UAAU,MAAM,IAAI,EACzB,OAAO,CAAC,SAAyB,OAAO,SAAS,QAAQ,CAAC;AAG/D,QAAM,uBAAiC,CAAA;AACvC,aAAW,aAAa,OAAO,KAAK,YAAY,GAAG;AACjD,QAAI,cAAc,OAAO,cAAc,kBAAkB;AACvD;IACF;AACA,QAAI,CAAC,UAAU,WAAW,IAAI,GAAG;AAC/B;IACF;AACA,UAAM,cAAc,UAAU,MAAM,CAAC;AACrC,UAAM,mBAAmB,uBAAuB,WAAW;AAC3D,QAAI,CAAC,mBAAmB,IAAI,gBAAgB,GAAG;AAC7C,2BAAqB,KAAK,gBAAgB;IAC5C;EACF;AAEA,MAAI,qBAAqB,SAAS,GAAG;AACnC,UAAM,IAAI,MACR,iFACK,qBAAqB,KAAK,IAAI,CAAC,6FACwC;EAEhF;AACF;;;ACpLA,OAAOC,WAAU;AACjB,OAAOC,sBAAqB;AAC5B,OAAOC,YAAW;AAClB,OAAO,WAAW;;;ACHlB,OAAOC,WAAU;AACjB,SAAS,SAAAC,cAAa;AACtB,SAAS,iBAAAC,sBAAqB;AAC9B,SAAS,iBAAAC,sBAAqB;AAC9B,OAAOC,sBAAqB;AAgD5B,IAAM,2BAA2B,CAAC,QAAQ,WAAW,kBAAkB,oBAAoB;AAC3F,IAAM,2BAA2B,oBAAI,IAAI,CAAC,WAAW,UAAU,CAAC;AAShE,IAAM,gDAAgD;EACpD;EACA;EACA;EACA;;AAQF,IAAM,4CAAoE;EACxE,WAAW;EACX,YAAY;;AAQd,IAAM,0CAAkE;EACtE,WAAW;EACX,YAAY;;AAQd,eAAsB,cACpB,SAA6B;AAE7B,QAAM,EAAE,iBAAgB,IAAK;AAC7B,QAAM,SAA4B,CAAA;AAClC,QAAM,WAA8B,CAAA;AAEpC,QAAM,iBAAiB,MAAM,iBAAiB,kBAAkB,MAAM;AAEtE,MAAI,QAAQ,kBAAkB,MAAM;AAClC,UAAM,cAAc,kBAAkB,MAAM;EAC9C;AAEA,QAAM,iBAAiB,gBAAgB;AACvC,QAAM,oBAAoB,kBAAkB,gBAAgB,MAAM;AAClE,QAAM,wBAAwB,kBAAkB,MAAM;AAEtD,SAAO,EAAE,QAAQ,SAAQ;AAC3B;AAEA,eAAe,iBACb,kBACA,QAAyB;AAEzB,QAAM,eAAeJ,MAAK,KAAK,kBAAkB,YAAY;AAC7D,MAAI,CAAE,MAAMI,iBAAgB,WAAW,YAAY,GAAI;AACrD,WAAO,KAAK;MACV,UAAU;MACV,SAAS;MACT,MAAM;KACP;AACD,WAAO;EACT;AACA,MAAI;AACJ,MAAI;AACF,UAAM,MAAMA,iBAAgB,SAAS,cAAc,MAAM;EAC3D,SAAS,WAAW;AAClB,WAAO,KAAK;MACV,UAAU;MACV,SAAS,8BAA+B,UAAoB,OAAO;MACnE,MAAM;KACP;AACD,WAAO;EACT;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;EACzB,SAAS,YAAY;AACnB,WAAO,KAAK;MACV,UAAU;MACV,SAAS,iCAAkC,WAAqB,OAAO;MACvE,MAAM;KACP;AACD,WAAO;EACT;AACA,MAAI,wBAAwB;AAC5B,aAAW,aAAa,0BAA0B;AAChD,UAAM,QAAQ,OAAO,SAAS;AAC9B,QAAI,OAAO,UAAU,YAAY,MAAM,KAAI,EAAG,WAAW,GAAG;AAC1D,aAAO,KAAK;QACV,UAAU;QACV,SAAS,6CAA6C,SAAS;QAC/D,MAAM;OACP;AACD,8BAAwB;IAC1B;EACF;AACA,QAAM,sBAAsB,OAAO;AACnC,MACE,OAAO,wBAAwB,YAC/B,CAAC,yBAAyB,IAAI,mBAAmB,GACjD;AACA,WAAO,KAAK;MACV,UAAU;MACV,SAAS,sEAAsE,mBAAmB;MAClG,MAAM;KACP;EACH;AACA,MAAI,uBAAuB;AACzB,WAAO;EACT;AACA,SAAO;IACL,MAAM,OAAO;IACb,SAAS,OAAO;IAChB,gBAAgB,OAAO;IACvB,oBAAoB,OAAO;;AAE/B;AAEA,eAAe,cACb,kBACA,QAAyB;AAEzB,QAAM,eAAeJ,MAAK,KAAK,kBAAkB,eAAe;AAChE,MAAI,CAAE,MAAMI,iBAAgB,WAAW,YAAY,GAAI;AACrD;EACF;AACA,QAAM,yBAAyB,0BAA0B,gBAAgB;AACzE,MAAI,2BAA2B,QAAW;AACxC,WAAO,KAAK;MACV,UAAU;MACV,SAAS;MACT,MAAM;KACP;AACD;EACF;AAEA,QAAM,EAAE,QAAQ,OAAM,IAAK,MAAM,WAC/B,QAAQ,UACR,CAAC,wBAAwB,YAAY,MAAM,YAAY,GACvD,gBAAgB;AAElB,QAAM,WAAW,GAAG,MAAM;EAAK,MAAM;AACrC,QAAM,sBACJ;AACF,MAAI;AACJ,UAAQ,QAAQ,oBAAoB,KAAK,QAAQ,OAAO,MAAM;AAC5D,UAAM,CAAC,EAAE,aAAa,UAAU,YAAY,gBAAgB,WAAW,IAAI;AAC3E,UAAM,mBAAmBJ,MAAK,WAAW,WAAW,IAChD,cACAA,MAAK,KAAK,kBAAkB,WAAW;AAC3C,WAAO,KAAK;MACV,UAAU;MACV,MAAM,OAAO,SAAS,UAAU,EAAE;MAClC,QAAQ,OAAO,SAAS,YAAY,EAAE;MACtC,SAAS,GAAG,cAAc,KAAK,YAAY,KAAI,CAAE;MACjD,MAAM;KACP;EACH;AACF;AAEA,SAAS,0BAA0B,kBAAwB;AACzD,QAAM,eAAeA,MAAK,KACxB,kBACA,gBACA,cACA,OACA,KAAK;AAEP,MAAII,iBAAgB,WAAW,YAAY,GAAG;AAC5C,WAAO;EACT;AACA,MAAI;AACF,UAAM,kBAAkBF,eAAc,YAAY,GAAG;AACrD,UAAM,4BAA4B,gBAAgB,QAAQ,yBAAyB;AACnF,UAAM,6BAA6BF,MAAK,QAAQ,yBAAyB;AACzE,UAAM,WAAWA,MAAK,KAAK,4BAA4B,OAAO,KAAK;AACnE,QAAII,iBAAgB,WAAW,QAAQ,GAAG;AACxC,aAAO;IACT;EACF,QAAQ;EAER;AACA,SAAO;AACT;AAEA,SAAS,WACP,SACA,cACA,kBAAwB;AAExB,SAAO,IAAI,QAAQ,CAAC,YAAW;AAC7B,UAAM,QAAQH,OAAM,SAAS,cAAc;MACzC,KAAK;MACL,KAAK,QAAQ;KACd;AACD,QAAI,SAAS;AACb,QAAI,SAAS;AACb,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAiB;AACxC,gBAAU,MAAM,SAAS,MAAM;IACjC,CAAC;AACD,UAAM,OAAO,GAAG,QAAQ,CAAC,UAAiB;AACxC,gBAAU,MAAM,SAAS,MAAM;IACjC,CAAC;AACD,UAAM,GAAG,SAAS,CAAC,aAAY;AAC7B,cAAQ,EAAE,QAAQ,QAAQ,SAAQ,CAAE;IACtC,CAAC;AACD,UAAM,GAAG,SAAS,MAAK;AACrB,cAAQ,EAAE,QAAQ,QAAQ,UAAU,KAAI,CAAE;IAC5C,CAAC;EACH,CAAC;AACH;AAEA,eAAe,oBACb,kBACA,gBACA,QAAyB;AAEzB,QAAM,yBAAyB,kBAAkB,MAAM;AACvD,QAAM,6BAA6B,kBAAkB,gBAAgB,MAAM;AAC7E;AAQA,eAAe,yBACb,kBACA,QAAyB;AAEzB,QAAM,uBAAuB;IAC3BD,MAAK,KAAK,kBAAkB,OAAO,YAAY;IAC/CA,MAAK,KAAK,kBAAkB,OAAO,SAAS;;AAE9C,QAAM,kBAAkB;AACxB,aAAW,iBAAiB,sBAAsB;AAChD,QAAI,CAAE,MAAMI,iBAAgB,WAAW,aAAa,GAAI;AACtD;IACF;AACA,UAAM,kBAAkB,MAAM,uBAAuB,aAAa;AAClE,eAAW,YAAY,iBAAiB;AACtC,YAAM,WAAW,MAAMA,iBAAgB,SAAS,UAAU,MAAM;AAChE,YAAM,QAAQ,SAAS,MAAM,OAAO;AACpC,eAAS,YAAY,GAAG,YAAY,MAAM,QAAQ,aAAa,GAAG;AAChE,cAAM,WAAW,MAAM,SAAS;AAChC,cAAM,UAAU,gBAAgB,KAAK,QAAQ;AAC7C,YAAI,YAAY,MAAM;AACpB;QACF;AACA,cAAM,oBAAoB,QAAQ,CAAC;AACnC,YACE,8CAA8C,KAC5C,CAAC,qBACC,sBAAsB,oBACtB,kBAAkB,WAAW,GAAG,gBAAgB,GAAG,CAAC,GAExD;AACA,iBAAO,KAAK;YACV;YACA,MAAM,YAAY;YAClB,SAAS,oBAAoB,iBAAiB;YAC9C,MAAM;WACP;QACH;MACF;IACF;EACF;AACF;AAQA,eAAe,6BACb,kBACA,gBACA,QAAyB;AAEzB,MAAI,mBAAmB,QAAW;AAChC;EACF;AACA,QAAM,qCACJ,0CAA0C,cAAc;AAC1D,QAAM,uCACJ,wCAAwC,cAAc;AACxD,MACE,uCAAuC,UACvC,yCAAyC,QACzC;AACA;EACF;AACA,QAAM,aAAaJ,MAAK,KAAK,kBAAkB,KAAK;AACpD,MAAI,CAAE,MAAMI,iBAAgB,WAAW,UAAU,GAAI;AACnD;EACF;AACA,QAAM,kBAAkB;AACxB,QAAM,kBAAkB,MAAM,uBAAuB,UAAU;AAC/D,aAAW,YAAY,iBAAiB;AACtC,UAAM,WAAW,MAAMA,iBAAgB,SAAS,UAAU,MAAM;AAChE,UAAM,QAAQ,SAAS,MAAM,OAAO;AACpC,aAAS,YAAY,GAAG,YAAY,MAAM,QAAQ,aAAa,GAAG;AAChE,YAAM,WAAW,MAAM,SAAS;AAChC,YAAM,UAAU,gBAAgB,KAAK,QAAQ;AAC7C,UAAI,YAAY,MAAM;AACpB;MACF;AACA,YAAM,oBAAoB,QAAQ,CAAC;AACnC,UACE,sBAAsB,sCACtB,kBAAkB,WAAW,GAAG,kCAAkC,GAAG,GACrE;AACA,eAAO,KAAK;UACV;UACA,MAAM,YAAY;UAClB,SAAS,oBAAoB,iBAAiB,0BAA0B,cAAc,mEAAmE,cAAc,yBAAyB,oCAAoC;UACpO,MAAM;SACP;MACH;IACF;EACF;AACF;AAEA,eAAe,uBAAuB,eAAqB;AACzD,QAAM,YAAsB,CAAA;AAC5B,QAAM,UAAU,MAAMA,iBAAgB,QAAQ,eAAe,EAAE,eAAe,KAAI,CAAE;AACpF,aAAW,SAAS,SAAS;AAC3B,UAAM,YAAYJ,MAAK,KAAK,eAAe,MAAM,IAAI;AACrD,QAAI,MAAM,YAAW,GAAI;AACvB,gBAAU,KAAK,GAAI,MAAM,uBAAuB,SAAS,CAAE;IAC7D,WAAW,MAAM,OAAM,KAAM,MAAM,KAAK,SAAS,KAAK,GAAG;AACvD,gBAAU,KAAK,SAAS;IAC1B;EACF;AACA,SAAO;AACT;AAEA,eAAe,wBACb,kBACA,QAAyB;AAEzB,QAAM,aAAaA,MAAK,KAAK,kBAAkB,KAAK;AACpD,QAAM,aAAaA,MAAK,KAAK,kBAAkB,QAAQ;AACvD,MAAI,CAAE,MAAMI,iBAAgB,WAAW,UAAU,GAAI;AACnD;EACF;AACA,QAAM,kBAAkB,MAAM,uBAAuB,UAAU;AAC/D,QAAM,mBAAmB;AACzB,QAAM,qBAAqB;AAE3B,aAAW,YAAY,iBAAiB;AACtC,UAAM,WAAW,MAAMA,iBAAgB,SAAS,UAAU,MAAM;AAChE,UAAM,QAAQ,SAAS,MAAM,OAAO;AACpC,UAAM,mBAA6D,CAAA;AAEnE,aAAS,YAAY,GAAG,YAAY,MAAM,QAAQ,aAAa,GAAG;AAChE,YAAM,WAAW,MAAM,SAAS;AAChC,uBAAiB,YAAY;AAC7B,UAAI;AACJ,cAAQ,eAAe,iBAAiB,KAAK,QAAQ,OAAO,MAAM;AAChE,cAAM,aAAa,aAAa,CAAC;AACjC,YAAI,WAAW,WAAW,GAAG;AAC3B;QACF;AACA,yBAAiB,KAAK,EAAE,cAAc,YAAY,MAAM,YAAY,EAAC,CAAE;MACzE;AACA,yBAAmB,YAAY;AAC/B,UAAI;AACJ,cAAQ,cAAc,mBAAmB,KAAK,QAAQ,OAAO,MAAM;AACjE,yBAAiB,KAAK;UACpB,cAAc,YAAY,CAAC;UAC3B,MAAM,YAAY;SACnB;MACH;IACF;AAEA,eAAW,EAAE,cAAc,KAAI,KAAM,kBAAkB;AACrD,YAAM,aAAa,aAAa,WAAW,SAAS,IAChD,aAAa,MAAM,UAAU,MAAM,IACnC;AACJ,YAAM,oBAAoBJ,MAAK,KAAK,YAAY,UAAU;AAC1D,UAAI,CAAE,MAAMI,iBAAgB,WAAW,iBAAiB,GAAI;AAC1D,eAAO,KAAK;UACV;UACA;UACA,SAAS,qBAAqB,YAAY;UAC1C,MAAM;SACP;MACH;IACF;EACF;AACF;;;ADpXM,IAAO,sBAAP,cAAmC,MAAK;EAC5B;EAChB,YAAmB,SAAiB,YAAwC;AAC1E,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,aAAa;EACpB;;AAOF,IAAM,qBAGD;EACH,EAAE,SAAS,iBAAiB,UAAU,QAAO;EAC7C,EAAE,SAAS,qBAAqB,UAAU,QAAO;EACjD,EAAE,SAAS,uBAAuB,UAAU,QAAO;EACnD,EAAE,SAAS,uBAAuB,UAAU,QAAO;EACnD,EAAE,SAAS,kBAAkB,UAAU,QAAO;EAC9C,EAAE,SAAS,gBAAgB,UAAU,QAAO;EAC5C,EAAE,SAAS,wBAAwB,UAAU,QAAO;EACpD,EAAE,SAAS,kBAAkB,UAAU,QAAO;EAC9C,EAAE,SAAS,oBAAoB,UAAU,QAAO;EAChD,EAAE,SAAS,eAAe,UAAU,UAAS;EAC7C,EAAE,SAAS,iBAAiB,UAAU,UAAS;;AAMjD,eAAe,kCACb,eAAqB;AAErB,MAAI,CAAE,MAAMC,iBAAgB,WAAW,aAAa,GAAI;AACtD,WAAO,CAAA;EACT;AACA,QAAM,YAAsB,CAAA;AAC5B,QAAM,UAAU,MAAMA,iBAAgB,QAAQ,eAAe,EAAE,eAAe,KAAI,CAAE;AACpF,aAAW,SAAS,SAAS;AAC3B,UAAM,YAAYC,MAAK,KAAK,eAAe,MAAM,IAAI;AACrD,QAAI,MAAM,YAAW,GAAI;AACvB,gBAAU,KAAK,GAAI,MAAM,kCAAkC,SAAS,CAAE;IACxE,WAAW,MAAM,OAAM,KAAM,MAAM,KAAK,SAAS,KAAK,GAAG;AACvD,gBAAU,KAAK,SAAS;IAC1B;EACF;AACA,SAAO;AACT;AAMA,eAAsB,oCACpB,kBAAwB;AAExB,QAAM,oBAAoB;IACxBA,MAAK,KAAK,kBAAkB,OAAO,YAAY;IAC/CA,MAAK,KAAK,kBAAkB,OAAO,SAAS;;AAE9C,QAAM,aAAkC,CAAA;AACxC,aAAW,iBAAiB,mBAAmB;AAC7C,UAAM,QAAQ,MAAM,kCAAkC,aAAa;AACnE,eAAW,YAAY,OAAO;AAC5B,YAAM,WAAW,MAAMD,iBAAgB,SAAS,UAAU,MAAM;AAChE,YAAM,QAAQ,SAAS,MAAM,OAAO;AACpC,eAAS,YAAY,GAAG,YAAY,MAAM,QAAQ,aAAa,GAAG;AAChE,cAAM,WAAW,MAAM,SAAS;AAChC,mBAAW,EAAE,SAAS,SAAQ,KAAM,oBAAoB;AACtD,kBAAQ,YAAY;AACpB,cAAI;AACJ,kBAAQ,QAAQ,QAAQ,KAAK,QAAQ,OAAO,MAAM;AAChD,uBAAW,KAAK;cACd;cACA,MAAM,YAAY;cAClB,SAAS,QAAQ;cACjB,OAAO,MAAM,CAAC;cACd;aACD;UACH;QACF;MACF;IACF;EACF;AACA,SAAO;AACT;AAoBA,eAAsB,wBACpB,kBACA,KAA8B;AAE9B,QAAM,yBAAyBC,MAAK,KAAK,kBAAkB,cAAc;AACzE,MAAI,CAAE,MAAMD,iBAAgB,WAAW,sBAAsB,GAAI;AAC/D,UAAM,IAAI,MACR,wEAAmE,sBAAsB,GAAG;EAEhG;AACA,QAAM,iBAAkB,MAAMA,iBAAgB,SAAS,sBAAsB;AAI7E,QAAM,qBAA6C;IACjD,GAAI,eAAe,gBAAgB,CAAA;IACnC,GAAI,eAAe,mBAAmB,CAAA;;AAGxC,QAAM,+BAA+B,yBAAyB;AAC9D,QAAM,oCAAoC,OAAO,KAAK,kBAAkB,EAAE,KACxE,CAAC,mBAAmB,eAAe,WAAW,SAAS,CAAC;AAG1D,MAAI,gCAAgC,mCAAmC;AACrE,QACEE,OAAM,OACJ,8IACmE,CACpE;AAEH,WAAO;EACT;AACA,MAAI,8BAA8B;AAChC,WAAO;EACT;AACA,MAAI,mCAAmC;AACrC,WAAO;EACT;AAEA,QAAM,IAAI,MACR,+DAA0D,sBAAsB,oLAEY;AAEhG;AAWA,SAAS,sBACP,UACA,mBAAoC;AAEpC,QAAM,WAAW,SAAS,eAAe,WAAW,MAAM;AAC1D,QAAM,gBACJ,sBAAsB,cAAc,6BAA6B;AACnE,QAAM,kBACJ,sBAAsB,cAAc,+BAA+B;AACrE,QAAM,gBACJ,sBAAsB,cAAc,6BAA6B;AACnE,SAAO;mDAC0C,aAAa;wDACR,eAAe;EACrE,WAAW,0DAA0D,aAAa;IAAS,EAAE;;;;;;;;;;;;;;;;;;IAkB3F,WAAW;;;;;;;oEAOqD,0EAA0E;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4B9I;AAKA,eAAe,oBACb,kBACA,UACA,mBAAoC;AAEpC,QAAM,iBAAiBD,MAAK,KAAK,kBAAkB,QAAQ;AAC3D,QAAMD,iBAAgB,UAAU,cAAc;AAC9C,QAAM,gBAAgBC,MAAK,KAAK,gBAAgB,YAAY;AAC5D,MAAI,CAAE,MAAMD,iBAAgB,WAAW,aAAa,GAAI;AACtD,UAAMA,iBAAgB,UAAU,eAAe,OAAO,MAAM;EAC9D;AACA,QAAM,YAAYC,MAAK,KAAK,gBAAgB,gBAAgB;AAC5D,QAAMD,iBAAgB,UACpB,WACA,sBAAsB,UAAU,iBAAiB,GACjD,MAAM;AAER,SAAO;AACT;AAKA,eAAsB,qBAAqB,YAK1C;AACC,QAAM,EAAE,kBAAkB,WAAW,gBAAgB,iBAAgB,IAAK;AAC1E,QAAM,eAAe,MAAM,mBAAmB,gBAAgB;AAC9D,QAAM,eACJ,iBAAiB,SACb,MAAM,0BAA0B,YAAY,IAC5C,CAAA;AACN,SAAO;IACL,MAAM;IACN,YAAY;IACZ,UAAU;IACV,SAAS,EAAE,OAAO,aAAY;IAC9B,OAAO;MACL,QAAQ;MACR,aAAa;MACb,QAAQ;MACR,KAAK;QACH,OAAO;QACP,SAAS,CAAC,IAAI;QACd,UAAU,MAAM;;MAElB,eAAe;QACb,QAAQ;UACN,sBAAsB;UACtB,gBAAgB;;;;;AAK1B;AAMA,eAAe,4BACb,wBACA,wBACA,qBACA,KAA8B;AAE9B,MAAI,iCAAiC;AACrC,MAAI,CAAE,MAAMA,iBAAgB,WAAW,sBAAsB,GAAI;AAC/D,WAAO,CAAA;EACT;AACA,QAAMA,iBAAgB,UAAU,sBAAsB;AACtD,QAAM,UAAsC,CAAA;AAE5C,iBAAe,KAAK,eAAuB,oBAA4B,gBAAsB;AAC3F,UAAM,mBAAmB,MAAMA,iBAAgB,QAAQ,eAAe,EAAE,eAAe,KAAI,CAAE;AAC7F,eAAW,kBAAkB,kBAAkB;AAC7C,UAAI,eAAe,SAAS,YAAY;AACtC;MACF;AACA,YAAM,aAAaC,MAAK,KAAK,eAAe,eAAe,IAAI;AAC/D,YAAM,kBAAkBA,MAAK,KAAK,oBAAoB,eAAe,IAAI;AACzE,YAAM,eAAe,mBAAmB,KACpC,eAAe,OACf,GAAG,cAAc,IAAI,eAAe,IAAI;AAC5C,UAAI,eAAe,YAAW,GAAI;AAChC,cAAMD,iBAAgB,UAAU,eAAe;AAC/C,cAAM,KAAK,YAAY,iBAAiB,YAAY;MACtD,WAAW,eAAe,OAAM,GAAI;AAClC,cAAM,mBAAmBC,MAAK,QAAQ,eAAe,IAAI,EAAE,YAAW;AACtE,cAAM,YAAY,iBAAiB,WAAW,GAAG,IAC7C,iBAAiB,MAAM,CAAC,IACxB;AACJ,cAAM,qBAAqB,CAAC,OAAO,OAAO,QAAQ,MAAM,EAAE,SAAS,SAAS;AAC5E,YAAI,uBAAuB;AAC3B,YAAI,oBAAoB;AACtB,gBAAM,kBAAkB,MAAM,oBAAoB,YAAY,SAAS;AACvE,cAAI,oBAAoB,MAAM;AAC5B,kBAAMD,iBAAgB,UAAU,iBAAiB,eAAe;AAChE,mCAAuB;UACzB,WAAW,CAAC,gCAAgC;AAC1C,gBAAIE,OAAM,OAAO,8DAA8D,CAAC;AAChF,6CAAiC;UACnC;QACF;AACA,YAAI,CAAC,sBAAsB;AACzB,gBAAMF,iBAAgB,KAAK,YAAY,eAAe;QACxD;AACA,cAAM,OAAO,MAAMA,iBAAgB,KAAK,eAAe;AACvD,gBAAQ,KAAK;UACX,MAAM,eAAe;UACrB,MAAM,UAAU,YAAY;UAC5B,MAAM;UACN,WAAW,KAAK;SACjB;MACH;IACF;EACF;AAEA,QAAM,KAAK,wBAAwB,wBAAwB,EAAE;AAC7D,SAAO;AACT;AAMA,eAAe,uBACb,kBAAwB;AAExB,QAAM,kBAA0C,CAAA;AAChD,QAAM,eAAe,MAAM,mBAAmB,gBAAgB;AAC9D,QAAM,oBAAoB,CAAC,QAAQ,QAAQ,QAAQ,SAAS,UAAU,QAAQ;AAC9E,MAAI,iBAAiB,QAAW;AAC9B,UAAM,oBAAoBC,MAAK,KAAK,cAAc,UAAU;AAC5D,eAAW,eAAe,mBAAmB;AAC3C,YAAM,kBAAkBA,MAAK,KAAK,mBAAmB,aAAa,cAAc;AAChF,UAAI,MAAMD,iBAAgB,WAAW,eAAe,GAAG;AACrD,cAAM,SAAU,MAAMA,iBAAgB,SAAS,eAAe;AAI9D,YAAI,OAAO,OAAO,SAAS,YAAY,OAAO,OAAO,YAAY,UAAU;AACzE,0BAAgB,OAAO,IAAI,IAAI,OAAO;QACxC;MACF;IACF;EACF;AACA,SAAO;AACT;AAKA,eAAe,6BACb,YAAmC;AAEnC,QAAM,aAAa,MAAM,OAAO,MAAM;AACtC,QAAM,WAAW,MAAM,UAAmB;AAC5C;AAMA,eAAsB,SAAS,SAAwB;AACrD,QAAM,MAAM,QAAQ,QAAQ,CAAC,YAAoB,QAAQ,IAAI,OAAO;AACpE,QAAM,wBAAwB,KAAK,IAAG;AACtC,QAAM,mBAAmBC,MAAK,QAAQ,QAAQ,gBAAgB;AAC9D,QAAM,kBAAkBA,MAAK,QAC3B,QAAQ,mBAAmBA,MAAK,KAAK,kBAAkB,MAAM,CAAC;AAIhE,QAAM,mBAAmB,MAAM,cAAc;IAC3C;IACA,eAAe;GAChB;AACD,MAAI,iBAAiB,OAAO,SAAS,GAAG;AACtC,UAAM,kBAAkB,iBAAiB,OACtC,IAAI,CAAC,oBAAoB,KAAK,gBAAgB,QAAQ,KAAK,gBAAgB,OAAO,EAAE,EACpF,KAAK,IAAI;AACZ,UAAM,IAAI,oBACR,0BAA0B,iBAAiB,OAAO,MAAM;EAAe,eAAe,IACtF,CAAA,CAAE;EAEN;AAGA,QAAM,eAAe,MAAM,oCAAoC,gBAAgB;AAC/E,QAAM,kBAAkB,aAAa,OAAO,CAAC,YAAY,QAAQ,aAAa,OAAO;AACrF,QAAM,WAAW,aAAa,OAAO,CAAC,YAAY,QAAQ,aAAa,SAAS;AAChF,MAAI,gBAAgB,SAAS,GAAG;AAC9B,UAAM,IAAI,oBACR,6BAA6B,gBAAgB,MAAM,iBACnD,eAAe;EAEnB;AAEA,QAAM,gBAAgB,MAAM,oBAAoB,gBAAgB;AAChE,MAAI,+BAA0B,cAAc,IAAI,MAAM,cAAc,cAAc,GAAG;AAGrF,QAAM,oBAAoB,MAAM,wBAAwB,kBAAkB,GAAG;AAG7E,QAAM,iBAAiB,MAAM,oBAC3B,kBACA,eACA,iBAAiB;AAInB,QAAM,mBAAmBA,MAAK,KAAK,iBAAiB,gBAAgB;AACpE,QAAMD,iBAAgB,UAAU,eAAe;AAC/C,QAAMA,iBAAgB,OAAO,gBAAgB;AAC7C,QAAMA,iBAAgB,UAAU,gBAAgB;AAEhD,QAAM,YAAYC,MAAK,KAAK,kBAAkB,QAAQ;AACtD,QAAM,aAAa,MAAM,qBAAqB;IAC5C;IACA;IACA;IACA;GACD;AACD,QAAM,UAAU,QAAQ,gBAAgB;AACxC,QAAM,QAAQ,UAAU;AAExB,QAAM,kBAAkBA,MAAK,KAAK,kBAAkB,SAAS;AAC7D,MAAI,CAAE,MAAMD,iBAAgB,WAAW,eAAe,GAAI;AACxD,UAAM,IAAI,MACR,0CAA0C,eAAe,yBAAoB;EAEjF;AAGA,QAAM,yBAAyBC,MAAK,KAAK,kBAAkB,QAAQ;AACnE,QAAM,yBAAyBA,MAAK,KAAK,kBAAkB,QAAQ;AACnE,QAAM,sBAAsB,QAAQ,sBAAsB;AAC1D,QAAM,eAAe,MAAM,4BACzB,wBACA,wBACA,qBACA,GAAG;AAIL,QAAM,kBAAkB,MAAM,uBAAuB,gBAAgB;AAGrE,QAAM,iBAAiB,MAAMD,iBAAgB,SAAS,eAAe;AACrE,QAAM,iBAAiB,GAAG,cAAc,IAAI,IAAK,cAAkD,WAAW,OAAO;AACrH,QAAM,aAAaC,MAAK,KAAK,iBAAiB,cAAc;AAE5D,QAAM,yBAAyB,MAAM,2BAA2B,gBAAgB;AAEhF,QAAM,kBAA2D;IAC/D,MAAM,cAAc;IACpB,SAAS;IACT,gBAAgB,cAAc;IAC9B,YAAY;IACZ,oBAAoB,cAAc,sBAAsB;IACxD;IACA,QAAQ;IACR,iBAAgB,oBAAI,KAAI,GAAG,YAAW;;AAGxC,QAAM,aAAa,IAAI,MAAK;AAC5B,aAAW,KAAK,WAAW,cAAc;AACzC,aAAW,cAAc,cAAc;AACrC,UAAM,oBAAoBA,MAAK,KAAK,kBAAkB,WAAW,IAAI;AACrE,UAAM,cAAc,MAAMD,iBAAgB,SAAS,iBAAiB;AACpE,eAAW,KAAK,WAAW,MAAM,WAAW;EAC9C;AAOA,MAAI,sBAAsB;AAC1B,MAAI,gBAAgB;AACpB,MAAI,cAAsB,OAAO,MAAM,CAAC;AACxC,MAAI,gBAAgC,EAAE,GAAG,iBAAiB,iBAAiB,EAAC;AAC5E,WAAS,YAAY,GAAG,YAAY,IAAI,aAAa,GAAG;AACtD,oBAAgB,EAAE,GAAG,iBAAiB,iBAAiB,oBAAmB;AAC1E,UAAM,mBAAmB,8BAA8B,eAAe,aAAa;AAGnF,eAAW,KAAK,iBAAiB,kBAAkB,EAAE,aAAa,QAAO,CAAE;AAC3E,kBAAc,MAAM,WAAW,cAAc;MAC3C,MAAM;MACN,aAAa;KACd;AACD,QAAI,YAAY,eAAe,qBAAqB;AAClD;IACF;AAGA,UAAM,iBAAiB,OAAO,mBAAmB,EAAE;AACnD,UAAM,YAAY,OAAO,YAAY,UAAU,EAAE;AACjD,QAAI,cAAc,gBAAgB;AAChC,uBAAiB,KAAK,IAAI,YAAY,cAAc,IAAI;IAC1D;AACA,0BAAsB,YAAY;EACpC;AACA,QAAMA,iBAAgB,UAAU,YAAY,WAAW;AACvD,QAAM,aAAa,MAAMA,iBAAgB,KAAK,UAAU;AACxD,MAAI,WAAW,SAAS,cAAc,iBAAiB;AACrD,UAAM,IAAI,MACR,0CAA0C,cAAc,eAAe,0CAC7B,WAAW,IAAI,IAAI;EAEjE;AAEA,QAAM,wBAAwB,KAAK,IAAG,IAAK;AAC3C,SAAO;IACL;IACA,iBAAiB,WAAW;IAC5B;IACA;IACA,YAAY,CAAA;IACZ,UAAU;;AAEd;AAOA,SAAS,8BACP,UACA,eAAqB;AAErB,QAAM,sBAAiE;IACrE,GAAG;IACH,cAAc,IAAI,OAAO,KAAK,IAAI,GAAG,aAAa,CAAC;;AAErD,SAAO,KAAK,UAAU,qBAAqB,MAAM,CAAC;AACpD;AAQA,eAAsB,6BACpB,YACA,WAAiB;AAEjB,MAAI;AACJ,MAAI;AACF,kBAAe,MAAM;;MAA0B;IAAiB;EAGlE,QAAQ;AACN,WAAO;EACT;AACA,MAAI;AACF,UAAM,WAAW,YAAY,QAAQ,UAAU,EAAE,OAAM;AACvD,QAAI;AACJ,QAAI,cAAc,OAAO;AACvB,mBAAa,SAAS,IAAI,EAAE,kBAAkB,EAAC,CAAE;IACnD,WAAW,cAAc,SAAS,cAAc,QAAQ;AACtD,mBAAa,SAAS,KAAK,EAAE,SAAS,IAAI,SAAS,KAAI,CAAE;IAC3D,WAAW,cAAc,QAAQ;AAC/B,mBAAa,SAAS,KAAK,EAAE,SAAS,GAAE,CAAE;IAC5C,OAAO;AACL,aAAO;IACT;AACA,UAAM,SAAS,MAAM,WAAW,SAAQ;AACxC,WAAO;EACT,QAAQ;AACN,WAAO;EACT;AACF;AAiBA,eAAe,2BAA2B,kBAAwB;AAChE,QAAM,eAAeC,MAAK,KAAK,kBAAkB,YAAY;AAC7D,MAAI,CAAE,MAAMD,iBAAgB,WAAW,YAAY,GAAI;AACrD,WAAO;EACT;AACA,QAAM,MAAO,MAAMA,iBAAgB,SAAS,YAAY;AACxD,SAAO,OAAO,IAAI,YAAY,WAAW,IAAI,UAAU;AACzD;;;AF1qBA,eAAsB,oBACpB,kBAAwB;AAExB,QAAM,eAAeG,MAAK,KAAK,kBAAkB,YAAY;AAC7D,MAAI,CAAE,MAAMC,iBAAgB,WAAW,YAAY,GAAI;AACrD,UAAM,IAAI,MACR,0BAA0B,gBAAgB,2BAA2B;EAEzE;AACA,QAAM,SAAU,MAAMA,iBAAgB,SAAS,YAAY;AAC3D,MAAI,OAAO,OAAO,SAAS,YAAY,OAAO,KAAK,WAAW,GAAG;AAC/D,UAAM,IAAI,MAAM,8CAA8C;EAChE;AACA,MAAI,OAAO,mBAAmB,aAAa,OAAO,mBAAmB,YAAY;AAC/E,UAAM,IAAI,MACR,kFAA6E;EAEjF;AACA,SAAO;IACL,MAAM,OAAO;IACb,gBAAgB,OAAO;IACvB,oBAAoB,OAAO;;AAE/B;AAOA,eAAsB,mBACpB,kBAAwB;AAExB,MAAI,mBAAmBD,MAAK,QAAQ,gBAAgB;AACpD,WAAS,QAAQ,GAAG,QAAQ,GAAG,SAAS;AACtC,UAAM,gBAAgBA,MAAK,KAAK,kBAAkB,qBAAqB;AACvE,QAAI,MAAMC,iBAAgB,WAAW,aAAa,GAAG;AACnD,aAAO;IACT;AACA,UAAM,kBAAkBD,MAAK,QAAQ,gBAAgB;AACrD,QAAI,oBAAoB,kBAAkB;AACxC,aAAO;IACT;AACA,uBAAmB;EACrB;AACA,SAAO;AACT;AAOA,eAAsB,mBACpB,kBACA,UACA,mBAAoC;AAEpC,QAAM,iBAAiBA,MAAK,KAAK,kBAAkB,QAAQ;AAC3D,QAAMC,iBAAgB,UAAU,cAAc;AAG9C,QAAM,gBAAgBD,MAAK,KAAK,gBAAgB,YAAY;AAC5D,QAAMC,iBAAgB,UAAU,eAAe,OAAO,MAAM;AAE5D,QAAM,gBAAgBD,MAAK,KAAK,gBAAgB,YAAY;AAC5D,QAAMC,iBAAgB,UAAU,eAAe,eAAe,QAAQ,GAAG,MAAM;AAE/E,QAAM,eAAeD,MAAK,KAAK,gBAAgB,cAAc;AAC7D,QAAMC,iBAAgB,UACpB,cACA,oBAAoB,UAAU,iBAAiB,GAC/C,MAAM;AAGR,SAAO;AACT;AAKA,SAAS,eAAe,UAA8B;AACpD,SAAO;;;;;aAKI,SAAS,IAAI;;;;;;;;;;;;AAY1B;AAYA,SAAS,oBACP,UACA,mBAAoC;AAEpC,QAAM,wBACJ,sBAAsB,cAAc,+BAA+B;AACrE,SAAO;6BACoB,qBAAqB;;;;;;;;;;;;;;;;;;;;;uBAqB3B,SAAS,cAAc;;;;;;6CAMD,SAAS,cAAc;;;;;;;AAOpE;AAMA,eAAsB,gBAAgB,YAKrC;AACC,QAAM,EAAE,kBAAkB,WAAW,MAAM,KAAI,IAAK;AACpD,QAAM,eAAe,MAAM,mBAAmB,gBAAgB;AAE9D,QAAM,eACJ,iBAAiB,SACb,MAAM,0BAA0B,YAAY,IAC5C,CAAA;AAEN,SAAO;IACL,MAAM;IACN,YAAY;IACZ,QAAQ;MACN;MACA;MACA,YAAY;MACZ,IAAI;QACF,OAAO;UACL;UACA,GAAI,iBAAiB,SAAY,CAAC,YAAY,IAAI,CAAA;;;;IAIxD,SAAS;MACP,OAAO;;IAET,cAAc;;;;AAIlB;AAQA,eAAsB,aAAa,SAA4B;AAC7D,QAAM,EACJ,kBACA,OAAO,MACP,OAAO,MACP,MAAM,CAAC,YAAoB,QAAQ,IAAI,OAAO,GAC9C,gBAAe,IACb;AAEJ,QAAM,WAAW,MAAM,oBAAoB,gBAAgB;AAC3D,MAAIC,OAAM,KAAK,6BAAwB,SAAS,IAAI,MAAM,SAAS,cAAc,GAAG,CAAC;AAErF,QAAM,oBAAoB,MAAM,wBAAwB,kBAAkB,GAAG;AAC7E,QAAM,YAAY,MAAM,mBAAmB,kBAAkB,UAAU,iBAAiB;AACxF,QAAM,aAAa,MAAM,gBAAgB,EAAE,kBAAkB,WAAW,MAAM,KAAI,CAAE;AAEpF,QAAM,cACJ,oBACC,OAAO,kBAA0C;AAChD,UAAM,aAAa,MAAM,OAAO,MAAM;AACtC,WAAO,WAAW,aAAa,aAAsB;EACvD;AAEF,QAAM,SAAS,MAAM,YAAY,UAAU;AAC3C,QAAM,OAAO,OAAM;AACnB,QAAM,mBAAmB,OAAO,cAAc,QAAQ,CAAC;AACvD,MAAI,qBAAqB,QAAW;AAClC,QAAIA,OAAM,MAAM,uBAAuB,gBAAgB,EAAE,CAAC;EAC5D,OAAO;AACL,QAAIA,OAAM,MAAM,gCAAgC,IAAI,EAAE,CAAC;EACzD;AACF;;;AInRA,OAAOC,WAAU;AACjB,OAAOC,sBAAqB;AAC5B,OAAOC,YAAW;AAClB,OAAO,WAAW;AAClB,YAAY,WAAW;AACvB,YAAY,eAAe;AA4DrB,IAAO,qBAAP,MAAyB;EAEV;EADnB,YACmB,SAAoC,CAAC,YACpD,QAAQ,IAAI,OAAO,GAAC;AADL,SAAA,SAAA;EAEhB;EAEI,MAAM,OACX,YAAkC;AAElC,UAAM,EAAE,UAAU,aAAY,IAAK;AACnC,SAAK,OACHA,OAAM,OACJ,qCAAqC,SAAS,IAAI,IAAI,SAAS,OAAO,KAAK,aAAa,UAAU,SAAS,CAC5G;AAEH,UAAM,UAAU,uBAAuB,mBACrC,SAAS,IAAI,CACd,IAAI,mBAAmB,SAAS,OAAO,CAAC;AACzC,WAAO,EAAE,QAAO;EAClB;;AAMF,IAAM,6BAA6B,oBAAI,IAAY;EACjD;EACA;EACA;EACA;EACA;CACD;AAKD,eAAsB,WACpB,SAA0B;AAE1B,QAAM,MAAM,QAAQ,QAAQ,CAAC,YAAoB,QAAQ,IAAI,OAAO;AACpE,QAAM,WAAW,QAAQ,YAAY,IAAI,mBAAmB,GAAG;AAC/D,QAAM,YAAY,QAAQ,aAAa;AAEvC,QAAM,aAAa,MAAM,kBACvB,QAAQ,kBACR,QAAQ,UAAU;AAEpB,MAAIA,OAAM,KAAK,+BAA+B,UAAU,EAAE,CAAC;AAE3D,QAAM,eAAe,MAAMD,iBAAgB,SAAS,UAAU;AAC9D,QAAM,aAAa,MAAM,MAAM,UAAU,YAAY;AAErD,QAAM,WAAW,MAAM,wBAAwB,UAAU;AACzD,QAAM,wBAAwB,UAAU;AACxC,QAAM,oBAAoB,YAAY,QAAQ;AAE9C,QAAM,aAAa,MAAM,gBAAgB,YAAY,SAAS;AAC9D,QAAM,aAAa,8BAA8B,UAAU;AAC3D,MAAI,WAAW,SAAS,GAAG;AACzB,UAAM,YAAY,WACf,IACC,CAAC,cACC,QAAQ,UAAU,OAAO,KAAK,UAAU,WAAW,MAClD,UAAU,SAAS,SAAY,UAAU,UAAU,IAAI,MAAM,GAAG,EAEpE,KAAK,IAAI;AACZ,UAAM,IAAI,MACR;EAA8D,SAAS,EAAE;EAE7E;AAEA,QAAM,WAAqB,CAAA;AAC3B,QAAM,aAAa,SAAS,OAAO;AACnC,QAAM,kBAAkB,aAAa;AAErC,MAAIC,OAAM,MAAM,qBAAqB,CAAC;AACtC,MAAI,mBAAmB,SAAS,IAAI,EAAE;AACtC,MAAI,mBAAmB,SAAS,OAAO,EAAE;AACzC,MAAI,mBAAmB,SAAS,cAAc,EAAE;AAChD,MAAI,mBAAmB,UAAU,EAAE;AACnC,MAAI,mBAAmB,eAAe,QAAQ;AAC9C,MAAIA,OAAM,OAAO,0CAAqC,CAAC;AAEvD,QAAM,EAAE,QAAO,IAAK,MAAM,SAAS,OAAO;IACxC;IACA;IACA;GACD;AACD,MAAIA,OAAM,KAAK;wBAA2B,OAAO,EAAE,CAAC;AAEpD,SAAO;IACL,SAAS;IACT;IACA;IACA;IACA;;AAEJ;AAEA,eAAe,kBACb,kBACA,oBAAsC;AAEtC,MAAI,uBAAuB,QAAW;AACpC,UAAM,qBAAqBF,MAAK,WAAW,kBAAkB,IACzD,qBACAA,MAAK,KAAK,kBAAkB,kBAAkB;AAClD,QAAI,CAAE,MAAMC,iBAAgB,WAAW,kBAAkB,GAAI;AAC3D,YAAM,IAAI,MAAM,uBAAuB,kBAAkB,EAAE;IAC7D;AACA,WAAO;EACT;AACA,QAAM,gBAAgBD,MAAK,KAAK,kBAAkB,MAAM;AACxD,MAAI,CAAE,MAAMC,iBAAgB,WAAW,aAAa,GAAI;AACtD,UAAM,IAAI,MACR,uDAAuD,aAAa,4BAA4B;EAEpG;AACA,QAAM,UAAU,MAAMA,iBAAgB,QAAQ,aAAa;AAC3D,QAAM,eAAe,QAAQ,OAAO,CAAC,UAAU,MAAM,SAAS,QAAQ,CAAC;AACvE,MAAI,aAAa,WAAW,GAAG;AAC7B,UAAM,IAAI,MACR,8BAA8B,aAAa,4BAA4B;EAE3E;AACA,QAAM,cAAc,MAAM,QAAQ,IAChC,aAAa,IAAI,OAAO,UAAS;AAC/B,UAAM,WAAWD,MAAK,KAAK,eAAe,KAAK;AAC/C,UAAM,QAAQ,MAAMC,iBAAgB,KAAK,QAAQ;AACjD,WAAO,EAAE,UAAU,0BAA0B,MAAM,QAAO;EAC5D,CAAC,CAAC;AAEJ,cAAY,KACV,CAAC,MAAM,UACL,MAAM,2BAA2B,KAAK,wBAAwB;AAElE,SAAO,YAAY,CAAC,EAAE;AACxB;AAEA,eAAe,wBACb,YAAiB;AAEjB,QAAM,eAAe,WAAW,KAAK,eAAe;AACpD,MAAI,iBAAiB,MAAM;AACzB,UAAM,IAAI,MAAM,2CAA2C;EAC7D;AACA,QAAM,eAAe,MAAM,aAAa,MAAM,QAAQ;AACtD,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,YAAY;EAClC,SAAS,YAAY;AACnB,UAAM,IAAI,MACR,2CAA4C,WAAqB,OAAO,EAAE;EAE9E;AACA,MAAI,OAAO,WAAW,YAAY,WAAW,MAAM;AACjD,UAAM,IAAI,MAAM,uCAAuC;EACzD;AACA,QAAM,YAAY;AAClB,QAAM,uBAAuB;IAC3B;IACA;IACA;IACA;IACA;IACA;;AAEF,aAAW,aAAa,sBAAsB;AAC5C,QACE,OAAO,UAAU,SAAS,MAAM,YAC/B,UAAU,SAAS,EAAa,WAAW,GAC5C;AACA,YAAM,IAAI,MACR,0DAA0D,SAAS,GAAG;IAE1E;EACF;AACA,QAAM,iBAAiB,UAAU;AACjC,MAAI,mBAAmB,aAAa,mBAAmB,YAAY;AACjE,UAAM,IAAI,MACR,oDAAoD,OAAO,cAAc,CAAC,GAAG;EAEjF;AACA,MAAI,UAAU,eAAe,WAAW;AACtC,UAAM,IAAI,MACR,2DAA2D,OAAO,UAAU,UAAU,CAAC,IAAI;EAE/F;AACA,MAAI,OAAO,UAAU,oBAAoB,UAAU;AACjD,UAAM,IAAI,MACR,yEAAyE;EAE7E;AACA,MACE,OAAO,UAAU,oBAAoB,YACrC,UAAU,oBAAoB,MAC9B;AACA,UAAM,IAAI,MACR,yEAAyE;EAE7E;AACA,MAAI,CAAC,MAAM,QAAQ,UAAU,MAAM,GAAG;AACpC,UAAM,IAAI,MACR,+DAA+D;EAEnE;AACA,SAAO;AACT;AAEA,eAAe,wBAAwB,YAAiB;AACtD,QAAM,QAAQ,WAAW,KAAK,SAAS;AACvC,MAAI,UAAU,MAAM;AAClB,UAAM,IAAI,MAAM,qCAAqC;EACvD;AACF;AAEA,eAAe,oBACb,YACA,UAAwB;AAExB,aAAW,SAAS,SAAS,QAAQ;AACnC,UAAM,QAAQ,WAAW,KAAK,MAAM,IAAI;AACxC,QAAI,UAAU,MAAM;AAClB,YAAM,IAAI,MACR,mCAAmC,MAAM,IAAI,sCAAsC;IAEvF;EACF;AACF;AAEA,eAAe,gBACb,YACA,WAAiB;AAEjB,QAAM,QAAQ,WAAW,KAAK,SAAS;AACvC,MAAI,UAAU,MAAM;AAClB,UAAM,IAAI,MAAM,mCAAmC,SAAS,EAAE;EAChE;AACA,SAAO,MAAM,MAAM,QAAQ;AAC7B;AAMM,SAAU,8BACd,YAAkB;AAElB,QAAM,aAAyC,CAAA;AAC/C,MAAI;AACJ,MAAI;AACF,UAAY,YAAM,YAAY;MAC5B,aAAa;MACb,YAAY;MACZ,WAAW;MACX,eAAe;KAChB;EACH,SAAS,YAAY;AAEnB,eAAW,KAAK;MACd,SAAS;MACT,aAAa,mCAAoC,WAAqB,OAAO;KAC9E;AACD,WAAO;EACT;AAGA,EAAU,iBAAO,KAAK;IACpB,eAAe,SAAO;AACpB,YAAM,OAAO;AACb,YAAM,SAAS,KAAK;AACpB,UAAI,OAAO,SAAS,cAAc;AAChC,cAAM,iBAAiB;AACvB,YAAI,2BAA2B,IAAI,eAAe,IAAI,GAAG;AACvD,qBAAW,KAAK;YACd,SAAS,eAAe;YACxB,aAAa,qBAAqB,eAAe,IAAI;YACrD,MAAM,KAAK,KAAK,MAAM;YACtB,QAAQ,KAAK,KAAK,MAAM;WACzB;QACH;MACF;IACF;IACA,cAAc,SAAO;AACnB,YAAM,OAAO;AACb,YAAM,SAAS,KAAK;AACpB,UAAI,OAAO,SAAS,cAAc;AAChC,cAAM,iBAAiB;AACvB,YAAI,2BAA2B,IAAI,eAAe,IAAI,GAAG;AACvD,qBAAW,KAAK;YACd,SAAS,eAAe;YACxB,aAAa,6BAA6B,eAAe,IAAI;YAC7D,MAAM,KAAK,KAAK,MAAM;YACtB,QAAQ,KAAK,KAAK,MAAM;WACzB;QACH;MACF;IACF;IACA,qBAAqB,SAAO;AAC1B,YAAM,OAAO;AAGb,UAAI,KAAK,KAAK,SAAS,oBAAoB;AACzC,cAAM,aAAa,KAAK;AACxB,YACE,WAAW,SAAS,SAAS,gBAC5B,WAAW,SAA8B,SAAS,aACnD;AACA,qBAAW,KAAK;YACd,SAAS;YACT,aAAa;YACb,MAAM,KAAK,KAAK,MAAM;YACtB,QAAQ,KAAK,KAAK,MAAM;WACzB;QACH;MACF;IACF;IACA,iBAAiB,SAAO;AACtB,YAAM,OAAO;AAGb,iBAAW,KAAK;QACd,SAAS;QACT,aAAa;QACb,MAAM,KAAK,KAAK,MAAM;QACtB,QAAQ,KAAK,KAAK,MAAM;OACzB;IACH;GACD;AAED,SAAO;AACT;;;AC5YA,OAAOE,WAAU;AACjB,OAAOC,sBAAqB;AAC5B,OAAOC,YAAW;AAClB,OAAOC,YAAW;AA6DX,IAAM,qBACX;AAUI,IAAO,yBAAP,MAA6B;EAEd;EADnB,YACmB,SAAoC,CAAC,YACpD,QAAQ,IAAI,OAAO,GAAC;AADL,SAAA,SAAA;EAEhB;EAEI,MAAM,OACX,UAA6B;AAE7B,SAAK,OACHD,OAAM,OAAO,8CAA8C,CAAC;AAE9D,UAAM,WAA4B;MAChC,EAAE,UAAU,QAAQ,aAAa,QAAO;;AAE1C,WAAO,EAAE,UAAU,aAAa,KAAK,UAAU,QAAQ,EAAC;EAC1D;;AAOI,SAAU,gCACd,SAAoC,CAAC,YAAY,QAAQ,IAAI,OAAO,GAAC;AAErE,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,UAAa,OAAO,WAAW,GAAG;AAC/C,WAAO,IAAI,uBAAuB,MAAM;EAC1C;AACA,SAAO,IAAI,4BAA4B,MAAM;AAC/C;AAKM,IAAO,8BAAP,MAAkC;EACF;EAApC,YAAoC,QAAc;AAAd,SAAA,SAAA;EAAiB;EAE9C,MAAM,OACX,SAA4B;AAE5B,UAAM,EAAE,SAAS,gBAAe,IAAK,MAAM,OAAO,mBAAmB;AACrE,UAAM,SAAS,IAAI,gBAAgB,EAAE,QAAQ,KAAK,OAAM,CAAE;AAC1D,UAAM,UAAU,MAAM,OAAO,SAAS,OAAO;MAC3C,OAAO;MACP,YAAY;MACZ,QAAQ,QAAQ;MAChB,UAAU;QACR;UACE,MAAM;UACN,SAAS,QAAQ;;;KAGtB;AACD,UAAM,iBAAiB,QAAQ,QAAQ,KACrC,CAAC,UACE,MAA2B,SAAS,MAAM;AAE/C,UAAM,cAAc,gBAAgB,QAAQ;AAC5C,UAAM,WAAW,4BAA4B,WAAW;AACxD,WAAO,EAAE,UAAU,YAAW;EAChC;;AAOI,SAAU,4BAA4B,MAAY;AACtD,QAAM,kBAAkB,KAAK,QAAQ,GAAG;AACxC,QAAM,eAAe,KAAK,YAAY,GAAG;AACzC,MAAI,oBAAoB,MAAM,iBAAiB,MAAM,gBAAgB,iBAAiB;AACpF,WAAO;MACL;QACE,UAAU;QACV,aAAa,kDAAkD,KAAK,MAAM,GAAG,GAAG,CAAC;;;EAGvF;AACA,QAAM,YAAY,KAAK,MAAM,iBAAiB,eAAe,CAAC;AAC9D,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,SAAS;AACnC,QAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,YAAM,IAAI,MAAM,qBAAqB;IACvC;AACA,WAAO,OAAO,IAAI,CAAC,UAAS;AAC1B,YAAM,SAAS;AACf,YAAM,WAAW,OAAO;AACxB,YAAM,qBACJ,aAAa,eAAe,aAAa,aAAa,aAAa,SAC/D,WACA;AACN,aAAO;QACL,UAAU;QACV,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;QACtD,aACE,OAAO,OAAO,gBAAgB,WAC1B,OAAO,cACP;;IAEV,CAAC;EACH,SAAS,YAAY;AACnB,WAAO;MACL;QACE,UAAU;QACV,aAAa,oCAAqC,WAAqB,OAAO;;;EAGpF;AACF;AAMA,eAAsB,UACpB,SAAyB;AAEzB,QAAM,MAAM,QAAQ,QAAQ,CAAC,YAAoB,QAAQ,IAAI,OAAO;AACpE,QAAM,eACJ,QAAQ,gBAAgB,gCAAgC,GAAG;AAE7D,QAAM,UAAU,MAAM,qBACpB,QAAQ,kBACR,QAAQ,UAAU;AAEpB,MAAI,QAAQ,WAAW,GAAG;AACxB,QAAIA,OAAM,OAAO,+CAA+C,CAAC;AACjE,WAAO;MACL,aAAa;MACb,UAAU;QACR,EAAE,UAAU,QAAQ,aAAa,8BAA6B;;;EAGpE;AAEA,QAAM,aAAa,QAChB,IACC,CAAC,SACC,gBAAgB,KAAK,WAAW;EAAS,KAAK,QAAQ;mBAAsB,KAAK,WAAW,MAAM,EAErG,KAAK,MAAM;AAEd,MAAIA,OAAM,KAAK,yBAAyB,QAAQ,MAAM,wBAAwB,CAAC;AAE/E,QAAM,WAAW,MAAM,aAAa,OAAO;IACzC;IACA,QAAQ;GACT;AACD,QAAM,eAAe,SAAS,SAAS,KACrC,CAAC,YAAY,QAAQ,aAAa,WAAW;AAG/C,aAAW,WAAW,SAAS,UAAU;AACvC,UAAM,SAAS,IAAI,QAAQ,SAAS,YAAW,CAAE,IAC/C,QAAQ,SAAS,SAAY,IAAI,QAAQ,IAAI,KAAK,EACpD;AACA,UAAM,OAAO,GAAG,MAAM,KAAK,QAAQ,WAAW;AAC9C,QAAI,QAAQ,aAAa,aAAa;AACpC,UAAIA,OAAM,IAAI,IAAI,CAAC;IACrB,WAAW,QAAQ,aAAa,WAAW;AACzC,UAAIA,OAAM,OAAO,IAAI,CAAC;IACxB,OAAO;AACL,UAAIA,OAAM,MAAM,IAAI,CAAC;IACvB;EACF;AAEA,SAAO;IACL,aAAa,iBAAiB;IAC9B,UAAU,SAAS;;AAEvB;AAOA,eAAe,qBACb,kBACA,YAA8B;AAE9B,MAAI,eAAe,QAAW;AAC5B,UAAM,YAAY,MAAM,+BACtB,kBACA,UAAU;AAEZ,QAAI,UAAU,SAAS,GAAG;AACxB,aAAO;IACT;EACF;AACA,SAAO,6BAA6B,gBAAgB;AACtD;AAEA,eAAe,+BACb,kBACA,YAAkB;AAElB,QAAM,qBAAqBF,MAAK,WAAW,UAAU,IACjD,aACAA,MAAK,KAAK,kBAAkB,UAAU;AAC1C,MAAI,CAAE,MAAMC,iBAAgB,WAAW,kBAAkB,GAAI;AAC3D,UAAM,IAAI,MAAM,uBAAuB,kBAAkB,EAAE;EAC7D;AACA,QAAM,SAAS,MAAMA,iBAAgB,SAAS,kBAAkB;AAChE,QAAM,aAAa,MAAME,OAAM,UAAU,MAAM;AAC/C,QAAM,UAA8B,CAAA;AACpC,QAAM,UAAuD,CAAA;AAC7D,aAAW,QAAQ,CAAC,cAAc,SAAQ;AACxC,QACE,aAAa,WAAW,MAAM,MAC7B,aAAa,SAAS,KAAK,KAAK,aAAa,SAAS,KAAK,MAC5D,KAAK,QAAQ,OACb;AACA,cAAQ,KAAK,EAAE,MAAM,cAAc,KAAI,CAAE;IAC3C;EACF,CAAC;AACD,aAAW,SAAS,SAAS;AAC3B,UAAM,WAAW,MAAM,MAAM,KAAK,MAAM,QAAQ;AAChD,YAAQ,KAAK,EAAE,aAAa,MAAM,MAAM,SAAQ,CAAE;EACpD;AACA,SAAO;AACT;AAEA,eAAe,6BACb,kBAAwB;AAExB,QAAM,sBAAsBH,MAAK,KAC/B,kBACA,OACA,YAAY;AAEd,QAAM,mBAAmBA,MAAK,KAAK,kBAAkB,OAAO,SAAS;AACrE,QAAM,UAA8B,CAAA;AACpC,aAAW,iBAAiB,CAAC,qBAAqB,gBAAgB,GAAG;AACnE,QAAI,CAAE,MAAMC,iBAAgB,WAAW,aAAa,GAAI;AACtD;IACF;AACA,UAAM,kBAAkB,MAAMG,mCAC5B,aAAa;AAEf,eAAW,YAAY,iBAAiB;AACtC,YAAM,WAAW,MAAMH,iBAAgB,SAAS,UAAU,MAAM;AAChE,cAAQ,KAAK;QACX,aAAaD,MAAK,SAAS,kBAAkB,QAAQ;QACrD;OACD;IACH;EACF;AACA,SAAO;AACT;AAEA,eAAeI,mCACb,eAAqB;AAErB,QAAM,YAAsB,CAAA;AAC5B,QAAM,UAAU,MAAMH,iBAAgB,QAAQ,eAAe;IAC3D,eAAe;GAChB;AACD,aAAW,SAAS,SAAS;AAC3B,UAAM,YAAYD,MAAK,KAAK,eAAe,MAAM,IAAI;AACrD,QAAI,MAAM,YAAW,GAAI;AACvB,gBAAU,KAAK,GAAI,MAAMI,mCAAkC,SAAS,CAAE;IACxE,WAAW,MAAM,OAAM,KAAM,MAAM,KAAK,SAAS,KAAK,GAAG;AACvD,gBAAU,KAAK,SAAS;IAC1B;EACF;AACA,SAAO;AACT;;;ARrRO,IAAM,UAAU,IAAI,QAAO;AAElC,QACG,KAAK,OAAO,EACZ,YAAY,wCAAwC,EACpD,QAAQ,OAAO;AAElB,QACG,QAAQ,MAAM,EACd,YAAY,mCAAmC,EAC/C,SAAS,kBAAkB,mCAAmC,EAC9D,OAAO,QAAQ,qCAAqC,KAAK,EACzD,OAAO,QAAQ,sCAAsC,KAAK,EAC1D,OAAO,kBAAkB,mDAAmD,KAAK,EACjF,OAAO,OAAO,aAAqB,YAAoC;AACtE,QAAM,sBAAsB,QAAQ,IAAI,MAAM;AAC9C,QAAM,iBAAiB,sBAAsB,aAAa;AAC1D,MAAI;AACF,UAAM,kBAAkB;MACtB;MACA;MACA,iBAAiB;MACjB,aAAa,QAAQ,gBAAgB;KACtC;EACH,SAAS,OAAO;AACd,YAAQ,MAAMC,OAAM,IAAK,MAAgB,OAAO,CAAC;AACjD,YAAQ,KAAK,CAAC;EAChB;AACF,CAAC;AAEH,QACG,QAAQ,KAAK,EACb,YAAY,+CAA+C,EAC3D,OAAO,iBAAiB,2BAA2B,MAAM,EACzD,OAAO,aAAa,4CAA4C,EAChE,OAAO,OAAO,YAA4C;AACzD,QAAM,aAAa,OAAO,SAAS,QAAQ,MAAM,EAAE;AACnD,MAAI;AACF,UAAM,aAAa;MACjB,kBAAkB,QAAQ,IAAG;MAC7B,MAAM,OAAO,SAAS,UAAU,IAAI,aAAa;MACjD,MAAM,QAAQ;KACf;EACH,SAAS,OAAO;AACd,YAAQ,MAAMA,OAAM,IAAK,MAAgB,OAAO,CAAC;AACjD,YAAQ,KAAK,CAAC;EAChB;AACF,CAAC;AAEH,QACG,QAAQ,UAAU,EAClB,YAAY,yDAAyD,EACrE,OAAO,YAAW;AACjB,MAAI;AACF,UAAM,SAAS,MAAM,cAAc,EAAE,kBAAkB,QAAQ,IAAG,EAAE,CAAE;AACtE,UAAM,gBAAgB,CAAC,YAAoC;AACzD,YAAM,gBAA0B,CAAC,QAAQ,QAAQ;AACjD,UAAI,QAAQ,SAAS,QAAW;AAC9B,sBAAc,KAAK,OAAO,QAAQ,IAAI,CAAC;AACvC,YAAI,QAAQ,WAAW,QAAW;AAChC,wBAAc,KAAK,OAAO,QAAQ,MAAM,CAAC;QAC3C;MACF;AACA,aAAO,GAAG,cAAc,KAAK,GAAG,CAAC,KAAK,QAAQ,IAAI,KAAK,QAAQ,OAAO;IACxE;AACA,eAAW,WAAW,OAAO,UAAU;AACrC,cAAQ,KAAKA,OAAM,OAAO,cAAc,OAAO,CAAC,CAAC;IACnD;AACA,eAAW,SAAS,OAAO,QAAQ;AACjC,cAAQ,MAAMA,OAAM,IAAI,cAAc,KAAK,CAAC,CAAC;IAC/C;AACA,QAAI,OAAO,OAAO,WAAW,KAAK,OAAO,SAAS,WAAW,GAAG;AAC9D,cAAQ,IAAIA,OAAM,MAAM,mCAAmC,CAAC;IAC9D;AACA,YAAQ,KAAK,OAAO,OAAO,WAAW,IAAI,IAAI,CAAC;EACjD,SAAS,OAAO;AACd,YAAQ,MAAMA,OAAM,IAAK,MAAgB,OAAO,CAAC;AACjD,YAAQ,KAAK,CAAC;EAChB;AACF,CAAC;AAEH,QACG,QAAQ,OAAO,EACf,YAAY,qDAAqD,EACjE,OAAO,kBAAkB,mCAAmC,MAAM,EAClE,OAAO,OAAO,YAA+B;AAC5C,QAAM,mBAAmB,QAAQ,IAAG;AACpC,QAAM,kBAAkBC,MAAK,WAAW,QAAQ,MAAM,IAClD,QAAQ,SACRA,MAAK,KAAK,kBAAkB,QAAQ,MAAM;AAC9C,MAAI;AACF,UAAM,SAAS,MAAM,SAAS,EAAE,kBAAkB,gBAAe,CAAE;AACnE,eAAW,WAAW,OAAO,UAAU;AACrC,cAAQ,KACND,OAAM,OACJ,GAAG,QAAQ,QAAQ,IAAI,QAAQ,IAAI,uBAAuB,QAAQ,OAAO,OAAO,QAAQ,KAAK,IAAI,CAClG;IAEL;AACA,YAAQ,IACNA,OAAM,MACJ,sBAAsB,OAAO,UAAU,KAAK,OAAO,eAAe,cAAc,OAAO,qBAAqB,IAAI,CACjH;EAEL,SAAS,OAAO;AACd,QAAI,iBAAiB,qBAAqB;AACxC,cAAQ,MAAMA,OAAM,IAAI,MAAM,OAAO,CAAC;AACtC,iBAAW,aAAa,MAAM,YAAY;AACxC,gBAAQ,MACNA,OAAM,IACJ,KAAK,UAAU,QAAQ,IAAI,UAAU,IAAI,cAAc,UAAU,OAAO,OAAO,UAAU,KAAK,IAAI,CACnG;MAEL;IACF,OAAO;AACL,cAAQ,MAAMA,OAAM,IAAK,MAAgB,OAAO,CAAC;IACnD;AACA,YAAQ,KAAK,CAAC;EAChB;AACF,CAAC;AAEH,QACG,QAAQ,SAAS,EACjB,YAAY,oDAAoD,EAChE,OAAO,mBAAmB,6CAA6C,EACvE,OAAO,wBAAwB,sCAAsC,EACrE,OAAO,OAAO,YAAoD;AACjE,MAAI;AACF,UAAM,SAAS,MAAM,WAAW;MAC9B,kBAAkB,QAAQ,IAAG;MAC7B,YAAY,QAAQ;MACpB,WAAW,QAAQ;KACpB;AACD,QAAI,OAAO,SAAS;AAClB,cAAQ,IAAIA,OAAM,MAAM,kBAAkB,OAAO,OAAO,EAAE,CAAC;IAC7D;EACF,SAAS,OAAO;AACd,YAAQ,MAAMA,OAAM,IAAK,MAAgB,OAAO,CAAC;AACjD,YAAQ,KAAK,CAAC;EAChB;AACF,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,8DAA8D,EAC1E,OAAO,mBAAmB,iDAAiD,EAC3E,OAAO,OAAO,YAAgC;AAC7C,MAAI;AACF,UAAM,SAAS,MAAM,UAAU;MAC7B,kBAAkB,QAAQ,IAAG;MAC7B,YAAY,QAAQ;KACrB;AACD,QAAI,OAAO,gBAAgB,OAAO;AAChC,cAAQ,KAAK,CAAC;IAChB;EACF,SAAS,OAAO;AACd,YAAQ,MAAMA,OAAM,IAAK,MAAgB,OAAO,CAAC;AACjD,YAAQ,KAAK,CAAC;EAChB;AACF,CAAC;AAKH,IAAM,kBACJ,YAAY,QAAQ,UAAU,QAAQ,KAAK,CAAC,CAAC,MAC7C,QAAQ,KAAK,CAAC,GAAG,SAAS,QAAQ,MAAM,QACxC,QAAQ,KAAK,CAAC,GAAG,SAAS,SAAS,MAAM,QACzC,QAAQ,KAAK,CAAC,GAAG,SAAS,kBAAkB,MAAM,QAClD,QAAQ,KAAK,CAAC,GAAG,SAAS,mBAAmB,MAAM;AAErD,IAAI,iBAAiB;AACnB,UAAQ,WAAW,QAAQ,IAAI;AACjC;;;ASpOA,MAAM,QAAgB,WAAW,QAAQ,IAAI;","names":["path","chalk","path","fileSystemExtra","fileSystemExtra","path","path","fileSystemExtra","chalk","path","fileSystemExtra","path","fileSystemExtra","chalk","path","spawn","createRequire","fileURLToPath","fileSystemExtra","fileSystemExtra","path","chalk","path","fileSystemExtra","chalk","path","fileSystemExtra","chalk","path","fileSystemExtra","chalk","JsZip","collectTypeScriptFilesRecursively","chalk","path"]}