UNPKG

forest-cli

Version:

The Lumberjacks' toolbelt is the Forest CLI which makes easy to manage your back office application directly from the terminal.

308 lines 26.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.formatOps = exports.assertNonInteractiveFlags = exports.buildAllOps = exports.toPlannedOps = void 0; const core_1 = require("@oclif/core"); const promises_1 = require("fs/promises"); const path_1 = __importDefault(require("path")); const abstract_authenticated_command_1 = __importDefault(require("../../abstract-authenticated-command")); const errors_1 = require("../../services/layout/errors"); const layout_manager_1 = __importDefault(require("../../services/layout/layout-manager")); const patch_rules_1 = require("../../services/layout/patch-rules"); const plan_format_1 = require("../../services/layout/plan-format"); const resolve_command_scope_1 = require("../../services/layout/resolve-command-scope"); const types_1 = require("../../services/layout/types"); /** * Domains this command accepts. Scoped to the layout-only domains on purpose: * `workflows` is deferred because creating a workflow is not a single layout * PATCH — it also needs a BPMN upload to S3 (use `forest layout apply` for that). */ const PATCH_DOMAINS = ['layout', 'folders']; const OP_PREFIX = { add: '+', remove: '-', replace: '~' }; const STDIN_FIRST_BYTE_TIMEOUT_MS = 10000; function readStdin() { return new Promise((resolve, reject) => { let data = ''; let timeout; // Detach our listeners and release stdin on every exit path: a lingering // flowing-mode listener would keep the event loop alive and hang the CLI. const cleanup = () => { clearTimeout(timeout); process.stdin.removeAllListeners('data'); process.stdin.removeAllListeners('end'); process.stdin.removeAllListeners('error'); process.stdin.pause(); }; // In a spawned process with an open-but-silent stdin, `isTTY` is undefined // and `end` never fires (oclif's own parser defends against the same hang): // give the FIRST byte a deadline, then let a slow-but-real pipe stream for // as long as it needs. timeout = setTimeout(() => { cleanup(); reject(new Error(`No data received on stdin after ${STDIN_FIRST_BYTE_TIMEOUT_MS / 1000}s. ` + 'Pass a JSON file argument, or pipe the patch via stdin.')); }, STDIN_FIRST_BYTE_TIMEOUT_MS); process.stdin.setEncoding('utf8'); process.stdin.on('data', chunk => { clearTimeout(timeout); data += chunk; }); process.stdin.on('end', () => { cleanup(); resolve(data); }); process.stdin.on('error', error => { cleanup(); reject(error); }); }); } async function readRawInput(filePath) { if (!filePath || filePath === '-') { if (process.stdin.isTTY) { throw new Error('No input provided. Pass a JSON file or pipe the patch via stdin.\n' + ' Example: forest layout:patch ops.json --env Production\n' + ' Example: echo \'{"layout":[...]}\' | forest layout:patch --force -p 42 -e Production -t Operations'); } return readStdin(); } return (0, promises_1.readFile)(path_1.default.resolve(process.cwd(), filePath), 'utf8'); } async function readInput(filePath) { const raw = await readRawInput(filePath); let parsed; try { parsed = JSON.parse(raw); } catch { throw new Error('Invalid JSON: could not parse the patch input.'); } if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { throw new Error('Invalid patch input: expected a JSON object with domain keys (e.g. {"layout": [...], "folders": [...]}).'); } return parsed; } function toPlannedOps(domain, rawOps) { return rawOps.map((raw, i) => { if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) { throw new Error(`[${domain}][${i}]: each operation must be a JSON object.`); } if (!['add', 'replace', 'remove', 'test'].includes(raw.op)) { throw new Error(`[${domain}][${i}]: unknown op "${raw.op}".`); } if (typeof raw.path !== 'string' || !raw.path.startsWith('/')) { throw new Error(`[${domain}][${i}]: path must be a string starting with "/".`); } if ((raw.op === 'add' || raw.op === 'replace' || raw.op === 'test') && raw.value === undefined) { throw new Error(`[${domain}][${i}]: op "${raw.op}" requires a "value" field.`); } if (!(0, patch_rules_1.matchesWhitelist)(domain, raw)) { throw new Error(`[${domain}][${i}]: path "${raw.path}" is not allowed by the server whitelist.`); } return { domain, jsonPath: raw.path, label: `${raw.op} ${raw.path}`, op: raw.op, path: raw.path, value: raw.value, }; }); } exports.toPlannedOps = toPlannedOps; function buildAllOps(input) { const unknownDomains = Object.keys(input).filter(key => !types_1.LAYOUT_DOMAINS.includes(key)); if (unknownDomains.length > 0) { throw new Error(`Unknown domain key${unknownDomains.length > 1 ? 's' : ''}: ${unknownDomains.join(', ')}. ` + `Supported domains are: ${PATCH_DOMAINS.join(', ')}.`); } if (input.workflows !== undefined) { throw new Error('The "workflows" domain is not supported by `forest layout patch`: creating a workflow ' + 'also needs a BPMN upload, not just a layout PATCH. Use `forest layout apply` for workflows.'); } return PATCH_DOMAINS.flatMap(domain => { const rawOps = input[domain]; if (!rawOps) return []; if (!Array.isArray(rawOps)) { throw new Error(`"${domain}" must be an array of patch operations.`); } return toPlannedOps(domain, rawOps); }); } exports.buildAllOps = buildAllOps; /** * When stdin is not an interactive terminal — the patch is piped, or the * command runs in CI / spawned by an agent, even with a file argument — no * inquirer prompt (project, environment, team, confirmation) can run: it would * read from a pipe and hang or crash. Non-TTY runs therefore require the full * scope and `--force` up front — fail fast, listing exactly what is missing. * * `FOREST_ENV_SECRET` only stands in for `-p/--projectId`: scope resolution * derives the project from the secret (`withCurrentProject`) but NEVER the * environment — without `-e/--env` it would still prompt. */ function assertNonInteractiveFlags(flags, hasEnvSecret) { const missing = []; if (!flags.env) missing.push('-e/--env'); if (!hasEnvSecret && flags.projectId === undefined) missing.push('-p/--projectId'); if (!flags.team) missing.push('-t/--team'); if (!flags.force) missing.push('--force'); if (missing.length > 0) { throw new Error('stdin is not an interactive terminal, so prompts ' + `(project, environment, team, confirmation) cannot run. Missing: ${missing.join(', ')}. ` + 'Set FOREST_ENV_SECRET to omit -p/--projectId, or use --dry-run to preview without a scope.'); } } exports.assertNonInteractiveFlags = assertNonInteractiveFlags; function formatOps(allOps) { return PATCH_DOMAINS.flatMap(domain => { const domainOps = allOps.filter(op => op.domain === domain); if (domainOps.length === 0) return []; return [ `${domain} (${domainOps.length} op${domainOps.length > 1 ? 's' : ''})`, ...domainOps.map(op => ` ${OP_PREFIX[op.op] ?? '·'} ${op.label}`), ]; }).join('\n'); } exports.formatOps = formatOps; /** * `forest layout patch` — send a JSON-Patch array straight to the environment, * exactly like the frontend does when you tweak a collection: a single * `PATCH /api/:domain` per domain, no `pull`/`diff`/`apply` round-trip. * * Scope: the layout-only domains (`layout`, `folders`). `workflows` is out of * scope here — see {@link buildAllOps}. * * Input format (file or stdin): * { * "layout": [{ "op": "replace", "path": "/collections/orders/displayName", "value": "Orders" }], * "folders": [{ "op": "add", "path": "/folders/<mainId>/children/-", "value": {...} }] * } * * Any non-TTY run (piped input, CI, spawned by an agent) is non-interactive by * construction: it requires the full scope flags and `--force` — see * {@link assertNonInteractiveFlags}. */ class LayoutPatchCommand extends abstract_authenticated_command_1.default { constructor(argv, config, plan) { super(argv, config, plan); const { assertPresent, env, inquirer } = this.context; assertPresent({ env, inquirer }); this.env = env; this.inquirer = inquirer; } async runAuthenticated() { const { args, flags } = await this.parse(LayoutPatchCommand); // No TTY on stdin ⇒ no prompt can run (scope or confirmation) — whether the // patch is piped or read from a file argument in CI. `--dry-run` is exempt: // it neither prompts nor sends. if (!process.stdin.isTTY && !flags['dry-run']) { assertNonInteractiveFlags(flags, Boolean(this.env.FOREST_ENV_SECRET)); } const allOps = buildAllOps(await readInput(args.file)); if (allOps.length === 0) { this.logger.success('Nothing to patch: input contains no operations.'); return; } if (flags['dry-run']) { this.log(formatOps(allOps)); this.log('\n(dry-run: nothing sent)'); return; } const scope = await (0, resolve_command_scope_1.resolveCommandScope)({ baseEnv: this.env, flags: { env: flags.env, projectId: flags.projectId, team: flags.team }, }); this.log(formatOps(allOps)); if (!flags.force && !(await this.confirm(scope.environmentName, scope.teamName, allOps.length))) { this.logger.info('Aborted: nothing was patched.'); return; } await this.sendPatches(new layout_manager_1.default(), scope, allOps); this.logger.success(`Patched ${allOps.length} op${allOps.length > 1 ? 's' : ''} on ${this.chalk.bold(scope.environmentName)} / ${this.chalk.bold(scope.teamName)}.`); } /** One atomic PATCH per domain, in stable order. Exits 2 on a recoverable API error. */ async sendPatches(manager, scope, allOps) { const applied = []; // eslint-disable-next-line no-restricted-syntax -- sequential: one atomic PATCH per domain for (const domain of PATCH_DOMAINS) { const domainOps = allOps.filter(op => op.domain === domain); if (domainOps.length === 0) continue; // eslint-disable-line no-continue try { // eslint-disable-next-line no-await-in-loop -- intentional: atomic, ordered per-domain patches await manager.patchDomain(domain, domainOps, scope); applied.push(domain); } catch (error) { if (error instanceof errors_1.LayoutApiError && error.status !== 401) { // Scope the error to this domain so path correlation in explainApiError is accurate. this.logger.error((0, plan_format_1.explainApiError)(error, domainOps)); if (applied.length > 0) { // Unlike `apply`, a raw patch is NOT idempotent: replaying its "add" // ops would create duplicates on the domains that already succeeded. this.logger.warn(`The "${applied.join('", "')}" patch was already applied before this failure. ` + 'Do not re-run the same input as-is: its "add" operations would be applied twice — ' + 'remove the applied domain(s) from the input before retrying.'); } this.exit(2); } throw error; } } } async confirm(environmentName, teamName, count) { const { confirm } = await this.inquirer.prompt([ { message: `Send ${count} patch operation${count > 1 ? 's' : ''} to ${environmentName} / ${teamName}?`, name: 'confirm', type: 'confirm', }, ]); return confirm; } } exports.default = LayoutPatchCommand; LayoutPatchCommand.args = { file: core_1.Args.string({ description: 'JSON patch file to apply. Use "-" or omit to read from stdin.', // Without this, oclif fills a missing positional FROM stdin (parser/parse.js), // so `echo '{...}' | forest layout patch` would readFile() the JSON as a filename. // Stdin must stay untouched for our own reader (multi-line JSON, explicit guard). ignoreStdin: true, required: false, }), }; LayoutPatchCommand.description = 'Patch the layout directly (single PATCH per domain, like the front — no pull/apply).'; LayoutPatchCommand.flags = { 'dry-run': core_1.Flags.boolean({ description: 'Print operations without sending them.', }), env: core_1.Flags.string({ char: 'e', description: 'Environment name or id.', }), force: core_1.Flags.boolean({ char: 'f', description: 'Skip the confirmation prompt (required when reading from stdin).', }), projectId: core_1.Flags.integer({ char: 'p', description: 'Project id.', }), team: core_1.Flags.string({ char: 't', description: 'Team name or id.', }), }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGF0Y2guanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29tbWFuZHMvbGF5b3V0L3BhdGNoLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUdBLHNDQUEwQztBQUMxQywwQ0FBdUM7QUFDdkMsZ0RBQXdCO0FBRXhCLDBHQUFnRjtBQUNoRix5REFBOEQ7QUFDOUQsMEZBQWlFO0FBQ2pFLG1FQUFxRTtBQUNyRSxtRUFBb0U7QUFDcEUsdUZBQWtGO0FBQ2xGLHVEQUE2RDtBQUU3RDs7OztHQUlHO0FBQ0gsTUFBTSxhQUFhLEdBQThDLENBQUMsUUFBUSxFQUFFLFNBQVMsQ0FBQyxDQUFDO0FBS3ZGLE1BQU0sU0FBUyxHQUEyQixFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsTUFBTSxFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsR0FBRyxFQUFFLENBQUM7QUFFbEYsTUFBTSwyQkFBMkIsR0FBRyxLQUFNLENBQUM7QUFFM0MsU0FBUyxTQUFTO0lBQ2hCLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7UUFDckMsSUFBSSxJQUFJLEdBQUcsRUFBRSxDQUFDO1FBQ2QsSUFBSSxPQUFzQyxDQUFDO1FBRTNDLHlFQUF5RTtRQUN6RSwwRUFBMEU7UUFDMUUsTUFBTSxPQUFPLEdBQUcsR0FBRyxFQUFFO1lBQ25CLFlBQVksQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUN0QixPQUFPLENBQUMsS0FBSyxDQUFDLGtCQUFrQixDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBQ3pDLE9BQU8sQ0FBQyxLQUFLLENBQUMsa0JBQWtCLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDeEMsT0FBTyxDQUFDLEtBQUssQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUMxQyxPQUFPLENBQUMsS0FBSyxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ3hCLENBQUMsQ0FBQztRQUVGLDJFQUEyRTtRQUMzRSw0RUFBNEU7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