gigadrive 1.1.2 → 2.1.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 +1459 -640
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1465 -638
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -11
package/dist/index.mjs
CHANGED
|
@@ -1,725 +1,1552 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command } from "
|
|
4
|
+
import { Command as Command8 } from "@effect/cli";
|
|
5
|
+
import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|
6
|
+
import { NetworkConfigLive as NetworkConfigLive2 } from "@gigadrive/network-config";
|
|
7
|
+
import { Effect as Effect15, Layer, LogLevel, Logger } from "effect";
|
|
5
8
|
|
|
6
9
|
// src/commands/build/index.ts
|
|
10
|
+
import { Command } from "@effect/cli";
|
|
11
|
+
import { FileSystem as FileSystem2, Path as Path2 } from "@effect/platform";
|
|
7
12
|
import { exec as exec2 } from "@gigadrive/build-utils";
|
|
8
|
-
import
|
|
9
|
-
import path2 from "path";
|
|
13
|
+
import { Console, Effect as Effect2 } from "effect";
|
|
10
14
|
|
|
11
|
-
// src/
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import { spawn } from "child_process";
|
|
17
|
-
async function exec({
|
|
18
|
-
command,
|
|
19
|
-
cwd = process.cwd(),
|
|
20
|
-
env,
|
|
21
|
-
onOutput = (data) => console.log(data),
|
|
22
|
-
onError = (data) => console.error(data)
|
|
15
|
+
// src/errors.ts
|
|
16
|
+
import { Schema } from "effect";
|
|
17
|
+
var OAuthDiscoveryError = class extends Schema.TaggedError()("OAuthDiscoveryError", {
|
|
18
|
+
message: Schema.String,
|
|
19
|
+
cause: Schema.optional(Schema.String)
|
|
23
20
|
}) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
21
|
+
};
|
|
22
|
+
var AuthStorageReadError = class extends Schema.TaggedError()("AuthStorageReadError", {
|
|
23
|
+
message: Schema.String
|
|
24
|
+
}) {
|
|
25
|
+
};
|
|
26
|
+
var AuthStorageWriteError = class extends Schema.TaggedError()("AuthStorageWriteError", {
|
|
27
|
+
message: Schema.String,
|
|
28
|
+
cause: Schema.optional(Schema.String)
|
|
29
|
+
}) {
|
|
30
|
+
};
|
|
31
|
+
var LoginFlowError = class extends Schema.TaggedError()("LoginFlowError", {
|
|
32
|
+
message: Schema.String,
|
|
33
|
+
cause: Schema.optional(Schema.String)
|
|
34
|
+
}) {
|
|
35
|
+
};
|
|
36
|
+
var TokenExchangeError = class extends Schema.TaggedError()("TokenExchangeError", {
|
|
37
|
+
message: Schema.String,
|
|
38
|
+
statusCode: Schema.optional(Schema.Number)
|
|
39
|
+
}) {
|
|
40
|
+
};
|
|
41
|
+
var TokenRefreshError = class extends Schema.TaggedError()("TokenRefreshError", {
|
|
42
|
+
message: Schema.String,
|
|
43
|
+
statusCode: Schema.optional(Schema.Number)
|
|
44
|
+
}) {
|
|
45
|
+
};
|
|
46
|
+
var NotAuthenticatedError = class extends Schema.TaggedError()("NotAuthenticatedError", {
|
|
47
|
+
message: Schema.String
|
|
48
|
+
}) {
|
|
49
|
+
};
|
|
50
|
+
var UserInfoFetchError = class extends Schema.TaggedError()("UserInfoFetchError", {
|
|
51
|
+
message: Schema.String,
|
|
52
|
+
statusCode: Schema.optional(Schema.Number)
|
|
53
|
+
}) {
|
|
54
|
+
};
|
|
55
|
+
var ConfigNotFoundError = class extends Schema.TaggedError()("ConfigNotFoundError", {
|
|
56
|
+
message: Schema.String,
|
|
57
|
+
directory: Schema.String
|
|
58
|
+
}) {
|
|
59
|
+
};
|
|
60
|
+
var ConfigParseError = class extends Schema.TaggedError()("ConfigParseError", {
|
|
61
|
+
message: Schema.String,
|
|
62
|
+
cause: Schema.optional(Schema.String)
|
|
63
|
+
}) {
|
|
64
|
+
};
|
|
65
|
+
var ConfigValidationError = class extends Schema.TaggedError()("ConfigValidationError", {
|
|
66
|
+
message: Schema.String,
|
|
67
|
+
errors: Schema.Array(Schema.String)
|
|
68
|
+
}) {
|
|
69
|
+
};
|
|
70
|
+
var PackageJsonNotFoundError = class extends Schema.TaggedError()(
|
|
71
|
+
"PackageJsonNotFoundError",
|
|
72
|
+
{
|
|
73
|
+
message: Schema.String,
|
|
74
|
+
directory: Schema.String
|
|
70
75
|
}
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
) {
|
|
77
|
+
};
|
|
78
|
+
var PackageJsonParseError = class extends Schema.TaggedError()("PackageJsonParseError", {
|
|
79
|
+
message: Schema.String,
|
|
80
|
+
directory: Schema.String,
|
|
81
|
+
cause: Schema.optional(Schema.String)
|
|
82
|
+
}) {
|
|
83
|
+
};
|
|
84
|
+
var BuildScriptNotFoundError = class extends Schema.TaggedError()(
|
|
85
|
+
"BuildScriptNotFoundError",
|
|
86
|
+
{
|
|
87
|
+
message: Schema.String
|
|
73
88
|
}
|
|
74
|
-
|
|
75
|
-
|
|
89
|
+
) {
|
|
90
|
+
};
|
|
91
|
+
var PackageManagerNotFoundError = class extends Schema.TaggedError()(
|
|
92
|
+
"PackageManagerNotFoundError",
|
|
93
|
+
{
|
|
94
|
+
message: Schema.String,
|
|
95
|
+
directory: Schema.String
|
|
76
96
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
97
|
+
) {
|
|
98
|
+
};
|
|
99
|
+
var ExecError = class extends Schema.TaggedError()("ExecError", {
|
|
100
|
+
message: Schema.String,
|
|
101
|
+
command: Schema.String,
|
|
102
|
+
cause: Schema.optional(Schema.String)
|
|
103
|
+
}) {
|
|
104
|
+
};
|
|
105
|
+
var DeploymentCreateError = class extends Schema.TaggedError()("DeploymentCreateError", {
|
|
106
|
+
message: Schema.String,
|
|
107
|
+
statusCode: Schema.optional(Schema.Number)
|
|
108
|
+
}) {
|
|
109
|
+
};
|
|
110
|
+
var UploadStartError = class extends Schema.TaggedError()("UploadStartError", {
|
|
111
|
+
message: Schema.String,
|
|
112
|
+
statusCode: Schema.optional(Schema.Number)
|
|
113
|
+
}) {
|
|
114
|
+
};
|
|
115
|
+
var PresignedUrlError = class extends Schema.TaggedError()("PresignedUrlError", {
|
|
116
|
+
message: Schema.String,
|
|
117
|
+
partNumber: Schema.Number
|
|
118
|
+
}) {
|
|
119
|
+
};
|
|
120
|
+
var UploadPartError = class extends Schema.TaggedError()("UploadPartError", {
|
|
121
|
+
message: Schema.String,
|
|
122
|
+
partNumber: Schema.Number
|
|
123
|
+
}) {
|
|
124
|
+
};
|
|
125
|
+
var UploadCompleteError = class extends Schema.TaggedError()("UploadCompleteError", {
|
|
126
|
+
message: Schema.String
|
|
127
|
+
}) {
|
|
128
|
+
};
|
|
129
|
+
var DeploymentStatusError = class extends Schema.TaggedError()("DeploymentStatusError", {
|
|
130
|
+
message: Schema.String
|
|
131
|
+
}) {
|
|
132
|
+
};
|
|
133
|
+
var DeploymentLogsFetchError = class extends Schema.TaggedError()(
|
|
134
|
+
"DeploymentLogsFetchError",
|
|
135
|
+
{
|
|
136
|
+
message: Schema.String
|
|
82
137
|
}
|
|
138
|
+
) {
|
|
83
139
|
};
|
|
84
|
-
var
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
140
|
+
var ArchiveCreateError = class extends Schema.TaggedError()("ArchiveCreateError", {
|
|
141
|
+
message: Schema.String,
|
|
142
|
+
cause: Schema.optional(Schema.String)
|
|
143
|
+
}) {
|
|
144
|
+
};
|
|
145
|
+
var DeploymentAuthError = class extends Schema.TaggedError()("DeploymentAuthError", {
|
|
146
|
+
message: Schema.String
|
|
147
|
+
}) {
|
|
148
|
+
};
|
|
149
|
+
var DeploymentFailedError = class extends Schema.TaggedError()("DeploymentFailedError", {
|
|
150
|
+
message: Schema.String
|
|
151
|
+
}) {
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// src/services/package-manager.ts
|
|
155
|
+
import { FileSystem, Path } from "@effect/platform";
|
|
156
|
+
import { exec } from "@gigadrive/build-utils";
|
|
157
|
+
import { Effect } from "effect";
|
|
158
|
+
var PackageManagerService = class extends Effect.Service()("PackageManagerService", {
|
|
159
|
+
accessors: true,
|
|
160
|
+
effect: Effect.gen(function* () {
|
|
161
|
+
const fs = yield* FileSystem.FileSystem;
|
|
162
|
+
const pathService = yield* Path.Path;
|
|
163
|
+
const detect = Effect.fn("PackageManagerService.detect")(function* (cwd) {
|
|
164
|
+
yield* Effect.annotateCurrentSpan("cwd", cwd);
|
|
165
|
+
const packageJsonPath = pathService.join(cwd, "package.json");
|
|
166
|
+
const exists = yield* fs.exists(packageJsonPath).pipe(
|
|
167
|
+
Effect.mapError(
|
|
168
|
+
() => new PackageManagerNotFoundError({ message: "Failed to check for package.json", directory: cwd })
|
|
169
|
+
)
|
|
170
|
+
);
|
|
171
|
+
if (!exists) {
|
|
172
|
+
return yield* Effect.fail(
|
|
173
|
+
new PackageManagerNotFoundError({
|
|
174
|
+
message: "No package.json found",
|
|
175
|
+
directory: cwd
|
|
176
|
+
})
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const lockChecks = [
|
|
180
|
+
{ file: "yarn.lock", pm: "yarn" },
|
|
181
|
+
{ file: "pnpm-lock.yaml", pm: "pnpm" },
|
|
182
|
+
{ file: "bun.lockb", pm: "bun" },
|
|
183
|
+
{ file: "package-lock.json", pm: "npm" }
|
|
184
|
+
];
|
|
185
|
+
for (const { file, pm: pm2 } of lockChecks) {
|
|
186
|
+
const lockExists = yield* fs.exists(pathService.join(cwd, file)).pipe(Effect.catchAll(() => Effect.succeed(false)));
|
|
187
|
+
if (lockExists) {
|
|
188
|
+
yield* Effect.log("Package manager detected from lock file", { packageManager: pm2, lockFile: file });
|
|
189
|
+
return pm2;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const bunAvailable = yield* Effect.tryPromise({
|
|
193
|
+
try: () => exec({ command: "bun --version", cwd }),
|
|
194
|
+
catch: () => new ExecError({ message: "bun not found", command: "bun --version" })
|
|
195
|
+
}).pipe(
|
|
196
|
+
Effect.as(true),
|
|
197
|
+
Effect.catchTag("ExecError", () => Effect.succeed(false))
|
|
198
|
+
);
|
|
199
|
+
const pm = bunAvailable ? "bun" : "npm";
|
|
200
|
+
yield* Effect.log("Package manager detected via fallback", { packageManager: pm });
|
|
201
|
+
return pm;
|
|
202
|
+
});
|
|
203
|
+
const installCommand = (pm) => ({
|
|
204
|
+
bun: "bun install",
|
|
205
|
+
npm: "npm install",
|
|
206
|
+
pnpm: "pnpm install",
|
|
207
|
+
yarn: "yarn install"
|
|
208
|
+
})[pm];
|
|
209
|
+
const buildCommand2 = (pm) => ({
|
|
210
|
+
bun: "bun run build",
|
|
211
|
+
npm: "npm run build",
|
|
212
|
+
pnpm: "pnpm build",
|
|
213
|
+
yarn: "yarn build"
|
|
214
|
+
})[pm];
|
|
215
|
+
return { detect, installCommand, buildCommand: buildCommand2 };
|
|
216
|
+
})
|
|
217
|
+
}) {
|
|
99
218
|
};
|
|
100
219
|
|
|
101
220
|
// src/commands/build/index.ts
|
|
102
|
-
var
|
|
103
|
-
|
|
104
|
-
|
|
221
|
+
var buildCommand = Command.make(
|
|
222
|
+
"build",
|
|
223
|
+
{},
|
|
224
|
+
() => Effect2.gen(function* () {
|
|
225
|
+
const pmService = yield* PackageManagerService;
|
|
226
|
+
const fs = yield* FileSystem2.FileSystem;
|
|
227
|
+
const pathService = yield* Path2.Path;
|
|
228
|
+
const cwd = process.cwd();
|
|
229
|
+
yield* Effect2.log("Starting build", { cwd });
|
|
230
|
+
const packageJsonPath = pathService.join(cwd, "package.json");
|
|
231
|
+
const packageJsonExists = yield* fs.exists(packageJsonPath).pipe(
|
|
232
|
+
Effect2.mapError(
|
|
233
|
+
() => new PackageJsonNotFoundError({ message: "Failed to check for package.json", directory: cwd })
|
|
234
|
+
)
|
|
235
|
+
);
|
|
236
|
+
if (!packageJsonExists) {
|
|
237
|
+
return yield* Effect2.fail(new PackageJsonNotFoundError({ message: "No package.json found", directory: cwd }));
|
|
238
|
+
}
|
|
239
|
+
const packageJsonContent = yield* fs.readFileString(packageJsonPath, "utf-8").pipe(
|
|
240
|
+
Effect2.mapError(() => new PackageJsonNotFoundError({ message: "Failed to read package.json", directory: cwd }))
|
|
241
|
+
);
|
|
242
|
+
const packageJson = yield* Effect2.try({
|
|
243
|
+
try: () => JSON.parse(packageJsonContent),
|
|
244
|
+
catch: (error) => new PackageJsonParseError({
|
|
245
|
+
message: "Failed to parse package.json",
|
|
246
|
+
directory: cwd,
|
|
247
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
248
|
+
})
|
|
249
|
+
});
|
|
250
|
+
if (!packageJson.scripts?.build) {
|
|
251
|
+
return yield* Effect2.fail(new BuildScriptNotFoundError({ message: "No build script found in package.json" }));
|
|
252
|
+
}
|
|
253
|
+
const pm = yield* pmService.detect(cwd);
|
|
254
|
+
const buildEnv = {
|
|
105
255
|
...process.env,
|
|
106
256
|
NODE_ENV: "production",
|
|
107
257
|
NEBULA: "1",
|
|
108
258
|
VERCEL: "1",
|
|
109
|
-
// Vercel compatibility
|
|
110
259
|
NOW_BUILDER: "1"
|
|
111
260
|
};
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
await exec2({
|
|
130
|
-
command: installCommand(packageManager),
|
|
131
|
-
cwd,
|
|
132
|
-
env,
|
|
133
|
-
onOutput: (chunk) => {
|
|
134
|
-
console.log(chunk);
|
|
135
|
-
}
|
|
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
|
+
})
|
|
136
277
|
});
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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", {
|
|
337
|
+
accessors: true,
|
|
338
|
+
dependencies: [NetworkConfigLive],
|
|
339
|
+
effect: Effect3.gen(function* () {
|
|
340
|
+
const rawReader = yield* RawConfigReader;
|
|
341
|
+
const resolve = Effect3.fn("ProjectConfigService.resolve")(function* (cwd) {
|
|
342
|
+
yield* Effect3.annotateCurrentSpan("cwd", cwd);
|
|
343
|
+
yield* Effect3.log("Resolving project config", { cwd });
|
|
344
|
+
const configPath = yield* rawReader.findConfig(cwd);
|
|
345
|
+
const detection = yield* detectFramework(cwd).pipe(
|
|
346
|
+
Effect3.catchTag("FrameworkNotDetectedError", () => Effect3.succeed(null)),
|
|
347
|
+
Effect3.catchTag(
|
|
348
|
+
"ManifestReadError",
|
|
349
|
+
(e) => Effect3.fail(new ConfigParseError({ message: `Framework detection failed: ${e.message}`, cause: e.filePath }))
|
|
350
|
+
),
|
|
351
|
+
Effect3.catchTag(
|
|
352
|
+
"ManifestParseError",
|
|
353
|
+
(e) => Effect3.fail(new ConfigParseError({ message: `Framework detection failed: ${e.message}`, cause: e.filePath }))
|
|
354
|
+
)
|
|
355
|
+
);
|
|
356
|
+
if (detection) {
|
|
357
|
+
yield* Effect3.log("Framework auto-detected", {
|
|
358
|
+
framework: detection.framework.name,
|
|
359
|
+
slug: detection.framework.slug
|
|
360
|
+
});
|
|
143
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
|
+
return yield* Effect3.fail(
|
|
381
|
+
new ConfigNotFoundError({
|
|
382
|
+
message: "No config file found and no framework detected.",
|
|
383
|
+
directory: cwd
|
|
384
|
+
})
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
for (const warning of config.warnings) {
|
|
388
|
+
yield* Effect3.logWarning(warning);
|
|
389
|
+
}
|
|
390
|
+
if (config.errors.length > 0) {
|
|
391
|
+
return yield* Effect3.fail(
|
|
392
|
+
new ConfigValidationError({
|
|
393
|
+
message: "Config file has validation errors",
|
|
394
|
+
errors: config.errors
|
|
395
|
+
})
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
return { config, configPath: resolvedConfigPath, framework };
|
|
144
399
|
});
|
|
145
|
-
|
|
400
|
+
return { resolve };
|
|
401
|
+
})
|
|
402
|
+
}) {
|
|
146
403
|
};
|
|
147
404
|
|
|
148
405
|
// src/commands/debug/config/index.ts
|
|
149
|
-
|
|
150
|
-
|
|
406
|
+
var debugConfigCommand = Command2.make(
|
|
407
|
+
"config",
|
|
408
|
+
{},
|
|
409
|
+
() => Effect4.gen(function* () {
|
|
410
|
+
const projectConfig = yield* ProjectConfigService;
|
|
411
|
+
const cwd = process.cwd();
|
|
412
|
+
const { config, configPath } = yield* projectConfig.resolve(cwd);
|
|
413
|
+
yield* Console2.log(`Config file found at: ${configPath}`);
|
|
414
|
+
yield* Console2.log(JSON.stringify(config, null, 2));
|
|
415
|
+
}).pipe(
|
|
416
|
+
Effect4.catchTags({
|
|
417
|
+
ConfigNotFoundError: (err) => Console2.error(err.message),
|
|
418
|
+
ConfigParseError: (err) => Console2.error(`Config parse error: ${err.message}`),
|
|
419
|
+
ConfigValidationError: (err) => Effect4.gen(function* () {
|
|
420
|
+
for (const e of err.errors) {
|
|
421
|
+
yield* Console2.error(e);
|
|
422
|
+
}
|
|
423
|
+
yield* Console2.error(err.message);
|
|
424
|
+
})
|
|
425
|
+
})
|
|
426
|
+
)
|
|
427
|
+
);
|
|
151
428
|
|
|
152
|
-
// src/
|
|
153
|
-
|
|
154
|
-
var
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
161
460
|
});
|
|
162
|
-
var
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
var
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
};
|
|
175
|
-
var
|
|
176
|
-
|
|
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", {
|
|
490
|
+
accessors: true,
|
|
491
|
+
effect: Effect6.gen(function* () {
|
|
492
|
+
const fs = yield* FileSystem3.FileSystem;
|
|
493
|
+
const pathService = yield* Path3.Path;
|
|
494
|
+
const homeDir = os.homedir();
|
|
495
|
+
const authDir = pathService.join(homeDir, ".gigadrive");
|
|
496
|
+
const authFile = pathService.join(authDir, "auth.json");
|
|
497
|
+
const ensureDirectory = Effect6.gen(function* () {
|
|
498
|
+
yield* fs.makeDirectory(authDir, { recursive: true }).pipe(
|
|
499
|
+
Effect6.mapError(
|
|
500
|
+
(error) => new AuthStorageWriteError({
|
|
501
|
+
message: "Failed to create auth directory",
|
|
502
|
+
cause: String(error)
|
|
503
|
+
})
|
|
504
|
+
)
|
|
505
|
+
);
|
|
506
|
+
yield* fs.chmod(authDir, 448).pipe(
|
|
507
|
+
Effect6.mapError(
|
|
508
|
+
(error) => new AuthStorageWriteError({
|
|
509
|
+
message: "Failed to set auth directory permissions",
|
|
510
|
+
cause: String(error)
|
|
511
|
+
})
|
|
512
|
+
)
|
|
513
|
+
);
|
|
514
|
+
});
|
|
515
|
+
const load = Effect6.gen(function* () {
|
|
516
|
+
const exists = yield* fs.exists(authFile).pipe(Effect6.mapError(() => new AuthStorageReadError({ message: "Failed to check auth file existence" })));
|
|
517
|
+
if (!exists) return Option.none();
|
|
518
|
+
const data = yield* fs.readFileString(authFile, "utf8").pipe(Effect6.mapError(() => new AuthStorageReadError({ message: "Failed to read auth file" })));
|
|
519
|
+
const json = yield* Effect6.try({
|
|
520
|
+
try: () => JSON.parse(data),
|
|
521
|
+
catch: () => new AuthStorageReadError({ message: "Auth file contains invalid JSON" })
|
|
522
|
+
});
|
|
523
|
+
const authData = yield* Schema3.decodeUnknown(StoredAuthData)(json).pipe(
|
|
524
|
+
Effect6.mapError(() => new AuthStorageReadError({ message: "Auth file has invalid schema" }))
|
|
525
|
+
);
|
|
526
|
+
return Option.some(authData);
|
|
527
|
+
});
|
|
528
|
+
const save = Effect6.fn("AuthStorageService.save")(function* (authData) {
|
|
529
|
+
yield* ensureDirectory;
|
|
530
|
+
yield* fs.writeFileString(authFile, JSON.stringify(authData, null, 2), { mode: 384 }).pipe(
|
|
531
|
+
Effect6.mapError(
|
|
532
|
+
(error) => new AuthStorageWriteError({
|
|
533
|
+
message: "Failed to write auth file",
|
|
534
|
+
cause: String(error)
|
|
535
|
+
})
|
|
536
|
+
)
|
|
537
|
+
);
|
|
538
|
+
yield* Effect6.log("Auth data saved to local storage");
|
|
539
|
+
});
|
|
540
|
+
const remove = Effect6.gen(function* () {
|
|
541
|
+
yield* fs.remove(authFile).pipe(
|
|
542
|
+
Effect6.mapError((error) => {
|
|
543
|
+
const cause = error instanceof Error ? error : error.error;
|
|
544
|
+
const code = cause && typeof cause === "object" && "code" in cause ? cause.code : "";
|
|
545
|
+
return { isNotFound: code === "ENOENT" || String(error).includes("ENOENT"), original: error };
|
|
546
|
+
}),
|
|
547
|
+
Effect6.catchAll(
|
|
548
|
+
(mapped) => mapped.isNotFound ? Effect6.void : Effect6.fail(
|
|
549
|
+
new AuthStorageWriteError({
|
|
550
|
+
message: "Failed to remove auth file",
|
|
551
|
+
cause: String(mapped.original)
|
|
552
|
+
})
|
|
553
|
+
)
|
|
554
|
+
)
|
|
555
|
+
);
|
|
556
|
+
yield* Effect6.log("Auth data removed from local storage");
|
|
557
|
+
});
|
|
558
|
+
return { load, save, remove };
|
|
559
|
+
})
|
|
560
|
+
}) {
|
|
177
561
|
};
|
|
178
|
-
|
|
179
|
-
|
|
562
|
+
|
|
563
|
+
// src/services/oauth-config.ts
|
|
564
|
+
import { Config, Effect as Effect7, Schema as Schema4 } from "effect";
|
|
565
|
+
var OAuthEnvConfig = Config.all({
|
|
566
|
+
issuerUrl: Config.string("GIGADRIVE_NETWORK_OAUTH_ISSUER_URL").pipe(Config.withDefault("https://idp.gigadrive.de")),
|
|
567
|
+
clientId: Config.string("GIGADRIVE_NETWORK_OAUTH_CLIENT_ID").pipe(Config.withDefault("todo_add_client_id"))
|
|
568
|
+
});
|
|
569
|
+
var OpenIdDiscoveryDocument = Schema4.Struct({
|
|
570
|
+
issuer: Schema4.optional(Schema4.String),
|
|
571
|
+
authorization_endpoint: Schema4.optional(Schema4.String),
|
|
572
|
+
token_endpoint: Schema4.optional(Schema4.String),
|
|
573
|
+
userinfo_endpoint: Schema4.optional(Schema4.String)
|
|
574
|
+
});
|
|
575
|
+
var DEFAULT_SCOPE = "offline_access openid profile email";
|
|
576
|
+
var OAuthConfigService = class extends Effect7.Service()("OAuthConfigService", {
|
|
577
|
+
accessors: true,
|
|
578
|
+
effect: Effect7.gen(function* () {
|
|
579
|
+
const envConfig = yield* OAuthEnvConfig;
|
|
580
|
+
let cached = null;
|
|
581
|
+
const getConfig = Effect7.gen(function* () {
|
|
582
|
+
if (cached) return cached;
|
|
583
|
+
const issuerBase = envConfig.issuerUrl.replace(/\/$/, "");
|
|
584
|
+
const discoveryUrl = `${issuerBase}/.well-known/openid-configuration`;
|
|
585
|
+
yield* Effect7.log("Fetching OIDC discovery document", { discoveryUrl });
|
|
586
|
+
const response = yield* Effect7.tryPromise({
|
|
587
|
+
try: (signal) => fetch(discoveryUrl, { signal }),
|
|
588
|
+
catch: (error) => new OAuthDiscoveryError({
|
|
589
|
+
message: "Failed to fetch OIDC discovery document",
|
|
590
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
591
|
+
})
|
|
592
|
+
});
|
|
593
|
+
if (!response.ok) {
|
|
594
|
+
return yield* Effect7.fail(
|
|
595
|
+
new OAuthDiscoveryError({
|
|
596
|
+
message: `OIDC discovery returned ${response.status} ${response.statusText}`
|
|
597
|
+
})
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
const json = yield* Effect7.tryPromise({
|
|
601
|
+
try: () => response.json(),
|
|
602
|
+
catch: () => new OAuthDiscoveryError({
|
|
603
|
+
message: "Failed to parse OIDC discovery JSON"
|
|
604
|
+
})
|
|
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
|
+
);
|
|
613
|
+
if (!decoded.authorization_endpoint || !decoded.token_endpoint || !decoded.userinfo_endpoint) {
|
|
614
|
+
return yield* Effect7.fail(
|
|
615
|
+
new OAuthDiscoveryError({
|
|
616
|
+
message: "OIDC discovery returned incomplete endpoints"
|
|
617
|
+
})
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
const config = {
|
|
621
|
+
clientId: envConfig.clientId,
|
|
622
|
+
issuer: decoded.issuer ?? issuerBase,
|
|
623
|
+
authorizeUrl: decoded.authorization_endpoint,
|
|
624
|
+
tokenUrl: decoded.token_endpoint,
|
|
625
|
+
scope: DEFAULT_SCOPE,
|
|
626
|
+
userinfoUrl: decoded.userinfo_endpoint
|
|
627
|
+
};
|
|
628
|
+
cached = config;
|
|
629
|
+
yield* Effect7.log("OIDC discovery completed", { issuer: config.issuer });
|
|
630
|
+
return config;
|
|
631
|
+
});
|
|
632
|
+
return { getConfig };
|
|
633
|
+
})
|
|
634
|
+
}) {
|
|
180
635
|
};
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
636
|
+
|
|
637
|
+
// src/services/auth.ts
|
|
638
|
+
var escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\//g, "/");
|
|
639
|
+
var base64URLEncode = (buffer) => buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
640
|
+
var generatePKCE = () => {
|
|
641
|
+
const codeVerifier = base64URLEncode(crypto.randomBytes(64));
|
|
642
|
+
const challengeBytes = crypto.createHash("sha256").update(codeVerifier, "utf8").digest();
|
|
643
|
+
const codeChallenge = base64URLEncode(challengeBytes);
|
|
644
|
+
return { codeVerifier, codeChallenge };
|
|
186
645
|
};
|
|
187
|
-
var
|
|
188
|
-
|
|
646
|
+
var AuthService = class extends Effect8.Service()("AuthService", {
|
|
647
|
+
accessors: true,
|
|
648
|
+
dependencies: [OAuthConfigService.Default, AuthStorageService.Default],
|
|
649
|
+
effect: Effect8.gen(function* () {
|
|
650
|
+
const oauthConfig = yield* OAuthConfigService;
|
|
651
|
+
const storage = yield* AuthStorageService;
|
|
652
|
+
let accessToken = null;
|
|
653
|
+
let tokenExpirationTime = 0;
|
|
654
|
+
const logout = Effect8.gen(function* () {
|
|
655
|
+
accessToken = null;
|
|
656
|
+
tokenExpirationTime = 0;
|
|
657
|
+
yield* storage.remove;
|
|
658
|
+
yield* Effect8.log("Logged out");
|
|
659
|
+
});
|
|
660
|
+
const exchangeCodeForTokens = (config, authCode, redirectUri, codeVerifier) => Effect8.gen(function* () {
|
|
661
|
+
const response = yield* Effect8.tryPromise({
|
|
662
|
+
try: () => fetch(config.tokenUrl, {
|
|
663
|
+
method: "POST",
|
|
664
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
665
|
+
body: new URLSearchParams({
|
|
666
|
+
grant_type: "authorization_code",
|
|
667
|
+
client_id: config.clientId,
|
|
668
|
+
code: authCode,
|
|
669
|
+
redirect_uri: redirectUri,
|
|
670
|
+
code_verifier: codeVerifier
|
|
671
|
+
}).toString()
|
|
672
|
+
}),
|
|
673
|
+
catch: (error) => new TokenExchangeError({
|
|
674
|
+
message: `Token exchange request failed: ${error instanceof Error ? error.message : String(error)}`
|
|
675
|
+
})
|
|
676
|
+
});
|
|
677
|
+
if (!response.ok) {
|
|
678
|
+
const body = yield* Effect8.tryPromise({
|
|
679
|
+
try: () => response.text(),
|
|
680
|
+
catch: () => new TokenExchangeError({ message: "Failed to read token exchange error response" })
|
|
681
|
+
});
|
|
682
|
+
return yield* Effect8.fail(
|
|
683
|
+
new TokenExchangeError({
|
|
684
|
+
message: `Token exchange failed: ${response.status} ${response.statusText} - ${body}`,
|
|
685
|
+
statusCode: response.status
|
|
686
|
+
})
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
const tokens = yield* Effect8.tryPromise({
|
|
690
|
+
try: () => response.json(),
|
|
691
|
+
catch: () => new TokenExchangeError({ message: "Failed to parse token exchange response" })
|
|
692
|
+
});
|
|
693
|
+
return tokens;
|
|
694
|
+
});
|
|
695
|
+
const startCallbackServer = (config, codeChallenge, state) => Effect8.async((resume) => {
|
|
696
|
+
const server = http.createServer();
|
|
697
|
+
const redirectPath = "/callback";
|
|
698
|
+
server.listen(0, "127.0.0.1", () => {
|
|
699
|
+
const address = server.address();
|
|
700
|
+
if (!address || typeof address !== "object") {
|
|
701
|
+
server.close(() => {
|
|
702
|
+
resume(Effect8.fail(new LoginFlowError({ message: "Failed to determine callback server address" })));
|
|
703
|
+
});
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
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
|
+
}
|
|
774
|
+
accessToken = tokens.access_token;
|
|
775
|
+
tokenExpirationTime = Date.now() + (tokens.expires_in || 3600) * 1e3;
|
|
776
|
+
yield* storage.save({
|
|
777
|
+
refreshToken: tokens.refresh_token,
|
|
778
|
+
accessToken,
|
|
779
|
+
tokenExpirationTime
|
|
780
|
+
});
|
|
781
|
+
yield* Effect8.log("Login successful");
|
|
782
|
+
return true;
|
|
783
|
+
});
|
|
784
|
+
const refreshAccessToken = Effect8.gen(function* () {
|
|
785
|
+
const stored = yield* storage.load.pipe(
|
|
786
|
+
Effect8.catchTag("AuthStorageReadError", (err) => Effect8.fail(new TokenRefreshError({ message: err.message })))
|
|
787
|
+
);
|
|
788
|
+
const config = yield* oauthConfig.getConfig.pipe(
|
|
789
|
+
Effect8.catchTag("OAuthDiscoveryError", (err) => Effect8.fail(new TokenRefreshError({ message: err.message })))
|
|
790
|
+
);
|
|
791
|
+
const authData = yield* Option2.match(stored, {
|
|
792
|
+
onNone: () => Effect8.fail(new TokenRefreshError({ message: "No refresh token available. Please log in." })),
|
|
793
|
+
onSome: (data) => data.refreshToken ? Effect8.succeed(data) : Effect8.fail(new TokenRefreshError({ message: "No refresh token available. Please log in." }))
|
|
794
|
+
});
|
|
795
|
+
const response = yield* Effect8.tryPromise({
|
|
796
|
+
try: () => fetch(config.tokenUrl, {
|
|
797
|
+
method: "POST",
|
|
798
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
799
|
+
body: new URLSearchParams({
|
|
800
|
+
grant_type: "refresh_token",
|
|
801
|
+
client_id: config.clientId,
|
|
802
|
+
refresh_token: authData.refreshToken
|
|
803
|
+
}).toString()
|
|
804
|
+
}),
|
|
805
|
+
catch: (error) => new TokenRefreshError({
|
|
806
|
+
message: `Token refresh request failed: ${error instanceof Error ? error.message : String(error)}`
|
|
807
|
+
})
|
|
808
|
+
});
|
|
809
|
+
if (!response.ok) {
|
|
810
|
+
if (response.status === 400) {
|
|
811
|
+
yield* logout.pipe(Effect8.catchAll(() => Effect8.void));
|
|
812
|
+
return yield* Effect8.fail(
|
|
813
|
+
new TokenRefreshError({
|
|
814
|
+
message: "Refresh token is invalid or expired. Please log in again.",
|
|
815
|
+
statusCode: response.status
|
|
816
|
+
})
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
const body = yield* Effect8.tryPromise({
|
|
820
|
+
try: () => response.text(),
|
|
821
|
+
catch: () => new TokenRefreshError({ message: "Failed to read refresh error response" })
|
|
822
|
+
});
|
|
823
|
+
return yield* Effect8.fail(
|
|
824
|
+
new TokenRefreshError({
|
|
825
|
+
message: `Token refresh failed: ${response.status} ${response.statusText} - ${body}`,
|
|
826
|
+
statusCode: response.status
|
|
827
|
+
})
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
const tokens = yield* Effect8.tryPromise({
|
|
831
|
+
try: () => response.json(),
|
|
832
|
+
catch: () => new TokenRefreshError({ message: "Failed to parse token refresh response" })
|
|
833
|
+
});
|
|
834
|
+
accessToken = tokens.access_token;
|
|
835
|
+
tokenExpirationTime = Date.now() + (tokens.expires_in || 3600) * 1e3;
|
|
836
|
+
const newRefreshToken = tokens.refresh_token || authData.refreshToken;
|
|
837
|
+
yield* storage.save({
|
|
838
|
+
refreshToken: newRefreshToken,
|
|
839
|
+
accessToken,
|
|
840
|
+
tokenExpirationTime
|
|
841
|
+
}).pipe(Effect8.catchAll(() => Effect8.void));
|
|
842
|
+
yield* Effect8.log("Access token refreshed successfully");
|
|
843
|
+
return true;
|
|
844
|
+
});
|
|
845
|
+
const getAccessToken = Effect8.gen(function* () {
|
|
846
|
+
if (accessToken && Date.now() < tokenExpirationTime) {
|
|
847
|
+
return accessToken;
|
|
848
|
+
}
|
|
849
|
+
const stored = yield* storage.load.pipe(
|
|
850
|
+
Effect8.catchAll(() => Effect8.succeed(Option2.none()))
|
|
851
|
+
);
|
|
852
|
+
Option2.match(stored, {
|
|
853
|
+
onNone: () => {
|
|
854
|
+
},
|
|
855
|
+
onSome: (data) => {
|
|
856
|
+
if (data.accessToken && data.tokenExpirationTime && Date.now() < data.tokenExpirationTime) {
|
|
857
|
+
accessToken = data.accessToken;
|
|
858
|
+
tokenExpirationTime = data.tokenExpirationTime;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
if (accessToken && Date.now() < tokenExpirationTime) {
|
|
863
|
+
return accessToken;
|
|
864
|
+
}
|
|
865
|
+
yield* Effect8.log("Access token expired or not available. Attempting to refresh...");
|
|
866
|
+
yield* refreshAccessToken.pipe(
|
|
867
|
+
Effect8.catchTag("TokenRefreshError", (err) => Effect8.fail(new NotAuthenticatedError({ message: err.message })))
|
|
868
|
+
);
|
|
869
|
+
if (!accessToken) {
|
|
870
|
+
return yield* Effect8.fail(
|
|
871
|
+
new NotAuthenticatedError({ message: "No valid access token available. Please log in first." })
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
return accessToken;
|
|
875
|
+
});
|
|
876
|
+
const getUserInfo = Effect8.gen(function* () {
|
|
877
|
+
const config = yield* oauthConfig.getConfig;
|
|
878
|
+
const token = yield* getAccessToken;
|
|
879
|
+
const fetchUserInfo = (t) => Effect8.tryPromise({
|
|
880
|
+
try: () => fetch(config.userinfoUrl, {
|
|
881
|
+
headers: {
|
|
882
|
+
Authorization: `Bearer ${t}`,
|
|
883
|
+
Accept: "application/json"
|
|
884
|
+
}
|
|
885
|
+
}),
|
|
886
|
+
catch: (error) => new UserInfoFetchError({
|
|
887
|
+
message: `Failed to fetch user info: ${error instanceof Error ? error.message : String(error)}`
|
|
888
|
+
})
|
|
889
|
+
});
|
|
890
|
+
let response = yield* fetchUserInfo(token);
|
|
891
|
+
if (response.status === 401 || response.status === 403) {
|
|
892
|
+
yield* refreshAccessToken.pipe(
|
|
893
|
+
Effect8.catchTag("TokenRefreshError", (err) => Effect8.fail(new UserInfoFetchError({ message: err.message })))
|
|
894
|
+
);
|
|
895
|
+
if (!accessToken) {
|
|
896
|
+
return yield* Effect8.fail(new UserInfoFetchError({ message: "Token refresh failed" }));
|
|
897
|
+
}
|
|
898
|
+
response = yield* fetchUserInfo(accessToken);
|
|
899
|
+
}
|
|
900
|
+
if (!response.ok) {
|
|
901
|
+
const body = yield* Effect8.tryPromise({
|
|
902
|
+
try: () => response.text(),
|
|
903
|
+
catch: () => new UserInfoFetchError({ message: "Failed to read user info error response" })
|
|
904
|
+
});
|
|
905
|
+
return yield* Effect8.fail(
|
|
906
|
+
new UserInfoFetchError({
|
|
907
|
+
message: `Failed to fetch user info: ${response.status} ${response.statusText}${body ? ` - ${body}` : ""}`,
|
|
908
|
+
statusCode: response.status
|
|
909
|
+
})
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
const data = yield* Effect8.tryPromise({
|
|
913
|
+
try: () => response.json(),
|
|
914
|
+
catch: () => new UserInfoFetchError({ message: "Failed to parse user info response" })
|
|
915
|
+
});
|
|
916
|
+
return data;
|
|
917
|
+
});
|
|
918
|
+
const inferUserName = (info) => {
|
|
919
|
+
const isNonEmptyString = (value) => typeof value === "string" && value.trim() !== "";
|
|
920
|
+
const name = info.name;
|
|
921
|
+
const givenName = info.given_name;
|
|
922
|
+
const familyName = info.family_name;
|
|
923
|
+
const preferredUsername = info.preferred_username;
|
|
924
|
+
const email = info.email;
|
|
925
|
+
return isNonEmptyString(name) && name || (isNonEmptyString(givenName) && isNonEmptyString(familyName) ? `${givenName} ${familyName}` : void 0) || isNonEmptyString(preferredUsername) && preferredUsername || isNonEmptyString(email) && email || "your account";
|
|
926
|
+
};
|
|
927
|
+
return { login, logout, getAccessToken, getUserInfo, refreshAccessToken, inferUserName };
|
|
928
|
+
})
|
|
929
|
+
}) {
|
|
189
930
|
};
|
|
190
931
|
|
|
191
|
-
// src/commands/
|
|
192
|
-
var
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
})
|
|
203
|
-
|
|
932
|
+
// src/commands/login/index.ts
|
|
933
|
+
var loginCommand = Command4.make(
|
|
934
|
+
"login",
|
|
935
|
+
{},
|
|
936
|
+
() => Effect9.gen(function* () {
|
|
937
|
+
yield* Console4.log("Initiating login...");
|
|
938
|
+
const auth = yield* AuthService;
|
|
939
|
+
yield* auth.login;
|
|
940
|
+
const info = yield* auth.getUserInfo.pipe(Effect9.catchAll(() => Effect9.succeed({})));
|
|
941
|
+
const name = auth.inferUserName(info);
|
|
942
|
+
yield* Console4.log(`You are now logged in as ${name}.`);
|
|
943
|
+
}).pipe(
|
|
944
|
+
Effect9.catchTags({
|
|
945
|
+
LoginFlowError: (err) => Console4.error(`Login failed: ${err.message}`),
|
|
946
|
+
TokenExchangeError: (err) => Console4.error(`Login failed: ${err.message}`),
|
|
947
|
+
OAuthDiscoveryError: (err) => Console4.error(`Login failed: ${err.message}`),
|
|
948
|
+
AuthStorageWriteError: (err) => Console4.error(`Failed to save credentials: ${err.message}`)
|
|
949
|
+
})
|
|
950
|
+
)
|
|
951
|
+
);
|
|
204
952
|
|
|
205
|
-
// src/commands/
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
config(debug3);
|
|
209
|
-
};
|
|
953
|
+
// src/commands/platform/index.ts
|
|
954
|
+
import { Command as Command6 } from "@effect/cli";
|
|
955
|
+
import { Effect as Effect13 } from "effect";
|
|
210
956
|
|
|
211
957
|
// src/commands/platform/deploy/index.ts
|
|
212
|
-
import {
|
|
213
|
-
import {
|
|
214
|
-
import
|
|
215
|
-
import
|
|
216
|
-
import
|
|
958
|
+
import { Command as Command5 } from "@effect/cli";
|
|
959
|
+
import { FileSystem as FileSystem5, Path as Path5 } from "@effect/platform";
|
|
960
|
+
import { formatFileSize } from "@gigadrive/commons";
|
|
961
|
+
import { Console as Console5, Duration, Effect as Effect12, Schedule, Stream } from "effect";
|
|
962
|
+
import * as os2 from "os";
|
|
217
963
|
|
|
218
|
-
// src/
|
|
964
|
+
// src/services/archive.ts
|
|
965
|
+
import { FileSystem as FileSystem4, Path as Path4 } from "@effect/platform";
|
|
219
966
|
import { getFilesForPattern } from "@gigadrive/build-utils";
|
|
220
967
|
import archiver from "archiver";
|
|
221
|
-
import
|
|
222
|
-
import fsPromises from "fs/promises";
|
|
968
|
+
import { Effect as Effect10 } from "effect";
|
|
223
969
|
import ignore from "ignore";
|
|
224
|
-
import
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
".
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
try {
|
|
234
|
-
await fsPromises.access(ignorePath);
|
|
235
|
-
const content = await fsPromises.readFile(ignorePath, "utf8");
|
|
236
|
-
return content.split("\n").filter((line) => line.trim() !== "");
|
|
237
|
-
} catch {
|
|
238
|
-
return [];
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
async function collectIgnorePatterns(dir, baseDir, ignoreRules) {
|
|
242
|
-
const items = await fsPromises.readdir(dir);
|
|
970
|
+
import * as nodeFs from "fs";
|
|
971
|
+
import * as nodeFsPromises from "fs/promises";
|
|
972
|
+
var IGNORE_FILE_NAMES = [".gigadriveignore", ".vercelignore", ".dockerignore", ".nowignore"];
|
|
973
|
+
var readIgnoreFile = (fs, ignorePath) => fs.readFileString(ignorePath, "utf8").pipe(
|
|
974
|
+
Effect10.map((content) => content.split("\n").filter((line) => line.trim() !== "")),
|
|
975
|
+
Effect10.catchAll(() => Effect10.succeed([]))
|
|
976
|
+
);
|
|
977
|
+
var collectIgnorePatterns = (fs, pathService, dir, baseDir, ignoreRules) => Effect10.gen(function* () {
|
|
978
|
+
const items = yield* fs.readDirectory(dir).pipe(Effect10.mapError(() => new ArchiveCreateError({ message: `Failed to read directory: ${dir}` })));
|
|
243
979
|
for (const item of items) {
|
|
244
|
-
const fullPath =
|
|
245
|
-
const relativePath =
|
|
246
|
-
const stat =
|
|
980
|
+
const fullPath = pathService.join(dir, item);
|
|
981
|
+
const relativePath = pathService.relative(baseDir, fullPath);
|
|
982
|
+
const stat = yield* Effect10.tryPromise({
|
|
983
|
+
try: () => nodeFsPromises.lstat(fullPath),
|
|
984
|
+
catch: () => new ArchiveCreateError({ message: `Failed to stat: ${fullPath}` })
|
|
985
|
+
});
|
|
247
986
|
if (stat.isDirectory()) {
|
|
248
|
-
if (ignoreRules.ignores(`${relativePath}/`))
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
ignoreRules.add(patterns);
|
|
259
|
-
} catch {
|
|
987
|
+
if (ignoreRules.ignores(`${relativePath}/`)) continue;
|
|
988
|
+
for (const ignoreFileName of IGNORE_FILE_NAMES) {
|
|
989
|
+
const ignoreFilePath = pathService.join(fullPath, ignoreFileName);
|
|
990
|
+
const patterns = yield* readIgnoreFile(fs, ignoreFilePath);
|
|
991
|
+
if (patterns.length > 0) {
|
|
992
|
+
ignoreRules.add(
|
|
993
|
+
patterns.map(
|
|
994
|
+
(pattern) => pattern.startsWith("!") ? `!${pathService.join(relativePath, pattern.slice(1))}` : pathService.join(relativePath, pattern)
|
|
995
|
+
)
|
|
996
|
+
);
|
|
260
997
|
}
|
|
261
998
|
}
|
|
262
|
-
|
|
999
|
+
yield* collectIgnorePatterns(fs, pathService, fullPath, baseDir, ignoreRules);
|
|
263
1000
|
}
|
|
264
1001
|
}
|
|
265
|
-
}
|
|
266
|
-
|
|
1002
|
+
}).pipe(Effect10.catchAll(() => Effect10.void));
|
|
1003
|
+
var initializeIgnoreRules = (fs, pathService, baseDir) => Effect10.gen(function* () {
|
|
267
1004
|
const ignoreRules = ignore();
|
|
268
1005
|
ignoreRules.add([".git/", "**/.DS_Store", "**/Thumbs.db"]);
|
|
269
|
-
for (const ignoreFileName of
|
|
270
|
-
const ignoreFilePath =
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
ignoreRules.add(patterns);
|
|
277
|
-
} catch {
|
|
1006
|
+
for (const ignoreFileName of IGNORE_FILE_NAMES) {
|
|
1007
|
+
const ignoreFilePath = pathService.join(baseDir, ignoreFileName);
|
|
1008
|
+
const patterns = yield* readIgnoreFile(fs, ignoreFilePath);
|
|
1009
|
+
if (patterns.length > 0) {
|
|
1010
|
+
ignoreRules.add(
|
|
1011
|
+
patterns.map((pattern) => pattern.startsWith("!") ? `!${pattern.slice(1)}` : pattern)
|
|
1012
|
+
);
|
|
278
1013
|
}
|
|
279
1014
|
}
|
|
280
|
-
|
|
1015
|
+
yield* collectIgnorePatterns(fs, pathService, baseDir, baseDir, ignoreRules);
|
|
281
1016
|
return ignoreRules;
|
|
282
|
-
}
|
|
283
|
-
|
|
1017
|
+
});
|
|
1018
|
+
var getFilesToInclude = (fs, pathService, dir, ignoreRules) => Effect10.gen(function* () {
|
|
284
1019
|
const filesToInclude = [];
|
|
285
|
-
|
|
286
|
-
const items =
|
|
1020
|
+
const walkDir = (currentDir) => Effect10.gen(function* () {
|
|
1021
|
+
const items = yield* fs.readDirectory(currentDir).pipe(Effect10.mapError(() => new ArchiveCreateError({ message: `Failed to read directory: ${currentDir}` })));
|
|
287
1022
|
for (const item of items) {
|
|
288
|
-
const fullPath =
|
|
289
|
-
const relativePath =
|
|
290
|
-
const stat =
|
|
1023
|
+
const fullPath = pathService.join(currentDir, item);
|
|
1024
|
+
const relativePath = pathService.relative(dir, fullPath);
|
|
1025
|
+
const stat = yield* Effect10.tryPromise({
|
|
1026
|
+
try: () => nodeFsPromises.lstat(fullPath),
|
|
1027
|
+
catch: () => new ArchiveCreateError({ message: `Failed to stat: ${fullPath}` })
|
|
1028
|
+
});
|
|
291
1029
|
if (stat.isDirectory()) {
|
|
292
|
-
if (ignoreRules.ignores(`${relativePath}/`)) {
|
|
293
|
-
|
|
1030
|
+
if (!ignoreRules.ignores(`${relativePath}/`)) {
|
|
1031
|
+
yield* walkDir(fullPath);
|
|
294
1032
|
}
|
|
295
|
-
await walkDir(fullPath);
|
|
296
1033
|
} else {
|
|
297
1034
|
if (!ignoreRules.ignores(relativePath)) {
|
|
298
1035
|
filesToInclude.push(fullPath);
|
|
299
|
-
} else if (ignoreRules.ignores(relativePath) && ignoreRules.test(relativePath).unignored) {
|
|
300
|
-
filesToInclude.push(fullPath);
|
|
301
1036
|
}
|
|
302
1037
|
}
|
|
303
1038
|
}
|
|
304
|
-
}
|
|
305
|
-
|
|
1039
|
+
}).pipe(Effect10.catchAll(() => Effect10.void));
|
|
1040
|
+
yield* walkDir(dir);
|
|
306
1041
|
return filesToInclude;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
debug(`Found config at: ${configPath}`);
|
|
334
|
-
const config2 = await parseConfig2(configPath, process.cwd());
|
|
335
|
-
debug("Parsed config", config2);
|
|
336
|
-
if (config2.warnings.length > 0) {
|
|
337
|
-
debug(`Found ${config2.warnings.length} warnings in config`);
|
|
338
|
-
config2.warnings.forEach((message) => {
|
|
339
|
-
warn(message);
|
|
1042
|
+
});
|
|
1043
|
+
var ArchiveService = class extends Effect10.Service()("ArchiveService", {
|
|
1044
|
+
accessors: true,
|
|
1045
|
+
effect: Effect10.gen(function* () {
|
|
1046
|
+
const fs = yield* FileSystem4.FileSystem;
|
|
1047
|
+
const pathService = yield* Path4.Path;
|
|
1048
|
+
const createZipArchive = Effect10.fn("ArchiveService.createZipArchive")(function* (inputDir, outputFile, options = {}) {
|
|
1049
|
+
yield* Effect10.annotateCurrentSpan("inputDir", inputDir);
|
|
1050
|
+
yield* Effect10.annotateCurrentSpan("outputFile", outputFile);
|
|
1051
|
+
yield* Effect10.log("Creating zip archive", { inputDir, outputFile });
|
|
1052
|
+
const ignoreRules = options.useIgnoreFiles !== false ? yield* initializeIgnoreRules(fs, pathService, inputDir) : ignore();
|
|
1053
|
+
const filesToInclude = options.whitelist ? yield* Effect10.tryPromise({
|
|
1054
|
+
try: () => Promise.all(
|
|
1055
|
+
options.whitelist.map((file) => getFilesForPattern(file, inputDir, options.excludeFiles ?? []))
|
|
1056
|
+
),
|
|
1057
|
+
catch: (error) => new ArchiveCreateError({
|
|
1058
|
+
message: "Failed to resolve whitelist patterns",
|
|
1059
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
1060
|
+
})
|
|
1061
|
+
}) : yield* Effect10.gen(function* () {
|
|
1062
|
+
if (options.excludeFiles && options.excludeFiles.length > 0) {
|
|
1063
|
+
ignoreRules.add(options.excludeFiles);
|
|
1064
|
+
}
|
|
1065
|
+
return yield* getFilesToInclude(fs, pathService, inputDir, ignoreRules).pipe(
|
|
1066
|
+
Effect10.map((files) => [files])
|
|
1067
|
+
);
|
|
340
1068
|
});
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
1069
|
+
const archiveExists = yield* fs.exists(outputFile).pipe(Effect10.catchAll(() => Effect10.succeed(false)));
|
|
1070
|
+
if (archiveExists) {
|
|
1071
|
+
yield* fs.remove(outputFile).pipe(
|
|
1072
|
+
Effect10.mapError(
|
|
1073
|
+
(error) => new ArchiveCreateError({
|
|
1074
|
+
message: `Failed to remove existing archive: ${String(error)}`
|
|
1075
|
+
})
|
|
1076
|
+
)
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
yield* Effect10.tryPromise({
|
|
1080
|
+
try: () => new Promise((resolve, reject) => {
|
|
1081
|
+
const output = nodeFs.createWriteStream(outputFile);
|
|
1082
|
+
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
1083
|
+
output.on("close", () => resolve());
|
|
1084
|
+
output.on("error", reject);
|
|
1085
|
+
archive.on("error", reject);
|
|
1086
|
+
archive.pipe(output);
|
|
1087
|
+
for (const file of filesToInclude.flat()) {
|
|
1088
|
+
archive.file(file, { name: pathService.relative(inputDir, file) });
|
|
1089
|
+
}
|
|
1090
|
+
void archive.finalize();
|
|
1091
|
+
}),
|
|
1092
|
+
catch: (error) => new ArchiveCreateError({
|
|
1093
|
+
message: "Failed to create zip archive",
|
|
1094
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
1095
|
+
})
|
|
346
1096
|
});
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
1097
|
+
const fileInfo = yield* fs.stat(outputFile).pipe(
|
|
1098
|
+
Effect10.mapError(
|
|
1099
|
+
(error) => new ArchiveCreateError({
|
|
1100
|
+
message: `Failed to stat archive: ${String(error)}`
|
|
1101
|
+
})
|
|
1102
|
+
)
|
|
1103
|
+
);
|
|
1104
|
+
const size = Number(fileInfo.size);
|
|
1105
|
+
yield* Effect10.log("Archive created", { size });
|
|
1106
|
+
return { path: outputFile, size };
|
|
353
1107
|
});
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
|
|
1108
|
+
return { createZipArchive };
|
|
1109
|
+
})
|
|
1110
|
+
}) {
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
// src/services/deployment-api.ts
|
|
1114
|
+
import { objectToQueryString } from "@gigadrive/commons";
|
|
1115
|
+
import { Config as Config2, Effect as Effect11, Schema as Schema5 } from "effect";
|
|
1116
|
+
var CreateDeploymentResponse = Schema5.Struct({ id: Schema5.String });
|
|
1117
|
+
var StartUploadResponse = Schema5.Struct({ uploadId: Schema5.String });
|
|
1118
|
+
var PresignedUrlResponse = Schema5.Struct({ url: Schema5.String });
|
|
1119
|
+
var DeploymentStatusResponse = Schema5.Struct({
|
|
1120
|
+
status: Schema5.Literal("PENDING", "BUILDING", "PROVISIONING", "FAILED", "ACTIVE", "SUSPENDED")
|
|
1121
|
+
});
|
|
1122
|
+
var DeploymentLogPageResponse = Schema5.Struct({
|
|
1123
|
+
totalItems: Schema5.Number,
|
|
1124
|
+
limit: Schema5.Number,
|
|
1125
|
+
offset: Schema5.Number,
|
|
1126
|
+
items: Schema5.Array(
|
|
1127
|
+
Schema5.Struct({
|
|
1128
|
+
id: Schema5.String,
|
|
1129
|
+
message: Schema5.String,
|
|
1130
|
+
type: Schema5.Literal("INFO", "ERROR", "WARN"),
|
|
1131
|
+
createdAt: Schema5.String
|
|
1132
|
+
})
|
|
1133
|
+
)
|
|
1134
|
+
});
|
|
1135
|
+
var ApiBaseUrl = Config2.string("GIGADRIVE_API_BASE_URL").pipe(Config2.withDefault("http://localhost:3000"));
|
|
1136
|
+
var DeploymentApiService = class extends Effect11.Service()("DeploymentApiService", {
|
|
1137
|
+
accessors: true,
|
|
1138
|
+
effect: Effect11.gen(function* () {
|
|
1139
|
+
const baseUrl = yield* ApiBaseUrl;
|
|
1140
|
+
const authService = yield* AuthService;
|
|
1141
|
+
const getAuthHeaders = Effect11.gen(function* () {
|
|
1142
|
+
const token = yield* authService.getAccessToken.pipe(
|
|
1143
|
+
Effect11.mapError((err) => new DeploymentAuthError({ message: `Authentication failed: ${err.message}` }))
|
|
1144
|
+
);
|
|
1145
|
+
return { Authorization: `Bearer ${token}` };
|
|
1146
|
+
});
|
|
1147
|
+
const createDeployment = Effect11.fn("DeploymentApiService.createDeployment")(function* (applicationId) {
|
|
1148
|
+
yield* Effect11.annotateCurrentSpan("applicationId", applicationId);
|
|
1149
|
+
yield* Effect11.log("Creating deployment", { applicationId });
|
|
1150
|
+
const authHeaders = yield* getAuthHeaders;
|
|
1151
|
+
const response = yield* Effect11.tryPromise({
|
|
1152
|
+
try: () => fetch(`${baseUrl}/${applicationId}/deployments`, {
|
|
1153
|
+
method: "POST",
|
|
1154
|
+
headers: { "Content-Type": "application/json", ...authHeaders },
|
|
1155
|
+
body: JSON.stringify({})
|
|
1156
|
+
}),
|
|
1157
|
+
catch: (error) => new DeploymentCreateError({
|
|
1158
|
+
message: `Failed to create deployment: ${error instanceof Error ? error.message : String(error)}`
|
|
1159
|
+
})
|
|
1160
|
+
});
|
|
1161
|
+
if (!response.ok) {
|
|
1162
|
+
const body = yield* Effect11.tryPromise({
|
|
1163
|
+
try: () => response.text(),
|
|
1164
|
+
catch: () => new DeploymentCreateError({
|
|
1165
|
+
message: "Failed to read deployment creation error",
|
|
1166
|
+
statusCode: response.status
|
|
1167
|
+
})
|
|
1168
|
+
});
|
|
1169
|
+
return yield* Effect11.fail(
|
|
1170
|
+
new DeploymentCreateError({
|
|
1171
|
+
message: `Failed to create deployment: ${body}`,
|
|
1172
|
+
statusCode: response.status
|
|
1173
|
+
})
|
|
1174
|
+
);
|
|
401
1175
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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;
|
|
1185
|
+
});
|
|
1186
|
+
const startMultipartUpload = Effect11.fn("DeploymentApiService.startMultipartUpload")(function* (deploymentId) {
|
|
1187
|
+
yield* Effect11.annotateCurrentSpan("deploymentId", deploymentId);
|
|
1188
|
+
const authHeaders = yield* getAuthHeaders;
|
|
1189
|
+
const response = yield* Effect11.tryPromise({
|
|
1190
|
+
try: () => fetch(`${baseUrl}/deployments/${deploymentId}/pre-signed-url/start`, {
|
|
1191
|
+
method: "GET",
|
|
1192
|
+
headers: { "Content-Type": "application/json", ...authHeaders }
|
|
1193
|
+
}),
|
|
1194
|
+
catch: (error) => new UploadStartError({
|
|
1195
|
+
message: `Failed to start multipart upload: ${error instanceof Error ? error.message : String(error)}`
|
|
1196
|
+
})
|
|
1197
|
+
});
|
|
1198
|
+
if (!response.ok) {
|
|
1199
|
+
const body = yield* Effect11.tryPromise({
|
|
1200
|
+
try: () => response.text(),
|
|
1201
|
+
catch: () => new UploadStartError({ message: "Failed to read upload start error" })
|
|
412
1202
|
});
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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;
|
|
1215
|
+
});
|
|
1216
|
+
const getPresignedUrl = Effect11.fn("DeploymentApiService.getPresignedUrl")(function* (deploymentId, uploadId, partNumber) {
|
|
1217
|
+
const authHeaders = yield* getAuthHeaders;
|
|
1218
|
+
const response = yield* Effect11.tryPromise({
|
|
1219
|
+
try: () => fetch(
|
|
1220
|
+
`${baseUrl}/deployments/${deploymentId}/pre-signed-url/part?uploadId=${uploadId}&partNumber=${partNumber}`,
|
|
1221
|
+
{
|
|
1222
|
+
method: "GET",
|
|
1223
|
+
headers: { "Content-Type": "application/json", ...authHeaders }
|
|
422
1224
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
1225
|
+
),
|
|
1226
|
+
catch: (error) => new PresignedUrlError({
|
|
1227
|
+
message: `Failed to get presigned URL: ${error instanceof Error ? error.message : String(error)}`,
|
|
1228
|
+
partNumber
|
|
1229
|
+
})
|
|
1230
|
+
});
|
|
1231
|
+
if (!response.ok) {
|
|
1232
|
+
const body = yield* Effect11.tryPromise({
|
|
1233
|
+
try: () => response.text(),
|
|
1234
|
+
catch: () => new PresignedUrlError({ message: "Failed to read presigned URL error", partNumber })
|
|
1235
|
+
});
|
|
1236
|
+
return yield* Effect11.fail(
|
|
1237
|
+
new PresignedUrlError({ message: `Failed to get presigned URL: ${body}`, partNumber })
|
|
1238
|
+
);
|
|
426
1239
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
message,
|
|
436
|
-
type,
|
|
437
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
438
|
-
});
|
|
439
|
-
if (type === "ERROR") {
|
|
440
|
-
error(message);
|
|
441
|
-
} else if (type === "WARN") {
|
|
442
|
-
warn(message);
|
|
443
|
-
} else {
|
|
444
|
-
log(message);
|
|
445
|
-
}
|
|
446
|
-
};
|
|
447
|
-
var uploadArchive = async (deploymentId, logs, config2) => {
|
|
448
|
-
debug("Starting uploadArchive");
|
|
449
|
-
addLog("Creating archive...", "INFO", logs);
|
|
450
|
-
const archivePath = path4.join(process.env.TEMP || os.tmpdir(), `project-${Date.now()}.zip`);
|
|
451
|
-
debug(`Archive path: ${archivePath}`);
|
|
452
|
-
if (fs4.existsSync(archivePath)) {
|
|
453
|
-
debug(`Archive already exists, removing: ${archivePath}`);
|
|
454
|
-
fs4.unlinkSync(archivePath);
|
|
455
|
-
}
|
|
456
|
-
debug("Creating zip archive");
|
|
457
|
-
await createZipArchive(config2.userArchive?.rootOverwrite || process.cwd(), archivePath, {
|
|
458
|
-
whitelist: config2.userArchive?.fileWhitelist,
|
|
459
|
-
useIgnoreFiles: false,
|
|
460
|
-
useManagedIgnore: false
|
|
461
|
-
});
|
|
462
|
-
const fileStats = fs4.statSync(archivePath);
|
|
463
|
-
const fileSize = fileStats.size;
|
|
464
|
-
debug(`Archive created, size: ${fileSize} bytes (${formatFileSize(fileSize)})`);
|
|
465
|
-
addLog(`Archive created (${formatFileSize(fileSize)})`, "INFO", logs);
|
|
466
|
-
addLog("Uploading archive...", "INFO", logs);
|
|
467
|
-
debug(`Starting multipart upload for deployment: ${deploymentId}`);
|
|
468
|
-
const uploadId = await startMultipartUpload(deploymentId);
|
|
469
|
-
debug(`Upload ID: ${uploadId}`);
|
|
470
|
-
debug("Uploading archive...");
|
|
471
|
-
const fileChunkSize = 10 * 1024 * 1024;
|
|
472
|
-
const numChunks = Math.ceil(fileSize / fileChunkSize);
|
|
473
|
-
debug(`File size: ${fileSize}, chunk size: ${fileChunkSize}, number of chunks: ${numChunks}`);
|
|
474
|
-
const promises = [];
|
|
475
|
-
let start, end;
|
|
476
|
-
for (let index = 1; index < numChunks + 1; index++) {
|
|
477
|
-
start = (index - 1) * fileChunkSize;
|
|
478
|
-
end = index * fileChunkSize;
|
|
479
|
-
debug(`Processing chunk ${index}/${numChunks}, start: ${start}, end: ${end}`);
|
|
480
|
-
const presignedUrl = await getPresignedUrl(deploymentId, uploadId, index);
|
|
481
|
-
debug("Presigned URL for part", index, presignedUrl);
|
|
482
|
-
const promise = new Promise((resolve, reject) => {
|
|
483
|
-
void (async () => {
|
|
484
|
-
try {
|
|
485
|
-
debug(`Reading chunk ${index} data`);
|
|
486
|
-
const readStream = fs4.createReadStream(archivePath, {
|
|
487
|
-
start,
|
|
488
|
-
end: index < numChunks ? end - 1 : void 0
|
|
489
|
-
});
|
|
490
|
-
const chunks = [];
|
|
491
|
-
readStream.on("data", (chunk) => {
|
|
492
|
-
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
493
|
-
});
|
|
494
|
-
await new Promise((resolveRead) => {
|
|
495
|
-
readStream.on("end", () => resolveRead());
|
|
496
|
-
readStream.on("error", reject);
|
|
497
|
-
});
|
|
498
|
-
const buffer = Buffer.concat(chunks);
|
|
499
|
-
debug(`Uploading chunk ${index} to presigned URL (${buffer.length} bytes)`);
|
|
500
|
-
const response = await fetch(presignedUrl, {
|
|
501
|
-
method: "PUT",
|
|
502
|
-
headers: {
|
|
503
|
-
"Content-Type": "application/zip",
|
|
504
|
-
"Content-Length": String(buffer.length)
|
|
505
|
-
},
|
|
506
|
-
body: buffer
|
|
507
|
-
});
|
|
508
|
-
resolve(response);
|
|
509
|
-
} catch (err) {
|
|
510
|
-
reject(err instanceof Error ? err : new Error(String(err)));
|
|
511
|
-
}
|
|
512
|
-
})();
|
|
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;
|
|
513
1248
|
});
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
1249
|
+
const uploadPart = Effect11.fn("DeploymentApiService.uploadPart")(function* (presignedUrl, data, partNumber) {
|
|
1250
|
+
yield* Effect11.annotateCurrentSpan("partNumber", partNumber);
|
|
1251
|
+
yield* Effect11.annotateCurrentSpan("size", data.length);
|
|
1252
|
+
const response = yield* Effect11.tryPromise({
|
|
1253
|
+
try: () => fetch(presignedUrl, {
|
|
1254
|
+
method: "PUT",
|
|
1255
|
+
headers: {
|
|
1256
|
+
"Content-Type": "application/zip",
|
|
1257
|
+
"Content-Length": String(data.length)
|
|
1258
|
+
},
|
|
1259
|
+
body: data
|
|
1260
|
+
}),
|
|
1261
|
+
catch: (error) => new UploadPartError({
|
|
1262
|
+
message: `Failed to upload part ${partNumber}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1263
|
+
partNumber
|
|
1264
|
+
})
|
|
1265
|
+
});
|
|
1266
|
+
if (!response.ok) {
|
|
1267
|
+
return yield* Effect11.fail(
|
|
1268
|
+
new UploadPartError({
|
|
1269
|
+
message: `Failed to upload part ${partNumber}: ${response.statusText}`,
|
|
1270
|
+
partNumber
|
|
1271
|
+
})
|
|
1272
|
+
);
|
|
527
1273
|
}
|
|
528
|
-
const etag =
|
|
1274
|
+
const etag = response.headers.get("ETag");
|
|
529
1275
|
if (!etag) {
|
|
530
|
-
|
|
531
|
-
|
|
1276
|
+
return yield* Effect11.fail(
|
|
1277
|
+
new UploadPartError({ message: `No ETag received for part ${partNumber}`, partNumber })
|
|
1278
|
+
);
|
|
532
1279
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
1280
|
+
return { partNumber, etag };
|
|
1281
|
+
});
|
|
1282
|
+
const completeUpload = Effect11.fn("DeploymentApiService.completeUpload")(function* (deploymentId, uploadId, parts) {
|
|
1283
|
+
yield* Effect11.annotateCurrentSpan("deploymentId", deploymentId);
|
|
1284
|
+
yield* Effect11.annotateCurrentSpan("partsCount", parts.length);
|
|
1285
|
+
const authHeaders = yield* getAuthHeaders;
|
|
1286
|
+
const response = yield* Effect11.tryPromise({
|
|
1287
|
+
try: () => fetch(`${baseUrl}/deployments/${deploymentId}/pre-signed-url/complete`, {
|
|
1288
|
+
method: "POST",
|
|
1289
|
+
headers: { "Content-Type": "application/json", ...authHeaders },
|
|
1290
|
+
body: JSON.stringify({ uploadId, parts })
|
|
1291
|
+
}),
|
|
1292
|
+
catch: (error) => new UploadCompleteError({
|
|
1293
|
+
message: `Failed to complete upload: ${error instanceof Error ? error.message : String(error)}`
|
|
1294
|
+
})
|
|
537
1295
|
});
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
debug(`Upload error: ${error2.message}`);
|
|
545
|
-
addLog(`Upload error: ${error2.message}`, "ERROR", logs);
|
|
546
|
-
throw error2;
|
|
547
|
-
}
|
|
548
|
-
};
|
|
549
|
-
var createDeployment = async ({ applicationId }) => {
|
|
550
|
-
debug(`Creating deployment for application: ${applicationId}`);
|
|
551
|
-
const res = await fetch(`http://localhost:3000/${applicationId}/deployments`, {
|
|
552
|
-
method: "POST",
|
|
553
|
-
headers: {
|
|
554
|
-
"Content-Type": "application/json"
|
|
555
|
-
},
|
|
556
|
-
body: JSON.stringify({}),
|
|
557
|
-
duplex: "half"
|
|
558
|
-
});
|
|
559
|
-
if (!res.ok) {
|
|
560
|
-
const errorText = await res.text();
|
|
561
|
-
debug(`Failed to create deployment: ${errorText}`);
|
|
562
|
-
throw new Error("Failed to create deployment: " + errorText);
|
|
563
|
-
}
|
|
564
|
-
const deployment = await res.json();
|
|
565
|
-
debug(`Deployment created with ID: ${deployment.id}`);
|
|
566
|
-
return deployment.id;
|
|
567
|
-
};
|
|
568
|
-
var startMultipartUpload = async (deploymentId) => {
|
|
569
|
-
debug(`Starting multipart upload for deployment: ${deploymentId}`);
|
|
570
|
-
const res = await fetch(`http://localhost:3000/deployments/${deploymentId}/pre-signed-url/start`, {
|
|
571
|
-
method: "GET",
|
|
572
|
-
headers: {
|
|
573
|
-
"Content-Type": "application/json"
|
|
574
|
-
}
|
|
575
|
-
});
|
|
576
|
-
if (!res.ok) {
|
|
577
|
-
const errorText = await res.text();
|
|
578
|
-
debug(`Failed to start multipart upload: ${errorText}`);
|
|
579
|
-
throw new Error("Failed to start multipart upload: " + errorText);
|
|
580
|
-
}
|
|
581
|
-
const upload = await res.json();
|
|
582
|
-
debug(`Multipart upload started with ID: ${upload.uploadId}`);
|
|
583
|
-
return upload.uploadId;
|
|
584
|
-
};
|
|
585
|
-
var getPresignedUrl = async (deploymentId, uploadId, partNumber) => {
|
|
586
|
-
debug(`Getting presigned URL for deployment: ${deploymentId}, upload: ${uploadId}, part: ${partNumber}`);
|
|
587
|
-
const res = await fetch(
|
|
588
|
-
`http://localhost:3000/deployments/${deploymentId}/pre-signed-url/part?uploadId=${uploadId}&partNumber=${partNumber}`,
|
|
589
|
-
{
|
|
590
|
-
method: "GET",
|
|
591
|
-
headers: {
|
|
592
|
-
"Content-Type": "application/json"
|
|
1296
|
+
if (!response.ok) {
|
|
1297
|
+
const body = yield* Effect11.tryPromise({
|
|
1298
|
+
try: () => response.text(),
|
|
1299
|
+
catch: () => new UploadCompleteError({ message: "Failed to read upload complete error" })
|
|
1300
|
+
});
|
|
1301
|
+
return yield* Effect11.fail(new UploadCompleteError({ message: `Failed to complete upload: ${body}` }));
|
|
593
1302
|
}
|
|
594
|
-
|
|
1303
|
+
yield* Effect11.log("Upload completed successfully");
|
|
1304
|
+
});
|
|
1305
|
+
const getDeploymentStatus = Effect11.fn("DeploymentApiService.getDeploymentStatus")(function* (deploymentId) {
|
|
1306
|
+
const authHeaders = yield* getAuthHeaders;
|
|
1307
|
+
const response = yield* Effect11.tryPromise({
|
|
1308
|
+
try: () => fetch(`${baseUrl}/deployments/${deploymentId}`, {
|
|
1309
|
+
method: "GET",
|
|
1310
|
+
headers: { "Content-Type": "application/json", ...authHeaders }
|
|
1311
|
+
}),
|
|
1312
|
+
catch: (error) => new DeploymentStatusError({
|
|
1313
|
+
message: `Failed to get deployment status: ${error instanceof Error ? error.message : String(error)}`
|
|
1314
|
+
})
|
|
1315
|
+
});
|
|
1316
|
+
if (!response.ok) {
|
|
1317
|
+
const body = yield* Effect11.tryPromise({
|
|
1318
|
+
try: () => response.text(),
|
|
1319
|
+
catch: () => new DeploymentStatusError({ message: "Failed to read deployment status error" })
|
|
1320
|
+
});
|
|
1321
|
+
return yield* Effect11.fail(new DeploymentStatusError({ message: `Failed to get deployment status: ${body}` }));
|
|
1322
|
+
}
|
|
1323
|
+
const json = yield* Effect11.tryPromise({
|
|
1324
|
+
try: () => response.json(),
|
|
1325
|
+
catch: () => new DeploymentStatusError({ message: "Failed to parse deployment status response" })
|
|
1326
|
+
});
|
|
1327
|
+
const decoded = yield* Schema5.decodeUnknown(DeploymentStatusResponse)(json).pipe(
|
|
1328
|
+
Effect11.mapError(() => new DeploymentStatusError({ message: "Invalid deployment status response schema" }))
|
|
1329
|
+
);
|
|
1330
|
+
return decoded.status;
|
|
1331
|
+
});
|
|
1332
|
+
const getLogs = Effect11.fn("DeploymentApiService.getLogs")(function* (deploymentId, options) {
|
|
1333
|
+
const authHeaders = yield* getAuthHeaders;
|
|
1334
|
+
const response = yield* Effect11.tryPromise({
|
|
1335
|
+
try: () => fetch(`${baseUrl}/deployments/${deploymentId}/logs${objectToQueryString(options)}`, {
|
|
1336
|
+
method: "GET",
|
|
1337
|
+
headers: { "Content-Type": "application/json", ...authHeaders }
|
|
1338
|
+
}),
|
|
1339
|
+
catch: (error) => new DeploymentLogsFetchError({
|
|
1340
|
+
message: `Failed to get logs: ${error instanceof Error ? error.message : String(error)}`
|
|
1341
|
+
})
|
|
1342
|
+
});
|
|
1343
|
+
if (!response.ok) {
|
|
1344
|
+
const body = yield* Effect11.tryPromise({
|
|
1345
|
+
try: () => response.text(),
|
|
1346
|
+
catch: () => new DeploymentLogsFetchError({ message: "Failed to read logs error" })
|
|
1347
|
+
});
|
|
1348
|
+
return yield* Effect11.fail(new DeploymentLogsFetchError({ message: `Failed to get logs: ${body}` }));
|
|
1349
|
+
}
|
|
1350
|
+
const json = yield* Effect11.tryPromise({
|
|
1351
|
+
try: () => response.json(),
|
|
1352
|
+
catch: () => new DeploymentLogsFetchError({ message: "Failed to parse logs response" })
|
|
1353
|
+
});
|
|
1354
|
+
const decoded = yield* Schema5.decodeUnknown(DeploymentLogPageResponse)(json).pipe(
|
|
1355
|
+
Effect11.mapError(() => new DeploymentLogsFetchError({ message: "Invalid logs response schema" }))
|
|
1356
|
+
);
|
|
1357
|
+
return decoded;
|
|
1358
|
+
});
|
|
1359
|
+
return {
|
|
1360
|
+
createDeployment,
|
|
1361
|
+
startMultipartUpload,
|
|
1362
|
+
getPresignedUrl,
|
|
1363
|
+
uploadPart,
|
|
1364
|
+
completeUpload,
|
|
1365
|
+
getDeploymentStatus,
|
|
1366
|
+
getLogs
|
|
1367
|
+
};
|
|
1368
|
+
})
|
|
1369
|
+
}) {
|
|
1370
|
+
};
|
|
1371
|
+
|
|
1372
|
+
// src/commands/platform/deploy/index.ts
|
|
1373
|
+
var deployCommand = Command5.make(
|
|
1374
|
+
"deploy",
|
|
1375
|
+
{},
|
|
1376
|
+
() => Effect12.gen(function* () {
|
|
1377
|
+
const projectConfig = yield* ProjectConfigService;
|
|
1378
|
+
const deploymentApi = yield* DeploymentApiService;
|
|
1379
|
+
const archiveService = yield* ArchiveService;
|
|
1380
|
+
const pathService = yield* Path5.Path;
|
|
1381
|
+
const cwd = process.cwd();
|
|
1382
|
+
yield* Effect12.log("Starting deploy command");
|
|
1383
|
+
const { config } = yield* projectConfig.resolve(cwd);
|
|
1384
|
+
yield* Console5.log("Creating deployment...");
|
|
1385
|
+
const deploymentId = yield* deploymentApi.createDeployment("test");
|
|
1386
|
+
yield* Console5.log(`Deployment ID: ${deploymentId}`);
|
|
1387
|
+
yield* Console5.log("Creating archive...");
|
|
1388
|
+
const archivePath = pathService.join(os2.tmpdir(), `project-${Date.now()}.zip`);
|
|
1389
|
+
const archive = yield* archiveService.createZipArchive(config.userArchive?.rootOverwrite || cwd, archivePath, {
|
|
1390
|
+
whitelist: config.userArchive?.fileWhitelist,
|
|
1391
|
+
excludeFiles: config.excludeFiles,
|
|
1392
|
+
useIgnoreFiles: false,
|
|
1393
|
+
useManagedIgnore: false
|
|
1394
|
+
});
|
|
1395
|
+
yield* Console5.log(`Archive created (${formatFileSize(archive.size)})`);
|
|
1396
|
+
yield* Console5.log("Uploading archive...");
|
|
1397
|
+
yield* uploadArchive(deploymentId, archivePath, archive.size);
|
|
1398
|
+
yield* Console5.log("Upload complete.");
|
|
1399
|
+
yield* Console5.log("The deployment pipeline is now being provisioned. This may take a few seconds.");
|
|
1400
|
+
yield* pollDeployment(deploymentId);
|
|
1401
|
+
}).pipe(
|
|
1402
|
+
Effect12.catchTags({
|
|
1403
|
+
ConfigNotFoundError: (err) => Console5.error(err.message).pipe(Effect12.andThen(Effect12.fail(err))),
|
|
1404
|
+
ConfigParseError: (err) => Console5.error(`Config parse error: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
|
|
1405
|
+
ConfigValidationError: (err) => Effect12.gen(function* () {
|
|
1406
|
+
for (const e of err.errors) {
|
|
1407
|
+
yield* Console5.error(e);
|
|
1408
|
+
}
|
|
1409
|
+
yield* Console5.error(err.message);
|
|
1410
|
+
}).pipe(Effect12.andThen(Effect12.fail(err))),
|
|
1411
|
+
DeploymentCreateError: (err) => Console5.error(`Deployment creation failed: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
|
|
1412
|
+
ArchiveCreateError: (err) => Console5.error(`Archive creation failed: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
|
|
1413
|
+
UploadStartError: (err) => Console5.error(`Upload failed: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
|
|
1414
|
+
UploadPartError: (err) => Console5.error(`Upload failed: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
|
|
1415
|
+
UploadCompleteError: (err) => Console5.error(`Upload failed: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
|
|
1416
|
+
PresignedUrlError: (err) => Console5.error(`Upload failed: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
|
|
1417
|
+
DeploymentAuthError: (err) => Console5.error(`Authentication failed: ${err.message}`).pipe(Effect12.andThen(Effect12.fail(err))),
|
|
1418
|
+
DeploymentFailedError: (err) => Console5.error(err.message).pipe(Effect12.andThen(Effect12.fail(err)))
|
|
1419
|
+
})
|
|
1420
|
+
)
|
|
1421
|
+
);
|
|
1422
|
+
var uploadArchive = (deploymentId, archivePath, fileSize) => Effect12.gen(function* () {
|
|
1423
|
+
const deploymentApi = yield* DeploymentApiService;
|
|
1424
|
+
const fs = yield* FileSystem5.FileSystem;
|
|
1425
|
+
const uploadId = yield* deploymentApi.startMultipartUpload(deploymentId);
|
|
1426
|
+
const FILE_CHUNK_SIZE = 10 * 1024 * 1024;
|
|
1427
|
+
const numChunks = Math.ceil(fileSize / FILE_CHUNK_SIZE);
|
|
1428
|
+
yield* Effect12.log("Starting multipart upload", { uploadId, numChunks, fileSize });
|
|
1429
|
+
const parts = yield* Effect12.all(
|
|
1430
|
+
Array.from({ length: numChunks }, (_, i) => {
|
|
1431
|
+
const partNumber = i + 1;
|
|
1432
|
+
const start = i * FILE_CHUNK_SIZE;
|
|
1433
|
+
const bytesToRead = partNumber < numChunks ? FILE_CHUNK_SIZE : fileSize - start;
|
|
1434
|
+
return Effect12.gen(function* () {
|
|
1435
|
+
const presignedUrl = yield* deploymentApi.getPresignedUrl(deploymentId, uploadId, partNumber);
|
|
1436
|
+
const chunkData = yield* Stream.runCollect(fs.stream(archivePath, { offset: start, bytesToRead })).pipe(
|
|
1437
|
+
Effect12.map((chunks) => {
|
|
1438
|
+
const arrays = Array.from(chunks);
|
|
1439
|
+
return Buffer.concat(arrays);
|
|
1440
|
+
}),
|
|
1441
|
+
Effect12.mapError(
|
|
1442
|
+
(error) => new UploadPartError({
|
|
1443
|
+
message: `Failed to read chunk ${partNumber}: ${String(error)}`,
|
|
1444
|
+
partNumber
|
|
1445
|
+
})
|
|
1446
|
+
)
|
|
1447
|
+
);
|
|
1448
|
+
yield* Effect12.log(`Uploading part ${partNumber}/${numChunks}`, { size: chunkData.length });
|
|
1449
|
+
return yield* deploymentApi.uploadPart(presignedUrl, chunkData, partNumber);
|
|
1450
|
+
});
|
|
1451
|
+
}),
|
|
1452
|
+
{ concurrency: 3 }
|
|
595
1453
|
);
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
1454
|
+
yield* deploymentApi.completeUpload(deploymentId, uploadId, parts);
|
|
1455
|
+
});
|
|
1456
|
+
var pollDeployment = (deploymentId) => Effect12.gen(function* () {
|
|
1457
|
+
const deploymentApi = yield* DeploymentApiService;
|
|
1458
|
+
let lastStatus = "PENDING";
|
|
1459
|
+
let logOffset = 0;
|
|
1460
|
+
yield* Console5.log("Status: Pending");
|
|
1461
|
+
yield* Effect12.repeat(
|
|
1462
|
+
Effect12.gen(function* () {
|
|
1463
|
+
const newStatus = yield* deploymentApi.getDeploymentStatus(deploymentId).pipe(Effect12.catchTag("DeploymentStatusError", () => Effect12.succeed(lastStatus)));
|
|
1464
|
+
if (newStatus !== lastStatus) {
|
|
1465
|
+
lastStatus = newStatus;
|
|
1466
|
+
const label = newStatus.charAt(0) + newStatus.slice(1).toLowerCase();
|
|
1467
|
+
yield* Console5.log(`Status: ${label}`);
|
|
1468
|
+
}
|
|
1469
|
+
const logsPage = yield* deploymentApi.getLogs(deploymentId, { offset: logOffset, limit: 100 }).pipe(Effect12.catchTag("DeploymentLogsFetchError", () => Effect12.succeed(null)));
|
|
1470
|
+
if (logsPage) {
|
|
1471
|
+
for (const logEntry of logsPage.items) {
|
|
1472
|
+
logOffset++;
|
|
1473
|
+
yield* printLog(logEntry);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
if (newStatus === "ACTIVE") {
|
|
1477
|
+
yield* Console5.log(`Deployed to https://${deploymentId}.gigadrivedev.com`);
|
|
1478
|
+
return yield* Effect12.fail("done");
|
|
1479
|
+
}
|
|
1480
|
+
if (newStatus === "FAILED") {
|
|
1481
|
+
return yield* Effect12.fail(
|
|
1482
|
+
new DeploymentFailedError({
|
|
1483
|
+
message: "The deployment failed. Please check the logs for more information."
|
|
1484
|
+
})
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
615
1487
|
}),
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
});
|
|
633
|
-
if (!res.ok) {
|
|
634
|
-
const errorText = await res.text();
|
|
635
|
-
debug(`Failed to get deployment status: ${errorText}`);
|
|
636
|
-
throw new Error("Failed to get deployment status: " + errorText);
|
|
637
|
-
}
|
|
638
|
-
const deployment = await res.json();
|
|
639
|
-
debug(`Deployment status: ${deployment.status}`);
|
|
640
|
-
return deployment.status;
|
|
641
|
-
};
|
|
642
|
-
var getLogs = async (deploymentId, options) => {
|
|
643
|
-
debug(`Getting logs for deployment: ${deploymentId}, options:`, options ?? "No options");
|
|
644
|
-
const res = await fetch(`http://localhost:3000/deployments/${deploymentId}/logs${objectToQueryString(options)}`, {
|
|
645
|
-
method: "GET",
|
|
646
|
-
headers: {
|
|
647
|
-
"Content-Type": "application/json"
|
|
648
|
-
}
|
|
649
|
-
});
|
|
650
|
-
if (!res.ok) {
|
|
651
|
-
const errorText = await res.text();
|
|
652
|
-
debug(`Failed to get logs: ${errorText}`);
|
|
653
|
-
throw new Error("Failed to get logs: " + errorText);
|
|
1488
|
+
Schedule.addDelay(Schedule.forever, () => Duration.seconds(1))
|
|
1489
|
+
).pipe(
|
|
1490
|
+
Effect12.catchAll((err) => {
|
|
1491
|
+
if (err === "done") return Effect12.void;
|
|
1492
|
+
return Effect12.fail(err);
|
|
1493
|
+
})
|
|
1494
|
+
);
|
|
1495
|
+
});
|
|
1496
|
+
var printLog = (logEntry) => {
|
|
1497
|
+
switch (logEntry.type) {
|
|
1498
|
+
case "ERROR":
|
|
1499
|
+
return Console5.error(logEntry.message);
|
|
1500
|
+
case "WARN":
|
|
1501
|
+
return Console5.warn(logEntry.message);
|
|
1502
|
+
default:
|
|
1503
|
+
return Console5.log(logEntry.message);
|
|
654
1504
|
}
|
|
655
|
-
const logs = await res.json();
|
|
656
|
-
debug(`Got ${logs.items.length} logs, total: ${logs.totalItems}`);
|
|
657
|
-
return logs;
|
|
658
1505
|
};
|
|
659
1506
|
|
|
660
1507
|
// src/commands/platform/index.ts
|
|
661
|
-
var
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
1508
|
+
var platformBase = Command6.make("platform", {}, () => Effect13.void);
|
|
1509
|
+
var platformCommand = platformBase.pipe(Command6.withSubcommands([deployCommand]));
|
|
1510
|
+
|
|
1511
|
+
// src/commands/whoami/index.ts
|
|
1512
|
+
import { Command as Command7 } from "@effect/cli";
|
|
1513
|
+
import { Console as Console6, Effect as Effect14 } from "effect";
|
|
1514
|
+
var whoamiCommand = Command7.make(
|
|
1515
|
+
"whoami",
|
|
1516
|
+
{},
|
|
1517
|
+
() => Effect14.gen(function* () {
|
|
1518
|
+
const auth = yield* AuthService;
|
|
1519
|
+
const info = yield* auth.getUserInfo;
|
|
1520
|
+
yield* Console6.log(JSON.stringify(info, null, 2));
|
|
1521
|
+
}).pipe(
|
|
1522
|
+
Effect14.catchTags({
|
|
1523
|
+
NotAuthenticatedError: () => Console6.log('You are not logged in. Run "gigadrive login" to authenticate.'),
|
|
1524
|
+
UserInfoFetchError: (err) => Console6.error(`Failed to retrieve user info: ${err.message}`),
|
|
1525
|
+
OAuthDiscoveryError: (err) => Console6.error(`Failed to retrieve user info: ${err.message}`)
|
|
1526
|
+
})
|
|
1527
|
+
)
|
|
1528
|
+
);
|
|
665
1529
|
|
|
666
1530
|
// src/index.ts
|
|
667
|
-
var
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
let exitCode = 0;
|
|
675
|
-
const program = new Command();
|
|
676
|
-
program.option("-v, --verbose", "Enable verbose logging", false);
|
|
677
|
-
program.hook("preSubcommand", (thisCommand) => {
|
|
678
|
-
setVerbose(thisCommand.opts().verbose === true);
|
|
679
|
-
if (thisCommand.opts().verbose) {
|
|
680
|
-
log("Using verbose mode");
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
program.hook("preAction", (thisCommand) => {
|
|
684
|
-
setVerbose(thisCommand.opts().verbose === true);
|
|
685
|
-
});
|
|
686
|
-
platform(program);
|
|
687
|
-
debug2(program);
|
|
688
|
-
build(program);
|
|
689
|
-
try {
|
|
690
|
-
await program.parseAsync(process.argv);
|
|
691
|
-
} catch (err) {
|
|
692
|
-
error(err);
|
|
693
|
-
exitCode = 1;
|
|
694
|
-
}
|
|
695
|
-
return exitCode;
|
|
696
|
-
};
|
|
697
|
-
var handleRejection = (err) => {
|
|
698
|
-
if (err) {
|
|
699
|
-
if (err instanceof Error) {
|
|
700
|
-
handleUnexpected(err);
|
|
701
|
-
} else {
|
|
702
|
-
console.error(`An unexpected rejection occurred
|
|
703
|
-
${JSON.stringify(err)}`);
|
|
704
|
-
}
|
|
705
|
-
} else {
|
|
706
|
-
console.error("An unexpected empty rejection occurred");
|
|
707
|
-
}
|
|
708
|
-
process.exit(1);
|
|
709
|
-
};
|
|
710
|
-
var handleUnexpected = (error2) => {
|
|
711
|
-
const { message } = error2;
|
|
712
|
-
console.error(`An unexpected error occurred
|
|
713
|
-
${message}`);
|
|
714
|
-
process.exit(1);
|
|
715
|
-
};
|
|
716
|
-
process.on("unhandledRejection", handleRejection);
|
|
717
|
-
process.on("uncaughtException", handleUnexpected);
|
|
718
|
-
process.on("exit", () => {
|
|
719
|
-
const terminal = process.stderr.isTTY ? process.stderr : process.stdout.isTTY ? process.stdout : void 0;
|
|
720
|
-
terminal?.write("\x1B[?25h");
|
|
1531
|
+
var gigadrive = Command8.make("gigadrive", {}, () => Effect15.void).pipe(
|
|
1532
|
+
Command8.withSubcommands([loginCommand, whoamiCommand, buildCommand, debugCommand, platformCommand])
|
|
1533
|
+
);
|
|
1534
|
+
var cli = Command8.run(gigadrive, {
|
|
1535
|
+
name: "Gigadrive CLI",
|
|
1536
|
+
version: "2.0.0"
|
|
721
1537
|
});
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1538
|
+
var BaseServices = Layer.mergeAll(
|
|
1539
|
+
OAuthConfigService.Default,
|
|
1540
|
+
AuthStorageService.Default,
|
|
1541
|
+
PackageManagerService.Default,
|
|
1542
|
+
ArchiveService.Default,
|
|
1543
|
+
NetworkConfigLive2,
|
|
1544
|
+
ProjectConfigService.Default
|
|
1545
|
+
).pipe(Layer.provideMerge(AuthService.Default));
|
|
1546
|
+
var DeploymentApiLive = Layer.provide(DeploymentApiService.Default, BaseServices);
|
|
1547
|
+
var ServicesLive = Layer.mergeAll(BaseServices, DeploymentApiLive);
|
|
1548
|
+
var AppLive = Layer.mergeAll(ServicesLive, Logger.minimumLogLevel(LogLevel.Info)).pipe(
|
|
1549
|
+
Layer.provideMerge(NodeContext.layer)
|
|
1550
|
+
);
|
|
1551
|
+
NodeRuntime.runMain(Effect15.provide(cli(process.argv), AppLive));
|
|
725
1552
|
//# sourceMappingURL=index.mjs.map
|