11///usr/bin/env jbang "$0" "$@" ; exit $?
22//JAVA 21+
3+ //DEPS info.picocli:picocli:4.7.7
4+
5+ import picocli .CommandLine ;
6+ import picocli .CommandLine .Command ;
7+ import picocli .CommandLine .Option ;
38
49import java .io .IOException ;
510import java .nio .file .Files ;
611import java .nio .file .Path ;
712import java .nio .file .StandardCopyOption ;
8- import java .util .ArrayList ;
913import java .util .List ;
14+ import java .util .concurrent .Callable ;
1015import java .util .regex .Matcher ;
1116import java .util .regex .Pattern ;
1217
1520 * pom.xml, runs the deploy, then restores the original pom.xml (even if the build fails or
1621 * the script is interrupted). Keeps deploy credentials/config out of the committed pom.xml.
1722 *
18- * Usage:
19- * jbang ./.github/scripts/DeployToPlatform.java --url <platformUrl> --key <key> --tenants <t1,t2,...> [options]
20- *
21- * Required:
22- * --url Timefold platform URL, e.g. https://sandbox.timefold.dev
23- * --key Model key, e.g. maintenance-scheduling-template
24- * --tenants Comma-separated tenant id(s)
25- *
26- * Optional:
27- * --module <path> Path to the module containing pom.xml (default: current directory)
28- * --overwrite <bool> Value for <overwrite> (default: true)
29- * --handle-subscription <bool> Value for <handleSubscription> (default: true)
30- *
3123 * Example:
32- * jbang ./ github/scripts/DeployToPlatform.java --module use-cases/maintenance-scheduling \
24+ * jbang .github/scripts/DeployToPlatform.java --module use-cases/maintenance-scheduling \
3325 * --url https://sandbox.timefold.dev \
3426 * --key maintenance-scheduling-template \
3527 * --tenants ae226da1-5aea-4aba-93fc-8f911f37aa23
3628 */
37- public final class DeployToPlatform {
29+ @ Command (name = "DeployToPlatform" ,
30+ mixinStandardHelpOptions = true ,
31+ description = "Deploys a quickstart to the Timefold Platform by temporarily injecting the "
32+ + "timefold-maven-plugin deploy configuration into its pom.xml." )
33+ public final class DeployToPlatform implements Callable <Integer > {
34+
35+ private static final String NL = System .lineSeparator ();
3836
3937 // Matches a top-level <build> (2-space indent) only, not one nested in a <profile>.
38+ // \R matches whatever line break the pom.xml actually uses.
4039 private static final Pattern TOP_LEVEL_BUILD =
41- Pattern .compile ("\\ n {2}<build>\\ n .*?\\ n {2}</build>\\ n " , Pattern .DOTALL );
42- private static final Pattern PLUGINS_OPEN = Pattern .compile ("\\ n {4}<plugins>\\ n " );
40+ Pattern .compile ("\\ R {2}<build>\\ R .*?\\ R {2}</build>\\ R " , Pattern .DOTALL );
41+ private static final Pattern PLUGINS_OPEN = Pattern .compile ("\\ R {4}<plugins>\\ R " );
4342 private static final Pattern PARENT_BLOCK = Pattern .compile ("<parent>(.*?)</parent>" , Pattern .DOTALL );
4443 private static final Pattern VERSION_TAG = Pattern .compile ("<version>\\ s*(.*?)\\ s*</version>" );
4544 private static final Pattern MAJOR_MINOR_PATCH = Pattern .compile ("^\\ d+\\ .\\ d+\\ .\\ d+$" );
4645
47- public static void main (String [] args ) throws Exception {
48- Path module = Path .of ("." );
49- String platformUrl = null ;
50- String key = null ;
51- String tenants = null ;
52- String overwrite = "true" ;
53- String handleSubscription = "true" ;
54-
55- for (int i = 0 ; i < args .length ; i ++) {
56- switch (args [i ]) {
57- case "--module" -> module = Path .of (args [++i ]);
58- case "--url" -> platformUrl = args [++i ];
59- case "--key" -> key = args [++i ];
60- case "--tenants" -> tenants = args [++i ];
61- case "--overwrite" -> overwrite = args [++i ];
62- case "--handle-subscription" -> handleSubscription = args [++i ];
63- case "-h" , "--help" -> usage (0 );
64- default -> {
65- System .err .println ("Unknown argument: " + args [i ]);
66- usage (1 );
67- }
68- }
69- }
46+ @ Option (names = {"-m" , "--module" }, defaultValue = "." ,
47+ description = "Path to the module containing pom.xml (default: ${DEFAULT-VALUE})." )
48+ Path module ;
7049
71- if ( platformUrl == null || key == null || tenants == null ) {
72- usage ( 1 );
73- }
50+ @ Option ( names = { "-u" , "--url" }, required = true ,
51+ description = "Timefold platform URL, e.g. https://sandbox.timefold.dev" )
52+ String platformUrl ;
7453
75- List <String > tenantList = new ArrayList <>();
76- for (String tenant : tenants .split ("," )) {
77- if (!tenant .isBlank ()) {
78- tenantList .add (tenant .strip ());
79- }
80- }
54+ @ Option (names = {"-k" , "--key" }, required = true ,
55+ description = "Model key, e.g. maintenance-scheduling-template" )
56+ String key ;
57+
58+ @ Option (names = {"-t" , "--tenants" }, required = true , split = "," , paramLabel = "<tenantId>" ,
59+ description = "Comma-separated tenant id(s)." )
60+ List <String > tenants ;
61+
62+ @ Option (names = {"--overwrite" }, defaultValue = "true" , arity = "0..1" , fallbackValue = "true" ,
63+ paramLabel = "<bool>" , description = "Value for <overwrite> (default: ${DEFAULT-VALUE})." )
64+ boolean overwrite ;
65+
66+ @ Option (names = {"--handle-subscription" }, defaultValue = "true" , arity = "0..1" , fallbackValue = "true" ,
67+ paramLabel = "<bool>" , description = "Value for <handleSubscription> (default: ${DEFAULT-VALUE})." )
68+ boolean handleSubscription ;
69+
70+ public static void main (String [] args ) {
71+ System .exit (new CommandLine (new DeployToPlatform ()).execute (args ));
72+ }
73+
74+ @ Override
75+ public Integer call () throws Exception {
76+ List <String > tenantList = tenants .stream ()
77+ .map (String ::strip )
78+ .filter (tenant -> !tenant .isBlank ())
79+ .toList ();
8180 if (tenantList .isEmpty ()) {
8281 System .err .println ("No tenants given" );
83- System . exit ( 1 ) ;
82+ return 1 ;
8483 }
8584
8685 Path pom = module .resolve ("pom.xml" );
8786 if (!Files .isRegularFile (pom )) {
8887 System .err .println ("No pom.xml found at " + pom );
89- System . exit ( 1 ) ;
88+ return 1 ;
9089 }
9190
9291 try {
9392 validateParentVersion (pom );
9493 } catch (IllegalStateException e ) {
9594 System .err .println (e .getMessage ());
96- System . exit ( 1 ) ;
95+ return 1 ;
9796 }
9897
9998 Path backup = Files .createTempFile ("deploy-to-platform-pom" , ".xml" );
@@ -107,15 +106,15 @@ public static void main(String[] args) throws Exception {
107106 }
108107 }));
109108
110- injectDeployPlugin (pom , platformUrl , key , tenantList , overwrite , handleSubscription );
109+ injectDeployPlugin (pom , tenantList );
111110
112111 System .out .println ("Deploying '" + module + "' to '" + platformUrl + "' (key=" + key
113- + ", tenants=" + tenants + ")..." );
112+ + ", tenants=" + String . join ( "," , tenantList ) + ")..." );
114113 Process process = new ProcessBuilder ("mvn" , "clean" , "package" , "-Denterprise=true" , "timefold:deploy" )
115114 .directory (module .toFile ())
116115 .inheritIO ()
117116 .start ();
118- System . exit ( process .waitFor () );
117+ return process .waitFor ();
119118 }
120119
121120 /**
@@ -142,13 +141,13 @@ private static void validateParentVersion(Path pom) throws IOException {
142141 }
143142 }
144143
145- private static void injectDeployPlugin (Path pom , String platformUrl , String key , List <String > tenants ,
146- String overwrite , String handleSubscription ) throws IOException {
147- String tenantXml = tenants .stream ()
144+ private void injectDeployPlugin (Path pom , List <String > tenantList ) throws IOException {
145+ String tenantXml = tenantList .stream ()
148146 .map (tenant -> " <tenant>" + tenant + "</tenant>" )
149147 .reduce ((a , b ) -> a + "\n " + b )
150148 .orElseThrow ();
151149
150+ // Text blocks always separate their lines with \n, so translate to the platform's separator.
152151 String pluginXml = """
153152 <plugin>
154153 <groupId>ai.timefold.solver</groupId>
@@ -157,9 +156,7 @@ private static void injectDeployPlugin(Path pom, String platformUrl, String key,
157156 <configuration>
158157 <platformUrl>%s</platformUrl>
159158 <key>%s</key>
160- <tenants>
161- %s
162- </tenants>
159+ <tenants>%s</tenants>
163160 <overwrite>%s</overwrite>
164161 <handleSubscription>%s</handleSubscription>
165162 </configuration>
@@ -171,7 +168,8 @@ private static void injectDeployPlugin(Path pom, String platformUrl, String key,
171168 </execution>
172169 </executions>
173170 </plugin>"""
174- .formatted (platformUrl , key , tenantXml , overwrite , handleSubscription );
171+ .formatted (platformUrl , key , tenantXml , overwrite , handleSubscription )
172+ .replace ("\n " , NL );
175173
176174 String content = Files .readString (pom );
177175 Matcher buildMatcher = TOP_LEVEL_BUILD .matcher (content );
@@ -182,35 +180,14 @@ private static void injectDeployPlugin(Path pom, String platformUrl, String key,
182180 if (!pluginsMatcher .find ()) {
183181 throw new IllegalStateException ("Found a top-level <build> without <plugins>; cannot insert automatically" );
184182 }
185- String newBlock = block .substring (0 , pluginsMatcher .end ()) + pluginXml + " \n "
183+ String newBlock = block .substring (0 , pluginsMatcher .end ()) + pluginXml + NL
186184 + block .substring (pluginsMatcher .end ());
187185 newContent = content .substring (0 , buildMatcher .start ()) + newBlock + content .substring (buildMatcher .end ());
188186 } else {
189- String newBlock = " <build>\n <plugins>\n " + pluginXml + "\n </plugins>\n </build>\n " ;
187+ String newBlock = " <build>" + NL + " <plugins>" + NL + pluginXml + NL
188+ + " </plugins>" + NL + " </build>" + NL ;
190189 newContent = content .replaceFirst (Pattern .quote ("</project>" ), Matcher .quoteReplacement (newBlock ) + "</project>" );
191190 }
192191 Files .writeString (pom , newContent );
193192 }
194-
195- private static void usage (int exitCode ) {
196- System .err .println ("""
197- Usage: jbang scripts/DeployToPlatform.java --url <platformUrl> --key <key> --tenants <t1,t2,...> [options]
198-
199- Required:
200- --url Timefold platform URL, e.g. https://sandbox.timefold.dev
201- --key Model key, e.g. maintenance-scheduling-template
202- --tenants Comma-separated tenant id(s)
203-
204- Optional:
205- --module <path> Path to the module containing pom.xml (default: current directory)
206- --overwrite <bool> Value for <overwrite> (default: true)
207- --handle-subscription <bool> Value for <handleSubscription> (default: true)
208-
209- Example:
210- jbang scripts/DeployToPlatform.java --module use-cases/maintenance-scheduling \\
211- --url https://app.timefold.ai \\
212- --key maintenance-scheduling-template \\
213- --tenants ae226da1-5aea-4aba-93fc-8f23411f37aa23""" );
214- System .exit (exitCode );
215- }
216193}
0 commit comments