@smonn/ids 0.3.4 → 0.4.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 +34 -34
- package/dist/cli.mjs +266 -225
- package/dist/cli.mjs.map +1 -1
- package/dist/codec-shell-C0arqqX3.mjs.map +1 -1
- package/dist/index.d.mts +9 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/{opaque-B-ttBfHO.mjs → opaque-CX-Lc5B9.mjs} +18 -6
- package/dist/opaque-CX-Lc5B9.mjs.map +1 -0
- package/dist/opaque.d.mts +11 -13
- package/dist/opaque.d.mts.map +1 -1
- package/dist/opaque.mjs +2 -2
- package/dist/{id-CcoPE2EX.mjs → timestamp-BjdAetut.mjs} +7 -7
- package/dist/timestamp-BjdAetut.mjs.map +1 -0
- package/dist/{types-Bv-63YK4.d.mts → types-g7CiQDyE.d.mts} +2 -2
- package/dist/{types-Bv-63YK4.d.mts.map → types-g7CiQDyE.d.mts.map} +1 -1
- package/package.json +1 -1
- package/dist/id-CcoPE2EX.mjs.map +0 -1
- package/dist/opaque-B-ttBfHO.mjs.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,38 +1,161 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as
|
|
3
|
-
import { i as encodeOpaqueKey, n as importOpaqueKey, r as decodeOpaqueKey, t as
|
|
4
|
-
//#region src/cli.ts
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
if (
|
|
8
|
-
if (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
import { t as createTimestampId } from "./timestamp-BjdAetut.mjs";
|
|
3
|
+
import { i as encodeOpaqueKey, n as importOpaqueKey, r as decodeOpaqueKey, t as createOpaqueTimestampId } from "./opaque-CX-Lc5B9.mjs";
|
|
4
|
+
//#region src/cli/codec-options.ts
|
|
5
|
+
function codecOpts(opts) {
|
|
6
|
+
const o = { allowDuplicateBrand: true };
|
|
7
|
+
if (opts.now !== void 0) o.now = opts.now;
|
|
8
|
+
if (opts.rng !== void 0) o.rng = opts.rng;
|
|
9
|
+
return o;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
//#region src/cli/constants.ts
|
|
13
|
+
const maxGenerateCount = 1e4;
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/cli/flags.ts
|
|
16
|
+
function splitFlagToken(arg) {
|
|
17
|
+
const eq = arg.indexOf("=");
|
|
18
|
+
if (eq <= 0) return {
|
|
19
|
+
flag: arg,
|
|
20
|
+
inlineValue: void 0
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
flag: arg.slice(0, eq),
|
|
24
|
+
inlineValue: arg.slice(eq + 1)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function splitFlags(args) {
|
|
28
|
+
const flags = /* @__PURE__ */ new Set();
|
|
29
|
+
const values = /* @__PURE__ */ new Map();
|
|
30
|
+
const positionals = [];
|
|
31
|
+
const errors = [];
|
|
32
|
+
const seenFlags = /* @__PURE__ */ new Set();
|
|
33
|
+
const valueFlags = new Set([
|
|
34
|
+
"--count",
|
|
35
|
+
"-c",
|
|
36
|
+
"--bits",
|
|
37
|
+
"--key-format"
|
|
38
|
+
]);
|
|
39
|
+
const addFlag = (flag) => {
|
|
40
|
+
const canonical = canonicalFlag(flag);
|
|
41
|
+
if (seenFlags.has(canonical)) errors.push(`duplicate flag: ${canonical}`);
|
|
42
|
+
seenFlags.add(canonical);
|
|
43
|
+
flags.add(flag);
|
|
44
|
+
};
|
|
45
|
+
for (let i = 0; i < args.length; i++) {
|
|
46
|
+
const raw = args[i];
|
|
47
|
+
const { flag, inlineValue } = splitFlagToken(raw);
|
|
48
|
+
if (flag === "--opaque") {
|
|
49
|
+
addFlag(flag);
|
|
50
|
+
if (inlineValue !== void 0) errors.push("flag does not take a value: --opaque");
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (valueFlags.has(flag)) {
|
|
54
|
+
if (inlineValue !== void 0) {
|
|
55
|
+
addFlag(flag);
|
|
56
|
+
values.set(flag, inlineValue);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const value = args[i + 1];
|
|
60
|
+
if (value === void 0 || value.startsWith("-")) {
|
|
61
|
+
addFlag(flag);
|
|
62
|
+
values.set(flag, "");
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
addFlag(flag);
|
|
66
|
+
values.set(flag, value);
|
|
67
|
+
i++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (flag.startsWith("-")) {
|
|
71
|
+
addFlag(flag);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
positionals.push(raw);
|
|
13
75
|
}
|
|
14
|
-
|
|
15
|
-
|
|
76
|
+
return {
|
|
77
|
+
flags,
|
|
78
|
+
values,
|
|
79
|
+
positionals,
|
|
80
|
+
errors
|
|
81
|
+
};
|
|
16
82
|
}
|
|
17
|
-
function
|
|
18
|
-
return
|
|
19
|
-
|
|
20
|
-
"",
|
|
21
|
-
"Subcommands:",
|
|
22
|
-
" inspect, i <id> [--opaque] [--key-format hex|base64url]",
|
|
23
|
-
" Decode an ID and print brand, timestamp, and canonical form.",
|
|
24
|
-
" --opaque reads the AES key from IDS_KEY (hex by default; IDS_KEY_FORMAT or --key-format).",
|
|
25
|
-
" generate, g <brand> [--count, -c N] [--opaque] [--key-format hex|base64url]",
|
|
26
|
-
" Mint one or more canonical IDs for the given brand.",
|
|
27
|
-
" --opaque reads the AES key from IDS_KEY (hex by default; IDS_KEY_FORMAT or --key-format).",
|
|
28
|
-
" keygen, k [--bits 128|192|256] [--key-format hex|base64url]",
|
|
29
|
-
" Emit a random AES key for importOpaqueKey (stdout only).",
|
|
30
|
-
""
|
|
31
|
-
].join("\n");
|
|
83
|
+
function canonicalFlag(flag) {
|
|
84
|
+
if (flag === "-c") return "--count";
|
|
85
|
+
return flag;
|
|
32
86
|
}
|
|
33
|
-
|
|
87
|
+
const knownFlags = new Set([
|
|
88
|
+
"--opaque",
|
|
89
|
+
"--key-format",
|
|
90
|
+
"--count",
|
|
91
|
+
"-c",
|
|
92
|
+
"--bits"
|
|
93
|
+
]);
|
|
94
|
+
function unsupportedFlagForCommand(command, flags, allowed) {
|
|
95
|
+
for (const flag of flags) if (!allowed.has(flag)) return knownFlags.has(flag) ? `unsupported flag for ${command}: ${flag}` : `unsupported flag: ${flag}`;
|
|
96
|
+
}
|
|
97
|
+
function parseCount(values) {
|
|
98
|
+
const raw = values.get("--count") ?? values.get("-c");
|
|
99
|
+
if (raw === void 0) return 1;
|
|
100
|
+
if (raw === "") return "--count requires a value";
|
|
101
|
+
if (!/^[1-9][0-9]*$/.test(raw)) return `--count must be a positive integer, got '${raw}'`;
|
|
102
|
+
const count = Number.parseInt(raw, 10);
|
|
103
|
+
if (!Number.isSafeInteger(count) || count > 1e4) return `--count must be at most ${maxGenerateCount}, got '${raw}'`;
|
|
104
|
+
return count;
|
|
105
|
+
}
|
|
106
|
+
function parseBits(values) {
|
|
107
|
+
const raw = values.get("--bits");
|
|
108
|
+
if (raw === void 0) return 256;
|
|
109
|
+
if (raw === "") return "--bits requires a value";
|
|
110
|
+
if (raw === "128") return 128;
|
|
111
|
+
if (raw === "192") return 192;
|
|
112
|
+
if (raw === "256") return 256;
|
|
113
|
+
return `--bits must be 128, 192, or 256, got '${raw}'`;
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/cli/opaque-key.ts
|
|
117
|
+
function isKeyFormatError(result) {
|
|
118
|
+
return result !== "hex" && result !== "base64url";
|
|
119
|
+
}
|
|
120
|
+
function parseKeyFormatFlag(values) {
|
|
121
|
+
const fromFlag = values.get("--key-format");
|
|
122
|
+
if (fromFlag === void 0) return void 0;
|
|
123
|
+
if (fromFlag === "") return "--key-format requires a value";
|
|
124
|
+
if (fromFlag === "hex" || fromFlag === "base64url") return fromFlag;
|
|
125
|
+
return `--key-format must be hex or base64url, got '${fromFlag}'`;
|
|
126
|
+
}
|
|
127
|
+
function parseKeygenFormat(values) {
|
|
128
|
+
const fromFlag = parseKeyFormatFlag(values);
|
|
129
|
+
if (fromFlag === void 0) return "hex";
|
|
130
|
+
return fromFlag;
|
|
131
|
+
}
|
|
132
|
+
function parseOpaqueKeyFormat(values, opts) {
|
|
133
|
+
const fromFlag = parseKeyFormatFlag(values);
|
|
134
|
+
if (fromFlag !== void 0) return fromFlag;
|
|
135
|
+
const fromEnv = (opts.env ?? process.env).IDS_KEY_FORMAT;
|
|
136
|
+
if (fromEnv === void 0 || fromEnv === "") return "hex";
|
|
137
|
+
if (fromEnv === "hex" || fromEnv === "base64url") return fromEnv;
|
|
138
|
+
return `IDS_KEY_FORMAT must be hex or base64url, got '${fromEnv}'`;
|
|
139
|
+
}
|
|
140
|
+
async function loadOpaqueKey(opts, format) {
|
|
141
|
+
const raw = (opts.env ?? process.env).IDS_KEY;
|
|
142
|
+
if (raw === void 0 || raw === "") return "missing IDS_KEY environment variable";
|
|
143
|
+
try {
|
|
144
|
+
return importOpaqueKey(decodeOpaqueKey(raw, format));
|
|
145
|
+
} catch (err) {
|
|
146
|
+
return err.message;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/cli/commands/generate.ts
|
|
151
|
+
function runGenerate(args, opts) {
|
|
34
152
|
const { flags, values, positionals, errors } = splitFlags(args);
|
|
35
|
-
const unsupported = unsupportedFlagForCommand("
|
|
153
|
+
const unsupported = unsupportedFlagForCommand("generate", flags, new Set([
|
|
154
|
+
"--count",
|
|
155
|
+
"-c",
|
|
156
|
+
"--opaque",
|
|
157
|
+
"--key-format"
|
|
158
|
+
]));
|
|
36
159
|
if (unsupported !== void 0) {
|
|
37
160
|
opts.stderr(unsupported + "\n");
|
|
38
161
|
return Promise.resolve(1);
|
|
@@ -41,57 +164,41 @@ function runInspect(args, opts) {
|
|
|
41
164
|
opts.stderr(errors[0] + "\n");
|
|
42
165
|
return Promise.resolve(1);
|
|
43
166
|
}
|
|
44
|
-
const [input] = positionals;
|
|
45
|
-
if (input === void 0) {
|
|
46
|
-
opts.stderr(usage());
|
|
47
|
-
return Promise.resolve(1);
|
|
48
|
-
}
|
|
49
167
|
const extra = positionals[1];
|
|
50
168
|
if (extra !== void 0) {
|
|
51
169
|
opts.stderr(`unexpected argument: ${extra}\n`);
|
|
52
170
|
return Promise.resolve(1);
|
|
53
171
|
}
|
|
172
|
+
const [brand] = positionals;
|
|
173
|
+
const count = parseCount(values);
|
|
174
|
+
if (typeof count === "string") {
|
|
175
|
+
opts.stderr(count + "\n");
|
|
176
|
+
return Promise.resolve(1);
|
|
177
|
+
}
|
|
54
178
|
const opaque = flags.has("--opaque");
|
|
55
179
|
if (!opaque && flags.has("--key-format")) {
|
|
56
180
|
opts.stderr("--key-format requires --opaque\n");
|
|
57
181
|
return Promise.resolve(1);
|
|
58
182
|
}
|
|
59
|
-
const brand = input.slice(0, 3).toLowerCase();
|
|
60
183
|
if (opaque) {
|
|
61
184
|
const format = parseOpaqueKeyFormat(values, opts);
|
|
62
185
|
if (isKeyFormatError(format)) {
|
|
63
186
|
opts.stderr(format + "\n");
|
|
64
187
|
return Promise.resolve(1);
|
|
65
188
|
}
|
|
66
|
-
return
|
|
189
|
+
return runOpaqueGenerate(brand ?? "", count, format, opts);
|
|
67
190
|
}
|
|
68
191
|
let codec;
|
|
69
192
|
try {
|
|
70
|
-
codec =
|
|
193
|
+
codec = createTimestampId(brand ?? "", codecOpts(opts));
|
|
71
194
|
} catch (err) {
|
|
72
195
|
opts.stderr(err.message + "\n");
|
|
73
196
|
return Promise.resolve(1);
|
|
74
197
|
}
|
|
75
|
-
|
|
76
|
-
if (validation.issues) {
|
|
77
|
-
opts.stderr(validation.issues[0].message + "\n");
|
|
78
|
-
return Promise.resolve(1);
|
|
79
|
-
}
|
|
80
|
-
const canonical = validation.value;
|
|
81
|
-
const timestamp = codec.extractTimestamp(canonical);
|
|
82
|
-
const nowMs = (opts.now ?? Date.now)();
|
|
83
|
-
const relative = formatRelative(timestamp.getTime(), nowMs);
|
|
84
|
-
const inputLine = describeInputForm(input, canonical);
|
|
85
|
-
opts.stdout([
|
|
86
|
-
`brand: ${brand}`,
|
|
87
|
-
`timestamp: ${timestamp.toISOString()} (${relative})`,
|
|
88
|
-
`canonical: ${canonical}`,
|
|
89
|
-
`input: ${inputLine}`,
|
|
90
|
-
""
|
|
91
|
-
].join("\n"));
|
|
198
|
+
for (let i = 0; i < count; i++) opts.stdout(codec.generate() + "\n");
|
|
92
199
|
return Promise.resolve(0);
|
|
93
200
|
}
|
|
94
|
-
async function
|
|
201
|
+
async function runOpaqueGenerate(brand, count, format, opts) {
|
|
95
202
|
const keyResult = await loadOpaqueKey(opts, format);
|
|
96
203
|
if (typeof keyResult === "string") {
|
|
97
204
|
opts.stderr(keyResult + "\n");
|
|
@@ -99,7 +206,7 @@ async function runOpaqueInspect(brand, input, format, opts) {
|
|
|
99
206
|
}
|
|
100
207
|
let codec;
|
|
101
208
|
try {
|
|
102
|
-
codec =
|
|
209
|
+
codec = createOpaqueTimestampId(brand, {
|
|
103
210
|
key: keyResult,
|
|
104
211
|
...codecOpts(opts)
|
|
105
212
|
});
|
|
@@ -107,25 +214,21 @@ async function runOpaqueInspect(brand, input, format, opts) {
|
|
|
107
214
|
opts.stderr(err.message + "\n");
|
|
108
215
|
return 1;
|
|
109
216
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
`brand: ${brand}`,
|
|
123
|
-
`timestamp: ${timestamp.toISOString()} (${relative})`,
|
|
124
|
-
`canonical: ${canonical}`,
|
|
217
|
+
for (let i = 0; i < count; i++) opts.stdout(await codec.generate() + "\n");
|
|
218
|
+
return 0;
|
|
219
|
+
}
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/cli/format.ts
|
|
222
|
+
function formatInspectOutput(result) {
|
|
223
|
+
const relative = formatRelative(result.timestamp.getTime(), result.nowMs);
|
|
224
|
+
const inputLine = describeInputForm(result.input, result.canonical);
|
|
225
|
+
return [
|
|
226
|
+
`brand: ${result.brand}`,
|
|
227
|
+
`timestamp: ${result.timestamp.toISOString()} (${relative})`,
|
|
228
|
+
`canonical: ${result.canonical}`,
|
|
125
229
|
`input: ${inputLine}`,
|
|
126
230
|
""
|
|
127
|
-
].join("\n")
|
|
128
|
-
return 0;
|
|
231
|
+
].join("\n");
|
|
129
232
|
}
|
|
130
233
|
function describeInputForm(input, canonical) {
|
|
131
234
|
if (input === canonical) return "canonical";
|
|
@@ -160,14 +263,29 @@ function headUnits(abs) {
|
|
|
160
263
|
function unit(n, name) {
|
|
161
264
|
return `${n} ${n === 1 ? name : `${name}s`}`;
|
|
162
265
|
}
|
|
163
|
-
|
|
266
|
+
//#endregion
|
|
267
|
+
//#region src/cli/usage.ts
|
|
268
|
+
function usage() {
|
|
269
|
+
return [
|
|
270
|
+
"Usage: ids <subcommand> [args]",
|
|
271
|
+
"",
|
|
272
|
+
"Subcommands:",
|
|
273
|
+
" inspect, i <id> [--opaque] [--key-format hex|base64url]",
|
|
274
|
+
" Decode an ID and print brand, timestamp, and canonical form.",
|
|
275
|
+
" --opaque reads the AES key from IDS_KEY (hex by default; IDS_KEY_FORMAT or --key-format).",
|
|
276
|
+
" generate, g <brand> [--count, -c N] [--opaque] [--key-format hex|base64url]",
|
|
277
|
+
` Mint 1..${maxGenerateCount} canonical IDs for the given brand.`,
|
|
278
|
+
" --opaque reads the AES key from IDS_KEY (hex by default; IDS_KEY_FORMAT or --key-format).",
|
|
279
|
+
" keygen, k [--bits 128|192|256] [--key-format hex|base64url]",
|
|
280
|
+
" Emit a random AES key for importOpaqueKey (stdout only).",
|
|
281
|
+
""
|
|
282
|
+
].join("\n");
|
|
283
|
+
}
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/cli/commands/inspect.ts
|
|
286
|
+
function runInspect(args, opts) {
|
|
164
287
|
const { flags, values, positionals, errors } = splitFlags(args);
|
|
165
|
-
const unsupported = unsupportedFlagForCommand("
|
|
166
|
-
"--count",
|
|
167
|
-
"-c",
|
|
168
|
-
"--opaque",
|
|
169
|
-
"--key-format"
|
|
170
|
-
]));
|
|
288
|
+
const unsupported = unsupportedFlagForCommand("inspect", flags, new Set(["--opaque", "--key-format"]));
|
|
171
289
|
if (unsupported !== void 0) {
|
|
172
290
|
opts.stderr(unsupported + "\n");
|
|
173
291
|
return Promise.resolve(1);
|
|
@@ -176,41 +294,55 @@ function runGenerate(args, opts) {
|
|
|
176
294
|
opts.stderr(errors[0] + "\n");
|
|
177
295
|
return Promise.resolve(1);
|
|
178
296
|
}
|
|
297
|
+
const [input] = positionals;
|
|
298
|
+
if (input === void 0) {
|
|
299
|
+
opts.stderr(usage());
|
|
300
|
+
return Promise.resolve(1);
|
|
301
|
+
}
|
|
179
302
|
const extra = positionals[1];
|
|
180
303
|
if (extra !== void 0) {
|
|
181
304
|
opts.stderr(`unexpected argument: ${extra}\n`);
|
|
182
305
|
return Promise.resolve(1);
|
|
183
306
|
}
|
|
184
|
-
const [brand] = positionals;
|
|
185
|
-
const count = parseCount(values);
|
|
186
|
-
if (typeof count === "string") {
|
|
187
|
-
opts.stderr(count + "\n");
|
|
188
|
-
return Promise.resolve(1);
|
|
189
|
-
}
|
|
190
307
|
const opaque = flags.has("--opaque");
|
|
191
308
|
if (!opaque && flags.has("--key-format")) {
|
|
192
309
|
opts.stderr("--key-format requires --opaque\n");
|
|
193
310
|
return Promise.resolve(1);
|
|
194
311
|
}
|
|
312
|
+
const brand = input.slice(0, 3).toLowerCase();
|
|
195
313
|
if (opaque) {
|
|
196
314
|
const format = parseOpaqueKeyFormat(values, opts);
|
|
197
315
|
if (isKeyFormatError(format)) {
|
|
198
316
|
opts.stderr(format + "\n");
|
|
199
317
|
return Promise.resolve(1);
|
|
200
318
|
}
|
|
201
|
-
return
|
|
319
|
+
return runOpaqueInspect(brand, input, format, opts);
|
|
202
320
|
}
|
|
203
321
|
let codec;
|
|
204
322
|
try {
|
|
205
|
-
codec =
|
|
323
|
+
codec = createTimestampId(brand, codecOpts(opts));
|
|
206
324
|
} catch (err) {
|
|
207
325
|
opts.stderr(err.message + "\n");
|
|
208
326
|
return Promise.resolve(1);
|
|
209
327
|
}
|
|
210
|
-
|
|
328
|
+
const validation = codec["~standard"].validate(input);
|
|
329
|
+
if (validation.issues) {
|
|
330
|
+
opts.stderr(validation.issues[0].message + "\n");
|
|
331
|
+
return Promise.resolve(1);
|
|
332
|
+
}
|
|
333
|
+
const canonical = validation.value;
|
|
334
|
+
const timestamp = codec.extractTimestamp(canonical);
|
|
335
|
+
const nowMs = (opts.now ?? Date.now)();
|
|
336
|
+
opts.stdout(formatInspectOutput({
|
|
337
|
+
brand,
|
|
338
|
+
timestamp,
|
|
339
|
+
canonical,
|
|
340
|
+
input,
|
|
341
|
+
nowMs
|
|
342
|
+
}));
|
|
211
343
|
return Promise.resolve(0);
|
|
212
344
|
}
|
|
213
|
-
async function
|
|
345
|
+
async function runOpaqueInspect(brand, input, format, opts) {
|
|
214
346
|
const keyResult = await loadOpaqueKey(opts, format);
|
|
215
347
|
if (typeof keyResult === "string") {
|
|
216
348
|
opts.stderr(keyResult + "\n");
|
|
@@ -218,7 +350,7 @@ async function runOpaqueGenerate(brand, count, format, opts) {
|
|
|
218
350
|
}
|
|
219
351
|
let codec;
|
|
220
352
|
try {
|
|
221
|
-
codec =
|
|
353
|
+
codec = createOpaqueTimestampId(brand, {
|
|
222
354
|
key: keyResult,
|
|
223
355
|
...codecOpts(opts)
|
|
224
356
|
});
|
|
@@ -226,9 +358,26 @@ async function runOpaqueGenerate(brand, count, format, opts) {
|
|
|
226
358
|
opts.stderr(err.message + "\n");
|
|
227
359
|
return 1;
|
|
228
360
|
}
|
|
229
|
-
|
|
361
|
+
const validation = codec["~standard"].validate(input);
|
|
362
|
+
if (validation.issues) {
|
|
363
|
+
opts.stderr(validation.issues[0].message + "\n");
|
|
364
|
+
return 1;
|
|
365
|
+
}
|
|
366
|
+
const canonical = validation.value;
|
|
367
|
+
const timestamp = await codec.extractTimestamp(canonical);
|
|
368
|
+
const nowMs = (opts.now ?? Date.now)();
|
|
369
|
+
opts.stderr("note: timestamp assumes IDS_KEY matches the key used at generation; a wrong key yields a plausible but incorrect timestamp\n");
|
|
370
|
+
opts.stdout(formatInspectOutput({
|
|
371
|
+
brand,
|
|
372
|
+
timestamp,
|
|
373
|
+
canonical,
|
|
374
|
+
input,
|
|
375
|
+
nowMs
|
|
376
|
+
}));
|
|
230
377
|
return 0;
|
|
231
378
|
}
|
|
379
|
+
//#endregion
|
|
380
|
+
//#region src/cli/commands/keygen.ts
|
|
232
381
|
function runKeygen(args, opts) {
|
|
233
382
|
const { flags, values, positionals, errors } = splitFlags(args);
|
|
234
383
|
const unsupported = unsupportedFlagForCommand("keygen", flags, new Set(["--bits", "--key-format"]));
|
|
@@ -260,140 +409,32 @@ function runKeygen(args, opts) {
|
|
|
260
409
|
opts.stdout(encodeOpaqueKey(bytes, format) + "\n");
|
|
261
410
|
return Promise.resolve(0);
|
|
262
411
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
412
|
+
//#endregion
|
|
413
|
+
//#region src/cli.ts
|
|
414
|
+
const commands = [
|
|
415
|
+
{
|
|
416
|
+
names: ["generate", "g"],
|
|
417
|
+
run: runGenerate
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
names: ["inspect", "i"],
|
|
421
|
+
run: runInspect
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
names: ["keygen", "k"],
|
|
425
|
+
run: runKeygen
|
|
270
426
|
}
|
|
271
|
-
|
|
272
|
-
function
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
if (
|
|
276
|
-
if (
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
function parseBits(values) {
|
|
280
|
-
const raw = values.get("--bits");
|
|
281
|
-
if (raw === void 0) return 256;
|
|
282
|
-
if (raw === "") return "--bits requires a value";
|
|
283
|
-
if (raw === "128") return 128;
|
|
284
|
-
if (raw === "192") return 192;
|
|
285
|
-
if (raw === "256") return 256;
|
|
286
|
-
return `--bits must be 128, 192, or 256, got '${raw}'`;
|
|
287
|
-
}
|
|
288
|
-
function isKeyFormatError(result) {
|
|
289
|
-
return result !== "hex" && result !== "base64url";
|
|
290
|
-
}
|
|
291
|
-
function parseKeyFormatFlag(values) {
|
|
292
|
-
const fromFlag = values.get("--key-format");
|
|
293
|
-
if (fromFlag === void 0) return void 0;
|
|
294
|
-
if (fromFlag === "") return "--key-format requires a value";
|
|
295
|
-
if (fromFlag === "hex" || fromFlag === "base64url") return fromFlag;
|
|
296
|
-
return `--key-format must be hex or base64url, got '${fromFlag}'`;
|
|
297
|
-
}
|
|
298
|
-
function parseKeygenFormat(values) {
|
|
299
|
-
const fromFlag = parseKeyFormatFlag(values);
|
|
300
|
-
if (fromFlag === void 0) return "hex";
|
|
301
|
-
return fromFlag;
|
|
302
|
-
}
|
|
303
|
-
function parseOpaqueKeyFormat(values, opts) {
|
|
304
|
-
const fromFlag = parseKeyFormatFlag(values);
|
|
305
|
-
if (fromFlag !== void 0) return fromFlag;
|
|
306
|
-
const fromEnv = (opts.env ?? process.env).IDS_KEY_FORMAT;
|
|
307
|
-
if (fromEnv === void 0 || fromEnv === "") return "hex";
|
|
308
|
-
if (fromEnv === "hex" || fromEnv === "base64url") return fromEnv;
|
|
309
|
-
return `IDS_KEY_FORMAT must be hex or base64url, got '${fromEnv}'`;
|
|
310
|
-
}
|
|
311
|
-
function splitFlagToken(arg) {
|
|
312
|
-
const eq = arg.indexOf("=");
|
|
313
|
-
if (eq <= 0) return {
|
|
314
|
-
flag: arg,
|
|
315
|
-
inlineValue: void 0
|
|
316
|
-
};
|
|
317
|
-
return {
|
|
318
|
-
flag: arg.slice(0, eq),
|
|
319
|
-
inlineValue: arg.slice(eq + 1)
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
function splitFlags(args) {
|
|
323
|
-
const flags = /* @__PURE__ */ new Set();
|
|
324
|
-
const values = /* @__PURE__ */ new Map();
|
|
325
|
-
const positionals = [];
|
|
326
|
-
const errors = [];
|
|
327
|
-
const seenFlags = /* @__PURE__ */ new Set();
|
|
328
|
-
const valueFlags = new Set([
|
|
329
|
-
"--count",
|
|
330
|
-
"-c",
|
|
331
|
-
"--bits",
|
|
332
|
-
"--key-format"
|
|
333
|
-
]);
|
|
334
|
-
const addFlag = (flag) => {
|
|
335
|
-
const canonical = canonicalFlag(flag);
|
|
336
|
-
if (seenFlags.has(canonical)) errors.push(`duplicate flag: ${canonical}`);
|
|
337
|
-
seenFlags.add(canonical);
|
|
338
|
-
flags.add(flag);
|
|
339
|
-
};
|
|
340
|
-
for (let i = 0; i < args.length; i++) {
|
|
341
|
-
const raw = args[i];
|
|
342
|
-
const { flag, inlineValue } = splitFlagToken(raw);
|
|
343
|
-
if (flag === "--opaque") {
|
|
344
|
-
addFlag(flag);
|
|
345
|
-
if (inlineValue !== void 0) errors.push("flag does not take a value: --opaque");
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
if (valueFlags.has(flag)) {
|
|
349
|
-
if (inlineValue !== void 0) {
|
|
350
|
-
addFlag(flag);
|
|
351
|
-
values.set(flag, inlineValue);
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
const value = args[i + 1];
|
|
355
|
-
if (value === void 0 || value.startsWith("-")) {
|
|
356
|
-
addFlag(flag);
|
|
357
|
-
values.set(flag, "");
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
addFlag(flag);
|
|
361
|
-
values.set(flag, value);
|
|
362
|
-
i++;
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
if (flag.startsWith("-")) {
|
|
366
|
-
addFlag(flag);
|
|
367
|
-
continue;
|
|
368
|
-
}
|
|
369
|
-
positionals.push(raw);
|
|
427
|
+
];
|
|
428
|
+
async function run(opts) {
|
|
429
|
+
const [subcommand, ...rest] = opts.argv;
|
|
430
|
+
const command = commands.find((candidate) => candidate.names.includes(subcommand ?? ""));
|
|
431
|
+
if (command !== void 0) return command.run(rest, opts);
|
|
432
|
+
if (subcommand === void 0 || subcommand === "--help" || subcommand === "-h") {
|
|
433
|
+
opts.stdout(usage());
|
|
434
|
+
return 0;
|
|
370
435
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
values,
|
|
374
|
-
positionals,
|
|
375
|
-
errors
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
function canonicalFlag(flag) {
|
|
379
|
-
if (flag === "-c") return "--count";
|
|
380
|
-
return flag;
|
|
381
|
-
}
|
|
382
|
-
const knownFlags = new Set([
|
|
383
|
-
"--opaque",
|
|
384
|
-
"--key-format",
|
|
385
|
-
"--count",
|
|
386
|
-
"-c",
|
|
387
|
-
"--bits"
|
|
388
|
-
]);
|
|
389
|
-
function unsupportedFlagForCommand(command, flags, allowed) {
|
|
390
|
-
for (const flag of flags) if (!allowed.has(flag)) return knownFlags.has(flag) ? `unsupported flag for ${command}: ${flag}` : `unsupported flag: ${flag}`;
|
|
391
|
-
}
|
|
392
|
-
function codecOpts(opts) {
|
|
393
|
-
const o = { allowDuplicateBrand: true };
|
|
394
|
-
if (opts.now !== void 0) o.now = opts.now;
|
|
395
|
-
if (opts.rng !== void 0) o.rng = opts.rng;
|
|
396
|
-
return o;
|
|
436
|
+
opts.stderr(usage());
|
|
437
|
+
return 1;
|
|
397
438
|
}
|
|
398
439
|
//#endregion
|
|
399
440
|
//#region bin/cli.ts
|