runspec-node 0.17.0 → 0.19.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/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +44 -0
- package/dist/errors.js.map +1 -1
- package/dist/loader.js +4 -0
- package/dist/loader.js.map +1 -1
- package/dist/models.d.ts +4 -0
- package/dist/models.d.ts.map +1 -1
- package/dist/parser.d.ts +3 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +61 -19
- package/dist/parser.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +44 -2
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +3 -0
- package/src/errors.ts +48 -0
- package/src/loader.ts +4 -0
- package/src/models.ts +4 -0
- package/src/parser.ts +70 -20
- package/src/types.ts +51 -3
- package/tests/test_emit_schema.test.ts +32 -0
- package/tests/test_integration.test.ts +20 -0
- package/tests/test_loader.test.ts +33 -0
- package/tests/test_parser.test.ts +175 -1
- package/tests/test_types.test.ts +73 -0
package/tests/test_types.test.ts
CHANGED
|
@@ -15,6 +15,38 @@ test('coerces number to string', () => {
|
|
|
15
15
|
expect(coerce(42, spec({ type: 'str' }))).toBe('42');
|
|
16
16
|
});
|
|
17
17
|
|
|
18
|
+
// ── str validation: pattern / min-length / max-length (str only) ───────────────
|
|
19
|
+
|
|
20
|
+
test('str matches pattern', () => {
|
|
21
|
+
expect(coerce('PROJ-123', spec({ type: 'str', pattern: '[A-Z]+-[0-9]+' }))).toBe('PROJ-123');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('str violates pattern', () => {
|
|
25
|
+
expect(() => coerce('proj-123', spec({ type: 'str', pattern: '[A-Z]+-[0-9]+' }))).toThrow();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('pattern is anchored (fullmatch, not substring)', () => {
|
|
29
|
+
expect(() => coerce('PROJ-123-extra', spec({ type: 'str', pattern: '[A-Z]+-[0-9]+' }))).toThrow();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('anchored pattern wraps top-level alternation correctly', () => {
|
|
33
|
+
// ^(?:a|b)$ accepts only "a" or "b" exactly, not a string ending in "b".
|
|
34
|
+
expect(coerce('a', spec({ type: 'str', pattern: 'a|b' }))).toBe('a');
|
|
35
|
+
expect(() => coerce('xb', spec({ type: 'str', pattern: 'a|b' }))).toThrow();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('str respects minLength', () => {
|
|
39
|
+
expect(() => coerce('ab', spec({ type: 'str', minLength: 3 }))).toThrow();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('str respects maxLength', () => {
|
|
43
|
+
expect(() => coerce('abcdef', spec({ type: 'str', maxLength: 5 }))).toThrow();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('str within length bounds passes', () => {
|
|
47
|
+
expect(coerce('hello', spec({ type: 'str', minLength: 3, maxLength: 10 }))).toBe('hello');
|
|
48
|
+
});
|
|
49
|
+
|
|
18
50
|
// ── int ───────────────────────────────────────────────────────────────────────
|
|
19
51
|
|
|
20
52
|
test('coerces integer string', () => {
|
|
@@ -97,6 +129,47 @@ test('rejects invalid choice', () => {
|
|
|
97
129
|
expect(() => coerce('xml', spec({ type: 'choice', options: ['json', 'csv'] }))).toThrow();
|
|
98
130
|
});
|
|
99
131
|
|
|
132
|
+
// ── multiple (per-item coercion) ───────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
test('multiple returns a list of coerced items', () => {
|
|
135
|
+
expect(coerce(['a', 'b', 'c'], spec({ type: 'str', multiple: true }))).toEqual(['a', 'b', 'c']);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('multiple int coerces each item', () => {
|
|
139
|
+
expect(coerce(['1', '2', '3'], spec({ type: 'int', multiple: true }))).toEqual([1, 2, 3]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('multiple applies pattern per item', () => {
|
|
143
|
+
const s = spec({ type: 'str', multiple: true, pattern: '[a-z]+' });
|
|
144
|
+
expect(coerce(['ab', 'cd'], s)).toEqual(['ab', 'cd']);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('multiple wraps a scalar value into a one-item list', () => {
|
|
148
|
+
expect(coerce('solo', spec({ type: 'str', multiple: true }))).toEqual(['solo']);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test('multiple collects all failing items with index and value', () => {
|
|
152
|
+
const s = spec({ name: 'tag', type: 'str', multiple: true, pattern: '[a-z]+' });
|
|
153
|
+
let msg = '';
|
|
154
|
+
try {
|
|
155
|
+
coerce(['ab', 'XY', 'z9', 'de'], s);
|
|
156
|
+
} catch (e) {
|
|
157
|
+
msg = (e as Error).message;
|
|
158
|
+
}
|
|
159
|
+
expect(msg).toContain('2 of 4 item(s) failed');
|
|
160
|
+
expect(msg).toContain('item 2 ("XY")');
|
|
161
|
+
expect(msg).toContain('item 3 ("z9")');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('multiple range applies per item', () => {
|
|
165
|
+
const s = spec({ name: 'n', type: 'int', multiple: true, range: [1, 10] });
|
|
166
|
+
expect(() => coerce(['5', '99'], s)).toThrow(/item 2 \("99"\)/);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('rest type is not treated as per-item', () => {
|
|
170
|
+
expect(coerce(['--flag', 'value'], spec({ type: 'rest' }))).toEqual(['--flag', 'value']);
|
|
171
|
+
});
|
|
172
|
+
|
|
100
173
|
// ── custom types ──────────────────────────────────────────────────────────────
|
|
101
174
|
|
|
102
175
|
test('registerType adds custom coercer', () => {
|