@hivecommons/promptargs 0.6.0 → 0.7.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.
Files changed (75) hide show
  1. package/README.md +46 -6
  2. package/dist/autodetect.d.ts +2 -0
  3. package/dist/autodetect.js +53 -3
  4. package/dist/autodetect.js.map +1 -1
  5. package/dist/autodetect.test.d.ts +1 -0
  6. package/dist/autodetect.test.js +207 -0
  7. package/dist/autodetect.test.js.map +1 -0
  8. package/dist/cli.js +13 -8
  9. package/dist/cli.js.map +1 -1
  10. package/dist/cli.test.d.ts +1 -0
  11. package/dist/cli.test.js +310 -0
  12. package/dist/cli.test.js.map +1 -0
  13. package/dist/index.test.d.ts +1 -0
  14. package/dist/index.test.js +59 -0
  15. package/dist/index.test.js.map +1 -0
  16. package/dist/iterate.d.ts +8 -0
  17. package/dist/iterate.js +28 -0
  18. package/dist/iterate.js.map +1 -0
  19. package/dist/iterate.test.d.ts +1 -0
  20. package/dist/iterate.test.js +20 -0
  21. package/dist/iterate.test.js.map +1 -0
  22. package/dist/loader.test.d.ts +1 -0
  23. package/dist/loader.test.js +65 -0
  24. package/dist/loader.test.js.map +1 -0
  25. package/dist/mustache.mjs +764 -0
  26. package/dist/parser.d.ts +1 -0
  27. package/dist/parser.js +16 -4
  28. package/dist/parser.js.map +1 -1
  29. package/dist/parser.test.js +37 -0
  30. package/dist/parser.test.js.map +1 -1
  31. package/dist/resolver.js +58 -44
  32. package/dist/resolver.js.map +1 -1
  33. package/dist/resolver.test.d.ts +1 -0
  34. package/dist/resolver.test.js +184 -0
  35. package/dist/resolver.test.js.map +1 -0
  36. package/dist/sanitize.d.ts +10 -0
  37. package/dist/sanitize.js +21 -0
  38. package/dist/sanitize.js.map +1 -0
  39. package/dist/sanitize.test.d.ts +1 -0
  40. package/dist/sanitize.test.js +38 -0
  41. package/dist/sanitize.test.js.map +1 -0
  42. package/dist/status.js +4 -1
  43. package/dist/status.js.map +1 -1
  44. package/dist/status.test.d.ts +1 -0
  45. package/dist/status.test.js +33 -0
  46. package/dist/status.test.js.map +1 -0
  47. package/dist/ui-bootstrap.test.d.ts +1 -0
  48. package/dist/ui-bootstrap.test.js +479 -0
  49. package/dist/ui-bootstrap.test.js.map +1 -0
  50. package/dist/ui-builder.test.d.ts +1 -0
  51. package/dist/ui-builder.test.js +272 -0
  52. package/dist/ui-builder.test.js.map +1 -0
  53. package/dist/ui-command-quoting.test.d.ts +1 -0
  54. package/dist/ui-command-quoting.test.js +95 -0
  55. package/dist/ui-command-quoting.test.js.map +1 -0
  56. package/dist/ui-harness.d.ts +20 -0
  57. package/dist/ui-harness.js +40 -0
  58. package/dist/ui-harness.js.map +1 -0
  59. package/dist/ui-harness.test.d.ts +1 -0
  60. package/dist/ui-harness.test.js +30 -0
  61. package/dist/ui-harness.test.js.map +1 -0
  62. package/dist/ui-inline.test.d.ts +1 -0
  63. package/dist/ui-inline.test.js +90 -0
  64. package/dist/ui-inline.test.js.map +1 -0
  65. package/dist/ui-interactions.test.d.ts +1 -0
  66. package/dist/ui-interactions.test.js +489 -0
  67. package/dist/ui-interactions.test.js.map +1 -0
  68. package/dist/ui.d.ts +13 -1
  69. package/dist/ui.html +30 -57
  70. package/dist/ui.js +61 -18
  71. package/dist/ui.js.map +1 -1
  72. package/dist/ui.test.d.ts +1 -0
  73. package/dist/ui.test.js +283 -0
  74. package/dist/ui.test.js.map +1 -0
  75. package/package.json +4 -3
@@ -0,0 +1,272 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { readFileSync } from 'node:fs';
4
+ import { join, dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { expand, parseVars, VAR_PATTERN } from './parser.js';
7
+ import { cartesian, zip } from './iterate.js';
8
+ import { compileInlineScript } from './ui-harness.js';
9
+ /**
10
+ * Behavior tests for ui.html's builder logic: `update()` (preview rendering
11
+ * and the copyable CLI command) plus the PRESETS catalogue.
12
+ *
13
+ * ui.html runs an IIFE against the live DOM, so `update()` cannot be
14
+ * imported. Instead the function bodies are extracted verbatim from the
15
+ * page (same approach as ui-inline.test.ts) and instantiated with a tiny
16
+ * stub DOM: `tmpl` exposes `.value`, `varBody.querySelectorAll('tr')`
17
+ * yields rows whose `.querySelector(sel).value` returns the cell values, and
18
+ * the three output nodes record `innerHTML` / `textContent` / `className`.
19
+ * The grammar and iteration helpers are the real compiled modules, exactly
20
+ * as the browser imports them.
21
+ */
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+ const uiHtml = readFileSync(join(__dirname, 'ui.html'), 'utf-8');
24
+ function extractFunction(name) {
25
+ const start = uiHtml.indexOf(`function ${name}(`);
26
+ assert.notEqual(start, -1, `function ${name} not found in ui.html`);
27
+ const bodyStart = uiHtml.indexOf('{', start);
28
+ let depth = 0;
29
+ for (let i = bodyStart; i < uiHtml.length; i++) {
30
+ if (uiHtml[i] === '{')
31
+ depth++;
32
+ else if (uiHtml[i] === '}') {
33
+ depth--;
34
+ if (depth === 0)
35
+ return uiHtml.slice(start, i + 1);
36
+ }
37
+ }
38
+ assert.fail(`unbalanced braces extracting ${name} from ui.html`);
39
+ }
40
+ function makeHarness(mode = 'zip') {
41
+ const node = () => ({ innerHTML: '', textContent: '', className: '' });
42
+ const tmpl = { value: '' };
43
+ const rows = [];
44
+ const previewBody = node();
45
+ const previewCount = node();
46
+ const cliText = node();
47
+ const varBody = {
48
+ querySelectorAll(selector) {
49
+ assert.equal(selector, 'tr');
50
+ return rows.map(row => ({
51
+ querySelector(sel) {
52
+ if (sel === '.var-name')
53
+ return { value: row.name };
54
+ if (sel === '.var-values')
55
+ return { value: row.values };
56
+ if (sel === '.var-source')
57
+ return { value: row.source ?? 'manual' };
58
+ assert.fail(`unexpected cell selector ${sel}`);
59
+ },
60
+ }));
61
+ },
62
+ };
63
+ const src = ['getRowData', 'splitValues', 'escHtml', 'highlightUnfilled', 'update']
64
+ .map(extractFunction)
65
+ .join('\n');
66
+ const factory = compileInlineScript('ui-update', [
67
+ 'tmpl', 'varBody', 'previewBody', 'previewCount', 'cliText', 'mode',
68
+ 'sharedCartesian', 'sharedZip', 'sharedExpand', 'VAR_PATTERN',
69
+ ], `${src}\nreturn update;`);
70
+ const update = factory(tmpl, varBody, previewBody, previewCount, cliText, mode, cartesian, zip, expand, VAR_PATTERN);
71
+ return {
72
+ update,
73
+ tmpl,
74
+ rows,
75
+ previewBody,
76
+ previewCount,
77
+ cliText,
78
+ cli: () => cliText.innerHTML.replace(/^<span class="dollar">\$<\/span>/, ''),
79
+ iterations: () => (previewBody.innerHTML.match(/<div class="iteration">/g) ?? []).length,
80
+ };
81
+ }
82
+ function expandedTexts(h) {
83
+ return [...h.previewBody.innerHTML.matchAll(/<div class="expanded-text">(.*?)<\/div>/gs)].map(m => m[1]);
84
+ }
85
+ test('update renders the empty state and a bare command when the template is blank', () => {
86
+ const h = makeHarness();
87
+ h.tmpl.value = ' \n';
88
+ h.rows.push({ name: 'file', values: 'a.go' });
89
+ h.update();
90
+ assert.match(h.previewBody.innerHTML, /preview-empty/);
91
+ assert.equal(h.previewCount.textContent, '0 results');
92
+ assert.equal(h.previewCount.className, 'preview-count empty');
93
+ assert.equal(h.cli(), 'promptargs');
94
+ });
95
+ test('update expands scalar values into a single unlabeled result', () => {
96
+ const h = makeHarness();
97
+ h.tmpl.value = 'Review {{file}} for {{focus=correctness}}';
98
+ h.rows.push({ name: 'file', values: 'api.go' }, { name: 'focus', values: 'security' });
99
+ h.update();
100
+ assert.equal(h.previewCount.textContent, '1 result');
101
+ assert.equal(h.previewCount.className, 'preview-count has-results');
102
+ assert.equal(h.iterations(), 1);
103
+ assert.ok(!h.previewBody.innerHTML.includes('iter-label'), 'single results carry no [i/n] label');
104
+ assert.deepEqual(expandedTexts(h), ['Review api.go for security']);
105
+ assert.equal(h.cli(), 'promptargs "Review {{file}} for {{focus=correctness}}" --file=api.go --focus=security');
106
+ });
107
+ test('update zips arrays 1:1 and labels each iteration', () => {
108
+ const h = makeHarness('zip');
109
+ h.tmpl.value = '{{file}}:{{focus}}';
110
+ h.rows.push({ name: 'file', values: 'a.go,b.go' }, { name: 'focus', values: 'x,y' });
111
+ h.update();
112
+ assert.equal(h.previewCount.textContent, '2 results');
113
+ assert.deepEqual(expandedTexts(h), ['a.go:x', 'b.go:y']);
114
+ assert.match(h.previewBody.innerHTML, /<div class="iter-label">\[1\/2\] file=a\.go {2}focus=x<\/div>/);
115
+ assert.match(h.previewBody.innerHTML, /<div class="iter-label">\[2\/2\] file=b\.go {2}focus=y<\/div>/);
116
+ assert.equal(h.cli(), 'promptargs "{{file}}:{{focus}}" --file=a.go,b.go --focus=x,y');
117
+ });
118
+ test('update in cross mode renders every combination and appends --cross', () => {
119
+ const h = makeHarness('cross');
120
+ h.tmpl.value = '{{file}}:{{focus}}';
121
+ h.rows.push({ name: 'file', values: 'a.go,b.go' }, { name: 'focus', values: 'x,y' });
122
+ h.update();
123
+ assert.equal(h.previewCount.textContent, '4 results');
124
+ assert.deepEqual(expandedTexts(h), ['a.go:x', 'a.go:y', 'b.go:x', 'b.go:y']);
125
+ assert.equal(h.cli(), 'promptargs "{{file}}:{{focus}}" --file=a.go,b.go --focus=x,y --cross');
126
+ });
127
+ test('update omits --cross when fewer than two arrays exist, matching CLI semantics', () => {
128
+ const h = makeHarness('cross');
129
+ h.tmpl.value = '{{file}} {{tone}}';
130
+ h.rows.push({ name: 'file', values: 'a.go,b.go' }, { name: 'tone', values: 'terse' });
131
+ h.update();
132
+ assert.equal(h.previewCount.textContent, '2 results');
133
+ assert.deepEqual(expandedTexts(h), ['a.go terse', 'b.go terse']);
134
+ assert.ok(!h.cli().endsWith('--cross'), `--cross is redundant for one array: ${h.cli()}`);
135
+ });
136
+ test('update mixes scalar and array values and carries scalars into every iteration', () => {
137
+ const h = makeHarness('zip');
138
+ h.tmpl.value = '{{file}} ({{tone}})';
139
+ h.rows.push({ name: 'tone', values: 'concise' }, { name: 'file', values: 'a.go,b.go,c.go' });
140
+ h.update();
141
+ assert.deepEqual(expandedTexts(h), ['a.go (concise)', 'b.go (concise)', 'c.go (concise)']);
142
+ assert.match(h.previewBody.innerHTML, /\[3\/3\] tone=concise {2}file=c\.go/);
143
+ });
144
+ test('update treats @file sources as one opaque value and quotes glob sources', () => {
145
+ const h = makeHarness();
146
+ h.tmpl.value = 'Check {{file}} and {{src}}';
147
+ h.rows.push({ name: 'file', values: 'list.txt', source: 'file' }, { name: 'src', values: 'src/*.go', source: 'glob' });
148
+ h.update();
149
+ // The browser cannot read list.txt or expand the glob, so the preview
150
+ // shows one iteration with the raw markers...
151
+ assert.equal(h.previewCount.textContent, '1 result');
152
+ assert.deepEqual(expandedTexts(h), ['Check list.txt and src/*.go']);
153
+ // ...while the command hands them to the CLI in the syntax `promptargs help` documents.
154
+ assert.equal(h.cli(), 'promptargs "Check {{file}} and {{src}}" --file=@list.txt --src="src/*.go"');
155
+ });
156
+ test('update does not split a comma-containing @file value into an array', () => {
157
+ const h = makeHarness('cross');
158
+ h.tmpl.value = '{{file}}/{{focus}}';
159
+ h.rows.push({ name: 'file', values: 'one,two', source: 'file' }, { name: 'focus', values: 'x,y' });
160
+ h.update();
161
+ assert.equal(h.previewCount.textContent, '2 results');
162
+ assert.deepEqual(expandedTexts(h), ['one,two/x', 'one,two/y']);
163
+ assert.ok(!h.cli().includes('--cross'), 'only one real array, so no --cross');
164
+ });
165
+ test('update skips rows with empty values in the command but still previews defaults', () => {
166
+ const h = makeHarness();
167
+ h.tmpl.value = 'Be {{tone=concise}} about {{file}}';
168
+ h.rows.push({ name: 'tone', values: '' }, { name: 'file', values: 'a.go' });
169
+ h.update();
170
+ assert.deepEqual(expandedTexts(h), ['Be concise about a.go']);
171
+ assert.equal(h.cli(), 'promptargs "Be {{tone=concise}} about {{file}}" --file=a.go');
172
+ });
173
+ test('update highlights variables that remain unfilled after expansion', () => {
174
+ const h = makeHarness();
175
+ h.tmpl.value = 'Fix {{issue}} in {{file}}';
176
+ h.rows.push({ name: 'file', values: 'main.go' });
177
+ h.update();
178
+ assert.deepEqual(expandedTexts(h), ['Fix <span class="unfilled">{{issue}}</span> in main.go']);
179
+ });
180
+ test('update HTML-escapes template text, values, and labels before rendering', () => {
181
+ const h = makeHarness('zip');
182
+ h.tmpl.value = '<b>{{x}}</b> & {{y}}';
183
+ h.rows.push({ name: 'x', values: '<img src=x onerror=alert(1)>,ok' }, { name: 'y', values: 'a,b' });
184
+ h.update();
185
+ const html = h.previewBody.innerHTML;
186
+ assert.ok(!html.includes('<img'), 'raw value markup must not reach the preview');
187
+ assert.ok(!html.includes('<b>'), 'raw template markup must not reach the preview');
188
+ assert.match(html, /&lt;b&gt;&lt;img src=x onerror=alert\(1\)&gt;&lt;\/b&gt; &amp; a/);
189
+ assert.match(html, /iter-label">\[1\/2\] x=&lt;img src=x onerror=alert\(1\)&gt; {2}y=a</);
190
+ assert.match(h.cliText.innerHTML, /--x='&lt;img src=x onerror=alert\(1\)&gt;,ok'/);
191
+ });
192
+ test('update inlines the template in the command only when it is short and single-line', () => {
193
+ const inline = makeHarness();
194
+ inline.tmpl.value = 'Say "hi" to {{name}}';
195
+ inline.rows.push({ name: 'name', values: 'World' });
196
+ inline.update();
197
+ assert.equal(inline.cli(), 'promptargs "Say \\"hi\\" to {{name}}" --name=World');
198
+ const multiline = makeHarness();
199
+ multiline.tmpl.value = 'Line one {{name}}\nLine two';
200
+ multiline.rows.push({ name: 'name', values: 'World' });
201
+ multiline.update();
202
+ assert.equal(multiline.cli(), 'promptargs --name=World');
203
+ assert.deepEqual(expandedTexts(multiline), ['Line one World\nLine two']);
204
+ const long = makeHarness();
205
+ long.tmpl.value = `${'x'.repeat(200)} {{name}}`;
206
+ long.rows.push({ name: 'name', values: 'World' });
207
+ long.update();
208
+ assert.equal(long.cli(), 'promptargs --name=World');
209
+ });
210
+ test('update ignores rows without a name', () => {
211
+ const h = makeHarness();
212
+ h.tmpl.value = 'Hello {{name}}';
213
+ h.rows.push({ name: ' ', values: 'orphan' }, { name: 'name', values: 'World' });
214
+ h.update();
215
+ assert.deepEqual(expandedTexts(h), ['Hello World']);
216
+ assert.equal(h.cli(), 'promptargs "Hello {{name}}" --name=World');
217
+ });
218
+ function extractPresets() {
219
+ const start = uiHtml.indexOf('const PRESETS = [');
220
+ assert.notEqual(start, -1, 'PRESETS not found in ui.html');
221
+ const end = uiHtml.indexOf('\n ];', start);
222
+ assert.notEqual(end, -1, 'PRESETS array is not terminated');
223
+ const literal = uiHtml.slice(start + 'const PRESETS = '.length, end + '\n ];'.length);
224
+ // eslint-disable-next-line no-new-func
225
+ return new Function(`return ${literal}`)();
226
+ }
227
+ const presets = extractPresets();
228
+ test('every preset fills each template variable and uses a known iteration mode', () => {
229
+ assert.ok(presets.length >= 8, `expected the shipped preset catalogue, got ${presets.length}`);
230
+ const names = new Set();
231
+ for (const p of presets) {
232
+ assert.ok(!names.has(p.name), `duplicate preset name ${p.name}`);
233
+ names.add(p.name);
234
+ assert.ok(['zip', 'cross'].includes(p.mode), `${p.name}: unknown mode ${p.mode}`);
235
+ const templateVars = parseVars(p.template).map(v => v.name);
236
+ assert.ok(templateVars.length > 0, `${p.name}: template declares no variables`);
237
+ for (const v of templateVars) {
238
+ assert.ok(v in p.vars, `${p.name}: template uses {{${v}}} but vars has no entry for it`);
239
+ assert.ok(p.vars[v].length > 0, `${p.name}: vars.${v} is empty`);
240
+ }
241
+ for (const v of Object.keys(p.vars)) {
242
+ assert.ok(templateVars.includes(v), `${p.name}: vars.${v} is not used by the template`);
243
+ }
244
+ }
245
+ });
246
+ test('every preset renders through update() with no unfilled variables', () => {
247
+ for (const p of presets) {
248
+ const h = makeHarness(p.mode);
249
+ h.tmpl.value = p.template;
250
+ for (const [name, values] of Object.entries(p.vars))
251
+ h.rows.push({ name, values });
252
+ h.update();
253
+ assert.ok(!h.previewBody.innerHTML.includes('class="unfilled"'), `${p.name}: preview left a variable unfilled`);
254
+ const arrays = Object.values(p.vars).filter(v => v.includes(',')).map(v => v.split(',').length);
255
+ const expected = arrays.length === 0
256
+ ? 1
257
+ : p.mode === 'cross'
258
+ ? arrays.reduce((a, b) => a * b, 1)
259
+ : Math.max(...arrays);
260
+ assert.equal(h.iterations(), expected, `${p.name}: iteration count`);
261
+ assert.ok(h.cli().startsWith('promptargs '), `${p.name}: command is built`);
262
+ }
263
+ });
264
+ test('preset templates use only the documented {{var}} / {{var=default}} grammar', () => {
265
+ for (const p of presets) {
266
+ const braces = p.template.match(/\{\{[^}]*\}\}/g) ?? [];
267
+ VAR_PATTERN.lastIndex = 0;
268
+ const parsed = p.template.match(VAR_PATTERN) ?? [];
269
+ assert.deepEqual(parsed, braces, `${p.name}: a {{...}} token does not match VAR_PATTERN`);
270
+ }
271
+ });
272
+ //# sourceMappingURL=ui-builder.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-builder.test.js","sourceRoot":"","sources":["../src/ui-builder.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,95 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { readFileSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { spawnSync } from 'node:child_process';
7
+ import { VAR_PATTERN, expand } from './parser.js';
8
+ import { cartesian, zip } from './iterate.js';
9
+ import { compileInlineScript } from './ui-harness.js';
10
+ /**
11
+ * Regression tests for the shell safety of the command `update()` in
12
+ * ui.html generates (#86): a value with whitespace or shell metacharacters
13
+ * used to be emitted bare, so the `explain` and `fix` presets produced a
14
+ * command whose prompt was silently truncated by the shell.
15
+ *
16
+ * `update()` lives inside ui.html's IIFE, so it is extracted verbatim and
17
+ * evaluated against a stub DOM holding only what the command builder reads.
18
+ */
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const uiHtml = readFileSync(join(__dirname, 'ui.html'), 'utf-8');
21
+ function extractFunction(name) {
22
+ const start = uiHtml.indexOf(`function ${name}(`);
23
+ assert.notEqual(start, -1, `function ${name} not found in ui.html`);
24
+ let depth = 0;
25
+ for (let i = uiHtml.indexOf('{', start); i < uiHtml.length; i++) {
26
+ if (uiHtml[i] === '{')
27
+ depth++;
28
+ else if (uiHtml[i] === '}' && --depth === 0)
29
+ return uiHtml.slice(start, i + 1);
30
+ }
31
+ assert.fail(`unbalanced braces extracting ${name} from ui.html`);
32
+ }
33
+ /** Runs update() for one template + rows and returns the generated command. */
34
+ function commandFor(template, rows) {
35
+ const node = () => ({ innerHTML: '', textContent: '', className: '' });
36
+ const cliText = node();
37
+ const varBody = {
38
+ querySelectorAll: () => rows.map(row => ({
39
+ querySelector: (sel) => sel === '.var-name' ? { value: row.name }
40
+ : sel === '.var-values' ? { value: row.values }
41
+ : { value: row.source ?? 'manual' },
42
+ })),
43
+ };
44
+ const src = ['getRowData', 'splitValues', 'escHtml', 'highlightUnfilled', 'update']
45
+ .map(extractFunction).join('\n');
46
+ const factory = compileInlineScript('ui-update', [
47
+ 'tmpl', 'varBody', 'previewBody', 'previewCount', 'cliText', 'mode',
48
+ 'sharedCartesian', 'sharedZip', 'sharedExpand', 'VAR_PATTERN',
49
+ ], `${src}\nreturn update;`);
50
+ const update = factory({ value: template }, varBody, node(), node(), cliText, 'zip', cartesian, zip, expand, VAR_PATTERN);
51
+ update();
52
+ return cliText.innerHTML
53
+ .replace(/^<span class="dollar">\$<\/span>/, '')
54
+ .replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
55
+ }
56
+ /** What a POSIX shell would hand to the CLI for the generated command. */
57
+ function shellWords(command) {
58
+ const args = command.replace(/^promptargs /, '');
59
+ const r = spawnSync('sh', ['-c', `printf '%s\\n' ${args}`], { encoding: 'utf-8' });
60
+ assert.equal(r.status, 0, r.stderr);
61
+ return r.stdout.replace(/\n$/, '').split('\n');
62
+ }
63
+ test('bare values stay unquoted, so the common command is unchanged', () => {
64
+ assert.equal(commandFor('Review {{file}} for {{focus=correctness}}', [{ name: 'file', values: 'api.go' }, { name: 'focus', values: 'security' }]), 'promptargs "Review {{file}} for {{focus=correctness}}" --file=api.go --focus=security');
65
+ assert.equal(commandFor('{{file}} {{src}}', [{ name: 'file', values: 'list.txt', source: 'file' }, { name: 'src', values: 'src/*.go', source: 'glob' }]), 'promptargs "{{file}} {{src}}" --file=@list.txt --src="src/*.go"');
66
+ });
67
+ test('explain preset: a multi-word audience reaches the CLI as one argument', () => {
68
+ const cmd = commandFor('Explain {{file}} to a {{audience=junior developer}}', [{ name: 'file', values: 'a.ts' }, { name: 'audience', values: 'junior developer' }]);
69
+ assert.equal(cmd, `promptargs "Explain {{file}} to a {{audience=junior developer}}" --file=a.ts --audience='junior developer'`);
70
+ assert.deepEqual(shellWords(cmd), ['Explain {{file}} to a {{audience=junior developer}}', '--file=a.ts', '--audience=junior developer']);
71
+ });
72
+ test('fix preset: expected/actual sentences are not split into stray positionals', () => {
73
+ const cmd = commandFor('Fix {{file}}: expected {{expected}} but {{actual}}', [
74
+ { name: 'file', values: 'x.go' },
75
+ { name: 'expected', values: 'returns user object' },
76
+ { name: 'actual', values: 'crashes on line 42' },
77
+ ]);
78
+ assert.deepEqual(shellWords(cmd), [
79
+ 'Fix {{file}}: expected {{expected}} but {{actual}}', '--file=x.go',
80
+ '--expected=returns user object', '--actual=crashes on line 42',
81
+ ]);
82
+ });
83
+ test('shell metacharacters in values and the template are literal', () => {
84
+ const cmd = commandFor('Cost is $HOME `whoami` \\ "quoted" {{v}}', [{ name: 'v', values: `it's $x; rm -rf *` }]);
85
+ assert.deepEqual(shellWords(cmd), ['Cost is $HOME `whoami` \\ "quoted" {{v}}', `--v=it's $x; rm -rf *`]);
86
+ });
87
+ test('a comma-separated array value stays a bare word so arrays still parse', () => {
88
+ assert.equal(commandFor('{{f}}', [{ name: 'f', values: 'a.go,b.go' }]), 'promptargs "{{f}}" --f=a.go,b.go');
89
+ });
90
+ test('a glob value keeps its double quotes but shell-expandable characters inside are escaped', () => {
91
+ const cmd = commandFor('{{src}}', [{ name: 'src', values: 'src/$HOME/`x`/"q"/*.go', source: 'glob' }]);
92
+ assert.equal(cmd, 'promptargs "{{src}}" --src="src/\\$HOME/\\`x\\`/\\"q\\"/*.go"');
93
+ assert.deepEqual(shellWords(cmd), ['{{src}}', '--src=src/$HOME/`x`/"q"/*.go']);
94
+ });
95
+ //# sourceMappingURL=ui-command-quoting.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-command-quoting.test.js","sourceRoot":"","sources":["../src/ui-command-quoting.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Test helper: compile a fragment of ui.html's inline script so the test
3
+ * runner's coverage reporter can see it.
4
+ *
5
+ * The ui-*.test.ts suites extract functions out of ui.html and run them
6
+ * against a stub DOM. Doing that with `new Function` hides the code from
7
+ * `--experimental-test-coverage`: V8 reports such scripts with an empty URL
8
+ * and the reporter drops them, so the thresholds in scripts/run-tests.mjs
9
+ * could never gate ui.html. Writing the assembled source to a scratch file
10
+ * and compiling it with `vm.Script` under that file's URL makes it show up
11
+ * in the report like any other source file.
12
+ *
13
+ * Scratch files live in `.test-scratch/ui-inline/` at the repository root —
14
+ * gitignored, and outside `dist/` so `npm publish` never ships them. The
15
+ * file name is `<label>-<content hash>.js`: suites that assemble identical
16
+ * source share one entry in the report, and editing ui.html simply produces
17
+ * a new file instead of clashing with a stale one.
18
+ */
19
+ export declare const SCRATCH_DIR: string;
20
+ export declare function compileInlineScript<T>(label: string, params: readonly string[], body: string): (...args: unknown[]) => T;
@@ -0,0 +1,40 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, mkdirSync, renameSync, writeFileSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath, pathToFileURL } from 'node:url';
5
+ import { Script } from 'node:vm';
6
+ /**
7
+ * Test helper: compile a fragment of ui.html's inline script so the test
8
+ * runner's coverage reporter can see it.
9
+ *
10
+ * The ui-*.test.ts suites extract functions out of ui.html and run them
11
+ * against a stub DOM. Doing that with `new Function` hides the code from
12
+ * `--experimental-test-coverage`: V8 reports such scripts with an empty URL
13
+ * and the reporter drops them, so the thresholds in scripts/run-tests.mjs
14
+ * could never gate ui.html. Writing the assembled source to a scratch file
15
+ * and compiling it with `vm.Script` under that file's URL makes it show up
16
+ * in the report like any other source file.
17
+ *
18
+ * Scratch files live in `.test-scratch/ui-inline/` at the repository root —
19
+ * gitignored, and outside `dist/` so `npm publish` never ships them. The
20
+ * file name is `<label>-<content hash>.js`: suites that assemble identical
21
+ * source share one entry in the report, and editing ui.html simply produces
22
+ * a new file instead of clashing with a stale one.
23
+ */
24
+ export const SCRATCH_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', '.test-scratch', 'ui-inline');
25
+ export function compileInlineScript(label, params, body) {
26
+ const source = `(function (${params.join(', ')}) {\n${body}\n})`;
27
+ const hash = createHash('sha256').update(source).digest('hex').slice(0, 8);
28
+ const file = join(SCRATCH_DIR, `${label}-${hash}.js`);
29
+ if (!existsSync(file)) {
30
+ mkdirSync(SCRATCH_DIR, { recursive: true });
31
+ // Suites run in parallel processes; write-then-rename keeps a reader
32
+ // in another process from seeing a half-written file.
33
+ const tmp = `${file}.${process.pid}.tmp`;
34
+ writeFileSync(tmp, source);
35
+ renameSync(tmp, file);
36
+ }
37
+ const script = new Script(source, { filename: pathToFileURL(file).href });
38
+ return script.runInThisContext();
39
+ }
40
+ //# sourceMappingURL=ui-harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-harness.js","sourceRoot":"","sources":["../src/ui-harness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjC;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,WAAW,CAAC,CAAC;AAE7G,MAAM,UAAU,mBAAmB,CACjC,KAAa,EACb,MAAyB,EACzB,IAAY;IAEZ,MAAM,MAAM,GAAG,cAAc,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,MAAM,CAAC;IACjE,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3E,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC;IAEtD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,SAAS,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5C,qEAAqE;QACrE,sDAAsD;QACtD,MAAM,GAAG,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;QACzC,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC3B,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACxB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC1E,OAAO,MAAM,CAAC,gBAAgB,EAA+B,CAAC;AAChE,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { SCRATCH_DIR, compileInlineScript } from './ui-harness.js';
5
+ test('compileInlineScript returns a callable bound to the given parameters', () => {
6
+ const add = compileInlineScript('harness-self-test', ['a', 'b'], 'return a + b;');
7
+ assert.equal(add(2, 3), 5);
8
+ assert.equal(compileInlineScript('harness-self-test', [], 'return "none";')(), 'none');
9
+ });
10
+ test('compileInlineScript compiles under a content-hashed scratch file URL', () => {
11
+ const body = 'throw new Error(msg);';
12
+ const thrower = compileInlineScript('harness-self-test', ['msg'], body);
13
+ // Compiling the same source again must reuse the file, not fail on it.
14
+ compileInlineScript('harness-self-test', ['msg'], body);
15
+ let stack = '';
16
+ try {
17
+ thrower('where');
18
+ }
19
+ catch (e) {
20
+ stack = e.stack ?? '';
21
+ }
22
+ // The frame inside the script names the file the reporter will key on.
23
+ const match = /file:\/\/[^\s)]+harness-self-test-[0-9a-f]{8}\.js/.exec(stack);
24
+ assert.ok(match, `expected scratch file URL in stack, got:\n${stack}`);
25
+ const path = decodeURIComponent(match[0].replace(/^file:\/\//, ''));
26
+ assert.ok(path.startsWith(SCRATCH_DIR), `${path} should live under ${SCRATCH_DIR}`);
27
+ assert.ok(existsSync(path));
28
+ assert.equal(readFileSync(path, 'utf-8'), `(function (msg) {\n${body}\n})`);
29
+ });
30
+ //# sourceMappingURL=ui-harness.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-harness.test.js","sourceRoot":"","sources":["../src/ui-harness.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEnE,IAAI,CAAC,sEAAsE,EAAE,GAAG,EAAE;IAChF,MAAM,GAAG,GAAG,mBAAmB,CAAS,mBAAmB,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,eAAe,CAAC,CAAC;IAC1F,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3B,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAS,mBAAmB,EAAE,EAAE,EAAE,gBAAgB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;AACjG,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,sEAAsE,EAAE,GAAG,EAAE;IAChF,MAAM,IAAI,GAAG,uBAAuB,CAAC;IACrC,MAAM,OAAO,GAAG,mBAAmB,CAAQ,mBAAmB,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;IAC/E,uEAAuE;IACvE,mBAAmB,CAAQ,mBAAmB,EAAE,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;IAE/D,IAAI,KAAK,GAAG,EAAE,CAAC;IACf,IAAI,CAAC;QAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QAAC,KAAK,GAAI,CAAW,CAAC,KAAK,IAAI,EAAE,CAAC;IAAC,CAAC;IAEzE,uEAAuE;IACvE,MAAM,KAAK,GAAG,mDAAmD,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9E,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,6CAA6C,KAAK,EAAE,CAAC,CAAC;IACvE,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,CAAC;IACpE,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,GAAG,IAAI,sBAAsB,WAAW,EAAE,CAAC,CAAC;IACpF,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5B,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,sBAAsB,IAAI,MAAM,CAAC,CAAC;AAC9E,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,90 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { readFileSync } from 'node:fs';
4
+ import { join, dirname } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { resolve } from './resolver.js';
7
+ import { compileInlineScript } from './ui-harness.js';
8
+ /**
9
+ * Regression tests for ui.html's browser <script>.
10
+ *
11
+ * ui.html previously hand-reimplemented the {{var}} grammar, expansion, and
12
+ * array-iteration logic from parser.ts/resolver.ts as plain functions, with
13
+ * nothing type-checking that copy — it could silently drift from the CLI
14
+ * (see issue #62). ui.html now imports parseVars/expand from './parser.js'
15
+ * and cartesian/zip from './iterate.js' directly (the exact compiled modules
16
+ * the CLI runs), served by ui.ts alongside the page. These tests assert that
17
+ * wiring stays in place and only the browser-only helpers (splitValues,
18
+ * esc, escHtml — which have no CLI counterpart) remain hand-written.
19
+ */
20
+ const __dirname = dirname(fileURLToPath(import.meta.url));
21
+ const uiHtml = readFileSync(join(__dirname, 'ui.html'), 'utf-8');
22
+ /** Extract a `function name(...) { ... }` declaration via brace matching. */
23
+ function extractFunction(name) {
24
+ const start = uiHtml.indexOf(`function ${name}(`);
25
+ assert.notEqual(start, -1, `function ${name} not found in ui.html`);
26
+ const bodyStart = uiHtml.indexOf('{', start);
27
+ let depth = 0;
28
+ for (let i = bodyStart; i < uiHtml.length; i++) {
29
+ if (uiHtml[i] === '{')
30
+ depth++;
31
+ else if (uiHtml[i] === '}') {
32
+ depth--;
33
+ if (depth === 0)
34
+ return uiHtml.slice(start, i + 1);
35
+ }
36
+ }
37
+ assert.fail(`unbalanced braces extracting ${name} from ui.html`);
38
+ }
39
+ function instantiate(name) {
40
+ const src = extractFunction(name);
41
+ return compileInlineScript(`ui-fn-${name}`, [], `return ${src};`)();
42
+ }
43
+ const uiSplitValues = instantiate('splitValues');
44
+ const uiEsc = instantiate('esc');
45
+ const uiEscHtml = instantiate('escHtml');
46
+ test('ui.html imports the canonical grammar/iteration modules instead of reimplementing them', () => {
47
+ assert.match(uiHtml, /import \{ parseVars, expand as \w+, VAR_PATTERN \} from '\.\/parser\.js';/);
48
+ assert.match(uiHtml, /import \{ cartesian as \w+, zip as \w+ \} from '\.\/iterate\.js';/);
49
+ // The functions that used to hand-duplicate parser/resolver logic must be
50
+ // gone — their behavior now comes solely from the imported modules.
51
+ for (const removed of ['parseVarsFromTemplate', 'function expand(', 'function cartesian(', 'function zip(']) {
52
+ assert.ok(!uiHtml.includes(removed), `${removed} should no longer be defined in ui.html`);
53
+ }
54
+ });
55
+ test('ui.html declares an import map resolving "mustache" for the browser', () => {
56
+ assert.match(uiHtml, /<script type="importmap">/);
57
+ assert.match(uiHtml, /"mustache":\s*"\.\/mustache\.mjs"/);
58
+ });
59
+ test('ui splitValues mirrors CLI comma-separated handling', () => {
60
+ assert.deepEqual(uiSplitValues('a.go, b.go ,c.go', 'manual'), ['a.go', 'b.go', 'c.go']);
61
+ assert.deepEqual(uiSplitValues('a,,b,', 'manual'), ['a', 'b']);
62
+ assert.deepEqual(uiSplitValues('', 'manual'), []);
63
+ // @file values are opaque single entries in the UI (server has no file access)
64
+ assert.deepEqual(uiSplitValues('one,two', 'file'), ['one,two']);
65
+ });
66
+ test('ui esc/escHtml escape injection-relevant characters', () => {
67
+ // esc feeds attribute values built with innerHTML
68
+ assert.equal(uiEsc('a&b "c" <d>'), 'a&amp;b &quot;c&quot; &lt;d>');
69
+ // escHtml feeds text content
70
+ assert.equal(uiEscHtml('a&b <script>'), 'a&amp;b &lt;script&gt;');
71
+ });
72
+ // Sanity check that resolver's cartesian/zip iteration order (which the
73
+ // browser now imports verbatim via iterate.js) still matches what
74
+ // resolve() produces, keeping this parity documented even though the logic
75
+ // itself is no longer duplicated.
76
+ test('resolver cartesian/zip iteration order used by resolve()', async () => {
77
+ const vars = [
78
+ { name: 'file', raw: '{{file}}' },
79
+ { name: 'focus', raw: '{{focus}}' },
80
+ ];
81
+ const cross = await resolve(vars, { file: 'a.go,b.go,c.go', focus: 'security,perf' }, false, true);
82
+ assert.deepEqual(cross.iterations.map(it => [it['file'], it['focus']]), [
83
+ ['a.go', 'security'], ['a.go', 'perf'],
84
+ ['b.go', 'security'], ['b.go', 'perf'],
85
+ ['c.go', 'security'], ['c.go', 'perf'],
86
+ ]);
87
+ const zipped = await resolve(vars, { file: 'a.go,b.go,c.go', focus: 'security,perf' }, false, false);
88
+ assert.deepEqual(zipped.iterations.map(it => [it['file'], it['focus']]), [['a.go', 'security'], ['b.go', 'perf'], ['c.go', 'security']]);
89
+ });
90
+ //# sourceMappingURL=ui-inline.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-inline.test.js","sourceRoot":"","sources":["../src/ui-inline.test.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD;;;;;;;;;;;GAWG;AAEH,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,CAAC;AAEjE,6EAA6E;AAC7E,SAAS,eAAe,CAAC,IAAY;IACnC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC;IAClD,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,YAAY,IAAI,uBAAuB,CAAC,CAAC;IACpE,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC7C,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,IAAI,CAAC,GAAG,SAAS,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/C,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,KAAK,EAAE,CAAC;aAC1B,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YAC3B,KAAK,EAAE,CAAC;YACR,IAAI,KAAK,KAAK,CAAC;gBAAE,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,gCAAgC,IAAI,eAAe,CAAC,CAAC;AACnE,CAAC;AAED,SAAS,WAAW,CAAI,IAAY;IAClC,MAAM,GAAG,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IAClC,OAAO,mBAAmB,CAAI,SAAS,IAAI,EAAE,EAAE,EAAE,EAAE,UAAU,GAAG,GAAG,CAAC,EAAE,CAAC;AACzE,CAAC;AAED,MAAM,aAAa,GAAG,WAAW,CAA4C,aAAa,CAAC,CAAC;AAC5F,MAAM,KAAK,GAAG,WAAW,CAAwB,KAAK,CAAC,CAAC;AACxD,MAAM,SAAS,GAAG,WAAW,CAAwB,SAAS,CAAC,CAAC;AAEhE,IAAI,CAAC,wFAAwF,EAAE,GAAG,EAAE;IAClG,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,2EAA2E,CAAC,CAAC;IAClG,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,mEAAmE,CAAC,CAAC;IAC1F,0EAA0E;IAC1E,oEAAoE;IACpE,KAAK,MAAM,OAAO,IAAI,CAAC,uBAAuB,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,eAAe,CAAC,EAAE,CAAC;QAC5G,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,GAAG,OAAO,yCAAyC,CAAC,CAAC;IAC5F,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qEAAqE,EAAE,GAAG,EAAE;IAC/E,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC;IAClD,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,mCAAmC,CAAC,CAAC;AAC5D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;IAC/D,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,kBAAkB,EAAE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACxF,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IAC/D,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;IAClD,+EAA+E;IAC/E,MAAM,CAAC,SAAS,CAAC,aAAa,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;IAC/D,kDAAkD;IAClD,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,8BAA8B,CAAC,CAAC;IACnE,6BAA6B;IAC7B,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,EAAE,wBAAwB,CAAC,CAAC;AACpE,CAAC,CAAC,CAAC;AAEH,wEAAwE;AACxE,kEAAkE;AAClE,2EAA2E;AAC3E,kCAAkC;AAClC,IAAI,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;IAC1E,MAAM,IAAI,GAAkB;QAC1B,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE;QACjC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE;KACpC,CAAC;IACF,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IACnG,MAAM,CAAC,SAAS,CACd,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EACrD;QACE,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;QACtC,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;QACtC,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;KACvC,CACF,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACrG,MAAM,CAAC,SAAS,CACd,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EACtD,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAC/D,CAAC;AACJ,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};