@hivecommons/promptargs 0.7.0 → 0.8.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 (80) hide show
  1. package/README.md +100 -15
  2. package/dist/autodetect.js +24 -9
  3. package/dist/autodetect.js.map +1 -1
  4. package/dist/cli.js +51 -147
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands.d.ts +4 -0
  7. package/dist/commands.js +130 -0
  8. package/dist/commands.js.map +1 -0
  9. package/dist/flags.d.ts +14 -0
  10. package/dist/flags.js +57 -0
  11. package/dist/flags.js.map +1 -0
  12. package/dist/loader.d.ts +2 -0
  13. package/dist/loader.js +64 -11
  14. package/dist/loader.js.map +1 -1
  15. package/dist/log.d.ts +19 -0
  16. package/dist/log.js +41 -0
  17. package/dist/log.js.map +1 -0
  18. package/dist/parser.js +33 -7
  19. package/dist/parser.js.map +1 -1
  20. package/dist/resolver.d.ts +2 -0
  21. package/dist/resolver.js +60 -30
  22. package/dist/resolver.js.map +1 -1
  23. package/dist/status.d.ts +1 -1
  24. package/dist/status.js +5 -4
  25. package/dist/status.js.map +1 -1
  26. package/dist/ui.html +18 -5
  27. package/dist/ui.js +36 -11
  28. package/dist/ui.js.map +1 -1
  29. package/package.json +13 -4
  30. package/dist/autodetect.test.d.ts +0 -1
  31. package/dist/autodetect.test.js +0 -207
  32. package/dist/autodetect.test.js.map +0 -1
  33. package/dist/cli.test.d.ts +0 -1
  34. package/dist/cli.test.js +0 -310
  35. package/dist/cli.test.js.map +0 -1
  36. package/dist/index.test.d.ts +0 -1
  37. package/dist/index.test.js +0 -59
  38. package/dist/index.test.js.map +0 -1
  39. package/dist/iterate.test.d.ts +0 -1
  40. package/dist/iterate.test.js +0 -20
  41. package/dist/iterate.test.js.map +0 -1
  42. package/dist/loader.test.d.ts +0 -1
  43. package/dist/loader.test.js +0 -65
  44. package/dist/loader.test.js.map +0 -1
  45. package/dist/parser.test.d.ts +0 -1
  46. package/dist/parser.test.js +0 -93
  47. package/dist/parser.test.js.map +0 -1
  48. package/dist/resolver.test.d.ts +0 -1
  49. package/dist/resolver.test.js +0 -184
  50. package/dist/resolver.test.js.map +0 -1
  51. package/dist/sanitize.test.d.ts +0 -1
  52. package/dist/sanitize.test.js +0 -38
  53. package/dist/sanitize.test.js.map +0 -1
  54. package/dist/status.test.d.ts +0 -1
  55. package/dist/status.test.js +0 -33
  56. package/dist/status.test.js.map +0 -1
  57. package/dist/ui-bootstrap.test.d.ts +0 -1
  58. package/dist/ui-bootstrap.test.js +0 -479
  59. package/dist/ui-bootstrap.test.js.map +0 -1
  60. package/dist/ui-builder.test.d.ts +0 -1
  61. package/dist/ui-builder.test.js +0 -272
  62. package/dist/ui-builder.test.js.map +0 -1
  63. package/dist/ui-command-quoting.test.d.ts +0 -1
  64. package/dist/ui-command-quoting.test.js +0 -95
  65. package/dist/ui-command-quoting.test.js.map +0 -1
  66. package/dist/ui-harness.d.ts +0 -20
  67. package/dist/ui-harness.js +0 -40
  68. package/dist/ui-harness.js.map +0 -1
  69. package/dist/ui-harness.test.d.ts +0 -1
  70. package/dist/ui-harness.test.js +0 -30
  71. package/dist/ui-harness.test.js.map +0 -1
  72. package/dist/ui-inline.test.d.ts +0 -1
  73. package/dist/ui-inline.test.js +0 -90
  74. package/dist/ui-inline.test.js.map +0 -1
  75. package/dist/ui-interactions.test.d.ts +0 -1
  76. package/dist/ui-interactions.test.js +0 -489
  77. package/dist/ui-interactions.test.js.map +0 -1
  78. package/dist/ui.test.d.ts +0 -1
  79. package/dist/ui.test.js +0 -283
  80. package/dist/ui.test.js.map +0 -1
@@ -0,0 +1,130 @@
1
+ import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { parseVars, VAR_PATTERN } from './parser.js';
4
+ import { findTemplate, loadTemplates } from './loader.js';
5
+ import { autodetect, AUTODETECT_VARS } from './autodetect.js';
6
+ import { sanitizeForTerminal } from './sanitize.js';
7
+ import { UsageError, assertNoReservedVars, subcommandCollision } from './flags.js';
8
+ const EXAMPLE_REVIEW = `Review {{file}} for {{focus=correctness}} issues.
9
+ Be {{tone=concise}} in your feedback.
10
+ Focus on real bugs, not style nitpicks.
11
+ `;
12
+ const EXAMPLE_EXPLAIN = `Explain what {{file}} does in {{style=simple}} terms.
13
+ Assume the reader is a {{audience=junior developer}}.
14
+ `;
15
+ const EXAMPLE_FIX = `Fix the {{issue}} in {{file}}.
16
+ The expected behavior is: {{expected}}
17
+ The actual behavior is: {{actual}}
18
+ `;
19
+ export function doInit() {
20
+ const dir = join(process.cwd(), '.prompts');
21
+ if (existsSync(dir)) {
22
+ console.log('.prompts/ already exists!');
23
+ return;
24
+ }
25
+ mkdirSync(dir, { recursive: true });
26
+ writeFileSync(join(dir, 'review.md'), EXAMPLE_REVIEW);
27
+ writeFileSync(join(dir, 'explain.md'), EXAMPLE_EXPLAIN);
28
+ writeFileSync(join(dir, 'fix.md'), EXAMPLE_FIX);
29
+ console.log('Created .prompts/ with 3 example templates:');
30
+ console.log(' review.md — Code review with focus area');
31
+ console.log(' explain.md — Explain code simply');
32
+ console.log(' fix.md — Bug fix template');
33
+ console.log('');
34
+ console.log('Try it: promptargs review --file=src/main.ts');
35
+ }
36
+ export function doList() {
37
+ const templates = loadTemplates();
38
+ if (templates.length === 0) {
39
+ console.log('No templates found.');
40
+ console.log('Run "promptargs init" to create example templates.');
41
+ return;
42
+ }
43
+ console.log('Available templates:\n');
44
+ for (const t of templates) {
45
+ const source = t.source === 'user' ? ' (user)' : '';
46
+ console.log(sanitizeForTerminal(` ${t.name}${source}`));
47
+ if (t.error) {
48
+ console.log(sanitizeForTerminal(` vars: (error: could not be read: ${t.error})`));
49
+ console.log('');
50
+ continue;
51
+ }
52
+ const collision = subcommandCollision(t.name);
53
+ if (collision) {
54
+ console.log(sanitizeForTerminal(` ${collision}`));
55
+ console.log('');
56
+ continue;
57
+ }
58
+ try {
59
+ const tplVars = parseVars(t.content);
60
+ assertNoReservedVars(tplVars);
61
+ const varNames = tplVars.map(v => {
62
+ if (v.defaultValue !== undefined)
63
+ return `${v.name}=${v.defaultValue}`;
64
+ return v.name;
65
+ });
66
+ if (varNames.length === 0) {
67
+ console.log(sanitizeForTerminal(` vars: (none)`));
68
+ }
69
+ else {
70
+ console.log(sanitizeForTerminal(` vars: {{${varNames.join('}} {{')}}}`));
71
+ }
72
+ }
73
+ catch (err) {
74
+ console.log(sanitizeForTerminal(` vars: (error: ${err instanceof Error ? err.message : String(err)})`));
75
+ }
76
+ console.log('');
77
+ }
78
+ }
79
+ export function doShow(templateName) {
80
+ if (!templateName) {
81
+ throw new UsageError('Usage: promptargs show <template>');
82
+ }
83
+ const tpl = findTemplate(templateName);
84
+ if (!tpl) {
85
+ throw new UsageError(`Template "${templateName}" not found.`);
86
+ }
87
+ const collision = subcommandCollision(tpl.name);
88
+ if (collision)
89
+ console.error(sanitizeForTerminal(collision));
90
+ // Defaults are merged across repeated blanks, so {{name}} … {{name=dflt}}
91
+ // must colour every occurrence as "has default", not just the one that
92
+ // carries it.
93
+ const tplVars = parseVars(tpl.content);
94
+ assertNoReservedVars(tplVars);
95
+ const hasDefault = new Set(tplVars
96
+ .filter((v) => v.defaultValue !== undefined)
97
+ .map((v) => v.name));
98
+ // Sanitize first: templates are repo-controlled and must not be able to
99
+ // inject their own terminal escapes; only our highlighting below may.
100
+ const highlighted = sanitizeForTerminal(tpl.content).replace(VAR_PATTERN, (raw, varName) => {
101
+ // Colour the tag exactly as written so padded tags ({{ name }}) are
102
+ // shown as the author typed them.
103
+ if (hasDefault.has(varName)) {
104
+ return `\x1b[33m${raw}\x1b[0m`;
105
+ }
106
+ return `\x1b[31m${raw}\x1b[0m`;
107
+ });
108
+ console.log(sanitizeForTerminal(`Template: ${tpl.name} (${tpl.path})`) + '\n');
109
+ console.log(highlighted);
110
+ console.log('\n\x1b[31mred\x1b[0m = required \x1b[33myellow\x1b[0m = has default');
111
+ }
112
+ const DIFF_PREVIEW_MAX_CHARS = 60;
113
+ export function printEnv() {
114
+ console.log('Auto-detected variables:\n');
115
+ for (const name of AUTODETECT_VARS) {
116
+ const value = autodetect(name);
117
+ if (value !== undefined) {
118
+ let display = value;
119
+ if (name === 'diff' && display.length > DIFF_PREVIEW_MAX_CHARS) {
120
+ display = display.slice(0, DIFF_PREVIEW_MAX_CHARS) + '...';
121
+ }
122
+ console.log(` \x1b[32m{{${name}}}\x1b[0m = ${sanitizeForTerminal(display)}`);
123
+ }
124
+ else {
125
+ console.log(` \x1b[90m{{${name}}}\x1b[0m = (not detected)`);
126
+ }
127
+ }
128
+ console.log('');
129
+ }
130
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export declare class UsageError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare const SWITCHES: Set<string>;
5
+ declare const SUBCOMMAND_NAMES: readonly ["help", "env", "ui", "init", "list", "show"];
6
+ export type Subcommand = (typeof SUBCOMMAND_NAMES)[number];
7
+ export declare const SUBCOMMANDS: Set<string>;
8
+ export declare function subcommandCollision(name: string): string | undefined;
9
+ export declare function assertNoReservedVars(vars: {
10
+ name: string;
11
+ }[]): void;
12
+ export declare function parseFlags(args: string[]): Record<string, string>;
13
+ export declare function parsePort(flags: Record<string, string>): number | undefined;
14
+ export {};
package/dist/flags.js ADDED
@@ -0,0 +1,57 @@
1
+ export class UsageError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'UsageError';
5
+ }
6
+ }
7
+ export const SWITCHES = new Set(['no-interactive', 'cross', 'json', 'status']);
8
+ const SUBCOMMAND_NAMES = ['help', 'env', 'ui', 'init', 'list', 'show'];
9
+ export const SUBCOMMANDS = new Set(SUBCOMMAND_NAMES);
10
+ export function subcommandCollision(name) {
11
+ return SUBCOMMANDS.has(name)
12
+ ? `(unreachable: name collides with the "${name}" subcommand; rename the file)`
13
+ : undefined;
14
+ }
15
+ export function assertNoReservedVars(vars) {
16
+ for (const v of vars) {
17
+ if (SWITCHES.has(v.name)) {
18
+ throw new UsageError(`Unsupported variable name "${v.name}": it is reserved for the --${v.name} switch; rename the blank`);
19
+ }
20
+ }
21
+ }
22
+ export function parseFlags(args) {
23
+ const flags = {};
24
+ for (let i = 0; i < args.length; i++) {
25
+ const arg = args[i];
26
+ if (!arg.startsWith('--')) {
27
+ throw new UsageError(`Unexpected argument: ${arg}`);
28
+ }
29
+ const eq = arg.indexOf('=');
30
+ if (eq > 0) {
31
+ flags[arg.slice(2, eq)] = arg.slice(eq + 1);
32
+ continue;
33
+ }
34
+ const name = arg.slice(2);
35
+ if (SWITCHES.has(name)) {
36
+ flags[name] = 'true';
37
+ }
38
+ else if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
39
+ flags[name] = args[++i];
40
+ }
41
+ else {
42
+ throw new UsageError(`Invalid --${name}: --${name} requires a value: --${name}=<value>`);
43
+ }
44
+ }
45
+ return flags;
46
+ }
47
+ export function parsePort(flags) {
48
+ if (!('port' in flags))
49
+ return undefined;
50
+ // Number('') is 0, so an empty --port= must be rejected before the range check.
51
+ const port = Number(flags['port']);
52
+ if (flags['port'] === '' || !Number.isInteger(port) || port < 0 || port > 65535) {
53
+ throw new UsageError('Invalid --port: expected an integer from 0 to 65535. Usage: promptargs ui --port=3700');
54
+ }
55
+ return port;
56
+ }
57
+ //# sourceMappingURL=flags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flags.js","sourceRoot":"","sources":["../src/flags.ts"],"names":[],"mappings":"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"}
package/dist/loader.d.ts CHANGED
@@ -6,6 +6,8 @@ export interface PromptTemplate {
6
6
  content: string;
7
7
  path: string;
8
8
  source: 'project' | 'user';
9
+ /** Set when the file could not be read; content is then empty. */
10
+ error?: string;
9
11
  }
10
12
  export declare function loadTemplates(cwd?: string): PromptTemplate[];
11
13
  export declare function findTemplate(name: string, cwd?: string): PromptTemplate | undefined;
package/dist/loader.js CHANGED
@@ -1,22 +1,71 @@
1
1
  /**
2
2
  * Find and load prompt templates from .prompts/ directories.
3
3
  */
4
- import { readFileSync, readdirSync, existsSync } from 'node:fs';
5
- import { join, basename, extname } from 'node:path';
4
+ import { readFileSync, readdirSync, existsSync, statSync, realpathSync } from 'node:fs';
5
+ import { join, basename, extname, sep } from 'node:path';
6
6
  import { homedir } from 'node:os';
7
+ import { UsageError } from './flags.js';
8
+ import { diag, classifyError } from './log.js';
9
+ import { sanitizeForTerminal } from './sanitize.js';
7
10
  const PROJECT_DIR = '.prompts';
8
11
  const USER_DIR = join(homedir(), '.prompts');
9
12
  function loadFromDir(dir, source) {
10
13
  if (!existsSync(dir))
11
14
  return [];
12
- return readdirSync(dir)
13
- .filter((f) => f.endsWith('.md'))
14
- .map((f) => ({
15
- name: basename(f, extname(f)),
16
- content: readFileSync(join(dir, f), 'utf8'),
17
- path: join(dir, f),
18
- source,
19
- }));
15
+ let entries;
16
+ try {
17
+ entries = readdirSync(dir, { withFileTypes: true });
18
+ }
19
+ catch (err) {
20
+ const msg = err instanceof Error ? err.message : String(err);
21
+ diag('loader_dir_unreadable', { error_class: classifyError(err) }, sanitizeForTerminal(`promptargs: cannot read ${dir}: ${msg}`));
22
+ return [];
23
+ }
24
+ // Symlinked templates must resolve to a file inside this .prompts dir; a
25
+ // cloned repo must not be able to make `promptargs x` read ~/.ssh/id_rsa.
26
+ let realDir;
27
+ try {
28
+ realDir = realpathSync(dir);
29
+ }
30
+ catch (err) {
31
+ const msg = err instanceof Error ? err.message : String(err);
32
+ diag('loader_dir_unreadable', { error_class: classifyError(err) }, sanitizeForTerminal(`promptargs: cannot resolve ${dir}: ${msg}`));
33
+ return [];
34
+ }
35
+ const realDirPrefix = realDir.endsWith(sep) ? realDir : realDir + sep;
36
+ const templates = [];
37
+ for (const entry of entries) {
38
+ const f = entry.name;
39
+ if (!f.endsWith('.md'))
40
+ continue;
41
+ const path = join(dir, f);
42
+ const isLink = entry.isSymbolicLink();
43
+ try {
44
+ const isFile = isLink ? statSync(path).isFile() : entry.isFile();
45
+ if (!isFile)
46
+ continue;
47
+ }
48
+ catch {
49
+ continue; // dangling symlink
50
+ }
51
+ const template = { name: basename(f, extname(f)), content: '', path, source };
52
+ try {
53
+ if (isLink) {
54
+ const target = realpathSync(path);
55
+ if (!target.startsWith(realDirPrefix)) {
56
+ template.error = `symlink target ${target} is outside ${dir}; templates must stay inside the .prompts directory`;
57
+ templates.push(template);
58
+ continue;
59
+ }
60
+ }
61
+ template.content = readFileSync(path, 'utf8');
62
+ }
63
+ catch (err) {
64
+ template.error = err instanceof Error ? err.message : String(err);
65
+ }
66
+ templates.push(template);
67
+ }
68
+ return templates;
20
69
  }
21
70
  export function loadTemplates(cwd) {
22
71
  const projectDir = join(cwd || process.cwd(), PROJECT_DIR);
@@ -31,6 +80,10 @@ export function loadTemplates(cwd) {
31
80
  return Array.from(byName.values());
32
81
  }
33
82
  export function findTemplate(name, cwd) {
34
- return loadTemplates(cwd).find(t => t.name === name);
83
+ const tpl = loadTemplates(cwd).find(t => t.name === name);
84
+ if (tpl?.error) {
85
+ throw new UsageError(`Template "${tpl.name}" at ${tpl.path} could not be read: ${tpl.error}`);
86
+ }
87
+ return tpl;
35
88
  }
36
89
  //# sourceMappingURL=loader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,MAAM,WAAW,GAAG,UAAU,CAAC;AAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AAS7C,SAAS,WAAW,CAAC,GAAW,EAAE,MAA0B;IAC1D,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAEhC,OAAO,WAAW,CAAC,GAAG,CAAC;SACpB,MAAM,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;SACxC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC;QACnB,IAAI,EAAE,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAC7B,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAC3C,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;QAClB,MAAM;KACP,CAAC,CAAC,CAAC;AACR,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,WAAW,CAAC,CAAC;IAC3D,MAAM,gBAAgB,GAAG,WAAW,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAC5D,MAAM,aAAa,GAAG,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAEpD,2DAA2D;IAC3D,MAAM,MAAM,GAAG,IAAI,GAAG,EAA0B,CAAC;IACjD,KAAK,MAAM,CAAC,IAAI,aAAa;QAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACrD,KAAK,MAAM,CAAC,IAAI,gBAAgB;QAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAExD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,GAAY;IACrD,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AACvD,CAAC"}
1
+ {"version":3,"file":"loader.js","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"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"}
package/dist/log.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Opt-in structured diagnostics. With PROMPTARGS_LOG=json, diagnostics are
3
+ * written to stderr as one JSON object per line with a fixed, bounded field
4
+ * set. Template contents, argument values, paths and URLs are never logged.
5
+ */
6
+ export type LogEvent = 'ui_host_rejected' | 'ui_host_rejected_total' | 'ui_asset_error' | 'ui_request' | 'ui_start_error' | 'ui_browser_open_failed' | 'cli_iteration' | 'cli_error' | 'loader_dir_unreadable';
7
+ export type ErrorClass = 'usage' | 'not_found' | 'io' | 'port_in_use' | 'unknown';
8
+ export interface LogFields {
9
+ status?: number | string;
10
+ duration_ms?: number;
11
+ error_class?: ErrorClass;
12
+ count?: number;
13
+ current?: number;
14
+ total?: number;
15
+ }
16
+ export declare function jsonLogEnabled(env?: NodeJS.ProcessEnv): boolean;
17
+ export declare function classifyError(err: unknown): ErrorClass;
18
+ export declare function diag(event: LogEvent, fields: LogFields, legacy?: string): void;
19
+ export declare function logRequest(status: number, startedAt: bigint): void;
package/dist/log.js ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Opt-in structured diagnostics. With PROMPTARGS_LOG=json, diagnostics are
3
+ * written to stderr as one JSON object per line with a fixed, bounded field
4
+ * set. Template contents, argument values, paths and URLs are never logged.
5
+ */
6
+ export function jsonLogEnabled(env = process.env) {
7
+ return env.PROMPTARGS_LOG === 'json';
8
+ }
9
+ export function classifyError(err) {
10
+ const code = err?.code;
11
+ if (code === 'ENOENT')
12
+ return 'not_found';
13
+ if (code === 'EADDRINUSE')
14
+ return 'port_in_use';
15
+ if (typeof code === 'string' && code.startsWith('E'))
16
+ return 'io';
17
+ if (err?.name === 'UsageError')
18
+ return 'usage';
19
+ return 'unknown';
20
+ }
21
+ // Writes a JSON line when enabled; otherwise prints the legacy text fallback
22
+ // (when given) so default output stays byte-for-byte unchanged.
23
+ export function diag(event, fields, legacy) {
24
+ if (!jsonLogEnabled()) {
25
+ if (legacy !== undefined)
26
+ console.error(legacy);
27
+ return;
28
+ }
29
+ const line = { event };
30
+ for (const key of ['status', 'duration_ms', 'error_class', 'count', 'current', 'total']) {
31
+ if (fields[key] !== undefined)
32
+ line[key] = fields[key];
33
+ }
34
+ console.error(JSON.stringify(line));
35
+ }
36
+ // Request logging has no legacy output, so it is silent unless enabled.
37
+ export function logRequest(status, startedAt) {
38
+ const duration_ms = Number((process.hrtime.bigint() - startedAt) / 1000n) / 1000;
39
+ diag('ui_request', { status, duration_ms });
40
+ }
41
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH,MAAM,UAAU,cAAc,CAAC,MAAyB,OAAO,CAAC,GAAG;IACjE,OAAO,GAAG,CAAC,cAAc,KAAK,MAAM,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,MAAM,IAAI,GAAI,GAAyC,EAAE,IAAI,CAAC;IAC9D,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,WAAW,CAAC;IAC1C,IAAI,IAAI,KAAK,YAAY;QAAE,OAAO,aAAa,CAAC;IAChD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAClE,IAAK,GAAyB,EAAE,IAAI,KAAK,YAAY;QAAE,OAAO,OAAO,CAAC;IACtE,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,6EAA6E;AAC7E,gEAAgE;AAChE,MAAM,UAAU,IAAI,CAAC,KAAe,EAAE,MAAiB,EAAE,MAAe;IACtE,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;QACtB,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAChD,OAAO;IACT,CAAC;IACD,MAAM,IAAI,GAA4B,EAAE,KAAK,EAAE,CAAC;IAChD,KAAK,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,CAAU,EAAE,CAAC;QACjG,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,SAAS;YAAE,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;AACtC,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,SAAiB;IAC1D,MAAM,WAAW,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC;IACjF,IAAI,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;AAC9C,CAAC"}
package/dist/parser.js CHANGED
@@ -3,6 +3,13 @@
3
3
  * Uses Mustache for expansion, with a promptargs extension for defaults.
4
4
  */
5
5
  import Mustache from 'mustache';
6
+ // Kept local (not imported from flags.ts) because ui.html loads parser.js in the browser,
7
+ // where only parser.js/iterate.js/mustache.mjs are served (see STATIC_JS_ROUTES in ui.ts).
8
+ function usageError(message) {
9
+ const e = new Error(message);
10
+ e.name = 'UsageError';
11
+ return e;
12
+ }
6
13
  // Exported so any code that needs to recognize the same {{var}} / {{var=default}}
7
14
  // grammar (e.g. CLI syntax highlighting) reuses this single definition instead
8
15
  // of re-declaring the pattern and risking drift.
@@ -13,15 +20,34 @@ import Mustache from 'mustache';
13
20
  // as an empty string — the blank silently vanished instead of being asked for,
14
21
  // filled from --flags, highlighted by `show`, or preserved as {{name}}.
15
22
  // Trailing padding after a default is not part of the default.
16
- export const VAR_PATTERN = /\{\{\s*(\w+)(?:=([^}]*?))?\s*\}\}/g;
23
+ export const VAR_PATTERN = /\{\{\s*([\w-]+)(?:=([^}]*?))?\s*\}\}/g;
24
+ // Dotted names ({{repo.owner}}) are Mustache context paths, which promptargs
25
+ // never fills; they would silently render empty, so they are rejected.
26
+ // The lookbehind skips triple-mustache tags; sigil tags ({{#x}}, {{!c}}) do
27
+ // not start with a name character and never match.
28
+ const DOTTED_PATTERN = /(?<!\{)\{\{\s*([\w-]+\.[\w.-]*)\s*(?:=[^}]*)?\}\}/;
17
29
  export function parseVars(template) {
18
- const seen = new Set();
19
30
  const vars = [];
31
+ // The first offending tag aborts; the message is built from the name only
32
+ // (restricted to [\w.-]) so raw template bytes never reach the terminal.
33
+ const dotted = DOTTED_PATTERN.exec(template);
34
+ if (dotted) {
35
+ const name = dotted[1];
36
+ throw usageError(`Unsupported variable name "${name}": dotted names are not supported; use {{${name.replace(/\./g, '_')}}}`);
37
+ }
20
38
  for (const match of template.matchAll(VAR_PATTERN)) {
21
39
  const name = match[1];
22
- if (seen.has(name))
40
+ const existing = vars.find((v) => v.name === name);
41
+ if (existing) {
42
+ const dflt = match[2];
43
+ if (dflt === undefined)
44
+ continue;
45
+ // First default wins; later occurrences may restate it (e.g. a code
46
+ // fence using a different casing) without being an error.
47
+ if (existing.defaultValue === undefined)
48
+ existing.defaultValue = dflt;
23
49
  continue;
24
- seen.add(name);
50
+ }
25
51
  vars.push({
26
52
  name,
27
53
  defaultValue: match[2],
@@ -32,13 +58,13 @@ export function parseVars(template) {
32
58
  }
33
59
  export function expand(template, values) {
34
60
  const vars = parseVars(template);
35
- const view = { ...values };
61
+ const view = Object.assign(Object.create(null), values);
36
62
  for (const v of vars) {
37
- if (!(v.name in view) && v.defaultValue !== undefined) {
63
+ if (!Object.hasOwn(view, v.name) && v.defaultValue !== undefined) {
38
64
  view[v.name] = v.defaultValue;
39
65
  }
40
66
  // Preserve unfilled vars as {{name}} in output
41
- if (!(v.name in view)) {
67
+ if (!Object.hasOwn(view, v.name)) {
42
68
  view[v.name] = `{{${v.name}}}`;
43
69
  }
44
70
  }
@@ -1 +1 @@
1
- {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,QAAQ,MAAM,UAAU,CAAC;AAQhC,kFAAkF;AAClF,+EAA+E;AAC/E,iDAAiD;AACjD,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,8EAA8E;AAC9E,+EAA+E;AAC/E,wEAAwE;AACxE,+DAA+D;AAC/D,MAAM,CAAC,MAAM,WAAW,GAAG,oCAAoC,CAAC;AAEhE,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,IAAI,GAAkB,EAAE,CAAC;IAE/B,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC7B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,CAAC;YACR,IAAI;YACJ,YAAY,EAAE,KAAK,CAAC,CAAC,CAAC;YACtB,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;SACd,CAAC,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,QAAgB,EAAE,MAA8B;IACrE,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IACjC,MAAM,IAAI,GAA2B,EAAE,GAAG,MAAM,EAAE,CAAC;IAEnD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YACtD,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC;QAChC,CAAC;QACD,+CAA+C;QAC/C,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;YACtB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC;QACjC,CAAC;IACH,CAAC;IAED,+DAA+D;IAC/D,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,IAAY,EAAE,EAAE,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;IAE1F,wEAAwE;IACxE,wEAAwE;IACxE,yEAAyE;IACzE,4EAA4E;IAC5E,OAAO,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AAC1F,CAAC"}
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"}
@@ -6,5 +6,7 @@ import type { TemplateVar } from './parser.js';
6
6
  export interface ResolvedValues {
7
7
  values: Record<string, string>;
8
8
  iterations: Record<string, string>[];
9
+ /** Names whose value was filled from the template default. */
10
+ defaulted: Set<string>;
9
11
  }
10
12
  export declare function resolve(vars: TemplateVar[], flags: Record<string, string>, interactive: boolean, cross?: boolean): Promise<ResolvedValues>;