@ratio-app/cli 0.1.1
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/README.md +504 -0
- package/bin/run.mjs +5 -0
- package/dist/commands/__test__/throw.d.ts +25 -0
- package/dist/commands/__test__/throw.js +238 -0
- package/dist/commands/__test__/throw.js.map +1 -0
- package/dist/commands/app/create.d.ts +55 -0
- package/dist/commands/app/create.js +609 -0
- package/dist/commands/app/create.js.map +1 -0
- package/dist/commands/app/deploy.d.ts +14 -0
- package/dist/commands/app/deploy.js +179 -0
- package/dist/commands/app/deploy.js.map +1 -0
- package/dist/commands/app/dev.d.ts +28 -0
- package/dist/commands/app/dev.js +701 -0
- package/dist/commands/app/dev.js.map +1 -0
- package/dist/commands/app/generate.d.ts +14 -0
- package/dist/commands/app/generate.js +179 -0
- package/dist/commands/app/generate.js.map +1 -0
- package/dist/commands/app/info.d.ts +14 -0
- package/dist/commands/app/info.js +179 -0
- package/dist/commands/app/info.js.map +1 -0
- package/dist/commands/app/link.d.ts +118 -0
- package/dist/commands/app/link.js +1120 -0
- package/dist/commands/app/link.js.map +1 -0
- package/dist/commands/auth/login.d.ts +43 -0
- package/dist/commands/auth/login.js +987 -0
- package/dist/commands/auth/login.js.map +1 -0
- package/dist/commands/auth/logout.d.ts +15 -0
- package/dist/commands/auth/logout.js +486 -0
- package/dist/commands/auth/logout.js.map +1 -0
- package/dist/commands/auth/whoami.d.ts +16 -0
- package/dist/commands/auth/whoami.js +531 -0
- package/dist/commands/auth/whoami.js.map +1 -0
- package/dist/commands/dev/listen.d.ts +84 -0
- package/dist/commands/dev/listen.js +1187 -0
- package/dist/commands/dev/listen.js.map +1 -0
- package/dist/commands/dev/trigger.d.ts +88 -0
- package/dist/commands/dev/trigger.js +729 -0
- package/dist/commands/dev/trigger.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +617 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/auth/authorize-code-exchange.d.ts +50 -0
- package/dist/lib/auth/authorize-code-exchange.js +114 -0
- package/dist/lib/auth/authorize-code-exchange.js.map +1 -0
- package/dist/lib/auth/authorize-url.d.ts +42 -0
- package/dist/lib/auth/authorize-url.js +35 -0
- package/dist/lib/auth/authorize-url.js.map +1 -0
- package/dist/lib/auth/browser.d.ts +16 -0
- package/dist/lib/auth/browser.js +48 -0
- package/dist/lib/auth/browser.js.map +1 -0
- package/dist/lib/auth/config.d.ts +41 -0
- package/dist/lib/auth/config.js +21 -0
- package/dist/lib/auth/config.js.map +1 -0
- package/dist/lib/auth/http-refresher.d.ts +30 -0
- package/dist/lib/auth/http-refresher.js +103 -0
- package/dist/lib/auth/http-refresher.js.map +1 -0
- package/dist/lib/auth/identity.d.ts +26 -0
- package/dist/lib/auth/identity.js +10 -0
- package/dist/lib/auth/identity.js.map +1 -0
- package/dist/lib/auth/index.d.ts +13 -0
- package/dist/lib/auth/index.js +527 -0
- package/dist/lib/auth/index.js.map +1 -0
- package/dist/lib/auth/loopback-server.d.ts +65 -0
- package/dist/lib/auth/loopback-server.js +245 -0
- package/dist/lib/auth/loopback-server.js.map +1 -0
- package/dist/lib/auth/manual-prompt.d.ts +17 -0
- package/dist/lib/auth/manual-prompt.js +20 -0
- package/dist/lib/auth/manual-prompt.js.map +1 -0
- package/dist/lib/auth/pkce.d.ts +24 -0
- package/dist/lib/auth/pkce.js +15 -0
- package/dist/lib/auth/pkce.js.map +1 -0
- package/dist/lib/auth/state.d.ts +20 -0
- package/dist/lib/auth/state.js +19 -0
- package/dist/lib/auth/state.js.map +1 -0
- package/dist/lib/auth/verbose-trace.d.ts +25 -0
- package/dist/lib/auth/verbose-trace.js +17 -0
- package/dist/lib/auth/verbose-trace.js.map +1 -0
- package/dist/lib/credentials/clock.d.ts +18 -0
- package/dist/lib/credentials/clock.js +10 -0
- package/dist/lib/credentials/clock.js.map +1 -0
- package/dist/lib/credentials/index.d.ts +5 -0
- package/dist/lib/credentials/index.js +340 -0
- package/dist/lib/credentials/index.js.map +1 -0
- package/dist/lib/credentials/path.d.ts +26 -0
- package/dist/lib/credentials/path.js +32 -0
- package/dist/lib/credentials/path.js.map +1 -0
- package/dist/lib/credentials/refresher.d.ts +26 -0
- package/dist/lib/credentials/refresher.js +34 -0
- package/dist/lib/credentials/refresher.js.map +1 -0
- package/dist/lib/credentials/schema.d.ts +138 -0
- package/dist/lib/credentials/schema.js +85 -0
- package/dist/lib/credentials/schema.js.map +1 -0
- package/dist/lib/credentials/store.d.ts +102 -0
- package/dist/lib/credentials/store.js +337 -0
- package/dist/lib/credentials/store.js.map +1 -0
- package/dist/lib/credentials/types.d.ts +65 -0
- package/dist/lib/credentials/types.js +1 -0
- package/dist/lib/credentials/types.js.map +1 -0
- package/dist/lib/dev-store-gate.d.ts +66 -0
- package/dist/lib/dev-store-gate.js +15 -0
- package/dist/lib/dev-store-gate.js.map +1 -0
- package/dist/lib/errors.d.ts +60 -0
- package/dist/lib/errors.js +101 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/manifest/index.d.ts +2 -0
- package/dist/lib/manifest/index.js +144 -0
- package/dist/lib/manifest/index.js.map +1 -0
- package/dist/lib/manifest/read-manifest.d.ts +32 -0
- package/dist/lib/manifest/read-manifest.js +87 -0
- package/dist/lib/manifest/read-manifest.js.map +1 -0
- package/dist/lib/manifest/write-client-id.d.ts +33 -0
- package/dist/lib/manifest/write-client-id.js +94 -0
- package/dist/lib/manifest/write-client-id.js.map +1 -0
- package/dist/lib/messages.d.ts +15 -0
- package/dist/lib/messages.js +16 -0
- package/dist/lib/messages.js.map +1 -0
- package/dist/lib/orchestrator/child-runner.d.ts +140 -0
- package/dist/lib/orchestrator/child-runner.js +471 -0
- package/dist/lib/orchestrator/child-runner.js.map +1 -0
- package/dist/lib/preview/index.d.ts +11 -0
- package/dist/lib/preview/index.js +17 -0
- package/dist/lib/preview/index.js.map +1 -0
- package/dist/lib/preview/types.d.ts +16 -0
- package/dist/lib/preview/types.js +11 -0
- package/dist/lib/preview/types.js.map +1 -0
- package/dist/lib/ratio-command.d.ts +52 -0
- package/dist/lib/ratio-command.js +141 -0
- package/dist/lib/ratio-command.js.map +1 -0
- package/dist/lib/relay/index.d.ts +7 -0
- package/dist/lib/relay/index.js +362 -0
- package/dist/lib/relay/index.js.map +1 -0
- package/dist/lib/relay/relay-client.d.ts +91 -0
- package/dist/lib/relay/relay-client.js +362 -0
- package/dist/lib/relay/relay-client.js.map +1 -0
- package/dist/lib/relay/types.d.ts +71 -0
- package/dist/lib/relay/types.js +1 -0
- package/dist/lib/relay/types.js.map +1 -0
- package/dist/lib/render-error.d.ts +35 -0
- package/dist/lib/render-error.js +115 -0
- package/dist/lib/render-error.js.map +1 -0
- package/dist/lib/verbose-flag.d.ts +12 -0
- package/dist/lib/verbose-flag.js +17 -0
- package/dist/lib/verbose-flag.js.map +1 -0
- package/dist/render-CrHGqTPH.d.ts +115 -0
- package/dist/templates/.gitkeep +0 -0
- package/dist/templates/minimal/.env.example.tmpl +4 -0
- package/dist/templates/minimal/README.md.tmpl +26 -0
- package/dist/templates/minimal/gitignore +5 -0
- package/dist/templates/minimal/manifest.json +5 -0
- package/dist/templates/minimal/package.json.tmpl +25 -0
- package/dist/templates/minimal/ratio.config.jsonc.tmpl +17 -0
- package/dist/templates/minimal/src/index.ts.tmpl +26 -0
- package/dist/templates/minimal/src/install.ts.tmpl +37 -0
- package/dist/templates/minimal/src/webhooks.ts.tmpl +57 -0
- package/dist/templates/minimal/tsconfig.json +17 -0
- package/dist/templates/serverless/README.md.tmpl +28 -0
- package/dist/templates/serverless/gitignore +6 -0
- package/dist/templates/serverless/manifest.json +5 -0
- package/dist/templates/serverless/package.json.tmpl +22 -0
- package/dist/templates/serverless/ratio.config.jsonc.tmpl +17 -0
- package/dist/templates/serverless/src/index.ts.tmpl +20 -0
- package/dist/templates/serverless/src/install.ts.tmpl +20 -0
- package/dist/templates/serverless/src/webhooks.ts.tmpl +36 -0
- package/dist/templates/serverless/tsconfig.json +18 -0
- package/dist/templates/serverless/wrangler.toml.tmpl +9 -0
- package/dist/templates/with-admin-ui/.env.example.tmpl +4 -0
- package/dist/templates/with-admin-ui/README.md.tmpl +28 -0
- package/dist/templates/with-admin-ui/gitignore +6 -0
- package/dist/templates/with-admin-ui/manifest.json +5 -0
- package/dist/templates/with-admin-ui/package.json.tmpl +31 -0
- package/dist/templates/with-admin-ui/ratio.config.jsonc.tmpl +17 -0
- package/dist/templates/with-admin-ui/src/admin/app/globals.css +19 -0
- package/dist/templates/with-admin-ui/src/admin/app/layout.tsx.tmpl +13 -0
- package/dist/templates/with-admin-ui/src/admin/app/page.tsx.tmpl +8 -0
- package/dist/templates/with-admin-ui/src/admin/next-env.d.ts.tmpl +5 -0
- package/dist/templates/with-admin-ui/src/admin/next.config.mjs +3 -0
- package/dist/templates/with-admin-ui/src/admin/tsconfig.json +20 -0
- package/dist/templates/with-admin-ui/src/index.ts.tmpl +26 -0
- package/dist/templates/with-admin-ui/src/install.ts.tmpl +37 -0
- package/dist/templates/with-admin-ui/src/webhooks.ts.tmpl +57 -0
- package/dist/templates/with-admin-ui/tsconfig.json +17 -0
- package/package.json +78 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/commands/app/create.ts","../../../src/lib/errors.ts","../../../src/lib/ratio-command.ts","../../../src/lib/render-error.ts","../../../src/lib/verbose-flag.ts","../../../src/scaffold/render.ts"],"sourcesContent":["import { existsSync, readFileSync, statSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { Args, Flags } from '@oclif/core';\nimport {\n RatioCLIError,\n TemplateExistsError,\n ValidationError,\n} from '../../lib/errors.js';\nimport { RatioCommand } from '../../lib/ratio-command.js';\nimport { jsonFlag, verboseFlag } from '../../lib/verbose-flag.js';\nimport { RatioTemplateError, renderTemplate } from '../../scaffold/index.js';\nimport type { RenderInput, RenderResult } from '../../scaffold/index.js';\n\n/** The bundled templates a new app can be scaffolded from. */\nexport const TEMPLATE_OPTIONS = ['minimal', 'with-admin-ui', 'serverless'] as const;\n\nexport type TemplateName = (typeof TEMPLATE_OPTIONS)[number];\n\n/**\n * Placeholder used in `nextSteps()` output for the app directory name.\n * Callers substitute it via `formatNextSteps()` before display.\n */\nexport const APP_NAME_PLACEHOLDER = '<APP_NAME>';\n\n/**\n * The 6-step post-scaffold guidance per template. Hardcoded on purpose\n * (rather than parsed out of the rendered README) so the CLI output stays\n * stable; a regression pin keeps the order in sync with each template's\n * README \"Getting started\" block.\n */\nexport function nextSteps(template: TemplateName): readonly string[] {\n const common = {\n cd: `cd ${APP_NAME_PLACEHOLDER}`,\n install: 'npm install',\n config:\n \"Open ratio.config.jsonc and paste the client id (or run 'ratio app link').\",\n endpoints:\n \"Point your merchant test store at the /oauth/callback and /webhooks endpoints (use 'ratio dev listen' for local testing).\",\n };\n if (template === 'minimal') {\n return [\n common.cd,\n common.install,\n common.config,\n 'Copy .env.example to .env and fill RATIO_CLIENT_SECRET + RATIO_WEBHOOK_SECRET.',\n 'Run npm run dev to start the local server on port 3000.',\n common.endpoints,\n ];\n }\n if (template === 'with-admin-ui') {\n return [\n common.cd,\n common.install,\n common.config,\n 'Copy .env.example to .env and fill RATIO_CLIENT_SECRET + RATIO_WEBHOOK_SECRET.',\n 'Run npm run dev to start the API server on port 3000, and npm run admin:dev to start the admin UI on http://localhost:3001.',\n common.endpoints,\n ];\n }\n return [\n common.cd,\n common.install,\n common.config,\n \"Set your secrets with 'wrangler secret put RATIO_CLIENT_SECRET' and 'wrangler secret put RATIO_WEBHOOK_SECRET'.\",\n 'Run npm run dev to start wrangler dev locally, then npm run deploy when ready.',\n \"Point your merchant test store at your workers.dev route's /oauth/callback and /webhooks endpoints.\",\n ];\n}\n\n/** `nextSteps()` with the app-name placeholder substituted for display. */\nexport function formatNextSteps(\n template: TemplateName,\n appName: string,\n): readonly string[] {\n return nextSteps(template).map((step) =>\n step.replace(APP_NAME_PLACEHOLDER, appName),\n );\n}\n\n/**\n * Read the `@ratio-app/sdk` version pin from the CLI's own package.json,\n * discovered by walking up from this module. Works from both the source\n * tree (`src/commands/app/`) and the built layout (`dist/commands/app/`,\n * where package.json lives at the package root, not inside dist/).\n */\nexport function readOwnSdkVersion(\n startDir: string = dirname(fileURLToPath(import.meta.url)),\n): string {\n let dir = startDir;\n for (;;) {\n const candidate = join(dir, 'package.json');\n if (existsSync(candidate)) {\n const parsed = JSON.parse(readFileSync(candidate, 'utf8')) as {\n dependencies?: Record<string, string>;\n };\n const version = parsed.dependencies?.['@ratio-app/sdk'];\n if (typeof version === 'string' && version.length > 0) {\n return version;\n }\n }\n const parent = dirname(dir);\n if (parent === dir) {\n throw new Error(\n 'Could not locate the CLI package.json with an @ratio-app/sdk dependency.',\n );\n }\n dir = parent;\n }\n}\n\n/**\n * Locate the bundled templates root by walking up from this module and\n * probing for a sibling `templates/` directory at each level. Resolves to\n * `dist/templates/` when running the built CLI and `src/templates/` when\n * running from source. The walk stops at the first directory that carries\n * a package.json (the package root) so it never escapes the install tree.\n */\nexport function discoverTemplatesRoot(\n startDir: string = dirname(fileURLToPath(import.meta.url)),\n): string {\n let dir = startDir;\n for (;;) {\n const candidate = join(dir, 'templates');\n if (existsSync(candidate) && statSync(candidate).isDirectory()) {\n return candidate;\n }\n const atPackageRoot = existsSync(join(dir, 'package.json'));\n const parent = dirname(dir);\n if (atPackageRoot || parent === dir) {\n throw new Error(\n `Could not locate the bundled templates directory (walked up from ${startDir}).`,\n );\n }\n dir = parent;\n }\n}\n\nexport default class AppCreate extends RatioCommand {\n static override description = 'Create a new Ratio app from a template.';\n\n static override examples = [\n '<%= config.bin %> app create my-app',\n '<%= config.bin %> app create my-app --template=serverless',\n '<%= config.bin %> app create my-app --json',\n ];\n\n static override args = {\n name: Args.string({\n name: 'name',\n required: true,\n description: 'Directory name for the new Ratio app.',\n }),\n };\n\n static override flags = {\n ...verboseFlag,\n ...jsonFlag,\n template: Flags.string({\n options: [...TEMPLATE_OPTIONS],\n default: 'minimal',\n description: 'Template to scaffold from.',\n }),\n };\n\n // ── Seams: overridable in tests; defaults hit the real process. ──────────\n public writeOut: (chunk: string) => void = (chunk) => {\n process.stdout.write(chunk);\n };\n\n public writeErr: (chunk: string) => void = (chunk) => {\n process.stderr.write(chunk);\n };\n\n public renderTemplate: (input: RenderInput) => Promise<RenderResult> =\n renderTemplate;\n\n public async run(): Promise<void> {\n const { args, flags } = await this.parse(AppCreate);\n const jsonMode = flags.json === true;\n const verbose = flags.verbose === true;\n const template = flags.template as TemplateName;\n const trace = (message: string): void => {\n if (verbose) {\n this.writeErr(`${message}\\n`);\n }\n };\n\n const destinationDir = resolve(process.cwd(), args.name);\n const sdkVersion = readOwnSdkVersion();\n trace(`template: ${template}`);\n trace(`destination: ${destinationDir}`);\n\n const substitutions = {\n appName: args.name,\n // Intentionally empty — `ratio app link` fills it later.\n clientId: '',\n sdkVersion,\n templateName: template,\n };\n\n let result: RenderResult;\n const started = Date.now();\n try {\n const input: RenderInput = {\n templateName: template,\n destinationDir,\n substitutions,\n // The built CLI bundles this command with its own copy of the\n // engine, so the engine's dist-relative discovery does not apply\n // here; the command resolves the templates root itself.\n sourceRootOverride: discoverTemplatesRoot(),\n ...(verbose\n ? {\n onFile: (relativePath: string): void => {\n this.writeErr(`wrote ${relativePath}\\n`);\n },\n }\n : {}),\n };\n result = await this.renderTemplate(input);\n } catch (err) {\n if (err instanceof RatioTemplateError) {\n if (err.code === 'destination_not_empty') {\n throw new TemplateExistsError(`Directory '${args.name}' is not empty.`);\n }\n if (err.code === 'template_not_found') {\n throw new ValidationError(\n `Unknown template '${template}'.`,\n 'Use --template=minimal|with-admin-ui|serverless.',\n );\n }\n // unknown_substitution_key / missing_substitution_value / io_error —\n // template drift or local I/O trouble; surface without swallowing.\n throw new RatioCLIError({\n code: 'TEMPLATE_IO',\n exitCode: 1,\n message: err.message,\n });\n }\n throw err;\n }\n trace(`rendered in ${Date.now() - started}ms`);\n\n const steps = formatNextSteps(template, args.name);\n if (jsonMode) {\n this.writeOut(\n `${JSON.stringify({\n ok: true,\n data: {\n template,\n path: destinationDir,\n filesWritten: result.filesWritten.length,\n next: steps,\n },\n })}\\n`,\n );\n return;\n }\n\n const numbered = steps.map((step, i) => ` ${i + 1}. ${step}`).join('\\n');\n this.writeOut(\n `Created ${args.name} from the ${template} template ` +\n `(${result.filesWritten.length} files, ${result.bytesWritten} bytes).\\n` +\n `\\nNext steps:\\n${numbered}\\n`,\n );\n }\n}\n","/**\n * Error hierarchy for the Ratio CLI. Every error thrown from a command's\n * run() is expected to be an instance of RatioCLIError so the global\n * render-error handler can map it to a deterministic stderr line and exit\n * code. Uncaught Error subclasses render as \"INTERNAL: <msg>\" with exit 1.\n *\n * No import from @oclif/core — this file is dependency-free so tests\n * can import it without oclif overhead.\n */\n\nexport class RatioCLIError extends Error {\n readonly code: string;\n readonly exitCode: number;\n readonly hint?: string;\n readonly docsUrl?: string;\n\n constructor(opts: {\n code: string;\n exitCode: number;\n message: string;\n hint?: string;\n docsUrl?: string;\n }) {\n super(opts.message);\n this.name = new.target.name;\n this.code = opts.code;\n this.exitCode = opts.exitCode;\n if (opts.hint !== undefined) this.hint = opts.hint;\n if (opts.docsUrl !== undefined) this.docsUrl = opts.docsUrl;\n }\n}\n\n/** Coming-soon stub UX. Renders on stdout with exit 0. */\nexport class ComingSoonError extends RatioCLIError {\n constructor(message: string) {\n super({ code: 'COMING_SOON', exitCode: 0, message });\n }\n}\n\n/** User has no credentials and command requires auth. */\nexport class NotLoggedInError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Run 'ratio auth login' to sign in.\",\n ) {\n super({ code: 'NOT_LOGGED_IN', exitCode: 3, message, hint });\n }\n}\n\nexport class TokenExpiredError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Run 'ratio auth login' to refresh your session.\",\n ) {\n super({ code: 'TOKEN_EXPIRED', exitCode: 3, message, hint });\n }\n}\n\nexport class ScopeDeniedError extends RatioCLIError {\n constructor(message: string, hint?: string) {\n const opts: { code: string; exitCode: number; message: string; hint?: string } = {\n code: 'SCOPE_DENIED',\n exitCode: 4,\n message,\n };\n if (hint !== undefined) opts.hint = hint;\n super(opts);\n }\n}\n\nexport class NetworkError extends RatioCLIError {\n constructor(message: string, hint: string = 'Check your network and retry.') {\n super({ code: 'NETWORK_ERROR', exitCode: 5, message, hint });\n }\n}\n\nexport class RelayConnectError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Verify the tunnel URL and that the auth-server is reachable.',\n ) {\n super({ code: 'RELAY_CONNECT', exitCode: 6, message, hint });\n }\n}\n\nexport class TemplateExistsError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Choose a different name or remove the existing directory.',\n ) {\n super({ code: 'TEMPLATE_EXISTS', exitCode: 7, message, hint });\n }\n}\n\nexport class ValidationError extends RatioCLIError {\n constructor(message: string, hint?: string) {\n const opts: { code: string; exitCode: number; message: string; hint?: string } = {\n code: 'VALIDATION_ERROR',\n exitCode: 8,\n message,\n };\n if (hint !== undefined) opts.hint = hint;\n super(opts);\n }\n}\n\nexport class RelayResumeGapError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Reconnect from the current cursor; older events are outside the buffer window.',\n ) {\n super({ code: 'RELAY_RESUME_GAP', exitCode: 9, message, hint });\n }\n}\n\nexport class OrchestratorSpawnError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Could not start your app's dev command — check that the command in ratio.config.jsonc runs from this directory.\",\n ) {\n super({ code: 'ORCHESTRATOR_SPAWN', exitCode: 10, message, hint });\n }\n}\n\nexport class OrchestratorDrainTimeoutError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Your dev command did not exit cleanly within 5 seconds — it was forcibly terminated.',\n ) {\n super({ code: 'ORCHESTRATOR_DRAIN', exitCode: 11, message, hint });\n }\n}\n\nexport function isRatioCLIError(err: unknown): err is RatioCLIError {\n return err instanceof RatioCLIError;\n}\n","import { Command, Errors } from '@oclif/core';\nimport { renderError } from './render-error.js';\n\n/**\n * Base class for all Ratio CLI commands.\n *\n * Overrides catch() to intercept every error thrown from run(), render it\n * per the Ratio CLI error-UX spec (typed hierarchy -> structured stderr/stdout,\n * --verbose for stack traces, --json for machine-readable envelope), and then\n * suppress oclif's own double-render by setting skipOclifErrorHandling on the\n * re-thrown error.\n *\n * Path B: base-class override — no oclif.hooks wiring needed.\n * oclif v4 does not expose a command_error hook; the reliable interception\n * point is Command.catch(), which is called by Command._run() before the\n * error bubbles up to oclif's global handle() function.\n *\n * Double-render prevention:\n * oclif's handle() checks err.skipOclifErrorHandling before printing.\n * After rendering we set skipOclifErrorHandling = true so oclif's\n * pretty-print is skipped. We also set err.oclif.exit = N so handle()\n * calls process.exit(N) with the correct code.\n *\n * Exit-code routing:\n * - exitCode === 0 (ComingSoonError): throw ExitError(0) so oclif calls\n * process.exit(0) cleanly. ExitError has code 'EEXIT' and is treated as\n * a clean exit — shouldPrint is false in handle().\n * - exitCode > 0: mark err.skipOclifErrorHandling = true and set\n * err.oclif = { exit: N }, then re-throw so handle() exits N.\n *\n * Verbose/json detection strategy:\n * We scan this.argv directly. this.argv is set by oclif's Command before\n * run() is called and always contains the raw argument list. This approach\n * works both for:\n * (a) post-parse errors (thrown after this.parse() succeeds) — argv has the\n * flags that were successfully recognised.\n * (b) pre-parse errors (thrown BY this.parse(), e.g. unknown flag) — argv\n * still contains --verbose / --json even though parse() threw.\n * Simple string scan is sufficient since --verbose and --json are boolean\n * flags with no value argument.\n */\nexport abstract class RatioCommand extends Command {\n private _getRenderFlags(): { verbose: boolean; json: boolean } {\n const argv = this.argv as string[];\n return {\n verbose: argv.includes('--verbose'),\n json: argv.includes('--json'),\n };\n }\n\n override async catch(\n err: Error & {\n exitCode?: number;\n oclif?: { exit?: number };\n skipOclifErrorHandling?: boolean;\n },\n ): Promise<never> {\n const { verbose, json } = this._getRenderFlags();\n\n const output = renderError({ err, verbose, json });\n\n // Write rendered output before any exit/throw\n if (output.stdout) process.stdout.write(output.stdout);\n if (output.stderr) process.stderr.write(output.stderr);\n\n if (output.exitCode === 0) {\n // ComingSoonError path — ExitError(0) causes oclif to call process.exit(0)\n // cleanly. ExitError has code 'EEXIT'; handle() treats it as a clean exit\n // (shouldPrint is false) so no additional output is written.\n throw new Errors.ExitError(0);\n }\n\n // For non-zero exits: suppress oclif's pretty-print by marking the error,\n // then route the correct exit code via oclif.exit so handle() calls process.exit(N).\n err.skipOclifErrorHandling = true;\n err.oclif = { exit: output.exitCode };\n throw err;\n }\n}\n","import { isRatioCLIError, RatioCLIError, ValidationError } from './errors.js';\n\nexport interface RenderInput {\n err: unknown;\n verbose: boolean;\n json: boolean;\n}\n\nexport interface RenderOutput {\n /** Text to write to stdout. Empty string means nothing to write. */\n stdout: string;\n /** Text to write to stderr. Empty string means nothing to write. */\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Detects whether an error originates from the oclif argument/flag parser.\n * oclif's CLIParseError subclasses (NonExistentFlagsError, RequiredArgsError,\n * etc.) all carry a `parse` property set by CLIParseErrorOptions. This is the\n * stable structural signal — more reliable than checking the class name string.\n */\nfunction isOclifParserError(err: unknown): err is Error & { parse: unknown } {\n return (\n err instanceof Error &&\n 'parse' in err &&\n err.parse !== null &&\n err.parse !== undefined\n );\n}\n\n/**\n * Builds the JSON error envelope. Fields with undefined values are omitted\n * so the consumer never sees `\"hint\": null` or `\"docsUrl\": null`.\n */\nfunction buildErrorEnvelope(\n code: string,\n message: string,\n hint?: string,\n docsUrl?: string,\n): string {\n const obj: Record<string, unknown> = { ok: false, code, message };\n if (hint !== undefined) obj['hint'] = hint;\n if (docsUrl !== undefined) obj['docsUrl'] = docsUrl;\n return JSON.stringify(obj) + '\\n';\n}\n\n/**\n * Pure rendering function — no side effects, no process.exit, no I/O.\n * The caller (RatioCommand.catch) is responsible for writing output and exiting.\n *\n * Rendering rules (all branches):\n *\n * | Error shape | --json | --verbose | stdout | stderr | exit |\n * |----------------------|--------|-----------|--------------------------------|----------------------------------|------|\n * | ComingSoonError | false | any | err.message + '\\n' | '' | 0 |\n * | ComingSoonError | true | any | '{\"ok\":true,\"data\":null}\\n' | '' | 0 |\n * | RatioCLIError >0 | false | false | '' | code: msg [+ hint] [+ docsUrl] | N |\n * | RatioCLIError >0 | false | true | '' | above + stack | N |\n * | RatioCLIError >0 | true | false | JSON envelope | '' | N |\n * | RatioCLIError >0 | true | true | JSON envelope | stack only | N |\n * | oclif ParserError | false | any | '' | VALIDATION_ERROR: msg [+stack] | 8 |\n * | oclif ParserError | true | any | JSON VALIDATION_ERROR envelope | stack if verbose | 8 |\n * | Plain Error / other | false | false | '' | INTERNAL: msg\\n | 1 |\n * | Plain Error / other | false | true | '' | INTERNAL: msg\\n + stack\\n | 1 |\n * | Plain Error / other | true | any | JSON INTERNAL envelope | stack if verbose | 1 |\n */\nexport function renderError(input: RenderInput): RenderOutput {\n const { err, verbose, json } = input;\n\n // ── Branch 1: our own typed hierarchy ───────────────────────────────────\n if (isRatioCLIError(err)) {\n const ratioErr = err as RatioCLIError;\n\n // exitCode === 0 → coming-soon: render on stdout, no stderr, exit 0\n if (ratioErr.exitCode === 0) {\n if (json) {\n return {\n stdout: JSON.stringify({ ok: true, data: null }) + '\\n',\n stderr: '',\n exitCode: 0,\n };\n }\n return {\n stdout: ratioErr.message + '\\n',\n stderr: '',\n exitCode: 0,\n };\n }\n\n // exitCode > 0 → error render\n let stderrLines = `${ratioErr.code}: ${ratioErr.message}\\n`;\n if (ratioErr.hint !== undefined) stderrLines += `${ratioErr.hint}\\n`;\n if (ratioErr.docsUrl !== undefined) stderrLines += `${ratioErr.docsUrl}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope(ratioErr.code, ratioErr.message, ratioErr.hint, ratioErr.docsUrl),\n stderr: verbose && ratioErr.stack ? ratioErr.stack + '\\n' : '',\n exitCode: ratioErr.exitCode,\n };\n }\n\n if (verbose && ratioErr.stack) {\n stderrLines += ratioErr.stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrLines, exitCode: ratioErr.exitCode };\n }\n\n // ── Branch 2: oclif parser error (unknown flag, missing required arg, etc.) ──\n if (isOclifParserError(err)) {\n const parserErr = err as Error & { parse: unknown };\n const msg = parserErr.message || 'Unknown parse error';\n\n // Strip oclif's \"Unexpected argument: --foo\" or similar prefix noise where\n // possible, but keep the core message as-is since it's already human-readable.\n const coercedErr = new ValidationError(msg);\n\n let stderrText = `${coercedErr.code}: ${msg}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope(coercedErr.code, msg),\n stderr: verbose && parserErr.stack ? parserErr.stack + '\\n' : '',\n exitCode: 8,\n };\n }\n\n if (verbose && parserErr.stack) {\n stderrText += parserErr.stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrText, exitCode: 8 };\n }\n\n // ── Branch 3: plain Error or non-Error throw ─────────────────────────────\n const message = err instanceof Error ? err.message : String(err);\n const stack = err instanceof Error ? err.stack : undefined;\n const stderrPrefix = `INTERNAL: ${message}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope('INTERNAL', message),\n stderr: verbose && stack ? stack + '\\n' : '',\n exitCode: 1,\n };\n }\n\n let stderrText = stderrPrefix;\n if (verbose && stack) {\n stderrText += stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrText, exitCode: 1 };\n}\n","import { Flags } from '@oclif/core';\n\n/** Global verbose flag — every command imports and spreads this. */\nexport const verboseFlag = {\n verbose: Flags.boolean({\n description:\n 'Show diagnostic detail: HTTP status/timing lines for auth requests (stderr) and stack traces for internal errors.',\n }),\n};\n\n/** JSON envelope flag — read-style commands (whoami, dev trigger --list) import. */\nexport const jsonFlag = {\n json: Flags.boolean({\n description: 'Emit machine-readable JSON on stdout.',\n }),\n};\n","/**\n * Template scaffold engine for `ratio app create`.\n *\n * Renders a bundled app template into a destination directory:\n *\n * - Files ending in `.tmpl` are read as UTF-8 and every `{{key}}`\n * placeholder is substituted from the provided substitution map,\n * then written with the `.tmpl` suffix stripped\n * (`package.json.tmpl` → `package.json`).\n * - All other files are copied byte-for-byte.\n *\n * The engine is intentionally minimal: a single-pass\n * `String.replace(/\\{\\{key\\}\\}/g, …)` with a replacer FUNCTION (so\n * `$&`, `$1`, `$$` in substitution values are never interpreted as\n * special replacement patterns). There is no template library and no\n * recursive substitution — if a substitution value itself contains\n * `{{...}}`, that literal text appears in the output untouched.\n *\n * Every template root carries a `manifest.json`:\n *\n * ```json\n * {\n * \"name\": \"minimal\",\n * \"description\": \"Express-based Ratio app (default).\",\n * \"substitutionKeys\": [\"appName\", \"clientId\", \"sdkVersion\", \"templateName\"]\n * }\n * ```\n *\n * `substitutionKeys` is the closed set of placeholders the template's\n * `.tmpl` files may use. A `{{key}}` found in a `.tmpl` file that is\n * not declared in the manifest is a render-time error\n * (`unknown_substitution_key`). The `manifest.json` itself is copied\n * verbatim into the generated app so tooling can re-inspect which\n * template produced it.\n *\n * The canonical `ratio.config.jsonc` produced by every template has\n * this shape (the scaffolder writes it; `ratio app link` later fills\n * `clientId`):\n *\n * ```jsonc\n * {\n * // Canonical Ratio app manifest. Committed to the app repo.\n * // Written by scaffolder; `clientId` filled by `ratio app link`.\n * \"$schema\": \"https://ratio.app/schema/app-manifest.v1.json\",\n * \"appName\": \"{{appName}}\",\n * \"clientId\": \"{{clientId}}\",\n * \"sdkVersion\": \"{{sdkVersion}}\",\n * \"template\": \"{{templateName}}\",\n * \"webhooks\": {\n * \"path\": \"/webhooks\",\n * \"topics\": []\n * },\n * \"oauth\": {\n * \"redirectPath\": \"/oauth/callback\",\n * \"scopes\": []\n * }\n * }\n * ```\n *\n * The engine does not hardcode that shape — it only substitutes keys —\n * but the block above documents the contract for template authors.\n */\n\nimport { copyFile, mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Per-template metadata, loaded from `<templateRoot>/manifest.json`. */\nexport interface TemplateManifest {\n /** Template identifier, e.g. 'minimal' | 'with-admin-ui' | 'serverless'. */\n readonly name: string;\n readonly description: string;\n /** Closed set of `{{key}}` placeholders the template's `.tmpl` files may use. */\n readonly substitutionKeys: readonly string[];\n}\n\nexport interface RenderInput {\n /** Must match one of the discovered templates. */\n readonly templateName: string;\n /** Absolute path to write into. Must be empty (or not yet exist). */\n readonly destinationDir: string;\n readonly substitutions: Readonly<Record<string, string>>;\n /** Overrides template-root discovery (test injection). */\n readonly sourceRootOverride?: string;\n /** Called once per file AFTER a successful write. Bytes = per-file, NOT cumulative. */\n readonly onFile?: (relativePath: string, bytesWritten: number) => void;\n}\n\nexport interface RenderResult {\n /** Absolute paths, in write order. */\n readonly filesWritten: readonly string[];\n readonly bytesWritten: number;\n}\n\nexport type RatioTemplateErrorCode =\n | 'template_not_found'\n | 'unknown_substitution_key'\n | 'missing_substitution_value'\n | 'destination_not_empty'\n | 'io_error';\n\n/** Error surface for the scaffold engine. All failures carry a stable `code`. */\nexport class RatioTemplateError extends Error {\n readonly code: RatioTemplateErrorCode;\n readonly templateName?: string;\n readonly key?: string;\n\n constructor(opts: {\n readonly code: RatioTemplateErrorCode;\n readonly message: string;\n readonly templateName?: string;\n readonly key?: string;\n readonly cause?: unknown;\n }) {\n super(opts.message, opts.cause === undefined ? undefined : { cause: opts.cause });\n this.name = 'RatioTemplateError';\n this.code = opts.code;\n if (opts.templateName !== undefined) {\n this.templateName = opts.templateName;\n }\n if (opts.key !== undefined) {\n this.key = opts.key;\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Private: placeholder syntax\n// ---------------------------------------------------------------------------\n\n/** `{{key}}` where key is a leading-alpha identifier. Single pass, global. */\nconst PLACEHOLDER_REGEX = /\\{\\{([a-zA-Z][a-zA-Z0-9_]*)\\}\\}/g;\n\nconst TMPL_SUFFIX = '.tmpl';\n\n// ---------------------------------------------------------------------------\n// Private: source-root discovery\n// ---------------------------------------------------------------------------\n\nasync function isDirectory(path: string): Promise<boolean> {\n try {\n return (await stat(path)).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Locate the templates root. Order:\n *\n * 1. `sourceRootOverride` when provided (test injection).\n * 2. `templates/` next to the compiled module (production —\n * `dist/templates/`, copied there at build time).\n * 3. `templates/` one level up from the module dir (dev — this file\n * lives at `src/scaffold/`, templates at `src/templates/`).\n */\nasync function discoverSourceRoot(sourceRootOverride?: string): Promise<string> {\n if (sourceRootOverride !== undefined) {\n return sourceRootOverride;\n }\n const moduleDir = dirname(fileURLToPath(import.meta.url));\n const candidates = [join(moduleDir, 'templates'), resolve(moduleDir, '..', 'templates')];\n for (const candidate of candidates) {\n if (await isDirectory(candidate)) {\n return candidate;\n }\n }\n throw new RatioTemplateError({\n code: 'io_error',\n message: `templates root not found (looked in: ${candidates.join(', ')})`,\n });\n}\n\n// ---------------------------------------------------------------------------\n// Private: manifest loading\n// ---------------------------------------------------------------------------\n\nfunction assertManifestShape(raw: unknown, path: string): TemplateManifest {\n if (\n typeof raw !== 'object' ||\n raw === null ||\n typeof (raw as { name?: unknown }).name !== 'string' ||\n typeof (raw as { description?: unknown }).description !== 'string' ||\n !Array.isArray((raw as { substitutionKeys?: unknown }).substitutionKeys) ||\n !(raw as { substitutionKeys: unknown[] }).substitutionKeys.every(\n (k): k is string => typeof k === 'string',\n )\n ) {\n throw new RatioTemplateError({\n code: 'io_error',\n message: `invalid template manifest at ${path}: expected { name, description, substitutionKeys[] }`,\n });\n }\n const shaped = raw as { name: string; description: string; substitutionKeys: string[] };\n return {\n name: shaped.name,\n description: shaped.description,\n substitutionKeys: shaped.substitutionKeys,\n };\n}\n\nasync function loadManifest(templateRoot: string): Promise<TemplateManifest> {\n const manifestPath = join(templateRoot, 'manifest.json');\n let content: string;\n try {\n content = await readFile(manifestPath, 'utf8');\n } catch (err) {\n throw new RatioTemplateError({\n code: 'io_error',\n message: `failed to read template manifest at ${manifestPath}`,\n cause: err,\n });\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(content);\n } catch (err) {\n throw new RatioTemplateError({\n code: 'io_error',\n message: `template manifest at ${manifestPath} is not valid JSON`,\n cause: err,\n });\n }\n return assertManifestShape(parsed, manifestPath);\n}\n\n// ---------------------------------------------------------------------------\n// Private: substitution (single pass, replacer function — never a string)\n// ---------------------------------------------------------------------------\n\nfunction substituteContent(\n content: string,\n substitutions: Readonly<Record<string, string>>,\n allowedKeys: ReadonlySet<string>,\n templateName: string,\n): string {\n // Replacer FUNCTION: return values are inserted literally, so `$&`,\n // `$1`, `$$` in a substitution value are never expanded. Output is\n // NOT re-scanned — no recursive substitution.\n return content.replace(PLACEHOLDER_REGEX, (_match, key: string) => {\n if (!allowedKeys.has(key)) {\n throw new RatioTemplateError({\n code: 'unknown_substitution_key',\n message: `placeholder {{${key}}} is not declared in the template manifest's substitutionKeys`,\n templateName,\n key,\n });\n }\n const value = substitutions[key];\n if (value === undefined) {\n throw new RatioTemplateError({\n code: 'missing_substitution_value',\n message: `no substitution value provided for {{${key}}}`,\n templateName,\n key,\n });\n }\n return value;\n });\n}\n\n// ---------------------------------------------------------------------------\n// Private: tree walk (stable, sorted order)\n// ---------------------------------------------------------------------------\n\ninterface TemplateFile {\n /** Path relative to the template root, POSIX-joined by node:path. */\n readonly relativePath: string;\n readonly absolutePath: string;\n}\n\nasync function walkTemplateTree(root: string, relative = ''): Promise<TemplateFile[]> {\n const entries = await readdir(join(root, relative), { withFileTypes: true });\n entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));\n const files: TemplateFile[] = [];\n for (const entry of entries) {\n const rel = relative === '' ? entry.name : join(relative, entry.name);\n if (entry.isDirectory()) {\n files.push(...(await walkTemplateTree(root, rel)));\n } else {\n files.push({ relativePath: rel, absolutePath: join(root, rel) });\n }\n }\n return files;\n}\n\n// ---------------------------------------------------------------------------\n// Public: listTemplates\n// ---------------------------------------------------------------------------\n\n/**\n * Discover all bundled templates and return their manifests, sorted by\n * template directory name for a stable order.\n */\nexport async function listTemplates(\n sourceRootOverride?: string,\n): Promise<readonly TemplateManifest[]> {\n const sourceRoot = await discoverSourceRoot(sourceRootOverride);\n let entries;\n try {\n entries = await readdir(sourceRoot, { withFileTypes: true });\n } catch (err) {\n throw new RatioTemplateError({\n code: 'io_error',\n message: `failed to read templates root at ${sourceRoot}`,\n cause: err,\n });\n }\n const dirs = entries\n .filter((e) => e.isDirectory())\n .map((e) => e.name)\n .sort();\n const manifests: TemplateManifest[] = [];\n for (const dir of dirs) {\n manifests.push(await loadManifest(join(sourceRoot, dir)));\n }\n return manifests;\n}\n\n// ---------------------------------------------------------------------------\n// Public: renderTemplate\n// ---------------------------------------------------------------------------\n\n/**\n * Render the named template into `destinationDir`.\n *\n * Strict about its destination: if the directory already contains any\n * entry the render throws `destination_not_empty` — the caller owns\n * the \"directory already has content\" UX.\n */\nexport async function renderTemplate(input: RenderInput): Promise<RenderResult> {\n const sourceRoot = await discoverSourceRoot(input.sourceRootOverride);\n const templateRoot = join(sourceRoot, input.templateName);\n\n if (!(await isDirectory(templateRoot))) {\n throw new RatioTemplateError({\n code: 'template_not_found',\n message: `template '${input.templateName}' not found under ${sourceRoot}`,\n templateName: input.templateName,\n });\n }\n\n const manifest = await loadManifest(templateRoot);\n const allowedKeys = new Set(manifest.substitutionKeys);\n\n // Destination must be empty (or absent — created on demand).\n try {\n const existing = await readdir(input.destinationDir);\n if (existing.length > 0) {\n throw new RatioTemplateError({\n code: 'destination_not_empty',\n message: `destination directory ${input.destinationDir} is not empty`,\n templateName: input.templateName,\n });\n }\n } catch (err) {\n if (err instanceof RatioTemplateError) {\n throw err;\n }\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw new RatioTemplateError({\n code: 'io_error',\n message: `failed to inspect destination directory ${input.destinationDir}`,\n templateName: input.templateName,\n cause: err,\n });\n }\n }\n\n let files: TemplateFile[];\n try {\n files = await walkTemplateTree(templateRoot);\n } catch (err) {\n throw new RatioTemplateError({\n code: 'io_error',\n message: `failed to walk template tree at ${templateRoot}`,\n templateName: input.templateName,\n cause: err,\n });\n }\n\n const filesWritten: string[] = [];\n let bytesWritten = 0;\n\n for (const file of files) {\n const isTmpl = file.relativePath.endsWith(TMPL_SUFFIX);\n const outRelative = isTmpl\n ? file.relativePath.slice(0, -TMPL_SUFFIX.length)\n : file.relativePath;\n // npm-packlist strips `.gitignore` files from the published tarball, so\n // templates ship the file as `gitignore` (template ROOT only — no `/` in\n // the relative path) and the engine renames it back on render. This is a\n // single explicit special case, not a general dot-strip convention.\n const isTopLevelGitignore = file.relativePath === 'gitignore';\n const effectiveOutRelative = isTopLevelGitignore ? '.gitignore' : outRelative;\n const outPath = join(input.destinationDir, effectiveOutRelative);\n let perFileBytes = 0;\n\n if (isTmpl) {\n let raw: string;\n try {\n raw = await readFile(file.absolutePath, 'utf8');\n } catch (err) {\n throw new RatioTemplateError({\n code: 'io_error',\n message: `failed to read template file ${file.absolutePath}`,\n templateName: input.templateName,\n cause: err,\n });\n }\n // May throw unknown_substitution_key / missing_substitution_value —\n // deliberately NOT wrapped as io_error.\n const rendered = substituteContent(\n raw,\n input.substitutions,\n allowedKeys,\n input.templateName,\n );\n try {\n await mkdir(dirname(outPath), { recursive: true });\n await writeFile(outPath, rendered, 'utf8');\n } catch (err) {\n throw new RatioTemplateError({\n code: 'io_error',\n message: `failed to write rendered file ${outPath}`,\n templateName: input.templateName,\n cause: err,\n });\n }\n perFileBytes = Buffer.byteLength(rendered, 'utf8');\n bytesWritten += perFileBytes;\n } else {\n try {\n await mkdir(dirname(outPath), { recursive: true });\n await copyFile(file.absolutePath, outPath);\n perFileBytes = (await stat(outPath)).size;\n bytesWritten += perFileBytes;\n } catch (err) {\n throw new RatioTemplateError({\n code: 'io_error',\n message: `failed to copy file ${file.absolutePath} to ${outPath}`,\n templateName: input.templateName,\n cause: err,\n });\n }\n }\n filesWritten.push(outPath);\n if (input.onFile !== undefined) {\n try {\n input.onFile(effectiveOutRelative, perFileBytes);\n } catch (err) {\n throw new RatioTemplateError({\n code: 'io_error',\n message: 'onFile callback threw',\n templateName: input.templateName,\n cause: err,\n });\n }\n }\n }\n\n return { filesWritten, bytesWritten };\n}\n"],"mappings":"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","names":["dirname","join","resolve","fileURLToPath","Flags","stderrText","dirname","fileURLToPath","join","Flags","resolve"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as _oclif_core_interfaces from '@oclif/core/interfaces';
|
|
2
|
+
import { RatioCommand } from '../../lib/ratio-command.js';
|
|
3
|
+
import '@oclif/core';
|
|
4
|
+
|
|
5
|
+
declare class AppDeploy extends RatioCommand {
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
verbose: _oclif_core_interfaces.BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { AppDeploy as default };
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// src/lib/messages.ts
|
|
2
|
+
function comingSoonMessage(command) {
|
|
3
|
+
return `${command} \u2014 coming soon.
|
|
4
|
+
This command will be available in an upcoming Ratio CLI release.
|
|
5
|
+
Run 'ratio --help' after your next update to see what's new.`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/lib/ratio-command.ts
|
|
9
|
+
import { Command, Errors } from "@oclif/core";
|
|
10
|
+
|
|
11
|
+
// src/lib/errors.ts
|
|
12
|
+
var RatioCLIError = class extends Error {
|
|
13
|
+
code;
|
|
14
|
+
exitCode;
|
|
15
|
+
hint;
|
|
16
|
+
docsUrl;
|
|
17
|
+
constructor(opts) {
|
|
18
|
+
super(opts.message);
|
|
19
|
+
this.name = new.target.name;
|
|
20
|
+
this.code = opts.code;
|
|
21
|
+
this.exitCode = opts.exitCode;
|
|
22
|
+
if (opts.hint !== void 0) this.hint = opts.hint;
|
|
23
|
+
if (opts.docsUrl !== void 0) this.docsUrl = opts.docsUrl;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var ComingSoonError = class extends RatioCLIError {
|
|
27
|
+
constructor(message) {
|
|
28
|
+
super({ code: "COMING_SOON", exitCode: 0, message });
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var ValidationError = class extends RatioCLIError {
|
|
32
|
+
constructor(message, hint) {
|
|
33
|
+
const opts = {
|
|
34
|
+
code: "VALIDATION_ERROR",
|
|
35
|
+
exitCode: 8,
|
|
36
|
+
message
|
|
37
|
+
};
|
|
38
|
+
if (hint !== void 0) opts.hint = hint;
|
|
39
|
+
super(opts);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
function isRatioCLIError(err) {
|
|
43
|
+
return err instanceof RatioCLIError;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/lib/render-error.ts
|
|
47
|
+
function isOclifParserError(err) {
|
|
48
|
+
return err instanceof Error && "parse" in err && err.parse !== null && err.parse !== void 0;
|
|
49
|
+
}
|
|
50
|
+
function buildErrorEnvelope(code, message, hint, docsUrl) {
|
|
51
|
+
const obj = { ok: false, code, message };
|
|
52
|
+
if (hint !== void 0) obj["hint"] = hint;
|
|
53
|
+
if (docsUrl !== void 0) obj["docsUrl"] = docsUrl;
|
|
54
|
+
return JSON.stringify(obj) + "\n";
|
|
55
|
+
}
|
|
56
|
+
function renderError(input) {
|
|
57
|
+
const { err, verbose, json } = input;
|
|
58
|
+
if (isRatioCLIError(err)) {
|
|
59
|
+
const ratioErr = err;
|
|
60
|
+
if (ratioErr.exitCode === 0) {
|
|
61
|
+
if (json) {
|
|
62
|
+
return {
|
|
63
|
+
stdout: JSON.stringify({ ok: true, data: null }) + "\n",
|
|
64
|
+
stderr: "",
|
|
65
|
+
exitCode: 0
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
stdout: ratioErr.message + "\n",
|
|
70
|
+
stderr: "",
|
|
71
|
+
exitCode: 0
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
let stderrLines = `${ratioErr.code}: ${ratioErr.message}
|
|
75
|
+
`;
|
|
76
|
+
if (ratioErr.hint !== void 0) stderrLines += `${ratioErr.hint}
|
|
77
|
+
`;
|
|
78
|
+
if (ratioErr.docsUrl !== void 0) stderrLines += `${ratioErr.docsUrl}
|
|
79
|
+
`;
|
|
80
|
+
if (json) {
|
|
81
|
+
return {
|
|
82
|
+
stdout: buildErrorEnvelope(ratioErr.code, ratioErr.message, ratioErr.hint, ratioErr.docsUrl),
|
|
83
|
+
stderr: verbose && ratioErr.stack ? ratioErr.stack + "\n" : "",
|
|
84
|
+
exitCode: ratioErr.exitCode
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (verbose && ratioErr.stack) {
|
|
88
|
+
stderrLines += ratioErr.stack + "\n";
|
|
89
|
+
}
|
|
90
|
+
return { stdout: "", stderr: stderrLines, exitCode: ratioErr.exitCode };
|
|
91
|
+
}
|
|
92
|
+
if (isOclifParserError(err)) {
|
|
93
|
+
const parserErr = err;
|
|
94
|
+
const msg = parserErr.message || "Unknown parse error";
|
|
95
|
+
const coercedErr = new ValidationError(msg);
|
|
96
|
+
let stderrText2 = `${coercedErr.code}: ${msg}
|
|
97
|
+
`;
|
|
98
|
+
if (json) {
|
|
99
|
+
return {
|
|
100
|
+
stdout: buildErrorEnvelope(coercedErr.code, msg),
|
|
101
|
+
stderr: verbose && parserErr.stack ? parserErr.stack + "\n" : "",
|
|
102
|
+
exitCode: 8
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (verbose && parserErr.stack) {
|
|
106
|
+
stderrText2 += parserErr.stack + "\n";
|
|
107
|
+
}
|
|
108
|
+
return { stdout: "", stderr: stderrText2, exitCode: 8 };
|
|
109
|
+
}
|
|
110
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
111
|
+
const stack = err instanceof Error ? err.stack : void 0;
|
|
112
|
+
const stderrPrefix = `INTERNAL: ${message}
|
|
113
|
+
`;
|
|
114
|
+
if (json) {
|
|
115
|
+
return {
|
|
116
|
+
stdout: buildErrorEnvelope("INTERNAL", message),
|
|
117
|
+
stderr: verbose && stack ? stack + "\n" : "",
|
|
118
|
+
exitCode: 1
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
let stderrText = stderrPrefix;
|
|
122
|
+
if (verbose && stack) {
|
|
123
|
+
stderrText += stack + "\n";
|
|
124
|
+
}
|
|
125
|
+
return { stdout: "", stderr: stderrText, exitCode: 1 };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/lib/ratio-command.ts
|
|
129
|
+
var RatioCommand = class extends Command {
|
|
130
|
+
_getRenderFlags() {
|
|
131
|
+
const argv = this.argv;
|
|
132
|
+
return {
|
|
133
|
+
verbose: argv.includes("--verbose"),
|
|
134
|
+
json: argv.includes("--json")
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
async catch(err) {
|
|
138
|
+
const { verbose, json } = this._getRenderFlags();
|
|
139
|
+
const output = renderError({ err, verbose, json });
|
|
140
|
+
if (output.stdout) process.stdout.write(output.stdout);
|
|
141
|
+
if (output.stderr) process.stderr.write(output.stderr);
|
|
142
|
+
if (output.exitCode === 0) {
|
|
143
|
+
throw new Errors.ExitError(0);
|
|
144
|
+
}
|
|
145
|
+
err.skipOclifErrorHandling = true;
|
|
146
|
+
err.oclif = { exit: output.exitCode };
|
|
147
|
+
throw err;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// src/lib/verbose-flag.ts
|
|
152
|
+
import { Flags } from "@oclif/core";
|
|
153
|
+
var verboseFlag = {
|
|
154
|
+
verbose: Flags.boolean({
|
|
155
|
+
description: "Show diagnostic detail: HTTP status/timing lines for auth requests (stderr) and stack traces for internal errors."
|
|
156
|
+
})
|
|
157
|
+
};
|
|
158
|
+
var jsonFlag = {
|
|
159
|
+
json: Flags.boolean({
|
|
160
|
+
description: "Emit machine-readable JSON on stdout."
|
|
161
|
+
})
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// src/commands/app/deploy.ts
|
|
165
|
+
var AppDeploy = class _AppDeploy extends RatioCommand {
|
|
166
|
+
static description = "Deploy the current app. (Coming soon.)";
|
|
167
|
+
static examples = ["<%= config.bin %> app deploy"];
|
|
168
|
+
static flags = {
|
|
169
|
+
...verboseFlag
|
|
170
|
+
};
|
|
171
|
+
async run() {
|
|
172
|
+
await this.parse(_AppDeploy);
|
|
173
|
+
throw new ComingSoonError(comingSoonMessage("ratio app deploy"));
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
export {
|
|
177
|
+
AppDeploy as default
|
|
178
|
+
};
|
|
179
|
+
//# sourceMappingURL=deploy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/messages.ts","../../../src/lib/ratio-command.ts","../../../src/lib/errors.ts","../../../src/lib/render-error.ts","../../../src/lib/verbose-flag.ts","../../../src/commands/app/deploy.ts"],"sourcesContent":["/**\n * User-facing placeholder-message helpers used by every command\n * whose real behaviour is not yet available. Kept in one module so\n * wording stays consistent and a future edit is one place.\n */\n\n/** Message for command classes registered as \"Coming soon\" stubs. */\nexport function comingSoonMessage(command: string): string {\n return (\n `${command} — coming soon.\\n` +\n `This command will be available in an upcoming Ratio CLI release.\\n` +\n `Run 'ratio --help' after your next update to see what's new.`\n );\n}\n\n/**\n * Message for real command surfaces whose behaviour is being built.\n * @param command display name, e.g. 'ratio auth login'\n * @param hint one short sentence naming what's being finalised\n */\nexport function notYetAvailableMessage(command: string, hint: string): string {\n return (\n `${command} — not yet available.\\n` +\n `${hint}\\n` +\n `Run 'ratio --help' after your next update to see when it lands.`\n );\n}\n","import { Command, Errors } from '@oclif/core';\nimport { renderError } from './render-error.js';\n\n/**\n * Base class for all Ratio CLI commands.\n *\n * Overrides catch() to intercept every error thrown from run(), render it\n * per the Ratio CLI error-UX spec (typed hierarchy -> structured stderr/stdout,\n * --verbose for stack traces, --json for machine-readable envelope), and then\n * suppress oclif's own double-render by setting skipOclifErrorHandling on the\n * re-thrown error.\n *\n * Path B: base-class override — no oclif.hooks wiring needed.\n * oclif v4 does not expose a command_error hook; the reliable interception\n * point is Command.catch(), which is called by Command._run() before the\n * error bubbles up to oclif's global handle() function.\n *\n * Double-render prevention:\n * oclif's handle() checks err.skipOclifErrorHandling before printing.\n * After rendering we set skipOclifErrorHandling = true so oclif's\n * pretty-print is skipped. We also set err.oclif.exit = N so handle()\n * calls process.exit(N) with the correct code.\n *\n * Exit-code routing:\n * - exitCode === 0 (ComingSoonError): throw ExitError(0) so oclif calls\n * process.exit(0) cleanly. ExitError has code 'EEXIT' and is treated as\n * a clean exit — shouldPrint is false in handle().\n * - exitCode > 0: mark err.skipOclifErrorHandling = true and set\n * err.oclif = { exit: N }, then re-throw so handle() exits N.\n *\n * Verbose/json detection strategy:\n * We scan this.argv directly. this.argv is set by oclif's Command before\n * run() is called and always contains the raw argument list. This approach\n * works both for:\n * (a) post-parse errors (thrown after this.parse() succeeds) — argv has the\n * flags that were successfully recognised.\n * (b) pre-parse errors (thrown BY this.parse(), e.g. unknown flag) — argv\n * still contains --verbose / --json even though parse() threw.\n * Simple string scan is sufficient since --verbose and --json are boolean\n * flags with no value argument.\n */\nexport abstract class RatioCommand extends Command {\n private _getRenderFlags(): { verbose: boolean; json: boolean } {\n const argv = this.argv as string[];\n return {\n verbose: argv.includes('--verbose'),\n json: argv.includes('--json'),\n };\n }\n\n override async catch(\n err: Error & {\n exitCode?: number;\n oclif?: { exit?: number };\n skipOclifErrorHandling?: boolean;\n },\n ): Promise<never> {\n const { verbose, json } = this._getRenderFlags();\n\n const output = renderError({ err, verbose, json });\n\n // Write rendered output before any exit/throw\n if (output.stdout) process.stdout.write(output.stdout);\n if (output.stderr) process.stderr.write(output.stderr);\n\n if (output.exitCode === 0) {\n // ComingSoonError path — ExitError(0) causes oclif to call process.exit(0)\n // cleanly. ExitError has code 'EEXIT'; handle() treats it as a clean exit\n // (shouldPrint is false) so no additional output is written.\n throw new Errors.ExitError(0);\n }\n\n // For non-zero exits: suppress oclif's pretty-print by marking the error,\n // then route the correct exit code via oclif.exit so handle() calls process.exit(N).\n err.skipOclifErrorHandling = true;\n err.oclif = { exit: output.exitCode };\n throw err;\n }\n}\n","/**\n * Error hierarchy for the Ratio CLI. Every error thrown from a command's\n * run() is expected to be an instance of RatioCLIError so the global\n * render-error handler can map it to a deterministic stderr line and exit\n * code. Uncaught Error subclasses render as \"INTERNAL: <msg>\" with exit 1.\n *\n * No import from @oclif/core — this file is dependency-free so tests\n * can import it without oclif overhead.\n */\n\nexport class RatioCLIError extends Error {\n readonly code: string;\n readonly exitCode: number;\n readonly hint?: string;\n readonly docsUrl?: string;\n\n constructor(opts: {\n code: string;\n exitCode: number;\n message: string;\n hint?: string;\n docsUrl?: string;\n }) {\n super(opts.message);\n this.name = new.target.name;\n this.code = opts.code;\n this.exitCode = opts.exitCode;\n if (opts.hint !== undefined) this.hint = opts.hint;\n if (opts.docsUrl !== undefined) this.docsUrl = opts.docsUrl;\n }\n}\n\n/** Coming-soon stub UX. Renders on stdout with exit 0. */\nexport class ComingSoonError extends RatioCLIError {\n constructor(message: string) {\n super({ code: 'COMING_SOON', exitCode: 0, message });\n }\n}\n\n/** User has no credentials and command requires auth. */\nexport class NotLoggedInError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Run 'ratio auth login' to sign in.\",\n ) {\n super({ code: 'NOT_LOGGED_IN', exitCode: 3, message, hint });\n }\n}\n\nexport class TokenExpiredError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Run 'ratio auth login' to refresh your session.\",\n ) {\n super({ code: 'TOKEN_EXPIRED', exitCode: 3, message, hint });\n }\n}\n\nexport class ScopeDeniedError extends RatioCLIError {\n constructor(message: string, hint?: string) {\n const opts: { code: string; exitCode: number; message: string; hint?: string } = {\n code: 'SCOPE_DENIED',\n exitCode: 4,\n message,\n };\n if (hint !== undefined) opts.hint = hint;\n super(opts);\n }\n}\n\nexport class NetworkError extends RatioCLIError {\n constructor(message: string, hint: string = 'Check your network and retry.') {\n super({ code: 'NETWORK_ERROR', exitCode: 5, message, hint });\n }\n}\n\nexport class RelayConnectError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Verify the tunnel URL and that the auth-server is reachable.',\n ) {\n super({ code: 'RELAY_CONNECT', exitCode: 6, message, hint });\n }\n}\n\nexport class TemplateExistsError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Choose a different name or remove the existing directory.',\n ) {\n super({ code: 'TEMPLATE_EXISTS', exitCode: 7, message, hint });\n }\n}\n\nexport class ValidationError extends RatioCLIError {\n constructor(message: string, hint?: string) {\n const opts: { code: string; exitCode: number; message: string; hint?: string } = {\n code: 'VALIDATION_ERROR',\n exitCode: 8,\n message,\n };\n if (hint !== undefined) opts.hint = hint;\n super(opts);\n }\n}\n\nexport class RelayResumeGapError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Reconnect from the current cursor; older events are outside the buffer window.',\n ) {\n super({ code: 'RELAY_RESUME_GAP', exitCode: 9, message, hint });\n }\n}\n\nexport class OrchestratorSpawnError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = \"Could not start your app's dev command — check that the command in ratio.config.jsonc runs from this directory.\",\n ) {\n super({ code: 'ORCHESTRATOR_SPAWN', exitCode: 10, message, hint });\n }\n}\n\nexport class OrchestratorDrainTimeoutError extends RatioCLIError {\n constructor(\n message: string,\n hint: string = 'Your dev command did not exit cleanly within 5 seconds — it was forcibly terminated.',\n ) {\n super({ code: 'ORCHESTRATOR_DRAIN', exitCode: 11, message, hint });\n }\n}\n\nexport function isRatioCLIError(err: unknown): err is RatioCLIError {\n return err instanceof RatioCLIError;\n}\n","import { isRatioCLIError, RatioCLIError, ValidationError } from './errors.js';\n\nexport interface RenderInput {\n err: unknown;\n verbose: boolean;\n json: boolean;\n}\n\nexport interface RenderOutput {\n /** Text to write to stdout. Empty string means nothing to write. */\n stdout: string;\n /** Text to write to stderr. Empty string means nothing to write. */\n stderr: string;\n exitCode: number;\n}\n\n/**\n * Detects whether an error originates from the oclif argument/flag parser.\n * oclif's CLIParseError subclasses (NonExistentFlagsError, RequiredArgsError,\n * etc.) all carry a `parse` property set by CLIParseErrorOptions. This is the\n * stable structural signal — more reliable than checking the class name string.\n */\nfunction isOclifParserError(err: unknown): err is Error & { parse: unknown } {\n return (\n err instanceof Error &&\n 'parse' in err &&\n err.parse !== null &&\n err.parse !== undefined\n );\n}\n\n/**\n * Builds the JSON error envelope. Fields with undefined values are omitted\n * so the consumer never sees `\"hint\": null` or `\"docsUrl\": null`.\n */\nfunction buildErrorEnvelope(\n code: string,\n message: string,\n hint?: string,\n docsUrl?: string,\n): string {\n const obj: Record<string, unknown> = { ok: false, code, message };\n if (hint !== undefined) obj['hint'] = hint;\n if (docsUrl !== undefined) obj['docsUrl'] = docsUrl;\n return JSON.stringify(obj) + '\\n';\n}\n\n/**\n * Pure rendering function — no side effects, no process.exit, no I/O.\n * The caller (RatioCommand.catch) is responsible for writing output and exiting.\n *\n * Rendering rules (all branches):\n *\n * | Error shape | --json | --verbose | stdout | stderr | exit |\n * |----------------------|--------|-----------|--------------------------------|----------------------------------|------|\n * | ComingSoonError | false | any | err.message + '\\n' | '' | 0 |\n * | ComingSoonError | true | any | '{\"ok\":true,\"data\":null}\\n' | '' | 0 |\n * | RatioCLIError >0 | false | false | '' | code: msg [+ hint] [+ docsUrl] | N |\n * | RatioCLIError >0 | false | true | '' | above + stack | N |\n * | RatioCLIError >0 | true | false | JSON envelope | '' | N |\n * | RatioCLIError >0 | true | true | JSON envelope | stack only | N |\n * | oclif ParserError | false | any | '' | VALIDATION_ERROR: msg [+stack] | 8 |\n * | oclif ParserError | true | any | JSON VALIDATION_ERROR envelope | stack if verbose | 8 |\n * | Plain Error / other | false | false | '' | INTERNAL: msg\\n | 1 |\n * | Plain Error / other | false | true | '' | INTERNAL: msg\\n + stack\\n | 1 |\n * | Plain Error / other | true | any | JSON INTERNAL envelope | stack if verbose | 1 |\n */\nexport function renderError(input: RenderInput): RenderOutput {\n const { err, verbose, json } = input;\n\n // ── Branch 1: our own typed hierarchy ───────────────────────────────────\n if (isRatioCLIError(err)) {\n const ratioErr = err as RatioCLIError;\n\n // exitCode === 0 → coming-soon: render on stdout, no stderr, exit 0\n if (ratioErr.exitCode === 0) {\n if (json) {\n return {\n stdout: JSON.stringify({ ok: true, data: null }) + '\\n',\n stderr: '',\n exitCode: 0,\n };\n }\n return {\n stdout: ratioErr.message + '\\n',\n stderr: '',\n exitCode: 0,\n };\n }\n\n // exitCode > 0 → error render\n let stderrLines = `${ratioErr.code}: ${ratioErr.message}\\n`;\n if (ratioErr.hint !== undefined) stderrLines += `${ratioErr.hint}\\n`;\n if (ratioErr.docsUrl !== undefined) stderrLines += `${ratioErr.docsUrl}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope(ratioErr.code, ratioErr.message, ratioErr.hint, ratioErr.docsUrl),\n stderr: verbose && ratioErr.stack ? ratioErr.stack + '\\n' : '',\n exitCode: ratioErr.exitCode,\n };\n }\n\n if (verbose && ratioErr.stack) {\n stderrLines += ratioErr.stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrLines, exitCode: ratioErr.exitCode };\n }\n\n // ── Branch 2: oclif parser error (unknown flag, missing required arg, etc.) ──\n if (isOclifParserError(err)) {\n const parserErr = err as Error & { parse: unknown };\n const msg = parserErr.message || 'Unknown parse error';\n\n // Strip oclif's \"Unexpected argument: --foo\" or similar prefix noise where\n // possible, but keep the core message as-is since it's already human-readable.\n const coercedErr = new ValidationError(msg);\n\n let stderrText = `${coercedErr.code}: ${msg}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope(coercedErr.code, msg),\n stderr: verbose && parserErr.stack ? parserErr.stack + '\\n' : '',\n exitCode: 8,\n };\n }\n\n if (verbose && parserErr.stack) {\n stderrText += parserErr.stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrText, exitCode: 8 };\n }\n\n // ── Branch 3: plain Error or non-Error throw ─────────────────────────────\n const message = err instanceof Error ? err.message : String(err);\n const stack = err instanceof Error ? err.stack : undefined;\n const stderrPrefix = `INTERNAL: ${message}\\n`;\n\n if (json) {\n return {\n stdout: buildErrorEnvelope('INTERNAL', message),\n stderr: verbose && stack ? stack + '\\n' : '',\n exitCode: 1,\n };\n }\n\n let stderrText = stderrPrefix;\n if (verbose && stack) {\n stderrText += stack + '\\n';\n }\n\n return { stdout: '', stderr: stderrText, exitCode: 1 };\n}\n","import { Flags } from '@oclif/core';\n\n/** Global verbose flag — every command imports and spreads this. */\nexport const verboseFlag = {\n verbose: Flags.boolean({\n description:\n 'Show diagnostic detail: HTTP status/timing lines for auth requests (stderr) and stack traces for internal errors.',\n }),\n};\n\n/** JSON envelope flag — read-style commands (whoami, dev trigger --list) import. */\nexport const jsonFlag = {\n json: Flags.boolean({\n description: 'Emit machine-readable JSON on stdout.',\n }),\n};\n","import { comingSoonMessage } from '../../lib/messages.js';\nimport { RatioCommand } from '../../lib/ratio-command.js';\nimport { verboseFlag } from '../../lib/verbose-flag.js';\nimport { ComingSoonError } from '../../lib/errors.js';\n\nexport default class AppDeploy extends RatioCommand {\n static override description = 'Deploy the current app. (Coming soon.)';\n\n static override examples = ['<%= config.bin %> app deploy'];\n\n static override flags = {\n ...verboseFlag,\n };\n\n public async run(): Promise<void> {\n await this.parse(AppDeploy);\n throw new ComingSoonError(comingSoonMessage('ratio app deploy'));\n }\n}\n"],"mappings":"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","names":["stderrText"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as _oclif_core_interfaces from '@oclif/core/interfaces';
|
|
2
|
+
import { SignalSource, SpawnFn } from '../../lib/orchestrator/child-runner.js';
|
|
3
|
+
import { RatioCommand } from '../../lib/ratio-command.js';
|
|
4
|
+
import '../../lib/preview/types.js';
|
|
5
|
+
import '@oclif/core';
|
|
6
|
+
|
|
7
|
+
declare class AppDev extends RatioCommand {
|
|
8
|
+
static description: string;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static flags: {
|
|
11
|
+
verbose: _oclif_core_interfaces.BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
writeOut: (chunk: string) => void;
|
|
14
|
+
writeErr: (chunk: string) => void;
|
|
15
|
+
signalSource: SignalSource;
|
|
16
|
+
hardExit: (code: number) => void;
|
|
17
|
+
getCwd: () => string;
|
|
18
|
+
envReader: () => NodeJS.ProcessEnv;
|
|
19
|
+
/** Test seam: spawner for the app child (real spawn when undefined). */
|
|
20
|
+
spawnApp: SpawnFn | undefined;
|
|
21
|
+
/** Test seam: spawner for the listener child (real spawn when undefined). */
|
|
22
|
+
spawnListen: SpawnFn | undefined;
|
|
23
|
+
/** Test seam: SIGTERM → SIGKILL grace override (default drain when undefined). */
|
|
24
|
+
drainMs: number | undefined;
|
|
25
|
+
run(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { AppDev as default };
|