UNPKG

forest-cli

Version:

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

167 lines 14.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const core_1 = require("@oclif/core"); const fs_1 = require("fs"); const abstract_authenticated_command_1 = __importDefault(require("../../abstract-authenticated-command")); const environment_manager_1 = __importDefault(require("../../services/environment-manager")); const role_manager_1 = __importDefault(require("../../services/role-manager")); const roles_csv_1 = require("../../services/roles-csv"); const with_current_project_1 = __importDefault(require("../../services/with-current-project")); class RolesApplyCommand 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; } resolveEnvByName(environments, name) { const match = environments.find(e => e.name === name); if (match) return match; const available = environments.map(e => e.name).join(', ') || '(none)'; this.logger.error(`Environment "${name}" not found in this project. Available: ${available}.`); return this.exit(1); } async runAuthenticated() { const { args, flags } = await this.parse(RolesApplyCommand); // 1. Read the CSV file let csvContent; try { csvContent = (0, fs_1.readFileSync)(args.file, 'utf8'); } catch { this.logger.error(`Cannot read file "${args.file}". Check that the path is correct and the file is readable.`); this.exit(1); return; } const config = await (0, with_current_project_1.default)({ ...this.env, projectId: flags.projectId }); try { await this.applyCsv(config, csvContent, flags.env, Boolean(flags.force)); } catch (error) { this.surfaceApiError(error); } } async applyCsv(config, csvContent, envName, force) { const environments = await new environment_manager_1.default(config).listEnvironments(); const environment = this.resolveEnvByName(environments, envName); const envId = String(environment.id); const roleManager = new role_manager_1.default(config); const roleList = await roleManager.listForProject(); // Write command: the current state must be complete to diff safely, so any // per-role fetch failure aborts rather than diffing against a partial baseline. const fullRoles = []; // eslint-disable-next-line no-restricted-syntax -- sequential: one fetch at a time for (const role of roleList) { // eslint-disable-next-line no-await-in-loop fullRoles.push(await roleManager.getRoleById(role.id)); } const desired = (0, roles_csv_1.parseWide)(csvContent, envId); const diffs = (0, roles_csv_1.computeDiff)(this.buildCurrentState(fullRoles, envId), desired); // apply only updates existing roles; creating roles is `roles:create`'s job. const unknown = diffs.filter(diff => diff.roleId == null).map(diff => diff.roleName); if (unknown.length) { this.logger.error(`Role(s) not found: ${unknown.join(', ')}. Create them first with \`forest roles:create\`.`); this.exit(1); return; } diffs.forEach(diff => this.logger.info(`Role ${diff.roleName}: ${diff.ops.length} change(s)`)); const totalOps = diffs.reduce((sum, diff) => sum + diff.ops.length, 0); if (totalOps === 0) { this.logger.info('Nothing to apply.'); return; } if (!force && !(await this.confirmApply(totalOps, envName))) { this.logger.info('Aborted: no change made.'); return; } await this.patchAll(roleManager, diffs); } // Normalize fetched roles into the parseWide shape, re-pairing ids by NAME (not // array index) so a reordering in format/parse can't misattribute them. // eslint-disable-next-line class-methods-use-this -- pure helper kept beside its caller buildCurrentState(fullRoles, envId) { const idByName = new Map(fullRoles.map(role => [role.name, role.id])); return (0, roles_csv_1.parseWide)((0, roles_csv_1.formatWide)(fullRoles, envId), envId).map(role => ({ ...role, id: idByName.get(role.name), })); } async confirmApply(totalOps, envName) { const { confirm } = await this.inquirer.prompt([ { message: `Apply ${totalOps} change(s) to environment "${envName}"?`, name: 'confirm', type: 'confirm', }, ]); return Boolean(confirm); } async patchAll(roleManager, diffs) { let applied = 0; try { // eslint-disable-next-line no-restricted-syntax -- sequential: one role at a time for (const diff of diffs) { // eslint-disable-next-line no-continue if (diff.ops.length === 0) continue; // roleId is non-null here: unknown roles were rejected above. // eslint-disable-next-line no-await-in-loop await roleManager.patchPermissions(diff.roleId, diff.ops); applied += 1; } } catch (error) { this.logger.warn(`Applied ${applied} role(s) before the error — \`forest roles:apply\` is idempotent, re-run to converge.`); throw error; } this.logger.info(`Applied changes to ${applied} role(s).`); } surfaceApiError(error) { // 401/403 keep flowing to the authenticated-command handler. const { response, status } = error; if (response && status !== 401 && status !== 403 && response.text) { let detail; try { detail = JSON.parse(response.text)?.errors?.[0]?.detail; } catch { // Non-JSON error body: fall through and rethrow the original error. } if (detail) { this.logger.error(detail); this.exit(1); return; } } throw error; } } exports.default = RolesApplyCommand; RolesApplyCommand.description = 'Apply a wide-format CSV to update role permissions in an environment.'; RolesApplyCommand.args = { file: core_1.Args.string({ description: 'Path to the wide-format CSV file.', required: true, }), }; RolesApplyCommand.flags = { env: core_1.Flags.string({ char: 'e', description: 'Environment name to apply to.', required: true, }), force: core_1.Flags.boolean({ char: 'F', description: 'Skip confirmation prompt.', }), projectId: core_1.Flags.integer({ char: 'p', description: 'Forest project ID.', }), }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwbHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29tbWFuZHMvcm9sZXMvYXBwbHkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFFQSxzQ0FBMEM7QUFDMUMsMkJBQWtDO0FBRWxDLDBHQUFnRjtBQUNoRiw2RkFBb0U7QUFDcEUsK0VBQXNEO0FBQ3RELHdEQUE4RTtBQUM5RSwrRkFBcUU7QUFVckUsTUFBcUIsaUJBQWtCLFNBQVEsd0NBQTRCO0lBK0J6RSxZQUFZLElBQWMsRUFBRSxNQUFjLEVBQUUsSUFBSztRQUMvQyxLQUFLLENBQUMsSUFBSSxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FBQztRQUMxQixNQUFNLEVBQUUsYUFBYSxFQUFFLEdBQUcsRUFBRSxRQUFRLEVBQUUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDO1FBQ3RELGFBQWEsQ0FBQyxFQUFFLEdBQUcsRUFBRSxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBQ2pDLElBQUksQ0FBQyxHQUFHLEdBQUcsR0FBRyxDQUFDO1FBQ2YsSUFBSSxDQUFDLFFBQVEsR0FBRyxRQUFRLENBQUM7SUFDM0IsQ0FBQztJQUVPLGdCQUFnQixDQUFDLFlBQTJCLEVBQUUsSUFBWTtRQUNoRSxNQUFNLEtBQUssR0FBRyxZQUFZLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksS0FBSyxJQUFJLENBQUMsQ0FBQztRQUN0RCxJQUFJLEtBQUs7WUFBRSxPQUFPLEtBQUssQ0FBQztRQUN4QixNQUFNLFNBQVMsR0FBRyxZQUFZLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxRQUFRLENBQUM7UUFDdkUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsZ0JBQWdCLElBQUksMkNBQTJDLFNBQVMsR0FBRyxDQUFDLENBQUM7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