agentic-workflow-manager 3.9.1 → 3.10.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.
|
@@ -4,10 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.preflight = preflight;
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
7
8
|
const fs_1 = __importDefault(require("fs"));
|
|
8
9
|
const path_1 = __importDefault(require("path"));
|
|
9
10
|
const status_1 = require("../sensors/status");
|
|
10
11
|
const init_1 = require("../sensors/init");
|
|
12
|
+
const paths_1 = require("../../core/paths");
|
|
11
13
|
const MANIFEST = path_1.default.join('.awm', 'sensors.json');
|
|
12
14
|
/**
|
|
13
15
|
* The agent needs project context delivered every session. A repo with neither file
|
|
@@ -106,6 +108,89 @@ function checkPack(cwd, manifest) {
|
|
|
106
108
|
}
|
|
107
109
|
return { id: 'pack', ok: true, detail: `${manifest.pack} matches the detected stack` };
|
|
108
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Extract just the hostname portion of a git remote URL — never match against the
|
|
113
|
+
* full URL string. A bare substring check against the whole remote (`remote.includes
|
|
114
|
+
* ('gitlab')`) false-positives on an org/repo name that happens to contain the word,
|
|
115
|
+
* e.g. `git@github.enterprise.internal:kodria/gitlab-migration-tool.git` is a GitHub
|
|
116
|
+
* Enterprise remote, not GitLab — "gitlab" only appears in the repo name.
|
|
117
|
+
*
|
|
118
|
+
* Covers the two common remote URL shapes:
|
|
119
|
+
* HTTPS: `https://github.com/org/repo.git` -> `github.com`
|
|
120
|
+
* SSH: `git@github.com:org/repo.git` -> `github.com`
|
|
121
|
+
*
|
|
122
|
+
* Scheme-prefixed remotes (`https://`, `ssh://`, ...) are parsed with the built-in
|
|
123
|
+
* `URL` class rather than a hand-rolled regex — `.hostname` is spec-defined to exclude
|
|
124
|
+
* both userinfo (`user:pass@`/`user@`) and `:port`, so a userinfo or password/token
|
|
125
|
+
* that happens to contain "github"/"gitlab" (e.g. an SSH username `ssh://gitlab@host/`
|
|
126
|
+
* or a CI credential-injection URL `https://x-access-token:$TOKEN@host/...`) can never
|
|
127
|
+
* leak into the matched host, and IPv6 literals in brackets are also handled correctly.
|
|
128
|
+
*
|
|
129
|
+
* The SCP-style shorthand (`user@host:path`, no scheme — not a real URI, so `URL`
|
|
130
|
+
* rejects it) falls back to a regex whose host-capture group excludes `@`, so a second
|
|
131
|
+
* `@` in the remote (`user@host@evil:path`) can't smuggle a bogus "host" past the colon
|
|
132
|
+
* check either — it simply fails to match and returns `undefined`.
|
|
133
|
+
*
|
|
134
|
+
* Returns `undefined` when neither shape matches, so callers fall through to the same
|
|
135
|
+
* "unrecognized host" handling as any other unmatched URL.
|
|
136
|
+
*/
|
|
137
|
+
function extractHost(remote) {
|
|
138
|
+
try {
|
|
139
|
+
return new URL(remote).hostname;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Not a valid URL — likely git's SCP-like shorthand (user@host:path, no scheme).
|
|
143
|
+
// Node's URL class does not parse this form (it's not a real URI).
|
|
144
|
+
}
|
|
145
|
+
const scpMatch = remote.match(/^[^@\s]+@([^:\s@]+):/);
|
|
146
|
+
return scpMatch?.[1];
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Advisory only — `ok` is ALWAYS `true` here, no matter what it finds. The
|
|
150
|
+
* `finishing-a-development-branch`/`receiving-code-review` skills detect the git host
|
|
151
|
+
* (GitHub vs GitLab) and shell out to `gh`/`glab` to open a PR/MR, degrading honestly
|
|
152
|
+
* when neither is on PATH. This check tells the operator, in advance, whether that
|
|
153
|
+
* downstream step will actually work — but plenty of legitimate workflows never create
|
|
154
|
+
* a PR/MR at all (merge locally, keep the branch as-is), so gating the whole harness on
|
|
155
|
+
* missing tooling here would be wrong. It is FYI, never a blocker.
|
|
156
|
+
*/
|
|
157
|
+
function checkHost(cwd) {
|
|
158
|
+
let remote;
|
|
159
|
+
try {
|
|
160
|
+
remote = (0, child_process_1.execFileSync)('git', ['remote', 'get-url', 'origin'], {
|
|
161
|
+
cwd,
|
|
162
|
+
encoding: 'utf-8',
|
|
163
|
+
stdio: ['ignore', 'pipe', 'ignore'], // no origin / not a repo prints to stderr — keep it off the operator's screen
|
|
164
|
+
}).trim();
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
// No `origin`, or not a git repo at all — nothing to advise on.
|
|
168
|
+
return { id: 'host', ok: true, detail: 'no git remote detected — PR/MR automation not applicable' };
|
|
169
|
+
}
|
|
170
|
+
const host = extractHost(remote);
|
|
171
|
+
if (host?.includes('github.com')) {
|
|
172
|
+
return (0, paths_1.resolveOnPath)('gh')
|
|
173
|
+
? { id: 'host', ok: true, detail: 'github detected, gh available' }
|
|
174
|
+
: {
|
|
175
|
+
id: 'host',
|
|
176
|
+
ok: true,
|
|
177
|
+
detail: 'github detected, gh not on PATH — PR creation will require manual steps',
|
|
178
|
+
remedy: 'install the GitHub CLI (gh), or PR creation will need to be done manually',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (host?.includes('gitlab')) {
|
|
182
|
+
return (0, paths_1.resolveOnPath)('glab')
|
|
183
|
+
? { id: 'host', ok: true, detail: 'gitlab detected, glab available' }
|
|
184
|
+
: {
|
|
185
|
+
id: 'host',
|
|
186
|
+
ok: true,
|
|
187
|
+
detail: 'gitlab detected, glab not on PATH — MR creation will require manual steps',
|
|
188
|
+
remedy: 'install the GitLab CLI (glab), or MR creation will need to be done manually',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
// Bitbucket, Azure DevOps, an internal git server, etc. — don't overclaim support.
|
|
192
|
+
return { id: 'host', ok: true, detail: 'git host not recognized (github/gitlab) — PR/MR automation not applicable' };
|
|
193
|
+
}
|
|
109
194
|
function preflight(cwd = process.cwd()) {
|
|
110
195
|
const manifest = readManifest(cwd);
|
|
111
196
|
const manifestExists = fs_1.default.existsSync(path_1.default.join(cwd, MANIFEST));
|
|
@@ -115,6 +200,9 @@ function preflight(cwd = process.cwd()) {
|
|
|
115
200
|
// Skipped when there is no manifest: reporting "tools broken" on a repo that was
|
|
116
201
|
// never set up buries the one thing the operator needs to read.
|
|
117
202
|
...(manifestExists ? [checkTools(cwd), checkPack(cwd, manifest)] : []),
|
|
203
|
+
// Runs unconditionally — orthogonal to sensor configuration entirely, this is
|
|
204
|
+
// about PR/MR tooling, not sensors.
|
|
205
|
+
checkHost(cwd),
|
|
118
206
|
];
|
|
119
207
|
const status = !manifestExists ? 'not_configured'
|
|
120
208
|
: checks.every(c => c.ok) ? 'ready'
|
|
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.computeSensorStatus = computeSensorStatus;
|
|
7
|
-
const child_process_1 = require("child_process");
|
|
8
7
|
const fs_1 = __importDefault(require("fs"));
|
|
9
8
|
const path_1 = __importDefault(require("path"));
|
|
10
9
|
const paths_1 = require("../../core/paths");
|
|
@@ -16,17 +15,6 @@ function npxTool(parts) {
|
|
|
16
15
|
}
|
|
17
16
|
return undefined;
|
|
18
17
|
}
|
|
19
|
-
/** Resolve a binary on PATH portably: `where` on win32, POSIX `command -v` elsewhere. */
|
|
20
|
-
function resolveOnPath(bin) {
|
|
21
|
-
const cmd = (0, paths_1.isWindowsNative)() ? `where ${bin}` : `command -v ${bin}`;
|
|
22
|
-
try {
|
|
23
|
-
(0, child_process_1.execSync)(cmd, { stdio: 'pipe' });
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
18
|
/** If the command references `--config <file>`, that file must exist in the repo. */
|
|
31
19
|
function configCheck(parts, cwd) {
|
|
32
20
|
const i = parts.indexOf('--config');
|
|
@@ -60,7 +48,7 @@ function checkCmd(cmd, cwd) {
|
|
|
60
48
|
}
|
|
61
49
|
return configCheck(parts, cwd) ?? { ok: true, detail: `${tool} (node_modules/.bin)` };
|
|
62
50
|
}
|
|
63
|
-
if (!resolveOnPath(bin)) {
|
|
51
|
+
if (!(0, paths_1.resolveOnPath)(bin)) {
|
|
64
52
|
return { ok: false, detail: `${bin} not found in PATH` };
|
|
65
53
|
}
|
|
66
54
|
return configCheck(parts, cwd) ?? { ok: true, detail: bin };
|
package/dist/src/core/paths.js
CHANGED
|
@@ -10,6 +10,7 @@ exports.platform = platform;
|
|
|
10
10
|
exports.isWindowsNative = isWindowsNative;
|
|
11
11
|
exports.platformLabel = platformLabel;
|
|
12
12
|
exports.warnIfUnsupportedPlatform = warnIfUnsupportedPlatform;
|
|
13
|
+
exports.resolveOnPath = resolveOnPath;
|
|
13
14
|
// cli/src/core/paths.ts
|
|
14
15
|
//
|
|
15
16
|
// Single source of truth for home / AWM_HOME resolution and platform detection.
|
|
@@ -17,6 +18,7 @@ exports.warnIfUnsupportedPlatform = warnIfUnsupportedPlatform;
|
|
|
17
18
|
// always honored and tests need no jest.resetModules().
|
|
18
19
|
const os_1 = __importDefault(require("os"));
|
|
19
20
|
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const child_process_1 = require("child_process");
|
|
20
22
|
/** User home directory with a robust fallback. Never returns a raw, possibly-empty process.env.HOME. */
|
|
21
23
|
function homeDir() {
|
|
22
24
|
return process.env.HOME || os_1.default.homedir();
|
|
@@ -54,3 +56,14 @@ function warnIfUnsupportedPlatform(log) {
|
|
|
54
56
|
if (isWindowsNative())
|
|
55
57
|
log(exports.WINDOWS_NATIVE_WARNING);
|
|
56
58
|
}
|
|
59
|
+
/** Resolve a binary on PATH portably: `where` on win32, POSIX `command -v` elsewhere. */
|
|
60
|
+
function resolveOnPath(bin) {
|
|
61
|
+
const cmd = isWindowsNative() ? `where ${bin}` : `command -v ${bin}`;
|
|
62
|
+
try {
|
|
63
|
+
(0, child_process_1.execSync)(cmd, { stdio: 'pipe' });
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -6,8 +6,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const fs_1 = __importDefault(require("fs"));
|
|
7
7
|
const os_1 = __importDefault(require("os"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
9
10
|
const checks_1 = require("../../../src/commands/preflight/checks");
|
|
10
11
|
const preflight_1 = require("../../../src/commands/preflight");
|
|
12
|
+
// Only `execSync` (used by `resolveOnPath` to check for `gh`/`glab`) is mocked — `git
|
|
13
|
+
// remote get-url origin` runs for real via `execFileSync` against real tmpdir git repos,
|
|
14
|
+
// same as every other check in this file exercises the real filesystem.
|
|
15
|
+
jest.mock('child_process', () => ({
|
|
16
|
+
...jest.requireActual('child_process'),
|
|
17
|
+
execSync: jest.fn(),
|
|
18
|
+
}));
|
|
19
|
+
const mockExecSync = child_process_1.execSync;
|
|
20
|
+
/** Turn a tmpdir into a real git repo with (optionally) an `origin` remote. */
|
|
21
|
+
function gitRepo(dir, remoteUrl) {
|
|
22
|
+
(0, child_process_1.execFileSync)('git', ['init'], { cwd: dir, stdio: 'pipe' });
|
|
23
|
+
if (remoteUrl)
|
|
24
|
+
(0, child_process_1.execFileSync)('git', ['remote', 'add', 'origin', remoteUrl], { cwd: dir, stdio: 'pipe' });
|
|
25
|
+
}
|
|
11
26
|
/** CLAUDE.md: no test may reach the real ~/.awm. Everything here is a tmpdir. */
|
|
12
27
|
function project(opts = {}) {
|
|
13
28
|
const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-preflight-'));
|
|
@@ -112,6 +127,156 @@ describe('preflight', () => {
|
|
|
112
127
|
expect((0, preflight_1.exitCodeFor)({ status: 'degraded', checks: [] })).toBe(1);
|
|
113
128
|
expect((0, preflight_1.exitCodeFor)({ status: 'ready', checks: [] })).toBe(0);
|
|
114
129
|
});
|
|
130
|
+
describe('host check (advisory — never changes the exit code)', () => {
|
|
131
|
+
beforeEach(() => { mockExecSync.mockReset(); });
|
|
132
|
+
it('reports github + gh available, and does not affect status', () => {
|
|
133
|
+
const dir = make({ manifest: { pack: 'generic', sensors: {} } });
|
|
134
|
+
gitRepo(dir, 'git@github.com:kodria/agentic-workflow.git');
|
|
135
|
+
mockExecSync.mockImplementation(((cmd) => {
|
|
136
|
+
if (cmd === 'command -v gh')
|
|
137
|
+
return Buffer.from('/usr/bin/gh');
|
|
138
|
+
throw new Error(`not found: ${cmd}`);
|
|
139
|
+
}));
|
|
140
|
+
const report = (0, checks_1.preflight)(dir);
|
|
141
|
+
expect(check(report, 'host').ok).toBe(true);
|
|
142
|
+
expect(check(report, 'host').detail).toBe('github detected, gh available');
|
|
143
|
+
expect(report.status).toBe('ready');
|
|
144
|
+
});
|
|
145
|
+
it('is still ok:true (advisory only) when gitlab is detected but glab is not on PATH, and status stays ready', () => {
|
|
146
|
+
// The only thing "wrong" in this fixture is the missing `glab` — proving the
|
|
147
|
+
// advisory contract: it must not drag an otherwise-clean repo to `degraded`.
|
|
148
|
+
const dir = make({ manifest: { pack: 'generic', sensors: {} } });
|
|
149
|
+
gitRepo(dir, 'https://gitlab.com/kodria/agentic-workflow.git');
|
|
150
|
+
mockExecSync.mockImplementation((() => {
|
|
151
|
+
throw new Error('not found');
|
|
152
|
+
}));
|
|
153
|
+
const report = (0, checks_1.preflight)(dir);
|
|
154
|
+
expect(check(report, 'host').ok).toBe(true);
|
|
155
|
+
expect(check(report, 'host').detail).toContain('glab not on PATH');
|
|
156
|
+
expect(check(report, 'host').remedy).toContain('glab');
|
|
157
|
+
expect(report.status).toBe('ready');
|
|
158
|
+
});
|
|
159
|
+
it('handles no origin remote gracefully — no throw, ok:true, minimal detail', () => {
|
|
160
|
+
const dir = make({ manifest: { pack: 'generic', sensors: {} } });
|
|
161
|
+
// Not a git repo at all — the common case for `execFileSync` failing here.
|
|
162
|
+
const report = (0, checks_1.preflight)(dir);
|
|
163
|
+
expect(check(report, 'host').ok).toBe(true);
|
|
164
|
+
expect(check(report, 'host').detail).toBe('no git remote detected — PR/MR automation not applicable');
|
|
165
|
+
expect(check(report, 'host').remedy).toBeUndefined();
|
|
166
|
+
expect(report.status).toBe('ready');
|
|
167
|
+
});
|
|
168
|
+
it('handles a git repo with no origin remote configured gracefully', () => {
|
|
169
|
+
const dir = make({ manifest: { pack: 'generic', sensors: {} } });
|
|
170
|
+
gitRepo(dir); // git init, no remote
|
|
171
|
+
const report = (0, checks_1.preflight)(dir);
|
|
172
|
+
expect(check(report, 'host').ok).toBe(true);
|
|
173
|
+
expect(check(report, 'host').detail).toContain('no git remote detected');
|
|
174
|
+
});
|
|
175
|
+
it('does not overclaim support for an unrecognized host', () => {
|
|
176
|
+
const dir = make({ manifest: { pack: 'generic', sensors: {} } });
|
|
177
|
+
gitRepo(dir, 'git@bitbucket.org:kodria/agentic-workflow.git');
|
|
178
|
+
const report = (0, checks_1.preflight)(dir);
|
|
179
|
+
expect(check(report, 'host').ok).toBe(true);
|
|
180
|
+
expect(check(report, 'host').detail).toContain('not recognized');
|
|
181
|
+
expect(report.status).toBe('ready');
|
|
182
|
+
});
|
|
183
|
+
it('does not misclassify a GitHub Enterprise host whose repo NAME contains "gitlab"', () => {
|
|
184
|
+
// The bug: a bare `remote.includes('gitlab')` matches the full remote URL
|
|
185
|
+
// string, so an org/repo name containing "gitlab" false-positives even though
|
|
186
|
+
// the actual host is unrelated. Hostname must be extracted first and matched
|
|
187
|
+
// in isolation.
|
|
188
|
+
const dir = make({ manifest: { pack: 'generic', sensors: {} } });
|
|
189
|
+
gitRepo(dir, 'git@github.enterprise.internal:kodria/gitlab-migration-tool.git');
|
|
190
|
+
const report = (0, checks_1.preflight)(dir);
|
|
191
|
+
expect(check(report, 'host').ok).toBe(true);
|
|
192
|
+
expect(check(report, 'host').detail).toContain('not recognized');
|
|
193
|
+
expect(report.status).toBe('ready');
|
|
194
|
+
});
|
|
195
|
+
it('does not misclassify a non-GitHub host whose repo NAME contains "github"', () => {
|
|
196
|
+
// Same class of bug on the github side: "something-github-tool" is a repo
|
|
197
|
+
// name, not the host.
|
|
198
|
+
const dir = make({ manifest: { pack: 'generic', sensors: {} } });
|
|
199
|
+
gitRepo(dir, 'https://example.com/kodria/something-github-tool.git');
|
|
200
|
+
const report = (0, checks_1.preflight)(dir);
|
|
201
|
+
expect(check(report, 'host').ok).toBe(true);
|
|
202
|
+
expect(check(report, 'host').detail).toContain('not recognized');
|
|
203
|
+
expect(report.status).toBe('ready');
|
|
204
|
+
});
|
|
205
|
+
it('does not misclassify a GitHub Enterprise host whose SSH USERNAME is "gitlab"', () => {
|
|
206
|
+
// The related bug: the scheme-based regex captured everything between
|
|
207
|
+
// `scheme://` and the first `/`, including `userinfo@` — so an SSH username
|
|
208
|
+
// of "gitlab" leaked into the matched "host" string and false-positived the
|
|
209
|
+
// `.includes('gitlab')` check even though the real host is GitHub
|
|
210
|
+
// Enterprise. `new URL(...).hostname` must exclude userinfo entirely.
|
|
211
|
+
//
|
|
212
|
+
// Note: `github.company-internal.com` legitimately contains the substring
|
|
213
|
+
// "github.com" (from "company"), so — with the userinfo bug fixed — this
|
|
214
|
+
// correctly classifies as github (checkHost's own substring matching is a
|
|
215
|
+
// separate, pre-existing design, not part of this fix). The regression this
|
|
216
|
+
// test guards is that it must never again read as gitlab.
|
|
217
|
+
const dir = make({ manifest: { pack: 'generic', sensors: {} } });
|
|
218
|
+
gitRepo(dir, 'ssh://gitlab@github.company-internal.com:22/team/repo.git');
|
|
219
|
+
mockExecSync.mockImplementation((() => { throw new Error('not found'); }));
|
|
220
|
+
const report = (0, checks_1.preflight)(dir);
|
|
221
|
+
expect(check(report, 'host').ok).toBe(true);
|
|
222
|
+
expect(check(report, 'host').detail).toContain('github detected');
|
|
223
|
+
expect(check(report, 'host').detail).not.toContain('gitlab');
|
|
224
|
+
expect(report.status).toBe('ready');
|
|
225
|
+
});
|
|
226
|
+
it('does not misclassify a host whose injected credential/token contains "gitlab"', () => {
|
|
227
|
+
// A realistic CI credential-injection remote:
|
|
228
|
+
// `git remote set-url origin https://x-access-token:$TOKEN@host/...`. If the
|
|
229
|
+
// token or password happens to contain "gitlab", it must not leak into the
|
|
230
|
+
// matched host either.
|
|
231
|
+
const dir = make({ manifest: { pack: 'generic', sensors: {} } });
|
|
232
|
+
gitRepo(dir, 'https://user:gitlab@example-host.com/org/repo.git');
|
|
233
|
+
const report = (0, checks_1.preflight)(dir);
|
|
234
|
+
expect(check(report, 'host').ok).toBe(true);
|
|
235
|
+
expect(check(report, 'host').detail).toContain('not recognized');
|
|
236
|
+
expect(report.status).toBe('ready');
|
|
237
|
+
});
|
|
238
|
+
it('does not misclassify an SCP-style remote with a second "@" in it', () => {
|
|
239
|
+
// `user@host:path` shorthand has no scheme for `URL` to parse, so it falls
|
|
240
|
+
// back to a regex. A second "@" (e.g. a malformed/adversarial remote) must
|
|
241
|
+
// not let a bogus "host@evil"-shaped capture slip past the colon check —
|
|
242
|
+
// the host-capture group excludes "@", so this fails to match at all and
|
|
243
|
+
// falls through to "unrecognized" rather than misclassifying as gitlab.
|
|
244
|
+
const dir = make({ manifest: { pack: 'generic', sensors: {} } });
|
|
245
|
+
gitRepo(dir, 'user@github.com@gitlab.evil:org/repo.git');
|
|
246
|
+
const report = (0, checks_1.preflight)(dir);
|
|
247
|
+
expect(check(report, 'host').ok).toBe(true);
|
|
248
|
+
expect(check(report, 'host').detail).toContain('not recognized');
|
|
249
|
+
expect(report.status).toBe('ready');
|
|
250
|
+
});
|
|
251
|
+
it('still detects github.com over HTTPS', () => {
|
|
252
|
+
const dir = make({ manifest: { pack: 'generic', sensors: {} } });
|
|
253
|
+
gitRepo(dir, 'https://github.com/org/repo.git');
|
|
254
|
+
mockExecSync.mockImplementation((() => { throw new Error('not found'); }));
|
|
255
|
+
const report = (0, checks_1.preflight)(dir);
|
|
256
|
+
expect(check(report, 'host').detail).toContain('github detected');
|
|
257
|
+
});
|
|
258
|
+
it('still detects github.com over SSH shorthand', () => {
|
|
259
|
+
const dir = make({ manifest: { pack: 'generic', sensors: {} } });
|
|
260
|
+
gitRepo(dir, 'git@github.com:org/repo.git');
|
|
261
|
+
mockExecSync.mockImplementation((() => { throw new Error('not found'); }));
|
|
262
|
+
const report = (0, checks_1.preflight)(dir);
|
|
263
|
+
expect(check(report, 'host').detail).toContain('github detected');
|
|
264
|
+
});
|
|
265
|
+
it('still detects gitlab over HTTPS', () => {
|
|
266
|
+
const dir = make({ manifest: { pack: 'generic', sensors: {} } });
|
|
267
|
+
gitRepo(dir, 'https://gitlab.example.com/org/repo.git');
|
|
268
|
+
mockExecSync.mockImplementation((() => { throw new Error('not found'); }));
|
|
269
|
+
const report = (0, checks_1.preflight)(dir);
|
|
270
|
+
expect(check(report, 'host').detail).toContain('gitlab detected');
|
|
271
|
+
});
|
|
272
|
+
it('still detects gitlab over SSH shorthand', () => {
|
|
273
|
+
const dir = make({ manifest: { pack: 'generic', sensors: {} } });
|
|
274
|
+
gitRepo(dir, 'git@gitlab.example.com:org/repo.git');
|
|
275
|
+
mockExecSync.mockImplementation((() => { throw new Error('not found'); }));
|
|
276
|
+
const report = (0, checks_1.preflight)(dir);
|
|
277
|
+
expect(check(report, 'host').detail).toContain('gitlab detected');
|
|
278
|
+
});
|
|
279
|
+
});
|
|
115
280
|
it('tells the operator not to hand a broken harness to an unattended run', () => {
|
|
116
281
|
const out = (0, preflight_1.formatReport)({
|
|
117
282
|
status: 'not_configured',
|
|
@@ -5,7 +5,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const os_1 = __importDefault(require("os"));
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
8
9
|
const paths_1 = require("../../src/core/paths");
|
|
10
|
+
jest.mock('child_process', () => ({ execSync: jest.fn() }));
|
|
11
|
+
const mockExecSync = child_process_1.execSync;
|
|
9
12
|
describe('core/paths', () => {
|
|
10
13
|
let origHome;
|
|
11
14
|
let origAwmHome;
|
|
@@ -13,6 +16,7 @@ describe('core/paths', () => {
|
|
|
13
16
|
beforeEach(() => {
|
|
14
17
|
origHome = process.env.HOME;
|
|
15
18
|
origAwmHome = process.env.AWM_HOME;
|
|
19
|
+
mockExecSync.mockReset();
|
|
16
20
|
});
|
|
17
21
|
afterEach(() => {
|
|
18
22
|
if (origHome === undefined)
|
|
@@ -75,4 +79,34 @@ describe('core/paths', () => {
|
|
|
75
79
|
(0, paths_1.warnIfUnsupportedPlatform)(log);
|
|
76
80
|
expect(calls).toEqual([paths_1.WINDOWS_NATIVE_WARNING]);
|
|
77
81
|
});
|
|
82
|
+
describe('resolveOnPath', () => {
|
|
83
|
+
it('uses `command -v` on POSIX and returns true when the binary resolves', () => {
|
|
84
|
+
setPlatform('linux');
|
|
85
|
+
mockExecSync.mockImplementation(((cmd) => {
|
|
86
|
+
if (cmd === 'command -v semgrep')
|
|
87
|
+
return Buffer.from('/usr/bin/semgrep');
|
|
88
|
+
throw new Error(`not found: ${cmd}`);
|
|
89
|
+
}));
|
|
90
|
+
expect((0, paths_1.resolveOnPath)('semgrep')).toBe(true);
|
|
91
|
+
});
|
|
92
|
+
it('returns false on POSIX when `command -v` fails to resolve the binary', () => {
|
|
93
|
+
setPlatform('linux');
|
|
94
|
+
mockExecSync.mockImplementation(() => { throw new Error('not found'); });
|
|
95
|
+
expect((0, paths_1.resolveOnPath)('semgrep')).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
it('uses `where` on win32, not `command -v`', () => {
|
|
98
|
+
setPlatform('win32');
|
|
99
|
+
mockExecSync.mockImplementation(((cmd) => {
|
|
100
|
+
if (cmd === 'where semgrep')
|
|
101
|
+
return Buffer.from('C:\\tools\\semgrep.exe');
|
|
102
|
+
throw new Error(`not found: ${cmd}`);
|
|
103
|
+
}));
|
|
104
|
+
expect((0, paths_1.resolveOnPath)('semgrep')).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
it('returns false on win32 when `where` cannot find the binary', () => {
|
|
107
|
+
setPlatform('win32');
|
|
108
|
+
mockExecSync.mockImplementation(() => { throw new Error('not found'); });
|
|
109
|
+
expect((0, paths_1.resolveOnPath)('semgrep')).toBe(false);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
78
112
|
});
|