@restormel/validate 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 +22 -0
- package/README.md +37 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +128 -0
- package/dist/store.d.ts +15 -0
- package/dist/store.js +25 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Allotment Technology Ltd
|
|
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.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @restormel/validate
|
|
2
|
+
|
|
3
|
+
Restormel Validate is an **open-source** CLI for validating provider credentials and configuration. It is designed to be CI-friendly and to act as an **entrypoint** into the Restormel Platform.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @restormel/validate
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or install in a repo:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add -D @restormel/validate
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
restormel-validate
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Output formats
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
restormel-validate --format json
|
|
27
|
+
restormel-validate --format json --out validate.json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Modes / CI behavior
|
|
31
|
+
|
|
32
|
+
- Default behavior is CI-friendly: **exit 1** if any known-provider key is invalid.\n- Use `--fail-on warn` or `--fail-on none` to relax gating.\n- Use `--strict` as a preset for CI (fail on invalid).
|
|
33
|
+
|
|
34
|
+
## Exit codes
|
|
35
|
+
|
|
36
|
+
- `0`: all checks passed\n- `1`: failed checks (per `--fail-on` policy)\n- `2`: usage/config error (unexpected failure)
|
|
37
|
+
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @restormel/validate — standalone OSS validation entrypoint.
|
|
4
|
+
*/
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { openaiProvider, anthropicProvider, googleProvider, } from "@restormel/keys";
|
|
8
|
+
import { readStore } from "./store.js";
|
|
9
|
+
const PROVIDERS = {
|
|
10
|
+
openai: openaiProvider,
|
|
11
|
+
anthropic: anthropicProvider,
|
|
12
|
+
google: googleProvider,
|
|
13
|
+
};
|
|
14
|
+
function exitCodeFor(report, failOn) {
|
|
15
|
+
if (failOn === "none")
|
|
16
|
+
return 0;
|
|
17
|
+
if (failOn === "warn") {
|
|
18
|
+
const hasFail = report.checks.some((c) => c.status === "fail");
|
|
19
|
+
return hasFail ? 1 : 0;
|
|
20
|
+
}
|
|
21
|
+
// invalid
|
|
22
|
+
return report.ok ? 0 : 1;
|
|
23
|
+
}
|
|
24
|
+
function writeOutput(report, format) {
|
|
25
|
+
if (format === "json") {
|
|
26
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
console.log(chalk.cyan("Restormel Validate"));
|
|
30
|
+
console.log("");
|
|
31
|
+
for (const c of report.checks) {
|
|
32
|
+
const icon = c.status === "ok" ? chalk.green("✓") : c.status === "warn" ? chalk.yellow("○") : chalk.red("✗");
|
|
33
|
+
const label = chalk.white(c.label);
|
|
34
|
+
const msg = c.message ? chalk.gray(`— ${c.message}`) : "";
|
|
35
|
+
console.log(`${icon} ${label} ${msg}`.trimEnd());
|
|
36
|
+
}
|
|
37
|
+
console.log("");
|
|
38
|
+
console.log(report.ok ? chalk.green("OK") : chalk.red("Issues found"));
|
|
39
|
+
}
|
|
40
|
+
async function runValidate() {
|
|
41
|
+
const cwd = process.cwd();
|
|
42
|
+
const store = await readStore(cwd);
|
|
43
|
+
const checks = [];
|
|
44
|
+
if (store.keys.length === 0) {
|
|
45
|
+
checks.push({
|
|
46
|
+
id: "keys",
|
|
47
|
+
label: "Stored keys",
|
|
48
|
+
status: "warn",
|
|
49
|
+
message: "no keys to validate",
|
|
50
|
+
});
|
|
51
|
+
return { ok: true, cwd, checks };
|
|
52
|
+
}
|
|
53
|
+
let allValid = true;
|
|
54
|
+
for (const k of store.keys) {
|
|
55
|
+
const provider = PROVIDERS[k.provider];
|
|
56
|
+
if (!provider) {
|
|
57
|
+
checks.push({
|
|
58
|
+
id: `key:${k.id}`,
|
|
59
|
+
label: `Key (${k.provider})`,
|
|
60
|
+
status: "warn",
|
|
61
|
+
message: "unknown provider (skipped)",
|
|
62
|
+
details: { provider: k.provider, id: k.id, mask: k.mask, label: k.label },
|
|
63
|
+
});
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const result = await provider.validateKey(k.apiKey);
|
|
67
|
+
if (result.valid) {
|
|
68
|
+
checks.push({
|
|
69
|
+
id: `key:${k.id}`,
|
|
70
|
+
label: `Key (${k.provider})`,
|
|
71
|
+
status: "ok",
|
|
72
|
+
message: k.mask ?? k.id,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
allValid = false;
|
|
77
|
+
checks.push({
|
|
78
|
+
id: `key:${k.id}`,
|
|
79
|
+
label: `Key (${k.provider})`,
|
|
80
|
+
status: "fail",
|
|
81
|
+
message: (result.errors?.[0] ?? "Invalid").toString(),
|
|
82
|
+
details: { provider: k.provider, id: k.id, mask: k.mask, label: k.label },
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { ok: allValid, cwd, checks };
|
|
87
|
+
}
|
|
88
|
+
async function main() {
|
|
89
|
+
const program = new Command();
|
|
90
|
+
program
|
|
91
|
+
.name("restormel-validate")
|
|
92
|
+
.description("Restormel Validate — open-source CLI for credential and configuration validation")
|
|
93
|
+
.version("0.1.0")
|
|
94
|
+
.option("--format <format>", "Output format: text|json", "text")
|
|
95
|
+
.option("--out <path>", "Write JSON output to a file (requires --format json)")
|
|
96
|
+
.option("--fail-on <mode>", "Fail policy: invalid|warn|none", "invalid")
|
|
97
|
+
.option("--strict", "Preset for CI: --fail-on invalid", false);
|
|
98
|
+
program.parse();
|
|
99
|
+
const opts = program.opts();
|
|
100
|
+
const format = (opts.format ?? "text");
|
|
101
|
+
if (format !== "text" && format !== "json") {
|
|
102
|
+
console.error(chalk.red("Invalid --format. Use text or json."));
|
|
103
|
+
process.exit(2);
|
|
104
|
+
}
|
|
105
|
+
if (opts.out && format !== "json") {
|
|
106
|
+
console.error(chalk.red("--out requires --format json."));
|
|
107
|
+
process.exit(2);
|
|
108
|
+
}
|
|
109
|
+
const failOn = (opts.strict ? "invalid" : (opts.failOn ?? "invalid"));
|
|
110
|
+
if (failOn !== "invalid" && failOn !== "warn" && failOn !== "none") {
|
|
111
|
+
console.error(chalk.red("Invalid --fail-on. Use invalid, warn, or none."));
|
|
112
|
+
process.exit(2);
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const report = await runValidate();
|
|
116
|
+
if (opts.out) {
|
|
117
|
+
const { writeFile } = await import("fs/promises");
|
|
118
|
+
await writeFile(opts.out, JSON.stringify(report, null, 2) + "\n", "utf-8");
|
|
119
|
+
}
|
|
120
|
+
writeOutput(report, format);
|
|
121
|
+
process.exit(exitCodeFor(report, failOn));
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
console.error(chalk.red("Validate failed:"), e instanceof Error ? e.message : String(e));
|
|
125
|
+
process.exit(2);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
void main();
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const STORE_DIR = ".restormel";
|
|
2
|
+
export declare const STORE_FILENAME = "key-store.json";
|
|
3
|
+
export interface StoredKey {
|
|
4
|
+
id: string;
|
|
5
|
+
provider: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
/** Masked for display only (e.g. sk-...abc). Never log or expose. */
|
|
8
|
+
mask?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface KeyStoreData {
|
|
11
|
+
keys: Array<StoredKey & {
|
|
12
|
+
apiKey: string;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export declare function readStore(cwd: string): Promise<KeyStoreData>;
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local key store. Lives under .restormel/ and must be in .gitignore.
|
|
3
|
+
* Contains secrets; validate never prints raw keys.
|
|
4
|
+
*/
|
|
5
|
+
import { readFile } from "fs/promises";
|
|
6
|
+
import { existsSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
export const STORE_DIR = ".restormel";
|
|
9
|
+
export const STORE_FILENAME = "key-store.json";
|
|
10
|
+
function storePath(cwd) {
|
|
11
|
+
return join(cwd, STORE_DIR, STORE_FILENAME);
|
|
12
|
+
}
|
|
13
|
+
export async function readStore(cwd) {
|
|
14
|
+
const path = storePath(cwd);
|
|
15
|
+
if (!existsSync(path))
|
|
16
|
+
return { keys: [] };
|
|
17
|
+
try {
|
|
18
|
+
const raw = await readFile(path, "utf-8");
|
|
19
|
+
const data = JSON.parse(raw);
|
|
20
|
+
return Array.isArray(data.keys) ? data : { keys: [] };
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return { keys: [] };
|
|
24
|
+
}
|
|
25
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@restormel/validate",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Restormel Validate — open-source CLI for credential and configuration validation.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"restormel",
|
|
7
|
+
"validate",
|
|
8
|
+
"credentials",
|
|
9
|
+
"cli",
|
|
10
|
+
"byok",
|
|
11
|
+
"llm"
|
|
12
|
+
],
|
|
13
|
+
"author": "Allotment Technology Ltd",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/Allotment-Technology-Ltd/restormel-keys.git"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/Allotment-Technology-Ltd/restormel-keys#readme",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/Allotment-Technology-Ltd/restormel-keys/issues"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "dist/index.js",
|
|
28
|
+
"types": "dist/index.d.ts",
|
|
29
|
+
"bin": {
|
|
30
|
+
"restormel-validate": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc",
|
|
39
|
+
"dev": "tsc --watch",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"test": "vitest run"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@restormel/keys": "workspace:*",
|
|
45
|
+
"chalk": "^5.3.0",
|
|
46
|
+
"commander": "^12.1.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^25.5.0",
|
|
50
|
+
"typescript": "^5.7.2",
|
|
51
|
+
"vitest": "^4.1.0"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18"
|
|
55
|
+
}
|
|
56
|
+
}
|