@invarn/cibuild 1.3.17 → 1.3.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { resolve, extname } from "node:path";
3
3
  import { pathToFileURL } from "node:url";
4
- import { existsSync, readdirSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, appendFileSync } from "node:fs";
4
+ import { existsSync, readdirSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, appendFileSync, statSync } from "node:fs";
5
5
  import { homedir } from "node:os";
6
6
  import { execSync } from "node:child_process";
7
7
  import prompts from "prompts";
@@ -26,7 +26,7 @@ import { generateGitHubActionsWorkflow } from "./commands/github-workflow.js";
26
26
  * Returns the detected project type, or null if neither is found.
27
27
  */
28
28
  function detectMobileProjectRoot(dir) {
29
- // Android: must have build.gradle or build.gradle.kts at root
29
+ // Android/KMM: must have build.gradle or build.gradle.kts at root
30
30
  const androidIndicators = [
31
31
  "build.gradle",
32
32
  "build.gradle.kts",
@@ -35,7 +35,22 @@ function detectMobileProjectRoot(dir) {
35
35
  "gradlew",
36
36
  ];
37
37
  const hasAndroidIndicator = androidIndicators.some((f) => existsSync(resolve(dir, f)));
38
+ // KMM: Gradle project with an iosApp/ directory or shared/ multiplatform module
39
+ // Must check before plain Android since KMM also has Gradle files
38
40
  if (hasAndroidIndicator) {
41
+ const kmmIndicators = ["iosApp", "shared", "composeApp"];
42
+ const hasKmmIndicator = kmmIndicators.some((d) => {
43
+ const fullPath = resolve(dir, d);
44
+ try {
45
+ return existsSync(fullPath) && statSync(fullPath).isDirectory();
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ });
51
+ if (hasKmmIndicator) {
52
+ return "kmm";
53
+ }
39
54
  return "android";
40
55
  }
41
56
  // iOS: must have a .xcodeproj or .xcworkspace directory, or a Podfile
@@ -88,9 +103,10 @@ async function handleInitCommand(opts = {}) {
88
103
  const projectType = detectMobileProjectRoot(cwd);
89
104
  if (!projectType) {
90
105
  console.error("Error: Not a mobile project root.");
91
- console.error("ci init must be run from the root folder of an Android or iOS project.");
92
- console.error("\nAndroid projects must contain one of:");
106
+ console.error("ci init must be run from the root folder of an Android, iOS, or KMM project.");
107
+ console.error("\nAndroid/KMM projects must contain one of:");
93
108
  console.error(" build.gradle, build.gradle.kts, settings.gradle, settings.gradle.kts, gradlew");
109
+ console.error("\nKMM projects additionally need: iosApp/, shared/, or composeApp/ directory");
94
110
  console.error("\niOS projects must contain one of:");
95
111
  console.error(" *.xcodeproj, *.xcworkspace, Podfile");
96
112
  process.exit(1);
@@ -104,7 +120,8 @@ async function handleInitCommand(opts = {}) {
104
120
  process.exit(1);
105
121
  }
106
122
  }
107
- console.log(`\nDetected project type: ${projectType === "android" ? "Android" : "iOS"}`);
123
+ const projectTypeLabel = projectType === "kmm" ? "KMM (Kotlin Multiplatform)" : projectType === "android" ? "Android" : "iOS";
124
+ console.log(`\nDetected project type: ${projectTypeLabel}`);
108
125
  console.log(`
109
126
  ╔══════════════════════════════════════════════════════════════╗
110
127
  ║ CI Build - Dependency Check ║
@@ -1,4 +1,4 @@
1
- export declare function handleBuildCommand(detectMobileProjectRoot: (dir: string) => "android" | "ios" | null, options?: {
1
+ export declare function handleBuildCommand(detectMobileProjectRoot: (dir: string) => "android" | "ios" | "kmm" | null, options?: {
2
2
  createPipelinesDir?: boolean;
3
3
  nonInteractive?: boolean;
4
4
  }): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"AAw9BA,wBAAsB,kBAAkB,CACtC,uBAAuB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,GAAG,KAAK,GAAG,IAAI,EAClE,OAAO,GAAE;IAAE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAAC,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GACvE,OAAO,CAAC,IAAI,CAAC,CAqRf"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"AA+7BA,wBAAsB,kBAAkB,CACtC,uBAAuB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI,EAC1E,OAAO,GAAE;IAAE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAAC,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GACvE,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
@@ -122,7 +122,7 @@ function detectSetupNeeds(warnings) {
122
122
  artifactType: 'apk',
123
123
  };
124
124
  }
125
- function generateAndroidPipeline(javaVersion = 17, setup = { keystore: false, keystoreProperties: false, googleServices: false, googlePlayDeploy: false, googlePlayPackageName: '', artifactType: 'apk', keystorePaths: {} }, variants = DEFAULT_VARIANTS) {
125
+ function generateAndroidPipeline(javaVersion = 17, setup = { keystore: false, keystoreProperties: false, googleServices: false, googlePlayDeploy: false, googlePlayPackageName: '', artifactType: 'apk', keystorePaths: {} }, variants = DEFAULT_VARIANTS, cacheTechnology = "gradle") {
126
126
  const keystoreStepFor = (wf) => {
127
127
  if (!setup.keystore)
128
128
  return "";
@@ -227,11 +227,7 @@ ${keystoreStepFor("primary")}${googleServicesStep}
227
227
 
228
228
  - cache-pull@1.0.0:
229
229
  inputs:
230
- cache_key: gradle-\$MODULE
231
- cache_paths:
232
- - ~/.gradle/caches
233
- - ~/.gradle/wrapper
234
- - .gradle
230
+ technology: ${cacheTechnology}
235
231
 
236
232
  - android-lint@1.0.0:
237
233
  is_skippable: true
@@ -257,11 +253,7 @@ ${keystoreStepFor("primary")}${googleServicesStep}
257
253
 
258
254
  - cache-push@1.0.0:
259
255
  inputs:
260
- cache_key: gradle-\$MODULE
261
- cache_paths:
262
- - ~/.gradle/caches
263
- - ~/.gradle/wrapper
264
- - .gradle
256
+ technology: ${cacheTechnology}
265
257
 
266
258
  pull-request:
267
259
  envs:
@@ -275,10 +267,7 @@ ${keystoreStepFor("pull-request")}${googleServicesStep}
275
267
 
276
268
  - cache-pull@1.0.0:
277
269
  inputs:
278
- cache_key: gradle-pr-\$MODULE
279
- cache_paths:
280
- - ~/.gradle/caches
281
- - ~/.gradle/wrapper
270
+ technology: ${cacheTechnology}
282
271
 
283
272
  - android-lint@1.0.0:
284
273
  is_skippable: true
@@ -303,10 +292,7 @@ ${keystoreStepFor("pull-request")}${googleServicesStep}
303
292
 
304
293
  - cache-push@1.0.0:
305
294
  inputs:
306
- cache_key: gradle-pr-\$MODULE
307
- cache_paths:
308
- - ~/.gradle/caches
309
- - ~/.gradle/wrapper
295
+ technology: ${cacheTechnology}
310
296
 
311
297
  release:
312
298
  envs:
@@ -416,10 +402,8 @@ function generateIosPipeline(projectPath, setup, variants) {
416
402
  submit_for_review: 'no'
417
403
  `
418
404
  : "";
419
- // Cache paths depend on whether CocoaPods is used
420
- const cachePaths = setup.cocoaPods
421
- ? ` - Pods\n - Podfile.lock`
422
- : ` - .build`;
405
+ // Cache technology depends on whether CocoaPods is used
406
+ const iosCacheTechnology = setup.cocoaPods ? "cocoapods" : "spm";
423
407
  return `format_version: '1'
424
408
 
425
409
  meta:
@@ -441,9 +425,7 @@ ${schemeEnvFor(pv.scheme)}${configEnvFor(pv.configuration)}
441
425
  steps:
442
426
  - cache-pull@1.0.0:
443
427
  inputs:
444
- cache_key: pods-\$SCHEME
445
- cache_paths:
446
- ${cachePaths}
428
+ technology: ${iosCacheTechnology}
447
429
  ${podInstallStep}
448
430
  - xcode-archive@1.0.0:
449
431
  inputs:
@@ -463,9 +445,7 @@ ${podInstallStep}
463
445
 
464
446
  - cache-push@1.0.0:
465
447
  inputs:
466
- cache_key: pods-\$SCHEME
467
- cache_paths:
468
- ${cachePaths}
448
+ technology: ${iosCacheTechnology}
469
449
 
470
450
  pull-request:
471
451
  envs:
@@ -473,9 +453,7 @@ ${schemeEnvFor(prv.scheme)}${configEnvFor(prv.configuration)}
473
453
  steps:
474
454
  - cache-pull@1.0.0:
475
455
  inputs:
476
- cache_key: pods-pr-\$SCHEME
477
- cache_paths:
478
- ${cachePaths}
456
+ technology: ${iosCacheTechnology}
479
457
  ${podInstallStepPR}
480
458
  - xcode-test@1.0.0:
481
459
  is_skippable: true
@@ -493,9 +471,7 @@ ${podInstallStepPR}
493
471
 
494
472
  - cache-push@1.0.0:
495
473
  inputs:
496
- cache_key: pods-pr-\$SCHEME
497
- cache_paths:
498
- ${cachePaths}
474
+ technology: ${iosCacheTechnology}
499
475
 
500
476
  release:
501
477
  envs:
@@ -505,9 +481,7 @@ ${schemeEnvFor(rv.scheme)}
505
481
  ${codeSigningStep}
506
482
  - cache-pull@1.0.0:
507
483
  inputs:
508
- cache_key: pods-\$SCHEME
509
- cache_paths:
510
- ${cachePaths}
484
+ technology: ${iosCacheTechnology}
511
485
  ${podInstallStep}
512
486
  - xcode-archive@1.0.0:
513
487
  inputs:
@@ -848,17 +822,23 @@ export async function handleBuildCommand(detectMobileProjectRoot, options = {})
848
822
  await handleIosBuildCommand(cwd, options);
849
823
  return;
850
824
  }
851
- if (projectType !== "android") {
852
- console.error("Error: Not a mobile project root.");
853
- console.error("'ci build' must be run from the root folder of an Android or iOS project.");
854
- console.error("\nAndroid projects must contain one of:");
855
- console.error(" build.gradle, build.gradle.kts, settings.gradle, settings.gradle.kts, gradlew");
856
- console.error("\niOS projects must contain one of:");
857
- console.error(" *.xcodeproj, *.xcworkspace, Podfile");
858
- process.exit(1);
825
+ if (projectType === "kmm" || projectType === "android") {
826
+ await handleAndroidBuildCommand(cwd, options, projectType === "kmm" ? "kmm" : "gradle");
827
+ return;
859
828
  }
829
+ console.error("Error: Not a mobile project root.");
830
+ console.error("'ci build' must be run from the root folder of an Android, iOS, or KMM project.");
831
+ console.error("\nAndroid/KMM projects must contain one of:");
832
+ console.error(" build.gradle, build.gradle.kts, settings.gradle, settings.gradle.kts, gradlew");
833
+ console.error("\nKMM projects additionally need: iosApp/, shared/, or composeApp/ directory");
834
+ console.error("\niOS projects must contain one of:");
835
+ console.error(" *.xcodeproj, *.xcworkspace, Podfile");
836
+ process.exit(1);
837
+ }
838
+ async function handleAndroidBuildCommand(cwd, options, cacheTechnology) {
839
+ const platformLabel = cacheTechnology === "kmm" ? "KMM (Kotlin Multiplatform)" : "Android";
860
840
  // 2. Scan the project for potential unknowns
861
- console.log("\n🔍 Scanning Android project for potential unknowns...\n");
841
+ console.log(`\n🔍 Scanning ${platformLabel} project for potential unknowns...\n`);
862
842
  const scanResult = await scanAndroidProject(cwd);
863
843
  console.log(formatScanResult(scanResult));
864
844
  // 3. Prompt for build variants per workflow
@@ -1059,10 +1039,10 @@ export async function handleBuildCommand(detectMobileProjectRoot, options = {})
1059
1039
  // that exact JDK — a newer JDK can cross-compile. Enforce a minimum of 17
1060
1040
  // since ubuntu-latest runners ship with JDK 17+ and JDK <17 is unavailable.
1061
1041
  const javaVersion = Math.max(scanResult.detectedJavaVersion ?? 17, 17);
1062
- const yaml = generateAndroidPipeline(javaVersion, setupOptions, variants);
1042
+ const yaml = generateAndroidPipeline(javaVersion, setupOptions, variants, cacheTechnology);
1063
1043
  writeFileSync(outputPath, yaml, "utf-8");
1064
1044
  console.log("\n✅ Generated .ci/pipelines/cibuild.yml");
1065
- console.log(" Platform: Android");
1045
+ console.log(` Platform: ${platformLabel}`);
1066
1046
  console.log(` Workflows: primary (${variants.primary.variant}), pull-request (${variants.pullRequest.variant}), release (${variants.release.variant})`);
1067
1047
  if (setupOptions.keystore || setupOptions.keystoreProperties || setupOptions.googleServices || setupOptions.googlePlayDeploy) {
1068
1048
  console.log(" Setup steps included:");
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAkB3C,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,CA6BxD"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAmB3C,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,QAAQ,CA6BxD"}
@@ -12,6 +12,7 @@ const DEFAULT_CONFIG = {
12
12
  paths: {
13
13
  buildsDir: ".ci-builds",
14
14
  cacheDir: ".ci-cache",
15
+ peerCacheDir: "peer",
15
16
  derivedDataDir: "~/Library/Developer/Xcode/DerivedData",
16
17
  },
17
18
  };
@@ -24,6 +24,7 @@ export interface CIConfig {
24
24
  paths: {
25
25
  buildsDir: string;
26
26
  cacheDir: string;
27
+ peerCacheDir?: string;
27
28
  derivedDataDir: string;
28
29
  };
29
30
  /** When true, steps that require remote infrastructure (S3, servers) skip gracefully */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE;QACL,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,wFAAwF;IACxF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACpF,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACpE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACnE;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACjC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI,CAAC;IAChD,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,KAAK,WAAW,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,QAAQ;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE;QACZ,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;IACF,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE;QACL,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,wFAAwF;IACxF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACpF,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACpE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACnE;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACjC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,YAAY,KAAK,IAAI,GAAG,IAAI,CAAC;IAChD,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,KAAK,WAAW,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAExD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAQrD,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,gBAAgB;IACnD,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YAoGzF,iBAAiB;CAoEhC;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,gBAAgB;IACnD,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YAwJzF,iBAAiB;CA0EhC"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAExD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CASrD,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,gBAAgB;IACnD,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YAoGzF,iBAAiB;CAoEhC;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,gBAAgB;IACnD,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YAwJzF,iBAAiB;CA0EhC"}
@@ -5,8 +5,9 @@ import { BaseStepExecutor } from './base.js';
5
5
  export const CACHE_PRESETS = {
6
6
  cocoapods: { lockfile: 'Podfile.lock', keyPrefix: 'pods', paths: ['Pods'] },
7
7
  carthage: { lockfile: 'Cartfile.resolved', keyPrefix: 'carthage', paths: ['Carthage'] },
8
- spm: { lockfile: 'Package.resolved', keyPrefix: 'spm', paths: ['.build'] },
9
- gradle: { lockfile: 'gradle/wrapper/gradle-wrapper.properties', keyPrefix: 'gradle', paths: ['~/.gradle/caches'] },
8
+ spm: { lockfile: 'Package.resolved', keyPrefix: 'spm', paths: ['~/Library/Developer/Xcode/DerivedData/*/SourcePackages'] },
9
+ gradle: { lockfile: 'gradle/wrapper/gradle-wrapper.properties', keyPrefix: 'gradle', paths: ['~/.gradle/caches', '~/.gradle/wrapper/dists'] },
10
+ kmm: { lockfile: 'gradle/wrapper/gradle-wrapper.properties', keyPrefix: 'kmm', paths: ['~/.gradle/caches', '~/.gradle/wrapper/dists', '~/.konan'] },
10
11
  npm: { lockfile: 'package-lock.json', keyPrefix: 'npm', paths: ['node_modules'] },
11
12
  yarn: { lockfile: 'yarn.lock', keyPrefix: 'yarn', paths: ['node_modules'] },
12
13
  dart: { lockfile: 'pubspec.lock', keyPrefix: 'dart', paths: ['.dart_tool', '.pub-cache'] },
@@ -58,7 +59,7 @@ export class CachePullStepExecutor extends BaseStepExecutor {
58
59
  // Generate cache file name based on cache key
59
60
  commands.push('');
60
61
  commands.push('# Generate cache file path');
61
- commands.push(`CACHE_FILE="$CACHE_DIR/${this.escapeBash(cacheKey)}.tar.gz"`);
62
+ commands.push(`CACHE_FILE="$CACHE_DIR/${this.escapeBash(cacheKey)}.tar.zst"`);
62
63
  if (isDebugMode) {
63
64
  commands.push('echo "Debug mode enabled"');
64
65
  commands.push('echo "Cache file: $CACHE_FILE"');
@@ -72,14 +73,14 @@ export class CachePullStepExecutor extends BaseStepExecutor {
72
73
  commands.push(' echo "Cache file size: $(du -h "$CACHE_FILE" | cut -f1)"');
73
74
  }
74
75
  // Extract cache
75
- commands.push(' tar -xzf "$CACHE_FILE" -C /');
76
+ commands.push(' zstd -dc "$CACHE_FILE" | tar -xf - -C /');
76
77
  commands.push(' echo "Cache restored successfully"');
77
78
  // List restored paths if debug mode
78
79
  if (isDebugMode && Array.isArray(cachePaths) && cachePaths.length > 0) {
79
80
  commands.push(' echo "Restored paths:"');
80
81
  for (const path of cachePaths) {
81
82
  commands.push(` EXPANDED_PATH="${this.escapeBash(path)}"`);
82
- commands.push(' EXPANDED_PATH="${EXPANDED_PATH/#~/$HOME}"');
83
+ commands.push(' EXPANDED_PATH="${EXPANDED_PATH/#~/${CIBUILD_USER_HOME:-$HOME}}"');
83
84
  commands.push(' if [ -e "$EXPANDED_PATH" ]; then');
84
85
  commands.push(' echo " ✓ $EXPANDED_PATH"');
85
86
  commands.push(' else');
@@ -123,20 +124,20 @@ export class CachePullStepExecutor extends BaseStepExecutor {
123
124
  commands.push('echo "Lockfile: $LOCKFILE"');
124
125
  commands.push('echo "Cache key: $CACHE_KEY"');
125
126
  }
126
- commands.push('CACHE_FILE="$CACHE_DIR/$CACHE_KEY.tar.gz"');
127
+ commands.push('CACHE_FILE="$CACHE_DIR/$CACHE_KEY.tar.zst"');
127
128
  commands.push('');
128
129
  commands.push('if [ -f "$CACHE_FILE" ]; then');
129
130
  commands.push(' echo "Cache found, extracting..."');
130
131
  if (isDebugMode) {
131
132
  commands.push(' echo "Cache file size: $(du -h "$CACHE_FILE" | cut -f1)"');
132
133
  }
133
- commands.push(' tar -xzf "$CACHE_FILE" -C /');
134
+ commands.push(' zstd -dc "$CACHE_FILE" | tar -xf - -C /');
134
135
  commands.push(' echo "Cache restored successfully"');
135
136
  if (isDebugMode) {
136
137
  commands.push(' echo "Restored paths:"');
137
138
  for (const p of preset.paths) {
138
139
  commands.push(` EXPANDED="${this.escapeBash(p)}"`);
139
- commands.push(' EXPANDED="${EXPANDED/#~/$HOME}"');
140
+ commands.push(' EXPANDED="${EXPANDED/#~/${CIBUILD_USER_HOME:-$HOME}}"');
140
141
  commands.push(' if [ -e "$EXPANDED" ]; then');
141
142
  commands.push(' echo " ✓ $EXPANDED"');
142
143
  commands.push(' else');
@@ -204,7 +205,7 @@ export class CachePushStepExecutor extends BaseStepExecutor {
204
205
  // Generate cache file name based on cache key
205
206
  commands.push('');
206
207
  commands.push('# Generate cache file path');
207
- commands.push(`CACHE_FILE="$CACHE_DIR/${this.escapeBash(cacheKey)}.tar.gz"`);
208
+ commands.push(`CACHE_FILE="$CACHE_DIR/${this.escapeBash(cacheKey)}.tar.zst"`);
208
209
  if (isDebugMode) {
209
210
  commands.push('echo "Debug mode enabled"');
210
211
  commands.push('echo "Cache file: $CACHE_FILE"');
@@ -233,7 +234,7 @@ export class CachePushStepExecutor extends BaseStepExecutor {
233
234
  }
234
235
  // Expand tilde by using parameter expansion
235
236
  commands.push(`EXPANDED_PATH="${this.escapeBash(resolvedPath)}"`);
236
- commands.push('EXPANDED_PATH="${EXPANDED_PATH/#~/$HOME}"');
237
+ commands.push('EXPANDED_PATH="${EXPANDED_PATH/#~/${CIBUILD_USER_HOME:-$HOME}}"');
237
238
  commands.push('');
238
239
  commands.push('# Expand glob patterns and check each match');
239
240
  commands.push('FOUND_MATCH=false');
@@ -267,7 +268,7 @@ export class CachePushStepExecutor extends BaseStepExecutor {
267
268
  commands.push(' echo "Paths to cache:"');
268
269
  commands.push(' printf " %s\\n" "${PATHS_TO_CACHE[@]}"');
269
270
  }
270
- commands.push(' tar -czf "$CACHE_FILE" "${PATHS_TO_CACHE[@]}" 2>/dev/null || true');
271
+ commands.push(' tar -cf - "${PATHS_TO_CACHE[@]}" 2>/dev/null | zstd -3 > "$CACHE_FILE.tmp" && mv "$CACHE_FILE.tmp" "$CACHE_FILE" || true');
271
272
  commands.push(' if [ -f "$CACHE_FILE" ]; then');
272
273
  commands.push(' echo "Cache created successfully"');
273
274
  if (isDebugMode) {
@@ -311,14 +312,14 @@ export class CachePushStepExecutor extends BaseStepExecutor {
311
312
  commands.push('echo "Lockfile: $LOCKFILE"');
312
313
  commands.push('echo "Cache key: $CACHE_KEY"');
313
314
  }
314
- commands.push('CACHE_FILE="$CACHE_DIR/$CACHE_KEY.tar.gz"');
315
+ commands.push('CACHE_FILE="$CACHE_DIR/$CACHE_KEY.tar.zst"');
315
316
  commands.push('');
316
317
  // Collect paths to cache
317
318
  commands.push('# Collect paths to cache');
318
319
  commands.push('PATHS_TO_CACHE=()');
319
320
  for (const p of preset.paths) {
320
321
  commands.push(`EXPANDED="${this.escapeBash(p)}"`);
321
- commands.push('EXPANDED="${EXPANDED/#~/$HOME}"');
322
+ commands.push('EXPANDED="${EXPANDED/#~/${CIBUILD_USER_HOME:-$HOME}}"');
322
323
  commands.push('if [ -e "$EXPANDED" ]; then');
323
324
  commands.push(' PATHS_TO_CACHE+=("$EXPANDED")');
324
325
  if (isDebugMode) {
@@ -332,7 +333,7 @@ export class CachePushStepExecutor extends BaseStepExecutor {
332
333
  // Create cache archive
333
334
  commands.push('if [ ${#PATHS_TO_CACHE[@]} -gt 0 ]; then');
334
335
  commands.push(' echo "Caching ${#PATHS_TO_CACHE[@]} path(s)..."');
335
- commands.push(' tar -czf "$CACHE_FILE" "${PATHS_TO_CACHE[@]}" 2>/dev/null || true');
336
+ commands.push(' tar -cf - "${PATHS_TO_CACHE[@]}" 2>/dev/null | zstd -3 > "$CACHE_FILE.tmp" && mv "$CACHE_FILE.tmp" "$CACHE_FILE" || true');
336
337
  commands.push(' if [ -f "$CACHE_FILE" ]; then');
337
338
  commands.push(' echo "Cache created successfully"');
338
339
  if (isDebugMode) {
@@ -171,6 +171,20 @@ describe('Step Implementations', () => {
171
171
  expect(result.script).toContain('yarn.lock');
172
172
  expect(result.script).toContain('yarn-');
173
173
  });
174
+ test('should auto-configure for kmm technology', async () => {
175
+ const executor = new CachePullStepExecutor();
176
+ const result = await executor.execute({ technology: 'kmm' }, {}, testConfig);
177
+ expect(result.script).toContain('gradle/wrapper/gradle-wrapper.properties');
178
+ expect(result.script).toContain('kmm-');
179
+ });
180
+ test('should use zstd compression format', async () => {
181
+ const executor = new CachePullStepExecutor();
182
+ const result = await executor.execute({ technology: 'cocoapods' }, {}, testConfig);
183
+ expect(result.script).toContain('.tar.zst');
184
+ expect(result.script).toContain('zstd -dc');
185
+ expect(result.script).not.toContain('.tar.gz');
186
+ expect(result.script).not.toContain('tar -xzf');
187
+ });
174
188
  test('should throw on unknown technology', async () => {
175
189
  const executor = new CachePullStepExecutor();
176
190
  await expect(executor.execute({ technology: 'unknown' }, {}, testConfig)).rejects.toThrow("Unknown cache technology 'unknown'");
@@ -222,7 +236,7 @@ describe('Step Implementations', () => {
222
236
  expect(result.script).toContain('Podfile.lock');
223
237
  expect(result.script).toContain('pods-');
224
238
  expect(result.script).toContain('Pods');
225
- expect(result.script).toContain('tar -czf');
239
+ expect(result.script).toContain('zstd -3');
226
240
  });
227
241
  test('should auto-configure for gradle technology', async () => {
228
242
  const executor = new CachePushStepExecutor();
@@ -243,6 +257,23 @@ describe('Step Implementations', () => {
243
257
  expect(result.script).toContain('.dart_tool');
244
258
  expect(result.script).toContain('.pub-cache');
245
259
  });
260
+ test('should auto-configure for kmm technology', async () => {
261
+ const executor = new CachePushStepExecutor();
262
+ const result = await executor.execute({ technology: 'kmm' }, {}, testConfig);
263
+ expect(result.script).toContain('gradle/wrapper/gradle-wrapper.properties');
264
+ expect(result.script).toContain('kmm-');
265
+ expect(result.script).toContain('~/.gradle/caches');
266
+ expect(result.script).toContain('~/.gradle/wrapper/dists');
267
+ expect(result.script).toContain('~/.konan');
268
+ });
269
+ test('should use zstd compression format', async () => {
270
+ const executor = new CachePushStepExecutor();
271
+ const result = await executor.execute({ technology: 'cocoapods' }, {}, testConfig);
272
+ expect(result.script).toContain('.tar.zst');
273
+ expect(result.script).toContain('zstd -3');
274
+ expect(result.script).not.toContain('.tar.gz');
275
+ expect(result.script).not.toContain('tar -czf');
276
+ });
246
277
  test('should throw on unknown technology', async () => {
247
278
  const executor = new CachePushStepExecutor();
248
279
  await expect(executor.execute({ technology: 'unknown' }, {}, testConfig)).rejects.toThrow("Unknown cache technology 'unknown'");
@@ -1 +1 @@
1
- {"version":3,"file":"test-config.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/test-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,eAAO,MAAM,UAAU,EAAE,QAcxB,CAAC"}
1
+ {"version":3,"file":"test-config.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/test-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,eAAO,MAAM,UAAU,EAAE,QAexB,CAAC"}
@@ -10,6 +10,7 @@ export const testConfig = {
10
10
  paths: {
11
11
  buildsDir: '.ci-builds',
12
12
  cacheDir: '.ci-cache',
13
+ peerCacheDir: 'peer',
13
14
  derivedDataDir: '~/Library/Developer/Xcode/DerivedData',
14
15
  },
15
16
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invarn/cibuild",
3
- "version": "1.3.17",
3
+ "version": "1.3.19",
4
4
  "description": "CI Build CLI — local pipeline orchestration and validation",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",