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