11import { pathToFileURL } from "node:url" ;
22
3- const VALID_MODES = new Set ( [ "studio" , "runtime" ] ) ;
3+ const VALID_MODES = new Set ( [ "studio" , "studio-preapproved" , " runtime"] ) ;
44const DEFAULTS_BY_MODE = {
5- studio : "dev,staging,prod" ,
6- runtime : "at_ring1,at_ring2,tt_ring1,tt_ring2,prod_ring1,prod_ring2" ,
5+ "studio" : "dev,staging,prod" ,
6+ "studio-preapproved" : "dev,staging,preapproved-prod" ,
7+ "runtime" : "at_ring1,at_ring2,tt_ring1,tt_ring2,prod_ring1,prod_ring2" ,
78} ;
8- const OVERRIDE_ENV_BY_MODE = {
9- studio : "OVERRIDE_STUDIO_ENVIRONMENTS" ,
10- runtime : "OVERRIDE_RUNTIME_RINGS" ,
9+ const OVERRIDE_DEFAULT_ENV_BY_MODE = {
10+ "studio" : "OVERRIDE_DEFAULT_STUDIO_ENVIRONMENTS" ,
11+ "studio-preapproved" : "OVERRIDE_DEFAULT_STUDIO_ENVIRONMENTS" ,
12+ "runtime" : "OVERRIDE_DEFAULT_RUNTIME_ENVIRONMENTS" ,
1113} ;
1214const ALLOWED_VALUES_BY_MODE = {
13- studio : new Set ( DEFAULTS_BY_MODE . studio . split ( "," ) . map ( ( value ) => value . trim ( ) ) ) ,
14- runtime : new Set ( DEFAULTS_BY_MODE . runtime . split ( "," ) . map ( ( value ) => value . trim ( ) ) ) ,
15+ "studio" : new Set ( DEFAULTS_BY_MODE [ "studio" ] . split ( "," ) . map ( ( value ) => value . trim ( ) ) ) ,
16+ "studio-preapproved" : new Set (
17+ DEFAULTS_BY_MODE [ "studio-preapproved" ] . split ( "," ) . map ( ( value ) => value . trim ( ) )
18+ ) ,
19+ "runtime" : new Set ( DEFAULTS_BY_MODE [ "runtime" ] . split ( "," ) . map ( ( value ) => value . trim ( ) ) ) ,
1520} ;
1621
1722/**
1823 * Construct parsed output for studio or runtime environment inputs.
19- * @param {{mode: string, value?: string, inputsJson ?: string, allowEmpty?: boolean|string} } input
24+ * @param {{mode: string, inputs ?: string, allowEmpty?: boolean|string} } input
2025 * @returns {Array<{environment: string, ring?: string}> }
2126 */
2227export function constructResult ( input ) {
2328 const mode = input . mode ;
2429 if ( ! VALID_MODES . has ( mode ) ) {
25- throw new Error ( "mode must be 'studio' or 'runtime'." ) ;
30+ throw new Error ( "mode must be 'studio', 'studio-preapproved', or 'runtime'." ) ;
2631 }
2732
2833 const allowEmpty = parseBool ( input . allowEmpty ) ;
29- const inputsJson = input . inputsJson ?? "null" ;
30- let raw = input . value ?? "" ;
34+ const inputs = input . inputs ?? "null" ;
35+ const parsedInputs = parseInputs ( inputs ) ;
36+ let raw = "" ;
3137
32- // No workflow_dispatch inputs context (e.g. push) with no explicit value:
38+ if ( raw === "" && parsedInputs !== null ) {
39+ raw = getEnvironmentsFromInputs ( parsedInputs ) ;
40+ }
41+
42+ // No workflow_dispatch inputs context (e.g. push) with no explicit value/input:
3343 // use override when present, otherwise mode defaults.
34- if ( inputsJson === "null " && raw === "" ) {
35- raw = getOverrideValue ( mode ) || DEFAULTS_BY_MODE [ mode ] ;
44+ if ( raw === "" && parsedInputs === null ) {
45+ raw = getDefaultOverrideValue ( mode ) || DEFAULTS_BY_MODE [ mode ] ;
3646 }
3747
3848 if ( raw === "" ) {
@@ -43,18 +53,66 @@ export function constructResult(input) {
4353 }
4454
4555 const values = splitCsvStrict ( raw ) ;
56+ const allowedValues = ALLOWED_VALUES_BY_MODE [ mode ] ;
4657 switch ( mode ) {
4758 case "studio" :
48- validateValues ( values , ALLOWED_VALUES_BY_MODE . studio , "studio environment" ) ;
59+ case "studio-preapproved" :
60+ validateValues ( values , allowedValues , "studio environment" ) ;
4961 return values . map ( ( environment ) => ( { environment } ) ) ;
5062 case "runtime" :
51- validateValues ( values , ALLOWED_VALUES_BY_MODE . runtime , "runtime ring" ) ;
63+ validateValues ( values , allowedValues , "runtime ring" ) ;
5264 return values . map ( ( ring ) => ( { ring, environment : `runtime_${ ring } ` } ) ) ;
5365 default :
5466 throw new Error ( "unreachable mode." ) ;
5567 }
5668}
5769
70+ /**
71+ * @param {string } inputs
72+ * @returns {Record<string, unknown> | null }
73+ */
74+ function parseInputs ( inputs ) {
75+ if ( inputs === "null" ) {
76+ return null ;
77+ }
78+
79+ let parsed ;
80+ try {
81+ parsed = JSON . parse ( inputs ) ;
82+ } catch {
83+ throw new Error ( "inputs must be valid JSON." ) ;
84+ }
85+
86+ if ( parsed === null ) {
87+ return null ;
88+ }
89+
90+ if ( typeof parsed !== "object" || Array . isArray ( parsed ) ) {
91+ throw new Error ( "inputs must be an object or null." ) ;
92+ }
93+
94+ return parsed ;
95+ }
96+
97+ /**
98+ * @param {Record<string, unknown> } parsedInputs
99+ * @returns {string }
100+ */
101+ function getEnvironmentsFromInputs ( parsedInputs ) {
102+ if ( ! ( "environments" in parsedInputs ) ) {
103+ return "" ;
104+ }
105+
106+ const value = parsedInputs . environments ;
107+ if ( value === null || value === undefined ) {
108+ return "" ;
109+ }
110+ if ( typeof value !== "string" ) {
111+ throw new Error ( "input 'environments' must be a string." ) ;
112+ }
113+ return value ;
114+ }
115+
58116/**
59117 * @param {string } raw
60118 * @returns {string[] }
@@ -92,22 +150,21 @@ function parseBool(value) {
92150}
93151
94152/**
95- * @param {"studio"|"runtime" } mode
153+ * @param {"studio"|"studio-preapproved"|" runtime" } mode
96154 * @returns {string }
97155 */
98- function getOverrideValue ( mode ) {
99- return ( process . env [ OVERRIDE_ENV_BY_MODE [ mode ] ] ?? "" ) . trim ( ) ;
156+ function getDefaultOverrideValue ( mode ) {
157+ return ( process . env [ OVERRIDE_DEFAULT_ENV_BY_MODE [ mode ] ] ?? "" ) . trim ( ) ;
100158}
101159
102160/**
103161 * @param {string[] } argv
104- * @returns {{mode: string, value: string, inputsJson : string, allowEmpty: string} }
162+ * @returns {{mode: string, inputs : string, allowEmpty: string} }
105163 */
106164function parseArgs ( argv ) {
107165 const args = {
108166 mode : "" ,
109- value : "" ,
110- inputsJson : "null" ,
167+ inputs : "null" ,
111168 allowEmpty : "false" ,
112169 } ;
113170
@@ -125,11 +182,8 @@ function parseArgs(argv) {
125182 case "--mode" :
126183 args . mode = next ;
127184 break ;
128- case "--value" :
129- args . value = next ;
130- break ;
131- case "--inputs-json" :
132- args . inputsJson = next ;
185+ case "--inputs" :
186+ args . inputs = next ;
133187 break ;
134188 case "--allow-empty" :
135189 args . allowEmpty = next ;
@@ -148,8 +202,7 @@ if (import.meta.url === pathToFileURL(process.argv[1]).href) {
148202 const args = parseArgs ( process . argv . slice ( 2 ) ) ;
149203 const result = constructResult ( {
150204 mode : args . mode ,
151- value : args . value ,
152- inputsJson : args . inputsJson ,
205+ inputs : args . inputs ,
153206 allowEmpty : args . allowEmpty ,
154207 } ) ;
155208 process . stdout . write ( `${ JSON . stringify ( result ) } \n` ) ;
0 commit comments