@argus-vrt/web 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -5
- package/dist-cli/index.js +131 -45
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -22,6 +22,20 @@ The `init` wizard generates a `docker-compose.yml`, `.env`, and optionally `ngin
|
|
|
22
22
|
|
|
23
23
|
- [Docker](https://docs.docker.com/get-docker/) installed and running
|
|
24
24
|
- Node.js >= 20 (for running the CLI via npx)
|
|
25
|
+
- A [GitHub OAuth App](https://github.com/settings/developers) for authentication
|
|
26
|
+
|
|
27
|
+
## Authentication
|
|
28
|
+
|
|
29
|
+
Argus requires GitHub OAuth for login. All browser routes are protected — unauthenticated users are redirected to GitHub to sign in.
|
|
30
|
+
|
|
31
|
+
The `/api/upload` endpoint used by CI/CD is authenticated separately via API key (`Authorization: Bearer <key>` header), so pipelines don't need GitHub credentials.
|
|
32
|
+
|
|
33
|
+
### Setting Up GitHub OAuth
|
|
34
|
+
|
|
35
|
+
1. Go to [GitHub Developer Settings](https://github.com/settings/developers) → **New OAuth App**
|
|
36
|
+
2. Set the **Authorization callback URL** to `https://your-domain.com/auth/github/callback` (or `http://localhost:3000/auth/github/callback` for local development)
|
|
37
|
+
3. Save the **Client ID** and generate a **Client Secret**
|
|
38
|
+
4. Provide these values when running `npx @argus-vrt/web init`
|
|
25
39
|
|
|
26
40
|
## Commands
|
|
27
41
|
|
|
@@ -35,6 +49,11 @@ Interactive setup wizard. Prompts for:
|
|
|
35
49
|
- **HTTPS** — Let's Encrypt, custom certificate, or none
|
|
36
50
|
- **Reverse proxy** — include an Nginx container or manage yourself
|
|
37
51
|
- **Screenshots path** — where uploaded screenshots are stored
|
|
52
|
+
- **GitHub Client ID** — from your GitHub OAuth App
|
|
53
|
+
- **GitHub Client Secret** — from your GitHub OAuth App
|
|
54
|
+
- **API key** — for CI/CD uploads (auto-generated by default)
|
|
55
|
+
|
|
56
|
+
A **session secret** is auto-generated and does not require input.
|
|
38
57
|
|
|
39
58
|
Generates configuration files into `./argus/` (or `--dir <path>`).
|
|
40
59
|
|
|
@@ -63,13 +82,17 @@ Show container status and run a health check on the web dashboard.
|
|
|
63
82
|
|
|
64
83
|
Pull the latest Docker images and restart containers.
|
|
65
84
|
|
|
85
|
+
### `npx @argus-vrt/web setup-ssl <domain>`
|
|
86
|
+
|
|
87
|
+
Obtain a Let's Encrypt SSL certificate for your domain. Requires the Nginx and Certbot containers (select Let's Encrypt during `init`).
|
|
88
|
+
|
|
66
89
|
```bash
|
|
67
|
-
npx @argus-vrt/web
|
|
90
|
+
npx @argus-vrt/web setup-ssl argus.yourcompany.com
|
|
68
91
|
```
|
|
69
92
|
|
|
70
93
|
## Options
|
|
71
94
|
|
|
72
|
-
All management commands (`start`, `stop`, `logs`, `status`, `upgrade`) accept:
|
|
95
|
+
All management commands (`start`, `stop`, `logs`, `status`, `upgrade`, `setup-ssl`) accept:
|
|
73
96
|
|
|
74
97
|
| Flag | Description |
|
|
75
98
|
|------|-------------|
|
|
@@ -77,11 +100,12 @@ All management commands (`start`, `stop`, `logs`, `status`, `upgrade`) accept:
|
|
|
77
100
|
|
|
78
101
|
## Connecting the CLI
|
|
79
102
|
|
|
80
|
-
Once the dashboard is running, point the testing CLI at it by adding `apiUrl` to your project's `.argus.json`:
|
|
103
|
+
Once the dashboard is running, point the testing CLI at it by adding `apiUrl` and `apiKey` to your project's `.argus.json`:
|
|
81
104
|
|
|
82
105
|
```json
|
|
83
106
|
{
|
|
84
|
-
"apiUrl": "http://localhost:3000"
|
|
107
|
+
"apiUrl": "http://localhost:3000",
|
|
108
|
+
"apiKey": "your-api-key-from-init"
|
|
85
109
|
}
|
|
86
110
|
```
|
|
87
111
|
|
|
@@ -91,9 +115,18 @@ Then upload results after running tests:
|
|
|
91
115
|
npx argus test
|
|
92
116
|
```
|
|
93
117
|
|
|
118
|
+
Or upload manually with a header:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
curl -X POST https://your-domain.com/api/upload \
|
|
122
|
+
-H "Authorization: Bearer your-api-key" \
|
|
123
|
+
-H "Content-Type: application/json" \
|
|
124
|
+
-d '{"branch":"main","commitHash":"abc123","stories":[...]}'
|
|
125
|
+
```
|
|
126
|
+
|
|
94
127
|
## Environment Variables
|
|
95
128
|
|
|
96
|
-
The generated `.env` file
|
|
129
|
+
The generated `.env` file includes:
|
|
97
130
|
|
|
98
131
|
| Variable | Default | Description |
|
|
99
132
|
|----------|---------|-------------|
|
|
@@ -101,6 +134,10 @@ The generated `.env` file supports:
|
|
|
101
134
|
| `DATABASE_URL` | (auto) | PostgreSQL connection string (external DB only) |
|
|
102
135
|
| `DB_PASSWORD` | `argus` | Database password (container DB only) |
|
|
103
136
|
| `SCREENSHOTS_PATH` | `./argus-data/images` | Path to screenshots directory |
|
|
137
|
+
| `GITHUB_CLIENT_ID` | — | GitHub OAuth App client ID |
|
|
138
|
+
| `GITHUB_CLIENT_SECRET` | — | GitHub OAuth App client secret |
|
|
139
|
+
| `SESSION_SECRET` | (auto-generated) | Encryption key for session cookies |
|
|
140
|
+
| `ARGUS_API_KEY` | (auto-generated) | API key for CI/CD upload authentication |
|
|
104
141
|
|
|
105
142
|
## Docker Image
|
|
106
143
|
|
package/dist-cli/index.js
CHANGED
|
@@ -5,11 +5,13 @@ import { Command } from "commander";
|
|
|
5
5
|
|
|
6
6
|
// cli/commands/init.ts
|
|
7
7
|
import * as p from "@clack/prompts";
|
|
8
|
-
import
|
|
8
|
+
import chalk3 from "chalk";
|
|
9
|
+
import { randomBytes } from "crypto";
|
|
9
10
|
import { existsSync as existsSync2, mkdirSync, writeFileSync } from "fs";
|
|
10
11
|
import { resolve as resolve2 } from "path";
|
|
11
12
|
|
|
12
13
|
// cli/utils/config.ts
|
|
14
|
+
import chalk from "chalk";
|
|
13
15
|
import { existsSync } from "fs";
|
|
14
16
|
import { resolve } from "path";
|
|
15
17
|
var DOCKER_IMAGE = "ghcr.io/maxcwolf/argus-web";
|
|
@@ -20,10 +22,11 @@ var DEFAULT_SCREENSHOTS_PATH = "./argus-data/images";
|
|
|
20
22
|
function findArgusDir(dir) {
|
|
21
23
|
const target = dir ? resolve(dir) : resolve(process.cwd(), DEFAULT_DIR);
|
|
22
24
|
if (!existsSync(resolve(target, "docker-compose.yml"))) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Run
|
|
25
|
+
console.error(
|
|
26
|
+
`${chalk.red("Error:")} No docker-compose.yml found in ${target}
|
|
27
|
+
Run ${chalk.cyan("argus-web init")} first to set up Argus.`
|
|
26
28
|
);
|
|
29
|
+
process.exit(1);
|
|
27
30
|
}
|
|
28
31
|
return target;
|
|
29
32
|
}
|
|
@@ -51,6 +54,10 @@ function generateDockerCompose(options) {
|
|
|
51
54
|
lines.push(" - DATABASE_URL=${DATABASE_URL}");
|
|
52
55
|
}
|
|
53
56
|
lines.push(" - NODE_ENV=production");
|
|
57
|
+
lines.push(" - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}");
|
|
58
|
+
lines.push(" - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET}");
|
|
59
|
+
lines.push(" - SESSION_SECRET=${SESSION_SECRET}");
|
|
60
|
+
lines.push(" - ARGUS_API_KEY=${ARGUS_API_KEY}");
|
|
54
61
|
if (options.includeDb) {
|
|
55
62
|
lines.push(" depends_on:");
|
|
56
63
|
lines.push(" db:");
|
|
@@ -147,6 +154,16 @@ function generateEnv(options) {
|
|
|
147
154
|
lines.push("# Path to screenshots directory (mounted read-only into the container)");
|
|
148
155
|
lines.push(`SCREENSHOTS_PATH=${options.screenshotsPath}`);
|
|
149
156
|
lines.push("");
|
|
157
|
+
lines.push("# GitHub OAuth (create app at https://github.com/settings/developers)");
|
|
158
|
+
lines.push(`GITHUB_CLIENT_ID=${options.githubClientId}`);
|
|
159
|
+
lines.push(`GITHUB_CLIENT_SECRET=${options.githubClientSecret}`);
|
|
160
|
+
lines.push("");
|
|
161
|
+
lines.push("# Session encryption key (auto-generated)");
|
|
162
|
+
lines.push(`SESSION_SECRET=${options.sessionSecret}`);
|
|
163
|
+
lines.push("");
|
|
164
|
+
lines.push("# API key for CI/CD uploads (Authorization: Bearer <key>)");
|
|
165
|
+
lines.push(`ARGUS_API_KEY=${options.apiKey}`);
|
|
166
|
+
lines.push("");
|
|
150
167
|
return lines.join("\n");
|
|
151
168
|
}
|
|
152
169
|
|
|
@@ -209,6 +226,7 @@ function generateNginxConf(options) {
|
|
|
209
226
|
}
|
|
210
227
|
|
|
211
228
|
// cli/utils/docker.ts
|
|
229
|
+
import chalk2 from "chalk";
|
|
212
230
|
import { execa } from "execa";
|
|
213
231
|
async function isDockerInstalled() {
|
|
214
232
|
try {
|
|
@@ -227,28 +245,41 @@ async function isDockerRunning() {
|
|
|
227
245
|
}
|
|
228
246
|
}
|
|
229
247
|
async function dockerCompose(args, cwd, options) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
248
|
+
try {
|
|
249
|
+
return await execa("docker", ["compose", ...args], {
|
|
250
|
+
cwd,
|
|
251
|
+
stdio: "inherit",
|
|
252
|
+
...options
|
|
253
|
+
});
|
|
254
|
+
} catch (error) {
|
|
255
|
+
const cmd = `docker compose ${args.join(" ")}`;
|
|
256
|
+
const exitCode = error && typeof error === "object" && "exitCode" in error ? error.exitCode : 1;
|
|
257
|
+
console.error(
|
|
258
|
+
`
|
|
259
|
+
${chalk2.red("Error:")} ${chalk2.dim(cmd)} exited with code ${exitCode}`
|
|
260
|
+
);
|
|
261
|
+
process.exit(exitCode);
|
|
262
|
+
}
|
|
235
263
|
}
|
|
236
264
|
async function ensureDocker() {
|
|
237
265
|
if (!await isDockerInstalled()) {
|
|
238
|
-
|
|
239
|
-
"Docker is not installed.
|
|
266
|
+
console.error(
|
|
267
|
+
`${chalk2.red("Error:")} Docker is not installed.
|
|
268
|
+
Install it from: https://docs.docker.com/get-docker/`
|
|
240
269
|
);
|
|
270
|
+
process.exit(1);
|
|
241
271
|
}
|
|
242
272
|
if (!await isDockerRunning()) {
|
|
243
|
-
|
|
244
|
-
"Docker is not running. Please start Docker Desktop or the Docker daemon
|
|
273
|
+
console.error(
|
|
274
|
+
`${chalk2.red("Error:")} Docker is not running. Please start Docker Desktop or the Docker daemon.`
|
|
245
275
|
);
|
|
276
|
+
process.exit(1);
|
|
246
277
|
}
|
|
247
278
|
}
|
|
248
279
|
|
|
249
280
|
// cli/commands/init.ts
|
|
250
281
|
async function initCommand(options) {
|
|
251
|
-
p.intro(
|
|
282
|
+
p.intro(chalk3.bold("Argus Web Dashboard Setup"));
|
|
252
283
|
const outputDir = resolve2(options.dir || DEFAULT_DIR);
|
|
253
284
|
if (existsSync2(resolve2(outputDir, "docker-compose.yml"))) {
|
|
254
285
|
const overwrite = await p.confirm({
|
|
@@ -341,6 +372,23 @@ async function initCommand(options) {
|
|
|
341
372
|
message: "Screenshots storage path",
|
|
342
373
|
defaultValue: DEFAULT_SCREENSHOTS_PATH,
|
|
343
374
|
placeholder: DEFAULT_SCREENSHOTS_PATH
|
|
375
|
+
}),
|
|
376
|
+
githubClientId: () => p.text({
|
|
377
|
+
message: "GitHub OAuth Client ID",
|
|
378
|
+
validate: (value) => {
|
|
379
|
+
if (!value) return "Client ID is required for authentication";
|
|
380
|
+
}
|
|
381
|
+
}),
|
|
382
|
+
githubClientSecret: () => p.password({
|
|
383
|
+
message: "GitHub OAuth Client Secret",
|
|
384
|
+
validate: (value) => {
|
|
385
|
+
if (!value) return "Client Secret is required for authentication";
|
|
386
|
+
}
|
|
387
|
+
}),
|
|
388
|
+
apiKey: () => p.text({
|
|
389
|
+
message: "API key for CI/CD uploads",
|
|
390
|
+
defaultValue: randomBytes(32).toString("hex"),
|
|
391
|
+
placeholder: "auto-generated"
|
|
344
392
|
})
|
|
345
393
|
},
|
|
346
394
|
{
|
|
@@ -350,6 +398,18 @@ async function initCommand(options) {
|
|
|
350
398
|
}
|
|
351
399
|
}
|
|
352
400
|
);
|
|
401
|
+
const sessionSecret = randomBytes(32).toString("hex");
|
|
402
|
+
const callbackUrl = answers.domain ? `https://${answers.domain}/auth/github/callback` : `http://localhost:${parseInt(answers.port, 10) || DEFAULT_PORT}/auth/github/callback`;
|
|
403
|
+
p.note(
|
|
404
|
+
[
|
|
405
|
+
`Create a GitHub OAuth App at:`,
|
|
406
|
+
` ${chalk3.cyan("https://github.com/settings/developers")}`,
|
|
407
|
+
``,
|
|
408
|
+
`Set the callback URL to:`,
|
|
409
|
+
` ${chalk3.cyan(callbackUrl)}`
|
|
410
|
+
].join("\n"),
|
|
411
|
+
"GitHub OAuth Setup"
|
|
412
|
+
);
|
|
353
413
|
const s = p.spinner();
|
|
354
414
|
s.start("Generating configuration files");
|
|
355
415
|
mkdirSync(outputDir, { recursive: true });
|
|
@@ -373,7 +433,11 @@ async function initCommand(options) {
|
|
|
373
433
|
includeDb: answers.includeDb,
|
|
374
434
|
dbConnectionString: answers.dbConnectionString,
|
|
375
435
|
dbPassword: answers.dbPassword || DEFAULT_DB_PASSWORD,
|
|
376
|
-
screenshotsPath: answers.screenshotsPath || DEFAULT_SCREENSHOTS_PATH
|
|
436
|
+
screenshotsPath: answers.screenshotsPath || DEFAULT_SCREENSHOTS_PATH,
|
|
437
|
+
githubClientId: answers.githubClientId,
|
|
438
|
+
githubClientSecret: answers.githubClientSecret,
|
|
439
|
+
sessionSecret,
|
|
440
|
+
apiKey: answers.apiKey
|
|
377
441
|
};
|
|
378
442
|
writeFileSync(resolve2(outputDir, ".env"), generateEnv(envOptions));
|
|
379
443
|
if (answers.includeNginx && answers.domain) {
|
|
@@ -386,44 +450,40 @@ async function initCommand(options) {
|
|
|
386
450
|
);
|
|
387
451
|
}
|
|
388
452
|
s.stop("Configuration files generated");
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
"Setup complete"
|
|
399
|
-
);
|
|
453
|
+
const dir = options.dir || DEFAULT_DIR;
|
|
454
|
+
const nextSteps = [
|
|
455
|
+
`${chalk3.dim("Directory:")} ${outputDir}`,
|
|
456
|
+
`${chalk3.dim("Files:")} docker-compose.yml, .env${answers.includeNginx ? ", nginx.conf" : ""}`,
|
|
457
|
+
"",
|
|
458
|
+
`${chalk3.dim("Next steps:")}`,
|
|
459
|
+
` cd ${dir}`
|
|
460
|
+
];
|
|
461
|
+
nextSteps.push(" argus-web start");
|
|
400
462
|
if (answers.https === "letsencrypt" && answers.domain) {
|
|
401
|
-
|
|
402
|
-
`Run this to obtain your initial SSL certificate:
|
|
403
|
-
docker compose run --rm certbot certonly --webroot -w /var/www/certbot -d ${answers.domain}`
|
|
404
|
-
);
|
|
463
|
+
nextSteps.push(` argus-web setup-ssl ${answers.domain}`);
|
|
405
464
|
}
|
|
406
|
-
p.
|
|
465
|
+
p.note(nextSteps.join("\n"), "Setup complete");
|
|
466
|
+
p.outro(chalk3.green("Happy testing!"));
|
|
407
467
|
}
|
|
408
468
|
|
|
409
469
|
// cli/commands/start.ts
|
|
410
|
-
import
|
|
470
|
+
import chalk4 from "chalk";
|
|
411
471
|
async function startCommand(options) {
|
|
412
472
|
const cwd = findArgusDir(options.dir);
|
|
413
473
|
await ensureDocker();
|
|
414
|
-
console.log(
|
|
474
|
+
console.log(chalk4.blue("Starting Argus..."));
|
|
415
475
|
await dockerCompose(["up", "-d"], cwd);
|
|
416
|
-
console.log(
|
|
476
|
+
console.log(chalk4.green("Argus is running."));
|
|
417
477
|
}
|
|
418
478
|
|
|
419
479
|
// cli/commands/stop.ts
|
|
420
|
-
import
|
|
480
|
+
import chalk5 from "chalk";
|
|
421
481
|
async function stopCommand(options) {
|
|
422
482
|
const cwd = findArgusDir(options.dir);
|
|
423
483
|
await ensureDocker();
|
|
424
|
-
console.log(
|
|
484
|
+
console.log(chalk5.blue("Stopping Argus..."));
|
|
425
485
|
await dockerCompose(["down"], cwd);
|
|
426
|
-
console.log(
|
|
486
|
+
console.log(chalk5.green("Argus stopped."));
|
|
427
487
|
}
|
|
428
488
|
|
|
429
489
|
// cli/commands/logs.ts
|
|
@@ -438,11 +498,11 @@ async function logsCommand(options) {
|
|
|
438
498
|
}
|
|
439
499
|
|
|
440
500
|
// cli/commands/status.ts
|
|
441
|
-
import
|
|
501
|
+
import chalk6 from "chalk";
|
|
442
502
|
async function statusCommand(options) {
|
|
443
503
|
const cwd = findArgusDir(options.dir);
|
|
444
504
|
await ensureDocker();
|
|
445
|
-
console.log(
|
|
505
|
+
console.log(chalk6.blue("Argus status:\n"));
|
|
446
506
|
await dockerCompose(["ps"], cwd);
|
|
447
507
|
try {
|
|
448
508
|
const result = await dockerCompose(
|
|
@@ -451,23 +511,48 @@ async function statusCommand(options) {
|
|
|
451
511
|
{ stdio: "pipe" }
|
|
452
512
|
);
|
|
453
513
|
if (result.exitCode === 0) {
|
|
454
|
-
console.log(
|
|
514
|
+
console.log(chalk6.green("\nWeb dashboard is healthy."));
|
|
455
515
|
}
|
|
456
516
|
} catch {
|
|
457
|
-
console.log(
|
|
517
|
+
console.log(chalk6.yellow("\nWeb dashboard health check failed \u2014 container may still be starting."));
|
|
458
518
|
}
|
|
459
519
|
}
|
|
460
520
|
|
|
461
521
|
// cli/commands/upgrade.ts
|
|
462
|
-
import
|
|
522
|
+
import chalk7 from "chalk";
|
|
463
523
|
async function upgradeCommand(options) {
|
|
464
524
|
const cwd = findArgusDir(options.dir);
|
|
465
525
|
await ensureDocker();
|
|
466
|
-
console.log(
|
|
526
|
+
console.log(chalk7.blue("Pulling latest images..."));
|
|
467
527
|
await dockerCompose(["pull"], cwd);
|
|
468
|
-
console.log(
|
|
528
|
+
console.log(chalk7.blue("Restarting with new images..."));
|
|
469
529
|
await dockerCompose(["up", "-d"], cwd);
|
|
470
|
-
console.log(
|
|
530
|
+
console.log(chalk7.green("Argus upgraded successfully."));
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// cli/commands/setup-ssl.ts
|
|
534
|
+
import chalk8 from "chalk";
|
|
535
|
+
async function setupSslCommand(domain, options) {
|
|
536
|
+
const cwd = findArgusDir(options.dir);
|
|
537
|
+
await ensureDocker();
|
|
538
|
+
console.log(chalk8.blue(`Obtaining SSL certificate for ${domain}...`));
|
|
539
|
+
await dockerCompose(
|
|
540
|
+
[
|
|
541
|
+
"run",
|
|
542
|
+
"--rm",
|
|
543
|
+
"certbot",
|
|
544
|
+
"certonly",
|
|
545
|
+
"--webroot",
|
|
546
|
+
"-w",
|
|
547
|
+
"/var/www/certbot",
|
|
548
|
+
"-d",
|
|
549
|
+
domain
|
|
550
|
+
],
|
|
551
|
+
cwd
|
|
552
|
+
);
|
|
553
|
+
console.log(chalk8.blue("Restarting nginx..."));
|
|
554
|
+
await dockerCompose(["restart", "nginx"], cwd);
|
|
555
|
+
console.log(chalk8.green("SSL certificate installed successfully."));
|
|
471
556
|
}
|
|
472
557
|
|
|
473
558
|
// cli/index.ts
|
|
@@ -479,4 +564,5 @@ program.command("stop").description("Stop Argus (docker compose down)").option("
|
|
|
479
564
|
program.command("logs").description("View logs (docker compose logs -f)").option("-d, --dir <path>", "Argus directory").option("-s, --service <name>", "Service name (web, db, nginx)").action(logsCommand);
|
|
480
565
|
program.command("status").description("Check container status and health").option("-d, --dir <path>", "Argus directory").action(statusCommand);
|
|
481
566
|
program.command("upgrade").description("Pull latest images and restart").option("-d, --dir <path>", "Argus directory").action(upgradeCommand);
|
|
567
|
+
program.command("setup-ssl").description("Obtain a Let's Encrypt SSL certificate").argument("<domain>", "Domain to obtain certificate for").option("-d, --dir <path>", "Argus directory").action(setupSslCommand);
|
|
482
568
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@argus-vrt/web",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"clsx": "^2.1.1",
|
|
56
56
|
"drizzle-kit": "^0.31.8",
|
|
57
57
|
"drizzle-orm": "^0.45.1",
|
|
58
|
+
"iron-session": "^8.0.4",
|
|
58
59
|
"jsdom": "^27.0.0",
|
|
59
60
|
"lucide-react": "^0.561.0",
|
|
60
61
|
"postgres": "^3.4.8",
|