| 412 |
412 |
|
/// Checks only what crates.io records permanently. A published version cannot
|
| 413 |
413 |
|
/// be edited, only yanked, and yanking does not correct a wrong URL — so these
|
| 414 |
414 |
|
/// are the last moment any of it can be fixed.
|
| 415 |
|
- |
pub fn crate_publish_problems(meta: &CrateMeta, repo_clonable: bool, published: &[String]) -> Vec<String> {
|
|
415 |
+ |
pub fn crate_publish_problems(
|
|
416 |
+ |
meta: &CrateMeta,
|
|
417 |
+ |
repo_clonable: bool,
|
|
418 |
+ |
published: &[String],
|
|
419 |
+ |
credentials_present: bool,
|
|
420 |
+ |
) -> Vec<String> {
|
| 416 |
421 |
|
let mut out = Vec::new();
|
|
422 |
+ |
if !credentials_present {
|
|
423 |
+ |
out.push(
|
|
424 |
+ |
"no crates.io credentials on the publishing host: `cargo login` there first. \
|
|
425 |
+ |
Checked now rather than at the upload, so this fails in seconds instead of \
|
|
426 |
+ |
after a full build and verify."
|
|
427 |
+ |
.to_string(),
|
|
428 |
+ |
);
|
|
429 |
+ |
}
|
| 417 |
430 |
|
match &meta.repository {
|
| 418 |
431 |
|
None => out.push(
|
| 419 |
432 |
|
"no `repository` field: the crates.io page will show no source link, permanently"
|
| 632 |
645 |
|
.unwrap_or(false)
|
| 633 |
646 |
|
});
|
| 634 |
647 |
|
|
|
648 |
+ |
// Ask the publishing host whether cargo has credentials, rather
|
|
649 |
+ |
// than moving the token anywhere. It stays in cargo's own 0600
|
|
650 |
+ |
// store; a shell line carrying it would be visible in `ps`.
|
|
651 |
+ |
let creds = ctx
|
|
652 |
+ |
.run(&ctx.build_host.clone(), "cargo login --help >/dev/null 2>&1 && \
|
|
653 |
+ |
test -s \"${CARGO_HOME:-$HOME/.cargo}/credentials.toml\" \
|
|
654 |
+ |
|| test -s \"${CARGO_HOME:-$HOME/.cargo}/credentials\"")
|
|
655 |
+ |
.map(|(code, _)| code == 0)
|
|
656 |
+ |
.unwrap_or(false);
|
|
657 |
+ |
|
| 635 |
658 |
|
let published = ctx.published_versions(&meta.name);
|
| 636 |
|
- |
let problems = crate_publish_problems(&meta, clonable, &published);
|
|
659 |
+ |
let problems = crate_publish_problems(&meta, clonable, &published, creds);
|
| 637 |
660 |
|
if !problems.is_empty() {
|
| 638 |
661 |
|
return Err(format!(
|
| 639 |
662 |
|
"{} {} is not safe to publish:\n - {}",
|
| 1178 |
1201 |
|
description: Some("d".into()),
|
| 1179 |
1202 |
|
licensed: true,
|
| 1180 |
1203 |
|
};
|
| 1181 |
|
- |
let problems = crate_publish_problems(&meta, false, &[]);
|
|
1204 |
+ |
let problems = crate_publish_problems(&meta, false, &[], true);
|
| 1182 |
1205 |
|
assert_eq!(problems.len(), 1, "{problems:?}");
|
| 1183 |
1206 |
|
assert!(problems[0].contains("not publicly clonable"), "{problems:?}");
|
| 1184 |
1207 |
|
|
| 1185 |
1208 |
|
// Same metadata, reachable URL: nothing to report.
|
| 1186 |
|
- |
assert!(crate_publish_problems(&meta, true, &[]).is_empty());
|
|
1209 |
+ |
assert!(crate_publish_problems(&meta, true, &[], true).is_empty());
|
| 1187 |
1210 |
|
}
|
| 1188 |
1211 |
|
|
| 1189 |
1212 |
|
#[test]
|
| 1195 |
1218 |
|
description: None,
|
| 1196 |
1219 |
|
licensed: false,
|
| 1197 |
1220 |
|
};
|
| 1198 |
|
- |
let problems = crate_publish_problems(&bare, false, &[]);
|
|
1221 |
+ |
let problems = crate_publish_problems(&bare, false, &[], true);
|
| 1199 |
1222 |
|
assert_eq!(problems.len(), 3, "{problems:?}");
|
| 1200 |
1223 |
|
assert!(problems.iter().any(|p| p.contains("repository")));
|
| 1201 |
1224 |
|
assert!(problems.iter().any(|p| p.contains("description")));
|
| 1211 |
1234 |
|
description: Some("d".into()),
|
| 1212 |
1235 |
|
licensed: true,
|
| 1213 |
1236 |
|
};
|
| 1214 |
|
- |
let problems = crate_publish_problems(&meta, true, &["0.9.0".into(), "0.10.0".into()]);
|
|
1237 |
+ |
let problems = crate_publish_problems(&meta, true, &["0.9.0".into(), "0.10.0".into()], true);
|
| 1215 |
1238 |
|
assert_eq!(problems.len(), 1, "{problems:?}");
|
| 1216 |
1239 |
|
assert!(problems[0].contains("already published"), "{problems:?}");
|
| 1217 |
1240 |
|
|
| 1218 |
1241 |
|
// An unreleased version against the same history is fine.
|
| 1219 |
1242 |
|
let mut next = meta.clone();
|
| 1220 |
1243 |
|
next.version = "0.11.0".into();
|
| 1221 |
|
- |
assert!(crate_publish_problems(&next, true, &["0.10.0".into()]).is_empty());
|
|
1244 |
+ |
assert!(crate_publish_problems(&next, true, &["0.10.0".into()], true).is_empty());
|
|
1245 |
+ |
}
|
|
1246 |
+ |
|
|
1247 |
+ |
/// Missing credentials must surface at preflight, not at the upload. The
|
|
1248 |
+ |
/// publish step is the irreversible one and runs last, after a full build
|
|
1249 |
+ |
/// and verify; discovering there that cargo cannot authenticate wastes the
|
|
1250 |
+ |
/// whole run.
|
|
1251 |
+ |
#[test]
|
|
1252 |
+ |
fn preflight_reports_missing_credentials_up_front() {
|
|
1253 |
+ |
let meta = CrateMeta {
|
|
1254 |
+ |
name: "makeover".into(),
|
|
1255 |
+ |
version: "0.11.0".into(),
|
|
1256 |
+ |
repository: Some("https://git.sr.ht/~maxmj/makeover".into()),
|
|
1257 |
+ |
description: Some("d".into()),
|
|
1258 |
+ |
licensed: true,
|
|
1259 |
+ |
};
|
|
1260 |
+ |
// Metadata is perfect; only the token is absent.
|
|
1261 |
+ |
let problems = crate_publish_problems(&meta, true, &[], false);
|
|
1262 |
+ |
assert_eq!(problems.len(), 1, "{problems:?}");
|
|
1263 |
+ |
assert!(problems[0].contains("credentials"), "{problems:?}");
|
|
1264 |
+ |
assert!(problems[0].contains("cargo login"), "should say how to fix it");
|
|
1265 |
+ |
|
|
1266 |
+ |
// Present: nothing to report.
|
|
1267 |
+ |
assert!(crate_publish_problems(&meta, true, &[], true).is_empty());
|
| 1222 |
1268 |
|
}
|
| 1223 |
1269 |
|
|
| 1224 |
1270 |
|
#[test]
|