Skip to content

Commit 7a60867

Browse files
authored
Merge branch 'main' into fix/8113-release-deploy-artifact
2 parents 86b2bb9 + 94722f6 commit 7a60867

10 files changed

Lines changed: 444 additions & 11 deletions

File tree

src/commands/deploy.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,12 @@ pub struct DeployArgs {
7272
/// Skip auto-pulling latest changes before deploy
7373
#[arg(long)]
7474
pub no_pull: bool,
75+
/// Deploy a local build even when its source checkout is behind its upstream
76+
#[arg(long)]
77+
pub allow_stale_source: bool,
78+
/// Deploy a local build even when its semantic version is older than the remote
79+
#[arg(long)]
80+
pub allow_downgrade: bool,
7581
/// Deploy from current branch HEAD instead of the latest tag
7682
#[arg(long)]
7783
pub head: bool,
@@ -359,6 +365,8 @@ fn build_config(args: &DeployArgs, skip_build: bool) -> DeployConfig {
359365
skip_deps_hydration: crate::commands::skip_deps_hydration(),
360366
expected_version: args.version.clone(),
361367
no_pull: args.no_pull,
368+
allow_stale_source: args.allow_stale_source,
369+
allow_downgrade: args.allow_downgrade,
362370
head: args.head,
363371
requested_ref: args.requested_ref.clone(),
364372
tagged: args.tagged,

src/core/deploy/execution/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ mod tests {
225225
skip_deps_hydration: false,
226226
expected_version: None,
227227
no_pull: false,
228+
allow_stale_source: false,
229+
allow_downgrade: false,
228230
head: false,
229231
requested_ref: None,
230232
tagged: false,
@@ -284,6 +286,8 @@ mod tests {
284286
skip_deps_hydration: false,
285287
expected_version: None,
286288
no_pull: false,
289+
allow_stale_source: false,
290+
allow_downgrade: false,
287291
head: true,
288292
requested_ref: None,
289293
tagged: false,
@@ -316,6 +320,8 @@ mod tests {
316320
skip_deps_hydration: false,
317321
expected_version: None,
318322
no_pull: false,
323+
allow_stale_source: false,
324+
allow_downgrade: false,
319325
head: false,
320326
requested_ref: None,
321327
tagged: true,
@@ -359,6 +365,8 @@ mod tests {
359365
skip_deps_hydration: false,
360366
expected_version: Some("1.2.3".to_string()),
361367
no_pull: false,
368+
allow_stale_source: false,
369+
allow_downgrade: false,
362370
head: false,
363371
requested_ref: None,
364372
tagged: false,
@@ -402,6 +410,8 @@ mod tests {
402410
skip_deps_hydration: false,
403411
expected_version: Some("1.2.3".to_string()),
404412
no_pull: false,
413+
allow_stale_source: false,
414+
allow_downgrade: false,
405415
head: false,
406416
requested_ref: None,
407417
tagged: false,
@@ -436,6 +446,8 @@ mod tests {
436446
skip_deps_hydration: false,
437447
expected_version: Some("1.2.3".to_string()),
438448
no_pull: false,
449+
allow_stale_source: false,
450+
allow_downgrade: false,
439451
head: false,
440452
requested_ref: None,
441453
tagged: false,
@@ -483,6 +495,8 @@ mod tests {
483495
skip_deps_hydration: false,
484496
expected_version: Some("1.2.3".to_string()),
485497
no_pull: false,
498+
allow_stale_source: false,
499+
allow_downgrade: false,
486500
head: false,
487501
requested_ref: None,
488502
tagged: false,
@@ -555,6 +569,8 @@ mod tests {
555569
skip_deps_hydration: false,
556570
expected_version: None,
557571
no_pull: false,
572+
allow_stale_source: false,
573+
allow_downgrade: false,
558574
head: true,
559575
requested_ref: None,
560576
tagged: false,

src/core/deploy/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ pub fn run_multi(
191191
skip_deps_hydration: config.skip_deps_hydration,
192192
expected_version: config.expected_version.clone(),
193193
no_pull: config.no_pull,
194+
allow_stale_source: config.allow_stale_source,
195+
allow_downgrade: config.allow_downgrade,
194196
head: config.head,
195197
requested_ref: config.requested_ref.clone(),
196198
tagged: config.tagged,
@@ -328,6 +330,8 @@ mod tests {
328330
skip_deps_hydration: false,
329331
expected_version: None,
330332
no_pull: false,
333+
allow_stale_source: false,
334+
allow_downgrade: false,
331335
head: false,
332336
requested_ref: None,
333337
tagged: false,

src/core/deploy/orchestration/mod.rs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ mod smoke_check;
2424

2525
use modes::{extension_skipped_results, run_check_mode, run_dry_run_mode};
2626
use preflight::{
27-
check_uncommitted_changes, check_unreleased_commits, sync_components, verify_expected_version,
28-
warn_non_default_branch,
27+
check_uncommitted_changes, check_unreleased_commits, guard_local_build_downgrades,
28+
guard_local_build_source_freshness, local_build_components, sync_components,
29+
verify_expected_version, warn_non_default_branch,
2930
};
3031
use smoke_check::run_post_deploy_smoke;
3132

@@ -169,13 +170,18 @@ pub(super) fn deploy_components(
169170
.iter()
170171
.filter_map(|c| version::get_component_version(c).map(|v| (c.id.clone(), v)))
171172
.collect();
172-
let remote_versions =
173-
if config.outdated || config.behind_upstream || config.dry_run || config.check {
174-
fetch_remote_versions_for_project(&components, Some(&project), base_path, &ctx.client)
175-
.versions
176-
} else {
177-
HashMap::new()
178-
};
173+
let local_build_components = local_build_components(&components, config);
174+
let remote_versions = if config.outdated
175+
|| config.behind_upstream
176+
|| config.dry_run
177+
|| config.check
178+
|| !local_build_components.is_empty()
179+
{
180+
fetch_remote_versions_for_project(&components, Some(&project), base_path, &ctx.client)
181+
.versions
182+
} else {
183+
HashMap::new()
184+
};
179185

180186
// Check and dry-run modes return early without building or deploying
181187
if config.check {
@@ -213,6 +219,8 @@ pub(super) fn deploy_components(
213219
sync_components(&local_build_components)?;
214220
}
215221

222+
guard_local_build_source_freshness(&local_build_components, config)?;
223+
216224
// Warn when --head deploys from a non-default branch (safety guardrail)
217225
if config.head && !config.skip_build {
218226
warn_non_default_branch(&local_build_components, config)?;
@@ -249,6 +257,13 @@ pub(super) fn deploy_components(
249257
.filter_map(|c| version::get_component_version(c).map(|v| (c.id.clone(), v)))
250258
.collect();
251259

260+
guard_local_build_downgrades(
261+
&local_build_components,
262+
&local_versions,
263+
&remote_versions,
264+
config,
265+
)?;
266+
252267
// Build and validate every local artifact before the first remote write.
253268
let prepared_deployments = match prepare_component_deployments(
254269
&components,
@@ -516,6 +531,8 @@ mod tests {
516531
skip_deps_hydration: false,
517532
expected_version: None,
518533
no_pull: false,
534+
allow_stale_source: false,
535+
allow_downgrade: false,
519536
head: false,
520537
requested_ref: None,
521538
tagged: false,

src/core/deploy/orchestration/modes.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,8 @@ mod tests {
296296
skip_deps_hydration: false,
297297
expected_version: None,
298298
no_pull: false,
299+
allow_stale_source: false,
300+
allow_downgrade: false,
299301
head: false,
300302
requested_ref: Some("reviewed".to_string()),
301303
tagged: false,
@@ -336,6 +338,63 @@ mod tests {
336338
);
337339
}
338340

341+
#[test]
342+
fn check_and_dry_run_report_remote_newer_versions_without_safety_refusal() {
343+
let component = Component {
344+
id: "fixture".to_string(),
345+
local_path: "/not/a/checkout".to_string(),
346+
build_artifact: Some("build/fixture.zip".to_string()),
347+
..Component::default()
348+
};
349+
let local_versions = HashMap::from([("fixture".to_string(), "1.2.3".to_string())]);
350+
let remote_versions = HashMap::from([("fixture".to_string(), "1.3.0".to_string())]);
351+
let config = DeployConfig {
352+
component_ids: vec!["fixture".to_string()],
353+
all: false,
354+
outdated: false,
355+
behind_upstream: false,
356+
dry_run: true,
357+
check: false,
358+
force: false,
359+
skip_build: false,
360+
keep_deps: false,
361+
skip_deps_hydration: false,
362+
expected_version: None,
363+
no_pull: true,
364+
allow_stale_source: false,
365+
allow_downgrade: false,
366+
head: true,
367+
requested_ref: None,
368+
tagged: false,
369+
};
370+
371+
let checked = run_check_mode(
372+
std::slice::from_ref(&component),
373+
&local_versions,
374+
&remote_versions,
375+
&[],
376+
&Project::default(),
377+
"/srv/site",
378+
&config,
379+
);
380+
assert_eq!(checked.results[0].status, "checked");
381+
assert_eq!(checked.results[0].local_version.as_deref(), Some("1.2.3"));
382+
assert_eq!(checked.results[0].remote_version.as_deref(), Some("1.3.0"));
383+
384+
let planned = run_dry_run_mode(
385+
&[component],
386+
&local_versions,
387+
&remote_versions,
388+
&Project::default(),
389+
"/srv/site",
390+
&config,
391+
)
392+
.expect("dry-run must report rather than refuse a remote-newer version");
393+
assert_eq!(planned.results[0].status, "planned");
394+
assert_eq!(planned.results[0].local_version.as_deref(), Some("1.2.3"));
395+
assert_eq!(planned.results[0].remote_version.as_deref(), Some("1.3.0"));
396+
}
397+
339398
fn git(path: &Path, args: &[&str]) {
340399
let output = Command::new("git")
341400
.args(args)

0 commit comments

Comments
 (0)