gigadrive 1.1.1 → 2.0.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/index.cjs CHANGED
@@ -26,6 +26,45 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  // src/index.ts
27
27
  var import_commander = require("commander");
28
28
 
29
+ // src/util/log.ts
30
+ var import_consola = require("consola");
31
+ var verbose = false;
32
+ var consola = (0, import_consola.createConsola)({
33
+ level: verbose ? 4 : 3,
34
+ // 4 includes debug messages, 3 is the default (info and above)
35
+ formatOptions: {
36
+ colors: true
37
+ }
38
+ });
39
+ var updateConsola = () => {
40
+ consola.level = verbose ? 4 : 3;
41
+ };
42
+ var setVerbose = (value) => {
43
+ verbose = value;
44
+ updateConsola();
45
+ };
46
+ var log = (message) => {
47
+ consola.info(message);
48
+ };
49
+ var error = (message) => {
50
+ consola.error(message);
51
+ };
52
+ var success = (message) => {
53
+ consola.success(message);
54
+ };
55
+ var warn = (message) => {
56
+ consola.warn(message);
57
+ };
58
+ var debug = (message, ...optionalParams) => {
59
+ if (!verbose) {
60
+ return;
61
+ }
62
+ consola.debug("[DEBUG]", message, ...optionalParams);
63
+ };
64
+ var spinner = (message) => {
65
+ log(message);
66
+ };
67
+
29
68
  // src/commands/build/index.ts
30
69
  var import_build_utils = require("@gigadrive/build-utils");
31
70
  var import_fs2 = __toESM(require("fs"), 1);
@@ -40,7 +79,7 @@ var import_child_process = require("child_process");
40
79
  async function exec({
41
80
  command,
42
81
  cwd = process.cwd(),
43
- env,
82
+ env: env2,
44
83
  onOutput = (data) => console.log(data),
45
84
  onError = (data) => console.error(data)
46
85
  }) {
@@ -58,7 +97,7 @@ async function exec({
58
97
  const childProcess = (0, import_child_process.spawn)(spawnCommand, spawnArgs, {
59
98
  cwd,
60
99
  shell: true,
61
- env
100
+ env: env2
62
101
  });
63
102
  childProcess.stdout.on("data", (data) => {
64
103
  onOutput(data.toString().trim());
@@ -100,7 +139,7 @@ var getPackageManager = async ({ cwd }) => {
100
139
  try {
101
140
  await exec({ command: "bun --version", cwd });
102
141
  return "bun";
103
- } catch (e) {
142
+ } catch {
104
143
  return "npm";
105
144
  }
106
145
  };
@@ -124,7 +163,7 @@ var buildCommand = (pm) => {
124
163
  // src/commands/build/index.ts
125
164
  var build = (parent) => {
126
165
  parent.command("build").description("Build the current project").action(async () => {
127
- const env = {
166
+ const env2 = {
128
167
  ...process.env,
129
168
  NODE_ENV: "production",
130
169
  NEBULA: "1",
@@ -135,34 +174,34 @@ var build = (parent) => {
135
174
  const cwd = process.cwd();
136
175
  const packageJsonPath = import_path2.default.join(cwd, "package.json");
137
176
  if (!import_fs2.default.existsSync(packageJsonPath)) {
138
- console.log("No package.json found");
177
+ error("No package.json found");
139
178
  return;
140
179
  }
141
180
  const packageJson = JSON.parse(import_fs2.default.readFileSync(packageJsonPath, "utf-8"));
142
181
  const buildScript = packageJson.scripts?.build;
143
182
  if (!buildScript) {
144
- console.log("No build script found in package.json");
183
+ error("No build script found in package.json");
145
184
  return;
146
185
  }
147
186
  const packageManager = await getPackageManager({ cwd });
148
187
  if (!packageManager) {
149
- console.log("No package manager found");
188
+ error("No package manager found");
150
189
  return;
151
190
  }
152
191
  await (0, import_build_utils.exec)({
153
192
  command: installCommand(packageManager),
154
193
  cwd,
155
- env,
194
+ env: env2,
156
195
  onOutput: (chunk) => {
157
- console.log(chunk);
196
+ log(chunk);
158
197
  }
159
198
  });
160
199
  await (0, import_build_utils.exec)({
161
200
  command: buildCommand(packageManager),
162
201
  cwd,
163
- env,
202
+ env: env2,
164
203
  onOutput: (chunk) => {
165
- console.log(chunk);
204
+ log(chunk);
166
205
  }
167
206
  });
168
207
  });
@@ -171,47 +210,6 @@ var build = (parent) => {
171
210
  // src/commands/debug/config/index.ts
172
211
  var import_network_config = require("@gigadrive/network-config");
173
212
  var import_console = require("console");
174
-
175
- // src/util/log.ts
176
- var import_consola = require("consola");
177
- var verbose = false;
178
- var consola = (0, import_consola.createConsola)({
179
- level: verbose ? 4 : 3,
180
- // 4 includes debug messages, 3 is the default (info and above)
181
- formatOptions: {
182
- colors: true
183
- }
184
- });
185
- var updateConsola = () => {
186
- consola.level = verbose ? 4 : 3;
187
- };
188
- var setVerbose = (value) => {
189
- verbose = value;
190
- updateConsola();
191
- };
192
- var log = (message) => {
193
- consola.info(message);
194
- };
195
- var error = (message) => {
196
- consola.error(message);
197
- };
198
- var success = (message) => {
199
- consola.success(message);
200
- };
201
- var warn = (message) => {
202
- consola.warn(message);
203
- };
204
- var debug = (message, ...optionalParams) => {
205
- if (!verbose) {
206
- return;
207
- }
208
- consola.debug("[DEBUG]", message, ...optionalParams);
209
- };
210
- var spinner = (message) => {
211
- log(message);
212
- };
213
-
214
- // src/commands/debug/config/index.ts
215
213
  var config = (parent) => {
216
214
  parent.command("config").description("Provide debug info about the project configuration in the current directory").action(async () => {
217
215
  const configPath = (0, import_network_config.findConfig)(process.cwd());
@@ -231,6 +229,427 @@ var debug2 = (program) => {
231
229
  config(debug3);
232
230
  };
233
231
 
232
+ // src/env.ts
233
+ var import_env_core = require("@t3-oss/env-core");
234
+ var import_dotenv = require("dotenv");
235
+ var import_node_fs = __toESM(require("fs"), 1);
236
+ var import_node_path = __toESM(require("path"), 1);
237
+ var import_zod = require("zod");
238
+ (() => {
239
+ const cwd = process.cwd();
240
+ const nodeEnv = process.env.NODE_ENV;
241
+ const candidates = [
242
+ nodeEnv ? `.env.${nodeEnv}.local` : null,
243
+ `.env.local`,
244
+ nodeEnv ? `.env.${nodeEnv}` : null,
245
+ `.env`
246
+ ].filter(Boolean);
247
+ for (const filename of candidates) {
248
+ const fullPath = import_node_path.default.join(cwd, filename);
249
+ if (import_node_fs.default.existsSync(fullPath)) {
250
+ console.debug(`Loading environment variables from ${fullPath}`);
251
+ (0, import_dotenv.config)({ path: fullPath, override: false });
252
+ }
253
+ }
254
+ })();
255
+ var env = (0, import_env_core.createEnv)({
256
+ server: {
257
+ GIGADRIVE_NETWORK_OAUTH_ISSUER_URL: import_zod.z.url().min(1).default("https://idp.gigadrive.de"),
258
+ GIGADRIVE_NETWORK_OAUTH_CLIENT_ID: import_zod.z.string().min(1).default("todo_add_client_id")
259
+ },
260
+ runtimeEnv: process.env,
261
+ emptyStringAsUndefined: true
262
+ });
263
+
264
+ // src/config/oauth.ts
265
+ var cachedConfig = null;
266
+ async function getOAuthConfig() {
267
+ if (cachedConfig) return cachedConfig;
268
+ const issuerBase = env.GIGADRIVE_NETWORK_OAUTH_ISSUER_URL.replace(/\/$/, "");
269
+ const controller = new AbortController();
270
+ const timeout = setTimeout(() => controller.abort(), 8e3);
271
+ let discovery = null;
272
+ try {
273
+ const discoveryUrl = `${issuerBase}/.well-known/openid-configuration`;
274
+ const res = await fetch(discoveryUrl, { signal: controller.signal });
275
+ if (res.ok) {
276
+ discovery = await res.json();
277
+ } else {
278
+ discovery = null;
279
+ }
280
+ } catch {
281
+ discovery = null;
282
+ } finally {
283
+ clearTimeout(timeout);
284
+ }
285
+ if (!discovery?.authorization_endpoint || !discovery?.token_endpoint || !discovery?.userinfo_endpoint) {
286
+ throw new Error("OIDC discovery failed or returned incomplete endpoints");
287
+ }
288
+ const DEFAULT_SCOPE = "offline_access openid profile email";
289
+ cachedConfig = {
290
+ clientId: env.GIGADRIVE_NETWORK_OAUTH_CLIENT_ID,
291
+ issuer: discovery.issuer ?? issuerBase,
292
+ authorizeUrl: discovery.authorization_endpoint,
293
+ tokenUrl: discovery.token_endpoint,
294
+ scope: DEFAULT_SCOPE,
295
+ userinfoUrl: discovery.userinfo_endpoint
296
+ };
297
+ return cachedConfig;
298
+ }
299
+
300
+ // src/auth/manager.ts
301
+ var crypto = __toESM(require("crypto"), 1);
302
+ var fs4 = __toESM(require("fs"), 1);
303
+ var http = __toESM(require("http"), 1);
304
+ var os = __toESM(require("os"), 1);
305
+ var path4 = __toESM(require("path"), 1);
306
+ var import_node_url = require("url");
307
+ var import_open = __toESM(require("open"), 1);
308
+ function getAuthStoragePaths() {
309
+ const homeDir = os.homedir();
310
+ const authDir = path4.join(homeDir, ".gigadrive");
311
+ const authFile = path4.join(authDir, "auth.json");
312
+ return { directory: authDir, file: authFile };
313
+ }
314
+ async function ensureAuthDirectory() {
315
+ const { directory } = getAuthStoragePaths();
316
+ try {
317
+ await fs4.promises.mkdir(directory, { recursive: true, mode: 448 });
318
+ await fs4.promises.chmod(directory, 448);
319
+ } catch (error2) {
320
+ const errorMessage = error2 instanceof Error ? error2.message : "Unknown error";
321
+ throw new Error(`Failed to create auth directory: ${errorMessage}`);
322
+ }
323
+ }
324
+ function generatePKCE() {
325
+ const codeVerifier = base64URLEncode(crypto.randomBytes(64));
326
+ const challengeBytes = crypto.createHash("sha256").update(codeVerifier, "utf8").digest();
327
+ const codeChallenge = base64URLEncode(challengeBytes);
328
+ return { codeVerifier, codeChallenge };
329
+ }
330
+ function base64URLEncode(buffer) {
331
+ return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
332
+ }
333
+ var AuthManager = class {
334
+ accessToken = null;
335
+ tokenExpirationTime = 0;
336
+ // Unix timestamp in milliseconds
337
+ codeVerifier = null;
338
+ state = null;
339
+ constructor() {
340
+ }
341
+ /**
342
+ * Retrieves the stored auth data from the local file.
343
+ * @returns {Promise<StoredAuthData | null>} The stored auth data if found, otherwise null.
344
+ * @private
345
+ */
346
+ async _loadStoredAuthData() {
347
+ try {
348
+ const { file } = getAuthStoragePaths();
349
+ const data = await fs4.promises.readFile(file, "utf8");
350
+ const authData = JSON.parse(data);
351
+ if (authData.accessToken && authData.tokenExpirationTime && Date.now() < authData.tokenExpirationTime) {
352
+ this.accessToken = authData.accessToken;
353
+ this.tokenExpirationTime = authData.tokenExpirationTime;
354
+ }
355
+ return authData;
356
+ } catch {
357
+ return null;
358
+ }
359
+ }
360
+ /**
361
+ * Stores the auth data in the local file.
362
+ * @param {StoredAuthData} authData The auth data to store.
363
+ * @private
364
+ */
365
+ async _saveStoredAuthData(authData) {
366
+ try {
367
+ await ensureAuthDirectory();
368
+ const { file } = getAuthStoragePaths();
369
+ await fs4.promises.writeFile(file, JSON.stringify(authData, null, 2), { encoding: "utf8", mode: 384 });
370
+ await fs4.promises.chmod(file, 384);
371
+ console.debug("Auth data saved to local storage.");
372
+ } catch (error2) {
373
+ const errorMessage = error2 instanceof Error ? error2.message : "Unknown error";
374
+ console.error("Error saving auth data to local storage:", errorMessage);
375
+ }
376
+ }
377
+ /**
378
+ * Deletes the stored auth data from the local file.
379
+ */
380
+ async logout() {
381
+ this.accessToken = null;
382
+ this.tokenExpirationTime = 0;
383
+ try {
384
+ const { file } = getAuthStoragePaths();
385
+ await fs4.promises.unlink(file);
386
+ log("Logged out. Auth data removed from local storage.");
387
+ } catch {
388
+ log("Logged out.");
389
+ }
390
+ }
391
+ /**
392
+ * Initiates the OAuth login flow with the IDP via a web portal.
393
+ * Opens a browser for user authentication and listens for the redirect.
394
+ * @returns {Promise<boolean>} True if login was successful, false otherwise.
395
+ */
396
+ async login() {
397
+ await this.logout();
398
+ const { codeVerifier, codeChallenge } = generatePKCE();
399
+ this.codeVerifier = codeVerifier;
400
+ this.state = crypto.randomBytes(16).toString("hex");
401
+ const OAUTH_CONFIG = await getOAuthConfig();
402
+ const loginState = this.state;
403
+ if (!loginState) {
404
+ throw new Error("Internal error: login state not initialized");
405
+ }
406
+ const server = http.createServer();
407
+ let usedRedirectUri = "";
408
+ const serverPromise = new Promise((resolve, reject) => {
409
+ const redirectPath = "/callback";
410
+ server.listen(0, "127.0.0.1", () => {
411
+ const address = server.address();
412
+ if (!address || typeof address !== "object") {
413
+ reject(new Error("Failed to determine callback server address"));
414
+ return;
415
+ }
416
+ const port = address.port;
417
+ usedRedirectUri = `http://127.0.0.1:${port}${redirectPath}`;
418
+ const authUrl = new import_node_url.URL(OAUTH_CONFIG.authorizeUrl);
419
+ authUrl.searchParams.append("client_id", OAUTH_CONFIG.clientId);
420
+ authUrl.searchParams.append("redirect_uri", usedRedirectUri);
421
+ authUrl.searchParams.append("response_type", "code");
422
+ authUrl.searchParams.append("scope", OAUTH_CONFIG.scope);
423
+ authUrl.searchParams.append("code_challenge", codeChallenge);
424
+ authUrl.searchParams.append("code_challenge_method", "S256");
425
+ authUrl.searchParams.append("state", loginState);
426
+ log(`Please open this URL in your browser to log in:
427
+ ${authUrl.toString()}
428
+ `);
429
+ log("Waiting for login callback...");
430
+ void (0, import_open.default)(authUrl.toString());
431
+ });
432
+ server.on("request", (req, res) => {
433
+ const base = usedRedirectUri || "http://127.0.0.1/callback";
434
+ const reqUrl = new import_node_url.URL(req.url || "", base);
435
+ if (reqUrl.pathname === "/callback") {
436
+ const authCode = reqUrl.searchParams.get("code");
437
+ const receivedState = reqUrl.searchParams.get("state");
438
+ const error2 = reqUrl.searchParams.get("error");
439
+ res.writeHead(200, { "Content-Type": "text/html", Connection: "close" });
440
+ if (error2) {
441
+ res.end("<h1>Login Failed!</h1><p>Error: " + error2 + "</p><p>You can close this tab.</p>");
442
+ reject(new Error(`OAuth error: ${error2}`));
443
+ server.close();
444
+ return;
445
+ }
446
+ if (receivedState !== this.state) {
447
+ res.end("<h1>Login Failed!</h1><p>State mismatch. Possible CSRF attack.</p><p>You can close this tab.</p>");
448
+ reject(new Error("State mismatch"));
449
+ server.close();
450
+ return;
451
+ }
452
+ if (authCode) {
453
+ res.end("<h1>Login Successful!</h1><p>You can close this tab.</p>");
454
+ resolve(authCode);
455
+ server.close();
456
+ } else {
457
+ res.end("<h1>Login Failed!</h1><p>No authorization code received.</p><p>You can close this tab.</p>");
458
+ reject(new Error("No authorization code received"));
459
+ server.close();
460
+ }
461
+ } else {
462
+ res.writeHead(404).end("Not Found");
463
+ }
464
+ });
465
+ server.on("error", (err) => {
466
+ console.error("Local server error:", err);
467
+ reject(err);
468
+ });
469
+ });
470
+ try {
471
+ const authCode = await serverPromise;
472
+ const tokenResponse = await fetch(OAUTH_CONFIG.tokenUrl, {
473
+ method: "POST",
474
+ headers: {
475
+ "Content-Type": "application/x-www-form-urlencoded"
476
+ },
477
+ body: new import_node_url.URLSearchParams({
478
+ grant_type: "authorization_code",
479
+ client_id: OAUTH_CONFIG.clientId,
480
+ code: authCode,
481
+ ...usedRedirectUri ? { redirect_uri: usedRedirectUri } : {},
482
+ code_verifier: this.codeVerifier || ""
483
+ // Use the stored codeVerifier
484
+ }).toString()
485
+ });
486
+ if (!tokenResponse.ok) {
487
+ const errorData = await tokenResponse.json().catch(() => null);
488
+ throw new Error(
489
+ `Token exchange failed: ${tokenResponse.status} ${tokenResponse.statusText} - ${errorData ? JSON.stringify(errorData) : "Unknown error"}`
490
+ );
491
+ }
492
+ const tokens = await tokenResponse.json();
493
+ this.accessToken = tokens.access_token;
494
+ this.tokenExpirationTime = Date.now() + (tokens.expires_in || 3600) * 1e3;
495
+ if (tokens.refresh_token) {
496
+ await this._saveStoredAuthData({
497
+ refreshToken: tokens.refresh_token,
498
+ accessToken: this.accessToken,
499
+ tokenExpirationTime: this.tokenExpirationTime
500
+ });
501
+ success("Login successful!");
502
+ return true;
503
+ } else {
504
+ console.error("Error: No refresh token received from Gigadrive IDP.");
505
+ return false;
506
+ }
507
+ } catch (error2) {
508
+ const errorMessage = error2 instanceof Error ? error2.message : "Unknown error";
509
+ console.error("Login process failed:", errorMessage);
510
+ this.accessToken = null;
511
+ this.tokenExpirationTime = 0;
512
+ return false;
513
+ } finally {
514
+ this.codeVerifier = null;
515
+ this.state = null;
516
+ }
517
+ }
518
+ /**
519
+ * Refreshes the access token using the stored refresh token.
520
+ * This method implicitly loads the refresh token from local storage.
521
+ * @returns {Promise<boolean>} True if refresh was successful, false otherwise.
522
+ */
523
+ async refreshAccessToken() {
524
+ const storedData = await this._loadStoredAuthData();
525
+ const OAUTH_CONFIG = await getOAuthConfig();
526
+ if (!storedData?.refreshToken) {
527
+ error("No refresh token available for refresh. Please log in.");
528
+ return false;
529
+ }
530
+ try {
531
+ const tokenResponse = await fetch(OAUTH_CONFIG.tokenUrl, {
532
+ method: "POST",
533
+ headers: {
534
+ "Content-Type": "application/x-www-form-urlencoded"
535
+ },
536
+ body: new import_node_url.URLSearchParams({
537
+ grant_type: "refresh_token",
538
+ client_id: OAUTH_CONFIG.clientId,
539
+ refresh_token: storedData.refreshToken
540
+ }).toString()
541
+ });
542
+ if (!tokenResponse.ok) {
543
+ const errorData = await tokenResponse.json().catch(() => null);
544
+ if (tokenResponse.status === 400 && errorData && (errorData.error === "oauth_token_not_found" || errorData.errors?.[0]?.code === "token_invalid" || errorData.errors?.[0]?.code === "token_expired" || errorData.errors?.[0]?.code === "token_revoked")) {
545
+ console.error("Refresh token is invalid or expired. Please log in again.");
546
+ await this.logout();
547
+ return false;
548
+ }
549
+ throw new Error(
550
+ `Token refresh failed: ${tokenResponse.status} ${tokenResponse.statusText} - ${errorData ? JSON.stringify(errorData) : "Unknown error"}`
551
+ );
552
+ }
553
+ const tokens = await tokenResponse.json();
554
+ this.accessToken = tokens.access_token;
555
+ this.tokenExpirationTime = Date.now() + (tokens.expires_in || 3600) * 1e3;
556
+ const newRefreshToken = tokens.refresh_token || storedData.refreshToken;
557
+ if (tokens.refresh_token && tokens.refresh_token !== storedData.refreshToken) {
558
+ console.debug("New refresh token received and updated.");
559
+ }
560
+ await this._saveStoredAuthData({
561
+ refreshToken: newRefreshToken,
562
+ accessToken: this.accessToken,
563
+ tokenExpirationTime: this.tokenExpirationTime
564
+ });
565
+ console.debug("Access token refreshed successfully!");
566
+ return true;
567
+ } catch (error2) {
568
+ const errorMessage = error2 instanceof Error ? error2.message : "Unknown error";
569
+ console.error("Error refreshing access token:", errorMessage);
570
+ this.accessToken = null;
571
+ this.tokenExpirationTime = 0;
572
+ return false;
573
+ }
574
+ }
575
+ /**
576
+ * Provides the current valid access token. If the in-memory token is expired,
577
+ * it attempts to refresh it using the stored refresh token.
578
+ * @returns {Promise<string | null>} The valid access token or null if no session.
579
+ */
580
+ async getAccessToken() {
581
+ if (this.accessToken && Date.now() < this.tokenExpirationTime) {
582
+ return this.accessToken;
583
+ }
584
+ console.debug("Access token expired or not available. Attempting to refresh...");
585
+ if (await this.refreshAccessToken()) {
586
+ return this.accessToken;
587
+ }
588
+ error("No valid access token available. Please log in first.");
589
+ return null;
590
+ }
591
+ /**
592
+ * Fetches the current user's information from the configured userinfo endpoint.
593
+ * Attempts a single token refresh and retry on 401/403 responses.
594
+ */
595
+ async getUserInfo() {
596
+ const OAUTH_CONFIG = await getOAuthConfig();
597
+ const currentToken = await this.getAccessToken();
598
+ if (!currentToken) {
599
+ error("Not authenticated. Please log in.");
600
+ return null;
601
+ }
602
+ const fetchUserInfo = async (token) => fetch(OAUTH_CONFIG.userinfoUrl, {
603
+ headers: {
604
+ Authorization: `Bearer ${token}`,
605
+ Accept: "application/json"
606
+ }
607
+ });
608
+ let response = await fetchUserInfo(currentToken);
609
+ if (response.status === 401 || response.status === 403) {
610
+ const refreshed = await this.refreshAccessToken();
611
+ if (!refreshed || !this.accessToken) {
612
+ return null;
613
+ }
614
+ response = await fetchUserInfo(this.accessToken);
615
+ }
616
+ if (!response.ok) {
617
+ const body = await response.text().catch(() => "");
618
+ throw new Error(
619
+ `Failed to fetch user info: ${response.status} ${response.statusText}${body ? ` - ${body}` : ""}`
620
+ );
621
+ }
622
+ const data = await response.json();
623
+ return data;
624
+ }
625
+ };
626
+ var authManager = new AuthManager();
627
+
628
+ // src/commands/login/index.ts
629
+ var login = (program) => {
630
+ program.command("login").description("Login to the platform").action(async () => {
631
+ log("Initiating login...");
632
+ const result = await authManager.login();
633
+ if (result) {
634
+ try {
635
+ const info = await authManager.getUserInfo() ?? {};
636
+ const isNonEmptyString = (value) => typeof value === "string" && value.trim() !== "";
637
+ const nameField = info.name;
638
+ const givenNameField = info.given_name;
639
+ const familyNameField = info.family_name;
640
+ const preferredUsernameField = info.preferred_username;
641
+ const emailField = info.email;
642
+ const inferredName = isNonEmptyString(nameField) && nameField || (isNonEmptyString(givenNameField) && isNonEmptyString(familyNameField) ? `${givenNameField} ${familyNameField}` : void 0) || isNonEmptyString(preferredUsernameField) && preferredUsernameField || isNonEmptyString(emailField) && emailField || "your account";
643
+ success(`You are now logged in as ${inferredName}.`);
644
+ } catch {
645
+ success("You are now logged in.");
646
+ }
647
+ } else {
648
+ error("Login failed. Please try again.");
649
+ }
650
+ });
651
+ };
652
+
234
653
  // src/commands/platform/deploy/index.ts
235
654
  var import_commons = require("@gigadrive/commons");
236
655
  var import_network_config2 = require("@gigadrive/network-config");
@@ -242,6 +661,7 @@ var import_path4 = __toESM(require("path"), 1);
242
661
  var import_build_utils2 = require("@gigadrive/build-utils");
243
662
  var import_archiver = __toESM(require("archiver"), 1);
244
663
  var import_fs3 = __toESM(require("fs"), 1);
664
+ var import_promises = __toESM(require("fs/promises"), 1);
245
665
  var import_ignore = __toESM(require("ignore"), 1);
246
666
  var import_path3 = __toESM(require("path"), 1);
247
667
  var ignoreFileNames = [
@@ -251,78 +671,84 @@ var ignoreFileNames = [
251
671
  ".dockerignore",
252
672
  ".nowignore"
253
673
  ];
254
- function readIgnoreFile(ignorePath) {
255
- if (import_fs3.default.existsSync(ignorePath)) {
256
- return import_fs3.default.readFileSync(ignorePath, "utf8").split("\n").filter((line) => line.trim() !== "");
674
+ async function readIgnoreFile(ignorePath) {
675
+ try {
676
+ await import_promises.default.access(ignorePath);
677
+ const content = await import_promises.default.readFile(ignorePath, "utf8");
678
+ return content.split("\n").filter((line) => line.trim() !== "");
679
+ } catch {
680
+ return [];
257
681
  }
258
- return [];
259
682
  }
260
- function collectIgnorePatterns(dir, baseDir, ignoreRules) {
261
- const items = import_fs3.default.readdirSync(dir);
262
- items.forEach((item) => {
683
+ async function collectIgnorePatterns(dir, baseDir, ignoreRules) {
684
+ const items = await import_promises.default.readdir(dir);
685
+ for (const item of items) {
263
686
  const fullPath = import_path3.default.join(dir, item);
264
687
  const relativePath = import_path3.default.relative(baseDir, fullPath);
265
- const stat = import_fs3.default.lstatSync(fullPath);
688
+ const stat = await import_promises.default.lstat(fullPath);
266
689
  if (stat.isDirectory()) {
267
690
  if (ignoreRules.ignores(`${relativePath}/`)) {
268
- return;
691
+ continue;
269
692
  }
270
- ignoreFileNames.forEach((ignoreFileName) => {
693
+ for (const ignoreFileName of ignoreFileNames) {
271
694
  const ignoreFilePath = import_path3.default.join(fullPath, ignoreFileName);
272
- if (import_fs3.default.existsSync(ignoreFilePath)) {
273
- const patterns = readIgnoreFile(ignoreFilePath).map((pattern) => {
695
+ try {
696
+ await import_promises.default.access(ignoreFilePath);
697
+ const patterns = (await readIgnoreFile(ignoreFilePath)).map((pattern) => {
274
698
  return pattern.startsWith("!") ? `!${import_path3.default.join(relativePath, pattern.slice(1))}` : import_path3.default.join(relativePath, pattern);
275
699
  });
276
700
  ignoreRules.add(patterns);
701
+ } catch {
277
702
  }
278
- });
279
- collectIgnorePatterns(fullPath, baseDir, ignoreRules);
703
+ }
704
+ await collectIgnorePatterns(fullPath, baseDir, ignoreRules);
280
705
  }
281
- });
706
+ }
282
707
  }
283
- function initializeIgnoreRules(baseDir) {
708
+ async function initializeIgnoreRules(baseDir) {
284
709
  const ignoreRules = (0, import_ignore.default)();
285
710
  ignoreRules.add([".git/", "**/.DS_Store", "**/Thumbs.db"]);
286
- ignoreFileNames.forEach((ignoreFileName) => {
711
+ for (const ignoreFileName of ignoreFileNames) {
287
712
  const ignoreFilePath = import_path3.default.join(baseDir, ignoreFileName);
288
- if (import_fs3.default.existsSync(ignoreFilePath)) {
289
- const patterns = readIgnoreFile(ignoreFilePath).map((pattern) => {
713
+ try {
714
+ await import_promises.default.access(ignoreFilePath);
715
+ const patterns = (await readIgnoreFile(ignoreFilePath)).map((pattern) => {
290
716
  return pattern.startsWith("!") ? `!${pattern.slice(1)}` : pattern;
291
717
  });
292
718
  ignoreRules.add(patterns);
719
+ } catch {
293
720
  }
294
- });
295
- collectIgnorePatterns(baseDir, baseDir, ignoreRules);
721
+ }
722
+ await collectIgnorePatterns(baseDir, baseDir, ignoreRules);
296
723
  return ignoreRules;
297
724
  }
298
725
  async function getFilesToInclude(dir, ignoreRules, whitelist) {
299
726
  const filesToInclude = [];
300
- function walkSync(currentDir) {
301
- const items = import_fs3.default.readdirSync(currentDir);
302
- items.forEach((item) => {
727
+ async function walkDir(currentDir) {
728
+ const items = await import_promises.default.readdir(currentDir);
729
+ for (const item of items) {
303
730
  const fullPath = import_path3.default.join(currentDir, item);
304
731
  const relativePath = import_path3.default.relative(dir, fullPath);
305
- const stat = import_fs3.default.lstatSync(fullPath);
732
+ const stat = await import_promises.default.lstat(fullPath);
306
733
  if (stat.isDirectory()) {
307
734
  if (ignoreRules.ignores(`${relativePath}/`)) {
308
- return;
735
+ continue;
309
736
  }
310
- walkSync(fullPath);
737
+ await walkDir(fullPath);
311
738
  } else {
312
739
  if (!ignoreRules.ignores(relativePath)) {
313
740
  filesToInclude.push(fullPath);
314
741
  } else if (ignoreRules.ignores(relativePath) && ignoreRules.test(relativePath).unignored) {
315
742
  filesToInclude.push(fullPath);
316
- } else {
317
743
  }
318
744
  }
319
- });
745
+ }
320
746
  }
321
- walkSync(dir);
747
+ await walkDir(dir);
322
748
  return filesToInclude;
323
749
  }
324
750
  async function createZipArchive(inputDir, outputFile, options = {}) {
325
- const ignoreRules = options.useIgnoreFiles !== false ? initializeIgnoreRules(inputDir) : (0, import_ignore.default)();
751
+ const ignoreRules = options.useIgnoreFiles !== false ? await initializeIgnoreRules(inputDir) : (0, import_ignore.default)();
326
752
  const filesToInclude = options.whitelist ? await Promise.all(options.whitelist.map((file) => (0, import_build_utils2.getFilesForPattern)(inputDir, file))) : await getFilesToInclude(inputDir, ignoreRules);
327
753
  const output = import_fs3.default.createWriteStream(outputFile);
328
754
  const archive = (0, import_archiver.default)("zip", { zlib: { level: 9 } });
@@ -330,10 +756,10 @@ async function createZipArchive(inputDir, outputFile, options = {}) {
330
756
  output.on("close", () => resolve());
331
757
  archive.on("error", reject);
332
758
  archive.pipe(output);
333
- filesToInclude.forEach((file) => {
759
+ for (const file of filesToInclude.flat()) {
334
760
  archive.file(file, { name: import_path3.default.relative(inputDir, file) });
335
- });
336
- archive.finalize();
761
+ }
762
+ void archive.finalize();
337
763
  });
338
764
  }
339
765
 
@@ -390,53 +816,57 @@ var handleDeployment = async (deploymentId, config2) => {
390
816
  error("The deployment failed. Please check the logs for more information.");
391
817
  process.exit(1);
392
818
  }
393
- const statusInterval = setInterval(async () => {
394
- try {
395
- debug(`Fetching deployment status for ${deploymentId}`);
396
- const newStatus = await getDeploymentStatus(deploymentId);
397
- debug(`Current status: ${status}, New status: ${newStatus}`);
398
- if (newStatus === "ACTIVE" || newStatus === "FAILED") {
399
- debug(`Final status reached: ${newStatus}, clearing interval`);
400
- clearInterval(statusInterval);
401
- clearInterval(logsInterval);
402
- if (newStatus === "ACTIVE") {
403
- success(`Deployed to https://${deploymentId}.gigadrivedev.com`);
404
- } else {
405
- error("The deployment failed. Please check the logs for more information.");
819
+ const statusInterval = setInterval(() => {
820
+ void (async () => {
821
+ try {
822
+ debug(`Fetching deployment status for ${deploymentId}`);
823
+ const newStatus = await getDeploymentStatus(deploymentId);
824
+ debug(`Current status: ${status}, New status: ${newStatus}`);
825
+ if (newStatus === "ACTIVE" || newStatus === "FAILED") {
826
+ debug(`Final status reached: ${newStatus}, clearing interval`);
827
+ clearInterval(statusInterval);
828
+ clearInterval(logsInterval);
829
+ if (newStatus === "ACTIVE") {
830
+ success(`Deployed to https://${deploymentId}.gigadrivedev.com`);
831
+ } else {
832
+ error("The deployment failed. Please check the logs for more information.");
833
+ }
834
+ return;
406
835
  }
407
- return;
408
- }
409
- if (newStatus !== status) {
410
- debug(`Updating status from ${status} to ${newStatus}`);
411
- status = newStatus;
412
- spinner(`Status: ${status.toLowerCase().replace(/^\w/, (c) => c.toUpperCase())}`);
836
+ if (newStatus !== status) {
837
+ debug(`Updating status from ${status} to ${newStatus}`);
838
+ status = newStatus;
839
+ spinner(`Status: ${status.toLowerCase().replace(/^\w/, (c) => c.toUpperCase())}`);
840
+ }
841
+ } catch (err) {
842
+ debug(`Error fetching status: ${err.message}`);
413
843
  }
414
- } catch (err) {
415
- debug(`Error fetching status: ${err.message}`);
416
- }
844
+ })();
417
845
  }, 1e3);
418
- const logsInterval = setInterval(async () => {
419
- try {
420
- debug(`Fetching logs for ${deploymentId}, current log count: ${logs.length}`);
421
- const newLogs = await getLogs(deploymentId, {
422
- offset: logs.length,
423
- limit: 100,
424
- "createdAt[gt]": logs[logs.length - 1]?.createdAt
425
- });
426
- debug(`Fetched ${newLogs.items.length} new logs`);
427
- for (const logEntry of newLogs.items) {
428
- logs.push(logEntry);
429
- if (logEntry.type === "ERROR") {
430
- error(logEntry.message);
431
- } else if (logEntry.type === "WARN") {
432
- warn(logEntry.message);
433
- } else {
434
- log(logEntry.message);
846
+ const logsInterval = setInterval(() => {
847
+ void (async () => {
848
+ try {
849
+ debug(`Fetching logs for ${deploymentId}, current log count: ${logs.length}`);
850
+ const newLogs = await getLogs(deploymentId, {
851
+ offset: logs.length,
852
+ limit: 100,
853
+ "createdAt[gt]": logs[logs.length - 1]?.createdAt
854
+ });
855
+ debug(`Fetched ${newLogs.items.length} new logs`);
856
+ for (const logEntry of newLogs.items) {
857
+ logs.push(logEntry);
858
+ if (logEntry.type === "ERROR") {
859
+ error(logEntry.message);
860
+ } else if (logEntry.type === "WARN") {
861
+ warn(logEntry.message);
862
+ } else {
863
+ log(logEntry.message);
864
+ }
435
865
  }
866
+ } catch (err) {
867
+ debug(`Error fetching logs: ${err.message}`);
436
868
  }
437
- } catch (err) {
438
- debug(`Error fetching logs: ${err.message}`);
439
- }
869
+ })();
440
870
  }, 1e3);
441
871
  spinner(`Status: ${status.toLowerCase().replace(/^\w/, (c) => c.toUpperCase())}`);
442
872
  };
@@ -483,7 +913,7 @@ var uploadArchive = async (deploymentId, logs, config2) => {
483
913
  const fileChunkSize = 10 * 1024 * 1024;
484
914
  const numChunks = Math.ceil(fileSize / fileChunkSize);
485
915
  debug(`File size: ${fileSize}, chunk size: ${fileChunkSize}, number of chunks: ${numChunks}`);
486
- const promises = [];
916
+ const promises2 = [];
487
917
  let start, end;
488
918
  for (let index = 1; index < numChunks + 1; index++) {
489
919
  start = (index - 1) * fileChunkSize;
@@ -491,42 +921,44 @@ var uploadArchive = async (deploymentId, logs, config2) => {
491
921
  debug(`Processing chunk ${index}/${numChunks}, start: ${start}, end: ${end}`);
492
922
  const presignedUrl = await getPresignedUrl(deploymentId, uploadId, index);
493
923
  debug("Presigned URL for part", index, presignedUrl);
494
- const promise = new Promise(async (resolve, reject) => {
495
- try {
496
- debug(`Reading chunk ${index} data`);
497
- const readStream = import_fs4.default.createReadStream(archivePath, {
498
- start,
499
- end: index < numChunks ? end - 1 : void 0
500
- });
501
- const chunks = [];
502
- readStream.on("data", (chunk) => {
503
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
504
- });
505
- await new Promise((resolveRead) => {
506
- readStream.on("end", () => resolveRead());
507
- readStream.on("error", reject);
508
- });
509
- const buffer = Buffer.concat(chunks);
510
- debug(`Uploading chunk ${index} to presigned URL (${buffer.length} bytes)`);
511
- const response = await fetch(presignedUrl, {
512
- method: "PUT",
513
- headers: {
514
- "Content-Type": "application/zip",
515
- "Content-Length": String(buffer.length)
516
- },
517
- body: buffer
518
- });
519
- resolve(response);
520
- } catch (err) {
521
- reject(err);
522
- }
924
+ const promise = new Promise((resolve, reject) => {
925
+ void (async () => {
926
+ try {
927
+ debug(`Reading chunk ${index} data`);
928
+ const readStream = import_fs4.default.createReadStream(archivePath, {
929
+ start,
930
+ end: index < numChunks ? end - 1 : void 0
931
+ });
932
+ const chunks = [];
933
+ readStream.on("data", (chunk) => {
934
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
935
+ });
936
+ await new Promise((resolveRead) => {
937
+ readStream.on("end", () => resolveRead());
938
+ readStream.on("error", reject);
939
+ });
940
+ const buffer = Buffer.concat(chunks);
941
+ debug(`Uploading chunk ${index} to presigned URL (${buffer.length} bytes)`);
942
+ const response = await fetch(presignedUrl, {
943
+ method: "PUT",
944
+ headers: {
945
+ "Content-Type": "application/zip",
946
+ "Content-Length": String(buffer.length)
947
+ },
948
+ body: buffer
949
+ });
950
+ resolve(response);
951
+ } catch (err) {
952
+ reject(err instanceof Error ? err : new Error(String(err)));
953
+ }
954
+ })();
523
955
  });
524
- promises.push(promise);
956
+ promises2.push(promise);
525
957
  }
526
958
  const finishedParts = [];
527
959
  try {
528
- debug(`Waiting for all ${promises.length} upload promises to complete`);
529
- const responses = await Promise.all(promises);
960
+ debug(`Waiting for all ${promises2.length} upload promises to complete`);
961
+ const responses = await Promise.all(promises2);
530
962
  debug("All upload promises completed");
531
963
  for (let index = 0; index < responses.length; index++) {
532
964
  const res = responses[index];
@@ -650,7 +1082,7 @@ var getDeploymentStatus = async (deploymentId) => {
650
1082
  return deployment.status;
651
1083
  };
652
1084
  var getLogs = async (deploymentId, options) => {
653
- debug(`Getting logs for deployment: ${deploymentId}, options:`, options);
1085
+ debug(`Getting logs for deployment: ${deploymentId}, options:`, options ?? "No options");
654
1086
  const res = await fetch(`http://localhost:3000/deployments/${deploymentId}/logs${(0, import_commons.objectToQueryString)(options)}`, {
655
1087
  method: "GET",
656
1088
  headers: {
@@ -673,6 +1105,23 @@ var platform = (program) => {
673
1105
  deploy(platform2);
674
1106
  };
675
1107
 
1108
+ // src/commands/whoami/index.ts
1109
+ var whoami = (program) => {
1110
+ program.command("whoami").description("Show the currently authenticated user information").action(async () => {
1111
+ try {
1112
+ const info = await authManager.getUserInfo();
1113
+ if (!info) {
1114
+ console.log('You are not logged in. Run "gigadrive login" to authenticate.');
1115
+ return;
1116
+ }
1117
+ console.log(JSON.stringify(info, null, 2));
1118
+ } catch (err) {
1119
+ const msg = err instanceof Error ? err.message : String(err);
1120
+ console.error(`Failed to retrieve user info: ${msg}`);
1121
+ }
1122
+ });
1123
+ };
1124
+
676
1125
  // src/index.ts
677
1126
  var { isTTY } = process.stdout;
678
1127
  var main = async () => {
@@ -684,7 +1133,7 @@ var main = async () => {
684
1133
  let exitCode = 0;
685
1134
  const program = new import_commander.Command();
686
1135
  program.option("-v, --verbose", "Enable verbose logging", false);
687
- program.hook("preSubcommand", (thisCommand, actionCommand) => {
1136
+ program.hook("preSubcommand", (thisCommand) => {
688
1137
  setVerbose(thisCommand.opts().verbose === true);
689
1138
  if (thisCommand.opts().verbose) {
690
1139
  log("Using verbose mode");
@@ -696,6 +1145,8 @@ var main = async () => {
696
1145
  platform(program);
697
1146
  debug2(program);
698
1147
  build(program);
1148
+ login(program);
1149
+ whoami(program);
699
1150
  try {
700
1151
  await program.parseAsync(process.argv);
701
1152
  } catch (err) {
@@ -704,13 +1155,13 @@ var main = async () => {
704
1155
  }
705
1156
  return exitCode;
706
1157
  };
707
- var handleRejection = async (err) => {
1158
+ var handleRejection = (err) => {
708
1159
  if (err) {
709
1160
  if (err instanceof Error) {
710
- await handleUnexpected(err);
1161
+ handleUnexpected(err);
711
1162
  } else {
712
1163
  console.error(`An unexpected rejection occurred
713
- ${err}`);
1164
+ ${JSON.stringify(err)}`);
714
1165
  }
715
1166
  } else {
716
1167
  console.error("An unexpected empty rejection occurred");
@@ -729,7 +1180,7 @@ process.on("exit", () => {
729
1180
  const terminal = process.stderr.isTTY ? process.stderr : process.stdout.isTTY ? process.stdout : void 0;
730
1181
  terminal?.write("\x1B[?25h");
731
1182
  });
732
- main().then(async (exitCode) => {
733
- process.exitCode = exitCode;
1183
+ main().then((exitCode) => {
1184
+ process.exit(exitCode);
734
1185
  }).catch(handleUnexpected);
735
1186
  //# sourceMappingURL=index.cjs.map