gigadrive 2.2.0 → 2.4.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 +975 -328
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +941 -294
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command15 } from "@effect/cli";
|
|
5
5
|
import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|
6
6
|
import { NetworkConfigLive as NetworkConfigLive2 } from "@gigadrive/network-config";
|
|
7
|
-
import { Effect as
|
|
7
|
+
import { Effect as Effect27, Layer, LogLevel, Logger } from "effect";
|
|
8
8
|
|
|
9
9
|
// package.json
|
|
10
10
|
var package_default = {
|
|
11
11
|
name: "gigadrive",
|
|
12
|
-
version: "2.
|
|
12
|
+
version: "2.4.0",
|
|
13
13
|
repository: {
|
|
14
14
|
type: "git",
|
|
15
15
|
url: "https://github.com/Gigadrive/sdk.git",
|
|
@@ -35,7 +35,7 @@ var package_default = {
|
|
|
35
35
|
dependencies: {
|
|
36
36
|
"@effect/cli": "^0.75.0",
|
|
37
37
|
"@effect/platform": "^0.96.0",
|
|
38
|
-
"@effect/platform-node": "^0.
|
|
38
|
+
"@effect/platform-node": "^0.107.0",
|
|
39
39
|
"@gigadrive/build-utils": "workspace:*",
|
|
40
40
|
"@gigadrive/commons": "workspace:*",
|
|
41
41
|
"@gigadrive/network-config": "workspace:*",
|
|
@@ -46,8 +46,8 @@ var package_default = {
|
|
|
46
46
|
open: "^10.2.0"
|
|
47
47
|
},
|
|
48
48
|
devDependencies: {
|
|
49
|
-
"@effect/language-service": "^0.
|
|
50
|
-
"@types/archiver": "^6.0.
|
|
49
|
+
"@effect/language-service": "^0.86.0",
|
|
50
|
+
"@types/archiver": "^6.0.4",
|
|
51
51
|
esbuild: "^0.28.0",
|
|
52
52
|
typescript: "catalog:"
|
|
53
53
|
},
|
|
@@ -90,6 +90,25 @@ var TokenExchangeError = class extends Schema.TaggedError()("TokenExchangeError"
|
|
|
90
90
|
statusCode: Schema.optional(Schema.Number)
|
|
91
91
|
}) {
|
|
92
92
|
};
|
|
93
|
+
var DeviceAuthorizationError = class extends Schema.TaggedError()(
|
|
94
|
+
"DeviceAuthorizationError",
|
|
95
|
+
{
|
|
96
|
+
message: Schema.String,
|
|
97
|
+
cause: Schema.optional(Schema.String)
|
|
98
|
+
}
|
|
99
|
+
) {
|
|
100
|
+
};
|
|
101
|
+
var AuthorizationDeniedError = class extends Schema.TaggedError()(
|
|
102
|
+
"AuthorizationDeniedError",
|
|
103
|
+
{
|
|
104
|
+
message: Schema.String
|
|
105
|
+
}
|
|
106
|
+
) {
|
|
107
|
+
};
|
|
108
|
+
var DeviceCodeExpiredError = class extends Schema.TaggedError()("DeviceCodeExpiredError", {
|
|
109
|
+
message: Schema.String
|
|
110
|
+
}) {
|
|
111
|
+
};
|
|
93
112
|
var TokenRefreshError = class extends Schema.TaggedError()("TokenRefreshError", {
|
|
94
113
|
message: Schema.String,
|
|
95
114
|
statusCode: Schema.optional(Schema.Number)
|
|
@@ -135,6 +154,31 @@ var ApplicationNotFoundError = class extends Schema.TaggedError()(
|
|
|
135
154
|
}
|
|
136
155
|
) {
|
|
137
156
|
};
|
|
157
|
+
var EnvFileReadError = class extends Schema.TaggedError()("EnvFileReadError", {
|
|
158
|
+
message: Schema.String,
|
|
159
|
+
cause: Schema.optional(Schema.String)
|
|
160
|
+
}) {
|
|
161
|
+
};
|
|
162
|
+
var EnvFileWriteError = class extends Schema.TaggedError()("EnvFileWriteError", {
|
|
163
|
+
message: Schema.String,
|
|
164
|
+
cause: Schema.optional(Schema.String)
|
|
165
|
+
}) {
|
|
166
|
+
};
|
|
167
|
+
var DevCredentialsStoreReadError = class extends Schema.TaggedError()(
|
|
168
|
+
"DevCredentialsStoreReadError",
|
|
169
|
+
{
|
|
170
|
+
message: Schema.String
|
|
171
|
+
}
|
|
172
|
+
) {
|
|
173
|
+
};
|
|
174
|
+
var DevCredentialsStoreWriteError = class extends Schema.TaggedError()(
|
|
175
|
+
"DevCredentialsStoreWriteError",
|
|
176
|
+
{
|
|
177
|
+
message: Schema.String,
|
|
178
|
+
cause: Schema.optional(Schema.String)
|
|
179
|
+
}
|
|
180
|
+
) {
|
|
181
|
+
};
|
|
138
182
|
var ConfigNotFoundError = class extends Schema.TaggedError()("ConfigNotFoundError", {
|
|
139
183
|
message: Schema.String,
|
|
140
184
|
directory: Schema.String
|
|
@@ -261,16 +305,10 @@ import { ApiError, GigadriveClient } from "@gigadrive/sdk";
|
|
|
261
305
|
import { Config as Config2, Effect as Effect4 } from "effect";
|
|
262
306
|
|
|
263
307
|
// src/services/auth.ts
|
|
264
|
-
import { Console, Effect as Effect3, Option as Option2 } from "effect";
|
|
265
|
-
import
|
|
266
|
-
import * as http from "http";
|
|
308
|
+
import { Console, Duration, Effect as Effect3, Option as Option2, Schema as Schema5 } from "effect";
|
|
309
|
+
import process3 from "process";
|
|
267
310
|
import open from "open";
|
|
268
311
|
|
|
269
|
-
// src/services/auth-storage.ts
|
|
270
|
-
import { FileSystem, Path } from "@effect/platform";
|
|
271
|
-
import { Effect, Option, Schema as Schema3 } from "effect";
|
|
272
|
-
import * as os from "os";
|
|
273
|
-
|
|
274
312
|
// src/domain.ts
|
|
275
313
|
import { Schema as Schema2 } from "effect";
|
|
276
314
|
var DeploymentId = Schema2.String.pipe(Schema2.brand("@Gigadrive/DeploymentId"));
|
|
@@ -288,9 +326,18 @@ var OAuthClientConfig = Schema2.Struct({
|
|
|
288
326
|
issuer: Schema2.String,
|
|
289
327
|
authorizeUrl: Schema2.String,
|
|
290
328
|
tokenUrl: Schema2.String,
|
|
329
|
+
deviceAuthorizeUrl: Schema2.String,
|
|
291
330
|
scope: Schema2.String,
|
|
292
331
|
userinfoUrl: Schema2.String
|
|
293
332
|
});
|
|
333
|
+
var DeviceAuthorizationResponse = Schema2.Struct({
|
|
334
|
+
device_code: Schema2.String,
|
|
335
|
+
user_code: Schema2.String,
|
|
336
|
+
verification_uri: Schema2.String,
|
|
337
|
+
verification_uri_complete: Schema2.optional(Schema2.String),
|
|
338
|
+
expires_in: Schema2.Number,
|
|
339
|
+
interval: Schema2.optional(Schema2.Number)
|
|
340
|
+
});
|
|
294
341
|
var StoredAuthData = Schema2.Struct({
|
|
295
342
|
refreshToken: Schema2.String,
|
|
296
343
|
accessToken: Schema2.optional(Schema2.String),
|
|
@@ -300,8 +347,52 @@ var ProjectLink = Schema2.Struct({
|
|
|
300
347
|
applicationId: Schema2.String,
|
|
301
348
|
organizationId: Schema2.optional(Schema2.String)
|
|
302
349
|
});
|
|
350
|
+
var DevKeyEntry = Schema2.Struct({
|
|
351
|
+
apiKeyId: Schema2.String
|
|
352
|
+
});
|
|
353
|
+
var DevKeyStore = Schema2.Record({ key: Schema2.String, value: DevKeyEntry });
|
|
354
|
+
|
|
355
|
+
// src/lib/clipboard.ts
|
|
356
|
+
import { spawn } from "child_process";
|
|
357
|
+
import process2 from "process";
|
|
358
|
+
var clipboardCandidates = () => {
|
|
359
|
+
switch (process2.platform) {
|
|
360
|
+
case "darwin":
|
|
361
|
+
return [{ cmd: "pbcopy", args: [] }];
|
|
362
|
+
case "win32":
|
|
363
|
+
return [{ cmd: "clip", args: [] }];
|
|
364
|
+
default:
|
|
365
|
+
return [
|
|
366
|
+
{ cmd: "wl-copy", args: [] },
|
|
367
|
+
{ cmd: "xclip", args: ["-selection", "clipboard"] },
|
|
368
|
+
{ cmd: "xsel", args: ["--clipboard", "--input"] }
|
|
369
|
+
];
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
function writeClipboard(text) {
|
|
373
|
+
const candidates = clipboardCandidates();
|
|
374
|
+
const tryNext = (index) => {
|
|
375
|
+
if (index >= candidates.length) return;
|
|
376
|
+
const { cmd, args } = candidates[index];
|
|
377
|
+
let proc;
|
|
378
|
+
try {
|
|
379
|
+
proc = spawn(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
380
|
+
} catch {
|
|
381
|
+
tryNext(index + 1);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
proc.on("error", () => tryNext(index + 1));
|
|
385
|
+
proc.stdin?.on("error", () => {
|
|
386
|
+
});
|
|
387
|
+
proc.stdin?.end(text);
|
|
388
|
+
};
|
|
389
|
+
tryNext(0);
|
|
390
|
+
}
|
|
303
391
|
|
|
304
392
|
// src/services/auth-storage.ts
|
|
393
|
+
import { FileSystem, Path } from "@effect/platform";
|
|
394
|
+
import { Effect, Option, Schema as Schema3 } from "effect";
|
|
395
|
+
import * as os from "os";
|
|
305
396
|
var AuthStorageService = class extends Effect.Service()("AuthStorageService", {
|
|
306
397
|
accessors: true,
|
|
307
398
|
effect: Effect.gen(function* () {
|
|
@@ -380,15 +471,46 @@ var AuthStorageService = class extends Effect.Service()("AuthStorageService", {
|
|
|
380
471
|
import { Config, Effect as Effect2, Schema as Schema4 } from "effect";
|
|
381
472
|
var OAuthEnvConfig = Config.all({
|
|
382
473
|
issuerUrl: Config.string("GIGADRIVE_NETWORK_OAUTH_ISSUER_URL").pipe(Config.withDefault("https://idp.gigadrive.de")),
|
|
383
|
-
|
|
474
|
+
// Default is the first-party CLI's public OAuth client (seeded in the Network
|
|
475
|
+
// repo). Override with GIGADRIVE_NETWORK_OAUTH_CLIENT_ID per environment.
|
|
476
|
+
clientId: Config.string("GIGADRIVE_NETWORK_OAUTH_CLIENT_ID").pipe(
|
|
477
|
+
Config.withDefault("0195c11c-0000-7000-8000-000000000001")
|
|
478
|
+
)
|
|
384
479
|
});
|
|
385
480
|
var OpenIdDiscoveryDocument = Schema4.Struct({
|
|
386
481
|
issuer: Schema4.optional(Schema4.String),
|
|
387
482
|
authorization_endpoint: Schema4.optional(Schema4.String),
|
|
388
483
|
token_endpoint: Schema4.optional(Schema4.String),
|
|
389
|
-
userinfo_endpoint: Schema4.optional(Schema4.String)
|
|
484
|
+
userinfo_endpoint: Schema4.optional(Schema4.String),
|
|
485
|
+
device_authorization_endpoint: Schema4.optional(Schema4.String)
|
|
390
486
|
});
|
|
391
|
-
var
|
|
487
|
+
var CLI_SCOPES = [
|
|
488
|
+
// Identity / OIDC
|
|
489
|
+
"offline_access",
|
|
490
|
+
"openid",
|
|
491
|
+
"profile",
|
|
492
|
+
"email",
|
|
493
|
+
// Network API capabilities
|
|
494
|
+
"network:applications:read",
|
|
495
|
+
"network:env_vars:read",
|
|
496
|
+
"network:env_vars:write",
|
|
497
|
+
"network:env_vars:delete",
|
|
498
|
+
"network:deployments:read",
|
|
499
|
+
"network:deployments:write",
|
|
500
|
+
"network:deployments:trigger",
|
|
501
|
+
"network:ai_gateway:chat",
|
|
502
|
+
"network:ai_gateway:models",
|
|
503
|
+
"network:ai_gateway:usage:read",
|
|
504
|
+
"network:ai_gateway:budgets:read",
|
|
505
|
+
"network:ai_gateway:budgets:write",
|
|
506
|
+
"network:ai_gateway:policies:read",
|
|
507
|
+
"network:ai_gateway:policies:write",
|
|
508
|
+
// Provisioning local-dev API credentials
|
|
509
|
+
"platform:api_keys:read",
|
|
510
|
+
"platform:api_keys:write",
|
|
511
|
+
"platform:api_keys:delete"
|
|
512
|
+
];
|
|
513
|
+
var DEFAULT_SCOPE = CLI_SCOPES.join(" ");
|
|
392
514
|
var OAuthConfigService = class extends Effect2.Service()("OAuthConfigService", {
|
|
393
515
|
accessors: true,
|
|
394
516
|
effect: Effect2.gen(function* () {
|
|
@@ -426,7 +548,7 @@ var OAuthConfigService = class extends Effect2.Service()("OAuthConfigService", {
|
|
|
426
548
|
})
|
|
427
549
|
)
|
|
428
550
|
);
|
|
429
|
-
if (!decoded.authorization_endpoint || !decoded.token_endpoint || !decoded.userinfo_endpoint) {
|
|
551
|
+
if (!decoded.authorization_endpoint || !decoded.token_endpoint || !decoded.userinfo_endpoint || !decoded.device_authorization_endpoint) {
|
|
430
552
|
return yield* Effect2.fail(
|
|
431
553
|
new OAuthDiscoveryError({
|
|
432
554
|
message: "OIDC discovery returned incomplete endpoints"
|
|
@@ -438,6 +560,7 @@ var OAuthConfigService = class extends Effect2.Service()("OAuthConfigService", {
|
|
|
438
560
|
issuer: decoded.issuer ?? issuerBase,
|
|
439
561
|
authorizeUrl: decoded.authorization_endpoint,
|
|
440
562
|
tokenUrl: decoded.token_endpoint,
|
|
563
|
+
deviceAuthorizeUrl: decoded.device_authorization_endpoint,
|
|
441
564
|
scope: DEFAULT_SCOPE,
|
|
442
565
|
userinfoUrl: decoded.userinfo_endpoint
|
|
443
566
|
};
|
|
@@ -451,14 +574,9 @@ var OAuthConfigService = class extends Effect2.Service()("OAuthConfigService", {
|
|
|
451
574
|
};
|
|
452
575
|
|
|
453
576
|
// src/services/auth.ts
|
|
454
|
-
var
|
|
455
|
-
var
|
|
456
|
-
var
|
|
457
|
-
const codeVerifier = base64URLEncode(crypto.randomBytes(64));
|
|
458
|
-
const challengeBytes = crypto.createHash("sha256").update(codeVerifier, "utf8").digest();
|
|
459
|
-
const codeChallenge = base64URLEncode(challengeBytes);
|
|
460
|
-
return { codeVerifier, codeChallenge };
|
|
461
|
-
};
|
|
577
|
+
var DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
578
|
+
var DEFAULT_POLL_INTERVAL_SECONDS = 5;
|
|
579
|
+
var REQUEST_TIMEOUT_SECONDS = 30;
|
|
462
580
|
var AuthService = class extends Effect3.Service()("AuthService", {
|
|
463
581
|
accessors: true,
|
|
464
582
|
dependencies: [OAuthConfigService.Default, AuthStorageService.Default],
|
|
@@ -473,115 +591,178 @@ var AuthService = class extends Effect3.Service()("AuthService", {
|
|
|
473
591
|
yield* storage.remove;
|
|
474
592
|
yield* Effect3.log("Logged out");
|
|
475
593
|
});
|
|
476
|
-
const
|
|
594
|
+
const requestDeviceAuthorization = (config) => Effect3.gen(function* () {
|
|
477
595
|
const response = yield* Effect3.tryPromise({
|
|
478
|
-
try: () => fetch(config.
|
|
596
|
+
try: (signal) => fetch(config.deviceAuthorizeUrl, {
|
|
479
597
|
method: "POST",
|
|
480
598
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
481
|
-
body: new URLSearchParams({
|
|
482
|
-
|
|
483
|
-
client_id: config.clientId,
|
|
484
|
-
code: authCode,
|
|
485
|
-
redirect_uri: redirectUri,
|
|
486
|
-
code_verifier: codeVerifier
|
|
487
|
-
}).toString()
|
|
599
|
+
body: new URLSearchParams({ client_id: config.clientId, scope: config.scope }).toString(),
|
|
600
|
+
signal
|
|
488
601
|
}),
|
|
489
|
-
catch: (error) => new
|
|
490
|
-
message: `
|
|
602
|
+
catch: (error) => new DeviceAuthorizationError({
|
|
603
|
+
message: `Device authorization request failed: ${error instanceof Error ? error.message : String(error)}`
|
|
491
604
|
})
|
|
492
605
|
});
|
|
493
606
|
if (!response.ok) {
|
|
494
607
|
const body = yield* Effect3.tryPromise({
|
|
495
608
|
try: () => response.text(),
|
|
496
|
-
catch: () => new
|
|
609
|
+
catch: () => new DeviceAuthorizationError({ message: "Failed to read device authorization error response" })
|
|
497
610
|
});
|
|
498
611
|
return yield* Effect3.fail(
|
|
499
|
-
new
|
|
500
|
-
message: `
|
|
501
|
-
statusCode: response.status
|
|
612
|
+
new DeviceAuthorizationError({
|
|
613
|
+
message: `Device authorization failed: ${response.status} ${response.statusText} - ${body}`
|
|
502
614
|
})
|
|
503
615
|
);
|
|
504
616
|
}
|
|
505
|
-
const
|
|
617
|
+
const json = yield* Effect3.tryPromise({
|
|
506
618
|
try: () => response.json(),
|
|
507
|
-
catch: () => new
|
|
619
|
+
catch: () => new DeviceAuthorizationError({ message: "Failed to parse device authorization response" })
|
|
508
620
|
});
|
|
509
|
-
return
|
|
621
|
+
return yield* Schema5.decodeUnknown(DeviceAuthorizationResponse)(json).pipe(
|
|
622
|
+
Effect3.mapError(
|
|
623
|
+
() => new DeviceAuthorizationError({ message: "Invalid device authorization response schema" })
|
|
624
|
+
)
|
|
625
|
+
);
|
|
626
|
+
}).pipe(
|
|
627
|
+
Effect3.timeoutFail({
|
|
628
|
+
duration: Duration.seconds(REQUEST_TIMEOUT_SECONDS),
|
|
629
|
+
onTimeout: () => new DeviceAuthorizationError({ message: "Device authorization request timed out" })
|
|
630
|
+
})
|
|
631
|
+
);
|
|
632
|
+
const pollTokenOnce = (config, deviceCode) => Effect3.gen(function* () {
|
|
633
|
+
const response = yield* Effect3.tryPromise({
|
|
634
|
+
try: (signal) => fetch(config.tokenUrl, {
|
|
635
|
+
method: "POST",
|
|
636
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
637
|
+
body: new URLSearchParams({
|
|
638
|
+
grant_type: DEVICE_CODE_GRANT_TYPE,
|
|
639
|
+
client_id: config.clientId,
|
|
640
|
+
device_code: deviceCode
|
|
641
|
+
}).toString(),
|
|
642
|
+
signal
|
|
643
|
+
}),
|
|
644
|
+
catch: (error) => new TokenExchangeError({
|
|
645
|
+
message: `Token poll request failed: ${error instanceof Error ? error.message : String(error)}`
|
|
646
|
+
})
|
|
647
|
+
});
|
|
648
|
+
if (response.ok) {
|
|
649
|
+
const tokens = yield* Effect3.tryPromise({
|
|
650
|
+
try: () => response.json(),
|
|
651
|
+
catch: () => new TokenExchangeError({ message: "Failed to parse token response" })
|
|
652
|
+
});
|
|
653
|
+
return { kind: "tokens", tokens };
|
|
654
|
+
}
|
|
655
|
+
const errorBody = yield* Effect3.tryPromise({
|
|
656
|
+
try: () => response.json(),
|
|
657
|
+
catch: () => new TokenExchangeError({ message: "Failed to parse token poll error response" })
|
|
658
|
+
});
|
|
659
|
+
switch (errorBody.error) {
|
|
660
|
+
case "authorization_pending":
|
|
661
|
+
return { kind: "pending" };
|
|
662
|
+
case "slow_down":
|
|
663
|
+
return { kind: "slow_down" };
|
|
664
|
+
case "access_denied":
|
|
665
|
+
return { kind: "denied" };
|
|
666
|
+
case "expired_token":
|
|
667
|
+
return { kind: "expired" };
|
|
668
|
+
default:
|
|
669
|
+
return yield* Effect3.fail(
|
|
670
|
+
new TokenExchangeError({
|
|
671
|
+
message: `Token poll failed: ${response.status} ${response.statusText} - ${errorBody.error ?? "unknown_error"}`,
|
|
672
|
+
statusCode: response.status
|
|
673
|
+
})
|
|
674
|
+
);
|
|
675
|
+
}
|
|
510
676
|
});
|
|
511
|
-
const
|
|
512
|
-
const
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
if (
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
677
|
+
const pollForToken = (config, device) => Effect3.gen(function* () {
|
|
678
|
+
const deadline = Date.now() + device.expires_in * 1e3;
|
|
679
|
+
let interval = device.interval ?? DEFAULT_POLL_INTERVAL_SECONDS;
|
|
680
|
+
while (true) {
|
|
681
|
+
yield* Effect3.sleep(Duration.seconds(interval));
|
|
682
|
+
if (Date.now() >= deadline) {
|
|
683
|
+
return yield* Effect3.fail(
|
|
684
|
+
new DeviceCodeExpiredError({
|
|
685
|
+
message: "The login code expired before it was approved. Please run `gigadrive login` again."
|
|
686
|
+
})
|
|
687
|
+
);
|
|
521
688
|
}
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
res.writeHead(200, { "Content-Type": "text/html", Connection: "close" });
|
|
547
|
-
if (oauthError) {
|
|
548
|
-
const safeOauthError = escapeHtml(oauthError);
|
|
549
|
-
res.end(`<h1>Login Failed!</h1><p>Error: ${safeOauthError}</p><p>You can close this tab.</p>`);
|
|
550
|
-
server.close();
|
|
551
|
-
resume(Effect3.fail(new LoginFlowError({ message: `OAuth error: ${oauthError}` })));
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
if (receivedState !== state) {
|
|
555
|
-
res.end(
|
|
556
|
-
"<h1>Login Failed!</h1><p>State mismatch. Possible CSRF attack.</p><p>You can close this tab.</p>"
|
|
689
|
+
const result = yield* pollTokenOnce(config, device.device_code).pipe(
|
|
690
|
+
// A hung poll must not stall the loop: on timeout treat it as pending so
|
|
691
|
+
// the next iteration re-checks the expiry deadline.
|
|
692
|
+
Effect3.timeoutTo({
|
|
693
|
+
duration: Duration.seconds(REQUEST_TIMEOUT_SECONDS),
|
|
694
|
+
onSuccess: (value) => value,
|
|
695
|
+
onTimeout: () => ({ kind: "pending" })
|
|
696
|
+
})
|
|
697
|
+
);
|
|
698
|
+
switch (result.kind) {
|
|
699
|
+
case "tokens":
|
|
700
|
+
return result.tokens;
|
|
701
|
+
case "pending":
|
|
702
|
+
continue;
|
|
703
|
+
case "slow_down":
|
|
704
|
+
interval += 5;
|
|
705
|
+
continue;
|
|
706
|
+
case "denied":
|
|
707
|
+
return yield* Effect3.fail(new AuthorizationDeniedError({ message: "The login request was denied." }));
|
|
708
|
+
case "expired":
|
|
709
|
+
return yield* Effect3.fail(
|
|
710
|
+
new DeviceCodeExpiredError({
|
|
711
|
+
message: "The login code expired before it was approved. Please run `gigadrive login` again."
|
|
712
|
+
})
|
|
557
713
|
);
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
resume(Effect3.fail(new LoginFlowError({ message:
|
|
575
|
-
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
const watchCopyKey = (url) => Effect3.async((resume) => {
|
|
718
|
+
const stdin = process3.stdin;
|
|
719
|
+
if (!stdin.isTTY) {
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
stdin.setRawMode(true);
|
|
723
|
+
stdin.resume();
|
|
724
|
+
stdin.setEncoding("utf8");
|
|
725
|
+
const onData = (key) => {
|
|
726
|
+
if (key === "c" || key === "C") {
|
|
727
|
+
writeClipboard(url);
|
|
728
|
+
Console.log("Copied the login URL to your clipboard.").pipe(Effect3.runSync);
|
|
729
|
+
} else if (key === "") {
|
|
730
|
+
resume(Effect3.fail(new LoginFlowError({ message: "Login canceled." })));
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
stdin.on("data", onData);
|
|
734
|
+
return Effect3.sync(() => {
|
|
735
|
+
stdin.off("data", onData);
|
|
736
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
737
|
+
stdin.pause();
|
|
576
738
|
});
|
|
577
739
|
});
|
|
578
740
|
const login = Effect3.gen(function* () {
|
|
579
741
|
yield* logout;
|
|
580
|
-
const { codeVerifier, codeChallenge } = generatePKCE();
|
|
581
|
-
const state = crypto.randomBytes(16).toString("hex");
|
|
582
742
|
const config = yield* oauthConfig.getConfig;
|
|
583
|
-
const
|
|
584
|
-
const
|
|
743
|
+
const device = yield* requestDeviceAuthorization(config);
|
|
744
|
+
const verificationUri = device.verification_uri;
|
|
745
|
+
const verificationUriComplete = device.verification_uri_complete ?? verificationUri;
|
|
746
|
+
const interactive = process3.stdin.isTTY === true;
|
|
747
|
+
yield* Console.log(
|
|
748
|
+
`
|
|
749
|
+
To sign in, visit:
|
|
750
|
+
|
|
751
|
+
${verificationUri}
|
|
752
|
+
|
|
753
|
+
and enter the code:
|
|
754
|
+
|
|
755
|
+
${device.user_code}
|
|
756
|
+
`
|
|
757
|
+
);
|
|
758
|
+
if (interactive) {
|
|
759
|
+
yield* Effect3.tryPromise(() => open(verificationUriComplete)).pipe(Effect3.catchAll(() => Effect3.void));
|
|
760
|
+
yield* Console.log("We tried to open your browser automatically. If it did not open, use the URL above.");
|
|
761
|
+
yield* Console.log('Press "c" to copy the URL to your clipboard, or Ctrl+C to cancel.');
|
|
762
|
+
}
|
|
763
|
+
yield* Console.log("\nWaiting for you to approve the login\u2026");
|
|
764
|
+
const poll = pollForToken(config, device);
|
|
765
|
+
const tokens = yield* interactive ? poll.pipe(Effect3.raceFirst(watchCopyKey(verificationUriComplete))) : poll;
|
|
585
766
|
if (!tokens.refresh_token) {
|
|
586
767
|
return yield* Effect3.fail(
|
|
587
768
|
new TokenExchangeError({ message: "No refresh token received from identity provider" })
|
|
@@ -776,7 +957,7 @@ var ApiClientService = class extends Effect4.Service()("ApiClientService", {
|
|
|
776
957
|
|
|
777
958
|
// src/services/project-link.ts
|
|
778
959
|
import { FileSystem as FileSystem2, Path as Path2 } from "@effect/platform";
|
|
779
|
-
import { Effect as Effect5, Option as Option3, Schema as
|
|
960
|
+
import { Effect as Effect5, Option as Option3, Schema as Schema6 } from "effect";
|
|
780
961
|
var LINK_DIR = ".gigadrive";
|
|
781
962
|
var LINK_FILE = "project.json";
|
|
782
963
|
var ProjectLinkService = class extends Effect5.Service()("ProjectLinkService", {
|
|
@@ -795,7 +976,7 @@ var ProjectLinkService = class extends Effect5.Service()("ProjectLinkService", {
|
|
|
795
976
|
try: () => JSON.parse(data),
|
|
796
977
|
catch: () => new ProjectLinkReadError({ message: `${LINK_DIR}/${LINK_FILE} contains invalid JSON` })
|
|
797
978
|
});
|
|
798
|
-
const link = yield*
|
|
979
|
+
const link = yield* Schema6.decodeUnknown(ProjectLink)(json).pipe(
|
|
799
980
|
Effect5.mapError(() => new ProjectLinkReadError({ message: `${LINK_DIR}/${LINK_FILE} has an invalid schema` }))
|
|
800
981
|
);
|
|
801
982
|
return Option3.some(link);
|
|
@@ -1539,8 +1720,231 @@ var deploymentsCommand = deploymentsBase.pipe(
|
|
|
1539
1720
|
);
|
|
1540
1721
|
|
|
1541
1722
|
// src/commands/env/index.ts
|
|
1542
|
-
import { Args as Args3, Command as Command7, Options as Options4 } from "@effect/cli";
|
|
1543
|
-
import { Console as Console7, Effect as
|
|
1723
|
+
import { Args as Args3, Command as Command7, Options as Options4, Prompt } from "@effect/cli";
|
|
1724
|
+
import { Console as Console7, Effect as Effect17, Option as Option10 } from "effect";
|
|
1725
|
+
|
|
1726
|
+
// src/services/env-file.ts
|
|
1727
|
+
import { FileSystem as FileSystem6, Path as Path5 } from "@effect/platform";
|
|
1728
|
+
import { Effect as Effect14, Option as Option7 } from "effect";
|
|
1729
|
+
|
|
1730
|
+
// src/lib/dotenv.ts
|
|
1731
|
+
var DEFAULT_ENV_HEADER = "# Generated by `gigadrive env pull` \u2014 do not edit by hand.";
|
|
1732
|
+
var UNQUOTED_SAFE = /^[A-Za-z0-9_.\-/@:+]*$/;
|
|
1733
|
+
var formatEnvValue = (value) => {
|
|
1734
|
+
if (UNQUOTED_SAFE.test(value)) {
|
|
1735
|
+
return value;
|
|
1736
|
+
}
|
|
1737
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
1738
|
+
return `"${escaped}"`;
|
|
1739
|
+
};
|
|
1740
|
+
var serializeEnv = (entries, opts) => {
|
|
1741
|
+
const header = opts?.header ?? DEFAULT_ENV_HEADER;
|
|
1742
|
+
if (entries.length === 0) {
|
|
1743
|
+
return `${header}
|
|
1744
|
+
`;
|
|
1745
|
+
}
|
|
1746
|
+
const body = entries.map((e) => `${e.key}=${formatEnvValue(e.value)}`).join("\n");
|
|
1747
|
+
return `${header}
|
|
1748
|
+
${body}
|
|
1749
|
+
`;
|
|
1750
|
+
};
|
|
1751
|
+
var parseEnv = (content) => {
|
|
1752
|
+
const result = {};
|
|
1753
|
+
for (const rawLine of content.split("\n")) {
|
|
1754
|
+
const line = rawLine.trim();
|
|
1755
|
+
if (line.length === 0 || line.startsWith("#")) {
|
|
1756
|
+
continue;
|
|
1757
|
+
}
|
|
1758
|
+
const eq = line.indexOf("=");
|
|
1759
|
+
if (eq <= 0) {
|
|
1760
|
+
continue;
|
|
1761
|
+
}
|
|
1762
|
+
const key = line.slice(0, eq).trim();
|
|
1763
|
+
let value = line.slice(eq + 1).trim();
|
|
1764
|
+
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
1765
|
+
value = value.slice(1, -1).replace(/\\(\\|"|n|r|t)/g, (_, ch) => {
|
|
1766
|
+
switch (ch) {
|
|
1767
|
+
case "n":
|
|
1768
|
+
return "\n";
|
|
1769
|
+
case "r":
|
|
1770
|
+
return "\r";
|
|
1771
|
+
case "t":
|
|
1772
|
+
return " ";
|
|
1773
|
+
case '"':
|
|
1774
|
+
return '"';
|
|
1775
|
+
default:
|
|
1776
|
+
return "\\";
|
|
1777
|
+
}
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
result[key] = value;
|
|
1781
|
+
}
|
|
1782
|
+
return result;
|
|
1783
|
+
};
|
|
1784
|
+
|
|
1785
|
+
// src/services/env-file.ts
|
|
1786
|
+
var EnvFileService = class extends Effect14.Service()("EnvFileService", {
|
|
1787
|
+
accessors: true,
|
|
1788
|
+
effect: Effect14.gen(function* () {
|
|
1789
|
+
const fs = yield* FileSystem6.FileSystem;
|
|
1790
|
+
const pathService = yield* Path5.Path;
|
|
1791
|
+
const exists = Effect14.fn("EnvFileService.exists")(function* (filePath) {
|
|
1792
|
+
return yield* fs.exists(filePath).pipe(Effect14.mapError(() => new EnvFileReadError({ message: `Failed to check whether ${filePath} exists` })));
|
|
1793
|
+
});
|
|
1794
|
+
const read = Effect14.fn("EnvFileService.read")(function* (filePath) {
|
|
1795
|
+
const present = yield* exists(filePath);
|
|
1796
|
+
if (!present) return Option7.none();
|
|
1797
|
+
const contents = yield* fs.readFileString(filePath, "utf8").pipe(Effect14.mapError(() => new EnvFileReadError({ message: `Failed to read ${filePath}` })));
|
|
1798
|
+
return Option7.some(contents);
|
|
1799
|
+
});
|
|
1800
|
+
const write = Effect14.fn("EnvFileService.write")(function* (filePath, entries, opts) {
|
|
1801
|
+
const dir = pathService.dirname(filePath);
|
|
1802
|
+
yield* fs.makeDirectory(dir, { recursive: true }).pipe(
|
|
1803
|
+
Effect14.mapError(
|
|
1804
|
+
(error) => new EnvFileWriteError({ message: `Failed to create directory for ${filePath}`, cause: String(error) })
|
|
1805
|
+
)
|
|
1806
|
+
);
|
|
1807
|
+
yield* fs.writeFileString(filePath, serializeEnv(entries, opts), { mode: 384 }).pipe(
|
|
1808
|
+
Effect14.mapError(
|
|
1809
|
+
(error) => new EnvFileWriteError({ message: `Failed to write ${filePath}`, cause: String(error) })
|
|
1810
|
+
)
|
|
1811
|
+
);
|
|
1812
|
+
yield* fs.chmod(filePath, 384).pipe(
|
|
1813
|
+
Effect14.mapError(
|
|
1814
|
+
(error) => new EnvFileWriteError({ message: `Failed to secure ${filePath}`, cause: String(error) })
|
|
1815
|
+
)
|
|
1816
|
+
);
|
|
1817
|
+
});
|
|
1818
|
+
const ensureGitignored = Effect14.fn("EnvFileService.ensureGitignored")(function* (dir, entry) {
|
|
1819
|
+
const gitignorePath = pathService.join(dir, ".gitignore");
|
|
1820
|
+
const existing = yield* read(gitignorePath);
|
|
1821
|
+
const current = Option7.getOrElse(existing, () => "");
|
|
1822
|
+
const alreadyIgnored = current.split("\n").some((line) => line.trim() === entry);
|
|
1823
|
+
if (alreadyIgnored) {
|
|
1824
|
+
return false;
|
|
1825
|
+
}
|
|
1826
|
+
const separator = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
|
|
1827
|
+
yield* fs.writeFileString(gitignorePath, `${current}${separator}${entry}
|
|
1828
|
+
`).pipe(
|
|
1829
|
+
Effect14.mapError(
|
|
1830
|
+
(error) => new EnvFileWriteError({ message: "Failed to update .gitignore", cause: String(error) })
|
|
1831
|
+
)
|
|
1832
|
+
);
|
|
1833
|
+
return true;
|
|
1834
|
+
});
|
|
1835
|
+
return { exists, read, write, ensureGitignored };
|
|
1836
|
+
})
|
|
1837
|
+
}) {
|
|
1838
|
+
};
|
|
1839
|
+
|
|
1840
|
+
// src/services/local-credentials.ts
|
|
1841
|
+
import { Config as Config3, Effect as Effect16, Option as Option9 } from "effect";
|
|
1842
|
+
import * as os3 from "os";
|
|
1843
|
+
|
|
1844
|
+
// src/services/dev-credentials-store.ts
|
|
1845
|
+
import { FileSystem as FileSystem7, Path as Path6 } from "@effect/platform";
|
|
1846
|
+
import { Effect as Effect15, Option as Option8, Schema as Schema7 } from "effect";
|
|
1847
|
+
import * as os2 from "os";
|
|
1848
|
+
var DevCredentialsStore = class extends Effect15.Service()("DevCredentialsStore", {
|
|
1849
|
+
accessors: true,
|
|
1850
|
+
effect: Effect15.gen(function* () {
|
|
1851
|
+
const fs = yield* FileSystem7.FileSystem;
|
|
1852
|
+
const pathService = yield* Path6.Path;
|
|
1853
|
+
const homeDir = os2.homedir();
|
|
1854
|
+
const storeDir = pathService.join(homeDir, ".gigadrive");
|
|
1855
|
+
const storeFile = pathService.join(storeDir, "dev-keys.json");
|
|
1856
|
+
const ensureDirectory = Effect15.gen(function* () {
|
|
1857
|
+
yield* fs.makeDirectory(storeDir, { recursive: true }).pipe(
|
|
1858
|
+
Effect15.mapError(
|
|
1859
|
+
(error) => new DevCredentialsStoreWriteError({ message: "Failed to create ~/.gigadrive", cause: String(error) })
|
|
1860
|
+
)
|
|
1861
|
+
);
|
|
1862
|
+
yield* fs.chmod(storeDir, 448).pipe(
|
|
1863
|
+
Effect15.mapError(
|
|
1864
|
+
(error) => new DevCredentialsStoreWriteError({
|
|
1865
|
+
message: "Failed to set ~/.gigadrive permissions",
|
|
1866
|
+
cause: String(error)
|
|
1867
|
+
})
|
|
1868
|
+
)
|
|
1869
|
+
);
|
|
1870
|
+
});
|
|
1871
|
+
const load = Effect15.gen(function* () {
|
|
1872
|
+
const present = yield* fs.exists(storeFile).pipe(Effect15.mapError(() => new DevCredentialsStoreReadError({ message: "Failed to check dev-keys.json" })));
|
|
1873
|
+
if (!present) return {};
|
|
1874
|
+
const data = yield* fs.readFileString(storeFile, "utf8").pipe(Effect15.mapError(() => new DevCredentialsStoreReadError({ message: "Failed to read dev-keys.json" })));
|
|
1875
|
+
const json = yield* Effect15.try({
|
|
1876
|
+
try: () => JSON.parse(data),
|
|
1877
|
+
catch: () => new DevCredentialsStoreReadError({ message: "dev-keys.json contains invalid JSON" })
|
|
1878
|
+
});
|
|
1879
|
+
return yield* Schema7.decodeUnknown(DevKeyStore)(json).pipe(
|
|
1880
|
+
Effect15.mapError(() => new DevCredentialsStoreReadError({ message: "dev-keys.json has an invalid schema" }))
|
|
1881
|
+
);
|
|
1882
|
+
});
|
|
1883
|
+
const get = Effect15.fn("DevCredentialsStore.get")(function* (applicationId) {
|
|
1884
|
+
const store = yield* load;
|
|
1885
|
+
const entry = store[applicationId];
|
|
1886
|
+
return entry ? Option8.some(entry) : Option8.none();
|
|
1887
|
+
});
|
|
1888
|
+
const set = Effect15.fn("DevCredentialsStore.set")(function* (applicationId, entry) {
|
|
1889
|
+
const store = yield* load;
|
|
1890
|
+
const next = { ...store, [applicationId]: entry };
|
|
1891
|
+
yield* ensureDirectory;
|
|
1892
|
+
yield* fs.writeFileString(storeFile, `${JSON.stringify(next, null, 2)}
|
|
1893
|
+
`, { mode: 384 }).pipe(
|
|
1894
|
+
Effect15.mapError(
|
|
1895
|
+
(error) => new DevCredentialsStoreWriteError({ message: "Failed to write dev-keys.json", cause: String(error) })
|
|
1896
|
+
)
|
|
1897
|
+
);
|
|
1898
|
+
});
|
|
1899
|
+
return { get, set };
|
|
1900
|
+
})
|
|
1901
|
+
}) {
|
|
1902
|
+
};
|
|
1903
|
+
|
|
1904
|
+
// src/services/local-credentials.ts
|
|
1905
|
+
var DEV_KEY_SCOPES = ["network:env_vars:read"];
|
|
1906
|
+
var ApiBaseUrl2 = Config3.string("GIGADRIVE_API_BASE_URL").pipe(Config3.withDefault("https://api.gigadrive.network"));
|
|
1907
|
+
var credentialEntries = (clientId, clientSecret, baseUrl) => [
|
|
1908
|
+
{ key: "GIGADRIVE_CLIENT_ID", value: clientId },
|
|
1909
|
+
{ key: "GIGADRIVE_CLIENT_SECRET", value: clientSecret },
|
|
1910
|
+
{ key: "GIGADRIVE_API_BASE_URL", value: baseUrl }
|
|
1911
|
+
];
|
|
1912
|
+
var LocalCredentialsService = class extends Effect16.Service()("LocalCredentialsService", {
|
|
1913
|
+
accessors: true,
|
|
1914
|
+
dependencies: [ApiClientService.Default, DevCredentialsStore.Default],
|
|
1915
|
+
effect: Effect16.gen(function* () {
|
|
1916
|
+
const apiClient = yield* ApiClientService;
|
|
1917
|
+
const store = yield* DevCredentialsStore;
|
|
1918
|
+
const baseUrl = yield* ApiBaseUrl2;
|
|
1919
|
+
const keyName = (applicationId) => `cli-dev:${os3.hostname()}:${applicationId}`;
|
|
1920
|
+
const provision = Effect16.fn("LocalCredentialsService.provision")(function* (input) {
|
|
1921
|
+
const stored = yield* store.get(input.applicationId);
|
|
1922
|
+
const existing = input.existingContent ? parseEnv(input.existingContent) : {};
|
|
1923
|
+
const existingClientId = existing["GIGADRIVE_CLIENT_ID"];
|
|
1924
|
+
const existingClientSecret = existing["GIGADRIVE_CLIENT_SECRET"];
|
|
1925
|
+
const canReuse = !input.rotate && existingClientId !== void 0 && existingClientSecret !== void 0 && Option9.isSome(stored) && stored.value.apiKeyId === existingClientId;
|
|
1926
|
+
if (canReuse) {
|
|
1927
|
+
return credentialEntries(existingClientId, existingClientSecret, baseUrl);
|
|
1928
|
+
}
|
|
1929
|
+
if (Option9.isSome(stored)) {
|
|
1930
|
+
yield* apiClient.request((client) => client.apiKeys.delete(stored.value.apiKeyId)).pipe(Effect16.catchTag("ApiRequestError", () => Effect16.void));
|
|
1931
|
+
}
|
|
1932
|
+
const created = yield* apiClient.request(
|
|
1933
|
+
(client) => client.apiKeys.create({
|
|
1934
|
+
name: keyName(input.applicationId),
|
|
1935
|
+
applicationId: input.applicationId,
|
|
1936
|
+
scopes: DEV_KEY_SCOPES
|
|
1937
|
+
})
|
|
1938
|
+
);
|
|
1939
|
+
yield* store.set(input.applicationId, { apiKeyId: created.id });
|
|
1940
|
+
return credentialEntries(created.id, created.secret, baseUrl);
|
|
1941
|
+
});
|
|
1942
|
+
return { provision };
|
|
1943
|
+
})
|
|
1944
|
+
}) {
|
|
1945
|
+
};
|
|
1946
|
+
|
|
1947
|
+
// src/commands/env/index.ts
|
|
1544
1948
|
var appOption3 = Options4.text("app").pipe(
|
|
1545
1949
|
Options4.withAlias("a"),
|
|
1546
1950
|
Options4.withDescription("Operate on this application (overrides the linked application)"),
|
|
@@ -1563,21 +1967,40 @@ var keyValueArg = Args3.text({ name: "key=value" }).pipe(
|
|
|
1563
1967
|
Args3.withDescription("Variable assignment, e.g. DATABASE_URL=postgres://user:pass@host/db")
|
|
1564
1968
|
);
|
|
1565
1969
|
var keyOrIdArg = Args3.text({ name: "key-or-id" }).pipe(Args3.withDescription("Variable key or ID to remove"));
|
|
1566
|
-
var
|
|
1567
|
-
|
|
1970
|
+
var fileArg = Args3.text({ name: "file" }).pipe(
|
|
1971
|
+
Args3.withDescription("Output file to write (default: .env.local)"),
|
|
1972
|
+
Args3.optional
|
|
1973
|
+
);
|
|
1974
|
+
var environmentOption = Options4.text("environment").pipe(
|
|
1975
|
+
Options4.withAlias("e"),
|
|
1976
|
+
Options4.withDescription("Environment slug to resolve overrides for (e.g. preview). Omit for the local baseline"),
|
|
1977
|
+
Options4.optional
|
|
1978
|
+
);
|
|
1979
|
+
var yesOption = Options4.boolean("yes").pipe(
|
|
1980
|
+
Options4.withAlias("y"),
|
|
1981
|
+
Options4.withDescription("Overwrite an existing file without confirmation")
|
|
1982
|
+
);
|
|
1983
|
+
var withCredentialsOption = Options4.boolean("with-credentials").pipe(
|
|
1984
|
+
Options4.withDescription("Also provision API-key credentials so the local app can call the API as this application")
|
|
1985
|
+
);
|
|
1986
|
+
var rotateOption = Options4.boolean("rotate").pipe(
|
|
1987
|
+
Options4.withDescription("Force a fresh API key when provisioning credentials, revoking the previous one")
|
|
1988
|
+
);
|
|
1989
|
+
var resolveScope = (app, org) => Effect17.gen(function* () {
|
|
1990
|
+
if (Option10.isSome(org)) {
|
|
1568
1991
|
return { kind: "organization", id: org.value };
|
|
1569
1992
|
}
|
|
1570
1993
|
const projectLink = yield* ProjectLinkService;
|
|
1571
|
-
const id = yield*
|
|
1572
|
-
onSome: (value) =>
|
|
1573
|
-
onNone: () => projectLink.resolve(process.cwd()).pipe(
|
|
1994
|
+
const id = yield* Option10.match(app, {
|
|
1995
|
+
onSome: (value) => Effect17.succeed(value),
|
|
1996
|
+
onNone: () => projectLink.resolve(process.cwd()).pipe(Effect17.map((link) => link.applicationId))
|
|
1574
1997
|
});
|
|
1575
1998
|
return { kind: "application", id };
|
|
1576
1999
|
});
|
|
1577
2000
|
var envListCommand = Command7.make(
|
|
1578
2001
|
"list",
|
|
1579
2002
|
{ app: appOption3, org: orgOption4 },
|
|
1580
|
-
({ app, org }) =>
|
|
2003
|
+
({ app, org }) => Effect17.gen(function* () {
|
|
1581
2004
|
const apiClient = yield* ApiClientService;
|
|
1582
2005
|
const scope = yield* resolveScope(app, org);
|
|
1583
2006
|
const { items } = yield* apiClient.request(
|
|
@@ -1591,23 +2014,23 @@ var envListCommand = Command7.make(
|
|
|
1591
2014
|
yield* Console7.log(`${v.key}=${v.sensitive ? "***" : v.value ?? ""}`);
|
|
1592
2015
|
}
|
|
1593
2016
|
}).pipe(
|
|
1594
|
-
|
|
2017
|
+
Effect17.catchTags({
|
|
1595
2018
|
NotAuthenticatedError: (err) => Console7.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
|
|
1596
|
-
|
|
2019
|
+
Effect17.andThen(Effect17.fail(err))
|
|
1597
2020
|
),
|
|
1598
|
-
ApiRequestError: (err) => Console7.error(`Failed to list environment variables: ${err.message}`).pipe(
|
|
1599
|
-
ProjectNotLinkedError: (err) => Console7.error(err.message).pipe(
|
|
1600
|
-
ProjectLinkReadError: (err) => Console7.error(err.message).pipe(
|
|
2021
|
+
ApiRequestError: (err) => Console7.error(`Failed to list environment variables: ${err.message}`).pipe(Effect17.andThen(Effect17.fail(err))),
|
|
2022
|
+
ProjectNotLinkedError: (err) => Console7.error(err.message).pipe(Effect17.andThen(Effect17.fail(err))),
|
|
2023
|
+
ProjectLinkReadError: (err) => Console7.error(err.message).pipe(Effect17.andThen(Effect17.fail(err)))
|
|
1601
2024
|
})
|
|
1602
2025
|
)
|
|
1603
2026
|
);
|
|
1604
2027
|
var envSetCommand = Command7.make(
|
|
1605
2028
|
"set",
|
|
1606
2029
|
{ keyValue: keyValueArg, app: appOption3, org: orgOption4, sensitive: sensitiveOption, env: envIdOption },
|
|
1607
|
-
({ keyValue, app, org, sensitive, env }) =>
|
|
2030
|
+
({ keyValue, app, org, sensitive, env }) => Effect17.gen(function* () {
|
|
1608
2031
|
const separator = keyValue.indexOf("=");
|
|
1609
2032
|
if (separator <= 0) {
|
|
1610
|
-
return yield*
|
|
2033
|
+
return yield* Effect17.fail(
|
|
1611
2034
|
new InvalidEnvVarFormatError({
|
|
1612
2035
|
message: 'Invalid format. Use KEY=VALUE, e.g. "gigadrive env set DATABASE_URL=postgres://...".'
|
|
1613
2036
|
})
|
|
@@ -1617,27 +2040,27 @@ var envSetCommand = Command7.make(
|
|
|
1617
2040
|
const value = keyValue.slice(separator + 1);
|
|
1618
2041
|
const apiClient = yield* ApiClientService;
|
|
1619
2042
|
const scope = yield* resolveScope(app, org);
|
|
1620
|
-
const environmentId =
|
|
2043
|
+
const environmentId = Option10.getOrUndefined(env);
|
|
1621
2044
|
const created = yield* apiClient.request(
|
|
1622
2045
|
(client) => scope.kind === "organization" ? client.organizations.envVars.create(scope.id, { key, value, sensitive }) : client.applications.envVars.create(scope.id, { key, value, sensitive, environmentId })
|
|
1623
2046
|
);
|
|
1624
2047
|
yield* Console7.log(`Set ${created.key}.`);
|
|
1625
2048
|
}).pipe(
|
|
1626
|
-
|
|
1627
|
-
InvalidEnvVarFormatError: (err) => Console7.error(err.message).pipe(
|
|
2049
|
+
Effect17.catchTags({
|
|
2050
|
+
InvalidEnvVarFormatError: (err) => Console7.error(err.message).pipe(Effect17.andThen(Effect17.fail(err))),
|
|
1628
2051
|
NotAuthenticatedError: (err) => Console7.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
|
|
1629
|
-
|
|
2052
|
+
Effect17.andThen(Effect17.fail(err))
|
|
1630
2053
|
),
|
|
1631
|
-
ApiRequestError: (err) => Console7.error(`Failed to set environment variable: ${err.message}`).pipe(
|
|
1632
|
-
ProjectNotLinkedError: (err) => Console7.error(err.message).pipe(
|
|
1633
|
-
ProjectLinkReadError: (err) => Console7.error(err.message).pipe(
|
|
2054
|
+
ApiRequestError: (err) => Console7.error(`Failed to set environment variable: ${err.message}`).pipe(Effect17.andThen(Effect17.fail(err))),
|
|
2055
|
+
ProjectNotLinkedError: (err) => Console7.error(err.message).pipe(Effect17.andThen(Effect17.fail(err))),
|
|
2056
|
+
ProjectLinkReadError: (err) => Console7.error(err.message).pipe(Effect17.andThen(Effect17.fail(err)))
|
|
1634
2057
|
})
|
|
1635
2058
|
)
|
|
1636
2059
|
);
|
|
1637
2060
|
var envRmCommand = Command7.make(
|
|
1638
2061
|
"rm",
|
|
1639
2062
|
{ keyOrId: keyOrIdArg, app: appOption3, org: orgOption4 },
|
|
1640
|
-
({ keyOrId, app, org }) =>
|
|
2063
|
+
({ keyOrId, app, org }) => Effect17.gen(function* () {
|
|
1641
2064
|
const apiClient = yield* ApiClientService;
|
|
1642
2065
|
const scope = yield* resolveScope(app, org);
|
|
1643
2066
|
const { items } = yield* apiClient.request(
|
|
@@ -1645,7 +2068,7 @@ var envRmCommand = Command7.make(
|
|
|
1645
2068
|
);
|
|
1646
2069
|
const target = items.find((v) => v.id === keyOrId || v.key === keyOrId);
|
|
1647
2070
|
if (target === void 0) {
|
|
1648
|
-
return yield*
|
|
2071
|
+
return yield* Effect17.fail(
|
|
1649
2072
|
new EnvVarNotFoundError({ message: `No environment variable matching "${keyOrId}".` })
|
|
1650
2073
|
);
|
|
1651
2074
|
}
|
|
@@ -1654,23 +2077,97 @@ var envRmCommand = Command7.make(
|
|
|
1654
2077
|
);
|
|
1655
2078
|
yield* Console7.log(`Removed ${target.key}.`);
|
|
1656
2079
|
}).pipe(
|
|
1657
|
-
|
|
1658
|
-
EnvVarNotFoundError: (err) => Console7.error(err.message).pipe(
|
|
2080
|
+
Effect17.catchTags({
|
|
2081
|
+
EnvVarNotFoundError: (err) => Console7.error(err.message).pipe(Effect17.andThen(Effect17.fail(err))),
|
|
2082
|
+
NotAuthenticatedError: (err) => Console7.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
|
|
2083
|
+
Effect17.andThen(Effect17.fail(err))
|
|
2084
|
+
),
|
|
2085
|
+
ApiRequestError: (err) => Console7.error(`Failed to remove environment variable: ${err.message}`).pipe(Effect17.andThen(Effect17.fail(err))),
|
|
2086
|
+
ProjectNotLinkedError: (err) => Console7.error(err.message).pipe(Effect17.andThen(Effect17.fail(err))),
|
|
2087
|
+
ProjectLinkReadError: (err) => Console7.error(err.message).pipe(Effect17.andThen(Effect17.fail(err)))
|
|
2088
|
+
})
|
|
2089
|
+
)
|
|
2090
|
+
);
|
|
2091
|
+
var envPullCommand = Command7.make(
|
|
2092
|
+
"pull",
|
|
2093
|
+
{
|
|
2094
|
+
file: fileArg,
|
|
2095
|
+
app: appOption3,
|
|
2096
|
+
environment: environmentOption,
|
|
2097
|
+
yes: yesOption,
|
|
2098
|
+
withCredentials: withCredentialsOption,
|
|
2099
|
+
rotate: rotateOption
|
|
2100
|
+
},
|
|
2101
|
+
({ file, app, environment, yes, withCredentials, rotate }) => Effect17.gen(function* () {
|
|
2102
|
+
const apiClient = yield* ApiClientService;
|
|
2103
|
+
const projectLink = yield* ProjectLinkService;
|
|
2104
|
+
const envFile = yield* EnvFileService;
|
|
2105
|
+
const applicationId = yield* Option10.match(app, {
|
|
2106
|
+
onSome: (value) => Effect17.succeed(value),
|
|
2107
|
+
onNone: () => projectLink.resolve(process.cwd()).pipe(Effect17.map((link) => link.applicationId))
|
|
2108
|
+
});
|
|
2109
|
+
const target = Option10.getOrElse(file, () => ".env.local");
|
|
2110
|
+
const environmentSlug = Option10.getOrUndefined(environment);
|
|
2111
|
+
const { items, omittedSensitive } = yield* apiClient.request(
|
|
2112
|
+
(client) => client.applications.envVars.pull(applicationId, environmentSlug ? { environment: environmentSlug } : void 0)
|
|
2113
|
+
);
|
|
2114
|
+
const existing = yield* envFile.read(target);
|
|
2115
|
+
if (Option10.isSome(existing) && !yes) {
|
|
2116
|
+
const overwrite = yield* Prompt.run(
|
|
2117
|
+
Prompt.confirm({ message: `${target} already exists. Overwrite?`, initial: false })
|
|
2118
|
+
);
|
|
2119
|
+
if (!overwrite) {
|
|
2120
|
+
return yield* Console7.log("Cancelled.");
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
let credentialEntries2 = [];
|
|
2124
|
+
if (withCredentials) {
|
|
2125
|
+
const localCredentials = yield* LocalCredentialsService;
|
|
2126
|
+
credentialEntries2 = yield* localCredentials.provision({
|
|
2127
|
+
applicationId,
|
|
2128
|
+
rotate,
|
|
2129
|
+
existingContent: Option10.getOrUndefined(existing)
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
const entries = [...items.map((v) => ({ key: v.key, value: v.value })), ...credentialEntries2];
|
|
2133
|
+
yield* envFile.write(target, entries);
|
|
2134
|
+
const gitignored = yield* envFile.ensureGitignored(process.cwd(), target);
|
|
2135
|
+
yield* Console7.log(
|
|
2136
|
+
`Pulled ${items.length} environment variable(s)${withCredentials ? " and API credentials" : ""} to ${target}.`
|
|
2137
|
+
);
|
|
2138
|
+
if (omittedSensitive > 0) {
|
|
2139
|
+
yield* Console7.log(
|
|
2140
|
+
`Skipped ${omittedSensitive} sensitive variable(s); secrets live only in production/preview environments.`
|
|
2141
|
+
);
|
|
2142
|
+
}
|
|
2143
|
+
if (gitignored) {
|
|
2144
|
+
yield* Console7.log(`Added ${target} to .gitignore.`);
|
|
2145
|
+
}
|
|
2146
|
+
}).pipe(
|
|
2147
|
+
Effect17.catchTags({
|
|
2148
|
+
// Ctrl+C during the overwrite prompt is a deliberate cancel — exit 0.
|
|
2149
|
+
QuitException: () => Console7.log("Cancelled."),
|
|
1659
2150
|
NotAuthenticatedError: (err) => Console7.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
|
|
1660
|
-
|
|
2151
|
+
Effect17.andThen(Effect17.fail(err))
|
|
1661
2152
|
),
|
|
1662
|
-
ApiRequestError: (err) => Console7.error(`Failed to
|
|
1663
|
-
ProjectNotLinkedError: (err) => Console7.error(err.message).pipe(
|
|
1664
|
-
ProjectLinkReadError: (err) => Console7.error(err.message).pipe(
|
|
2153
|
+
ApiRequestError: (err) => Console7.error(`Failed to pull environment variables: ${err.message}`).pipe(Effect17.andThen(Effect17.fail(err))),
|
|
2154
|
+
ProjectNotLinkedError: (err) => Console7.error(err.message).pipe(Effect17.andThen(Effect17.fail(err))),
|
|
2155
|
+
ProjectLinkReadError: (err) => Console7.error(err.message).pipe(Effect17.andThen(Effect17.fail(err))),
|
|
2156
|
+
EnvFileReadError: (err) => Console7.error(err.message).pipe(Effect17.andThen(Effect17.fail(err))),
|
|
2157
|
+
EnvFileWriteError: (err) => Console7.error(err.message).pipe(Effect17.andThen(Effect17.fail(err))),
|
|
2158
|
+
DevCredentialsStoreReadError: (err) => Console7.error(err.message).pipe(Effect17.andThen(Effect17.fail(err))),
|
|
2159
|
+
DevCredentialsStoreWriteError: (err) => Console7.error(err.message).pipe(Effect17.andThen(Effect17.fail(err)))
|
|
1665
2160
|
})
|
|
1666
2161
|
)
|
|
1667
2162
|
);
|
|
1668
|
-
var envBase = Command7.make("env", {}, () =>
|
|
1669
|
-
var envCommand = envBase.pipe(
|
|
2163
|
+
var envBase = Command7.make("env", {}, () => Effect17.void);
|
|
2164
|
+
var envCommand = envBase.pipe(
|
|
2165
|
+
Command7.withSubcommands([envListCommand, envSetCommand, envRmCommand, envPullCommand])
|
|
2166
|
+
);
|
|
1670
2167
|
|
|
1671
2168
|
// src/commands/link/index.ts
|
|
1672
|
-
import { Command as Command8, Options as Options5, Prompt } from "@effect/cli";
|
|
1673
|
-
import { Console as Console8, Effect as
|
|
2169
|
+
import { Command as Command8, Options as Options5, Prompt as Prompt2 } from "@effect/cli";
|
|
2170
|
+
import { Console as Console8, Effect as Effect18, Option as Option11 } from "effect";
|
|
1674
2171
|
var appOption4 = Options5.text("app").pipe(
|
|
1675
2172
|
Options5.withAlias("a"),
|
|
1676
2173
|
Options5.withDescription("Application ID to link (skips the interactive picker)"),
|
|
@@ -1684,26 +2181,26 @@ var orgOption5 = Options5.text("org").pipe(
|
|
|
1684
2181
|
var linkCommand = Command8.make(
|
|
1685
2182
|
"link",
|
|
1686
2183
|
{ app: appOption4, org: orgOption5 },
|
|
1687
|
-
({ app, org }) =>
|
|
2184
|
+
({ app, org }) => Effect18.gen(function* () {
|
|
1688
2185
|
const apiClient = yield* ApiClientService;
|
|
1689
2186
|
const projectLink = yield* ProjectLinkService;
|
|
1690
2187
|
const cwd = process.cwd();
|
|
1691
|
-
const organizationId =
|
|
2188
|
+
const organizationId = Option11.getOrUndefined(org);
|
|
1692
2189
|
const { items: apps } = yield* apiClient.request((client) => client.applications.list({ organizationId }));
|
|
1693
2190
|
if (apps.length === 0) {
|
|
1694
|
-
return yield*
|
|
2191
|
+
return yield* Effect18.fail(
|
|
1695
2192
|
new NoApplicationsFoundError({
|
|
1696
2193
|
message: 'No applications found. Create one in the Gigadrive console, then run "gigadrive link" again.'
|
|
1697
2194
|
})
|
|
1698
2195
|
);
|
|
1699
2196
|
}
|
|
1700
|
-
const explicitAppId =
|
|
2197
|
+
const explicitAppId = Option11.getOrUndefined(app);
|
|
1701
2198
|
let applicationId;
|
|
1702
2199
|
let linkedOrganizationId;
|
|
1703
2200
|
if (explicitAppId !== void 0) {
|
|
1704
2201
|
const match = apps.find((a) => a.id === explicitAppId);
|
|
1705
2202
|
if (match === void 0) {
|
|
1706
|
-
return yield*
|
|
2203
|
+
return yield* Effect18.fail(
|
|
1707
2204
|
new ApplicationNotFoundError({
|
|
1708
2205
|
message: `Application "${explicitAppId}" was not found among your applications.`
|
|
1709
2206
|
})
|
|
@@ -1715,8 +2212,8 @@ var linkCommand = Command8.make(
|
|
|
1715
2212
|
applicationId = apps[0].id;
|
|
1716
2213
|
linkedOrganizationId = apps[0].organization.id;
|
|
1717
2214
|
} else {
|
|
1718
|
-
const chosen = yield*
|
|
1719
|
-
|
|
2215
|
+
const chosen = yield* Prompt2.run(
|
|
2216
|
+
Prompt2.select({
|
|
1720
2217
|
message: "Select an application to link",
|
|
1721
2218
|
choices: apps.map((a) => ({ title: `${a.name} (${a.organization.name})`, value: a }))
|
|
1722
2219
|
})
|
|
@@ -1728,49 +2225,52 @@ var linkCommand = Command8.make(
|
|
|
1728
2225
|
yield* Console8.log(`Linked this directory to application ${applicationId}.`);
|
|
1729
2226
|
yield* Console8.log('Tip: add ".gigadrive/" to your .gitignore.');
|
|
1730
2227
|
}).pipe(
|
|
1731
|
-
|
|
2228
|
+
Effect18.catchTags({
|
|
1732
2229
|
// Ctrl+C during the picker is a deliberate cancel — exit 0.
|
|
1733
2230
|
QuitException: () => Console8.log("Cancelled."),
|
|
1734
2231
|
NotAuthenticatedError: (err) => Console8.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
|
|
1735
|
-
|
|
2232
|
+
Effect18.andThen(Effect18.fail(err))
|
|
1736
2233
|
),
|
|
1737
|
-
ApiRequestError: (err) => Console8.error(`Failed to list applications: ${err.message}`).pipe(
|
|
1738
|
-
NoApplicationsFoundError: (err) => Console8.error(err.message).pipe(
|
|
1739
|
-
ApplicationNotFoundError: (err) => Console8.error(err.message).pipe(
|
|
1740
|
-
ProjectLinkWriteError: (err) => Console8.error(err.message).pipe(
|
|
2234
|
+
ApiRequestError: (err) => Console8.error(`Failed to list applications: ${err.message}`).pipe(Effect18.andThen(Effect18.fail(err))),
|
|
2235
|
+
NoApplicationsFoundError: (err) => Console8.error(err.message).pipe(Effect18.andThen(Effect18.fail(err))),
|
|
2236
|
+
ApplicationNotFoundError: (err) => Console8.error(err.message).pipe(Effect18.andThen(Effect18.fail(err))),
|
|
2237
|
+
ProjectLinkWriteError: (err) => Console8.error(err.message).pipe(Effect18.andThen(Effect18.fail(err)))
|
|
1741
2238
|
})
|
|
1742
2239
|
)
|
|
1743
2240
|
);
|
|
1744
2241
|
var unlinkCommand = Command8.make(
|
|
1745
2242
|
"unlink",
|
|
1746
2243
|
{},
|
|
1747
|
-
() =>
|
|
2244
|
+
() => Effect18.gen(function* () {
|
|
1748
2245
|
const projectLink = yield* ProjectLinkService;
|
|
1749
2246
|
yield* projectLink.remove(process.cwd());
|
|
1750
2247
|
yield* Console8.log("Unlinked this directory.");
|
|
1751
2248
|
}).pipe(
|
|
1752
|
-
|
|
1753
|
-
ProjectLinkWriteError: (err) => Console8.error(err.message).pipe(
|
|
2249
|
+
Effect18.catchTags({
|
|
2250
|
+
ProjectLinkWriteError: (err) => Console8.error(err.message).pipe(Effect18.andThen(Effect18.fail(err)))
|
|
1754
2251
|
})
|
|
1755
2252
|
)
|
|
1756
2253
|
);
|
|
1757
2254
|
|
|
1758
2255
|
// src/commands/login/index.ts
|
|
1759
2256
|
import { Command as Command9 } from "@effect/cli";
|
|
1760
|
-
import { Console as Console9, Effect as
|
|
2257
|
+
import { Console as Console9, Effect as Effect19 } from "effect";
|
|
1761
2258
|
var loginCommand = Command9.make(
|
|
1762
2259
|
"login",
|
|
1763
2260
|
{},
|
|
1764
|
-
() =>
|
|
2261
|
+
() => Effect19.gen(function* () {
|
|
1765
2262
|
yield* Console9.log("Initiating login...");
|
|
1766
2263
|
const auth = yield* AuthService;
|
|
1767
2264
|
yield* auth.login;
|
|
1768
|
-
const info = yield* auth.getUserInfo.pipe(
|
|
2265
|
+
const info = yield* auth.getUserInfo.pipe(Effect19.catchAll(() => Effect19.succeed({})));
|
|
1769
2266
|
const name = auth.inferUserName(info);
|
|
1770
2267
|
yield* Console9.log(`You are now logged in as ${name}.`);
|
|
1771
2268
|
}).pipe(
|
|
1772
|
-
|
|
1773
|
-
LoginFlowError: (err) => Console9.error(
|
|
2269
|
+
Effect19.catchTags({
|
|
2270
|
+
LoginFlowError: (err) => Console9.error(err.message),
|
|
2271
|
+
DeviceAuthorizationError: (err) => Console9.error(`Login failed: ${err.message}`),
|
|
2272
|
+
AuthorizationDeniedError: (err) => Console9.error(err.message),
|
|
2273
|
+
DeviceCodeExpiredError: (err) => Console9.error(err.message),
|
|
1774
2274
|
TokenExchangeError: (err) => Console9.error(`Login failed: ${err.message}`),
|
|
1775
2275
|
OAuthDiscoveryError: (err) => Console9.error(`Login failed: ${err.message}`),
|
|
1776
2276
|
AuthStorageWriteError: (err) => Console9.error(`Failed to save credentials: ${err.message}`)
|
|
@@ -1780,51 +2280,51 @@ var loginCommand = Command9.make(
|
|
|
1780
2280
|
|
|
1781
2281
|
// src/commands/logout/index.ts
|
|
1782
2282
|
import { Command as Command10 } from "@effect/cli";
|
|
1783
|
-
import { Console as Console10, Effect as
|
|
2283
|
+
import { Console as Console10, Effect as Effect20 } from "effect";
|
|
1784
2284
|
var logoutCommand = Command10.make(
|
|
1785
2285
|
"logout",
|
|
1786
2286
|
{},
|
|
1787
|
-
() =>
|
|
2287
|
+
() => Effect20.gen(function* () {
|
|
1788
2288
|
const auth = yield* AuthService;
|
|
1789
2289
|
yield* auth.logout;
|
|
1790
2290
|
yield* Console10.log("Logged out.");
|
|
1791
2291
|
}).pipe(
|
|
1792
|
-
|
|
1793
|
-
AuthStorageWriteError: (err) => Console10.error(`Failed to log out: ${err.message}`).pipe(
|
|
2292
|
+
Effect20.catchTags({
|
|
2293
|
+
AuthStorageWriteError: (err) => Console10.error(`Failed to log out: ${err.message}`).pipe(Effect20.andThen(Effect20.fail(err)))
|
|
1794
2294
|
})
|
|
1795
2295
|
)
|
|
1796
2296
|
);
|
|
1797
2297
|
|
|
1798
2298
|
// src/commands/platform/index.ts
|
|
1799
2299
|
import { Command as Command12 } from "@effect/cli";
|
|
1800
|
-
import { Effect as
|
|
2300
|
+
import { Effect as Effect24 } from "effect";
|
|
1801
2301
|
|
|
1802
2302
|
// src/commands/platform/deploy/index.ts
|
|
1803
2303
|
import { Command as Command11, Options as Options6 } from "@effect/cli";
|
|
1804
|
-
import { FileSystem as
|
|
2304
|
+
import { FileSystem as FileSystem9, Path as Path8 } from "@effect/platform";
|
|
1805
2305
|
import { formatFileSize } from "@gigadrive/commons";
|
|
1806
|
-
import { Console as Console11, Duration, Effect as
|
|
1807
|
-
import * as
|
|
2306
|
+
import { Console as Console11, Duration as Duration2, Effect as Effect23, Option as Option12, Schedule, Stream } from "effect";
|
|
2307
|
+
import * as os4 from "os";
|
|
1808
2308
|
|
|
1809
2309
|
// src/services/archive.ts
|
|
1810
|
-
import { FileSystem as
|
|
2310
|
+
import { FileSystem as FileSystem8, Path as Path7 } from "@effect/platform";
|
|
1811
2311
|
import { getFilesForPattern } from "@gigadrive/build-utils";
|
|
1812
2312
|
import archiver from "archiver";
|
|
1813
|
-
import { Effect as
|
|
2313
|
+
import { Effect as Effect21 } from "effect";
|
|
1814
2314
|
import ignore from "ignore";
|
|
1815
2315
|
import * as nodeFs from "fs";
|
|
1816
2316
|
import * as nodeFsPromises from "fs/promises";
|
|
1817
2317
|
var IGNORE_FILE_NAMES = [".gigadriveignore", ".vercelignore", ".dockerignore", ".nowignore"];
|
|
1818
2318
|
var readIgnoreFile = (fs, ignorePath) => fs.readFileString(ignorePath, "utf8").pipe(
|
|
1819
|
-
|
|
1820
|
-
|
|
2319
|
+
Effect21.map((content) => content.split("\n").filter((line) => line.trim() !== "")),
|
|
2320
|
+
Effect21.catchAll(() => Effect21.succeed([]))
|
|
1821
2321
|
);
|
|
1822
|
-
var collectIgnorePatterns = (fs, pathService, dir, baseDir, ignoreRules) =>
|
|
1823
|
-
const items = yield* fs.readDirectory(dir).pipe(
|
|
2322
|
+
var collectIgnorePatterns = (fs, pathService, dir, baseDir, ignoreRules) => Effect21.gen(function* () {
|
|
2323
|
+
const items = yield* fs.readDirectory(dir).pipe(Effect21.mapError(() => new ArchiveCreateError({ message: `Failed to read directory: ${dir}` })));
|
|
1824
2324
|
for (const item of items) {
|
|
1825
2325
|
const fullPath = pathService.join(dir, item);
|
|
1826
2326
|
const relativePath = pathService.relative(baseDir, fullPath);
|
|
1827
|
-
const stat = yield*
|
|
2327
|
+
const stat = yield* Effect21.tryPromise({
|
|
1828
2328
|
try: () => nodeFsPromises.lstat(fullPath),
|
|
1829
2329
|
catch: () => new ArchiveCreateError({ message: `Failed to stat: ${fullPath}` })
|
|
1830
2330
|
});
|
|
@@ -1844,8 +2344,8 @@ var collectIgnorePatterns = (fs, pathService, dir, baseDir, ignoreRules) => Effe
|
|
|
1844
2344
|
yield* collectIgnorePatterns(fs, pathService, fullPath, baseDir, ignoreRules);
|
|
1845
2345
|
}
|
|
1846
2346
|
}
|
|
1847
|
-
}).pipe(
|
|
1848
|
-
var initializeIgnoreRules = (fs, pathService, baseDir) =>
|
|
2347
|
+
}).pipe(Effect21.catchAll(() => Effect21.void));
|
|
2348
|
+
var initializeIgnoreRules = (fs, pathService, baseDir) => Effect21.gen(function* () {
|
|
1849
2349
|
const ignoreRules = ignore();
|
|
1850
2350
|
ignoreRules.add([".git/", "**/.DS_Store", "**/Thumbs.db"]);
|
|
1851
2351
|
for (const ignoreFileName of IGNORE_FILE_NAMES) {
|
|
@@ -1860,14 +2360,14 @@ var initializeIgnoreRules = (fs, pathService, baseDir) => Effect18.gen(function*
|
|
|
1860
2360
|
yield* collectIgnorePatterns(fs, pathService, baseDir, baseDir, ignoreRules);
|
|
1861
2361
|
return ignoreRules;
|
|
1862
2362
|
});
|
|
1863
|
-
var getFilesToInclude = (fs, pathService, dir, ignoreRules) =>
|
|
2363
|
+
var getFilesToInclude = (fs, pathService, dir, ignoreRules) => Effect21.gen(function* () {
|
|
1864
2364
|
const filesToInclude = [];
|
|
1865
|
-
const walkDir = (currentDir) =>
|
|
1866
|
-
const items = yield* fs.readDirectory(currentDir).pipe(
|
|
2365
|
+
const walkDir = (currentDir) => Effect21.gen(function* () {
|
|
2366
|
+
const items = yield* fs.readDirectory(currentDir).pipe(Effect21.mapError(() => new ArchiveCreateError({ message: `Failed to read directory: ${currentDir}` })));
|
|
1867
2367
|
for (const item of items) {
|
|
1868
2368
|
const fullPath = pathService.join(currentDir, item);
|
|
1869
2369
|
const relativePath = pathService.relative(dir, fullPath);
|
|
1870
|
-
const stat = yield*
|
|
2370
|
+
const stat = yield* Effect21.tryPromise({
|
|
1871
2371
|
try: () => nodeFsPromises.lstat(fullPath),
|
|
1872
2372
|
catch: () => new ArchiveCreateError({ message: `Failed to stat: ${fullPath}` })
|
|
1873
2373
|
});
|
|
@@ -1881,21 +2381,21 @@ var getFilesToInclude = (fs, pathService, dir, ignoreRules) => Effect18.gen(func
|
|
|
1881
2381
|
}
|
|
1882
2382
|
}
|
|
1883
2383
|
}
|
|
1884
|
-
}).pipe(
|
|
2384
|
+
}).pipe(Effect21.catchAll(() => Effect21.void));
|
|
1885
2385
|
yield* walkDir(dir);
|
|
1886
2386
|
return filesToInclude;
|
|
1887
2387
|
});
|
|
1888
|
-
var ArchiveService = class extends
|
|
2388
|
+
var ArchiveService = class extends Effect21.Service()("ArchiveService", {
|
|
1889
2389
|
accessors: true,
|
|
1890
|
-
effect:
|
|
1891
|
-
const fs = yield*
|
|
1892
|
-
const pathService = yield*
|
|
1893
|
-
const createZipArchive =
|
|
1894
|
-
yield*
|
|
1895
|
-
yield*
|
|
1896
|
-
yield*
|
|
2390
|
+
effect: Effect21.gen(function* () {
|
|
2391
|
+
const fs = yield* FileSystem8.FileSystem;
|
|
2392
|
+
const pathService = yield* Path7.Path;
|
|
2393
|
+
const createZipArchive = Effect21.fn("ArchiveService.createZipArchive")(function* (inputDir, outputFile, options = {}) {
|
|
2394
|
+
yield* Effect21.annotateCurrentSpan("inputDir", inputDir);
|
|
2395
|
+
yield* Effect21.annotateCurrentSpan("outputFile", outputFile);
|
|
2396
|
+
yield* Effect21.log("Creating zip archive", { inputDir, outputFile });
|
|
1897
2397
|
const ignoreRules = options.useIgnoreFiles !== false ? yield* initializeIgnoreRules(fs, pathService, inputDir) : ignore();
|
|
1898
|
-
const filesToInclude = options.whitelist ? yield*
|
|
2398
|
+
const filesToInclude = options.whitelist ? yield* Effect21.tryPromise({
|
|
1899
2399
|
try: () => Promise.all(
|
|
1900
2400
|
options.whitelist.map((file) => getFilesForPattern(file, inputDir, options.excludeFiles ?? []))
|
|
1901
2401
|
),
|
|
@@ -1903,25 +2403,25 @@ var ArchiveService = class extends Effect18.Service()("ArchiveService", {
|
|
|
1903
2403
|
message: "Failed to resolve whitelist patterns",
|
|
1904
2404
|
cause: error instanceof Error ? error.message : String(error)
|
|
1905
2405
|
})
|
|
1906
|
-
}) : yield*
|
|
2406
|
+
}) : yield* Effect21.gen(function* () {
|
|
1907
2407
|
if (options.excludeFiles && options.excludeFiles.length > 0) {
|
|
1908
2408
|
ignoreRules.add(options.excludeFiles);
|
|
1909
2409
|
}
|
|
1910
2410
|
return yield* getFilesToInclude(fs, pathService, inputDir, ignoreRules).pipe(
|
|
1911
|
-
|
|
2411
|
+
Effect21.map((files) => [files])
|
|
1912
2412
|
);
|
|
1913
2413
|
});
|
|
1914
|
-
const archiveExists = yield* fs.exists(outputFile).pipe(
|
|
2414
|
+
const archiveExists = yield* fs.exists(outputFile).pipe(Effect21.catchAll(() => Effect21.succeed(false)));
|
|
1915
2415
|
if (archiveExists) {
|
|
1916
2416
|
yield* fs.remove(outputFile).pipe(
|
|
1917
|
-
|
|
2417
|
+
Effect21.mapError(
|
|
1918
2418
|
(error) => new ArchiveCreateError({
|
|
1919
2419
|
message: `Failed to remove existing archive: ${String(error)}`
|
|
1920
2420
|
})
|
|
1921
2421
|
)
|
|
1922
2422
|
);
|
|
1923
2423
|
}
|
|
1924
|
-
yield*
|
|
2424
|
+
yield* Effect21.tryPromise({
|
|
1925
2425
|
try: () => new Promise((resolve, reject) => {
|
|
1926
2426
|
const output = nodeFs.createWriteStream(outputFile);
|
|
1927
2427
|
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
@@ -1940,14 +2440,14 @@ var ArchiveService = class extends Effect18.Service()("ArchiveService", {
|
|
|
1940
2440
|
})
|
|
1941
2441
|
});
|
|
1942
2442
|
const fileInfo = yield* fs.stat(outputFile).pipe(
|
|
1943
|
-
|
|
2443
|
+
Effect21.mapError(
|
|
1944
2444
|
(error) => new ArchiveCreateError({
|
|
1945
2445
|
message: `Failed to stat archive: ${String(error)}`
|
|
1946
2446
|
})
|
|
1947
2447
|
)
|
|
1948
2448
|
);
|
|
1949
2449
|
const size = Number(fileInfo.size);
|
|
1950
|
-
yield*
|
|
2450
|
+
yield* Effect21.log("Archive created", { size });
|
|
1951
2451
|
return { path: outputFile, size };
|
|
1952
2452
|
});
|
|
1953
2453
|
return { createZipArchive };
|
|
@@ -1956,32 +2456,32 @@ var ArchiveService = class extends Effect18.Service()("ArchiveService", {
|
|
|
1956
2456
|
};
|
|
1957
2457
|
|
|
1958
2458
|
// src/services/deployment-api.ts
|
|
1959
|
-
import { Effect as
|
|
1960
|
-
var DeploymentApiService = class extends
|
|
2459
|
+
import { Effect as Effect22 } from "effect";
|
|
2460
|
+
var DeploymentApiService = class extends Effect22.Service()("DeploymentApiService", {
|
|
1961
2461
|
accessors: true,
|
|
1962
2462
|
dependencies: [ApiClientService.Default],
|
|
1963
|
-
effect:
|
|
2463
|
+
effect: Effect22.gen(function* () {
|
|
1964
2464
|
const apiClient = yield* ApiClientService;
|
|
1965
2465
|
const getClient = apiClient.getClient.pipe(
|
|
1966
|
-
|
|
2466
|
+
Effect22.mapError((err) => new DeploymentAuthError({ message: `Authentication failed: ${err.message}` }))
|
|
1967
2467
|
);
|
|
1968
|
-
const createDeployment =
|
|
1969
|
-
yield*
|
|
1970
|
-
yield*
|
|
2468
|
+
const createDeployment = Effect22.fn("DeploymentApiService.createDeployment")(function* (applicationId) {
|
|
2469
|
+
yield* Effect22.annotateCurrentSpan("applicationId", applicationId);
|
|
2470
|
+
yield* Effect22.log("Creating deployment", { applicationId });
|
|
1971
2471
|
const client = yield* getClient;
|
|
1972
|
-
const deployment = yield*
|
|
2472
|
+
const deployment = yield* Effect22.tryPromise({
|
|
1973
2473
|
try: () => client.deployments.create({ applicationId }),
|
|
1974
2474
|
catch: (error) => new DeploymentCreateError({
|
|
1975
2475
|
message: `Failed to create deployment: ${error instanceof Error ? error.message : String(error)}`
|
|
1976
2476
|
})
|
|
1977
2477
|
});
|
|
1978
|
-
yield*
|
|
2478
|
+
yield* Effect22.log("Deployment created", { deploymentId: deployment.id });
|
|
1979
2479
|
return deployment.id;
|
|
1980
2480
|
});
|
|
1981
|
-
const startMultipartUpload =
|
|
1982
|
-
yield*
|
|
2481
|
+
const startMultipartUpload = Effect22.fn("DeploymentApiService.startMultipartUpload")(function* (deploymentId) {
|
|
2482
|
+
yield* Effect22.annotateCurrentSpan("deploymentId", deploymentId);
|
|
1983
2483
|
const client = yield* getClient;
|
|
1984
|
-
const result = yield*
|
|
2484
|
+
const result = yield* Effect22.tryPromise({
|
|
1985
2485
|
try: () => client.deployments.startUpload(deploymentId),
|
|
1986
2486
|
catch: (error) => new UploadStartError({
|
|
1987
2487
|
message: `Failed to start multipart upload: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -1989,9 +2489,9 @@ var DeploymentApiService = class extends Effect19.Service()("DeploymentApiServic
|
|
|
1989
2489
|
});
|
|
1990
2490
|
return result.uploadId;
|
|
1991
2491
|
});
|
|
1992
|
-
const getPresignedUrl =
|
|
2492
|
+
const getPresignedUrl = Effect22.fn("DeploymentApiService.getPresignedUrl")(function* (deploymentId, uploadId, partNumber) {
|
|
1993
2493
|
const client = yield* getClient;
|
|
1994
|
-
const result = yield*
|
|
2494
|
+
const result = yield* Effect22.tryPromise({
|
|
1995
2495
|
try: () => client.deployments.getPresignedUrl(deploymentId, uploadId, partNumber),
|
|
1996
2496
|
catch: (error) => new PresignedUrlError({
|
|
1997
2497
|
message: `Failed to get presigned URL: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -2000,11 +2500,11 @@ var DeploymentApiService = class extends Effect19.Service()("DeploymentApiServic
|
|
|
2000
2500
|
});
|
|
2001
2501
|
return result.url;
|
|
2002
2502
|
});
|
|
2003
|
-
const uploadPart =
|
|
2004
|
-
yield*
|
|
2005
|
-
yield*
|
|
2503
|
+
const uploadPart = Effect22.fn("DeploymentApiService.uploadPart")(function* (presignedUrl, data, partNumber) {
|
|
2504
|
+
yield* Effect22.annotateCurrentSpan("partNumber", partNumber);
|
|
2505
|
+
yield* Effect22.annotateCurrentSpan("size", data.length);
|
|
2006
2506
|
const client = yield* getClient;
|
|
2007
|
-
const result = yield*
|
|
2507
|
+
const result = yield* Effect22.tryPromise({
|
|
2008
2508
|
try: () => client.deployments.uploadPart(presignedUrl, data, partNumber),
|
|
2009
2509
|
catch: (error) => new UploadPartError({
|
|
2010
2510
|
message: `Failed to upload part ${partNumber}: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -2013,21 +2513,21 @@ var DeploymentApiService = class extends Effect19.Service()("DeploymentApiServic
|
|
|
2013
2513
|
});
|
|
2014
2514
|
return result;
|
|
2015
2515
|
});
|
|
2016
|
-
const completeUpload =
|
|
2017
|
-
yield*
|
|
2018
|
-
yield*
|
|
2516
|
+
const completeUpload = Effect22.fn("DeploymentApiService.completeUpload")(function* (deploymentId, uploadId, parts) {
|
|
2517
|
+
yield* Effect22.annotateCurrentSpan("deploymentId", deploymentId);
|
|
2518
|
+
yield* Effect22.annotateCurrentSpan("partsCount", parts.length);
|
|
2019
2519
|
const client = yield* getClient;
|
|
2020
|
-
yield*
|
|
2520
|
+
yield* Effect22.tryPromise({
|
|
2021
2521
|
try: () => client.deployments.completeUpload(deploymentId, uploadId, parts),
|
|
2022
2522
|
catch: (error) => new UploadCompleteError({
|
|
2023
2523
|
message: `Failed to complete upload: ${error instanceof Error ? error.message : String(error)}`
|
|
2024
2524
|
})
|
|
2025
2525
|
});
|
|
2026
|
-
yield*
|
|
2526
|
+
yield* Effect22.log("Upload completed successfully");
|
|
2027
2527
|
});
|
|
2028
|
-
const getDeploymentStatus =
|
|
2528
|
+
const getDeploymentStatus = Effect22.fn("DeploymentApiService.getDeploymentStatus")(function* (deploymentId) {
|
|
2029
2529
|
const client = yield* getClient;
|
|
2030
|
-
const deployment = yield*
|
|
2530
|
+
const deployment = yield* Effect22.tryPromise({
|
|
2031
2531
|
try: () => client.deployments.get(deploymentId),
|
|
2032
2532
|
catch: (error) => new DeploymentStatusError({
|
|
2033
2533
|
message: `Failed to get deployment status: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -2035,9 +2535,9 @@ var DeploymentApiService = class extends Effect19.Service()("DeploymentApiServic
|
|
|
2035
2535
|
});
|
|
2036
2536
|
return deployment.status;
|
|
2037
2537
|
});
|
|
2038
|
-
const getLogs =
|
|
2538
|
+
const getLogs = Effect22.fn("DeploymentApiService.getLogs")(function* (deploymentId, options) {
|
|
2039
2539
|
const client = yield* getClient;
|
|
2040
|
-
const result = yield*
|
|
2540
|
+
const result = yield* Effect22.tryPromise({
|
|
2041
2541
|
try: () => client.deployments.getLogs(deploymentId, options),
|
|
2042
2542
|
catch: (error) => new DeploymentLogsFetchError({
|
|
2043
2543
|
message: `Failed to get logs: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -2045,9 +2545,9 @@ var DeploymentApiService = class extends Effect19.Service()("DeploymentApiServic
|
|
|
2045
2545
|
});
|
|
2046
2546
|
return result;
|
|
2047
2547
|
});
|
|
2048
|
-
const getHostnames =
|
|
2548
|
+
const getHostnames = Effect22.fn("DeploymentApiService.getHostnames")(function* (deploymentId) {
|
|
2049
2549
|
const client = yield* getClient;
|
|
2050
|
-
const result = yield*
|
|
2550
|
+
const result = yield* Effect22.tryPromise({
|
|
2051
2551
|
try: () => client.deployments.getHostnames(deploymentId),
|
|
2052
2552
|
catch: (error) => new DeploymentHostnamesFetchError({
|
|
2053
2553
|
message: `Failed to get hostnames: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -2078,24 +2578,24 @@ var appOption5 = Options6.text("app").pipe(
|
|
|
2078
2578
|
var deployCommand = Command11.make(
|
|
2079
2579
|
"deploy",
|
|
2080
2580
|
{ app: appOption5 },
|
|
2081
|
-
({ app }) =>
|
|
2581
|
+
({ app }) => Effect23.gen(function* () {
|
|
2082
2582
|
const projectConfig = yield* ProjectConfigService;
|
|
2083
2583
|
const deploymentApi = yield* DeploymentApiService;
|
|
2084
2584
|
const archiveService = yield* ArchiveService;
|
|
2085
2585
|
const projectLink = yield* ProjectLinkService;
|
|
2086
|
-
const pathService = yield*
|
|
2586
|
+
const pathService = yield* Path8.Path;
|
|
2087
2587
|
const cwd = process.cwd();
|
|
2088
|
-
const applicationId = yield*
|
|
2089
|
-
onSome: (id) =>
|
|
2090
|
-
onNone: () => projectLink.resolve(cwd).pipe(
|
|
2588
|
+
const applicationId = yield* Option12.match(app, {
|
|
2589
|
+
onSome: (id) => Effect23.succeed(id),
|
|
2590
|
+
onNone: () => projectLink.resolve(cwd).pipe(Effect23.map((link) => link.applicationId))
|
|
2091
2591
|
});
|
|
2092
|
-
yield*
|
|
2592
|
+
yield* Effect23.log("Starting deploy command");
|
|
2093
2593
|
const { config } = yield* projectConfig.resolve(cwd);
|
|
2094
2594
|
yield* Console11.log("Creating deployment...");
|
|
2095
2595
|
const deploymentId = yield* deploymentApi.createDeployment(applicationId);
|
|
2096
2596
|
yield* Console11.log(`Deployment ID: ${deploymentId}`);
|
|
2097
2597
|
yield* Console11.log("Creating archive...");
|
|
2098
|
-
const archivePath = pathService.join(
|
|
2598
|
+
const archivePath = pathService.join(os4.tmpdir(), `project-${Date.now()}.zip`);
|
|
2099
2599
|
const archive = yield* archiveService.createZipArchive(config.userArchive?.rootOverwrite || cwd, archivePath, {
|
|
2100
2600
|
whitelist: config.userArchive?.fileWhitelist,
|
|
2101
2601
|
excludeFiles: config.excludeFiles,
|
|
@@ -2109,55 +2609,55 @@ var deployCommand = Command11.make(
|
|
|
2109
2609
|
yield* Console11.log("The deployment pipeline is now being provisioned. This may take a few seconds.");
|
|
2110
2610
|
yield* pollDeployment(deploymentId);
|
|
2111
2611
|
}).pipe(
|
|
2112
|
-
|
|
2113
|
-
ProjectNotLinkedError: (err) => Console11.error(err.message).pipe(
|
|
2114
|
-
ProjectLinkReadError: (err) => Console11.error(err.message).pipe(
|
|
2115
|
-
ConfigNotFoundError: (err) => Console11.error(err.message).pipe(
|
|
2116
|
-
ConfigParseError: (err) => Console11.error(`Config parse error: ${err.message}`).pipe(
|
|
2117
|
-
ConfigValidationError: (err) =>
|
|
2612
|
+
Effect23.catchTags({
|
|
2613
|
+
ProjectNotLinkedError: (err) => Console11.error(err.message).pipe(Effect23.andThen(Effect23.fail(err))),
|
|
2614
|
+
ProjectLinkReadError: (err) => Console11.error(err.message).pipe(Effect23.andThen(Effect23.fail(err))),
|
|
2615
|
+
ConfigNotFoundError: (err) => Console11.error(err.message).pipe(Effect23.andThen(Effect23.fail(err))),
|
|
2616
|
+
ConfigParseError: (err) => Console11.error(`Config parse error: ${err.message}`).pipe(Effect23.andThen(Effect23.fail(err))),
|
|
2617
|
+
ConfigValidationError: (err) => Effect23.gen(function* () {
|
|
2118
2618
|
for (const e of err.errors) {
|
|
2119
2619
|
yield* Console11.error(e);
|
|
2120
2620
|
}
|
|
2121
2621
|
yield* Console11.error(err.message);
|
|
2122
|
-
}).pipe(
|
|
2123
|
-
DeploymentCreateError: (err) => Console11.error(`Deployment creation failed: ${err.message}`).pipe(
|
|
2124
|
-
ArchiveCreateError: (err) => Console11.error(`Archive creation failed: ${err.message}`).pipe(
|
|
2125
|
-
UploadStartError: (err) => Console11.error(`Upload failed: ${err.message}`).pipe(
|
|
2126
|
-
UploadPartError: (err) => Console11.error(`Upload failed: ${err.message}`).pipe(
|
|
2127
|
-
UploadCompleteError: (err) => Console11.error(`Upload failed: ${err.message}`).pipe(
|
|
2128
|
-
PresignedUrlError: (err) => Console11.error(`Upload failed: ${err.message}`).pipe(
|
|
2129
|
-
DeploymentAuthError: (err) => Console11.error(`Authentication failed: ${err.message}`).pipe(
|
|
2130
|
-
DeploymentFailedError: (err) => Console11.error(err.message).pipe(
|
|
2622
|
+
}).pipe(Effect23.andThen(Effect23.fail(err))),
|
|
2623
|
+
DeploymentCreateError: (err) => Console11.error(`Deployment creation failed: ${err.message}`).pipe(Effect23.andThen(Effect23.fail(err))),
|
|
2624
|
+
ArchiveCreateError: (err) => Console11.error(`Archive creation failed: ${err.message}`).pipe(Effect23.andThen(Effect23.fail(err))),
|
|
2625
|
+
UploadStartError: (err) => Console11.error(`Upload failed: ${err.message}`).pipe(Effect23.andThen(Effect23.fail(err))),
|
|
2626
|
+
UploadPartError: (err) => Console11.error(`Upload failed: ${err.message}`).pipe(Effect23.andThen(Effect23.fail(err))),
|
|
2627
|
+
UploadCompleteError: (err) => Console11.error(`Upload failed: ${err.message}`).pipe(Effect23.andThen(Effect23.fail(err))),
|
|
2628
|
+
PresignedUrlError: (err) => Console11.error(`Upload failed: ${err.message}`).pipe(Effect23.andThen(Effect23.fail(err))),
|
|
2629
|
+
DeploymentAuthError: (err) => Console11.error(`Authentication failed: ${err.message}`).pipe(Effect23.andThen(Effect23.fail(err))),
|
|
2630
|
+
DeploymentFailedError: (err) => Console11.error(err.message).pipe(Effect23.andThen(Effect23.fail(err)))
|
|
2131
2631
|
})
|
|
2132
2632
|
)
|
|
2133
2633
|
);
|
|
2134
|
-
var uploadArchive = (deploymentId, archivePath, fileSize) =>
|
|
2634
|
+
var uploadArchive = (deploymentId, archivePath, fileSize) => Effect23.gen(function* () {
|
|
2135
2635
|
const deploymentApi = yield* DeploymentApiService;
|
|
2136
|
-
const fs = yield*
|
|
2636
|
+
const fs = yield* FileSystem9.FileSystem;
|
|
2137
2637
|
const uploadId = yield* deploymentApi.startMultipartUpload(deploymentId);
|
|
2138
2638
|
const FILE_CHUNK_SIZE = 10 * 1024 * 1024;
|
|
2139
2639
|
const numChunks = Math.ceil(fileSize / FILE_CHUNK_SIZE);
|
|
2140
|
-
yield*
|
|
2141
|
-
const parts = yield*
|
|
2640
|
+
yield* Effect23.log("Starting multipart upload", { uploadId, numChunks, fileSize });
|
|
2641
|
+
const parts = yield* Effect23.all(
|
|
2142
2642
|
Array.from({ length: numChunks }, (_, i) => {
|
|
2143
2643
|
const partNumber = i + 1;
|
|
2144
2644
|
const start = i * FILE_CHUNK_SIZE;
|
|
2145
2645
|
const bytesToRead = partNumber < numChunks ? FILE_CHUNK_SIZE : fileSize - start;
|
|
2146
|
-
return
|
|
2646
|
+
return Effect23.gen(function* () {
|
|
2147
2647
|
const presignedUrl = yield* deploymentApi.getPresignedUrl(deploymentId, uploadId, partNumber);
|
|
2148
2648
|
const chunkData = yield* Stream.runCollect(fs.stream(archivePath, { offset: start, bytesToRead })).pipe(
|
|
2149
|
-
|
|
2649
|
+
Effect23.map((chunks) => {
|
|
2150
2650
|
const arrays = Array.from(chunks);
|
|
2151
2651
|
return Buffer.concat(arrays);
|
|
2152
2652
|
}),
|
|
2153
|
-
|
|
2653
|
+
Effect23.mapError(
|
|
2154
2654
|
(error) => new UploadPartError({
|
|
2155
2655
|
message: `Failed to read chunk ${partNumber}: ${String(error)}`,
|
|
2156
2656
|
partNumber
|
|
2157
2657
|
})
|
|
2158
2658
|
)
|
|
2159
2659
|
);
|
|
2160
|
-
yield*
|
|
2660
|
+
yield* Effect23.log(`Uploading part ${partNumber}/${numChunks}`, { size: chunkData.length });
|
|
2161
2661
|
return yield* deploymentApi.uploadPart(presignedUrl, chunkData, partNumber);
|
|
2162
2662
|
});
|
|
2163
2663
|
}),
|
|
@@ -2165,20 +2665,20 @@ var uploadArchive = (deploymentId, archivePath, fileSize) => Effect20.gen(functi
|
|
|
2165
2665
|
);
|
|
2166
2666
|
yield* deploymentApi.completeUpload(deploymentId, uploadId, parts);
|
|
2167
2667
|
});
|
|
2168
|
-
var pollDeployment = (deploymentId) =>
|
|
2668
|
+
var pollDeployment = (deploymentId) => Effect23.gen(function* () {
|
|
2169
2669
|
const deploymentApi = yield* DeploymentApiService;
|
|
2170
2670
|
let lastStatus = "PENDING";
|
|
2171
2671
|
let logOffset = 0;
|
|
2172
2672
|
yield* Console11.log("Status: Pending");
|
|
2173
|
-
yield*
|
|
2174
|
-
|
|
2175
|
-
const newStatus = yield* deploymentApi.getDeploymentStatus(deploymentId).pipe(
|
|
2673
|
+
yield* Effect23.repeat(
|
|
2674
|
+
Effect23.gen(function* () {
|
|
2675
|
+
const newStatus = yield* deploymentApi.getDeploymentStatus(deploymentId).pipe(Effect23.catchTag("DeploymentStatusError", () => Effect23.succeed(lastStatus)));
|
|
2176
2676
|
if (newStatus !== lastStatus) {
|
|
2177
2677
|
lastStatus = newStatus;
|
|
2178
2678
|
const label = newStatus.charAt(0) + newStatus.slice(1).toLowerCase();
|
|
2179
2679
|
yield* Console11.log(`Status: ${label}`);
|
|
2180
2680
|
}
|
|
2181
|
-
const logsPage = yield* deploymentApi.getLogs(deploymentId, { offset: logOffset, limit: 100 }).pipe(
|
|
2681
|
+
const logsPage = yield* deploymentApi.getLogs(deploymentId, { offset: logOffset, limit: 100 }).pipe(Effect23.catchTag("DeploymentLogsFetchError", () => Effect23.succeed(null)));
|
|
2182
2682
|
if (logsPage) {
|
|
2183
2683
|
for (const logEntry of logsPage.items) {
|
|
2184
2684
|
logOffset++;
|
|
@@ -2186,28 +2686,28 @@ var pollDeployment = (deploymentId) => Effect20.gen(function* () {
|
|
|
2186
2686
|
}
|
|
2187
2687
|
}
|
|
2188
2688
|
if (newStatus === "ACTIVE") {
|
|
2189
|
-
const hostnames = yield* deploymentApi.getHostnames(deploymentId).pipe(
|
|
2689
|
+
const hostnames = yield* deploymentApi.getHostnames(deploymentId).pipe(Effect23.catchTag("DeploymentHostnamesFetchError", () => Effect23.succeed([])));
|
|
2190
2690
|
const active = hostnames.find((h) => h.active) ?? hostnames.at(0);
|
|
2191
2691
|
if (active) {
|
|
2192
2692
|
yield* Console11.log(`Deployed to https://${active.hostname}`);
|
|
2193
2693
|
} else {
|
|
2194
2694
|
yield* Console11.log("Deployment is live.");
|
|
2195
2695
|
}
|
|
2196
|
-
return yield*
|
|
2696
|
+
return yield* Effect23.fail("done");
|
|
2197
2697
|
}
|
|
2198
2698
|
if (newStatus === "FAILED") {
|
|
2199
|
-
return yield*
|
|
2699
|
+
return yield* Effect23.fail(
|
|
2200
2700
|
new DeploymentFailedError({
|
|
2201
2701
|
message: "The deployment failed. Please check the logs for more information."
|
|
2202
2702
|
})
|
|
2203
2703
|
);
|
|
2204
2704
|
}
|
|
2205
2705
|
}),
|
|
2206
|
-
Schedule.addDelay(Schedule.forever, () =>
|
|
2706
|
+
Schedule.addDelay(Schedule.forever, () => Duration2.seconds(1))
|
|
2207
2707
|
).pipe(
|
|
2208
|
-
|
|
2209
|
-
if (err === "done") return
|
|
2210
|
-
return
|
|
2708
|
+
Effect23.catchAll((err) => {
|
|
2709
|
+
if (err === "done") return Effect23.void;
|
|
2710
|
+
return Effect23.fail(err);
|
|
2211
2711
|
})
|
|
2212
2712
|
);
|
|
2213
2713
|
});
|
|
@@ -2223,31 +2723,174 @@ var printLog = (logEntry) => {
|
|
|
2223
2723
|
};
|
|
2224
2724
|
|
|
2225
2725
|
// src/commands/platform/index.ts
|
|
2226
|
-
var platformBase = Command12.make("platform", {}, () =>
|
|
2726
|
+
var platformBase = Command12.make("platform", {}, () => Effect24.void);
|
|
2227
2727
|
var platformCommand = platformBase.pipe(Command12.withSubcommands([deployCommand]));
|
|
2228
2728
|
|
|
2729
|
+
// src/commands/setup/index.ts
|
|
2730
|
+
import { Args as Args4, Command as Command13, Options as Options7, Prompt as Prompt3 } from "@effect/cli";
|
|
2731
|
+
import { Console as Console12, Effect as Effect25, Option as Option13 } from "effect";
|
|
2732
|
+
var fileArg2 = Args4.text({ name: "file" }).pipe(
|
|
2733
|
+
Args4.withDescription("Output file to write (default: .env.local)"),
|
|
2734
|
+
Args4.optional
|
|
2735
|
+
);
|
|
2736
|
+
var appOption6 = Options7.text("app").pipe(
|
|
2737
|
+
Options7.withAlias("a"),
|
|
2738
|
+
Options7.withDescription("Application ID to set up (skips the interactive picker)"),
|
|
2739
|
+
Options7.optional
|
|
2740
|
+
);
|
|
2741
|
+
var orgOption6 = Options7.text("org").pipe(
|
|
2742
|
+
Options7.withAlias("o"),
|
|
2743
|
+
Options7.withDescription("Only list applications belonging to this organization ID when picking"),
|
|
2744
|
+
Options7.optional
|
|
2745
|
+
);
|
|
2746
|
+
var environmentOption2 = Options7.text("environment").pipe(
|
|
2747
|
+
Options7.withAlias("e"),
|
|
2748
|
+
Options7.withDescription("Environment slug to resolve overrides for (e.g. preview)"),
|
|
2749
|
+
Options7.optional
|
|
2750
|
+
);
|
|
2751
|
+
var yesOption2 = Options7.boolean("yes").pipe(
|
|
2752
|
+
Options7.withAlias("y"),
|
|
2753
|
+
Options7.withDescription("Overwrite an existing file without confirmation")
|
|
2754
|
+
);
|
|
2755
|
+
var rotateOption2 = Options7.boolean("rotate").pipe(
|
|
2756
|
+
Options7.withDescription("Force a fresh API key, revoking the previous one")
|
|
2757
|
+
);
|
|
2758
|
+
var setupCommand = Command13.make(
|
|
2759
|
+
"setup",
|
|
2760
|
+
{
|
|
2761
|
+
file: fileArg2,
|
|
2762
|
+
app: appOption6,
|
|
2763
|
+
org: orgOption6,
|
|
2764
|
+
environment: environmentOption2,
|
|
2765
|
+
yes: yesOption2,
|
|
2766
|
+
rotate: rotateOption2
|
|
2767
|
+
},
|
|
2768
|
+
({ file, app, org, environment, yes, rotate }) => Effect25.gen(function* () {
|
|
2769
|
+
const apiClient = yield* ApiClientService;
|
|
2770
|
+
const projectLink = yield* ProjectLinkService;
|
|
2771
|
+
const envFile = yield* EnvFileService;
|
|
2772
|
+
const localCredentials = yield* LocalCredentialsService;
|
|
2773
|
+
const cwd = process.cwd();
|
|
2774
|
+
const existingLink = yield* projectLink.load(cwd);
|
|
2775
|
+
let applicationId;
|
|
2776
|
+
if (Option13.isSome(existingLink)) {
|
|
2777
|
+
applicationId = existingLink.value.applicationId;
|
|
2778
|
+
if (Option13.isSome(app) || Option13.isSome(org)) {
|
|
2779
|
+
yield* Console12.log(
|
|
2780
|
+
`Note: this directory is already linked to ${applicationId}; ignoring --app/--org. Run "gigadrive unlink" first to target a different application.`
|
|
2781
|
+
);
|
|
2782
|
+
}
|
|
2783
|
+
} else {
|
|
2784
|
+
const organizationId = Option13.getOrUndefined(org);
|
|
2785
|
+
const { items: apps } = yield* apiClient.request((client) => client.applications.list({ organizationId }));
|
|
2786
|
+
if (apps.length === 0) {
|
|
2787
|
+
return yield* Effect25.fail(
|
|
2788
|
+
new NoApplicationsFoundError({
|
|
2789
|
+
message: 'No applications found. Create one in the Gigadrive console, then run "gigadrive setup" again.'
|
|
2790
|
+
})
|
|
2791
|
+
);
|
|
2792
|
+
}
|
|
2793
|
+
const explicitAppId = Option13.getOrUndefined(app);
|
|
2794
|
+
let chosen;
|
|
2795
|
+
if (explicitAppId !== void 0) {
|
|
2796
|
+
const match = apps.find((a) => a.id === explicitAppId);
|
|
2797
|
+
if (match === void 0) {
|
|
2798
|
+
return yield* Effect25.fail(
|
|
2799
|
+
new ApplicationNotFoundError({
|
|
2800
|
+
message: `Application "${explicitAppId}" was not found among your applications.`
|
|
2801
|
+
})
|
|
2802
|
+
);
|
|
2803
|
+
}
|
|
2804
|
+
chosen = match;
|
|
2805
|
+
} else if (apps.length === 1) {
|
|
2806
|
+
chosen = apps[0];
|
|
2807
|
+
} else {
|
|
2808
|
+
chosen = yield* Prompt3.run(
|
|
2809
|
+
Prompt3.select({
|
|
2810
|
+
message: "Select an application to set up",
|
|
2811
|
+
choices: apps.map((a) => ({ title: `${a.name} (${a.organization.name})`, value: a }))
|
|
2812
|
+
})
|
|
2813
|
+
);
|
|
2814
|
+
}
|
|
2815
|
+
applicationId = chosen.id;
|
|
2816
|
+
yield* projectLink.save(cwd, { applicationId, organizationId: chosen.organization.id });
|
|
2817
|
+
yield* Console12.log(`Linked this directory to application ${applicationId}.`);
|
|
2818
|
+
}
|
|
2819
|
+
const target = Option13.getOrElse(file, () => ".env.local");
|
|
2820
|
+
const environmentSlug = Option13.getOrUndefined(environment);
|
|
2821
|
+
const { items, omittedSensitive } = yield* apiClient.request(
|
|
2822
|
+
(client) => client.applications.envVars.pull(applicationId, environmentSlug ? { environment: environmentSlug } : void 0)
|
|
2823
|
+
);
|
|
2824
|
+
const existing = yield* envFile.read(target);
|
|
2825
|
+
if (Option13.isSome(existing) && !yes) {
|
|
2826
|
+
const overwrite = yield* Prompt3.run(
|
|
2827
|
+
Prompt3.confirm({ message: `${target} already exists. Overwrite?`, initial: false })
|
|
2828
|
+
);
|
|
2829
|
+
if (!overwrite) {
|
|
2830
|
+
return yield* Console12.log("Cancelled.");
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
const credentialEntries2 = yield* localCredentials.provision({
|
|
2834
|
+
applicationId,
|
|
2835
|
+
rotate,
|
|
2836
|
+
existingContent: Option13.getOrUndefined(existing)
|
|
2837
|
+
});
|
|
2838
|
+
const entries = [...items.map((v) => ({ key: v.key, value: v.value })), ...credentialEntries2];
|
|
2839
|
+
yield* envFile.write(target, entries);
|
|
2840
|
+
const gitignored = yield* envFile.ensureGitignored(cwd, target);
|
|
2841
|
+
yield* Console12.log("\nLocal dev environment ready.");
|
|
2842
|
+
yield* Console12.log(` \u2022 Wrote ${items.length} variable(s) and API credentials to ${target}.`);
|
|
2843
|
+
if (omittedSensitive > 0) {
|
|
2844
|
+
yield* Console12.log(
|
|
2845
|
+
` \u2022 Skipped ${omittedSensitive} sensitive variable(s) (only production/preview hold secrets).`
|
|
2846
|
+
);
|
|
2847
|
+
}
|
|
2848
|
+
if (gitignored) {
|
|
2849
|
+
yield* Console12.log(` \u2022 Added ${target} to .gitignore.`);
|
|
2850
|
+
}
|
|
2851
|
+
yield* Console12.log(" \u2022 Your app can now call the Gigadrive API via the SDK (GIGADRIVE_CLIENT_ID/SECRET).");
|
|
2852
|
+
}).pipe(
|
|
2853
|
+
Effect25.catchTags({
|
|
2854
|
+
// Ctrl+C during a prompt is a deliberate cancel — exit 0.
|
|
2855
|
+
QuitException: () => Console12.log("Cancelled."),
|
|
2856
|
+
NotAuthenticatedError: (err) => Console12.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
|
|
2857
|
+
Effect25.andThen(Effect25.fail(err))
|
|
2858
|
+
),
|
|
2859
|
+
ApiRequestError: (err) => Console12.error(`Setup failed: ${err.message}`).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
2860
|
+
NoApplicationsFoundError: (err) => Console12.error(err.message).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
2861
|
+
ApplicationNotFoundError: (err) => Console12.error(err.message).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
2862
|
+
ProjectLinkReadError: (err) => Console12.error(err.message).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
2863
|
+
ProjectLinkWriteError: (err) => Console12.error(err.message).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
2864
|
+
EnvFileReadError: (err) => Console12.error(err.message).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
2865
|
+
EnvFileWriteError: (err) => Console12.error(err.message).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
2866
|
+
DevCredentialsStoreReadError: (err) => Console12.error(err.message).pipe(Effect25.andThen(Effect25.fail(err))),
|
|
2867
|
+
DevCredentialsStoreWriteError: (err) => Console12.error(err.message).pipe(Effect25.andThen(Effect25.fail(err)))
|
|
2868
|
+
})
|
|
2869
|
+
)
|
|
2870
|
+
);
|
|
2871
|
+
|
|
2229
2872
|
// src/commands/whoami/index.ts
|
|
2230
|
-
import { Command as
|
|
2231
|
-
import { Console as
|
|
2232
|
-
var whoamiCommand =
|
|
2873
|
+
import { Command as Command14 } from "@effect/cli";
|
|
2874
|
+
import { Console as Console13, Effect as Effect26 } from "effect";
|
|
2875
|
+
var whoamiCommand = Command14.make(
|
|
2233
2876
|
"whoami",
|
|
2234
2877
|
{},
|
|
2235
|
-
() =>
|
|
2878
|
+
() => Effect26.gen(function* () {
|
|
2236
2879
|
const auth = yield* AuthService;
|
|
2237
2880
|
const info = yield* auth.getUserInfo;
|
|
2238
|
-
yield*
|
|
2881
|
+
yield* Console13.log(JSON.stringify(info, null, 2));
|
|
2239
2882
|
}).pipe(
|
|
2240
|
-
|
|
2241
|
-
NotAuthenticatedError: () =>
|
|
2242
|
-
UserInfoFetchError: (err) =>
|
|
2243
|
-
OAuthDiscoveryError: (err) =>
|
|
2883
|
+
Effect26.catchTags({
|
|
2884
|
+
NotAuthenticatedError: () => Console13.log('You are not logged in. Run "gigadrive login" to authenticate.'),
|
|
2885
|
+
UserInfoFetchError: (err) => Console13.error(`Failed to retrieve user info: ${err.message}`),
|
|
2886
|
+
OAuthDiscoveryError: (err) => Console13.error(`Failed to retrieve user info: ${err.message}`)
|
|
2244
2887
|
})
|
|
2245
2888
|
)
|
|
2246
2889
|
);
|
|
2247
2890
|
|
|
2248
2891
|
// src/index.ts
|
|
2249
|
-
var gigadrive =
|
|
2250
|
-
|
|
2892
|
+
var gigadrive = Command15.make("gigadrive", {}, () => Effect27.void).pipe(
|
|
2893
|
+
Command15.withSubcommands([
|
|
2251
2894
|
loginCommand,
|
|
2252
2895
|
logoutCommand,
|
|
2253
2896
|
whoamiCommand,
|
|
@@ -2259,10 +2902,11 @@ var gigadrive = Command14.make("gigadrive", {}, () => Effect23.void).pipe(
|
|
|
2259
2902
|
aiCommand,
|
|
2260
2903
|
buildCommand,
|
|
2261
2904
|
debugCommand,
|
|
2262
|
-
platformCommand
|
|
2905
|
+
platformCommand,
|
|
2906
|
+
setupCommand
|
|
2263
2907
|
])
|
|
2264
2908
|
);
|
|
2265
|
-
var cli =
|
|
2909
|
+
var cli = Command15.run(gigadrive, {
|
|
2266
2910
|
name: "Gigadrive CLI",
|
|
2267
2911
|
version: package_default.version
|
|
2268
2912
|
});
|
|
@@ -2273,13 +2917,16 @@ var BaseServices = Layer.mergeAll(
|
|
|
2273
2917
|
ArchiveService.Default,
|
|
2274
2918
|
NetworkConfigLive2,
|
|
2275
2919
|
ProjectConfigService.Default,
|
|
2276
|
-
ProjectLinkService.Default
|
|
2920
|
+
ProjectLinkService.Default,
|
|
2921
|
+
EnvFileService.Default,
|
|
2922
|
+
DevCredentialsStore.Default
|
|
2277
2923
|
).pipe(Layer.provideMerge(AuthService.Default));
|
|
2278
2924
|
var ApiClientLive = Layer.provide(ApiClientService.Default, BaseServices);
|
|
2279
2925
|
var DeploymentApiLive = Layer.provide(DeploymentApiService.Default, BaseServices);
|
|
2280
|
-
var
|
|
2926
|
+
var LocalCredentialsLive = Layer.provide(LocalCredentialsService.Default, BaseServices);
|
|
2927
|
+
var ServicesLive = Layer.mergeAll(BaseServices, ApiClientLive, DeploymentApiLive, LocalCredentialsLive);
|
|
2281
2928
|
var AppLive = Layer.mergeAll(ServicesLive, Logger.minimumLogLevel(LogLevel.Info)).pipe(
|
|
2282
2929
|
Layer.provideMerge(NodeContext.layer)
|
|
2283
2930
|
);
|
|
2284
|
-
NodeRuntime.runMain(
|
|
2931
|
+
NodeRuntime.runMain(Effect27.provide(cli(process.argv), AppLive));
|
|
2285
2932
|
//# sourceMappingURL=index.mjs.map
|