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