mapterrain 0.1.1 → 0.1.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 +2 -0
- package/bin/terrain-installer.js +117 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,6 +58,8 @@ terrain impact "What validations matter for this change?"
|
|
|
58
58
|
terrain explain "Why did Terrain make this decision?"
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
> **About the example outputs below.** The CLI dumps in this section illustrate the *shape* of Terrain's reports on a large pandas-style repository — they are not literal output from a single live run. A few specific signals shown (`xfailAccumulation` age, statistical flaky-test failure rates, the `0.91+` duplicate similarity threshold) are marked `[experimental]` or `[planned]` in 0.1.2; see [docs/release/feature-status.md](docs/release/feature-status.md) for what's stable, what's experimental, and what's planned. The headline "30 seconds" promise refers to small-to-medium repos (≤ 1,000 test files) on commodity hardware; expect 5–15 seconds on a typical service repo and longer on monorepos.
|
|
62
|
+
|
|
61
63
|
### 1. Analyze — understand the test system
|
|
62
64
|
|
|
63
65
|
```bash
|
package/bin/terrain-installer.js
CHANGED
|
@@ -78,6 +78,116 @@ function archiveDownloadUrl(version) {
|
|
|
78
78
|
return `${baseUrl}/v${version}/${archiveFileName(version)}`;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
function signatureDownloadUrl(version) {
|
|
82
|
+
return `${archiveDownloadUrl(version)}.sig`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function certificateDownloadUrl(version) {
|
|
86
|
+
return `${archiveDownloadUrl(version)}.pem`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function expectedSignerIdentity(version) {
|
|
90
|
+
// The keyless Sigstore signature is anchored to the GitHub Actions workflow
|
|
91
|
+
// that ran goreleaser at release time. The workflow runs on the v<version>
|
|
92
|
+
// tag, so the OIDC subject identity is deterministic.
|
|
93
|
+
return (
|
|
94
|
+
`https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}` +
|
|
95
|
+
`/.github/workflows/release.yml@refs/tags/v${version}`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const SIGSTORE_OIDC_ISSUER = 'https://token.actions.githubusercontent.com';
|
|
100
|
+
|
|
101
|
+
function isCosignAvailable() {
|
|
102
|
+
try {
|
|
103
|
+
execFileSync('cosign', ['version'], { stdio: 'pipe' });
|
|
104
|
+
return true;
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Best-effort signature verification. In 0.1.2 this is warn-only: a missing
|
|
111
|
+
// cosign, missing signature artifact, or verification failure logs to stderr
|
|
112
|
+
// and does NOT block install. The signing pipeline is still maturing and we
|
|
113
|
+
// don't want to break npm installs while it stabilises.
|
|
114
|
+
//
|
|
115
|
+
// In 0.2 this becomes hard-fail unless TERRAIN_INSTALLER_SKIP_VERIFY=1 is set,
|
|
116
|
+
// at which point the warning escalates to an error.
|
|
117
|
+
async function verifySignatureBestEffort({
|
|
118
|
+
archivePath,
|
|
119
|
+
version,
|
|
120
|
+
tempDir,
|
|
121
|
+
quiet,
|
|
122
|
+
env,
|
|
123
|
+
}) {
|
|
124
|
+
if (env.TERRAIN_INSTALLER_SKIP_VERIFY === '1') {
|
|
125
|
+
log(
|
|
126
|
+
'Skipping signature verification (TERRAIN_INSTALLER_SKIP_VERIFY=1).',
|
|
127
|
+
quiet
|
|
128
|
+
);
|
|
129
|
+
return { verified: false, reason: 'skipped-by-env' };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (!isCosignAvailable()) {
|
|
133
|
+
log(
|
|
134
|
+
'cosign not found on PATH; skipping signature verification. ' +
|
|
135
|
+
'Install cosign (https://github.com/sigstore/cosign) for stronger ' +
|
|
136
|
+
'integrity guarantees in future releases.',
|
|
137
|
+
quiet
|
|
138
|
+
);
|
|
139
|
+
return { verified: false, reason: 'cosign-missing' };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const sigPath = path.join(tempDir, `${path.basename(archivePath)}.sig`);
|
|
143
|
+
const certPath = path.join(tempDir, `${path.basename(archivePath)}.pem`);
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
await downloadFile(signatureDownloadUrl(version), sigPath);
|
|
147
|
+
await downloadFile(certificateDownloadUrl(version), certPath);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
log(
|
|
150
|
+
`Could not fetch signature artifacts (${error.message}); ` +
|
|
151
|
+
'skipping verification.',
|
|
152
|
+
quiet
|
|
153
|
+
);
|
|
154
|
+
return { verified: false, reason: 'sig-download-failed' };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
execFileSync(
|
|
159
|
+
'cosign',
|
|
160
|
+
[
|
|
161
|
+
'verify-blob',
|
|
162
|
+
'--certificate',
|
|
163
|
+
certPath,
|
|
164
|
+
'--signature',
|
|
165
|
+
sigPath,
|
|
166
|
+
'--certificate-identity',
|
|
167
|
+
expectedSignerIdentity(version),
|
|
168
|
+
'--certificate-oidc-issuer',
|
|
169
|
+
SIGSTORE_OIDC_ISSUER,
|
|
170
|
+
archivePath,
|
|
171
|
+
],
|
|
172
|
+
{ stdio: 'pipe' }
|
|
173
|
+
);
|
|
174
|
+
log(
|
|
175
|
+
`Verified Sigstore signature for ${path.basename(archivePath)}.`,
|
|
176
|
+
quiet
|
|
177
|
+
);
|
|
178
|
+
return { verified: true, reason: 'ok' };
|
|
179
|
+
} catch (error) {
|
|
180
|
+
log(
|
|
181
|
+
`WARNING: cosign verify-blob failed for ${path.basename(archivePath)}. ` +
|
|
182
|
+
'The downloaded archive may be tampered with. Continuing install ' +
|
|
183
|
+
'(verification will become mandatory in 0.2). Error: ' +
|
|
184
|
+
(error.stderr ? error.stderr.toString().trim() : error.message),
|
|
185
|
+
quiet
|
|
186
|
+
);
|
|
187
|
+
return { verified: false, reason: 'verify-failed' };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
81
191
|
async function ensureDirectory(dir) {
|
|
82
192
|
await fs.mkdir(dir, { recursive: true });
|
|
83
193
|
}
|
|
@@ -233,6 +343,13 @@ export async function ensureTerrainBinary({
|
|
|
233
343
|
quiet
|
|
234
344
|
);
|
|
235
345
|
await downloadFile(archiveDownloadUrl(version), archivePath);
|
|
346
|
+
await verifySignatureBestEffort({
|
|
347
|
+
archivePath,
|
|
348
|
+
version,
|
|
349
|
+
tempDir,
|
|
350
|
+
quiet,
|
|
351
|
+
env,
|
|
352
|
+
});
|
|
236
353
|
await ensureDirectory(extractDir);
|
|
237
354
|
extractArchive(archivePath, extractDir);
|
|
238
355
|
|