@go-labs-sg/rsvp 0.3.0 → 0.6.1
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/README.md +37 -2
- package/dist/index.js +352 -423
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -49,18 +49,53 @@ rsvp auth status
|
|
|
49
49
|
rsvp auth whoami
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
`rsvp auth login` opens RSVP in the system browser. Sign in through RSVP's normal
|
|
52
|
+
`rsvp auth login` opens RSVP in the system browser. Sign in through RSVP's normal email one-time-password flow if needed, verify that the displayed device code matches the terminal, then click **Authorize CLI**. Existing browser sessions skip another email-code prompt but never skip explicit CLI approval.
|
|
53
53
|
|
|
54
54
|
RSVP CLI access is limited to global administrators. The server rechecks the account's current global role and ban state when issuing, refreshing, and authenticating CLI credentials, so removing the admin role immediately blocks further CLI access.
|
|
55
55
|
|
|
56
|
-
The CLI polls automatically and stores only RSVP-issued credentials through `Bun.secrets`. It does not store
|
|
56
|
+
The CLI polls automatically and stores only RSVP-issued credentials through `Bun.secrets`. It does not store browser or email-OTP credentials and has no API-key or environment-variable fallback. Use `rsvp auth logout` to revoke the remote session and remove its local credential. For SSH or containers, pass `--no-browser` and open the printed URL on another device.
|
|
57
57
|
|
|
58
58
|
The default API is `https://rsvp.getout.events`. Override it with `RSVP_API_URL` or `--api-url`; non-local overrides must use HTTPS.
|
|
59
59
|
|
|
60
|
+
Global administrators can update another account's global role from the web user manager or with `rsvp user role update --allow-state-change --target <user-id> --payload '{"userId":"<user-id>","role":"admin"}'`. Administrators cannot remove their own global admin role.
|
|
61
|
+
|
|
60
62
|
## Command safety
|
|
61
63
|
|
|
64
|
+
Argument-parsing errors use a safe command label and never echo option values,
|
|
65
|
+
including roster JSON passed through `--payload`. Surrounding shell history and
|
|
66
|
+
automation can still record arguments independently of the CLI's error output.
|
|
67
|
+
|
|
62
68
|
Use `rsvp help` for the command catalogue. Every response is a JSON envelope on stdout; errors and diagnostics go to stderr. `rsvp manifest` emits the machine-readable command reference for tooling.
|
|
63
69
|
|
|
70
|
+
For a read-only registration-journey diagnosis, inspect the event, draft,
|
|
71
|
+
published versions, and event-owned resources together:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
rsvp event get --organization-id org_123 --event-id evt_123
|
|
75
|
+
rsvp event flow draft get --organization-id org_123 --event-id evt_123
|
|
76
|
+
rsvp event flow versions list --organization-id org_123 --event-id evt_123
|
|
77
|
+
rsvp event flow resources get --organization-id org_123 --event-id evt_123
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Create an immutable menu version with an explicit mutation acknowledgement. The
|
|
81
|
+
target is the event ID in the payload:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
rsvp event flow menu version create \
|
|
85
|
+
--allow-state-change \
|
|
86
|
+
--target evt_123 \
|
|
87
|
+
--payload '{"organizationId":"org_123","eventId":"evt_123","menu":{"mode":"NEW","key":"dinner","name":"Dinner"},"choices":[{"key":"standard","label":"Standard meal","dietaryTags":[]}]}'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
To version an existing menu, use `"menu":{"mode":"EXISTING","menuId":"..."}`
|
|
91
|
+
in the payload and keep the event ID as `--target`.
|
|
92
|
+
|
|
93
|
+
Reference the returned `version.id` from the draft's `MENU_SELECTION` blocks,
|
|
94
|
+
then save with `event flow draft save`, `--allow-state-change`, and the event ID
|
|
95
|
+
as `--target`. Include the latest draft revision as `expectedRevision`; nested
|
|
96
|
+
resource IDs such as a seating block's `seatMapId` are not approval targets for
|
|
97
|
+
this command. Saving the draft does not publish the registration flow.
|
|
98
|
+
|
|
64
99
|
Commands returning invitee, registration, roster, report, audit, export, payment, or user data require `--ack-sensitive-data`. Mutations require their exact effect gates and a `--target` matching the affected resource in the payload. Depending on the operation, gates include:
|
|
65
100
|
|
|
66
101
|
- `--allow-state-change`
|
package/dist/index.js
CHANGED
|
@@ -1,38 +1,6 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
1
|
+
#!/usr/bin/env -S bun --no-env-file
|
|
2
2
|
// @bun
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
5
3
|
var __defProp = Object.defineProperty;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
function __accessProp(key) {
|
|
9
|
-
return this[key];
|
|
10
|
-
}
|
|
11
|
-
var __toESMCache_node;
|
|
12
|
-
var __toESMCache_esm;
|
|
13
|
-
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
-
var canCache = mod != null && typeof mod === "object";
|
|
15
|
-
if (canCache) {
|
|
16
|
-
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
-
var cached = cache.get(mod);
|
|
18
|
-
if (cached)
|
|
19
|
-
return cached;
|
|
20
|
-
}
|
|
21
|
-
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
|
-
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
|
-
if (mod && typeof mod === "object" || typeof mod === "function") {
|
|
24
|
-
for (let key of __getOwnPropNames(mod))
|
|
25
|
-
if (!__hasOwnProp.call(to, key))
|
|
26
|
-
__defProp(to, key, {
|
|
27
|
-
get: __accessProp.bind(mod, key),
|
|
28
|
-
enumerable: true
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
if (canCache)
|
|
32
|
-
cache.set(mod, to);
|
|
33
|
-
return to;
|
|
34
|
-
};
|
|
35
|
-
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
36
4
|
var __returnValue = (v) => v;
|
|
37
5
|
function __exportSetter(name, newValue) {
|
|
38
6
|
this[name] = __returnValue.bind(null, newValue);
|
|
@@ -46,334 +14,24 @@ var __export = (target, all) => {
|
|
|
46
14
|
set: __exportSetter.bind(all, name)
|
|
47
15
|
});
|
|
48
16
|
};
|
|
49
|
-
var __require = import.meta.require;
|
|
50
|
-
|
|
51
|
-
// ../../../node_modules/.bun/dotenv@17.4.2/node_modules/dotenv/lib/main.js
|
|
52
|
-
var require_main = __commonJS(function(exports, module) {
|
|
53
|
-
var fs = __require("fs");
|
|
54
|
-
var path = __require("path");
|
|
55
|
-
var os = __require("os");
|
|
56
|
-
var crypto = __require("crypto");
|
|
57
|
-
var TIPS = [
|
|
58
|
-
"\u25C8 encrypted .env [www.dotenvx.com]",
|
|
59
|
-
"\u25C8 secrets for agents [www.dotenvx.com]",
|
|
60
|
-
"\u2301 auth for agents [www.vestauth.com]",
|
|
61
|
-
"\u2318 custom filepath { path: '/custom/path/.env' }",
|
|
62
|
-
"\u2318 enable debugging { debug: true }",
|
|
63
|
-
"\u2318 override existing { override: true }",
|
|
64
|
-
"\u2318 suppress logs { quiet: true }",
|
|
65
|
-
"\u2318 multiple files { path: ['.env.local', '.env'] }"
|
|
66
|
-
];
|
|
67
|
-
function _getRandomTip() {
|
|
68
|
-
return TIPS[Math.floor(Math.random() * TIPS.length)];
|
|
69
|
-
}
|
|
70
|
-
function parseBoolean(value) {
|
|
71
|
-
if (typeof value === "string") {
|
|
72
|
-
return !["false", "0", "no", "off", ""].includes(value.toLowerCase());
|
|
73
|
-
}
|
|
74
|
-
return Boolean(value);
|
|
75
|
-
}
|
|
76
|
-
function supportsAnsi() {
|
|
77
|
-
return process.stdout.isTTY;
|
|
78
|
-
}
|
|
79
|
-
function dim(text) {
|
|
80
|
-
return supportsAnsi() ? `\x1B[2m${text}\x1B[0m` : text;
|
|
81
|
-
}
|
|
82
|
-
var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
|
|
83
|
-
function parse(src) {
|
|
84
|
-
const obj = {};
|
|
85
|
-
let lines = src.toString();
|
|
86
|
-
lines = lines.replace(/\r\n?/mg, `
|
|
87
|
-
`);
|
|
88
|
-
let match;
|
|
89
|
-
while ((match = LINE.exec(lines)) != null) {
|
|
90
|
-
const key = match[1];
|
|
91
|
-
let value = match[2] || "";
|
|
92
|
-
value = value.trim();
|
|
93
|
-
const maybeQuote = value[0];
|
|
94
|
-
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, "$2");
|
|
95
|
-
if (maybeQuote === '"') {
|
|
96
|
-
value = value.replace(/\\n/g, `
|
|
97
|
-
`);
|
|
98
|
-
value = value.replace(/\\r/g, "\r");
|
|
99
|
-
}
|
|
100
|
-
obj[key] = value;
|
|
101
|
-
}
|
|
102
|
-
return obj;
|
|
103
|
-
}
|
|
104
|
-
function _parseVault(options) {
|
|
105
|
-
options = options || {};
|
|
106
|
-
const vaultPath = _vaultPath(options);
|
|
107
|
-
options.path = vaultPath;
|
|
108
|
-
const result = DotenvModule.configDotenv(options);
|
|
109
|
-
if (!result.parsed) {
|
|
110
|
-
const err = new Error(`MISSING_DATA: Cannot parse ${vaultPath} for an unknown reason`);
|
|
111
|
-
err.code = "MISSING_DATA";
|
|
112
|
-
throw err;
|
|
113
|
-
}
|
|
114
|
-
const keys = _dotenvKey(options).split(",");
|
|
115
|
-
const length = keys.length;
|
|
116
|
-
let decrypted;
|
|
117
|
-
for (let i = 0;i < length; i++) {
|
|
118
|
-
try {
|
|
119
|
-
const key = keys[i].trim();
|
|
120
|
-
const attrs = _instructions(result, key);
|
|
121
|
-
decrypted = DotenvModule.decrypt(attrs.ciphertext, attrs.key);
|
|
122
|
-
break;
|
|
123
|
-
} catch (error) {
|
|
124
|
-
if (i + 1 >= length) {
|
|
125
|
-
throw error;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return DotenvModule.parse(decrypted);
|
|
130
|
-
}
|
|
131
|
-
function _warn(message) {
|
|
132
|
-
console.error(`\u26A0 ${message}`);
|
|
133
|
-
}
|
|
134
|
-
function _debug(message) {
|
|
135
|
-
console.log(`\u2506 ${message}`);
|
|
136
|
-
}
|
|
137
|
-
function _log(message) {
|
|
138
|
-
console.log(`\u25C7 ${message}`);
|
|
139
|
-
}
|
|
140
|
-
function _dotenvKey(options) {
|
|
141
|
-
if (options && options.DOTENV_KEY && options.DOTENV_KEY.length > 0) {
|
|
142
|
-
return options.DOTENV_KEY;
|
|
143
|
-
}
|
|
144
|
-
if (process.env.DOTENV_KEY && process.env.DOTENV_KEY.length > 0) {
|
|
145
|
-
return process.env.DOTENV_KEY;
|
|
146
|
-
}
|
|
147
|
-
return "";
|
|
148
|
-
}
|
|
149
|
-
function _instructions(result, dotenvKey) {
|
|
150
|
-
let uri;
|
|
151
|
-
try {
|
|
152
|
-
uri = new URL(dotenvKey);
|
|
153
|
-
} catch (error) {
|
|
154
|
-
if (error.code === "ERR_INVALID_URL") {
|
|
155
|
-
const err = new Error("INVALID_DOTENV_KEY: Wrong format. Must be in valid uri format like dotenv://:key_1234@dotenvx.com/vault/.env.vault?environment=development");
|
|
156
|
-
err.code = "INVALID_DOTENV_KEY";
|
|
157
|
-
throw err;
|
|
158
|
-
}
|
|
159
|
-
throw error;
|
|
160
|
-
}
|
|
161
|
-
const key = uri.password;
|
|
162
|
-
if (!key) {
|
|
163
|
-
const err = new Error("INVALID_DOTENV_KEY: Missing key part");
|
|
164
|
-
err.code = "INVALID_DOTENV_KEY";
|
|
165
|
-
throw err;
|
|
166
|
-
}
|
|
167
|
-
const environment = uri.searchParams.get("environment");
|
|
168
|
-
if (!environment) {
|
|
169
|
-
const err = new Error("INVALID_DOTENV_KEY: Missing environment part");
|
|
170
|
-
err.code = "INVALID_DOTENV_KEY";
|
|
171
|
-
throw err;
|
|
172
|
-
}
|
|
173
|
-
const environmentKey = `DOTENV_VAULT_${environment.toUpperCase()}`;
|
|
174
|
-
const ciphertext = result.parsed[environmentKey];
|
|
175
|
-
if (!ciphertext) {
|
|
176
|
-
const err = new Error(`NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment ${environmentKey} in your .env.vault file.`);
|
|
177
|
-
err.code = "NOT_FOUND_DOTENV_ENVIRONMENT";
|
|
178
|
-
throw err;
|
|
179
|
-
}
|
|
180
|
-
return { ciphertext, key };
|
|
181
|
-
}
|
|
182
|
-
function _vaultPath(options) {
|
|
183
|
-
let possibleVaultPath = null;
|
|
184
|
-
if (options && options.path && options.path.length > 0) {
|
|
185
|
-
if (Array.isArray(options.path)) {
|
|
186
|
-
for (const filepath of options.path) {
|
|
187
|
-
if (fs.existsSync(filepath)) {
|
|
188
|
-
possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
} else {
|
|
192
|
-
possibleVaultPath = options.path.endsWith(".vault") ? options.path : `${options.path}.vault`;
|
|
193
|
-
}
|
|
194
|
-
} else {
|
|
195
|
-
possibleVaultPath = path.resolve(process.cwd(), ".env.vault");
|
|
196
|
-
}
|
|
197
|
-
if (fs.existsSync(possibleVaultPath)) {
|
|
198
|
-
return possibleVaultPath;
|
|
199
|
-
}
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
function _resolveHome(envPath) {
|
|
203
|
-
return envPath[0] === "~" ? path.join(os.homedir(), envPath.slice(1)) : envPath;
|
|
204
|
-
}
|
|
205
|
-
function _configVault(options) {
|
|
206
|
-
const debug = parseBoolean(process.env.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
207
|
-
const quiet = parseBoolean(process.env.DOTENV_CONFIG_QUIET || options && options.quiet);
|
|
208
|
-
if (debug || !quiet) {
|
|
209
|
-
_log("loading env from encrypted .env.vault");
|
|
210
|
-
}
|
|
211
|
-
const parsed = DotenvModule._parseVault(options);
|
|
212
|
-
let processEnv = process.env;
|
|
213
|
-
if (options && options.processEnv != null) {
|
|
214
|
-
processEnv = options.processEnv;
|
|
215
|
-
}
|
|
216
|
-
DotenvModule.populate(processEnv, parsed, options);
|
|
217
|
-
return { parsed };
|
|
218
|
-
}
|
|
219
|
-
function configDotenv(options) {
|
|
220
|
-
const dotenvPath = path.resolve(process.cwd(), ".env");
|
|
221
|
-
let encoding = "utf8";
|
|
222
|
-
let processEnv = process.env;
|
|
223
|
-
if (options && options.processEnv != null) {
|
|
224
|
-
processEnv = options.processEnv;
|
|
225
|
-
}
|
|
226
|
-
let debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || options && options.debug);
|
|
227
|
-
let quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || options && options.quiet);
|
|
228
|
-
if (options && options.encoding) {
|
|
229
|
-
encoding = options.encoding;
|
|
230
|
-
} else {
|
|
231
|
-
if (debug) {
|
|
232
|
-
_debug("no encoding is specified (UTF-8 is used by default)");
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
let optionPaths = [dotenvPath];
|
|
236
|
-
if (options && options.path) {
|
|
237
|
-
if (!Array.isArray(options.path)) {
|
|
238
|
-
optionPaths = [_resolveHome(options.path)];
|
|
239
|
-
} else {
|
|
240
|
-
optionPaths = [];
|
|
241
|
-
for (const filepath of options.path) {
|
|
242
|
-
optionPaths.push(_resolveHome(filepath));
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
let lastError;
|
|
247
|
-
const parsedAll = {};
|
|
248
|
-
for (const path2 of optionPaths) {
|
|
249
|
-
try {
|
|
250
|
-
const parsed = DotenvModule.parse(fs.readFileSync(path2, { encoding }));
|
|
251
|
-
DotenvModule.populate(parsedAll, parsed, options);
|
|
252
|
-
} catch (e) {
|
|
253
|
-
if (debug) {
|
|
254
|
-
_debug(`failed to load ${path2} ${e.message}`);
|
|
255
|
-
}
|
|
256
|
-
lastError = e;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
const populated = DotenvModule.populate(processEnv, parsedAll, options);
|
|
260
|
-
debug = parseBoolean(processEnv.DOTENV_CONFIG_DEBUG || debug);
|
|
261
|
-
quiet = parseBoolean(processEnv.DOTENV_CONFIG_QUIET || quiet);
|
|
262
|
-
if (debug || !quiet) {
|
|
263
|
-
const keysCount = Object.keys(populated).length;
|
|
264
|
-
const shortPaths = [];
|
|
265
|
-
for (const filePath of optionPaths) {
|
|
266
|
-
try {
|
|
267
|
-
const relative = path.relative(process.cwd(), filePath);
|
|
268
|
-
shortPaths.push(relative);
|
|
269
|
-
} catch (e) {
|
|
270
|
-
if (debug) {
|
|
271
|
-
_debug(`failed to load ${filePath} ${e.message}`);
|
|
272
|
-
}
|
|
273
|
-
lastError = e;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
_log(`injected env (${keysCount}) from ${shortPaths.join(",")} ${dim(`// tip: ${_getRandomTip()}`)}`);
|
|
277
|
-
}
|
|
278
|
-
if (lastError) {
|
|
279
|
-
return { parsed: parsedAll, error: lastError };
|
|
280
|
-
} else {
|
|
281
|
-
return { parsed: parsedAll };
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
function config(options) {
|
|
285
|
-
if (_dotenvKey(options).length === 0) {
|
|
286
|
-
return DotenvModule.configDotenv(options);
|
|
287
|
-
}
|
|
288
|
-
const vaultPath = _vaultPath(options);
|
|
289
|
-
if (!vaultPath) {
|
|
290
|
-
_warn(`you set DOTENV_KEY but you are missing a .env.vault file at ${vaultPath}`);
|
|
291
|
-
return DotenvModule.configDotenv(options);
|
|
292
|
-
}
|
|
293
|
-
return DotenvModule._configVault(options);
|
|
294
|
-
}
|
|
295
|
-
function decrypt(encrypted, keyStr) {
|
|
296
|
-
const key = Buffer.from(keyStr.slice(-64), "hex");
|
|
297
|
-
let ciphertext = Buffer.from(encrypted, "base64");
|
|
298
|
-
const nonce = ciphertext.subarray(0, 12);
|
|
299
|
-
const authTag = ciphertext.subarray(-16);
|
|
300
|
-
ciphertext = ciphertext.subarray(12, -16);
|
|
301
|
-
try {
|
|
302
|
-
const aesgcm = crypto.createDecipheriv("aes-256-gcm", key, nonce);
|
|
303
|
-
aesgcm.setAuthTag(authTag);
|
|
304
|
-
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
|
|
305
|
-
} catch (error) {
|
|
306
|
-
const isRange = error instanceof RangeError;
|
|
307
|
-
const invalidKeyLength = error.message === "Invalid key length";
|
|
308
|
-
const decryptionFailed = error.message === "Unsupported state or unable to authenticate data";
|
|
309
|
-
if (isRange || invalidKeyLength) {
|
|
310
|
-
const err = new Error("INVALID_DOTENV_KEY: It must be 64 characters long (or more)");
|
|
311
|
-
err.code = "INVALID_DOTENV_KEY";
|
|
312
|
-
throw err;
|
|
313
|
-
} else if (decryptionFailed) {
|
|
314
|
-
const err = new Error("DECRYPTION_FAILED: Please check your DOTENV_KEY");
|
|
315
|
-
err.code = "DECRYPTION_FAILED";
|
|
316
|
-
throw err;
|
|
317
|
-
} else {
|
|
318
|
-
throw error;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
function populate(processEnv, parsed, options = {}) {
|
|
323
|
-
const debug = Boolean(options && options.debug);
|
|
324
|
-
const override = Boolean(options && options.override);
|
|
325
|
-
const populated = {};
|
|
326
|
-
if (typeof parsed !== "object") {
|
|
327
|
-
const err = new Error("OBJECT_REQUIRED: Please check the processEnv argument being passed to populate");
|
|
328
|
-
err.code = "OBJECT_REQUIRED";
|
|
329
|
-
throw err;
|
|
330
|
-
}
|
|
331
|
-
for (const key of Object.keys(parsed)) {
|
|
332
|
-
if (Object.prototype.hasOwnProperty.call(processEnv, key)) {
|
|
333
|
-
if (override === true) {
|
|
334
|
-
processEnv[key] = parsed[key];
|
|
335
|
-
populated[key] = parsed[key];
|
|
336
|
-
}
|
|
337
|
-
if (debug) {
|
|
338
|
-
if (override === true) {
|
|
339
|
-
_debug(`"${key}" is already defined and WAS overwritten`);
|
|
340
|
-
} else {
|
|
341
|
-
_debug(`"${key}" is already defined and was NOT overwritten`);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
} else {
|
|
345
|
-
processEnv[key] = parsed[key];
|
|
346
|
-
populated[key] = parsed[key];
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return populated;
|
|
350
|
-
}
|
|
351
|
-
var DotenvModule = {
|
|
352
|
-
configDotenv,
|
|
353
|
-
_configVault,
|
|
354
|
-
_parseVault,
|
|
355
|
-
config,
|
|
356
|
-
decrypt,
|
|
357
|
-
parse,
|
|
358
|
-
populate
|
|
359
|
-
};
|
|
360
|
-
module.exports.configDotenv = DotenvModule.configDotenv;
|
|
361
|
-
module.exports._configVault = DotenvModule._configVault;
|
|
362
|
-
module.exports._parseVault = DotenvModule._parseVault;
|
|
363
|
-
module.exports.config = DotenvModule.config;
|
|
364
|
-
module.exports.decrypt = DotenvModule.decrypt;
|
|
365
|
-
module.exports.parse = DotenvModule.parse;
|
|
366
|
-
module.exports.populate = DotenvModule.populate;
|
|
367
|
-
module.exports = DotenvModule;
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
// src/load-env.ts
|
|
371
|
-
var import_dotenv = __toESM(require_main(), 1);
|
|
372
|
-
import_dotenv.config({ quiet: true });
|
|
373
17
|
|
|
374
18
|
// src/cli.ts
|
|
375
19
|
import { readFileSync } from "fs";
|
|
376
20
|
|
|
21
|
+
// ../../cli-auth/src/client/credential-store.ts
|
|
22
|
+
import { createHash as createHash2, randomUUID } from "crypto";
|
|
23
|
+
import {
|
|
24
|
+
chmod,
|
|
25
|
+
lstat,
|
|
26
|
+
mkdir,
|
|
27
|
+
open as open2,
|
|
28
|
+
readFile,
|
|
29
|
+
stat,
|
|
30
|
+
unlink
|
|
31
|
+
} from "fs/promises";
|
|
32
|
+
import { userInfo } from "os";
|
|
33
|
+
import { join } from "path";
|
|
34
|
+
|
|
377
35
|
// ../../cli-auth/src/protocol/constants.ts
|
|
378
36
|
var CLI_ACCESS_TOKEN_TTL_MS = 15 * 60 * 1000;
|
|
379
37
|
var CLI_DEVICE_AUTHORIZATION_TTL_MS = 10 * 60 * 1000;
|
|
@@ -502,7 +160,7 @@ __export(exports_external, {
|
|
|
502
160
|
codec: () => codec,
|
|
503
161
|
coerce: () => exports_coerce,
|
|
504
162
|
compile: () => compile,
|
|
505
|
-
config: () =>
|
|
163
|
+
config: () => config,
|
|
506
164
|
core: () => exports_core2,
|
|
507
165
|
creditCard: () => creditCard2,
|
|
508
166
|
cuid: () => cuid3,
|
|
@@ -905,7 +563,7 @@ __export(exports_core2, {
|
|
|
905
563
|
clone: () => clone,
|
|
906
564
|
compile: () => compile,
|
|
907
565
|
compileFn: () => compileFn,
|
|
908
|
-
config: () =>
|
|
566
|
+
config: () => config,
|
|
909
567
|
createStandardJSONSchemaMethod: () => createStandardJSONSchemaMethod,
|
|
910
568
|
createToJSONSchemaMethod: () => createToJSONSchemaMethod,
|
|
911
569
|
decode: () => decode,
|
|
@@ -1569,7 +1227,7 @@ function attachSchema(issues, start, inst) {
|
|
|
1569
1227
|
(_a = issues[i]).schema ?? (_a.schema = inst);
|
|
1570
1228
|
}
|
|
1571
1229
|
}
|
|
1572
|
-
function finalizeIssue(iss, ctx,
|
|
1230
|
+
function finalizeIssue(iss, ctx, config) {
|
|
1573
1231
|
var _a;
|
|
1574
1232
|
const traits = iss.inst?._zod?.traits;
|
|
1575
1233
|
if (traits?.has("$ZodType")) {
|
|
@@ -1579,7 +1237,7 @@ function finalizeIssue(iss, ctx, config2) {
|
|
|
1579
1237
|
iss.schema = iss.inst;
|
|
1580
1238
|
}
|
|
1581
1239
|
const schemaError = iss.schema !== iss.inst ? iss.schema?._zod.def?.error : undefined;
|
|
1582
|
-
const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(schemaError?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(
|
|
1240
|
+
const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(schemaError?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config.customError?.(iss)) ?? unwrapMessage(config.localeError?.(iss)) ?? "Invalid input";
|
|
1583
1241
|
const { inst: _inst, schema: _schema, continue: _continue, input: _input, ...rest } = iss;
|
|
1584
1242
|
rest.path ?? (rest.path = []);
|
|
1585
1243
|
rest.message = message;
|
|
@@ -1913,7 +1571,7 @@ class $ZodEncodeError extends Error {
|
|
|
1913
1571
|
}
|
|
1914
1572
|
(_a = globalThis).__zod_globalConfig ?? (_a.__zod_globalConfig = {});
|
|
1915
1573
|
var globalConfig = globalThis.__zod_globalConfig;
|
|
1916
|
-
function
|
|
1574
|
+
function config(newConfig) {
|
|
1917
1575
|
if (newConfig)
|
|
1918
1576
|
Object.assign(globalConfig, newConfig);
|
|
1919
1577
|
return globalConfig;
|
|
@@ -2127,7 +1785,7 @@ var _parse = (_Err) => {
|
|
|
2127
1785
|
throw new $ZodAsyncError;
|
|
2128
1786
|
}
|
|
2129
1787
|
if (result.issues.length) {
|
|
2130
|
-
const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
1788
|
+
const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
|
|
2131
1789
|
captureStackTrace(e, _params?.callee ?? fn);
|
|
2132
1790
|
throw e;
|
|
2133
1791
|
}
|
|
@@ -2143,7 +1801,7 @@ var _parseAsync = (_Err) => {
|
|
|
2143
1801
|
if (result instanceof Promise)
|
|
2144
1802
|
result = await result;
|
|
2145
1803
|
if (result.issues.length) {
|
|
2146
|
-
const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
1804
|
+
const e = new (params?.Err ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));
|
|
2147
1805
|
captureStackTrace(e, params?.callee ?? fn);
|
|
2148
1806
|
throw e;
|
|
2149
1807
|
}
|
|
@@ -2160,7 +1818,7 @@ var _safeParse = (_Err) => (schema, value, _ctx) => {
|
|
|
2160
1818
|
}
|
|
2161
1819
|
return result.issues.length ? {
|
|
2162
1820
|
success: false,
|
|
2163
|
-
error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
1821
|
+
error: new (_Err ?? $ZodError)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
|
|
2164
1822
|
} : { success: true, data: result.value };
|
|
2165
1823
|
};
|
|
2166
1824
|
var safeParse = /* @__PURE__ */ _safeParse($ZodRealError);
|
|
@@ -2171,7 +1829,7 @@ var _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
|
|
|
2171
1829
|
result = await result;
|
|
2172
1830
|
return result.issues.length ? {
|
|
2173
1831
|
success: false,
|
|
2174
|
-
error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
1832
|
+
error: new _Err(result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
|
|
2175
1833
|
} : { success: true, data: result.value };
|
|
2176
1834
|
};
|
|
2177
1835
|
var safeParseAsync = /* @__PURE__ */ _safeParseAsync($ZodRealError);
|
|
@@ -4047,7 +3705,7 @@ function handleUnionResults(results, final, inst, ctx) {
|
|
|
4047
3705
|
code: "invalid_union",
|
|
4048
3706
|
input: final.value,
|
|
4049
3707
|
inst,
|
|
4050
|
-
errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
3708
|
+
errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
|
|
4051
3709
|
});
|
|
4052
3710
|
return final;
|
|
4053
3711
|
}
|
|
@@ -4111,7 +3769,7 @@ function handleExclusiveUnionResults(results, final, inst, ctx) {
|
|
|
4111
3769
|
code: "invalid_union",
|
|
4112
3770
|
input: final.value,
|
|
4113
3771
|
inst,
|
|
4114
|
-
errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
3772
|
+
errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config())))
|
|
4115
3773
|
});
|
|
4116
3774
|
} else {
|
|
4117
3775
|
final.issues.push({
|
|
@@ -4503,7 +4161,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
|
|
|
4503
4161
|
payload.issues.push({
|
|
4504
4162
|
code: "invalid_key",
|
|
4505
4163
|
origin: "record",
|
|
4506
|
-
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
4164
|
+
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
|
|
4507
4165
|
input: key,
|
|
4508
4166
|
path: [key],
|
|
4509
4167
|
inst
|
|
@@ -4583,7 +4241,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
|
|
|
4583
4241
|
payload.issues.push({
|
|
4584
4242
|
code: "invalid_key",
|
|
4585
4243
|
origin: "record",
|
|
4586
|
-
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
4244
|
+
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
|
|
4587
4245
|
input: key,
|
|
4588
4246
|
path: [key],
|
|
4589
4247
|
inst
|
|
@@ -4668,7 +4326,7 @@ function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
|
|
|
4668
4326
|
origin: "map",
|
|
4669
4327
|
input,
|
|
4670
4328
|
inst,
|
|
4671
|
-
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
4329
|
+
issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config()))
|
|
4672
4330
|
});
|
|
4673
4331
|
}
|
|
4674
4332
|
}
|
|
@@ -4682,7 +4340,7 @@ function handleMapResult(keyResult, valueResult, final, key, input, inst, ctx) {
|
|
|
4682
4340
|
input,
|
|
4683
4341
|
inst,
|
|
4684
4342
|
key,
|
|
4685
|
-
issues: valueResult.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
4343
|
+
issues: valueResult.issues.map((iss) => finalizeIssue(iss, ctx, config()))
|
|
4686
4344
|
});
|
|
4687
4345
|
}
|
|
4688
4346
|
}
|
|
@@ -4946,7 +4604,7 @@ function handleCatchResult(payload, result, def, ctx) {
|
|
|
4946
4604
|
...result,
|
|
4947
4605
|
value: payload.value,
|
|
4948
4606
|
error: {
|
|
4949
|
-
issues: result.issues.map((iss) => finalizeIssue(iss, ctx,
|
|
4607
|
+
issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config()))
|
|
4950
4608
|
},
|
|
4951
4609
|
input: payload.value
|
|
4952
4610
|
});
|
|
@@ -16797,11 +16455,11 @@ var safeDecodeAsync2 = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
|
|
|
16797
16455
|
// ../../../node_modules/.bun/zod@4.5.4/node_modules/zod/v4/classic/schemas.js
|
|
16798
16456
|
function _ensureDefaultLocale() {
|
|
16799
16457
|
if (!globalConfig.localeError)
|
|
16800
|
-
|
|
16458
|
+
config(en_default());
|
|
16801
16459
|
}
|
|
16802
16460
|
function _ensureDefaultMemoizer() {
|
|
16803
16461
|
if (!globalConfig.memoizer)
|
|
16804
|
-
|
|
16462
|
+
config({ memoizer: memoizer() });
|
|
16805
16463
|
}
|
|
16806
16464
|
var ZodType = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|
16807
16465
|
_ensureDefaultLocale();
|
|
@@ -18213,12 +17871,12 @@ var ZodIssueCode = {
|
|
|
18213
17871
|
custom: "custom"
|
|
18214
17872
|
};
|
|
18215
17873
|
function setErrorMap(map2) {
|
|
18216
|
-
|
|
17874
|
+
config({
|
|
18217
17875
|
customError: map2
|
|
18218
17876
|
});
|
|
18219
17877
|
}
|
|
18220
17878
|
function getErrorMap() {
|
|
18221
|
-
return
|
|
17879
|
+
return config().customError;
|
|
18222
17880
|
}
|
|
18223
17881
|
var ZodFirstPartyTypeKind;
|
|
18224
17882
|
(function(ZodFirstPartyTypeKind2) {})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
|
|
@@ -19027,13 +18685,15 @@ var deviceTokenErrorSchema = exports_external.object({
|
|
|
19027
18685
|
"authorization_pending",
|
|
19028
18686
|
"slow_down",
|
|
19029
18687
|
"access_denied",
|
|
19030
|
-
"expired_token"
|
|
18688
|
+
"expired_token",
|
|
18689
|
+
"invalid_grant"
|
|
19031
18690
|
])
|
|
19032
18691
|
}).strict();
|
|
19033
18692
|
var refreshRequestSchema = exports_external.object({ refreshToken: exports_external.string().min(40) }).strict();
|
|
19034
18693
|
var storedCliCredentialsSchema = exports_external.object({
|
|
19035
18694
|
accessToken: exports_external.string().min(1),
|
|
19036
18695
|
accessTokenExpiresAt: dateTimeString,
|
|
18696
|
+
issuer: exports_external.string().url().optional(),
|
|
19037
18697
|
refreshToken: exports_external.string().min(1),
|
|
19038
18698
|
refreshTokenExpiresAt: dateTimeString,
|
|
19039
18699
|
session: cliSessionIdentitySchema
|
|
@@ -19085,6 +18745,77 @@ var defaultSecrets = () => {
|
|
|
19085
18745
|
assertBunRuntime();
|
|
19086
18746
|
return Bun.secrets;
|
|
19087
18747
|
};
|
|
18748
|
+
var refreshLockTimeoutMs = 5 * 60 * 1000;
|
|
18749
|
+
var refreshLockRetryMs = 50;
|
|
18750
|
+
var getCredentialLockDirectoryPath = () => {
|
|
18751
|
+
return join(userInfo().homedir, ".cache", "go-labs-cli", "locks");
|
|
18752
|
+
};
|
|
18753
|
+
var ensurePrivateCredentialLockDirectory = async () => {
|
|
18754
|
+
const directory = getCredentialLockDirectoryPath();
|
|
18755
|
+
await mkdir(directory, { mode: 448, recursive: true });
|
|
18756
|
+
const inspectDirectory = async () => {
|
|
18757
|
+
const directoryStat2 = await lstat(directory);
|
|
18758
|
+
if (directoryStat2.isSymbolicLink() || !directoryStat2.isDirectory()) {
|
|
18759
|
+
throw new Error("The CLI credential lock path is not a private directory.");
|
|
18760
|
+
}
|
|
18761
|
+
const uid = process.getuid?.();
|
|
18762
|
+
if (uid !== undefined && directoryStat2.uid !== uid) {
|
|
18763
|
+
throw new Error("The CLI credential lock directory belongs to another user.");
|
|
18764
|
+
}
|
|
18765
|
+
return directoryStat2;
|
|
18766
|
+
};
|
|
18767
|
+
const directoryStat = await inspectDirectory();
|
|
18768
|
+
if ((directoryStat.mode & 63) !== 0) {
|
|
18769
|
+
await chmod(directory, 448);
|
|
18770
|
+
const securedDirectoryStat = await inspectDirectory();
|
|
18771
|
+
if ((securedDirectoryStat.mode & 63) !== 0) {
|
|
18772
|
+
throw new Error("Unable to secure the CLI credential lock directory.");
|
|
18773
|
+
}
|
|
18774
|
+
}
|
|
18775
|
+
return directory;
|
|
18776
|
+
};
|
|
18777
|
+
var getCredentialLockPath = async (product, account) => {
|
|
18778
|
+
const key = createHash2("sha256").update(`${product}:${account}`).digest("hex");
|
|
18779
|
+
return join(await ensurePrivateCredentialLockDirectory(), `${key}.lock`);
|
|
18780
|
+
};
|
|
18781
|
+
var acquireRefreshLock = async (path) => {
|
|
18782
|
+
const deadline = Date.now() + refreshLockTimeoutMs;
|
|
18783
|
+
while (Date.now() < deadline) {
|
|
18784
|
+
try {
|
|
18785
|
+
const handle = await open2(path, "wx");
|
|
18786
|
+
const owner = randomUUID();
|
|
18787
|
+
try {
|
|
18788
|
+
await handle.writeFile(owner);
|
|
18789
|
+
} catch (cause) {
|
|
18790
|
+
await handle.close().catch(() => {
|
|
18791
|
+
return;
|
|
18792
|
+
});
|
|
18793
|
+
await unlink(path).catch(() => {
|
|
18794
|
+
return;
|
|
18795
|
+
});
|
|
18796
|
+
throw cause;
|
|
18797
|
+
}
|
|
18798
|
+
return { handle, owner };
|
|
18799
|
+
} catch (cause) {
|
|
18800
|
+
if (!(cause instanceof Error) || !("code" in cause) || cause.code !== "EEXIST") {
|
|
18801
|
+
throw cause;
|
|
18802
|
+
}
|
|
18803
|
+
try {
|
|
18804
|
+
const lock = await stat(path);
|
|
18805
|
+
if (Date.now() - lock.mtimeMs > refreshLockTimeoutMs) {
|
|
18806
|
+
throw new Error(`The CLI credential lock appears stale at ${path}. Ensure no CLI authentication command is running, remove that lock file, and retry.`);
|
|
18807
|
+
}
|
|
18808
|
+
} catch (statCause) {
|
|
18809
|
+
if (statCause instanceof Error && "code" in statCause && statCause.code === "ENOENT") {
|
|
18810
|
+
continue;
|
|
18811
|
+
}
|
|
18812
|
+
throw statCause;
|
|
18813
|
+
}
|
|
18814
|
+
await Bun.sleep(refreshLockRetryMs);
|
|
18815
|
+
}
|
|
18816
|
+
}
|
|
18817
|
+
throw new Error("Timed out waiting for another CLI process to refresh credentials. Retry the command.");
|
|
18818
|
+
};
|
|
19088
18819
|
var createBunCredentialStore = ({
|
|
19089
18820
|
account = "default",
|
|
19090
18821
|
product,
|
|
@@ -19112,16 +18843,46 @@ var createBunCredentialStore = ({
|
|
|
19112
18843
|
service,
|
|
19113
18844
|
value: JSON.stringify(parsed)
|
|
19114
18845
|
}));
|
|
18846
|
+
},
|
|
18847
|
+
withCredentialLock: async (operation) => {
|
|
18848
|
+
const refreshLockPath = await getCredentialLockPath(product, account);
|
|
18849
|
+
const lock = await acquireRefreshLock(refreshLockPath);
|
|
18850
|
+
try {
|
|
18851
|
+
return await operation();
|
|
18852
|
+
} finally {
|
|
18853
|
+
await lock.handle.close();
|
|
18854
|
+
const owner = await readFile(refreshLockPath, "utf8").catch(() => null);
|
|
18855
|
+
if (owner === lock.owner) {
|
|
18856
|
+
await unlink(refreshLockPath).catch(() => {
|
|
18857
|
+
return;
|
|
18858
|
+
});
|
|
18859
|
+
}
|
|
18860
|
+
}
|
|
19115
18861
|
}
|
|
19116
18862
|
};
|
|
19117
18863
|
};
|
|
19118
18864
|
// ../../cli-auth/src/client/login.ts
|
|
19119
18865
|
import { hostname as hostname3 } from "os";
|
|
19120
|
-
var
|
|
19121
|
-
|
|
18866
|
+
var normalizeCliAuthIssuer = (value) => {
|
|
18867
|
+
const url2 = new URL(value);
|
|
18868
|
+
url2.hash = "";
|
|
18869
|
+
url2.search = "";
|
|
18870
|
+
url2.pathname = url2.pathname.replace(/\/+$/, "");
|
|
18871
|
+
return url2.toString().replace(/\/+$/, "");
|
|
18872
|
+
};
|
|
18873
|
+
var isCliCredentialForIssuer = (credentials, apiBaseUrl) => credentials.issuer === normalizeCliAuthIssuer(apiBaseUrl);
|
|
18874
|
+
var credentialIssuerError = (issuer) => issuer ? new Error("These CLI credentials belong to a different API URL. Run auth login for this API URL before retrying.") : new Error("These CLI credentials were created by an older CLI and are not bound to an API URL. Run auth login again.");
|
|
18875
|
+
var assertCredentialIssuer = (credentials, apiBaseUrl) => {
|
|
18876
|
+
const expectedIssuer = normalizeCliAuthIssuer(apiBaseUrl);
|
|
18877
|
+
if (!credentials.issuer || credentials.issuer !== expectedIssuer) {
|
|
18878
|
+
throw credentialIssuerError(credentials.issuer);
|
|
18879
|
+
}
|
|
18880
|
+
};
|
|
18881
|
+
var postJson = async (fetchImplementation, url2, body, options = {}) => fetchImplementation(url2, {
|
|
19122
18882
|
body: JSON.stringify(body),
|
|
19123
18883
|
headers: { "content-type": "application/json" },
|
|
19124
|
-
method: "POST"
|
|
18884
|
+
method: "POST",
|
|
18885
|
+
...options
|
|
19125
18886
|
});
|
|
19126
18887
|
var readJson = async (response) => {
|
|
19127
18888
|
try {
|
|
@@ -19130,9 +18891,29 @@ var readJson = async (response) => {
|
|
|
19130
18891
|
throw new Error(`CLI authentication returned HTTP ${response.status} without valid JSON.`);
|
|
19131
18892
|
}
|
|
19132
18893
|
};
|
|
19133
|
-
var
|
|
18894
|
+
var localCredentialLocks = new WeakMap;
|
|
18895
|
+
var withCredentialLock = (store, operation) => {
|
|
18896
|
+
if (store.withCredentialLock) {
|
|
18897
|
+
return store.withCredentialLock(operation);
|
|
18898
|
+
}
|
|
18899
|
+
const previous = localCredentialLocks.get(store) ?? Promise.resolve();
|
|
18900
|
+
const pending = previous.then(operation, operation);
|
|
18901
|
+
const tail = pending.then(() => {
|
|
18902
|
+
return;
|
|
18903
|
+
}, () => {
|
|
18904
|
+
return;
|
|
18905
|
+
});
|
|
18906
|
+
localCredentialLocks.set(store, tail);
|
|
18907
|
+
return pending.finally(() => {
|
|
18908
|
+
if (localCredentialLocks.get(store) === tail) {
|
|
18909
|
+
localCredentialLocks.delete(store);
|
|
18910
|
+
}
|
|
18911
|
+
});
|
|
18912
|
+
};
|
|
18913
|
+
var toStoredCredentials = (response, issuer, now) => ({
|
|
19134
18914
|
accessToken: response.accessToken,
|
|
19135
18915
|
accessTokenExpiresAt: new Date(now.getTime() + response.expiresIn * 1000).toISOString(),
|
|
18916
|
+
issuer,
|
|
19136
18917
|
refreshToken: response.refreshToken,
|
|
19137
18918
|
refreshTokenExpiresAt: new Date(now.getTime() + response.refreshExpiresIn * 1000).toISOString(),
|
|
19138
18919
|
session: response.session
|
|
@@ -19166,7 +18947,7 @@ var loginWithBrowser = async ({
|
|
|
19166
18947
|
sleep = (milliseconds) => Bun.sleep(milliseconds),
|
|
19167
18948
|
store
|
|
19168
18949
|
}) => {
|
|
19169
|
-
const baseUrl =
|
|
18950
|
+
const baseUrl = normalizeCliAuthIssuer(apiBaseUrl);
|
|
19170
18951
|
const pkce = createPkcePair();
|
|
19171
18952
|
const beginResponse = await postJson(fetchImplementation, `${baseUrl}/api/cli-auth/device`, {
|
|
19172
18953
|
clientName,
|
|
@@ -19193,8 +18974,8 @@ var loginWithBrowser = async ({
|
|
|
19193
18974
|
const payload = await readJson(response);
|
|
19194
18975
|
if (response.ok) {
|
|
19195
18976
|
const issued = tokenResponseSchema.parse(payload);
|
|
19196
|
-
const credentials = toStoredCredentials(issued, now());
|
|
19197
|
-
await store.set(credentials);
|
|
18977
|
+
const credentials = toStoredCredentials(issued, baseUrl, now());
|
|
18978
|
+
await withCredentialLock(store, () => store.set(credentials));
|
|
19198
18979
|
return credentials;
|
|
19199
18980
|
}
|
|
19200
18981
|
const error61 = deviceTokenErrorSchema.safeParse(payload);
|
|
@@ -19215,6 +18996,26 @@ var loginWithBrowser = async ({
|
|
|
19215
18996
|
throw new Error("CLI authentication expired. Run auth login again.");
|
|
19216
18997
|
};
|
|
19217
18998
|
var refreshes = new WeakMap;
|
|
18999
|
+
var refreshRequestTimeoutMs = 30000;
|
|
19000
|
+
var isInvalidGrantResponse = (payload) => {
|
|
19001
|
+
const parsed = deviceTokenErrorSchema.safeParse(payload);
|
|
19002
|
+
return parsed.success && parsed.data.error === "invalid_grant";
|
|
19003
|
+
};
|
|
19004
|
+
var readJsonOrNull = async (response) => {
|
|
19005
|
+
try {
|
|
19006
|
+
return await response.json();
|
|
19007
|
+
} catch {
|
|
19008
|
+
return null;
|
|
19009
|
+
}
|
|
19010
|
+
};
|
|
19011
|
+
var deleteInvalidCredentials = async (store, credentials) => {
|
|
19012
|
+
const current = await store.get();
|
|
19013
|
+
if (!current)
|
|
19014
|
+
return;
|
|
19015
|
+
if (current.issuer === credentials.issuer && current.refreshToken === credentials.refreshToken) {
|
|
19016
|
+
await store.delete();
|
|
19017
|
+
}
|
|
19018
|
+
};
|
|
19218
19019
|
var refreshAccessToken = async ({
|
|
19219
19020
|
apiBaseUrl,
|
|
19220
19021
|
credentials,
|
|
@@ -19222,16 +19023,56 @@ var refreshAccessToken = async ({
|
|
|
19222
19023
|
now,
|
|
19223
19024
|
store
|
|
19224
19025
|
}) => {
|
|
19225
|
-
|
|
19026
|
+
assertCredentialIssuer(credentials, apiBaseUrl);
|
|
19027
|
+
let response;
|
|
19028
|
+
try {
|
|
19029
|
+
response = await postJson(fetchImplementation, `${normalizeCliAuthIssuer(apiBaseUrl)}/api/cli-auth/refresh`, { refreshToken: credentials.refreshToken }, { signal: AbortSignal.timeout(refreshRequestTimeoutMs) });
|
|
19030
|
+
} catch {
|
|
19031
|
+
throw new Error("Unable to refresh the CLI session because the API could not be reached. Credentials were kept; retry the command.");
|
|
19032
|
+
}
|
|
19226
19033
|
if (!response.ok) {
|
|
19227
|
-
await
|
|
19228
|
-
|
|
19034
|
+
const payload2 = await readJsonOrNull(response);
|
|
19035
|
+
if (isInvalidGrantResponse(payload2)) {
|
|
19036
|
+
await deleteInvalidCredentials(store, credentials);
|
|
19037
|
+
throw new Error("The CLI session expired or was revoked. Run auth login again.");
|
|
19038
|
+
}
|
|
19039
|
+
throw new Error(`Unable to refresh the CLI session (HTTP ${response.status}). Credentials were kept; retry the command.`);
|
|
19040
|
+
}
|
|
19041
|
+
const payload = await readJsonOrNull(response);
|
|
19042
|
+
if (payload === null) {
|
|
19043
|
+
throw new Error("Unable to refresh the CLI session because the API returned malformed JSON. Credentials were kept; retry the command.");
|
|
19229
19044
|
}
|
|
19230
|
-
|
|
19231
|
-
|
|
19045
|
+
let issued;
|
|
19046
|
+
try {
|
|
19047
|
+
issued = tokenResponseSchema.parse(payload);
|
|
19048
|
+
} catch {
|
|
19049
|
+
throw new Error("Unable to refresh the CLI session because the API returned invalid credentials. Credentials were kept; retry the command.");
|
|
19050
|
+
}
|
|
19051
|
+
const stored = toStoredCredentials(issued, normalizeCliAuthIssuer(apiBaseUrl), now());
|
|
19232
19052
|
await store.set(stored);
|
|
19233
19053
|
return stored.accessToken;
|
|
19234
19054
|
};
|
|
19055
|
+
var refreshWithCurrentCredentials = async ({
|
|
19056
|
+
apiBaseUrl,
|
|
19057
|
+
fetch: fetchImplementation,
|
|
19058
|
+
now,
|
|
19059
|
+
store
|
|
19060
|
+
}) => {
|
|
19061
|
+
const credentials = await store.get();
|
|
19062
|
+
if (!credentials)
|
|
19063
|
+
throw new Error("Not authenticated. Run auth login first.");
|
|
19064
|
+
assertCredentialIssuer(credentials, apiBaseUrl);
|
|
19065
|
+
if (Date.parse(credentials.accessTokenExpiresAt) - now().getTime() > 60000) {
|
|
19066
|
+
return credentials.accessToken;
|
|
19067
|
+
}
|
|
19068
|
+
return refreshAccessToken({
|
|
19069
|
+
apiBaseUrl,
|
|
19070
|
+
credentials,
|
|
19071
|
+
fetch: fetchImplementation,
|
|
19072
|
+
now,
|
|
19073
|
+
store
|
|
19074
|
+
});
|
|
19075
|
+
};
|
|
19235
19076
|
var getAuthenticatedAccessToken = async ({
|
|
19236
19077
|
apiBaseUrl,
|
|
19237
19078
|
fetch: fetchImplementation = globalThis.fetch,
|
|
@@ -19241,19 +19082,20 @@ var getAuthenticatedAccessToken = async ({
|
|
|
19241
19082
|
const credentials = await store.get();
|
|
19242
19083
|
if (!credentials)
|
|
19243
19084
|
throw new Error("Not authenticated. Run auth login first.");
|
|
19085
|
+
assertCredentialIssuer(credentials, apiBaseUrl);
|
|
19244
19086
|
if (Date.parse(credentials.accessTokenExpiresAt) - now().getTime() > 60000) {
|
|
19245
19087
|
return credentials.accessToken;
|
|
19246
19088
|
}
|
|
19247
19089
|
const existing = refreshes.get(store);
|
|
19248
19090
|
if (existing)
|
|
19249
19091
|
return existing;
|
|
19250
|
-
const
|
|
19092
|
+
const refreshOperation = () => withCredentialLock(store, () => refreshWithCurrentCredentials({
|
|
19251
19093
|
apiBaseUrl,
|
|
19252
|
-
credentials,
|
|
19253
19094
|
fetch: fetchImplementation,
|
|
19254
19095
|
now,
|
|
19255
19096
|
store
|
|
19256
|
-
})
|
|
19097
|
+
}));
|
|
19098
|
+
const refresh = refreshOperation().finally(() => refreshes.delete(store));
|
|
19257
19099
|
refreshes.set(store, refresh);
|
|
19258
19100
|
return refresh;
|
|
19259
19101
|
};
|
|
@@ -19262,18 +19104,22 @@ var logoutCliSession = async ({
|
|
|
19262
19104
|
fetch: fetchImplementation = globalThis.fetch,
|
|
19263
19105
|
store
|
|
19264
19106
|
}) => {
|
|
19265
|
-
const
|
|
19266
|
-
|
|
19267
|
-
|
|
19268
|
-
|
|
19269
|
-
|
|
19270
|
-
|
|
19271
|
-
|
|
19272
|
-
|
|
19107
|
+
const issuer = normalizeCliAuthIssuer(apiBaseUrl);
|
|
19108
|
+
return withCredentialLock(store, async () => {
|
|
19109
|
+
const credentials = await store.get();
|
|
19110
|
+
let remoteRevoked = credentials === null;
|
|
19111
|
+
if (credentials) {
|
|
19112
|
+
try {
|
|
19113
|
+
assertCredentialIssuer(credentials, apiBaseUrl);
|
|
19114
|
+
const response = await postJson(fetchImplementation, `${issuer}/api/cli-auth/revoke`, { refreshToken: credentials.refreshToken }, { signal: AbortSignal.timeout(refreshRequestTimeoutMs) });
|
|
19115
|
+
remoteRevoked = response.ok;
|
|
19116
|
+
} catch {
|
|
19117
|
+
remoteRevoked = false;
|
|
19118
|
+
}
|
|
19273
19119
|
}
|
|
19274
|
-
|
|
19275
|
-
|
|
19276
|
-
|
|
19120
|
+
await store.delete();
|
|
19121
|
+
return { remoteRevoked };
|
|
19122
|
+
});
|
|
19277
19123
|
};
|
|
19278
19124
|
// src/api-client.ts
|
|
19279
19125
|
import {
|
|
@@ -19364,10 +19210,24 @@ var commands = {
|
|
|
19364
19210
|
scope: "event",
|
|
19365
19211
|
summary: "Get the editable registration flow draft."
|
|
19366
19212
|
},
|
|
19213
|
+
"event flow resources get": {
|
|
19214
|
+
effects: [],
|
|
19215
|
+
operation: (api2, input2) => api2.eventFlow.getResourceCatalog.query(input2),
|
|
19216
|
+
scope: "event",
|
|
19217
|
+
summary: "List the menus, consent documents, images, and seat maps available to the registration flow."
|
|
19218
|
+
},
|
|
19219
|
+
"event flow menu version create": {
|
|
19220
|
+
effects: ["state-change"],
|
|
19221
|
+
operation: (api2, input2) => api2.eventFlow.createMenuVersion.mutate(input2),
|
|
19222
|
+
payloadOnly: true,
|
|
19223
|
+
targetKey: "eventId",
|
|
19224
|
+
summary: "Create an immutable event menu version (--payload JSON)."
|
|
19225
|
+
},
|
|
19367
19226
|
"event flow draft save": {
|
|
19368
19227
|
effects: ["state-change"],
|
|
19369
19228
|
operation: (api2, input2) => api2.eventFlow.saveDraft.mutate(input2),
|
|
19370
19229
|
payloadOnly: true,
|
|
19230
|
+
targetKey: "eventId",
|
|
19371
19231
|
summary: "Save a revision-checked registration flow draft (--payload JSON)."
|
|
19372
19232
|
},
|
|
19373
19233
|
"event flow published get": {
|
|
@@ -19587,6 +19447,12 @@ var commands = {
|
|
|
19587
19447
|
operation: (api2, input2) => api2.user.unban.mutate(input2),
|
|
19588
19448
|
payloadOnly: true,
|
|
19589
19449
|
summary: "Unban a user (--payload JSON)."
|
|
19450
|
+
},
|
|
19451
|
+
"user role update": {
|
|
19452
|
+
effects: ["state-change"],
|
|
19453
|
+
operation: (api2, input2) => api2.user.updateGlobalRole.mutate(input2),
|
|
19454
|
+
payloadOnly: true,
|
|
19455
|
+
summary: "Update a user's global role (--payload JSON)."
|
|
19590
19456
|
}
|
|
19591
19457
|
};
|
|
19592
19458
|
var payloadCommand = (operation, summary, effects = []) => ({ effects, operation, payloadOnly: true, summary });
|
|
@@ -19901,15 +19767,27 @@ var collectValuesForKey = (value, targetKey) => {
|
|
|
19901
19767
|
return collectValuesForKey(nested, targetKey);
|
|
19902
19768
|
});
|
|
19903
19769
|
};
|
|
19904
|
-
var
|
|
19905
|
-
|
|
19906
|
-
|
|
19770
|
+
var collectDirectTargetValues = (input2, targetKey) => {
|
|
19771
|
+
if (!input2)
|
|
19772
|
+
return [];
|
|
19773
|
+
const value = input2[targetKey];
|
|
19774
|
+
if (Array.isArray(value))
|
|
19775
|
+
return value.map(String);
|
|
19776
|
+
if (["string", "number"].includes(typeof value))
|
|
19777
|
+
return [String(value)];
|
|
19778
|
+
return [];
|
|
19779
|
+
};
|
|
19780
|
+
var collectTargets = (input2, targetKey) => {
|
|
19781
|
+
if (targetKey)
|
|
19782
|
+
return collectDirectTargetValues(input2, targetKey);
|
|
19783
|
+
for (const targetKey2 of targetKeys) {
|
|
19784
|
+
const values = collectValuesForKey(input2, targetKey2);
|
|
19907
19785
|
if (values.length > 0)
|
|
19908
19786
|
return values;
|
|
19909
19787
|
}
|
|
19910
19788
|
return [];
|
|
19911
19789
|
};
|
|
19912
|
-
var requireEffects = async (effects, flags, confirm, input2) => {
|
|
19790
|
+
var requireEffects = async (effects, flags, confirm, input2, targetKey) => {
|
|
19913
19791
|
if (effects.length === 0)
|
|
19914
19792
|
return;
|
|
19915
19793
|
const missing = effects.filter((effect) => {
|
|
@@ -19918,7 +19796,7 @@ var requireEffects = async (effects, flags, confirm, input2) => {
|
|
|
19918
19796
|
});
|
|
19919
19797
|
const targetRequired = effects.some((effect) => effect !== "sensitive-data");
|
|
19920
19798
|
const target = getStringFlag(flags, "--target");
|
|
19921
|
-
const candidates = targetRequired ? collectTargets(input2) : [];
|
|
19799
|
+
const candidates = targetRequired ? collectTargets(input2, targetKey) : [];
|
|
19922
19800
|
const targetMatches = !targetRequired || target !== undefined && candidates.includes(target);
|
|
19923
19801
|
if (missing.length === 0 && targetMatches)
|
|
19924
19802
|
return;
|
|
@@ -19933,13 +19811,26 @@ var requireEffects = async (effects, flags, confirm, input2) => {
|
|
|
19933
19811
|
];
|
|
19934
19812
|
throw new CliUsageError(`Command requires ${requirements.join(" and ")} in non-interactive use.${candidates.length > 0 ? ` Expected target: ${candidates.join(" or ")}.` : ""}`);
|
|
19935
19813
|
};
|
|
19814
|
+
var knownCommandNames = new Set([
|
|
19815
|
+
"help",
|
|
19816
|
+
"manifest",
|
|
19817
|
+
"version",
|
|
19818
|
+
"health",
|
|
19819
|
+
"auth login",
|
|
19820
|
+
"auth status",
|
|
19821
|
+
"auth logout",
|
|
19822
|
+
"auth whoami",
|
|
19823
|
+
...Object.keys(commands)
|
|
19824
|
+
]);
|
|
19936
19825
|
var runRsvpCli = async (argv, dependencies) => {
|
|
19937
|
-
let command =
|
|
19938
|
-
if (!command)
|
|
19939
|
-
command = "help";
|
|
19826
|
+
let command = "unknown";
|
|
19940
19827
|
try {
|
|
19941
19828
|
const parsed = parseArgs(argv);
|
|
19942
|
-
|
|
19829
|
+
const requestedCommand = parsed.positionals.join(" ") || "help";
|
|
19830
|
+
if (!knownCommandNames.has(requestedCommand)) {
|
|
19831
|
+
throw new CliUsageError("Unknown command. Run 'rsvp help' to list commands.");
|
|
19832
|
+
}
|
|
19833
|
+
command = requestedCommand;
|
|
19943
19834
|
if (command === "help") {
|
|
19944
19835
|
output2(dependencies.io, command, getCommandManifest());
|
|
19945
19836
|
return 0;
|
|
@@ -19986,15 +19877,53 @@ Waiting for approval\u2026
|
|
|
19986
19877
|
return 0;
|
|
19987
19878
|
}
|
|
19988
19879
|
if (command === "auth status") {
|
|
19989
|
-
const
|
|
19990
|
-
|
|
19991
|
-
|
|
19992
|
-
|
|
19993
|
-
|
|
19994
|
-
|
|
19995
|
-
|
|
19996
|
-
|
|
19997
|
-
|
|
19880
|
+
const store = getCredentialStore();
|
|
19881
|
+
const credentials = await store.get();
|
|
19882
|
+
if (!credentials) {
|
|
19883
|
+
output2(dependencies.io, command, {
|
|
19884
|
+
authenticated: false,
|
|
19885
|
+
storage: "Bun.secrets"
|
|
19886
|
+
});
|
|
19887
|
+
return 0;
|
|
19888
|
+
}
|
|
19889
|
+
if (!credentials.issuer) {
|
|
19890
|
+
output2(dependencies.io, command, {
|
|
19891
|
+
authenticated: false,
|
|
19892
|
+
reason: "These CLI credentials were created by an older CLI and are not bound to an API URL. Run auth login again.",
|
|
19893
|
+
storage: "Bun.secrets"
|
|
19894
|
+
});
|
|
19895
|
+
return 0;
|
|
19896
|
+
}
|
|
19897
|
+
try {
|
|
19898
|
+
const accessToken2 = await getAuthenticatedAccessToken({
|
|
19899
|
+
apiBaseUrl,
|
|
19900
|
+
store
|
|
19901
|
+
});
|
|
19902
|
+
const api3 = dependencies.apiClient ?? createClient(apiBaseUrl, accessToken2);
|
|
19903
|
+
await api3.user.getCurrent.query();
|
|
19904
|
+
const currentCredentials = await store.get();
|
|
19905
|
+
if (!currentCredentials || !isCliCredentialForIssuer(currentCredentials, apiBaseUrl) || currentCredentials.accessToken !== accessToken2) {
|
|
19906
|
+
throw new Error("CLI credentials changed while validating the session. Retry the command.");
|
|
19907
|
+
}
|
|
19908
|
+
output2(dependencies.io, command, {
|
|
19909
|
+
authenticated: true,
|
|
19910
|
+
accessTokenExpiresAt: currentCredentials.accessTokenExpiresAt,
|
|
19911
|
+
refreshTokenExpiresAt: currentCredentials.refreshTokenExpiresAt,
|
|
19912
|
+
session: currentCredentials.session,
|
|
19913
|
+
storage: "Bun.secrets",
|
|
19914
|
+
validated: true
|
|
19915
|
+
});
|
|
19916
|
+
return 0;
|
|
19917
|
+
} catch (cause) {
|
|
19918
|
+
output2(dependencies.io, command, {
|
|
19919
|
+
authenticated: false,
|
|
19920
|
+
accessTokenExpiresAt: credentials.accessTokenExpiresAt,
|
|
19921
|
+
refreshTokenExpiresAt: credentials.refreshTokenExpiresAt,
|
|
19922
|
+
reason: cause instanceof Error ? cause.message : "Unable to validate the CLI session. Retry the command.",
|
|
19923
|
+
storage: "Bun.secrets"
|
|
19924
|
+
});
|
|
19925
|
+
return 0;
|
|
19926
|
+
}
|
|
19998
19927
|
}
|
|
19999
19928
|
if (command === "auth logout") {
|
|
20000
19929
|
output2(dependencies.io, command, {
|
|
@@ -20019,7 +19948,7 @@ Waiting for approval\u2026
|
|
|
20019
19948
|
if (!definition)
|
|
20020
19949
|
throw new CliUsageError("Unknown command. Run 'rsvp help' to list commands.");
|
|
20021
19950
|
const input2 = getInput(parsed.flags, definition.payloadOnly, definition.scope);
|
|
20022
|
-
await requireEffects(definition.effects, parsed.flags, dependencies.confirm, input2);
|
|
19951
|
+
await requireEffects(definition.effects, parsed.flags, dependencies.confirm, input2, definition.targetKey);
|
|
20023
19952
|
const accessToken = !dependencies.apiClient && !definition.public ? await getAuthenticatedAccessToken({
|
|
20024
19953
|
apiBaseUrl,
|
|
20025
19954
|
store: getCredentialStore()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@go-labs-sg/rsvp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "RSVP CLI for authorized event operations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {},
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
],
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@trpc/client": "^11.18.0",
|
|
17
|
-
"dotenv": "^17.4.2",
|
|
18
17
|
"superjson": "^2.2.6"
|
|
19
18
|
},
|
|
20
19
|
"engines": {
|