commitshow 0.3.28 → 0.3.29
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/dist/commands/audit.js +19 -3
- package/dist/index.js +13 -7
- package/dist/lib/api.js +4 -0
- package/dist/lib/target.js +71 -14
- package/package.json +1 -1
package/dist/commands/audit.js
CHANGED
|
@@ -23,10 +23,22 @@ export async function audit(args) {
|
|
|
23
23
|
?? (sourceIdx >= 0 ? args[sourceIdx + 1] : undefined)
|
|
24
24
|
?? process.env.COMMITSHOW_SOURCE
|
|
25
25
|
?? null;
|
|
26
|
-
|
|
26
|
+
// --workspace <path> · explicit override of the server-side auto-pick
|
|
27
|
+
// for monorepo audits. Both --workspace=apps/web and --workspace apps/web
|
|
28
|
+
// forms accepted. Passes through to the Edge Function as `workspace`
|
|
29
|
+
// in the audit request body. Inline path inside the URL
|
|
30
|
+
// (`github.com/o/r/apps/web`) is the second-priority source — picked
|
|
31
|
+
// up by resolveTarget() if the flag isn't set.
|
|
32
|
+
const workspaceIdx = args.indexOf('--workspace');
|
|
33
|
+
const workspaceFlag = args.find(a => a.startsWith('--workspace='))?.split('=')[1]
|
|
34
|
+
?? (workspaceIdx >= 0 ? args[workspaceIdx + 1] : undefined)
|
|
35
|
+
?? null;
|
|
36
|
+
const positional = args.find(a => !a.startsWith('--') &&
|
|
37
|
+
a !== sourceFlag &&
|
|
38
|
+
a !== workspaceFlag);
|
|
27
39
|
let target;
|
|
28
40
|
try {
|
|
29
|
-
target = resolveTarget(positional);
|
|
41
|
+
target = resolveTarget(positional, { workspace: workspaceFlag });
|
|
30
42
|
}
|
|
31
43
|
catch (err) {
|
|
32
44
|
if (err instanceof TargetError) {
|
|
@@ -141,7 +153,11 @@ export async function audit(args) {
|
|
|
141
153
|
else
|
|
142
154
|
console.log(c.dim('First time on commit.show for this repo — running a preview audit…'));
|
|
143
155
|
}
|
|
144
|
-
const result = await runPreviewAudit(target.github_url, undefined, {
|
|
156
|
+
const result = await runPreviewAudit(target.github_url, undefined, {
|
|
157
|
+
force,
|
|
158
|
+
source: sourceFlag,
|
|
159
|
+
workspace: target.workspace,
|
|
160
|
+
});
|
|
145
161
|
// Error envelope
|
|
146
162
|
if ('error' in result) {
|
|
147
163
|
const err = result;
|
package/dist/index.js
CHANGED
|
@@ -39,15 +39,21 @@ ${c.muted('COMMANDS')}
|
|
|
39
39
|
${c.gold('whoami')} who am I signed in as
|
|
40
40
|
|
|
41
41
|
${c.muted('FLAGS')}
|
|
42
|
-
${c.gold('--json')}
|
|
43
|
-
${c.gold('--refresh')}
|
|
42
|
+
${c.gold('--json')} stable machine-readable output (for agents · CI · jq pipes)
|
|
43
|
+
${c.gold('--refresh')} bypass the 7-day cache · re-run a fresh audit ${c.dim('(counts against IP cap)')}
|
|
44
|
+
${c.gold('--workspace')} ${c.cream('<path>')} monorepo: audit only this sub-app (e.g. apps/web · packages/next)
|
|
44
45
|
|
|
45
46
|
${c.muted('TARGET FORMS')} ${c.dim('(default: cwd)')}
|
|
46
|
-
${c.cream('commitshow audit')}
|
|
47
|
-
${c.cream('commitshow audit ./my-repo')}
|
|
48
|
-
${c.cream('commitshow audit github.com/owner/repo')}
|
|
49
|
-
${c.cream('commitshow audit https://github.com/o/r')}
|
|
50
|
-
${c.cream('commitshow audit owner/repo')}
|
|
47
|
+
${c.cream('commitshow audit')} ${c.dim('# cwd · git remote origin')}
|
|
48
|
+
${c.cream('commitshow audit ./my-repo')} ${c.dim('# local path')}
|
|
49
|
+
${c.cream('commitshow audit github.com/owner/repo')} ${c.dim('# remote shorthand')}
|
|
50
|
+
${c.cream('commitshow audit https://github.com/o/r')} ${c.dim('# full URL')}
|
|
51
|
+
${c.cream('commitshow audit owner/repo')} ${c.dim('# last-ditch shorthand')}
|
|
52
|
+
|
|
53
|
+
${c.muted('MONOREPO TARGETS')} ${c.dim('(all three forms produce the same audit)')}
|
|
54
|
+
${c.cream('commitshow audit github.com/o/r --workspace apps/web')} ${c.dim('# explicit flag')}
|
|
55
|
+
${c.cream('commitshow audit github.com/o/r/apps/web')} ${c.dim('# inline path')}
|
|
56
|
+
${c.cream('commitshow audit https://github.com/o/r/tree/main/apps/web')} ${c.dim('# paste a GitHub browse URL')}
|
|
51
57
|
|
|
52
58
|
${c.muted('FOR AGENTS')}
|
|
53
59
|
${c.cream('commitshow audit github.com/owner/repo --json | jq .concerns')}
|
package/dist/lib/api.js
CHANGED
|
@@ -98,6 +98,10 @@ export async function runPreviewAudit(githubUrl, liveUrl, opts = {}) {
|
|
|
98
98
|
live_url: liveUrl,
|
|
99
99
|
force: opts.force === true,
|
|
100
100
|
source: opts.source ?? null,
|
|
101
|
+
// Workspace = explicit override of the server's monorepo auto-pick.
|
|
102
|
+
// null/undefined = let the server pick (default); a path like
|
|
103
|
+
// 'apps/web' = audit only that workspace.
|
|
104
|
+
workspace: opts.workspace ?? null,
|
|
101
105
|
}),
|
|
102
106
|
});
|
|
103
107
|
const body = await res.json().catch(() => ({ error: 'invalid_json' }));
|
package/dist/lib/target.js
CHANGED
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
// { kind: 'remote-url', github_url } or { kind: 'local', path, github_url? }.
|
|
3
3
|
//
|
|
4
4
|
// Accepted inputs (all resolve to a GitHub HTTPS URL):
|
|
5
|
-
// · (omitted)
|
|
6
|
-
// · ./my-repo · /abs/path
|
|
7
|
-
// · github.com/owner/repo
|
|
8
|
-
// · https://github.com/owner/repo
|
|
9
|
-
// · git@github.com:owner/repo.git
|
|
10
|
-
// · owner/repo
|
|
5
|
+
// · (omitted) → cwd · read `git remote get-url origin`
|
|
6
|
+
// · ./my-repo · /abs/path → local dir · same remote inference
|
|
7
|
+
// · github.com/owner/repo → bare host shorthand
|
|
8
|
+
// · https://github.com/owner/repo → full URL
|
|
9
|
+
// · git@github.com:owner/repo.git → ssh form (common in `git remote`)
|
|
10
|
+
// · owner/repo → last-ditch shorthand (2 segments, no dot)
|
|
11
|
+
// · github.com/owner/repo/apps/web → inline workspace (post-2026-05-06)
|
|
12
|
+
// · https://github.com/owner/repo/tree/main/apps/web → GitHub browse URL (paste-friendly)
|
|
13
|
+
//
|
|
14
|
+
// Workspace selection precedence:
|
|
15
|
+
// 1. --workspace <path> CLI flag (highest · explicit override)
|
|
16
|
+
// 2. Inline path in target URL (sub-path after <owner>/<repo>)
|
|
17
|
+
// 3. Auto-pick on the server (Edge Function priority-name → repo-name → file count)
|
|
11
18
|
import { execSync } from 'node:child_process';
|
|
12
19
|
import { existsSync, statSync } from 'node:fs';
|
|
13
20
|
import { resolve } from 'node:path';
|
|
@@ -45,21 +52,65 @@ export async function verifyRemoteExists(githubUrl) {
|
|
|
45
52
|
return { exists: true, ambiguous: true };
|
|
46
53
|
}
|
|
47
54
|
}
|
|
48
|
-
|
|
49
|
-
|
|
55
|
+
// `github_url` patterns. Each captures (owner, repo, optional sub-path).
|
|
56
|
+
// The sub-path lets users inline a workspace inside the URL itself instead
|
|
57
|
+
// of using the --workspace flag (`github.com/o/r/apps/web` or paste of a
|
|
58
|
+
// GitHub browse URL `https://github.com/o/r/tree/main/apps/web`).
|
|
59
|
+
const GITHUB_URL_RE = /^https?:\/\/github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?(?:\/(.+))?\/?$/i;
|
|
60
|
+
const GITHUB_HOST_RE = /^github\.com\/([^/\s]+)\/([^/\s]+?)(?:\.git)?(?:\/(.+))?\/?$/i;
|
|
50
61
|
const GITHUB_SSH_RE = /^git@github\.com:([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i;
|
|
51
62
|
const SLUG_RE = /^([A-Za-z0-9][\w.-]*)\/([A-Za-z0-9][\w.-]*)$/;
|
|
52
63
|
function canonical(owner, repo) {
|
|
53
64
|
return `https://github.com/${owner}/${repo.replace(/\.git$/, '')}`;
|
|
54
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Normalize the sub-path portion of a GitHub URL into a workspace path.
|
|
68
|
+
* Handles the GitHub browse URL prefixes (`tree/<branch>/...` ·
|
|
69
|
+
* `blob/<branch>/...`) so users can paste any URL they're looking at.
|
|
70
|
+
*
|
|
71
|
+
* Rejects non-workspace prefixes (issues · pulls · actions · settings ·
|
|
72
|
+
* releases · wiki · etc.) — those aren't workspaces, returning null
|
|
73
|
+
* means "no inline workspace" rather than a confusing error.
|
|
74
|
+
*/
|
|
75
|
+
function normalizeSubpath(raw) {
|
|
76
|
+
if (!raw)
|
|
77
|
+
return null;
|
|
78
|
+
let s = raw.replace(/^\/+/, '').replace(/\/+$/, '');
|
|
79
|
+
if (!s)
|
|
80
|
+
return null;
|
|
81
|
+
// GitHub browse URL prefixes — strip and keep the path part
|
|
82
|
+
const browseMatch = s.match(/^(?:tree|blob)\/[^/]+\/(.+)$/i);
|
|
83
|
+
if (browseMatch)
|
|
84
|
+
s = browseMatch[1];
|
|
85
|
+
// Non-workspace GitHub URL prefixes — ignore the sub-path entirely
|
|
86
|
+
const NON_WORKSPACE_PREFIXES = [
|
|
87
|
+
'issues', 'pull', 'pulls', 'actions', 'settings', 'releases',
|
|
88
|
+
'wiki', 'security', 'pulse', 'graphs', 'network', 'commits',
|
|
89
|
+
'commit', 'compare', 'tags', 'branches', 'discussions', 'projects',
|
|
90
|
+
];
|
|
91
|
+
const head = s.split('/')[0].toLowerCase();
|
|
92
|
+
if (NON_WORKSPACE_PREFIXES.includes(head))
|
|
93
|
+
return null;
|
|
94
|
+
return s;
|
|
95
|
+
}
|
|
55
96
|
function matchUrl(raw) {
|
|
56
97
|
const s = raw.trim();
|
|
57
|
-
const urlMatch = s.match(GITHUB_URL_RE) ?? s.match(GITHUB_HOST_RE)
|
|
58
|
-
if (urlMatch)
|
|
59
|
-
return {
|
|
98
|
+
const urlMatch = s.match(GITHUB_URL_RE) ?? s.match(GITHUB_HOST_RE);
|
|
99
|
+
if (urlMatch) {
|
|
100
|
+
return {
|
|
101
|
+
owner: urlMatch[1],
|
|
102
|
+
repo: urlMatch[2],
|
|
103
|
+
workspace: normalizeSubpath(urlMatch[3]),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const sshMatch = s.match(GITHUB_SSH_RE);
|
|
107
|
+
if (sshMatch) {
|
|
108
|
+
return { owner: sshMatch[1], repo: sshMatch[2], workspace: null };
|
|
109
|
+
}
|
|
60
110
|
const slug = s.match(SLUG_RE);
|
|
61
|
-
if (slug && !slug[2].includes('.'))
|
|
62
|
-
return { owner: slug[1], repo: slug[2] };
|
|
111
|
+
if (slug && !slug[2].includes('.')) {
|
|
112
|
+
return { owner: slug[1], repo: slug[2], workspace: null };
|
|
113
|
+
}
|
|
63
114
|
return null;
|
|
64
115
|
}
|
|
65
116
|
function gitRemoteOrigin(cwd) {
|
|
@@ -79,7 +130,11 @@ function gitRemoteOrigin(cwd) {
|
|
|
79
130
|
return null;
|
|
80
131
|
}
|
|
81
132
|
}
|
|
82
|
-
export function resolveTarget(rawArg) {
|
|
133
|
+
export function resolveTarget(rawArg, opts = {}) {
|
|
134
|
+
// Workspace precedence: --workspace flag wins over inline URL path.
|
|
135
|
+
// Both normalized through the same path so the server receives a
|
|
136
|
+
// single shape.
|
|
137
|
+
const flagWorkspace = normalizeSubpath(opts.workspace ?? null);
|
|
83
138
|
// 1 · Explicit URL forms
|
|
84
139
|
if (rawArg) {
|
|
85
140
|
const m = matchUrl(rawArg);
|
|
@@ -88,6 +143,7 @@ export function resolveTarget(rawArg) {
|
|
|
88
143
|
kind: 'remote-url',
|
|
89
144
|
github_url: canonical(m.owner, m.repo),
|
|
90
145
|
slug: `${m.owner}/${m.repo.replace(/\.git$/, '')}`,
|
|
146
|
+
workspace: flagWorkspace ?? m.workspace ?? null,
|
|
91
147
|
};
|
|
92
148
|
}
|
|
93
149
|
}
|
|
@@ -115,5 +171,6 @@ export function resolveTarget(rawArg) {
|
|
|
115
171
|
github_url: canonical(m.owner, m.repo),
|
|
116
172
|
localPath: path,
|
|
117
173
|
slug: `${m.owner}/${m.repo.replace(/\.git$/, '')}`,
|
|
174
|
+
workspace: flagWorkspace, // local repos don't carry an inline URL workspace
|
|
118
175
|
};
|
|
119
176
|
}
|