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.cjs +1569 -836
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1759 -1026
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
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
|
|
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
|
|
7
|
+
import { Effect as Effect23, Layer, LogLevel, Logger } from "effect";
|
|
8
8
|
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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/
|
|
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 {
|
|
157
|
-
import
|
|
158
|
-
|
|
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
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
316
|
+
(error) => new AuthStorageWriteError({
|
|
317
|
+
message: "Failed to create auth directory",
|
|
318
|
+
cause: String(error)
|
|
319
|
+
})
|
|
169
320
|
)
|
|
170
321
|
);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
new
|
|
174
|
-
message: "
|
|
175
|
-
|
|
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
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
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/
|
|
221
|
-
var
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\//g, "/");
|
|
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: [
|
|
464
|
+
dependencies: [OAuthConfigService.Default, AuthStorageService.Default],
|
|
339
465
|
effect: Effect3.gen(function* () {
|
|
340
|
-
const
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
|
382
|
-
message:
|
|
383
|
-
|
|
499
|
+
new TokenExchangeError({
|
|
500
|
+
message: `Token exchange failed: ${response.status} ${response.statusText} - ${body}`,
|
|
501
|
+
statusCode: response.status
|
|
384
502
|
})
|
|
385
503
|
);
|
|
386
504
|
}
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
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
|
|
393
|
-
message:
|
|
394
|
-
|
|
640
|
+
new TokenRefreshError({
|
|
641
|
+
message: `Token refresh failed: ${response.status} ${response.statusText} - ${body}`,
|
|
642
|
+
statusCode: response.status
|
|
395
643
|
})
|
|
396
644
|
);
|
|
397
645
|
}
|
|
398
|
-
|
|
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
|
-
|
|
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/
|
|
406
|
-
var
|
|
407
|
-
|
|
408
|
-
{
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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:
|
|
492
|
-
const
|
|
493
|
-
const
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
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
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
|
796
|
+
catch: () => new ProjectLinkReadError({ message: `${LINK_DIR}/${LINK_FILE} contains invalid JSON` })
|
|
522
797
|
});
|
|
523
|
-
const
|
|
524
|
-
|
|
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
|
|
801
|
+
return Option3.some(link);
|
|
527
802
|
});
|
|
528
|
-
const
|
|
529
|
-
yield*
|
|
530
|
-
yield*
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
message:
|
|
534
|
-
|
|
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*
|
|
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 =
|
|
541
|
-
yield* fs.remove(
|
|
542
|
-
|
|
543
|
-
|
|
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/
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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/
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
var
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
|
684
|
-
message:
|
|
685
|
-
|
|
1189
|
+
new PackageManagerNotFoundError({
|
|
1190
|
+
message: "No package.json found",
|
|
1191
|
+
directory: cwd
|
|
686
1192
|
})
|
|
687
1193
|
);
|
|
688
1194
|
}
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
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
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
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
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
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 (
|
|
870
|
-
|
|
871
|
-
|
|
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
|
-
|
|
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 (
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
() =>
|
|
937
|
-
yield*
|
|
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(
|
|
1768
|
+
const info = yield* auth.getUserInfo.pipe(Effect16.catchAll(() => Effect16.succeed({})));
|
|
941
1769
|
const name = auth.inferUserName(info);
|
|
942
|
-
yield*
|
|
1770
|
+
yield* Console9.log(`You are now logged in as ${name}.`);
|
|
943
1771
|
}).pipe(
|
|
944
|
-
|
|
945
|
-
LoginFlowError: (err) =>
|
|
946
|
-
TokenExchangeError: (err) =>
|
|
947
|
-
OAuthDiscoveryError: (err) =>
|
|
948
|
-
AuthStorageWriteError: (err) =>
|
|
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
|
|
955
|
-
import { Effect as
|
|
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
|
|
959
|
-
import { FileSystem as
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
975
|
-
|
|
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) =>
|
|
978
|
-
const items = yield* fs.readDirectory(dir).pipe(
|
|
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*
|
|
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(
|
|
1003
|
-
var initializeIgnoreRules = (fs, pathService, baseDir) =>
|
|
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) =>
|
|
1863
|
+
var getFilesToInclude = (fs, pathService, dir, ignoreRules) => Effect18.gen(function* () {
|
|
1019
1864
|
const filesToInclude = [];
|
|
1020
|
-
const walkDir = (currentDir) =>
|
|
1021
|
-
const items = yield* fs.readDirectory(currentDir).pipe(
|
|
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*
|
|
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(
|
|
1884
|
+
}).pipe(Effect18.catchAll(() => Effect18.void));
|
|
1040
1885
|
yield* walkDir(dir);
|
|
1041
1886
|
return filesToInclude;
|
|
1042
1887
|
});
|
|
1043
|
-
var ArchiveService = class extends
|
|
1888
|
+
var ArchiveService = class extends Effect18.Service()("ArchiveService", {
|
|
1044
1889
|
accessors: true,
|
|
1045
|
-
effect:
|
|
1046
|
-
const fs = yield*
|
|
1047
|
-
const pathService = yield*
|
|
1048
|
-
const createZipArchive =
|
|
1049
|
-
yield*
|
|
1050
|
-
yield*
|
|
1051
|
-
yield*
|
|
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*
|
|
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*
|
|
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
|
-
|
|
1911
|
+
Effect18.map((files) => [files])
|
|
1067
1912
|
);
|
|
1068
1913
|
});
|
|
1069
|
-
const archiveExists = yield* fs.exists(outputFile).pipe(
|
|
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
|
-
|
|
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*
|
|
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
|
-
|
|
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*
|
|
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 {
|
|
1115
|
-
|
|
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
|
-
|
|
1139
|
-
|
|
1140
|
-
const
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
yield*
|
|
1149
|
-
|
|
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
|
-
|
|
1162
|
-
|
|
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 =
|
|
1187
|
-
yield*
|
|
1188
|
-
const
|
|
1189
|
-
const
|
|
1190
|
-
try: () =>
|
|
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
|
-
|
|
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 =
|
|
1217
|
-
const
|
|
1218
|
-
const
|
|
1219
|
-
try: () =>
|
|
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
|
-
|
|
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 =
|
|
1250
|
-
yield*
|
|
1251
|
-
yield*
|
|
1252
|
-
const
|
|
1253
|
-
|
|
1254
|
-
|
|
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
|
-
|
|
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 =
|
|
1283
|
-
yield*
|
|
1284
|
-
yield*
|
|
1285
|
-
const
|
|
1286
|
-
|
|
1287
|
-
try: () =>
|
|
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
|
-
|
|
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 =
|
|
1306
|
-
const
|
|
1307
|
-
const
|
|
1308
|
-
try: () =>
|
|
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
|
-
|
|
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 =
|
|
1333
|
-
const
|
|
1334
|
-
const
|
|
1335
|
-
try: () =>
|
|
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
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
() =>
|
|
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
|
|
2085
|
+
const projectLink = yield* ProjectLinkService;
|
|
2086
|
+
const pathService = yield* Path6.Path;
|
|
1381
2087
|
const cwd = process.cwd();
|
|
1382
|
-
yield*
|
|
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*
|
|
1385
|
-
const deploymentId = yield* deploymentApi.createDeployment(
|
|
1386
|
-
yield*
|
|
1387
|
-
yield*
|
|
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*
|
|
1396
|
-
yield*
|
|
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*
|
|
1399
|
-
yield*
|
|
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
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
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*
|
|
2119
|
+
yield* Console11.error(e);
|
|
1408
2120
|
}
|
|
1409
|
-
yield*
|
|
1410
|
-
}).pipe(
|
|
1411
|
-
DeploymentCreateError: (err) =>
|
|
1412
|
-
ArchiveCreateError: (err) =>
|
|
1413
|
-
UploadStartError: (err) =>
|
|
1414
|
-
UploadPartError: (err) =>
|
|
1415
|
-
UploadCompleteError: (err) =>
|
|
1416
|
-
PresignedUrlError: (err) =>
|
|
1417
|
-
DeploymentAuthError: (err) =>
|
|
1418
|
-
DeploymentFailedError: (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) =>
|
|
2134
|
+
var uploadArchive = (deploymentId, archivePath, fileSize) => Effect20.gen(function* () {
|
|
1423
2135
|
const deploymentApi = yield* DeploymentApiService;
|
|
1424
|
-
const fs = yield*
|
|
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*
|
|
1429
|
-
const parts = yield*
|
|
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
|
|
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
|
-
|
|
2149
|
+
Effect20.map((chunks) => {
|
|
1438
2150
|
const arrays = Array.from(chunks);
|
|
1439
2151
|
return Buffer.concat(arrays);
|
|
1440
2152
|
}),
|
|
1441
|
-
|
|
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*
|
|
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) =>
|
|
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*
|
|
1461
|
-
yield*
|
|
1462
|
-
|
|
1463
|
-
const newStatus = yield* deploymentApi.getDeploymentStatus(deploymentId).pipe(
|
|
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*
|
|
2179
|
+
yield* Console11.log(`Status: ${label}`);
|
|
1468
2180
|
}
|
|
1469
|
-
const logsPage = yield* deploymentApi.getLogs(deploymentId, { offset: logOffset, limit: 100 }).pipe(
|
|
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*
|
|
1478
|
-
|
|
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*
|
|
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
|
-
|
|
1491
|
-
if (err === "done") return
|
|
1492
|
-
return
|
|
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
|
|
2217
|
+
return Console11.error(logEntry.message);
|
|
1500
2218
|
case "WARN":
|
|
1501
|
-
return
|
|
2219
|
+
return Console11.warn(logEntry.message);
|
|
1502
2220
|
default:
|
|
1503
|
-
return
|
|
2221
|
+
return Console11.log(logEntry.message);
|
|
1504
2222
|
}
|
|
1505
2223
|
};
|
|
1506
2224
|
|
|
1507
2225
|
// src/commands/platform/index.ts
|
|
1508
|
-
var platformBase =
|
|
1509
|
-
var platformCommand = platformBase.pipe(
|
|
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
|
|
1513
|
-
import { Console as
|
|
1514
|
-
var whoamiCommand =
|
|
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
|
-
() =>
|
|
2235
|
+
() => Effect22.gen(function* () {
|
|
1518
2236
|
const auth = yield* AuthService;
|
|
1519
2237
|
const info = yield* auth.getUserInfo;
|
|
1520
|
-
yield*
|
|
2238
|
+
yield* Console12.log(JSON.stringify(info, null, 2));
|
|
1521
2239
|
}).pipe(
|
|
1522
|
-
|
|
1523
|
-
NotAuthenticatedError: () =>
|
|
1524
|
-
UserInfoFetchError: (err) =>
|
|
1525
|
-
OAuthDiscoveryError: (err) =>
|
|
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 =
|
|
1532
|
-
|
|
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 =
|
|
2265
|
+
var cli = Command14.run(gigadrive, {
|
|
1535
2266
|
name: "Gigadrive CLI",
|
|
1536
|
-
version:
|
|
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(
|
|
2284
|
+
NodeRuntime.runMain(Effect23.provide(cli(process.argv), AppLive));
|
|
1552
2285
|
//# sourceMappingURL=index.mjs.map
|