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
JavaScript
;
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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