@studiometa/forge-cli 0.5.1 → 0.5.3
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 +105 -1
- package/dist/cli.js +1204 -44
- package/dist/cli.js.map +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/servers/command.d.ts.map +1 -1
- package/dist/commands/servers/handlers.d.ts +5 -0
- package/dist/commands/servers/handlers.d.ts.map +1 -1
- package/dist/commands/servers/help.d.ts.map +1 -1
- package/dist/commands/sites/apply/apply.d.ts +61 -0
- package/dist/commands/sites/apply/apply.d.ts.map +1 -0
- package/dist/commands/sites/apply/handler.d.ts +8 -0
- package/dist/commands/sites/apply/handler.d.ts.map +1 -0
- package/dist/commands/sites/apply/nginx.d.ts +31 -0
- package/dist/commands/sites/apply/nginx.d.ts.map +1 -0
- package/dist/commands/sites/apply/ownership.d.ts +88 -0
- package/dist/commands/sites/apply/ownership.d.ts.map +1 -0
- package/dist/commands/sites/apply/plan.d.ts +147 -0
- package/dist/commands/sites/apply/plan.d.ts.map +1 -0
- package/dist/commands/sites/apply/site-file.d.ts +94 -0
- package/dist/commands/sites/apply/site-file.d.ts.map +1 -0
- package/dist/commands/sites/apply/test-fixtures.d.ts +104 -0
- package/dist/commands/sites/apply/test-fixtures.d.ts.map +1 -0
- package/dist/commands/sites/command.d.ts.map +1 -1
- package/dist/commands/sites/help.d.ts.map +1 -1
- package/dist/commands/ssh-keys/sync.d.ts.map +1 -1
- package/dist/context.d.ts +15 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/{error-handler-Adi84RcF.js → error-handler-s-BOJ1-h.js} +16 -4
- package/dist/error-handler-s-BOJ1-h.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/utils/args.d.ts.map +1 -1
- package/dist/utils/resolve.d.ts +19 -13
- package/dist/utils/resolve.d.ts.map +1 -1
- package/package.json +6 -4
- package/schemas/site.schema.json +149 -0
- package/dist/error-handler-Adi84RcF.js.map +0 -1
package/dist/context.d.ts
CHANGED
|
@@ -7,13 +7,27 @@
|
|
|
7
7
|
import type { HttpClient } from "@studiometa/forge-api";
|
|
8
8
|
import type { ExecutorContext } from "@studiometa/forge-core";
|
|
9
9
|
import { OutputFormatter } from "./output.ts";
|
|
10
|
+
/**
|
|
11
|
+
* Executor context of the CLI. It also tells the resolvers of --server and --site
|
|
12
|
+
* how to match names.
|
|
13
|
+
*/
|
|
14
|
+
export interface CliExecutorContext extends ExecutorContext {
|
|
15
|
+
/** Accept only a numeric ID or an exact name (`--exact` or `FORGE_EXACT_MATCH=1`). */
|
|
16
|
+
readonly exactMatch: boolean;
|
|
17
|
+
}
|
|
10
18
|
export interface CommandOptions {
|
|
11
19
|
[key: string]: string | boolean | string[] | undefined;
|
|
12
20
|
format?: string;
|
|
13
21
|
f?: string;
|
|
14
22
|
token?: string;
|
|
15
23
|
"no-color"?: boolean;
|
|
24
|
+
exact?: boolean;
|
|
16
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Whether --server and --site must match an exact name: the `--exact` flag or the
|
|
28
|
+
* `FORGE_EXACT_MATCH` environment variable (`1` or `true`) turns it on.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isExactMatch(options: CommandOptions, env?: Record<string, string | undefined>): boolean;
|
|
17
31
|
/**
|
|
18
32
|
* Context containing all dependencies needed by command handlers.
|
|
19
33
|
*/
|
|
@@ -23,7 +37,7 @@ export interface CommandContext {
|
|
|
23
37
|
/** Raw CLI options */
|
|
24
38
|
readonly options: CommandOptions;
|
|
25
39
|
/** Create an ExecutorContext from this command context */
|
|
26
|
-
createExecutorContext(token: string):
|
|
40
|
+
createExecutorContext(token: string): CliExecutorContext;
|
|
27
41
|
/** Resolve the API token or throw ConfigError */
|
|
28
42
|
getToken(): string;
|
|
29
43
|
}
|
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAO9D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,cAAc;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAO9D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,MAAM,WAAW,kBAAmB,SAAQ,eAAe;IACzD,sFAAsF;IACtF,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,OAAO,CAGT;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,sBAAsB;IACtB,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,0DAA0D;IAC1D,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,CAAC;IACzD,iDAAiD;IACjD,QAAQ,IAAI,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,cAAmB,GAAG,cAAc,CAkC1E;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE;IAC3C,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,UAAU,CAAC;CACzB,GAAG,cAAc,CA8BjB"}
|
|
@@ -319,17 +319,28 @@ var OutputFormatter = class {
|
|
|
319
319
|
//#endregion
|
|
320
320
|
//#region src/context.ts
|
|
321
321
|
/**
|
|
322
|
+
* Whether --server and --site must match an exact name: the `--exact` flag or the
|
|
323
|
+
* `FORGE_EXACT_MATCH` environment variable (`1` or `true`) turns it on.
|
|
324
|
+
*/
|
|
325
|
+
function isExactMatch(options, env = process.env) {
|
|
326
|
+
const value = env.FORGE_EXACT_MATCH?.trim().toLowerCase();
|
|
327
|
+
return options.exact === true || value === "1" || value === "true";
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
322
330
|
* Creates a command context from CLI options.
|
|
323
331
|
*/
|
|
324
332
|
function createContext(options = {}) {
|
|
325
333
|
const rawFormat = options.format ?? options.f ?? "human";
|
|
334
|
+
const formatter = new OutputFormatter(isOutputFormat(rawFormat) ? rawFormat : "human", options["no-color"] === true);
|
|
335
|
+
const exactMatch = isExactMatch(options);
|
|
326
336
|
return {
|
|
327
|
-
formatter
|
|
337
|
+
formatter,
|
|
328
338
|
options,
|
|
329
339
|
createExecutorContext(token) {
|
|
330
340
|
return {
|
|
331
341
|
client: new HttpClient({ token }),
|
|
332
|
-
organizationSlug: typeof options.org === "string" && options.org || getOrganizationSlug(createConfigStore$1()) || void 0
|
|
342
|
+
organizationSlug: typeof options.org === "string" && options.org || getOrganizationSlug(createConfigStore$1()) || void 0,
|
|
343
|
+
exactMatch
|
|
333
344
|
};
|
|
334
345
|
},
|
|
335
346
|
getToken() {
|
|
@@ -364,7 +375,8 @@ function createTestContext(overrides) {
|
|
|
364
375
|
if (!overrides.mockClient) throw new Error("createTestContext: provide mockClient to use createExecutorContext in tests");
|
|
365
376
|
return {
|
|
366
377
|
client: overrides.mockClient,
|
|
367
|
-
organizationSlug: "test-org"
|
|
378
|
+
organizationSlug: "test-org",
|
|
379
|
+
exactMatch: isExactMatch(opts, {})
|
|
368
380
|
};
|
|
369
381
|
},
|
|
370
382
|
getToken() {
|
|
@@ -440,4 +452,4 @@ function exitWithValidationError(field, usage, formatter) {
|
|
|
440
452
|
//#endregion
|
|
441
453
|
export { isOutputFormat as C, isCliError as S, setToken as _, runCommand as a, ConfigError as b, OutputFormatter as c, createConfigStore$1 as d, deleteToken as f, setOrganizationSlug as g, getToken$1 as h, handleError as i, colors as l, getOrganizationSlug as m, exitWithError as n, createContext as o, getConfig as p, exitWithValidationError as r, createTestContext as s, ExitCode as t, setColorEnabled as u, ApiError as v, ValidationError as x, CliError as y };
|
|
442
454
|
|
|
443
|
-
//# sourceMappingURL=error-handler-
|
|
455
|
+
//# sourceMappingURL=error-handler-s-BOJ1-h.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler-s-BOJ1-h.js","names":[],"sources":["../src/types.ts","../src/errors.ts","../src/config.ts","../src/utils/colors.ts","../src/output.ts","../src/context.ts","../src/error-handler.ts"],"sourcesContent":["// CLI-specific types for forge\n\nexport type OutputFormat = \"json\" | \"human\" | \"table\";\n\nexport function isOutputFormat(value: unknown): value is OutputFormat {\n return value === \"json\" || value === \"human\" || value === \"table\";\n}\n\nexport interface CliOptions {\n format?: OutputFormat;\n quiet?: boolean;\n noColor?: boolean;\n}\n","/**\n * Error types for forge.\n *\n * Provides specific error types for different failure scenarios with\n * recovery hints for both humans and AI agents.\n */\n\n/**\n * Base error class for all CLI errors.\n */\nexport abstract class CliError extends Error {\n abstract readonly code: string;\n abstract readonly isRecoverable: boolean;\n\n constructor(\n message: string,\n public readonly hints?: string[],\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = this.constructor.name;\n\n Error.captureStackTrace?.(this, this.constructor);\n }\n\n toJSON(): Record<string, unknown> {\n return {\n error: this.code,\n name: this.name,\n message: this.message,\n isRecoverable: this.isRecoverable,\n ...(this.hints && this.hints.length > 0 ? { hints: this.hints } : {}),\n ...(this.cause instanceof Error ? { cause: { message: this.cause.message } } : {}),\n };\n }\n\n toFormattedMessage(): string {\n let msg = this.message;\n if (this.hints && this.hints.length > 0) {\n msg += \"\\n\\nHints:\\n\" + this.hints.map((h) => ` • ${h}`).join(\"\\n\");\n }\n return msg;\n }\n}\n\n// ── Config Errors ────────────────────────────────────\n\nexport class ConfigError extends CliError {\n readonly code = \"CONFIG_ERROR\";\n readonly isRecoverable = true;\n\n static missingToken(): ConfigError {\n return new ConfigError(\"API token not configured\", [\n \"Set via CLI flag: --token <token>\",\n \"Set via environment: export FORGE_API_TOKEN=<token>\",\n \"Set via config: forge config set YOUR_TOKEN\",\n ]);\n }\n}\n\n// ── Validation Errors ────────────────────────────────\n\nexport class ValidationError extends CliError {\n readonly code = \"VALIDATION_ERROR\";\n readonly isRecoverable = true;\n\n constructor(\n message: string,\n public readonly field?: string,\n hints?: string[],\n cause?: unknown,\n ) {\n super(message, hints, cause);\n }\n\n override toJSON() {\n return { ...super.toJSON(), field: this.field };\n }\n\n static required(field: string, hints?: string[]): ValidationError {\n return new ValidationError(`${field} is required`, field, hints);\n }\n}\n\n// ── API Errors ───────────────────────────────────────\n\nexport class ApiError extends CliError {\n readonly code = \"API_ERROR\";\n readonly isRecoverable: boolean;\n\n constructor(\n message: string,\n public readonly statusCode?: number,\n hints?: string[],\n cause?: unknown,\n ) {\n super(message, hints ?? getApiErrorHints(statusCode), cause);\n this.isRecoverable = statusCode === undefined || (statusCode >= 500 && statusCode < 600);\n }\n\n override toJSON() {\n return { ...super.toJSON(), statusCode: this.statusCode };\n }\n\n static fromForgeError(error: unknown): ApiError {\n if (error instanceof Error && \"statusCode\" in error) {\n const code = (error as { statusCode: unknown }).statusCode;\n return new ApiError(\n error.message,\n typeof code === \"number\" ? code : undefined,\n undefined,\n error,\n );\n }\n if (error instanceof Error) {\n return new ApiError(error.message, undefined, undefined, error);\n }\n return new ApiError(String(error));\n }\n}\n\nfunction getApiErrorHints(statusCode?: number): string[] {\n if (statusCode === 401) {\n return [\"Check that your API token is valid\", \"Set via: export FORGE_API_TOKEN=<token>\"];\n }\n if (statusCode === 403) {\n return [\"You may not have permission to access this resource\"];\n }\n if (statusCode === 404) {\n return [\n \"The resource may not exist\",\n \"Verify the ID is correct\",\n \"Use the list command to find valid IDs\",\n ];\n }\n if (statusCode === 429) {\n return [\"Too many requests — wait before retrying\"];\n }\n if (statusCode !== undefined && statusCode >= 500) {\n return [\"Server error — try again later\"];\n }\n return [];\n}\n\n// ── Type Guards ──────────────────────────────────────\n\nexport function isCliError(error: unknown): error is CliError {\n return error instanceof CliError;\n}\n","/**\n * Config loading for forge.\n *\n * Resolution priority (highest to lowest):\n * 1. CLI arguments (--token)\n * 2. Environment variable (FORGE_API_TOKEN)\n * 3. Config file (XDG-compliant)\n */\n\nimport { createConfigStore, getToken } from \"@studiometa/forge-api\";\n\nexport interface ForgeCliConfig {\n /** Forge API token */\n apiToken: string | undefined;\n}\n\n/**\n * Resolve config from multiple sources with priority order.\n */\nexport function getConfig(\n cliOptions?: Record<string, string | boolean | string[]>,\n): ForgeCliConfig {\n // 1. CLI argument --token\n const cliToken = cliOptions?.token;\n\n // 2. Environment variable / config file (handled by getToken)\n const storedToken = getToken(createConfigStore());\n\n return {\n apiToken: typeof cliToken === \"string\" ? cliToken : (storedToken ?? undefined),\n };\n}\n\nexport {\n createConfigStore,\n getToken,\n setToken,\n deleteToken,\n getOrganizationSlug,\n setOrganizationSlug,\n} from \"@studiometa/forge-api\";\n","/**\n * ANSI color utilities using native Node.js.\n * Provides terminal colors without external dependencies.\n */\n\n// ANSI escape codes\nconst RESET = \"\\x1b[0m\";\nconst BOLD = \"\\x1b[1m\";\nconst DIM = \"\\x1b[2m\";\n\n// Foreground colors\nconst FG_RED = \"\\x1b[31m\";\nconst FG_GREEN = \"\\x1b[32m\";\nconst FG_YELLOW = \"\\x1b[33m\";\nconst FG_CYAN = \"\\x1b[36m\";\nconst FG_GRAY = \"\\x1b[90m\";\n\n// Colors enabled by default, disabled by NO_COLOR env var\nlet colorEnabled = process.env.NO_COLOR === undefined;\n\nexport function setColorEnabled(enabled: boolean): void {\n colorEnabled = enabled;\n}\n\nexport function isColorEnabled(): boolean {\n return colorEnabled;\n}\n\nfunction colorize(text: string, code: string): string {\n if (!colorEnabled) return text;\n return `${code}${text}${RESET}`;\n}\n\nexport const colors = {\n reset: (text: string) => (colorEnabled ? `${RESET}${text}` : text),\n bold: (text: string) => colorize(text, BOLD),\n dim: (text: string) => colorize(text, DIM),\n red: (text: string) => colorize(text, FG_RED),\n green: (text: string) => colorize(text, FG_GREEN),\n yellow: (text: string) => colorize(text, FG_YELLOW),\n cyan: (text: string) => colorize(text, FG_CYAN),\n gray: (text: string) => colorize(text, FG_GRAY),\n};\n","/* eslint-disable typescript-eslint/no-base-to-string -- generic formatter converts unknown values to strings */\nimport type { OutputFormat } from \"./types.ts\";\n\nimport { colors } from \"./utils/colors.ts\";\n\n/**\n * Output formatter for CLI commands.\n * Supports human-readable, JSON, and table formats.\n */\nexport class OutputFormatter {\n constructor(\n private format: OutputFormat = \"human\",\n private noColor: boolean = false,\n ) {}\n\n /**\n * Returns true when the current format is JSON.\n */\n isJson(): boolean {\n return this.format === \"json\";\n }\n\n /**\n * Output data in the configured format.\n * Use format='json' for AI agents and structured data.\n */\n output(data: unknown): void {\n switch (this.format) {\n case \"json\":\n console.log(JSON.stringify(data, null, 2));\n break;\n case \"table\":\n this.outputTable(data);\n break;\n default:\n console.log(data);\n break;\n }\n }\n\n /**\n * Output raw text such as a log. Human and table formats print the text\n * verbatim (a log is not tabular data); JSON wraps it as a JSON string.\n */\n outputText(text: string): void {\n if (this.format === \"json\") {\n console.log(JSON.stringify(text));\n return;\n }\n console.log(text);\n }\n\n /**\n * Output a list with a human-readable one-liner per item and a table for table format.\n * Falls back to JSON for json format.\n *\n * @param data - Array of items to display\n * @param columns - Ordered list of field names to include in human/table output\n * @param emptyMessage - Message shown when data is empty (human format only)\n * @param lineFormat - Optional custom line formatter for human format\n */\n outputList<T extends object>(\n data: T[],\n columns: (keyof T & string)[],\n emptyMessage: string,\n lineFormat?: (item: T) => string,\n ): void {\n if (this.format === \"json\") {\n console.log(JSON.stringify(data, null, 2));\n return;\n }\n\n if (this.format === \"table\") {\n this.outputTable(data.map((item) => Object.fromEntries(columns.map((c) => [c, item[c]]))));\n return;\n }\n\n // human\n if (data.length === 0) {\n console.log(colors.cyan(emptyMessage));\n return;\n }\n\n if (lineFormat) {\n for (const item of data) {\n console.log(lineFormat(item));\n }\n return;\n }\n\n // Default: padded columns\n const widths = columns.map((col) =>\n Math.max(\n col.length,\n ...data.map((item) => String((item as Record<string, unknown>)[col] ?? \"\").length),\n ),\n );\n for (const item of data) {\n const row = item as Record<string, unknown>;\n const line = columns.map((col, i) => String(row[col] ?? \"\").padEnd(widths[i])).join(\" \");\n console.log(line.trimEnd());\n }\n }\n\n /**\n * Output a single resource in human-readable format.\n * For json format, uses JSON.stringify. For table format, uses a two-column key/value table.\n *\n * @param data - Object to display\n * @param fields - Ordered list of fields to display (defaults to all keys)\n */\n outputOne<T extends object>(data: T, fields?: (keyof T & string)[]): void {\n if (this.format === \"json\") {\n console.log(JSON.stringify(data, null, 2));\n return;\n }\n\n const record = data as Record<string, unknown>;\n const keys = fields ?? (Object.keys(record) as (keyof T & string)[]);\n\n if (this.format === \"table\") {\n const rows = keys.map((k) => ({ field: k, value: String(record[k] ?? \"\") }));\n this.outputTable(rows);\n return;\n }\n\n // human: key: value pairs\n const keyWidth = Math.max(...keys.map((k) => k.length));\n for (const key of keys) {\n const value = record[key];\n const valueStr =\n value !== null && typeof value === \"object\" ? JSON.stringify(value) : String(value ?? \"\");\n console.log(`${colors.cyan(key.padEnd(keyWidth))} ${valueStr}`);\n }\n }\n\n success(message: string): void {\n if (this.format === \"json\") {\n console.log(JSON.stringify({ status: \"success\", message }));\n } else {\n console.log(colors.green(`✓ ${message}`));\n }\n }\n\n error(message: string, details?: unknown): void {\n if (this.format === \"json\") {\n console.error(JSON.stringify({ status: \"error\", message, details }));\n } else {\n console.error(colors.red(`✗ ${message}`));\n if (details !== undefined) {\n console.error(details);\n }\n }\n }\n\n warning(message: string): void {\n if (this.format === \"json\") {\n console.log(JSON.stringify({ status: \"warning\", message }));\n } else {\n console.log(colors.yellow(`⚠ ${message}`));\n }\n }\n\n info(message: string): void {\n if (this.format === \"json\") {\n console.log(JSON.stringify({ status: \"info\", message }));\n } else {\n console.log(colors.cyan(message));\n }\n }\n\n private outputTable(data: unknown): void {\n if (Array.isArray(data) && data.length > 0) {\n const headers = Object.keys(data[0] as Record<string, unknown>);\n const colWidths = headers.map((h) => {\n const maxDataWidth = Math.max(\n ...data.map((row) => String((row as Record<string, unknown>)[h] ?? \"\").length),\n );\n return Math.max(h.length, maxDataWidth);\n });\n\n // Header\n const headerRow = headers.map((h, i) => h.padEnd(colWidths[i])).join(\" | \");\n console.log(headerRow);\n console.log(colWidths.map((w) => \"-\".repeat(w)).join(\"-+-\"));\n\n // Rows\n for (const row of data) {\n const rowStr = headers\n .map((h, i) => String((row as Record<string, unknown>)[h] ?? \"\").padEnd(colWidths[i]))\n .join(\" | \");\n console.log(rowStr);\n }\n }\n }\n}\n","/**\n * CLI execution context with dependency injection.\n *\n * Instead of hidden dependencies in command handlers, this module provides\n * explicit dependency passing for testability and transparency.\n */\n\nimport type { HttpClient } from \"@studiometa/forge-api\";\nimport type { ExecutorContext } from \"@studiometa/forge-core\";\n\nimport { type OutputFormat, isOutputFormat } from \"./types.ts\";\n\nimport { HttpClient as ForgeHttpClient } from \"@studiometa/forge-api\";\nimport { ConfigError } from \"./errors.ts\";\nimport { createConfigStore, getToken, getOrganizationSlug } from \"./config.ts\";\nimport { OutputFormatter } from \"./output.ts\";\n\n/**\n * Executor context of the CLI. It also tells the resolvers of --server and --site\n * how to match names.\n */\nexport interface CliExecutorContext extends ExecutorContext {\n /** Accept only a numeric ID or an exact name (`--exact` or `FORGE_EXACT_MATCH=1`). */\n readonly exactMatch: boolean;\n}\n\nexport interface CommandOptions {\n [key: string]: string | boolean | string[] | undefined;\n format?: string;\n f?: string;\n token?: string;\n \"no-color\"?: boolean;\n exact?: boolean;\n}\n\n/**\n * Whether --server and --site must match an exact name: the `--exact` flag or the\n * `FORGE_EXACT_MATCH` environment variable (`1` or `true`) turns it on.\n */\nexport function isExactMatch(\n options: CommandOptions,\n env: Record<string, string | undefined> = process.env,\n): boolean {\n const value = env.FORGE_EXACT_MATCH?.trim().toLowerCase();\n return options.exact === true || value === \"1\" || value === \"true\";\n}\n\n/**\n * Context containing all dependencies needed by command handlers.\n */\nexport interface CommandContext {\n /** Output formatter for the current format */\n readonly formatter: OutputFormatter;\n /** Raw CLI options */\n readonly options: CommandOptions;\n /** Create an ExecutorContext from this command context */\n createExecutorContext(token: string): CliExecutorContext;\n /** Resolve the API token or throw ConfigError */\n getToken(): string;\n}\n\n/**\n * Creates a command context from CLI options.\n */\nexport function createContext(options: CommandOptions = {}): CommandContext {\n const rawFormat = options.format ?? options.f ?? \"human\";\n const format: OutputFormat = isOutputFormat(rawFormat) ? rawFormat : \"human\";\n const noColor = options[\"no-color\"] === true;\n const formatter = new OutputFormatter(format, noColor);\n const exactMatch = isExactMatch(options);\n\n return {\n formatter,\n options,\n\n createExecutorContext(token: string): CliExecutorContext {\n const client: HttpClient = new ForgeHttpClient({ token });\n const orgSlug =\n (typeof options.org === \"string\" && options.org) ||\n getOrganizationSlug(createConfigStore()) ||\n undefined;\n return { client, organizationSlug: orgSlug, exactMatch };\n },\n\n getToken(): string {\n // 1. CLI arg\n if (typeof options.token === \"string\" && options.token) {\n return options.token;\n }\n // 2. Env var / config file\n const stored = getToken(createConfigStore());\n if (stored) {\n return stored;\n }\n // 3. Not found — throw\n throw ConfigError.missingToken();\n },\n };\n}\n\n/**\n * Creates a test context for unit tests.\n *\n * @example\n * ```typescript\n * const ctx = createTestContext({\n * mockClient: { get: async () => ({ servers: [] }) } as never,\n * });\n * ```\n */\nexport function createTestContext(overrides: {\n options?: CommandOptions;\n formatter?: OutputFormatter;\n token?: string;\n mockClient?: HttpClient;\n}): CommandContext {\n const opts: CommandOptions = overrides.options ?? {\n format: \"json\",\n \"no-color\": true,\n };\n const rawFormat = opts.format ?? \"json\";\n const format: OutputFormat = isOutputFormat(rawFormat) ? rawFormat : \"json\";\n const formatter = overrides.formatter ?? new OutputFormatter(format, true);\n\n return {\n formatter,\n options: opts,\n createExecutorContext(_token: string): CliExecutorContext {\n if (!overrides.mockClient) {\n throw new Error(\n \"createTestContext: provide mockClient to use createExecutorContext in tests\",\n );\n }\n return {\n client: overrides.mockClient,\n organizationSlug: \"test-org\",\n exactMatch: isExactMatch(opts, {}),\n };\n },\n getToken(): string {\n if (overrides.token) return overrides.token;\n if (typeof opts.token === \"string\") return opts.token;\n return \"test-token\";\n },\n };\n}\n","/**\n * Centralized error handling for forge.\n */\n\nimport type { OutputFormatter } from \"./output.ts\";\n\nimport { ApiError, CliError, ConfigError, ValidationError, isCliError } from \"./errors.ts\";\n\n/**\n * Exit codes for different error categories.\n */\nexport const ExitCode = {\n SUCCESS: 0,\n GENERAL_ERROR: 1,\n AUTHENTICATION_ERROR: 2,\n VALIDATION_ERROR: 3,\n CONFIG_ERROR: 4,\n NOT_FOUND_ERROR: 5,\n} as const;\n\nexport type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];\n\n/**\n * Determine the appropriate exit code for an error.\n */\nexport function getExitCode(error: unknown): ExitCode {\n if (error instanceof ValidationError) {\n return ExitCode.VALIDATION_ERROR;\n }\n if (error instanceof ApiError) {\n if (error.statusCode === 401) return ExitCode.AUTHENTICATION_ERROR;\n if (error.statusCode === 404) return ExitCode.NOT_FOUND_ERROR;\n }\n if (isCliError(error) && error.code === \"CONFIG_ERROR\") {\n return ExitCode.CONFIG_ERROR;\n }\n return ExitCode.GENERAL_ERROR;\n}\n\n/**\n * Central error handler for all commands.\n */\nexport function handleError(\n error: unknown,\n formatter: OutputFormatter,\n options?: { exit?: boolean },\n): ExitCode {\n const shouldExit = options?.exit ?? true;\n\n let cliError: CliError;\n if (isCliError(error)) {\n cliError = error;\n } else {\n cliError = ApiError.fromForgeError(error);\n }\n\n // Output error in appropriate format\n if (formatter.isJson()) {\n formatter.output(cliError.toJSON());\n } else {\n formatter.error(cliError.toFormattedMessage());\n }\n\n const exitCode = getExitCode(cliError);\n\n if (shouldExit) {\n process.exit(exitCode);\n }\n\n return exitCode;\n}\n\n/**\n * Wraps a command function with automatic error handling.\n */\nexport async function runCommand<T>(\n fn: () => Promise<T>,\n formatter: OutputFormatter,\n): Promise<T | undefined> {\n try {\n return await fn();\n } catch (error) {\n handleError(error, formatter);\n return undefined; // unreachable due to process.exit\n }\n}\n\n/**\n * Outputs an error and exits.\n */\nexport function exitWithError(error: CliError, formatter: OutputFormatter): never {\n handleError(error, formatter);\n // Unreachable in production\n throw error;\n}\n\n/**\n * Exits with a validation error for a missing required argument.\n */\nexport function exitWithValidationError(\n field: string,\n usage: string,\n formatter: OutputFormatter,\n): never {\n exitWithError(ValidationError.required(field, [`Usage: ${usage}`]), formatter);\n}\n\n/**\n * Exits with a config error when token is missing.\n */\nexport function exitWithConfigError(formatter: OutputFormatter): never {\n const error = ConfigError.missingToken();\n handleError(error, formatter);\n throw error;\n}\n"],"mappings":";;AAIA,SAAgB,eAAe,OAAuC;CACpE,OAAO,UAAU,UAAU,UAAU,WAAW,UAAU;AAC5D;;;;;;;;;;;;ACIA,IAAsB,WAAtB,cAAuC,MAAM;CAMzB;CACA;CAHlB,YACE,SACA,OACA,OACA;EACA,MAAM,OAAO;EAHG,KAAA,QAAA;EACA,KAAA,QAAA;EAGhB,KAAK,OAAO,KAAK,YAAY;EAE7B,MAAM,oBAAoB,MAAM,KAAK,WAAW;CAClD;CAEA,SAAkC;EAChC,OAAO;GACL,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,SAAS,KAAK;GACd,eAAe,KAAK;GACpB,GAAI,KAAK,SAAS,KAAK,MAAM,SAAS,IAAI,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;GACnE,GAAI,KAAK,iBAAiB,QAAQ,EAAE,OAAO,EAAE,SAAS,KAAK,MAAM,QAAQ,EAAE,IAAI,CAAC;EAClF;CACF;CAEA,qBAA6B;EAC3B,IAAI,MAAM,KAAK;EACf,IAAI,KAAK,SAAS,KAAK,MAAM,SAAS,GACpC,OAAO,iBAAiB,KAAK,MAAM,KAAK,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,IAAI;EAErE,OAAO;CACT;AACF;AAIA,IAAa,cAAb,MAAa,oBAAoB,SAAS;CACxC,OAAgB;CAChB,gBAAyB;CAEzB,OAAO,eAA4B;EACjC,OAAO,IAAI,YAAY,4BAA4B;GACjD;GACA;GACA;EACF,CAAC;CACH;AACF;AAIA,IAAa,kBAAb,MAAa,wBAAwB,SAAS;CAM1B;CALlB,OAAgB;CAChB,gBAAyB;CAEzB,YACE,SACA,OACA,OACA,OACA;EACA,MAAM,SAAS,OAAO,KAAK;EAJX,KAAA,QAAA;CAKlB;CAEA,SAAkB;EAChB,OAAO;GAAE,GAAG,MAAM,OAAO;GAAG,OAAO,KAAK;EAAM;CAChD;CAEA,OAAO,SAAS,OAAe,OAAmC;EAChE,OAAO,IAAI,gBAAgB,GAAG,MAAM,eAAe,OAAO,KAAK;CACjE;AACF;AAIA,IAAa,WAAb,MAAa,iBAAiB,SAAS;CAMnB;CALlB,OAAgB;CAChB;CAEA,YACE,SACA,YACA,OACA,OACA;EACA,MAAM,SAAS,SAAS,iBAAiB,UAAU,GAAG,KAAK;EAJ3C,KAAA,aAAA;EAKhB,KAAK,gBAAgB,eAAe,KAAA,KAAc,cAAc,OAAO,aAAa;CACtF;CAEA,SAAkB;EAChB,OAAO;GAAE,GAAG,MAAM,OAAO;GAAG,YAAY,KAAK;EAAW;CAC1D;CAEA,OAAO,eAAe,OAA0B;EAC9C,IAAI,iBAAiB,SAAS,gBAAgB,OAAO;GACnD,MAAM,OAAQ,MAAkC;GAChD,OAAO,IAAI,SACT,MAAM,SACN,OAAO,SAAS,WAAW,OAAO,KAAA,GAClC,KAAA,GACA,KACF;EACF;EACA,IAAI,iBAAiB,OACnB,OAAO,IAAI,SAAS,MAAM,SAAS,KAAA,GAAW,KAAA,GAAW,KAAK;EAEhE,OAAO,IAAI,SAAS,OAAO,KAAK,CAAC;CACnC;AACF;AAEA,SAAS,iBAAiB,YAA+B;CACvD,IAAI,eAAe,KACjB,OAAO,CAAC,sCAAsC,yCAAyC;CAEzF,IAAI,eAAe,KACjB,OAAO,CAAC,qDAAqD;CAE/D,IAAI,eAAe,KACjB,OAAO;EACL;EACA;EACA;CACF;CAEF,IAAI,eAAe,KACjB,OAAO,CAAC,0CAA0C;CAEpD,IAAI,eAAe,KAAA,KAAa,cAAc,KAC5C,OAAO,CAAC,gCAAgC;CAE1C,OAAO,CAAC;AACV;AAIA,SAAgB,WAAW,OAAmC;CAC5D,OAAO,iBAAiB;AAC1B;;;;;;;;;;;;;;ACjIA,SAAgB,UACd,YACgB;CAEhB,MAAM,WAAW,YAAY;CAG7B,MAAM,cAAc,SAAS,kBAAkB,CAAC;CAEhD,OAAO,EACL,UAAU,OAAO,aAAa,WAAW,WAAY,eAAe,KAAA,EACtE;AACF;;;;;;;ACzBA,IAAM,QAAQ;AACd,IAAM,OAAO;AACb,IAAM,MAAM;AAGZ,IAAM,SAAS;AACf,IAAM,WAAW;AACjB,IAAM,YAAY;AAClB,IAAM,UAAU;AAChB,IAAM,UAAU;AAGhB,IAAI,eAAe,QAAQ,IAAI,aAAa,KAAA;AAE5C,SAAgB,gBAAgB,SAAwB;CACtD,eAAe;AACjB;AAMA,SAAS,SAAS,MAAc,MAAsB;CACpD,IAAI,CAAC,cAAc,OAAO;CAC1B,OAAO,GAAG,OAAO,OAAO;AAC1B;AAEA,IAAa,SAAS;CACpB,QAAQ,SAAkB,eAAe,GAAG,QAAQ,SAAS;CAC7D,OAAO,SAAiB,SAAS,MAAM,IAAI;CAC3C,MAAM,SAAiB,SAAS,MAAM,GAAG;CACzC,MAAM,SAAiB,SAAS,MAAM,MAAM;CAC5C,QAAQ,SAAiB,SAAS,MAAM,QAAQ;CAChD,SAAS,SAAiB,SAAS,MAAM,SAAS;CAClD,OAAO,SAAiB,SAAS,MAAM,OAAO;CAC9C,OAAO,SAAiB,SAAS,MAAM,OAAO;AAChD;;;;;;;ACjCA,IAAa,kBAAb,MAA6B;CAEjB;CACA;CAFV,YACE,SAA+B,SAC/B,UAA2B,OAC3B;EAFQ,KAAA,SAAA;EACA,KAAA,UAAA;CACP;;;;CAKH,SAAkB;EAChB,OAAO,KAAK,WAAW;CACzB;;;;;CAMA,OAAO,MAAqB;EAC1B,QAAQ,KAAK,QAAb;GACE,KAAK;IACH,QAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;IACzC;GACF,KAAK;IACH,KAAK,YAAY,IAAI;IACrB;GACF;IACE,QAAQ,IAAI,IAAI;IAChB;EACJ;CACF;;;;;CAMA,WAAW,MAAoB;EAC7B,IAAI,KAAK,WAAW,QAAQ;GAC1B,QAAQ,IAAI,KAAK,UAAU,IAAI,CAAC;GAChC;EACF;EACA,QAAQ,IAAI,IAAI;CAClB;;;;;;;;;;CAWA,WACE,MACA,SACA,cACA,YACM;EACN,IAAI,KAAK,WAAW,QAAQ;GAC1B,QAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;GACzC;EACF;EAEA,IAAI,KAAK,WAAW,SAAS;GAC3B,KAAK,YAAY,KAAK,KAAK,SAAS,OAAO,YAAY,QAAQ,KAAK,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;GACzF;EACF;EAGA,IAAI,KAAK,WAAW,GAAG;GACrB,QAAQ,IAAI,OAAO,KAAK,YAAY,CAAC;GACrC;EACF;EAEA,IAAI,YAAY;GACd,KAAK,MAAM,QAAQ,MACjB,QAAQ,IAAI,WAAW,IAAI,CAAC;GAE9B;EACF;EAGA,MAAM,SAAS,QAAQ,KAAK,QAC1B,KAAK,IACH,IAAI,QACJ,GAAG,KAAK,KAAK,SAAS,OAAQ,KAAiC,QAAQ,EAAE,CAAC,CAAC,MAAM,CACnF,CACF;EACA,KAAK,MAAM,QAAQ,MAAM;GACvB,MAAM,MAAM;GACZ,MAAM,OAAO,QAAQ,KAAK,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC,CAAC,OAAO,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI;GACxF,QAAQ,IAAI,KAAK,QAAQ,CAAC;EAC5B;CACF;;;;;;;;CASA,UAA4B,MAAS,QAAqC;EACxE,IAAI,KAAK,WAAW,QAAQ;GAC1B,QAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;GACzC;EACF;EAEA,MAAM,SAAS;EACf,MAAM,OAAO,UAAW,OAAO,KAAK,MAAM;EAE1C,IAAI,KAAK,WAAW,SAAS;GAC3B,MAAM,OAAO,KAAK,KAAK,OAAO;IAAE,OAAO;IAAG,OAAO,OAAO,OAAO,MAAM,EAAE;GAAE,EAAE;GAC3E,KAAK,YAAY,IAAI;GACrB;EACF;EAGA,MAAM,WAAW,KAAK,IAAI,GAAG,KAAK,KAAK,MAAM,EAAE,MAAM,CAAC;EACtD,KAAK,MAAM,OAAO,MAAM;GACtB,MAAM,QAAQ,OAAO;GACrB,MAAM,WACJ,UAAU,QAAQ,OAAO,UAAU,WAAW,KAAK,UAAU,KAAK,IAAI,OAAO,SAAS,EAAE;GAC1F,QAAQ,IAAI,GAAG,OAAO,KAAK,IAAI,OAAO,QAAQ,CAAC,EAAE,IAAI,UAAU;EACjE;CACF;CAEA,QAAQ,SAAuB;EAC7B,IAAI,KAAK,WAAW,QAClB,QAAQ,IAAI,KAAK,UAAU;GAAE,QAAQ;GAAW;EAAQ,CAAC,CAAC;OAE1D,QAAQ,IAAI,OAAO,MAAM,KAAK,SAAS,CAAC;CAE5C;CAEA,MAAM,SAAiB,SAAyB;EAC9C,IAAI,KAAK,WAAW,QAClB,QAAQ,MAAM,KAAK,UAAU;GAAE,QAAQ;GAAS;GAAS;EAAQ,CAAC,CAAC;OAC9D;GACL,QAAQ,MAAM,OAAO,IAAI,KAAK,SAAS,CAAC;GACxC,IAAI,YAAY,KAAA,GACd,QAAQ,MAAM,OAAO;EAEzB;CACF;CAEA,QAAQ,SAAuB;EAC7B,IAAI,KAAK,WAAW,QAClB,QAAQ,IAAI,KAAK,UAAU;GAAE,QAAQ;GAAW;EAAQ,CAAC,CAAC;OAE1D,QAAQ,IAAI,OAAO,OAAO,KAAK,SAAS,CAAC;CAE7C;CAEA,KAAK,SAAuB;EAC1B,IAAI,KAAK,WAAW,QAClB,QAAQ,IAAI,KAAK,UAAU;GAAE,QAAQ;GAAQ;EAAQ,CAAC,CAAC;OAEvD,QAAQ,IAAI,OAAO,KAAK,OAAO,CAAC;CAEpC;CAEA,YAAoB,MAAqB;EACvC,IAAI,MAAM,QAAQ,IAAI,KAAK,KAAK,SAAS,GAAG;GAC1C,MAAM,UAAU,OAAO,KAAK,KAAK,EAA6B;GAC9D,MAAM,YAAY,QAAQ,KAAK,MAAM;IACnC,MAAM,eAAe,KAAK,IACxB,GAAG,KAAK,KAAK,QAAQ,OAAQ,IAAgC,MAAM,EAAE,CAAC,CAAC,MAAM,CAC/E;IACA,OAAO,KAAK,IAAI,EAAE,QAAQ,YAAY;GACxC,CAAC;GAGD,MAAM,YAAY,QAAQ,KAAK,GAAG,MAAM,EAAE,OAAO,UAAU,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK;GAC1E,QAAQ,IAAI,SAAS;GACrB,QAAQ,IAAI,UAAU,KAAK,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC;GAG3D,KAAK,MAAM,OAAO,MAAM;IACtB,MAAM,SAAS,QACZ,KAAK,GAAG,MAAM,OAAQ,IAAgC,MAAM,EAAE,CAAC,CAAC,OAAO,UAAU,EAAE,CAAC,CAAC,CACrF,KAAK,KAAK;IACb,QAAQ,IAAI,MAAM;GACpB;EACF;CACF;AACF;;;;;;;AC5JA,SAAgB,aACd,SACA,MAA0C,QAAQ,KACzC;CACT,MAAM,QAAQ,IAAI,mBAAmB,KAAK,CAAC,CAAC,YAAY;CACxD,OAAO,QAAQ,UAAU,QAAQ,UAAU,OAAO,UAAU;AAC9D;;;;AAmBA,SAAgB,cAAc,UAA0B,CAAC,GAAmB;CAC1E,MAAM,YAAY,QAAQ,UAAU,QAAQ,KAAK;CAGjD,MAAM,YAAY,IAAI,gBAFO,eAAe,SAAS,IAAI,YAAY,SACrD,QAAQ,gBAAgB,IACa;CACrD,MAAM,aAAa,aAAa,OAAO;CAEvC,OAAO;EACL;EACA;EAEA,sBAAsB,OAAmC;GAMvD,OAAO;IAAE,QAAA,IALsB,WAAgB,EAAE,MAAM,CAK9C;IAAQ,kBAHd,OAAO,QAAQ,QAAQ,YAAY,QAAQ,OAC5C,oBAAoB,oBAAkB,CAAC,KACvC,KAAA;IAC0C;GAAW;EACzD;EAEA,WAAmB;GAEjB,IAAI,OAAO,QAAQ,UAAU,YAAY,QAAQ,OAC/C,OAAO,QAAQ;GAGjB,MAAM,SAAS,WAAS,oBAAkB,CAAC;GAC3C,IAAI,QACF,OAAO;GAGT,MAAM,YAAY,aAAa;EACjC;CACF;AACF;;;;;;;;;;;AAYA,SAAgB,kBAAkB,WAKf;CACjB,MAAM,OAAuB,UAAU,WAAW;EAChD,QAAQ;EACR,YAAY;CACd;CACA,MAAM,YAAY,KAAK,UAAU;CACjC,MAAM,SAAuB,eAAe,SAAS,IAAI,YAAY;CAGrE,OAAO;EACL,WAHgB,UAAU,aAAa,IAAI,gBAAgB,QAAQ,IAAI;EAIvE,SAAS;EACT,sBAAsB,QAAoC;GACxD,IAAI,CAAC,UAAU,YACb,MAAM,IAAI,MACR,6EACF;GAEF,OAAO;IACL,QAAQ,UAAU;IAClB,kBAAkB;IAClB,YAAY,aAAa,MAAM,CAAC,CAAC;GACnC;EACF;EACA,WAAmB;GACjB,IAAI,UAAU,OAAO,OAAO,UAAU;GACtC,IAAI,OAAO,KAAK,UAAU,UAAU,OAAO,KAAK;GAChD,OAAO;EACT;CACF;AACF;;;;;;ACtIA,IAAa,WAAW;CACtB,SAAS;CACT,eAAe;CACf,sBAAsB;CACtB,kBAAkB;CAClB,cAAc;CACd,iBAAiB;AACnB;;;;AAOA,SAAgB,YAAY,OAA0B;CACpD,IAAI,iBAAiB,iBACnB,OAAO,SAAS;CAElB,IAAI,iBAAiB,UAAU;EAC7B,IAAI,MAAM,eAAe,KAAK,OAAO,SAAS;EAC9C,IAAI,MAAM,eAAe,KAAK,OAAO,SAAS;CAChD;CACA,IAAI,WAAW,KAAK,KAAK,MAAM,SAAS,gBACtC,OAAO,SAAS;CAElB,OAAO,SAAS;AAClB;;;;AAKA,SAAgB,YACd,OACA,WACA,SACU;CACV,MAAM,aAAa,SAAS,QAAQ;CAEpC,IAAI;CACJ,IAAI,WAAW,KAAK,GAClB,WAAW;MAEX,WAAW,SAAS,eAAe,KAAK;CAI1C,IAAI,UAAU,OAAO,GACnB,UAAU,OAAO,SAAS,OAAO,CAAC;MAElC,UAAU,MAAM,SAAS,mBAAmB,CAAC;CAG/C,MAAM,WAAW,YAAY,QAAQ;CAErC,IAAI,YACF,QAAQ,KAAK,QAAQ;CAGvB,OAAO;AACT;;;;AAKA,eAAsB,WACpB,IACA,WACwB;CACxB,IAAI;EACF,OAAO,MAAM,GAAG;CAClB,SAAS,OAAO;EACd,YAAY,OAAO,SAAS;EAC5B;CACF;AACF;;;;AAKA,SAAgB,cAAc,OAAiB,WAAmC;CAChF,YAAY,OAAO,SAAS;CAE5B,MAAM;AACR;;;;AAKA,SAAgB,wBACd,OACA,OACA,WACO;CACP,cAAc,gBAAgB,SAAS,OAAO,CAAC,UAAU,OAAO,CAAC,GAAG,SAAS;AAC/E"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { S as isCliError, a as runCommand, b as ConfigError, c as OutputFormatter, i as handleError, o as createContext, p as getConfig, s as createTestContext, t as ExitCode, v as ApiError, x as ValidationError, y as CliError } from "./error-handler-
|
|
1
|
+
import { S as isCliError, a as runCommand, b as ConfigError, c as OutputFormatter, i as handleError, o as createContext, p as getConfig, s as createTestContext, t as ExitCode, v as ApiError, x as ValidationError, y as CliError } from "./error-handler-s-BOJ1-h.js";
|
|
2
2
|
export { ApiError, CliError, ConfigError, ExitCode, OutputFormatter, ValidationError, createContext, createTestContext, getConfig, handleError, isCliError, runCommand };
|
package/dist/utils/args.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/utils/args.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC;AACtD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAEzD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/utils/args.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,EAAE,CAAC;AACtD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAEzD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AA2CD,wBAAgB,SAAS,CAAC,IAAI,GAAE,MAAM,EAA0B,GAAG,UAAU,CAwD5E;AAED,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACpC,KAAK,EAAE,MAAM,EAAE,EACf,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,GAAG,SAAS,CAQpB;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAEtF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,UAAU,GAAG,OAAO,CAE1E"}
|
package/dist/utils/resolve.d.ts
CHANGED
|
@@ -2,32 +2,38 @@
|
|
|
2
2
|
* Smart resolution utilities for --server and --site options.
|
|
3
3
|
*
|
|
4
4
|
* Accepts numeric IDs (used as-is) or plain strings (resolved by name/partial match).
|
|
5
|
+
* In exact mode (`--exact` or `FORGE_EXACT_MATCH=1`), a plain string must match a name exactly.
|
|
5
6
|
*/
|
|
6
|
-
import type {
|
|
7
|
+
import type { CliExecutorContext } from "../context.ts";
|
|
8
|
+
interface NamedResource {
|
|
9
|
+
id: number;
|
|
10
|
+
name: string;
|
|
11
|
+
}
|
|
7
12
|
/**
|
|
8
13
|
* Resolve a --server value to a numeric server ID string.
|
|
9
14
|
*
|
|
10
15
|
* - Numeric string → returned as-is (no API call)
|
|
11
|
-
* - Plain string → fetch all servers,
|
|
12
|
-
* - 0 matches → ValidationError listing available servers
|
|
13
|
-
* - 2+ matches → ValidationError listing ambiguous matches
|
|
16
|
+
* - Plain string → fetch all servers, then see {@link matchServerId}
|
|
14
17
|
*/
|
|
15
|
-
export declare function resolveServerId(value: string, execCtx:
|
|
18
|
+
export declare function resolveServerId(value: string, execCtx: CliExecutorContext): Promise<string>;
|
|
16
19
|
/**
|
|
17
20
|
* Resolve a --server value to a numeric server ID string, against a list of servers
|
|
18
|
-
* that is already loaded
|
|
21
|
+
* that is already loaded, with no API call.
|
|
22
|
+
*
|
|
23
|
+
* - Exact name match first, then a single partial match (unless `exactMatch`)
|
|
24
|
+
* - 0 matches → ValidationError listing available servers (near matches in exact mode)
|
|
25
|
+
* - 2+ matches → ValidationError listing ambiguous matches
|
|
19
26
|
*/
|
|
20
|
-
export declare function matchServerId(value: string, servers:
|
|
21
|
-
id: number;
|
|
22
|
-
name: string;
|
|
23
|
-
}>): string;
|
|
27
|
+
export declare function matchServerId(value: string, servers: NamedResource[], exactMatch: boolean): string;
|
|
24
28
|
/**
|
|
25
29
|
* Resolve a --site value to a numeric site ID string.
|
|
26
30
|
*
|
|
27
31
|
* - Numeric string → returned as-is (no API call)
|
|
28
|
-
* - Plain string → fetch all sites for the server, exact domain match first, then
|
|
29
|
-
*
|
|
32
|
+
* - Plain string → fetch all sites for the server, exact domain match first, then a single
|
|
33
|
+
* partial match (unless `execCtx.exactMatch`)
|
|
34
|
+
* - 0 matches → ValidationError listing available sites (near matches in exact mode)
|
|
30
35
|
* - 2+ matches → ValidationError listing ambiguous matches
|
|
31
36
|
*/
|
|
32
|
-
export declare function resolveSiteId(value: string, serverId: string, execCtx:
|
|
37
|
+
export declare function resolveSiteId(value: string, serverId: string, execCtx: CliExecutorContext): Promise<string>;
|
|
38
|
+
export {};
|
|
33
39
|
//# sourceMappingURL=resolve.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/utils/resolve.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/utils/resolve.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGxD,UAAU,aAAa;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAiED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAOjG;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,aAAa,EAAE,EACxB,UAAU,EAAE,OAAO,GAClB,MAAM,CAER;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAOjB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@studiometa/forge-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "CLI tool for managing Laravel Forge servers, sites, and deployments",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agent",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"forge": "./dist/cli.js"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
|
-
"dist"
|
|
25
|
+
"dist",
|
|
26
|
+
"schemas"
|
|
26
27
|
],
|
|
27
28
|
"type": "module",
|
|
28
29
|
"main": "./dist/index.js",
|
|
@@ -48,8 +49,9 @@
|
|
|
48
49
|
"typecheck": "tsgo -p tsconfig.typecheck.json --noEmit"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
|
-
"@studiometa/forge-api": "0.5.
|
|
52
|
-
"@studiometa/forge-core": "0.5.
|
|
52
|
+
"@studiometa/forge-api": "0.5.3",
|
|
53
|
+
"@studiometa/forge-core": "0.5.3",
|
|
54
|
+
"valibot": "^1.3.1"
|
|
53
55
|
},
|
|
54
56
|
"devDependencies": {
|
|
55
57
|
"@types/node": "^24.0.0",
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://unpkg.com/@studiometa/forge-cli/schemas/site.schema.json",
|
|
4
|
+
"title": "Forge site",
|
|
5
|
+
"description": "A Laravel Forge site for `forge sites apply`. A key that is absent is not managed. Only server and domain are required.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["server", "domain"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": { "type": "string" },
|
|
11
|
+
"server": {
|
|
12
|
+
"description": "Server name (exact match) or ID.",
|
|
13
|
+
"oneOf": [
|
|
14
|
+
{ "type": "string", "minLength": 1 },
|
|
15
|
+
{ "type": "integer", "minimum": 1 }
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"domain": {
|
|
19
|
+
"description": "Site name on Forge, e.g. www.example.com (exact match).",
|
|
20
|
+
"type": "string",
|
|
21
|
+
"minLength": 1
|
|
22
|
+
},
|
|
23
|
+
"aliases": {
|
|
24
|
+
"description": "Domains to add to the site. Aliases are added, never removed.",
|
|
25
|
+
"type": "array",
|
|
26
|
+
"items": { "type": "string", "minLength": 1 }
|
|
27
|
+
},
|
|
28
|
+
"type": {
|
|
29
|
+
"description": "Site type, used to create the site.",
|
|
30
|
+
"enum": [
|
|
31
|
+
"laravel",
|
|
32
|
+
"symfony",
|
|
33
|
+
"statamic",
|
|
34
|
+
"wordpress",
|
|
35
|
+
"phpmyadmin",
|
|
36
|
+
"php",
|
|
37
|
+
"nextjs",
|
|
38
|
+
"nuxtjs",
|
|
39
|
+
"static-html",
|
|
40
|
+
"other",
|
|
41
|
+
"custom"
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"php": {
|
|
45
|
+
"description": "PHP version, e.g. php83.",
|
|
46
|
+
"type": "string",
|
|
47
|
+
"pattern": "^php\\d+$"
|
|
48
|
+
},
|
|
49
|
+
"webDirectory": {
|
|
50
|
+
"description": "Web directory, relative to the site root, e.g. /public.",
|
|
51
|
+
"type": "string",
|
|
52
|
+
"minLength": 1
|
|
53
|
+
},
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"additionalProperties": false,
|
|
57
|
+
"required": ["provider", "url"],
|
|
58
|
+
"properties": {
|
|
59
|
+
"provider": { "enum": ["github", "gitlab", "bitbucket", "gitlab-custom", "custom"] },
|
|
60
|
+
"url": { "type": "string", "minLength": 1 },
|
|
61
|
+
"branch": { "type": "string", "minLength": 1 }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"database": {
|
|
65
|
+
"description": "Database and user, created when missing. A new user gets a random password, written into the site .env only.",
|
|
66
|
+
"type": "object",
|
|
67
|
+
"additionalProperties": false,
|
|
68
|
+
"required": ["name", "user"],
|
|
69
|
+
"properties": {
|
|
70
|
+
"name": { "type": "string", "minLength": 1 },
|
|
71
|
+
"user": { "type": "string", "minLength": 1 },
|
|
72
|
+
"env": {
|
|
73
|
+
"description": ".env keys that get the database name, user and password of a new user. The keys must be different.",
|
|
74
|
+
"type": "object",
|
|
75
|
+
"additionalProperties": false,
|
|
76
|
+
"properties": {
|
|
77
|
+
"name": {
|
|
78
|
+
"type": "string",
|
|
79
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$",
|
|
80
|
+
"default": "DB_DATABASE"
|
|
81
|
+
},
|
|
82
|
+
"user": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$",
|
|
85
|
+
"default": "DB_USERNAME"
|
|
86
|
+
},
|
|
87
|
+
"password": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$",
|
|
90
|
+
"default": "DB_PASSWORD"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"env": {
|
|
97
|
+
"description": ".env variables to add when missing. Existing values are never overwritten.",
|
|
98
|
+
"type": "object",
|
|
99
|
+
"propertyNames": { "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" },
|
|
100
|
+
"additionalProperties": { "type": ["string", "number", "boolean"] }
|
|
101
|
+
},
|
|
102
|
+
"nginx": {
|
|
103
|
+
"description": "Nginx file, relative to the site file, with the Forge template variables {{DIRECTORY}}, {{DOMAINS}}, {{PATH}}, {{PORT}}, {{PORT_V6}}, {{PROXY_PASS}}, {{ROOT_PATH}}, {{SERVER_PUBLIC_IP}}, {{SERVER_PRIVATE_IP}}, {{SITE}}, {{SITE_ID}} and {{USER}}. Rendered for the site and written when it differs from Forge. An unknown variable is an error.",
|
|
104
|
+
"type": "string",
|
|
105
|
+
"minLength": 1
|
|
106
|
+
},
|
|
107
|
+
"workers": {
|
|
108
|
+
"description": "Forge background processes of the site. [] deletes all the workers of the site.",
|
|
109
|
+
"type": "array",
|
|
110
|
+
"items": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"additionalProperties": false,
|
|
113
|
+
"required": ["command"],
|
|
114
|
+
"properties": {
|
|
115
|
+
"command": { "type": "string", "minLength": 1 },
|
|
116
|
+
"processes": { "type": "integer", "minimum": 1, "default": 1 },
|
|
117
|
+
"directory": {
|
|
118
|
+
"description": "Directory, absolute or relative to the site root. Default: the site root.",
|
|
119
|
+
"type": "string",
|
|
120
|
+
"minLength": 1
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"schedule": {
|
|
126
|
+
"description": "Forge scheduled jobs of the site. [] deletes all the scheduled jobs of the site.",
|
|
127
|
+
"type": "array",
|
|
128
|
+
"items": {
|
|
129
|
+
"type": "object",
|
|
130
|
+
"additionalProperties": false,
|
|
131
|
+
"required": ["command", "frequency"],
|
|
132
|
+
"properties": {
|
|
133
|
+
"command": { "type": "string", "minLength": 1 },
|
|
134
|
+
"frequency": {
|
|
135
|
+
"enum": ["minutely", "hourly", "nightly", "weekly", "monthly", "reboot", "custom"]
|
|
136
|
+
},
|
|
137
|
+
"cron": {
|
|
138
|
+
"description": "Cron expression, required with the custom frequency.",
|
|
139
|
+
"type": "string",
|
|
140
|
+
"minLength": 1
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
"if": { "properties": { "frequency": { "const": "custom" } } },
|
|
144
|
+
"then": { "required": ["cron"] },
|
|
145
|
+
"else": { "not": { "required": ["cron"] } }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"error-handler-Adi84RcF.js","names":[],"sources":["../src/types.ts","../src/errors.ts","../src/config.ts","../src/utils/colors.ts","../src/output.ts","../src/context.ts","../src/error-handler.ts"],"sourcesContent":["// CLI-specific types for forge\n\nexport type OutputFormat = \"json\" | \"human\" | \"table\";\n\nexport function isOutputFormat(value: unknown): value is OutputFormat {\n return value === \"json\" || value === \"human\" || value === \"table\";\n}\n\nexport interface CliOptions {\n format?: OutputFormat;\n quiet?: boolean;\n noColor?: boolean;\n}\n","/**\n * Error types for forge.\n *\n * Provides specific error types for different failure scenarios with\n * recovery hints for both humans and AI agents.\n */\n\n/**\n * Base error class for all CLI errors.\n */\nexport abstract class CliError extends Error {\n abstract readonly code: string;\n abstract readonly isRecoverable: boolean;\n\n constructor(\n message: string,\n public readonly hints?: string[],\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = this.constructor.name;\n\n Error.captureStackTrace?.(this, this.constructor);\n }\n\n toJSON(): Record<string, unknown> {\n return {\n error: this.code,\n name: this.name,\n message: this.message,\n isRecoverable: this.isRecoverable,\n ...(this.hints && this.hints.length > 0 ? { hints: this.hints } : {}),\n ...(this.cause instanceof Error ? { cause: { message: this.cause.message } } : {}),\n };\n }\n\n toFormattedMessage(): string {\n let msg = this.message;\n if (this.hints && this.hints.length > 0) {\n msg += \"\\n\\nHints:\\n\" + this.hints.map((h) => ` • ${h}`).join(\"\\n\");\n }\n return msg;\n }\n}\n\n// ── Config Errors ────────────────────────────────────\n\nexport class ConfigError extends CliError {\n readonly code = \"CONFIG_ERROR\";\n readonly isRecoverable = true;\n\n static missingToken(): ConfigError {\n return new ConfigError(\"API token not configured\", [\n \"Set via CLI flag: --token <token>\",\n \"Set via environment: export FORGE_API_TOKEN=<token>\",\n \"Set via config: forge config set YOUR_TOKEN\",\n ]);\n }\n}\n\n// ── Validation Errors ────────────────────────────────\n\nexport class ValidationError extends CliError {\n readonly code = \"VALIDATION_ERROR\";\n readonly isRecoverable = true;\n\n constructor(\n message: string,\n public readonly field?: string,\n hints?: string[],\n cause?: unknown,\n ) {\n super(message, hints, cause);\n }\n\n override toJSON() {\n return { ...super.toJSON(), field: this.field };\n }\n\n static required(field: string, hints?: string[]): ValidationError {\n return new ValidationError(`${field} is required`, field, hints);\n }\n}\n\n// ── API Errors ───────────────────────────────────────\n\nexport class ApiError extends CliError {\n readonly code = \"API_ERROR\";\n readonly isRecoverable: boolean;\n\n constructor(\n message: string,\n public readonly statusCode?: number,\n hints?: string[],\n cause?: unknown,\n ) {\n super(message, hints ?? getApiErrorHints(statusCode), cause);\n this.isRecoverable = statusCode === undefined || (statusCode >= 500 && statusCode < 600);\n }\n\n override toJSON() {\n return { ...super.toJSON(), statusCode: this.statusCode };\n }\n\n static fromForgeError(error: unknown): ApiError {\n if (error instanceof Error && \"statusCode\" in error) {\n const code = (error as { statusCode: unknown }).statusCode;\n return new ApiError(\n error.message,\n typeof code === \"number\" ? code : undefined,\n undefined,\n error,\n );\n }\n if (error instanceof Error) {\n return new ApiError(error.message, undefined, undefined, error);\n }\n return new ApiError(String(error));\n }\n}\n\nfunction getApiErrorHints(statusCode?: number): string[] {\n if (statusCode === 401) {\n return [\"Check that your API token is valid\", \"Set via: export FORGE_API_TOKEN=<token>\"];\n }\n if (statusCode === 403) {\n return [\"You may not have permission to access this resource\"];\n }\n if (statusCode === 404) {\n return [\n \"The resource may not exist\",\n \"Verify the ID is correct\",\n \"Use the list command to find valid IDs\",\n ];\n }\n if (statusCode === 429) {\n return [\"Too many requests — wait before retrying\"];\n }\n if (statusCode !== undefined && statusCode >= 500) {\n return [\"Server error — try again later\"];\n }\n return [];\n}\n\n// ── Type Guards ──────────────────────────────────────\n\nexport function isCliError(error: unknown): error is CliError {\n return error instanceof CliError;\n}\n","/**\n * Config loading for forge.\n *\n * Resolution priority (highest to lowest):\n * 1. CLI arguments (--token)\n * 2. Environment variable (FORGE_API_TOKEN)\n * 3. Config file (XDG-compliant)\n */\n\nimport { createConfigStore, getToken } from \"@studiometa/forge-api\";\n\nexport interface ForgeCliConfig {\n /** Forge API token */\n apiToken: string | undefined;\n}\n\n/**\n * Resolve config from multiple sources with priority order.\n */\nexport function getConfig(\n cliOptions?: Record<string, string | boolean | string[]>,\n): ForgeCliConfig {\n // 1. CLI argument --token\n const cliToken = cliOptions?.token;\n\n // 2. Environment variable / config file (handled by getToken)\n const storedToken = getToken(createConfigStore());\n\n return {\n apiToken: typeof cliToken === \"string\" ? cliToken : (storedToken ?? undefined),\n };\n}\n\nexport {\n createConfigStore,\n getToken,\n setToken,\n deleteToken,\n getOrganizationSlug,\n setOrganizationSlug,\n} from \"@studiometa/forge-api\";\n","/**\n * ANSI color utilities using native Node.js.\n * Provides terminal colors without external dependencies.\n */\n\n// ANSI escape codes\nconst RESET = \"\\x1b[0m\";\nconst BOLD = \"\\x1b[1m\";\nconst DIM = \"\\x1b[2m\";\n\n// Foreground colors\nconst FG_RED = \"\\x1b[31m\";\nconst FG_GREEN = \"\\x1b[32m\";\nconst FG_YELLOW = \"\\x1b[33m\";\nconst FG_CYAN = \"\\x1b[36m\";\nconst FG_GRAY = \"\\x1b[90m\";\n\n// Colors enabled by default, disabled by NO_COLOR env var\nlet colorEnabled = process.env.NO_COLOR === undefined;\n\nexport function setColorEnabled(enabled: boolean): void {\n colorEnabled = enabled;\n}\n\nexport function isColorEnabled(): boolean {\n return colorEnabled;\n}\n\nfunction colorize(text: string, code: string): string {\n if (!colorEnabled) return text;\n return `${code}${text}${RESET}`;\n}\n\nexport const colors = {\n reset: (text: string) => (colorEnabled ? `${RESET}${text}` : text),\n bold: (text: string) => colorize(text, BOLD),\n dim: (text: string) => colorize(text, DIM),\n red: (text: string) => colorize(text, FG_RED),\n green: (text: string) => colorize(text, FG_GREEN),\n yellow: (text: string) => colorize(text, FG_YELLOW),\n cyan: (text: string) => colorize(text, FG_CYAN),\n gray: (text: string) => colorize(text, FG_GRAY),\n};\n","/* eslint-disable typescript-eslint/no-base-to-string -- generic formatter converts unknown values to strings */\nimport type { OutputFormat } from \"./types.ts\";\n\nimport { colors } from \"./utils/colors.ts\";\n\n/**\n * Output formatter for CLI commands.\n * Supports human-readable, JSON, and table formats.\n */\nexport class OutputFormatter {\n constructor(\n private format: OutputFormat = \"human\",\n private noColor: boolean = false,\n ) {}\n\n /**\n * Returns true when the current format is JSON.\n */\n isJson(): boolean {\n return this.format === \"json\";\n }\n\n /**\n * Output data in the configured format.\n * Use format='json' for AI agents and structured data.\n */\n output(data: unknown): void {\n switch (this.format) {\n case \"json\":\n console.log(JSON.stringify(data, null, 2));\n break;\n case \"table\":\n this.outputTable(data);\n break;\n default:\n console.log(data);\n break;\n }\n }\n\n /**\n * Output raw text such as a log. Human and table formats print the text\n * verbatim (a log is not tabular data); JSON wraps it as a JSON string.\n */\n outputText(text: string): void {\n if (this.format === \"json\") {\n console.log(JSON.stringify(text));\n return;\n }\n console.log(text);\n }\n\n /**\n * Output a list with a human-readable one-liner per item and a table for table format.\n * Falls back to JSON for json format.\n *\n * @param data - Array of items to display\n * @param columns - Ordered list of field names to include in human/table output\n * @param emptyMessage - Message shown when data is empty (human format only)\n * @param lineFormat - Optional custom line formatter for human format\n */\n outputList<T extends object>(\n data: T[],\n columns: (keyof T & string)[],\n emptyMessage: string,\n lineFormat?: (item: T) => string,\n ): void {\n if (this.format === \"json\") {\n console.log(JSON.stringify(data, null, 2));\n return;\n }\n\n if (this.format === \"table\") {\n this.outputTable(data.map((item) => Object.fromEntries(columns.map((c) => [c, item[c]]))));\n return;\n }\n\n // human\n if (data.length === 0) {\n console.log(colors.cyan(emptyMessage));\n return;\n }\n\n if (lineFormat) {\n for (const item of data) {\n console.log(lineFormat(item));\n }\n return;\n }\n\n // Default: padded columns\n const widths = columns.map((col) =>\n Math.max(\n col.length,\n ...data.map((item) => String((item as Record<string, unknown>)[col] ?? \"\").length),\n ),\n );\n for (const item of data) {\n const row = item as Record<string, unknown>;\n const line = columns.map((col, i) => String(row[col] ?? \"\").padEnd(widths[i])).join(\" \");\n console.log(line.trimEnd());\n }\n }\n\n /**\n * Output a single resource in human-readable format.\n * For json format, uses JSON.stringify. For table format, uses a two-column key/value table.\n *\n * @param data - Object to display\n * @param fields - Ordered list of fields to display (defaults to all keys)\n */\n outputOne<T extends object>(data: T, fields?: (keyof T & string)[]): void {\n if (this.format === \"json\") {\n console.log(JSON.stringify(data, null, 2));\n return;\n }\n\n const record = data as Record<string, unknown>;\n const keys = fields ?? (Object.keys(record) as (keyof T & string)[]);\n\n if (this.format === \"table\") {\n const rows = keys.map((k) => ({ field: k, value: String(record[k] ?? \"\") }));\n this.outputTable(rows);\n return;\n }\n\n // human: key: value pairs\n const keyWidth = Math.max(...keys.map((k) => k.length));\n for (const key of keys) {\n const value = record[key];\n const valueStr =\n value !== null && typeof value === \"object\" ? JSON.stringify(value) : String(value ?? \"\");\n console.log(`${colors.cyan(key.padEnd(keyWidth))} ${valueStr}`);\n }\n }\n\n success(message: string): void {\n if (this.format === \"json\") {\n console.log(JSON.stringify({ status: \"success\", message }));\n } else {\n console.log(colors.green(`✓ ${message}`));\n }\n }\n\n error(message: string, details?: unknown): void {\n if (this.format === \"json\") {\n console.error(JSON.stringify({ status: \"error\", message, details }));\n } else {\n console.error(colors.red(`✗ ${message}`));\n if (details !== undefined) {\n console.error(details);\n }\n }\n }\n\n warning(message: string): void {\n if (this.format === \"json\") {\n console.log(JSON.stringify({ status: \"warning\", message }));\n } else {\n console.log(colors.yellow(`⚠ ${message}`));\n }\n }\n\n info(message: string): void {\n if (this.format === \"json\") {\n console.log(JSON.stringify({ status: \"info\", message }));\n } else {\n console.log(colors.cyan(message));\n }\n }\n\n private outputTable(data: unknown): void {\n if (Array.isArray(data) && data.length > 0) {\n const headers = Object.keys(data[0] as Record<string, unknown>);\n const colWidths = headers.map((h) => {\n const maxDataWidth = Math.max(\n ...data.map((row) => String((row as Record<string, unknown>)[h] ?? \"\").length),\n );\n return Math.max(h.length, maxDataWidth);\n });\n\n // Header\n const headerRow = headers.map((h, i) => h.padEnd(colWidths[i])).join(\" | \");\n console.log(headerRow);\n console.log(colWidths.map((w) => \"-\".repeat(w)).join(\"-+-\"));\n\n // Rows\n for (const row of data) {\n const rowStr = headers\n .map((h, i) => String((row as Record<string, unknown>)[h] ?? \"\").padEnd(colWidths[i]))\n .join(\" | \");\n console.log(rowStr);\n }\n }\n }\n}\n","/**\n * CLI execution context with dependency injection.\n *\n * Instead of hidden dependencies in command handlers, this module provides\n * explicit dependency passing for testability and transparency.\n */\n\nimport type { HttpClient } from \"@studiometa/forge-api\";\nimport type { ExecutorContext } from \"@studiometa/forge-core\";\n\nimport { type OutputFormat, isOutputFormat } from \"./types.ts\";\n\nimport { HttpClient as ForgeHttpClient } from \"@studiometa/forge-api\";\nimport { ConfigError } from \"./errors.ts\";\nimport { createConfigStore, getToken, getOrganizationSlug } from \"./config.ts\";\nimport { OutputFormatter } from \"./output.ts\";\n\nexport interface CommandOptions {\n [key: string]: string | boolean | string[] | undefined;\n format?: string;\n f?: string;\n token?: string;\n \"no-color\"?: boolean;\n}\n\n/**\n * Context containing all dependencies needed by command handlers.\n */\nexport interface CommandContext {\n /** Output formatter for the current format */\n readonly formatter: OutputFormatter;\n /** Raw CLI options */\n readonly options: CommandOptions;\n /** Create an ExecutorContext from this command context */\n createExecutorContext(token: string): ExecutorContext;\n /** Resolve the API token or throw ConfigError */\n getToken(): string;\n}\n\n/**\n * Creates a command context from CLI options.\n */\nexport function createContext(options: CommandOptions = {}): CommandContext {\n const rawFormat = options.format ?? options.f ?? \"human\";\n const format: OutputFormat = isOutputFormat(rawFormat) ? rawFormat : \"human\";\n const noColor = options[\"no-color\"] === true;\n const formatter = new OutputFormatter(format, noColor);\n\n return {\n formatter,\n options,\n\n createExecutorContext(token: string): ExecutorContext {\n const client: HttpClient = new ForgeHttpClient({ token });\n const orgSlug =\n (typeof options.org === \"string\" && options.org) ||\n getOrganizationSlug(createConfigStore()) ||\n undefined;\n return { client, organizationSlug: orgSlug };\n },\n\n getToken(): string {\n // 1. CLI arg\n if (typeof options.token === \"string\" && options.token) {\n return options.token;\n }\n // 2. Env var / config file\n const stored = getToken(createConfigStore());\n if (stored) {\n return stored;\n }\n // 3. Not found — throw\n throw ConfigError.missingToken();\n },\n };\n}\n\n/**\n * Creates a test context for unit tests.\n *\n * @example\n * ```typescript\n * const ctx = createTestContext({\n * mockClient: { get: async () => ({ servers: [] }) } as never,\n * });\n * ```\n */\nexport function createTestContext(overrides: {\n options?: CommandOptions;\n formatter?: OutputFormatter;\n token?: string;\n mockClient?: HttpClient;\n}): CommandContext {\n const opts: CommandOptions = overrides.options ?? {\n format: \"json\",\n \"no-color\": true,\n };\n const rawFormat = opts.format ?? \"json\";\n const format: OutputFormat = isOutputFormat(rawFormat) ? rawFormat : \"json\";\n const formatter = overrides.formatter ?? new OutputFormatter(format, true);\n\n return {\n formatter,\n options: opts,\n createExecutorContext(_token: string): ExecutorContext {\n if (!overrides.mockClient) {\n throw new Error(\n \"createTestContext: provide mockClient to use createExecutorContext in tests\",\n );\n }\n return { client: overrides.mockClient, organizationSlug: \"test-org\" };\n },\n getToken(): string {\n if (overrides.token) return overrides.token;\n if (typeof opts.token === \"string\") return opts.token;\n return \"test-token\";\n },\n };\n}\n","/**\n * Centralized error handling for forge.\n */\n\nimport type { OutputFormatter } from \"./output.ts\";\n\nimport { ApiError, CliError, ConfigError, ValidationError, isCliError } from \"./errors.ts\";\n\n/**\n * Exit codes for different error categories.\n */\nexport const ExitCode = {\n SUCCESS: 0,\n GENERAL_ERROR: 1,\n AUTHENTICATION_ERROR: 2,\n VALIDATION_ERROR: 3,\n CONFIG_ERROR: 4,\n NOT_FOUND_ERROR: 5,\n} as const;\n\nexport type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];\n\n/**\n * Determine the appropriate exit code for an error.\n */\nexport function getExitCode(error: unknown): ExitCode {\n if (error instanceof ValidationError) {\n return ExitCode.VALIDATION_ERROR;\n }\n if (error instanceof ApiError) {\n if (error.statusCode === 401) return ExitCode.AUTHENTICATION_ERROR;\n if (error.statusCode === 404) return ExitCode.NOT_FOUND_ERROR;\n }\n if (isCliError(error) && error.code === \"CONFIG_ERROR\") {\n return ExitCode.CONFIG_ERROR;\n }\n return ExitCode.GENERAL_ERROR;\n}\n\n/**\n * Central error handler for all commands.\n */\nexport function handleError(\n error: unknown,\n formatter: OutputFormatter,\n options?: { exit?: boolean },\n): ExitCode {\n const shouldExit = options?.exit ?? true;\n\n let cliError: CliError;\n if (isCliError(error)) {\n cliError = error;\n } else {\n cliError = ApiError.fromForgeError(error);\n }\n\n // Output error in appropriate format\n if (formatter.isJson()) {\n formatter.output(cliError.toJSON());\n } else {\n formatter.error(cliError.toFormattedMessage());\n }\n\n const exitCode = getExitCode(cliError);\n\n if (shouldExit) {\n process.exit(exitCode);\n }\n\n return exitCode;\n}\n\n/**\n * Wraps a command function with automatic error handling.\n */\nexport async function runCommand<T>(\n fn: () => Promise<T>,\n formatter: OutputFormatter,\n): Promise<T | undefined> {\n try {\n return await fn();\n } catch (error) {\n handleError(error, formatter);\n return undefined; // unreachable due to process.exit\n }\n}\n\n/**\n * Outputs an error and exits.\n */\nexport function exitWithError(error: CliError, formatter: OutputFormatter): never {\n handleError(error, formatter);\n // Unreachable in production\n throw error;\n}\n\n/**\n * Exits with a validation error for a missing required argument.\n */\nexport function exitWithValidationError(\n field: string,\n usage: string,\n formatter: OutputFormatter,\n): never {\n exitWithError(ValidationError.required(field, [`Usage: ${usage}`]), formatter);\n}\n\n/**\n * Exits with a config error when token is missing.\n */\nexport function exitWithConfigError(formatter: OutputFormatter): never {\n const error = ConfigError.missingToken();\n handleError(error, formatter);\n throw error;\n}\n"],"mappings":"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"}
|