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