diff --git a/docs/screenshots/lifecycle-rule-desktop.svg b/docs/screenshots/lifecycle-rule-desktop.svg
new file mode 100644
index 00000000..721cd049
--- /dev/null
+++ b/docs/screenshots/lifecycle-rule-desktop.svg
@@ -0,0 +1,10 @@
+
diff --git a/docs/screenshots/lifecycle-rule-mobile.svg b/docs/screenshots/lifecycle-rule-mobile.svg
new file mode 100644
index 00000000..2bdaf8d1
--- /dev/null
+++ b/docs/screenshots/lifecycle-rule-mobile.svg
@@ -0,0 +1,9 @@
+
diff --git a/lib/lifecycle-display.ts b/lib/lifecycle-display.ts
index ad1f5e6a..85a4c53a 100644
--- a/lib/lifecycle-display.ts
+++ b/lib/lifecycle-display.ts
@@ -9,15 +9,14 @@ export interface LifecycleRule {
Expiration?: {
Days?: number
Date?: string | Date
- StorageClass?: string
ExpiredObjectDeleteMarker?: boolean
}
- NoncurrentVersionExpiration?: { NoncurrentDays?: number }
- Transitions?: Array<{ Days?: number; Date?: string | Date; StorageClass?: string }>
- NoncurrentVersionTransitions?: Array<{
+ NoncurrentVersionExpiration?: {
NoncurrentDays?: number
- StorageClass?: string
- }>
+ NewerNoncurrentVersions?: number
+ }
+ Transitions?: Array<{ Days?: number; Date?: string | Date; StorageClass?: string }>
+ NoncurrentVersionTransitions?: Array<{ NoncurrentDays?: number; StorageClass?: string }>
}
export interface LifecycleAction {
@@ -27,27 +26,46 @@ export interface LifecycleAction {
date?: string | Date
tier?: string
deleteMarker?: boolean
+ newerNoncurrentVersions?: number
+}
+
+function hasExpirationAction(expiration?: LifecycleRule["Expiration"]) {
+ return (
+ expiration?.Days !== undefined ||
+ expiration?.Date !== undefined ||
+ expiration?.ExpiredObjectDeleteMarker !== undefined
+ )
+}
+
+function hasNoncurrentExpirationAction(expiration?: LifecycleRule["NoncurrentVersionExpiration"]) {
+ return (
+ expiration?.NoncurrentDays !== undefined ||
+ (expiration?.NewerNoncurrentVersions !== undefined && expiration.NewerNoncurrentVersions > 0)
+ )
}
export function getLifecycleActions(rule: LifecycleRule): LifecycleAction[] {
const actions: LifecycleAction[] = []
- if (rule.Expiration) {
+ if (hasExpirationAction(rule.Expiration)) {
actions.push({
type: "Expire",
version: "Current Version",
- days: rule.Expiration.Days,
- date: rule.Expiration.Date,
- deleteMarker: rule.Expiration.ExpiredObjectDeleteMarker,
+ days: rule.Expiration?.Days,
+ date: rule.Expiration?.Date,
+ deleteMarker: rule.Expiration?.ExpiredObjectDeleteMarker,
})
}
- if (rule.NoncurrentVersionExpiration) {
+ if (hasNoncurrentExpirationAction(rule.NoncurrentVersionExpiration)) {
actions.push({
type: "Expire",
version: "Non-current Version",
- days: rule.NoncurrentVersionExpiration.NoncurrentDays,
+ days: rule.NoncurrentVersionExpiration?.NoncurrentDays,
+ newerNoncurrentVersions: rule.NoncurrentVersionExpiration?.NewerNoncurrentVersions,
})
}
for (const transition of rule.Transitions ?? []) {
+ if (transition.Days === undefined && transition.Date === undefined && transition.StorageClass === undefined)
+ continue
actions.push({
type: "Transition",
version: "Current Version",
@@ -57,6 +75,7 @@ export function getLifecycleActions(rule: LifecycleRule): LifecycleAction[] {
})
}
for (const transition of rule.NoncurrentVersionTransitions ?? []) {
+ if (transition.NoncurrentDays === undefined && transition.StorageClass === undefined) continue
actions.push({
type: "Transition",
version: "Non-current Version",
diff --git a/tests/lib/lifecycle-display.test.ts b/tests/lib/lifecycle-display.test.ts
index 83034b06..3e70a42c 100644
--- a/tests/lib/lifecycle-display.test.ts
+++ b/tests/lib/lifecycle-display.test.ts
@@ -21,6 +21,24 @@ test("mixed expiration rules retain both version labels and their own days (rust
}
})
+test("empty expiration containers do not create fake actions", () => {
+ assert.deepEqual(getLifecycleActions({ Expiration: {} }), [])
+ assert.deepEqual(getLifecycleActions({ NoncurrentVersionExpiration: {} }), [])
+ assert.deepEqual(getLifecycleActions({ Transitions: [{}], NoncurrentVersionTransitions: [{}] }), [])
+})
+
+test("explicit delete-marker false remains an explicit action setting", () => {
+ assert.deepEqual(getLifecycleActions({ Expiration: { ExpiredObjectDeleteMarker: false } }), [
+ {
+ type: "Expire",
+ version: "Current Version",
+ days: undefined,
+ date: undefined,
+ deleteMarker: false,
+ },
+ ])
+})
+
test("each transition keeps its version, tier and zero-day schedule", () => {
assert.deepEqual(
getLifecycleActions({