gigadrive 1.1.2 → 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 +494 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +513 -75
- package/dist/index.mjs.map +1 -1
- package/package.json +12 -4
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());
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,20 +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
|
|
215
|
-
import
|
|
216
|
-
import
|
|
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
|
|
640
|
+
import fs5 from "fs";
|
|
222
641
|
import fsPromises from "fs/promises";
|
|
223
642
|
import ignore from "ignore";
|
|
224
|
-
import
|
|
643
|
+
import path5 from "path";
|
|
225
644
|
var ignoreFileNames = [
|
|
226
645
|
/*'.gitignore', */
|
|
227
646
|
".gigadriveignore",
|
|
@@ -241,19 +660,19 @@ async function readIgnoreFile(ignorePath) {
|
|
|
241
660
|
async function collectIgnorePatterns(dir, baseDir, ignoreRules) {
|
|
242
661
|
const items = await fsPromises.readdir(dir);
|
|
243
662
|
for (const item of items) {
|
|
244
|
-
const fullPath =
|
|
245
|
-
const relativePath =
|
|
663
|
+
const fullPath = path5.join(dir, item);
|
|
664
|
+
const relativePath = path5.relative(baseDir, fullPath);
|
|
246
665
|
const stat = await fsPromises.lstat(fullPath);
|
|
247
666
|
if (stat.isDirectory()) {
|
|
248
667
|
if (ignoreRules.ignores(`${relativePath}/`)) {
|
|
249
668
|
continue;
|
|
250
669
|
}
|
|
251
670
|
for (const ignoreFileName of ignoreFileNames) {
|
|
252
|
-
const ignoreFilePath =
|
|
671
|
+
const ignoreFilePath = path5.join(fullPath, ignoreFileName);
|
|
253
672
|
try {
|
|
254
673
|
await fsPromises.access(ignoreFilePath);
|
|
255
674
|
const patterns = (await readIgnoreFile(ignoreFilePath)).map((pattern) => {
|
|
256
|
-
return pattern.startsWith("!") ? `!${
|
|
675
|
+
return pattern.startsWith("!") ? `!${path5.join(relativePath, pattern.slice(1))}` : path5.join(relativePath, pattern);
|
|
257
676
|
});
|
|
258
677
|
ignoreRules.add(patterns);
|
|
259
678
|
} catch {
|
|
@@ -267,7 +686,7 @@ async function initializeIgnoreRules(baseDir) {
|
|
|
267
686
|
const ignoreRules = ignore();
|
|
268
687
|
ignoreRules.add([".git/", "**/.DS_Store", "**/Thumbs.db"]);
|
|
269
688
|
for (const ignoreFileName of ignoreFileNames) {
|
|
270
|
-
const ignoreFilePath =
|
|
689
|
+
const ignoreFilePath = path5.join(baseDir, ignoreFileName);
|
|
271
690
|
try {
|
|
272
691
|
await fsPromises.access(ignoreFilePath);
|
|
273
692
|
const patterns = (await readIgnoreFile(ignoreFilePath)).map((pattern) => {
|
|
@@ -285,8 +704,8 @@ async function getFilesToInclude(dir, ignoreRules, whitelist) {
|
|
|
285
704
|
async function walkDir(currentDir) {
|
|
286
705
|
const items = await fsPromises.readdir(currentDir);
|
|
287
706
|
for (const item of items) {
|
|
288
|
-
const fullPath =
|
|
289
|
-
const relativePath =
|
|
707
|
+
const fullPath = path5.join(currentDir, item);
|
|
708
|
+
const relativePath = path5.relative(dir, fullPath);
|
|
290
709
|
const stat = await fsPromises.lstat(fullPath);
|
|
291
710
|
if (stat.isDirectory()) {
|
|
292
711
|
if (ignoreRules.ignores(`${relativePath}/`)) {
|
|
@@ -308,14 +727,14 @@ async function getFilesToInclude(dir, ignoreRules, whitelist) {
|
|
|
308
727
|
async function createZipArchive(inputDir, outputFile, options = {}) {
|
|
309
728
|
const ignoreRules = options.useIgnoreFiles !== false ? await initializeIgnoreRules(inputDir) : ignore();
|
|
310
729
|
const filesToInclude = options.whitelist ? await Promise.all(options.whitelist.map((file) => getFilesForPattern(inputDir, file))) : await getFilesToInclude(inputDir, ignoreRules);
|
|
311
|
-
const output =
|
|
730
|
+
const output = fs5.createWriteStream(outputFile);
|
|
312
731
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
313
732
|
return new Promise((resolve, reject) => {
|
|
314
733
|
output.on("close", () => resolve());
|
|
315
734
|
archive.on("error", reject);
|
|
316
735
|
archive.pipe(output);
|
|
317
736
|
for (const file of filesToInclude.flat()) {
|
|
318
|
-
archive.file(file, { name:
|
|
737
|
+
archive.file(file, { name: path5.relative(inputDir, file) });
|
|
319
738
|
}
|
|
320
739
|
void archive.finalize();
|
|
321
740
|
});
|
|
@@ -447,11 +866,11 @@ var addLog = (message, type, logs) => {
|
|
|
447
866
|
var uploadArchive = async (deploymentId, logs, config2) => {
|
|
448
867
|
debug("Starting uploadArchive");
|
|
449
868
|
addLog("Creating archive...", "INFO", logs);
|
|
450
|
-
const archivePath =
|
|
869
|
+
const archivePath = path6.join(process.env.TEMP || os2.tmpdir(), `project-${Date.now()}.zip`);
|
|
451
870
|
debug(`Archive path: ${archivePath}`);
|
|
452
|
-
if (
|
|
871
|
+
if (fs6.existsSync(archivePath)) {
|
|
453
872
|
debug(`Archive already exists, removing: ${archivePath}`);
|
|
454
|
-
|
|
873
|
+
fs6.unlinkSync(archivePath);
|
|
455
874
|
}
|
|
456
875
|
debug("Creating zip archive");
|
|
457
876
|
await createZipArchive(config2.userArchive?.rootOverwrite || process.cwd(), archivePath, {
|
|
@@ -459,7 +878,7 @@ var uploadArchive = async (deploymentId, logs, config2) => {
|
|
|
459
878
|
useIgnoreFiles: false,
|
|
460
879
|
useManagedIgnore: false
|
|
461
880
|
});
|
|
462
|
-
const fileStats =
|
|
881
|
+
const fileStats = fs6.statSync(archivePath);
|
|
463
882
|
const fileSize = fileStats.size;
|
|
464
883
|
debug(`Archive created, size: ${fileSize} bytes (${formatFileSize(fileSize)})`);
|
|
465
884
|
addLog(`Archive created (${formatFileSize(fileSize)})`, "INFO", logs);
|
|
@@ -471,7 +890,7 @@ var uploadArchive = async (deploymentId, logs, config2) => {
|
|
|
471
890
|
const fileChunkSize = 10 * 1024 * 1024;
|
|
472
891
|
const numChunks = Math.ceil(fileSize / fileChunkSize);
|
|
473
892
|
debug(`File size: ${fileSize}, chunk size: ${fileChunkSize}, number of chunks: ${numChunks}`);
|
|
474
|
-
const
|
|
893
|
+
const promises2 = [];
|
|
475
894
|
let start, end;
|
|
476
895
|
for (let index = 1; index < numChunks + 1; index++) {
|
|
477
896
|
start = (index - 1) * fileChunkSize;
|
|
@@ -483,7 +902,7 @@ var uploadArchive = async (deploymentId, logs, config2) => {
|
|
|
483
902
|
void (async () => {
|
|
484
903
|
try {
|
|
485
904
|
debug(`Reading chunk ${index} data`);
|
|
486
|
-
const readStream =
|
|
905
|
+
const readStream = fs6.createReadStream(archivePath, {
|
|
487
906
|
start,
|
|
488
907
|
end: index < numChunks ? end - 1 : void 0
|
|
489
908
|
});
|
|
@@ -511,12 +930,12 @@ var uploadArchive = async (deploymentId, logs, config2) => {
|
|
|
511
930
|
}
|
|
512
931
|
})();
|
|
513
932
|
});
|
|
514
|
-
|
|
933
|
+
promises2.push(promise);
|
|
515
934
|
}
|
|
516
935
|
const finishedParts = [];
|
|
517
936
|
try {
|
|
518
|
-
debug(`Waiting for all ${
|
|
519
|
-
const responses = await Promise.all(
|
|
937
|
+
debug(`Waiting for all ${promises2.length} upload promises to complete`);
|
|
938
|
+
const responses = await Promise.all(promises2);
|
|
520
939
|
debug("All upload promises completed");
|
|
521
940
|
for (let index = 0; index < responses.length; index++) {
|
|
522
941
|
const res = responses[index];
|
|
@@ -663,6 +1082,23 @@ var platform = (program) => {
|
|
|
663
1082
|
deploy(platform2);
|
|
664
1083
|
};
|
|
665
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
|
+
|
|
666
1102
|
// src/index.ts
|
|
667
1103
|
var { isTTY } = process.stdout;
|
|
668
1104
|
var main = async () => {
|
|
@@ -686,6 +1122,8 @@ var main = async () => {
|
|
|
686
1122
|
platform(program);
|
|
687
1123
|
debug2(program);
|
|
688
1124
|
build(program);
|
|
1125
|
+
login(program);
|
|
1126
|
+
whoami(program);
|
|
689
1127
|
try {
|
|
690
1128
|
await program.parseAsync(process.argv);
|
|
691
1129
|
} catch (err) {
|
|
@@ -720,6 +1158,6 @@ process.on("exit", () => {
|
|
|
720
1158
|
terminal?.write("\x1B[?25h");
|
|
721
1159
|
});
|
|
722
1160
|
main().then((exitCode) => {
|
|
723
|
-
process.exitCode
|
|
1161
|
+
process.exit(exitCode);
|
|
724
1162
|
}).catch(handleUnexpected);
|
|
725
1163
|
//# sourceMappingURL=index.mjs.map
|