@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
package/dist/ui.html CHANGED
@@ -649,9 +649,19 @@
649
649
  </div>
650
650
  </div>
651
651
 
652
- <script>
652
+ <script type="importmap">
653
+ { "imports": { "mustache": "./mustache.mjs" } }
654
+ </script>
655
+ <script type="module">
656
+ // Import the canonical {{var}} grammar and iteration logic from the same
657
+ // compiled modules the CLI runs, instead of hand-reimplementing them here
658
+ // (see issue #69). Only splitValues/esc/escHtml stay UI-local: they encode
659
+ // browser-only concerns (no glob/file access, HTML escaping) with no CLI
660
+ // counterpart to drift from.
661
+ import { parseVars, expand as sharedExpand, VAR_PATTERN } from './parser.js';
662
+ import { cartesian as sharedCartesian, zip as sharedZip } from './iterate.js';
663
+
653
664
  (function () {
654
- const VAR_RE = /\{\{(\w+)(?:=([^}]*))?\}\}/g;
655
665
  const DEBOUNCE_MS = 120;
656
666
 
657
667
  const PRESETS = [
@@ -726,20 +736,6 @@
726
736
  const copyBtn = document.getElementById('copy-btn');
727
737
  const modeDesc = document.getElementById('mode-desc');
728
738
 
729
- function parseVarsFromTemplate(text) {
730
- const seen = new Set();
731
- const vars = [];
732
- let m;
733
- VAR_RE.lastIndex = 0;
734
- while ((m = VAR_RE.exec(text)) !== null) {
735
- if (!seen.has(m[1])) {
736
- seen.add(m[1]);
737
- vars.push({ name: m[1], defaultValue: m[2] });
738
- }
739
- }
740
- return vars;
741
- }
742
-
743
739
  function getRowData() {
744
740
  const rows = [];
745
741
  for (const tr of varBody.querySelectorAll('tr')) {
@@ -753,7 +749,7 @@
753
749
 
754
750
  function syncVarsFromTemplate() {
755
751
  const text = tmpl.value;
756
- const parsed = parseVarsFromTemplate(text);
752
+ const parsed = parseVars(text);
757
753
  const existing = getRowData();
758
754
  const existingNames = new Set(existing.map(r => r.name));
759
755
 
@@ -814,41 +810,9 @@
814
810
  return raw.split(',').map(s => s.trim()).filter(Boolean);
815
811
  }
816
812
 
817
- function cartesian(arrays) {
818
- if (arrays.length === 0) return [[]];
819
- const first = arrays[0];
820
- const rest = cartesian(arrays.slice(1));
821
- const out = [];
822
- for (const v of first) {
823
- for (const r of rest) {
824
- out.push([v, ...r]);
825
- }
826
- }
827
- return out;
828
- }
829
-
830
- function zip(arrays) {
831
- const maxLen = Math.max(...arrays.map(a => a.length), 1);
832
- const out = [];
833
- for (let i = 0; i < maxLen; i++) {
834
- out.push(arrays.map(a => a[Math.min(i, a.length - 1)]));
835
- }
836
- return out;
837
- }
838
-
839
- function expand(template, values) {
840
- let result = template;
841
- VAR_RE.lastIndex = 0;
842
- result = result.replace(VAR_RE, (match, name, def) => {
843
- if (name in values) return values[name];
844
- if (def !== undefined) return def;
845
- return match;
846
- });
847
- return result;
848
- }
849
-
850
813
  function highlightUnfilled(html) {
851
- return html.replace(/\{\{(\w+)(?:=[^}]*)?\}\}/g, '<span class="unfilled">$&</span>');
814
+ VAR_PATTERN.lastIndex = 0;
815
+ return html.replace(VAR_PATTERN, '<span class="unfilled">$&</span>');
852
816
  }
853
817
 
854
818
  function update() {
@@ -879,16 +843,16 @@
879
843
  if (arrayVars.length === 0) {
880
844
  combos = [[]];
881
845
  } else if (mode === 'cross') {
882
- combos = cartesian(arrayVars.map(v => v.values));
846
+ combos = sharedCartesian(arrayVars.map(v => v.values));
883
847
  } else {
884
- combos = zip(arrayVars.map(v => v.values));
848
+ combos = sharedZip(arrayVars.map(v => v.values));
885
849
  }
886
850
 
887
851
  const results = [];
888
852
  for (const combo of combos) {
889
853
  const vals = { ...scalarValues };
890
854
  arrayVars.forEach((v, i) => { vals[v.name] = combo[i]; });
891
- results.push({ values: { ...vals }, expanded: expand(text, vals) });
855
+ results.push({ values: { ...vals }, expanded: sharedExpand(text, vals) });
892
856
  }
893
857
 
894
858
  const count = results.length;
@@ -908,16 +872,25 @@
908
872
  }
909
873
  previewBody.innerHTML = html || '<span class="preview-empty">No output</span>';
910
874
 
911
- // Build CLI command
875
+ // Build CLI command. Every value must survive a POSIX shell unchanged:
876
+ // a bare word is left as-is, anything else is single-quoted (the only
877
+ // quoting under which $, backtick, backslash and whitespace are all
878
+ // literal). The inlined template stays double-quoted but also escapes
879
+ // the characters the shell expands inside double quotes.
880
+ const bareWord = /^[A-Za-z0-9_@%+=:,.\/-]+$/;
881
+ const shellQuote = s => bareWord.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`;
882
+ const dquote = s => `"${s.replace(/[\\"$`]/g, '\\$&')}"`;
912
883
  let cli = 'promptargs';
913
884
  const hasInline = !text.includes('\n') && text.length < 200;
914
885
  if (hasInline) {
915
- cli += ` "${text.replace(/"/g, '\\"')}"`;
886
+ cli += ` ${dquote(text)}`;
916
887
  }
917
888
  for (const row of rows) {
918
889
  if (!row.values) continue;
919
890
  const prefix = row.source === 'file' ? '@' : '';
920
- const val = row.source === 'glob' ? `"${row.values}"` : `${prefix}${row.values}`;
891
+ // Globs keep their double quotes so the CLI, not the shell, expands them;
892
+ // the same escaping as the template keeps the pattern itself literal.
893
+ const val = row.source === 'glob' ? dquote(row.values) : shellQuote(`${prefix}${row.values}`);
921
894
  cli += ` --${row.name}=${val}`;
922
895
  }
923
896
  if (mode === 'cross' && arrayVars.length > 1) {
package/dist/ui.js CHANGED
@@ -2,7 +2,7 @@ import { createServer } from 'node:http';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { join, dirname } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { autodetect, AUTODETECT_VARS } from './autodetect.js';
5
+ import { autodetect, isSensitiveEnvName, isSensitiveEnvValue, AUTODETECT_VARS } from './autodetect.js';
6
6
  const DEFAULT_PORT = 3700;
7
7
  const OPEN_DELAY_MS = 500;
8
8
  const DIFF_PREVIEW_MAX_CHARS = 60;
@@ -18,15 +18,15 @@ const SKIP_ENV_EXACT = new Set([
18
18
  'TERM_PROGRAM_VERSION', 'TERM_SESSION_ID', 'Apple_PubSub_Socket_Render',
19
19
  'MallocNanoZone', 'ORIGINAL_XDG_CURRENT_DESKTOP', 'GIT_ASKPASS',
20
20
  ]);
21
- function shouldSkipEnv(key) {
21
+ export function shouldSkipEnv(key) {
22
22
  if (SKIP_ENV_EXACT.has(key))
23
23
  return true;
24
24
  return SKIP_ENV_PREFIXES.some(p => key.startsWith(p));
25
25
  }
26
- function truncateValue(val, max) {
26
+ export function truncateValue(val, max) {
27
27
  return val.length > max ? val.slice(0, max) + '...' : val;
28
28
  }
29
- function collectEnvVars() {
29
+ export function collectEnvVars() {
30
30
  const git = {};
31
31
  for (const name of AUTODETECT_VARS) {
32
32
  const value = autodetect(name);
@@ -38,13 +38,29 @@ function collectEnvVars() {
38
38
  }
39
39
  const terminal = {};
40
40
  for (const [key, val] of Object.entries(process.env)) {
41
- if (!val || shouldSkipEnv(key))
41
+ // Never embed credential-looking names or credential-shaped values in the
42
+ // served page.
43
+ if (!val || shouldSkipEnv(key) || isSensitiveEnvName(key) || isSensitiveEnvValue(val))
42
44
  continue;
43
45
  terminal[key] = truncateValue(val, ENV_VAL_PREVIEW_MAX_CHARS);
44
46
  }
45
47
  return { git, terminal };
46
48
  }
47
- export function startUI(port = DEFAULT_PORT) {
49
+ export function buildUIHtml(rawHtml, envVars) {
50
+ // Escape "<" so env values cannot break out of the script tag (e.g. "</script>").
51
+ const envJson = JSON.stringify(envVars).replace(/</g, '\\u003c');
52
+ const envScript = `<script>window.__PROMPTARGS_ENV__ = ${envJson};</script>`;
53
+ return rawHtml.replace('<script>', envScript + '\n<script>');
54
+ }
55
+ // Browser-facing modules the builder UI imports directly instead of
56
+ // hand-reimplementing their logic (see buildUIHtml / ui.html). Serving the
57
+ // exact compiled files means the browser runs the same code the CLI does.
58
+ const STATIC_JS_ROUTES = {
59
+ '/parser.js': 'parser.js',
60
+ '/iterate.js': 'iterate.js',
61
+ '/mustache.mjs': 'mustache.mjs',
62
+ };
63
+ export function startUI(port = DEFAULT_PORT, options = {}) {
48
64
  const __dirname = dirname(fileURLToPath(import.meta.url));
49
65
  const htmlPath = join(__dirname, 'ui.html');
50
66
  let rawHtml;
@@ -55,24 +71,50 @@ export function startUI(port = DEFAULT_PORT) {
55
71
  console.error('UI file not found. Reinstall @hivecommons/promptargs.');
56
72
  process.exit(1);
57
73
  }
58
- const envVars = collectEnvVars();
59
- const envScript = `<script>window.__PROMPTARGS_ENV__ = ${JSON.stringify(envVars)};</script>`;
60
- const html = rawHtml.replace('<script>', envScript + '\n<script>');
61
- const server = createServer((_req, res) => {
74
+ const html = buildUIHtml(rawHtml, collectEnvVars());
75
+ const ALLOWED_HOSTS = new Set([
76
+ `localhost:${port}`,
77
+ `127.0.0.1:${port}`,
78
+ `[::1]:${port}`,
79
+ ]);
80
+ const server = createServer((req, res) => {
81
+ // Reject non-local Host headers to block DNS-rebinding reads of env data.
82
+ if (!req.headers.host || !ALLOWED_HOSTS.has(req.headers.host)) {
83
+ res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' });
84
+ res.end('Forbidden');
85
+ return;
86
+ }
87
+ const file = req.url ? STATIC_JS_ROUTES[req.url] : undefined;
88
+ if (file) {
89
+ try {
90
+ const js = readFileSync(join(__dirname, file), 'utf-8');
91
+ res.writeHead(200, { 'Content-Type': 'text/javascript; charset=utf-8' });
92
+ res.end(js);
93
+ }
94
+ catch {
95
+ res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
96
+ res.end('Not found');
97
+ }
98
+ return;
99
+ }
62
100
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
63
101
  res.end(html);
64
102
  });
65
- server.listen(port, () => {
103
+ // Bind loopback only: the page embeds environment variables and must never
104
+ // be reachable from other machines on the network.
105
+ server.listen(port, '127.0.0.1', () => {
66
106
  const url = `http://localhost:${port}`;
67
107
  console.log(`promptargs builder running at ${url}`);
68
108
  console.log('Press Ctrl+C to stop.\n');
69
- setTimeout(() => {
70
- import('node:child_process').then(({ exec }) => {
71
- const cmd = process.platform === 'darwin' ? 'open' :
72
- process.platform === 'win32' ? 'start' : 'xdg-open';
73
- exec(`${cmd} ${url}`);
74
- });
75
- }, OPEN_DELAY_MS);
109
+ if (options.openBrowser ?? true) {
110
+ setTimeout(() => {
111
+ import('node:child_process').then(({ exec }) => {
112
+ const cmd = process.platform === 'darwin' ? 'open' :
113
+ process.platform === 'win32' ? 'start' : 'xdg-open';
114
+ exec(`${cmd} ${url}`);
115
+ });
116
+ }, OPEN_DELAY_MS);
117
+ }
76
118
  });
77
119
  server.on('error', (err) => {
78
120
  if (err.code === 'EADDRINUSE') {
@@ -81,5 +123,6 @@ export function startUI(port = DEFAULT_PORT) {
81
123
  }
82
124
  throw err;
83
125
  });
126
+ return server;
84
127
  }
85
128
  //# sourceMappingURL=ui.js.map
package/dist/ui.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,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,UAAU,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAE9D,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,aAAa,GAAG,GAAG,CAAC;AAC1B,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAClC,MAAM,yBAAyB,GAAG,EAAE,CAAC;AAErC,MAAM,iBAAiB,GAAG;IACxB,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB;IAC3E,cAAc,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ;IACvE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG;CACrE,CAAC;AAEF,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU;IACtE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,cAAc;IAC1E,sBAAsB,EAAE,iBAAiB,EAAE,4BAA4B;IACvE,gBAAgB,EAAE,8BAA8B,EAAE,aAAa;CAChE,CAAC,CAAC;AAEH,SAAS,aAAa,CAAC,GAAW;IAChC,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,aAAa,CAAC,GAAW,EAAE,GAAW;IAC7C,OAAO,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;AAC5D,CAAC;AAOD,SAAS,cAAc;IACrB,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,IAAI,IAAI,eAAe,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,MAAM;gBACzB,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,sBAAsB,CAAC;gBAC9C,CAAC,CAAC,KAAK,CAAC;QACZ,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAC5C,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC,GAAG,IAAI,aAAa,CAAC,GAAG,CAAC;YAAE,SAAS;QACzC,QAAQ,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,GAAG,EAAE,yBAAyB,CAAC,CAAC;IAChE,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,IAAI,GAAG,YAAY;IACzC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAC5C,IAAI,OAAe,CAAC;IAEpB,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;QACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;IACjC,MAAM,SAAS,GAAG,uCAAuC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,YAAY,CAAC;IAC7F,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE,SAAS,GAAG,YAAY,CAAC,CAAC;IAEnE,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QACxC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAC;QACnE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACvB,MAAM,GAAG,GAAG,oBAAoB,IAAI,EAAE,CAAC;QACvC,OAAO,CAAC,GAAG,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAC;QAEvC,UAAU,CAAC,GAAG,EAAE;YACd,MAAM,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE;gBAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;oBACxC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;gBAChE,IAAI,CAAC,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC;YACxB,CAAC,CAAC,CAAC;QACL,CAAC,EAAE,aAAa,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAA0B,EAAE,EAAE;QAChD,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YAC9B,OAAO,CAAC,KAAK,CAAC,QAAQ,IAAI,yCAAyC,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC;YAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,283 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { get } from 'node:http';
4
+ import { createServer as createNetServer } from 'node:net';
5
+ import { spawnSync } from 'node:child_process';
6
+ import { copyFileSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
7
+ import { join, dirname } from 'node:path';
8
+ import { tmpdir } from 'node:os';
9
+ import { fileURLToPath, pathToFileURL } from 'node:url';
10
+ import { buildUIHtml, collectEnvVars, shouldSkipEnv, startUI, truncateValue, } from './ui.js';
11
+ test('shouldSkipEnv filters noisy shell and tooling variables', () => {
12
+ assert.equal(shouldSkipEnv('HOME'), true);
13
+ assert.equal(shouldSkipEnv('npm_config_cache'), true);
14
+ assert.equal(shouldSkipEnv('VSCODE_PID'), true);
15
+ assert.equal(shouldSkipEnv('PROMPTARGS_VISIBLE'), false);
16
+ });
17
+ test('truncateValue shortens long values and leaves short values unchanged', () => {
18
+ assert.equal(truncateValue('short', 10), 'short');
19
+ assert.equal(truncateValue('abcdefghijklmnopqrstuvwxyz', 8), 'abcdefgh...');
20
+ });
21
+ test('collectEnvVars includes terminal env values without skipped keys', () => {
22
+ const savedVisible = process.env['PROMPTARGS_TEST_VISIBLE'];
23
+ const savedHome = process.env['HOME'];
24
+ process.env['PROMPTARGS_TEST_VISIBLE'] = 'visible-value';
25
+ process.env['HOME'] = 'hidden-home';
26
+ try {
27
+ const env = collectEnvVars();
28
+ assert.equal(env.terminal['PROMPTARGS_TEST_VISIBLE'], 'visible-value');
29
+ assert.equal(env.terminal['HOME'], undefined);
30
+ }
31
+ finally {
32
+ if (savedVisible === undefined)
33
+ delete process.env['PROMPTARGS_TEST_VISIBLE'];
34
+ else
35
+ process.env['PROMPTARGS_TEST_VISIBLE'] = savedVisible;
36
+ if (savedHome === undefined)
37
+ delete process.env['HOME'];
38
+ else
39
+ process.env['HOME'] = savedHome;
40
+ }
41
+ });
42
+ test('collectEnvVars omits credential-looking names and credential-shaped values', () => {
43
+ const keys = ['PROMPTARGS_TEST_SIGNING_KEY', 'PROMPTARGS_TEST_DB_URL', 'PROMPTARGS_TEST_PLAIN'];
44
+ const saved = Object.fromEntries(keys.map(k => [k, process.env[k]]));
45
+ process.env['PROMPTARGS_TEST_SIGNING_KEY'] = 'sig';
46
+ process.env['PROMPTARGS_TEST_DB_URL'] = 'postgres://app:hunter2@db.internal/app';
47
+ process.env['PROMPTARGS_TEST_PLAIN'] = 'plain';
48
+ try {
49
+ const env = collectEnvVars();
50
+ assert.equal(env.terminal['PROMPTARGS_TEST_SIGNING_KEY'], undefined);
51
+ assert.equal(env.terminal['PROMPTARGS_TEST_DB_URL'], undefined);
52
+ assert.equal(env.terminal['PROMPTARGS_TEST_PLAIN'], 'plain');
53
+ }
54
+ finally {
55
+ for (const k of keys) {
56
+ if (saved[k] === undefined)
57
+ delete process.env[k];
58
+ else
59
+ process.env[k] = saved[k];
60
+ }
61
+ }
62
+ });
63
+ test('buildUIHtml escapes env JSON so values cannot escape the script tag', () => {
64
+ const html = buildUIHtml('<html><head><script></script></head></html>', {
65
+ git: {},
66
+ terminal: { PROMPTARGS_TEST: '</script><script>alert(1)</script>' },
67
+ });
68
+ assert.match(html, /window\.__PROMPTARGS_ENV__/);
69
+ assert.doesNotMatch(html, /<\/script><script>alert\(1\)<\/script>/);
70
+ assert.match(html, /\\u003c\/script>\\u003cscript>alert\(1\)\\u003c\/script>/);
71
+ });
72
+ test('startUI binds a server that rejects non-local Host headers', async () => {
73
+ const server = startUI(0, { openBrowser: false });
74
+ await onceListening(server);
75
+ const { port } = server.address();
76
+ try {
77
+ const allowed = await request(port, '127.0.0.1:0');
78
+ assert.equal(allowed.statusCode, 200);
79
+ assert.match(allowed.body, /window\.__PROMPTARGS_ENV__/);
80
+ const forbidden = await request(port, 'evil.example');
81
+ assert.equal(forbidden.statusCode, 403);
82
+ assert.equal(forbidden.body, 'Forbidden');
83
+ }
84
+ finally {
85
+ await closeServer(server);
86
+ }
87
+ });
88
+ test('startUI serves the parser/iterate/mustache modules the browser imports', async () => {
89
+ const server = startUI(0, { openBrowser: false });
90
+ await onceListening(server);
91
+ const { port } = server.address();
92
+ try {
93
+ const parser = await request(port, '127.0.0.1:0', '/parser.js');
94
+ assert.equal(parser.statusCode, 200);
95
+ assert.match(parser.contentType ?? '', /text\/javascript/);
96
+ assert.match(parser.body, /export function parseVars/);
97
+ assert.match(parser.body, /export const VAR_PATTERN/);
98
+ const iterate = await request(port, '127.0.0.1:0', '/iterate.js');
99
+ assert.equal(iterate.statusCode, 200);
100
+ assert.match(iterate.body, /export function cartesian/);
101
+ assert.match(iterate.body, /export function zip/);
102
+ const mustache = await request(port, '127.0.0.1:0', '/mustache.mjs');
103
+ assert.equal(mustache.statusCode, 200);
104
+ assert.match(mustache.body, /export default mustache/);
105
+ const missing = await request(port, '127.0.0.1:0', '/does-not-exist.js');
106
+ assert.equal(missing.statusCode, 200);
107
+ assert.match(missing.body, /window\.__PROMPTARGS_ENV__/);
108
+ }
109
+ finally {
110
+ await closeServer(server);
111
+ }
112
+ });
113
+ test('startUI answers 404 for a static route whose backing file is missing', () => {
114
+ // Copy the compiled ui.js (plus its one local dep) into a directory
115
+ // without parser.js/iterate.js/mustache.mjs, so requesting a known static
116
+ // route hits the readFileSync catch branch.
117
+ const __dirnameUi = dirname(fileURLToPath(import.meta.url));
118
+ const dir = mkdtempSync(join(tmpdir(), 'promptargs-ui-static-'));
119
+ try {
120
+ for (const f of ['ui.js', 'ui.html', 'autodetect.js']) {
121
+ copyFileSync(join(__dirnameUi, f), join(dir, f));
122
+ }
123
+ writeFileSync(join(dir, 'package.json'), '{"type":"module"}');
124
+ // Intentionally omit parser.js/iterate.js/mustache.mjs so the route 404s.
125
+ const script = `
126
+ import { get } from 'node:http';
127
+ import(${JSON.stringify(pathToFileURL(join(dir, 'ui.js')).href)})
128
+ .then(m => {
129
+ const server = m.startUI(0, { openBrowser: false });
130
+ server.once('listening', () => {
131
+ const { port } = server.address();
132
+ get({
133
+ hostname: '127.0.0.1', port, path: '/parser.js',
134
+ headers: { Host: '127.0.0.1:0' },
135
+ }, res => {
136
+ process.stdout.write('STATUS:' + res.statusCode);
137
+ server.close(() => process.exit(0));
138
+ });
139
+ });
140
+ });
141
+ `;
142
+ const res = spawnSync(process.execPath, ['--input-type=module', '-e', script], {
143
+ encoding: 'utf-8',
144
+ timeout: 15000,
145
+ });
146
+ assert.match(res.stdout, /STATUS:404/, res.stderr);
147
+ }
148
+ finally {
149
+ rmSync(dir, { recursive: true, force: true });
150
+ }
151
+ });
152
+ function onceListening(server) {
153
+ if (server.listening)
154
+ return Promise.resolve();
155
+ return new Promise(resolve => server.once('listening', resolve));
156
+ }
157
+ function closeServer(server) {
158
+ return new Promise((resolve, reject) => {
159
+ server.close(err => err ? reject(err) : resolve());
160
+ });
161
+ }
162
+ function request(port, host, path = '/') {
163
+ return new Promise((resolve, reject) => {
164
+ const req = get({
165
+ hostname: '127.0.0.1',
166
+ port,
167
+ path,
168
+ headers: { Host: host },
169
+ }, res => {
170
+ let body = '';
171
+ res.setEncoding('utf8');
172
+ res.on('data', chunk => { body += chunk; });
173
+ res.on('end', () => resolve({
174
+ statusCode: res.statusCode ?? 0,
175
+ body,
176
+ contentType: res.headers['content-type'],
177
+ }));
178
+ });
179
+ req.on('error', reject);
180
+ });
181
+ }
182
+ // startUI exits the process on fatal errors, so those branches are
183
+ // exercised in a child node process rather than in-process.
184
+ const __dirname = dirname(fileURLToPath(import.meta.url));
185
+ function runStartUI(uiModulePath, port) {
186
+ const script = `
187
+ import(${JSON.stringify(pathToFileURL(uiModulePath).href)})
188
+ .then(m => m.startUI(${port}, { openBrowser: false }));
189
+ `;
190
+ const res = spawnSync(process.execPath, ['--input-type=module', '-e', script], {
191
+ encoding: 'utf-8',
192
+ timeout: 15000,
193
+ });
194
+ return { status: res.status, stderr: res.stderr };
195
+ }
196
+ test('startUI exits 1 with a port hint when the port is in use', async () => {
197
+ const blocker = createNetServer();
198
+ await new Promise(resolve => blocker.listen(0, '127.0.0.1', resolve));
199
+ const { port } = blocker.address();
200
+ try {
201
+ const res = runStartUI(join(__dirname, 'ui.js'), port);
202
+ assert.strictEqual(res.status, 1);
203
+ assert.match(res.stderr, new RegExp(`Port ${port} is in use`));
204
+ assert.match(res.stderr, new RegExp(`--port=${port + 1}`));
205
+ }
206
+ finally {
207
+ await new Promise(resolve => blocker.close(resolve));
208
+ }
209
+ });
210
+ test('startUI exits 1 with a reinstall hint when ui.html is missing', () => {
211
+ // Copy the compiled module (and its one local dep) somewhere without
212
+ // ui.html so the readFileSync catch branch runs.
213
+ const dir = mkdtempSync(join(tmpdir(), 'promptargs-ui-test-'));
214
+ try {
215
+ copyFileSync(join(__dirname, 'ui.js'), join(dir, 'ui.js'));
216
+ copyFileSync(join(__dirname, 'autodetect.js'), join(dir, 'autodetect.js'));
217
+ // Node 18 has no ESM syntax detection; mark the temp dir as ESM.
218
+ writeFileSync(join(dir, 'package.json'), '{"type":"module"}');
219
+ const res = runStartUI(join(dir, 'ui.js'), 0);
220
+ assert.strictEqual(res.status, 1);
221
+ assert.match(res.stderr, /UI file not found\. Reinstall @hivecommons\/promptargs\./);
222
+ }
223
+ finally {
224
+ rmSync(dir, { recursive: true, force: true });
225
+ }
226
+ });
227
+ test('startUI schedules a browser open by default', () => {
228
+ // openBrowser defaults to true; run in a child with an empty PATH so the
229
+ // platform open command is a harmless shell failure, never a real browser.
230
+ const script = `
231
+ import(${JSON.stringify(pathToFileURL(join(__dirname, 'ui.js')).href)})
232
+ .then(m => {
233
+ const server = m.startUI(0);
234
+ setTimeout(() => server.close(() => process.exit(0)), 900);
235
+ });
236
+ `;
237
+ const res = spawnSync(process.execPath, ['--input-type=module', '-e', script], {
238
+ encoding: 'utf-8',
239
+ timeout: 15000,
240
+ env: { ...process.env, PATH: '' },
241
+ });
242
+ assert.strictEqual(res.status, 0);
243
+ assert.match(res.stdout, /promptargs builder running at http:\/\/localhost:\d+/);
244
+ });
245
+ test('browser open picks the right command for each platform', () => {
246
+ // The darwin/win32 arms of the open-command ternary never run on Linux CI;
247
+ // force each platform value in a child and let the command fail on PATH=''.
248
+ for (const platform of ['darwin', 'win32', 'linux']) {
249
+ const script = `
250
+ Object.defineProperty(process, 'platform', { value: ${JSON.stringify(platform)} });
251
+ import(${JSON.stringify(pathToFileURL(join(__dirname, 'ui.js')).href)})
252
+ .then(m => {
253
+ const server = m.startUI(0);
254
+ setTimeout(() => server.close(() => process.exit(0)), 900);
255
+ });
256
+ `;
257
+ const res = spawnSync(process.execPath, ['--input-type=module', '-e', script], {
258
+ encoding: 'utf-8',
259
+ timeout: 15000,
260
+ env: { ...process.env, PATH: '' },
261
+ });
262
+ assert.strictEqual(res.status, 0, `platform ${platform}: ${res.stderr}`);
263
+ assert.match(res.stdout, /promptargs builder running at http:\/\/localhost:\d+/);
264
+ }
265
+ });
266
+ test('startUI rethrows non-EADDRINUSE server errors', async (t) => {
267
+ // Only run where binding port 1 really fails with EACCES; containers with
268
+ // ip_unprivileged_port_start=0 (or root) can bind it and would hang instead.
269
+ const probeErr = await new Promise(resolve => {
270
+ const probe = createNetServer();
271
+ probe.once('error', err => resolve(err));
272
+ probe.listen(1, '127.0.0.1', () => probe.close(() => resolve(null)));
273
+ });
274
+ if (probeErr?.code !== 'EACCES') {
275
+ t.skip('binding port 1 did not fail with EACCES');
276
+ return;
277
+ }
278
+ const res = runStartUI(join(__dirname, 'ui.js'), 1);
279
+ assert.notStrictEqual(res.status, 0);
280
+ assert.match(res.stderr, /EACCES/);
281
+ assert.doesNotMatch(res.stderr, /is in use/);
282
+ });
283
+ //# sourceMappingURL=ui.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.test.js","sourceRoot":"","sources":["../src/ui.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hivecommons/promptargs",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Template arguments for AI prompts — variable substitution for Claude Code, Copilot, and more",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8,9 +8,10 @@
8
8
  "promptargs": "dist/cli.js"
9
9
  },
10
10
  "scripts": {
11
- "build": "tsc && cp src/ui.html dist/ui.html",
11
+ "build": "tsc && cp src/ui.html dist/ui.html && cp node_modules/mustache/mustache.mjs dist/mustache.mjs",
12
12
  "dev": "tsc --watch",
13
- "test": "node --test dist/**/*.test.js",
13
+ "test": "node scripts/run-tests.mjs",
14
+ "test:coverage": "node scripts/run-tests.mjs --coverage",
14
15
  "lint": "tsc --noEmit"
15
16
  },
16
17
  "keywords": [