gigadrive 2.1.3 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,16 +1,68 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command8 } from "@effect/cli";
4
+ import { Command as Command14 } 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 Effect15, Layer, LogLevel, Logger } from "effect";
7
+ import { Effect as Effect23, Layer, LogLevel, Logger } from "effect";
8
8
 
9
- // src/commands/build/index.ts
10
- import { Command } from "@effect/cli";
11
- import { FileSystem as FileSystem2, Path as Path2 } from "@effect/platform";
12
- import { exec as exec2 } from "@gigadrive/build-utils";
13
- import { Console, Effect as Effect2 } from "effect";
9
+ // package.json
10
+ var package_default = {
11
+ name: "gigadrive",
12
+ version: "2.2.0",
13
+ repository: {
14
+ type: "git",
15
+ url: "https://github.com/Gigadrive/sdk.git",
16
+ directory: "packages/cli"
17
+ },
18
+ license: "Apache-2.0",
19
+ author: "Gigadrive <business@gigadrivegroup.com> (https://gigadrive.de)",
20
+ type: "module",
21
+ module: "src/index.ts",
22
+ bin: {
23
+ gigadrive: "dist/index.mjs",
24
+ gn: "dist/index.mjs"
25
+ },
26
+ files: [
27
+ "dist"
28
+ ],
29
+ scripts: {
30
+ build: "tsup",
31
+ clean: "rm -rf dist",
32
+ dev: "pnpm build && node dist/index.mjs",
33
+ typecheck: "tsc --noEmit"
34
+ },
35
+ dependencies: {
36
+ "@effect/cli": "^0.75.0",
37
+ "@effect/platform": "^0.96.0",
38
+ "@effect/platform-node": "^0.106.0",
39
+ "@gigadrive/build-utils": "workspace:*",
40
+ "@gigadrive/commons": "workspace:*",
41
+ "@gigadrive/network-config": "workspace:*",
42
+ "@gigadrive/sdk": "workspace:*",
43
+ archiver: "^7.0.1",
44
+ effect: "^3.19.17",
45
+ ignore: "^7.0.5",
46
+ open: "^10.2.0"
47
+ },
48
+ devDependencies: {
49
+ "@effect/language-service": "^0.84.0",
50
+ "@types/archiver": "^6.0.3",
51
+ esbuild: "^0.28.0",
52
+ typescript: "catalog:"
53
+ },
54
+ engines: {
55
+ node: ">=20.0.0"
56
+ },
57
+ publishConfig: {
58
+ access: "public"
59
+ }
60
+ };
61
+
62
+ // src/commands/ai/index.ts
63
+ import { Args, Command, Options } from "@effect/cli";
64
+ import { FileSystem as FileSystem3 } from "@effect/platform";
65
+ import { Console as Console2, Effect as Effect6, Option as Option4 } from "effect";
14
66
 
15
67
  // src/errors.ts
16
68
  import { Schema } from "effect";
@@ -52,11 +104,57 @@ var UserInfoFetchError = class extends Schema.TaggedError()("UserInfoFetchError"
52
104
  statusCode: Schema.optional(Schema.Number)
53
105
  }) {
54
106
  };
107
+ var ApiRequestError = class extends Schema.TaggedError()("ApiRequestError", {
108
+ message: Schema.String,
109
+ statusCode: Schema.optional(Schema.Number),
110
+ code: Schema.optional(Schema.String)
111
+ }) {
112
+ };
113
+ var InvalidEnvVarFormatError = class extends Schema.TaggedError()(
114
+ "InvalidEnvVarFormatError",
115
+ {
116
+ message: Schema.String
117
+ }
118
+ ) {
119
+ };
120
+ var EnvVarNotFoundError = class extends Schema.TaggedError()("EnvVarNotFoundError", {
121
+ message: Schema.String
122
+ }) {
123
+ };
124
+ var NoApplicationsFoundError = class extends Schema.TaggedError()(
125
+ "NoApplicationsFoundError",
126
+ {
127
+ message: Schema.String
128
+ }
129
+ ) {
130
+ };
131
+ var ApplicationNotFoundError = class extends Schema.TaggedError()(
132
+ "ApplicationNotFoundError",
133
+ {
134
+ message: Schema.String
135
+ }
136
+ ) {
137
+ };
55
138
  var ConfigNotFoundError = class extends Schema.TaggedError()("ConfigNotFoundError", {
56
139
  message: Schema.String,
57
140
  directory: Schema.String
58
141
  }) {
59
142
  };
143
+ var ProjectNotLinkedError = class extends Schema.TaggedError()("ProjectNotLinkedError", {
144
+ message: Schema.String,
145
+ directory: Schema.String
146
+ }) {
147
+ };
148
+ var ProjectLinkReadError = class extends Schema.TaggedError()("ProjectLinkReadError", {
149
+ message: Schema.String,
150
+ cause: Schema.optional(Schema.String)
151
+ }) {
152
+ };
153
+ var ProjectLinkWriteError = class extends Schema.TaggedError()("ProjectLinkWriteError", {
154
+ message: Schema.String,
155
+ cause: Schema.optional(Schema.String)
156
+ }) {
157
+ };
60
158
  var ConfigParseError = class extends Schema.TaggedError()("ConfigParseError", {
61
159
  message: Schema.String,
62
160
  cause: Schema.optional(Schema.String)
@@ -137,6 +235,13 @@ var DeploymentLogsFetchError = class extends Schema.TaggedError()(
137
235
  }
138
236
  ) {
139
237
  };
238
+ var DeploymentHostnamesFetchError = class extends Schema.TaggedError()(
239
+ "DeploymentHostnamesFetchError",
240
+ {
241
+ message: Schema.String
242
+ }
243
+ ) {
244
+ };
140
245
  var ArchiveCreateError = class extends Schema.TaggedError()("ArchiveCreateError", {
141
246
  message: Schema.String,
142
247
  cause: Schema.optional(Schema.String)
@@ -151,835 +256,1575 @@ var DeploymentFailedError = class extends Schema.TaggedError()("DeploymentFailed
151
256
  }) {
152
257
  };
153
258
 
154
- // src/services/package-manager.ts
259
+ // src/services/api-client.ts
260
+ import { ApiError, GigadriveClient } from "@gigadrive/sdk";
261
+ import { Config as Config2, Effect as Effect4 } from "effect";
262
+
263
+ // src/services/auth.ts
264
+ import { Console, Effect as Effect3, Option as Option2 } from "effect";
265
+ import * as crypto from "crypto";
266
+ import * as http from "http";
267
+ import open from "open";
268
+
269
+ // src/services/auth-storage.ts
155
270
  import { FileSystem, Path } from "@effect/platform";
156
- import { exec } from "@gigadrive/build-utils";
157
- import { Effect } from "effect";
158
- var PackageManagerService = class extends Effect.Service()("PackageManagerService", {
271
+ import { Effect, Option, Schema as Schema3 } from "effect";
272
+ import * as os from "os";
273
+
274
+ // src/domain.ts
275
+ import { Schema as Schema2 } from "effect";
276
+ var DeploymentId = Schema2.String.pipe(Schema2.brand("@Gigadrive/DeploymentId"));
277
+ var ApplicationId = Schema2.String.pipe(Schema2.brand("@Gigadrive/ApplicationId"));
278
+ var UploadId = Schema2.String.pipe(Schema2.brand("@Gigadrive/UploadId"));
279
+ var PackageManager = Schema2.Literal("npm", "yarn", "pnpm", "bun");
280
+ var OAuthTokens = Schema2.Struct({
281
+ access_token: Schema2.String,
282
+ refresh_token: Schema2.optional(Schema2.String),
283
+ expires_in: Schema2.optional(Schema2.Number),
284
+ token_type: Schema2.optional(Schema2.String)
285
+ });
286
+ var OAuthClientConfig = Schema2.Struct({
287
+ clientId: Schema2.String,
288
+ issuer: Schema2.String,
289
+ authorizeUrl: Schema2.String,
290
+ tokenUrl: Schema2.String,
291
+ scope: Schema2.String,
292
+ userinfoUrl: Schema2.String
293
+ });
294
+ var StoredAuthData = Schema2.Struct({
295
+ refreshToken: Schema2.String,
296
+ accessToken: Schema2.optional(Schema2.String),
297
+ tokenExpirationTime: Schema2.optional(Schema2.Number)
298
+ });
299
+ var ProjectLink = Schema2.Struct({
300
+ applicationId: Schema2.String,
301
+ organizationId: Schema2.optional(Schema2.String)
302
+ });
303
+
304
+ // src/services/auth-storage.ts
305
+ var AuthStorageService = class extends Effect.Service()("AuthStorageService", {
159
306
  accessors: true,
160
307
  effect: Effect.gen(function* () {
161
308
  const fs = yield* FileSystem.FileSystem;
162
309
  const pathService = yield* Path.Path;
163
- const detect = Effect.fn("PackageManagerService.detect")(function* (cwd) {
164
- yield* Effect.annotateCurrentSpan("cwd", cwd);
165
- const packageJsonPath = pathService.join(cwd, "package.json");
166
- const exists = yield* fs.exists(packageJsonPath).pipe(
310
+ const homeDir = os.homedir();
311
+ const authDir = pathService.join(homeDir, ".gigadrive");
312
+ const authFile = pathService.join(authDir, "auth.json");
313
+ const ensureDirectory = Effect.gen(function* () {
314
+ yield* fs.makeDirectory(authDir, { recursive: true }).pipe(
167
315
  Effect.mapError(
168
- () => new PackageManagerNotFoundError({ message: "Failed to check for package.json", directory: cwd })
316
+ (error) => new AuthStorageWriteError({
317
+ message: "Failed to create auth directory",
318
+ cause: String(error)
319
+ })
169
320
  )
170
321
  );
171
- if (!exists) {
172
- return yield* Effect.fail(
173
- new PackageManagerNotFoundError({
174
- message: "No package.json found",
175
- directory: cwd
322
+ yield* fs.chmod(authDir, 448).pipe(
323
+ Effect.mapError(
324
+ (error) => new AuthStorageWriteError({
325
+ message: "Failed to set auth directory permissions",
326
+ cause: String(error)
327
+ })
328
+ )
329
+ );
330
+ });
331
+ const load = Effect.gen(function* () {
332
+ const exists = yield* fs.exists(authFile).pipe(Effect.mapError(() => new AuthStorageReadError({ message: "Failed to check auth file existence" })));
333
+ if (!exists) return Option.none();
334
+ const data = yield* fs.readFileString(authFile, "utf8").pipe(Effect.mapError(() => new AuthStorageReadError({ message: "Failed to read auth file" })));
335
+ const json = yield* Effect.try({
336
+ try: () => JSON.parse(data),
337
+ catch: () => new AuthStorageReadError({ message: "Auth file contains invalid JSON" })
338
+ });
339
+ const authData = yield* Schema3.decodeUnknown(StoredAuthData)(json).pipe(
340
+ Effect.mapError(() => new AuthStorageReadError({ message: "Auth file has invalid schema" }))
341
+ );
342
+ return Option.some(authData);
343
+ });
344
+ const save = Effect.fn("AuthStorageService.save")(function* (authData) {
345
+ yield* ensureDirectory;
346
+ yield* fs.writeFileString(authFile, JSON.stringify(authData, null, 2), { mode: 384 }).pipe(
347
+ Effect.mapError(
348
+ (error) => new AuthStorageWriteError({
349
+ message: "Failed to write auth file",
350
+ cause: String(error)
351
+ })
352
+ )
353
+ );
354
+ yield* Effect.log("Auth data saved to local storage");
355
+ });
356
+ const remove = Effect.gen(function* () {
357
+ yield* fs.remove(authFile).pipe(
358
+ Effect.mapError((error) => {
359
+ const cause = error instanceof Error ? error : error.error;
360
+ const code = cause && typeof cause === "object" && "code" in cause ? cause.code : "";
361
+ return { isNotFound: code === "ENOENT" || String(error).includes("ENOENT"), original: error };
362
+ }),
363
+ Effect.catchAll(
364
+ (mapped) => mapped.isNotFound ? Effect.void : Effect.fail(
365
+ new AuthStorageWriteError({
366
+ message: "Failed to remove auth file",
367
+ cause: String(mapped.original)
368
+ })
369
+ )
370
+ )
371
+ );
372
+ yield* Effect.log("Auth data removed from local storage");
373
+ });
374
+ return { load, save, remove };
375
+ })
376
+ }) {
377
+ };
378
+
379
+ // src/services/oauth-config.ts
380
+ import { Config, Effect as Effect2, Schema as Schema4 } from "effect";
381
+ var OAuthEnvConfig = Config.all({
382
+ issuerUrl: Config.string("GIGADRIVE_NETWORK_OAUTH_ISSUER_URL").pipe(Config.withDefault("https://idp.gigadrive.de")),
383
+ clientId: Config.string("GIGADRIVE_NETWORK_OAUTH_CLIENT_ID").pipe(Config.withDefault("todo_add_client_id"))
384
+ });
385
+ var OpenIdDiscoveryDocument = Schema4.Struct({
386
+ issuer: Schema4.optional(Schema4.String),
387
+ authorization_endpoint: Schema4.optional(Schema4.String),
388
+ token_endpoint: Schema4.optional(Schema4.String),
389
+ userinfo_endpoint: Schema4.optional(Schema4.String)
390
+ });
391
+ var DEFAULT_SCOPE = "offline_access openid profile email";
392
+ var OAuthConfigService = class extends Effect2.Service()("OAuthConfigService", {
393
+ accessors: true,
394
+ effect: Effect2.gen(function* () {
395
+ const envConfig = yield* OAuthEnvConfig;
396
+ let cached = null;
397
+ const getConfig = Effect2.gen(function* () {
398
+ if (cached) return cached;
399
+ const issuerBase = envConfig.issuerUrl.replace(/\/$/, "");
400
+ const discoveryUrl = `${issuerBase}/.well-known/openid-configuration`;
401
+ yield* Effect2.log("Fetching OIDC discovery document", { discoveryUrl });
402
+ const response = yield* Effect2.tryPromise({
403
+ try: (signal) => fetch(discoveryUrl, { signal }),
404
+ catch: (error) => new OAuthDiscoveryError({
405
+ message: "Failed to fetch OIDC discovery document",
406
+ cause: error instanceof Error ? error.message : String(error)
407
+ })
408
+ });
409
+ if (!response.ok) {
410
+ return yield* Effect2.fail(
411
+ new OAuthDiscoveryError({
412
+ message: `OIDC discovery returned ${response.status} ${response.statusText}`
176
413
  })
177
414
  );
178
415
  }
179
- const lockChecks = [
180
- { file: "yarn.lock", pm: "yarn" },
181
- { file: "pnpm-lock.yaml", pm: "pnpm" },
182
- { file: "bun.lockb", pm: "bun" },
183
- { file: "package-lock.json", pm: "npm" }
184
- ];
185
- for (const { file, pm: pm2 } of lockChecks) {
186
- const lockExists = yield* fs.exists(pathService.join(cwd, file)).pipe(Effect.catchAll(() => Effect.succeed(false)));
187
- if (lockExists) {
188
- yield* Effect.log("Package manager detected from lock file", { packageManager: pm2, lockFile: file });
189
- return pm2;
190
- }
191
- }
192
- const bunAvailable = yield* Effect.tryPromise({
193
- try: () => exec({ command: "bun --version", cwd }),
194
- catch: () => new ExecError({ message: "bun not found", command: "bun --version" })
195
- }).pipe(
196
- Effect.as(true),
197
- Effect.catchTag("ExecError", () => Effect.succeed(false))
416
+ const json = yield* Effect2.tryPromise({
417
+ try: () => response.json(),
418
+ catch: () => new OAuthDiscoveryError({
419
+ message: "Failed to parse OIDC discovery JSON"
420
+ })
421
+ });
422
+ const decoded = yield* Schema4.decodeUnknown(OpenIdDiscoveryDocument)(json).pipe(
423
+ Effect2.mapError(
424
+ () => new OAuthDiscoveryError({
425
+ message: "Invalid OIDC discovery document schema"
426
+ })
427
+ )
198
428
  );
199
- const pm = bunAvailable ? "bun" : "npm";
200
- yield* Effect.log("Package manager detected via fallback", { packageManager: pm });
201
- return pm;
429
+ if (!decoded.authorization_endpoint || !decoded.token_endpoint || !decoded.userinfo_endpoint) {
430
+ return yield* Effect2.fail(
431
+ new OAuthDiscoveryError({
432
+ message: "OIDC discovery returned incomplete endpoints"
433
+ })
434
+ );
435
+ }
436
+ const config = {
437
+ clientId: envConfig.clientId,
438
+ issuer: decoded.issuer ?? issuerBase,
439
+ authorizeUrl: decoded.authorization_endpoint,
440
+ tokenUrl: decoded.token_endpoint,
441
+ scope: DEFAULT_SCOPE,
442
+ userinfoUrl: decoded.userinfo_endpoint
443
+ };
444
+ cached = config;
445
+ yield* Effect2.log("OIDC discovery completed", { issuer: config.issuer });
446
+ return config;
202
447
  });
203
- const installCommand = (pm) => ({
204
- bun: "bun install",
205
- npm: "npm install",
206
- pnpm: "pnpm install",
207
- yarn: "yarn install"
208
- })[pm];
209
- const buildCommand2 = (pm) => ({
210
- bun: "bun run build",
211
- npm: "npm run build",
212
- pnpm: "pnpm build",
213
- yarn: "yarn build"
214
- })[pm];
215
- return { detect, installCommand, buildCommand: buildCommand2 };
448
+ return { getConfig };
216
449
  })
217
450
  }) {
218
451
  };
219
452
 
220
- // src/commands/build/index.ts
221
- var buildCommand = Command.make(
222
- "build",
223
- {},
224
- () => Effect2.gen(function* () {
225
- const pmService = yield* PackageManagerService;
226
- const fs = yield* FileSystem2.FileSystem;
227
- const pathService = yield* Path2.Path;
228
- const cwd = process.cwd();
229
- yield* Effect2.log("Starting build", { cwd });
230
- const packageJsonPath = pathService.join(cwd, "package.json");
231
- const packageJsonExists = yield* fs.exists(packageJsonPath).pipe(
232
- Effect2.mapError(
233
- () => new PackageJsonNotFoundError({ message: "Failed to check for package.json", directory: cwd })
234
- )
235
- );
236
- if (!packageJsonExists) {
237
- return yield* Effect2.fail(new PackageJsonNotFoundError({ message: "No package.json found", directory: cwd }));
238
- }
239
- const packageJsonContent = yield* fs.readFileString(packageJsonPath, "utf-8").pipe(
240
- Effect2.mapError(() => new PackageJsonNotFoundError({ message: "Failed to read package.json", directory: cwd }))
241
- );
242
- const packageJson = yield* Effect2.try({
243
- try: () => JSON.parse(packageJsonContent),
244
- catch: (error) => new PackageJsonParseError({
245
- message: "Failed to parse package.json",
246
- directory: cwd,
247
- cause: error instanceof Error ? error.message : String(error)
248
- })
249
- });
250
- if (!packageJson.scripts?.build) {
251
- return yield* Effect2.fail(new BuildScriptNotFoundError({ message: "No build script found in package.json" }));
252
- }
253
- const pm = yield* pmService.detect(cwd);
254
- const buildEnv = {
255
- ...process.env,
256
- NODE_ENV: "production",
257
- NEBULA: "1",
258
- VERCEL: "1",
259
- NOW_BUILDER: "1"
260
- };
261
- const installCmd = pmService.installCommand(pm);
262
- yield* Console.log(`Running: ${installCmd}`);
263
- yield* Effect2.tryPromise({
264
- try: () => exec2({
265
- command: installCmd,
266
- cwd,
267
- env: buildEnv,
268
- onOutput: (chunk) => {
269
- Console.log(chunk).pipe(Effect2.runSync);
270
- }
271
- }),
272
- catch: (error) => new ExecError({
273
- message: "Install failed",
274
- command: installCmd,
275
- cause: error instanceof Error ? error.message : String(error)
276
- })
277
- });
278
- const buildCmd = pmService.buildCommand(pm);
279
- yield* Console.log(`Running: ${buildCmd}`);
280
- yield* Effect2.tryPromise({
281
- try: () => exec2({
282
- command: buildCmd,
283
- cwd,
284
- env: buildEnv,
285
- onOutput: (chunk) => {
286
- Console.log(chunk).pipe(Effect2.runSync);
287
- }
288
- }),
289
- catch: (error) => new ExecError({
290
- message: "Build failed",
291
- command: buildCmd,
292
- cause: error instanceof Error ? error.message : String(error)
293
- })
294
- });
295
- yield* Console.log("Build completed successfully.");
296
- }).pipe(
297
- Effect2.catchTags({
298
- PackageJsonNotFoundError: (err) => Console.error(err.message).pipe(Effect2.andThen(Effect2.fail(err))),
299
- PackageJsonParseError: (err) => Console.error(err.message).pipe(Effect2.andThen(Effect2.fail(err))),
300
- BuildScriptNotFoundError: (err) => Console.error(err.message).pipe(Effect2.andThen(Effect2.fail(err))),
301
- PackageManagerNotFoundError: (err) => Console.error(err.message).pipe(Effect2.andThen(Effect2.fail(err))),
302
- ExecError: (err) => Console.error(`${err.message}: ${err.cause ?? err.command}`).pipe(Effect2.andThen(Effect2.fail(err)))
303
- })
304
- )
305
- );
306
-
307
- // src/commands/debug/index.ts
308
- import { Command as Command3 } from "@effect/cli";
309
- import { Effect as Effect5 } from "effect";
310
-
311
- // src/commands/debug/config/index.ts
312
- import { Command as Command2 } from "@effect/cli";
313
- import { Console as Console2, Effect as Effect4 } from "effect";
314
-
315
- // src/services/project-config.ts
316
- import {
317
- detectFramework,
318
- mergeWithFrameworkDefaults,
319
- NetworkConfigLive,
320
- parseConfig,
321
- parseConfigRaw,
322
- postProcessConfig,
323
- RawConfigReader
324
- } from "@gigadrive/network-config";
325
- import { Effect as Effect3 } from "effect";
326
- var wrapParseErrors = (effect) => effect.pipe(
327
- Effect3.catchTags({
328
- ConfigFileNotFoundError: (e) => Effect3.fail(new ConfigParseError({ message: e.message, cause: e.filePath })),
329
- ConfigFileEmptyError: (e) => Effect3.fail(new ConfigParseError({ message: e.message, cause: e.filePath })),
330
- ConfigFileParseError: (e) => Effect3.fail(new ConfigParseError({ message: e.message, cause: e.cause })),
331
- ConfigVersionError: (e) => Effect3.fail(new ConfigParseError({ message: e.message, cause: e.filePath })),
332
- ConfigSchemaValidationError: (e) => Effect3.fail(new ConfigParseError({ message: e.message, cause: e.validationErrors.join(", ") })),
333
- FunctionConfigError: (e) => Effect3.fail(new ConfigParseError({ message: e.message, cause: e.functionPath }))
334
- })
335
- );
336
- var ProjectConfigService = class extends Effect3.Service()("ProjectConfigService", {
453
+ // src/services/auth.ts
454
+ var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\//g, "&#x2F;");
455
+ var base64URLEncode = (buffer) => buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
456
+ var generatePKCE = () => {
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
+ };
462
+ var AuthService = class extends Effect3.Service()("AuthService", {
337
463
  accessors: true,
338
- dependencies: [NetworkConfigLive],
464
+ dependencies: [OAuthConfigService.Default, AuthStorageService.Default],
339
465
  effect: Effect3.gen(function* () {
340
- const rawReader = yield* RawConfigReader;
341
- const resolve = Effect3.fn("ProjectConfigService.resolve")(function* (cwd) {
342
- yield* Effect3.annotateCurrentSpan("cwd", cwd);
343
- yield* Effect3.log("Resolving project config", { cwd });
344
- const configPath = yield* rawReader.findConfig(cwd);
345
- const detection = yield* detectFramework(cwd).pipe(
346
- Effect3.catchTag("FrameworkNotDetectedError", () => Effect3.succeed(null)),
347
- Effect3.catchTag(
348
- "ManifestReadError",
349
- (e) => Effect3.fail(new ConfigParseError({ message: `Framework detection failed: ${e.message}`, cause: e.filePath }))
350
- ),
351
- Effect3.catchTag(
352
- "ManifestParseError",
353
- (e) => Effect3.fail(new ConfigParseError({ message: `Framework detection failed: ${e.message}`, cause: e.filePath }))
354
- )
355
- );
356
- if (detection) {
357
- yield* Effect3.log("Framework auto-detected", {
358
- framework: detection.framework.name,
359
- slug: detection.framework.slug
466
+ const oauthConfig = yield* OAuthConfigService;
467
+ const storage = yield* AuthStorageService;
468
+ let accessToken = null;
469
+ let tokenExpirationTime = 0;
470
+ const logout = Effect3.gen(function* () {
471
+ accessToken = null;
472
+ tokenExpirationTime = 0;
473
+ yield* storage.remove;
474
+ yield* Effect3.log("Logged out");
475
+ });
476
+ const exchangeCodeForTokens = (config, authCode, redirectUri, codeVerifier) => Effect3.gen(function* () {
477
+ const response = yield* Effect3.tryPromise({
478
+ try: () => fetch(config.tokenUrl, {
479
+ method: "POST",
480
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
481
+ body: new URLSearchParams({
482
+ grant_type: "authorization_code",
483
+ client_id: config.clientId,
484
+ code: authCode,
485
+ redirect_uri: redirectUri,
486
+ code_verifier: codeVerifier
487
+ }).toString()
488
+ }),
489
+ catch: (error) => new TokenExchangeError({
490
+ message: `Token exchange request failed: ${error instanceof Error ? error.message : String(error)}`
491
+ })
492
+ });
493
+ if (!response.ok) {
494
+ const body = yield* Effect3.tryPromise({
495
+ try: () => response.text(),
496
+ catch: () => new TokenExchangeError({ message: "Failed to read token exchange error response" })
360
497
  });
361
- }
362
- let config;
363
- let resolvedConfigPath = null;
364
- let framework;
365
- if (configPath && detection) {
366
- yield* Effect3.log("Config file found, merging with framework defaults", { configPath });
367
- resolvedConfigPath = configPath;
368
- const userConfig = yield* wrapParseErrors(parseConfigRaw(configPath, cwd));
369
- const merged = yield* mergeWithFrameworkDefaults(userConfig, detection.config);
370
- config = yield* wrapParseErrors(postProcessConfig(merged, cwd));
371
- framework = { name: detection.framework.name, slug: detection.framework.slug };
372
- } else if (configPath) {
373
- yield* Effect3.log("Config file found", { configPath });
374
- resolvedConfigPath = configPath;
375
- config = yield* wrapParseErrors(parseConfig(configPath, cwd));
376
- } else if (detection) {
377
- config = yield* wrapParseErrors(postProcessConfig(detection.config, cwd));
378
- framework = { name: detection.framework.name, slug: detection.framework.slug };
379
- } else {
380
498
  return yield* Effect3.fail(
381
- new ConfigNotFoundError({
382
- message: "No config file found and no framework detected.",
383
- directory: cwd
499
+ new TokenExchangeError({
500
+ message: `Token exchange failed: ${response.status} ${response.statusText} - ${body}`,
501
+ statusCode: response.status
384
502
  })
385
503
  );
386
504
  }
387
- for (const warning of config.warnings) {
388
- yield* Effect3.logWarning(warning);
505
+ const tokens = yield* Effect3.tryPromise({
506
+ try: () => response.json(),
507
+ catch: () => new TokenExchangeError({ message: "Failed to parse token exchange response" })
508
+ });
509
+ return tokens;
510
+ });
511
+ const startCallbackServer = (config, codeChallenge, state) => Effect3.async((resume) => {
512
+ const server = http.createServer();
513
+ const redirectPath = "/callback";
514
+ server.listen(0, "127.0.0.1", () => {
515
+ const address = server.address();
516
+ if (!address || typeof address !== "object") {
517
+ server.close(() => {
518
+ resume(Effect3.fail(new LoginFlowError({ message: "Failed to determine callback server address" })));
519
+ });
520
+ return;
521
+ }
522
+ const port = address.port;
523
+ const redirectUri = `http://127.0.0.1:${port}${redirectPath}`;
524
+ const authUrl = new URL(config.authorizeUrl);
525
+ authUrl.searchParams.append("client_id", config.clientId);
526
+ authUrl.searchParams.append("redirect_uri", redirectUri);
527
+ authUrl.searchParams.append("response_type", "code");
528
+ authUrl.searchParams.append("scope", config.scope);
529
+ authUrl.searchParams.append("code_challenge", codeChallenge);
530
+ authUrl.searchParams.append("code_challenge_method", "S256");
531
+ authUrl.searchParams.append("state", state);
532
+ Console.log(`Please open this URL in your browser to log in:
533
+ ${authUrl.toString()}
534
+ `).pipe(Effect3.runSync);
535
+ Console.log("Waiting for login callback...").pipe(Effect3.runSync);
536
+ void open(authUrl.toString());
537
+ server.on("request", (req, res) => {
538
+ const reqUrl = new URL(req.url || "", redirectUri);
539
+ if (reqUrl.pathname !== "/callback") {
540
+ res.writeHead(404).end("Not Found");
541
+ return;
542
+ }
543
+ const authCode = reqUrl.searchParams.get("code");
544
+ const receivedState = reqUrl.searchParams.get("state");
545
+ const oauthError = reqUrl.searchParams.get("error");
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>"
557
+ );
558
+ server.close();
559
+ resume(Effect3.fail(new LoginFlowError({ message: "State mismatch" })));
560
+ return;
561
+ }
562
+ if (authCode) {
563
+ res.end("<h1>Login Successful!</h1><p>You can close this tab.</p>");
564
+ server.close();
565
+ resume(Effect3.succeed({ authCode, redirectUri }));
566
+ } else {
567
+ res.end("<h1>Login Failed!</h1><p>No authorization code received.</p><p>You can close this tab.</p>");
568
+ server.close();
569
+ resume(Effect3.fail(new LoginFlowError({ message: "No authorization code received" })));
570
+ }
571
+ });
572
+ server.on("error", (err) => {
573
+ server.close();
574
+ resume(Effect3.fail(new LoginFlowError({ message: `Local server error: ${err.message}` })));
575
+ });
576
+ });
577
+ });
578
+ const login = Effect3.gen(function* () {
579
+ yield* logout;
580
+ const { codeVerifier, codeChallenge } = generatePKCE();
581
+ const state = crypto.randomBytes(16).toString("hex");
582
+ const config = yield* oauthConfig.getConfig;
583
+ const { authCode, redirectUri } = yield* startCallbackServer(config, codeChallenge, state);
584
+ const tokens = yield* exchangeCodeForTokens(config, authCode, redirectUri, codeVerifier);
585
+ if (!tokens.refresh_token) {
586
+ return yield* Effect3.fail(
587
+ new TokenExchangeError({ message: "No refresh token received from identity provider" })
588
+ );
389
589
  }
390
- if (config.errors.length > 0) {
590
+ accessToken = tokens.access_token;
591
+ tokenExpirationTime = Date.now() + (tokens.expires_in || 3600) * 1e3;
592
+ yield* storage.save({
593
+ refreshToken: tokens.refresh_token,
594
+ accessToken,
595
+ tokenExpirationTime
596
+ });
597
+ yield* Effect3.log("Login successful");
598
+ return true;
599
+ });
600
+ const refreshAccessToken = Effect3.gen(function* () {
601
+ const stored = yield* storage.load.pipe(
602
+ Effect3.catchTag("AuthStorageReadError", (err) => Effect3.fail(new TokenRefreshError({ message: err.message })))
603
+ );
604
+ const config = yield* oauthConfig.getConfig.pipe(
605
+ Effect3.catchTag("OAuthDiscoveryError", (err) => Effect3.fail(new TokenRefreshError({ message: err.message })))
606
+ );
607
+ const authData = yield* Option2.match(stored, {
608
+ onNone: () => Effect3.fail(new TokenRefreshError({ message: "No refresh token available. Please log in." })),
609
+ onSome: (data) => data.refreshToken ? Effect3.succeed(data) : Effect3.fail(new TokenRefreshError({ message: "No refresh token available. Please log in." }))
610
+ });
611
+ const response = yield* Effect3.tryPromise({
612
+ try: () => fetch(config.tokenUrl, {
613
+ method: "POST",
614
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
615
+ body: new URLSearchParams({
616
+ grant_type: "refresh_token",
617
+ client_id: config.clientId,
618
+ refresh_token: authData.refreshToken
619
+ }).toString()
620
+ }),
621
+ catch: (error) => new TokenRefreshError({
622
+ message: `Token refresh request failed: ${error instanceof Error ? error.message : String(error)}`
623
+ })
624
+ });
625
+ if (!response.ok) {
626
+ if (response.status === 400) {
627
+ yield* logout.pipe(Effect3.catchAll(() => Effect3.void));
628
+ return yield* Effect3.fail(
629
+ new TokenRefreshError({
630
+ message: "Refresh token is invalid or expired. Please log in again.",
631
+ statusCode: response.status
632
+ })
633
+ );
634
+ }
635
+ const body = yield* Effect3.tryPromise({
636
+ try: () => response.text(),
637
+ catch: () => new TokenRefreshError({ message: "Failed to read refresh error response" })
638
+ });
391
639
  return yield* Effect3.fail(
392
- new ConfigValidationError({
393
- message: "Config file has validation errors",
394
- errors: config.errors
640
+ new TokenRefreshError({
641
+ message: `Token refresh failed: ${response.status} ${response.statusText} - ${body}`,
642
+ statusCode: response.status
395
643
  })
396
644
  );
397
645
  }
398
- return { config, configPath: resolvedConfigPath, framework };
646
+ const tokens = yield* Effect3.tryPromise({
647
+ try: () => response.json(),
648
+ catch: () => new TokenRefreshError({ message: "Failed to parse token refresh response" })
649
+ });
650
+ accessToken = tokens.access_token;
651
+ tokenExpirationTime = Date.now() + (tokens.expires_in || 3600) * 1e3;
652
+ const newRefreshToken = tokens.refresh_token || authData.refreshToken;
653
+ yield* storage.save({
654
+ refreshToken: newRefreshToken,
655
+ accessToken,
656
+ tokenExpirationTime
657
+ }).pipe(Effect3.catchAll(() => Effect3.void));
658
+ yield* Effect3.log("Access token refreshed successfully");
659
+ return true;
399
660
  });
400
- return { resolve };
661
+ const getAccessToken = Effect3.gen(function* () {
662
+ if (accessToken && Date.now() < tokenExpirationTime) {
663
+ return accessToken;
664
+ }
665
+ const stored = yield* storage.load.pipe(
666
+ Effect3.catchAll(() => Effect3.succeed(Option2.none()))
667
+ );
668
+ Option2.match(stored, {
669
+ onNone: () => {
670
+ },
671
+ onSome: (data) => {
672
+ if (data.accessToken && data.tokenExpirationTime && Date.now() < data.tokenExpirationTime) {
673
+ accessToken = data.accessToken;
674
+ tokenExpirationTime = data.tokenExpirationTime;
675
+ }
676
+ }
677
+ });
678
+ if (accessToken && Date.now() < tokenExpirationTime) {
679
+ return accessToken;
680
+ }
681
+ yield* Effect3.log("Access token expired or not available. Attempting to refresh...");
682
+ yield* refreshAccessToken.pipe(
683
+ Effect3.catchTag("TokenRefreshError", (err) => Effect3.fail(new NotAuthenticatedError({ message: err.message })))
684
+ );
685
+ if (!accessToken) {
686
+ return yield* Effect3.fail(
687
+ new NotAuthenticatedError({ message: "No valid access token available. Please log in first." })
688
+ );
689
+ }
690
+ return accessToken;
691
+ });
692
+ const getUserInfo = Effect3.gen(function* () {
693
+ const config = yield* oauthConfig.getConfig;
694
+ const token = yield* getAccessToken;
695
+ const fetchUserInfo = (t) => Effect3.tryPromise({
696
+ try: () => fetch(config.userinfoUrl, {
697
+ headers: {
698
+ Authorization: `Bearer ${t}`,
699
+ Accept: "application/json"
700
+ }
701
+ }),
702
+ catch: (error) => new UserInfoFetchError({
703
+ message: `Failed to fetch user info: ${error instanceof Error ? error.message : String(error)}`
704
+ })
705
+ });
706
+ let response = yield* fetchUserInfo(token);
707
+ if (response.status === 401 || response.status === 403) {
708
+ yield* refreshAccessToken.pipe(
709
+ Effect3.catchTag("TokenRefreshError", (err) => Effect3.fail(new UserInfoFetchError({ message: err.message })))
710
+ );
711
+ if (!accessToken) {
712
+ return yield* Effect3.fail(new UserInfoFetchError({ message: "Token refresh failed" }));
713
+ }
714
+ response = yield* fetchUserInfo(accessToken);
715
+ }
716
+ if (!response.ok) {
717
+ const body = yield* Effect3.tryPromise({
718
+ try: () => response.text(),
719
+ catch: () => new UserInfoFetchError({ message: "Failed to read user info error response" })
720
+ });
721
+ return yield* Effect3.fail(
722
+ new UserInfoFetchError({
723
+ message: `Failed to fetch user info: ${response.status} ${response.statusText}${body ? ` - ${body}` : ""}`,
724
+ statusCode: response.status
725
+ })
726
+ );
727
+ }
728
+ const data = yield* Effect3.tryPromise({
729
+ try: () => response.json(),
730
+ catch: () => new UserInfoFetchError({ message: "Failed to parse user info response" })
731
+ });
732
+ return data;
733
+ });
734
+ const inferUserName = (info) => {
735
+ const isNonEmptyString = (value) => typeof value === "string" && value.trim() !== "";
736
+ const name = info.name;
737
+ const givenName = info.given_name;
738
+ const familyName = info.family_name;
739
+ const preferredUsername = info.preferred_username;
740
+ const email = info.email;
741
+ return isNonEmptyString(name) && name || (isNonEmptyString(givenName) && isNonEmptyString(familyName) ? `${givenName} ${familyName}` : void 0) || isNonEmptyString(preferredUsername) && preferredUsername || isNonEmptyString(email) && email || "your account";
742
+ };
743
+ return { login, logout, getAccessToken, getUserInfo, refreshAccessToken, inferUserName };
401
744
  })
402
745
  }) {
403
746
  };
404
747
 
405
- // src/commands/debug/config/index.ts
406
- var debugConfigCommand = Command2.make(
407
- "config",
408
- {},
409
- () => Effect4.gen(function* () {
410
- const projectConfig = yield* ProjectConfigService;
411
- const cwd = process.cwd();
412
- const { config, configPath } = yield* projectConfig.resolve(cwd);
413
- yield* Console2.log(`Config file found at: ${configPath}`);
414
- yield* Console2.log(JSON.stringify(config, null, 2));
415
- }).pipe(
416
- Effect4.catchTags({
417
- ConfigNotFoundError: (err) => Console2.error(err.message),
418
- ConfigParseError: (err) => Console2.error(`Config parse error: ${err.message}`),
419
- ConfigValidationError: (err) => Effect4.gen(function* () {
420
- for (const e of err.errors) {
421
- yield* Console2.error(e);
422
- }
423
- yield* Console2.error(err.message);
424
- })
425
- })
426
- )
427
- );
428
-
429
- // src/commands/debug/index.ts
430
- var debugBase = Command3.make("debug", {}, () => Effect5.void);
431
- var debugCommand = debugBase.pipe(Command3.withSubcommands([debugConfigCommand]));
432
-
433
- // src/commands/login/index.ts
434
- import { Command as Command4 } from "@effect/cli";
435
- import { Console as Console4, Effect as Effect9 } from "effect";
436
-
437
- // src/services/auth.ts
438
- import { Console as Console3, Effect as Effect8, Option as Option2 } from "effect";
439
- import * as crypto from "crypto";
440
- import * as http from "http";
441
- import open from "open";
442
-
443
- // src/services/auth-storage.ts
444
- import { FileSystem as FileSystem3, Path as Path3 } from "@effect/platform";
445
- import { Effect as Effect6, Option, Schema as Schema3 } from "effect";
446
- import * as os from "os";
447
-
448
- // src/domain.ts
449
- import { Schema as Schema2 } from "effect";
450
- var DeploymentId = Schema2.String.pipe(Schema2.brand("@Gigadrive/DeploymentId"));
451
- var ApplicationId = Schema2.String.pipe(Schema2.brand("@Gigadrive/ApplicationId"));
452
- var UploadId = Schema2.String.pipe(Schema2.brand("@Gigadrive/UploadId"));
453
- var DeploymentStatus = Schema2.Literal("PENDING", "BUILDING", "PROVISIONING", "FAILED", "ACTIVE", "SUSPENDED");
454
- var DeploymentLogType = Schema2.Literal("INFO", "ERROR", "WARN");
455
- var DeploymentLog = Schema2.Struct({
456
- id: Schema2.String,
457
- message: Schema2.String,
458
- type: DeploymentLogType,
459
- createdAt: Schema2.String
460
- });
461
- var DeploymentLogPage = Schema2.Struct({
462
- totalItems: Schema2.Number,
463
- limit: Schema2.Number,
464
- offset: Schema2.Number,
465
- items: Schema2.Array(DeploymentLog)
466
- });
467
- var PackageManager = Schema2.Literal("npm", "yarn", "pnpm", "bun");
468
- var OAuthTokens = Schema2.Struct({
469
- access_token: Schema2.String,
470
- refresh_token: Schema2.optional(Schema2.String),
471
- expires_in: Schema2.optional(Schema2.Number),
472
- token_type: Schema2.optional(Schema2.String)
473
- });
474
- var OAuthClientConfig = Schema2.Struct({
475
- clientId: Schema2.String,
476
- issuer: Schema2.String,
477
- authorizeUrl: Schema2.String,
478
- tokenUrl: Schema2.String,
479
- scope: Schema2.String,
480
- userinfoUrl: Schema2.String
481
- });
482
- var StoredAuthData = Schema2.Struct({
483
- refreshToken: Schema2.String,
484
- accessToken: Schema2.optional(Schema2.String),
485
- tokenExpirationTime: Schema2.optional(Schema2.Number)
486
- });
487
-
488
- // src/services/auth-storage.ts
489
- var AuthStorageService = class extends Effect6.Service()("AuthStorageService", {
748
+ // src/services/api-client.ts
749
+ var ApiBaseUrl = Config2.string("GIGADRIVE_API_BASE_URL").pipe(Config2.withDefault("https://api.gigadrive.network"));
750
+ var toApiRequestError = (error) => {
751
+ if (error instanceof ApiError) {
752
+ return new ApiRequestError({ message: error.message, statusCode: error.status, code: error.code });
753
+ }
754
+ return new ApiRequestError({ message: error instanceof Error ? error.message : String(error) });
755
+ };
756
+ var ApiClientService = class extends Effect4.Service()("ApiClientService", {
490
757
  accessors: true,
491
- effect: Effect6.gen(function* () {
492
- const fs = yield* FileSystem3.FileSystem;
493
- const pathService = yield* Path3.Path;
494
- const homeDir = os.homedir();
495
- const authDir = pathService.join(homeDir, ".gigadrive");
496
- const authFile = pathService.join(authDir, "auth.json");
497
- const ensureDirectory = Effect6.gen(function* () {
498
- yield* fs.makeDirectory(authDir, { recursive: true }).pipe(
499
- Effect6.mapError(
500
- (error) => new AuthStorageWriteError({
501
- message: "Failed to create auth directory",
502
- cause: String(error)
503
- })
504
- )
505
- );
506
- yield* fs.chmod(authDir, 448).pipe(
507
- Effect6.mapError(
508
- (error) => new AuthStorageWriteError({
509
- message: "Failed to set auth directory permissions",
510
- cause: String(error)
511
- })
512
- )
513
- );
758
+ effect: Effect4.gen(function* () {
759
+ const baseUrl = yield* ApiBaseUrl;
760
+ const authService = yield* AuthService;
761
+ const getClient = Effect4.gen(function* () {
762
+ const token = yield* authService.getAccessToken;
763
+ return new GigadriveClient({ bearerToken: token, baseUrl });
514
764
  });
515
- const load = Effect6.gen(function* () {
516
- const exists = yield* fs.exists(authFile).pipe(Effect6.mapError(() => new AuthStorageReadError({ message: "Failed to check auth file existence" })));
517
- if (!exists) return Option.none();
518
- const data = yield* fs.readFileString(authFile, "utf8").pipe(Effect6.mapError(() => new AuthStorageReadError({ message: "Failed to read auth file" })));
519
- const json = yield* Effect6.try({
765
+ const request = (f) => Effect4.gen(function* () {
766
+ const client = yield* getClient;
767
+ return yield* Effect4.tryPromise({
768
+ try: () => f(client),
769
+ catch: toApiRequestError
770
+ });
771
+ });
772
+ return { getClient, request };
773
+ })
774
+ }) {
775
+ };
776
+
777
+ // src/services/project-link.ts
778
+ import { FileSystem as FileSystem2, Path as Path2 } from "@effect/platform";
779
+ import { Effect as Effect5, Option as Option3, Schema as Schema5 } from "effect";
780
+ var LINK_DIR = ".gigadrive";
781
+ var LINK_FILE = "project.json";
782
+ var ProjectLinkService = class extends Effect5.Service()("ProjectLinkService", {
783
+ accessors: true,
784
+ effect: Effect5.gen(function* () {
785
+ const fs = yield* FileSystem2.FileSystem;
786
+ const pathService = yield* Path2.Path;
787
+ const linkDir = (projectFolder) => pathService.join(projectFolder, LINK_DIR);
788
+ const linkFile = (projectFolder) => pathService.join(projectFolder, LINK_DIR, LINK_FILE);
789
+ const load = Effect5.fn("ProjectLinkService.load")(function* (projectFolder) {
790
+ const file = linkFile(projectFolder);
791
+ const exists = yield* fs.exists(file).pipe(Effect5.mapError(() => new ProjectLinkReadError({ message: "Failed to check project link existence" })));
792
+ if (!exists) return Option3.none();
793
+ const data = yield* fs.readFileString(file, "utf8").pipe(Effect5.mapError(() => new ProjectLinkReadError({ message: "Failed to read project link file" })));
794
+ const json = yield* Effect5.try({
520
795
  try: () => JSON.parse(data),
521
- catch: () => new AuthStorageReadError({ message: "Auth file contains invalid JSON" })
796
+ catch: () => new ProjectLinkReadError({ message: `${LINK_DIR}/${LINK_FILE} contains invalid JSON` })
522
797
  });
523
- const authData = yield* Schema3.decodeUnknown(StoredAuthData)(json).pipe(
524
- Effect6.mapError(() => new AuthStorageReadError({ message: "Auth file has invalid schema" }))
798
+ const link = yield* Schema5.decodeUnknown(ProjectLink)(json).pipe(
799
+ Effect5.mapError(() => new ProjectLinkReadError({ message: `${LINK_DIR}/${LINK_FILE} has an invalid schema` }))
525
800
  );
526
- return Option.some(authData);
801
+ return Option3.some(link);
527
802
  });
528
- const save = Effect6.fn("AuthStorageService.save")(function* (authData) {
529
- yield* ensureDirectory;
530
- yield* fs.writeFileString(authFile, JSON.stringify(authData, null, 2), { mode: 384 }).pipe(
531
- Effect6.mapError(
532
- (error) => new AuthStorageWriteError({
533
- message: "Failed to write auth file",
534
- cause: String(error)
803
+ const resolve = Effect5.fn("ProjectLinkService.resolve")(function* (projectFolder) {
804
+ const link = yield* load(projectFolder);
805
+ return yield* Option3.match(link, {
806
+ onNone: () => Effect5.fail(
807
+ new ProjectNotLinkedError({
808
+ message: 'This directory is not linked to an application. Run "gigadrive link" first.',
809
+ directory: projectFolder
535
810
  })
811
+ ),
812
+ onSome: (value) => Effect5.succeed(value)
813
+ });
814
+ });
815
+ const save = Effect5.fn("ProjectLinkService.save")(function* (projectFolder, link) {
816
+ yield* fs.makeDirectory(linkDir(projectFolder), { recursive: true }).pipe(
817
+ Effect5.mapError(
818
+ (error) => new ProjectLinkWriteError({ message: "Failed to create .gigadrive directory", cause: String(error) })
536
819
  )
537
820
  );
538
- yield* Effect6.log("Auth data saved to local storage");
821
+ yield* fs.writeFileString(linkFile(projectFolder), `${JSON.stringify(link, null, 2)}
822
+ `).pipe(
823
+ Effect5.mapError(
824
+ (error) => new ProjectLinkWriteError({ message: "Failed to write project link file", cause: String(error) })
825
+ )
826
+ );
827
+ yield* Effect5.log("Project link saved", { applicationId: link.applicationId });
539
828
  });
540
- const remove = Effect6.gen(function* () {
541
- yield* fs.remove(authFile).pipe(
542
- Effect6.mapError((error) => {
543
- const cause = error instanceof Error ? error : error.error;
544
- const code = cause && typeof cause === "object" && "code" in cause ? cause.code : "";
545
- return { isNotFound: code === "ENOENT" || String(error).includes("ENOENT"), original: error };
546
- }),
547
- Effect6.catchAll(
548
- (mapped) => mapped.isNotFound ? Effect6.void : Effect6.fail(
549
- new AuthStorageWriteError({
550
- message: "Failed to remove auth file",
551
- cause: String(mapped.original)
552
- })
553
- )
829
+ const remove = Effect5.fn("ProjectLinkService.remove")(function* (projectFolder) {
830
+ yield* fs.remove(linkFile(projectFolder), { force: true }).pipe(
831
+ Effect5.mapError(
832
+ (error) => new ProjectLinkWriteError({ message: "Failed to remove project link file", cause: String(error) })
554
833
  )
555
834
  );
556
- yield* Effect6.log("Auth data removed from local storage");
557
835
  });
558
- return { load, save, remove };
836
+ return { load, resolve, save, remove };
559
837
  })
560
838
  }) {
561
839
  };
562
840
 
563
- // src/services/oauth-config.ts
564
- import { Config, Effect as Effect7, Schema as Schema4 } from "effect";
565
- var OAuthEnvConfig = Config.all({
566
- issuerUrl: Config.string("GIGADRIVE_NETWORK_OAUTH_ISSUER_URL").pipe(Config.withDefault("https://idp.gigadrive.de")),
567
- clientId: Config.string("GIGADRIVE_NETWORK_OAUTH_CLIENT_ID").pipe(Config.withDefault("todo_add_client_id"))
841
+ // src/commands/ai/index.ts
842
+ var orgOption = Options.text("org").pipe(
843
+ Options.withAlias("o"),
844
+ Options.withDescription("Organization ID (defaults to the linked organization)"),
845
+ Options.optional
846
+ );
847
+ var appOption = Options.text("app").pipe(
848
+ Options.withAlias("a"),
849
+ Options.withDescription("Restrict to this application ID"),
850
+ Options.optional
851
+ );
852
+ var fromOption = Options.text("from").pipe(
853
+ Options.withDescription("Include usage at or after this ISO 8601 timestamp"),
854
+ Options.optional
855
+ );
856
+ var toOption = Options.text("to").pipe(
857
+ Options.withDescription("Include usage before or at this ISO 8601 timestamp"),
858
+ Options.optional
859
+ );
860
+ var fileOption = Options.text("file").pipe(
861
+ Options.withAlias("f"),
862
+ Options.withDescription('Path to a JSON file, or "-" for stdin (default: stdin)'),
863
+ Options.optional
864
+ );
865
+ var resolveOrgId = (org) => Effect6.gen(function* () {
866
+ if (Option4.isSome(org)) return org.value;
867
+ const projectLink = yield* ProjectLinkService;
868
+ const link = yield* projectLink.resolve(process.cwd());
869
+ if (link.organizationId === void 0) {
870
+ return yield* Effect6.fail(
871
+ new ProjectNotLinkedError({
872
+ message: 'No organization is linked. Pass --org <id> or re-run "gigadrive link".',
873
+ directory: process.cwd()
874
+ })
875
+ );
876
+ }
877
+ return link.organizationId;
568
878
  });
569
- var OpenIdDiscoveryDocument = Schema4.Struct({
570
- issuer: Schema4.optional(Schema4.String),
571
- authorization_endpoint: Schema4.optional(Schema4.String),
572
- token_endpoint: Schema4.optional(Schema4.String),
573
- userinfo_endpoint: Schema4.optional(Schema4.String)
879
+ var readStdin = Effect6.async((resume) => {
880
+ let data = "";
881
+ const stdin = process.stdin;
882
+ stdin.setEncoding("utf8");
883
+ const onData = (chunk) => {
884
+ data += chunk;
885
+ };
886
+ const cleanup = () => {
887
+ stdin.off("data", onData);
888
+ stdin.off("end", onEnd);
889
+ stdin.off("error", onError);
890
+ };
891
+ const onEnd = () => {
892
+ cleanup();
893
+ resume(Effect6.succeed(data));
894
+ };
895
+ const onError = (err) => {
896
+ cleanup();
897
+ resume(Effect6.fail(new ApiRequestError({ message: `Failed to read stdin: ${err.message}` })));
898
+ };
899
+ stdin.on("data", onData);
900
+ stdin.on("end", onEnd);
901
+ stdin.on("error", onError);
902
+ stdin.resume();
574
903
  });
575
- var DEFAULT_SCOPE = "offline_access openid profile email";
576
- var OAuthConfigService = class extends Effect7.Service()("OAuthConfigService", {
577
- accessors: true,
578
- effect: Effect7.gen(function* () {
579
- const envConfig = yield* OAuthEnvConfig;
580
- let cached = null;
581
- const getConfig = Effect7.gen(function* () {
582
- if (cached) return cached;
583
- const issuerBase = envConfig.issuerUrl.replace(/\/$/, "");
584
- const discoveryUrl = `${issuerBase}/.well-known/openid-configuration`;
585
- yield* Effect7.log("Fetching OIDC discovery document", { discoveryUrl });
586
- const response = yield* Effect7.tryPromise({
587
- try: (signal) => fetch(discoveryUrl, { signal }),
588
- catch: (error) => new OAuthDiscoveryError({
589
- message: "Failed to fetch OIDC discovery document",
590
- cause: error instanceof Error ? error.message : String(error)
591
- })
592
- });
593
- if (!response.ok) {
594
- return yield* Effect7.fail(
595
- new OAuthDiscoveryError({
596
- message: `OIDC discovery returned ${response.status} ${response.statusText}`
597
- })
598
- );
599
- }
600
- const json = yield* Effect7.tryPromise({
601
- try: () => response.json(),
602
- catch: () => new OAuthDiscoveryError({
603
- message: "Failed to parse OIDC discovery JSON"
904
+ var readJsonInput = (file) => Effect6.gen(function* () {
905
+ const path = Option4.getOrUndefined(file);
906
+ let raw;
907
+ if (path === void 0 || path === "-") {
908
+ raw = yield* readStdin;
909
+ } else {
910
+ const fs = yield* FileSystem3.FileSystem;
911
+ raw = yield* fs.readFileString(path, "utf8").pipe(Effect6.mapError((e) => new ApiRequestError({ message: `Failed to read ${path}: ${String(e)}` })));
912
+ }
913
+ return yield* Effect6.try({
914
+ try: () => JSON.parse(raw),
915
+ catch: () => new ApiRequestError({ message: "Input is not valid JSON." })
916
+ });
917
+ });
918
+ var governanceErrorHandlers = {
919
+ NotAuthenticatedError: (err) => Console2.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
920
+ Effect6.andThen(Effect6.fail(err))
921
+ ),
922
+ ApiRequestError: (err) => Console2.error(err.message).pipe(Effect6.andThen(Effect6.fail(err))),
923
+ ProjectNotLinkedError: (err) => Console2.error(err.message).pipe(Effect6.andThen(Effect6.fail(err))),
924
+ ProjectLinkReadError: (err) => Console2.error(err.message).pipe(Effect6.andThen(Effect6.fail(err)))
925
+ };
926
+ var usageSummaryCommand = Command.make(
927
+ "summary",
928
+ { org: orgOption, app: appOption, from: fromOption, to: toOption },
929
+ ({ org, app, from, to }) => Effect6.gen(function* () {
930
+ const apiClient = yield* ApiClientService;
931
+ const orgId = yield* resolveOrgId(org);
932
+ const summary = yield* apiClient.request(
933
+ (client) => client.organizations.aiGateway.usage.summary(orgId, {
934
+ applicationId: Option4.getOrUndefined(app),
935
+ from: Option4.getOrUndefined(from),
936
+ to: Option4.getOrUndefined(to)
937
+ })
938
+ );
939
+ yield* Console2.log(JSON.stringify(summary, null, 2));
940
+ }).pipe(Effect6.catchTags(governanceErrorHandlers))
941
+ );
942
+ var usageRequestsCommand = Command.make(
943
+ "requests",
944
+ { org: orgOption, app: appOption, from: fromOption, to: toOption },
945
+ ({ org, app, from, to }) => Effect6.gen(function* () {
946
+ const apiClient = yield* ApiClientService;
947
+ const orgId = yield* resolveOrgId(org);
948
+ const { items, total } = yield* apiClient.request(
949
+ (client) => client.organizations.aiGateway.usage.requests(orgId, {
950
+ applicationId: Option4.getOrUndefined(app),
951
+ from: Option4.getOrUndefined(from),
952
+ to: Option4.getOrUndefined(to)
953
+ })
954
+ );
955
+ if (items.length === 0) {
956
+ yield* Console2.log("No AI Gateway requests found.");
957
+ return;
958
+ }
959
+ for (const e of items) {
960
+ yield* Console2.log(
961
+ `${e.createdAt} ${e.modelRequested} ${e.status} ${e.totalTokens} tok ${e.billableCostMicros}\xB5$`
962
+ );
963
+ }
964
+ yield* Console2.log(`
965
+ ${total} request(s).`);
966
+ }).pipe(Effect6.catchTags(governanceErrorHandlers))
967
+ );
968
+ var outOption = Options.text("out").pipe(
969
+ Options.withDescription("Write the CSV to this file instead of stdout"),
970
+ Options.optional
971
+ );
972
+ var usageExportCommand = Command.make(
973
+ "export",
974
+ { org: orgOption, app: appOption, from: fromOption, to: toOption, out: outOption },
975
+ ({ org, app, from, to, out }) => Effect6.gen(function* () {
976
+ const apiClient = yield* ApiClientService;
977
+ const orgId = yield* resolveOrgId(org);
978
+ const csv = yield* apiClient.request(
979
+ (client) => client.organizations.aiGateway.usage.export(orgId, {
980
+ applicationId: Option4.getOrUndefined(app),
981
+ from: Option4.getOrUndefined(from),
982
+ to: Option4.getOrUndefined(to)
983
+ })
984
+ );
985
+ const outPath = Option4.getOrUndefined(out);
986
+ if (outPath === void 0) {
987
+ yield* Console2.log(csv);
988
+ } else {
989
+ const fs = yield* FileSystem3.FileSystem;
990
+ yield* fs.writeFileString(outPath, csv).pipe(Effect6.mapError((e) => new ApiRequestError({ message: `Failed to write ${outPath}: ${String(e)}` })));
991
+ yield* Console2.log(`Wrote ${outPath}.`);
992
+ }
993
+ }).pipe(Effect6.catchTags(governanceErrorHandlers))
994
+ );
995
+ var usageCommand = Command.make("usage", {}, () => Effect6.void).pipe(
996
+ Command.withSubcommands([usageSummaryCommand, usageRequestsCommand, usageExportCommand])
997
+ );
998
+ var budgetsGetCommand = Command.make(
999
+ "get",
1000
+ { org: orgOption },
1001
+ ({ org }) => Effect6.gen(function* () {
1002
+ const apiClient = yield* ApiClientService;
1003
+ const orgId = yield* resolveOrgId(org);
1004
+ const { items } = yield* apiClient.request((client) => client.organizations.aiGateway.budgets.list(orgId));
1005
+ yield* Console2.log(JSON.stringify(items, null, 2));
1006
+ }).pipe(Effect6.catchTags(governanceErrorHandlers))
1007
+ );
1008
+ var budgetsSetCommand = Command.make(
1009
+ "set",
1010
+ { org: orgOption, file: fileOption },
1011
+ ({ org, file }) => Effect6.gen(function* () {
1012
+ const apiClient = yield* ApiClientService;
1013
+ const orgId = yield* resolveOrgId(org);
1014
+ const parsed = yield* readJsonInput(file);
1015
+ const budgets = Array.isArray(parsed) ? parsed : parsed.budgets;
1016
+ if (!Array.isArray(budgets)) {
1017
+ return yield* Effect6.fail(
1018
+ new ApiRequestError({
1019
+ message: 'Expected a JSON array of budgets, or an object of the form { "budgets": [...] }.'
604
1020
  })
605
- });
606
- const decoded = yield* Schema4.decodeUnknown(OpenIdDiscoveryDocument)(json).pipe(
607
- Effect7.mapError(
608
- () => new OAuthDiscoveryError({
609
- message: "Invalid OIDC discovery document schema"
610
- })
611
- )
612
1021
  );
613
- if (!decoded.authorization_endpoint || !decoded.token_endpoint || !decoded.userinfo_endpoint) {
614
- return yield* Effect7.fail(
615
- new OAuthDiscoveryError({
616
- message: "OIDC discovery returned incomplete endpoints"
617
- })
618
- );
619
- }
620
- const config = {
621
- clientId: envConfig.clientId,
622
- issuer: decoded.issuer ?? issuerBase,
623
- authorizeUrl: decoded.authorization_endpoint,
624
- tokenUrl: decoded.token_endpoint,
625
- scope: DEFAULT_SCOPE,
626
- userinfoUrl: decoded.userinfo_endpoint
627
- };
628
- cached = config;
629
- yield* Effect7.log("OIDC discovery completed", { issuer: config.issuer });
630
- return config;
631
- });
632
- return { getConfig };
633
- })
634
- }) {
1022
+ }
1023
+ const { items } = yield* apiClient.request(
1024
+ (client) => client.organizations.aiGateway.budgets.replace(orgId, budgets)
1025
+ );
1026
+ yield* Console2.log(`Saved ${items.length} budget(s).`);
1027
+ }).pipe(Effect6.catchTags(governanceErrorHandlers))
1028
+ );
1029
+ var budgetsCommand = Command.make("budgets", {}, () => Effect6.void).pipe(
1030
+ Command.withSubcommands([budgetsGetCommand, budgetsSetCommand])
1031
+ );
1032
+ var policiesGetCommand = Command.make(
1033
+ "get",
1034
+ { org: orgOption, app: appOption },
1035
+ ({ org, app }) => Effect6.gen(function* () {
1036
+ const apiClient = yield* ApiClientService;
1037
+ const orgId = yield* resolveOrgId(org);
1038
+ const policy = yield* apiClient.request(
1039
+ (client) => client.organizations.aiGateway.policies.get(orgId, { applicationId: Option4.getOrUndefined(app) })
1040
+ );
1041
+ yield* Console2.log(policy === null ? "No policy set." : JSON.stringify(policy, null, 2));
1042
+ }).pipe(Effect6.catchTags(governanceErrorHandlers))
1043
+ );
1044
+ var policiesSetCommand = Command.make(
1045
+ "set",
1046
+ { org: orgOption, file: fileOption },
1047
+ ({ org, file }) => Effect6.gen(function* () {
1048
+ const apiClient = yield* ApiClientService;
1049
+ const orgId = yield* resolveOrgId(org);
1050
+ const parsed = yield* readJsonInput(file);
1051
+ const policy = yield* apiClient.request(
1052
+ (client) => client.organizations.aiGateway.policies.put(orgId, parsed)
1053
+ );
1054
+ yield* Console2.log(`Policy saved${policy.applicationId ? ` for application ${policy.applicationId}` : ""}.`);
1055
+ }).pipe(Effect6.catchTags(governanceErrorHandlers))
1056
+ );
1057
+ var policiesCommand = Command.make("policies", {}, () => Effect6.void).pipe(
1058
+ Command.withSubcommands([policiesGetCommand, policiesSetCommand])
1059
+ );
1060
+ var inferenceErrorHandlers = {
1061
+ NotAuthenticatedError: (err) => Console2.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
1062
+ Effect6.andThen(Effect6.fail(err))
1063
+ ),
1064
+ ApiRequestError: (err) => Console2.error(err.message).pipe(Effect6.andThen(Effect6.fail(err)))
635
1065
  };
1066
+ var modelsListCommand = Command.make(
1067
+ "list",
1068
+ {},
1069
+ () => Effect6.gen(function* () {
1070
+ const apiClient = yield* ApiClientService;
1071
+ const { items } = yield* apiClient.request((client) => client.aiGateway.listModels());
1072
+ for (const m of items) {
1073
+ yield* Console2.log(`${m.id} ${m.name} (ctx ${m.contextWindow})`);
1074
+ }
1075
+ }).pipe(Effect6.catchTags(inferenceErrorHandlers))
1076
+ );
1077
+ var modelIdArg = Args.text({ name: "model-id" }).pipe(Args.withDescription("The model ID, e.g. openai/gpt-4o"));
1078
+ var modelsGetCommand = Command.make(
1079
+ "get",
1080
+ { modelId: modelIdArg },
1081
+ ({ modelId }) => Effect6.gen(function* () {
1082
+ const apiClient = yield* ApiClientService;
1083
+ const model = yield* apiClient.request((client) => client.aiGateway.getModel(modelId));
1084
+ yield* Console2.log(JSON.stringify(model, null, 2));
1085
+ }).pipe(Effect6.catchTags(inferenceErrorHandlers))
1086
+ );
1087
+ var modelsCommand = Command.make("models", {}, () => Effect6.void).pipe(
1088
+ Command.withSubcommands([modelsListCommand, modelsGetCommand])
1089
+ );
1090
+ var modelOption = Options.text("model").pipe(
1091
+ Options.withAlias("m"),
1092
+ Options.withDescription("The model ID to use, e.g. openai/gpt-4o")
1093
+ );
1094
+ var streamOption = Options.boolean("stream").pipe(Options.withDescription("Stream the response token by token"));
1095
+ var promptArg = Args.text({ name: "prompt" }).pipe(
1096
+ Args.withDescription('The prompt text, or "-" to read from stdin')
1097
+ );
1098
+ var chatCommand = Command.make(
1099
+ "chat",
1100
+ { prompt: promptArg, model: modelOption, stream: streamOption },
1101
+ ({ prompt, model, stream }) => Effect6.gen(function* () {
1102
+ const apiClient = yield* ApiClientService;
1103
+ const text = prompt === "-" ? yield* readStdin : prompt;
1104
+ const messages = [{ role: "user", content: text }];
1105
+ if (stream) {
1106
+ const client = yield* apiClient.getClient;
1107
+ yield* Effect6.tryPromise({
1108
+ try: async () => {
1109
+ for await (const chunk of client.aiGateway.chatCompletionsStream({ model, messages })) {
1110
+ process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
1111
+ }
1112
+ process.stdout.write("\n");
1113
+ },
1114
+ catch: (error) => new ApiRequestError({ message: error instanceof Error ? error.message : String(error) })
1115
+ });
1116
+ return;
1117
+ }
1118
+ const response = yield* apiClient.request((client) => client.aiGateway.chatCompletions({ model, messages }));
1119
+ const content = response.choices[0]?.message.content;
1120
+ yield* Console2.log(typeof content === "string" ? content : JSON.stringify(content));
1121
+ }).pipe(Effect6.catchTags(inferenceErrorHandlers))
1122
+ );
1123
+ var aiBase = Command.make("ai", {}, () => Effect6.void);
1124
+ var aiCommand = aiBase.pipe(
1125
+ Command.withSubcommands([usageCommand, budgetsCommand, policiesCommand, modelsCommand, chatCommand])
1126
+ );
636
1127
 
637
- // src/services/auth.ts
638
- var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\//g, "&#x2F;");
639
- var base64URLEncode = (buffer) => buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
640
- var generatePKCE = () => {
641
- const codeVerifier = base64URLEncode(crypto.randomBytes(64));
642
- const challengeBytes = crypto.createHash("sha256").update(codeVerifier, "utf8").digest();
643
- const codeChallenge = base64URLEncode(challengeBytes);
644
- return { codeVerifier, codeChallenge };
645
- };
646
- var AuthService = class extends Effect8.Service()("AuthService", {
1128
+ // src/commands/apps/index.ts
1129
+ import { Command as Command2, Options as Options2 } from "@effect/cli";
1130
+ import { Console as Console3, Effect as Effect7, Option as Option5 } from "effect";
1131
+ var orgOption2 = Options2.text("org").pipe(
1132
+ Options2.withAlias("o"),
1133
+ Options2.withDescription("Only list applications belonging to this organization ID"),
1134
+ Options2.optional
1135
+ );
1136
+ var appsListCommand = Command2.make(
1137
+ "list",
1138
+ { org: orgOption2 },
1139
+ ({ org }) => Effect7.gen(function* () {
1140
+ const apiClient = yield* ApiClientService;
1141
+ const organizationId = Option5.getOrUndefined(org);
1142
+ const { items, total } = yield* apiClient.request((client) => client.applications.list({ organizationId }));
1143
+ if (items.length === 0) {
1144
+ yield* Console3.log("No applications found.");
1145
+ return;
1146
+ }
1147
+ for (const app of items) {
1148
+ yield* Console3.log(`${app.id} ${app.name} (${app.organization.name})`);
1149
+ }
1150
+ yield* Console3.log(`
1151
+ ${total} application(s).`);
1152
+ }).pipe(
1153
+ Effect7.catchTags({
1154
+ NotAuthenticatedError: (err) => Console3.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
1155
+ Effect7.andThen(Effect7.fail(err))
1156
+ ),
1157
+ ApiRequestError: (err) => Console3.error(`Failed to list applications: ${err.message}`).pipe(Effect7.andThen(Effect7.fail(err)))
1158
+ })
1159
+ )
1160
+ );
1161
+ var appsBase = Command2.make("apps", {}, () => Effect7.void);
1162
+ var appsCommand = appsBase.pipe(Command2.withSubcommands([appsListCommand]));
1163
+
1164
+ // src/commands/build/index.ts
1165
+ import { Command as Command3 } from "@effect/cli";
1166
+ import { FileSystem as FileSystem5, Path as Path4 } from "@effect/platform";
1167
+ import { exec as exec2 } from "@gigadrive/build-utils";
1168
+ import { Console as Console4, Effect as Effect9 } from "effect";
1169
+
1170
+ // src/services/package-manager.ts
1171
+ import { FileSystem as FileSystem4, Path as Path3 } from "@effect/platform";
1172
+ import { exec } from "@gigadrive/build-utils";
1173
+ import { Effect as Effect8 } from "effect";
1174
+ var PackageManagerService = class extends Effect8.Service()("PackageManagerService", {
647
1175
  accessors: true,
648
- dependencies: [OAuthConfigService.Default, AuthStorageService.Default],
649
1176
  effect: Effect8.gen(function* () {
650
- const oauthConfig = yield* OAuthConfigService;
651
- const storage = yield* AuthStorageService;
652
- let accessToken = null;
653
- let tokenExpirationTime = 0;
654
- const logout = Effect8.gen(function* () {
655
- accessToken = null;
656
- tokenExpirationTime = 0;
657
- yield* storage.remove;
658
- yield* Effect8.log("Logged out");
659
- });
660
- const exchangeCodeForTokens = (config, authCode, redirectUri, codeVerifier) => Effect8.gen(function* () {
661
- const response = yield* Effect8.tryPromise({
662
- try: () => fetch(config.tokenUrl, {
663
- method: "POST",
664
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
665
- body: new URLSearchParams({
666
- grant_type: "authorization_code",
667
- client_id: config.clientId,
668
- code: authCode,
669
- redirect_uri: redirectUri,
670
- code_verifier: codeVerifier
671
- }).toString()
672
- }),
673
- catch: (error) => new TokenExchangeError({
674
- message: `Token exchange request failed: ${error instanceof Error ? error.message : String(error)}`
675
- })
676
- });
677
- if (!response.ok) {
678
- const body = yield* Effect8.tryPromise({
679
- try: () => response.text(),
680
- catch: () => new TokenExchangeError({ message: "Failed to read token exchange error response" })
681
- });
1177
+ const fs = yield* FileSystem4.FileSystem;
1178
+ const pathService = yield* Path3.Path;
1179
+ const detect = Effect8.fn("PackageManagerService.detect")(function* (cwd) {
1180
+ yield* Effect8.annotateCurrentSpan("cwd", cwd);
1181
+ const packageJsonPath = pathService.join(cwd, "package.json");
1182
+ const exists = yield* fs.exists(packageJsonPath).pipe(
1183
+ Effect8.mapError(
1184
+ () => new PackageManagerNotFoundError({ message: "Failed to check for package.json", directory: cwd })
1185
+ )
1186
+ );
1187
+ if (!exists) {
682
1188
  return yield* Effect8.fail(
683
- new TokenExchangeError({
684
- message: `Token exchange failed: ${response.status} ${response.statusText} - ${body}`,
685
- statusCode: response.status
1189
+ new PackageManagerNotFoundError({
1190
+ message: "No package.json found",
1191
+ directory: cwd
686
1192
  })
687
1193
  );
688
1194
  }
689
- const tokens = yield* Effect8.tryPromise({
690
- try: () => response.json(),
691
- catch: () => new TokenExchangeError({ message: "Failed to parse token exchange response" })
692
- });
693
- return tokens;
694
- });
695
- const startCallbackServer = (config, codeChallenge, state) => Effect8.async((resume) => {
696
- const server = http.createServer();
697
- const redirectPath = "/callback";
698
- server.listen(0, "127.0.0.1", () => {
699
- const address = server.address();
700
- if (!address || typeof address !== "object") {
701
- server.close(() => {
702
- resume(Effect8.fail(new LoginFlowError({ message: "Failed to determine callback server address" })));
703
- });
704
- return;
1195
+ const lockChecks = [
1196
+ { file: "yarn.lock", pm: "yarn" },
1197
+ { file: "pnpm-lock.yaml", pm: "pnpm" },
1198
+ { file: "bun.lockb", pm: "bun" },
1199
+ { file: "package-lock.json", pm: "npm" }
1200
+ ];
1201
+ for (const { file, pm: pm2 } of lockChecks) {
1202
+ const lockExists = yield* fs.exists(pathService.join(cwd, file)).pipe(Effect8.catchAll(() => Effect8.succeed(false)));
1203
+ if (lockExists) {
1204
+ yield* Effect8.log("Package manager detected from lock file", { packageManager: pm2, lockFile: file });
1205
+ return pm2;
705
1206
  }
706
- const port = address.port;
707
- const redirectUri = `http://127.0.0.1:${port}${redirectPath}`;
708
- const authUrl = new URL(config.authorizeUrl);
709
- authUrl.searchParams.append("client_id", config.clientId);
710
- authUrl.searchParams.append("redirect_uri", redirectUri);
711
- authUrl.searchParams.append("response_type", "code");
712
- authUrl.searchParams.append("scope", config.scope);
713
- authUrl.searchParams.append("code_challenge", codeChallenge);
714
- authUrl.searchParams.append("code_challenge_method", "S256");
715
- authUrl.searchParams.append("state", state);
716
- Console3.log(`Please open this URL in your browser to log in:
717
- ${authUrl.toString()}
718
- `).pipe(Effect8.runSync);
719
- Console3.log("Waiting for login callback...").pipe(Effect8.runSync);
720
- void open(authUrl.toString());
721
- server.on("request", (req, res) => {
722
- const reqUrl = new URL(req.url || "", redirectUri);
723
- if (reqUrl.pathname !== "/callback") {
724
- res.writeHead(404).end("Not Found");
725
- return;
726
- }
727
- const authCode = reqUrl.searchParams.get("code");
728
- const receivedState = reqUrl.searchParams.get("state");
729
- const oauthError = reqUrl.searchParams.get("error");
730
- res.writeHead(200, { "Content-Type": "text/html", Connection: "close" });
731
- if (oauthError) {
732
- const safeOauthError = escapeHtml(oauthError);
733
- res.end(`<h1>Login Failed!</h1><p>Error: ${safeOauthError}</p><p>You can close this tab.</p>`);
734
- server.close();
735
- resume(Effect8.fail(new LoginFlowError({ message: `OAuth error: ${oauthError}` })));
736
- return;
737
- }
738
- if (receivedState !== state) {
739
- res.end(
740
- "<h1>Login Failed!</h1><p>State mismatch. Possible CSRF attack.</p><p>You can close this tab.</p>"
741
- );
742
- server.close();
743
- resume(Effect8.fail(new LoginFlowError({ message: "State mismatch" })));
744
- return;
745
- }
746
- if (authCode) {
747
- res.end("<h1>Login Successful!</h1><p>You can close this tab.</p>");
748
- server.close();
749
- resume(Effect8.succeed({ authCode, redirectUri }));
750
- } else {
751
- res.end("<h1>Login Failed!</h1><p>No authorization code received.</p><p>You can close this tab.</p>");
752
- server.close();
753
- resume(Effect8.fail(new LoginFlowError({ message: "No authorization code received" })));
754
- }
755
- });
756
- server.on("error", (err) => {
757
- server.close();
758
- resume(Effect8.fail(new LoginFlowError({ message: `Local server error: ${err.message}` })));
759
- });
760
- });
761
- });
762
- const login = Effect8.gen(function* () {
763
- yield* logout;
764
- const { codeVerifier, codeChallenge } = generatePKCE();
765
- const state = crypto.randomBytes(16).toString("hex");
766
- const config = yield* oauthConfig.getConfig;
767
- const { authCode, redirectUri } = yield* startCallbackServer(config, codeChallenge, state);
768
- const tokens = yield* exchangeCodeForTokens(config, authCode, redirectUri, codeVerifier);
769
- if (!tokens.refresh_token) {
770
- return yield* Effect8.fail(
771
- new TokenExchangeError({ message: "No refresh token received from identity provider" })
772
- );
773
1207
  }
774
- accessToken = tokens.access_token;
775
- tokenExpirationTime = Date.now() + (tokens.expires_in || 3600) * 1e3;
776
- yield* storage.save({
777
- refreshToken: tokens.refresh_token,
778
- accessToken,
779
- tokenExpirationTime
780
- });
781
- yield* Effect8.log("Login successful");
782
- return true;
783
- });
784
- const refreshAccessToken = Effect8.gen(function* () {
785
- const stored = yield* storage.load.pipe(
786
- Effect8.catchTag("AuthStorageReadError", (err) => Effect8.fail(new TokenRefreshError({ message: err.message })))
787
- );
788
- const config = yield* oauthConfig.getConfig.pipe(
789
- Effect8.catchTag("OAuthDiscoveryError", (err) => Effect8.fail(new TokenRefreshError({ message: err.message })))
1208
+ const bunAvailable = yield* Effect8.tryPromise({
1209
+ try: () => exec({ command: "bun --version", cwd }),
1210
+ catch: () => new ExecError({ message: "bun not found", command: "bun --version" })
1211
+ }).pipe(
1212
+ Effect8.as(true),
1213
+ Effect8.catchTag("ExecError", () => Effect8.succeed(false))
790
1214
  );
791
- const authData = yield* Option2.match(stored, {
792
- onNone: () => Effect8.fail(new TokenRefreshError({ message: "No refresh token available. Please log in." })),
793
- onSome: (data) => data.refreshToken ? Effect8.succeed(data) : Effect8.fail(new TokenRefreshError({ message: "No refresh token available. Please log in." }))
794
- });
795
- const response = yield* Effect8.tryPromise({
796
- try: () => fetch(config.tokenUrl, {
797
- method: "POST",
798
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
799
- body: new URLSearchParams({
800
- grant_type: "refresh_token",
801
- client_id: config.clientId,
802
- refresh_token: authData.refreshToken
803
- }).toString()
804
- }),
805
- catch: (error) => new TokenRefreshError({
806
- message: `Token refresh request failed: ${error instanceof Error ? error.message : String(error)}`
807
- })
808
- });
809
- if (!response.ok) {
810
- if (response.status === 400) {
811
- yield* logout.pipe(Effect8.catchAll(() => Effect8.void));
812
- return yield* Effect8.fail(
813
- new TokenRefreshError({
814
- message: "Refresh token is invalid or expired. Please log in again.",
815
- statusCode: response.status
816
- })
817
- );
1215
+ const pm = bunAvailable ? "bun" : "npm";
1216
+ yield* Effect8.log("Package manager detected via fallback", { packageManager: pm });
1217
+ return pm;
1218
+ });
1219
+ const installCommand = (pm) => ({
1220
+ bun: "bun install",
1221
+ npm: "npm install",
1222
+ pnpm: "pnpm install",
1223
+ yarn: "yarn install"
1224
+ })[pm];
1225
+ const buildCommand2 = (pm) => ({
1226
+ bun: "bun run build",
1227
+ npm: "npm run build",
1228
+ pnpm: "pnpm build",
1229
+ yarn: "yarn build"
1230
+ })[pm];
1231
+ return { detect, installCommand, buildCommand: buildCommand2 };
1232
+ })
1233
+ }) {
1234
+ };
1235
+
1236
+ // src/commands/build/index.ts
1237
+ var buildCommand = Command3.make(
1238
+ "build",
1239
+ {},
1240
+ () => Effect9.gen(function* () {
1241
+ const pmService = yield* PackageManagerService;
1242
+ const fs = yield* FileSystem5.FileSystem;
1243
+ const pathService = yield* Path4.Path;
1244
+ const cwd = process.cwd();
1245
+ yield* Effect9.log("Starting build", { cwd });
1246
+ const packageJsonPath = pathService.join(cwd, "package.json");
1247
+ const packageJsonExists = yield* fs.exists(packageJsonPath).pipe(
1248
+ Effect9.mapError(
1249
+ () => new PackageJsonNotFoundError({ message: "Failed to check for package.json", directory: cwd })
1250
+ )
1251
+ );
1252
+ if (!packageJsonExists) {
1253
+ return yield* Effect9.fail(new PackageJsonNotFoundError({ message: "No package.json found", directory: cwd }));
1254
+ }
1255
+ const packageJsonContent = yield* fs.readFileString(packageJsonPath, "utf-8").pipe(
1256
+ Effect9.mapError(() => new PackageJsonNotFoundError({ message: "Failed to read package.json", directory: cwd }))
1257
+ );
1258
+ const packageJson = yield* Effect9.try({
1259
+ try: () => JSON.parse(packageJsonContent),
1260
+ catch: (error) => new PackageJsonParseError({
1261
+ message: "Failed to parse package.json",
1262
+ directory: cwd,
1263
+ cause: error instanceof Error ? error.message : String(error)
1264
+ })
1265
+ });
1266
+ if (!packageJson.scripts?.build) {
1267
+ return yield* Effect9.fail(new BuildScriptNotFoundError({ message: "No build script found in package.json" }));
1268
+ }
1269
+ const pm = yield* pmService.detect(cwd);
1270
+ const buildEnv = {
1271
+ ...process.env,
1272
+ NODE_ENV: "production",
1273
+ NEBULA: "1",
1274
+ VERCEL: "1",
1275
+ NOW_BUILDER: "1"
1276
+ };
1277
+ const installCmd = pmService.installCommand(pm);
1278
+ yield* Console4.log(`Running: ${installCmd}`);
1279
+ yield* Effect9.tryPromise({
1280
+ try: () => exec2({
1281
+ command: installCmd,
1282
+ cwd,
1283
+ env: buildEnv,
1284
+ onOutput: (chunk) => {
1285
+ Console4.log(chunk).pipe(Effect9.runSync);
818
1286
  }
819
- const body = yield* Effect8.tryPromise({
820
- try: () => response.text(),
821
- catch: () => new TokenRefreshError({ message: "Failed to read refresh error response" })
822
- });
823
- return yield* Effect8.fail(
824
- new TokenRefreshError({
825
- message: `Token refresh failed: ${response.status} ${response.statusText} - ${body}`,
826
- statusCode: response.status
827
- })
828
- );
829
- }
830
- const tokens = yield* Effect8.tryPromise({
831
- try: () => response.json(),
832
- catch: () => new TokenRefreshError({ message: "Failed to parse token refresh response" })
833
- });
834
- accessToken = tokens.access_token;
835
- tokenExpirationTime = Date.now() + (tokens.expires_in || 3600) * 1e3;
836
- const newRefreshToken = tokens.refresh_token || authData.refreshToken;
837
- yield* storage.save({
838
- refreshToken: newRefreshToken,
839
- accessToken,
840
- tokenExpirationTime
841
- }).pipe(Effect8.catchAll(() => Effect8.void));
842
- yield* Effect8.log("Access token refreshed successfully");
843
- return true;
1287
+ }),
1288
+ catch: (error) => new ExecError({
1289
+ message: "Install failed",
1290
+ command: installCmd,
1291
+ cause: error instanceof Error ? error.message : String(error)
1292
+ })
844
1293
  });
845
- const getAccessToken = Effect8.gen(function* () {
846
- if (accessToken && Date.now() < tokenExpirationTime) {
847
- return accessToken;
848
- }
849
- const stored = yield* storage.load.pipe(
850
- Effect8.catchAll(() => Effect8.succeed(Option2.none()))
851
- );
852
- Option2.match(stored, {
853
- onNone: () => {
854
- },
855
- onSome: (data) => {
856
- if (data.accessToken && data.tokenExpirationTime && Date.now() < data.tokenExpirationTime) {
857
- accessToken = data.accessToken;
858
- tokenExpirationTime = data.tokenExpirationTime;
859
- }
1294
+ const buildCmd = pmService.buildCommand(pm);
1295
+ yield* Console4.log(`Running: ${buildCmd}`);
1296
+ yield* Effect9.tryPromise({
1297
+ try: () => exec2({
1298
+ command: buildCmd,
1299
+ cwd,
1300
+ env: buildEnv,
1301
+ onOutput: (chunk) => {
1302
+ Console4.log(chunk).pipe(Effect9.runSync);
860
1303
  }
861
- });
862
- if (accessToken && Date.now() < tokenExpirationTime) {
863
- return accessToken;
864
- }
865
- yield* Effect8.log("Access token expired or not available. Attempting to refresh...");
866
- yield* refreshAccessToken.pipe(
867
- Effect8.catchTag("TokenRefreshError", (err) => Effect8.fail(new NotAuthenticatedError({ message: err.message })))
1304
+ }),
1305
+ catch: (error) => new ExecError({
1306
+ message: "Build failed",
1307
+ command: buildCmd,
1308
+ cause: error instanceof Error ? error.message : String(error)
1309
+ })
1310
+ });
1311
+ yield* Console4.log("Build completed successfully.");
1312
+ }).pipe(
1313
+ Effect9.catchTags({
1314
+ PackageJsonNotFoundError: (err) => Console4.error(err.message).pipe(Effect9.andThen(Effect9.fail(err))),
1315
+ PackageJsonParseError: (err) => Console4.error(err.message).pipe(Effect9.andThen(Effect9.fail(err))),
1316
+ BuildScriptNotFoundError: (err) => Console4.error(err.message).pipe(Effect9.andThen(Effect9.fail(err))),
1317
+ PackageManagerNotFoundError: (err) => Console4.error(err.message).pipe(Effect9.andThen(Effect9.fail(err))),
1318
+ ExecError: (err) => Console4.error(`${err.message}: ${err.cause ?? err.command}`).pipe(Effect9.andThen(Effect9.fail(err)))
1319
+ })
1320
+ )
1321
+ );
1322
+
1323
+ // src/commands/debug/index.ts
1324
+ import { Command as Command5 } from "@effect/cli";
1325
+ import { Effect as Effect12 } from "effect";
1326
+
1327
+ // src/commands/debug/config/index.ts
1328
+ import { Command as Command4 } from "@effect/cli";
1329
+ import { Console as Console5, Effect as Effect11 } from "effect";
1330
+
1331
+ // src/services/project-config.ts
1332
+ import {
1333
+ detectFramework,
1334
+ mergeWithFrameworkDefaults,
1335
+ NetworkConfigLive,
1336
+ parseConfig,
1337
+ parseConfigRaw,
1338
+ postProcessConfig,
1339
+ RawConfigReader
1340
+ } from "@gigadrive/network-config";
1341
+ import { Effect as Effect10 } from "effect";
1342
+ var wrapParseErrors = (effect) => effect.pipe(
1343
+ Effect10.catchTags({
1344
+ ConfigFileNotFoundError: (e) => Effect10.fail(new ConfigParseError({ message: e.message, cause: e.filePath })),
1345
+ ConfigFileEmptyError: (e) => Effect10.fail(new ConfigParseError({ message: e.message, cause: e.filePath })),
1346
+ ConfigFileParseError: (e) => Effect10.fail(new ConfigParseError({ message: e.message, cause: e.cause })),
1347
+ ConfigVersionError: (e) => Effect10.fail(new ConfigParseError({ message: e.message, cause: e.filePath })),
1348
+ ConfigSchemaValidationError: (e) => Effect10.fail(new ConfigParseError({ message: e.message, cause: e.validationErrors.join(", ") })),
1349
+ FunctionConfigError: (e) => Effect10.fail(new ConfigParseError({ message: e.message, cause: e.functionPath }))
1350
+ })
1351
+ );
1352
+ var ProjectConfigService = class extends Effect10.Service()("ProjectConfigService", {
1353
+ accessors: true,
1354
+ dependencies: [NetworkConfigLive],
1355
+ effect: Effect10.gen(function* () {
1356
+ const rawReader = yield* RawConfigReader;
1357
+ const resolve = Effect10.fn("ProjectConfigService.resolve")(function* (cwd) {
1358
+ yield* Effect10.annotateCurrentSpan("cwd", cwd);
1359
+ yield* Effect10.log("Resolving project config", { cwd });
1360
+ const configPath = yield* rawReader.findConfig(cwd);
1361
+ const detection = yield* detectFramework(cwd).pipe(
1362
+ Effect10.catchTag("FrameworkNotDetectedError", () => Effect10.succeed(null)),
1363
+ Effect10.catchTag(
1364
+ "ManifestReadError",
1365
+ (e) => Effect10.fail(new ConfigParseError({ message: `Framework detection failed: ${e.message}`, cause: e.filePath }))
1366
+ ),
1367
+ Effect10.catchTag(
1368
+ "ManifestParseError",
1369
+ (e) => Effect10.fail(new ConfigParseError({ message: `Framework detection failed: ${e.message}`, cause: e.filePath }))
1370
+ )
868
1371
  );
869
- if (!accessToken) {
870
- return yield* Effect8.fail(
871
- new NotAuthenticatedError({ message: "No valid access token available. Please log in first." })
1372
+ if (detection) {
1373
+ yield* Effect10.log("Framework auto-detected", {
1374
+ framework: detection.framework.name,
1375
+ slug: detection.framework.slug
1376
+ });
1377
+ }
1378
+ let config;
1379
+ let resolvedConfigPath = null;
1380
+ let framework;
1381
+ if (configPath && detection) {
1382
+ yield* Effect10.log("Config file found, merging with framework defaults", { configPath });
1383
+ resolvedConfigPath = configPath;
1384
+ const userConfig = yield* wrapParseErrors(parseConfigRaw(configPath, cwd));
1385
+ const merged = yield* mergeWithFrameworkDefaults(userConfig, detection.config);
1386
+ config = yield* wrapParseErrors(postProcessConfig(merged, cwd));
1387
+ framework = { name: detection.framework.name, slug: detection.framework.slug };
1388
+ } else if (configPath) {
1389
+ yield* Effect10.log("Config file found", { configPath });
1390
+ resolvedConfigPath = configPath;
1391
+ config = yield* wrapParseErrors(parseConfig(configPath, cwd));
1392
+ } else if (detection) {
1393
+ config = yield* wrapParseErrors(postProcessConfig(detection.config, cwd));
1394
+ framework = { name: detection.framework.name, slug: detection.framework.slug };
1395
+ } else {
1396
+ return yield* Effect10.fail(
1397
+ new ConfigNotFoundError({
1398
+ message: "No config file found and no framework detected.",
1399
+ directory: cwd
1400
+ })
872
1401
  );
873
1402
  }
874
- return accessToken;
875
- });
876
- const getUserInfo = Effect8.gen(function* () {
877
- const config = yield* oauthConfig.getConfig;
878
- const token = yield* getAccessToken;
879
- const fetchUserInfo = (t) => Effect8.tryPromise({
880
- try: () => fetch(config.userinfoUrl, {
881
- headers: {
882
- Authorization: `Bearer ${t}`,
883
- Accept: "application/json"
884
- }
885
- }),
886
- catch: (error) => new UserInfoFetchError({
887
- message: `Failed to fetch user info: ${error instanceof Error ? error.message : String(error)}`
888
- })
889
- });
890
- let response = yield* fetchUserInfo(token);
891
- if (response.status === 401 || response.status === 403) {
892
- yield* refreshAccessToken.pipe(
893
- Effect8.catchTag("TokenRefreshError", (err) => Effect8.fail(new UserInfoFetchError({ message: err.message })))
894
- );
895
- if (!accessToken) {
896
- return yield* Effect8.fail(new UserInfoFetchError({ message: "Token refresh failed" }));
897
- }
898
- response = yield* fetchUserInfo(accessToken);
1403
+ for (const warning of config.warnings) {
1404
+ yield* Effect10.logWarning(warning);
899
1405
  }
900
- if (!response.ok) {
901
- const body = yield* Effect8.tryPromise({
902
- try: () => response.text(),
903
- catch: () => new UserInfoFetchError({ message: "Failed to read user info error response" })
904
- });
905
- return yield* Effect8.fail(
906
- new UserInfoFetchError({
907
- message: `Failed to fetch user info: ${response.status} ${response.statusText}${body ? ` - ${body}` : ""}`,
908
- statusCode: response.status
1406
+ if (config.errors.length > 0) {
1407
+ return yield* Effect10.fail(
1408
+ new ConfigValidationError({
1409
+ message: "Config file has validation errors",
1410
+ errors: config.errors
909
1411
  })
910
1412
  );
911
1413
  }
912
- const data = yield* Effect8.tryPromise({
913
- try: () => response.json(),
914
- catch: () => new UserInfoFetchError({ message: "Failed to parse user info response" })
915
- });
916
- return data;
1414
+ return { config, configPath: resolvedConfigPath, framework };
917
1415
  });
918
- const inferUserName = (info) => {
919
- const isNonEmptyString = (value) => typeof value === "string" && value.trim() !== "";
920
- const name = info.name;
921
- const givenName = info.given_name;
922
- const familyName = info.family_name;
923
- const preferredUsername = info.preferred_username;
924
- const email = info.email;
925
- return isNonEmptyString(name) && name || (isNonEmptyString(givenName) && isNonEmptyString(familyName) ? `${givenName} ${familyName}` : void 0) || isNonEmptyString(preferredUsername) && preferredUsername || isNonEmptyString(email) && email || "your account";
926
- };
927
- return { login, logout, getAccessToken, getUserInfo, refreshAccessToken, inferUserName };
1416
+ return { resolve };
928
1417
  })
929
1418
  }) {
930
1419
  };
931
1420
 
1421
+ // src/commands/debug/config/index.ts
1422
+ var debugConfigCommand = Command4.make(
1423
+ "config",
1424
+ {},
1425
+ () => Effect11.gen(function* () {
1426
+ const projectConfig = yield* ProjectConfigService;
1427
+ const cwd = process.cwd();
1428
+ const { config, configPath } = yield* projectConfig.resolve(cwd);
1429
+ yield* Console5.log(`Config file found at: ${configPath}`);
1430
+ yield* Console5.log(JSON.stringify(config, null, 2));
1431
+ }).pipe(
1432
+ Effect11.catchTags({
1433
+ ConfigNotFoundError: (err) => Console5.error(err.message),
1434
+ ConfigParseError: (err) => Console5.error(`Config parse error: ${err.message}`),
1435
+ ConfigValidationError: (err) => Effect11.gen(function* () {
1436
+ for (const e of err.errors) {
1437
+ yield* Console5.error(e);
1438
+ }
1439
+ yield* Console5.error(err.message);
1440
+ })
1441
+ })
1442
+ )
1443
+ );
1444
+
1445
+ // src/commands/debug/index.ts
1446
+ var debugBase = Command5.make("debug", {}, () => Effect12.void);
1447
+ var debugCommand = debugBase.pipe(Command5.withSubcommands([debugConfigCommand]));
1448
+
1449
+ // src/commands/deployments/index.ts
1450
+ import { Args as Args2, Command as Command6, Options as Options3 } from "@effect/cli";
1451
+ import { Console as Console6, Effect as Effect13, Option as Option6 } from "effect";
1452
+ var DEPLOYMENT_STATUSES = ["PENDING", "QUEUED", "STARTING", "BUILDING", "PROVISIONING", "ACTIVE", "FAILED"];
1453
+ var appOption2 = Options3.text("app").pipe(
1454
+ Options3.withAlias("a"),
1455
+ Options3.withDescription("Filter by application ID (defaults to the linked application)"),
1456
+ Options3.optional
1457
+ );
1458
+ var orgOption3 = Options3.text("org").pipe(
1459
+ Options3.withAlias("o"),
1460
+ Options3.withDescription("Filter by organization ID"),
1461
+ Options3.optional
1462
+ );
1463
+ var statusOption = Options3.choice("status", DEPLOYMENT_STATUSES).pipe(
1464
+ Options3.withAlias("s"),
1465
+ Options3.withDescription("Filter by deployment status"),
1466
+ Options3.optional
1467
+ );
1468
+ var resolveApplicationFilter = (app) => Option6.match(app, {
1469
+ onSome: (id) => Effect13.succeed(id),
1470
+ onNone: () => Effect13.gen(function* () {
1471
+ const projectLink = yield* ProjectLinkService;
1472
+ const link = yield* projectLink.load(process.cwd());
1473
+ return Option6.match(link, { onNone: () => void 0, onSome: (l) => l.applicationId });
1474
+ })
1475
+ });
1476
+ var deploymentsListCommand = Command6.make(
1477
+ "list",
1478
+ { app: appOption2, org: orgOption3, status: statusOption },
1479
+ ({ app, org, status }) => Effect13.gen(function* () {
1480
+ const apiClient = yield* ApiClientService;
1481
+ const applicationId = yield* resolveApplicationFilter(app);
1482
+ const { items, total } = yield* apiClient.request(
1483
+ (client) => client.deployments.list({
1484
+ applicationId,
1485
+ organizationId: Option6.getOrUndefined(org),
1486
+ status: Option6.getOrUndefined(status)
1487
+ })
1488
+ );
1489
+ if (items.length === 0) {
1490
+ yield* Console6.log("No deployments found.");
1491
+ return;
1492
+ }
1493
+ for (const d of items) {
1494
+ yield* Console6.log(`${d.id} ${d.status.padEnd(12)} ${d.createdAt}`);
1495
+ }
1496
+ yield* Console6.log(`
1497
+ ${total} deployment(s).`);
1498
+ }).pipe(
1499
+ Effect13.catchTags({
1500
+ NotAuthenticatedError: (err) => Console6.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
1501
+ Effect13.andThen(Effect13.fail(err))
1502
+ ),
1503
+ ApiRequestError: (err) => Console6.error(`Failed to list deployments: ${err.message}`).pipe(Effect13.andThen(Effect13.fail(err))),
1504
+ ProjectLinkReadError: (err) => Console6.error(err.message).pipe(Effect13.andThen(Effect13.fail(err)))
1505
+ })
1506
+ )
1507
+ );
1508
+ var deploymentIdArg = Args2.text({ name: "deployment-id" }).pipe(Args2.withDescription("The deployment ID to inspect"));
1509
+ var deploymentsInspectCommand = Command6.make(
1510
+ "inspect",
1511
+ { deploymentId: deploymentIdArg },
1512
+ ({ deploymentId }) => Effect13.gen(function* () {
1513
+ const apiClient = yield* ApiClientService;
1514
+ const deployment = yield* apiClient.request((client) => client.deployments.get(deploymentId));
1515
+ yield* Console6.log(`ID: ${deployment.id}`);
1516
+ yield* Console6.log(`Application: ${deployment.applicationId}`);
1517
+ yield* Console6.log(`Status: ${deployment.status}`);
1518
+ yield* Console6.log(`Created: ${deployment.createdAt}`);
1519
+ yield* Console6.log(`Updated: ${deployment.updatedAt}`);
1520
+ const hostnames = yield* apiClient.request((client) => client.deployments.getHostnames(deploymentId)).pipe(Effect13.catchAll(() => Effect13.succeed({ items: [], total: 0 })));
1521
+ if (hostnames.items.length > 0) {
1522
+ yield* Console6.log("Hostnames:");
1523
+ for (const h of hostnames.items) {
1524
+ yield* Console6.log(` https://${h.hostname}${h.active ? "" : " (inactive)"}`);
1525
+ }
1526
+ }
1527
+ }).pipe(
1528
+ Effect13.catchTags({
1529
+ NotAuthenticatedError: (err) => Console6.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
1530
+ Effect13.andThen(Effect13.fail(err))
1531
+ ),
1532
+ ApiRequestError: (err) => Console6.error(`Failed to inspect deployment: ${err.message}`).pipe(Effect13.andThen(Effect13.fail(err)))
1533
+ })
1534
+ )
1535
+ );
1536
+ var deploymentsBase = Command6.make("deployments", {}, () => Effect13.void);
1537
+ var deploymentsCommand = deploymentsBase.pipe(
1538
+ Command6.withSubcommands([deploymentsListCommand, deploymentsInspectCommand])
1539
+ );
1540
+
1541
+ // 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 Effect14, Option as Option7 } from "effect";
1544
+ var appOption3 = Options4.text("app").pipe(
1545
+ Options4.withAlias("a"),
1546
+ Options4.withDescription("Operate on this application (overrides the linked application)"),
1547
+ Options4.optional
1548
+ );
1549
+ var orgOption4 = Options4.text("org").pipe(
1550
+ Options4.withAlias("o"),
1551
+ Options4.withDescription("Operate on this organization instead of an application"),
1552
+ Options4.optional
1553
+ );
1554
+ var sensitiveOption = Options4.boolean("sensitive").pipe(
1555
+ Options4.withDescription("Store the value as sensitive (hidden in API responses)")
1556
+ );
1557
+ var envIdOption = Options4.text("env").pipe(
1558
+ Options4.withAlias("e"),
1559
+ Options4.withDescription("Environment ID for an environment-scoped variable (application scope only)"),
1560
+ Options4.optional
1561
+ );
1562
+ var keyValueArg = Args3.text({ name: "key=value" }).pipe(
1563
+ Args3.withDescription("Variable assignment, e.g. DATABASE_URL=postgres://user:pass@host/db")
1564
+ );
1565
+ var keyOrIdArg = Args3.text({ name: "key-or-id" }).pipe(Args3.withDescription("Variable key or ID to remove"));
1566
+ var resolveScope = (app, org) => Effect14.gen(function* () {
1567
+ if (Option7.isSome(org)) {
1568
+ return { kind: "organization", id: org.value };
1569
+ }
1570
+ const projectLink = yield* ProjectLinkService;
1571
+ const id = yield* Option7.match(app, {
1572
+ onSome: (value) => Effect14.succeed(value),
1573
+ onNone: () => projectLink.resolve(process.cwd()).pipe(Effect14.map((link) => link.applicationId))
1574
+ });
1575
+ return { kind: "application", id };
1576
+ });
1577
+ var envListCommand = Command7.make(
1578
+ "list",
1579
+ { app: appOption3, org: orgOption4 },
1580
+ ({ app, org }) => Effect14.gen(function* () {
1581
+ const apiClient = yield* ApiClientService;
1582
+ const scope = yield* resolveScope(app, org);
1583
+ const { items } = yield* apiClient.request(
1584
+ (client) => scope.kind === "organization" ? client.organizations.envVars.list(scope.id) : client.applications.envVars.list(scope.id)
1585
+ );
1586
+ if (items.length === 0) {
1587
+ yield* Console7.log("No environment variables set.");
1588
+ return;
1589
+ }
1590
+ for (const v of items) {
1591
+ yield* Console7.log(`${v.key}=${v.sensitive ? "***" : v.value ?? ""}`);
1592
+ }
1593
+ }).pipe(
1594
+ Effect14.catchTags({
1595
+ NotAuthenticatedError: (err) => Console7.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
1596
+ Effect14.andThen(Effect14.fail(err))
1597
+ ),
1598
+ ApiRequestError: (err) => Console7.error(`Failed to list environment variables: ${err.message}`).pipe(Effect14.andThen(Effect14.fail(err))),
1599
+ ProjectNotLinkedError: (err) => Console7.error(err.message).pipe(Effect14.andThen(Effect14.fail(err))),
1600
+ ProjectLinkReadError: (err) => Console7.error(err.message).pipe(Effect14.andThen(Effect14.fail(err)))
1601
+ })
1602
+ )
1603
+ );
1604
+ var envSetCommand = Command7.make(
1605
+ "set",
1606
+ { keyValue: keyValueArg, app: appOption3, org: orgOption4, sensitive: sensitiveOption, env: envIdOption },
1607
+ ({ keyValue, app, org, sensitive, env }) => Effect14.gen(function* () {
1608
+ const separator = keyValue.indexOf("=");
1609
+ if (separator <= 0) {
1610
+ return yield* Effect14.fail(
1611
+ new InvalidEnvVarFormatError({
1612
+ message: 'Invalid format. Use KEY=VALUE, e.g. "gigadrive env set DATABASE_URL=postgres://...".'
1613
+ })
1614
+ );
1615
+ }
1616
+ const key = keyValue.slice(0, separator);
1617
+ const value = keyValue.slice(separator + 1);
1618
+ const apiClient = yield* ApiClientService;
1619
+ const scope = yield* resolveScope(app, org);
1620
+ const environmentId = Option7.getOrUndefined(env);
1621
+ const created = yield* apiClient.request(
1622
+ (client) => scope.kind === "organization" ? client.organizations.envVars.create(scope.id, { key, value, sensitive }) : client.applications.envVars.create(scope.id, { key, value, sensitive, environmentId })
1623
+ );
1624
+ yield* Console7.log(`Set ${created.key}.`);
1625
+ }).pipe(
1626
+ Effect14.catchTags({
1627
+ InvalidEnvVarFormatError: (err) => Console7.error(err.message).pipe(Effect14.andThen(Effect14.fail(err))),
1628
+ NotAuthenticatedError: (err) => Console7.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
1629
+ Effect14.andThen(Effect14.fail(err))
1630
+ ),
1631
+ ApiRequestError: (err) => Console7.error(`Failed to set environment variable: ${err.message}`).pipe(Effect14.andThen(Effect14.fail(err))),
1632
+ ProjectNotLinkedError: (err) => Console7.error(err.message).pipe(Effect14.andThen(Effect14.fail(err))),
1633
+ ProjectLinkReadError: (err) => Console7.error(err.message).pipe(Effect14.andThen(Effect14.fail(err)))
1634
+ })
1635
+ )
1636
+ );
1637
+ var envRmCommand = Command7.make(
1638
+ "rm",
1639
+ { keyOrId: keyOrIdArg, app: appOption3, org: orgOption4 },
1640
+ ({ keyOrId, app, org }) => Effect14.gen(function* () {
1641
+ const apiClient = yield* ApiClientService;
1642
+ const scope = yield* resolveScope(app, org);
1643
+ const { items } = yield* apiClient.request(
1644
+ (client) => scope.kind === "organization" ? client.organizations.envVars.list(scope.id) : client.applications.envVars.list(scope.id)
1645
+ );
1646
+ const target = items.find((v) => v.id === keyOrId || v.key === keyOrId);
1647
+ if (target === void 0) {
1648
+ return yield* Effect14.fail(
1649
+ new EnvVarNotFoundError({ message: `No environment variable matching "${keyOrId}".` })
1650
+ );
1651
+ }
1652
+ yield* apiClient.request(
1653
+ (client) => scope.kind === "organization" ? client.organizations.envVars.delete(scope.id, target.id) : client.applications.envVars.delete(scope.id, target.id)
1654
+ );
1655
+ yield* Console7.log(`Removed ${target.key}.`);
1656
+ }).pipe(
1657
+ Effect14.catchTags({
1658
+ EnvVarNotFoundError: (err) => Console7.error(err.message).pipe(Effect14.andThen(Effect14.fail(err))),
1659
+ NotAuthenticatedError: (err) => Console7.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
1660
+ Effect14.andThen(Effect14.fail(err))
1661
+ ),
1662
+ ApiRequestError: (err) => Console7.error(`Failed to remove environment variable: ${err.message}`).pipe(Effect14.andThen(Effect14.fail(err))),
1663
+ ProjectNotLinkedError: (err) => Console7.error(err.message).pipe(Effect14.andThen(Effect14.fail(err))),
1664
+ ProjectLinkReadError: (err) => Console7.error(err.message).pipe(Effect14.andThen(Effect14.fail(err)))
1665
+ })
1666
+ )
1667
+ );
1668
+ var envBase = Command7.make("env", {}, () => Effect14.void);
1669
+ var envCommand = envBase.pipe(Command7.withSubcommands([envListCommand, envSetCommand, envRmCommand]));
1670
+
1671
+ // src/commands/link/index.ts
1672
+ import { Command as Command8, Options as Options5, Prompt } from "@effect/cli";
1673
+ import { Console as Console8, Effect as Effect15, Option as Option8 } from "effect";
1674
+ var appOption4 = Options5.text("app").pipe(
1675
+ Options5.withAlias("a"),
1676
+ Options5.withDescription("Application ID to link (skips the interactive picker)"),
1677
+ Options5.optional
1678
+ );
1679
+ var orgOption5 = Options5.text("org").pipe(
1680
+ Options5.withAlias("o"),
1681
+ Options5.withDescription("Only list applications belonging to this organization ID"),
1682
+ Options5.optional
1683
+ );
1684
+ var linkCommand = Command8.make(
1685
+ "link",
1686
+ { app: appOption4, org: orgOption5 },
1687
+ ({ app, org }) => Effect15.gen(function* () {
1688
+ const apiClient = yield* ApiClientService;
1689
+ const projectLink = yield* ProjectLinkService;
1690
+ const cwd = process.cwd();
1691
+ const organizationId = Option8.getOrUndefined(org);
1692
+ const { items: apps } = yield* apiClient.request((client) => client.applications.list({ organizationId }));
1693
+ if (apps.length === 0) {
1694
+ return yield* Effect15.fail(
1695
+ new NoApplicationsFoundError({
1696
+ message: 'No applications found. Create one in the Gigadrive console, then run "gigadrive link" again.'
1697
+ })
1698
+ );
1699
+ }
1700
+ const explicitAppId = Option8.getOrUndefined(app);
1701
+ let applicationId;
1702
+ let linkedOrganizationId;
1703
+ if (explicitAppId !== void 0) {
1704
+ const match = apps.find((a) => a.id === explicitAppId);
1705
+ if (match === void 0) {
1706
+ return yield* Effect15.fail(
1707
+ new ApplicationNotFoundError({
1708
+ message: `Application "${explicitAppId}" was not found among your applications.`
1709
+ })
1710
+ );
1711
+ }
1712
+ applicationId = match.id;
1713
+ linkedOrganizationId = match.organization.id;
1714
+ } else if (apps.length === 1) {
1715
+ applicationId = apps[0].id;
1716
+ linkedOrganizationId = apps[0].organization.id;
1717
+ } else {
1718
+ const chosen = yield* Prompt.run(
1719
+ Prompt.select({
1720
+ message: "Select an application to link",
1721
+ choices: apps.map((a) => ({ title: `${a.name} (${a.organization.name})`, value: a }))
1722
+ })
1723
+ );
1724
+ applicationId = chosen.id;
1725
+ linkedOrganizationId = chosen.organization.id;
1726
+ }
1727
+ yield* projectLink.save(cwd, { applicationId, organizationId: linkedOrganizationId });
1728
+ yield* Console8.log(`Linked this directory to application ${applicationId}.`);
1729
+ yield* Console8.log('Tip: add ".gigadrive/" to your .gitignore.');
1730
+ }).pipe(
1731
+ Effect15.catchTags({
1732
+ // Ctrl+C during the picker is a deliberate cancel — exit 0.
1733
+ QuitException: () => Console8.log("Cancelled."),
1734
+ NotAuthenticatedError: (err) => Console8.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
1735
+ Effect15.andThen(Effect15.fail(err))
1736
+ ),
1737
+ ApiRequestError: (err) => Console8.error(`Failed to list applications: ${err.message}`).pipe(Effect15.andThen(Effect15.fail(err))),
1738
+ NoApplicationsFoundError: (err) => Console8.error(err.message).pipe(Effect15.andThen(Effect15.fail(err))),
1739
+ ApplicationNotFoundError: (err) => Console8.error(err.message).pipe(Effect15.andThen(Effect15.fail(err))),
1740
+ ProjectLinkWriteError: (err) => Console8.error(err.message).pipe(Effect15.andThen(Effect15.fail(err)))
1741
+ })
1742
+ )
1743
+ );
1744
+ var unlinkCommand = Command8.make(
1745
+ "unlink",
1746
+ {},
1747
+ () => Effect15.gen(function* () {
1748
+ const projectLink = yield* ProjectLinkService;
1749
+ yield* projectLink.remove(process.cwd());
1750
+ yield* Console8.log("Unlinked this directory.");
1751
+ }).pipe(
1752
+ Effect15.catchTags({
1753
+ ProjectLinkWriteError: (err) => Console8.error(err.message).pipe(Effect15.andThen(Effect15.fail(err)))
1754
+ })
1755
+ )
1756
+ );
1757
+
932
1758
  // src/commands/login/index.ts
933
- var loginCommand = Command4.make(
1759
+ import { Command as Command9 } from "@effect/cli";
1760
+ import { Console as Console9, Effect as Effect16 } from "effect";
1761
+ var loginCommand = Command9.make(
934
1762
  "login",
935
1763
  {},
936
- () => Effect9.gen(function* () {
937
- yield* Console4.log("Initiating login...");
1764
+ () => Effect16.gen(function* () {
1765
+ yield* Console9.log("Initiating login...");
938
1766
  const auth = yield* AuthService;
939
1767
  yield* auth.login;
940
- const info = yield* auth.getUserInfo.pipe(Effect9.catchAll(() => Effect9.succeed({})));
1768
+ const info = yield* auth.getUserInfo.pipe(Effect16.catchAll(() => Effect16.succeed({})));
941
1769
  const name = auth.inferUserName(info);
942
- yield* Console4.log(`You are now logged in as ${name}.`);
1770
+ yield* Console9.log(`You are now logged in as ${name}.`);
943
1771
  }).pipe(
944
- Effect9.catchTags({
945
- LoginFlowError: (err) => Console4.error(`Login failed: ${err.message}`),
946
- TokenExchangeError: (err) => Console4.error(`Login failed: ${err.message}`),
947
- OAuthDiscoveryError: (err) => Console4.error(`Login failed: ${err.message}`),
948
- AuthStorageWriteError: (err) => Console4.error(`Failed to save credentials: ${err.message}`)
1772
+ Effect16.catchTags({
1773
+ LoginFlowError: (err) => Console9.error(`Login failed: ${err.message}`),
1774
+ TokenExchangeError: (err) => Console9.error(`Login failed: ${err.message}`),
1775
+ OAuthDiscoveryError: (err) => Console9.error(`Login failed: ${err.message}`),
1776
+ AuthStorageWriteError: (err) => Console9.error(`Failed to save credentials: ${err.message}`)
1777
+ })
1778
+ )
1779
+ );
1780
+
1781
+ // src/commands/logout/index.ts
1782
+ import { Command as Command10 } from "@effect/cli";
1783
+ import { Console as Console10, Effect as Effect17 } from "effect";
1784
+ var logoutCommand = Command10.make(
1785
+ "logout",
1786
+ {},
1787
+ () => Effect17.gen(function* () {
1788
+ const auth = yield* AuthService;
1789
+ yield* auth.logout;
1790
+ yield* Console10.log("Logged out.");
1791
+ }).pipe(
1792
+ Effect17.catchTags({
1793
+ AuthStorageWriteError: (err) => Console10.error(`Failed to log out: ${err.message}`).pipe(Effect17.andThen(Effect17.fail(err)))
949
1794
  })
950
1795
  )
951
1796
  );
952
1797
 
953
1798
  // src/commands/platform/index.ts
954
- import { Command as Command6 } from "@effect/cli";
955
- import { Effect as Effect13 } from "effect";
1799
+ import { Command as Command12 } from "@effect/cli";
1800
+ import { Effect as Effect21 } from "effect";
956
1801
 
957
1802
  // src/commands/platform/deploy/index.ts
958
- import { Command as Command5 } from "@effect/cli";
959
- import { FileSystem as FileSystem5, Path as Path5 } from "@effect/platform";
1803
+ import { Command as Command11, Options as Options6 } from "@effect/cli";
1804
+ import { FileSystem as FileSystem7, Path as Path6 } from "@effect/platform";
960
1805
  import { formatFileSize } from "@gigadrive/commons";
961
- import { Console as Console5, Duration, Effect as Effect12, Schedule, Stream } from "effect";
1806
+ import { Console as Console11, Duration, Effect as Effect20, Option as Option9, Schedule, Stream } from "effect";
962
1807
  import * as os2 from "os";
963
1808
 
964
1809
  // src/services/archive.ts
965
- import { FileSystem as FileSystem4, Path as Path4 } from "@effect/platform";
1810
+ import { FileSystem as FileSystem6, Path as Path5 } from "@effect/platform";
966
1811
  import { getFilesForPattern } from "@gigadrive/build-utils";
967
1812
  import archiver from "archiver";
968
- import { Effect as Effect10 } from "effect";
1813
+ import { Effect as Effect18 } from "effect";
969
1814
  import ignore from "ignore";
970
1815
  import * as nodeFs from "fs";
971
1816
  import * as nodeFsPromises from "fs/promises";
972
1817
  var IGNORE_FILE_NAMES = [".gigadriveignore", ".vercelignore", ".dockerignore", ".nowignore"];
973
1818
  var readIgnoreFile = (fs, ignorePath) => fs.readFileString(ignorePath, "utf8").pipe(
974
- Effect10.map((content) => content.split("\n").filter((line) => line.trim() !== "")),
975
- Effect10.catchAll(() => Effect10.succeed([]))
1819
+ Effect18.map((content) => content.split("\n").filter((line) => line.trim() !== "")),
1820
+ Effect18.catchAll(() => Effect18.succeed([]))
976
1821
  );
977
- var collectIgnorePatterns = (fs, pathService, dir, baseDir, ignoreRules) => Effect10.gen(function* () {
978
- const items = yield* fs.readDirectory(dir).pipe(Effect10.mapError(() => new ArchiveCreateError({ message: `Failed to read directory: ${dir}` })));
1822
+ var collectIgnorePatterns = (fs, pathService, dir, baseDir, ignoreRules) => Effect18.gen(function* () {
1823
+ const items = yield* fs.readDirectory(dir).pipe(Effect18.mapError(() => new ArchiveCreateError({ message: `Failed to read directory: ${dir}` })));
979
1824
  for (const item of items) {
980
1825
  const fullPath = pathService.join(dir, item);
981
1826
  const relativePath = pathService.relative(baseDir, fullPath);
982
- const stat = yield* Effect10.tryPromise({
1827
+ const stat = yield* Effect18.tryPromise({
983
1828
  try: () => nodeFsPromises.lstat(fullPath),
984
1829
  catch: () => new ArchiveCreateError({ message: `Failed to stat: ${fullPath}` })
985
1830
  });
@@ -999,8 +1844,8 @@ var collectIgnorePatterns = (fs, pathService, dir, baseDir, ignoreRules) => Effe
999
1844
  yield* collectIgnorePatterns(fs, pathService, fullPath, baseDir, ignoreRules);
1000
1845
  }
1001
1846
  }
1002
- }).pipe(Effect10.catchAll(() => Effect10.void));
1003
- var initializeIgnoreRules = (fs, pathService, baseDir) => Effect10.gen(function* () {
1847
+ }).pipe(Effect18.catchAll(() => Effect18.void));
1848
+ var initializeIgnoreRules = (fs, pathService, baseDir) => Effect18.gen(function* () {
1004
1849
  const ignoreRules = ignore();
1005
1850
  ignoreRules.add([".git/", "**/.DS_Store", "**/Thumbs.db"]);
1006
1851
  for (const ignoreFileName of IGNORE_FILE_NAMES) {
@@ -1015,14 +1860,14 @@ var initializeIgnoreRules = (fs, pathService, baseDir) => Effect10.gen(function*
1015
1860
  yield* collectIgnorePatterns(fs, pathService, baseDir, baseDir, ignoreRules);
1016
1861
  return ignoreRules;
1017
1862
  });
1018
- var getFilesToInclude = (fs, pathService, dir, ignoreRules) => Effect10.gen(function* () {
1863
+ var getFilesToInclude = (fs, pathService, dir, ignoreRules) => Effect18.gen(function* () {
1019
1864
  const filesToInclude = [];
1020
- const walkDir = (currentDir) => Effect10.gen(function* () {
1021
- const items = yield* fs.readDirectory(currentDir).pipe(Effect10.mapError(() => new ArchiveCreateError({ message: `Failed to read directory: ${currentDir}` })));
1865
+ const walkDir = (currentDir) => Effect18.gen(function* () {
1866
+ const items = yield* fs.readDirectory(currentDir).pipe(Effect18.mapError(() => new ArchiveCreateError({ message: `Failed to read directory: ${currentDir}` })));
1022
1867
  for (const item of items) {
1023
1868
  const fullPath = pathService.join(currentDir, item);
1024
1869
  const relativePath = pathService.relative(dir, fullPath);
1025
- const stat = yield* Effect10.tryPromise({
1870
+ const stat = yield* Effect18.tryPromise({
1026
1871
  try: () => nodeFsPromises.lstat(fullPath),
1027
1872
  catch: () => new ArchiveCreateError({ message: `Failed to stat: ${fullPath}` })
1028
1873
  });
@@ -1036,21 +1881,21 @@ var getFilesToInclude = (fs, pathService, dir, ignoreRules) => Effect10.gen(func
1036
1881
  }
1037
1882
  }
1038
1883
  }
1039
- }).pipe(Effect10.catchAll(() => Effect10.void));
1884
+ }).pipe(Effect18.catchAll(() => Effect18.void));
1040
1885
  yield* walkDir(dir);
1041
1886
  return filesToInclude;
1042
1887
  });
1043
- var ArchiveService = class extends Effect10.Service()("ArchiveService", {
1888
+ var ArchiveService = class extends Effect18.Service()("ArchiveService", {
1044
1889
  accessors: true,
1045
- effect: Effect10.gen(function* () {
1046
- const fs = yield* FileSystem4.FileSystem;
1047
- const pathService = yield* Path4.Path;
1048
- const createZipArchive = Effect10.fn("ArchiveService.createZipArchive")(function* (inputDir, outputFile, options = {}) {
1049
- yield* Effect10.annotateCurrentSpan("inputDir", inputDir);
1050
- yield* Effect10.annotateCurrentSpan("outputFile", outputFile);
1051
- yield* Effect10.log("Creating zip archive", { inputDir, outputFile });
1890
+ effect: Effect18.gen(function* () {
1891
+ const fs = yield* FileSystem6.FileSystem;
1892
+ const pathService = yield* Path5.Path;
1893
+ const createZipArchive = Effect18.fn("ArchiveService.createZipArchive")(function* (inputDir, outputFile, options = {}) {
1894
+ yield* Effect18.annotateCurrentSpan("inputDir", inputDir);
1895
+ yield* Effect18.annotateCurrentSpan("outputFile", outputFile);
1896
+ yield* Effect18.log("Creating zip archive", { inputDir, outputFile });
1052
1897
  const ignoreRules = options.useIgnoreFiles !== false ? yield* initializeIgnoreRules(fs, pathService, inputDir) : ignore();
1053
- const filesToInclude = options.whitelist ? yield* Effect10.tryPromise({
1898
+ const filesToInclude = options.whitelist ? yield* Effect18.tryPromise({
1054
1899
  try: () => Promise.all(
1055
1900
  options.whitelist.map((file) => getFilesForPattern(file, inputDir, options.excludeFiles ?? []))
1056
1901
  ),
@@ -1058,25 +1903,25 @@ var ArchiveService = class extends Effect10.Service()("ArchiveService", {
1058
1903
  message: "Failed to resolve whitelist patterns",
1059
1904
  cause: error instanceof Error ? error.message : String(error)
1060
1905
  })
1061
- }) : yield* Effect10.gen(function* () {
1906
+ }) : yield* Effect18.gen(function* () {
1062
1907
  if (options.excludeFiles && options.excludeFiles.length > 0) {
1063
1908
  ignoreRules.add(options.excludeFiles);
1064
1909
  }
1065
1910
  return yield* getFilesToInclude(fs, pathService, inputDir, ignoreRules).pipe(
1066
- Effect10.map((files) => [files])
1911
+ Effect18.map((files) => [files])
1067
1912
  );
1068
1913
  });
1069
- const archiveExists = yield* fs.exists(outputFile).pipe(Effect10.catchAll(() => Effect10.succeed(false)));
1914
+ const archiveExists = yield* fs.exists(outputFile).pipe(Effect18.catchAll(() => Effect18.succeed(false)));
1070
1915
  if (archiveExists) {
1071
1916
  yield* fs.remove(outputFile).pipe(
1072
- Effect10.mapError(
1917
+ Effect18.mapError(
1073
1918
  (error) => new ArchiveCreateError({
1074
1919
  message: `Failed to remove existing archive: ${String(error)}`
1075
1920
  })
1076
1921
  )
1077
1922
  );
1078
1923
  }
1079
- yield* Effect10.tryPromise({
1924
+ yield* Effect18.tryPromise({
1080
1925
  try: () => new Promise((resolve, reject) => {
1081
1926
  const output = nodeFs.createWriteStream(outputFile);
1082
1927
  const archive = archiver("zip", { zlib: { level: 9 } });
@@ -1095,14 +1940,14 @@ var ArchiveService = class extends Effect10.Service()("ArchiveService", {
1095
1940
  })
1096
1941
  });
1097
1942
  const fileInfo = yield* fs.stat(outputFile).pipe(
1098
- Effect10.mapError(
1943
+ Effect18.mapError(
1099
1944
  (error) => new ArchiveCreateError({
1100
1945
  message: `Failed to stat archive: ${String(error)}`
1101
1946
  })
1102
1947
  )
1103
1948
  );
1104
1949
  const size = Number(fileInfo.size);
1105
- yield* Effect10.log("Archive created", { size });
1950
+ yield* Effect18.log("Archive created", { size });
1106
1951
  return { path: outputFile, size };
1107
1952
  });
1108
1953
  return { createZipArchive };
@@ -1111,250 +1956,104 @@ var ArchiveService = class extends Effect10.Service()("ArchiveService", {
1111
1956
  };
1112
1957
 
1113
1958
  // src/services/deployment-api.ts
1114
- import { objectToQueryString } from "@gigadrive/commons";
1115
- import { Config as Config2, Effect as Effect11, Schema as Schema5 } from "effect";
1116
- var CreateDeploymentResponse = Schema5.Struct({ id: Schema5.String });
1117
- var StartUploadResponse = Schema5.Struct({ uploadId: Schema5.String });
1118
- var PresignedUrlResponse = Schema5.Struct({ url: Schema5.String });
1119
- var DeploymentStatusResponse = Schema5.Struct({
1120
- status: Schema5.Literal("PENDING", "BUILDING", "PROVISIONING", "FAILED", "ACTIVE", "SUSPENDED")
1121
- });
1122
- var DeploymentLogPageResponse = Schema5.Struct({
1123
- totalItems: Schema5.Number,
1124
- limit: Schema5.Number,
1125
- offset: Schema5.Number,
1126
- items: Schema5.Array(
1127
- Schema5.Struct({
1128
- id: Schema5.String,
1129
- message: Schema5.String,
1130
- type: Schema5.Literal("INFO", "ERROR", "WARN"),
1131
- createdAt: Schema5.String
1132
- })
1133
- )
1134
- });
1135
- var ApiBaseUrl = Config2.string("GIGADRIVE_API_BASE_URL").pipe(Config2.withDefault("http://localhost:3000"));
1136
- var DeploymentApiService = class extends Effect11.Service()("DeploymentApiService", {
1959
+ import { Effect as Effect19 } from "effect";
1960
+ var DeploymentApiService = class extends Effect19.Service()("DeploymentApiService", {
1137
1961
  accessors: true,
1138
- effect: Effect11.gen(function* () {
1139
- const baseUrl = yield* ApiBaseUrl;
1140
- const authService = yield* AuthService;
1141
- const getAuthHeaders = Effect11.gen(function* () {
1142
- const token = yield* authService.getAccessToken.pipe(
1143
- Effect11.mapError((err) => new DeploymentAuthError({ message: `Authentication failed: ${err.message}` }))
1144
- );
1145
- return { Authorization: `Bearer ${token}` };
1146
- });
1147
- const createDeployment = Effect11.fn("DeploymentApiService.createDeployment")(function* (applicationId) {
1148
- yield* Effect11.annotateCurrentSpan("applicationId", applicationId);
1149
- yield* Effect11.log("Creating deployment", { applicationId });
1150
- const authHeaders = yield* getAuthHeaders;
1151
- const response = yield* Effect11.tryPromise({
1152
- try: () => fetch(`${baseUrl}/${applicationId}/deployments`, {
1153
- method: "POST",
1154
- headers: { "Content-Type": "application/json", ...authHeaders },
1155
- body: JSON.stringify({})
1156
- }),
1962
+ dependencies: [ApiClientService.Default],
1963
+ effect: Effect19.gen(function* () {
1964
+ const apiClient = yield* ApiClientService;
1965
+ const getClient = apiClient.getClient.pipe(
1966
+ Effect19.mapError((err) => new DeploymentAuthError({ message: `Authentication failed: ${err.message}` }))
1967
+ );
1968
+ const createDeployment = Effect19.fn("DeploymentApiService.createDeployment")(function* (applicationId) {
1969
+ yield* Effect19.annotateCurrentSpan("applicationId", applicationId);
1970
+ yield* Effect19.log("Creating deployment", { applicationId });
1971
+ const client = yield* getClient;
1972
+ const deployment = yield* Effect19.tryPromise({
1973
+ try: () => client.deployments.create({ applicationId }),
1157
1974
  catch: (error) => new DeploymentCreateError({
1158
1975
  message: `Failed to create deployment: ${error instanceof Error ? error.message : String(error)}`
1159
1976
  })
1160
1977
  });
1161
- if (!response.ok) {
1162
- const body = yield* Effect11.tryPromise({
1163
- try: () => response.text(),
1164
- catch: () => new DeploymentCreateError({
1165
- message: "Failed to read deployment creation error",
1166
- statusCode: response.status
1167
- })
1168
- });
1169
- return yield* Effect11.fail(
1170
- new DeploymentCreateError({
1171
- message: `Failed to create deployment: ${body}`,
1172
- statusCode: response.status
1173
- })
1174
- );
1175
- }
1176
- const json = yield* Effect11.tryPromise({
1177
- try: () => response.json(),
1178
- catch: () => new DeploymentCreateError({ message: "Failed to parse deployment response" })
1179
- });
1180
- const decoded = yield* Schema5.decodeUnknown(CreateDeploymentResponse)(json).pipe(
1181
- Effect11.mapError(() => new DeploymentCreateError({ message: "Invalid deployment response schema" }))
1182
- );
1183
- yield* Effect11.log("Deployment created", { deploymentId: decoded.id });
1184
- return decoded.id;
1978
+ yield* Effect19.log("Deployment created", { deploymentId: deployment.id });
1979
+ return deployment.id;
1185
1980
  });
1186
- const startMultipartUpload = Effect11.fn("DeploymentApiService.startMultipartUpload")(function* (deploymentId) {
1187
- yield* Effect11.annotateCurrentSpan("deploymentId", deploymentId);
1188
- const authHeaders = yield* getAuthHeaders;
1189
- const response = yield* Effect11.tryPromise({
1190
- try: () => fetch(`${baseUrl}/deployments/${deploymentId}/pre-signed-url/start`, {
1191
- method: "GET",
1192
- headers: { "Content-Type": "application/json", ...authHeaders }
1193
- }),
1981
+ const startMultipartUpload = Effect19.fn("DeploymentApiService.startMultipartUpload")(function* (deploymentId) {
1982
+ yield* Effect19.annotateCurrentSpan("deploymentId", deploymentId);
1983
+ const client = yield* getClient;
1984
+ const result = yield* Effect19.tryPromise({
1985
+ try: () => client.deployments.startUpload(deploymentId),
1194
1986
  catch: (error) => new UploadStartError({
1195
1987
  message: `Failed to start multipart upload: ${error instanceof Error ? error.message : String(error)}`
1196
1988
  })
1197
1989
  });
1198
- if (!response.ok) {
1199
- const body = yield* Effect11.tryPromise({
1200
- try: () => response.text(),
1201
- catch: () => new UploadStartError({ message: "Failed to read upload start error" })
1202
- });
1203
- return yield* Effect11.fail(
1204
- new UploadStartError({ message: `Failed to start multipart upload: ${body}`, statusCode: response.status })
1205
- );
1206
- }
1207
- const json = yield* Effect11.tryPromise({
1208
- try: () => response.json(),
1209
- catch: () => new UploadStartError({ message: "Failed to parse upload start response" })
1210
- });
1211
- const decoded = yield* Schema5.decodeUnknown(StartUploadResponse)(json).pipe(
1212
- Effect11.mapError(() => new UploadStartError({ message: "Invalid upload start response schema" }))
1213
- );
1214
- return decoded.uploadId;
1990
+ return result.uploadId;
1215
1991
  });
1216
- const getPresignedUrl = Effect11.fn("DeploymentApiService.getPresignedUrl")(function* (deploymentId, uploadId, partNumber) {
1217
- const authHeaders = yield* getAuthHeaders;
1218
- const response = yield* Effect11.tryPromise({
1219
- try: () => fetch(
1220
- `${baseUrl}/deployments/${deploymentId}/pre-signed-url/part?uploadId=${uploadId}&partNumber=${partNumber}`,
1221
- {
1222
- method: "GET",
1223
- headers: { "Content-Type": "application/json", ...authHeaders }
1224
- }
1225
- ),
1992
+ const getPresignedUrl = Effect19.fn("DeploymentApiService.getPresignedUrl")(function* (deploymentId, uploadId, partNumber) {
1993
+ const client = yield* getClient;
1994
+ const result = yield* Effect19.tryPromise({
1995
+ try: () => client.deployments.getPresignedUrl(deploymentId, uploadId, partNumber),
1226
1996
  catch: (error) => new PresignedUrlError({
1227
1997
  message: `Failed to get presigned URL: ${error instanceof Error ? error.message : String(error)}`,
1228
1998
  partNumber
1229
1999
  })
1230
2000
  });
1231
- if (!response.ok) {
1232
- const body = yield* Effect11.tryPromise({
1233
- try: () => response.text(),
1234
- catch: () => new PresignedUrlError({ message: "Failed to read presigned URL error", partNumber })
1235
- });
1236
- return yield* Effect11.fail(
1237
- new PresignedUrlError({ message: `Failed to get presigned URL: ${body}`, partNumber })
1238
- );
1239
- }
1240
- const json = yield* Effect11.tryPromise({
1241
- try: () => response.json(),
1242
- catch: () => new PresignedUrlError({ message: "Failed to parse presigned URL response", partNumber })
1243
- });
1244
- const decoded = yield* Schema5.decodeUnknown(PresignedUrlResponse)(json).pipe(
1245
- Effect11.mapError(() => new PresignedUrlError({ message: "Invalid presigned URL response schema", partNumber }))
1246
- );
1247
- return decoded.url;
2001
+ return result.url;
1248
2002
  });
1249
- const uploadPart = Effect11.fn("DeploymentApiService.uploadPart")(function* (presignedUrl, data, partNumber) {
1250
- yield* Effect11.annotateCurrentSpan("partNumber", partNumber);
1251
- yield* Effect11.annotateCurrentSpan("size", data.length);
1252
- const response = yield* Effect11.tryPromise({
1253
- try: () => fetch(presignedUrl, {
1254
- method: "PUT",
1255
- headers: {
1256
- "Content-Type": "application/zip",
1257
- "Content-Length": String(data.length)
1258
- },
1259
- body: data
1260
- }),
2003
+ const uploadPart = Effect19.fn("DeploymentApiService.uploadPart")(function* (presignedUrl, data, partNumber) {
2004
+ yield* Effect19.annotateCurrentSpan("partNumber", partNumber);
2005
+ yield* Effect19.annotateCurrentSpan("size", data.length);
2006
+ const client = yield* getClient;
2007
+ const result = yield* Effect19.tryPromise({
2008
+ try: () => client.deployments.uploadPart(presignedUrl, data, partNumber),
1261
2009
  catch: (error) => new UploadPartError({
1262
2010
  message: `Failed to upload part ${partNumber}: ${error instanceof Error ? error.message : String(error)}`,
1263
2011
  partNumber
1264
2012
  })
1265
2013
  });
1266
- if (!response.ok) {
1267
- return yield* Effect11.fail(
1268
- new UploadPartError({
1269
- message: `Failed to upload part ${partNumber}: ${response.statusText}`,
1270
- partNumber
1271
- })
1272
- );
1273
- }
1274
- const etag = response.headers.get("ETag");
1275
- if (!etag) {
1276
- return yield* Effect11.fail(
1277
- new UploadPartError({ message: `No ETag received for part ${partNumber}`, partNumber })
1278
- );
1279
- }
1280
- return { partNumber, etag };
2014
+ return result;
1281
2015
  });
1282
- const completeUpload = Effect11.fn("DeploymentApiService.completeUpload")(function* (deploymentId, uploadId, parts) {
1283
- yield* Effect11.annotateCurrentSpan("deploymentId", deploymentId);
1284
- yield* Effect11.annotateCurrentSpan("partsCount", parts.length);
1285
- const authHeaders = yield* getAuthHeaders;
1286
- const response = yield* Effect11.tryPromise({
1287
- try: () => fetch(`${baseUrl}/deployments/${deploymentId}/pre-signed-url/complete`, {
1288
- method: "POST",
1289
- headers: { "Content-Type": "application/json", ...authHeaders },
1290
- body: JSON.stringify({ uploadId, parts })
1291
- }),
2016
+ const completeUpload = Effect19.fn("DeploymentApiService.completeUpload")(function* (deploymentId, uploadId, parts) {
2017
+ yield* Effect19.annotateCurrentSpan("deploymentId", deploymentId);
2018
+ yield* Effect19.annotateCurrentSpan("partsCount", parts.length);
2019
+ const client = yield* getClient;
2020
+ yield* Effect19.tryPromise({
2021
+ try: () => client.deployments.completeUpload(deploymentId, uploadId, parts),
1292
2022
  catch: (error) => new UploadCompleteError({
1293
2023
  message: `Failed to complete upload: ${error instanceof Error ? error.message : String(error)}`
1294
2024
  })
1295
2025
  });
1296
- if (!response.ok) {
1297
- const body = yield* Effect11.tryPromise({
1298
- try: () => response.text(),
1299
- catch: () => new UploadCompleteError({ message: "Failed to read upload complete error" })
1300
- });
1301
- return yield* Effect11.fail(new UploadCompleteError({ message: `Failed to complete upload: ${body}` }));
1302
- }
1303
- yield* Effect11.log("Upload completed successfully");
2026
+ yield* Effect19.log("Upload completed successfully");
1304
2027
  });
1305
- const getDeploymentStatus = Effect11.fn("DeploymentApiService.getDeploymentStatus")(function* (deploymentId) {
1306
- const authHeaders = yield* getAuthHeaders;
1307
- const response = yield* Effect11.tryPromise({
1308
- try: () => fetch(`${baseUrl}/deployments/${deploymentId}`, {
1309
- method: "GET",
1310
- headers: { "Content-Type": "application/json", ...authHeaders }
1311
- }),
2028
+ const getDeploymentStatus = Effect19.fn("DeploymentApiService.getDeploymentStatus")(function* (deploymentId) {
2029
+ const client = yield* getClient;
2030
+ const deployment = yield* Effect19.tryPromise({
2031
+ try: () => client.deployments.get(deploymentId),
1312
2032
  catch: (error) => new DeploymentStatusError({
1313
2033
  message: `Failed to get deployment status: ${error instanceof Error ? error.message : String(error)}`
1314
2034
  })
1315
2035
  });
1316
- if (!response.ok) {
1317
- const body = yield* Effect11.tryPromise({
1318
- try: () => response.text(),
1319
- catch: () => new DeploymentStatusError({ message: "Failed to read deployment status error" })
1320
- });
1321
- return yield* Effect11.fail(new DeploymentStatusError({ message: `Failed to get deployment status: ${body}` }));
1322
- }
1323
- const json = yield* Effect11.tryPromise({
1324
- try: () => response.json(),
1325
- catch: () => new DeploymentStatusError({ message: "Failed to parse deployment status response" })
1326
- });
1327
- const decoded = yield* Schema5.decodeUnknown(DeploymentStatusResponse)(json).pipe(
1328
- Effect11.mapError(() => new DeploymentStatusError({ message: "Invalid deployment status response schema" }))
1329
- );
1330
- return decoded.status;
2036
+ return deployment.status;
1331
2037
  });
1332
- const getLogs = Effect11.fn("DeploymentApiService.getLogs")(function* (deploymentId, options) {
1333
- const authHeaders = yield* getAuthHeaders;
1334
- const response = yield* Effect11.tryPromise({
1335
- try: () => fetch(`${baseUrl}/deployments/${deploymentId}/logs${objectToQueryString(options)}`, {
1336
- method: "GET",
1337
- headers: { "Content-Type": "application/json", ...authHeaders }
1338
- }),
2038
+ const getLogs = Effect19.fn("DeploymentApiService.getLogs")(function* (deploymentId, options) {
2039
+ const client = yield* getClient;
2040
+ const result = yield* Effect19.tryPromise({
2041
+ try: () => client.deployments.getLogs(deploymentId, options),
1339
2042
  catch: (error) => new DeploymentLogsFetchError({
1340
2043
  message: `Failed to get logs: ${error instanceof Error ? error.message : String(error)}`
1341
2044
  })
1342
2045
  });
1343
- if (!response.ok) {
1344
- const body = yield* Effect11.tryPromise({
1345
- try: () => response.text(),
1346
- catch: () => new DeploymentLogsFetchError({ message: "Failed to read logs error" })
1347
- });
1348
- return yield* Effect11.fail(new DeploymentLogsFetchError({ message: `Failed to get logs: ${body}` }));
1349
- }
1350
- const json = yield* Effect11.tryPromise({
1351
- try: () => response.json(),
1352
- catch: () => new DeploymentLogsFetchError({ message: "Failed to parse logs response" })
2046
+ return result;
2047
+ });
2048
+ const getHostnames = Effect19.fn("DeploymentApiService.getHostnames")(function* (deploymentId) {
2049
+ const client = yield* getClient;
2050
+ const result = yield* Effect19.tryPromise({
2051
+ try: () => client.deployments.getHostnames(deploymentId),
2052
+ catch: (error) => new DeploymentHostnamesFetchError({
2053
+ message: `Failed to get hostnames: ${error instanceof Error ? error.message : String(error)}`
2054
+ })
1353
2055
  });
1354
- const decoded = yield* Schema5.decodeUnknown(DeploymentLogPageResponse)(json).pipe(
1355
- Effect11.mapError(() => new DeploymentLogsFetchError({ message: "Invalid logs response schema" }))
1356
- );
1357
- return decoded;
2056
+ return result.items;
1358
2057
  });
1359
2058
  return {
1360
2059
  createDeployment,
@@ -1363,28 +2062,39 @@ var DeploymentApiService = class extends Effect11.Service()("DeploymentApiServic
1363
2062
  uploadPart,
1364
2063
  completeUpload,
1365
2064
  getDeploymentStatus,
1366
- getLogs
2065
+ getLogs,
2066
+ getHostnames
1367
2067
  };
1368
2068
  })
1369
2069
  }) {
1370
2070
  };
1371
2071
 
1372
2072
  // src/commands/platform/deploy/index.ts
1373
- var deployCommand = Command5.make(
2073
+ var appOption5 = Options6.text("app").pipe(
2074
+ Options6.withAlias("a"),
2075
+ Options6.withDescription("Application ID to deploy to (overrides the linked application)"),
2076
+ Options6.optional
2077
+ );
2078
+ var deployCommand = Command11.make(
1374
2079
  "deploy",
1375
- {},
1376
- () => Effect12.gen(function* () {
2080
+ { app: appOption5 },
2081
+ ({ app }) => Effect20.gen(function* () {
1377
2082
  const projectConfig = yield* ProjectConfigService;
1378
2083
  const deploymentApi = yield* DeploymentApiService;
1379
2084
  const archiveService = yield* ArchiveService;
1380
- const pathService = yield* Path5.Path;
2085
+ const projectLink = yield* ProjectLinkService;
2086
+ const pathService = yield* Path6.Path;
1381
2087
  const cwd = process.cwd();
1382
- yield* Effect12.log("Starting deploy command");
2088
+ const applicationId = yield* Option9.match(app, {
2089
+ onSome: (id) => Effect20.succeed(id),
2090
+ onNone: () => projectLink.resolve(cwd).pipe(Effect20.map((link) => link.applicationId))
2091
+ });
2092
+ yield* Effect20.log("Starting deploy command");
1383
2093
  const { config } = yield* projectConfig.resolve(cwd);
1384
- yield* Console5.log("Creating deployment...");
1385
- const deploymentId = yield* deploymentApi.createDeployment("test");
1386
- yield* Console5.log(`Deployment ID: ${deploymentId}`);
1387
- yield* Console5.log("Creating archive...");
2094
+ yield* Console11.log("Creating deployment...");
2095
+ const deploymentId = yield* deploymentApi.createDeployment(applicationId);
2096
+ yield* Console11.log(`Deployment ID: ${deploymentId}`);
2097
+ yield* Console11.log("Creating archive...");
1388
2098
  const archivePath = pathService.join(os2.tmpdir(), `project-${Date.now()}.zip`);
1389
2099
  const archive = yield* archiveService.createZipArchive(config.userArchive?.rootOverwrite || cwd, archivePath, {
1390
2100
  whitelist: config.userArchive?.fileWhitelist,
@@ -1392,60 +2102,62 @@ var deployCommand = Command5.make(
1392
2102
  useIgnoreFiles: false,
1393
2103
  useManagedIgnore: false
1394
2104
  });
1395
- yield* Console5.log(`Archive created (${formatFileSize(archive.size)})`);
1396
- yield* Console5.log("Uploading archive...");
2105
+ yield* Console11.log(`Archive created (${formatFileSize(archive.size)})`);
2106
+ yield* Console11.log("Uploading archive...");
1397
2107
  yield* uploadArchive(deploymentId, archivePath, archive.size);
1398
- yield* Console5.log("Upload complete.");
1399
- yield* Console5.log("The deployment pipeline is now being provisioned. This may take a few seconds.");
2108
+ yield* Console11.log("Upload complete.");
2109
+ yield* Console11.log("The deployment pipeline is now being provisioned. This may take a few seconds.");
1400
2110
  yield* pollDeployment(deploymentId);
1401
2111
  }).pipe(
1402
- Effect12.catchTags({
1403
- ConfigNotFoundError: (err) => Console5.error(err.message).pipe(Effect12.andThen(Effect12.fail(err))),
1404
- ConfigParseError: (err) => Console5.error(`Config parse error: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
1405
- ConfigValidationError: (err) => Effect12.gen(function* () {
2112
+ Effect20.catchTags({
2113
+ ProjectNotLinkedError: (err) => Console11.error(err.message).pipe(Effect20.andThen(Effect20.fail(err))),
2114
+ ProjectLinkReadError: (err) => Console11.error(err.message).pipe(Effect20.andThen(Effect20.fail(err))),
2115
+ ConfigNotFoundError: (err) => Console11.error(err.message).pipe(Effect20.andThen(Effect20.fail(err))),
2116
+ ConfigParseError: (err) => Console11.error(`Config parse error: ${err.message}`).pipe(Effect20.andThen(Effect20.fail(err))),
2117
+ ConfigValidationError: (err) => Effect20.gen(function* () {
1406
2118
  for (const e of err.errors) {
1407
- yield* Console5.error(e);
2119
+ yield* Console11.error(e);
1408
2120
  }
1409
- yield* Console5.error(err.message);
1410
- }).pipe(Effect12.andThen(Effect12.fail(err))),
1411
- DeploymentCreateError: (err) => Console5.error(`Deployment creation failed: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
1412
- ArchiveCreateError: (err) => Console5.error(`Archive creation failed: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
1413
- UploadStartError: (err) => Console5.error(`Upload failed: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
1414
- UploadPartError: (err) => Console5.error(`Upload failed: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
1415
- UploadCompleteError: (err) => Console5.error(`Upload failed: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
1416
- PresignedUrlError: (err) => Console5.error(`Upload failed: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
1417
- DeploymentAuthError: (err) => Console5.error(`Authentication failed: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
1418
- DeploymentFailedError: (err) => Console5.error(err.message).pipe(Effect12.andThen(Effect12.fail(err)))
2121
+ yield* Console11.error(err.message);
2122
+ }).pipe(Effect20.andThen(Effect20.fail(err))),
2123
+ DeploymentCreateError: (err) => Console11.error(`Deployment creation failed: ${err.message}`).pipe(Effect20.andThen(Effect20.fail(err))),
2124
+ ArchiveCreateError: (err) => Console11.error(`Archive creation failed: ${err.message}`).pipe(Effect20.andThen(Effect20.fail(err))),
2125
+ UploadStartError: (err) => Console11.error(`Upload failed: ${err.message}`).pipe(Effect20.andThen(Effect20.fail(err))),
2126
+ UploadPartError: (err) => Console11.error(`Upload failed: ${err.message}`).pipe(Effect20.andThen(Effect20.fail(err))),
2127
+ UploadCompleteError: (err) => Console11.error(`Upload failed: ${err.message}`).pipe(Effect20.andThen(Effect20.fail(err))),
2128
+ PresignedUrlError: (err) => Console11.error(`Upload failed: ${err.message}`).pipe(Effect20.andThen(Effect20.fail(err))),
2129
+ DeploymentAuthError: (err) => Console11.error(`Authentication failed: ${err.message}`).pipe(Effect20.andThen(Effect20.fail(err))),
2130
+ DeploymentFailedError: (err) => Console11.error(err.message).pipe(Effect20.andThen(Effect20.fail(err)))
1419
2131
  })
1420
2132
  )
1421
2133
  );
1422
- var uploadArchive = (deploymentId, archivePath, fileSize) => Effect12.gen(function* () {
2134
+ var uploadArchive = (deploymentId, archivePath, fileSize) => Effect20.gen(function* () {
1423
2135
  const deploymentApi = yield* DeploymentApiService;
1424
- const fs = yield* FileSystem5.FileSystem;
2136
+ const fs = yield* FileSystem7.FileSystem;
1425
2137
  const uploadId = yield* deploymentApi.startMultipartUpload(deploymentId);
1426
2138
  const FILE_CHUNK_SIZE = 10 * 1024 * 1024;
1427
2139
  const numChunks = Math.ceil(fileSize / FILE_CHUNK_SIZE);
1428
- yield* Effect12.log("Starting multipart upload", { uploadId, numChunks, fileSize });
1429
- const parts = yield* Effect12.all(
2140
+ yield* Effect20.log("Starting multipart upload", { uploadId, numChunks, fileSize });
2141
+ const parts = yield* Effect20.all(
1430
2142
  Array.from({ length: numChunks }, (_, i) => {
1431
2143
  const partNumber = i + 1;
1432
2144
  const start = i * FILE_CHUNK_SIZE;
1433
2145
  const bytesToRead = partNumber < numChunks ? FILE_CHUNK_SIZE : fileSize - start;
1434
- return Effect12.gen(function* () {
2146
+ return Effect20.gen(function* () {
1435
2147
  const presignedUrl = yield* deploymentApi.getPresignedUrl(deploymentId, uploadId, partNumber);
1436
2148
  const chunkData = yield* Stream.runCollect(fs.stream(archivePath, { offset: start, bytesToRead })).pipe(
1437
- Effect12.map((chunks) => {
2149
+ Effect20.map((chunks) => {
1438
2150
  const arrays = Array.from(chunks);
1439
2151
  return Buffer.concat(arrays);
1440
2152
  }),
1441
- Effect12.mapError(
2153
+ Effect20.mapError(
1442
2154
  (error) => new UploadPartError({
1443
2155
  message: `Failed to read chunk ${partNumber}: ${String(error)}`,
1444
2156
  partNumber
1445
2157
  })
1446
2158
  )
1447
2159
  );
1448
- yield* Effect12.log(`Uploading part ${partNumber}/${numChunks}`, { size: chunkData.length });
2160
+ yield* Effect20.log(`Uploading part ${partNumber}/${numChunks}`, { size: chunkData.length });
1449
2161
  return yield* deploymentApi.uploadPart(presignedUrl, chunkData, partNumber);
1450
2162
  });
1451
2163
  }),
@@ -1453,20 +2165,20 @@ var uploadArchive = (deploymentId, archivePath, fileSize) => Effect12.gen(functi
1453
2165
  );
1454
2166
  yield* deploymentApi.completeUpload(deploymentId, uploadId, parts);
1455
2167
  });
1456
- var pollDeployment = (deploymentId) => Effect12.gen(function* () {
2168
+ var pollDeployment = (deploymentId) => Effect20.gen(function* () {
1457
2169
  const deploymentApi = yield* DeploymentApiService;
1458
2170
  let lastStatus = "PENDING";
1459
2171
  let logOffset = 0;
1460
- yield* Console5.log("Status: Pending");
1461
- yield* Effect12.repeat(
1462
- Effect12.gen(function* () {
1463
- const newStatus = yield* deploymentApi.getDeploymentStatus(deploymentId).pipe(Effect12.catchTag("DeploymentStatusError", () => Effect12.succeed(lastStatus)));
2172
+ yield* Console11.log("Status: Pending");
2173
+ yield* Effect20.repeat(
2174
+ Effect20.gen(function* () {
2175
+ const newStatus = yield* deploymentApi.getDeploymentStatus(deploymentId).pipe(Effect20.catchTag("DeploymentStatusError", () => Effect20.succeed(lastStatus)));
1464
2176
  if (newStatus !== lastStatus) {
1465
2177
  lastStatus = newStatus;
1466
2178
  const label = newStatus.charAt(0) + newStatus.slice(1).toLowerCase();
1467
- yield* Console5.log(`Status: ${label}`);
2179
+ yield* Console11.log(`Status: ${label}`);
1468
2180
  }
1469
- const logsPage = yield* deploymentApi.getLogs(deploymentId, { offset: logOffset, limit: 100 }).pipe(Effect12.catchTag("DeploymentLogsFetchError", () => Effect12.succeed(null)));
2181
+ const logsPage = yield* deploymentApi.getLogs(deploymentId, { offset: logOffset, limit: 100 }).pipe(Effect20.catchTag("DeploymentLogsFetchError", () => Effect20.succeed(null)));
1470
2182
  if (logsPage) {
1471
2183
  for (const logEntry of logsPage.items) {
1472
2184
  logOffset++;
@@ -1474,11 +2186,17 @@ var pollDeployment = (deploymentId) => Effect12.gen(function* () {
1474
2186
  }
1475
2187
  }
1476
2188
  if (newStatus === "ACTIVE") {
1477
- yield* Console5.log(`Deployed to https://${deploymentId}.gigadrivedev.com`);
1478
- return yield* Effect12.fail("done");
2189
+ const hostnames = yield* deploymentApi.getHostnames(deploymentId).pipe(Effect20.catchTag("DeploymentHostnamesFetchError", () => Effect20.succeed([])));
2190
+ const active = hostnames.find((h) => h.active) ?? hostnames.at(0);
2191
+ if (active) {
2192
+ yield* Console11.log(`Deployed to https://${active.hostname}`);
2193
+ } else {
2194
+ yield* Console11.log("Deployment is live.");
2195
+ }
2196
+ return yield* Effect20.fail("done");
1479
2197
  }
1480
2198
  if (newStatus === "FAILED") {
1481
- return yield* Effect12.fail(
2199
+ return yield* Effect20.fail(
1482
2200
  new DeploymentFailedError({
1483
2201
  message: "The deployment failed. Please check the logs for more information."
1484
2202
  })
@@ -1487,53 +2205,66 @@ var pollDeployment = (deploymentId) => Effect12.gen(function* () {
1487
2205
  }),
1488
2206
  Schedule.addDelay(Schedule.forever, () => Duration.seconds(1))
1489
2207
  ).pipe(
1490
- Effect12.catchAll((err) => {
1491
- if (err === "done") return Effect12.void;
1492
- return Effect12.fail(err);
2208
+ Effect20.catchAll((err) => {
2209
+ if (err === "done") return Effect20.void;
2210
+ return Effect20.fail(err);
1493
2211
  })
1494
2212
  );
1495
2213
  });
1496
2214
  var printLog = (logEntry) => {
1497
2215
  switch (logEntry.type) {
1498
2216
  case "ERROR":
1499
- return Console5.error(logEntry.message);
2217
+ return Console11.error(logEntry.message);
1500
2218
  case "WARN":
1501
- return Console5.warn(logEntry.message);
2219
+ return Console11.warn(logEntry.message);
1502
2220
  default:
1503
- return Console5.log(logEntry.message);
2221
+ return Console11.log(logEntry.message);
1504
2222
  }
1505
2223
  };
1506
2224
 
1507
2225
  // src/commands/platform/index.ts
1508
- var platformBase = Command6.make("platform", {}, () => Effect13.void);
1509
- var platformCommand = platformBase.pipe(Command6.withSubcommands([deployCommand]));
2226
+ var platformBase = Command12.make("platform", {}, () => Effect21.void);
2227
+ var platformCommand = platformBase.pipe(Command12.withSubcommands([deployCommand]));
1510
2228
 
1511
2229
  // src/commands/whoami/index.ts
1512
- import { Command as Command7 } from "@effect/cli";
1513
- import { Console as Console6, Effect as Effect14 } from "effect";
1514
- var whoamiCommand = Command7.make(
2230
+ import { Command as Command13 } from "@effect/cli";
2231
+ import { Console as Console12, Effect as Effect22 } from "effect";
2232
+ var whoamiCommand = Command13.make(
1515
2233
  "whoami",
1516
2234
  {},
1517
- () => Effect14.gen(function* () {
2235
+ () => Effect22.gen(function* () {
1518
2236
  const auth = yield* AuthService;
1519
2237
  const info = yield* auth.getUserInfo;
1520
- yield* Console6.log(JSON.stringify(info, null, 2));
2238
+ yield* Console12.log(JSON.stringify(info, null, 2));
1521
2239
  }).pipe(
1522
- Effect14.catchTags({
1523
- NotAuthenticatedError: () => Console6.log('You are not logged in. Run "gigadrive login" to authenticate.'),
1524
- UserInfoFetchError: (err) => Console6.error(`Failed to retrieve user info: ${err.message}`),
1525
- OAuthDiscoveryError: (err) => Console6.error(`Failed to retrieve user info: ${err.message}`)
2240
+ Effect22.catchTags({
2241
+ NotAuthenticatedError: () => Console12.log('You are not logged in. Run "gigadrive login" to authenticate.'),
2242
+ UserInfoFetchError: (err) => Console12.error(`Failed to retrieve user info: ${err.message}`),
2243
+ OAuthDiscoveryError: (err) => Console12.error(`Failed to retrieve user info: ${err.message}`)
1526
2244
  })
1527
2245
  )
1528
2246
  );
1529
2247
 
1530
2248
  // src/index.ts
1531
- var gigadrive = Command8.make("gigadrive", {}, () => Effect15.void).pipe(
1532
- Command8.withSubcommands([loginCommand, whoamiCommand, buildCommand, debugCommand, platformCommand])
2249
+ var gigadrive = Command14.make("gigadrive", {}, () => Effect23.void).pipe(
2250
+ Command14.withSubcommands([
2251
+ loginCommand,
2252
+ logoutCommand,
2253
+ whoamiCommand,
2254
+ linkCommand,
2255
+ unlinkCommand,
2256
+ appsCommand,
2257
+ envCommand,
2258
+ deploymentsCommand,
2259
+ aiCommand,
2260
+ buildCommand,
2261
+ debugCommand,
2262
+ platformCommand
2263
+ ])
1533
2264
  );
1534
- var cli = Command8.run(gigadrive, {
2265
+ var cli = Command14.run(gigadrive, {
1535
2266
  name: "Gigadrive CLI",
1536
- version: "2.0.0"
2267
+ version: package_default.version
1537
2268
  });
1538
2269
  var BaseServices = Layer.mergeAll(
1539
2270
  OAuthConfigService.Default,
@@ -1541,12 +2272,14 @@ var BaseServices = Layer.mergeAll(
1541
2272
  PackageManagerService.Default,
1542
2273
  ArchiveService.Default,
1543
2274
  NetworkConfigLive2,
1544
- ProjectConfigService.Default
2275
+ ProjectConfigService.Default,
2276
+ ProjectLinkService.Default
1545
2277
  ).pipe(Layer.provideMerge(AuthService.Default));
2278
+ var ApiClientLive = Layer.provide(ApiClientService.Default, BaseServices);
1546
2279
  var DeploymentApiLive = Layer.provide(DeploymentApiService.Default, BaseServices);
1547
- var ServicesLive = Layer.mergeAll(BaseServices, DeploymentApiLive);
2280
+ var ServicesLive = Layer.mergeAll(BaseServices, ApiClientLive, DeploymentApiLive);
1548
2281
  var AppLive = Layer.mergeAll(ServicesLive, Logger.minimumLogLevel(LogLevel.Info)).pipe(
1549
2282
  Layer.provideMerge(NodeContext.layer)
1550
2283
  );
1551
- NodeRuntime.runMain(Effect15.provide(cli(process.argv), AppLive));
2284
+ NodeRuntime.runMain(Effect23.provide(cli(process.argv), AppLive));
1552
2285
  //# sourceMappingURL=index.mjs.map