33
44const { ERR_CONFIG , ERR_VALIDATION } = require ( "./error_codes.cjs" ) ;
55const { writeDenialSummary } = require ( "./pre_activation_summary.cjs" ) ;
6+
7+ // Matches a relative time delta such as "+25h", "+3d", "+1w", "+1mo", "+1d12h".
8+ // Mirrors pkg/workflow/time_delta.go's parseTimeDeltaForStopAfter: minutes are not
9+ // supported since the minimum unit for stop-after is hours.
10+ const TIME_DELTA_PATTERN = / ( \d + ) ( m o | w | d | h ) / g;
11+
12+ /** @param {string } stopTime */
13+ function isRelativeStopTime ( stopTime ) {
14+ return stopTime . startsWith ( "+" ) ;
15+ }
16+
17+ /** @param {string } deltaStr */
18+ function parseTimeDeltaForStopAfter ( deltaStr ) {
19+ const rest = deltaStr . slice ( 1 ) ;
20+ if ( ! rest ) {
21+ throw new Error ( "empty time delta after '+'" ) ;
22+ }
23+
24+ const matches = [ ...rest . matchAll ( TIME_DELTA_PATTERN ) ] ;
25+ if ( matches . length === 0 ) {
26+ throw new Error ( `invalid time delta format: +${ rest } . Expected format like +25h, +3d, +1w, +1mo, +1d12h` ) ;
27+ }
28+
29+ const consumed = matches . reduce ( ( sum , match ) => sum + match [ 0 ] . length , 0 ) ;
30+ if ( consumed !== rest . length ) {
31+ throw new Error ( `invalid time delta format: +${ rest } . Extra characters detected` ) ;
32+ }
33+
34+ const delta = { months : 0 , weeks : 0 , days : 0 , hours : 0 } ;
35+ const seenUnits = new Set ( ) ;
36+ for ( const [ , valueStr , unit ] of matches ) {
37+ if ( seenUnits . has ( unit ) ) {
38+ throw new Error ( `duplicate unit '${ unit } ' in time delta: +${ rest } ` ) ;
39+ }
40+ seenUnits . add ( unit ) ;
41+ const value = parseInt ( valueStr , 10 ) ;
42+ if ( unit === "mo" ) delta . months = value ;
43+ else if ( unit === "w" ) delta . weeks = value ;
44+ else if ( unit === "d" ) delta . days = value ;
45+ else if ( unit === "h" ) delta . hours = value ;
46+ }
47+ return delta ;
48+ }
49+
50+ /**
51+ * Resolves a relative stop-time delta (e.g. "+48h") to an absolute Date, relative to baseTime.
52+ * @param {string } deltaStr
53+ * @param {Date } baseTime
54+ */
55+ function resolveRelativeStopTime ( deltaStr , baseTime ) {
56+ const delta = parseTimeDeltaForStopAfter ( deltaStr ) ;
57+ const result = new Date ( baseTime . getTime ( ) ) ;
58+ result . setUTCMonth ( result . getUTCMonth ( ) + delta . months ) ;
59+ result . setUTCDate ( result . getUTCDate ( ) + delta . weeks * 7 + delta . days ) ;
60+ result . setUTCHours ( result . getUTCHours ( ) + delta . hours ) ;
61+ return result ;
62+ }
63+
664async function main ( ) {
765 const stopTime = process . env . GH_AW_STOP_TIME ;
866 const workflowName = process . env . GH_AW_WORKFLOW_NAME ;
@@ -19,8 +77,21 @@ async function main() {
1977
2078 core . info ( `Checking stop-time limit: ${ stopTime } ` ) ;
2179
22- // Parse the stop time (format: "YYYY-MM-DD HH:MM:SS")
23- const stopTimeDate = new Date ( stopTime ) ;
80+ // Resolve the stop time. A GitHub Actions expression (e.g. "${{ inputs.stop-after }}")
81+ // is passed through verbatim at compile time and evaluated by the runner before this
82+ // step runs, so it may still be a relative delta (e.g. "+48h") rather than an already
83+ // resolved absolute timestamp (format: "YYYY-MM-DD HH:MM:SS").
84+ let stopTimeDate ;
85+ if ( isRelativeStopTime ( stopTime ) ) {
86+ try {
87+ stopTimeDate = resolveRelativeStopTime ( stopTime , new Date ( ) ) ;
88+ } catch ( err ) {
89+ core . setFailed ( `${ ERR_VALIDATION } : Invalid stop-time format: ${ stopTime } . ${ err instanceof Error ? err . message : String ( err ) } ` ) ;
90+ return ;
91+ }
92+ } else {
93+ stopTimeDate = new Date ( stopTime ) ;
94+ }
2495
2596 if ( Number . isNaN ( stopTimeDate . getTime ( ) ) ) {
2697 core . setFailed ( `${ ERR_VALIDATION } : Invalid stop-time format: ${ stopTime } . Expected format: YYYY-MM-DD HH:MM:SS` ) ;
0 commit comments