@argusaudit/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +64 -0
- package/dist/bin/argus.d.ts +2 -0
- package/dist/bin/argus.js +32 -0
- package/dist/bin/argus.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/src/commands/audit.d.ts +7 -0
- package/dist/src/commands/audit.js +179 -0
- package/dist/src/commands/audit.js.map +1 -0
- package/dist/src/commands/login.d.ts +2 -0
- package/dist/src/commands/login.js +102 -0
- package/dist/src/commands/login.js.map +1 -0
- package/dist/src/commands/logout.d.ts +2 -0
- package/dist/src/commands/logout.js +20 -0
- package/dist/src/commands/logout.js.map +1 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.js +30 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/lib/api.d.ts +12 -0
- package/dist/src/lib/api.js +120 -0
- package/dist/src/lib/api.js.map +1 -0
- package/dist/src/lib/auth.d.ts +2 -0
- package/dist/src/lib/auth.js +33 -0
- package/dist/src/lib/auth.js.map +1 -0
- package/dist/src/lib/callback-page.d.ts +1 -0
- package/dist/src/lib/callback-page.js +48 -0
- package/dist/src/lib/callback-page.js.map +1 -0
- package/dist/src/lib/docker.d.ts +33 -0
- package/dist/src/lib/docker.js +139 -0
- package/dist/src/lib/docker.js.map +1 -0
- package/dist/src/lib/http.d.ts +2 -0
- package/dist/src/lib/http.js +4 -0
- package/dist/src/lib/http.js.map +1 -0
- package/dist/src/lib/keychain.d.ts +4 -0
- package/dist/src/lib/keychain.js +46 -0
- package/dist/src/lib/keychain.js.map +1 -0
- package/dist/src/lib/registry.d.ts +1 -0
- package/dist/src/lib/registry.js +31 -0
- package/dist/src/lib/registry.js.map +1 -0
- package/dist/src/lib/worker-compose.yml +80 -0
- package/dist/src/types/audit.d.ts +8 -0
- package/dist/src/types/audit.js +2 -0
- package/dist/src/types/audit.js.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 the-mzakrzewski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# ARGUS CLI
|
|
2
|
+
|
|
3
|
+
**SQL Stress-Test Auditor.** Your SQL is fast — and we have proof.
|
|
4
|
+
|
|
5
|
+
ARGUS takes your schema and a query, generates realistic data at scale, benchmarks candidate optimizations against each other on your own machine, and reports which one actually wins — with hard numbers.
|
|
6
|
+
|
|
7
|
+
ARGUS currently supports **PostgreSQL only**.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @argusaudit/cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Requirements
|
|
16
|
+
|
|
17
|
+
- **Node.js >= 24**
|
|
18
|
+
- **Docker** and **Docker Compose >= 2.20** — benchmarks run in local containers. Compose 2.20 is the floor because the CLI drives the stack with `docker compose wait`.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
argus login # authenticate via browser
|
|
24
|
+
argus audit --ddl schema.sql --query slow.sql
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The audit prints a link to your report when it finishes.
|
|
28
|
+
|
|
29
|
+
### `argus audit`
|
|
30
|
+
|
|
31
|
+
| Flag | Purpose |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `--ddl <path>` | Path to your DDL/schema file. **Required.** |
|
|
34
|
+
| `--query <path>` | Path to the query to optimize. **Required.** |
|
|
35
|
+
| `--postgres-version <version>` | Benchmark against a specific Postgres, e.g. `17`, `17.5`, `17-bookworm`. Defaults to `18-alpine`. |
|
|
36
|
+
| `--keep-containers` | Leave containers running after the benchmark instead of tearing them down. Useful for debugging. |
|
|
37
|
+
|
|
38
|
+
### Other commands
|
|
39
|
+
|
|
40
|
+
- `argus login` — authenticate via browser; credentials are stored in your OS keychain.
|
|
41
|
+
- `argus logout` — remove stored credentials.
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
1. Your DDL and query are sent to the ARGUS engine, which proposes optimization variants (index / query rewrite / schema change).
|
|
46
|
+
2. The CLI starts an isolated Postgres via Docker Compose and pulls the `argusaudit/worker` image.
|
|
47
|
+
3. The worker seeds data matching realistic distributions, then benchmarks each variant 5x with `ROLLBACK` between runs.
|
|
48
|
+
4. Results are reported back and rendered at [argusaudit.dev](https://www.argusaudit.dev).
|
|
49
|
+
|
|
50
|
+
**Your data never leaves your machine** — only the DDL and query go to the cloud. Benchmarking happens entirely in local containers.
|
|
51
|
+
|
|
52
|
+
## Environment variables
|
|
53
|
+
|
|
54
|
+
All optional — the defaults point at production.
|
|
55
|
+
|
|
56
|
+
| Variable | Default | Purpose |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| `ARGUS_API_URL` | `https://api.argusaudit.dev/api/v1` | Engine API base URL. |
|
|
59
|
+
| `ARGUS_HUB_URL` | `https://www.argusaudit.dev` | Web app base URL, used for report links. |
|
|
60
|
+
| `ARGUS_WORKER_IMAGE` | `argusaudit/worker:0.1.0` | Worker image to run. Pinned by default so benchmarks stay reproducible. |
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT — see [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { dirname, resolve } from 'node:path';
|
|
6
|
+
import { readFileSync } from 'node:fs';
|
|
7
|
+
import { auditCommand } from '../src/commands/audit.js';
|
|
8
|
+
import { loginCommand } from '../src/commands/login.js';
|
|
9
|
+
import { logoutCommand } from '../src/commands/logout.js';
|
|
10
|
+
process.on('uncaughtException', (error) => {
|
|
11
|
+
console.log(chalk.red('✖ Unexpected error: Something went wrong. Please try again.'));
|
|
12
|
+
console.error('[argus internal]', error);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
});
|
|
15
|
+
process.on('unhandledRejection', (reason) => {
|
|
16
|
+
console.log(chalk.red('✖ Unexpected error: Something went wrong. Please try again.'));
|
|
17
|
+
console.error('[argus internal]', reason);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
});
|
|
20
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
// At runtime this file is at dist/bin/argus.js, so ../../package.json resolves to the project root
|
|
22
|
+
const { version } = JSON.parse(readFileSync(resolve(__dirname, '../../package.json'), 'utf8'));
|
|
23
|
+
program
|
|
24
|
+
.name('argus')
|
|
25
|
+
.description('ARGUS — SQL Stress-Test Auditor. Your SQL is fast — and we have proof.')
|
|
26
|
+
.version(version);
|
|
27
|
+
program.addHelpText('after', chalk.dim('\nARGUS currently supports PostgreSQL only.'));
|
|
28
|
+
program.addCommand(auditCommand());
|
|
29
|
+
program.addCommand(loginCommand());
|
|
30
|
+
program.addCommand(logoutCommand());
|
|
31
|
+
program.parse(process.argv);
|
|
32
|
+
//# sourceMappingURL=argus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"argus.js","sourceRoot":"","sources":["../../bin/argus.ts"],"names":[],"mappings":";AACA,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAC,aAAa,EAAC,MAAM,UAAU,CAAC;AACvC,OAAO,EAAC,OAAO,EAAE,OAAO,EAAC,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAC,YAAY,EAAC,MAAM,SAAS,CAAC;AACrC,OAAO,EAAC,YAAY,EAAC,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAC,YAAY,EAAC,MAAM,0BAA0B,CAAC;AACtD,OAAO,EAAC,aAAa,EAAC,MAAM,2BAA2B,CAAC;AAExD,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,KAAK,EAAE,EAAE;IACtC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,6DAA6D,CAAC,CAAC,CAAC;IACtF,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;IACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,EAAE;IACxC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,6DAA6D,CAAC,CAAC,CAAC;IACtF,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,mGAAmG;AACnG,MAAM,EAAC,OAAO,EAAC,GAAG,IAAI,CAAC,KAAK,CACxB,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,oBAAoB,CAAC,EAAE,MAAM,CAAC,CAC1C,CAAC;AAEzB,OAAO;KACF,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,wEAAwE,CAAC;KACrF,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC,CAAC;AAEvF,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC,CAAC;AAEpC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { audit, auditCommand } from './src/commands/audit.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAE,YAAY,EAAC,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { AuditCreateRequest } from '../types/audit.js';
|
|
3
|
+
export declare function audit({ ddlPath, queryPath, keepContainers, postgresVersion }: AuditCreateRequest & {
|
|
4
|
+
keepContainers?: boolean;
|
|
5
|
+
postgresVersion?: string;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
export declare function auditCommand(): Command;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { assertComposeVersion, cleanupWorker, generateCompose, getContainerErrors, runWorker } from '../lib/docker.js';
|
|
6
|
+
import { ApiError, get, post, postMultipart } from '../lib/api.js';
|
|
7
|
+
import { refreshTokens, requireAuth } from '../lib/auth.js';
|
|
8
|
+
import { resolvePostgresImage } from '../lib/registry.js';
|
|
9
|
+
import { getRefreshToken } from '../lib/keychain.js';
|
|
10
|
+
import { getEngineBaseUrl, getHubBaseUrl, getWorkerImage } from '../config.js';
|
|
11
|
+
export async function audit({ ddlPath, queryPath, keepContainers = false, postgresVersion }) {
|
|
12
|
+
const resolvedDdl = path.resolve(ddlPath);
|
|
13
|
+
const resolvedQuery = path.resolve(queryPath);
|
|
14
|
+
const toFileField = (filePath) => ({ filePath, filename: path.basename(filePath) });
|
|
15
|
+
let activeSpinner;
|
|
16
|
+
let composePath;
|
|
17
|
+
let tmpTokenPath;
|
|
18
|
+
const startSpinner = (text) => {
|
|
19
|
+
activeSpinner = ora({ text, discardStdin: false }).start();
|
|
20
|
+
return activeSpinner;
|
|
21
|
+
};
|
|
22
|
+
process.on('SIGINT', () => {
|
|
23
|
+
activeSpinner?.stop();
|
|
24
|
+
console.log(chalk.yellow('\n✖ Interrupted — stopping containers…'));
|
|
25
|
+
if (composePath)
|
|
26
|
+
cleanupWorker({ composePath, tmpTokenPath });
|
|
27
|
+
process.exit(130);
|
|
28
|
+
});
|
|
29
|
+
// Step 0: preflight — ensure Docker Compose is new enough for `docker compose wait`
|
|
30
|
+
const composeSpinner = startSpinner('Checking Docker Compose version…');
|
|
31
|
+
try {
|
|
32
|
+
assertComposeVersion();
|
|
33
|
+
composeSpinner.succeed('Docker Compose version OK');
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
composeSpinner.fail(err.message);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
// Step 0: resolve the requested postgres version before anything is created
|
|
40
|
+
let postgresImage;
|
|
41
|
+
if (postgresVersion) {
|
|
42
|
+
const resolveSpinner = startSpinner(`Resolving postgres version ${chalk.cyan(postgresVersion)}…`);
|
|
43
|
+
try {
|
|
44
|
+
postgresImage = await resolvePostgresImage(postgresVersion);
|
|
45
|
+
resolveSpinner.succeed(`Postgres image resolved · ${chalk.cyan(postgresImage)}`);
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
resolveSpinner.fail(err.message);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Step 1: upload files
|
|
53
|
+
let spinner = startSpinner('Uploading schema & query, creating audit…');
|
|
54
|
+
let result;
|
|
55
|
+
try {
|
|
56
|
+
result = await postMultipart('/audits', {
|
|
57
|
+
ddl: toFileField(resolvedDdl),
|
|
58
|
+
query: toFileField(resolvedQuery),
|
|
59
|
+
});
|
|
60
|
+
spinner.succeed(`Audit created · ${chalk.cyan(result.public_id)}`);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (err instanceof ApiError && err.status === 402) {
|
|
64
|
+
spinner.fail('No audits remaining. Visit the Argus website to top up.');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
spinner.fail(err.message);
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
// Step 2: get a fresh token to pass to containers
|
|
71
|
+
spinner = startSpinner('Refreshing credentials…');
|
|
72
|
+
let authToken;
|
|
73
|
+
try {
|
|
74
|
+
const storedRefreshToken = await getRefreshToken();
|
|
75
|
+
if (storedRefreshToken) {
|
|
76
|
+
authToken = await refreshTokens(storedRefreshToken);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// CI path: ARGUS_AUTH_TOKEN is set directly in env
|
|
80
|
+
authToken = await requireAuth();
|
|
81
|
+
}
|
|
82
|
+
spinner.succeed('Credentials refreshed');
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
spinner.fail(err.message);
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
// Step 3: prepare Docker environment
|
|
89
|
+
spinner = startSpinner('Preparing Docker environment…');
|
|
90
|
+
try {
|
|
91
|
+
({ composePath, tmpTokenPath } = generateCompose({
|
|
92
|
+
ddlPath: resolvedDdl,
|
|
93
|
+
auditId: result.public_id,
|
|
94
|
+
apiUrl: getEngineBaseUrl(),
|
|
95
|
+
authToken,
|
|
96
|
+
workerImage: getWorkerImage(),
|
|
97
|
+
postgresImage,
|
|
98
|
+
}));
|
|
99
|
+
spinner.succeed('Docker environment ready');
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
spinner.fail(err.message);
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
// Step 4: run benchmark
|
|
107
|
+
spinner = startSpinner('Running benchmark…');
|
|
108
|
+
try {
|
|
109
|
+
await runWorker({ composePath });
|
|
110
|
+
spinner.succeed('Benchmark complete');
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
spinner.fail(err.message);
|
|
114
|
+
// best-effort: report each container's error to the API
|
|
115
|
+
const containerErrors = getContainerErrors(composePath);
|
|
116
|
+
if (containerErrors.length > 0) {
|
|
117
|
+
const reportSpinner = startSpinner('Reporting errors…');
|
|
118
|
+
try {
|
|
119
|
+
await Promise.all(containerErrors.map((e) => post(`/audits/${result.public_id}/container-errors`, {
|
|
120
|
+
container_name: e.containerName,
|
|
121
|
+
error_details: e.errorDetails,
|
|
122
|
+
})));
|
|
123
|
+
reportSpinner.succeed('Errors reported');
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
reportSpinner.warn('Could not report errors to API');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
// Step 5: fetch results
|
|
132
|
+
spinner = startSpinner('Fetching results…');
|
|
133
|
+
let recipe;
|
|
134
|
+
try {
|
|
135
|
+
recipe = await get(`/audits/${result.public_id}/recipe`);
|
|
136
|
+
spinner.succeed(`Report ready · ${chalk.cyan(`${getHubBaseUrl()}/audits/${recipe.public_id}`)}`);
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
spinner.fail(err.message);
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
if (!composePath) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (keepContainers) {
|
|
148
|
+
console.log(chalk.yellow('\nContainers left running. To clean up manually:'));
|
|
149
|
+
console.log(chalk.dim(` docker compose -f ${composePath} down -v`));
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
cleanupWorker({ composePath, tmpTokenPath });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export function auditCommand() {
|
|
157
|
+
const cmd = new Command('audit');
|
|
158
|
+
cmd
|
|
159
|
+
.description('Run a benchmark audit on a SQL schema')
|
|
160
|
+
.requiredOption('--ddl <path>', 'Path to DDL file')
|
|
161
|
+
.requiredOption('--query <path>', 'Path to query file')
|
|
162
|
+
.option('--keep-containers', 'Skip docker compose down after benchmark (containers remain running)', false)
|
|
163
|
+
.option('--postgres-version <version>', 'PostgreSQL version to benchmark against (e.g. 17, 17.5, 17-bookworm); defaults to 18-alpine')
|
|
164
|
+
.action(async (options) => {
|
|
165
|
+
try {
|
|
166
|
+
await audit({
|
|
167
|
+
ddlPath: options.ddl,
|
|
168
|
+
queryPath: options.query,
|
|
169
|
+
keepContainers: options.keepContainers,
|
|
170
|
+
postgresVersion: options.postgresVersion,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
return cmd;
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/commands/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// `argus login` command — opens a browser for OAuth-style CLI login via loopback callback.
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import http from 'node:http';
|
|
4
|
+
import net from 'node:net';
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
import { URL } from 'node:url';
|
|
7
|
+
import open from 'open';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { getAccessToken, setTokens } from '../lib/keychain.js';
|
|
10
|
+
import { callbackPage } from '../lib/callback-page.js';
|
|
11
|
+
import { getEngineBaseUrl, getHubBaseUrl } from '../config.js';
|
|
12
|
+
async function findFreePort(start, end) {
|
|
13
|
+
for (let port = start; port <= end; port++) {
|
|
14
|
+
const available = await new Promise((resolve) => {
|
|
15
|
+
const server = net.createServer();
|
|
16
|
+
server.once('error', () => resolve(false));
|
|
17
|
+
server.once('listening', () => {
|
|
18
|
+
server.close(() => resolve(true));
|
|
19
|
+
});
|
|
20
|
+
server.listen(port, '127.0.0.1');
|
|
21
|
+
});
|
|
22
|
+
if (available)
|
|
23
|
+
return port;
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`No free port found in range ${start}-${end}`);
|
|
26
|
+
}
|
|
27
|
+
function generateCodeVerifier() {
|
|
28
|
+
return crypto.randomBytes(32).toString('base64url');
|
|
29
|
+
}
|
|
30
|
+
function generateCodeChallenge(verifier) {
|
|
31
|
+
return crypto.createHash('sha256').update(verifier).digest('base64url');
|
|
32
|
+
}
|
|
33
|
+
async function login() {
|
|
34
|
+
const existing = await getAccessToken();
|
|
35
|
+
if (existing) {
|
|
36
|
+
console.log(chalk.yellow('Already logged in. Run `argus logout` to switch accounts.'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const port = await findFreePort(9000, 9100);
|
|
40
|
+
const redirectUri = `http://127.0.0.1:${port}`;
|
|
41
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
42
|
+
const codeVerifier = generateCodeVerifier();
|
|
43
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
44
|
+
const codeReceived = new Promise((resolve, reject) => {
|
|
45
|
+
const server = http.createServer((req, res) => {
|
|
46
|
+
try {
|
|
47
|
+
const reqUrl = new URL(req.url ?? '/', `http://127.0.0.1:${port}`);
|
|
48
|
+
const code = reqUrl.searchParams.get('code');
|
|
49
|
+
const returnedState = reqUrl.searchParams.get('state');
|
|
50
|
+
if (returnedState !== state) {
|
|
51
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
52
|
+
res.end(callbackPage(false, 'Request rejected', 'The state parameter did not match, so this login attempt was rejected. Return to your terminal and run argus login again.'));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (!code) {
|
|
56
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
57
|
+
res.end(callbackPage(false, 'Login failed', 'No authorization code was received. Return to your terminal and run argus login again.'));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
61
|
+
res.end(callbackPage(true, 'Login successful', 'You are all set. You can close this tab and return to your terminal.'));
|
|
62
|
+
server.close();
|
|
63
|
+
resolve({ code });
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
reject(err);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
server.listen(port, '127.0.0.1');
|
|
70
|
+
});
|
|
71
|
+
const loginUrl = `${getHubBaseUrl()}/auth/login?redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}&code_challenge=${encodeURIComponent(codeChallenge)}`;
|
|
72
|
+
await open(loginUrl);
|
|
73
|
+
console.log(chalk.dim('Opening browser for login... (waiting for callback)'));
|
|
74
|
+
const { code } = await codeReceived;
|
|
75
|
+
const exchangeRes = await fetch(`${getEngineBaseUrl()}/auth/cli-token`, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: { 'Content-Type': 'application/json' },
|
|
78
|
+
body: JSON.stringify({ code, code_verifier: codeVerifier }),
|
|
79
|
+
});
|
|
80
|
+
if (!exchangeRes.ok) {
|
|
81
|
+
throw new Error('Failed to exchange login code for tokens.');
|
|
82
|
+
}
|
|
83
|
+
const tokens = await exchangeRes.json();
|
|
84
|
+
await setTokens(tokens.access_token, tokens.refresh_token);
|
|
85
|
+
console.log(chalk.green('Logged in successfully.'));
|
|
86
|
+
}
|
|
87
|
+
export function loginCommand() {
|
|
88
|
+
const cmd = new Command('login');
|
|
89
|
+
cmd
|
|
90
|
+
.description('Authenticate with ARGUS via browser login')
|
|
91
|
+
.action(async () => {
|
|
92
|
+
try {
|
|
93
|
+
await login();
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
console.error(chalk.red(`Login failed: ${err.message}`));
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
return cmd;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../../src/commands/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// `argus logout` command — clears stored tokens from the OS keychain.
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { clearTokens, getAccessToken } from '../lib/keychain.js';
|
|
5
|
+
export function logoutCommand() {
|
|
6
|
+
const cmd = new Command('logout');
|
|
7
|
+
cmd
|
|
8
|
+
.description('Log out and remove stored credentials')
|
|
9
|
+
.action(async () => {
|
|
10
|
+
const token = await getAccessToken();
|
|
11
|
+
if (!token) {
|
|
12
|
+
console.log(chalk.yellow('Not logged in.'));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
await clearTokens();
|
|
16
|
+
console.log(chalk.green('Logged out.'));
|
|
17
|
+
});
|
|
18
|
+
return cmd;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=logout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.js","sourceRoot":"","sources":["../../../src/commands/logout.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,OAAO,EAAC,OAAO,EAAC,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAC,WAAW,EAAE,cAAc,EAAC,MAAM,oBAAoB,CAAA;AAE9D,MAAM,UAAU,aAAa;IACzB,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAA;IAEjC,GAAG;SACE,WAAW,CAAC,uCAAuC,CAAC;SACpD,MAAM,CAAC,KAAK,IAAI,EAAE;QACf,MAAM,KAAK,GAAG,MAAM,cAAc,EAAE,CAAA;QACpC,IAAI,CAAC,KAAK,EAAE,CAAC;YACT,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAA;YAC3C,OAAM;QACV,CAAC;QACD,MAAM,WAAW,EAAE,CAAA;QACnB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAA;IAC3C,CAAC,CAAC,CAAA;IAEN,OAAO,GAAG,CAAA;AACd,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const DEFAULT_URL = 'https://api.argusaudit.dev/api/v1';
|
|
2
|
+
const DEFAULT_HUB_URL = 'https://www.argusaudit.dev';
|
|
3
|
+
// Pinned, not `latest`: a benchmarking tool must run a reproducible worker.
|
|
4
|
+
// Bump in lockstep with argus-worker releases.
|
|
5
|
+
const DEFAULT_WORKER_IMAGE = 'argusaudit/worker:0.1.0';
|
|
6
|
+
function warnIfInsecure(url) {
|
|
7
|
+
if (!url.startsWith('http://'))
|
|
8
|
+
return;
|
|
9
|
+
try {
|
|
10
|
+
const { hostname } = new URL(url);
|
|
11
|
+
if (hostname !== 'localhost' && hostname !== '127.0.0.1') {
|
|
12
|
+
process.stderr.write('⚠ WARNING: ARGUS_API_URL uses insecure HTTP for a non-local address. Use HTTPS in production.\n');
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// unparseable URL — let the request fail naturally
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function getEngineBaseUrl() {
|
|
20
|
+
const url = process.env['ARGUS_API_URL'] || DEFAULT_URL;
|
|
21
|
+
warnIfInsecure(url);
|
|
22
|
+
return url;
|
|
23
|
+
}
|
|
24
|
+
export function getHubBaseUrl() {
|
|
25
|
+
return process.env['ARGUS_HUB_URL'] || DEFAULT_HUB_URL;
|
|
26
|
+
}
|
|
27
|
+
export function getWorkerImage() {
|
|
28
|
+
return process.env['ARGUS_WORKER_IMAGE'] || DEFAULT_WORKER_IMAGE;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,GAAG,mCAAmC,CAAC;AACxD,MAAM,eAAe,GAAG,4BAA4B,CAAC;AAErD,4EAA4E;AAC5E,+CAA+C;AAC/C,MAAM,oBAAoB,GAAG,yBAAyB,CAAC;AAEvD,SAAS,cAAc,CAAC,GAAW;IAC/B,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO;IACvC,IAAI,CAAC;QACD,MAAM,EAAC,QAAQ,EAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,WAAW,EAAE,CAAC;YACvD,OAAO,CAAC,MAAM,CAAC,KAAK,CAChB,iGAAiG,CACpG,CAAC;QACN,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACL,mDAAmD;IACvD,CAAC;AACL,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC5B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,WAAW,CAAC;IACxD,cAAc,CAAC,GAAG,CAAC,CAAC;IACpB,OAAO,GAAG,CAAC;AACf,CAAC;AAED,MAAM,UAAU,aAAa;IACzB,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,eAAe,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,cAAc;IAC1B,OAAO,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,oBAAoB,CAAC;AACrE,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class ApiError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
constructor(status: number, message: string);
|
|
4
|
+
}
|
|
5
|
+
export interface FileField {
|
|
6
|
+
filePath: string;
|
|
7
|
+
filename: string;
|
|
8
|
+
}
|
|
9
|
+
export type MultipartField = string | FileField;
|
|
10
|
+
export declare function get<T>(path: string): Promise<T>;
|
|
11
|
+
export declare function post<T>(path: string, body: unknown): Promise<T>;
|
|
12
|
+
export declare function postMultipart<T>(path: string, fields: Record<string, MultipartField>): Promise<T>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { agent } from './http.js';
|
|
3
|
+
import { getEngineBaseUrl } from '../config.js';
|
|
4
|
+
import { refreshTokens, requireAuth } from './auth.js';
|
|
5
|
+
import { getRefreshToken } from './keychain.js';
|
|
6
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
7
|
+
export class ApiError extends Error {
|
|
8
|
+
status;
|
|
9
|
+
constructor(status, message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.name = this.constructor.name;
|
|
13
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
14
|
+
if (Error.captureStackTrace) {
|
|
15
|
+
Error.captureStackTrace(this, this.constructor);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// ─── Token refresh queue ──────────────────────────────────────────────────────
|
|
20
|
+
//
|
|
21
|
+
// Multiple concurrent requests may receive a 401 simultaneously. Instead of
|
|
22
|
+
// each firing its own refresh call, they all share a single in-flight promise.
|
|
23
|
+
// Once the refresh settles (success or failure) the promise is cleared so the
|
|
24
|
+
// next token expiry starts fresh.
|
|
25
|
+
let refreshPromise = null;
|
|
26
|
+
function getRefreshedToken() {
|
|
27
|
+
if (!refreshPromise) {
|
|
28
|
+
refreshPromise = (async () => {
|
|
29
|
+
const refreshToken = await getRefreshToken();
|
|
30
|
+
if (!refreshToken)
|
|
31
|
+
return null;
|
|
32
|
+
try {
|
|
33
|
+
return await refreshTokens(refreshToken);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
})().finally(() => {
|
|
39
|
+
refreshPromise = null;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return refreshPromise;
|
|
43
|
+
}
|
|
44
|
+
// ─── Core request ─────────────────────────────────────────────────────────────
|
|
45
|
+
async function request(perform) {
|
|
46
|
+
const jwt = await requireAuth();
|
|
47
|
+
let response = await perform(jwt);
|
|
48
|
+
if (response.status === 401) {
|
|
49
|
+
const newJwt = await getRefreshedToken();
|
|
50
|
+
if (!newJwt)
|
|
51
|
+
throw new Error('Session expired. Run: argus login');
|
|
52
|
+
response = await perform(newJwt);
|
|
53
|
+
}
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const body = await response.text();
|
|
56
|
+
let message = `API request failed [${response.status}]`;
|
|
57
|
+
try {
|
|
58
|
+
const json = JSON.parse(body);
|
|
59
|
+
if (json.detail?.message) {
|
|
60
|
+
message = json.detail.message;
|
|
61
|
+
}
|
|
62
|
+
else if (json.message) {
|
|
63
|
+
message = json.message;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
message = `${message}: ${body}`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
message = `${message}: ${body}`;
|
|
71
|
+
}
|
|
72
|
+
throw new ApiError(response.status, message);
|
|
73
|
+
}
|
|
74
|
+
return (response.status === 204 ? null : response.json());
|
|
75
|
+
}
|
|
76
|
+
// ─── Request helpers ──────────────────────────────────────────────────────────
|
|
77
|
+
function isFileField(value) {
|
|
78
|
+
return typeof value === 'object' && 'filePath' in value;
|
|
79
|
+
}
|
|
80
|
+
async function buildForm(fields) {
|
|
81
|
+
const form = new FormData();
|
|
82
|
+
for (const [name, value] of Object.entries(fields)) {
|
|
83
|
+
if (isFileField(value)) {
|
|
84
|
+
const buffer = await fs.readFile(value.filePath);
|
|
85
|
+
form.append(name, new Blob([buffer]), value.filename);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
form.append(name, value);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return form;
|
|
92
|
+
}
|
|
93
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
94
|
+
export function get(path) {
|
|
95
|
+
const url = `${getEngineBaseUrl()}${path}`;
|
|
96
|
+
return request(jwt => fetch(url, {
|
|
97
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
98
|
+
dispatcher: agent,
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
export function post(path, body) {
|
|
102
|
+
const url = `${getEngineBaseUrl()}${path}`;
|
|
103
|
+
return request(jwt => fetch(url, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
body: JSON.stringify(body),
|
|
106
|
+
headers: { Authorization: `Bearer ${jwt}`, 'Content-Type': 'application/json' },
|
|
107
|
+
dispatcher: agent,
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
export async function postMultipart(path, fields) {
|
|
111
|
+
const form = await buildForm(fields);
|
|
112
|
+
const url = `${getEngineBaseUrl()}${path}`;
|
|
113
|
+
return request(jwt => fetch(url, {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
body: form,
|
|
116
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
117
|
+
dispatcher: agent,
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/lib/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Core authentication logic: token retrieval and silent refresh on 401.
|
|
2
|
+
import { clearTokens, getAccessToken, setTokens } from './keychain.js';
|
|
3
|
+
import { getEngineBaseUrl } from '../config.js';
|
|
4
|
+
import { agent } from './http.js';
|
|
5
|
+
export async function refreshTokens(refreshToken) {
|
|
6
|
+
const url = `${getEngineBaseUrl()}/auth/refresh`;
|
|
7
|
+
const response = await fetch(url, {
|
|
8
|
+
method: 'POST',
|
|
9
|
+
headers: { 'Content-Type': 'application/json' },
|
|
10
|
+
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
11
|
+
dispatcher: agent,
|
|
12
|
+
});
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
await clearTokens();
|
|
15
|
+
throw new Error('Session expired. Run: argus login');
|
|
16
|
+
}
|
|
17
|
+
const data = await response.json();
|
|
18
|
+
await setTokens(data.access_token, data.refresh_token);
|
|
19
|
+
return data.access_token;
|
|
20
|
+
}
|
|
21
|
+
export async function requireAuth() {
|
|
22
|
+
// CI path: honour explicit token override
|
|
23
|
+
const envToken = process.env['ARGUS_AUTH_TOKEN'];
|
|
24
|
+
if (envToken) {
|
|
25
|
+
return envToken;
|
|
26
|
+
}
|
|
27
|
+
const accessToken = await getAccessToken();
|
|
28
|
+
if (!accessToken) {
|
|
29
|
+
throw new Error('Not authenticated. Run: argus login');
|
|
30
|
+
}
|
|
31
|
+
return accessToken;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/lib/auth.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,OAAO,EAAC,WAAW,EAAE,cAAc,EAAE,SAAS,EAAC,MAAM,eAAe,CAAA;AACpE,OAAO,EAAC,gBAAgB,EAAC,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAC,KAAK,EAAC,MAAM,WAAW,CAAA;AAE/B,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,YAAoB;IACpD,MAAM,GAAG,GAAG,GAAG,gBAAgB,EAAE,eAAe,CAAA;IAChD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC9B,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAC,cAAc,EAAE,kBAAkB,EAAC;QAC7C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAC,aAAa,EAAE,YAAY,EAAC,CAAC;QACnD,UAAU,EAAE,KAAK;KACL,CAAC,CAAA;IAEjB,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,WAAW,EAAE,CAAA;QACnB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAA;IACxD,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAqD,CAAA;IACrF,MAAM,SAAS,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,aAAa,CAAC,CAAA;IACtD,OAAO,IAAI,CAAC,YAAY,CAAA;AAC5B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC7B,0CAA0C;IAC1C,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;IAChD,IAAI,QAAQ,EAAE,CAAC;QACX,OAAO,QAAQ,CAAA;IACnB,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,cAAc,EAAE,CAAA;IAC1C,IAAI,CAAC,WAAW,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;IAC1D,CAAC;IAED,OAAO,WAAW,CAAA;AACtB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function callbackPage(success: boolean, title: string, message: string): string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// HTML page rendered in the browser after the OAuth loopback callback (`argus login`).
|
|
2
|
+
export function callbackPage(success, title, message) {
|
|
3
|
+
const accent = success ? '#4ade80' : '#f87171';
|
|
4
|
+
const mark = success
|
|
5
|
+
? '<path class="mark" d="M15 25.5l6.5 6.5L34 19"/>'
|
|
6
|
+
: '<path class="mark" d="M17.5 17.5l13 13m0-13l-13 13"/>';
|
|
7
|
+
return `<!DOCTYPE html>
|
|
8
|
+
<html lang="en">
|
|
9
|
+
<head>
|
|
10
|
+
<meta charset="utf-8">
|
|
11
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
12
|
+
<title>ARGUS CLI — ${title}</title>
|
|
13
|
+
<style>
|
|
14
|
+
:root{color-scheme:dark}
|
|
15
|
+
*{margin:0;box-sizing:border-box}
|
|
16
|
+
body{min-height:100vh;display:grid;place-items:center;padding:24px;background:#0a0a0a;background-image:radial-gradient(640px 320px at 50% -10%,${accent}14,transparent 70%);font-family:ui-monospace,'SF Mono',SFMono-Regular,Menlo,Consolas,'Liberation Mono',monospace;color:#e5e5e5}
|
|
17
|
+
main{width:100%;max-width:26rem;background:#171717;border:1px solid #262626;border-radius:12px;padding:40px 36px;text-align:center;animation:rise .5s cubic-bezier(.22,1,.36,1) both}
|
|
18
|
+
.brand{display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:32px;animation:fade .6s .05s both}
|
|
19
|
+
.brand span{font-size:13px;letter-spacing:.35em;color:#a3a3a3}
|
|
20
|
+
.brand em{font-style:normal;font-size:10px;letter-spacing:.15em;color:#737373;border:1px solid #333;border-radius:4px;padding:2px 6px}
|
|
21
|
+
svg{display:block;margin:0 auto 24px;color:${accent}}
|
|
22
|
+
.ring{stroke-dasharray:139;stroke-dashoffset:139;animation:draw .6s .15s ease-out forwards}
|
|
23
|
+
.mark{stroke-dasharray:40;stroke-dashoffset:40;animation:draw .35s .6s ease-out forwards}
|
|
24
|
+
h1{font-size:20px;font-weight:600;letter-spacing:-.01em;animation:fade .6s .25s both}
|
|
25
|
+
p{margin-top:12px;font-size:13px;line-height:1.7;color:#a3a3a3;animation:fade .6s .35s both}
|
|
26
|
+
.prompt{margin-top:28px;padding-top:20px;border-top:1px solid #262626;font-size:12px;color:#525252;animation:fade .6s .45s both}
|
|
27
|
+
.prompt b{font-weight:400;color:${accent}}
|
|
28
|
+
@keyframes rise{from{opacity:0;transform:translateY(12px) scale(.98)}to{opacity:1;transform:none}}
|
|
29
|
+
@keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
|
|
30
|
+
@keyframes draw{to{stroke-dashoffset:0}}
|
|
31
|
+
@media (prefers-reduced-motion:reduce){*{animation:none!important}.ring,.mark{stroke-dashoffset:0}}
|
|
32
|
+
</style>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<main>
|
|
36
|
+
<div class="brand"><span>ARGUS</span><em>CLI</em></div>
|
|
37
|
+
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
38
|
+
<circle class="ring" cx="24" cy="24" r="22"/>
|
|
39
|
+
${mark}
|
|
40
|
+
</svg>
|
|
41
|
+
<h1>${title}</h1>
|
|
42
|
+
<p>${message}</p>
|
|
43
|
+
<p class="prompt">$ argus login <b>${success ? '✓' : '✗'}</b></p>
|
|
44
|
+
</main>
|
|
45
|
+
</body>
|
|
46
|
+
</html>`;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=callback-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callback-page.js","sourceRoot":"","sources":["../../../src/lib/callback-page.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,MAAM,UAAU,YAAY,CAAC,OAAgB,EAAE,KAAa,EAAE,OAAe;IACzE,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAA;IAC9C,MAAM,IAAI,GAAG,OAAO;QAChB,CAAC,CAAC,iDAAiD;QACnD,CAAC,CAAC,uDAAuD,CAAA;IAC7D,OAAO;;;;;qBAKU,KAAK;;;;iJAIuH,MAAM;;;;;6CAK1G,MAAM;;;;;;kCAMjB,MAAM;;;;;;;;;;;;EAYtC,IAAI;;MAEA,KAAK;KACN,OAAO;qCACyB,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;;;QAGhD,CAAA;AACR,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
interface GenerateComposeOptions {
|
|
2
|
+
ddlPath: string;
|
|
3
|
+
auditId: string;
|
|
4
|
+
apiUrl: string;
|
|
5
|
+
authToken: string;
|
|
6
|
+
workerImage: string;
|
|
7
|
+
postgresImage?: string;
|
|
8
|
+
}
|
|
9
|
+
interface WorkerOptions {
|
|
10
|
+
composePath: string;
|
|
11
|
+
tmpTokenPath?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const MIN_COMPOSE_VERSION = "2.20.0";
|
|
14
|
+
/**
|
|
15
|
+
* Verify the installed Docker Compose is >= MIN_COMPOSE_VERSION.
|
|
16
|
+
*
|
|
17
|
+
* Runs `docker compose version --short`, parses the `major.minor.patch` it prints,
|
|
18
|
+
* and throws a clear, actionable Error when Docker/Compose is missing, the version
|
|
19
|
+
* cannot be parsed, or it is older than the minimum. Returns nothing on success.
|
|
20
|
+
*/
|
|
21
|
+
export declare function assertComposeVersion(): void;
|
|
22
|
+
export declare function generateCompose({ ddlPath, auditId, apiUrl, authToken, workerImage, postgresImage }: GenerateComposeOptions): {
|
|
23
|
+
composePath: string;
|
|
24
|
+
tmpTokenPath: string;
|
|
25
|
+
};
|
|
26
|
+
export declare function runWorker({ composePath }: WorkerOptions): Promise<void>;
|
|
27
|
+
export declare function cleanupWorker({ composePath, tmpTokenPath }: WorkerOptions): void;
|
|
28
|
+
export interface ContainerError {
|
|
29
|
+
containerName: string;
|
|
30
|
+
errorDetails: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function getContainerErrors(composePath: string): ContainerError[];
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
5
|
+
import yaml from 'js-yaml';
|
|
6
|
+
const COMPOSE_TEMPLATE_PATH = path.join(import.meta.dirname, 'worker-compose.yml');
|
|
7
|
+
// `docker compose ... wait` requires Compose v2.20.0+, so audits cannot run below it.
|
|
8
|
+
export const MIN_COMPOSE_VERSION = '2.20.0';
|
|
9
|
+
const MIN_COMPOSE_PARTS = MIN_COMPOSE_VERSION.split('.').map(Number);
|
|
10
|
+
/**
|
|
11
|
+
* Verify the installed Docker Compose is >= MIN_COMPOSE_VERSION.
|
|
12
|
+
*
|
|
13
|
+
* Runs `docker compose version --short`, parses the `major.minor.patch` it prints,
|
|
14
|
+
* and throws a clear, actionable Error when Docker/Compose is missing, the version
|
|
15
|
+
* cannot be parsed, or it is older than the minimum. Returns nothing on success.
|
|
16
|
+
*/
|
|
17
|
+
export function assertComposeVersion() {
|
|
18
|
+
const result = spawnSync('docker', ['compose', 'version', '--short'], { stdio: 'pipe', encoding: 'utf8' });
|
|
19
|
+
if (result.error || result.status !== 0) {
|
|
20
|
+
throw new Error(`Docker Compose not found. Install Docker Compose >= ${MIN_COMPOSE_VERSION} to run audits.`);
|
|
21
|
+
}
|
|
22
|
+
const match = result.stdout.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
23
|
+
if (!match) {
|
|
24
|
+
throw new Error(`Could not determine the Docker Compose version. Ensure Docker Compose >= ${MIN_COMPOSE_VERSION} is installed.`);
|
|
25
|
+
}
|
|
26
|
+
const found = [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
27
|
+
for (let i = 0; i < MIN_COMPOSE_PARTS.length; i++) {
|
|
28
|
+
if (found[i] > MIN_COMPOSE_PARTS[i])
|
|
29
|
+
break;
|
|
30
|
+
if (found[i] < MIN_COMPOSE_PARTS[i]) {
|
|
31
|
+
throw new Error(`Docker Compose ${found.join('.')} is too old. Argus requires Docker Compose >= ${MIN_COMPOSE_VERSION}. Please upgrade Docker Compose.`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function toContainerUrl(apiUrl) {
|
|
36
|
+
return apiUrl.replace(/^(https?:\/\/)(localhost|127\.0\.0\.1)(:\d+)/, '$1host.docker.internal$3');
|
|
37
|
+
}
|
|
38
|
+
function sanitizeLogs(raw) {
|
|
39
|
+
return raw
|
|
40
|
+
.split('\n')
|
|
41
|
+
.slice(0, 50)
|
|
42
|
+
.join('\n')
|
|
43
|
+
.replace(/(password|secret|token|key)=[^\s&]*/gi, '$1=[REDACTED]')
|
|
44
|
+
.replace(/:\/\/[^:]+:[^@]+@/g, '://[REDACTED]:[REDACTED]@');
|
|
45
|
+
}
|
|
46
|
+
export function generateCompose({ ddlPath, auditId, apiUrl, authToken, workerImage, postgresImage }) {
|
|
47
|
+
const runId = Date.now().toString();
|
|
48
|
+
const composePath = path.join(os.tmpdir(), `argus-compose-${runId}.yml`);
|
|
49
|
+
const tmpTokenPath = path.join(os.tmpdir(), `argus-token-${runId}`);
|
|
50
|
+
fs.writeFileSync(tmpTokenPath, authToken, { mode: 0o600 });
|
|
51
|
+
const resolvedDdlPath = path.resolve(ddlPath);
|
|
52
|
+
const ddlFilename = path.basename(ddlPath);
|
|
53
|
+
const template = fs.readFileSync(COMPOSE_TEMPLATE_PATH, 'utf8').replaceAll('__RUN_ID__', runId);
|
|
54
|
+
const compose = yaml.load(template);
|
|
55
|
+
const services = compose.services;
|
|
56
|
+
if (postgresImage) {
|
|
57
|
+
services.postgres.image = postgresImage;
|
|
58
|
+
}
|
|
59
|
+
services.seeder.image = workerImage;
|
|
60
|
+
services.worker.image = workerImage;
|
|
61
|
+
const containerTokenPath = '/run/secrets/auth_token';
|
|
62
|
+
const auditEnv = {
|
|
63
|
+
ARGUS_API_URL: toContainerUrl(apiUrl),
|
|
64
|
+
ARGUS_AUDIT_ID: auditId,
|
|
65
|
+
ARGUS_AUTH_TOKEN_FILE: containerTokenPath,
|
|
66
|
+
};
|
|
67
|
+
const seeder = services.seeder;
|
|
68
|
+
seeder.volumes = [
|
|
69
|
+
`${resolvedDdlPath}:/test/${ddlFilename}:ro`,
|
|
70
|
+
`${tmpTokenPath}:${containerTokenPath}:ro`,
|
|
71
|
+
];
|
|
72
|
+
Object.assign(seeder.environment, { DDL_FILE: `/test/${ddlFilename}`, ...auditEnv });
|
|
73
|
+
const worker = services.worker;
|
|
74
|
+
worker.volumes = [
|
|
75
|
+
`${tmpTokenPath}:${containerTokenPath}:ro`,
|
|
76
|
+
];
|
|
77
|
+
Object.assign(worker.environment, auditEnv);
|
|
78
|
+
fs.writeFileSync(composePath, yaml.dump(compose));
|
|
79
|
+
return { composePath, tmpTokenPath };
|
|
80
|
+
}
|
|
81
|
+
function spawnAsync(cmd, args) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const child = spawn(cmd, args, { stdio: 'pipe' });
|
|
84
|
+
child.on('close', (code) => {
|
|
85
|
+
if (code !== 0)
|
|
86
|
+
reject(new Error(`${cmd} ${args.slice(0, 3).join(' ')} failed`));
|
|
87
|
+
else
|
|
88
|
+
resolve();
|
|
89
|
+
});
|
|
90
|
+
child.on('error', reject);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
export async function runWorker({ composePath }) {
|
|
94
|
+
await spawnAsync('docker', ['compose', '-f', composePath, 'up', '-d']);
|
|
95
|
+
await spawnAsync('docker', ['compose', '-f', composePath, 'wait', 'worker']);
|
|
96
|
+
}
|
|
97
|
+
export function cleanupWorker({ composePath, tmpTokenPath }) {
|
|
98
|
+
try {
|
|
99
|
+
spawnSync('docker', ['compose', '-f', composePath, 'down', '-v'], { stdio: 'pipe' });
|
|
100
|
+
fs.unlinkSync(composePath);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// best-effort cleanup
|
|
104
|
+
}
|
|
105
|
+
if (tmpTokenPath) {
|
|
106
|
+
try {
|
|
107
|
+
fs.unlinkSync(tmpTokenPath);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// best-effort cleanup
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export function getContainerErrors(composePath) {
|
|
115
|
+
try {
|
|
116
|
+
const ps = spawnSync('docker', ['compose', '-f', composePath, 'ps', '--format', 'json'], { stdio: 'pipe', encoding: 'utf8' });
|
|
117
|
+
if (ps.status !== 0)
|
|
118
|
+
return [];
|
|
119
|
+
const containers = ps.stdout
|
|
120
|
+
.trim()
|
|
121
|
+
.split('\n')
|
|
122
|
+
.filter(Boolean)
|
|
123
|
+
.map((line) => JSON.parse(line));
|
|
124
|
+
return containers
|
|
125
|
+
.filter((c) => c.ExitCode !== 0)
|
|
126
|
+
.map((c) => {
|
|
127
|
+
const logsResult = spawnSync('docker', ['compose', '-f', composePath, 'logs', c.Service, '--no-color', '--tail', '50'], { stdio: 'pipe', encoding: 'utf8' });
|
|
128
|
+
if (logsResult.status !== 0) {
|
|
129
|
+
return { containerName: c.Service, errorDetails: `exited with code ${c.ExitCode}` };
|
|
130
|
+
}
|
|
131
|
+
const raw = logsResult.stdout.trim() || `exited with code ${c.ExitCode}`;
|
|
132
|
+
return { containerName: c.Service, errorDetails: sanitizeLogs(raw) };
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=docker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docker.js","sourceRoot":"","sources":["../../../src/lib/docker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/lib/http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAC,MAAM,QAAQ,CAAC;AAE7B,kFAAkF;AAClF,MAAM,CAAC,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,EAAC,gBAAgB,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,EAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function getAccessToken(): Promise<string | null>;
|
|
2
|
+
export declare function getRefreshToken(): Promise<string | null>;
|
|
3
|
+
export declare function setTokens(accessToken: string, refreshToken: string): Promise<void>;
|
|
4
|
+
export declare function clearTokens(): Promise<void>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Thin wrapper around @napi-rs/keyring for secure OS-level token storage.
|
|
2
|
+
import { Entry } from '@napi-rs/keyring';
|
|
3
|
+
const SERVICE = 'argus-cli';
|
|
4
|
+
const ACCESS_TOKEN_ACCOUNT = 'access_token';
|
|
5
|
+
const REFRESH_TOKEN_ACCOUNT = 'refresh_token';
|
|
6
|
+
export async function getAccessToken() {
|
|
7
|
+
try {
|
|
8
|
+
const entry = new Entry(SERVICE, ACCESS_TOKEN_ACCOUNT);
|
|
9
|
+
return entry.getPassword() ?? null;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function getRefreshToken() {
|
|
16
|
+
try {
|
|
17
|
+
const entry = new Entry(SERVICE, REFRESH_TOKEN_ACCOUNT);
|
|
18
|
+
return entry.getPassword() ?? null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export async function setTokens(accessToken, refreshToken) {
|
|
25
|
+
const accessEntry = new Entry(SERVICE, ACCESS_TOKEN_ACCOUNT);
|
|
26
|
+
accessEntry.setPassword(accessToken);
|
|
27
|
+
const refreshEntry = new Entry(SERVICE, REFRESH_TOKEN_ACCOUNT);
|
|
28
|
+
refreshEntry.setPassword(refreshToken);
|
|
29
|
+
}
|
|
30
|
+
export async function clearTokens() {
|
|
31
|
+
try {
|
|
32
|
+
const accessEntry = new Entry(SERVICE, ACCESS_TOKEN_ACCOUNT);
|
|
33
|
+
accessEntry.deletePassword();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// ignore if already absent
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const refreshEntry = new Entry(SERVICE, REFRESH_TOKEN_ACCOUNT);
|
|
40
|
+
refreshEntry.deletePassword();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// ignore if already absent
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=keychain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keychain.js","sourceRoot":"","sources":["../../../src/lib/keychain.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,OAAO,EAAC,KAAK,EAAC,MAAM,kBAAkB,CAAA;AAEtC,MAAM,OAAO,GAAG,WAAW,CAAA;AAC3B,MAAM,oBAAoB,GAAG,cAAc,CAAA;AAC3C,MAAM,qBAAqB,GAAG,eAAe,CAAA;AAE7C,MAAM,CAAC,KAAK,UAAU,cAAc;IAChC,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAA;QACtD,OAAO,KAAK,CAAC,WAAW,EAAE,IAAI,IAAI,CAAA;IACtC,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAA;IACf,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe;IACjC,IAAI,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAA;QACvD,OAAO,KAAK,CAAC,WAAW,EAAE,IAAI,IAAI,CAAA;IACtC,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAA;IACf,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,WAAmB,EAAE,YAAoB;IACrE,MAAM,WAAW,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAA;IAC5D,WAAW,CAAC,WAAW,CAAC,WAAW,CAAC,CAAA;IACpC,MAAM,YAAY,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAA;IAC9D,YAAY,CAAC,WAAW,CAAC,YAAY,CAAC,CAAA;AAC1C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC7B,IAAI,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAA;QAC5D,WAAW,CAAC,cAAc,EAAE,CAAA;IAChC,CAAC;IAAC,MAAM,CAAC;QACL,2BAA2B;IAC/B,CAAC;IACD,IAAI,CAAC;QACD,MAAM,YAAY,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,qBAAqB,CAAC,CAAA;QAC9D,YAAY,CAAC,cAAc,EAAE,CAAA;IACjC,CAAC;IAAC,MAAM,CAAC;QACL,2BAA2B;IAC/B,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolvePostgresImage(version: string): Promise<string>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { request } from 'undici';
|
|
2
|
+
import { agent } from './http.js';
|
|
3
|
+
const DOCKER_HUB_TAGS_URL = 'https://hub.docker.com/v2/repositories/library/postgres/tags';
|
|
4
|
+
// Debian/Alpine flavor suffixes published for the official postgres image.
|
|
5
|
+
const FLAVOR_PATTERN = /-(alpine|bookworm|bullseye|trixie|slim)/;
|
|
6
|
+
async function tagExists(tag) {
|
|
7
|
+
let statusCode;
|
|
8
|
+
try {
|
|
9
|
+
const response = await request(`${DOCKER_HUB_TAGS_URL}/${encodeURIComponent(tag)}`, { dispatcher: agent });
|
|
10
|
+
statusCode = response.statusCode;
|
|
11
|
+
await response.body.dump();
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
throw new Error(`Could not verify postgres version against Docker Hub: ${err.message}`);
|
|
15
|
+
}
|
|
16
|
+
if (statusCode === 200)
|
|
17
|
+
return true;
|
|
18
|
+
if (statusCode === 404)
|
|
19
|
+
return false;
|
|
20
|
+
throw new Error(`Could not verify postgres version against Docker Hub: unexpected HTTP ${statusCode}`);
|
|
21
|
+
}
|
|
22
|
+
export async function resolvePostgresImage(version) {
|
|
23
|
+
const candidates = FLAVOR_PATTERN.test(version) ? [version] : [`${version}-alpine`, version];
|
|
24
|
+
for (const tag of candidates) {
|
|
25
|
+
if (await tagExists(tag)) {
|
|
26
|
+
return `postgres:${tag}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
throw new Error(`Postgres version '${version}' does not exist on Docker Hub (tried tags: ${candidates.join(', ')})`);
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/lib/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,OAAO,EAAC,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAC,KAAK,EAAC,MAAM,WAAW,CAAC;AAEhC,MAAM,mBAAmB,GAAG,8DAA8D,CAAC;AAE3F,2EAA2E;AAC3E,MAAM,cAAc,GAAG,yCAAyC,CAAC;AAEjE,KAAK,UAAU,SAAS,CAAC,GAAW;IAChC,IAAI,UAAkB,CAAC;IACvB,IAAI,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,mBAAmB,IAAI,kBAAkB,CAAC,GAAG,CAAC,EAAE,EAAE,EAAC,UAAU,EAAE,KAAK,EAAC,CAAC,CAAC;QACzG,UAAU,GAAG,QAAQ,CAAC,UAAU,CAAC;QACjC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IAC/B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,yDAA0D,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IACvG,CAAC;IACD,IAAI,UAAU,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,UAAU,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IACrC,MAAM,IAAI,KAAK,CAAC,yEAAyE,UAAU,EAAE,CAAC,CAAC;AAC3G,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,OAAe;IACtD,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,SAAS,EAAE,OAAO,CAAC,CAAC;IAE7F,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,MAAM,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,YAAY,GAAG,EAAE,CAAC;QAC7B,CAAC;IACL,CAAC;IAED,MAAM,IAAI,KAAK,CACX,qBAAqB,OAAO,+CAA+C,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACtG,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
version: '3.9'
|
|
2
|
+
|
|
3
|
+
name: argus-__RUN_ID__
|
|
4
|
+
|
|
5
|
+
services:
|
|
6
|
+
postgres:
|
|
7
|
+
container_name: argus-postgres-__RUN_ID__
|
|
8
|
+
image: postgres:18-alpine
|
|
9
|
+
restart: "no"
|
|
10
|
+
shm_size: '1gb'
|
|
11
|
+
ulimits:
|
|
12
|
+
nofile:
|
|
13
|
+
soft: 65536
|
|
14
|
+
hard: 65536
|
|
15
|
+
environment:
|
|
16
|
+
POSTGRES_USER: argus
|
|
17
|
+
POSTGRES_PASSWORD: argus
|
|
18
|
+
POSTGRES_DB: argus
|
|
19
|
+
volumes:
|
|
20
|
+
- pg_data:/var/lib/postgresql/data
|
|
21
|
+
healthcheck:
|
|
22
|
+
test: [ "CMD-SHELL", "pg_isready -U argus" ]
|
|
23
|
+
interval: 2s
|
|
24
|
+
timeout: 5s
|
|
25
|
+
retries: 10
|
|
26
|
+
deploy:
|
|
27
|
+
resources:
|
|
28
|
+
limits:
|
|
29
|
+
cpus: '2.0'
|
|
30
|
+
memory: 2G
|
|
31
|
+
pids: 200
|
|
32
|
+
reservations:
|
|
33
|
+
memory: 512M
|
|
34
|
+
|
|
35
|
+
seeder:
|
|
36
|
+
container_name: argus-seeder-__RUN_ID__
|
|
37
|
+
# Placeholder — always overwritten by generateCompose from config.ts
|
|
38
|
+
image: argus-worker:placeholder
|
|
39
|
+
command: argus-seeder
|
|
40
|
+
restart: "no"
|
|
41
|
+
ulimits:
|
|
42
|
+
nofile:
|
|
43
|
+
soft: 65536
|
|
44
|
+
hard: 65536
|
|
45
|
+
depends_on:
|
|
46
|
+
postgres:
|
|
47
|
+
condition: service_healthy
|
|
48
|
+
environment:
|
|
49
|
+
DATABASE_URL: postgres://argus:argus@postgres:5432/argus?sslmode=disable
|
|
50
|
+
deploy:
|
|
51
|
+
resources:
|
|
52
|
+
limits:
|
|
53
|
+
cpus: '1.0'
|
|
54
|
+
memory: 512M
|
|
55
|
+
pids: 100
|
|
56
|
+
|
|
57
|
+
worker:
|
|
58
|
+
container_name: argus-worker-__RUN_ID__
|
|
59
|
+
# Placeholder — always overwritten by generateCompose from config.ts
|
|
60
|
+
image: argus-worker:placeholder
|
|
61
|
+
command: argus-worker
|
|
62
|
+
restart: "no"
|
|
63
|
+
ulimits:
|
|
64
|
+
nofile:
|
|
65
|
+
soft: 65536
|
|
66
|
+
hard: 65536
|
|
67
|
+
environment:
|
|
68
|
+
DATABASE_URL: postgres://argus:argus@postgres:5432/argus?sslmode=disable
|
|
69
|
+
depends_on:
|
|
70
|
+
seeder:
|
|
71
|
+
condition: service_completed_successfully
|
|
72
|
+
deploy:
|
|
73
|
+
resources:
|
|
74
|
+
limits:
|
|
75
|
+
cpus: '1.0'
|
|
76
|
+
memory: 1G
|
|
77
|
+
pids: 100
|
|
78
|
+
|
|
79
|
+
volumes:
|
|
80
|
+
pg_data:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/types/audit.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@argusaudit/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ARGUS — SQL Stress-Test Auditor. Benchmark-as-a-Service CLI tool that proves SQL optimizations with hard numbers.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"argus": "./dist/bin/argus.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc && cp src/lib/worker-compose.yml dist/src/lib/worker-compose.yml",
|
|
21
|
+
"prepublishOnly": "pnpm run build",
|
|
22
|
+
"test": "node --test",
|
|
23
|
+
"lint": "eslint ."
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"sql",
|
|
27
|
+
"benchmark",
|
|
28
|
+
"performance",
|
|
29
|
+
"postgres",
|
|
30
|
+
"audit",
|
|
31
|
+
"optimization"
|
|
32
|
+
],
|
|
33
|
+
"author": "the-mzakrzewski",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/the-mzakrzewski/argus-cli.git"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/the-mzakrzewski/argus-cli#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/the-mzakrzewski/argus-cli/issues"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=24.0.0"
|
|
45
|
+
},
|
|
46
|
+
"packageManager": "pnpm@10.7.0",
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@napi-rs/keyring": "1.2.0",
|
|
49
|
+
"chalk": "5.6.2",
|
|
50
|
+
"commander": "14.0.3",
|
|
51
|
+
"js-yaml": "4.1.1",
|
|
52
|
+
"open": "11.0.0",
|
|
53
|
+
"ora": "9.3.0",
|
|
54
|
+
"undici": "7.24.5"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/js-yaml": "4.0.9",
|
|
58
|
+
"@types/node": "25.5.0",
|
|
59
|
+
"eslint": "10.0.3",
|
|
60
|
+
"typescript": "5.9.3"
|
|
61
|
+
}
|
|
62
|
+
}
|