@kata.dev/challenge-cli 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/commands/login.js +0 -8
- package/src/commands/pack.js +3 -13
- package/src/commands/validate.js +3 -19
- package/src/lib/artifacts.js +2 -12
- package/src/lib/config.js +1 -8
- package/src/lib/http.js +5 -0
package/package.json
CHANGED
package/src/commands/login.js
CHANGED
|
@@ -7,7 +7,6 @@ export function registerLoginCommand(program) {
|
|
|
7
7
|
.description('Save API credentials to ~/.challengerc.json')
|
|
8
8
|
.requiredOption('--api <url>', 'Eval Engine API base URL')
|
|
9
9
|
.requiredOption('--token <token>', 'Authentication token (JWT)')
|
|
10
|
-
.option('--signing-secret <secret>', 'Challenge artifact signing secret (optional, for local validation)')
|
|
11
10
|
.action(async (opts) => {
|
|
12
11
|
const existing = loadConfig();
|
|
13
12
|
|
|
@@ -17,17 +16,10 @@ export function registerLoginCommand(program) {
|
|
|
17
16
|
token: opts.token,
|
|
18
17
|
};
|
|
19
18
|
|
|
20
|
-
if (opts.signingSecret) {
|
|
21
|
-
config.signingSecret = opts.signingSecret;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
19
|
const configPath = saveConfig(config);
|
|
25
20
|
|
|
26
21
|
console.log(chalk.green('✔') + ' Credentials saved to ' + chalk.dim(configPath));
|
|
27
22
|
console.log(' API: ' + chalk.cyan(config.apiUrl));
|
|
28
23
|
console.log(' Token: ' + chalk.dim(config.token.slice(0, 20) + '…'));
|
|
29
|
-
if (config.signingSecret) {
|
|
30
|
-
console.log(' Signing secret: ' + chalk.dim('configured'));
|
|
31
|
-
}
|
|
32
24
|
});
|
|
33
25
|
}
|
package/src/commands/pack.js
CHANGED
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
resolveRuntimeMode,
|
|
12
12
|
} from '../lib/helpers.js';
|
|
13
13
|
import { resolveRuntimeDepsSourceDir } from '../lib/runtime-deps.js';
|
|
14
|
-
import { resolveSigningSecret } from '../lib/config.js';
|
|
15
14
|
|
|
16
15
|
export function registerPackCommand(program) {
|
|
17
16
|
program
|
|
@@ -20,19 +19,11 @@ export function registerPackCommand(program) {
|
|
|
20
19
|
.requiredOption('--dir <challengeDir>', 'Path to the challenge directory')
|
|
21
20
|
.option('--out <outDir>', 'Output directory (defaults to <challengeDir>/dist)')
|
|
22
21
|
.option('--runtime-mode <mode>', 'Runtime deps mode: auto (default) or manual', 'auto')
|
|
23
|
-
.option('--signing-secret <secret>', 'Artifact signing secret (falls back to config)')
|
|
24
22
|
.action(async (opts) => {
|
|
25
23
|
const challengeDir = path.resolve(opts.dir);
|
|
26
24
|
const outDir = path.resolve(opts.out || path.join(challengeDir, 'dist'));
|
|
27
25
|
const runtimeMode = resolveRuntimeMode(opts.runtimeMode);
|
|
28
26
|
|
|
29
|
-
const signingSecret = resolveSigningSecret(opts.signingSecret);
|
|
30
|
-
if (!signingSecret) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
'Signing secret is required. Set it via --signing-secret, CHALLENGE_ARTIFACT_SIGNING_SECRET env var, or run "challenge login --signing-secret <secret>".'
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
27
|
const challengeJsonPath = path.join(challengeDir, 'challenge.json');
|
|
37
28
|
if (!fs.existsSync(challengeJsonPath)) {
|
|
38
29
|
throw new Error(`challenge.json not found: ${challengeJsonPath}`);
|
|
@@ -63,11 +54,10 @@ export function registerPackCommand(program) {
|
|
|
63
54
|
}
|
|
64
55
|
|
|
65
56
|
const outputFile = path.join(outDir, `${type}.tar.gz`);
|
|
66
|
-
const packed = await packArtifact(sourceDir, outputFile
|
|
57
|
+
const packed = await packArtifact(sourceDir, outputFile);
|
|
67
58
|
|
|
68
59
|
artifacts[type] = {
|
|
69
60
|
sha256: packed.sha256,
|
|
70
|
-
signature: packed.signature,
|
|
71
61
|
sizeBytes: packed.sizeBytes,
|
|
72
62
|
file: path.basename(outputFile),
|
|
73
63
|
};
|
|
@@ -94,7 +84,7 @@ export function registerPackCommand(program) {
|
|
|
94
84
|
console.log(chalk.green('✔') + ` Manifest: ${chalk.cyan(manifestPath)}`);
|
|
95
85
|
console.log();
|
|
96
86
|
console.log(' Next steps:');
|
|
97
|
-
console.log(` ${chalk.cyan(`npx @kata.dev/challenge-cli validate --manifest ${path.relative(process.cwd(), manifestPath)}`)}`);
|
|
98
|
-
console.log(` ${chalk.cyan(`npx @kata.dev/challenge-cli publish --manifest ${path.relative(process.cwd(), manifestPath)}`)}`);
|
|
87
|
+
console.log(` ${chalk.cyan(`npx "@kata.dev/challenge-cli" validate --manifest ${path.relative(process.cwd(), manifestPath)}`)}`);
|
|
88
|
+
console.log(` ${chalk.cyan(`npx "@kata.dev/challenge-cli" publish --manifest ${path.relative(process.cwd(), manifestPath)}`)}`);
|
|
99
89
|
});
|
|
100
90
|
}
|
package/src/commands/validate.js
CHANGED
|
@@ -5,27 +5,17 @@ import ora from 'ora';
|
|
|
5
5
|
import {
|
|
6
6
|
REQUIRED_ARTIFACT_TYPES,
|
|
7
7
|
computeSha256Hex,
|
|
8
|
-
signSha256Hex,
|
|
9
8
|
validateTarGzBuffer,
|
|
10
9
|
} from '../lib/artifacts.js';
|
|
11
10
|
import { readJson } from '../lib/helpers.js';
|
|
12
|
-
import { resolveSigningSecret } from '../lib/config.js';
|
|
13
11
|
|
|
14
12
|
export function registerValidateCommand(program) {
|
|
15
13
|
program
|
|
16
14
|
.command('validate')
|
|
17
|
-
.description('Validate packed artifacts locally')
|
|
15
|
+
.description('Validate packed artifacts locally (SHA-256 + tar safety)')
|
|
18
16
|
.requiredOption('--manifest <path>', 'Path to cks-manifest.json')
|
|
19
|
-
.option('--signing-secret <secret>', 'Artifact signing secret (falls back to config)')
|
|
20
17
|
.action(async (opts) => {
|
|
21
18
|
const manifestPath = path.resolve(opts.manifest);
|
|
22
|
-
const signingSecret = resolveSigningSecret(opts.signingSecret);
|
|
23
|
-
|
|
24
|
-
if (!signingSecret) {
|
|
25
|
-
throw new Error(
|
|
26
|
-
'Signing secret is required for validation. Set it via --signing-secret, CHALLENGE_ARTIFACT_SIGNING_SECRET env var, or run "challenge login --signing-secret <secret>".'
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
19
|
|
|
30
20
|
const manifest = readJson(manifestPath);
|
|
31
21
|
const slug = manifest.metadata?.slug || 'unknown';
|
|
@@ -56,14 +46,7 @@ export function registerValidateCommand(program) {
|
|
|
56
46
|
throw new Error(`SHA mismatch for ${type}. expected=${artifact.sha256} actual=${actualSha}`);
|
|
57
47
|
}
|
|
58
48
|
|
|
59
|
-
//
|
|
60
|
-
const actualSig = signSha256Hex(actualSha, signingSecret);
|
|
61
|
-
if (actualSig !== artifact.signature) {
|
|
62
|
-
spinner.fail(`${type}: signature mismatch`);
|
|
63
|
-
throw new Error(`Signature mismatch for ${type}. expected=${artifact.signature} actual=${actualSig}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Validate tar contents
|
|
49
|
+
// Validate tar contents (path safety, symlinks, size limits)
|
|
67
50
|
spinner.text = `Validating ${type} tar contents…`;
|
|
68
51
|
const { entryCount, totalSize } = await validateTarGzBuffer({ buffer });
|
|
69
52
|
|
|
@@ -73,5 +56,6 @@ export function registerValidateCommand(program) {
|
|
|
73
56
|
|
|
74
57
|
console.log();
|
|
75
58
|
console.log(chalk.green('✔') + ' All artifacts validated successfully');
|
|
59
|
+
console.log(chalk.dim(' Note: HMAC signature verification happens server-side during publish.'));
|
|
76
60
|
});
|
|
77
61
|
}
|
package/src/lib/artifacts.js
CHANGED
|
@@ -14,15 +14,7 @@ export function computeSha256Hex(buffer) {
|
|
|
14
14
|
return crypto.createHash('sha256').update(buffer).digest('hex');
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
if (!sha256Hex || typeof sha256Hex !== 'string') {
|
|
19
|
-
throw new Error('sha256Hex is required to compute signature.');
|
|
20
|
-
}
|
|
21
|
-
if (!secret || typeof secret !== 'string') {
|
|
22
|
-
throw new Error('Signing secret is required to compute signature.');
|
|
23
|
-
}
|
|
24
|
-
return crypto.createHmac('sha256', secret).update(sha256Hex).digest('hex');
|
|
25
|
-
}
|
|
17
|
+
|
|
26
18
|
|
|
27
19
|
export function assertSafeTarPath(entryPath) {
|
|
28
20
|
const normalized = String(entryPath || '').replace(/\\/g, '/');
|
|
@@ -96,7 +88,7 @@ export async function validateTarGzBuffer({ buffer, maxEntries = 2000, maxExtrac
|
|
|
96
88
|
return { entryCount, totalSize };
|
|
97
89
|
}
|
|
98
90
|
|
|
99
|
-
export async function packArtifact(sourceDir, outputFile
|
|
91
|
+
export async function packArtifact(sourceDir, outputFile) {
|
|
100
92
|
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
|
101
93
|
|
|
102
94
|
await tarCreate(
|
|
@@ -112,12 +104,10 @@ export async function packArtifact(sourceDir, outputFile, signingSecret) {
|
|
|
112
104
|
|
|
113
105
|
const buffer = fs.readFileSync(outputFile);
|
|
114
106
|
const sha256 = computeSha256Hex(buffer);
|
|
115
|
-
const signature = signSha256Hex(sha256, signingSecret);
|
|
116
107
|
|
|
117
108
|
return {
|
|
118
109
|
file: outputFile,
|
|
119
110
|
sha256,
|
|
120
|
-
signature,
|
|
121
111
|
sizeBytes: buffer.length,
|
|
122
112
|
};
|
|
123
113
|
}
|
package/src/lib/config.js
CHANGED
|
@@ -39,11 +39,4 @@ export function resolveToken(flagValue) {
|
|
|
39
39
|
return flagValue || process.env.CKS_API_TOKEN || loadConfig().token || null;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
flagValue ||
|
|
45
|
-
process.env.CHALLENGE_ARTIFACT_SIGNING_SECRET ||
|
|
46
|
-
loadConfig().signingSecret ||
|
|
47
|
-
null
|
|
48
|
-
);
|
|
49
|
-
}
|
|
42
|
+
|
package/src/lib/http.js
CHANGED
|
@@ -63,6 +63,11 @@ export async function requestJson(url, { method, body, token }) {
|
|
|
63
63
|
const error = new Error(message);
|
|
64
64
|
error.status = response.status;
|
|
65
65
|
error.payload = payload;
|
|
66
|
+
|
|
67
|
+
if (response.status === 401) {
|
|
68
|
+
error.message += `\n\n${require('chalk').yellow('Hint:')} Your token has expired or is invalid.\nRun ${require('chalk').cyan('npx "@kata.dev/challenge-cli" login')} to refresh it.`;
|
|
69
|
+
}
|
|
70
|
+
|
|
66
71
|
throw error;
|
|
67
72
|
}
|
|
68
73
|
|