shippingszn 0.9.0 → 0.9.2
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 +40 -61
- package/dist/index.js +124 -76
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -6,65 +6,45 @@ leaked API keys, missing crawl assets, weak browser defenses, dangerous code
|
|
|
6
6
|
patterns, and last-mile polish gaps.
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
npx shippingszn
|
|
9
|
+
npx shippingszn@latest
|
|
10
10
|
# or
|
|
11
|
-
pnpm dlx shippingszn
|
|
11
|
+
pnpm dlx shippingszn@latest
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
The CLI **never writes, modifies, or deletes** any files - it only reads.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
Normal runs send one anonymous aggregate summary with score, severity counts,
|
|
16
|
+
files scanned, scanner version, and safe stack tags. It never uploads source
|
|
17
|
+
code, paths, filenames, repo URLs, project names, secrets, handles, or emails.
|
|
18
|
+
This aggregate signal is automatic and not user-configurable.
|
|
19
19
|
|
|
20
|
-
Human output starts with a Launch Readiness Score, scope covered,
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
Human output starts with a Launch Readiness Score, scope covered, and severity
|
|
21
|
+
counts. Run with `--json` to get the same score-and-counts summary in a
|
|
22
|
+
machine-readable shape:
|
|
23
23
|
|
|
24
24
|
```json
|
|
25
25
|
{
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"confidence": "high",
|
|
33
|
-
"coveragePenalty": 0,
|
|
34
|
-
"coverageSummary": "CLI scan covered 4 readiness areas with 4 areas requiring owner verification.",
|
|
35
|
-
"topNextStep": "Fix Lock up your API keys and passwords: Possible OpenAI API key hardcoded in source.",
|
|
36
|
-
"wallUrl": "https://shippingszn.com/wall",
|
|
37
|
-
"reportUrl": "https://shippingszn.com/report"
|
|
38
|
-
},
|
|
39
|
-
"findings": [
|
|
40
|
-
{
|
|
41
|
-
"severity": "critical",
|
|
42
|
-
"message": "Possible OpenAI API key hardcoded in source.",
|
|
43
|
-
"whatFailed": "A secret-like token appears in a scanned file.",
|
|
44
|
-
"whyItBlocksLaunch": "A leaked key can spend money or expose customer data before you notice."
|
|
45
|
-
}
|
|
46
|
-
]
|
|
26
|
+
"score": 43,
|
|
27
|
+
"band": "no_go",
|
|
28
|
+
"counts": { "critical": 1, "high": 3, "medium": 4, "lower": 2 },
|
|
29
|
+
"filesScanned": 128,
|
|
30
|
+
"detailsLocked": true,
|
|
31
|
+
"unlockUrl": "https://shippingszn.com/fix-kit"
|
|
47
32
|
}
|
|
48
33
|
```
|
|
49
34
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
paid report CTA.
|
|
35
|
+
Finding titles, files, evidence, fix prompts, and verification steps are paid.
|
|
36
|
+
They unlock inside the Launch Fix Kit.
|
|
53
37
|
|
|
54
|
-
##
|
|
38
|
+
## Aggregate summary
|
|
55
39
|
|
|
56
|
-
Plain `npx shippingszn`
|
|
57
|
-
anonymous and intentionally small: score, launch label, files
|
|
58
|
-
counts, detected stack tags, scanner version, and timestamp.
|
|
40
|
+
Plain `npx shippingszn@latest` sends the aggregate summary automatically. The
|
|
41
|
+
stored row is anonymous and intentionally small: score, launch label, files
|
|
42
|
+
scanned, finding counts, detected stack tags, scanner version, and timestamp.
|
|
59
43
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
For a paid-report handoff, run `npx shippingszn --json > shippingszn-scan.json`
|
|
67
|
-
and paste or upload the file at `https://shippingszn.com/scan`.
|
|
44
|
+
For a scan-specific paid-report handoff, run `npx shippingszn@latest --proof`.
|
|
45
|
+
That uploads the canonical scan result, returns a proof URL plus a
|
|
46
|
+
`/fix-kit?scanResultId=...` URL, and gives the paid Launch Fix Kit the evidence
|
|
47
|
+
it needs after checkout.
|
|
68
48
|
|
|
69
49
|
## What gets checked
|
|
70
50
|
|
|
@@ -90,8 +70,9 @@ maps back to one of the items on the checklist.
|
|
|
90
70
|
- Placeholder content (`lorem ipsum`, `John Doe`, `test@example.com`) and
|
|
91
71
|
`TODO` / `FIXME` / `XXX` / `HACK` comments.
|
|
92
72
|
|
|
93
|
-
|
|
94
|
-
relevant checklist item
|
|
73
|
+
Internally each finding is tagged Critical, High, Medium, or Lower and maps to
|
|
74
|
+
the relevant checklist item. The free CLI output keeps that finding-level detail
|
|
75
|
+
locked.
|
|
95
76
|
|
|
96
77
|
## Suppressing false positives
|
|
97
78
|
|
|
@@ -141,12 +122,12 @@ ship.
|
|
|
141
122
|
shippingszn [path] [options]
|
|
142
123
|
|
|
143
124
|
Options:
|
|
144
|
-
--json Output a machine-readable JSON
|
|
145
|
-
--
|
|
146
|
-
|
|
147
|
-
--base-url <url> Base URL used to build
|
|
125
|
+
--json Output a machine-readable JSON summary.
|
|
126
|
+
--proof Upload full scan evidence and return a scan-specific
|
|
127
|
+
Launch Fix Kit URL.
|
|
128
|
+
--base-url <url> Base URL used to build checkout and Fix Kit links.
|
|
148
129
|
--cwd <path> Directory to scan. Default: current working directory.
|
|
149
|
-
--no-color Disable ANSI colors in the human-readable
|
|
130
|
+
--no-color Disable ANSI colors in the human-readable summary.
|
|
150
131
|
-h, --help Show help.
|
|
151
132
|
-v, --version Print version.
|
|
152
133
|
```
|
|
@@ -161,22 +142,20 @@ This makes the CLI suitable for CI:
|
|
|
161
142
|
|
|
162
143
|
```yaml
|
|
163
144
|
# .github/workflows/launch-check.yml
|
|
164
|
-
- run: npx shippingszn --json > launch-check.json
|
|
145
|
+
- run: npx shippingszn@latest --json > launch-check.json
|
|
165
146
|
```
|
|
166
147
|
|
|
167
148
|
For PR scan signal, have GitHub Actions run the scanner and post the JSON
|
|
168
|
-
summary as a comment: score, severity counts,
|
|
169
|
-
URL, and worst Critical/High findings.
|
|
149
|
+
summary as a comment: score, severity counts, and unlock URL.
|
|
170
150
|
|
|
171
151
|
## Privacy
|
|
172
152
|
|
|
173
153
|
`shippingszn` reads files on your machine. It never uploads source code. By
|
|
174
|
-
default it makes one best-effort outbound request to post anonymous
|
|
175
|
-
score, launch label, files scanned count, finding counts by severity,
|
|
176
|
-
stack tags, scanner version, and timestamp. It never uploads source
|
|
177
|
-
paths, filenames, project names, repo URLs, secrets, emails, handles,
|
|
178
|
-
contents.
|
|
179
|
-
request.
|
|
154
|
+
default it makes one best-effort outbound request to post an anonymous aggregate
|
|
155
|
+
summary: score, launch label, files scanned count, finding counts by severity,
|
|
156
|
+
detected stack tags, scanner version, and timestamp. It never uploads source
|
|
157
|
+
code, file paths, filenames, project names, repo URLs, secrets, emails, handles,
|
|
158
|
+
or report contents. The aggregate summary is automatic and not user-configurable.
|
|
180
159
|
|
|
181
160
|
## License
|
|
182
161
|
|
package/dist/index.js
CHANGED
|
@@ -3598,7 +3598,10 @@ async function detectStack(cwd2) {
|
|
|
3598
3598
|
try {
|
|
3599
3599
|
const raw = await fs2.readFile(path17.join(cwd2, "package.json"), "utf8");
|
|
3600
3600
|
const pkg = JSON.parse(raw);
|
|
3601
|
-
const deps = {
|
|
3601
|
+
const deps = {
|
|
3602
|
+
...pkg.dependencies ?? {},
|
|
3603
|
+
...pkg.devDependencies ?? {}
|
|
3604
|
+
};
|
|
3602
3605
|
const has = (n) => n in deps;
|
|
3603
3606
|
if (has("react")) tags.add("react");
|
|
3604
3607
|
if (has("next")) tags.add("next");
|
|
@@ -3726,6 +3729,7 @@ function buildProofPayload(report, scannerVersion) {
|
|
|
3726
3729
|
checkedAt: report.generatedAt,
|
|
3727
3730
|
counts,
|
|
3728
3731
|
findings: report.findings.slice(0, MAX_FINDINGS).map((finding2) => ({
|
|
3732
|
+
itemId: finding2.itemId,
|
|
3729
3733
|
severity: finding2.severity,
|
|
3730
3734
|
title: clampText(finding2.itemTitle || finding2.checkId, 160),
|
|
3731
3735
|
body: clampText(finding2.message, 3e3),
|
|
@@ -3825,7 +3829,7 @@ async function uploadProof(report, opts) {
|
|
|
3825
3829
|
status: "uploaded",
|
|
3826
3830
|
id,
|
|
3827
3831
|
proofUrl: `${baseUrl}/proof/${encodeURIComponent(id)}`,
|
|
3828
|
-
reportUrl: `${baseUrl}/
|
|
3832
|
+
reportUrl: `${baseUrl}/fix-kit?${new URLSearchParams({ scanResultId: id }).toString()}`,
|
|
3829
3833
|
badgeMarkdown: buildBadgeMarkdown(
|
|
3830
3834
|
baseUrl,
|
|
3831
3835
|
id,
|
|
@@ -4137,21 +4141,7 @@ function locationLine(finding2) {
|
|
|
4137
4141
|
if (!finding2.file?.trim()) return "";
|
|
4138
4142
|
return finding2.line ? `${finding2.file.trim()}:${finding2.line}` : finding2.file.trim();
|
|
4139
4143
|
}
|
|
4140
|
-
function
|
|
4141
|
-
const where = input.location ? `Likely location: ${input.location}.` : "Inspect the relevant app, deploy, and configuration files before editing.";
|
|
4142
|
-
return [
|
|
4143
|
-
"You are fixing a shippingszn launch-readiness blocker in an AI-built app.",
|
|
4144
|
-
`Severity: ${input.severity}.`,
|
|
4145
|
-
`Finding: ${input.title}.`,
|
|
4146
|
-
`What failed: ${input.whatFailed}`,
|
|
4147
|
-
`Why it blocks launch: ${input.whyItBlocksLaunch}`,
|
|
4148
|
-
where,
|
|
4149
|
-
`Fix exactly this: ${input.fixInstructions}`,
|
|
4150
|
-
`Verification required: ${input.verificationStep}`,
|
|
4151
|
-
"Make the smallest production-safe change, preserve existing behavior, list files changed, and do not suppress the scanner unless you prove a false positive."
|
|
4152
|
-
].join(" ");
|
|
4153
|
-
}
|
|
4154
|
-
function normalizeLaunchFinding(finding2, source = "manual") {
|
|
4144
|
+
function normalizeLaunchFinding(finding2, source = "manual", promptBuilder) {
|
|
4155
4145
|
const severity = canonicalSeverity(finding2.severity);
|
|
4156
4146
|
const title = clampText2(
|
|
4157
4147
|
finding2.title ?? finding2.itemTitle,
|
|
@@ -4182,7 +4172,7 @@ function normalizeLaunchFinding(finding2, source = "manual") {
|
|
|
4182
4172
|
const location = locationLine(finding2);
|
|
4183
4173
|
const aiBuilderPrompt = clampText2(
|
|
4184
4174
|
finding2.aiBuilderPrompt,
|
|
4185
|
-
|
|
4175
|
+
promptBuilder ? promptBuilder({
|
|
4186
4176
|
severity,
|
|
4187
4177
|
title,
|
|
4188
4178
|
whatFailed,
|
|
@@ -4190,7 +4180,7 @@ function normalizeLaunchFinding(finding2, source = "manual") {
|
|
|
4190
4180
|
fixInstructions,
|
|
4191
4181
|
verificationStep,
|
|
4192
4182
|
location
|
|
4193
|
-
}),
|
|
4183
|
+
}) : "",
|
|
4194
4184
|
4e3
|
|
4195
4185
|
);
|
|
4196
4186
|
const evidence2 = clampText2(
|
|
@@ -4224,8 +4214,8 @@ function normalizeLaunchFinding(finding2, source = "manual") {
|
|
|
4224
4214
|
...finding2.itemTitle ? { itemTitle: finding2.itemTitle } : {}
|
|
4225
4215
|
};
|
|
4226
4216
|
}
|
|
4227
|
-
function prioritizeLaunchFindings(findings, source = "manual") {
|
|
4228
|
-
return findings.map((finding2) => normalizeLaunchFinding(finding2, source)).sort((a, b) => {
|
|
4217
|
+
function prioritizeLaunchFindings(findings, source = "manual", promptBuilder) {
|
|
4218
|
+
return findings.map((finding2) => normalizeLaunchFinding(finding2, source, promptBuilder)).sort((a, b) => {
|
|
4229
4219
|
const bySeverity = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
|
|
4230
4220
|
if (bySeverity !== 0) return bySeverity;
|
|
4231
4221
|
return a.title.localeCompare(b.title);
|
|
@@ -4286,9 +4276,13 @@ function aggregateConfidence(source, findings) {
|
|
|
4286
4276
|
if (source === "url") return "medium";
|
|
4287
4277
|
return "high";
|
|
4288
4278
|
}
|
|
4289
|
-
function assessLaunchReadiness(input) {
|
|
4279
|
+
function assessLaunchReadiness(input, promptBuilder) {
|
|
4290
4280
|
const source = input.source ?? "manual";
|
|
4291
|
-
const findings = prioritizeLaunchFindings(
|
|
4281
|
+
const findings = prioritizeLaunchFindings(
|
|
4282
|
+
input.findings ?? [],
|
|
4283
|
+
source,
|
|
4284
|
+
promptBuilder
|
|
4285
|
+
);
|
|
4292
4286
|
const counts = input.counts ? normalizeLaunchCounts(input.counts) : countLaunchFindings(findings);
|
|
4293
4287
|
const { score, rawScore, weightedPenalty, coveragePenalty } = scoreFromCounts(
|
|
4294
4288
|
counts,
|
|
@@ -4320,7 +4314,11 @@ function assessLaunchReadiness(input) {
|
|
|
4320
4314
|
coverageSummary: coverageSummary(source, coverage),
|
|
4321
4315
|
checkedAreas,
|
|
4322
4316
|
uncheckedAreas,
|
|
4323
|
-
topNextStep: topNextStep(
|
|
4317
|
+
topNextStep: topNextStep(
|
|
4318
|
+
blockers.length ? blockers : findings,
|
|
4319
|
+
counts,
|
|
4320
|
+
source
|
|
4321
|
+
),
|
|
4324
4322
|
reportRecommended,
|
|
4325
4323
|
confidence: aggregateConfidence(source, findings),
|
|
4326
4324
|
blockers,
|
|
@@ -4328,6 +4326,26 @@ function assessLaunchReadiness(input) {
|
|
|
4328
4326
|
};
|
|
4329
4327
|
}
|
|
4330
4328
|
|
|
4329
|
+
// ../../lib/launch-readiness/src/server-prompts.ts
|
|
4330
|
+
var defaultPrompt = (input) => {
|
|
4331
|
+
const where = input.location ? `Likely location: ${input.location}.` : "Inspect the relevant app, deploy, and configuration files before editing.";
|
|
4332
|
+
return [
|
|
4333
|
+
"You are fixing a shippingszn launch-readiness blocker in an AI-built app.",
|
|
4334
|
+
`Severity: ${input.severity}.`,
|
|
4335
|
+
`Finding: ${input.title}.`,
|
|
4336
|
+
`What failed: ${input.whatFailed}`,
|
|
4337
|
+
`Why it blocks launch: ${input.whyItBlocksLaunch}`,
|
|
4338
|
+
where,
|
|
4339
|
+
`Fix exactly this: ${input.fixInstructions}`,
|
|
4340
|
+
`Verification required: ${input.verificationStep}`,
|
|
4341
|
+
"Make the smallest production-safe change, preserve existing behavior, list files changed, and do not suppress the scanner unless you prove a false positive."
|
|
4342
|
+
].join(" ");
|
|
4343
|
+
};
|
|
4344
|
+
|
|
4345
|
+
// ../../lib/launch-readiness/src/server.ts
|
|
4346
|
+
var normalizeLaunchFinding2 = (finding2, source, promptBuilder) => normalizeLaunchFinding(finding2, source, promptBuilder ?? defaultPrompt);
|
|
4347
|
+
var assessLaunchReadiness2 = (input, promptBuilder) => assessLaunchReadiness(input, promptBuilder ?? defaultPrompt);
|
|
4348
|
+
|
|
4331
4349
|
// src/index.ts
|
|
4332
4350
|
var UNTRACKED_DOWNGRADE = {
|
|
4333
4351
|
critical: "lower",
|
|
@@ -4364,7 +4382,6 @@ function parseArgs(argv2) {
|
|
|
4364
4382
|
help: false,
|
|
4365
4383
|
version: false,
|
|
4366
4384
|
noColor: !!process2.env.NO_COLOR,
|
|
4367
|
-
publish: !publishDisabledByEnv(),
|
|
4368
4385
|
proof: shouldUploadProof()
|
|
4369
4386
|
};
|
|
4370
4387
|
for (let i = 0; i < argv2.length; i++) {
|
|
@@ -4372,31 +4389,16 @@ function parseArgs(argv2) {
|
|
|
4372
4389
|
if (a === "--help" || a === "-h") opts.help = true;
|
|
4373
4390
|
else if (a === "--version" || a === "-v") opts.version = true;
|
|
4374
4391
|
else if (a === "--json") opts.json = true;
|
|
4375
|
-
else if (a === "--publish") opts.publish = true;
|
|
4376
|
-
else if (a === "--no-publish") opts.publish = false;
|
|
4377
4392
|
else if (a === "--proof") opts.proof = true;
|
|
4378
4393
|
else if (a === "--no-color") opts.noColor = true;
|
|
4394
|
+
else if (a === "--no-publish" || a === "--publish")
|
|
4395
|
+
opts.unsupportedWallFlag = a;
|
|
4379
4396
|
else if (a === "--base-url") opts.baseUrl = argv2[++i] ?? opts.baseUrl;
|
|
4380
4397
|
else if (a === "--cwd") opts.cwd = path19.resolve(argv2[++i] ?? opts.cwd);
|
|
4381
4398
|
else if (!a.startsWith("-")) opts.cwd = path19.resolve(a);
|
|
4382
4399
|
}
|
|
4383
4400
|
return opts;
|
|
4384
4401
|
}
|
|
4385
|
-
function envTruthy(value) {
|
|
4386
|
-
if (!value) return false;
|
|
4387
|
-
const normalized = value.trim().toLowerCase();
|
|
4388
|
-
return normalized === "1" || normalized === "true" || normalized === "yes";
|
|
4389
|
-
}
|
|
4390
|
-
function envFalsey(value) {
|
|
4391
|
-
if (!value) return false;
|
|
4392
|
-
const normalized = value.trim().toLowerCase();
|
|
4393
|
-
return normalized === "0" || normalized === "false" || normalized === "no";
|
|
4394
|
-
}
|
|
4395
|
-
function publishDisabledByEnv() {
|
|
4396
|
-
if (envTruthy(process2.env.SHIPPINGSZN_DISABLE_PUBLISH)) return true;
|
|
4397
|
-
if (envFalsey(process2.env.SHIPPINGSZN_PUBLISH)) return true;
|
|
4398
|
-
return false;
|
|
4399
|
-
}
|
|
4400
4402
|
function color(enabled) {
|
|
4401
4403
|
const wrap = (codes) => (s) => enabled ? `\x1B[${codes}m${s}\x1B[0m` : s;
|
|
4402
4404
|
return {
|
|
@@ -4420,13 +4422,13 @@ Read-only scanner that checks the current project against a small set of
|
|
|
4420
4422
|
high-signal launch-readiness items from shippingszn.
|
|
4421
4423
|
|
|
4422
4424
|
Usage:
|
|
4423
|
-
npx shippingszn [path] [options]
|
|
4425
|
+
npx shippingszn@latest [path] [options]
|
|
4424
4426
|
|
|
4425
4427
|
Options:
|
|
4426
4428
|
--json Output a machine-readable JSON summary.
|
|
4427
|
-
--
|
|
4428
|
-
|
|
4429
|
-
--base-url <url> Base URL used to build
|
|
4429
|
+
--proof Upload full scan evidence and return a scan-specific
|
|
4430
|
+
Launch Fix Kit URL.
|
|
4431
|
+
--base-url <url> Base URL used to build checkout and Fix Kit links.
|
|
4430
4432
|
(default: ${DEFAULT_BASE_URL2})
|
|
4431
4433
|
--cwd <path> Directory to scan. Default: current working directory.
|
|
4432
4434
|
--no-color Disable ANSI colors in the human-readable summary.
|
|
@@ -4437,7 +4439,9 @@ This is the FREE preflight. It tells you launch debt exists. It does NOT tell
|
|
|
4437
4439
|
you what's broken or how to fix it \u2014 that's gated behind the $49 Launch Fix Kit
|
|
4438
4440
|
(unlocks full findings + 46-item workbook + paste-ready AI-builder prompts +
|
|
4439
4441
|
verification steps + red-flag checks). Read-only on disk. Anonymous aggregate
|
|
4440
|
-
counts
|
|
4442
|
+
counts (score, severity counts, file count, scanner version \u2014 no project name,
|
|
4443
|
+
no paths, no findings) are sent on every normal run; there is no publish opt-in
|
|
4444
|
+
or opt-out flag. Exit code non-zero if any critical findings.
|
|
4441
4445
|
`
|
|
4442
4446
|
);
|
|
4443
4447
|
}
|
|
@@ -4455,6 +4459,13 @@ async function run() {
|
|
|
4455
4459
|
`);
|
|
4456
4460
|
return 0;
|
|
4457
4461
|
}
|
|
4462
|
+
if (opts.unsupportedWallFlag) {
|
|
4463
|
+
process2.stderr.write(
|
|
4464
|
+
`${opts.unsupportedWallFlag} is not supported. shippingszn sends anonymous score and severity counts on normal runs; it never uploads code, file paths, findings, project names, repo URLs, secrets, emails, or handles.
|
|
4465
|
+
`
|
|
4466
|
+
);
|
|
4467
|
+
return 2;
|
|
4468
|
+
}
|
|
4458
4469
|
const c = color(!opts.noColor && process2.stdout.isTTY === true && !opts.json);
|
|
4459
4470
|
const files = await listFiles(opts.cwd);
|
|
4460
4471
|
const tracked = getTrackedFiles(opts.cwd);
|
|
@@ -4478,14 +4489,14 @@ async function run() {
|
|
|
4478
4489
|
const fixKitUrl = `${trimmedBaseUrl}/fix-kit`;
|
|
4479
4490
|
const reportUrl = fixKitUrl;
|
|
4480
4491
|
const proofCreatePath = `${trimmedBaseUrl}/scan`;
|
|
4481
|
-
const proofUploadHint = "Run `npx shippingszn --proof`
|
|
4492
|
+
const proofUploadHint = "Run `npx shippingszn@latest --proof` to generate a scan-specific Launch Fix Kit URL.";
|
|
4482
4493
|
const tracked_aware = applyTrackingAwareSeverity(all, tracked);
|
|
4483
4494
|
const source = scanSource();
|
|
4484
4495
|
const enriched = tracked_aware.map((f) => {
|
|
4485
4496
|
const item = CHECKLIST_ITEMS[f.itemId];
|
|
4486
4497
|
const itemTitle = item?.title ?? f.itemId;
|
|
4487
4498
|
const permalink = permalinkFor(f.itemId, opts.baseUrl);
|
|
4488
|
-
const normalized =
|
|
4499
|
+
const normalized = normalizeLaunchFinding2(
|
|
4489
4500
|
{
|
|
4490
4501
|
severity: f.severity,
|
|
4491
4502
|
title: itemTitle,
|
|
@@ -4550,7 +4561,7 @@ async function run() {
|
|
|
4550
4561
|
lower: 0
|
|
4551
4562
|
};
|
|
4552
4563
|
for (const f of enriched) totals[f.severity]++;
|
|
4553
|
-
const assessment =
|
|
4564
|
+
const assessment = assessLaunchReadiness2({
|
|
4554
4565
|
source,
|
|
4555
4566
|
findings: enriched,
|
|
4556
4567
|
counts: totals
|
|
@@ -4607,7 +4618,7 @@ async function run() {
|
|
|
4607
4618
|
let publishResult = "skipped";
|
|
4608
4619
|
try {
|
|
4609
4620
|
const proofAlreadyPublishedWall = proofResult.status === "uploaded" && !!proofResult.wallUrl;
|
|
4610
|
-
if (
|
|
4621
|
+
if (!proofAlreadyPublishedWall) {
|
|
4611
4622
|
publishResult = await publishScan(totals, files.length, {
|
|
4612
4623
|
cwd: opts.cwd,
|
|
4613
4624
|
baseUrl: opts.baseUrl,
|
|
@@ -4627,19 +4638,31 @@ async function run() {
|
|
|
4627
4638
|
const ownerVerifyAreas = CHECKLIST.filter(
|
|
4628
4639
|
(item) => item.cliCoverage === "manual_only"
|
|
4629
4640
|
).length;
|
|
4630
|
-
const
|
|
4631
|
-
|
|
4632
|
-
no_go: "NO-GO",
|
|
4641
|
+
const SCORE_BAND_LABEL = {
|
|
4642
|
+
blocked: "NO-GO",
|
|
4633
4643
|
fix_first: "FIX FIRST",
|
|
4634
|
-
|
|
4644
|
+
verify_first: "VERIFY BEFORE LAUNCH",
|
|
4635
4645
|
launchable: "LAUNCHABLE"
|
|
4636
|
-
}
|
|
4646
|
+
};
|
|
4647
|
+
const CLI_BAND_ID = {
|
|
4648
|
+
blocked: "no_go",
|
|
4649
|
+
fix_first: "fix_first",
|
|
4650
|
+
verify_first: "verify_before_launch",
|
|
4651
|
+
launchable: "launchable"
|
|
4652
|
+
};
|
|
4653
|
+
const canonicalBand = SCORE_BANDS.find(
|
|
4654
|
+
(b) => launchReadiness.score >= b.minScore && launchReadiness.score <= b.maxScore
|
|
4655
|
+
) ?? SCORE_BANDS[0];
|
|
4656
|
+
const band = CLI_BAND_ID[canonicalBand.id];
|
|
4657
|
+
const bandLabel = SCORE_BAND_LABEL[canonicalBand.id];
|
|
4637
4658
|
const wallPublishedViaProof = proofResult.status === "uploaded" && !!proofResult.wallUrl;
|
|
4638
4659
|
const wallStatus = wallPublishedViaProof ? "published" : proofResult.status === "uploaded" && proofResult.wallPublishError ? "failed" : publishResult;
|
|
4639
4660
|
const wallUrl = wallPublishedViaProof ? proofResult.wallUrl : publishResult === "published" ? `${trimmedBaseUrl}/wall` : void 0;
|
|
4640
4661
|
const wallError = proofResult.wallPublishError;
|
|
4662
|
+
const scanSpecificUnlockUrl = proofResult.status === "uploaded" && proofResult.reportUrl ? proofResult.reportUrl : void 0;
|
|
4663
|
+
const unlockUrl = scanSpecificUnlockUrl ?? fixKitUrl;
|
|
4641
4664
|
if (opts.json) {
|
|
4642
|
-
const
|
|
4665
|
+
const scoreSummary = {
|
|
4643
4666
|
score: launchReadiness.score,
|
|
4644
4667
|
band,
|
|
4645
4668
|
counts: { ...totals },
|
|
@@ -4651,7 +4674,7 @@ async function run() {
|
|
|
4651
4674
|
},
|
|
4652
4675
|
scannerVersion: PKG_VERSION,
|
|
4653
4676
|
detailsLocked: true,
|
|
4654
|
-
unlockUrl
|
|
4677
|
+
unlockUrl,
|
|
4655
4678
|
wall: {
|
|
4656
4679
|
status: wallStatus,
|
|
4657
4680
|
...wallUrl ? { url: wallUrl } : {},
|
|
@@ -4661,12 +4684,13 @@ async function run() {
|
|
|
4661
4684
|
status: proofResult.status,
|
|
4662
4685
|
...proofResult.status === "uploaded" ? {
|
|
4663
4686
|
url: proofResult.proofUrl,
|
|
4664
|
-
resultId: proofResult.id
|
|
4687
|
+
resultId: proofResult.id,
|
|
4688
|
+
reportUrl: proofResult.reportUrl
|
|
4665
4689
|
} : {},
|
|
4666
4690
|
...proofResult.status === "failed" && proofResult.error ? { error: proofResult.error } : {}
|
|
4667
4691
|
}
|
|
4668
4692
|
};
|
|
4669
|
-
process2.stdout.write(JSON.stringify(
|
|
4693
|
+
process2.stdout.write(JSON.stringify(scoreSummary, null, 2) + "\n");
|
|
4670
4694
|
return totals.critical > 0 ? 1 : 0;
|
|
4671
4695
|
}
|
|
4672
4696
|
const bandColor = band === "no_go" ? c.red : band === "fix_first" ? c.yellow : band === "verify_before_launch" ? c.blue : c.green;
|
|
@@ -4708,26 +4732,51 @@ ${c.bold("shippingszn")} ${c.dim(`v${PKG_VERSION}`)}
|
|
|
4708
4732
|
`)
|
|
4709
4733
|
);
|
|
4710
4734
|
if (totals.critical + totals.high + totals.medium + totals.lower > 0) {
|
|
4711
|
-
|
|
4712
|
-
c.bold(
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4735
|
+
if (scanSpecificUnlockUrl) {
|
|
4736
|
+
process2.stdout.write(c.bold("Unlock this exact scan:\n"));
|
|
4737
|
+
process2.stdout.write(` ${c.cyan(scanSpecificUnlockUrl)}
|
|
4738
|
+
`);
|
|
4739
|
+
process2.stdout.write(
|
|
4740
|
+
c.dim(
|
|
4741
|
+
" Full findings, file evidence, and AI-builder prompts stay locked until purchase.\n\n"
|
|
4742
|
+
)
|
|
4743
|
+
);
|
|
4744
|
+
} else {
|
|
4745
|
+
process2.stdout.write(c.bold("Full findings are locked:\n"));
|
|
4746
|
+
process2.stdout.write(` ${c.cyan(fixKitUrl)}
|
|
4718
4747
|
`);
|
|
4748
|
+
process2.stdout.write(
|
|
4749
|
+
c.dim(
|
|
4750
|
+
" For a scan-specific Fix Kit link, rerun: npx shippingszn@latest --proof\n\n"
|
|
4751
|
+
)
|
|
4752
|
+
);
|
|
4753
|
+
}
|
|
4719
4754
|
} else {
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
"No findings detected
|
|
4723
|
-
)
|
|
4724
|
-
|
|
4725
|
-
|
|
4755
|
+
if (scanSpecificUnlockUrl) {
|
|
4756
|
+
process2.stdout.write(
|
|
4757
|
+
c.green("No automated findings detected for this scan.\n")
|
|
4758
|
+
);
|
|
4759
|
+
process2.stdout.write(c.bold("Unlock the owner-verification Fix Kit:\n"));
|
|
4760
|
+
process2.stdout.write(` ${c.cyan(scanSpecificUnlockUrl)}
|
|
4726
4761
|
|
|
4727
4762
|
`);
|
|
4763
|
+
} else {
|
|
4764
|
+
process2.stdout.write(
|
|
4765
|
+
c.green(
|
|
4766
|
+
"No automated findings detected. The Launch Fix Kit covers the 27 owner-verify areas the scanner can't reach:\n"
|
|
4767
|
+
)
|
|
4768
|
+
);
|
|
4769
|
+
process2.stdout.write(` ${c.cyan(fixKitUrl)}
|
|
4770
|
+
`);
|
|
4771
|
+
process2.stdout.write(
|
|
4772
|
+
c.dim(
|
|
4773
|
+
" For a scan-specific Fix Kit link, rerun: npx shippingszn@latest --proof\n\n"
|
|
4774
|
+
)
|
|
4775
|
+
);
|
|
4776
|
+
}
|
|
4728
4777
|
}
|
|
4729
4778
|
if (proofResult.status === "uploaded" && proofResult.proofUrl) {
|
|
4730
|
-
process2.stdout.write(c.dim(`
|
|
4779
|
+
process2.stdout.write(c.dim(`Share score proof: ${proofResult.proofUrl}
|
|
4731
4780
|
`));
|
|
4732
4781
|
} else if (proofResult.status === "failed") {
|
|
4733
4782
|
process2.stdout.write(
|
|
@@ -4737,8 +4786,7 @@ ${c.bold("shippingszn")} ${c.dim(`v${PKG_VERSION}`)}
|
|
|
4737
4786
|
}
|
|
4738
4787
|
if (publishResult === "published") {
|
|
4739
4788
|
process2.stdout.write(
|
|
4740
|
-
c.dim(
|
|
4741
|
-
`)
|
|
4789
|
+
c.dim("(Anonymous aggregate score sent to shippingszn.)\n")
|
|
4742
4790
|
);
|
|
4743
4791
|
}
|
|
4744
4792
|
if (totals.critical > 0) {
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shippingszn",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"description": "Read-only CLI scanner that checks a project for common pre-launch issues from the shippingszn.com launch checklist.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"shippingszn": "
|
|
8
|
+
"shippingszn": "dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"repository": {
|
|
32
32
|
"type": "git",
|
|
33
|
-
"url": "https://github.com/keeptahoeblueish/shippingszn-cli.git"
|
|
33
|
+
"url": "git+https://github.com/keeptahoeblueish/shippingszn-cli.git"
|
|
34
34
|
},
|
|
35
35
|
"engines": {
|
|
36
36
|
"node": ">=18"
|