commitshow 0.3.33 → 0.4.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/dist/commands/audit.js +106 -1
- package/dist/index.js +2 -0
- package/dist/lib/api.js +20 -0
- package/dist/lib/target.js +58 -5
- package/package.json +1 -1
package/dist/commands/audit.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveTarget, verifyRemoteExists, TargetError } from '../lib/target.js';
|
|
2
|
-
import { findProjectByGithubUrl, fetchLatestSnapshot, fetchStanding, runPreviewAudit, waitForPreviewSnapshot, } from '../lib/api.js';
|
|
2
|
+
import { findProjectByGithubUrl, fetchLatestSnapshot, fetchStanding, runPreviewAudit, runSiteFastLaneAudit, waitForPreviewSnapshot, } from '../lib/api.js';
|
|
3
3
|
import { renderAudit, renderMarkdown, renderJson, renderUpsell, renderStarCta, renderQuotaFooter, renderRateLimitDeny, renderAuditError, writeAuditMarkdown, writeAuditJson, } from '../lib/render.js';
|
|
4
4
|
import { c } from '../lib/colors.js';
|
|
5
5
|
import { Spinner } from '../lib/spinner.js';
|
|
@@ -47,6 +47,13 @@ export async function audit(args) {
|
|
|
47
47
|
}
|
|
48
48
|
throw err;
|
|
49
49
|
}
|
|
50
|
+
// §15-E URL Fast Lane · short-circuit before the github-specific
|
|
51
|
+
// verifyRemoteExists / findProjectByGithubUrl path. The site-url lane
|
|
52
|
+
// has its own Edge Function (audit-site-preview) and identifies projects
|
|
53
|
+
// by live_url, not github_url, so the cached-flow lookup wouldn't apply.
|
|
54
|
+
if (target.kind === 'site-url') {
|
|
55
|
+
return await runSiteAudit(target, { force, sourceFlag, asJson });
|
|
56
|
+
}
|
|
50
57
|
// Pre-flight: when the user pointed at a remote URL (or owner/repo
|
|
51
58
|
// shorthand), confirm it actually resolves on github.com before we
|
|
52
59
|
// spend any audit budget. Catches the 'agent invented a slug' case
|
|
@@ -323,3 +330,101 @@ function emitError(asJson, code, message, target) {
|
|
|
323
330
|
console.error(c.scarlet(message));
|
|
324
331
|
}
|
|
325
332
|
}
|
|
333
|
+
// §15-E URL Fast Lane CLI handler. Mirrors the runPreviewAudit branch
|
|
334
|
+
// of the main flow but routes to audit-site-preview and uses live_url
|
|
335
|
+
// as the project identifier instead of github_url.
|
|
336
|
+
//
|
|
337
|
+
// On success: renders the same audit panel with the URL lane upsell —
|
|
338
|
+
// the only delta vs the repo lane is partial-cap framing in the upsell
|
|
339
|
+
// copy, handled by renderUpsell when github_url is empty.
|
|
340
|
+
async function runSiteAudit(target, opts) {
|
|
341
|
+
const { force, sourceFlag, asJson } = opts;
|
|
342
|
+
if (!target.site_url) {
|
|
343
|
+
emitError(asJson, 'bad_target', 'Site URL missing — internal target resolution issue.', target.slug);
|
|
344
|
+
return 2;
|
|
345
|
+
}
|
|
346
|
+
if (!asJson) {
|
|
347
|
+
if (force)
|
|
348
|
+
console.log(c.dim(`Refreshing URL audit for ${target.slug}…`));
|
|
349
|
+
else
|
|
350
|
+
console.log(c.dim(`Auditing ${target.slug} (URL fast lane · partial)…`));
|
|
351
|
+
}
|
|
352
|
+
const result = await runSiteFastLaneAudit(target.site_url, { force, source: sourceFlag });
|
|
353
|
+
if ('error' in result) {
|
|
354
|
+
const err = result;
|
|
355
|
+
if (err.error === 'rate_limited' && err.quota) {
|
|
356
|
+
const reason = (err.reason ?? 'ip_cap');
|
|
357
|
+
// Map server's domain_cap onto url_cap for renderRateLimitDeny (same shape).
|
|
358
|
+
const capReason = reason === 'ip_cap' ? 'ip_cap' : reason === 'global_cap' ? 'global_cap' : 'url_cap';
|
|
359
|
+
if (asJson) {
|
|
360
|
+
process.stdout.write(JSON.stringify({
|
|
361
|
+
error: 'rate_limited', reason: capReason,
|
|
362
|
+
message: err.message ?? 'Rate limit hit.',
|
|
363
|
+
quota: err.quota,
|
|
364
|
+
}) + '\n');
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
console.error('');
|
|
368
|
+
console.error(renderRateLimitDeny({
|
|
369
|
+
reason: capReason,
|
|
370
|
+
message: err.message ?? 'Rate limit hit. Try again later.',
|
|
371
|
+
limit: err.limit ?? 0,
|
|
372
|
+
count: err.count ?? 0,
|
|
373
|
+
quota: err.quota,
|
|
374
|
+
}));
|
|
375
|
+
console.error('');
|
|
376
|
+
}
|
|
377
|
+
return 1;
|
|
378
|
+
}
|
|
379
|
+
if (err.error === 'domain_opted_out') {
|
|
380
|
+
emitError(asJson, 'domain_opted_out', err.message ?? `${target.slug} declined audits via DNS TXT.`, target.site_url);
|
|
381
|
+
return 1;
|
|
382
|
+
}
|
|
383
|
+
emitError(asJson, err.error ?? 'site_audit_failed', err.message ?? 'URL audit failed.', target.site_url);
|
|
384
|
+
return 1;
|
|
385
|
+
}
|
|
386
|
+
let envelope;
|
|
387
|
+
if ('status' in result && result.status === 'running') {
|
|
388
|
+
const pending = result;
|
|
389
|
+
const spinner = new Spinner();
|
|
390
|
+
if (!asJson)
|
|
391
|
+
spinner.start(`Auditing ${target.slug}`);
|
|
392
|
+
let waited;
|
|
393
|
+
try {
|
|
394
|
+
waited = await waitForPreviewSnapshot(pending.project_id, null);
|
|
395
|
+
}
|
|
396
|
+
finally {
|
|
397
|
+
spinner.stop();
|
|
398
|
+
}
|
|
399
|
+
if (!waited) {
|
|
400
|
+
emitError(asJson, 'timeout', 'URL audit is taking longer than expected. Refresh in a minute.', target.site_url);
|
|
401
|
+
return 1;
|
|
402
|
+
}
|
|
403
|
+
envelope = { ...waited, quota: pending.quota };
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
envelope = result;
|
|
407
|
+
}
|
|
408
|
+
const view = { project: envelope.project, snapshot: envelope.snapshot, standing: null };
|
|
409
|
+
if (asJson) {
|
|
410
|
+
const shape = JSON.parse(renderJson(view));
|
|
411
|
+
if (envelope.quota)
|
|
412
|
+
shape.quota = envelope.quota;
|
|
413
|
+
shape.audit_kind = 'url_fast_lane';
|
|
414
|
+
process.stdout.write(JSON.stringify(shape, null, 2) + '\n');
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
console.log('');
|
|
418
|
+
console.log(renderAudit(view));
|
|
419
|
+
console.log('');
|
|
420
|
+
if (envelope.quota) {
|
|
421
|
+
console.log(renderQuotaFooter(envelope.quota));
|
|
422
|
+
console.log('');
|
|
423
|
+
}
|
|
424
|
+
// Custom upsell · partial-cap framing
|
|
425
|
+
console.log(c.dim(` ${c.gold('partial audit')} · the engine couldn't see your repo signals (tests · CI · LICENSE · brief).\n` +
|
|
426
|
+
` Push past the URL ceiling: ${c.cream('commit.show/submit')} with your repo.`));
|
|
427
|
+
console.log('');
|
|
428
|
+
}
|
|
429
|
+
return 0;
|
|
430
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -51,6 +51,8 @@ ${c.muted('TARGET FORMS')} ${c.dim('(default: cwd)')}
|
|
|
51
51
|
${c.cream('commitshow audit github.com/owner/repo')} ${c.dim('# remote shorthand')}
|
|
52
52
|
${c.cream('commitshow audit https://github.com/o/r')} ${c.dim('# full URL')}
|
|
53
53
|
${c.cream('commitshow audit owner/repo')} ${c.dim('# last-ditch shorthand')}
|
|
54
|
+
${c.cream('commitshow audit yoursite.com')} ${c.dim('# URL Fast Lane · partial audit · no repo needed')}
|
|
55
|
+
${c.cream('commitshow audit https://yoursite.com')} ${c.dim('# same · full URL form')}
|
|
54
56
|
|
|
55
57
|
${c.muted('MONOREPO TARGETS')} ${c.dim('(all three forms produce the same audit)')}
|
|
56
58
|
${c.cream('commitshow audit github.com/o/r --workspace apps/web')} ${c.dim('# explicit flag')}
|
package/dist/lib/api.js
CHANGED
|
@@ -121,6 +121,26 @@ export async function runPreviewAudit(githubUrl, liveUrl, opts = {}) {
|
|
|
121
121
|
return body;
|
|
122
122
|
return body;
|
|
123
123
|
}
|
|
124
|
+
/** §15-E URL Fast Lane · sister of runPreviewAudit. Kicks off audit on a
|
|
125
|
+
* deployed site URL (no repo). Server returns 202 + project_id like
|
|
126
|
+
* audit-preview · same poller (waitForPreviewSnapshot) handles the wait. */
|
|
127
|
+
export async function runSiteFastLaneAudit(siteUrl, opts = {}) {
|
|
128
|
+
const res = await fetch(`${baseUrl()}/functions/v1/audit-site-preview`, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: headers(),
|
|
131
|
+
body: JSON.stringify({
|
|
132
|
+
site_url: siteUrl,
|
|
133
|
+
force: opts.force === true,
|
|
134
|
+
source: opts.source ?? null,
|
|
135
|
+
}),
|
|
136
|
+
});
|
|
137
|
+
const body = await res.json().catch(() => ({ error: 'invalid_json' }));
|
|
138
|
+
if (res.status === 202)
|
|
139
|
+
return body;
|
|
140
|
+
if (!res.ok)
|
|
141
|
+
return body;
|
|
142
|
+
return body;
|
|
143
|
+
}
|
|
124
144
|
/** Poll a preview job until the snapshot lands or we time out.
|
|
125
145
|
*
|
|
126
146
|
* `since` (ISO timestamp) is the baseline we wait past. For an INITIAL audit
|
package/dist/lib/target.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
// Target detection — turns the CLI positional arg into a canonical
|
|
2
|
-
// { kind: 'remote-url', github_url } or { kind: 'local', path, github_url? }
|
|
2
|
+
// { kind: 'remote-url', github_url } or { kind: 'local', path, github_url? }
|
|
3
|
+
// or { kind: 'site-url', site_url } (§15-E URL fast lane).
|
|
3
4
|
//
|
|
4
|
-
// Accepted inputs
|
|
5
|
+
// Accepted inputs:
|
|
5
6
|
// · (omitted) → cwd · read `git remote get-url origin`
|
|
6
7
|
// · ./my-repo · /abs/path → local dir · same remote inference
|
|
7
8
|
// · github.com/owner/repo → bare host shorthand
|
|
8
9
|
// · https://github.com/owner/repo → full URL
|
|
9
10
|
// · git@github.com:owner/repo.git → ssh form (common in `git remote`)
|
|
10
11
|
// · owner/repo → last-ditch shorthand (2 segments, no dot)
|
|
11
|
-
// · github.com/owner/repo/apps/web → inline workspace
|
|
12
|
+
// · github.com/owner/repo/apps/web → inline workspace
|
|
12
13
|
// · https://github.com/owner/repo/tree/main/apps/web → GitHub browse URL (paste-friendly)
|
|
14
|
+
// · yoursite.com / https://yoursite.com → site URL (§15-E URL Fast Lane · NEW)
|
|
15
|
+
// routes to audit-site-preview ·
|
|
16
|
+
// partial cap ~32/50 · no repo needed
|
|
13
17
|
//
|
|
14
|
-
// Workspace selection precedence:
|
|
18
|
+
// Workspace selection precedence (repo lanes only):
|
|
15
19
|
// 1. --workspace <path> CLI flag (highest · explicit override)
|
|
16
20
|
// 2. Inline path in target URL (sub-path after <owner>/<repo>)
|
|
17
21
|
// 3. Auto-pick on the server (Edge Function priority-name → repo-name → file count)
|
|
@@ -113,6 +117,43 @@ function matchUrl(raw) {
|
|
|
113
117
|
}
|
|
114
118
|
return null;
|
|
115
119
|
}
|
|
120
|
+
// Site URL detection (§15-E URL Fast Lane).
|
|
121
|
+
// Accepts:
|
|
122
|
+
// · https://example.com / http://example.com → full URL
|
|
123
|
+
// · example.com / sub.example.com → bare host (added https://)
|
|
124
|
+
// · example.com/path → host + path · we strip path (origin only)
|
|
125
|
+
// Rejects:
|
|
126
|
+
// · github.com URLs (those go to remote-url path · matchUrl above)
|
|
127
|
+
// · localhost / private IPs / 1-segment hosts (no dot)
|
|
128
|
+
// · commit.show itself (reflexive)
|
|
129
|
+
function matchSiteUrl(raw) {
|
|
130
|
+
const s = raw.trim();
|
|
131
|
+
if (!s)
|
|
132
|
+
return null;
|
|
133
|
+
// owner/repo shorthand has no dot in the second segment — handled by matchUrl.
|
|
134
|
+
// Anything reaching here that contains "github.com" is a github form we already
|
|
135
|
+
// tried · don't double-route.
|
|
136
|
+
if (/github\.com/i.test(s))
|
|
137
|
+
return null;
|
|
138
|
+
const candidate = /^https?:\/\//i.test(s) ? s : `https://${s}`;
|
|
139
|
+
let u;
|
|
140
|
+
try {
|
|
141
|
+
u = new URL(candidate);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
if (u.protocol !== 'http:' && u.protocol !== 'https:')
|
|
147
|
+
return null;
|
|
148
|
+
const host = u.host.toLowerCase().replace(/^www\./, '');
|
|
149
|
+
if (!host.includes('.'))
|
|
150
|
+
return null; // localhost · single-label hosts
|
|
151
|
+
if (/^(localhost|127\.|10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/.test(host))
|
|
152
|
+
return null;
|
|
153
|
+
if (host === 'commit.show' || host.endsWith('.commit.show'))
|
|
154
|
+
return null;
|
|
155
|
+
return { origin: `${u.protocol}//${host}`, host };
|
|
156
|
+
}
|
|
116
157
|
function gitRemoteOrigin(cwd) {
|
|
117
158
|
try {
|
|
118
159
|
const out = execSync('git remote get-url origin', {
|
|
@@ -135,7 +176,7 @@ export function resolveTarget(rawArg, opts = {}) {
|
|
|
135
176
|
// Both normalized through the same path so the server receives a
|
|
136
177
|
// single shape.
|
|
137
178
|
const flagWorkspace = normalizeSubpath(opts.workspace ?? null);
|
|
138
|
-
// 1 · Explicit URL forms
|
|
179
|
+
// 1 · Explicit URL forms — github first (most common · most specific)
|
|
139
180
|
if (rawArg) {
|
|
140
181
|
const m = matchUrl(rawArg);
|
|
141
182
|
if (m) {
|
|
@@ -146,6 +187,18 @@ export function resolveTarget(rawArg, opts = {}) {
|
|
|
146
187
|
workspace: flagWorkspace ?? m.workspace ?? null,
|
|
147
188
|
};
|
|
148
189
|
}
|
|
190
|
+
// 1b · Site URL fast lane (§15-E) — anything URL-shaped that isn't
|
|
191
|
+
// github.com / a local path. Routes to audit-site-preview.
|
|
192
|
+
const site = matchSiteUrl(rawArg);
|
|
193
|
+
if (site) {
|
|
194
|
+
return {
|
|
195
|
+
kind: 'site-url',
|
|
196
|
+
github_url: '',
|
|
197
|
+
site_url: site.origin,
|
|
198
|
+
slug: site.host,
|
|
199
|
+
workspace: null,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
149
202
|
}
|
|
150
203
|
// 2 · Local path (arg resolves to a directory) or cwd
|
|
151
204
|
const path = resolve(rawArg ?? '.');
|