@invarn/cibuild 1.6.2 → 1.8.0

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.
Files changed (34) hide show
  1. package/dist/cli.cjs +1 -1
  2. package/dist/src/commands/build.d.ts +43 -0
  3. package/dist/src/commands/build.d.ts.map +1 -1
  4. package/dist/src/commands/build.js +6 -6
  5. package/dist/src/commands/build.test.d.ts +13 -0
  6. package/dist/src/commands/build.test.d.ts.map +1 -0
  7. package/dist/src/commands/build.test.js +59 -0
  8. package/dist/src/commands/edit.d.ts +9 -0
  9. package/dist/src/commands/edit.d.ts.map +1 -1
  10. package/dist/src/commands/edit.js +5 -4
  11. package/dist/src/commands/edit.test.d.ts +6 -0
  12. package/dist/src/commands/edit.test.d.ts.map +1 -0
  13. package/dist/src/commands/edit.test.js +45 -0
  14. package/dist/src/commands/init.d.ts +7 -0
  15. package/dist/src/commands/init.d.ts.map +1 -1
  16. package/dist/src/commands/init.js +3 -1
  17. package/dist/src/yaml/converter.d.ts.map +1 -1
  18. package/dist/src/yaml/converter.js +5 -4
  19. package/dist/src/yaml/converter.test.js +51 -0
  20. package/dist/src/yaml/meta-helpers.d.ts +19 -0
  21. package/dist/src/yaml/meta-helpers.d.ts.map +1 -0
  22. package/dist/src/yaml/meta-helpers.js +20 -0
  23. package/dist/src/yaml/parser.d.ts.map +1 -1
  24. package/dist/src/yaml/parser.js +23 -20
  25. package/dist/src/yaml/parser.test.d.ts +5 -0
  26. package/dist/src/yaml/parser.test.d.ts.map +1 -0
  27. package/dist/src/yaml/parser.test.js +83 -0
  28. package/dist/src/yaml/platform-detector.d.ts +1 -1
  29. package/dist/src/yaml/platform-detector.d.ts.map +1 -1
  30. package/dist/src/yaml/platform-detector.js +7 -6
  31. package/dist/src/yaml/platform-detector.test.js +111 -0
  32. package/dist/src/yaml/types.d.ts +1 -0
  33. package/dist/src/yaml/types.d.ts.map +1 -1
  34. package/package.json +1 -1
@@ -1,5 +1,48 @@
1
+ export interface SetupOptions {
2
+ keystore: boolean;
3
+ keystoreProperties: boolean;
4
+ googleServices: boolean;
5
+ googlePlayDeploy: boolean;
6
+ googlePlayPackageName: string;
7
+ /** Whether the release workflow should produce an AAB or APK. Defaults to 'apk'. */
8
+ artifactType: 'apk' | 'aab';
9
+ /** Resolved keystore file paths per workflow (relative to project root), if found on disk. */
10
+ keystorePaths: Record<string, string>;
11
+ }
12
+ export interface WorkflowVariantConfig {
13
+ /** Value for the VARIANT env var, e.g. "debug", "release", "stagingDebug". */
14
+ variant: string;
15
+ /** Capitalised build type for gradle-build inputs, e.g. "Debug", "Release", "StagingDebug". */
16
+ buildType: string;
17
+ /** Gradle assemble task, e.g. "assembleDebug", "assembleRelease", "assembleStagingDebug". */
18
+ gradleTask: string;
19
+ }
20
+ export interface WorkflowVariants {
21
+ primary: WorkflowVariantConfig;
22
+ pullRequest: WorkflowVariantConfig;
23
+ release: WorkflowVariantConfig;
24
+ }
25
+ export interface IosWorkflowSchemeConfig {
26
+ scheme: string;
27
+ configuration: string;
28
+ distributionMethod: string;
29
+ }
30
+ export interface IosWorkflowVariants {
31
+ primary: IosWorkflowSchemeConfig;
32
+ pullRequest: IosWorkflowSchemeConfig;
33
+ release: IosWorkflowSchemeConfig;
34
+ }
35
+ export interface IosSetupOptions {
36
+ cocoaPods: boolean;
37
+ codeSigning: boolean;
38
+ appStoreDeploy: boolean;
39
+ bundleId: string;
40
+ }
41
+ export declare function generateAndroidPipeline(javaVersion?: number, setup?: SetupOptions, variants?: WorkflowVariants, cacheTechnology?: "gradle" | "kmm", metaNamespace?: 'invarn' | 'cibuild.io'): string;
42
+ export declare function generateIosPipeline(projectPath: string, setup: IosSetupOptions, variants: IosWorkflowVariants, metaNamespace?: 'invarn' | 'cibuild.io'): string;
1
43
  export declare function handleBuildCommand(detectMobileProjectRoot: (dir: string) => "android" | "ios" | "kmm" | null, options?: {
2
44
  createPipelinesDir?: boolean;
3
45
  nonInteractive?: boolean;
46
+ metaNamespace?: 'invarn' | 'cibuild.io';
4
47
  }): Promise<void>;
5
48
  //# sourceMappingURL=build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"AAm8BA,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"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,oFAAoF;IACpF,YAAY,EAAE,KAAK,GAAG,KAAK,CAAC;IAC5B,8FAA8F;IAC9F,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,qBAAqB;IACpC,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,+FAA+F;IAC/F,SAAS,EAAE,MAAM,CAAC;IAClB,6FAA6F;IAC7F,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,qBAAqB,CAAC;IAC/B,WAAW,EAAE,qBAAqB,CAAC;IACnC,OAAO,EAAE,qBAAqB,CAAC;CAChC;AAiBD,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,uBAAuB,CAAC;IACjC,WAAW,EAAE,uBAAuB,CAAC;IACrC,OAAO,EAAE,uBAAuB,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAoJD,wBAAgB,uBAAuB,CACrC,WAAW,GAAE,MAAW,EACxB,KAAK,GAAE,YAAgL,EACvL,QAAQ,GAAE,gBAAmC,EAC7C,eAAe,GAAE,QAAQ,GAAG,KAAgB,EAC5C,aAAa,GAAE,QAAQ,GAAG,YAA2B,GACpD,MAAM,CA2NR;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,mBAAmB,EAC7B,aAAa,GAAE,QAAQ,GAAG,YAA2B,GACpD,MAAM,CA4LR;AAgVD,wBAAsB,kBAAkB,CACtC,uBAAuB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI,EAC1E,OAAO,GAAE;IACP,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;CACpC,GACL,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, cacheTechnology = "gradle") {
125
+ export function generateAndroidPipeline(javaVersion = 17, setup = { keystore: false, keystoreProperties: false, googleServices: false, googlePlayDeploy: false, googlePlayPackageName: '', artifactType: 'apk', keystorePaths: {} }, variants = DEFAULT_VARIANTS, cacheTechnology = "gradle", metaNamespace = 'cibuild.io') {
126
126
  const keystoreStepFor = (wf) => {
127
127
  if (!setup.keystore)
128
128
  return "";
@@ -204,7 +204,7 @@ function generateAndroidPipeline(javaVersion = 17, setup = { keystore: false, ke
204
204
  return `format_version: '1'
205
205
 
206
206
  meta:
207
- cibuild.io:
207
+ ${metaNamespace}:
208
208
  stack: ${stack}
209
209
  machine_type: standard
210
210
  platform: ${platformMeta}
@@ -333,7 +333,7 @@ ${googlePlayStep}
333
333
  fi
334
334
  `;
335
335
  }
336
- function generateIosPipeline(projectPath, setup, variants) {
336
+ export function generateIosPipeline(projectPath, setup, variants, metaNamespace = 'cibuild.io') {
337
337
  const { primary: pv, pullRequest: prv, release: rv } = variants;
338
338
  // Global scheme is the primary scheme (used as default across workflows)
339
339
  const globalScheme = pv.scheme;
@@ -409,7 +409,7 @@ function generateIosPipeline(projectPath, setup, variants) {
409
409
  return `format_version: '1'
410
410
 
411
411
  meta:
412
- cibuild.io:
412
+ ${metaNamespace}:
413
413
  stack: macos-xcode-26.4
414
414
  machine_type: performance
415
415
  platform: ios
@@ -771,7 +771,7 @@ async function handleIosBuildCommand(cwd, options = {}) {
771
771
  }
772
772
  }
773
773
  // 7. Generate and write the pipeline
774
- const yaml = generateIosPipeline(iosScanResult.projectPath, iosSetupOptions, iosVariants);
774
+ const yaml = generateIosPipeline(iosScanResult.projectPath, iosSetupOptions, iosVariants, options.metaNamespace);
775
775
  writeFileSync(outputPath, yaml, "utf-8");
776
776
  console.log("\n✅ Generated .ci/pipelines/cibuild.yml");
777
777
  console.log(" Platform: iOS");
@@ -1042,7 +1042,7 @@ async function handleAndroidBuildCommand(cwd, options, cacheTechnology) {
1042
1042
  // that exact JDK — a newer JDK can cross-compile. Enforce a minimum of 17
1043
1043
  // since ubuntu-latest runners ship with JDK 17+ and JDK <17 is unavailable.
1044
1044
  const javaVersion = Math.max(scanResult.detectedJavaVersion ?? 17, 17);
1045
- const yaml = generateAndroidPipeline(javaVersion, setupOptions, variants, cacheTechnology);
1045
+ const yaml = generateAndroidPipeline(javaVersion, setupOptions, variants, cacheTechnology, options.metaNamespace);
1046
1046
  writeFileSync(outputPath, yaml, "utf-8");
1047
1047
  console.log("\n✅ Generated .ci/pipelines/cibuild.yml");
1048
1048
  console.log(` Platform: ${platformLabel}`);
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Unit tests for the YAML scaffold generators in build.ts.
3
+ *
4
+ * `generateIosPipeline` and `generateAndroidPipeline` are pure
5
+ * functions today (input → YAML string), so we drive them directly
6
+ * rather than through the interactive `handleBuildCommand` shell.
7
+ *
8
+ * Focus is the meta-namespace flip: standalone `cibuild` callers get
9
+ * `meta.cibuild.io:` (default), while invarn-CLI-routed calls pass
10
+ * `metaNamespace: 'invarn'` and get `meta.invarn:`.
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=build.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.test.d.ts","sourceRoot":"","sources":["../../../src/commands/build.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Unit tests for the YAML scaffold generators in build.ts.
3
+ *
4
+ * `generateIosPipeline` and `generateAndroidPipeline` are pure
5
+ * functions today (input → YAML string), so we drive them directly
6
+ * rather than through the interactive `handleBuildCommand` shell.
7
+ *
8
+ * Focus is the meta-namespace flip: standalone `cibuild` callers get
9
+ * `meta.cibuild.io:` (default), while invarn-CLI-routed calls pass
10
+ * `metaNamespace: 'invarn'` and get `meta.invarn:`.
11
+ */
12
+ import { describe, test, expect } from '@jest/globals';
13
+ import { generateIosPipeline, generateAndroidPipeline, } from './build.js';
14
+ const iosSetup = {
15
+ cocoaPods: false,
16
+ codeSigning: false,
17
+ appStoreDeploy: false,
18
+ bundleId: 'com.example.app',
19
+ };
20
+ const iosVariants = {
21
+ primary: { scheme: 'MyApp', configuration: 'Debug', distributionMethod: 'development' },
22
+ pullRequest: { scheme: 'MyApp', configuration: 'Debug', distributionMethod: 'development' },
23
+ release: { scheme: 'MyApp', configuration: 'Release', distributionMethod: 'app-store' },
24
+ };
25
+ describe('generateIosPipeline — meta namespace', () => {
26
+ test('emits meta.cibuild.io: by default (standalone cibuild path)', () => {
27
+ const yaml = generateIosPipeline('./MyApp', iosSetup, iosVariants);
28
+ expect(yaml).toContain('meta:\n cibuild.io:\n');
29
+ expect(yaml).not.toContain('meta:\n invarn:\n');
30
+ });
31
+ test('emits meta.cibuild.io: when explicitly passed "cibuild.io"', () => {
32
+ const yaml = generateIosPipeline('./MyApp', iosSetup, iosVariants, 'cibuild.io');
33
+ expect(yaml).toContain('meta:\n cibuild.io:\n');
34
+ expect(yaml).not.toContain('meta:\n invarn:\n');
35
+ });
36
+ test('emits meta.invarn: when explicitly passed "invarn" (invarn-CLI path)', () => {
37
+ const yaml = generateIosPipeline('./MyApp', iosSetup, iosVariants, 'invarn');
38
+ expect(yaml).toContain('meta:\n invarn:\n');
39
+ expect(yaml).not.toContain('meta:\n cibuild.io:\n');
40
+ });
41
+ });
42
+ describe('generateAndroidPipeline — meta namespace', () => {
43
+ test('emits meta.cibuild.io: by default (standalone cibuild path)', () => {
44
+ const yaml = generateAndroidPipeline();
45
+ expect(yaml).toContain('meta:\n cibuild.io:\n');
46
+ expect(yaml).not.toContain('meta:\n invarn:\n');
47
+ });
48
+ test('emits meta.cibuild.io: when explicitly passed "cibuild.io"', () => {
49
+ const yaml = generateAndroidPipeline(17, undefined, undefined, 'gradle', 'cibuild.io');
50
+ expect(yaml).toContain('meta:\n cibuild.io:\n');
51
+ expect(yaml).not.toContain('meta:\n invarn:\n');
52
+ });
53
+ test('emits meta.invarn: when explicitly passed "invarn" (invarn-CLI path)', () => {
54
+ const yaml = generateAndroidPipeline(17, undefined, undefined, 'gradle', 'invarn');
55
+ expect(yaml).toContain('meta:\n invarn:\n');
56
+ expect(yaml).not.toContain('meta:\n cibuild.io:\n');
57
+ });
58
+ });
59
+ //# sourceMappingURL=build.test.js.map
@@ -1,3 +1,12 @@
1
+ import type { YAMLPipeline } from '../yaml/types.js';
1
2
  import '../yaml/steps/index.js';
3
+ /**
4
+ * Detects the target platform of a workflow.
5
+ * Checks (in order):
6
+ * 1. meta.invarn.platform (preferred), falling back to meta.cibuild.io.platform
7
+ * 2. Existing steps in the workflow — fallback
8
+ * Returns 'ios' or 'android', or null if platform cannot be determined.
9
+ */
10
+ export declare function detectWorkflowPlatform(pipeline: YAMLPipeline, workflowName: string): 'ios' | 'android' | null;
2
11
  export declare function handleEditCommand(pipelinePath: string, workflowName?: string): Promise<void>;
3
12
  //# sourceMappingURL=edit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../../src/commands/edit.ts"],"names":[],"mappings":"AAUA,OAAO,wBAAwB,CAAC;AAkYhC,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CA+Sf"}
1
+ {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../../src/commands/edit.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAY,MAAM,kBAAkB,CAAC;AAE/D,OAAO,wBAAwB,CAAC;AAsShC;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,IAAI,CAgB7G;AAqED,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CA+Sf"}
@@ -4,6 +4,7 @@ import { resolve, basename } from 'node:path';
4
4
  import * as yaml from 'js-yaml';
5
5
  import prompts from 'prompts';
6
6
  import { loadYAMLPipeline } from '../yaml/parser.js';
7
+ import { getInvarnMeta } from '../yaml/meta-helpers.js';
7
8
  import { SecretsManager } from '../yaml/secrets-manager.js';
8
9
  import { getAvailableSteps, getStepMetadata } from '../yaml/steps/registry.js';
9
10
  // Ensure the step registry is populated
@@ -286,13 +287,13 @@ async function handleAddRegisteredStep(pipelinePath, workflowName, _pipeline, st
286
287
  /**
287
288
  * Detects the target platform of a workflow.
288
289
  * Checks (in order):
289
- * 1. meta.cibuild.io.platform explicit declaration, most reliable
290
+ * 1. meta.invarn.platform (preferred), falling back to meta.cibuild.io.platform
290
291
  * 2. Existing steps in the workflow — fallback
291
292
  * Returns 'ios' or 'android', or null if platform cannot be determined.
292
293
  */
293
- function detectWorkflowPlatform(pipeline, workflowName) {
294
- // 1. Explicit platform declaration in meta
295
- const metaPlatform = pipeline.meta?.['cibuild.io']?.platform;
294
+ export function detectWorkflowPlatform(pipeline, workflowName) {
295
+ // 1. Explicit platform declaration on the Invarn-owned meta block
296
+ const metaPlatform = getInvarnMeta(pipeline)?.platform;
296
297
  if (metaPlatform === 'ios' || metaPlatform === 'android' || metaPlatform === 'kmm')
297
298
  return metaPlatform === 'kmm' ? 'ios' : metaPlatform;
298
299
  // 2. Scan existing steps as fallback
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Unit tests for edit-command helpers.
3
+ * Focused on the dual-read of meta.invarn + meta.cibuild.io for the platform hint.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=edit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit.test.d.ts","sourceRoot":"","sources":["../../../src/commands/edit.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Unit tests for edit-command helpers.
3
+ * Focused on the dual-read of meta.invarn + meta.cibuild.io for the platform hint.
4
+ */
5
+ import { describe, test, expect } from '@jest/globals';
6
+ import { detectWorkflowPlatform } from './edit.js';
7
+ describe('detectWorkflowPlatform — meta dual-read', () => {
8
+ function pipelineWithMeta(meta) {
9
+ return {
10
+ format_version: '4',
11
+ meta,
12
+ workflows: {
13
+ primary: {
14
+ steps: [{ 'script@1.0.0': { inputs: {} } }],
15
+ },
16
+ },
17
+ };
18
+ }
19
+ test('reads platform from meta.invarn when only invarn is present', () => {
20
+ const pipeline = pipelineWithMeta({
21
+ invarn: { platform: 'ios' },
22
+ });
23
+ expect(detectWorkflowPlatform(pipeline, 'primary')).toBe('ios');
24
+ });
25
+ test('prefers meta.invarn.platform over meta.cibuild.io.platform when both are present', () => {
26
+ const pipeline = pipelineWithMeta({
27
+ invarn: { platform: 'ios' },
28
+ 'cibuild.io': { platform: 'android' },
29
+ });
30
+ expect(detectWorkflowPlatform(pipeline, 'primary')).toBe('ios');
31
+ });
32
+ test('falls back to meta.cibuild.io.platform when meta.invarn is absent', () => {
33
+ const pipeline = pipelineWithMeta({
34
+ 'cibuild.io': { platform: 'android' },
35
+ });
36
+ expect(detectWorkflowPlatform(pipeline, 'primary')).toBe('android');
37
+ });
38
+ test('maps kmm to ios from meta.invarn', () => {
39
+ const pipeline = pipelineWithMeta({
40
+ invarn: { platform: 'kmm' },
41
+ });
42
+ expect(detectWorkflowPlatform(pipeline, 'primary')).toBe('ios');
43
+ });
44
+ });
45
+ //# sourceMappingURL=edit.test.js.map
@@ -1,6 +1,13 @@
1
1
  export interface InitOptions {
2
2
  importPath?: string;
3
3
  create?: boolean;
4
+ /**
5
+ * Which namespace to emit in the generated `meta.<ns>` block of the new
6
+ * pipeline YAML. Defaults to `'cibuild.io'` so the standalone `cibuild`
7
+ * CLI keeps emitting the legacy namespace. Invarn-CLI-routed calls pass
8
+ * `'invarn'` so wrapped flows emit the canonical Invarn namespace.
9
+ */
10
+ metaNamespace?: 'invarn' | 'cibuild.io';
4
11
  }
5
12
  /**
6
13
  * Check dependencies, then either import an existing YAML pipeline or
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAUD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0Q7E"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;CACzC;AAUD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2Q7E"}
@@ -5,6 +5,7 @@ import { resolve } from "node:path";
5
5
  import prompts from "prompts";
6
6
  import { detectMobileProjectRoot } from "../shared/detect-project.js";
7
7
  import { ensureCiBuildGitignoreEntries } from "../shared/gitignore.js";
8
+ import { getInvarnMeta } from "../yaml/meta-helpers.js";
8
9
  import { loadYAMLPipeline } from "../yaml/parser.js";
9
10
  import { handleBuildCommand } from "./build.js";
10
11
  import { generateGitHubActionsWorkflow } from "./github-workflow.js";
@@ -188,6 +189,7 @@ export async function handleInitCommand(opts = {}) {
188
189
  await handleBuildCommand(detectMobileProjectRoot, {
189
190
  createPipelinesDir: true,
190
191
  nonInteractive: !!opts.create,
192
+ metaNamespace: opts.metaNamespace,
191
193
  });
192
194
  process.exit(0);
193
195
  }
@@ -238,7 +240,7 @@ export async function handleInitCommand(opts = {}) {
238
240
  let importedPlatform = null;
239
241
  try {
240
242
  const yamlPipeline = loadYAMLPipeline(destPath);
241
- const meta = yamlPipeline.meta?.["cibuild.io"];
243
+ const meta = getInvarnMeta(yamlPipeline);
242
244
  if (meta?.platform === "ios" || meta?.platform === "android" || meta?.platform === "kmm") {
243
245
  importedPlatform = meta.platform === "kmm" ? "ios" : meta.platform;
244
246
  }
@@ -1 +1 @@
1
- {"version":3,"file":"converter.d.ts","sourceRoot":"","sources":["../../../src/yaml/converter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAA4B,MAAM,YAAY,CAAC;AAKzE,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAkCD;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,QAAQ,EAChB,YAAY,CAAC,EAAE,MAAM,EACrB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,gBAAgB,CAAC,CA0I3B;AAwGD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,EAAE,CAEtE"}
1
+ {"version":3,"file":"converter.d.ts","sourceRoot":"","sources":["../../../src/yaml/converter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAA4B,MAAM,YAAY,CAAC;AAMzE,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAkCD;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,QAAQ,EAChB,YAAY,CAAC,EAAE,MAAM,EACrB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,gBAAgB,CAAC,CA0I3B;AAwGD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,EAAE,CAEtE"}
@@ -2,6 +2,7 @@
2
2
  * Converts YAML pipeline workflows to PipelineDef format
3
3
  * This bridges the YAML configuration with the existing runner engine
4
4
  */
5
+ import { getInvarnMeta } from './meta-helpers.js';
5
6
  import { detectPlatformInfo } from './platform-detector.js';
6
7
  import { EnvResolver } from './env-resolver.js';
7
8
  import { getStepExecutor, UnrecognizedStepError } from './steps/registry.js';
@@ -61,16 +62,16 @@ export async function convertYAMLToPipelineDef(pipeline, config, workflowName, y
61
62
  // Step 3: Environment variable resolution
62
63
  const envResolver = new EnvResolver(pipeline, selectedWorkflow, selectedWorkflowName, platformInfo.platform, platformInfo.stack, yamlFilePath);
63
64
  // Step 3.5: Auto-inject cache steps if cachedBuild is enabled
64
- const cibuildMeta = pipeline.meta?.['cibuild.io'];
65
- const cachedBuild = cibuildMeta?.cachedBuild === true;
65
+ const invarnMeta = getInvarnMeta(pipeline);
66
+ const cachedBuild = invarnMeta?.cachedBuild === true;
66
67
  let workflowSteps = [...selectedWorkflow.steps];
67
- if (cachedBuild && cibuildMeta?.platform) {
68
+ if (cachedBuild && invarnMeta?.platform) {
68
69
  const platformToCacheTechnology = {
69
70
  ios: 'ios',
70
71
  android: 'gradle',
71
72
  kmm: 'kmm',
72
73
  };
73
- const technology = platformToCacheTechnology[cibuildMeta.platform];
74
+ const technology = platformToCacheTechnology[invarnMeta.platform];
74
75
  if (technology) {
75
76
  // Remove any manually written cache-pull/cache-push steps
76
77
  workflowSteps = workflowSteps.filter(step => {
@@ -492,6 +492,57 @@ describe('convertYAMLToPipelineDef - Warning System', () => {
492
492
  expect(result.pipeline.steps[2].name).toBe('cache-push');
493
493
  expect(result.pipeline.steps[2].script).toContain('kmm-');
494
494
  });
495
+ test('should inject cache steps when cachedBuild + platform live under meta.invarn', async () => {
496
+ const pipeline = {
497
+ format_version: '1',
498
+ meta: {
499
+ invarn: {
500
+ stack: 'macos-xcode-26.4',
501
+ platform: 'kmm',
502
+ cachedBuild: true,
503
+ },
504
+ },
505
+ workflows: {
506
+ build: {
507
+ steps: [
508
+ { 'script@1.0.0': { inputs: { content: 'echo "building"' } } },
509
+ ],
510
+ },
511
+ },
512
+ };
513
+ const result = await convertYAMLToPipelineDef(pipeline, mockConfig, 'build');
514
+ expect(result.pipeline.steps.length).toBe(3);
515
+ expect(result.pipeline.steps[0].name).toBe('cache-pull');
516
+ expect(result.pipeline.steps[1].name).toBe('script');
517
+ expect(result.pipeline.steps[2].name).toBe('cache-push');
518
+ });
519
+ test('should prefer meta.invarn cachedBuild over meta.cibuild.io when both are present', async () => {
520
+ const pipeline = {
521
+ format_version: '1',
522
+ meta: {
523
+ invarn: {
524
+ stack: 'macos-xcode-26.4',
525
+ platform: 'kmm',
526
+ cachedBuild: true,
527
+ },
528
+ 'cibuild.io': {
529
+ stack: 'macos-xcode-26.4',
530
+ platform: 'kmm',
531
+ cachedBuild: false,
532
+ },
533
+ },
534
+ workflows: {
535
+ build: {
536
+ steps: [
537
+ { 'script@1.0.0': { inputs: { content: 'echo "building"' } } },
538
+ ],
539
+ },
540
+ },
541
+ };
542
+ const result = await convertYAMLToPipelineDef(pipeline, mockConfig, 'build');
543
+ expect(result.pipeline.steps.length).toBe(3);
544
+ expect(result.pipeline.steps[0].name).toBe('cache-pull');
545
+ });
495
546
  });
496
547
  });
497
548
  //# sourceMappingURL=converter.test.js.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Helpers for reading the Invarn-owned metadata block from a parsed YAML pipeline.
3
+ *
4
+ * Invarn-owned configuration historically lived under `meta.cibuild.io` and is
5
+ * being migrated to `meta.invarn`. Both keys share the same shape (YAMLCIBuildMeta).
6
+ * Readers should always go through `getInvarnMeta()` so that:
7
+ *
8
+ * 1. New YAMLs that emit `meta.invarn` are read directly.
9
+ * 2. Legacy YAMLs that still carry `meta.cibuild.io` continue to work.
10
+ * 3. When both blocks are present, `meta.invarn` wins as a whole — the legacy
11
+ * block is treated as stale and ignored field-by-field, matching the
12
+ * migration plan in `.scratch/cibuild-io-namespace-rename/PRD.md`.
13
+ *
14
+ * Removing the `meta.cibuild.io` fallback is the last step of that migration; it
15
+ * happens in a single place once usage of the legacy namespace drops to zero.
16
+ */
17
+ import type { YAMLCIBuildMeta, YAMLPipeline } from './types.js';
18
+ export declare function getInvarnMeta(pipeline: YAMLPipeline): YAMLCIBuildMeta | undefined;
19
+ //# sourceMappingURL=meta-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta-helpers.d.ts","sourceRoot":"","sources":["../../../src/yaml/meta-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,YAAY,GAAG,eAAe,GAAG,SAAS,CAEjF"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Helpers for reading the Invarn-owned metadata block from a parsed YAML pipeline.
3
+ *
4
+ * Invarn-owned configuration historically lived under `meta.cibuild.io` and is
5
+ * being migrated to `meta.invarn`. Both keys share the same shape (YAMLCIBuildMeta).
6
+ * Readers should always go through `getInvarnMeta()` so that:
7
+ *
8
+ * 1. New YAMLs that emit `meta.invarn` are read directly.
9
+ * 2. Legacy YAMLs that still carry `meta.cibuild.io` continue to work.
10
+ * 3. When both blocks are present, `meta.invarn` wins as a whole — the legacy
11
+ * block is treated as stale and ignored field-by-field, matching the
12
+ * migration plan in `.scratch/cibuild-io-namespace-rename/PRD.md`.
13
+ *
14
+ * Removing the `meta.cibuild.io` fallback is the last step of that migration; it
15
+ * happens in a single place once usage of the legacy namespace drops to zero.
16
+ */
17
+ export function getInvarnMeta(pipeline) {
18
+ return pipeline.meta?.invarn ?? pipeline.meta?.['cibuild.io'];
19
+ }
20
+ //# sourceMappingURL=meta-helpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../src/yaml/parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,qBAAa,cAAe,SAAQ,KAAK;aACM,QAAQ,CAAC,EAAE,MAAM;gBAAlD,OAAO,EAAE,MAAM,EAAkB,QAAQ,CAAC,EAAE,MAAM,YAAA;CAI/D;AAED,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CA0C/D;AA8GD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,EAAE,CAEjE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAEjF"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../../src/yaml/parser.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,qBAAa,cAAe,SAAQ,KAAK;aACM,QAAQ,CAAC,EAAE,MAAM;gBAAlD,OAAO,EAAE,MAAM,EAAkB,QAAQ,CAAC,EAAE,MAAM,YAAA;CAI/D;AAED,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CA0C/D;AAkHD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,EAAE,CAEjE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAEjF"}
@@ -102,26 +102,29 @@ function validateYAMLPipeline(parsed, filePath) {
102
102
  throw new YAMLValidationError(`Invalid platform '${parsed.meta.platform}' in ${filePath}. Must be one of: ${validPlatforms.join(', ')}`);
103
103
  }
104
104
  }
105
- // Validate cibuild.io section if present
106
- if (parsed.meta['cibuild.io']) {
107
- const cibuildMeta = parsed.meta['cibuild.io'];
108
- if (typeof cibuildMeta !== 'object') {
109
- throw new YAMLValidationError(`Invalid 'meta.cibuild.io' section in ${filePath}: Expected an object`);
110
- }
111
- // Validate stack pattern if specified
112
- if (cibuildMeta.stack && typeof cibuildMeta.stack === 'string') {
113
- const stackPattern = /^(macos|linux)-/;
114
- if (cibuildMeta.stack !== 'local' && !stackPattern.test(cibuildMeta.stack)) {
115
- throw new YAMLValidationError(`Invalid stack '${cibuildMeta.stack}' in ${filePath}. Stack must be 'local' or start with 'macos-' or 'linux-'`);
116
- }
117
- }
118
- // Validate machine_type if specified
119
- if (cibuildMeta.machine_type) {
120
- const validMachineTypes = ['standard', 'performance'];
121
- if (!validMachineTypes.includes(cibuildMeta.machine_type)) {
122
- throw new YAMLValidationError(`Invalid machine_type '${cibuildMeta.machine_type}' in ${filePath}. Must be one of: ${validMachineTypes.join(', ')}`);
123
- }
124
- }
105
+ // Validate the Invarn-owned metadata block under either namespace.
106
+ // Both `meta.invarn` (preferred) and `meta.cibuild.io` (legacy) share the same shape.
107
+ validateInvarnMetaBlock(parsed.meta.invarn, 'meta.invarn', filePath);
108
+ validateInvarnMetaBlock(parsed.meta['cibuild.io'], 'meta.cibuild.io', filePath);
109
+ }
110
+ }
111
+ function validateInvarnMetaBlock(block, blockName, filePath) {
112
+ if (block === undefined || block === null) {
113
+ return;
114
+ }
115
+ if (typeof block !== 'object') {
116
+ throw new YAMLValidationError(`Invalid '${blockName}' section in ${filePath}: Expected an object`);
117
+ }
118
+ if (block.stack && typeof block.stack === 'string') {
119
+ const stackPattern = /^(macos|linux)-/;
120
+ if (block.stack !== 'local' && !stackPattern.test(block.stack)) {
121
+ throw new YAMLValidationError(`Invalid stack '${block.stack}' in ${filePath}. Stack must be 'local' or start with 'macos-' or 'linux-'`);
122
+ }
123
+ }
124
+ if (block.machine_type) {
125
+ const validMachineTypes = ['standard', 'performance'];
126
+ if (!validMachineTypes.includes(block.machine_type)) {
127
+ throw new YAMLValidationError(`Invalid machine_type '${block.machine_type}' in ${filePath}. Must be one of: ${validMachineTypes.join(', ')}`);
125
128
  }
126
129
  }
127
130
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Unit tests for parser validation of the meta.invarn / meta.cibuild.io blocks.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=parser.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.test.d.ts","sourceRoot":"","sources":["../../../src/yaml/parser.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Unit tests for parser validation of the meta.invarn / meta.cibuild.io blocks.
3
+ */
4
+ import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
5
+ import { writeFileSync, mkdtempSync, rmSync } from 'fs';
6
+ import { tmpdir } from 'os';
7
+ import { join } from 'path';
8
+ import { loadYAMLPipeline, YAMLValidationError } from './parser.js';
9
+ describe('parser validation — meta.invarn block', () => {
10
+ let workDir;
11
+ beforeEach(() => {
12
+ workDir = mkdtempSync(join(tmpdir(), 'cibuild-parser-test-'));
13
+ });
14
+ afterEach(() => {
15
+ rmSync(workDir, { recursive: true, force: true });
16
+ });
17
+ function writeYaml(name, content) {
18
+ const path = join(workDir, name);
19
+ writeFileSync(path, content, 'utf-8');
20
+ return path;
21
+ }
22
+ test('accepts a well-formed meta.invarn block', () => {
23
+ const path = writeYaml('good.yml', `format_version: '4'
24
+ meta:
25
+ invarn:
26
+ stack: macos-xcode-26.4
27
+ machine_type: standard
28
+ workflows:
29
+ primary:
30
+ steps:
31
+ - script@1.0.0:
32
+ inputs:
33
+ content: echo "test"
34
+ `);
35
+ expect(() => loadYAMLPipeline(path)).not.toThrow();
36
+ });
37
+ test('rejects a meta.invarn block with an invalid stack pattern', () => {
38
+ const path = writeYaml('bad-stack.yml', `format_version: '4'
39
+ meta:
40
+ invarn:
41
+ stack: windows-fancy-1.0
42
+ workflows:
43
+ primary:
44
+ steps:
45
+ - script@1.0.0:
46
+ inputs:
47
+ content: echo
48
+ `);
49
+ expect(() => loadYAMLPipeline(path)).toThrow(YAMLValidationError);
50
+ expect(() => loadYAMLPipeline(path)).toThrow(/Invalid stack 'windows-fancy-1.0'/);
51
+ });
52
+ test('rejects a meta.invarn block with an invalid machine_type', () => {
53
+ const path = writeYaml('bad-machine.yml', `format_version: '4'
54
+ meta:
55
+ invarn:
56
+ stack: linux-docker-android-22.04
57
+ machine_type: turbo
58
+ workflows:
59
+ primary:
60
+ steps:
61
+ - script@1.0.0:
62
+ inputs:
63
+ content: echo
64
+ `);
65
+ expect(() => loadYAMLPipeline(path)).toThrow(YAMLValidationError);
66
+ expect(() => loadYAMLPipeline(path)).toThrow(/Invalid machine_type 'turbo'/);
67
+ });
68
+ test('still rejects malformed meta.cibuild.io for backward compat', () => {
69
+ const path = writeYaml('legacy-bad.yml', `format_version: '4'
70
+ meta:
71
+ cibuild.io:
72
+ stack: bsd-something
73
+ workflows:
74
+ primary:
75
+ steps:
76
+ - script@1.0.0:
77
+ inputs:
78
+ content: echo
79
+ `);
80
+ expect(() => loadYAMLPipeline(path)).toThrow(/Invalid stack 'bsd-something'/);
81
+ });
82
+ });
83
+ //# sourceMappingURL=parser.test.js.map
@@ -16,7 +16,7 @@ export declare class PlatformDetector {
16
16
  constructor(pipeline: YAMLPipeline, workflow: YAMLWorkflow, workflowName: string);
17
17
  /**
18
18
  * Detects the platform using 3-tier priority detection
19
- * Priority 1: meta.cibuild.io.stack
19
+ * Priority 1: meta.invarn.stack (preferred), falling back to meta.cibuild.io.stack
20
20
  * Priority 2: meta.platform
21
21
  * Priority 3: auto-detect from workflow steps
22
22
  *