@hivecommons/promptargs 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -20
- package/dist/autodetect.js +39 -12
- package/dist/autodetect.js.map +1 -1
- package/dist/cli.js +78 -147
- package/dist/cli.js.map +1 -1
- package/dist/commands.d.ts +4 -0
- package/dist/commands.js +148 -0
- package/dist/commands.js.map +1 -0
- package/dist/flags.d.ts +20 -0
- package/dist/flags.js +101 -0
- package/dist/flags.js.map +1 -0
- package/dist/loader.d.ts +2 -0
- package/dist/loader.js +84 -11
- package/dist/loader.js.map +1 -1
- package/dist/log.d.ts +19 -0
- package/dist/log.js +41 -0
- package/dist/log.js.map +1 -0
- package/dist/parser.js +39 -9
- package/dist/parser.js.map +1 -1
- package/dist/resolver.d.ts +3 -1
- package/dist/resolver.js +105 -34
- package/dist/resolver.js.map +1 -1
- package/dist/status.d.ts +1 -1
- package/dist/status.js +5 -4
- package/dist/status.js.map +1 -1
- package/dist/ui.d.ts +3 -0
- package/dist/ui.html +21 -6
- package/dist/ui.js +89 -12
- package/dist/ui.js.map +1 -1
- package/package.json +13 -4
- package/dist/autodetect.test.d.ts +0 -1
- package/dist/autodetect.test.js +0 -207
- package/dist/autodetect.test.js.map +0 -1
- package/dist/cli.test.d.ts +0 -1
- package/dist/cli.test.js +0 -310
- package/dist/cli.test.js.map +0 -1
- package/dist/index.test.d.ts +0 -1
- package/dist/index.test.js +0 -59
- package/dist/index.test.js.map +0 -1
- package/dist/iterate.test.d.ts +0 -1
- package/dist/iterate.test.js +0 -20
- package/dist/iterate.test.js.map +0 -1
- package/dist/loader.test.d.ts +0 -1
- package/dist/loader.test.js +0 -65
- package/dist/loader.test.js.map +0 -1
- package/dist/parser.test.d.ts +0 -1
- package/dist/parser.test.js +0 -93
- package/dist/parser.test.js.map +0 -1
- package/dist/resolver.test.d.ts +0 -1
- package/dist/resolver.test.js +0 -184
- package/dist/resolver.test.js.map +0 -1
- package/dist/sanitize.test.d.ts +0 -1
- package/dist/sanitize.test.js +0 -38
- package/dist/sanitize.test.js.map +0 -1
- package/dist/status.test.d.ts +0 -1
- package/dist/status.test.js +0 -33
- package/dist/status.test.js.map +0 -1
- package/dist/ui-bootstrap.test.d.ts +0 -1
- package/dist/ui-bootstrap.test.js +0 -479
- package/dist/ui-bootstrap.test.js.map +0 -1
- package/dist/ui-builder.test.d.ts +0 -1
- package/dist/ui-builder.test.js +0 -272
- package/dist/ui-builder.test.js.map +0 -1
- package/dist/ui-command-quoting.test.d.ts +0 -1
- package/dist/ui-command-quoting.test.js +0 -95
- package/dist/ui-command-quoting.test.js.map +0 -1
- package/dist/ui-harness.d.ts +0 -20
- package/dist/ui-harness.js +0 -40
- package/dist/ui-harness.js.map +0 -1
- package/dist/ui-harness.test.d.ts +0 -1
- package/dist/ui-harness.test.js +0 -30
- package/dist/ui-harness.test.js.map +0 -1
- package/dist/ui-inline.test.d.ts +0 -1
- package/dist/ui-inline.test.js +0 -90
- package/dist/ui-inline.test.js.map +0 -1
- package/dist/ui-interactions.test.d.ts +0 -1
- package/dist/ui-interactions.test.js +0 -489
- package/dist/ui-interactions.test.js.map +0 -1
- package/dist/ui.test.d.ts +0 -1
- package/dist/ui.test.js +0 -283
- package/dist/ui.test.js.map +0 -1
package/dist/ui.html
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<title>promptargs builder</title>
|
|
2
2
|
<meta charset="utf-8">
|
|
3
3
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
4
|
+
<!-- The page URL carries the per-run access token; never send it as a Referer. -->
|
|
5
|
+
<meta name="referrer" content="no-referrer">
|
|
4
6
|
<style>
|
|
5
7
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
6
8
|
|
|
@@ -749,7 +751,12 @@ import { cartesian as sharedCartesian, zip as sharedZip } from './iterate.js';
|
|
|
749
751
|
|
|
750
752
|
function syncVarsFromTemplate() {
|
|
751
753
|
const text = tmpl.value;
|
|
752
|
-
|
|
754
|
+
let parsed;
|
|
755
|
+
try {
|
|
756
|
+
parsed = parseVars(text);
|
|
757
|
+
} catch {
|
|
758
|
+
return; // update() reports the parse error in the preview
|
|
759
|
+
}
|
|
753
760
|
const existing = getRowData();
|
|
754
761
|
const existingNames = new Set(existing.map(r => r.name));
|
|
755
762
|
|
|
@@ -828,7 +835,7 @@ import { cartesian as sharedCartesian, zip as sharedZip } from './iterate.js';
|
|
|
828
835
|
}
|
|
829
836
|
|
|
830
837
|
const arrayVars = [];
|
|
831
|
-
const scalarValues =
|
|
838
|
+
const scalarValues = Object.create(null);
|
|
832
839
|
|
|
833
840
|
for (const row of rows) {
|
|
834
841
|
const vals = splitValues(row.values, row.source);
|
|
@@ -849,10 +856,18 @@ import { cartesian as sharedCartesian, zip as sharedZip } from './iterate.js';
|
|
|
849
856
|
}
|
|
850
857
|
|
|
851
858
|
const results = [];
|
|
852
|
-
|
|
853
|
-
const
|
|
854
|
-
|
|
855
|
-
|
|
859
|
+
try {
|
|
860
|
+
for (const combo of combos) {
|
|
861
|
+
const vals = Object.assign(Object.create(null), scalarValues);
|
|
862
|
+
arrayVars.forEach((v, i) => { vals[v.name] = combo[i]; });
|
|
863
|
+
results.push({ values: { ...vals }, expanded: sharedExpand(text, vals) });
|
|
864
|
+
}
|
|
865
|
+
} catch (err) {
|
|
866
|
+
previewBody.innerHTML = `<span class="preview-empty">${escHtml(err instanceof Error ? err.message : String(err))}</span>`;
|
|
867
|
+
previewCount.textContent = 'error';
|
|
868
|
+
previewCount.className = 'preview-count empty';
|
|
869
|
+
cliText.innerHTML = '<span class="dollar">$</span>promptargs';
|
|
870
|
+
return;
|
|
856
871
|
}
|
|
857
872
|
|
|
858
873
|
const count = results.length;
|
package/dist/ui.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
|
+
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
3
|
import { readFileSync } from 'node:fs';
|
|
3
4
|
import { join, dirname } from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { autodetect, isSensitiveEnvName, isSensitiveEnvValue, AUTODETECT_VARS } from './autodetect.js';
|
|
7
|
+
import { diag, classifyError, jsonLogEnabled, logRequest } from './log.js';
|
|
6
8
|
const DEFAULT_PORT = 3700;
|
|
7
9
|
const OPEN_DELAY_MS = 500;
|
|
8
10
|
const DIFF_PREVIEW_MAX_CHARS = 60;
|
|
@@ -50,7 +52,9 @@ export function buildUIHtml(rawHtml, envVars) {
|
|
|
50
52
|
// Escape "<" so env values cannot break out of the script tag (e.g. "</script>").
|
|
51
53
|
const envJson = JSON.stringify(envVars).replace(/</g, '\\u003c');
|
|
52
54
|
const envScript = `<script>window.__PROMPTARGS_ENV__ = ${envJson};</script>`;
|
|
53
|
-
|
|
55
|
+
// Function replacer: a replacement *string* would expand `$&`, "$`" and "$'"
|
|
56
|
+
// found in env values into surrounding raw HTML, undoing the escaping above.
|
|
57
|
+
return rawHtml.replace(/<script\b/, (match) => `${envScript}\n${match}`);
|
|
54
58
|
}
|
|
55
59
|
// Browser-facing modules the builder UI imports directly instead of
|
|
56
60
|
// hand-reimplementing their logic (see buildUIHtml / ui.html). Serving the
|
|
@@ -60,6 +64,34 @@ const STATIC_JS_ROUTES = {
|
|
|
60
64
|
'/iterate.js': 'iterate.js',
|
|
61
65
|
'/mustache.mjs': 'mustache.mjs',
|
|
62
66
|
};
|
|
67
|
+
// Query parameter carrying the per-run access token (see startUI).
|
|
68
|
+
export const UI_TOKEN_PARAM = 't';
|
|
69
|
+
const UI_TOKEN_BYTES = 16;
|
|
70
|
+
// The page embeds environment data and is reached through a tokened URL, so
|
|
71
|
+
// the browser must not keep a copy on disk after the server exits, and must
|
|
72
|
+
// not send that URL as the Referer when the page's outbound links are followed.
|
|
73
|
+
export const PAGE_HEADERS = Object.freeze({
|
|
74
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
75
|
+
'Cache-Control': 'no-store',
|
|
76
|
+
'Referrer-Policy': 'no-referrer',
|
|
77
|
+
'X-Content-Type-Options': 'nosniff',
|
|
78
|
+
});
|
|
79
|
+
export function requestCarriesToken(url, token) {
|
|
80
|
+
if (!url)
|
|
81
|
+
return false;
|
|
82
|
+
let supplied;
|
|
83
|
+
try {
|
|
84
|
+
supplied = new URL(url, 'http://localhost').searchParams.get(UI_TOKEN_PARAM);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
if (supplied === null)
|
|
90
|
+
return false;
|
|
91
|
+
const a = Buffer.from(supplied, 'utf8');
|
|
92
|
+
const b = Buffer.from(token, 'utf8');
|
|
93
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
94
|
+
}
|
|
63
95
|
export function startUI(port = DEFAULT_PORT, options = {}) {
|
|
64
96
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
65
97
|
const htmlPath = join(__dirname, 'ui.html');
|
|
@@ -68,18 +100,30 @@ export function startUI(port = DEFAULT_PORT, options = {}) {
|
|
|
68
100
|
rawHtml = readFileSync(htmlPath, 'utf-8');
|
|
69
101
|
}
|
|
70
102
|
catch {
|
|
71
|
-
|
|
103
|
+
diag('ui_start_error', { error_class: 'not_found' }, 'UI file not found. Reinstall @hivecommons/promptargs.');
|
|
72
104
|
process.exit(1);
|
|
73
105
|
}
|
|
74
106
|
const html = buildUIHtml(rawHtml, collectEnvVars());
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
107
|
+
// Filled once the server is bound, because port 0 means the OS picks one.
|
|
108
|
+
const ALLOWED_HOSTS = new Set();
|
|
109
|
+
// Loopback is shared by every account on the machine, so the Host check
|
|
110
|
+
// alone still lets another local user read the embedded environment. The
|
|
111
|
+
// page is only served to a client presenting this per-run random token,
|
|
112
|
+
// which reaches the user's browser solely through the printed/opened URL.
|
|
113
|
+
const token = randomBytes(UI_TOKEN_BYTES).toString('hex');
|
|
114
|
+
let rejectedHosts = 0;
|
|
115
|
+
let rejectedTokens = 0;
|
|
80
116
|
const server = createServer((req, res) => {
|
|
117
|
+
if (jsonLogEnabled()) {
|
|
118
|
+
const startedAt = process.hrtime.bigint();
|
|
119
|
+
res.on('finish', () => logRequest(res.statusCode, startedAt));
|
|
120
|
+
}
|
|
81
121
|
// Reject non-local Host headers to block DNS-rebinding reads of env data.
|
|
82
122
|
if (!req.headers.host || !ALLOWED_HOSTS.has(req.headers.host)) {
|
|
123
|
+
// Never log the Host header or URL: both are attacker-controlled.
|
|
124
|
+
if (rejectedHosts++ === 0) {
|
|
125
|
+
diag('ui_host_rejected', { status: 403 }, 'promptargs: rejected request with unexpected Host header (further rejections suppressed)');
|
|
126
|
+
}
|
|
83
127
|
res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
84
128
|
res.end('Forbidden');
|
|
85
129
|
return;
|
|
@@ -91,19 +135,34 @@ export function startUI(port = DEFAULT_PORT, options = {}) {
|
|
|
91
135
|
res.writeHead(200, { 'Content-Type': 'text/javascript; charset=utf-8' });
|
|
92
136
|
res.end(js);
|
|
93
137
|
}
|
|
94
|
-
catch {
|
|
138
|
+
catch (err) {
|
|
139
|
+
diag('ui_asset_error', { status: 404, error_class: classifyError(err) }, `promptargs: failed to load static asset ${req.url} (${err.code ?? 'unknown'})`);
|
|
95
140
|
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
96
141
|
res.end('Not found');
|
|
97
142
|
}
|
|
98
143
|
return;
|
|
99
144
|
}
|
|
100
|
-
|
|
145
|
+
// Only the page embeds environment data; the module routes above are
|
|
146
|
+
// public package code and stay reachable for the page's relative imports.
|
|
147
|
+
if (!requestCarriesToken(req.url, token)) {
|
|
148
|
+
if (rejectedTokens++ === 0) {
|
|
149
|
+
diag('ui_token_rejected', { status: 403 }, 'promptargs: rejected request without the access token printed at startup (further rejections suppressed)');
|
|
150
|
+
}
|
|
151
|
+
res.writeHead(403, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
152
|
+
res.end('Forbidden');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
res.writeHead(200, PAGE_HEADERS);
|
|
101
156
|
res.end(html);
|
|
102
157
|
});
|
|
103
158
|
// Bind loopback only: the page embeds environment variables and must never
|
|
104
159
|
// be reachable from other machines on the network.
|
|
105
160
|
server.listen(port, '127.0.0.1', () => {
|
|
106
|
-
const
|
|
161
|
+
const boundPort = server.address().port;
|
|
162
|
+
ALLOWED_HOSTS.add(`localhost:${boundPort}`);
|
|
163
|
+
ALLOWED_HOSTS.add(`127.0.0.1:${boundPort}`);
|
|
164
|
+
ALLOWED_HOSTS.add(`[::1]:${boundPort}`);
|
|
165
|
+
const url = `http://localhost:${boundPort}/?${UI_TOKEN_PARAM}=${token}`;
|
|
107
166
|
console.log(`promptargs builder running at ${url}`);
|
|
108
167
|
console.log('Press Ctrl+C to stop.\n');
|
|
109
168
|
if (options.openBrowser ?? true) {
|
|
@@ -111,14 +170,32 @@ export function startUI(port = DEFAULT_PORT, options = {}) {
|
|
|
111
170
|
import('node:child_process').then(({ exec }) => {
|
|
112
171
|
const cmd = process.platform === 'darwin' ? 'open' :
|
|
113
172
|
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
114
|
-
|
|
173
|
+
// The URL is program-generated (digits and hex only) but carries a
|
|
174
|
+
// "?", so quote it for the shell; `start` reads its first quoted
|
|
175
|
+
// argument as a window title, hence the empty one on Windows.
|
|
176
|
+
const command = cmd === 'start' ? `start "" "${url}"` : `${cmd} "${url}"`;
|
|
177
|
+
exec(command, err => {
|
|
178
|
+
if (!err)
|
|
179
|
+
return;
|
|
180
|
+
// Through a shell, a missing opener surfaces as exit status 127 rather than ENOENT.
|
|
181
|
+
const why = err.code === 127 ? 'not found' : String(err.code ?? err.message);
|
|
182
|
+
diag('ui_browser_open_failed', { error_class: classifyError(err) }, `promptargs: could not open a browser (${cmd}: ${why}); open ${url} manually`);
|
|
183
|
+
});
|
|
115
184
|
});
|
|
116
185
|
}, OPEN_DELAY_MS);
|
|
117
186
|
}
|
|
118
187
|
});
|
|
188
|
+
server.on('close', () => {
|
|
189
|
+
if (rejectedHosts > 1) {
|
|
190
|
+
diag('ui_host_rejected_total', { count: rejectedHosts }, `promptargs: rejected ${rejectedHosts} requests with unexpected Host headers in total`);
|
|
191
|
+
}
|
|
192
|
+
if (rejectedTokens > 1) {
|
|
193
|
+
diag('ui_token_rejected_total', { count: rejectedTokens }, `promptargs: rejected ${rejectedTokens} requests without the access token in total`);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
119
196
|
server.on('error', (err) => {
|
|
120
197
|
if (err.code === 'EADDRINUSE') {
|
|
121
|
-
|
|
198
|
+
diag('ui_start_error', { error_class: 'port_in_use' }, `Port ${port} is in use. Try: promptargs ui --port=${port + 1}`);
|
|
122
199
|
process.exit(1);
|
|
123
200
|
}
|
|
124
201
|
throw err;
|
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;
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hivecommons/promptargs",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Template arguments for AI prompts — variable substitution for Claude Code, Copilot, and more",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Template arguments for AI prompts — variable substitution for Claude Code, Copilot, Goose, Bob, and more",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -21,7 +21,12 @@
|
|
|
21
21
|
"claude",
|
|
22
22
|
"copilot",
|
|
23
23
|
"args",
|
|
24
|
-
"cli"
|
|
24
|
+
"cli",
|
|
25
|
+
"claude-code",
|
|
26
|
+
"goose",
|
|
27
|
+
"bob",
|
|
28
|
+
"agent-skills",
|
|
29
|
+
"prompt-templates"
|
|
25
30
|
],
|
|
26
31
|
"author": "hivecommons",
|
|
27
32
|
"license": "Apache-2.0",
|
|
@@ -34,10 +39,14 @@
|
|
|
34
39
|
},
|
|
35
40
|
"homepage": "https://github.com/hivecommons/promptargs#readme",
|
|
36
41
|
"engines": {
|
|
37
|
-
"node": ">=
|
|
42
|
+
"node": ">=22"
|
|
38
43
|
},
|
|
39
44
|
"files": [
|
|
40
45
|
"dist",
|
|
46
|
+
"!dist/**/*.test.js",
|
|
47
|
+
"!dist/**/*.test.js.map",
|
|
48
|
+
"!dist/**/*.test.d.ts",
|
|
49
|
+
"!dist/ui-harness.*",
|
|
41
50
|
"README.md",
|
|
42
51
|
"LICENSE"
|
|
43
52
|
],
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/autodetect.test.js
DELETED
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { describe, it, before, after } from 'node:test';
|
|
2
|
-
import assert from 'node:assert';
|
|
3
|
-
import { execSync } from 'node:child_process';
|
|
4
|
-
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
5
|
-
import { join, delimiter } from 'node:path';
|
|
6
|
-
import { tmpdir } from 'node:os';
|
|
7
|
-
import { autodetect, autodetectAll, isSensitiveEnvName, isSensitiveEnvValue, AUTODETECT_VARS } from './autodetect.js';
|
|
8
|
-
// git-backed detectors read the process cwd, so run them inside a scratch
|
|
9
|
-
// repo with a known origin remote (each test file is its own process).
|
|
10
|
-
let repo;
|
|
11
|
-
let prevCwd;
|
|
12
|
-
before(() => {
|
|
13
|
-
prevCwd = process.cwd();
|
|
14
|
-
repo = mkdtempSync(join(process.cwd(), '.pa-auto-'));
|
|
15
|
-
process.chdir(repo);
|
|
16
|
-
const run = (cmd) => execSync(cmd, { stdio: 'pipe' });
|
|
17
|
-
run('git init -q -b pa-test-branch');
|
|
18
|
-
run('git config user.email pa@test.local');
|
|
19
|
-
run('git config user.name "PA Tester"');
|
|
20
|
-
run('git remote add origin https://github.com/pa-test-org/pa-test-repo.git');
|
|
21
|
-
run('git commit -q --allow-empty -m init');
|
|
22
|
-
});
|
|
23
|
-
after(() => {
|
|
24
|
-
process.chdir(prevCwd);
|
|
25
|
-
rmSync(repo, { recursive: true, force: true });
|
|
26
|
-
});
|
|
27
|
-
describe('git detectors', () => {
|
|
28
|
-
it('detects the current branch', () => {
|
|
29
|
-
assert.strictEqual(autodetect('branch'), 'pa-test-branch');
|
|
30
|
-
});
|
|
31
|
-
it('detects repo name from the origin remote, stripping .git', () => {
|
|
32
|
-
assert.strictEqual(autodetect('repo'), 'pa-test-repo');
|
|
33
|
-
});
|
|
34
|
-
it('detects the org from the origin remote', () => {
|
|
35
|
-
assert.strictEqual(autodetect('org'), 'pa-test-org');
|
|
36
|
-
});
|
|
37
|
-
it('detects repo/org from ssh-style remotes', () => {
|
|
38
|
-
execSync('git remote set-url origin git@github.com:ssh-org/ssh-repo.git', { stdio: 'pipe' });
|
|
39
|
-
try {
|
|
40
|
-
assert.strictEqual(autodetect('repo'), 'ssh-repo');
|
|
41
|
-
assert.strictEqual(autodetect('org'), 'ssh-org');
|
|
42
|
-
}
|
|
43
|
-
finally {
|
|
44
|
-
execSync('git remote set-url origin https://github.com/pa-test-org/pa-test-repo.git', { stdio: 'pipe' });
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
it('detects the git user name', () => {
|
|
48
|
-
assert.strictEqual(autodetect('user'), 'PA Tester');
|
|
49
|
-
});
|
|
50
|
-
it('returns undefined for branch on a detached HEAD', () => {
|
|
51
|
-
// `git branch --show-current` exits 0 and prints nothing when detached;
|
|
52
|
-
// that must not become an empty value that beats the template default.
|
|
53
|
-
execSync('git checkout -q --detach', { stdio: 'pipe' });
|
|
54
|
-
try {
|
|
55
|
-
assert.strictEqual(autodetect('branch'), undefined);
|
|
56
|
-
assert.ok(!('branch' in autodetectAll(['branch'])));
|
|
57
|
-
}
|
|
58
|
-
finally {
|
|
59
|
-
execSync('git checkout -q pa-test-branch', { stdio: 'pipe' });
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
it('returns undefined for diff on a clean tree and the diff once there is one', () => {
|
|
63
|
-
assert.strictEqual(autodetect('diff'), undefined);
|
|
64
|
-
writeFileSync(join(repo, 'tracked.txt'), 'one\n');
|
|
65
|
-
execSync('git add tracked.txt && git commit -q -m add', { stdio: 'pipe' });
|
|
66
|
-
writeFileSync(join(repo, 'tracked.txt'), 'two\n');
|
|
67
|
-
try {
|
|
68
|
-
assert.match(autodetect('diff') ?? '', /^diff --git a\/tracked\.txt/);
|
|
69
|
-
}
|
|
70
|
-
finally {
|
|
71
|
-
execSync('git checkout -q -- tracked.txt', { stdio: 'pipe' });
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
describe('date detector', () => {
|
|
76
|
-
it('returns an ISO date (YYYY-MM-DD)', () => {
|
|
77
|
-
assert.match(autodetect('date'), /^\d{4}-\d{2}-\d{2}$/);
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
describe('environment fallback', () => {
|
|
81
|
-
it('falls back to environment variables for unknown names', () => {
|
|
82
|
-
process.env.pa_custom_var = 'env-value';
|
|
83
|
-
try {
|
|
84
|
-
assert.strictEqual(autodetect('pa_custom_var'), 'env-value');
|
|
85
|
-
}
|
|
86
|
-
finally {
|
|
87
|
-
delete process.env.pa_custom_var;
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
it('returns undefined for unknown names not in the environment', () => {
|
|
91
|
-
assert.strictEqual(autodetect('pa_definitely_not_set'), undefined);
|
|
92
|
-
});
|
|
93
|
-
it('never expands credential-looking env vars', () => {
|
|
94
|
-
process.env.PA_TEST_TOKEN = 'sekrit';
|
|
95
|
-
process.env.PA_TEST_API_KEY = 'sekrit';
|
|
96
|
-
try {
|
|
97
|
-
assert.strictEqual(autodetect('PA_TEST_TOKEN'), undefined);
|
|
98
|
-
assert.strictEqual(autodetect('PA_TEST_API_KEY'), undefined);
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
delete process.env.PA_TEST_TOKEN;
|
|
102
|
-
delete process.env.PA_TEST_API_KEY;
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
it('never expands credential-shaped values under innocuous names', () => {
|
|
106
|
-
process.env.PA_TEST_DATABASE_URL = 'postgres://app:hunter2@db.internal/app';
|
|
107
|
-
process.env.PA_TEST_PLAIN_URL = 'postgres://db.internal/app';
|
|
108
|
-
try {
|
|
109
|
-
assert.strictEqual(autodetect('PA_TEST_DATABASE_URL'), undefined);
|
|
110
|
-
assert.strictEqual(autodetect('PA_TEST_PLAIN_URL'), 'postgres://db.internal/app');
|
|
111
|
-
}
|
|
112
|
-
finally {
|
|
113
|
-
delete process.env.PA_TEST_DATABASE_URL;
|
|
114
|
-
delete process.env.PA_TEST_PLAIN_URL;
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
});
|
|
118
|
-
describe('isSensitiveEnvName', () => {
|
|
119
|
-
it('flags credential-looking names', () => {
|
|
120
|
-
for (const name of [
|
|
121
|
-
'GITHUB_TOKEN', 'GH_TOKEN', 'NPM_TOKEN', 'MY_SECRET', 'DB_PASSWORD',
|
|
122
|
-
'PGPASSWD', 'AWS_SECRET_ACCESS_KEY', 'OPENAI_API_KEY', 'APIKEY',
|
|
123
|
-
'GOOGLE_APPLICATION_CREDENTIALS', 'SSH_PRIVATE_KEY', 'AUTH_HEADER',
|
|
124
|
-
'X_AUTH', 'SESSION_COOKIE', 'BEARER_VALUE', 'gh_token',
|
|
125
|
-
// Suffix conventions outside API_/ACCESS_/PRIVATE_KEY
|
|
126
|
-
'OPENAI_KEY', 'STRIPE_KEY', 'SIGNING_KEY', 'ENCRYPTION_KEY', 'HIVE_HEARTBEAT_KEY',
|
|
127
|
-
'LICENSE_KEY', 'SSH_KEYS', 'KEY', 'MYSQL_PWD', 'ORACLE_PWD', 'GITHUB_PAT', 'AZURE_DEVOPS_PAT',
|
|
128
|
-
'PAT', 'GIT_PASS', 'SSH_PASSPHRASE', 'SLACK_WEBHOOK_URL', 'DISCORD_WEBHOOK', 'SENTRY_DSN',
|
|
129
|
-
'DB_CONNECTION_STRING', 'AZURE_CONN_STR', 'JWT_SIGNING', 'HMAC_VALUE', 'OAUTH_CLIENT_ID',
|
|
130
|
-
]) {
|
|
131
|
-
assert.strictEqual(isSensitiveEnvName(name), true, `should flag ${name}`);
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
it('does not flag ordinary names', () => {
|
|
135
|
-
for (const name of [
|
|
136
|
-
'EDITOR', 'PATH', 'GOPATH', 'AUTHOR', 'AUTHORIZED_USERS_FILE', 'branch', 'KEYBOARD',
|
|
137
|
-
'PWD', 'OLDPWD', 'PATTERN', 'PATCH_LEVEL', 'PASSENGER_COUNT', 'MONKEY', 'DSNAME', 'COMPASS',
|
|
138
|
-
]) {
|
|
139
|
-
assert.strictEqual(isSensitiveEnvName(name), false, `should not flag ${name}`);
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
describe('isSensitiveEnvValue', () => {
|
|
144
|
-
it('flags credential-shaped values regardless of name', () => {
|
|
145
|
-
for (const value of [
|
|
146
|
-
'postgres://app:hunter2@db.internal:5432/app',
|
|
147
|
-
'redis://:p4ss@cache.internal/0',
|
|
148
|
-
'mongodb+srv://user:pw@cluster0.example.net/db',
|
|
149
|
-
'-----BEGIN OPENSSH PRIVATE KEY-----\nabc',
|
|
150
|
-
'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.sig',
|
|
151
|
-
'ghp_' + 'A'.repeat(36),
|
|
152
|
-
'github_pat_' + 'A'.repeat(22) + '_' + 'b'.repeat(59),
|
|
153
|
-
'glpat-' + 'x'.repeat(20),
|
|
154
|
-
'sk-' + 'x'.repeat(48),
|
|
155
|
-
'sk_live_' + 'x'.repeat(24),
|
|
156
|
-
'xoxb-' + '1'.repeat(12) + '-abc',
|
|
157
|
-
'AKIA' + 'A'.repeat(16),
|
|
158
|
-
'AIza' + 'a'.repeat(35),
|
|
159
|
-
'npm_' + 'a'.repeat(36),
|
|
160
|
-
'hvs.' + 'a'.repeat(24),
|
|
161
|
-
]) {
|
|
162
|
-
assert.strictEqual(isSensitiveEnvValue(value), true, `should flag ${value.slice(0, 12)}...`);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
it('does not flag ordinary values', () => {
|
|
166
|
-
for (const value of [
|
|
167
|
-
'main', '/usr/local/bin:/usr/bin', 'https://github.com/hivecommons/promptargs',
|
|
168
|
-
'postgres://db.internal/app', 'https://user@example.com/path', 'user@example.com',
|
|
169
|
-
'skills', 'sk-short', 'eyJ-not-a-jwt', 'AKIA-nope', 'Hello, world', '42',
|
|
170
|
-
]) {
|
|
171
|
-
assert.strictEqual(isSensitiveEnvValue(value), false, `should not flag ${value}`);
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
describe('autodetectAll', () => {
|
|
176
|
-
it('collects only detectable values', () => {
|
|
177
|
-
const result = autodetectAll(['branch', 'pa_definitely_not_set', 'date']);
|
|
178
|
-
assert.strictEqual(result.branch, 'pa-test-branch');
|
|
179
|
-
assert.ok('date' in result);
|
|
180
|
-
assert.ok(!('pa_definitely_not_set' in result));
|
|
181
|
-
});
|
|
182
|
-
it('AUTODETECT_VARS lists the built-in detectors', () => {
|
|
183
|
-
for (const name of ['branch', 'repo', 'org', 'diff', 'pr', 'user', 'date']) {
|
|
184
|
-
assert.ok(AUTODETECT_VARS.includes(name), `missing ${name}`);
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
describe('pr detector', () => {
|
|
189
|
-
it('returns undefined when gh exits 0 with empty output', t => {
|
|
190
|
-
// A branch with no PR: gh succeeds but prints nothing, so the detector
|
|
191
|
-
// must map '' to undefined rather than returning an empty value.
|
|
192
|
-
if (process.platform === 'win32')
|
|
193
|
-
return t.skip('POSIX shim script');
|
|
194
|
-
const shim = mkdtempSync(join(tmpdir(), 'pa-gh-shim-'));
|
|
195
|
-
writeFileSync(join(shim, 'gh'), '#!/bin/sh\nexit 0\n', { mode: 0o755 });
|
|
196
|
-
const prevPath = process.env.PATH;
|
|
197
|
-
process.env.PATH = `${shim}${delimiter}${prevPath ?? ''}`;
|
|
198
|
-
try {
|
|
199
|
-
assert.strictEqual(autodetect('pr'), undefined);
|
|
200
|
-
}
|
|
201
|
-
finally {
|
|
202
|
-
process.env.PATH = prevPath;
|
|
203
|
-
rmSync(shim, { recursive: true, force: true });
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
//# sourceMappingURL=autodetect.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"autodetect.test.js","sourceRoot":"","sources":["../src/autodetect.test.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|