swadhinbiswas

[email protected]
2026-01-22 00:32:04 +0600 committed NaN years ago
0c9b918
commit 0c9b918
46 files changed
+4069 -1093
Showing 46 changed files+4069-1093
.astro/settings.jsonCHANGELOG.mdcli/bin/och.tscli/src/commands/auth.tscli/src/commands/stack/index.tscli/src/lib/config.tspackage-lock.jsonpackage.jsonscripts/ssh-server.tssrc/components/dashboard/StackCard.astrosrc/components/dashboard/UserAvatar.tsxsrc/db/schema/merge-queue.tssrc/lib/analysis.tssrc/lib/auth-basic.tssrc/lib/env-validation.tssrc/lib/git-server.tssrc/lib/git-storage.tssrc/lib/git.tssrc/lib/queue-worker.tssrc/lib/storage.tssrc/middleware.tssrc/pages/[owner]/[repo].git/[...path].tssrc/pages/[owner]/[repo].git/index.tssrc/pages/[owner]/[repo]/commit/[sha].astrosrc/pages/[owner]/[repo]/pulls/[number].astrosrc/pages/[owner]/[repo]/raw/[branch]/[...path].tssrc/pages/[owner]/[repo]/tree/[...ref].astrosrc/pages/[owner]/[repo]/tree/[branch]/[...path].astrosrc/pages/api/cron/gc.tssrc/pages/api/repos/[owner]/[repo]/branches/[branch].tssrc/pages/api/repos/[owner]/[repo]/branches/index.tssrc/pages/api/repos/[owner]/[repo]/compare/index.tssrc/pages/api/repos/[owner]/[repo]/git/push.tssrc/pages/api/repos/[owner]/[repo]/pulls/[number]/merge.tssrc/pages/api/repos/[owner]/[repo]/pulls/index.tssrc/pages/api/repos/[owner]/[repo]/queue/join.tssrc/pages/api/repos/[owner]/[repo]/security/scan.tssrc/pages/api/repos/[repoId]/commit.tssrc/pages/api/repos/index.tssrc/pages/api/stacks/index.tssrc/pages/api/stacks/user.tssrc/pages/dashboard.astrosrc/pages/git/[owner]/[repo]/git-receive-pack.tssrc/pages/git/[owner]/[repo]/git-upload-pack.tssrc/pages/git/[owner]/[repo]/info/refs.tssrc/pages/stacks.astro
▼.astro/settings.json
+1-1
1
1
{
2
2
"_variables": {
3
- "lastUpdateCheck": 1768929857510
3
+ "lastUpdateCheck": 1769015135620
4
4
}
5
5
}
6
6
▼CHANGELOG.md
+47-0
32
32
- Added permission check to prevent unauthorized users from enumerating members of private repositories.
33
33
- **CLI**: Fixed incorrect binary path in `cli/package.json` that prevented global execution.
34
34
35
+## [0.2.0] - 2026-01-21
36
+
37
+### Added
38
+- **Cloudflare R2 Storage Integration**:
39
+ - Full support for Cloudflare R2 as a repository storage backend.
40
+ - Configure with `STORAGE_TYPE=s3` and R2-specific environment variables.
41
+ - Repositories are automatically uploaded to R2 on creation.
42
+ - Repositories are automatically deleted from R2 on deletion.
43
+ - Git clone/push operations now work with cloud-stored repositories.
44
+- **UI Modernization**:
45
+ - Added global toast notification system using `sonner` package.
46
+ - Created `src/components/ui/sonner.tsx` Shadcn UI wrapper.
47
+ - Added `<Toaster />` component to `BaseLayout.astro` for app-wide notifications.
48
+ - New `RepoSettings.tsx` React component for repository settings management.
49
+ - Replaced native `alert()` and `confirm()` with Shadcn `AlertDialog` components.
50
+- **New Pages & Features**:
51
+ - `src/pages/insights.astro` - Platform-wide analytics dashboard.
52
+ - `src/pages/merge-queue.astro` - Merge queue management interface.
53
+ - `src/pages/settings/ai-review-rules.astro` - AI code review configuration.
54
+ - `src/pages/settings/automations.astro` - Workflow automation settings.
55
+ - `src/pages/settings/notification-preferences.astro` - User notification settings.
56
+ - `src/pages/api/user/notification-preferences.ts` - New API endpoint.
57
+
58
+### Changed
59
+- **Database**: Migrated all schema files to use `pgTable` with PostgreSQL types.
60
+- **API Routes**: Standardized logging and error handling across 60+ API files.
61
+- **Drizzle ORM**: Automated TypeScript casting fixes across the codebase.
62
+- **Repository Creation**: Made cloud storage sync asynchronous for faster API responses.
63
+- **Git Operations**: Refactored `initRepository` to separate cloud storage concerns from git initialization.
64
+
65
+### Fixed
66
+- **Cloud Storage**: Fixed `S3StorageAdapter` incorrectly prepending base path to S3 keys (caused `NoSuchKey` errors).
67
+- **Cloud Storage**: Fixed `uploadRepoToStorage` uploading 0 files due to duplicate `initRepoInStorage` calls.
68
+- **Cloud Storage**: Fixed `triggerRepoWorkflows` failing with "directory does not exist" for R2-backed repos.
69
+- **Cloud Storage**: Fixed repository deletion not cleaning up objects from R2 storage.
70
+- **Git Backend**: Fixed `git clone` and `git push` operations for cloud-stored repositories.
71
+- **Git Operations**: Added `resolveRepoPath` for resolving logical cloud paths to local temp directories.
72
+- **Admin Pages**: Fixed TypeScript errors in `admin/settings.astro` and `admin/users.astro`.
73
+- **Tree Pages**: Fixed layout issues by replacing missing `RepositoryLayout.astro`.
74
+- **API**: Fixed Drizzle ORM "count" property errors in admin pages.
75
+- **Stacks API**: Fixed TypeScript type limitations.
76
+
77
+### Security
78
+- **Postgres Support**: Full PostgreSQL support with `node-postgres` driver.
79
+- **Environment Variables**: Improved `isCloudStorage()` detection using `STORAGE_TYPE` env directly.
80
+
35
81
## [0.1.0] - 2026-01-14
36
82
### Added
37
83
- Initial release of OpenCodeHub.
38
84
- Basic repo management, issue tracking, and PR workflow.
85
+
39
86
▼cli/bin/och.ts
+97-29
147
147
// Shorthand Commands
148
148
// ================================
149
149
150
+// Stacking Commands (Graphite-inspired)
151
+program
152
+ .command("stack")
153
+ .description("Manage stacked pull requests")
154
+ .command("submit")
155
+ .description("Submit the current branch as a stacked PR")
156
+ .option("-m, --message <message>", "PR Title/Description")
157
+ .action(async (options) => {
158
+ console.log("🚀 Submitting stack...");
159
+
160
+ // 1. Git Push
161
+ try {
162
+ const { execSync } = require('child_process');
163
+ const branch = execSync('git iconv -futf-8 -tutf-8 branch --show-current').toString().trim();
164
+ console.log(`Pushing branch ${branch}...`);
165
+ execSync(`git push origin ${branch}`); // Assumes 'origin' is set
166
+ } catch (e: any) {
167
+ console.error("Failed to push branch:", e.message);
168
+ return;
169
+ }
170
+
171
+ // 2. Identify PR Context
172
+ // We need to know who we are (config) and where we are (remote)
173
+ // For MVP, just log instructions
174
+ console.log(`✅ Branch pushed. To create/update stack, visit OpenCodeHub or use 'och pr create' (coming soon).`);
175
+ });
176
+
177
+program
178
+ .command("queue")
179
+ .description("Manage merge queue")
180
+ .command("join")
181
+ .description("Add the current PR to the merge queue")
182
+ .action(async () => {
183
+ console.log("⏳ Adding to merge queue...");
184
+
185
+ try {
186
+ const { execSync } = require('child_process');
187
+ const branch = execSync('git branch --show-current').toString().trim();
188
+ // Parse remote to get owner/repo
189
+ const remoteUrl = execSync('git remote get-url origin').toString().trim();
190
+ // Regex to match http://host/owner/repo.git or git@...
191
+ // MVP: Assume standard format
192
+ // This requires robust parsing.
193
+
194
+ console.log(`Detected branch: ${branch}`);
195
+ console.log(`Use 'cho queue join <pr-id>' implementation pending API client config.`);
196
+
197
+ // Real implementation requires Authenticated API Client
198
+ // which involves reading ~/.och/config.json
199
+ // I will stub this with a TODO for the user to configure auth first.
200
+
201
+ console.log("⚠️ Please configure CLI authentication first.");
202
+ } catch (e: any) {
203
+ console.error("Error:", e.message);
204
+ }
205
+ });
206
+
150
207
// och push (shorthand for repo push)
151
208
program
152
209
.command("push")
223
280
.alias("st")
224
281
.description("Show current stack and branch status")
225
282
.action(async () => {
226
- const branches = await git.branchLocal();
227
- const currentBranch = branches.current;
228
- const stackBranches = branches.all.filter(b => b.startsWith("stack/"));
229
-
230
- console.log(chalk.bold("\n📊 Status\n"));
231
- console.log(`Current branch: ${chalk.cyan(currentBranch)}`);
232
-
233
- if (stackBranches.length > 0) {
234
- console.log(`\nStack branches: ${stackBranches.length}`);
235
- for (const branch of stackBranches) {
236
- const isCurrent = branch === currentBranch;
237
- const marker = isCurrent ? chalk.yellow("●") : chalk.dim("○");
238
- console.log(` ${marker} ${isCurrent ? chalk.bold(branch) : branch}`);
283
+ try {
284
+ const isRepo = await git.checkIsRepo();
285
+ if (!isRepo) {
286
+ console.log(chalk.yellow("Not a git repository. Run this command inside a git repository."));
287
+ return;
239
288
}
240
- }
241
289
242
- // Show git status
243
- const status = await git.status();
244
- if (status.modified.length > 0 || status.not_added.length > 0 || status.staged.length > 0) {
245
- console.log(chalk.bold("\nChanges:"));
246
- if (status.staged.length > 0) {
247
- console.log(chalk.green(` Staged: ${status.staged.length} files`));
248
- }
249
- if (status.modified.length > 0) {
250
- console.log(chalk.yellow(` Modified: ${status.modified.length} files`));
290
+ const branches = await git.branchLocal();
291
+ const currentBranch = branches.current;
292
+ const stackBranches = branches.all.filter(b => b.startsWith("stack/"));
293
+
294
+ console.log(chalk.bold("\n📊 Status\n"));
295
+ console.log(`Current branch: ${chalk.cyan(currentBranch)}`);
296
+
297
+ if (stackBranches.length > 0) {
298
+ console.log(`\nStack branches: ${stackBranches.length}`);
299
+ for (const branch of stackBranches) {
300
+ const isCurrent = branch === currentBranch;
301
+ const marker = isCurrent ? chalk.yellow("●") : chalk.dim("○");
302
+ console.log(` ${marker} ${isCurrent ? chalk.bold(branch) : branch}`);
303
+ }
251
304
}
252
- if (status.not_added.length > 0) {
253
- console.log(chalk.red(` Untracked: ${status.not_added.length} files`));
305
+
306
+ // Show git status
307
+ const status = await git.status();
308
+ if (status.modified.length > 0 || status.not_added.length > 0 || status.staged.length > 0) {
309
+ console.log(chalk.bold("\nChanges:"));
310
+ if (status.staged.length > 0) {
311
+ console.log(chalk.green(` Staged: ${status.staged.length} files`));
312
+ }
313
+ if (status.modified.length > 0) {
314
+ console.log(chalk.yellow(` Modified: ${status.modified.length} files`));
315
+ }
316
+ if (status.not_added.length > 0) {
317
+ console.log(chalk.red(` Untracked: ${status.not_added.length} files`));
318
+ }
319
+ } else {
320
+ console.log(chalk.dim("\nWorking tree clean"));
254
321
}
255
- } else {
256
- console.log(chalk.dim("\nWorking tree clean"));
257
- }
258
322
259
- console.log("");
323
+ console.log("");
324
+ } catch (error) {
325
+ console.error(chalk.red("\nError checking status:"));
326
+ console.error(chalk.dim(error instanceof Error ? error.message : "Unknown error"));
327
+ }
260
328
});
261
329
262
330
// och whoami (alias for auth status)
263
331
▼cli/src/commands/auth.ts
+1-0
106
106
saveConfig({
107
107
serverUrl,
108
108
token,
109
+ username,
109
110
});
110
111
111
112
console.log(chalk.green("\n✓ Logged in to " + serverUrl));
112
113
▼cli/src/commands/stack/index.ts
+26-4
81
81
}
82
82
83
83
// Parse owner/repo from remote URL
84
+ // Handles both SSH (git@host:owner/repo) and HTTP (http://host/owner/repo.git) formats
84
85
const remoteUrl = origin.refs.push;
85
- const match = remoteUrl.match(/[:/]([^/]+)\/([^/.]+)/);
86
+ // Match the last two path segments before .git (owner/repo)
87
+ const match = remoteUrl.match(/\/([^/]+)\/([^/]+?)(?:\.git)?$/);
86
88
87
89
if (!match) {
88
90
spinner.fail("Could not parse repository from remote URL");
91
93
92
94
const [, owner, repoName] = match;
93
95
94
- // Push all stack branches
96
+ // Push all stack branches with authentication
95
97
spinner.text = "Pushing branches...";
96
98
const pushedBranches: string[] = [];
97
99
100
+ // Build authenticated URL for push (format: http://user:token@host/owner/repo.git)
101
+ const urlParts = new URL(config.serverUrl);
102
+ const pushUrl = `${urlParts.protocol}//${config.username || owner}:${config.token}@${urlParts.host}/${owner}/${repoName}.git`;
103
+
104
+ console.log(chalk.dim(`\n Remote URL: ${origin.refs.push}`));
105
+ console.log(chalk.dim(` Parsed: owner=${owner}, repo=${repoName}`));
106
+ console.log(chalk.dim(` Push URL: ${pushUrl.replace(config.token, '[TOKEN]')}`));
107
+
98
108
for (const branch of stackBranches) {
99
109
try {
100
- await git.push(["-u", "origin", branch, "--force-with-lease"]);
110
+ // Use the authenticated URL for pushing
111
+ console.log(chalk.dim(` Pushing ${branch} to authenticated URL...`));
112
+ await git.push([pushUrl, branch, "--force-with-lease"]);
101
113
pushedBranches.push(branch);
114
+ console.log(chalk.green(` ✓ Pushed ${branch}`));
102
115
} catch (e) {
103
- console.log(chalk.yellow(`\n Warning: Could not push ${branch}`));
116
+ console.log(chalk.yellow(`\n First push attempt failed: ${e instanceof Error ? e.message : 'Unknown error'}`));
117
+ // Try regular push if authenticated push fails
118
+ try {
119
+ console.log(chalk.dim(` Trying fallback push to origin...`));
120
+ await git.push(["-u", "origin", branch, "--force-with-lease"]);
121
+ pushedBranches.push(branch);
122
+ console.log(chalk.green(` ✓ Pushed ${branch} via origin`));
123
+ } catch (e2) {
124
+ console.log(chalk.red(`\n ✗ Could not push ${branch}: ${e2 instanceof Error ? e2.message : 'Unknown error'}`));
125
+ }
104
126
}
105
127
}
106
128
107
129
▼cli/src/lib/config.ts
+5-0
8
8
interface OchConfig {
9
9
serverUrl: string;
10
10
token: string;
11
+ username?: string;
11
12
defaultBranch?: string;
12
13
}
13
14
24
25
return {
25
26
serverUrl: config.get("serverUrl"),
26
27
token: config.get("token"),
28
+ username: config.get("username"),
27
29
defaultBranch: config.get("defaultBranch"),
28
30
};
29
31
}
35
37
if (updates.token !== undefined) {
36
38
config.set("token", updates.token);
37
39
}
40
+ if (updates.username !== undefined) {
41
+ config.set("username", updates.username);
42
+ }
38
43
if (updates.defaultBranch !== undefined) {
39
44
config.set("defaultBranch", updates.defaultBranch);
40
45
}
41
46
▼package-lock.json
+1539-508
14
14
"@astrojs/tailwind": "^5.1.0",
15
15
"@astrojs/vercel": "^7.7.0",
16
16
"@aws-sdk/client-s3": "^3.948.0",
17
+ "@aws-sdk/lib-storage": "^3.972.0",
17
18
"@aws-sdk/s3-request-presigner": "^3.948.0",
18
19
"@libsql/client": "^0.15.15",
19
20
"@monaco-editor/react": "^4.6.0",
1246
1247
},
1247
1248
"node_modules/@aws-crypto/crc32": {
1248
1249
"version": "5.2.0",
1250
+ "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz",
1251
+ "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==",
1249
1252
"license": "Apache-2.0",
1250
1253
"dependencies": {
1251
1254
"@aws-crypto/util": "^5.2.0",
1258
1261
},
1259
1262
"node_modules/@aws-crypto/crc32c": {
1260
1263
"version": "5.2.0",
1264
+ "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz",
1265
+ "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==",
1261
1266
"license": "Apache-2.0",
1262
1267
"dependencies": {
1263
1268
"@aws-crypto/util": "^5.2.0",
1415
1420
}
1416
1421
},
1417
1422
"node_modules/@aws-sdk/client-s3": {
1418
- "version": "3.948.0",
1423
+ "version": "3.972.0",
1424
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.972.0.tgz",
1425
+ "integrity": "sha512-ghpDQtjZvbhbnHWymq/V5TL8NppdAGF2THAxYRRBLCJ5JRlq71T24NdovAzvzYaGdH7HtcRkgErBRsFT1gtq4g==",
1419
1426
"license": "Apache-2.0",
1420
1427
"dependencies": {
1421
1428
"@aws-crypto/sha1-browser": "5.2.0",
1422
1429
"@aws-crypto/sha256-browser": "5.2.0",
1423
1430
"@aws-crypto/sha256-js": "5.2.0",
1424
- "@aws-sdk/core": "3.947.0",
1425
- "@aws-sdk/credential-provider-node": "3.948.0",
1426
- "@aws-sdk/middleware-bucket-endpoint": "3.936.0",
1427
- "@aws-sdk/middleware-expect-continue": "3.936.0",
1428
- "@aws-sdk/middleware-flexible-checksums": "3.947.0",
1429
- "@aws-sdk/middleware-host-header": "3.936.0",
1430
- "@aws-sdk/middleware-location-constraint": "3.936.0",
1431
- "@aws-sdk/middleware-logger": "3.936.0",
1432
- "@aws-sdk/middleware-recursion-detection": "3.948.0",
1433
- "@aws-sdk/middleware-sdk-s3": "3.947.0",
1434
- "@aws-sdk/middleware-ssec": "3.936.0",
1435
- "@aws-sdk/middleware-user-agent": "3.947.0",
1436
- "@aws-sdk/region-config-resolver": "3.936.0",
1437
- "@aws-sdk/signature-v4-multi-region": "3.947.0",
1438
- "@aws-sdk/types": "3.936.0",
1439
- "@aws-sdk/util-endpoints": "3.936.0",
1440
- "@aws-sdk/util-user-agent-browser": "3.936.0",
1441
- "@aws-sdk/util-user-agent-node": "3.947.0",
1442
- "@smithy/config-resolver": "^4.4.3",
1443
- "@smithy/core": "^3.18.7",
1444
- "@smithy/eventstream-serde-browser": "^4.2.5",
1445
- "@smithy/eventstream-serde-config-resolver": "^4.3.5",
1446
- "@smithy/eventstream-serde-node": "^4.2.5",
1447
- "@smithy/fetch-http-handler": "^5.3.6",
1448
- "@smithy/hash-blob-browser": "^4.2.6",
1449
- "@smithy/hash-node": "^4.2.5",
1450
- "@smithy/hash-stream-node": "^4.2.5",
1451
- "@smithy/invalid-dependency": "^4.2.5",
1452
- "@smithy/md5-js": "^4.2.5",
1453
- "@smithy/middleware-content-length": "^4.2.5",
1454
- "@smithy/middleware-endpoint": "^4.3.14",
1455
- "@smithy/middleware-retry": "^4.4.14",
1456
- "@smithy/middleware-serde": "^4.2.6",
1457
- "@smithy/middleware-stack": "^4.2.5",
1458
- "@smithy/node-config-provider": "^4.3.5",
1459
- "@smithy/node-http-handler": "^4.4.5",
1460
- "@smithy/protocol-http": "^5.3.5",
1461
- "@smithy/smithy-client": "^4.9.10",
1462
- "@smithy/types": "^4.9.0",
1463
- "@smithy/url-parser": "^4.2.5",
1431
+ "@aws-sdk/core": "3.972.0",
1432
+ "@aws-sdk/credential-provider-node": "3.972.0",
1433
+ "@aws-sdk/middleware-bucket-endpoint": "3.972.0",
1434
+ "@aws-sdk/middleware-expect-continue": "3.972.0",
1435
+ "@aws-sdk/middleware-flexible-checksums": "3.972.0",
1436
+ "@aws-sdk/middleware-host-header": "3.972.0",
1437
+ "@aws-sdk/middleware-location-constraint": "3.972.0",
1438
+ "@aws-sdk/middleware-logger": "3.972.0",
1439
+ "@aws-sdk/middleware-recursion-detection": "3.972.0",
1440
+ "@aws-sdk/middleware-sdk-s3": "3.972.0",
1441
+ "@aws-sdk/middleware-ssec": "3.972.0",
1442
+ "@aws-sdk/middleware-user-agent": "3.972.0",
1443
+ "@aws-sdk/region-config-resolver": "3.972.0",
1444
+ "@aws-sdk/signature-v4-multi-region": "3.972.0",
1445
+ "@aws-sdk/types": "3.972.0",
1446
+ "@aws-sdk/util-endpoints": "3.972.0",
1447
+ "@aws-sdk/util-user-agent-browser": "3.972.0",
1448
+ "@aws-sdk/util-user-agent-node": "3.972.0",
1449
+ "@smithy/config-resolver": "^4.4.6",
1450
+ "@smithy/core": "^3.20.6",
1451
+ "@smithy/eventstream-serde-browser": "^4.2.8",
1452
+ "@smithy/eventstream-serde-config-resolver": "^4.3.8",
1453
+ "@smithy/eventstream-serde-node": "^4.2.8",
1454
+ "@smithy/fetch-http-handler": "^5.3.9",
1455
+ "@smithy/hash-blob-browser": "^4.2.9",
1456
+ "@smithy/hash-node": "^4.2.8",
1457
+ "@smithy/hash-stream-node": "^4.2.8",
1458
+ "@smithy/invalid-dependency": "^4.2.8",
1459
+ "@smithy/md5-js": "^4.2.8",
1460
+ "@smithy/middleware-content-length": "^4.2.8",
1461
+ "@smithy/middleware-endpoint": "^4.4.7",
1462
+ "@smithy/middleware-retry": "^4.4.23",
1463
+ "@smithy/middleware-serde": "^4.2.9",
1464
+ "@smithy/middleware-stack": "^4.2.8",
1465
+ "@smithy/node-config-provider": "^4.3.8",
1466
+ "@smithy/node-http-handler": "^4.4.8",
1467
+ "@smithy/protocol-http": "^5.3.8",
1468
+ "@smithy/smithy-client": "^4.10.8",
1469
+ "@smithy/types": "^4.12.0",
1470
+ "@smithy/url-parser": "^4.2.8",
1464
1471
"@smithy/util-base64": "^4.3.0",
1465
1472
"@smithy/util-body-length-browser": "^4.2.0",
1466
1473
"@smithy/util-body-length-node": "^4.2.1",
1467
- "@smithy/util-defaults-mode-browser": "^4.3.13",
1468
- "@smithy/util-defaults-mode-node": "^4.2.16",
1469
- "@smithy/util-endpoints": "^3.2.5",
1470
- "@smithy/util-middleware": "^4.2.5",
1471
- "@smithy/util-retry": "^4.2.5",
1472
- "@smithy/util-stream": "^4.5.6",
1474
+ "@smithy/util-defaults-mode-browser": "^4.3.22",
1475
+ "@smithy/util-defaults-mode-node": "^4.2.25",
1476
+ "@smithy/util-endpoints": "^3.2.8",
1477
+ "@smithy/util-middleware": "^4.2.8",
1478
+ "@smithy/util-retry": "^4.2.8",
1479
+ "@smithy/util-stream": "^4.5.10",
1473
1480
"@smithy/util-utf8": "^4.2.0",
1474
- "@smithy/util-waiter": "^4.2.5",
1481
+ "@smithy/util-waiter": "^4.2.8",
1475
1482
"tslib": "^2.6.2"
1476
1483
},
1477
1484
"engines": {
1478
- "node": ">=18.0.0"
1485
+ "node": ">=20.0.0"
1486
+ }
1487
+ },
1488
+ "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/core": {
1489
+ "version": "3.972.0",
1490
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz",
1491
+ "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==",
1492
+ "license": "Apache-2.0",
1493
+ "dependencies": {
1494
+ "@aws-sdk/types": "3.972.0",
1495
+ "@aws-sdk/xml-builder": "3.972.0",
1496
+ "@smithy/core": "^3.20.6",
1497
+ "@smithy/node-config-provider": "^4.3.8",
1498
+ "@smithy/property-provider": "^4.2.8",
1499
+ "@smithy/protocol-http": "^5.3.8",
1500
+ "@smithy/signature-v4": "^5.3.8",
1501
+ "@smithy/smithy-client": "^4.10.8",
1502
+ "@smithy/types": "^4.12.0",
1503
+ "@smithy/util-base64": "^4.3.0",
1504
+ "@smithy/util-middleware": "^4.2.8",
1505
+ "@smithy/util-utf8": "^4.2.0",
1506
+ "tslib": "^2.6.2"
1507
+ },
1508
+ "engines": {
1509
+ "node": ">=20.0.0"
1510
+ }
1511
+ },
1512
+ "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/middleware-sdk-s3": {
1513
+ "version": "3.972.0",
1514
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.0.tgz",
1515
+ "integrity": "sha512-0bcKFXWx+NZ7tIlOo7KjQ+O2rydiHdIQahrq+fN6k9Osky29v17guy68urUKfhTobR6iY6KvxkroFWaFtTgS5w==",
1516
+ "license": "Apache-2.0",
1517
+ "dependencies": {
1518
+ "@aws-sdk/core": "3.972.0",
1519
+ "@aws-sdk/types": "3.972.0",
1520
+ "@aws-sdk/util-arn-parser": "3.972.0",
1521
+ "@smithy/core": "^3.20.6",
1522
+ "@smithy/node-config-provider": "^4.3.8",
1523
+ "@smithy/protocol-http": "^5.3.8",
1524
+ "@smithy/signature-v4": "^5.3.8",
1525
+ "@smithy/smithy-client": "^4.10.8",
1526
+ "@smithy/types": "^4.12.0",
1527
+ "@smithy/util-config-provider": "^4.2.0",
1528
+ "@smithy/util-middleware": "^4.2.8",
1529
+ "@smithy/util-stream": "^4.5.10",
1530
+ "@smithy/util-utf8": "^4.2.0",
1531
+ "tslib": "^2.6.2"
1532
+ },
1533
+ "engines": {
1534
+ "node": ">=20.0.0"
1535
+ }
1536
+ },
1537
+ "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/signature-v4-multi-region": {
1538
+ "version": "3.972.0",
1539
+ "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.972.0.tgz",
1540
+ "integrity": "sha512-2udiRijmjpN81Pvajje4TsjbXDZNP6K9bYUanBYH8hXa/tZG5qfGCySD+TyX0sgDxCQmEDMg3LaQdfjNHBDEgQ==",
1541
+ "license": "Apache-2.0",
1542
+ "dependencies": {
1543
+ "@aws-sdk/middleware-sdk-s3": "3.972.0",
1544
+ "@aws-sdk/types": "3.972.0",
1545
+ "@smithy/protocol-http": "^5.3.8",
1546
+ "@smithy/signature-v4": "^5.3.8",
1547
+ "@smithy/types": "^4.12.0",
1548
+ "tslib": "^2.6.2"
1549
+ },
1550
+ "engines": {
1551
+ "node": ">=20.0.0"
1552
+ }
1553
+ },
1554
+ "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/types": {
1555
+ "version": "3.972.0",
1556
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
1557
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
1558
+ "license": "Apache-2.0",
1559
+ "dependencies": {
1560
+ "@smithy/types": "^4.12.0",
1561
+ "tslib": "^2.6.2"
1562
+ },
1563
+ "engines": {
1564
+ "node": ">=20.0.0"
1565
+ }
1566
+ },
1567
+ "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/util-arn-parser": {
1568
+ "version": "3.972.0",
1569
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.0.tgz",
1570
+ "integrity": "sha512-RM5Mmo/KJ593iMSrALlHEOcc9YOIyOsDmS5x2NLOMdEmzv1o00fcpAkCQ02IGu1eFneBFT7uX0Mpag0HI+Cz2g==",
1571
+ "license": "Apache-2.0",
1572
+ "dependencies": {
1573
+ "tslib": "^2.6.2"
1574
+ },
1575
+ "engines": {
1576
+ "node": ">=20.0.0"
1577
+ }
1578
+ },
1579
+ "node_modules/@aws-sdk/client-s3/node_modules/@aws-sdk/xml-builder": {
1580
+ "version": "3.972.0",
1581
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz",
1582
+ "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==",
1583
+ "license": "Apache-2.0",
1584
+ "dependencies": {
1585
+ "@smithy/types": "^4.12.0",
1586
+ "fast-xml-parser": "5.2.5",
1587
+ "tslib": "^2.6.2"
1588
+ },
1589
+ "engines": {
1590
+ "node": ">=20.0.0"
1479
1591
}
1480
1592
},
1481
1593
"node_modules/@aws-sdk/client-sesv2": {
2040
2152
}
2041
2153
},
2042
2154
"node_modules/@aws-sdk/client-sso": {
2043
- "version": "3.948.0",
2155
+ "version": "3.972.0",
2156
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.972.0.tgz",
2157
+ "integrity": "sha512-5qw6qLiRE4SUiz0hWy878dSR13tSVhbTWhsvFT8mGHe37NRRiaobm5MA2sWD0deRAuO98djSiV+dhWXa1xIFNw==",
2044
2158
"license": "Apache-2.0",
2045
2159
"dependencies": {
2046
2160
"@aws-crypto/sha256-browser": "5.2.0",
2047
2161
"@aws-crypto/sha256-js": "5.2.0",
2048
- "@aws-sdk/core": "3.947.0",
2049
- "@aws-sdk/middleware-host-header": "3.936.0",
2050
- "@aws-sdk/middleware-logger": "3.936.0",
2051
- "@aws-sdk/middleware-recursion-detection": "3.948.0",
2052
- "@aws-sdk/middleware-user-agent": "3.947.0",
2053
- "@aws-sdk/region-config-resolver": "3.936.0",
2054
- "@aws-sdk/types": "3.936.0",
2055
- "@aws-sdk/util-endpoints": "3.936.0",
2056
- "@aws-sdk/util-user-agent-browser": "3.936.0",
2057
- "@aws-sdk/util-user-agent-node": "3.947.0",
2058
- "@smithy/config-resolver": "^4.4.3",
2059
- "@smithy/core": "^3.18.7",
2060
- "@smithy/fetch-http-handler": "^5.3.6",
2061
- "@smithy/hash-node": "^4.2.5",
2062
- "@smithy/invalid-dependency": "^4.2.5",
2063
- "@smithy/middleware-content-length": "^4.2.5",
2064
- "@smithy/middleware-endpoint": "^4.3.14",
2065
- "@smithy/middleware-retry": "^4.4.14",
2066
- "@smithy/middleware-serde": "^4.2.6",
2067
- "@smithy/middleware-stack": "^4.2.5",
2068
- "@smithy/node-config-provider": "^4.3.5",
2069
- "@smithy/node-http-handler": "^4.4.5",
2070
- "@smithy/protocol-http": "^5.3.5",
2071
- "@smithy/smithy-client": "^4.9.10",
2072
- "@smithy/types": "^4.9.0",
2073
- "@smithy/url-parser": "^4.2.5",
2162
+ "@aws-sdk/core": "3.972.0",
2163
+ "@aws-sdk/middleware-host-header": "3.972.0",
2164
+ "@aws-sdk/middleware-logger": "3.972.0",
2165
+ "@aws-sdk/middleware-recursion-detection": "3.972.0",
2166
+ "@aws-sdk/middleware-user-agent": "3.972.0",
2167
+ "@aws-sdk/region-config-resolver": "3.972.0",
2168
+ "@aws-sdk/types": "3.972.0",
2169
+ "@aws-sdk/util-endpoints": "3.972.0",
2170
+ "@aws-sdk/util-user-agent-browser": "3.972.0",
2171
+ "@aws-sdk/util-user-agent-node": "3.972.0",
2172
+ "@smithy/config-resolver": "^4.4.6",
2173
+ "@smithy/core": "^3.20.6",
2174
+ "@smithy/fetch-http-handler": "^5.3.9",
2175
+ "@smithy/hash-node": "^4.2.8",
2176
+ "@smithy/invalid-dependency": "^4.2.8",
2177
+ "@smithy/middleware-content-length": "^4.2.8",
2178
+ "@smithy/middleware-endpoint": "^4.4.7",
2179
+ "@smithy/middleware-retry": "^4.4.23",
2180
+ "@smithy/middleware-serde": "^4.2.9",
2181
+ "@smithy/middleware-stack": "^4.2.8",
2182
+ "@smithy/node-config-provider": "^4.3.8",
2183
+ "@smithy/node-http-handler": "^4.4.8",
2184
+ "@smithy/protocol-http": "^5.3.8",
2185
+ "@smithy/smithy-client": "^4.10.8",
2186
+ "@smithy/types": "^4.12.0",
2187
+ "@smithy/url-parser": "^4.2.8",
2074
2188
"@smithy/util-base64": "^4.3.0",
2075
2189
"@smithy/util-body-length-browser": "^4.2.0",
2076
2190
"@smithy/util-body-length-node": "^4.2.1",
2077
- "@smithy/util-defaults-mode-browser": "^4.3.13",
2078
- "@smithy/util-defaults-mode-node": "^4.2.16",
2079
- "@smithy/util-endpoints": "^3.2.5",
2080
- "@smithy/util-middleware": "^4.2.5",
2081
- "@smithy/util-retry": "^4.2.5",
2191
+ "@smithy/util-defaults-mode-browser": "^4.3.22",
2192
+ "@smithy/util-defaults-mode-node": "^4.2.25",
2193
+ "@smithy/util-endpoints": "^3.2.8",
2194
+ "@smithy/util-middleware": "^4.2.8",
2195
+ "@smithy/util-retry": "^4.2.8",
2082
2196
"@smithy/util-utf8": "^4.2.0",
2083
2197
"tslib": "^2.6.2"
2084
2198
},
2085
2199
"engines": {
2086
- "node": ">=18.0.0"
2200
+ "node": ">=20.0.0"
2201
+ }
2202
+ },
2203
+ "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/core": {
2204
+ "version": "3.972.0",
2205
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz",
2206
+ "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==",
2207
+ "license": "Apache-2.0",
2208
+ "dependencies": {
2209
+ "@aws-sdk/types": "3.972.0",
2210
+ "@aws-sdk/xml-builder": "3.972.0",
2211
+ "@smithy/core": "^3.20.6",
2212
+ "@smithy/node-config-provider": "^4.3.8",
2213
+ "@smithy/property-provider": "^4.2.8",
2214
+ "@smithy/protocol-http": "^5.3.8",
2215
+ "@smithy/signature-v4": "^5.3.8",
2216
+ "@smithy/smithy-client": "^4.10.8",
2217
+ "@smithy/types": "^4.12.0",
2218
+ "@smithy/util-base64": "^4.3.0",
2219
+ "@smithy/util-middleware": "^4.2.8",
2220
+ "@smithy/util-utf8": "^4.2.0",
2221
+ "tslib": "^2.6.2"
2222
+ },
2223
+ "engines": {
2224
+ "node": ">=20.0.0"
2225
+ }
2226
+ },
2227
+ "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": {
2228
+ "version": "3.972.0",
2229
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
2230
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
2231
+ "license": "Apache-2.0",
2232
+ "dependencies": {
2233
+ "@smithy/types": "^4.12.0",
2234
+ "tslib": "^2.6.2"
2235
+ },
2236
+ "engines": {
2237
+ "node": ">=20.0.0"
2238
+ }
2239
+ },
2240
+ "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/xml-builder": {
2241
+ "version": "3.972.0",
2242
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz",
2243
+ "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==",
2244
+ "license": "Apache-2.0",
2245
+ "dependencies": {
2246
+ "@smithy/types": "^4.12.0",
2247
+ "fast-xml-parser": "5.2.5",
2248
+ "tslib": "^2.6.2"
2249
+ },
2250
+ "engines": {
2251
+ "node": ">=20.0.0"
2087
2252
}
2088
2253
},
2089
2254
"node_modules/@aws-sdk/core": {
2108
2273
"node": ">=18.0.0"
2109
2274
}
2110
2275
},
2111
- "node_modules/@aws-sdk/credential-provider-env": {
2112
- "version": "3.947.0",
2276
+ "node_modules/@aws-sdk/crc64-nvme": {
2277
+ "version": "3.972.0",
2278
+ "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.972.0.tgz",
2279
+ "integrity": "sha512-ThlLhTqX68jvoIVv+pryOdb5coP1cX1/MaTbB9xkGDCbWbsqQcLqzPxuSoW1DCnAAIacmXCWpzUNOB9pv+xXQw==",
2113
2280
"license": "Apache-2.0",
2114
2281
"dependencies": {
2115
- "@aws-sdk/core": "3.947.0",
2116
- "@aws-sdk/types": "3.936.0",
2117
- "@smithy/property-provider": "^4.2.5",
2118
- "@smithy/types": "^4.9.0",
2282
+ "@smithy/types": "^4.12.0",
2119
2283
"tslib": "^2.6.2"
2120
2284
},
2121
2285
"engines": {
2122
- "node": ">=18.0.0"
2286
+ "node": ">=20.0.0"
2123
2287
}
2124
2288
},
2125
- "node_modules/@aws-sdk/credential-provider-http": {
2126
- "version": "3.947.0",
2289
+ "node_modules/@aws-sdk/credential-provider-env": {
2290
+ "version": "3.972.0",
2291
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.0.tgz",
2292
+ "integrity": "sha512-kKHoNv+maHlPQOAhYamhap0PObd16SAb3jwaY0KYgNTiSbeXlbGUZPLioo9oA3wU10zItJzx83ClU7d7h40luA==",
2127
2293
"license": "Apache-2.0",
2128
2294
"dependencies": {
2129
- "@aws-sdk/core": "3.947.0",
2130
- "@aws-sdk/types": "3.936.0",
2131
- "@smithy/fetch-http-handler": "^5.3.6",
2132
- "@smithy/node-http-handler": "^4.4.5",
2133
- "@smithy/property-provider": "^4.2.5",
2134
- "@smithy/protocol-http": "^5.3.5",
2135
- "@smithy/smithy-client": "^4.9.10",
2136
- "@smithy/types": "^4.9.0",
2137
- "@smithy/util-stream": "^4.5.6",
2295
+ "@aws-sdk/core": "3.972.0",
2296
+ "@aws-sdk/types": "3.972.0",
2297
+ "@smithy/property-provider": "^4.2.8",
2298
+ "@smithy/types": "^4.12.0",
2138
2299
"tslib": "^2.6.2"
2139
2300
},
2140
2301
"engines": {
2141
- "node": ">=18.0.0"
2302
+ "node": ">=20.0.0"
2142
2303
}
2143
2304
},
2144
- "node_modules/@aws-sdk/credential-provider-ini": {
2145
- "version": "3.948.0",
2305
+ "node_modules/@aws-sdk/credential-provider-env/node_modules/@aws-sdk/core": {
2306
+ "version": "3.972.0",
2307
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz",
2308
+ "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==",
2146
2309
"license": "Apache-2.0",
2147
2310
"dependencies": {
2148
- "@aws-sdk/core": "3.947.0",
2149
- "@aws-sdk/credential-provider-env": "3.947.0",
2150
- "@aws-sdk/credential-provider-http": "3.947.0",
2151
- "@aws-sdk/credential-provider-login": "3.948.0",
2152
- "@aws-sdk/credential-provider-process": "3.947.0",
2153
- "@aws-sdk/credential-provider-sso": "3.948.0",
2154
- "@aws-sdk/credential-provider-web-identity": "3.948.0",
2155
- "@aws-sdk/nested-clients": "3.948.0",
2156
- "@aws-sdk/types": "3.936.0",
2157
- "@smithy/credential-provider-imds": "^4.2.5",
2158
- "@smithy/property-provider": "^4.2.5",
2159
- "@smithy/shared-ini-file-loader": "^4.4.0",
2160
- "@smithy/types": "^4.9.0",
2311
+ "@aws-sdk/types": "3.972.0",
2312
+ "@aws-sdk/xml-builder": "3.972.0",
2313
+ "@smithy/core": "^3.20.6",
2314
+ "@smithy/node-config-provider": "^4.3.8",
2315
+ "@smithy/property-provider": "^4.2.8",
2316
+ "@smithy/protocol-http": "^5.3.8",
2317
+ "@smithy/signature-v4": "^5.3.8",
2318
+ "@smithy/smithy-client": "^4.10.8",
2319
+ "@smithy/types": "^4.12.0",
2320
+ "@smithy/util-base64": "^4.3.0",
2321
+ "@smithy/util-middleware": "^4.2.8",
2322
+ "@smithy/util-utf8": "^4.2.0",
2161
2323
"tslib": "^2.6.2"
2162
2324
},
2163
2325
"engines": {
2164
- "node": ">=18.0.0"
2326
+ "node": ">=20.0.0"
2165
2327
}
2166
2328
},
2167
- "node_modules/@aws-sdk/credential-provider-login": {
2168
- "version": "3.948.0",
2329
+ "node_modules/@aws-sdk/credential-provider-env/node_modules/@aws-sdk/types": {
2330
+ "version": "3.972.0",
2331
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
2332
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
2169
2333
"license": "Apache-2.0",
2170
2334
"dependencies": {
2171
- "@aws-sdk/core": "3.947.0",
2172
- "@aws-sdk/nested-clients": "3.948.0",
2173
- "@aws-sdk/types": "3.936.0",
2174
- "@smithy/property-provider": "^4.2.5",
2175
- "@smithy/protocol-http": "^5.3.5",
2176
- "@smithy/shared-ini-file-loader": "^4.4.0",
2177
- "@smithy/types": "^4.9.0",
2335
+ "@smithy/types": "^4.12.0",
2178
2336
"tslib": "^2.6.2"
2179
2337
},
2180
2338
"engines": {
2181
- "node": ">=18.0.0"
2339
+ "node": ">=20.0.0"
2182
2340
}
2183
2341
},
2184
- "node_modules/@aws-sdk/credential-provider-node": {
2185
- "version": "3.948.0",
2342
+ "node_modules/@aws-sdk/credential-provider-env/node_modules/@aws-sdk/xml-builder": {
2343
+ "version": "3.972.0",
2344
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz",
2345
+ "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==",
2186
2346
"license": "Apache-2.0",
2187
2347
"dependencies": {
2188
- "@aws-sdk/credential-provider-env": "3.947.0",
2189
- "@aws-sdk/credential-provider-http": "3.947.0",
2190
- "@aws-sdk/credential-provider-ini": "3.948.0",
2191
- "@aws-sdk/credential-provider-process": "3.947.0",
2192
- "@aws-sdk/credential-provider-sso": "3.948.0",
2193
- "@aws-sdk/credential-provider-web-identity": "3.948.0",
2194
- "@aws-sdk/types": "3.936.0",
2195
- "@smithy/credential-provider-imds": "^4.2.5",
2196
- "@smithy/property-provider": "^4.2.5",
2197
- "@smithy/shared-ini-file-loader": "^4.4.0",
2198
- "@smithy/types": "^4.9.0",
2348
+ "@smithy/types": "^4.12.0",
2349
+ "fast-xml-parser": "5.2.5",
2199
2350
"tslib": "^2.6.2"
2200
2351
},
2201
2352
"engines": {
2202
- "node": ">=18.0.0"
2353
+ "node": ">=20.0.0"
2203
2354
}
2204
2355
},
2205
- "node_modules/@aws-sdk/credential-provider-process": {
2206
- "version": "3.947.0",
2207
- "license": "Apache-2.0",
2208
- "dependencies": {
2209
- "@aws-sdk/core": "3.947.0",
2210
- "@aws-sdk/types": "3.936.0",
2211
- "@smithy/property-provider": "^4.2.5",
2212
- "@smithy/shared-ini-file-loader": "^4.4.0",
2213
- "@smithy/types": "^4.9.0",
2356
+ "node_modules/@aws-sdk/credential-provider-http": {
2357
+ "version": "3.972.0",
2358
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.0.tgz",
2359
+ "integrity": "sha512-xzEi81L7I5jGUbpmqEHCe7zZr54hCABdj4H+3LzktHYuovV/oqnvoDdvZpGFR0e/KAw1+PL38NbGrpG30j6qlA==",
2360
+ "license": "Apache-2.0",
2361
+ "dependencies": {
2362
+ "@aws-sdk/core": "3.972.0",
2363
+ "@aws-sdk/types": "3.972.0",
2364
+ "@smithy/fetch-http-handler": "^5.3.9",
2365
+ "@smithy/node-http-handler": "^4.4.8",
2366
+ "@smithy/property-provider": "^4.2.8",
2367
+ "@smithy/protocol-http": "^5.3.8",
2368
+ "@smithy/smithy-client": "^4.10.8",
2369
+ "@smithy/types": "^4.12.0",
2370
+ "@smithy/util-stream": "^4.5.10",
2214
2371
"tslib": "^2.6.2"
2215
2372
},
2216
2373
"engines": {
2217
- "node": ">=18.0.0"
2374
+ "node": ">=20.0.0"
2218
2375
}
2219
2376
},
2220
- "node_modules/@aws-sdk/credential-provider-sso": {
2221
- "version": "3.948.0",
2377
+ "node_modules/@aws-sdk/credential-provider-http/node_modules/@aws-sdk/core": {
2378
+ "version": "3.972.0",
2379
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz",
2380
+ "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==",
2222
2381
"license": "Apache-2.0",
2223
2382
"dependencies": {
2224
- "@aws-sdk/client-sso": "3.948.0",
2225
- "@aws-sdk/core": "3.947.0",
2226
- "@aws-sdk/token-providers": "3.948.0",
2227
- "@aws-sdk/types": "3.936.0",
2228
- "@smithy/property-provider": "^4.2.5",
2229
- "@smithy/shared-ini-file-loader": "^4.4.0",
2230
- "@smithy/types": "^4.9.0",
2383
+ "@aws-sdk/types": "3.972.0",
2384
+ "@aws-sdk/xml-builder": "3.972.0",
2385
+ "@smithy/core": "^3.20.6",
2386
+ "@smithy/node-config-provider": "^4.3.8",
2387
+ "@smithy/property-provider": "^4.2.8",
2388
+ "@smithy/protocol-http": "^5.3.8",
2389
+ "@smithy/signature-v4": "^5.3.8",
2390
+ "@smithy/smithy-client": "^4.10.8",
2391
+ "@smithy/types": "^4.12.0",
2392
+ "@smithy/util-base64": "^4.3.0",
2393
+ "@smithy/util-middleware": "^4.2.8",
2394
+ "@smithy/util-utf8": "^4.2.0",
2231
2395
"tslib": "^2.6.2"
2232
2396
},
2233
2397
"engines": {
2234
- "node": ">=18.0.0"
2398
+ "node": ">=20.0.0"
2235
2399
}
2236
2400
},
2237
- "node_modules/@aws-sdk/credential-provider-web-identity": {
2238
- "version": "3.948.0",
2401
+ "node_modules/@aws-sdk/credential-provider-http/node_modules/@aws-sdk/types": {
2402
+ "version": "3.972.0",
2403
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
2404
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
2239
2405
"license": "Apache-2.0",
2240
2406
"dependencies": {
2241
- "@aws-sdk/core": "3.947.0",
2242
- "@aws-sdk/nested-clients": "3.948.0",
2243
- "@aws-sdk/types": "3.936.0",
2244
- "@smithy/property-provider": "^4.2.5",
2245
- "@smithy/shared-ini-file-loader": "^4.4.0",
2246
- "@smithy/types": "^4.9.0",
2407
+ "@smithy/types": "^4.12.0",
2247
2408
"tslib": "^2.6.2"
2248
2409
},
2249
2410
"engines": {
2250
- "node": ">=18.0.0"
2411
+ "node": ">=20.0.0"
2251
2412
}
2252
2413
},
2253
- "node_modules/@aws-sdk/middleware-bucket-endpoint": {
2254
- "version": "3.936.0",
2414
+ "node_modules/@aws-sdk/credential-provider-http/node_modules/@aws-sdk/xml-builder": {
2415
+ "version": "3.972.0",
2416
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz",
2417
+ "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==",
2255
2418
"license": "Apache-2.0",
2256
2419
"dependencies": {
2257
- "@aws-sdk/types": "3.936.0",
2258
- "@aws-sdk/util-arn-parser": "3.893.0",
2259
- "@smithy/node-config-provider": "^4.3.5",
2260
- "@smithy/protocol-http": "^5.3.5",
2261
- "@smithy/types": "^4.9.0",
2262
- "@smithy/util-config-provider": "^4.2.0",
2420
+ "@smithy/types": "^4.12.0",
2421
+ "fast-xml-parser": "5.2.5",
2263
2422
"tslib": "^2.6.2"
2264
2423
},
2265
2424
"engines": {
2266
- "node": ">=18.0.0"
2425
+ "node": ">=20.0.0"
2267
2426
}
2268
2427
},
2269
- "node_modules/@aws-sdk/middleware-expect-continue": {
2270
- "version": "3.936.0",
2271
- "license": "Apache-2.0",
2272
- "dependencies": {
2273
- "@aws-sdk/types": "3.936.0",
2274
- "@smithy/protocol-http": "^5.3.5",
2275
- "@smithy/types": "^4.9.0",
2428
+ "node_modules/@aws-sdk/credential-provider-ini": {
2429
+ "version": "3.972.0",
2430
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.0.tgz",
2431
+ "integrity": "sha512-ruhAMceUIq2aknFd3jhWxmO0P0Efab5efjyIXOkI9i80g+zDY5VekeSxfqRKStEEJSKSCHDLQuOu0BnAn4Rzew==",
2432
+ "license": "Apache-2.0",
2433
+ "dependencies": {
2434
+ "@aws-sdk/core": "3.972.0",
2435
+ "@aws-sdk/credential-provider-env": "3.972.0",
2436
+ "@aws-sdk/credential-provider-http": "3.972.0",
2437
+ "@aws-sdk/credential-provider-login": "3.972.0",
2438
+ "@aws-sdk/credential-provider-process": "3.972.0",
2439
+ "@aws-sdk/credential-provider-sso": "3.972.0",
2440
+ "@aws-sdk/credential-provider-web-identity": "3.972.0",
2441
+ "@aws-sdk/nested-clients": "3.972.0",
2442
+ "@aws-sdk/types": "3.972.0",
2443
+ "@smithy/credential-provider-imds": "^4.2.8",
2444
+ "@smithy/property-provider": "^4.2.8",
2445
+ "@smithy/shared-ini-file-loader": "^4.4.3",
2446
+ "@smithy/types": "^4.12.0",
2276
2447
"tslib": "^2.6.2"
2277
2448
},
2278
2449
"engines": {
2279
- "node": ">=18.0.0"
2450
+ "node": ">=20.0.0"
2280
2451
}
2281
2452
},
2282
- "node_modules/@aws-sdk/middleware-flexible-checksums": {
2283
- "version": "3.947.0",
2453
+ "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/core": {
2454
+ "version": "3.972.0",
2455
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz",
2456
+ "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==",
2284
2457
"license": "Apache-2.0",
2285
2458
"dependencies": {
2286
- "@aws-crypto/crc32": "5.2.0",
2287
- "@aws-crypto/crc32c": "5.2.0",
2288
- "@aws-crypto/util": "5.2.0",
2289
- "@aws-sdk/core": "3.947.0",
2290
- "@aws-sdk/types": "3.936.0",
2291
- "@smithy/is-array-buffer": "^4.2.0",
2292
- "@smithy/node-config-provider": "^4.3.5",
2293
- "@smithy/protocol-http": "^5.3.5",
2294
- "@smithy/types": "^4.9.0",
2295
- "@smithy/util-middleware": "^4.2.5",
2296
- "@smithy/util-stream": "^4.5.6",
2459
+ "@aws-sdk/types": "3.972.0",
2460
+ "@aws-sdk/xml-builder": "3.972.0",
2461
+ "@smithy/core": "^3.20.6",
2462
+ "@smithy/node-config-provider": "^4.3.8",
2463
+ "@smithy/property-provider": "^4.2.8",
2464
+ "@smithy/protocol-http": "^5.3.8",
2465
+ "@smithy/signature-v4": "^5.3.8",
2466
+ "@smithy/smithy-client": "^4.10.8",
2467
+ "@smithy/types": "^4.12.0",
2468
+ "@smithy/util-base64": "^4.3.0",
2469
+ "@smithy/util-middleware": "^4.2.8",
2297
2470
"@smithy/util-utf8": "^4.2.0",
2298
2471
"tslib": "^2.6.2"
2299
2472
},
2300
2473
"engines": {
2301
- "node": ">=18.0.0"
2474
+ "node": ">=20.0.0"
2302
2475
}
2303
2476
},
2304
- "node_modules/@aws-sdk/middleware-host-header": {
2305
- "version": "3.936.0",
2477
+ "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/types": {
2478
+ "version": "3.972.0",
2479
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
2480
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
2306
2481
"license": "Apache-2.0",
2307
2482
"dependencies": {
2308
- "@aws-sdk/types": "3.936.0",
2309
- "@smithy/protocol-http": "^5.3.5",
2310
- "@smithy/types": "^4.9.0",
2483
+ "@smithy/types": "^4.12.0",
2311
2484
"tslib": "^2.6.2"
2312
2485
},
2313
2486
"engines": {
2314
- "node": ">=18.0.0"
2487
+ "node": ">=20.0.0"
2315
2488
}
2316
2489
},
2317
- "node_modules/@aws-sdk/middleware-location-constraint": {
2318
- "version": "3.936.0",
2490
+ "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/xml-builder": {
2491
+ "version": "3.972.0",
2492
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz",
2493
+ "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==",
2319
2494
"license": "Apache-2.0",
2320
2495
"dependencies": {
2321
- "@aws-sdk/types": "3.936.0",
2322
- "@smithy/types": "^4.9.0",
2496
+ "@smithy/types": "^4.12.0",
2497
+ "fast-xml-parser": "5.2.5",
2323
2498
"tslib": "^2.6.2"
2324
2499
},
2325
2500
"engines": {
2326
- "node": ">=18.0.0"
2501
+ "node": ">=20.0.0"
2327
2502
}
2328
2503
},
2329
- "node_modules/@aws-sdk/middleware-logger": {
2330
- "version": "3.936.0",
2331
- "license": "Apache-2.0",
2332
- "dependencies": {
2333
- "@aws-sdk/types": "3.936.0",
2334
- "@smithy/types": "^4.9.0",
2504
+ "node_modules/@aws-sdk/credential-provider-login": {
2505
+ "version": "3.972.0",
2506
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.0.tgz",
2507
+ "integrity": "sha512-SsrsFJsEYAJHO4N/r2P0aK6o8si6f1lprR+Ej8J731XJqTckSGs/HFHcbxOyW/iKt+LNUvZa59/VlJmjhF4bEQ==",
2508
+ "license": "Apache-2.0",
2509
+ "dependencies": {
2510
+ "@aws-sdk/core": "3.972.0",
2511
+ "@aws-sdk/nested-clients": "3.972.0",
2512
+ "@aws-sdk/types": "3.972.0",
2513
+ "@smithy/property-provider": "^4.2.8",
2514
+ "@smithy/protocol-http": "^5.3.8",
2515
+ "@smithy/shared-ini-file-loader": "^4.4.3",
2516
+ "@smithy/types": "^4.12.0",
2335
2517
"tslib": "^2.6.2"
2336
2518
},
2337
2519
"engines": {
2338
- "node": ">=18.0.0"
2520
+ "node": ">=20.0.0"
2339
2521
}
2340
2522
},
2341
- "node_modules/@aws-sdk/middleware-recursion-detection": {
2342
- "version": "3.948.0",
2523
+ "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/core": {
2524
+ "version": "3.972.0",
2525
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz",
2526
+ "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==",
2343
2527
"license": "Apache-2.0",
2344
2528
"dependencies": {
2345
- "@aws-sdk/types": "3.936.0",
2346
- "@aws/lambda-invoke-store": "^0.2.2",
2347
- "@smithy/protocol-http": "^5.3.5",
2348
- "@smithy/types": "^4.9.0",
2529
+ "@aws-sdk/types": "3.972.0",
2530
+ "@aws-sdk/xml-builder": "3.972.0",
2531
+ "@smithy/core": "^3.20.6",
2532
+ "@smithy/node-config-provider": "^4.3.8",
2533
+ "@smithy/property-provider": "^4.2.8",
2534
+ "@smithy/protocol-http": "^5.3.8",
2535
+ "@smithy/signature-v4": "^5.3.8",
2536
+ "@smithy/smithy-client": "^4.10.8",
2537
+ "@smithy/types": "^4.12.0",
2538
+ "@smithy/util-base64": "^4.3.0",
2539
+ "@smithy/util-middleware": "^4.2.8",
2540
+ "@smithy/util-utf8": "^4.2.0",
2349
2541
"tslib": "^2.6.2"
2350
2542
},
2351
2543
"engines": {
2352
- "node": ">=18.0.0"
2544
+ "node": ">=20.0.0"
2353
2545
}
2354
2546
},
2355
- "node_modules/@aws-sdk/middleware-sdk-s3": {
2356
- "version": "3.947.0",
2547
+ "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/types": {
2548
+ "version": "3.972.0",
2549
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
2550
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
2357
2551
"license": "Apache-2.0",
2358
2552
"dependencies": {
2359
- "@aws-sdk/core": "3.947.0",
2553
+ "@smithy/types": "^4.12.0",
2554
+ "tslib": "^2.6.2"
2555
+ },
2556
+ "engines": {
2557
+ "node": ">=20.0.0"
2558
+ }
2559
+ },
2560
+ "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/xml-builder": {
2561
+ "version": "3.972.0",
2562
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz",
2563
+ "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==",
2564
+ "license": "Apache-2.0",
2565
+ "dependencies": {
2566
+ "@smithy/types": "^4.12.0",
2567
+ "fast-xml-parser": "5.2.5",
2568
+ "tslib": "^2.6.2"
2569
+ },
2570
+ "engines": {
2571
+ "node": ">=20.0.0"
2572
+ }
2573
+ },
2574
+ "node_modules/@aws-sdk/credential-provider-node": {
2575
+ "version": "3.972.0",
2576
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.0.tgz",
2577
+ "integrity": "sha512-wwJDpEGl6+sOygic8QKu0OHVB8SiodqF1fr5jvUlSFfS6tJss/E9vBc2aFjl7zI6KpAIYfIzIgM006lRrZtWCQ==",
2578
+ "license": "Apache-2.0",
2579
+ "dependencies": {
2580
+ "@aws-sdk/credential-provider-env": "3.972.0",
2581
+ "@aws-sdk/credential-provider-http": "3.972.0",
2582
+ "@aws-sdk/credential-provider-ini": "3.972.0",
2583
+ "@aws-sdk/credential-provider-process": "3.972.0",
2584
+ "@aws-sdk/credential-provider-sso": "3.972.0",
2585
+ "@aws-sdk/credential-provider-web-identity": "3.972.0",
2586
+ "@aws-sdk/types": "3.972.0",
2587
+ "@smithy/credential-provider-imds": "^4.2.8",
2588
+ "@smithy/property-provider": "^4.2.8",
2589
+ "@smithy/shared-ini-file-loader": "^4.4.3",
2590
+ "@smithy/types": "^4.12.0",
2591
+ "tslib": "^2.6.2"
2592
+ },
2593
+ "engines": {
2594
+ "node": ">=20.0.0"
2595
+ }
2596
+ },
2597
+ "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/types": {
2598
+ "version": "3.972.0",
2599
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
2600
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
2601
+ "license": "Apache-2.0",
2602
+ "dependencies": {
2603
+ "@smithy/types": "^4.12.0",
2604
+ "tslib": "^2.6.2"
2605
+ },
2606
+ "engines": {
2607
+ "node": ">=20.0.0"
2608
+ }
2609
+ },
2610
+ "node_modules/@aws-sdk/credential-provider-process": {
2611
+ "version": "3.972.0",
2612
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.0.tgz",
2613
+ "integrity": "sha512-nmzYhamLDJ8K+v3zWck79IaKMc350xZnWsf/GeaXO6E3MewSzd3lYkTiMi7lEp3/UwDm9NHfPguoPm+mhlSWQQ==",
2614
+ "license": "Apache-2.0",
2615
+ "dependencies": {
2616
+ "@aws-sdk/core": "3.972.0",
2617
+ "@aws-sdk/types": "3.972.0",
2618
+ "@smithy/property-provider": "^4.2.8",
2619
+ "@smithy/shared-ini-file-loader": "^4.4.3",
2620
+ "@smithy/types": "^4.12.0",
2621
+ "tslib": "^2.6.2"
2622
+ },
2623
+ "engines": {
2624
+ "node": ">=20.0.0"
2625
+ }
2626
+ },
2627
+ "node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/core": {
2628
+ "version": "3.972.0",
2629
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz",
2630
+ "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==",
2631
+ "license": "Apache-2.0",
2632
+ "dependencies": {
2633
+ "@aws-sdk/types": "3.972.0",
2634
+ "@aws-sdk/xml-builder": "3.972.0",
2635
+ "@smithy/core": "^3.20.6",
2636
+ "@smithy/node-config-provider": "^4.3.8",
2637
+ "@smithy/property-provider": "^4.2.8",
2638
+ "@smithy/protocol-http": "^5.3.8",
2639
+ "@smithy/signature-v4": "^5.3.8",
2640
+ "@smithy/smithy-client": "^4.10.8",
2641
+ "@smithy/types": "^4.12.0",
2642
+ "@smithy/util-base64": "^4.3.0",
2643
+ "@smithy/util-middleware": "^4.2.8",
2644
+ "@smithy/util-utf8": "^4.2.0",
2645
+ "tslib": "^2.6.2"
2646
+ },
2647
+ "engines": {
2648
+ "node": ">=20.0.0"
2649
+ }
2650
+ },
2651
+ "node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/types": {
2652
+ "version": "3.972.0",
2653
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
2654
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
2655
+ "license": "Apache-2.0",
2656
+ "dependencies": {
2657
+ "@smithy/types": "^4.12.0",
2658
+ "tslib": "^2.6.2"
2659
+ },
2660
+ "engines": {
2661
+ "node": ">=20.0.0"
2662
+ }
2663
+ },
2664
+ "node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/xml-builder": {
2665
+ "version": "3.972.0",
2666
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz",
2667
+ "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==",
2668
+ "license": "Apache-2.0",
2669
+ "dependencies": {
2670
+ "@smithy/types": "^4.12.0",
2671
+ "fast-xml-parser": "5.2.5",
2672
+ "tslib": "^2.6.2"
2673
+ },
2674
+ "engines": {
2675
+ "node": ">=20.0.0"
2676
+ }
2677
+ },
2678
+ "node_modules/@aws-sdk/credential-provider-sso": {
2679
+ "version": "3.972.0",
2680
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.0.tgz",
2681
+ "integrity": "sha512-6mYyfk1SrMZ15cH9T53yAF4YSnvq4yU1Xlgm3nqV1gZVQzmF5kr4t/F3BU3ygbvzi4uSwWxG3I3TYYS5eMlAyg==",
2682
+ "license": "Apache-2.0",
2683
+ "dependencies": {
2684
+ "@aws-sdk/client-sso": "3.972.0",
2685
+ "@aws-sdk/core": "3.972.0",
2686
+ "@aws-sdk/token-providers": "3.972.0",
2687
+ "@aws-sdk/types": "3.972.0",
2688
+ "@smithy/property-provider": "^4.2.8",
2689
+ "@smithy/shared-ini-file-loader": "^4.4.3",
2690
+ "@smithy/types": "^4.12.0",
2691
+ "tslib": "^2.6.2"
2692
+ },
2693
+ "engines": {
2694
+ "node": ">=20.0.0"
2695
+ }
2696
+ },
2697
+ "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/core": {
2698
+ "version": "3.972.0",
2699
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz",
2700
+ "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==",
2701
+ "license": "Apache-2.0",
2702
+ "dependencies": {
2703
+ "@aws-sdk/types": "3.972.0",
2704
+ "@aws-sdk/xml-builder": "3.972.0",
2705
+ "@smithy/core": "^3.20.6",
2706
+ "@smithy/node-config-provider": "^4.3.8",
2707
+ "@smithy/property-provider": "^4.2.8",
2708
+ "@smithy/protocol-http": "^5.3.8",
2709
+ "@smithy/signature-v4": "^5.3.8",
2710
+ "@smithy/smithy-client": "^4.10.8",
2711
+ "@smithy/types": "^4.12.0",
2712
+ "@smithy/util-base64": "^4.3.0",
2713
+ "@smithy/util-middleware": "^4.2.8",
2714
+ "@smithy/util-utf8": "^4.2.0",
2715
+ "tslib": "^2.6.2"
2716
+ },
2717
+ "engines": {
2718
+ "node": ">=20.0.0"
2719
+ }
2720
+ },
2721
+ "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/types": {
2722
+ "version": "3.972.0",
2723
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
2724
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
2725
+ "license": "Apache-2.0",
2726
+ "dependencies": {
2727
+ "@smithy/types": "^4.12.0",
2728
+ "tslib": "^2.6.2"
2729
+ },
2730
+ "engines": {
2731
+ "node": ">=20.0.0"
2732
+ }
2733
+ },
2734
+ "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/xml-builder": {
2735
+ "version": "3.972.0",
2736
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz",
2737
+ "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==",
2738
+ "license": "Apache-2.0",
2739
+ "dependencies": {
2740
+ "@smithy/types": "^4.12.0",
2741
+ "fast-xml-parser": "5.2.5",
2742
+ "tslib": "^2.6.2"
2743
+ },
2744
+ "engines": {
2745
+ "node": ">=20.0.0"
2746
+ }
2747
+ },
2748
+ "node_modules/@aws-sdk/credential-provider-web-identity": {
2749
+ "version": "3.972.0",
2750
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.0.tgz",
2751
+ "integrity": "sha512-vsJXBGL8H54kz4T6do3p5elATj5d1izVGUXMluRJntm9/I0be/zUYtdd4oDTM2kSUmd4Zhyw3fMQ9lw7CVhd4A==",
2752
+ "license": "Apache-2.0",
2753
+ "dependencies": {
2754
+ "@aws-sdk/core": "3.972.0",
2755
+ "@aws-sdk/nested-clients": "3.972.0",
2756
+ "@aws-sdk/types": "3.972.0",
2757
+ "@smithy/property-provider": "^4.2.8",
2758
+ "@smithy/shared-ini-file-loader": "^4.4.3",
2759
+ "@smithy/types": "^4.12.0",
2760
+ "tslib": "^2.6.2"
2761
+ },
2762
+ "engines": {
2763
+ "node": ">=20.0.0"
2764
+ }
2765
+ },
2766
+ "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/core": {
2767
+ "version": "3.972.0",
2768
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz",
2769
+ "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==",
2770
+ "license": "Apache-2.0",
2771
+ "dependencies": {
2772
+ "@aws-sdk/types": "3.972.0",
2773
+ "@aws-sdk/xml-builder": "3.972.0",
2774
+ "@smithy/core": "^3.20.6",
2775
+ "@smithy/node-config-provider": "^4.3.8",
2776
+ "@smithy/property-provider": "^4.2.8",
2777
+ "@smithy/protocol-http": "^5.3.8",
2778
+ "@smithy/signature-v4": "^5.3.8",
2779
+ "@smithy/smithy-client": "^4.10.8",
2780
+ "@smithy/types": "^4.12.0",
2781
+ "@smithy/util-base64": "^4.3.0",
2782
+ "@smithy/util-middleware": "^4.2.8",
2783
+ "@smithy/util-utf8": "^4.2.0",
2784
+ "tslib": "^2.6.2"
2785
+ },
2786
+ "engines": {
2787
+ "node": ">=20.0.0"
2788
+ }
2789
+ },
2790
+ "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/types": {
2791
+ "version": "3.972.0",
2792
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
2793
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
2794
+ "license": "Apache-2.0",
2795
+ "dependencies": {
2796
+ "@smithy/types": "^4.12.0",
2797
+ "tslib": "^2.6.2"
2798
+ },
2799
+ "engines": {
2800
+ "node": ">=20.0.0"
2801
+ }
2802
+ },
2803
+ "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/xml-builder": {
2804
+ "version": "3.972.0",
2805
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz",
2806
+ "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==",
2807
+ "license": "Apache-2.0",
2808
+ "dependencies": {
2809
+ "@smithy/types": "^4.12.0",
2810
+ "fast-xml-parser": "5.2.5",
2811
+ "tslib": "^2.6.2"
2812
+ },
2813
+ "engines": {
2814
+ "node": ">=20.0.0"
2815
+ }
2816
+ },
2817
+ "node_modules/@aws-sdk/lib-storage": {
2818
+ "version": "3.972.0",
2819
+ "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.972.0.tgz",
2820
+ "integrity": "sha512-4xNJ1B4zKzvwEedNuhJRoLmrNCfHorwBFvZjjR/bFSWxxCK0Mh5NSarB4sgeycVyZjCwOGFTFHxk4RtXi9JksA==",
2821
+ "license": "Apache-2.0",
2822
+ "dependencies": {
2823
+ "@smithy/abort-controller": "^4.2.8",
2824
+ "@smithy/middleware-endpoint": "^4.4.7",
2825
+ "@smithy/smithy-client": "^4.10.8",
2826
+ "buffer": "5.6.0",
2827
+ "events": "3.3.0",
2828
+ "stream-browserify": "3.0.0",
2829
+ "tslib": "^2.6.2"
2830
+ },
2831
+ "engines": {
2832
+ "node": ">=20.0.0"
2833
+ },
2834
+ "peerDependencies": {
2835
+ "@aws-sdk/client-s3": "3.972.0"
2836
+ }
2837
+ },
2838
+ "node_modules/@aws-sdk/lib-storage/node_modules/buffer": {
2839
+ "version": "5.6.0",
2840
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
2841
+ "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
2842
+ "license": "MIT",
2843
+ "dependencies": {
2844
+ "base64-js": "^1.0.2",
2845
+ "ieee754": "^1.1.4"
2846
+ }
2847
+ },
2848
+ "node_modules/@aws-sdk/middleware-bucket-endpoint": {
2849
+ "version": "3.972.0",
2850
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.972.0.tgz",
2851
+ "integrity": "sha512-IrIjAehc3PrseAGfk2ldtAf+N0BAnNHR1DCZIDh9IAcFrTVWC3Fi9KJdtabrxcY3Onpt/8opOco4EIEAWgMz7A==",
2852
+ "license": "Apache-2.0",
2853
+ "dependencies": {
2854
+ "@aws-sdk/types": "3.972.0",
2855
+ "@aws-sdk/util-arn-parser": "3.972.0",
2856
+ "@smithy/node-config-provider": "^4.3.8",
2857
+ "@smithy/protocol-http": "^5.3.8",
2858
+ "@smithy/types": "^4.12.0",
2859
+ "@smithy/util-config-provider": "^4.2.0",
2860
+ "tslib": "^2.6.2"
2861
+ },
2862
+ "engines": {
2863
+ "node": ">=20.0.0"
2864
+ }
2865
+ },
2866
+ "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@aws-sdk/types": {
2867
+ "version": "3.972.0",
2868
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
2869
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
2870
+ "license": "Apache-2.0",
2871
+ "dependencies": {
2872
+ "@smithy/types": "^4.12.0",
2873
+ "tslib": "^2.6.2"
2874
+ },
2875
+ "engines": {
2876
+ "node": ">=20.0.0"
2877
+ }
2878
+ },
2879
+ "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/@aws-sdk/util-arn-parser": {
2880
+ "version": "3.972.0",
2881
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.0.tgz",
2882
+ "integrity": "sha512-RM5Mmo/KJ593iMSrALlHEOcc9YOIyOsDmS5x2NLOMdEmzv1o00fcpAkCQ02IGu1eFneBFT7uX0Mpag0HI+Cz2g==",
2883
+ "license": "Apache-2.0",
2884
+ "dependencies": {
2885
+ "tslib": "^2.6.2"
2886
+ },
2887
+ "engines": {
2888
+ "node": ">=20.0.0"
2889
+ }
2890
+ },
2891
+ "node_modules/@aws-sdk/middleware-expect-continue": {
2892
+ "version": "3.972.0",
2893
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.972.0.tgz",
2894
+ "integrity": "sha512-xyhDoY0qse8MvQC4RZCpT5WoIQ4/kwqv71Dh1s3mdXjL789Z4a6L/khBTSXECR5+egSZ960AInj3aR+CrezDRQ==",
2895
+ "license": "Apache-2.0",
2896
+ "dependencies": {
2897
+ "@aws-sdk/types": "3.972.0",
2898
+ "@smithy/protocol-http": "^5.3.8",
2899
+ "@smithy/types": "^4.12.0",
2900
+ "tslib": "^2.6.2"
2901
+ },
2902
+ "engines": {
2903
+ "node": ">=20.0.0"
2904
+ }
2905
+ },
2906
+ "node_modules/@aws-sdk/middleware-expect-continue/node_modules/@aws-sdk/types": {
2907
+ "version": "3.972.0",
2908
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
2909
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
2910
+ "license": "Apache-2.0",
2911
+ "dependencies": {
2912
+ "@smithy/types": "^4.12.0",
2913
+ "tslib": "^2.6.2"
2914
+ },
2915
+ "engines": {
2916
+ "node": ">=20.0.0"
2917
+ }
2918
+ },
2919
+ "node_modules/@aws-sdk/middleware-flexible-checksums": {
2920
+ "version": "3.972.0",
2921
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.972.0.tgz",
2922
+ "integrity": "sha512-zxK0ezmT7fLEPJ650S8QBc4rGDq5+5rdsLnnuZ6hPaZE4/+QtUoTw+gSDETyiWodNcRuz2ZWnqi17K+7nKtSRg==",
2923
+ "license": "Apache-2.0",
2924
+ "dependencies": {
2925
+ "@aws-crypto/crc32": "5.2.0",
2926
+ "@aws-crypto/crc32c": "5.2.0",
2927
+ "@aws-crypto/util": "5.2.0",
2928
+ "@aws-sdk/core": "3.972.0",
2929
+ "@aws-sdk/crc64-nvme": "3.972.0",
2930
+ "@aws-sdk/types": "3.972.0",
2931
+ "@smithy/is-array-buffer": "^4.2.0",
2932
+ "@smithy/node-config-provider": "^4.3.8",
2933
+ "@smithy/protocol-http": "^5.3.8",
2934
+ "@smithy/types": "^4.12.0",
2935
+ "@smithy/util-middleware": "^4.2.8",
2936
+ "@smithy/util-stream": "^4.5.10",
2937
+ "@smithy/util-utf8": "^4.2.0",
2938
+ "tslib": "^2.6.2"
2939
+ },
2940
+ "engines": {
2941
+ "node": ">=20.0.0"
2942
+ }
2943
+ },
2944
+ "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/core": {
2945
+ "version": "3.972.0",
2946
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz",
2947
+ "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==",
2948
+ "license": "Apache-2.0",
2949
+ "dependencies": {
2950
+ "@aws-sdk/types": "3.972.0",
2951
+ "@aws-sdk/xml-builder": "3.972.0",
2952
+ "@smithy/core": "^3.20.6",
2953
+ "@smithy/node-config-provider": "^4.3.8",
2954
+ "@smithy/property-provider": "^4.2.8",
2955
+ "@smithy/protocol-http": "^5.3.8",
2956
+ "@smithy/signature-v4": "^5.3.8",
2957
+ "@smithy/smithy-client": "^4.10.8",
2958
+ "@smithy/types": "^4.12.0",
2959
+ "@smithy/util-base64": "^4.3.0",
2960
+ "@smithy/util-middleware": "^4.2.8",
2961
+ "@smithy/util-utf8": "^4.2.0",
2962
+ "tslib": "^2.6.2"
2963
+ },
2964
+ "engines": {
2965
+ "node": ">=20.0.0"
2966
+ }
2967
+ },
2968
+ "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/types": {
2969
+ "version": "3.972.0",
2970
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
2971
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
2972
+ "license": "Apache-2.0",
2973
+ "dependencies": {
2974
+ "@smithy/types": "^4.12.0",
2975
+ "tslib": "^2.6.2"
2976
+ },
2977
+ "engines": {
2978
+ "node": ">=20.0.0"
2979
+ }
2980
+ },
2981
+ "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/@aws-sdk/xml-builder": {
2982
+ "version": "3.972.0",
2983
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz",
2984
+ "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==",
2985
+ "license": "Apache-2.0",
2986
+ "dependencies": {
2987
+ "@smithy/types": "^4.12.0",
2988
+ "fast-xml-parser": "5.2.5",
2989
+ "tslib": "^2.6.2"
2990
+ },
2991
+ "engines": {
2992
+ "node": ">=20.0.0"
2993
+ }
2994
+ },
2995
+ "node_modules/@aws-sdk/middleware-host-header": {
2996
+ "version": "3.972.0",
2997
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.0.tgz",
2998
+ "integrity": "sha512-3eztFI6F9/eHtkIaWKN3nT+PM+eQ6p1MALDuNshFk323ixuCZzOOVT8oUqtZa30Z6dycNXJwhlIq7NhUVFfimw==",
2999
+ "license": "Apache-2.0",
3000
+ "dependencies": {
3001
+ "@aws-sdk/types": "3.972.0",
3002
+ "@smithy/protocol-http": "^5.3.8",
3003
+ "@smithy/types": "^4.12.0",
3004
+ "tslib": "^2.6.2"
3005
+ },
3006
+ "engines": {
3007
+ "node": ">=20.0.0"
3008
+ }
3009
+ },
3010
+ "node_modules/@aws-sdk/middleware-host-header/node_modules/@aws-sdk/types": {
3011
+ "version": "3.972.0",
3012
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
3013
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
3014
+ "license": "Apache-2.0",
3015
+ "dependencies": {
3016
+ "@smithy/types": "^4.12.0",
3017
+ "tslib": "^2.6.2"
3018
+ },
3019
+ "engines": {
3020
+ "node": ">=20.0.0"
3021
+ }
3022
+ },
3023
+ "node_modules/@aws-sdk/middleware-location-constraint": {
3024
+ "version": "3.972.0",
3025
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.972.0.tgz",
3026
+ "integrity": "sha512-WpsxoVPzbGPQGb/jupNYjpE0REcCPtjz7Q7zAt+dyo7fxsLBn4J+Rp6AYzSa04J9VrmrvCqCbVLu6B88PlSKSQ==",
3027
+ "license": "Apache-2.0",
3028
+ "dependencies": {
3029
+ "@aws-sdk/types": "3.972.0",
3030
+ "@smithy/types": "^4.12.0",
3031
+ "tslib": "^2.6.2"
3032
+ },
3033
+ "engines": {
3034
+ "node": ">=20.0.0"
3035
+ }
3036
+ },
3037
+ "node_modules/@aws-sdk/middleware-location-constraint/node_modules/@aws-sdk/types": {
3038
+ "version": "3.972.0",
3039
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
3040
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
3041
+ "license": "Apache-2.0",
3042
+ "dependencies": {
3043
+ "@smithy/types": "^4.12.0",
3044
+ "tslib": "^2.6.2"
3045
+ },
3046
+ "engines": {
3047
+ "node": ">=20.0.0"
3048
+ }
3049
+ },
3050
+ "node_modules/@aws-sdk/middleware-logger": {
3051
+ "version": "3.972.0",
3052
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.0.tgz",
3053
+ "integrity": "sha512-ZvdyVRwzK+ra31v1pQrgbqR/KsLD+wwJjHgko6JfoKUBIcEfAwJzQKO6HspHxdHWTVUz6MgvwskheR/TTYZl2g==",
3054
+ "license": "Apache-2.0",
3055
+ "dependencies": {
3056
+ "@aws-sdk/types": "3.972.0",
3057
+ "@smithy/types": "^4.12.0",
3058
+ "tslib": "^2.6.2"
3059
+ },
3060
+ "engines": {
3061
+ "node": ">=20.0.0"
3062
+ }
3063
+ },
3064
+ "node_modules/@aws-sdk/middleware-logger/node_modules/@aws-sdk/types": {
3065
+ "version": "3.972.0",
3066
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
3067
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
3068
+ "license": "Apache-2.0",
3069
+ "dependencies": {
3070
+ "@smithy/types": "^4.12.0",
3071
+ "tslib": "^2.6.2"
3072
+ },
3073
+ "engines": {
3074
+ "node": ">=20.0.0"
3075
+ }
3076
+ },
3077
+ "node_modules/@aws-sdk/middleware-recursion-detection": {
3078
+ "version": "3.972.0",
3079
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.0.tgz",
3080
+ "integrity": "sha512-F2SmUeO+S6l1h6dydNet3BQIk173uAkcfU1HDkw/bUdRLAnh15D3HP9vCZ7oCPBNcdEICbXYDmx0BR9rRUHGlQ==",
3081
+ "license": "Apache-2.0",
3082
+ "dependencies": {
3083
+ "@aws-sdk/types": "3.972.0",
3084
+ "@aws/lambda-invoke-store": "^0.2.2",
3085
+ "@smithy/protocol-http": "^5.3.8",
3086
+ "@smithy/types": "^4.12.0",
3087
+ "tslib": "^2.6.2"
3088
+ },
3089
+ "engines": {
3090
+ "node": ">=20.0.0"
3091
+ }
3092
+ },
3093
+ "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@aws-sdk/types": {
3094
+ "version": "3.972.0",
3095
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
3096
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
3097
+ "license": "Apache-2.0",
3098
+ "dependencies": {
3099
+ "@smithy/types": "^4.12.0",
3100
+ "tslib": "^2.6.2"
3101
+ },
3102
+ "engines": {
3103
+ "node": ">=20.0.0"
3104
+ }
3105
+ },
3106
+ "node_modules/@aws-sdk/middleware-sdk-s3": {
3107
+ "version": "3.947.0",
3108
+ "license": "Apache-2.0",
3109
+ "dependencies": {
3110
+ "@aws-sdk/core": "3.947.0",
2360
3111
"@aws-sdk/types": "3.936.0",
2361
3112
"@aws-sdk/util-arn-parser": "3.893.0",
2362
3113
"@smithy/core": "^3.18.7",
2372
3123
"tslib": "^2.6.2"
2373
3124
},
2374
3125
"engines": {
2375
- "node": ">=18.0.0"
3126
+ "node": ">=18.0.0"
3127
+ }
3128
+ },
3129
+ "node_modules/@aws-sdk/middleware-ssec": {
3130
+ "version": "3.972.0",
3131
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.0.tgz",
3132
+ "integrity": "sha512-cEr2HtK4R2fi8Y0P95cjbr4KJOjKBt8ms95mEJhabJN8KM4CpD4iS/J1lhvMj+qWir0KBTV6gKmxECXdfL9S6w==",
3133
+ "license": "Apache-2.0",
3134
+ "dependencies": {
3135
+ "@aws-sdk/types": "3.972.0",
3136
+ "@smithy/types": "^4.12.0",
3137
+ "tslib": "^2.6.2"
3138
+ },
3139
+ "engines": {
3140
+ "node": ">=20.0.0"
3141
+ }
3142
+ },
3143
+ "node_modules/@aws-sdk/middleware-ssec/node_modules/@aws-sdk/types": {
3144
+ "version": "3.972.0",
3145
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
3146
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
3147
+ "license": "Apache-2.0",
3148
+ "dependencies": {
3149
+ "@smithy/types": "^4.12.0",
3150
+ "tslib": "^2.6.2"
3151
+ },
3152
+ "engines": {
3153
+ "node": ">=20.0.0"
3154
+ }
3155
+ },
3156
+ "node_modules/@aws-sdk/middleware-user-agent": {
3157
+ "version": "3.972.0",
3158
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.0.tgz",
3159
+ "integrity": "sha512-kFHQm2OCBJCzGWRafgdWHGFjitUXY/OxXngymcX4l8CiyiNDZB27HDDBg2yLj3OUJc4z4fexLMmP8r9vgag19g==",
3160
+ "license": "Apache-2.0",
3161
+ "dependencies": {
3162
+ "@aws-sdk/core": "3.972.0",
3163
+ "@aws-sdk/types": "3.972.0",
3164
+ "@aws-sdk/util-endpoints": "3.972.0",
3165
+ "@smithy/core": "^3.20.6",
3166
+ "@smithy/protocol-http": "^5.3.8",
3167
+ "@smithy/types": "^4.12.0",
3168
+ "tslib": "^2.6.2"
3169
+ },
3170
+ "engines": {
3171
+ "node": ">=20.0.0"
3172
+ }
3173
+ },
3174
+ "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/core": {
3175
+ "version": "3.972.0",
3176
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz",
3177
+ "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==",
3178
+ "license": "Apache-2.0",
3179
+ "dependencies": {
3180
+ "@aws-sdk/types": "3.972.0",
3181
+ "@aws-sdk/xml-builder": "3.972.0",
3182
+ "@smithy/core": "^3.20.6",
3183
+ "@smithy/node-config-provider": "^4.3.8",
3184
+ "@smithy/property-provider": "^4.2.8",
3185
+ "@smithy/protocol-http": "^5.3.8",
3186
+ "@smithy/signature-v4": "^5.3.8",
3187
+ "@smithy/smithy-client": "^4.10.8",
3188
+ "@smithy/types": "^4.12.0",
3189
+ "@smithy/util-base64": "^4.3.0",
3190
+ "@smithy/util-middleware": "^4.2.8",
3191
+ "@smithy/util-utf8": "^4.2.0",
3192
+ "tslib": "^2.6.2"
3193
+ },
3194
+ "engines": {
3195
+ "node": ">=20.0.0"
3196
+ }
3197
+ },
3198
+ "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/types": {
3199
+ "version": "3.972.0",
3200
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
3201
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
3202
+ "license": "Apache-2.0",
3203
+ "dependencies": {
3204
+ "@smithy/types": "^4.12.0",
3205
+ "tslib": "^2.6.2"
3206
+ },
3207
+ "engines": {
3208
+ "node": ">=20.0.0"
3209
+ }
3210
+ },
3211
+ "node_modules/@aws-sdk/middleware-user-agent/node_modules/@aws-sdk/xml-builder": {
3212
+ "version": "3.972.0",
3213
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz",
3214
+ "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==",
3215
+ "license": "Apache-2.0",
3216
+ "dependencies": {
3217
+ "@smithy/types": "^4.12.0",
3218
+ "fast-xml-parser": "5.2.5",
3219
+ "tslib": "^2.6.2"
3220
+ },
3221
+ "engines": {
3222
+ "node": ">=20.0.0"
3223
+ }
3224
+ },
3225
+ "node_modules/@aws-sdk/nested-clients": {
3226
+ "version": "3.972.0",
3227
+ "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.972.0.tgz",
3228
+ "integrity": "sha512-QGlbnuGzSQJVG6bR9Qw6G0Blh6abFR4VxNa61ttMbzy9jt28xmk2iGtrYLrQPlCCPhY6enHqjTWm3n3LOb0wAw==",
3229
+ "license": "Apache-2.0",
3230
+ "dependencies": {
3231
+ "@aws-crypto/sha256-browser": "5.2.0",
3232
+ "@aws-crypto/sha256-js": "5.2.0",
3233
+ "@aws-sdk/core": "3.972.0",
3234
+ "@aws-sdk/middleware-host-header": "3.972.0",
3235
+ "@aws-sdk/middleware-logger": "3.972.0",
3236
+ "@aws-sdk/middleware-recursion-detection": "3.972.0",
3237
+ "@aws-sdk/middleware-user-agent": "3.972.0",
3238
+ "@aws-sdk/region-config-resolver": "3.972.0",
3239
+ "@aws-sdk/types": "3.972.0",
3240
+ "@aws-sdk/util-endpoints": "3.972.0",
3241
+ "@aws-sdk/util-user-agent-browser": "3.972.0",
3242
+ "@aws-sdk/util-user-agent-node": "3.972.0",
3243
+ "@smithy/config-resolver": "^4.4.6",
3244
+ "@smithy/core": "^3.20.6",
3245
+ "@smithy/fetch-http-handler": "^5.3.9",
3246
+ "@smithy/hash-node": "^4.2.8",
3247
+ "@smithy/invalid-dependency": "^4.2.8",
3248
+ "@smithy/middleware-content-length": "^4.2.8",
3249
+ "@smithy/middleware-endpoint": "^4.4.7",
3250
+ "@smithy/middleware-retry": "^4.4.23",
3251
+ "@smithy/middleware-serde": "^4.2.9",
3252
+ "@smithy/middleware-stack": "^4.2.8",
3253
+ "@smithy/node-config-provider": "^4.3.8",
3254
+ "@smithy/node-http-handler": "^4.4.8",
3255
+ "@smithy/protocol-http": "^5.3.8",
3256
+ "@smithy/smithy-client": "^4.10.8",
3257
+ "@smithy/types": "^4.12.0",
3258
+ "@smithy/url-parser": "^4.2.8",
3259
+ "@smithy/util-base64": "^4.3.0",
3260
+ "@smithy/util-body-length-browser": "^4.2.0",
3261
+ "@smithy/util-body-length-node": "^4.2.1",
3262
+ "@smithy/util-defaults-mode-browser": "^4.3.22",
3263
+ "@smithy/util-defaults-mode-node": "^4.2.25",
3264
+ "@smithy/util-endpoints": "^3.2.8",
3265
+ "@smithy/util-middleware": "^4.2.8",
3266
+ "@smithy/util-retry": "^4.2.8",
3267
+ "@smithy/util-utf8": "^4.2.0",
3268
+ "tslib": "^2.6.2"
3269
+ },
3270
+ "engines": {
3271
+ "node": ">=20.0.0"
2376
3272
}
2377
3273
},
2378
- "node_modules/@aws-sdk/middleware-ssec": {
2379
- "version": "3.936.0",
3274
+ "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/core": {
3275
+ "version": "3.972.0",
3276
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz",
3277
+ "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==",
2380
3278
"license": "Apache-2.0",
2381
3279
"dependencies": {
2382
- "@aws-sdk/types": "3.936.0",
2383
- "@smithy/types": "^4.9.0",
3280
+ "@aws-sdk/types": "3.972.0",
3281
+ "@aws-sdk/xml-builder": "3.972.0",
3282
+ "@smithy/core": "^3.20.6",
3283
+ "@smithy/node-config-provider": "^4.3.8",
3284
+ "@smithy/property-provider": "^4.2.8",
3285
+ "@smithy/protocol-http": "^5.3.8",
3286
+ "@smithy/signature-v4": "^5.3.8",
3287
+ "@smithy/smithy-client": "^4.10.8",
3288
+ "@smithy/types": "^4.12.0",
3289
+ "@smithy/util-base64": "^4.3.0",
3290
+ "@smithy/util-middleware": "^4.2.8",
3291
+ "@smithy/util-utf8": "^4.2.0",
2384
3292
"tslib": "^2.6.2"
2385
3293
},
2386
3294
"engines": {
2387
- "node": ">=18.0.0"
3295
+ "node": ">=20.0.0"
2388
3296
}
2389
3297
},
2390
- "node_modules/@aws-sdk/middleware-user-agent": {
2391
- "version": "3.947.0",
3298
+ "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/types": {
3299
+ "version": "3.972.0",
3300
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
3301
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
2392
3302
"license": "Apache-2.0",
2393
3303
"dependencies": {
2394
- "@aws-sdk/core": "3.947.0",
2395
- "@aws-sdk/types": "3.936.0",
2396
- "@aws-sdk/util-endpoints": "3.936.0",
2397
- "@smithy/core": "^3.18.7",
2398
- "@smithy/protocol-http": "^5.3.5",
2399
- "@smithy/types": "^4.9.0",
3304
+ "@smithy/types": "^4.12.0",
2400
3305
"tslib": "^2.6.2"
2401
3306
},
2402
3307
"engines": {
2403
- "node": ">=18.0.0"
3308
+ "node": ">=20.0.0"
2404
3309
}
2405
3310
},
2406
- "node_modules/@aws-sdk/nested-clients": {
2407
- "version": "3.948.0",
3311
+ "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/xml-builder": {
3312
+ "version": "3.972.0",
3313
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz",
3314
+ "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==",
2408
3315
"license": "Apache-2.0",
2409
3316
"dependencies": {
2410
- "@aws-crypto/sha256-browser": "5.2.0",
2411
- "@aws-crypto/sha256-js": "5.2.0",
2412
- "@aws-sdk/core": "3.947.0",
2413
- "@aws-sdk/middleware-host-header": "3.936.0",
2414
- "@aws-sdk/middleware-logger": "3.936.0",
2415
- "@aws-sdk/middleware-recursion-detection": "3.948.0",
2416
- "@aws-sdk/middleware-user-agent": "3.947.0",
2417
- "@aws-sdk/region-config-resolver": "3.936.0",
2418
- "@aws-sdk/types": "3.936.0",
2419
- "@aws-sdk/util-endpoints": "3.936.0",
2420
- "@aws-sdk/util-user-agent-browser": "3.936.0",
2421
- "@aws-sdk/util-user-agent-node": "3.947.0",
2422
- "@smithy/config-resolver": "^4.4.3",
2423
- "@smithy/core": "^3.18.7",
2424
- "@smithy/fetch-http-handler": "^5.3.6",
2425
- "@smithy/hash-node": "^4.2.5",
2426
- "@smithy/invalid-dependency": "^4.2.5",
2427
- "@smithy/middleware-content-length": "^4.2.5",
2428
- "@smithy/middleware-endpoint": "^4.3.14",
2429
- "@smithy/middleware-retry": "^4.4.14",
2430
- "@smithy/middleware-serde": "^4.2.6",
2431
- "@smithy/middleware-stack": "^4.2.5",
2432
- "@smithy/node-config-provider": "^4.3.5",
2433
- "@smithy/node-http-handler": "^4.4.5",
2434
- "@smithy/protocol-http": "^5.3.5",
2435
- "@smithy/smithy-client": "^4.9.10",
2436
- "@smithy/types": "^4.9.0",
2437
- "@smithy/url-parser": "^4.2.5",
2438
- "@smithy/util-base64": "^4.3.0",
2439
- "@smithy/util-body-length-browser": "^4.2.0",
2440
- "@smithy/util-body-length-node": "^4.2.1",
2441
- "@smithy/util-defaults-mode-browser": "^4.3.13",
2442
- "@smithy/util-defaults-mode-node": "^4.2.16",
2443
- "@smithy/util-endpoints": "^3.2.5",
2444
- "@smithy/util-middleware": "^4.2.5",
2445
- "@smithy/util-retry": "^4.2.5",
2446
- "@smithy/util-utf8": "^4.2.0",
3317
+ "@smithy/types": "^4.12.0",
3318
+ "fast-xml-parser": "5.2.5",
2447
3319
"tslib": "^2.6.2"
2448
3320
},
2449
3321
"engines": {
2450
- "node": ">=18.0.0"
3322
+ "node": ">=20.0.0"
2451
3323
}
2452
3324
},
2453
3325
"node_modules/@aws-sdk/region-config-resolver": {
2454
- "version": "3.936.0",
3326
+ "version": "3.972.0",
3327
+ "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.0.tgz",
3328
+ "integrity": "sha512-JyOf+R/6vJW8OEVFCAyzEOn2reri/Q+L0z9zx4JQSKWvTmJ1qeFO25sOm8VIfB8URKhfGRTQF30pfYaH2zxt/A==",
2455
3329
"license": "Apache-2.0",
2456
3330
"dependencies": {
2457
- "@aws-sdk/types": "3.936.0",
2458
- "@smithy/config-resolver": "^4.4.3",
2459
- "@smithy/node-config-provider": "^4.3.5",
2460
- "@smithy/types": "^4.9.0",
3331
+ "@aws-sdk/types": "3.972.0",
3332
+ "@smithy/config-resolver": "^4.4.6",
3333
+ "@smithy/node-config-provider": "^4.3.8",
3334
+ "@smithy/types": "^4.12.0",
2461
3335
"tslib": "^2.6.2"
2462
3336
},
2463
3337
"engines": {
2464
- "node": ">=18.0.0"
3338
+ "node": ">=20.0.0"
3339
+ }
3340
+ },
3341
+ "node_modules/@aws-sdk/region-config-resolver/node_modules/@aws-sdk/types": {
3342
+ "version": "3.972.0",
3343
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
3344
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
3345
+ "license": "Apache-2.0",
3346
+ "dependencies": {
3347
+ "@smithy/types": "^4.12.0",
3348
+ "tslib": "^2.6.2"
3349
+ },
3350
+ "engines": {
3351
+ "node": ">=20.0.0"
2465
3352
}
2466
3353
},
2467
3354
"node_modules/@aws-sdk/s3-request-presigner": {
2497
3384
}
2498
3385
},
2499
3386
"node_modules/@aws-sdk/token-providers": {
2500
- "version": "3.948.0",
3387
+ "version": "3.972.0",
3388
+ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.972.0.tgz",
3389
+ "integrity": "sha512-kWlXG+y5nZhgXGEtb72Je+EvqepBPs8E3vZse//1PYLWs2speFqbGE/ywCXmzEJgHgVqSB/u/lqBvs5WlYmSqQ==",
2501
3390
"license": "Apache-2.0",
2502
3391
"dependencies": {
2503
- "@aws-sdk/core": "3.947.0",
2504
- "@aws-sdk/nested-clients": "3.948.0",
2505
- "@aws-sdk/types": "3.936.0",
2506
- "@smithy/property-provider": "^4.2.5",
2507
- "@smithy/shared-ini-file-loader": "^4.4.0",
2508
- "@smithy/types": "^4.9.0",
3392
+ "@aws-sdk/core": "3.972.0",
3393
+ "@aws-sdk/nested-clients": "3.972.0",
3394
+ "@aws-sdk/types": "3.972.0",
3395
+ "@smithy/property-provider": "^4.2.8",
3396
+ "@smithy/shared-ini-file-loader": "^4.4.3",
3397
+ "@smithy/types": "^4.12.0",
2509
3398
"tslib": "^2.6.2"
2510
3399
},
2511
3400
"engines": {
2512
- "node": ">=18.0.0"
3401
+ "node": ">=20.0.0"
3402
+ }
3403
+ },
3404
+ "node_modules/@aws-sdk/token-providers/node_modules/@aws-sdk/core": {
3405
+ "version": "3.972.0",
3406
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz",
3407
+ "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==",
3408
+ "license": "Apache-2.0",
3409
+ "dependencies": {
3410
+ "@aws-sdk/types": "3.972.0",
3411
+ "@aws-sdk/xml-builder": "3.972.0",
3412
+ "@smithy/core": "^3.20.6",
3413
+ "@smithy/node-config-provider": "^4.3.8",
3414
+ "@smithy/property-provider": "^4.2.8",
3415
+ "@smithy/protocol-http": "^5.3.8",
3416
+ "@smithy/signature-v4": "^5.3.8",
3417
+ "@smithy/smithy-client": "^4.10.8",
3418
+ "@smithy/types": "^4.12.0",
3419
+ "@smithy/util-base64": "^4.3.0",
3420
+ "@smithy/util-middleware": "^4.2.8",
3421
+ "@smithy/util-utf8": "^4.2.0",
3422
+ "tslib": "^2.6.2"
3423
+ },
3424
+ "engines": {
3425
+ "node": ">=20.0.0"
3426
+ }
3427
+ },
3428
+ "node_modules/@aws-sdk/token-providers/node_modules/@aws-sdk/types": {
3429
+ "version": "3.972.0",
3430
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
3431
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
3432
+ "license": "Apache-2.0",
3433
+ "dependencies": {
3434
+ "@smithy/types": "^4.12.0",
3435
+ "tslib": "^2.6.2"
3436
+ },
3437
+ "engines": {
3438
+ "node": ">=20.0.0"
3439
+ }
3440
+ },
3441
+ "node_modules/@aws-sdk/token-providers/node_modules/@aws-sdk/xml-builder": {
3442
+ "version": "3.972.0",
3443
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz",
3444
+ "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==",
3445
+ "license": "Apache-2.0",
3446
+ "dependencies": {
3447
+ "@smithy/types": "^4.12.0",
3448
+ "fast-xml-parser": "5.2.5",
3449
+ "tslib": "^2.6.2"
3450
+ },
3451
+ "engines": {
3452
+ "node": ">=20.0.0"
2513
3453
}
2514
3454
},
2515
3455
"node_modules/@aws-sdk/types": {
2534
3474
}
2535
3475
},
2536
3476
"node_modules/@aws-sdk/util-endpoints": {
2537
- "version": "3.936.0",
3477
+ "version": "3.972.0",
3478
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz",
3479
+ "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==",
2538
3480
"license": "Apache-2.0",
2539
3481
"dependencies": {
2540
- "@aws-sdk/types": "3.936.0",
2541
- "@smithy/types": "^4.9.0",
2542
- "@smithy/url-parser": "^4.2.5",
2543
- "@smithy/util-endpoints": "^3.2.5",
3482
+ "@aws-sdk/types": "3.972.0",
3483
+ "@smithy/types": "^4.12.0",
3484
+ "@smithy/url-parser": "^4.2.8",
3485
+ "@smithy/util-endpoints": "^3.2.8",
2544
3486
"tslib": "^2.6.2"
2545
3487
},
2546
3488
"engines": {
2547
- "node": ">=18.0.0"
3489
+ "node": ">=20.0.0"
3490
+ }
3491
+ },
3492
+ "node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": {
3493
+ "version": "3.972.0",
3494
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
3495
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
3496
+ "license": "Apache-2.0",
3497
+ "dependencies": {
3498
+ "@smithy/types": "^4.12.0",
3499
+ "tslib": "^2.6.2"
3500
+ },
3501
+ "engines": {
3502
+ "node": ">=20.0.0"
2548
3503
}
2549
3504
},
2550
3505
"node_modules/@aws-sdk/util-format-url": {
2571
3526
}
2572
3527
},
2573
3528
"node_modules/@aws-sdk/util-user-agent-browser": {
2574
- "version": "3.936.0",
3529
+ "version": "3.972.0",
3530
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.0.tgz",
3531
+ "integrity": "sha512-eOLdkQyoRbDgioTS3Orr7iVsVEutJyMZxvyZ6WAF95IrF0kfWx5Rd/KXnfbnG/VKa2CvjZiitWfouLzfVEyvJA==",
2575
3532
"license": "Apache-2.0",
2576
3533
"dependencies": {
2577
- "@aws-sdk/types": "3.936.0",
2578
- "@smithy/types": "^4.9.0",
3534
+ "@aws-sdk/types": "3.972.0",
3535
+ "@smithy/types": "^4.12.0",
2579
3536
"bowser": "^2.11.0",
2580
3537
"tslib": "^2.6.2"
2581
3538
}
2582
3539
},
3540
+ "node_modules/@aws-sdk/util-user-agent-browser/node_modules/@aws-sdk/types": {
3541
+ "version": "3.972.0",
3542
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
3543
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
3544
+ "license": "Apache-2.0",
3545
+ "dependencies": {
3546
+ "@smithy/types": "^4.12.0",
3547
+ "tslib": "^2.6.2"
3548
+ },
3549
+ "engines": {
3550
+ "node": ">=20.0.0"
3551
+ }
3552
+ },
2583
3553
"node_modules/@aws-sdk/util-user-agent-node": {
2584
- "version": "3.947.0",
3554
+ "version": "3.972.0",
3555
+ "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.0.tgz",
3556
+ "integrity": "sha512-GOy+AiSrE9kGiojiwlZvVVSXwylu4+fmP0MJfvras/MwP09RB/YtQuOVR1E0fKQc6OMwaTNBjgAbOEhxuWFbAw==",
2585
3557
"license": "Apache-2.0",
2586
3558
"dependencies": {
2587
- "@aws-sdk/middleware-user-agent": "3.947.0",
2588
- "@aws-sdk/types": "3.936.0",
2589
- "@smithy/node-config-provider": "^4.3.5",
2590
- "@smithy/types": "^4.9.0",
3559
+ "@aws-sdk/middleware-user-agent": "3.972.0",
3560
+ "@aws-sdk/types": "3.972.0",
3561
+ "@smithy/node-config-provider": "^4.3.8",
3562
+ "@smithy/types": "^4.12.0",
2591
3563
"tslib": "^2.6.2"
2592
3564
},
2593
3565
"engines": {
2594
- "node": ">=18.0.0"
3566
+ "node": ">=20.0.0"
2595
3567
},
2596
3568
"peerDependencies": {
2597
3569
"aws-crt": ">=1.0.0"
2602
3574
}
2603
3575
}
2604
3576
},
3577
+ "node_modules/@aws-sdk/util-user-agent-node/node_modules/@aws-sdk/types": {
3578
+ "version": "3.972.0",
3579
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz",
3580
+ "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==",
3581
+ "license": "Apache-2.0",
3582
+ "dependencies": {
3583
+ "@smithy/types": "^4.12.0",
3584
+ "tslib": "^2.6.2"
3585
+ },
3586
+ "engines": {
3587
+ "node": ">=20.0.0"
3588
+ }
3589
+ },
2605
3590
"node_modules/@aws-sdk/xml-builder": {
2606
3591
"version": "3.930.0",
2607
3592
"license": "Apache-2.0",
7888
8873
}
7889
8874
},
7890
8875
"node_modules/@smithy/abort-controller": {
7891
- "version": "4.2.7",
7892
- "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.7.tgz",
7893
- "integrity": "sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw==",
8876
+ "version": "4.2.8",
8877
+ "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz",
8878
+ "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==",
7894
8879
"license": "Apache-2.0",
7895
8880
"dependencies": {
7896
- "@smithy/types": "^4.11.0",
8881
+ "@smithy/types": "^4.12.0",
7897
8882
"tslib": "^2.6.2"
7898
8883
},
7899
8884
"engines": {
7902
8887
},
7903
8888
"node_modules/@smithy/chunked-blob-reader": {
7904
8889
"version": "5.2.0",
8890
+ "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.0.tgz",
8891
+ "integrity": "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==",
7905
8892
"license": "Apache-2.0",
7906
8893
"dependencies": {
7907
8894
"tslib": "^2.6.2"
7912
8899
},
7913
8900
"node_modules/@smithy/chunked-blob-reader-native": {
7914
8901
"version": "4.2.1",
8902
+ "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-4.2.1.tgz",
8903
+ "integrity": "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ==",
7915
8904
"license": "Apache-2.0",
7916
8905
"dependencies": {
7917
8906
"@smithy/util-base64": "^4.3.0",
7922
8911
}
7923
8912
},
7924
8913
"node_modules/@smithy/config-resolver": {
7925
- "version": "4.4.5",
7926
- "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.5.tgz",
7927
- "integrity": "sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg==",
8914
+ "version": "4.4.6",
8915
+ "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz",
8916
+ "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==",
7928
8917
"license": "Apache-2.0",
7929
8918
"dependencies": {
7930
- "@smithy/node-config-provider": "^4.3.7",
7931
- "@smithy/types": "^4.11.0",
8919
+ "@smithy/node-config-provider": "^4.3.8",
8920
+ "@smithy/types": "^4.12.0",
7932
8921
"@smithy/util-config-provider": "^4.2.0",
7933
- "@smithy/util-endpoints": "^3.2.7",
7934
- "@smithy/util-middleware": "^4.2.7",
8922
+ "@smithy/util-endpoints": "^3.2.8",
8923
+ "@smithy/util-middleware": "^4.2.8",
7935
8924
"tslib": "^2.6.2"
7936
8925
},
7937
8926
"engines": {
7939
8928
}
7940
8929
},
7941
8930
"node_modules/@smithy/core": {
7942
- "version": "3.20.0",
7943
- "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.0.tgz",
7944
- "integrity": "sha512-WsSHCPq/neD5G/MkK4csLI5Y5Pkd9c1NMfpYEKeghSGaD4Ja1qLIohRQf2D5c1Uy5aXp76DeKHkzWZ9KAlHroQ==",
8931
+ "version": "3.21.0",
8932
+ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.21.0.tgz",
8933
+ "integrity": "sha512-bg2TfzgsERyETAxc/Ims/eJX8eAnIeTi4r4LHpMpfF/2NyO6RsWis0rjKcCPaGksljmOb23BZRiCeT/3NvwkXw==",
7945
8934
"license": "Apache-2.0",
7946
8935
"dependencies": {
7947
- "@smithy/middleware-serde": "^4.2.8",
7948
- "@smithy/protocol-http": "^5.3.7",
7949
- "@smithy/types": "^4.11.0",
8936
+ "@smithy/middleware-serde": "^4.2.9",
8937
+ "@smithy/protocol-http": "^5.3.8",
8938
+ "@smithy/types": "^4.12.0",
7950
8939
"@smithy/util-base64": "^4.3.0",
7951
8940
"@smithy/util-body-length-browser": "^4.2.0",
7952
- "@smithy/util-middleware": "^4.2.7",
7953
- "@smithy/util-stream": "^4.5.8",
8941
+ "@smithy/util-middleware": "^4.2.8",
8942
+ "@smithy/util-stream": "^4.5.10",
7954
8943
"@smithy/util-utf8": "^4.2.0",
7955
8944
"@smithy/uuid": "^1.1.0",
7956
8945
"tslib": "^2.6.2"
7960
8949
}
7961
8950
},
7962
8951
"node_modules/@smithy/credential-provider-imds": {
7963
- "version": "4.2.7",
7964
- "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.7.tgz",
7965
- "integrity": "sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA==",
8952
+ "version": "4.2.8",
8953
+ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz",
8954
+ "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==",
7966
8955
"license": "Apache-2.0",
7967
8956
"dependencies": {
7968
- "@smithy/node-config-provider": "^4.3.7",
7969
- "@smithy/property-provider": "^4.2.7",
7970
- "@smithy/types": "^4.11.0",
7971
- "@smithy/url-parser": "^4.2.7",
8957
+ "@smithy/node-config-provider": "^4.3.8",
8958
+ "@smithy/property-provider": "^4.2.8",
8959
+ "@smithy/types": "^4.12.0",
8960
+ "@smithy/url-parser": "^4.2.8",
7972
8961
"tslib": "^2.6.2"
7973
8962
},
7974
8963
"engines": {
7976
8965
}
7977
8966
},
7978
8967
"node_modules/@smithy/eventstream-codec": {
7979
- "version": "4.2.5",
8968
+ "version": "4.2.8",
8969
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.8.tgz",
8970
+ "integrity": "sha512-jS/O5Q14UsufqoGhov7dHLOPCzkYJl9QDzusI2Psh4wyYx/izhzvX9P4D69aTxcdfVhEPhjK+wYyn/PzLjKbbw==",
7980
8971
"license": "Apache-2.0",
7981
8972
"dependencies": {
7982
8973
"@aws-crypto/crc32": "5.2.0",
7983
- "@smithy/types": "^4.9.0",
8974
+ "@smithy/types": "^4.12.0",
7984
8975
"@smithy/util-hex-encoding": "^4.2.0",
7985
8976
"tslib": "^2.6.2"
7986
8977
},
7989
8980
}
7990
8981
},
7991
8982
"node_modules/@smithy/eventstream-serde-browser": {
7992
- "version": "4.2.5",
8983
+ "version": "4.2.8",
8984
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.8.tgz",
8985
+ "integrity": "sha512-MTfQT/CRQz5g24ayXdjg53V0mhucZth4PESoA5IhvaWVDTOQLfo8qI9vzqHcPsdd2v6sqfTYqF5L/l+pea5Uyw==",
7993
8986
"license": "Apache-2.0",
7994
8987
"dependencies": {
7995
- "@smithy/eventstream-serde-universal": "^4.2.5",
7996
- "@smithy/types": "^4.9.0",
8988
+ "@smithy/eventstream-serde-universal": "^4.2.8",
8989
+ "@smithy/types": "^4.12.0",
7997
8990
"tslib": "^2.6.2"
7998
8991
},
7999
8992
"engines": {
8001
8994
}
8002
8995
},
8003
8996
"node_modules/@smithy/eventstream-serde-config-resolver": {
8004
- "version": "4.3.5",
8997
+ "version": "4.3.8",
8998
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.8.tgz",
8999
+ "integrity": "sha512-ah12+luBiDGzBruhu3efNy1IlbwSEdNiw8fOZksoKoWW1ZHvO/04MQsdnws/9Aj+5b0YXSSN2JXKy/ClIsW8MQ==",
8005
9000
"license": "Apache-2.0",
8006
9001
"dependencies": {
8007
- "@smithy/types": "^4.9.0",
9002
+ "@smithy/types": "^4.12.0",
8008
9003
"tslib": "^2.6.2"
8009
9004
},
8010
9005
"engines": {
8012
9007
}
8013
9008
},
8014
9009
"node_modules/@smithy/eventstream-serde-node": {
8015
- "version": "4.2.5",
9010
+ "version": "4.2.8",
9011
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.8.tgz",
9012
+ "integrity": "sha512-cYpCpp29z6EJHa5T9WL0KAlq3SOKUQkcgSoeRfRVwjGgSFl7Uh32eYGt7IDYCX20skiEdRffyDpvF2efEZPC0A==",
8016
9013
"license": "Apache-2.0",
8017
9014
"dependencies": {
8018
- "@smithy/eventstream-serde-universal": "^4.2.5",
8019
- "@smithy/types": "^4.9.0",
9015
+ "@smithy/eventstream-serde-universal": "^4.2.8",
9016
+ "@smithy/types": "^4.12.0",
8020
9017
"tslib": "^2.6.2"
8021
9018
},
8022
9019
"engines": {
8024
9021
}
8025
9022
},
8026
9023
"node_modules/@smithy/eventstream-serde-universal": {
8027
- "version": "4.2.5",
9024
+ "version": "4.2.8",
9025
+ "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.8.tgz",
9026
+ "integrity": "sha512-iJ6YNJd0bntJYnX6s52NC4WFYcZeKrPUr1Kmmr5AwZcwCSzVpS7oavAmxMR7pMq7V+D1G4s9F5NJK0xwOsKAlQ==",
8028
9027
"license": "Apache-2.0",
8029
9028
"dependencies": {
8030
- "@smithy/eventstream-codec": "^4.2.5",
8031
- "@smithy/types": "^4.9.0",
9029
+ "@smithy/eventstream-codec": "^4.2.8",
9030
+ "@smithy/types": "^4.12.0",
8032
9031
"tslib": "^2.6.2"
8033
9032
},
8034
9033
"engines": {
8036
9035
}
8037
9036
},
8038
9037
"node_modules/@smithy/fetch-http-handler": {
8039
- "version": "5.3.8",
8040
- "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.8.tgz",
8041
- "integrity": "sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg==",
9038
+ "version": "5.3.9",
9039
+ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz",
9040
+ "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==",
8042
9041
"license": "Apache-2.0",
8043
9042
"dependencies": {
8044
- "@smithy/protocol-http": "^5.3.7",
8045
- "@smithy/querystring-builder": "^4.2.7",
8046
- "@smithy/types": "^4.11.0",
9043
+ "@smithy/protocol-http": "^5.3.8",
9044
+ "@smithy/querystring-builder": "^4.2.8",
9045
+ "@smithy/types": "^4.12.0",
8047
9046
"@smithy/util-base64": "^4.3.0",
8048
9047
"tslib": "^2.6.2"
8049
9048
},
8052
9051
}
8053
9052
},
8054
9053
"node_modules/@smithy/hash-blob-browser": {
8055
- "version": "4.2.6",
9054
+ "version": "4.2.9",
9055
+ "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.9.tgz",
9056
+ "integrity": "sha512-m80d/iicI7DlBDxyQP6Th7BW/ejDGiF0bgI754+tiwK0lgMkcaIBgvwwVc7OFbY4eUzpGtnig52MhPAEJ7iNYg==",
8056
9057
"license": "Apache-2.0",
8057
9058
"dependencies": {
8058
9059
"@smithy/chunked-blob-reader": "^5.2.0",
8059
9060
"@smithy/chunked-blob-reader-native": "^4.2.1",
8060
- "@smithy/types": "^4.9.0",
9061
+ "@smithy/types": "^4.12.0",
8061
9062
"tslib": "^2.6.2"
8062
9063
},
8063
9064
"engines": {
8065
9066
}
8066
9067
},
8067
9068
"node_modules/@smithy/hash-node": {
8068
- "version": "4.2.7",
8069
- "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.7.tgz",
8070
- "integrity": "sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw==",
9069
+ "version": "4.2.8",
9070
+ "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz",
9071
+ "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==",
8071
9072
"license": "Apache-2.0",
8072
9073
"dependencies": {
8073
- "@smithy/types": "^4.11.0",
9074
+ "@smithy/types": "^4.12.0",
8074
9075
"@smithy/util-buffer-from": "^4.2.0",
8075
9076
"@smithy/util-utf8": "^4.2.0",
8076
9077
"tslib": "^2.6.2"
8080
9081
}
8081
9082
},
8082
9083
"node_modules/@smithy/hash-stream-node": {
8083
- "version": "4.2.5",
9084
+ "version": "4.2.8",
9085
+ "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.8.tgz",
9086
+ "integrity": "sha512-v0FLTXgHrTeheYZFGhR+ehX5qUm4IQsjAiL9qehad2cyjMWcN2QG6/4mSwbSgEQzI7jwfoXj7z4fxZUx/Mhj2w==",
8084
9087
"license": "Apache-2.0",
8085
9088
"dependencies": {
8086
- "@smithy/types": "^4.9.0",
9089
+ "@smithy/types": "^4.12.0",
8087
9090
"@smithy/util-utf8": "^4.2.0",
8088
9091
"tslib": "^2.6.2"
8089
9092
},
8092
9095
}
8093
9096
},
8094
9097
"node_modules/@smithy/invalid-dependency": {
8095
- "version": "4.2.7",
8096
- "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.7.tgz",
8097
- "integrity": "sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ==",
9098
+ "version": "4.2.8",
9099
+ "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz",
9100
+ "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==",
8098
9101
"license": "Apache-2.0",
8099
9102
"dependencies": {
8100
- "@smithy/types": "^4.11.0",
9103
+ "@smithy/types": "^4.12.0",
8101
9104
"tslib": "^2.6.2"
8102
9105
},
8103
9106
"engines": {
8115
9118
}
8116
9119
},
8117
9120
"node_modules/@smithy/md5-js": {
8118
- "version": "4.2.5",
9121
+ "version": "4.2.8",
9122
+ "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.8.tgz",
9123
+ "integrity": "sha512-oGMaLj4tVZzLi3itBa9TCswgMBr7k9b+qKYowQ6x1rTyTuO1IU2YHdHUa+891OsOH+wCsH7aTPRsTJO3RMQmjQ==",
8119
9124
"license": "Apache-2.0",
8120
9125
"dependencies": {
8121
- "@smithy/types": "^4.9.0",
9126
+ "@smithy/types": "^4.12.0",
8122
9127
"@smithy/util-utf8": "^4.2.0",
8123
9128
"tslib": "^2.6.2"
8124
9129
},
8127
9132
}
8128
9133
},
8129
9134
"node_modules/@smithy/middleware-content-length": {
8130
- "version": "4.2.7",
8131
- "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.7.tgz",
8132
- "integrity": "sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg==",
9135
+ "version": "4.2.8",
9136
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz",
9137
+ "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==",
8133
9138
"license": "Apache-2.0",
8134
9139
"dependencies": {
8135
- "@smithy/protocol-http": "^5.3.7",
8136
- "@smithy/types": "^4.11.0",
9140
+ "@smithy/protocol-http": "^5.3.8",
9141
+ "@smithy/types": "^4.12.0",
8137
9142
"tslib": "^2.6.2"
8138
9143
},
8139
9144
"engines": {
8141
9146
}
8142
9147
},
8143
9148
"node_modules/@smithy/middleware-endpoint": {
8144
- "version": "4.4.1",
8145
- "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.1.tgz",
8146
- "integrity": "sha512-gpLspUAoe6f1M6H0u4cVuFzxZBrsGZmjx2O9SigurTx4PbntYa4AJ+o0G0oGm1L2oSX6oBhcGHwrfJHup2JnJg==",
8147
- "license": "Apache-2.0",
8148
- "dependencies": {
8149
- "@smithy/core": "^3.20.0",
8150
- "@smithy/middleware-serde": "^4.2.8",
8151
- "@smithy/node-config-provider": "^4.3.7",
8152
- "@smithy/shared-ini-file-loader": "^4.4.2",
8153
- "@smithy/types": "^4.11.0",
8154
- "@smithy/url-parser": "^4.2.7",
8155
- "@smithy/util-middleware": "^4.2.7",
9149
+ "version": "4.4.10",
9150
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.10.tgz",
9151
+ "integrity": "sha512-kwWpNltpxrvPabnjEFvwSmA+66l6s2ReCvgVSzW/z92LU4T28fTdgZ18IdYRYOrisu2NMQ0jUndRScbO65A/zg==",
9152
+ "license": "Apache-2.0",
9153
+ "dependencies": {
9154
+ "@smithy/core": "^3.21.0",
9155
+ "@smithy/middleware-serde": "^4.2.9",
9156
+ "@smithy/node-config-provider": "^4.3.8",
9157
+ "@smithy/shared-ini-file-loader": "^4.4.3",
9158
+ "@smithy/types": "^4.12.0",
9159
+ "@smithy/url-parser": "^4.2.8",
9160
+ "@smithy/util-middleware": "^4.2.8",
8156
9161
"tslib": "^2.6.2"
8157
9162
},
8158
9163
"engines": {
8160
9165
}
8161
9166
},
8162
9167
"node_modules/@smithy/middleware-retry": {
8163
- "version": "4.4.17",
8164
- "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.17.tgz",
8165
- "integrity": "sha512-MqbXK6Y9uq17h+4r0ogu/sBT6V/rdV+5NvYL7ZV444BKfQygYe8wAhDrVXagVebN6w2RE0Fm245l69mOsPGZzg==",
8166
- "license": "Apache-2.0",
8167
- "dependencies": {
8168
- "@smithy/node-config-provider": "^4.3.7",
8169
- "@smithy/protocol-http": "^5.3.7",
8170
- "@smithy/service-error-classification": "^4.2.7",
8171
- "@smithy/smithy-client": "^4.10.2",
8172
- "@smithy/types": "^4.11.0",
8173
- "@smithy/util-middleware": "^4.2.7",
8174
- "@smithy/util-retry": "^4.2.7",
9168
+ "version": "4.4.26",
9169
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.26.tgz",
9170
+ "integrity": "sha512-ozZMoTAr+B2aVYfLYfkssFvc8ZV3p/vLpVQ7/k277xxUOA9ykSPe5obL2j6yHfbdrM/SZV7qj0uk/hSqavHrLw==",
9171
+ "license": "Apache-2.0",
9172
+ "dependencies": {
9173
+ "@smithy/node-config-provider": "^4.3.8",
9174
+ "@smithy/protocol-http": "^5.3.8",
9175
+ "@smithy/service-error-classification": "^4.2.8",
9176
+ "@smithy/smithy-client": "^4.10.11",
9177
+ "@smithy/types": "^4.12.0",
9178
+ "@smithy/util-middleware": "^4.2.8",
9179
+ "@smithy/util-retry": "^4.2.8",
8175
9180
"@smithy/uuid": "^1.1.0",
8176
9181
"tslib": "^2.6.2"
8177
9182
},
8180
9185
}
8181
9186
},
8182
9187
"node_modules/@smithy/middleware-serde": {
8183
- "version": "4.2.8",
8184
- "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.8.tgz",
8185
- "integrity": "sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w==",
9188
+ "version": "4.2.9",
9189
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz",
9190
+ "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==",
8186
9191
"license": "Apache-2.0",
8187
9192
"dependencies": {
8188
- "@smithy/protocol-http": "^5.3.7",
8189
- "@smithy/types": "^4.11.0",
9193
+ "@smithy/protocol-http": "^5.3.8",
9194
+ "@smithy/types": "^4.12.0",
8190
9195
"tslib": "^2.6.2"
8191
9196
},
8192
9197
"engines": {
8194
9199
}
8195
9200
},
8196
9201
"node_modules/@smithy/middleware-stack": {
8197
- "version": "4.2.7",
8198
- "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.7.tgz",
8199
- "integrity": "sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw==",
9202
+ "version": "4.2.8",
9203
+ "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz",
9204
+ "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==",
8200
9205
"license": "Apache-2.0",
8201
9206
"dependencies": {
8202
- "@smithy/types": "^4.11.0",
9207
+ "@smithy/types": "^4.12.0",
8203
9208
"tslib": "^2.6.2"
8204
9209
},
8205
9210
"engines": {
8207
9212
}
8208
9213
},
8209
9214
"node_modules/@smithy/node-config-provider": {
8210
- "version": "4.3.7",
8211
- "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.7.tgz",
8212
- "integrity": "sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==",
9215
+ "version": "4.3.8",
9216
+ "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz",
9217
+ "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==",
8213
9218
"license": "Apache-2.0",
8214
9219
"dependencies": {
8215
- "@smithy/property-provider": "^4.2.7",
8216
- "@smithy/shared-ini-file-loader": "^4.4.2",
8217
- "@smithy/types": "^4.11.0",
9220
+ "@smithy/property-provider": "^4.2.8",
9221
+ "@smithy/shared-ini-file-loader": "^4.4.3",
9222
+ "@smithy/types": "^4.12.0",
8218
9223
"tslib": "^2.6.2"
8219
9224
},
8220
9225
"engines": {
8222
9227
}
8223
9228
},
8224
9229
"node_modules/@smithy/node-http-handler": {
8225
- "version": "4.4.7",
8226
- "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.7.tgz",
8227
- "integrity": "sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ==",
9230
+ "version": "4.4.8",
9231
+ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.8.tgz",
9232
+ "integrity": "sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg==",
8228
9233
"license": "Apache-2.0",
8229
9234
"dependencies": {
8230
- "@smithy/abort-controller": "^4.2.7",
8231
- "@smithy/protocol-http": "^5.3.7",
8232
- "@smithy/querystring-builder": "^4.2.7",
8233
- "@smithy/types": "^4.11.0",
9235
+ "@smithy/abort-controller": "^4.2.8",
9236
+ "@smithy/protocol-http": "^5.3.8",
9237
+ "@smithy/querystring-builder": "^4.2.8",
9238
+ "@smithy/types": "^4.12.0",
8234
9239
"tslib": "^2.6.2"
8235
9240
},
8236
9241
"engines": {
8238
9243
}
8239
9244
},
8240
9245
"node_modules/@smithy/property-provider": {
8241
- "version": "4.2.7",
8242
- "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.7.tgz",
8243
- "integrity": "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==",
9246
+ "version": "4.2.8",
9247
+ "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz",
9248
+ "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==",
8244
9249
"license": "Apache-2.0",
8245
9250
"dependencies": {
8246
- "@smithy/types": "^4.11.0",
9251
+ "@smithy/types": "^4.12.0",
8247
9252
"tslib": "^2.6.2"
8248
9253
},
8249
9254
"engines": {
8251
9256
}
8252
9257
},
8253
9258
"node_modules/@smithy/protocol-http": {
8254
- "version": "5.3.7",
8255
- "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.7.tgz",
8256
- "integrity": "sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA==",
9259
+ "version": "5.3.8",
9260
+ "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz",
9261
+ "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==",
8257
9262
"license": "Apache-2.0",
8258
9263
"dependencies": {
8259
- "@smithy/types": "^4.11.0",
9264
+ "@smithy/types": "^4.12.0",
8260
9265
"tslib": "^2.6.2"
8261
9266
},
8262
9267
"engines": {
8264
9269
}
8265
9270
},
8266
9271
"node_modules/@smithy/querystring-builder": {
8267
- "version": "4.2.7",
8268
- "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.7.tgz",
8269
- "integrity": "sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg==",
9272
+ "version": "4.2.8",
9273
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz",
9274
+ "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==",
8270
9275
"license": "Apache-2.0",
8271
9276
"dependencies": {
8272
- "@smithy/types": "^4.11.0",
9277
+ "@smithy/types": "^4.12.0",
8273
9278
"@smithy/util-uri-escape": "^4.2.0",
8274
9279
"tslib": "^2.6.2"
8275
9280
},
8278
9283
}
8279
9284
},
8280
9285
"node_modules/@smithy/querystring-parser": {
8281
- "version": "4.2.7",
8282
- "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.7.tgz",
8283
- "integrity": "sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w==",
9286
+ "version": "4.2.8",
9287
+ "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz",
9288
+ "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==",
8284
9289
"license": "Apache-2.0",
8285
9290
"dependencies": {
8286
- "@smithy/types": "^4.11.0",
9291
+ "@smithy/types": "^4.12.0",
8287
9292
"tslib": "^2.6.2"
8288
9293
},
8289
9294
"engines": {
8291
9296
}
8292
9297
},
8293
9298
"node_modules/@smithy/service-error-classification": {
8294
- "version": "4.2.7",
8295
- "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.7.tgz",
8296
- "integrity": "sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA==",
9299
+ "version": "4.2.8",
9300
+ "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz",
9301
+ "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==",
8297
9302
"license": "Apache-2.0",
8298
9303
"dependencies": {
8299
- "@smithy/types": "^4.11.0"
9304
+ "@smithy/types": "^4.12.0"
8300
9305
},
8301
9306
"engines": {
8302
9307
"node": ">=18.0.0"
8303
9308
}
8304
9309
},
8305
9310
"node_modules/@smithy/shared-ini-file-loader": {
8306
- "version": "4.4.2",
8307
- "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.2.tgz",
8308
- "integrity": "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==",
9311
+ "version": "4.4.3",
9312
+ "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz",
9313
+ "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==",
8309
9314
"license": "Apache-2.0",
8310
9315
"dependencies": {
8311
- "@smithy/types": "^4.11.0",
9316
+ "@smithy/types": "^4.12.0",
8312
9317
"tslib": "^2.6.2"
8313
9318
},
8314
9319
"engines": {
8316
9321
}
8317
9322
},
8318
9323
"node_modules/@smithy/signature-v4": {
8319
- "version": "5.3.7",
8320
- "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.7.tgz",
8321
- "integrity": "sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg==",
9324
+ "version": "5.3.8",
9325
+ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz",
9326
+ "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==",
8322
9327
"license": "Apache-2.0",
8323
9328
"dependencies": {
8324
9329
"@smithy/is-array-buffer": "^4.2.0",
8325
- "@smithy/protocol-http": "^5.3.7",
8326
- "@smithy/types": "^4.11.0",
9330
+ "@smithy/protocol-http": "^5.3.8",
9331
+ "@smithy/types": "^4.12.0",
8327
9332
"@smithy/util-hex-encoding": "^4.2.0",
8328
- "@smithy/util-middleware": "^4.2.7",
9333
+ "@smithy/util-middleware": "^4.2.8",
8329
9334
"@smithy/util-uri-escape": "^4.2.0",
8330
9335
"@smithy/util-utf8": "^4.2.0",
8331
9336
"tslib": "^2.6.2"
8335
9340
}
8336
9341
},
8337
9342
"node_modules/@smithy/smithy-client": {
8338
- "version": "4.10.2",
8339
- "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.2.tgz",
8340
- "integrity": "sha512-D5z79xQWpgrGpAHb054Fn2CCTQZpog7JELbVQ6XAvXs5MNKWf28U9gzSBlJkOyMl9LA1TZEjRtwvGXfP0Sl90g==",
9343
+ "version": "4.10.11",
9344
+ "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.11.tgz",
9345
+ "integrity": "sha512-6o804SCyHGMXAb5mFJ+iTy9kVKv7F91a9szN0J+9X6p8A0NrdpUxdaC57aye2ipQkP2C4IAqETEpGZ0Zj77Haw==",
8341
9346
"license": "Apache-2.0",
8342
9347
"dependencies": {
8343
- "@smithy/core": "^3.20.0",
8344
- "@smithy/middleware-endpoint": "^4.4.1",
8345
- "@smithy/middleware-stack": "^4.2.7",
8346
- "@smithy/protocol-http": "^5.3.7",
8347
- "@smithy/types": "^4.11.0",
8348
- "@smithy/util-stream": "^4.5.8",
9348
+ "@smithy/core": "^3.21.0",
9349
+ "@smithy/middleware-endpoint": "^4.4.10",
9350
+ "@smithy/middleware-stack": "^4.2.8",
9351
+ "@smithy/protocol-http": "^5.3.8",
9352
+ "@smithy/types": "^4.12.0",
9353
+ "@smithy/util-stream": "^4.5.10",
8349
9354
"tslib": "^2.6.2"
8350
9355
},
8351
9356
"engines": {
8353
9358
}
8354
9359
},
8355
9360
"node_modules/@smithy/types": {
8356
- "version": "4.11.0",
8357
- "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.11.0.tgz",
8358
- "integrity": "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==",
9361
+ "version": "4.12.0",
9362
+ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz",
9363
+ "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==",
8359
9364
"license": "Apache-2.0",
8360
9365
"dependencies": {
8361
9366
"tslib": "^2.6.2"
8365
9370
}
8366
9371
},
8367
9372
"node_modules/@smithy/url-parser": {
8368
- "version": "4.2.7",
8369
- "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.7.tgz",
8370
- "integrity": "sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg==",
9373
+ "version": "4.2.8",
9374
+ "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz",
9375
+ "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==",
8371
9376
"license": "Apache-2.0",
8372
9377
"dependencies": {
8373
- "@smithy/querystring-parser": "^4.2.7",
8374
- "@smithy/types": "^4.11.0",
9378
+ "@smithy/querystring-parser": "^4.2.8",
9379
+ "@smithy/types": "^4.12.0",
8375
9380
"tslib": "^2.6.2"
8376
9381
},
8377
9382
"engines": {
8432
9437
}
8433
9438
},
8434
9439
"node_modules/@smithy/util-defaults-mode-browser": {
8435
- "version": "4.3.16",
8436
- "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.16.tgz",
8437
- "integrity": "sha512-/eiSP3mzY3TsvUOYMeL4EqUX6fgUOj2eUOU4rMMgVbq67TiRLyxT7Xsjxq0bW3OwuzK009qOwF0L2OgJqperAQ==",
9440
+ "version": "4.3.25",
9441
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.25.tgz",
9442
+ "integrity": "sha512-8ugoNMtss2dJHsXnqsibGPqoaafvWJPACmYKxJ4E6QWaDrixsAemmiMMAVbvwYadjR0H9G2+AlzsInSzRi8PSw==",
8438
9443
"license": "Apache-2.0",
8439
9444
"dependencies": {
8440
- "@smithy/property-provider": "^4.2.7",
8441
- "@smithy/smithy-client": "^4.10.2",
8442
- "@smithy/types": "^4.11.0",
9445
+ "@smithy/property-provider": "^4.2.8",
9446
+ "@smithy/smithy-client": "^4.10.11",
9447
+ "@smithy/types": "^4.12.0",
8443
9448
"tslib": "^2.6.2"
8444
9449
},
8445
9450
"engines": {
8447
9452
}
8448
9453
},
8449
9454
"node_modules/@smithy/util-defaults-mode-node": {
8450
- "version": "4.2.19",
8451
- "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.19.tgz",
8452
- "integrity": "sha512-3a4+4mhf6VycEJyHIQLypRbiwG6aJvbQAeRAVXydMmfweEPnLLabRbdyo/Pjw8Rew9vjsh5WCdhmDaHkQnhhhA==",
9455
+ "version": "4.2.28",
9456
+ "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.28.tgz",
9457
+ "integrity": "sha512-mjUdcP8h3E0K/XvNMi9oBXRV3DMCzeRiYIieZ1LQ7jq5tu6GH/GTWym7a1xIIE0pKSoLcpGsaImuQhGPSIJzAA==",
8453
9458
"license": "Apache-2.0",
8454
9459
"dependencies": {
8455
- "@smithy/config-resolver": "^4.4.5",
8456
- "@smithy/credential-provider-imds": "^4.2.7",
8457
- "@smithy/node-config-provider": "^4.3.7",
8458
- "@smithy/property-provider": "^4.2.7",
8459
- "@smithy/smithy-client": "^4.10.2",
8460
- "@smithy/types": "^4.11.0",
9460
+ "@smithy/config-resolver": "^4.4.6",
9461
+ "@smithy/credential-provider-imds": "^4.2.8",
9462
+ "@smithy/node-config-provider": "^4.3.8",
9463
+ "@smithy/property-provider": "^4.2.8",
9464
+ "@smithy/smithy-client": "^4.10.11",
9465
+ "@smithy/types": "^4.12.0",
8461
9466
"tslib": "^2.6.2"
8462
9467
},
8463
9468
"engines": {
8465
9470
}
8466
9471
},
8467
9472
"node_modules/@smithy/util-endpoints": {
8468
- "version": "3.2.7",
8469
- "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.7.tgz",
8470
- "integrity": "sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg==",
9473
+ "version": "3.2.8",
9474
+ "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz",
9475
+ "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==",
8471
9476
"license": "Apache-2.0",
8472
9477
"dependencies": {
8473
- "@smithy/node-config-provider": "^4.3.7",
8474
- "@smithy/types": "^4.11.0",
9478
+ "@smithy/node-config-provider": "^4.3.8",
9479
+ "@smithy/types": "^4.12.0",
8475
9480
"tslib": "^2.6.2"
8476
9481
},
8477
9482
"engines": {
8489
9494
}
8490
9495
},
8491
9496
"node_modules/@smithy/util-middleware": {
8492
- "version": "4.2.7",
8493
- "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.7.tgz",
8494
- "integrity": "sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==",
9497
+ "version": "4.2.8",
9498
+ "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz",
9499
+ "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==",
8495
9500
"license": "Apache-2.0",
8496
9501
"dependencies": {
8497
- "@smithy/types": "^4.11.0",
9502
+ "@smithy/types": "^4.12.0",
8498
9503
"tslib": "^2.6.2"
8499
9504
},
8500
9505
"engines": {
8502
9507
}
8503
9508
},
8504
9509
"node_modules/@smithy/util-retry": {
8505
- "version": "4.2.7",
8506
- "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.7.tgz",
8507
- "integrity": "sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg==",
9510
+ "version": "4.2.8",
9511
+ "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz",
9512
+ "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==",
8508
9513
"license": "Apache-2.0",
8509
9514
"dependencies": {
8510
- "@smithy/service-error-classification": "^4.2.7",
8511
- "@smithy/types": "^4.11.0",
9515
+ "@smithy/service-error-classification": "^4.2.8",
9516
+ "@smithy/types": "^4.12.0",
8512
9517
"tslib": "^2.6.2"
8513
9518
},
8514
9519
"engines": {
8516
9521
}
8517
9522
},
8518
9523
"node_modules/@smithy/util-stream": {
8519
- "version": "4.5.8",
8520
- "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.8.tgz",
8521
- "integrity": "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w==",
9524
+ "version": "4.5.10",
9525
+ "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.10.tgz",
9526
+ "integrity": "sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g==",
8522
9527
"license": "Apache-2.0",
8523
9528
"dependencies": {
8524
- "@smithy/fetch-http-handler": "^5.3.8",
8525
- "@smithy/node-http-handler": "^4.4.7",
8526
- "@smithy/types": "^4.11.0",
9529
+ "@smithy/fetch-http-handler": "^5.3.9",
9530
+ "@smithy/node-http-handler": "^4.4.8",
9531
+ "@smithy/types": "^4.12.0",
8527
9532
"@smithy/util-base64": "^4.3.0",
8528
9533
"@smithy/util-buffer-from": "^4.2.0",
8529
9534
"@smithy/util-hex-encoding": "^4.2.0",
8556
9561
}
8557
9562
},
8558
9563
"node_modules/@smithy/util-waiter": {
8559
- "version": "4.2.5",
9564
+ "version": "4.2.8",
9565
+ "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.8.tgz",
9566
+ "integrity": "sha512-n+lahlMWk+aejGuax7DPWtqav8HYnWxQwR+LCG2BgCUmaGcTe9qZCFsmw8TMg9iG75HOwhrJCX9TCJRLH+Yzqg==",
8560
9567
"license": "Apache-2.0",
8561
9568
"dependencies": {
8562
- "@smithy/abort-controller": "^4.2.5",
8563
- "@smithy/types": "^4.9.0",
9569
+ "@smithy/abort-controller": "^4.2.8",
9570
+ "@smithy/types": "^4.12.0",
8564
9571
"tslib": "^2.6.2"
8565
9572
},
8566
9573
"engines": {
17231
18238
"url": "https://github.com/sponsors/sindresorhus"
17232
18239
}
17233
18240
},
18241
+ "node_modules/stream-browserify": {
18242
+ "version": "3.0.0",
18243
+ "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz",
18244
+ "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==",
18245
+ "license": "MIT",
18246
+ "dependencies": {
18247
+ "inherits": "~2.0.4",
18248
+ "readable-stream": "^3.5.0"
18249
+ }
18250
+ },
18251
+ "node_modules/stream-browserify/node_modules/readable-stream": {
18252
+ "version": "3.6.2",
18253
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
18254
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
18255
+ "license": "MIT",
18256
+ "dependencies": {
18257
+ "inherits": "^2.0.3",
18258
+ "string_decoder": "^1.1.1",
18259
+ "util-deprecate": "^1.0.1"
18260
+ },
18261
+ "engines": {
18262
+ "node": ">= 6"
18263
+ }
18264
+ },
17234
18265
"node_modules/streamx": {
17235
18266
"version": "2.23.0",
17236
18267
"license": "MIT",
17237
18268
▼package.json
+1-0
36
36
"@astrojs/tailwind": "^5.1.0",
37
37
"@astrojs/vercel": "^7.7.0",
38
38
"@aws-sdk/client-s3": "^3.948.0",
39
+ "@aws-sdk/lib-storage": "^3.972.0",
39
40
"@aws-sdk/s3-request-presigner": "^3.948.0",
40
41
"@libsql/client": "^0.15.15",
41
42
"@monaco-editor/react": "^4.6.0",
42
43
▼scripts/ssh-server.ts
+9-0
4
4
import { startSSHServer } from "@/lib/ssh";
5
5
import { and, eq } from "drizzle-orm";
6
6
import { join } from "path";
7
+import { uploadRepoToStorage, getStorageRepoPath, isCloudStorage } from "@/lib/git-storage";
7
8
8
9
const DATA_DIR = join(process.cwd(), "data");
9
10
const REPOS_DIR = join(DATA_DIR, "repos");
111
112
}
112
113
});
113
114
});
115
+ // 3. Sync to Cloud Storage (if enabled)
116
+ if (await isCloudStorage()) {
117
+ console.log(`[SSH] Syncing ${repoPath} to storage...`);
118
+ const storagePath = getStorageRepoPath(owner.username, repo.name);
119
+ const localRepoPath = join(REPOS_DIR, owner.username, `${repo.name}.git`);
120
+ await uploadRepoToStorage(localRepoPath, storagePath);
121
+ console.log(`[SSH] Sync complete.`);
122
+ }
114
123
}
115
124
}
116
125
});
117
126
▼src/components/dashboard/StackCard.astro
+92-0
1
+---
2
+interface Props {
3
+ stack: {
4
+ id: string;
5
+ name: string;
6
+ repoName: string;
7
+ repoOwner: string;
8
+ status: string;
9
+ updatedAt: string;
10
+ prs: {
11
+ id: string;
12
+ number: number;
13
+ title: string;
14
+ status: string;
15
+ url: string;
16
+ position: number;
17
+ isMerged: boolean;
18
+ isClosed: boolean;
19
+ }[];
20
+ };
21
+}
22
+
23
+const { stack } = Astro.props;
24
+
25
+// Determine stack status color
26
+const isFullyMerged = stack.prs.every(pr => pr.isMerged);
27
+const hasFailures = false; // TODO: Check CI status if available
28
+
29
+const statusColor = isFullyMerged ? "text-green-400" : hasFailures ? "text-red-400" : "text-blue-400";
30
+const statusBorder = isFullyMerged ? "border-green-500/30" : hasFailures ? "border-red-500/30" : "border-blue-500/30";
31
+const statusBg = isFullyMerged ? "bg-green-500/10" : hasFailures ? "bg-red-500/10" : "bg-blue-500/10";
32
+---
33
+
34
+<div class="relative group">
35
+ <div class="absolute -inset-[1px] bg-gradient-to-r from-blue-500/20 via-cyan-500/20 to-purple-500/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity" />
36
+ <div class="relative rounded-xl border border-white/10 bg-[#0d1117]/80 backdrop-blur-sm p-5 hover:bg-white/[0.02] transition-colors">
37
+
38
+ <!-- Header -->
39
+ <div class="flex items-center justify-between mb-4">
40
+ <div>
41
+ <div class="flex items-center gap-2">
42
+ <span class={`text-sm font-semibold ${statusColor}`}>{stack.name}</span>
43
+ <span class="text-xs text-gray-500">in {stack.repoOwner}/{stack.repoName}</span>
44
+ </div>
45
+ <div class="text-xs text-gray-600 mt-0.5">
46
+ Updated {new Date(stack.updatedAt).toLocaleDateString()}
47
+ </div>
48
+ </div>
49
+ <div class={`text-xs px-2 py-0.5 rounded-full border ${statusBorder} ${statusColor} ${statusBg}`}>
50
+ {isFullyMerged ? "Merged" : "Active"}
51
+ </div>
52
+ </div>
53
+
54
+ <!-- Stack Visualization (Subway Map) -->
55
+ <div class="relative pl-2 space-y-0">
56
+ {stack.prs.map((pr, index) => (
57
+ <div class="relative flex items-start gap-3 pb-4 last:pb-0">
58
+ <!-- Connecting Line -->
59
+ {index !== stack.prs.length - 1 && (
60
+ <div class="absolute left-[5px] top-2.5 bottom-0 w-0.5 bg-gray-800" />
61
+ )}
62
+
63
+ <!-- Dot -->
64
+ <div class={`relative z-10 w-3 h-3 rounded-full mt-1.5 border-2 ${
65
+ pr.isMerged
66
+ ? "bg-green-500 border-green-500"
67
+ : pr.isClosed
68
+ ? "bg-red-500 border-red-500"
69
+ : "bg-[#0d1117] border-blue-500"
70
+ }`} />
71
+
72
+ <!-- Content -->
73
+ <div class="flex-1 min-w-0 -mt-0.5">
74
+ <a href={pr.url} class="block hover:underline truncate text-sm font-medium text-gray-300 hover:text-blue-400 decoration-blue-400/50">
75
+ {pr.title}
76
+ </a>
77
+ <div class="flex items-center gap-2 text-xs text-gray-500">
78
+ <span class="font-mono">#{pr.number}</span>
79
+ <span>•</span>
80
+ <span class={
81
+ pr.isMerged ? "text-green-500" : pr.isClosed ? "text-red-500" : "text-blue-500"
82
+ }>
83
+ {pr.status.charAt(0).toUpperCase() + pr.status.slice(1)}
84
+ </span>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ ))}
89
+ </div>
90
+
91
+ </div>
92
+</div>
0
93
▼src/components/dashboard/UserAvatar.tsx
+21-0
1
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
2
+
3
+interface UserAvatarProps {
4
+ user: {
5
+ username: string;
6
+ displayName?: string | null;
7
+ avatarUrl?: string | null;
8
+ };
9
+ className?: string;
10
+}
11
+
12
+export function UserAvatar({ user, className }: UserAvatarProps) {
13
+ return (
14
+ <Avatar className={className}>
15
+ <AvatarImage src={user.avatarUrl || undefined} alt={user.username} />
16
+ <AvatarFallback className="bg-gradient-to-br from-cyan-500 to-blue-600 text-white text-xl">
17
+ {user.username[0].toUpperCase()}
18
+ </AvatarFallback>
19
+ </Avatar>
20
+ );
21
+}
0
22
▼src/db/schema/merge-queue.ts
+16-47
1
-/**
2
- * Merge Queue Schema
3
- * Stack-aware merge queue with CI optimization
4
- */
5
-
6
1
import { relations } from "drizzle-orm";
7
-import { boolean, integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
2
+import { integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
8
3
import { pullRequests } from "./pull-requests";
9
4
import { repositories } from "./repositories";
10
-import { users } from "./users";
11
-import { prStacks } from "./stacked-prs";
12
5
13
-// Merge queue entries
14
-export const mergeQueue = pgTable("merge_queue", {
6
+export const mergeQueueItems = pgTable("merge_queue_items", {
15
7
id: text("id").primaryKey(),
16
8
repositoryId: text("repository_id")
17
9
.notNull()
19
11
pullRequestId: text("pull_request_id")
20
12
.notNull()
21
13
.references(() => pullRequests.id, { onDelete: "cascade" }),
22
- stackId: text("stack_id").references(() => prStacks.id), // Stack-aware
23
14
24
- // Queue status
25
- status: text("status").notNull().default("pending"), // pending, testing, ready, merging, merged, failed
26
- priority: integer("priority").notNull().default(0), // Higher = merge first
27
- position: integer("position"), // Current position in queue
15
+ // Status tracking
16
+ status: text("status").notNull().default("queued"), // queued, running, failed, merged
28
17
29
- // CI integration
30
- ciStatus: text("ci_status").default("pending"), // pending, running, passed, failed
31
- ciRunId: text("ci_run_id"),
18
+ // Execution details
19
+ attemptCount: integer("attempt_count").default(0),
20
+ lastAttemptAt: timestamp("last_attempt_at"),
21
+ executionBranch: text("execution_branch"), // mq/pr-123-attempt-1
22
+ position: integer("position").default(0),
32
23
33
- // Timing
34
- addedById: text("added_by_id")
35
- .notNull()
36
- .references(() => users.id),
37
- addedAt: timestamp("added_at").notNull().defaultNow(),
38
- estimatedMergeAt: timestamp("estimated_merge_at"),
24
+ // Timestamps
25
+ queuedAt: timestamp("queued_at").notNull().defaultNow(),
39
26
startedAt: timestamp("started_at"),
40
27
completedAt: timestamp("completed_at"),
41
-
42
- // Error handling
43
- failureReason: text("failure_reason"),
44
- retryCount: integer("retry_count").default(0),
45
-
46
- // Merge options
47
- mergeMethod: text("merge_method").default("merge"), // merge, squash, rebase
48
- deleteOnMerge: boolean("delete_on_merge").default(true),
49
28
});
50
29
51
-// Relations
52
-export const mergeQueueRelations = relations(mergeQueue, ({ one }) => ({
30
+export const mergeQueueItemsRelations = relations(mergeQueueItems, ({ one }) => ({
53
31
repository: one(repositories, {
54
- fields: [mergeQueue.repositoryId],
32
+ fields: [mergeQueueItems.repositoryId],
55
33
references: [repositories.id],
56
34
}),
57
35
pullRequest: one(pullRequests, {
58
- fields: [mergeQueue.pullRequestId],
36
+ fields: [mergeQueueItems.pullRequestId],
59
37
references: [pullRequests.id],
60
38
}),
61
- stack: one(prStacks, {
62
- fields: [mergeQueue.stackId],
63
- references: [prStacks.id],
64
- }),
65
- addedBy: one(users, {
66
- fields: [mergeQueue.addedById],
67
- references: [users.id],
68
- }),
69
39
}));
70
40
71
-// Types
72
-export type MergeQueueEntry = typeof mergeQueue.$inferSelect;
73
-export type NewMergeQueueEntry = typeof mergeQueue.$inferInsert;
41
+export type MergeQueueItem = typeof mergeQueueItems.$inferSelect;
42
+export type NewMergeQueueItem = typeof mergeQueueItems.$inferInsert;
74
43
▼src/lib/analysis.ts
+13-3
3
3
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
4
4
import { logger } from "@/lib/logger";
5
5
import { getCommits, getRepoSize, listFiles, getCommitDiff } from "./git";
6
+import { resolveRepoPath } from "./git-storage";
6
7
import { eq } from "drizzle-orm";
7
8
import { extname } from "path";
8
9
46
47
logger.info({ repo: repo.name }, "Analyzing repository");
47
48
48
49
try {
50
+ // Resolve disk path for cloud storage
51
+ let repoPath: string;
52
+ try {
53
+ repoPath = await resolveRepoPath(repo.diskPath);
54
+ } catch (err) {
55
+ logger.warn({ repo: repo.name, err }, "Could not resolve repo path for analysis, skipping");
56
+ return;
57
+ }
58
+
49
59
// 2. Sync Commits (Last 20 to update recent history)
50
60
// In a full system we'd diff 'refs/heads/main' vs DB, but here we just fetch recent
51
- const commits = await getCommits(repo.diskPath, { limit: 20 });
61
+ const commits = await getCommits(repoPath, { limit: 20 });
52
62
let newCommitsCount = 0;
53
63
54
64
for (const c of commits) {
59
69
60
70
if (!exists) {
61
71
// Get diff stats
62
- const diffs = await getCommitDiff(repo.diskPath, c.sha);
72
+ const diffs = await getCommitDiff(repoPath, c.sha);
63
73
const additions = diffs.reduce((sum, d) => sum + d.additions, 0);
64
74
const deletions = diffs.reduce((sum, d) => sum + d.deletions, 0);
65
75
88
98
89
99
// 3. Analyze Languages
90
100
// Scan HEAD files
91
- const files = await listFiles(repo.diskPath, "HEAD");
101
+ const files = await listFiles(repoPath, "HEAD");
92
102
const langCounts: Record<string, number> = {};
93
103
94
104
for (const file of files) {
95
105
▼src/lib/auth-basic.ts
+26-15
19
19
where: eq(schema.users.username, username),
20
20
});
21
21
22
- if (!user || !user.passwordHash) return null;
22
+ if (!user) return null;
23
+
24
+ // Check if password looks like a Personal Access Token (och_xxx format)
25
+ if (password.startsWith("och_")) {
26
+ const pat = await db.query.personalAccessTokens.findFirst({
27
+ where: (tokens, { and, eq }) =>
28
+ and(eq(tokens.userId, user.id), eq(tokens.token, password)),
29
+ });
30
+
31
+ if (pat) {
32
+ // Update last used at
33
+ await db
34
+ .update(schema.personalAccessTokens)
35
+ .set({ lastUsedAt: new Date() })
36
+ .where(eq(schema.personalAccessTokens.id, pat.id));
37
+ console.log(`[validateBasicAuth] Authenticated ${username} via PAT`);
38
+ return user.id;
39
+ }
40
+ // Token format but not valid
41
+ return null;
42
+ }
23
43
24
- const isValid = await bcrypt.compare(password, user.passwordHash);
25
- if (isValid) return user.id;
44
+ // Fall back to password check
45
+ if (!user.passwordHash) return null;
26
46
27
- // Check Personal Access Token
28
- const pat = await db.query.personalAccessTokens.findFirst({
29
- where: (tokens, { and, eq }) =>
30
- and(eq(tokens.userId, user.id), eq(tokens.token, password)),
31
- });
32
-
33
- if (pat) {
34
- // Update last used at
35
- await db
36
- .update(schema.personalAccessTokens)
37
- .set({ lastUsedAt: new Date() })
38
- .where(eq(schema.personalAccessTokens.id, pat.id));
47
+ const isValid = await bcrypt.compare(password, user.passwordHash);
48
+ if (isValid) {
49
+ console.log(`[validateBasicAuth] Authenticated ${username} via password`);
39
50
return user.id;
40
51
}
41
52
42
53
▼src/lib/env-validation.ts
+40-0
63
63
required: false,
64
64
defaultValue: "./data/storage",
65
65
},
66
+ // S3 Storage (Required if STORAGE_TYPE=s3)
67
+ {
68
+ name: "STORAGE_BUCKET",
69
+ required: false,
70
+ },
71
+ {
72
+ name: "STORAGE_ENDPOINT",
73
+ required: false,
74
+ },
75
+ {
76
+ name: "STORAGE_ACCESS_KEY_ID",
77
+ required: false,
78
+ },
79
+ {
80
+ name: "STORAGE_SECRET_ACCESS_KEY",
81
+ required: false,
82
+ },
83
+ {
84
+ name: "STORAGE_REGION",
85
+ required: false,
86
+ defaultValue: "us-east-1",
87
+ },
88
+ // Rclone Storage (Required if STORAGE_TYPE=rclone)
89
+ {
90
+ name: "STORAGE_RCLONE_REMOTE",
91
+ required: false,
92
+ },
66
93
67
94
// Google Drive
68
95
{
177
204
if (!process.env.GOOGLE_FOLDER_ID) errors.push("❌ GOOGLE_FOLDER_ID is required when STORAGE_TYPE is 'gdrive'");
178
205
}
179
206
207
+ // S3 Validation
208
+ if (process.env.STORAGE_TYPE === "s3") {
209
+ if (!process.env.STORAGE_BUCKET) errors.push("❌ STORAGE_BUCKET is required when STORAGE_TYPE is 's3'");
210
+ if (!process.env.STORAGE_ACCESS_KEY_ID) errors.push("❌ STORAGE_ACCESS_KEY_ID is required when STORAGE_TYPE is 's3'");
211
+ if (!process.env.STORAGE_SECRET_ACCESS_KEY) errors.push("❌ STORAGE_SECRET_ACCESS_KEY is required when STORAGE_TYPE is 's3'");
212
+ // Endpoint might be optional for AWS S3 but usually required for others.
213
+ }
214
+
215
+ // Rclone Validation
216
+ if (process.env.STORAGE_TYPE === "rclone") {
217
+ if (!process.env.STORAGE_RCLONE_REMOTE) errors.push("❌ STORAGE_RCLONE_REMOTE is required when STORAGE_TYPE is 'rclone'");
218
+ }
219
+
180
220
// Print results
181
221
if (warnings.length > 0) {
182
222
console.log("\n⚠️ Warnings:");
183
223
▼src/lib/git-server.ts
+387-0
1
+
2
+import { spawn, ChildProcess } from "child_process";
3
+import { randomBytes } from "crypto";
4
+import { createReadStream, createWriteStream } from "fs";
5
+import * as fs from "fs/promises";
6
+import { join, dirname } from "path";
7
+import { PassThrough, Readable, Transform, Writable } from "stream";
8
+import { pipeline } from "stream/promises";
9
+import { getStorage } from "./storage";
10
+import { logger } from "./logger";
11
+
12
+// Packet Line Utilities
13
+export function pktLine(str: string): string {
14
+ const len = Buffer.byteLength(str, "utf8") + 4;
15
+ const hex = len.toString(16).padStart(4, "0");
16
+ return `${hex}${str}`;
17
+}
18
+
19
+export function flushPkt(): string {
20
+ return "0000";
21
+}
22
+
23
+// Git Service Types
24
+type GitService = "git-upload-pack" | "git-receive-pack";
25
+
26
+interface ServiceOptions {
27
+ repoPath: string; // Absolute path to bare repo
28
+ service: GitService;
29
+}
30
+
31
+/**
32
+ * Handles the info/refs request (Step 1 of push/pull)
33
+ */
34
+export async function getAdvertisedRefs(options: ServiceOptions): Promise<Buffer> {
35
+ const { repoPath, service } = options;
36
+ const serviceHeader = `# service=${service}\n`;
37
+ const header = pktLine(serviceHeader) + flushPkt();
38
+ const args = ["--stateless-rpc", "--advertise-refs", repoPath];
39
+ const cmd = service === "git-upload-pack" ? "upload-pack" : "receive-pack";
40
+
41
+ return new Promise((resolve, reject) => {
42
+ const child = spawn("git", [cmd, ...args]);
43
+ const chunks: Buffer[] = [];
44
+ chunks.push(Buffer.from(header, "utf8"));
45
+ child.stdout.on("data", (chunk) => chunks.push(chunk));
46
+ child.on("close", (code) => {
47
+ if (code === 0) resolve(Buffer.concat(chunks));
48
+ else reject(new Error(`Git ${cmd} exited with code ${code}`));
49
+ });
50
+ child.on("error", reject);
51
+ });
52
+}
53
+
54
+/**
55
+ * Handles git-upload-pack (Clone/Fetch)
56
+ */
57
+export async function handleUploadPack(
58
+ repoPath: string,
59
+ reqBody: Readable | ReadableStream
60
+): Promise<Readable> {
61
+ const nodeStream = isReadable(reqBody) ? reqBody : Readable.fromWeb(reqBody as any);
62
+ const child = spawn("git", ["upload-pack", "--stateless-rpc", repoPath], {
63
+ stdio: ["pipe", "pipe", "pipe"],
64
+ });
65
+ nodeStream.pipe(child.stdin);
66
+ return child.stdout;
67
+}
68
+
69
+class PackStreamProcessor extends Writable {
70
+ private initialized = false;
71
+ private storagePass?: PassThrough;
72
+ private indexPass?: PassThrough;
73
+ private uploadPromise?: Promise<void>;
74
+ private indexPack?: ChildProcess;
75
+ private indexPackPromise?: Promise<void>;
76
+ public packHash = "";
77
+
78
+ private tempIdxPath: string;
79
+ private storageTempKey: string;
80
+
81
+ constructor(
82
+ private repoPath: string,
83
+ private dbRepoPath: string,
84
+ private storage: any
85
+ ) {
86
+ super();
87
+ const tempId = randomBytes(8).toString('hex');
88
+ const tempPackName = `incoming-${tempId}.pack`;
89
+ this.storageTempKey = `${dbRepoPath}/objects/pack/${tempPackName}`;
90
+ this.tempIdxPath = join(process.cwd(), ".tmp", `incoming-${tempId}.idx`);
91
+ }
92
+
93
+ async cleanup() {
94
+ // Logic moved to handleReceivePack to avoid premature deletion
95
+ }
96
+
97
+ getFinalPaths(hash: string) {
98
+ return {
99
+ packKey: `${this.dbRepoPath}/objects/pack/pack-${hash}.pack`,
100
+ idxKey: `${this.dbRepoPath}/objects/pack/pack-${hash}.idx`,
101
+ idxPath: this.tempIdxPath,
102
+ tempKey: this.storageTempKey
103
+ };
104
+ }
105
+
106
+ _write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null) => void): void {
107
+ if (!this.initialized) {
108
+ this.initialized = true;
109
+ this.init(chunk).then(() => callback()).catch(callback);
110
+ } else {
111
+ this.storagePass?.write(chunk);
112
+ this.indexPass?.write(chunk);
113
+ callback();
114
+ }
115
+ }
116
+
117
+ private async init(firstChunk: any) {
118
+ await fs.mkdir(join(process.cwd(), ".tmp"), { recursive: true });
119
+
120
+ this.storagePass = new PassThrough();
121
+ this.indexPass = new PassThrough();
122
+
123
+ // Start Storage Upload
124
+ this.uploadPromise = this.storage.writeStream(this.storageTempKey, this.storagePass);
125
+
126
+ // Start Index Pack
127
+ // Note: --keep might cause packfile to be written to repo objects/pack directly if cwd is repoPath
128
+ this.indexPack = spawn("git", ["index-pack", "--stdin", "-v", "--fix-thin", "--keep", `--index-version=2`, `-o`, this.tempIdxPath], {
129
+ cwd: this.repoPath,
130
+ stdio: ['pipe', 'pipe', 'pipe']
131
+ });
132
+
133
+ this.indexPackPromise = new Promise<void>((resolve, reject) => {
134
+ let out = "";
135
+ this.indexPack!.stdout!.on('data', (d: any) => out += d.toString());
136
+ this.indexPack!.on('close', (code: number) => {
137
+ const match = out.trim().match(/([0-9a-f]{40})/);
138
+ if (code === 0 && match) {
139
+ this.packHash = match[1];
140
+ resolve();
141
+ } else {
142
+ reject(new Error(`index-pack failed code=${code} out=${out}`));
143
+ }
144
+ });
145
+ this.indexPack!.stderr!.on('data', (d: any) => logger.debug(`[index-pack] ${d}`));
146
+ this.indexPack!.on('error', reject);
147
+ });
148
+
149
+ // Pipe PassThroughs
150
+ this.storagePass.write(firstChunk);
151
+ this.indexPass.pipe(this.indexPack!.stdin!);
152
+ this.indexPass.write(firstChunk);
153
+ }
154
+
155
+ _final(callback: (error?: Error | null) => void): void {
156
+ if (this.initialized) {
157
+ this.storagePass?.end();
158
+ this.indexPass?.end();
159
+ Promise.all([this.uploadPromise, this.indexPackPromise])
160
+ .then(() => callback())
161
+ .catch((err) => callback(err as Error));
162
+ } else {
163
+ callback();
164
+ }
165
+ }
166
+}
167
+
168
+/**
169
+ * Handles git-receive-pack (Push)
170
+ */
171
+export async function handleReceivePack(
172
+ repoPath: string,
173
+ reqBody: Readable | ReadableStream,
174
+ dbRepoPath: string
175
+): Promise<Readable> {
176
+ const storage = await getStorage();
177
+ const inputStream = isReadable(reqBody) ? reqBody : Readable.fromWeb(reqBody as any);
178
+
179
+ const { commands, packs } = splitReceivePackStream(inputStream);
180
+
181
+ const commandLines: string[] = [];
182
+ const commandPromise = new Promise<void>((resolve, reject) => {
183
+ commands.on('data', (chunk) => commandLines.push(chunk.toString()));
184
+ commands.on('end', resolve);
185
+ commands.on('error', reject);
186
+ });
187
+
188
+ const packProcessor = new PackStreamProcessor(repoPath, dbRepoPath, storage);
189
+
190
+ await new Promise<void>((resolve, reject) => {
191
+ pipeline(packs, packProcessor).then(resolve).catch(reject);
192
+ });
193
+
194
+ await commandPromise;
195
+
196
+ if (packProcessor.packHash) {
197
+ const hash = packProcessor.packHash;
198
+ const { tempKey, packKey, idxKey, idxPath } = packProcessor.getFinalPaths(hash);
199
+
200
+ logger.info(`[ReceivePack] Pack hash: ${hash}`);
201
+
202
+ await storage.move(tempKey, packKey);
203
+ await storage.put(idxKey, await fs.readFile(idxPath));
204
+
205
+ // Local Object Availability Logic
206
+ const localPackDir = join(repoPath, "objects", "pack");
207
+ await fs.mkdir(localPackDir, { recursive: true });
208
+
209
+ const targetIdx = join(localPackDir, `pack-${hash}.idx`);
210
+ const targetPack = join(localPackDir, `pack-${hash}.pack`);
211
+ const sourcePack = idxPath.replace(".idx", ".pack");
212
+
213
+ // Packfile might already be at destination due to index-pack behavior
214
+ try {
215
+ await fs.access(targetPack);
216
+ } catch {
217
+ try { await fs.copyFile(sourcePack, targetPack); } catch { }
218
+ }
219
+
220
+ // Index file is definitely in .tmp
221
+ try { await fs.copyFile(idxPath, targetIdx); } catch { }
222
+
223
+ // Cleanup
224
+ try { await fs.unlink(idxPath); } catch { }
225
+ try { await fs.unlink(sourcePack); } catch { }
226
+
227
+ await updateRefs(repoPath, commandLines);
228
+ } else if (commandLines.length > 0) {
229
+ logger.info(`[ReceivePack] No pack, updating refs only.`);
230
+ await updateRefs(repoPath, commandLines);
231
+ }
232
+
233
+ return reportStatus(commandLines);
234
+}
235
+
236
+// Stateful PktLine Parser
237
+class PktLineDemuxer extends Transform {
238
+ private buffer: Buffer = Buffer.alloc(0);
239
+ private state: 'LENGTH' | 'DATA' | 'PACK' = 'LENGTH';
240
+ private remainingData: number = 0;
241
+
242
+ constructor(private options: { onCommand: (data: Buffer) => void, onPack: (data: Buffer) => void }) {
243
+ super();
244
+ }
245
+
246
+ _transform(chunk: Buffer, encoding: string, callback: () => void) {
247
+ if (this.state === 'PACK') {
248
+ this.options.onPack(chunk);
249
+ return callback();
250
+ }
251
+
252
+ this.buffer = Buffer.concat([this.buffer, chunk]);
253
+
254
+ while (true) {
255
+ if (this.state === 'LENGTH') {
256
+ if (this.buffer.length < 4) break;
257
+
258
+ const lenStr = this.buffer.slice(0, 4).toString('utf8');
259
+
260
+ if (lenStr === '0000') {
261
+ const rest = this.buffer.slice(4);
262
+ if (rest.length > 0) {
263
+ this.options.onPack(rest);
264
+ }
265
+ this.state = 'PACK';
266
+ this.buffer = Buffer.alloc(0);
267
+ return callback();
268
+ }
269
+
270
+ const len = parseInt(lenStr, 16);
271
+ if (isNaN(len)) {
272
+ return callback();
273
+ }
274
+
275
+ this.remainingData = len - 4;
276
+ this.buffer = this.buffer.slice(4);
277
+
278
+ if (this.remainingData === 0) {
279
+ this.state = 'LENGTH';
280
+ continue;
281
+ }
282
+
283
+ this.state = 'DATA';
284
+ }
285
+
286
+ if (this.state === 'DATA') {
287
+ if (this.buffer.length < this.remainingData) break;
288
+
289
+ const payload = this.buffer.slice(0, this.remainingData);
290
+ this.options.onCommand(payload);
291
+
292
+ this.buffer = this.buffer.slice(this.remainingData);
293
+ this.state = 'LENGTH';
294
+ }
295
+ }
296
+ callback();
297
+ }
298
+}
299
+
300
+function splitReceivePackStream(stream: Readable): { commands: Readable; packs: Readable } {
301
+ const commands = new PassThrough();
302
+ const packs = new PassThrough();
303
+ const demux = new PktLineDemuxer({
304
+ onCommand: (d) => commands.write(d),
305
+ onPack: (d) => packs.write(d)
306
+ });
307
+
308
+ stream.pipe(demux);
309
+
310
+ demux.on('finish', () => {
311
+ commands.end();
312
+ packs.end();
313
+ });
314
+
315
+ demux.on('error', (err) => {
316
+ commands.destroy(err);
317
+ packs.destroy(err);
318
+ });
319
+
320
+ return { commands, packs };
321
+}
322
+
323
+async function updateRefs(repoPath: string, commandLines: string[]) {
324
+ for (const line of commandLines) {
325
+ const parts = line.trim().split(' ');
326
+ if (parts.length < 3) continue;
327
+
328
+ const [oldSha, newSha, refName] = parts;
329
+ const cleanRefName = refName.split('\0')[0];
330
+
331
+ if (newSha === "0000000000000000000000000000000000000000") {
332
+ await runGit(repoPath, ["update-ref", "-d", cleanRefName, oldSha]);
333
+ } else if (oldSha === "0000000000000000000000000000000000000000") {
334
+ await runGit(repoPath, ["update-ref", cleanRefName, newSha]);
335
+ } else {
336
+ await runGit(repoPath, ["update-ref", cleanRefName, newSha, oldSha]);
337
+ }
338
+ }
339
+}
340
+
341
+function reportStatus(commandBuffs: string[], error?: string): Readable {
342
+ const s = new Readable();
343
+ s._read = () => { };
344
+
345
+ // Git receive-pack uses sideband protocol (band 2 for status messages)
346
+ const sidebandMsg = (msg: string): Buffer => {
347
+ const band = Buffer.from([0x02]); // Band 2 for progress/status
348
+ const text = Buffer.from(msg, 'utf8');
349
+ const data = Buffer.concat([band, text]);
350
+ const len = data.length + 4;
351
+ const hex = len.toString(16).padStart(4, '0');
352
+ return Buffer.concat([Buffer.from(hex, 'utf8'), data]);
353
+ };
354
+
355
+ s.push(sidebandMsg(error ? `unpack ${error}\n` : "unpack ok\n"));
356
+
357
+ const seen = new Set<string>();
358
+
359
+ for (const line of commandBuffs) {
360
+ const parts = line.trim().split(' ');
361
+ if (parts.length >= 3) {
362
+ const refName = parts[2].split('\0')[0];
363
+ if (!seen.has(refName)) {
364
+ s.push(sidebandMsg(error ? `ng ${refName} ${error}\n` : `ok ${refName}\n`));
365
+ seen.add(refName);
366
+ }
367
+ }
368
+ }
369
+ s.push(flushPkt());
370
+ s.push(null);
371
+ return s;
372
+}
373
+
374
+async function runGit(cwd: string, args: string[]): Promise<void> {
375
+ return new Promise((resolve, reject) => {
376
+ const child = spawn("git", args, { cwd });
377
+ child.on('close', (code) => {
378
+ if (code === 0) resolve();
379
+ else reject(new Error(`git ${args[0]} failed with code ${code}`));
380
+ });
381
+ child.on('error', reject);
382
+ });
383
+}
384
+
385
+function isReadable(stream: any): stream is Readable {
386
+ return stream instanceof Readable || (stream && typeof stream.on === 'function');
387
+}
0
388
▼src/lib/git-storage.ts
+162-7
13
13
import { join, relative } from "path";
14
14
import { Readable } from "stream";
15
15
import { getStorage, type StorageAdapter } from "./storage";
16
+import { initRepository } from "./git";
17
+import { logger } from "./logger";
16
18
17
19
// Cache for warm Vercel functions
18
20
const repoCache = new Map<string, { localPath: string; lastUsed: Date; modified: boolean }>();
72
74
// List all files in the storage path
73
75
const result = await storage.list({ prefix: storagePath });
74
76
77
+ // If no objects found, repo might not be initialized yet (lazy init)
78
+ // This is OK - just return with empty directory
79
+ if (!result.objects || result.objects.length === 0) {
80
+ console.log(`[downloadRepoFromStorage] No files found at ${storagePath} - repo may not be initialized yet`);
81
+ return;
82
+ }
83
+
75
84
// Download each file
76
85
for (const obj of result.objects) {
77
86
const relativePath = obj.key.replace(storagePath, "").replace(/^\//, "");
112
121
const storage = await getStorage();
113
122
console.log(`[uploadRepoToStorage] Starting upload from ${localPath} to ${storagePath}`);
114
123
124
+ // Check if directory exists
125
+ if (!existsSync(localPath)) {
126
+ console.error(`[uploadRepoToStorage] ERROR: Directory does not exist: ${localPath}`);
127
+ return;
128
+ }
129
+
130
+ // Check what's in the directory
131
+ const topLevelEntries = readdirSync(localPath, { withFileTypes: true });
132
+ console.log(`[uploadRepoToStorage] Found ${topLevelEntries.length} top-level entries: ${topLevelEntries.map(e => e.name).join(', ')}`);
133
+
115
134
let fileCount = 0;
116
135
async function uploadDir(dirPath: string): Promise<void> {
117
- const entries = readdirSync(dirPath, { withFileTypes: true });
136
+ let entries;
137
+ try {
138
+ entries = readdirSync(dirPath, { withFileTypes: true });
139
+ } catch (err) {
140
+ console.error(`[uploadRepoToStorage] Failed to read directory ${dirPath}:`, err);
141
+ return;
142
+ }
118
143
119
144
for (const entry of entries) {
120
145
const fullPath = join(dirPath, entry.name);
124
149
if (entry.isDirectory()) {
125
150
await uploadDir(fullPath);
126
151
} else {
127
- const content = await fs.readFile(fullPath);
128
- console.log(`[uploadRepoToStorage] Uploading: ${storageKey} (${content.length} bytes)`);
129
- await storage.put(storageKey, content);
130
- fileCount++;
152
+ try {
153
+ const content = await fs.readFile(fullPath);
154
+ console.log(`[uploadRepoToStorage] Uploading: ${storageKey} (${content.length} bytes)`);
155
+ await storage.put(storageKey, content);
156
+ fileCount++;
157
+ } catch (err) {
158
+ console.error(`[uploadRepoToStorage] Failed to upload ${fullPath}:`, err);
159
+ }
131
160
}
132
161
}
133
162
}
191
220
// Download from storage
192
221
const localPath = getLocalTempPath(owner, repoName);
193
222
194
- // Clean up any existing temp files
223
+ // Check if local directory already has a valid git repo
224
+ // This handles:
225
+ // 1. Race condition where upload is in progress but page loads
226
+ // 2. Recently created repos where async S3 upload hasn't finished
227
+ const headPath = join(localPath, 'HEAD');
228
+ if (existsSync(localPath) && existsSync(headPath)) {
229
+ console.log(`[acquireRepo] Local repo exists and has HEAD file, using existing: ${localPath}`);
230
+ // Add to cache so subsequent requests use it
231
+ repoCache.set(cacheKey, {
232
+ localPath,
233
+ lastUsed: new Date(),
234
+ modified: false,
235
+ });
236
+ return localPath;
237
+ }
238
+
239
+ // Clean up any existing temp files (only if not a valid git repo)
195
240
if (existsSync(localPath)) {
241
+ console.log(`[acquireRepo] Cleaning up incomplete temp directory: ${localPath}`);
196
242
rmSync(localPath, { recursive: true, force: true });
197
243
}
198
244
199
- await downloadRepoFromStorage(storagePath, localPath);
245
+ // Download from S3 (this might fail if async upload is still in progress)
246
+ try {
247
+ await downloadRepoFromStorage(storagePath, localPath);
248
+ } catch (err) {
249
+ console.error(`[acquireRepo] Failed to download from storage:`, err);
250
+ // If download fails, the repo might not be uploaded yet
251
+ // Try to use local git repos path as fallback
252
+ const gitReposPath = import.meta.env?.GIT_REPOS_PATH || process.env.GIT_REPOS_PATH;
253
+ const reposPath = gitReposPath
254
+ ? (path.isAbsolute(gitReposPath) ? gitReposPath : join(process.cwd(), gitReposPath))
255
+ : join(process.cwd(), "data", "repos");
256
+ const fallbackPath = join(reposPath, owner, `${repoName}.git`);
257
+
258
+ if (existsSync(fallbackPath) && existsSync(join(fallbackPath, 'HEAD'))) {
259
+ console.log(`[acquireRepo] Using fallback local path: ${fallbackPath}`);
260
+ // For local fallback, copy to temp location
261
+ // Remove target dir if exists, then copy
262
+ if (existsSync(localPath)) {
263
+ rmSync(localPath, { recursive: true, force: true });
264
+ }
265
+ // Create target directory
266
+ mkdirSync(localPath, { recursive: true });
267
+ const { execSync } = await import('child_process');
268
+ // Copy directory CONTENTS (using /. to copy contents, not the directory itself)
269
+ execSync(`cp -R "${fallbackPath}/." "${localPath}/"`, { stdio: 'inherit' });
270
+ console.log(`[acquireRepo] Successfully copied from fallback`);
271
+ } else {
272
+ console.error(`[acquireRepo] No fallback found at: ${fallbackPath}`);
273
+ throw new Error(`Repository not found in S3 and local fallback unavailable. S3 upload may still be in progress - try again in a moment.`);
274
+ }
275
+ }
276
+
277
+ // After download, log if repo was initialized
278
+ // headPath already declared at line 225
279
+ console.log(`[acquireRepo] After download - HEAD exists: ${existsSync(headPath)}`);
200
280
201
281
// Cache the path
202
282
repoCache.set(cacheKey, {
369
449
370
450
return acquireRepo(parsed.owner, parsed.repoName);
371
451
}
452
+
453
+/**
454
+ * Check if a repository has been initialized with Git
455
+ * Checks for the presence of HEAD file in both local and cloud storage
456
+ */
457
+export async function isRepoInitialized(owner: string, repoName: string): Promise<boolean> {
458
+ if (!(await isCloudStorage())) {
459
+ // Local: check if HEAD file exists
460
+ const diskPath = await getDiskPath(owner, repoName);
461
+ return existsSync(join(diskPath, 'HEAD'));
462
+ }
463
+
464
+ // Cloud: check if HEAD exists in S3
465
+ const storage = await getStorage();
466
+ const headPath = `${getStorageRepoPath(owner, repoName)}/HEAD`;
467
+ return await storage.exists(headPath);
468
+}
469
+
470
+/**
471
+ * Ensure repository is initialized, creating it if needed
472
+ * Thread-safe for concurrent first pushes (checks again after acquiring repo)
473
+ *
474
+ * @returns Local path to the initialized repository
475
+ */
476
+export async function ensureRepoInitialized(
477
+ owner: string,
478
+ repoName: string,
479
+ options: {
480
+ defaultBranch?: string;
481
+ readme?: boolean;
482
+ gitignoreTemplate?: string;
483
+ licenseType?: string;
484
+ repoName?: string;
485
+ ownerName?: string;
486
+ }
487
+): Promise<string> {
488
+ logger.info({ owner, repoName }, "Ensuring repo is initialized");
489
+
490
+ // Quick check if already initialized (avoid download if possible)
491
+ if (await isRepoInitialized(owner, repoName)) {
492
+ logger.info("Repo already initialized, acquiring");
493
+ return await acquireRepo(owner, repoName);
494
+ }
495
+
496
+ logger.info("Repo not initialized, initializing now");
497
+
498
+ // Initialize locally
499
+ const localPath = await initRepoInStorage(owner, repoName);
500
+
501
+ // Double-check in case of concurrent initialization
502
+ const headPath = join(localPath, 'HEAD');
503
+ if (existsSync(headPath)) {
504
+ logger.info("HEAD file exists (concurrent init?), using existing");
505
+ return localPath;
506
+ }
507
+
508
+ logger.info({ localPath }, "Running git init");
509
+ await initRepository(localPath, {
510
+ defaultBranch: options.defaultBranch || 'main',
511
+ readme: options.readme ?? false, // Don't add README on lazy init by default
512
+ gitignoreTemplate: options.gitignoreTemplate,
513
+ licenseType: options.licenseType,
514
+ repoName: options.repoName || repoName,
515
+ ownerName: options.ownerName || owner,
516
+ });
517
+
518
+ // Upload to cloud if needed
519
+ if (await isCloudStorage()) {
520
+ logger.info("Uploading initialized repo to cloud storage");
521
+ await finalizeRepoInit(owner, repoName);
522
+ }
523
+
524
+ logger.info("Repo initialization complete");
525
+ return localPath;
526
+}
372
527
▼src/lib/git.ts
+130-52
14
14
releaseRepo,
15
15
} from "@/lib/git-storage";
16
16
import { spawn } from "child_process";
17
-import { existsSync, mkdirSync, rmSync } from "fs";
17
+import { existsSync, mkdirSync, rmSync, readdirSync } from "fs";
18
18
import { basename, dirname, extname, join, resolve } from "path";
19
19
import { simpleGit, SimpleGit, SimpleGitOptions } from "simple-git";
20
20
116
116
ownerName = "Owner",
117
117
} = options;
118
118
119
+ console.log(`[initRepository] Starting initialization at: ${repoPath}`);
120
+
119
121
// repoPath should already be a valid local path (provided by caller)
120
122
const localRepoPath = repoPath;
121
123
122
- // Ensure directory exists
124
+ // Ensure parent directory exists
123
125
const dir = dirname(localRepoPath);
124
126
if (!existsSync(dir)) {
127
+ console.log(`[initRepository] Creating parent directory: ${dir}`);
125
128
mkdirSync(dir, { recursive: true });
126
129
}
127
130
131
+ // Ensure the repo directory itself exists
132
+ if (!existsSync(localRepoPath)) {
133
+ console.log(`[initRepository] Creating repo directory: ${localRepoPath}`);
134
+ mkdirSync(localRepoPath, { recursive: true });
135
+ }
136
+
128
137
// Initialize bare repository
129
- const git = simpleGit();
130
- await git.init(true, [localRepoPath]);
131
- await installHooks(localRepoPath);
138
+ try {
139
+ console.log(`[initRepository] Running git init --bare at: ${localRepoPath}`);
140
+ const git = simpleGit(localRepoPath);
141
+ await git.init(true);
142
+ console.log(`[initRepository] git init completed`);
143
+ } catch (err) {
144
+ console.error(`[initRepository] git init failed:`, err);
145
+ throw err;
146
+ }
147
+
148
+ // Install hooks
149
+ try {
150
+ console.log(`[initRepository] Installing hooks`);
151
+ await installHooks(localRepoPath);
152
+ console.log(`[initRepository] Hooks installed`);
153
+ } catch (err) {
154
+ console.error(`[initRepository] Hook installation failed:`, err);
155
+ throw err;
156
+ }
132
157
133
158
// If we need initial content, create a temporary working copy
134
159
if (readme || gitignoreTemplate || licenseType) {
135
160
const tempPath = `${localRepoPath}.tmp`;
161
+ console.log(`[initRepository] Creating temp working copy at: ${tempPath}`);
136
162
mkdirSync(tempPath, { recursive: true });
137
163
138
- const workingGit = simpleGit(tempPath);
139
- await workingGit.init();
140
- await workingGit.addConfig("user.name", ownerName);
141
- await workingGit.addConfig("user.email", "[email protected]");
142
-
143
- // Create README.md
144
- if (readme) {
145
- const readmeContent = `# ${repoName}\\n\\nA new repository created with OpenCodeHub.\\n`;
146
- const readmePath = join(tempPath, "README.md");
147
- const fs = await import("fs/promises");
148
- await fs.writeFile(readmePath, readmeContent);
149
- await workingGit.add("README.md");
150
- }
151
-
152
- // Create .gitignore
153
- if (gitignoreTemplate) {
154
- const gitignorePath = join(tempPath, ".gitignore");
155
- const gitignoreContent = getGitignoreTemplate(gitignoreTemplate);
156
- const fs = await import("fs/promises");
157
- await fs.writeFile(gitignorePath, gitignoreContent);
158
- await workingGit.add(".gitignore");
159
- }
164
+ try {
165
+ const workingGit = simpleGit(tempPath);
166
+ await workingGit.init();
167
+ await workingGit.addConfig("user.name", ownerName);
168
+ await workingGit.addConfig("user.email", "[email protected]");
169
+
170
+ // Create README.md
171
+ if (readme) {
172
+ const readmeContent = `# ${repoName}\n\nA new repository created with OpenCodeHub.\n`;
173
+ const readmePath = join(tempPath, "README.md");
174
+ const fs = await import("fs/promises");
175
+ await fs.writeFile(readmePath, readmeContent);
176
+ await workingGit.add("README.md");
177
+ console.log(`[initRepository] Created README.md`);
178
+ }
160
179
161
- // Create LICENSE
162
- if (licenseType) {
163
- const licensePath = join(tempPath, "LICENSE");
164
- const licenseContent = getLicenseTemplate(licenseType, ownerName);
165
- if (licenseContent) {
180
+ // Create .gitignore
181
+ if (gitignoreTemplate) {
182
+ const gitignorePath = join(tempPath, ".gitignore");
183
+ const gitignoreContent = getGitignoreTemplate(gitignoreTemplate);
166
184
const fs = await import("fs/promises");
167
- await fs.writeFile(licensePath, licenseContent);
168
- await workingGit.add("LICENSE");
185
+ await fs.writeFile(gitignorePath, gitignoreContent);
186
+ await workingGit.add(".gitignore");
187
+ console.log(`[initRepository] Created .gitignore`);
169
188
}
170
- }
171
189
172
- // Create initial commit
173
- await workingGit.commit("Initial commit");
190
+ // Create LICENSE
191
+ if (licenseType) {
192
+ const licensePath = join(tempPath, "LICENSE");
193
+ const licenseContent = getLicenseTemplate(licenseType, ownerName);
194
+ if (licenseContent) {
195
+ const fs = await import("fs/promises");
196
+ await fs.writeFile(licensePath, licenseContent);
197
+ await workingGit.add("LICENSE");
198
+ console.log(`[initRepository] Created LICENSE`);
199
+ }
200
+ }
174
201
175
- // Rename branch to default
176
- if (defaultBranch !== "master") {
177
- await workingGit.branch(["-M", defaultBranch]);
178
- }
202
+ // Create initial commit
203
+ await workingGit.commit("Initial commit");
204
+ console.log(`[initRepository] Created initial commit`);
205
+
206
+ // Rename branch to default
207
+ if (defaultBranch !== "master") {
208
+ await workingGit.branch(["-M", defaultBranch]);
209
+ }
179
210
180
- // Push to bare repository
181
- await workingGit.addRemote("origin", localRepoPath);
182
- await workingGit.push("origin", defaultBranch);
211
+ // Push to bare repository
212
+ await workingGit.addRemote("origin", localRepoPath);
213
+ await workingGit.push("origin", defaultBranch);
214
+ console.log(`[initRepository] Pushed to bare repo`);
183
215
184
- // Clean up temp directory
185
- rmSync(tempPath, { recursive: true, force: true });
216
+ // Clean up temp directory
217
+ rmSync(tempPath, { recursive: true, force: true });
218
+ console.log(`[initRepository] Cleaned up temp directory`);
219
+ } catch (err) {
220
+ console.error(`[initRepository] Working copy operations failed:`, err);
221
+ // Clean up temp if it exists
222
+ if (existsSync(tempPath)) {
223
+ rmSync(tempPath, { recursive: true, force: true });
224
+ }
225
+ throw err;
226
+ }
186
227
}
187
228
188
229
// Set default branch in bare repo
189
- const bareGit = simpleGit(localRepoPath);
190
- await bareGit.raw(["symbolic-ref", "HEAD", `refs/heads/${defaultBranch}`]);
230
+ try {
231
+ const bareGit = simpleGit(localRepoPath);
232
+ await bareGit.raw(["symbolic-ref", "HEAD", `refs/heads/${defaultBranch}`]);
233
+ console.log(`[initRepository] Set default branch to ${defaultBranch}`);
234
+ } catch (err) {
235
+ console.error(`[initRepository] Failed to set default branch:`, err);
236
+ throw err;
237
+ }
238
+
239
+ // Verify files were created
240
+ const files = readdirSync(localRepoPath);
241
+ console.log(`[initRepository] Completed. Files in repo: ${files.join(", ")}`);
191
242
}
192
243
193
244
/**
592
643
593
644
for (const line of output.trim().split("\n").filter(Boolean)) {
594
645
const [name, sha] = line.split("|");
646
+
647
+ // Filter out HEAD and other invalid branch names
648
+ if (!name || name === 'HEAD' || name.includes('HEAD')) {
649
+ continue;
650
+ }
651
+
595
652
branches.push({
596
653
name,
597
654
sha,
860
917
base: string,
861
918
head: string
862
919
): Promise<{ commits: CommitInfo[]; diffs: DiffInfo[] }> {
920
+ // Verify branches exist first to avoid "ambiguous argument" errors
921
+ const fullBase = base.startsWith('refs/') ? base : `refs/heads/${base}`;
922
+ const fullHead = head.startsWith('refs/') ? head : `refs/heads/${head}`;
923
+
924
+ const git = getGit(repoPath);
925
+
926
+ try {
927
+ await git.raw(['rev-parse', '--verify', fullBase]);
928
+ } catch {
929
+ logger.warn({ base, fullBase }, "Base branch not found in compareBranches");
930
+ return { commits: [], diffs: [] };
931
+ }
932
+
933
+ try {
934
+ await git.raw(['rev-parse', '--verify', fullHead]);
935
+ } catch {
936
+ // Only warn if head is missing but base exists (uncommon but possible)
937
+ logger.warn({ head, fullHead }, "Head branch not found in compareBranches");
938
+ return { commits: [], diffs: [] };
939
+ }
940
+
863
941
try {
864
942
// Get commits between base and head
865
943
// git log base..head
866
944
const commits = await getCommits(repoPath, {
867
- ref: `${base}..${head}`,
945
+ ref: `${fullBase}..${fullHead}`,
868
946
limit: 100, // Limit to prevent overload
869
947
});
870
948
871
949
// Get diff stats
872
950
// git diff --numstat base...head (triple dot for merge base comparison)
873
- const git = getGit(repoPath);
874
951
const output = await git.raw([
875
952
"diff",
876
953
"--numstat",
877
- `${base}...${head}`, // Triple dot finds merge base automatically
954
+ `${fullBase}...${fullHead}`, // Triple dot finds merge base automatically
878
955
]);
879
956
880
957
const diffs: DiffInfo[] = [];
896
973
}
897
974
898
975
return { commits, diffs };
899
- } catch (error) {
976
+ } catch (error: any) {
977
+ // Fallback for any other errors
900
978
logger.error({ err: error }, "Error comparing branches");
901
979
return { commits: [], diffs: [] };
902
980
}
903
981
▼src/lib/queue-worker.ts
+134-0
1
+import { getDatabase, schema } from "@/db";
2
+import { eq, and, asc } from "drizzle-orm";
3
+import { logger } from "@/lib/logger";
4
+import type { NodePgDatabase } from "drizzle-orm/node-postgres";
5
+import { mergeBranch } from "@/lib/git";
6
+import { acquireRepo, releaseRepo } from "@/lib/git-storage";
7
+
8
+export class QueueWorker {
9
+ private isProcessing = false;
10
+
11
+ async processQueue(repositoryId: string) {
12
+ if (this.isProcessing) return;
13
+ this.isProcessing = true;
14
+
15
+ const db = getDatabase() as NodePgDatabase<typeof schema>;
16
+
17
+ try {
18
+ // 1. Check if anything is currently running
19
+ const runningItem = await db.query.mergeQueueItems.findFirst({
20
+ where: and(
21
+ eq(schema.mergeQueueItems.repositoryId, repositoryId),
22
+ eq(schema.mergeQueueItems.status, "running")
23
+ ),
24
+ });
25
+
26
+ if (runningItem) {
27
+ // In a real system, we'd check if the CI job is still active.
28
+ // For MVP, we'll assume the previous run crashed if it's been running too long (>10m)
29
+ const runtime = Date.now() - (runningItem.startedAt?.getTime() || 0);
30
+ if (runtime > 10 * 60 * 1000) {
31
+ logger.warn(`Queue item ${runningItem.id} timed out. Marking failed.`);
32
+ await db.update(schema.mergeQueueItems)
33
+ .set({ status: "failed", completedAt: new Date() })
34
+ .where(eq(schema.mergeQueueItems.id, runningItem.id));
35
+ // Continue to next item
36
+ } else {
37
+ return;
38
+ }
39
+ }
40
+
41
+ // 2. Pick next item
42
+ const nextItem = await db.query.mergeQueueItems.findFirst({
43
+ where: and(
44
+ eq(schema.mergeQueueItems.repositoryId, repositoryId),
45
+ eq(schema.mergeQueueItems.status, "queued")
46
+ ),
47
+ orderBy: [asc(schema.mergeQueueItems.queuedAt)],
48
+ with: {
49
+ repository: {
50
+ with: { owner: true }
51
+ },
52
+ pullRequest: true
53
+ }
54
+ });
55
+
56
+ if (!nextItem) {
57
+ return;
58
+ }
59
+
60
+ // 3. Start processing
61
+ await db.update(schema.mergeQueueItems)
62
+ .set({
63
+ status: "running",
64
+ startedAt: new Date(),
65
+ attemptCount: (nextItem.attemptCount || 0) + 1,
66
+ lastAttemptAt: new Date()
67
+ })
68
+ .where(eq(schema.mergeQueueItems.id, nextItem.id));
69
+
70
+ logger.info(`Starting merge for PR ${nextItem.pullRequestId} (${nextItem.repository.owner.username}/${nextItem.repository.name})`);
71
+
72
+ // 4. Execution
73
+ // A. Acquire Repo (Local or Cloud)
74
+ const repoPath = await acquireRepo(nextItem.repository.owner.username, nextItem.repository.name);
75
+
76
+ // B. Simulate CI - In a real world, we'd trigger a workflow here and exit, waiting for a webhook callback.
77
+ // For MVP "Flesh out", we simulate 'running tests' on the merge result.
78
+
79
+ // Attempt Merge Locally first to check conflicts
80
+ const result = await mergeBranch(repoPath, nextItem.pullRequest.baseBranch, nextItem.pullRequest.headBranch);
81
+
82
+ // Simulate Build Time
83
+ await new Promise(r => setTimeout(r, 5000));
84
+
85
+ if (result.success) {
86
+ // Success!
87
+ // 1. Update PR Status
88
+ const now = new Date();
89
+ await db.update(schema.pullRequests)
90
+ .set({
91
+ state: "merged",
92
+ isMerged: true,
93
+ mergedAt: now,
94
+ // mergedById: system user?
95
+ updatedAt: now,
96
+ })
97
+ .where(eq(schema.pullRequests.id, nextItem.pullRequestId));
98
+
99
+ // 2. Update Queue Item
100
+ await db.update(schema.mergeQueueItems)
101
+ .set({ status: "merged", completedAt: now })
102
+ .where(eq(schema.mergeQueueItems.id, nextItem.id));
103
+
104
+ // 3. Sync Logic (Release Repo with modified=true)
105
+ // Since mergeBranch modified the repo (update-ref), we must push back.
106
+ await releaseRepo(nextItem.repository.owner.username, nextItem.repository.name, true);
107
+
108
+ logger.info(`PR ${nextItem.pullRequestId} merged successfully via queue.`);
109
+ } else {
110
+ // Failure (Conflict)
111
+ await db.update(schema.mergeQueueItems)
112
+ .set({ status: "failed", completedAt: new Date() })
113
+ .where(eq(schema.mergeQueueItems.id, nextItem.id));
114
+
115
+ // Release without sync (no changes to base, strict speaking merge-tree doesn't touch base if fail?
116
+ // Actually mergeBranch implementation might have failed before update-ref or during conflict check.
117
+ await releaseRepo(nextItem.repository.owner.username, nextItem.repository.name, false);
118
+
119
+ logger.warn(`PR ${nextItem.pullRequestId} failed to merge: ${result.message}`);
120
+ }
121
+
122
+ } catch (error) {
123
+ logger.error("Error processing merge queue", error);
124
+ // Try to release repo if stuck?
125
+ // We don't have scope here easily, but acquireRepo is safe to re-acquire (cleans up).
126
+ } finally {
127
+ this.isProcessing = false;
128
+ // Trigger next immediately?
129
+ // this.processQueue(repositoryId);
130
+ }
131
+ }
132
+}
133
+
134
+export const queueWorker = new QueueWorker();
0
135
▼src/lib/storage.ts
+69-15
90
90
91
91
abstract put(
92
92
key: string,
93
- data: Buffer | Readable,
93
+ data: Buffer | Readable | ReadableStream,
94
94
options?: PutOptions
95
95
): Promise<void>;
96
+
97
+ async writeStream(
98
+ key: string,
99
+ stream: Readable | ReadableStream,
100
+ options?: PutOptions
101
+ ): Promise<void> {
102
+ return this.put(key, stream, options);
103
+ }
104
+
96
105
abstract get(key: string, options?: GetOptions): Promise<Buffer>;
97
106
abstract getStream(key: string, options?: GetOptions): Promise<Readable>;
98
107
abstract delete(key: string): Promise<void>;
115
124
116
125
async put(
117
126
key: string,
118
- data: Buffer | Readable,
127
+ data: Buffer | Readable | ReadableStream,
119
128
options?: PutOptions
120
129
): Promise<void> {
121
130
const fullPath = this.getFullPath(key);
125
134
await fs.writeFile(fullPath, data);
126
135
} else {
127
136
const writeStream = createWriteStream(fullPath);
137
+ // @ts-ignore
128
138
await pipeline(data, writeStream);
129
139
}
130
140
318
328
await client.send(
319
329
new PutObjectCommand({
320
330
Bucket: this.config.bucket,
321
- Key: key,
331
+ Key: path.join(this.config.basePath, key),
322
332
Body: data,
323
333
ContentType: options?.contentType,
324
334
Metadata: options?.metadata,
325
335
CacheControl: options?.cacheControl,
326
- ContentDisposition: options?.contentDisposition,
327
336
})
328
337
);
329
338
}
330
339
340
+ async writeStream(
341
+ key: string,
342
+ stream: Readable | ReadableStream,
343
+ options?: PutOptions
344
+ ): Promise<void> {
345
+ const { Upload } = await import("@aws-sdk/lib-storage");
346
+ const client = await this.getClient();
347
+
348
+ const upload = new Upload({
349
+ client,
350
+ params: {
351
+ Bucket: this.config.bucket,
352
+ Key: path.join(this.config.basePath, key),
353
+ Body: stream,
354
+ ContentType: options?.contentType,
355
+ Metadata: options?.metadata,
356
+ CacheControl: options?.cacheControl,
357
+ ContentDisposition: options?.contentDisposition,
358
+ },
359
+ queueSize: 4, // Concurrent upload parts
360
+ partSize: 5 * 1024 * 1024, // 5MB chunks
361
+ });
362
+
363
+ await upload.done();
364
+ }
365
+
331
366
async get(key: string, options?: GetOptions): Promise<Buffer> {
332
367
const { GetObjectCommand } = await import("@aws-sdk/client-s3");
333
368
const client = await this.getClient();
335
370
const response = await client.send(
336
371
new GetObjectCommand({
337
372
Bucket: this.config.bucket,
338
- Key: key,
373
+ Key: path.join(this.config.basePath, key),
339
374
Range: options?.range
340
375
? `bytes=${options.range.start}-${options.range.end}`
341
376
: undefined,
356
391
const response = await client.send(
357
392
new GetObjectCommand({
358
393
Bucket: this.config.bucket,
359
- Key: key,
394
+ Key: path.join(this.config.basePath, key),
360
395
Range: options?.range
361
396
? `bytes=${options.range.start}-${options.range.end}`
362
397
: undefined,
373
408
await client.send(
374
409
new DeleteObjectCommand({
375
410
Bucket: this.config.bucket,
376
- Key: key,
411
+ Key: path.join(this.config.basePath, key),
377
412
})
378
413
);
379
414
}
386
421
await client.send(
387
422
new HeadObjectCommand({
388
423
Bucket: this.config.bucket,
389
- Key: key,
424
+ Key: path.join(this.config.basePath, key),
390
425
})
391
426
);
392
427
return true;
428
463
const { CopyObjectCommand } = await import("@aws-sdk/client-s3");
429
464
const client = await this.getClient();
430
465
466
+ // CopySource must be key, potentially URL encoded
467
+ const sourceFullPath = path.join(this.config.basePath, sourceKey);
468
+ const copySource = `${this.config.bucket}/${encodeURIComponent(sourceFullPath)}`;
469
+
431
470
await client.send(
432
471
new CopyObjectCommand({
433
472
Bucket: this.config.bucket,
434
- CopySource: `${this.config.bucket}/${path.join(
435
- this.config.basePath,
436
- sourceKey
437
- )}`,
473
+ CopySource: copySource,
438
474
Key: path.join(this.config.basePath, destKey),
439
475
})
440
476
);
548
584
await fs.writeFile(tempFile, data);
549
585
} else {
550
586
const writeStream = createWriteStream(tempFile);
551
- await pipeline(data, writeStream);
587
+ // @ts-ignore
588
+ await pipeline(Readable.from(data as any), writeStream);
552
589
}
553
590
554
591
// Use rclone copyto for single file
558
595
}
559
596
}
560
597
598
+ async writeStream(
599
+ key: string,
600
+ stream: Readable | ReadableStream,
601
+ options?: PutOptions
602
+ ): Promise<void> {
603
+ return this.put(key, stream, options); // defined above to handle streams via temp file
604
+ }
605
+
561
606
async get(key: string, options?: GetOptions): Promise<Buffer> {
562
607
const tempDir = path.join(process.cwd(), 'data', 'temp');
563
608
await fs.mkdir(tempDir, { recursive: true });
1194
1239
}
1195
1240
1196
1241
if (!storageInstance) {
1242
+ const storageType = (import.meta.env?.STORAGE_TYPE || process.env.STORAGE_TYPE || "local") as StorageConfig["type"];
1243
+
1244
+ // BasePath should be empty for cloud storage (S3, GCS, etc)
1245
+ // Only use directory path for local storage
1246
+ let basePath = "";
1247
+ if (storageType === "local") {
1248
+ basePath = import.meta.env?.STORAGE_PATH || process.env.STORAGE_PATH || "./data/storage";
1249
+ }
1250
+
1197
1251
const config: StorageConfig = {
1198
- type: (import.meta.env?.STORAGE_TYPE || process.env.STORAGE_TYPE || "local") as StorageConfig["type"],
1199
- basePath: import.meta.env?.STORAGE_PATH || process.env.STORAGE_PATH || "./data/storage",
1252
+ type: storageType,
1253
+ basePath,
1200
1254
bucket: import.meta.env?.STORAGE_BUCKET || process.env.STORAGE_BUCKET,
1201
1255
region: import.meta.env?.STORAGE_REGION || process.env.STORAGE_REGION,
1202
1256
endpoint: import.meta.env?.STORAGE_ENDPOINT || process.env.STORAGE_ENDPOINT,
1203
1257
▼src/middleware.ts
+4-1
52
52
context.cookies.delete("och_session", { path: "/" });
53
53
}
54
54
} else {
55
- console.log(`[Middleware] No token found for request: ${context.url.pathname}`);
55
+ // Only log warning for non-git requests, as git uses Basic Auth handled by backend
56
+ if (!context.url.pathname.endsWith(".git") && !context.url.pathname.includes(".git/")) {
57
+ console.log(`[Middleware] No token found for request: ${context.url.pathname}`);
58
+ }
56
59
}
57
60
58
61
// Protected routes logic
59
62
▼src/pages/[owner]/[repo].git/[...path].ts
+19-6
34
34
35
35
// 2. Auth Check
36
36
const authHeader = request.headers.get("Authorization");
37
+ console.log(`[GitBackend] Auth check for ${ownerName}/${repoName}: authHeader=${authHeader ? 'present' : 'missing'}`);
37
38
let userId: string | null = null;
38
39
if (authHeader) {
39
40
userId = await validateBasicAuth(authHeader);
41
+ console.log(`[GitBackend] Auth result: userId=${userId || 'null'}`);
40
42
}
41
43
42
44
// 3. Handle Routes
149
151
// Handle process exit
150
152
child.on("close", async (code) => {
151
153
if (code === 0) {
154
+ // Trigger Analysis on successful push
152
155
// Trigger Analysis on successful push
153
156
if (!isUpload) { // git-receive-pack
154
157
try {
155
- // SYNC BACK TO R2
156
- await releaseRepo(ownerName, repoName, true);
157
- console.log(`Synced ${ownerName}/${repoName} back to R2 after push`);
158
+ console.log(`[GitBackend] Push successful, triggering analysis for ${ownerName}/${repoName}`);
158
159
159
- // Trigger Analysis
160
+ // Trigger Analysis FIRST (while files are still locally present)
160
161
const { analyzeRepository } = await import("@/lib/analysis");
161
- await analyzeRepository(repoData.id, userId).catch(console.error);
162
+ await analyzeRepository(repoData.id, userId).catch((err) => {
163
+ console.error(`[GitBackend] Analysis failed for ${ownerName}/${repoName}:`, err);
164
+ });
165
+ console.log(`[GitBackend] Analysis completed for ${ownerName}/${repoName}`);
162
166
} catch (err) {
163
- console.error("Failed to sync/analyze after push:", err);
167
+ console.error("Failed to trigger analysis after push:", err);
168
+ } finally {
169
+ // SYNC BACK TO R2 (Upload and Cleanup)
170
+ // Always run this even if analysis fails
171
+ try {
172
+ await releaseRepo(ownerName, repoName, true);
173
+ console.log(`Synced ${ownerName}/${repoName} back to R2 after push`);
174
+ } catch (cleanupErr) {
175
+ console.error(`[GitBackend] Failed to clean up ${ownerName}/${repoName}:`, cleanupErr);
176
+ }
164
177
}
165
178
}
166
179
}
167
180
▼src/pages/[owner]/[repo].git/index.ts
+23-0
1
+import type { APIRoute } from "astro";
2
+
3
+/**
4
+ * Redirect /owner/repo.git to /owner/repo
5
+ * This handles the case when a user visits the .git URL directly in a browser
6
+ */
7
+export const GET: APIRoute = async ({ params, redirect }) => {
8
+ const { owner, repo } = params;
9
+
10
+ if (!owner || !repo) {
11
+ return new Response("Not Found", { status: 404 });
12
+ }
13
+
14
+ // Remove .git suffix from repo name if present
15
+ const repoName = repo.endsWith(".git") ? repo.slice(0, -4) : repo;
16
+
17
+ return redirect(`/${owner}/${repoName}`, 302);
18
+};
19
+
20
+// Also handle POST for git operations that come without a path
21
+export const POST: APIRoute = async ({ params }) => {
22
+ return new Response("Bad Request", { status: 400 });
23
+};
0
24
▼src/pages/[owner]/[repo]/commit/[sha].astro
+171-0
1
+---
2
+import BaseLayout from "@/layouts/BaseLayout.astro";
3
+import { getDatabase, schema } from "@/db";
4
+import { eq, and } from "drizzle-orm";
5
+import { resolveRepoPath } from "@/lib/git-storage";
6
+import { getCommitDiff } from "@/lib/git";
7
+import RepoHeader from "@/components/repo/RepoHeader.astro";
8
+import { canReadRepo } from "@/lib/permissions";
9
+
10
+const { owner: ownerName, repo: repoName,sha } = Astro.params;
11
+const db = getDatabase();
12
+
13
+// Fetch Repo & Owner
14
+const user = await db.query.users.findFirst({
15
+ where: eq(schema.users.username, ownerName!),
16
+});
17
+
18
+if (!user) return Astro.redirect("/404");
19
+
20
+const repoData = await db.query.repositories.findFirst({
21
+ where: and(
22
+ eq(schema.repositories.ownerId, user.id),
23
+ eq(schema.repositories.name, repoName!),
24
+ ),
25
+ with: {
26
+ owner: true,
27
+ },
28
+});
29
+
30
+if (!repoData) return Astro.redirect("/404");
31
+
32
+// Check permissions
33
+const currentUser = Astro.locals.user;
34
+const hasAccess = await canReadRepo(currentUser?.id, repoData);
35
+
36
+if (!hasAccess) {
37
+ return Astro.redirect("/404");
38
+}
39
+
40
+const repo = {
41
+ owner: repoData.owner.username,
42
+ name: repoData.name,
43
+ description: repoData.description,
44
+ visibility: repoData.visibility,
45
+ defaultBranch: repoData.defaultBranch,
46
+ stars: repoData.starCount,
47
+ forks: repoData.forkCount,
48
+ watchers: repoData.watchCount,
49
+ language: repoData.language || "Unknown",
50
+ license: repoData.licenseType || "None",
51
+ topics: repoData.topics ? JSON.parse(repoData.topics) : [],
52
+ updatedAt: repoData.updatedAt,
53
+ httpCloneUrl: repoData.httpCloneUrl,
54
+ sshCloneUrl: repoData.sshCloneUrl,
55
+ openIssueCount: repoData.openIssueCount,
56
+ openPrCount: repoData.openPrCount,
57
+ website: repoData.website,
58
+};
59
+
60
+// Get commit diff
61
+const resolvedLocalPath = await resolveRepoPath(repoData.diskPath);
62
+let commit: any = null;
63
+let error: string | null = null;
64
+
65
+try {
66
+ commit = await getCommitDiff(resolvedLocalPath, sha!);
67
+} catch (e: any) {
68
+ console.error("Error fetching commit:", e);
69
+ error = e.message || "Failed to fetch commit";
70
+}
71
+
72
+if (!commit && !error) {
73
+ return Astro.redirect("/404");
74
+}
75
+
76
+function timeAgo(date: Date | string | number) {
77
+ if (!date) return "";
78
+ const seconds = Math.floor(
79
+ (new Date().getTime() - new Date(date).getTime()) / 1000,
80
+ );
81
+ let interval = seconds / 31536000;
82
+ if (interval > 1) return Math.floor(interval) + " years ago";
83
+ interval = seconds / 2592000;
84
+ if (interval > 1) return Math.floor(interval) + " months ago";
85
+ interval = seconds / 86400;
86
+ if (interval > 1) return Math.floor(interval) + " days ago";
87
+ interval = seconds / 3600;
88
+ if (interval > 1) return Math.floor(interval) + " hours ago";
89
+ interval = seconds / 60;
90
+ if (interval > 1) return Math.floor(interval) + " minutes ago";
91
+ return Math.floor(seconds) + " seconds ago";
92
+}
93
+---
94
+
95
+<BaseLayout title={`Commit ${sha?.substring(0, 7)} · ${repo.owner}/${repo.name}`}>
96
+ <div class="container py-6">
97
+ <RepoHeader repo={repo} activeTab="code" />
98
+
99
+ {error ? (
100
+ <div class="p-6 text-center">
101
+ <p class="text-destructive">{error}</p>
102
+ </div>
103
+ ) : commit && (
104
+ <div class="space-y-6">
105
+ <!-- Commit Header -->
106
+ <div class="rounded-lg border bg-card p-6">
107
+ <div class="mb-4">
108
+ <h1 class="text-2xl font-bold mb-2">{commit.message}</h1>
109
+ {commit.body && (
110
+ <pre class="text-sm text-muted-foreground whitespace-pre-wrap font-sans">{commit.body}</pre>
111
+ )}
112
+ </div>
113
+
114
+ <div class="flex items-center gap-4 text-sm text-muted-foreground border-t pt-4">
115
+ <div class="flex items-center gap-2">
116
+ <div class="h-8 w-8 rounded-full bg-muted flex items-center justify-center">
117
+ <span class="text-sm font-medium">
118
+ {commit.authorName?.[0]?.toUpperCase() || "?"}
119
+ </span>
120
+ </div>
121
+ <div>
122
+ <div class="font-medium text-foreground">{commit.authorName}</div>
123
+ <div>{commit.authorEmail}</div>
124
+ </div>
125
+ </div>
126
+ <div class="flex-1"></div>
127
+ <div>
128
+ committed {timeAgo(commit.authorDate)}
129
+ </div>
130
+ </div>
131
+
132
+ <div class="mt-4 flex items-center gap-4 text-sm">
133
+ <div class="font-mono bg-muted px-3 py-1 rounded">
134
+ {(commit.sha || sha)?.substring(0, 7)}
135
+ </div>
136
+ <div class="text-muted-foreground">
137
+ {commit.stats?.additions || 0} additions, {commit.stats?.deletions || 0} deletions
138
+ </div>
139
+ </div>
140
+ </div>
141
+
142
+ <!-- Changed Files -->
143
+ <div class="space-y-4">
144
+ <h2 class="text-lg font-semibold">Changed files</h2>
145
+
146
+ {commit.files && commit.files.length > 0 ? (
147
+ commit.files.map((file: any) => (
148
+ <div class="rounded-lg border overflow-hidden">
149
+ <div class="flex items-center justify-between bg-muted/30 px-4 py-2 border-b">
150
+ <span class="font-mono text-sm">{file.path}</span>
151
+ <span class="text-sm text-muted-foreground">
152
+ +{file.additions || 0} -{file.deletions || 0}
153
+ </span>
154
+ </div>
155
+ {file.diff && (
156
+ <div class="overflow-x-auto">
157
+ <pre class="text-xs p-4 bg-background font-mono"><code>{file.diff}</code></pre>
158
+ </div>
159
+ )}
160
+ </div>
161
+ ))
162
+ ) : (
163
+ <div class="text-center text-muted-foreground py-8">
164
+ No files changed
165
+ </div>
166
+ )}
167
+ </div>
168
+ </div>
169
+ )}
170
+ </div>
171
+</BaseLayout>
0
172
▼src/pages/[owner]/[repo]/pulls/[number].astro
+82-12
22
22
AlertCircle,
23
23
Layers,
24
24
ChevronRight,
25
+ ListOrdered,
25
26
} from "lucide-react";
26
27
28
+import type { NodePgDatabase } from "drizzle-orm/node-postgres";
29
+
27
30
const { owner: ownerName, repo: repoName, number } = Astro.params;
28
-const db = getDatabase();
31
+const db = getDatabase() as NodePgDatabase<typeof schema>;
29
32
30
33
const user = Astro.locals.user;
31
34
64
67
65
68
if (!pr) return Astro.redirect("/404");
66
69
70
+// Check Queue Status
71
+const queueItem = await db.query.mergeQueueItems.findFirst({
72
+ where: eq(schema.mergeQueueItems.pullRequestId, pr.id),
73
+});
74
+
67
75
// Fetch Stack Info
68
76
let stackInfo = null;
69
77
try {
97
105
98
106
// Fetch open issue count (for header)
99
107
const issueCountResult = (await db
100
- // @ts-expect-error: count argument mismatch
101
108
.select({ count: sql<number>`count(*)` })
102
109
.from(schema.issues)
103
110
.where(
105
112
eq(schema.issues.repositoryId, repoData.id),
106
113
eq(schema.issues.state, "open"),
107
114
),
108
- )
109
- ) as { count: number }[];
115
+ )) as { count: number }[];
110
116
111
-const openIssueCount = issueCountResult?.count || 0;
117
+const openIssueCount = issueCountResult[0]?.count || 0;
112
118
113
119
const repo = {
114
120
owner: repoData.owner.username,
135
141
diffs: [],
136
142
};
137
143
144
+import { resolveRepoPath } from "@/lib/git-storage";
138
145
try {
139
- const repoPath = getRepoPath(repoData.owner.username, repoData.name);
146
+ const repoPath = await resolveRepoPath(
147
+ getRepoPath(repoData.owner.username, repoData.name),
148
+ );
140
149
diffData = await compareBranches(repoPath, pr.baseBranch, pr.headBranch);
141
150
} catch (error) {
142
151
console.error("Error fetching comparison:", error);
249
258
<div class="border rounded-lg bg-muted/10 p-4">
250
259
<div class="flex items-start gap-4">
251
260
<div class="mt-1">
252
- <div class="h-8 w-8 rounded-full bg-green-500 flex items-center justify-center text-white">
253
- <GitMerge className="h-4 w-4" />
254
- </div>
261
+ {queueItem ? (
262
+ <div class="h-8 w-8 rounded-full bg-blue-500 flex items-center justify-center text-white">
263
+ <ListOrdered className="h-4 w-4 animate-pulse" />
264
+ </div>
265
+ ) : (
266
+ <div class="h-8 w-8 rounded-full bg-green-500 flex items-center justify-center text-white">
267
+ <GitMerge className="h-4 w-4" />
268
+ </div>
269
+ )}
255
270
</div>
256
271
<div class="flex-1">
257
272
<h4 class="font-medium mb-1">
258
- This branch has no conflicts with the base branch
273
+ {queueItem
274
+ ? "This PR is in the merge queue"
275
+ : "This branch has no conflicts with the base branch"}
259
276
</h4>
260
277
<p class="text-sm text-muted-foreground mb-4">
261
- Merging can be performed automatically.
278
+ {queueItem
279
+ ? `Position: ${queueItem.position || "Pending"} • Status: ${queueItem.status}`
280
+ : "Merging can be performed automatically."}
262
281
</p>
263
282
264
- {canWrite && (
283
+ {canWrite && !queueItem && (
265
284
<div class="flex items-center gap-2">
266
285
<Button
267
286
id="merge-btn"
269
288
>
270
289
Merge pull request
271
290
</Button>
291
+ <Button
292
+ id="queue-btn"
293
+ variant="secondary"
294
+ className="border-primary/20 hover:bg-primary/10 text-primary"
295
+ >
296
+ <ListOrdered className="mr-2 h-4 w-4" />
297
+ Add to merge queue
298
+ </Button>
272
299
<div
273
300
id="merge-error"
274
301
class="text-sm text-red-500 hidden font-medium flex items-center gap-1"
278
305
</div>
279
306
</div>
280
307
)}
308
+ {queueItem && (
309
+ <div class="flex items-center gap-2">
310
+ <Button variant="outline" disabled>
311
+ In Queue
312
+ </Button>
313
+ <a
314
+ href={`/${repo.owner}/${repo.name}/merge-queue`}
315
+ class="text-sm text-blue-500 hover:underline"
316
+ >
317
+ View Queue
318
+ </a>
319
+ </div>
320
+ )}
281
321
</div>
282
322
</div>
283
323
</div>
420
460
repoOwner: repo.owner,
421
461
repoName: repo.name,
422
462
prNumber: pr.number,
463
+ prId: pr.id,
423
464
}}
424
465
>
425
466
const mergeBtn = document.getElementById("merge-btn");
467
+ const queueBtn = document.getElementById("queue-btn");
426
468
const errorDiv = document.getElementById("merge-error");
427
469
470
+ queueBtn?.addEventListener("click", async () => {
471
+ queueBtn.setAttribute("disabled", "true");
472
+ queueBtn.textContent = "Queueing...";
473
+ errorDiv.classList.add("hidden");
474
+
475
+ try {
476
+ const res = await fetch(
477
+ `/api/repos/${repoOwner}/${repoName}/queue/join`,
478
+ {
479
+ method: "POST",
480
+ headers: { "Content-Type": "application/json" },
481
+ body: JSON.stringify({ pullRequestId: prId }),
482
+ },
483
+ );
484
+
485
+ if (!res.ok) {
486
+ const data = await res.json();
487
+ throw new Error(data.error?.message || "Failed to enqueue");
488
+ }
489
+ window.location.reload();
490
+ } catch (e) {
491
+ errorDiv.querySelector("span").textContent = e.message;
492
+ errorDiv.classList.remove("hidden");
493
+ queueBtn.removeAttribute("disabled");
494
+ queueBtn.textContent = "Add to merge queue";
495
+ }
496
+ });
497
+
428
498
mergeBtn?.addEventListener("click", async () => {
429
499
if (!confirm("Are you sure you want to merge this pull request?")) return;
430
500
431
501
▼src/pages/[owner]/[repo]/raw/[branch]/[...path].ts
+5-1
1
1
import { getDatabase, schema } from "@/db";
2
2
import { getFileRawContent } from "@/lib/git";
3
+import { resolveRepoPath } from "@/lib/git-storage";
3
4
import { canReadRepo } from "@/lib/permissions";
4
5
import type { APIRoute } from "astro";
5
6
import { and, eq } from "drizzle-orm";
39
40
const filePath = path || "";
40
41
41
42
try {
43
+ // Resolve disk path for cloud storage
44
+ const repoPath = await resolveRepoPath(repoData.diskPath);
45
+
42
46
const content = await getFileRawContent(
43
- repoData.diskPath,
47
+ repoPath,
44
48
filePath,
45
49
currentBranch
46
50
);
47
51
▼src/pages/[owner]/[repo]/tree/[...ref].astro
+101-8
90
90
let currentBranch = repoData.defaultBranch;
91
91
let currentPath = "";
92
92
93
-// Find the longest matching branch name
93
+// Helper to check if a ref exists
94
+// We need to check if the refParam (or parts of it) is a valid branch/ref
95
+// Since getBranches might return incomplete list or fail, we'll try to resolve it directly
96
+let resolvedBranch = "";
97
+let resolvedPath = "";
98
+
99
+// 1. Check against known branches (fastest)
94
100
const matchingBranch = branches
95
101
.filter((b) => refParam === b || refParam.startsWith(b + "/"))
96
102
.sort((a, b) => b.length - a.length)[0];
99
105
currentBranch = matchingBranch;
100
106
currentPath = refParam.slice(matchingBranch.length).replace(/^\//, "");
101
107
} else {
102
- // If no match, maybe it's a commit hash or we default to main and treat Ref as path?
103
- // For now, assume default branch if no match found (or 404)
104
- // Actually, if we are in /tree/, we expect the first part to be a ref.
105
- // If it's a valid commit SHA, we could use that.
106
- // Let's assume it's a branch for now.
107
- currentBranch = refParam.split("/")[0];
108
- currentPath = refParam.split("/").slice(1).join("/");
108
+ // 2. Fallback: Try to identify where the branch name ends and path starts
109
+ // This is tricky for "stack/feature-1/src". Is it branch "stack" path "feature-1/src" or branch "stack/feature-1" path "src"?
110
+ // We'll optimistically assume the longest possible prefix that *could* be a ref is the branch.
111
+
112
+ // Since we can't easily check every permutation without spamming git,
113
+ // and we know getBranches might have missed it if it's new/remote-only that just got synced?
114
+ // Actually, resolveRepoPath ensures we have the repo. getBranches should see it.
115
+
116
+ // IF getBranches missed it, it might be because the branch is "refs/heads/stack/feature-1"
117
+ // and getBranches returned "stack/feature-1".
118
+
119
+ // Let's assume the first segment is the branch if no match found, BUT
120
+ // for "stack/feature-1", current logic picked "stack".
121
+
122
+ // Improved Heuristic:
123
+ // If the refParam contains slashes, try to verify if the full thing is a ref first?
124
+ // No, because it might be "main/README.md".
125
+
126
+ // Better fallback:
127
+ // Default to treating the whole first part as branch is dangerous.
128
+ // Instead, let's try to pass the WHOLE refParam as the ref if it looks like a branch?
129
+ // But subsequent git operations (listFiles) need strict separation.
130
+
131
+ // We will try to resolve the branch by invoking a quick check?
132
+ // No, we can't easily do async inside this synchronous block structure easily without restructuring.
133
+
134
+ // FIX: Just update the fallback to handle common "stack/" or "feature/" prefixes?
135
+ // Or, since we know getBranches output matches, maybe getBranches output IS "stack/feature-1"?
136
+ // The Logs said: "Available refs... refs/heads/stack/feature-1".
137
+ // So getBranches SHOULD have returned "stack/feature-1".
138
+
139
+ // Why did it fail?
140
+ // "branches" variable is populated via: branches = branchInfos.map(b => b.name);
141
+
142
+ // Hypothethis: getBranches failed or returned empty?
143
+ // The previous logs showed "Ambiguous argument" error before this.
144
+ // Maybe getBranches failed silently in the try/catch block line 85?
145
+
146
+ // If getBranches failed, branches = [repoData.defaultBranch].
147
+ // Repo default is "main". User is visiting "stack/feature-1".
148
+ // "stack/feature-1" does not start with "main".
149
+ // So it goes to fallback.
150
+ // Fallback: currentBranch = refParam.split("/")[0] -> "stack".
151
+ // currentPath = "feature-1".
152
+ // git log stack -- feature-1 -> ERROR.
153
+
154
+ // SOLUTION: If getBranches failed or didn't contain it, we need to try to trust the refParam more.
155
+ // If the ref looks like a branch, treat it as one.
156
+
157
+ // Let's add a check using `git rev-parse --verify` to resolve the split accurately.
158
+ // We can do this because we are in an async function context (Astro component script).
159
+
160
+ try {
161
+ const { getGit } = await import("@/lib/git");
162
+ const git = getGit(resolvedLocalPath);
163
+
164
+ // Try to see if the full refParam is a ref
165
+ try {
166
+ await git.raw(["rev-parse", "--verify", refParam]);
167
+ // It is a valid ref!
168
+ currentBranch = refParam;
169
+ currentPath = "";
170
+ } catch (e) {
171
+ // Not a full ref. Try to find the splitting point.
172
+ // Split by / and find the longest prefix that is a ref.
173
+ const parts = refParam.split("/");
174
+ let found = false;
175
+ for (let i = parts.length - 1; i > 0; i--) {
176
+ const potentialBranch = parts.slice(0, i).join("/");
177
+ try {
178
+ await git.raw(["rev-parse", "--verify", potentialBranch]);
179
+ currentBranch = potentialBranch;
180
+ currentPath = parts.slice(i).join("/");
181
+ found = true;
182
+ break;
183
+ } catch {}
184
+ }
185
+
186
+ if (!found) {
187
+ // If still nothing, fallback to first part (legacy behavior) or default branch?
188
+ // If we default to defaultBranch, we treat the whole thing as path.
189
+ // currentBranch = repoData.defaultBranch;
190
+ // currentPath = refParam;
191
+ // But existing legacy behavior was split[0]. Let's keep legacy if detection fails,
192
+ // OR default to the sophisticated split[0].
193
+ currentBranch = parts[0];
194
+ currentPath = parts.slice(1).join("/");
195
+ }
196
+ }
197
+ } catch (err) {
198
+ // If git fails entirely, fallback
199
+ currentBranch = refParam.split("/")[0];
200
+ currentPath = refParam.split("/").slice(1).join("/");
201
+ }
109
202
}
110
203
111
204
let files: any[] = [];
112
205
▼src/pages/[owner]/[repo]/tree/[branch]/[...path].astro
+15-15
14
14
import { getDatabase, schema } from "@/db";
15
15
import { eq, and, sql } from "drizzle-orm";
16
16
import { listFiles, getLastCommit, getCommits, getBranches } from "@/lib/git";
17
+import { resolveRepoPath } from "@/lib/git-storage";
17
18
import { canReadRepo } from "@/lib/permissions";
18
19
import RepoHeader from "@/components/repo/RepoHeader.astro";
19
20
import FileExplorer from "@/components/repo/FileExplorer.astro";
58
59
eq(schema.issues.repositoryId, repoData.id),
59
60
eq(schema.issues.state, "open"),
60
61
),
61
- )
62
- ;
63
-
62
+ );
64
63
const openIssueCount =
65
64
(issueCountResult as { count: number } | undefined)?.count || 0;
66
65
66
+// Resolve disk path for cloud storage
67
+let repoPath: string;
68
+try {
69
+ repoPath = await resolveRepoPath(repoData.diskPath);
70
+} catch (err) {
71
+ console.error("Failed to resolve repo path:", err);
72
+ return Astro.redirect("/404");
73
+}
74
+
67
75
// 2. Git Operations
68
76
const currentBranch = branch || repoData.defaultBranch;
69
77
const currentPath = path || "";
91
99
92
100
try {
93
101
// Get files
94
- const gitFiles = await listFiles(
95
- repoData.diskPath,
96
- currentBranch,
97
- currentPath,
98
- );
102
+ const gitFiles = await listFiles(repoPath, currentBranch, currentPath);
99
103
100
104
// Map to UI format with commits
101
105
files = await Promise.all(
102
106
gitFiles.map(async (f) => {
103
107
// For subdirectories, we need to pass the full path to getLastCommit
104
108
// listFiles returns 'path' which is the full relative path
105
- const commit = await getLastCommit(
106
- repoData.diskPath,
107
- currentBranch,
108
- f.path,
109
- );
109
+ const commit = await getLastCommit(repoPath, currentBranch, f.path);
110
110
111
111
const lastCommit = commit
112
112
? {
132
132
});
133
133
134
134
// Get latest commit for the directory
135
- const commits = await getCommits(repoData.diskPath, {
135
+ const commits = await getCommits(repoPath, {
136
136
limit: 1,
137
137
ref: currentBranch,
138
138
path: currentPath,
167
167
// Fetch all branches
168
168
let branches: string[] = [repoData.defaultBranch];
169
169
try {
170
- const branchInfos = await getBranches(repoData.diskPath);
170
+ const branchInfos = await getBranches(repoPath);
171
171
branches = branchInfos.map((b) => b.name);
172
172
// Ensure default branch is first
173
173
branches = [
174
174
▼src/pages/api/cron/gc.ts
+3-1
1
1
import { getDatabase, schema } from "@/db";
2
2
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
3
3
import { logger } from "@/lib/logger";
4
+import { resolveRepoPath } from "@/lib/git-storage";
4
5
import type { APIRoute } from "astro";
5
6
import { spawn } from "child_process";
6
7
// removed
27
28
try {
28
29
logger.debug({ owner: repo.ownerId, name: repo.name }, "Processing repository");
29
30
// Run git gc --auto --quiet
31
+ const repoPath = await resolveRepoPath(repo.diskPath);
30
32
const child = spawn("git", ["gc", "--auto", "--quiet"], {
31
- cwd: repo.diskPath,
33
+ cwd: repoPath,
32
34
stdio: "ignore",
33
35
});
34
36
35
37
▼src/pages/api/repos/[owner]/[repo]/branches/[branch].ts
+3-1
1
1
import { getDatabase, schema } from "@/db";
2
2
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
3
3
import { deleteBranch } from "@/lib/git";
4
+import { resolveRepoPath } from "@/lib/git-storage";
4
5
import { canWriteRepo } from "@/lib/permissions";
5
6
import type { APIRoute } from "astro";
6
7
import { and, eq } from "drizzle-orm";
55
56
}
56
57
57
58
// Delete branch
58
- await deleteBranch(repo.diskPath, branch);
59
+ const repoPath = await resolveRepoPath(repo.diskPath);
60
+ await deleteBranch(repoPath, branch);
59
61
60
62
logger.info({ userId: user.id, repoId: repo.id, branch }, "Branch deleted");
61
63
62
64
▼src/pages/api/repos/[owner]/[repo]/branches/index.ts
+3-1
1
1
import { getDatabase, schema } from "@/db";
2
2
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
3
3
import { createBranch, getBranches } from "@/lib/git";
4
+import { resolveRepoPath } from "@/lib/git-storage";
4
5
import { canReadRepo, canWriteRepo } from "@/lib/permissions";
5
6
import type { APIRoute } from "astro";
6
7
import { and, eq } from "drizzle-orm";
46
47
}
47
48
48
49
// Get branches
49
- const branches = await getBranches(repo.diskPath);
50
+ const repoPath = await resolveRepoPath(repo.diskPath);
51
+ const branches = await getBranches(repoPath);
50
52
return success(branches);
51
53
});
52
54
53
55
▼src/pages/api/repos/[owner]/[repo]/compare/index.ts
+4-2
1
1
import { getDatabase, schema } from "@/db";
2
2
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
3
3
import { compareBranches, getMergeBase } from "@/lib/git";
4
+import { resolveRepoPath } from "@/lib/git-storage";
4
5
import { canReadRepo } from "@/lib/permissions";
5
6
import type { APIRoute } from "astro";
6
7
import { and, eq } from "drizzle-orm";
48
49
}
49
50
50
51
// Compare branches
51
- const { commits, diffs } = await compareBranches(repo.diskPath, base, head);
52
- const mergeBase = await getMergeBase(repo.diskPath, base, head);
52
+ const repoPath = await resolveRepoPath(repo.diskPath);
53
+ const { commits, diffs } = await compareBranches(repoPath, base, head);
54
+ const mergeBase = await getMergeBase(repoPath, base, head);
53
55
54
56
return success({ commits, diffs, mergeBase });
55
57
});
56
58
▼src/pages/api/repos/[owner]/[repo]/git/push.ts
+31-5
17
17
acquireRepo,
18
18
releaseRepo,
19
19
getDiskPath,
20
- parseStoragePath
20
+ parseStoragePath,
21
+ ensureRepoInitialized
21
22
} from "@/lib/git-storage";
22
23
import { spawn, execSync } from "child_process";
23
24
import { existsSync, mkdirSync, writeFileSync, unlinkSync } from "fs";
80
81
return badRequest("Empty bundle");
81
82
}
82
83
83
- // Get local path to the repository
84
+ // Get local path to the repository (with lazy initialization)
84
85
let repoPath: string;
85
86
86
87
if (await isCloudStorage()) {
87
88
const parsed = parseStoragePath(repository.diskPath);
88
89
if (parsed) {
89
- repoPath = await acquireRepo(parsed.owner, parsed.repoName);
90
+ // Ensure repo is initialized before accepting push (lazy init)
91
+ repoPath = await ensureRepoInitialized(
92
+ parsed.owner,
93
+ parsed.repoName,
94
+ { defaultBranch: repository.defaultBranch }
95
+ );
90
96
} else {
91
97
// Fallback to standard path
92
- repoPath = await acquireRepo(owner, repoName);
98
+ repoPath = await ensureRepoInitialized(
99
+ owner,
100
+ repoName,
101
+ { defaultBranch: repository.defaultBranch }
102
+ );
93
103
}
94
104
} else {
95
- repoPath = repository.diskPath;
105
+ // Local storage - ensure repo directory exists and is initialized
106
+ const diskPath = repository.diskPath;
107
+ if (!existsSync(join(diskPath, 'HEAD'))) {
108
+ // Repo not initialized, initialize it
109
+ repoPath = await ensureRepoInitialized(
110
+ owner,
111
+ repoName,
112
+ { defaultBranch: repository.defaultBranch }
113
+ );
114
+ } else {
115
+ repoPath = diskPath;
116
+ }
96
117
}
97
118
98
119
// Ensure repo directory exists
173
194
174
195
// If using cloud storage, sync back
175
196
if (await isCloudStorage()) {
197
+ logger.info({ repoPath, diskPath: repository.diskPath }, "Syncing to cloud storage after bundle push");
176
198
const parsed = parseStoragePath(repository.diskPath);
177
199
if (parsed) {
200
+ logger.info({ parsed }, "Releasing repo with modifications");
178
201
await releaseRepo(parsed.owner, parsed.repoName, true);
202
+ logger.info("Repo released and synced to storage");
203
+ } else {
204
+ logger.warn({ diskPath: repository.diskPath }, "Could not parse storage path for sync");
179
205
}
180
206
}
181
207
182
208
▼src/pages/api/repos/[owner]/[repo]/pulls/[number]/merge.ts
+3-1
1
1
import { getDatabase, schema } from "@/db";
2
2
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
3
3
import { mergeBranch } from "@/lib/git";
4
+import { resolveRepoPath } from "@/lib/git-storage";
4
5
import { canWriteRepo } from "@/lib/permissions";
5
6
import type { APIRoute } from "astro";
6
7
import { and, eq } from "drizzle-orm";
66
67
}
67
68
68
69
// Merge branch
69
- const result = await mergeBranch(repo.diskPath, pr.baseBranch, pr.headBranch);
70
+ const repoPath = await resolveRepoPath(repo.diskPath);
71
+ const result = await mergeBranch(repoPath, pr.baseBranch, pr.headBranch);
70
72
71
73
if (result.success) {
72
74
// Update PR state with all merge fields
73
75
▼src/pages/api/repos/[owner]/[repo]/pulls/index.ts
+1-1
62
62
return badRequest("Base and head branches must be different");
63
63
}
64
64
65
- const repoPath = repo.diskPath;
65
+ const repoPath = await resolveRepoPath(repo.diskPath);
66
66
67
67
// Verify branches exist and get SHAs
68
68
// In a real implementation we should verify they exist using git.branch() or similar
69
69
▼src/pages/api/repos/[owner]/[repo]/queue/join.ts
+72-0
1
+import { getDatabase, schema } from "@/db";
2
+import { error, success, unauthorized } from "@/lib/api";
3
+import { withErrorHandler } from "@/lib/errors";
4
+import { queueWorker } from "@/lib/queue-worker";
5
+import { and, eq } from "drizzle-orm";
6
+import type { NodePgDatabase } from "drizzle-orm/node-postgres";
7
+import type { APIContext } from "astro";
8
+import { v4 as uuidv4 } from "uuid";
9
+
10
+export const POST = withErrorHandler(async ({ params, request, locals }: APIContext) => {
11
+ const user = locals.user;
12
+ if (!user) {
13
+ return unauthorized();
14
+ }
15
+
16
+ const { owner, repo } = params;
17
+ const { pullRequestId } = await request.json();
18
+
19
+ if (!pullRequestId) {
20
+ return error("BAD_REQUEST", "Pull Request ID is required", 400);
21
+ }
22
+
23
+ const db = getDatabase() as NodePgDatabase<typeof schema>;
24
+
25
+ // 1. Verify Repository
26
+ const repository = await db.query.repositories.findFirst({
27
+ where: and(eq(schema.repositories.name, repo!), eq(schema.repositories.ownerId, user.id)), // basic check, refine for orgs
28
+ with: { owner: true }
29
+ });
30
+
31
+ if (!repository || repository.owner.username !== owner) {
32
+ const repoByOwner = await db.query.repositories.findFirst({
33
+ where: eq(schema.repositories.name, repo!),
34
+ with: { owner: true }
35
+ });
36
+ if (!repoByOwner || repoByOwner.owner.username !== owner) {
37
+ return error("NOT_FOUND", "Repository not found", 404);
38
+ }
39
+ // Check basic permissions here...
40
+ }
41
+
42
+ // 2. Add to Queue
43
+ const queueItem = {
44
+ id: uuidv4(),
45
+ repositoryId: repository!.id, // Need to fetch correct repo ID if permission check passes
46
+ pullRequestId,
47
+ status: "queued",
48
+ attemptCount: 0,
49
+ queuedAt: new Date(),
50
+ };
51
+
52
+ // Fix repo id fetching above or assume owner/repo is correct:
53
+ const targetRepo = await db.query.repositories.findFirst({
54
+ where: and(
55
+ eq(schema.repositories.name, repo!),
56
+ eq(schema.repositories.ownerId, (await db.query.users.findFirst({ where: eq(schema.users.username, owner!) }))!.id)
57
+ )
58
+ });
59
+
60
+ if (!targetRepo) return error("NOT_FOUND", "Repo not found", 404);
61
+
62
+ await db.insert(schema.mergeQueueItems).values({
63
+ ...queueItem,
64
+ repositoryId: targetRepo.id
65
+ });
66
+
67
+ // 3. Trigger Worker (Async)
68
+ // In production, this might be a cron or event queue. For now, we trigger immediately.
69
+ queueWorker.processQueue(targetRepo.id).catch(console.error);
70
+
71
+ return success({ message: "Added to merge queue", queueId: queueItem.id });
72
+});
0
73
▼src/pages/api/repos/[owner]/[repo]/security/scan.ts
+3-1
2
2
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
3
3
import { canWriteRepo } from "@/lib/permissions";
4
4
import { runSecurityScan } from "@/lib/security";
5
+import { resolveRepoPath } from "@/lib/git-storage";
5
6
import { generateId } from "@/lib/utils";
6
7
import type { APIRoute } from "astro";
7
8
import { and, eq } from "drizzle-orm";
50
51
});
51
52
52
53
// Start scan asynchronously
53
- runSecurityScan(repo.diskPath, scanId, repo.id).catch(err => {
54
+ const repoPath = await resolveRepoPath(repo.diskPath);
55
+ runSecurityScan(repoPath, scanId, repo.id).catch(err => {
54
56
logger.error({ err, repoId: repo.id, scanId }, "Background scan failed to start properly");
55
57
});
56
58
57
59
▼src/pages/api/repos/[repoId]/commit.ts
+3-1
4
4
import { getDatabase, schema } from "@/db";
5
5
import { eq } from "drizzle-orm";
6
6
import { commitFile } from "@/lib/git";
7
+import { resolveRepoPath } from "@/lib/git-storage";
7
8
import { canWriteRepo } from "@/lib/permissions";
8
9
import { analyzeRepository } from "@/lib/analysis";
9
10
40
41
if (!hasAccess) return forbidden();
41
42
42
43
// Commit
44
+ const repoPath = await resolveRepoPath(repo.diskPath);
43
45
const commitSha = await commitFile(
44
- repo.diskPath,
46
+ repoPath,
45
47
branch,
46
48
path,
47
49
content,
48
50
▼src/pages/api/repos/index.ts
+10-10
245
245
updatedAt: timestamp,
246
246
});
247
247
248
- // Initialize git repository
248
+ // Initialize git repository immediately (fast, ~100ms)
249
+ // Upload to S3 happens asynchronously to not block response
249
250
try {
250
251
// Get local path for git operations (temp path for cloud storage, direct path for local)
251
252
const localGitPath = await initRepoInStorage(user.username, slug);
252
253
253
254
await initRepository(localGitPath, {
254
255
defaultBranch,
255
- readme,
256
+ readme: false, // No README on creation to keep it fast
256
257
gitignoreTemplate,
257
258
licenseType,
258
259
repoName: name,
259
260
ownerName: user.displayName || user.username,
260
261
});
261
262
262
- // If using cloud storage, upload the initialized repo
263
+ // If using cloud storage, upload asynchronously (don't wait)
263
264
const isCloud = await isCloudStorage();
264
- logger.info({ isCloud, storageType: import.meta.env.STORAGE_TYPE }, 'Checking cloud storage strict');
265
265
266
266
if (isCloud) {
267
- logger.info('Starting cloud storage sync...');
268
- // Fire-and-forget to avoid blocking the response
267
+ logger.info('Uploading to cloud storage (async)...');
268
+ // Fire-and-forget async upload
269
269
finalizeRepoInit(user.username, slug)
270
- .then(() => logger.info({ repoId, diskPath }, 'Repository synced to cloud storage'))
271
- .catch((err) => logger.error({ err, repoId, diskPath }, 'Background cloud sync failed'));
272
- } else {
273
- logger.info('Skipping cloud sync (isCloudStorage=false)');
270
+ .then(() => logger.info({ repoId, diskPath }, 'Repository uploaded to S3'))
271
+ .catch((err) => logger.error({ err, repoId }, 'Background S3 upload failed'));
274
272
}
273
+
274
+ logger.info({ repoId, diskPath }, 'Repository initialized and ready (S3 upload in progress if cloud)');
275
275
} catch (error) {
276
276
// Rollback database entry if git init fails
277
277
await db.delete(repositories).where(eq(repositories.id, repoId));
278
278
▼src/pages/api/stacks/index.ts
+30-2
15
15
import crypto from "crypto";
16
16
17
17
const createStackSchema = z.object({
18
- repositoryId: z.string(),
18
+ repositoryId: z.string().optional(),
19
+ owner: z.string().optional(),
20
+ repo: z.string().optional(),
19
21
baseBranch: z.string().default("main"),
20
22
name: z.string().optional(),
21
23
branches: z.array(z.object({
24
26
description: z.string().optional(),
25
27
parentBranch: z.string().optional(),
26
28
})),
29
+}).refine(data => data.repositoryId || (data.owner && data.repo), {
30
+ message: "Either repositoryId or owner and repo must be provided",
27
31
});
28
32
29
33
type CreateStackInput = z.infer<typeof createStackSchema>;
86
90
const parsed = await parseBody(request, createStackSchema);
87
91
if ("error" in parsed) return parsed.error;
88
92
89
- const { repositoryId, name, branches } = parsed.data;
93
+ const { name, branches } = parsed.data;
90
94
const baseBranch = parsed.data.baseBranch || "main";
95
+ let repositoryId = parsed.data.repositoryId;
91
96
92
97
const db = getDatabase() as NodePgDatabase<typeof schema>;
93
98
99
+ // Find repo by owner/name if only provided
100
+ if (!repositoryId && parsed.data.owner && parsed.data.repo) {
101
+ const ownerUser = await db.query.users.findFirst({
102
+ where: eq(schema.users.username, parsed.data.owner),
103
+ });
104
+
105
+ if (ownerUser) {
106
+ const repo = await db.query.repositories.findFirst({
107
+ where: and(
108
+ eq(schema.repositories.ownerId, ownerUser.id),
109
+ eq(schema.repositories.name, parsed.data.repo)
110
+ ),
111
+ });
112
+ if (repo) {
113
+ repositoryId = repo.id;
114
+ }
115
+ }
116
+ }
117
+
118
+ if (!repositoryId) {
119
+ throw Errors.notFound("Repository not found");
120
+ }
121
+
94
122
// Verify repository exists and user has access
95
123
const repo = await db.query.repositories.findFirst({
96
124
where: eq(schema.repositories.id, repositoryId),
97
125
▼src/pages/api/stacks/user.ts
+85-0
1
+import { getDatabase, schema } from "@/db";
2
+import { type APIContext } from "astro";
3
+import { and, desc, eq, inArray } from "drizzle-orm";
4
+import { success, error, unauthorized } from "@/lib/api";
5
+import { logger } from "@/lib/logger";
6
+import { withErrorHandler } from "@/lib/errors";
7
+
8
+export const GET = withErrorHandler(async ({ locals }: APIContext) => {
9
+ const user = locals.user;
10
+ if (!user) {
11
+ return unauthorized();
12
+ }
13
+
14
+ const db = getDatabase();
15
+
16
+ // 1. Find all PRs created by the user
17
+ const userPrs = await db.query.pullRequests.findMany({
18
+ where: eq(schema.pullRequests.authorId, user.id),
19
+ columns: { id: true }
20
+ });
21
+
22
+ if (userPrs.length === 0) {
23
+ return success([]);
24
+ }
25
+
26
+ const prIds = userPrs.map(pr => pr.id);
27
+
28
+ // 2. Find which stacks these PRs belong to
29
+ // We get unique stackIds from stack entries
30
+ const stackEntries = await db.query.prStackEntries.findMany({
31
+ where: inArray(schema.prStackEntries.pullRequestId, prIds),
32
+ columns: { stackId: true }
33
+ });
34
+
35
+ const uniqueStackIds = [...new Set(stackEntries.map(e => e.stackId))];
36
+
37
+ if (uniqueStackIds.length === 0) {
38
+ return success([]);
39
+ }
40
+
41
+ // 3. Fetch full stack details + all entries (to show full context of the stack)
42
+ // We need to fetch the stack info AND all its entries (even PRs not owned by current user, if that ever happens)
43
+ const stacks = await db.query.prStacks.findMany({
44
+ where: inArray(schema.prStacks.id, uniqueStackIds),
45
+ orderBy: [desc(schema.prStacks.updatedAt)],
46
+ with: {
47
+ repository: {
48
+ with: {
49
+ owner: true
50
+ }
51
+ },
52
+ entries: {
53
+ orderBy: (entries, { asc }) => [asc(entries.stackOrder)],
54
+ with: {
55
+ pullRequest: true
56
+ }
57
+ }
58
+ }
59
+ });
60
+
61
+ // 4. Format response for UI
62
+ const formattedStacks = stacks.map(stack => {
63
+ return {
64
+ id: stack.id,
65
+ name: stack.name || `Stack-${stack.id.substring(0, 6)}`, // Fallback name
66
+ baseBranch: stack.baseBranch,
67
+ status: stack.status,
68
+ repoName: stack.repository.name,
69
+ repoOwner: stack.repository.owner.username,
70
+ updatedAt: stack.updatedAt,
71
+ prs: stack.entries.map(entry => ({
72
+ id: entry.pullRequest.id,
73
+ number: entry.pullRequest.number,
74
+ title: entry.pullRequest.title,
75
+ status: entry.pullRequest.state, // 'open', 'closed', 'merged'
76
+ url: `/${stack.repository.owner.username}/${stack.repository.name}/pulls/${entry.pullRequest.number}`,
77
+ position: entry.stackOrder,
78
+ isMerged: entry.pullRequest.state === 'merged',
79
+ isClosed: entry.pullRequest.state === 'closed',
80
+ }))
81
+ };
82
+ });
83
+
84
+ return success(formattedStacks);
85
+});
0
86
▼src/pages/dashboard.astro
+244-343
2
2
import { getDatabase, schema } from "@/db";
3
3
import BaseLayout from "@/layouts/BaseLayout.astro";
4
4
import { desc, eq } from "drizzle-orm";
5
+// Import Shadcn UI Components (using path aliases or relative paths)
6
+// Assuming components are in @/components/ui or src/components/ui
7
+import {
8
+ Card,
9
+ CardContent,
10
+ CardDescription,
11
+ CardFooter,
12
+ CardHeader,
13
+ CardTitle,
14
+} from "@/components/ui/card";
15
+import { Button } from "@/components/ui/button";
16
+import { UserAvatar } from "@/components/dashboard/UserAvatar";
17
+import { Badge } from "@/components/ui/badge";
5
18
6
19
const user = Astro.locals.user;
7
20
if (!user) {
10
23
11
24
const db = getDatabase();
12
25
13
-// Fetch user's repositories
14
26
// Fetch user's repositories
15
27
const repos = await db.query.repositories.findMany({
16
28
where: eq(schema.repositories.ownerId, user.id),
33
45
---
34
46
35
47
<BaseLayout title="Dashboard - OpenCodeHub">
36
- <div class="container py-8">
37
- <div class="flex flex-col lg:flex-row gap-8">
38
- <!-- Sidebar -->
39
- <aside class="w-full lg:w-72 space-y-6">
40
- <!-- User Card -->
41
- <div class="relative">
42
- <div
43
- class="absolute -inset-[1px] bg-gradient-to-b from-cyan-500/30 to-transparent rounded-xl opacity-50"
44
- >
45
- </div>
46
- <div
47
- class="relative rounded-xl border border-white/10 bg-[#0d1117]/80 backdrop-blur-sm p-4"
48
- >
49
- <div class="flex items-center gap-4">
50
- {
51
- user.avatarUrl ? (
52
- <img
53
- src={user.avatarUrl}
54
- alt={user.username}
55
- class="h-14 w-14 rounded-full object-cover ring-2 ring-cyan-500/30"
56
- />
57
- ) : (
58
- <div class="h-14 w-14 rounded-full bg-gradient-to-br from-cyan-400 to-blue-600 flex items-center justify-center text-xl font-bold text-white ring-2 ring-cyan-500/30">
59
- {user.username[0].toUpperCase()}
60
- </div>
61
- )
62
- }
63
- <div class="overflow-hidden">
64
- <h2 class="font-semibold text-white truncate">
65
- {user.displayName || user.username}
66
- </h2>
67
- <p class="text-sm text-gray-500 truncate">
68
- @{user.username}
69
- </p>
70
- </div>
71
- </div>
72
-
73
- <!-- Quick Stats -->
74
- <div
75
- class="grid grid-cols-3 gap-2 mt-4 pt-4 border-t border-white/5"
76
- >
77
- <div class="text-center">
78
- <div class="text-lg font-bold text-white">{repos.length}</div>
79
- <div class="text-xs text-gray-500">Repos</div>
80
- </div>
81
- <div class="text-center">
82
- <div class="text-lg font-bold text-yellow-400">
83
- {totalStars}
84
- </div>
85
- <div class="text-xs text-gray-500">Stars</div>
86
- </div>
87
- <div class="text-center">
88
- <div class="text-lg font-bold text-purple-400">
89
- {totalForks}
90
- </div>
91
- <div class="text-xs text-gray-500">Forks</div>
92
- </div>
93
- </div>
94
- </div>
48
+ <div class="container py-8 max-w-7xl mx-auto space-y-8">
49
+ <!-- Welcome Header -->
50
+ <div
51
+ class="flex flex-col md:flex-row items-start md:items-center justify-between gap-4"
52
+ >
53
+ <div class="flex items-center gap-4">
54
+ <UserAvatar
55
+ user={user}
56
+ className="h-16 w-16 border-2 border-white/10"
57
+ client:load
58
+ />
59
+ <div>
60
+ <h1 class="text-3xl font-bold text-white tracking-tight">
61
+ Welcome back, {user.displayName || user.username}
62
+ </h1>
63
+ <p class="text-gray-400">
64
+ Manage your repositories and development workflow.
65
+ </p>
95
66
</div>
96
-
97
- <!-- Quick Links -->
98
- <div class="relative">
99
- <div
100
- class="absolute -inset-[1px] bg-gradient-to-b from-purple-500/20 to-transparent rounded-xl opacity-50"
67
+ </div>
68
+ <div class="flex items-center gap-3">
69
+ <a href="/stacks">
70
+ <Button
71
+ variant="outline"
72
+ className="border-white/10 text-gray-300 hover:text-white hover:bg-white/5"
101
73
>
102
- </div>
103
- <div
104
- class="relative rounded-xl border border-white/10 bg-[#0d1117]/80 backdrop-blur-sm p-4"
105
- >
106
- <h3
107
- class="font-semibold text-sm text-white mb-3 flex items-center gap-2"
108
- >
109
- <svg
110
- class="h-4 w-4 text-purple-400"
111
- fill="none"
112
- stroke="currentColor"
113
- viewBox="0 0 24 24"
114
- >
115
- <path
116
- stroke-linecap="round"
117
- stroke-linejoin="round"
118
- stroke-width="2"
119
- d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
120
- ></path>
121
- </svg>
122
- Your Repositories
123
- </h3>
124
- <nav class="space-y-1 max-h-48 overflow-y-auto custom-scrollbar">
125
- {
126
- repos.slice(0, 8).map((repo) => (
127
- <a
128
- href={`/${user.username}/${repo.name}`}
129
- class="flex items-center gap-2 px-3 py-2 text-sm text-gray-400 hover:text-white hover:bg-white/5 rounded-lg transition-colors"
130
- >
131
- <svg
132
- class="h-4 w-4 text-gray-500"
133
- fill="none"
134
- stroke="currentColor"
135
- viewBox="0 0 24 24"
136
- >
137
- <path
138
- stroke-linecap="round"
139
- stroke-linejoin="round"
140
- stroke-width="2"
141
- d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
142
- />
143
- </svg>
144
- <span class="truncate">{repo.name}</span>
145
- </a>
146
- ))
147
- }
148
- </nav>
149
- <a
150
- href="/new"
151
- class="flex items-center gap-2 px-3 py-2 mt-2 text-sm font-medium text-cyan-400 hover:bg-cyan-500/10 rounded-lg transition-colors"
74
+ <svg
75
+ class="mr-2 h-4 w-4"
76
+ fill="none"
77
+ stroke="currentColor"
78
+ viewBox="0 0 24 24"
79
+ ><path
80
+ stroke-linecap="round"
81
+ stroke-linejoin="round"
82
+ stroke-width="2"
83
+ d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
84
+ ></path></svg
152
85
>
153
- <svg
154
- class="h-4 w-4"
155
- fill="none"
156
- stroke="currentColor"
157
- viewBox="0 0 24 24"
158
- >
159
- <path
160
- stroke-linecap="round"
161
- stroke-linejoin="round"
162
- stroke-width="2"
163
- d="M12 4v16m8-8H4"></path>
164
- </svg>
165
- New Repository
166
- </a>
167
- </div>
168
- </div>
169
- </aside>
170
-
171
- <!-- Main Content -->
172
- <main class="flex-1 space-y-8">
173
- <!-- Header -->
174
- <div class="flex items-center justify-between">
175
- <div>
176
- <h1 class="text-2xl font-bold text-white">Dashboard</h1>
177
- <p class="text-gray-500 text-sm mt-1">
178
- Welcome back, {user.displayName || user.username}
179
- </p>
180
- </div>
181
- <a
182
- href="/new"
183
- class="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg bg-gradient-to-r from-green-500 to-emerald-600 text-sm font-medium text-white shadow-lg shadow-green-500/25 hover:shadow-green-500/40 transition-all"
86
+ View Stacks
87
+ </Button>
88
+ </a>
89
+ <a href="/new">
90
+ <Button
91
+ className="bg-gradient-to-r from-green-500 to-emerald-600 hover:from-green-600 hover:to-emerald-700 text-white border-0 shadow-lg shadow-green-500/20"
184
92
>
185
93
<svg
186
- class="h-4 w-4"
94
+ class="mr-2 h-4 w-4"
187
95
fill="none"
188
96
stroke="currentColor"
189
97
viewBox="0 0 24 24"
190
- >
191
- <path
98
+ ><path
192
99
stroke-linecap="round"
193
100
stroke-linejoin="round"
194
101
stroke-width="2"
195
- d="M12 4v16m8-8H4"></path>
196
- </svg>
102
+ d="M12 4v16m8-8H4"></path></svg
103
+ >
197
104
New Repository
198
- </a>
199
- </div>
105
+ </Button>
106
+ </a>
107
+ </div>
108
+ </div>
200
109
201
- <!-- Activity Feed -->
202
- <div class="relative">
203
- <div
204
- class="absolute -inset-[1px] bg-gradient-to-r from-orange-500/20 via-transparent to-pink-500/20 rounded-xl opacity-50"
110
+ <!-- Stats Grid -->
111
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
112
+ <Card className="bg-[#0d1117]/50 border-white/10 backdrop-blur-sm">
113
+ <CardHeader className="pb-2">
114
+ <CardTitle className="text-sm font-medium text-gray-400"
115
+ >Total Repositories</CardTitle
205
116
>
206
- </div>
207
- <div
208
- class="relative rounded-xl border border-white/10 bg-[#0d1117]/80 backdrop-blur-sm p-6"
117
+ </CardHeader>
118
+ <CardContent>
119
+ <div class="text-3xl font-bold text-white">{repos.length}</div>
120
+ </CardContent>
121
+ </Card>
122
+ <Card className="bg-[#0d1117]/50 border-white/10 backdrop-blur-sm">
123
+ <CardHeader className="pb-2">
124
+ <CardTitle className="text-sm font-medium text-gray-400"
125
+ >Total Stars</CardTitle
209
126
>
210
- <h2
211
- class="text-lg font-semibold text-white flex items-center gap-2 mb-4"
212
- >
213
- <svg
214
- class="h-5 w-5 text-orange-400"
215
- fill="none"
216
- stroke="currentColor"
217
- viewBox="0 0 24 24"
218
- >
219
- <path
220
- stroke-linecap="round"
221
- stroke-linejoin="round"
222
- stroke-width="2"
223
- d="M13 10V3L4 14h7v7l9-11h-7z"></path>
224
- </svg>
225
- Recent Activity
226
- </h2>
227
- <div class="text-center py-8">
228
- <div class="relative inline-block mb-4">
229
- <svg
230
- class="h-12 w-12 text-gray-600"
231
- fill="none"
232
- stroke="currentColor"
233
- viewBox="0 0 24 24"
234
- >
235
- <path
236
- stroke-linecap="round"
237
- stroke-linejoin="round"
238
- stroke-width="1.5"
239
- d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
240
- </svg>
241
- </div>
242
- <p class="text-gray-500">No recent activity to show.</p>
243
- <p class="text-sm text-gray-600 mt-1">
244
- Get started by creating a repository or exploring existing ones.
245
- </p>
246
- <a
247
- href="/explore"
248
- class="inline-flex items-center gap-2 px-4 py-2 mt-4 rounded-lg border border-white/10 bg-white/5 text-sm text-gray-300 hover:bg-white/10 transition-colors"
249
- >
250
- <svg
251
- class="h-4 w-4"
252
- fill="none"
253
- stroke="currentColor"
254
- viewBox="0 0 24 24"
255
- >
256
- <path
257
- stroke-linecap="round"
258
- stroke-linejoin="round"
259
- stroke-width="2"
260
- d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
261
- </svg>
262
- Explore Repositories
263
- </a>
264
- </div>
265
- </div>
266
- </div>
267
-
268
- <!-- Repositories Grid -->
269
- <div>
270
- <h2
271
- class="text-lg font-semibold text-white mb-4 flex items-center gap-2"
127
+ </CardHeader>
128
+ <CardContent>
129
+ <div class="text-3xl font-bold text-yellow-400">{totalStars}</div>
130
+ </CardContent>
131
+ </Card>
132
+ <Card className="bg-[#0d1117]/50 border-white/10 backdrop-blur-sm">
133
+ <CardHeader className="pb-2">
134
+ <CardTitle className="text-sm font-medium text-gray-400"
135
+ >Total Forks</CardTitle
272
136
>
137
+ </CardHeader>
138
+ <CardContent>
139
+ <div class="text-3xl font-bold text-purple-400">{totalForks}</div>
140
+ </CardContent>
141
+ </Card>
142
+ </div>
143
+
144
+ <!-- Main Content Grid -->
145
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-8">
146
+ <!-- User's Repositories -->
147
+ <div class="md:col-span-2 space-y-6">
148
+ <div class="flex items-center justify-between">
149
+ <h2 class="text-xl font-semibold text-white flex items-center gap-2">
273
150
<svg
274
151
class="h-5 w-5 text-cyan-400"
275
152
fill="none"
276
153
stroke="currentColor"
277
154
viewBox="0 0 24 24"
278
- >
279
- <path
155
+ ><path
280
156
stroke-linecap="round"
281
157
stroke-linejoin="round"
282
158
stroke-width="2"
283
159
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
284
- ></path>
285
- </svg>
286
- Your Repositories
160
+ ></path></svg
161
+ >
162
+ Repositories
287
163
</h2>
288
- {
289
- repos.length > 0 ? (
290
- <div class="grid gap-4 md:grid-cols-2">
291
- {repos.map((repo) => (
292
- <a
293
- href={`/${user.username}/${repo.name}`}
294
- class="group relative"
295
- >
296
- <div class="absolute -inset-[1px] bg-gradient-to-r from-cyan-500/20 via-transparent to-purple-500/20 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity" />
297
- <div class="relative rounded-xl border border-white/10 bg-[#0d1117]/80 backdrop-blur-sm p-5 hover:bg-white/[0.02] transition-colors">
298
- <div class="flex items-start justify-between mb-2">
299
- <div class="flex items-center gap-2">
300
- <svg
301
- class="h-5 w-5 text-gray-500"
302
- fill="none"
303
- stroke="currentColor"
304
- viewBox="0 0 24 24"
305
- >
306
- <path
307
- stroke-linecap="round"
308
- stroke-linejoin="round"
309
- stroke-width="2"
310
- d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
311
- />
312
- </svg>
313
- <span class="font-semibold text-white group-hover:text-cyan-400 transition-colors">
314
- {repo.name}
315
- </span>
316
- <span
317
- class={`text-xs px-2 py-0.5 rounded-full border ${
318
- repo.visibility === "public"
319
- ? "border-green-500/30 text-green-400 bg-green-500/10"
320
- : "border-orange-500/30 text-orange-400 bg-orange-500/10"
321
- }`}
322
- >
323
- {repo.visibility}
324
- </span>
164
+ <!-- Filter/Search could go here -->
165
+ </div>
166
+
167
+ {
168
+ repos.length > 0 ? (
169
+ <div class="grid gap-4">
170
+ {repos.map((repo) => (
171
+ <a href={`/${user.username}/${repo.name}`} class="block group">
172
+ <Card className="bg-[#0d1117]/80 border-white/10 hover:border-white/20 hover:bg-[#161b22] transition-all duration-200">
173
+ <CardContent className="p-5">
174
+ <div class="flex items-start justify-between">
175
+ <div class="space-y-1">
176
+ <div class="flex items-center gap-2">
177
+ <h3 class="font-semibold text-lg text-white group-hover:text-cyan-400 transition-colors">
178
+ {repo.name}
179
+ </h3>
180
+ <Badge
181
+ variant="secondary"
182
+ className={`bg-opacity-10 border ${repo.visibility === "public" ? "border-green-500/30 text-green-400 bg-green-500" : "border-orange-500/30 text-orange-400 bg-orange-500"}`}
183
+ >
184
+ {repo.visibility}
185
+ </Badge>
186
+ </div>
187
+ <p class="text-sm text-gray-400 line-clamp-1">
188
+ {repo.description || "No description provided."}
189
+ </p>
190
+ </div>
191
+ <div class="text-xs text-gray-500 whitespace-nowrap">
192
+ {timeAgo(repo.updatedAt)}
325
193
</div>
326
194
</div>
327
- <p class="text-sm text-gray-500 line-clamp-2 mb-4">
328
- {repo.description || "No description provided."}
329
- </p>
330
- <div class="flex items-center gap-4 text-xs text-gray-500">
195
+ <div class="mt-4 flex items-center gap-6 text-sm text-gray-500">
331
196
{repo.language && (
332
- <div class="flex items-center gap-1">
333
- <span class="h-2.5 w-2.5 rounded-full bg-blue-500" />
197
+ <div class="flex items-center gap-1.5">
198
+ <span class="w-2 h-2 rounded-full bg-blue-500" />
334
199
{repo.language}
335
200
</div>
336
201
)}
337
- <div class="flex items-center gap-1">
202
+ <div class="flex items-center gap-1 hover:text-yellow-400 transition-colors">
338
203
<svg
339
- class="h-3.5 w-3.5 text-yellow-400"
204
+ class="h-4 w-4"
340
205
fill="currentColor"
341
206
viewBox="0 0 20 20"
342
207
>
344
209
</svg>
345
210
{repo.starCount || 0}
346
211
</div>
347
- <div class="flex items-center gap-1">
212
+ <div class="flex items-center gap-1 hover:text-purple-400 transition-colors">
348
213
<svg
349
- class="h-3.5 w-3.5 text-purple-400"
214
+ class="h-4 w-4"
350
215
fill="none"
351
216
stroke="currentColor"
352
217
viewBox="0 0 24 24"
360
225
</svg>
361
226
{repo.forkCount || 0}
362
227
</div>
363
- <div class="ml-auto text-gray-600">
364
- Updated {timeAgo(repo.updatedAt)}
365
- </div>
366
228
</div>
367
- </div>
368
- </a>
369
- ))}
229
+ </CardContent>
230
+ </Card>
231
+ </a>
232
+ ))}
233
+ </div>
234
+ ) : (
235
+ <div class="bg-[#0d1117]/30 border border-white/5 rounded-xl p-12 text-center">
236
+ <div class="inline-flex items-center justify-center p-4 bg-white/5 rounded-full mb-4">
237
+ <svg
238
+ class="h-8 w-8 text-gray-500"
239
+ fill="none"
240
+ stroke="currentColor"
241
+ viewBox="0 0 24 24"
242
+ >
243
+ <path
244
+ stroke-linecap="round"
245
+ stroke-linejoin="round"
246
+ stroke-width="2"
247
+ d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
248
+ />
249
+ </svg>
370
250
</div>
371
- ) : (
372
- <div class="relative">
373
- <div class="absolute -inset-[1px] bg-gradient-to-r from-cyan-500/20 via-purple-500/20 to-pink-500/20 rounded-xl opacity-50" />
374
- <div class="relative rounded-xl border border-white/10 bg-[#0d1117]/80 backdrop-blur-sm p-12 text-center">
375
- <div class="relative inline-block mb-4">
376
- <svg
377
- class="h-16 w-16 text-cyan-400"
378
- fill="none"
379
- stroke="currentColor"
380
- viewBox="0 0 24 24"
381
- >
382
- <path
383
- stroke-linecap="round"
384
- stroke-linejoin="round"
385
- stroke-width="1.5"
386
- d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
387
- />
388
- </svg>
389
- <div class="absolute inset-0 bg-cyan-500 blur-2xl opacity-20" />
390
- </div>
391
- <h3 class="text-lg font-semibold text-white mb-2">
392
- No repositories yet
393
- </h3>
394
- <p class="text-gray-500 mb-6">
395
- Create your first repository to get started.
396
- </p>
397
- <a
398
- href="/new"
399
- class="inline-flex items-center gap-2 px-6 py-2.5 rounded-lg bg-gradient-to-r from-cyan-500 to-blue-600 text-sm font-medium text-white shadow-lg shadow-cyan-500/25"
251
+ <h3 class="text-lg font-medium text-white mb-2">
252
+ No repositories found
253
+ </h3>
254
+ <p class="text-gray-500 mb-6">
255
+ Get started by creating your first repository.
256
+ </p>
257
+ <a href="/new">
258
+ <Button>Create Repository</Button>
259
+ </a>
260
+ </div>
261
+ )
262
+ }
263
+ </div>
264
+
265
+ <!-- Sidebar / Activity -->
266
+ <div class="space-y-6">
267
+ <Card className="bg-[#0d1117]/50 border-white/10 backdrop-blur-sm">
268
+ <CardHeader>
269
+ <CardTitle className="flex items-center gap-2 text-white">
270
+ <svg
271
+ class="h-5 w-5 text-orange-400"
272
+ fill="none"
273
+ stroke="currentColor"
274
+ viewBox="0 0 24 24"
275
+ ><path
276
+ stroke-linecap="round"
277
+ stroke-linejoin="round"
278
+ stroke-width="2"
279
+ d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg
280
+ >
281
+ Recent Activity
282
+ </CardTitle>
283
+ </CardHeader>
284
+ <CardContent>
285
+ <div class="text-center py-6 text-gray-500 text-sm">
286
+ No recent activity to show.
287
+ </div>
288
+ <div class="pt-4 border-t border-white/5">
289
+ <a href="/explore" class="block">
290
+ <Button
291
+ variant="ghost"
292
+ className="w-full justify-start text-gray-400 hover:text-white"
293
+ >
294
+ <svg
295
+ class="mr-2 h-4 w-4"
296
+ fill="none"
297
+ stroke="currentColor"
298
+ viewBox="0 0 24 24"
299
+ ><path
300
+ stroke-linecap="round"
301
+ stroke-linejoin="round"
302
+ stroke-width="2"
303
+ d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
304
+ ></path></svg
400
305
>
401
- <svg
402
- class="h-4 w-4"
403
- fill="none"
404
- stroke="currentColor"
405
- viewBox="0 0 24 24"
406
- >
407
- <path
408
- stroke-linecap="round"
409
- stroke-linejoin="round"
410
- stroke-width="2"
411
- d="M12 4v16m8-8H4"
412
- />
413
- </svg>
414
- Create a repository
415
- </a>
416
- </div>
417
- </div>
418
- )
419
- }
420
- </div>
421
- </main>
306
+ Explore Repositories
307
+ </Button>
308
+ </a>
309
+ </div>
310
+ </CardContent>
311
+ </Card>
312
+
313
+ <Card
314
+ className="bg-gradient-to-br from-purple-900/10 to-blue-900/10 border-white/10"
315
+ >
316
+ <CardContent className="p-6">
317
+ <h3 class="font-semibold text-white mb-2">Try the CLI</h3>
318
+ <p class="text-sm text-gray-400 mb-4">
319
+ Push code, create PRs, and manage stacks directly from your
320
+ terminal.
321
+ </p>
322
+ <div
323
+ class="bg-black/50 rounded-lg p-3 font-mono text-xs text-green-400 mb-4 border border-white/10"
324
+ >
325
+ npm install -g opencodehub
326
+ </div>
327
+ <a
328
+ href="/docs/cli"
329
+ class="text-sm text-blue-400 hover:text-blue-300 hover:underline"
330
+ >
331
+ Read the documentation &rarr;
332
+ </a>
333
+ </CardContent>
334
+ </Card>
335
+ </div>
422
336
</div>
423
337
</div>
424
338
</BaseLayout>
425
-
426
-<style>
427
- .custom-scrollbar::-webkit-scrollbar {
428
- width: 4px;
429
- }
430
- .custom-scrollbar::-webkit-scrollbar-track {
431
- background: transparent;
432
- }
433
- .custom-scrollbar::-webkit-scrollbar-thumb {
434
- background: rgba(255, 255, 255, 0.1);
435
- border-radius: 4px;
436
- }
437
-</style>
438
339
▼src/pages/git/[owner]/[repo]/git-receive-pack.ts
+56-0
1
+
2
+import type { APIRoute } from "astro";
3
+import { handleReceivePack } from "@/lib/git-server";
4
+import { acquireRepo, releaseRepo, getStorageRepoPath } from "@/lib/git-storage";
5
+import { logger } from "@/lib/logger";
6
+
7
+export const POST: APIRoute = async ({ params, request }) => {
8
+ const { owner, repo } = params;
9
+
10
+ if (!owner || !repo) {
11
+ return new Response("Missing owner or repo", { status: 400 });
12
+ }
13
+
14
+ // Handle .git suffix
15
+ const repoName = repo.endsWith(".git") ? repo.slice(0, -4) : repo;
16
+
17
+ // Check Content-Type
18
+ const contentType = request.headers.get("Content-Type");
19
+ if (contentType !== "application/x-git-receive-pack-request") {
20
+ return new Response("Invalid Content-Type", { status: 415 });
21
+ }
22
+
23
+ logger.info({ owner, repoName }, "Git receive-pack request");
24
+
25
+ let repoPath: string;
26
+ try {
27
+ // Repos are always initialized on creation now, just acquire
28
+ repoPath = await acquireRepo(owner, repoName);
29
+ } catch (err) {
30
+ logger.error({ err }, "Failed to acquire repo");
31
+ return new Response("Repository not found", { status: 404 });
32
+ }
33
+
34
+ const storagePath = getStorageRepoPath(owner, repoName);
35
+
36
+ try {
37
+ // Process the pack stream
38
+ const responseStream = await handleReceivePack(repoPath, request.body!, storagePath);
39
+
40
+ // Release repo with modification flag true
41
+ // Note: This might trigger re-upload of the packfile effectively checking consistency
42
+ await releaseRepo(owner, repoName, true);
43
+
44
+ return new Response(responseStream as any, {
45
+ headers: {
46
+ "Content-Type": "application/x-git-receive-pack-result",
47
+ "Cache-Control": "no-cache",
48
+ },
49
+ });
50
+ } catch (err) {
51
+ logger.error({ err }, "Failed to handle receive-pack");
52
+ // Release repo without saving changes (rollback attempt)
53
+ await releaseRepo(owner, repoName, false);
54
+ return new Response("Internal Server Error", { status: 500 });
55
+ }
56
+};
0
57
▼src/pages/git/[owner]/[repo]/git-upload-pack.ts
+63-0
1
+
2
+import type { APIRoute } from "astro";
3
+import { handleUploadPack } from "@/lib/git-server";
4
+import { acquireRepo, releaseRepo } from "@/lib/git-storage";
5
+import { logger } from "@/lib/logger";
6
+
7
+export const POST: APIRoute = async ({ params, request }) => {
8
+ const { owner, repo } = params;
9
+
10
+ if (!owner || !repo) {
11
+ return new Response("Missing owner or repo", { status: 400 });
12
+ }
13
+
14
+ // Handle .git suffix
15
+ const repoName = repo.endsWith(".git") ? repo.slice(0, -4) : repo;
16
+
17
+ // Check Content-Type
18
+ const contentType = request.headers.get("Content-Type");
19
+ if (contentType !== "application/x-git-upload-pack-request") {
20
+ return new Response("Invalid Content-Type", { status: 415 });
21
+ }
22
+
23
+ logger.info({ owner, repoName }, "Git upload-pack request");
24
+
25
+ let repoPath: string;
26
+ try {
27
+ repoPath = await acquireRepo(owner, repoName);
28
+ } catch (err) {
29
+ logger.error({ err }, "Failed to acquire repo");
30
+ return new Response("Repository not found", { status: 404 });
31
+ }
32
+
33
+ // Check if repository is initialized (has HEAD file)
34
+ const { existsSync } = await import("fs");
35
+ const { join } = await import("path");
36
+ const headPath = join(repoPath, 'HEAD');
37
+
38
+ if (!existsSync(headPath)) {
39
+ logger.warn({ repoPath }, "Attempted to clone uninitialized repository");
40
+ return new Response(
41
+ "Repository is empty. Push some code first before cloning.",
42
+ { status: 404 }
43
+ );
44
+ }
45
+
46
+ try {
47
+ // Process the upload request
48
+ const responseStream = await handleUploadPack(repoPath, request.body!);
49
+
50
+ return new Response(responseStream as any, {
51
+ headers: {
52
+ "Content-Type": "application/x-git-upload-pack-result",
53
+ "Cache-Control": "no-cache",
54
+ },
55
+ });
56
+ } catch (err) {
57
+ logger.error({ err }, "Failed to handle upload-pack");
58
+ return new Response("Internal Server Error", { status: 500 });
59
+ } finally {
60
+ // Release repo (read-only)
61
+ await releaseRepo(owner, repoName, false);
62
+ }
63
+};
0
64
▼src/pages/git/[owner]/[repo]/info/refs.ts
+88-0
1
+
2
+import type { APIRoute } from "astro";
3
+import { getAdvertisedRefs } from "@/lib/git-server";
4
+import { acquireRepo, releaseRepo } from "@/lib/git-storage";
5
+import { logger } from "@/lib/logger";
6
+
7
+export const GET: APIRoute = async ({ params, request, url }) => {
8
+ const { owner, repo } = params;
9
+
10
+ if (!owner || !repo) {
11
+ return new Response("Missing owner or repo", { status: 400 });
12
+ }
13
+
14
+ // Handle .git suffix
15
+ const repoName = repo.endsWith(".git") ? repo.slice(0, -4) : repo;
16
+
17
+ const service = url.searchParams.get("service");
18
+
19
+ // Git protocol requires service param (e.g., git-upload-pack or git-receive-pack)
20
+ if (!service || !["git-upload-pack", "git-receive-pack"].includes(service)) {
21
+ // Dumb protocol buffer? Or just error. Modern clients use smart protocol
22
+ return new Response("Service not specified or invalid", { status: 400 });
23
+ }
24
+
25
+ logger.info({ owner, repoName, service }, "=== GIT INFO/REFS REQUEST START ===");
26
+
27
+ // Acquire repo (download/cache)
28
+ let repoPath: string;
29
+ try {
30
+ repoPath = await acquireRepo(owner, repoName);
31
+ } catch (err) {
32
+ logger.error({ err }, "Failed to acquire repo");
33
+ return new Response("Repository not found", { status: 404 });
34
+ }
35
+
36
+ // Check if repository is initialized (has HEAD file)
37
+ const { existsSync } = await import("fs");
38
+ const { join } = await import("path");
39
+ const headPath = join(repoPath, 'HEAD');
40
+
41
+ logger.info({ headPath, exists: existsSync(headPath) }, "Checking if repo is initialized");
42
+
43
+ if (!existsSync(headPath)) {
44
+ // Repository not initialized yet (lazy init)
45
+ logger.warn({ repoName, service, headPath }, "Repository not initialized - returning error");
46
+
47
+ if (service === "git-upload-pack") {
48
+ // Return error for clone attempts on empty repo
49
+ logger.error({ repoName }, "Blocking clone attempt on empty repository");
50
+ return new Response("Repository is empty. Push some code first before cloning.", { status: 404 });
51
+ }
52
+
53
+ // For receive-pack (push), return minimal capabilities to allow first push
54
+ logger.info("Returning empty refs for receive-pack");
55
+ const emptyRefs = `001e# service=${service}\n0000` +
56
+ `00000000000000000000000000000000000000000000 capabilities^{}\0report-status report-status-v2 side-band-64k object-format=sha1\n` +
57
+ `0000`;
58
+
59
+ return new Response(emptyRefs, {
60
+ headers: {
61
+ "Content-Type": `application/x-${service}-advertisement`,
62
+ "Cache-Control": "no-cache",
63
+ },
64
+ });
65
+ }
66
+
67
+ logger.info("Repository is initialized, proceeding with normal refs");
68
+
69
+ try {
70
+ const refs = await getAdvertisedRefs({
71
+ repoPath,
72
+ service: service as "git-upload-pack" | "git-receive-pack",
73
+ });
74
+
75
+ return new Response(refs, {
76
+ headers: {
77
+ "Content-Type": `application/x-${service}-advertisement`,
78
+ "Cache-Control": "no-cache",
79
+ },
80
+ });
81
+ } catch (err) {
82
+ logger.error({ err }, "Failed to get refs");
83
+ return new Response("Internal Server Error", { status: 500 });
84
+ } finally {
85
+ // Release repo (no modification)
86
+ await releaseRepo(owner, repoName, false);
87
+ }
88
+};
0
89
▼src/pages/stacks.astro
+131-0
1
+---
2
+import { getDatabase, schema } from "@/db";
3
+import BaseLayout from "@/layouts/BaseLayout.astro";
4
+import { desc, eq, inArray } from "drizzle-orm";
5
+import StackCard from "@/components/dashboard/StackCard.astro";
6
+
7
+const user = Astro.locals.user;
8
+if (!user) {
9
+ return Astro.redirect("/login");
10
+}
11
+
12
+const db = getDatabase();
13
+
14
+// Fetch user's stacks
15
+const userPrs = await db.query.pullRequests.findMany({
16
+ where: eq(schema.pullRequests.authorId, user.id),
17
+ columns: { id: true },
18
+});
19
+
20
+let stacks: any[] = [];
21
+const prIds = userPrs.map((pr) => pr.id);
22
+
23
+if (prIds.length > 0) {
24
+ const stackEntries = await db.query.prStackEntries.findMany({
25
+ where: inArray(schema.prStackEntries.pullRequestId, prIds),
26
+ columns: { stackId: true },
27
+ });
28
+
29
+ const uniqueStackIds = [...new Set(stackEntries.map((e) => e.stackId))];
30
+
31
+ if (uniqueStackIds.length > 0) {
32
+ const dbStacks = await db.query.prStacks.findMany({
33
+ where: inArray(schema.prStacks.id, uniqueStackIds),
34
+ orderBy: [desc(schema.prStacks.updatedAt)],
35
+ with: {
36
+ repository: {
37
+ with: {
38
+ owner: true,
39
+ },
40
+ },
41
+ entries: {
42
+ orderBy: (entries, { asc }) => [asc(entries.stackOrder)],
43
+ with: {
44
+ pullRequest: true,
45
+ },
46
+ },
47
+ },
48
+ });
49
+
50
+ stacks = dbStacks.map((stack) => ({
51
+ id: stack.id,
52
+ name: stack.name || `Stack-${stack.id.substring(0, 6)}`,
53
+ baseBranch: stack.baseBranch,
54
+ status: stack.status,
55
+ repoName: stack.repository.name,
56
+ repoOwner: stack.repository.owner.username,
57
+ updatedAt: stack.updatedAt.toISOString(),
58
+ prs: stack.entries.map((entry) => ({
59
+ id: entry.pullRequest.id,
60
+ number: entry.pullRequest.number,
61
+ title: entry.pullRequest.title,
62
+ status: entry.pullRequest.state,
63
+ url: `/${stack.repository.owner.username}/${stack.repository.name}/pulls/${entry.pullRequest.number}`,
64
+ position: entry.stackOrder,
65
+ isMerged: entry.pullRequest.state === "merged",
66
+ isClosed: entry.pullRequest.state === "closed",
67
+ })),
68
+ }));
69
+ }
70
+}
71
+---
72
+
73
+<BaseLayout title={`Your Stacks - OpenCodeHub`}>
74
+ <div class="container py-8 max-w-5xl mx-auto">
75
+ <div class="flex items-center justify-between mb-8">
76
+ <div>
77
+ <h1 class="text-2xl font-bold text-white mb-2">Your Stacks</h1>
78
+ <p class="text-gray-400">
79
+ Manage and visualize your stacked pull requests.
80
+ </p>
81
+ </div>
82
+ <a
83
+ href="/dashboard"
84
+ class="px-4 py-2 rounded-lg bg-white/5 hover:bg-white/10 text-sm text-gray-300 transition-colors"
85
+ >
86
+ Back to Dashboard
87
+ </a>
88
+ </div>
89
+
90
+ {
91
+ stacks.length > 0 ? (
92
+ <div class="grid gap-6 md:grid-cols-2 lg:grid-cols-2">
93
+ {stacks.map((stack) => (
94
+ <StackCard stack={stack} />
95
+ ))}
96
+ </div>
97
+ ) : (
98
+ <div class="text-center py-20 bg-[#0d1117]/50 rounded-xl border border-white/5">
99
+ <div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-blue-500/10 text-blue-400 mb-4">
100
+ <svg
101
+ class="w-8 h-8"
102
+ fill="none"
103
+ stroke="currentColor"
104
+ viewBox="0 0 24 24"
105
+ >
106
+ <path
107
+ stroke-linecap="round"
108
+ stroke-linejoin="round"
109
+ stroke-width="2"
110
+ d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"
111
+ />
112
+ </svg>
113
+ </div>
114
+ <h3 class="text-lg font-medium text-white mb-2">
115
+ No active stacks
116
+ </h3>
117
+ <p class="text-gray-500 max-w-sm mx-auto mb-6">
118
+ Create stacked pull requests to see them visualized
119
+ here.
120
+ </p>
121
+ <a
122
+ href="/docs/stacks"
123
+ class="text-blue-400 hover:text-blue-300 text-sm"
124
+ >
125
+ Learn how to create stacks &rarr;
126
+ </a>
127
+ </div>
128
+ )
129
+ }
130
+ </div>
131
+</BaseLayout>
0
132