rubrkit 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/README.md +126 -0
- package/bin/rubrkit.js +16 -0
- package/package.json +28 -0
- package/src/adapters.js +118 -0
- package/src/api.js +101 -0
- package/src/args.js +175 -0
- package/src/cli.js +93 -0
- package/src/config.js +169 -0
- package/src/errors.js +55 -0
- package/src/formats.js +222 -0
- package/src/index.d.ts +76 -0
- package/src/localChecks.js +680 -0
- package/src/manifest.js +118 -0
- package/src/pathSafety.js +62 -0
- package/src/prompts.js +149 -0
- package/src/pull.js +676 -0
- package/src/sdk.js +443 -0
- package/src/testingCli.js +431 -0
package/src/config.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { usageError } from './errors.js';
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_API_URL = 'https://rubrkit.com/api/v1';
|
|
7
|
+
|
|
8
|
+
const CONFIG_FIELDS = new Set([
|
|
9
|
+
'apiUrl',
|
|
10
|
+
'destination',
|
|
11
|
+
'agent',
|
|
12
|
+
'artifactBundle',
|
|
13
|
+
'artifact',
|
|
14
|
+
'rubric',
|
|
15
|
+
'format',
|
|
16
|
+
'output',
|
|
17
|
+
'failUnder',
|
|
18
|
+
'failOn',
|
|
19
|
+
'all',
|
|
20
|
+
'yes',
|
|
21
|
+
'dryRun',
|
|
22
|
+
'force',
|
|
23
|
+
'prune',
|
|
24
|
+
'updateOnly',
|
|
25
|
+
'ci',
|
|
26
|
+
'local',
|
|
27
|
+
'remote',
|
|
28
|
+
'noAi',
|
|
29
|
+
'watch',
|
|
30
|
+
'changed',
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {{
|
|
35
|
+
* parsed: ReturnType<import('./args.js').parseArgs>,
|
|
36
|
+
* env?: Record<string, string | undefined>,
|
|
37
|
+
* cwd?: string,
|
|
38
|
+
* fsImpl?: Pick<typeof fs, 'readFile' | 'stat'>,
|
|
39
|
+
* }} params
|
|
40
|
+
*/
|
|
41
|
+
export async function resolveConfig({ parsed, env = process.env, cwd = process.cwd(), fsImpl = fs }) {
|
|
42
|
+
const config = await loadConfig({ configPath: stringOption(parsed.options.config), cwd, fsImpl });
|
|
43
|
+
const options = parsed.options;
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
command: parsed.command,
|
|
47
|
+
selector: parsed.selector,
|
|
48
|
+
apiKey: stringOption(options['api-key']) ?? env.RUBRKIT_API_KEY ?? null,
|
|
49
|
+
apiUrl: normalizeApiUrl(stringOption(options['api-url']) ?? env.RUBRKIT_API_URL ?? stringOption(config.apiUrl) ?? DEFAULT_API_URL),
|
|
50
|
+
destination: stringOption(options.destination) ?? stringOption(config.destination) ?? cwd,
|
|
51
|
+
agent: stringOption(options.agent) ?? stringOption(config.agent) ?? 'auto',
|
|
52
|
+
artifactBundle: stringOption(options['artifact-bundle']) ?? stringOption(config.artifactBundle) ?? null,
|
|
53
|
+
artifact: stringOption(options.artifact) ?? stringOption(config.artifact) ?? null,
|
|
54
|
+
rubric: stringOption(options.rubric) ?? stringOption(config.rubric) ?? null,
|
|
55
|
+
format: stringOption(options.format) ?? stringOption(config.format) ?? 'text',
|
|
56
|
+
output: stringOption(options.output) ?? stringOption(config.output) ?? null,
|
|
57
|
+
failUnder: numberOption(options['fail-under']) ?? numberOption(config.failUnder),
|
|
58
|
+
failOn: stringOption(options['fail-on']) ?? stringOption(config.failOn) ?? null,
|
|
59
|
+
all: booleanOption(options.all) || booleanOption(config.all),
|
|
60
|
+
yes: booleanOption(options.yes) || booleanOption(config.yes),
|
|
61
|
+
dryRun: booleanOption(options['dry-run']) || booleanOption(config.dryRun),
|
|
62
|
+
force: booleanOption(options.force) || booleanOption(config.force),
|
|
63
|
+
prune: booleanOption(options.prune) || booleanOption(config.prune),
|
|
64
|
+
updateOnly: booleanOption(options['update-only']) || booleanOption(config.updateOnly),
|
|
65
|
+
ci: booleanOption(options.ci) || booleanOption(config.ci),
|
|
66
|
+
local: booleanOption(options.local) || booleanOption(config.local),
|
|
67
|
+
remote: booleanOption(options.remote) || booleanOption(config.remote),
|
|
68
|
+
noAi: booleanOption(options['no-ai']) || booleanOption(config.noAi),
|
|
69
|
+
watch: booleanOption(options.watch) || booleanOption(config.watch),
|
|
70
|
+
changed: booleanOption(options.changed) || booleanOption(config.changed),
|
|
71
|
+
configPath: stringOption(options.config) ?? null,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {{ configPath?: string | null, cwd: string, fsImpl: Pick<typeof fs, 'readFile' | 'stat'> }} params
|
|
77
|
+
*/
|
|
78
|
+
async function loadConfig({ configPath, cwd, fsImpl }) {
|
|
79
|
+
const candidate = configPath ? path.resolve(cwd, configPath) : path.join(cwd, '.rubrkit', 'config.json');
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
if (!configPath) {
|
|
83
|
+
await fsImpl.stat(candidate);
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let parsed;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
parsed = JSON.parse(await fsImpl.readFile(candidate, 'utf8'));
|
|
97
|
+
} catch (error) {
|
|
98
|
+
throw usageError(`Could not read Rubrkit config at ${candidate}: ${error instanceof Error ? error.message : 'invalid JSON'}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
102
|
+
throw usageError('Rubrkit config must be a JSON object.');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if ('apiKey' in parsed || 'api-key' in parsed || 'RUBRKIT_API_KEY' in parsed) {
|
|
106
|
+
throw usageError('Do not store API keys in Rubrkit config files. Use RUBRKIT_API_KEY or --api-key instead.');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const field of Object.keys(parsed)) {
|
|
110
|
+
if (!CONFIG_FIELDS.has(field)) {
|
|
111
|
+
throw usageError(`Unknown Rubrkit config field "${field}".`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return parsed;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @param {unknown} value
|
|
120
|
+
*/
|
|
121
|
+
function stringOption(value) {
|
|
122
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {unknown} value
|
|
127
|
+
*/
|
|
128
|
+
function booleanOption(value) {
|
|
129
|
+
return value === true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @param {unknown} value
|
|
134
|
+
*/
|
|
135
|
+
function numberOption(value) {
|
|
136
|
+
if (value === undefined || value === null || value === false) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const parsed = Number(value);
|
|
141
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @param {string} value
|
|
146
|
+
*/
|
|
147
|
+
export function normalizeApiUrl(value) {
|
|
148
|
+
let parsed;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
parsed = new URL(value);
|
|
152
|
+
} catch {
|
|
153
|
+
throw usageError(`Invalid Rubrkit API URL "${value}".`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
157
|
+
throw usageError('Rubrkit API URL must use http or https.');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (parsed.pathname === '/' || parsed.pathname === '') {
|
|
161
|
+
parsed.pathname = '/api/v1';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
parsed.pathname = parsed.pathname.replace(/\/+$/, '');
|
|
165
|
+
parsed.search = '';
|
|
166
|
+
parsed.hash = '';
|
|
167
|
+
|
|
168
|
+
return parsed.toString().replace(/\/$/, '');
|
|
169
|
+
}
|
package/src/errors.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export class RubrkitCliError extends Error {
|
|
2
|
+
/**
|
|
3
|
+
* @param {string} message
|
|
4
|
+
* @param {{ code?: string, exitCode?: number, details?: unknown }} [options]
|
|
5
|
+
*/
|
|
6
|
+
constructor(message, { code = 'rubrkit_cli_error', exitCode = 1, details = undefined } = {}) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'RubrkitCliError';
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.exitCode = exitCode;
|
|
11
|
+
this.details = details;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const EXIT_CODES = {
|
|
16
|
+
checksFailed: 1,
|
|
17
|
+
usage: 2,
|
|
18
|
+
auth: 3,
|
|
19
|
+
blocked: 4,
|
|
20
|
+
network: 5,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} message
|
|
25
|
+
*/
|
|
26
|
+
export function usageError(message) {
|
|
27
|
+
return new RubrkitCliError(message, { code: 'invalid_cli_usage', exitCode: EXIT_CODES.usage });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {string} message
|
|
32
|
+
*/
|
|
33
|
+
export function authError(message) {
|
|
34
|
+
return new RubrkitCliError(message, { code: 'missing_api_key', exitCode: EXIT_CODES.auth });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {string | null | undefined} code
|
|
39
|
+
* @param {number} [status]
|
|
40
|
+
*/
|
|
41
|
+
export function exitCodeForApiFailure(code, status = 0) {
|
|
42
|
+
if (status === 401 || status === 403) {
|
|
43
|
+
return EXIT_CODES.auth;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (['credit_limit_exceeded', 'usage_limit_exceeded', 'ai_circuit_breaker_open'].includes(String(code))) {
|
|
47
|
+
return EXIT_CODES.blocked;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (status >= 500 || status === 429 || status === 0) {
|
|
51
|
+
return EXIT_CODES.network;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return EXIT_CODES.checksFailed;
|
|
55
|
+
}
|
package/src/formats.js
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {{ payload: Record<string, any>, format?: string, output?: string | null, stdout: NodeJS.WritableStream, fsImpl?: typeof fs }} params
|
|
7
|
+
*/
|
|
8
|
+
export async function writeFormattedResult({ payload, format = 'text', output = null, stdout, fsImpl = fs }) {
|
|
9
|
+
const rendered = renderResult(payload, format);
|
|
10
|
+
|
|
11
|
+
if (output) {
|
|
12
|
+
await fsImpl.writeFile(output, rendered, 'utf8');
|
|
13
|
+
if (format !== 'json') {
|
|
14
|
+
stdout.write(`Wrote Rubrkit ${format} report to ${output}\n`);
|
|
15
|
+
}
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
stdout.write(rendered);
|
|
20
|
+
if (!rendered.endsWith('\n')) {
|
|
21
|
+
stdout.write('\n');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {Record<string, any>} payload
|
|
27
|
+
* @param {string} format
|
|
28
|
+
*/
|
|
29
|
+
export function renderResult(payload, format = 'text') {
|
|
30
|
+
if (format === 'json') {
|
|
31
|
+
return `${JSON.stringify(payload, null, 2)}\n`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (format === 'junit') {
|
|
35
|
+
return renderJUnit(payload);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return renderText(payload);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {Record<string, any>} payload
|
|
43
|
+
*/
|
|
44
|
+
function renderText(payload) {
|
|
45
|
+
if (payload.mode === 'remote-dry-run') {
|
|
46
|
+
return [
|
|
47
|
+
`Rubrkit ${payload.command} remote dry run`,
|
|
48
|
+
`Target: ${payload.target ?? '(none)'}`,
|
|
49
|
+
`Endpoint: ${payload.endpoint}`,
|
|
50
|
+
`Method: ${payload.method}`,
|
|
51
|
+
`Polls job: ${payload.pollsJob ? 'yes' : 'no'}`,
|
|
52
|
+
payload.requiresApiKey ? 'Requires API key: yes' : 'Requires API key: no',
|
|
53
|
+
].join('\n') + '\n';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (payload.mode === 'remote') {
|
|
57
|
+
const job = payload.job ?? payload.data?.job ?? payload.data ?? {};
|
|
58
|
+
const state = job.state ?? 'unknown';
|
|
59
|
+
const score = scoreFromPayload(payload);
|
|
60
|
+
const lines = [
|
|
61
|
+
`Rubrkit ${payload.command} remote ${state}`,
|
|
62
|
+
`Job: ${job.id ?? payload.jobId ?? '(unknown)'}`,
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
if (job.phase || job.message) {
|
|
66
|
+
lines.push(`Latest: ${[job.phase, job.message].filter(Boolean).join(' - ')}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (typeof score === 'number') {
|
|
70
|
+
lines.push(`Score: ${score}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (job.error?.message) {
|
|
74
|
+
lines.push(`Error: ${job.error.code ?? 'job_failed'} - ${job.error.message}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return `${lines.join('\n')}\n`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (payload.mode === 'report') {
|
|
81
|
+
const job = payload.job ?? payload.data?.job ?? payload;
|
|
82
|
+
return [
|
|
83
|
+
`Rubrkit report ${job.state ?? 'unknown'}`,
|
|
84
|
+
`Job: ${job.id ?? payload.id ?? '(unknown)'}`,
|
|
85
|
+
`Kind: ${job.kind ?? '(unknown)'}`,
|
|
86
|
+
`Latest: ${[job.phase, job.message].filter(Boolean).join(' - ') || '(none)'}`,
|
|
87
|
+
].join('\n') + '\n';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const summary = payload.summary ?? {};
|
|
91
|
+
const status = payload.passed ? 'PASS' : 'FAIL';
|
|
92
|
+
const lines = [
|
|
93
|
+
`Rubrkit ${payload.command ?? 'validate'} ${payload.mode ?? 'local'}`,
|
|
94
|
+
`${status} ${payload.target ?? ''} (${summary.fileCount ?? 0} files, ${summary.errorCount ?? 0} errors, ${summary.warningCount ?? 0} warnings, score ${summary.score ?? 0})`,
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
for (const file of payload.files ?? []) {
|
|
98
|
+
lines.push(`- ${file.passed ? 'PASS' : 'FAIL'} ${file.path}`);
|
|
99
|
+
|
|
100
|
+
for (const issue of file.issues ?? []) {
|
|
101
|
+
const location = issue.line ? ` line ${issue.line}${issue.column ? `:${issue.column}` : ''}` : '';
|
|
102
|
+
lines.push(` ${issue.severity.toUpperCase()} ${issue.code}${location}: ${issue.message}`);
|
|
103
|
+
lines.push(` fix: ${issue.fix}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const gate of payload.gates?.failures ?? []) {
|
|
108
|
+
lines.push(`Gate ${gate.code}: ${gate.message}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return `${lines.join('\n')}\n`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {Record<string, any>} payload
|
|
116
|
+
*/
|
|
117
|
+
function renderJUnit(payload) {
|
|
118
|
+
const files = Array.isArray(payload.files) ? payload.files : [];
|
|
119
|
+
const tests = files.length || 1;
|
|
120
|
+
const failures = files.reduce((sum, file) => sum + Number(file.errorCount ?? 0), 0);
|
|
121
|
+
const warnings = files.reduce((sum, file) => sum + Number(file.warningCount ?? 0), 0);
|
|
122
|
+
const name = `rubrkit.${payload.command ?? 'test'}.${payload.mode ?? 'local'}`;
|
|
123
|
+
const testcases = files.length > 0 ? files.map(renderFileTestCase).join('\n') : renderRemoteTestCase(payload);
|
|
124
|
+
|
|
125
|
+
return [
|
|
126
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
127
|
+
`<testsuite name="${xml(name)}" tests="${tests}" failures="${failures}" errors="0" skipped="0">`,
|
|
128
|
+
warnings > 0 ? ` <system-out>${xml(`${warnings} Rubrkit warning${warnings === 1 ? '' : 's'}`)}</system-out>` : '',
|
|
129
|
+
testcases,
|
|
130
|
+
'</testsuite>',
|
|
131
|
+
'',
|
|
132
|
+
].filter(line => line !== '').join('\n');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {Record<string, any>} file
|
|
137
|
+
*/
|
|
138
|
+
function renderFileTestCase(file) {
|
|
139
|
+
const failures = (file.issues ?? []).filter(issue => issue.severity === 'error');
|
|
140
|
+
const warnings = (file.issues ?? []).filter(issue => issue.severity === 'warning');
|
|
141
|
+
const body = [];
|
|
142
|
+
|
|
143
|
+
if (failures.length > 0) {
|
|
144
|
+
body.push(
|
|
145
|
+
` <failure message="${xml(`${failures.length} Rubrkit validation error${failures.length === 1 ? '' : 's'}`)}">${xml(
|
|
146
|
+
failures.map(formatIssue).join('\n'),
|
|
147
|
+
)}</failure>`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (warnings.length > 0) {
|
|
152
|
+
body.push(` <system-out>${xml(warnings.map(formatIssue).join('\n'))}</system-out>`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (body.length === 0) {
|
|
156
|
+
return ` <testcase name="${xml(file.path)}" classname="RubrkitLocalChecks" />`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return [` <testcase name="${xml(file.path)}" classname="RubrkitLocalChecks">`, ...body, ' </testcase>'].join('\n');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @param {Record<string, any>} payload
|
|
164
|
+
*/
|
|
165
|
+
function renderRemoteTestCase(payload) {
|
|
166
|
+
const job = payload.job ?? {};
|
|
167
|
+
const failed = ['failed', 'cancelled', 'paused'].includes(job.state);
|
|
168
|
+
|
|
169
|
+
if (!failed) {
|
|
170
|
+
return ` <testcase name="${xml(job.id ?? payload.jobId ?? 'remote-job')}" classname="RubrkitRemoteJob" />`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return [
|
|
174
|
+
` <testcase name="${xml(job.id ?? payload.jobId ?? 'remote-job')}" classname="RubrkitRemoteJob">`,
|
|
175
|
+
` <failure message="${xml(job.error?.message ?? 'Remote Rubrkit job did not succeed.')}">${xml(JSON.stringify(job.error ?? {}, null, 2))}</failure>`,
|
|
176
|
+
' </testcase>',
|
|
177
|
+
].join('\n');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @param {Record<string, any>} issue
|
|
182
|
+
*/
|
|
183
|
+
function formatIssue(issue) {
|
|
184
|
+
const location = issue.line ? `line ${issue.line}${issue.column ? `:${issue.column}` : ''}` : 'file';
|
|
185
|
+
return `${location} ${issue.severity?.toUpperCase?.() ?? 'ISSUE'} ${issue.code}: ${issue.message}\nfix: ${issue.fix}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @param {string} value
|
|
190
|
+
*/
|
|
191
|
+
function xml(value) {
|
|
192
|
+
return String(value)
|
|
193
|
+
.replace(/&/g, '&')
|
|
194
|
+
.replace(/</g, '<')
|
|
195
|
+
.replace(/>/g, '>')
|
|
196
|
+
.replace(/"/g, '"')
|
|
197
|
+
.replace(/'/g, ''');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @param {Record<string, any>} payload
|
|
202
|
+
*/
|
|
203
|
+
export function scoreFromPayload(payload) {
|
|
204
|
+
const candidates = [
|
|
205
|
+
payload.summary?.score,
|
|
206
|
+
payload.job?.result?.overallScore,
|
|
207
|
+
payload.job?.result?.score,
|
|
208
|
+
payload.started?.auditRun?.result?.overallScore,
|
|
209
|
+
payload.started?.auditRun?.summary?.overallScore,
|
|
210
|
+
payload.started?.evalRun?.summary?.score,
|
|
211
|
+
payload.started?.result?.overallScore,
|
|
212
|
+
];
|
|
213
|
+
|
|
214
|
+
for (const candidate of candidates) {
|
|
215
|
+
const value = Number(candidate);
|
|
216
|
+
if (Number.isFinite(value)) {
|
|
217
|
+
return value;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return null;
|
|
222
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export interface RubrkitOptions {
|
|
2
|
+
apiKey?: string | null;
|
|
3
|
+
apiUrl?: string;
|
|
4
|
+
fetchImpl?: typeof fetch;
|
|
5
|
+
userAgent?: string;
|
|
6
|
+
retryReads?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface JobWaitOptions {
|
|
10
|
+
intervalMs?: number;
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
onProgress?: (job: Record<string, unknown>) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class RubrkitError extends Error {
|
|
17
|
+
code: string;
|
|
18
|
+
details?: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class RubrkitApiError extends RubrkitError {
|
|
22
|
+
status: number;
|
|
23
|
+
requestId: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class RubrkitNetworkError extends RubrkitError {}
|
|
27
|
+
|
|
28
|
+
export class Rubrkit {
|
|
29
|
+
constructor(options?: RubrkitOptions);
|
|
30
|
+
|
|
31
|
+
me: {
|
|
32
|
+
get(): Promise<Record<string, unknown>>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
artifactBundles: {
|
|
36
|
+
list(params?: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
37
|
+
create(params?: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
38
|
+
get(id: string): Promise<Record<string, unknown>>;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
artifacts: {
|
|
42
|
+
list(params: { artifactBundleId: string } & Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
43
|
+
pull(params: { artifactBundleId: string; artifactId?: string; fileId?: string } & Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
44
|
+
upload(params: { artifactBundleId: string; files: Array<Record<string, unknown>> } & Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
45
|
+
test(params?: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
audits: {
|
|
49
|
+
run(params: { artifactBundleId: string } & Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
evals: {
|
|
53
|
+
run(params: { artifactBundleId: string } & Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
rubrFlow: {
|
|
57
|
+
convert(params: { artifactBundleId: string } & Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
jobs: {
|
|
61
|
+
get(id: string): Promise<Record<string, unknown>>;
|
|
62
|
+
wait(id: string, options?: JobWaitOptions): Promise<Record<string, unknown>>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
proofReports: {
|
|
66
|
+
get(id: string, options: { artifactBundleId: string }): Promise<Record<string, unknown>>;
|
|
67
|
+
get(params: { artifactBundleId: string; proofReportId: string }): Promise<Record<string, unknown>>;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
request(path: string, options?: {
|
|
71
|
+
method?: string;
|
|
72
|
+
query?: Record<string, unknown>;
|
|
73
|
+
body?: unknown;
|
|
74
|
+
signal?: AbortSignal;
|
|
75
|
+
}): Promise<Record<string, unknown>>;
|
|
76
|
+
}
|