@auth-gate/rbac 0.8.0

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/dist/cli.d.cts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/cli.mjs ADDED
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ RbacSyncClient,
4
+ __spreadValues,
5
+ computeRbacDiff,
6
+ formatRbacDiff,
7
+ loadRbacConfig
8
+ } from "./chunk-HE57TIQI.mjs";
9
+
10
+ // src/cli.ts
11
+ import chalk from "chalk";
12
+ import { writeFileSync, existsSync } from "fs";
13
+ import { resolve } from "path";
14
+ var HELP = `
15
+ Usage: @auth-gate/rbac <command> [options]
16
+
17
+ Commands:
18
+ sync Preview or apply RBAC config changes
19
+ init Create a starter authgate.rbac.ts config file
20
+ check Validate config locally (no server round-trip)
21
+
22
+ Options (sync):
23
+ --apply Apply changes (default: dry-run only)
24
+ --force Allow archiving roles with assigned members
25
+ --strict Treat warnings as errors (recommended for CI/CD)
26
+ --json Output diff as JSON
27
+
28
+ Environment:
29
+ AUTHGATE_API_KEY Your project API key (required for sync)
30
+ AUTHGATE_BASE_URL AuthGate instance URL (required for sync)
31
+ `;
32
+ async function main() {
33
+ const args = process.argv.slice(2);
34
+ const command = args[0];
35
+ if (!command || command === "--help" || command === "-h") {
36
+ console.log(HELP);
37
+ process.exit(0);
38
+ }
39
+ if (command === "init") {
40
+ await runInit();
41
+ return;
42
+ }
43
+ if (command === "check") {
44
+ await runCheck();
45
+ return;
46
+ }
47
+ if (command === "sync") {
48
+ const apply = args.includes("--apply");
49
+ const force = args.includes("--force");
50
+ const strict = args.includes("--strict");
51
+ const json = args.includes("--json");
52
+ await runSync({ apply, force, strict, json });
53
+ return;
54
+ }
55
+ console.error(chalk.red(`Unknown command: ${command}`));
56
+ console.log(HELP);
57
+ process.exit(1);
58
+ }
59
+ async function runInit() {
60
+ const configPath = resolve(process.cwd(), "authgate.rbac.ts");
61
+ if (existsSync(configPath)) {
62
+ console.error(chalk.yellow(`Config file already exists: ${configPath}`));
63
+ process.exit(1);
64
+ }
65
+ const template = `import { defineRbac } from "@auth-gate/rbac";
66
+
67
+ /**
68
+ * RBAC config \u2014 resource/role/permission keys provide full IDE autocomplete.
69
+ *
70
+ * Sync: npx @auth-gate/rbac sync --apply
71
+ */
72
+ export const rbac = defineRbac({
73
+ resources: {
74
+ documents: { actions: ["read", "write", "delete", "share"] },
75
+ billing: { actions: ["read", "manage"] },
76
+ members: { actions: ["invite", "remove", "update_role"] },
77
+ },
78
+ roles: {
79
+ admin: {
80
+ name: "Administrator",
81
+ grants: {
82
+ documents: { read: true, write: true, delete: true, share: true },
83
+ billing: { read: true, manage: true },
84
+ members: { invite: true, remove: true, update_role: true },
85
+ },
86
+ },
87
+ editor: {
88
+ name: "Editor",
89
+ grants: {
90
+ documents: { read: true, write: true },
91
+ billing: { read: true },
92
+ },
93
+ },
94
+ viewer: {
95
+ name: "Viewer",
96
+ grants: {
97
+ documents: { read: true },
98
+ },
99
+ },
100
+ },
101
+ });
102
+
103
+ // Default export for CLI compatibility
104
+ export default rbac;
105
+ `;
106
+ writeFileSync(configPath, template, "utf-8");
107
+ console.log(chalk.green(`Created ${configPath}`));
108
+ console.log(chalk.dim("Edit your resources and roles, then run: npx @auth-gate/rbac sync"));
109
+ }
110
+ async function runCheck() {
111
+ let config;
112
+ try {
113
+ config = await loadRbacConfig(process.cwd());
114
+ } catch (err) {
115
+ console.error(chalk.red(`Config error: ${err.message}`));
116
+ process.exit(1);
117
+ }
118
+ const resourceCount = Object.keys(config.resources).length;
119
+ const roleCount = Object.keys(config.roles).length;
120
+ let permCount = 0;
121
+ for (const resource of Object.values(config.resources)) {
122
+ permCount += resource.actions.length;
123
+ }
124
+ console.log(chalk.green("Config valid!"));
125
+ console.log(chalk.dim(`${resourceCount} resource${resourceCount > 1 ? "s" : ""}, ${roleCount} role${roleCount > 1 ? "s" : ""}, ${permCount} permission${permCount > 1 ? "s" : ""}`));
126
+ }
127
+ async function runSync(opts) {
128
+ const apiKey = process.env.AUTHGATE_API_KEY;
129
+ const baseUrl = process.env.AUTHGATE_BASE_URL;
130
+ if (!apiKey) {
131
+ console.error(chalk.red("Missing AUTHGATE_API_KEY environment variable."));
132
+ process.exit(1);
133
+ }
134
+ if (!baseUrl) {
135
+ console.error(chalk.red("Missing AUTHGATE_BASE_URL environment variable."));
136
+ process.exit(1);
137
+ }
138
+ let config;
139
+ try {
140
+ config = await loadRbacConfig(process.cwd());
141
+ } catch (err) {
142
+ console.error(chalk.red(`Config error: ${err.message}`));
143
+ process.exit(1);
144
+ }
145
+ const resourceCount = Object.keys(config.resources).length;
146
+ const roleCount = Object.keys(config.roles).length;
147
+ console.log(chalk.dim(`Loaded config: ${resourceCount} resource${resourceCount > 1 ? "s" : ""}, ${roleCount} role${roleCount > 1 ? "s" : ""}`));
148
+ const client = new RbacSyncClient({ baseUrl, apiKey });
149
+ let serverState;
150
+ let memberCounts;
151
+ try {
152
+ [serverState, memberCounts] = await Promise.all([
153
+ client.getState(),
154
+ client.getMemberCounts()
155
+ ]);
156
+ } catch (err) {
157
+ console.error(chalk.red(`Failed to fetch server state: ${err.message}`));
158
+ process.exit(1);
159
+ }
160
+ const diff = computeRbacDiff(config, serverState, memberCounts);
161
+ if (opts.json) {
162
+ console.log(JSON.stringify({
163
+ dryRun: !opts.apply,
164
+ resourceOps: diff.resourceOps.map((op) => ({ type: op.type, key: op.key })),
165
+ roleOps: diff.roleOps.map((op) => __spreadValues(__spreadValues({
166
+ type: op.type,
167
+ key: op.key
168
+ }, op.type === "rename" ? { oldKey: op.oldKey } : {}), op.type === "archive" ? { assignedMembers: op.assignedMembers } : {})),
169
+ conditionOps: diff.conditionOps.map((op) => ({ type: op.type, key: op.key })),
170
+ hasDestructive: diff.hasDestructive
171
+ }, null, 2));
172
+ process.exit(diff.hasDestructive && opts.strict ? 1 : 0);
173
+ }
174
+ console.log("");
175
+ console.log(formatRbacDiff(diff, !opts.apply));
176
+ if (!opts.apply) {
177
+ if (opts.strict && diff.hasDestructive) {
178
+ console.error(chalk.red.bold("\n --strict: destructive changes detected. Failing."));
179
+ process.exit(1);
180
+ }
181
+ process.exit(0);
182
+ }
183
+ if (diff.hasDestructive && !opts.force) {
184
+ console.error(chalk.red("\nDestructive changes require --force flag."));
185
+ process.exit(1);
186
+ }
187
+ const totalOps = diff.resourceOps.length + diff.roleOps.length + diff.conditionOps.length;
188
+ if (totalOps === 0) {
189
+ process.exit(0);
190
+ }
191
+ try {
192
+ console.log("");
193
+ const result = await client.apply(config, opts.force);
194
+ console.log(chalk.green.bold("Sync complete!"));
195
+ if (result.created.length) console.log(chalk.green(` Created: ${result.created.join(", ")}`));
196
+ if (result.updated.length) console.log(chalk.yellow(` Updated: ${result.updated.join(", ")}`));
197
+ if (result.renamed.length) console.log(chalk.cyan(` Renamed: ${result.renamed.join(", ")}`));
198
+ if (result.archived.length) console.log(chalk.red(` Archived: ${result.archived.join(", ")}`));
199
+ if (result.warnings.length) {
200
+ for (const w of result.warnings) {
201
+ console.log(chalk.yellow(` Warning: ${w}`));
202
+ }
203
+ if (opts.strict) {
204
+ console.error(chalk.red.bold(`
205
+ --strict: ${result.warnings.length} warning${result.warnings.length > 1 ? "s" : ""} treated as errors.`));
206
+ process.exit(1);
207
+ }
208
+ }
209
+ } catch (err) {
210
+ console.error(chalk.red(`Sync failed: ${err.message}`));
211
+ process.exit(1);
212
+ }
213
+ }
214
+ main().catch((err) => {
215
+ console.error(chalk.red(err.message));
216
+ process.exit(1);
217
+ });