@invarn/cibuild 2.6.5 → 2.6.7

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.
@@ -0,0 +1,116 @@
1
+ /**
2
+ * A cache key that cannot be computed costs a cache, not a pipeline.
3
+ *
4
+ * `cache_key: pods-{{checksum "Podfile.lock"}}` in a repository with no
5
+ * Podfile.lock used to abort conversion, so the build died at `cache-pull`
6
+ * before compiling a single file — including in repositories that use Swift
7
+ * Package Manager and have no reason to own a CocoaPods lockfile at all.
8
+ * Every other step keeps the hard failure: a checksum in a real input is a
9
+ * value that step needs.
10
+ */
11
+ import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
12
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
13
+ import { tmpdir } from 'node:os';
14
+ import { join } from 'node:path';
15
+ import { convertYAMLToPipelineDef } from './converter.js';
16
+ import { clearRegistry, registerStep } from './steps/registry.js';
17
+ import { ScriptStepExecutor } from './steps/script.js';
18
+ import { CachePullStepExecutor, CachePushStepExecutor } from './steps/cache.js';
19
+ const mockConfig = {
20
+ artifactsDir: '/test/artifacts',
21
+ maxConcurrentJobs: 1,
22
+ paths: {
23
+ buildsDir: '/test/builds',
24
+ cacheDir: '/test/cache',
25
+ derivedDataDir: '/test/derived-data',
26
+ },
27
+ interpreters: {
28
+ bash: '/bin/bash',
29
+ python: 'python3',
30
+ ruby: 'ruby',
31
+ node: 'node',
32
+ },
33
+ };
34
+ let workdir;
35
+ let previousCwd;
36
+ beforeEach(() => {
37
+ clearRegistry();
38
+ registerStep('script', new ScriptStepExecutor());
39
+ registerStep('cache-pull', new CachePullStepExecutor());
40
+ registerStep('cache-push', new CachePushStepExecutor());
41
+ previousCwd = process.cwd();
42
+ workdir = mkdtempSync(join(tmpdir(), 'cibuild-checksum-'));
43
+ process.chdir(workdir);
44
+ });
45
+ afterEach(() => {
46
+ process.chdir(previousCwd);
47
+ rmSync(workdir, { recursive: true, force: true });
48
+ clearRegistry();
49
+ });
50
+ function pipelineWithPodsCache() {
51
+ return {
52
+ format_version: '1',
53
+ workflows: {
54
+ primary: {
55
+ steps: [
56
+ {
57
+ 'cache-pull': {
58
+ inputs: {
59
+ cache_key: 'pods-{{checksum "Podfile.lock"}}',
60
+ cache_paths: ['Pods'],
61
+ },
62
+ },
63
+ },
64
+ {
65
+ script: {
66
+ title: 'Build',
67
+ inputs: { content: 'echo building' },
68
+ },
69
+ },
70
+ {
71
+ 'cache-push': {
72
+ inputs: {
73
+ cache_key: 'pods-{{checksum "Podfile.lock"}}',
74
+ cache_paths: ['Pods'],
75
+ },
76
+ },
77
+ },
78
+ ],
79
+ },
80
+ },
81
+ };
82
+ }
83
+ describe('a cache key naming a file this repository does not have', () => {
84
+ test('skips the cache steps and keeps the build', async () => {
85
+ const result = await convertYAMLToPipelineDef(pipelineWithPodsCache(), mockConfig, 'primary');
86
+ expect(result.skippedSteps).toBe(2);
87
+ expect(Object.keys(result.pipeline.steps ?? {})).toHaveLength(1);
88
+ expect(result.warnings.join('\n')).toContain('Podfile.lock');
89
+ expect(result.warnings.join('\n')).toContain('uncached');
90
+ });
91
+ test('keeps the cache steps once the file is there', async () => {
92
+ writeFileSync(join(workdir, 'Podfile.lock'), 'PODFILE CHECKSUM: abc\n');
93
+ const result = await convertYAMLToPipelineDef(pipelineWithPodsCache(), mockConfig, 'primary');
94
+ expect(result.skippedSteps).toBe(0);
95
+ expect(Object.keys(result.pipeline.steps ?? {})).toHaveLength(3);
96
+ });
97
+ test('still fails a non-cache step whose checksum names a missing file', async () => {
98
+ const pipeline = {
99
+ format_version: '1',
100
+ workflows: {
101
+ primary: {
102
+ steps: [
103
+ {
104
+ script: {
105
+ title: 'Build',
106
+ inputs: { content: 'echo {{checksum "Podfile.lock"}}' },
107
+ },
108
+ },
109
+ ],
110
+ },
111
+ },
112
+ };
113
+ await expect(convertYAMLToPipelineDef(pipeline, mockConfig, 'primary')).rejects.toThrow('Podfile.lock');
114
+ });
115
+ });
116
+ //# sourceMappingURL=cache-key-missing-file.test.js.map
@@ -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;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"}
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;AA6CD;;;;;;;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,CAgK3B;AAwGD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,EAAE,CAEtE"}
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { getInvarnMeta } from './meta-helpers.js';
6
6
  import { detectPlatformInfo } from './platform-detector.js';
7
- import { EnvResolver } from './env-resolver.js';
7
+ import { EnvResolver, ChecksumFileMissingError } from './env-resolver.js';
8
8
  import { getStepExecutor, UnrecognizedStepError } from './steps/registry.js';
9
9
  export class YAMLConversionError extends Error {
10
10
  constructor(message) {
@@ -12,6 +12,15 @@ export class YAMLConversionError extends Error {
12
12
  this.name = 'YAMLConversionError';
13
13
  }
14
14
  }
15
+ /**
16
+ * The steps whose whole job is to make a build faster. Nothing they do is
17
+ * required for the build to be correct, so an input they cannot resolve
18
+ * costs a cache, not a pipeline.
19
+ */
20
+ const CACHE_STEPS = new Set(['cache-pull', 'cache-push']);
21
+ function isCacheStep(stepName) {
22
+ return CACHE_STEPS.has(stepName);
23
+ }
15
24
  /**
16
25
  * Mapping of common unsupported steps to CI Build alternatives
17
26
  */
@@ -123,7 +132,28 @@ export async function convertYAMLToPipelineDef(pipeline, config, workflowName, y
123
132
  throw error;
124
133
  }
125
134
  // Interpolate step inputs
126
- const interpolatedInputs = envResolver.interpolateObject(parsedStep.inputs, parsedStep.title || parsedStep.name);
135
+ //
136
+ // A cache step whose key cannot be computed is not a broken pipeline. The
137
+ // key is usually `{{checksum "<lockfile>"}}`, and a repository without
138
+ // that lockfile has nothing to key a cache on — so there is no cache, and
139
+ // the build runs cold. Letting the error through instead killed five iOS
140
+ // builds in a thirty-repository study at `cache-pull`, before a single
141
+ // file compiled, on a CocoaPods lockfile that two of them had no reason
142
+ // to have. Every other step keeps the hard failure: a checksum in a real
143
+ // input is a value the step needs.
144
+ let interpolatedInputs;
145
+ try {
146
+ interpolatedInputs = envResolver.interpolateObject(parsedStep.inputs, parsedStep.title || parsedStep.name);
147
+ }
148
+ catch (error) {
149
+ if (error instanceof ChecksumFileMissingError && isCacheStep(resolvedStepName)) {
150
+ warnings.push(`⚠️ ${resolvedStepName}: no ${error.filePath} in this repository, so there is ` +
151
+ `nothing to key the cache on (skipping — the build runs uncached)`);
152
+ skippedSteps++;
153
+ continue;
154
+ }
155
+ throw error;
156
+ }
127
157
  // Execute step to get script
128
158
  const stepDef = await stepExecutor.execute(interpolatedInputs, envResolver.getAll(), config);
129
159
  // Generate unique step ID
@@ -10,6 +10,19 @@ export declare class MissingEnvironmentVariableError extends Error {
10
10
  readonly hint?: string | undefined;
11
11
  constructor(variableName: string, stepName?: string | undefined, hint?: string | undefined);
12
12
  }
13
+ /**
14
+ * A `{{checksum "path"}}` naming a file this checkout does not have.
15
+ *
16
+ * Distinct from a generic read failure because a caller can reasonably
17
+ * decide it does not matter. A cache key is the usual case: the file is
18
+ * absent, so there is nothing to key a cache on, so there is no cache — which
19
+ * is a slower build, not a broken one.
20
+ */
21
+ export declare class ChecksumFileMissingError extends Error {
22
+ readonly filePath: string;
23
+ readonly stepName?: string | undefined;
24
+ constructor(filePath: string, stepName?: string | undefined);
25
+ }
13
26
  /**
14
27
  * EnvResolver handles environment variable merging and interpolation
15
28
  */
@@ -1 +1 @@
1
- {"version":3,"file":"env-resolver.d.ts","sourceRoot":"","sources":["../../../src/yaml/env-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAc,QAAQ,EAAE,MAAM,YAAY,CAAC;AAgEnF,qBAAa,+BAAgC,SAAQ,KAAK;aAEtC,YAAY,EAAE,MAAM;aACpB,QAAQ,CAAC,EAAE,MAAM;aACjB,IAAI,CAAC,EAAE,MAAM;gBAFb,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,YAAA,EACjB,IAAI,CAAC,EAAE,MAAM,YAAA;CAUhC;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,cAAc,CAAiB;gBAGrC,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,YAAY,EACtB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,QAAQ,EAClB,KAAK,CAAC,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM;IA0BvB;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKrC;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B;;;;OAIG;IACH,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMtC;;;OAGG;IACH,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAQhC;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;IA0BnD;;;;;OAKG;IACH,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,GAAG;IAoBnD;;;OAGG;IACH,sBAAsB,IAAI,IAAI;IAQ9B;;;;;;OAMG;IACH,OAAO,CAAC,UAAU;IAalB;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAwBvB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IA4FtB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAkBpB;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAuB9B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;CAS5B;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,YAAY,EACtB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,QAAQ,EAClB,KAAK,CAAC,EAAE,MAAM,GACb,WAAW,CAOb"}
1
+ {"version":3,"file":"env-resolver.d.ts","sourceRoot":"","sources":["../../../src/yaml/env-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAc,QAAQ,EAAE,MAAM,YAAY,CAAC;AAgEnF,qBAAa,+BAAgC,SAAQ,KAAK;aAEtC,YAAY,EAAE,MAAM;aACpB,QAAQ,CAAC,EAAE,MAAM;aACjB,IAAI,CAAC,EAAE,MAAM;gBAFb,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,YAAA,EACjB,IAAI,CAAC,EAAE,MAAM,YAAA;CAUhC;AAED;;;;;;;GAOG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;aAE/B,QAAQ,EAAE,MAAM;aAChB,QAAQ,CAAC,EAAE,MAAM;gBADjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,YAAA;CAKpC;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,cAAc,CAAiB;gBAGrC,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,YAAY,EACtB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,QAAQ,EAClB,KAAK,CAAC,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM;IA0BvB;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKrC;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B;;;;OAIG;IACH,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMtC;;;OAGG;IACH,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAQhC;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;IA0BnD;;;;;OAKG;IACH,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,GAAG;IAoBnD;;;OAGG;IACH,sBAAsB,IAAI,IAAI;IAQ9B;;;;;;OAMG;IACH,OAAO,CAAC,UAAU;IAalB;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IA0BvB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IA4FtB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAkBpB;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAuB9B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;CAS5B;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,YAAY,EACtB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,QAAQ,EAClB,KAAK,CAAC,EAAE,MAAM,GACb,WAAW,CAOb"}
@@ -76,6 +76,24 @@ export class MissingEnvironmentVariableError extends Error {
76
76
  this.name = 'MissingEnvironmentVariableError';
77
77
  }
78
78
  }
79
+ /**
80
+ * A `{{checksum "path"}}` naming a file this checkout does not have.
81
+ *
82
+ * Distinct from a generic read failure because a caller can reasonably
83
+ * decide it does not matter. A cache key is the usual case: the file is
84
+ * absent, so there is nothing to key a cache on, so there is no cache — which
85
+ * is a slower build, not a broken one.
86
+ */
87
+ export class ChecksumFileMissingError extends Error {
88
+ filePath;
89
+ stepName;
90
+ constructor(filePath, stepName) {
91
+ super(`Failed to compute checksum for "${filePath}": File not found: ${filePath}`);
92
+ this.filePath = filePath;
93
+ this.stepName = stepName;
94
+ this.name = 'ChecksumFileMissingError';
95
+ }
96
+ }
79
97
  /**
80
98
  * EnvResolver handles environment variable merging and interpolation
81
99
  */
@@ -158,7 +176,7 @@ export class EnvResolver {
158
176
  let result = str;
159
177
  // Format 1: {{checksum "file"}}
160
178
  result = result.replace(/\{\{checksum "([^"]+)"\}\}/g, (match, filePath) => {
161
- return this.computeChecksum(filePath);
179
+ return this.computeChecksum(filePath, stepName);
162
180
  });
163
181
  // Format 2: {{getenv "VAR"}}
164
182
  result = result.replace(/\{\{getenv "([^"]+)"\}\}/g, (match, varName) => {
@@ -230,14 +248,16 @@ export class EnvResolver {
230
248
  * @returns MD5 hash of file contents
231
249
  * @throws Error if file does not exist or cannot be read
232
250
  */
233
- computeChecksum(filePath) {
251
+ computeChecksum(filePath, stepName) {
252
+ // Resolve relative path to absolute path
253
+ const absolutePath = path.resolve(process.cwd(), filePath);
254
+ // A file this checkout does not have gets its own error type, so a
255
+ // caller that can carry on without it — a cache key — can tell it apart
256
+ // from a file that exists and could not be read.
257
+ if (!fs.existsSync(absolutePath)) {
258
+ throw new ChecksumFileMissingError(filePath, stepName);
259
+ }
234
260
  try {
235
- // Resolve relative path to absolute path
236
- const absolutePath = path.resolve(process.cwd(), filePath);
237
- // Check if file exists
238
- if (!fs.existsSync(absolutePath)) {
239
- throw new Error(`File not found: ${filePath}`);
240
- }
241
261
  // Read file and compute MD5 hash
242
262
  const fileBuffer = fs.readFileSync(absolutePath);
243
263
  const hash = crypto.createHash('md5');
@@ -1 +1 @@
1
- {"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/android.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/android.ts"],"names":[],"mappings":"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"}
@@ -22,7 +22,13 @@ export class SetJavaVersionStepExecutor extends BaseStepExecutor {
22
22
  name: `java-${javaVersion}`,
23
23
  description: `Java ${javaVersion} JDK`,
24
24
  timing: 'pre-execution',
25
- severity: 'info', // Info because we check multiple locations including Homebrew
25
+ // The step this validates ends in `exit 1` when it cannot find the
26
+ // JDK, so a failed check is a build that is already lost. Reported as
27
+ // info, it printed "Java <n> not found", let the run proceed, and the
28
+ // pipeline died at the very next step — the same message, one VM
29
+ // later. The check now looks in every place the step looks, so the
30
+ // two can only disagree about a JDK that is genuinely absent.
31
+ severity: 'error',
26
32
  hint: process.platform === 'darwin'
27
33
  ? `Install: brew install openjdk@${javaVersion}`
28
34
  : `Install: sudo apt install openjdk-${javaVersion}-jdk`,
@@ -81,6 +87,16 @@ export class SetJavaVersionStepExecutor extends BaseStepExecutor {
81
87
  }
82
88
  }
83
89
  }
90
+ // GitHub Actions publishes each preinstalled JDK as JAVA_HOME_<ver>_X64.
91
+ // The step reads it; without this the check could fail a build that the
92
+ // step itself would have run perfectly well.
93
+ const actionsHome = process.env[`JAVA_HOME_${version}_X64`];
94
+ if (actionsHome && existsSync(actionsHome)) {
95
+ return {
96
+ passed: true,
97
+ message: `Java ${version} found at: ${actionsHome} (GitHub Actions)`,
98
+ };
99
+ }
84
100
  // Check Linux paths
85
101
  const linuxPaths = [
86
102
  `/usr/lib/jvm/java-${version}-openjdk-amd64`,
@@ -736,6 +736,38 @@ describe('Step Implementations', () => {
736
736
  const result = await executor.execute(inputs, {}, testConfig);
737
737
  expect(result.script).toContain('11');
738
738
  });
739
+ // The generated script ends in `exit 1` when it cannot find the JDK, so a
740
+ // missing one is not information — it is the build, already lost. Reported
741
+ // as info it let the run proceed and die at the very next step.
742
+ test('missing JDK is a pre-execution error, not an info note', () => {
743
+ const executor = new SetJavaVersionStepExecutor();
744
+ const [requirement] = executor.getValidationRequirements({ java_version: '23' }, {}, testConfig);
745
+ expect(requirement.severity).toBe('error');
746
+ expect(requirement.timing).toBe('pre-execution');
747
+ });
748
+ // GitHub Actions publishes preinstalled JDKs as JAVA_HOME_<ver>_X64 and
749
+ // the step reads it; a check that did not would fail a build the step
750
+ // would have run.
751
+ test('finds a JDK GitHub Actions published, as the step does', async () => {
752
+ const executor = new SetJavaVersionStepExecutor();
753
+ // A version no developer machine has, so the Homebrew and java_home
754
+ // branches cannot answer first and the Actions branch is what is tested.
755
+ const key = 'JAVA_HOME_99_X64';
756
+ const previous = process.env[key];
757
+ process.env[key] = process.cwd();
758
+ try {
759
+ const [requirement] = executor.getValidationRequirements({ java_version: '99' }, {}, testConfig);
760
+ const result = await requirement.check();
761
+ expect(result.passed).toBe(true);
762
+ expect(result.message).toContain('GitHub Actions');
763
+ }
764
+ finally {
765
+ if (previous === undefined)
766
+ delete process.env[key];
767
+ else
768
+ process.env[key] = previous;
769
+ }
770
+ });
739
771
  });
740
772
  describe('GradleBuildStepExecutor', () => {
741
773
  test('should generate gradle build script', async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invarn/cibuild",
3
- "version": "2.6.5",
3
+ "version": "2.6.7",
4
4
  "description": "CI Build CLI — local pipeline orchestration and validation",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",