@invarn/cibuild 2.6.1 → 2.6.3
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/cli.cjs +10 -10
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +3 -1
- package/dist/src/commands/run.d.ts.map +1 -1
- package/dist/src/commands/run.js +7 -0
- package/dist/src/yaml/interactive-prompts.d.ts.map +1 -1
- package/dist/src/yaml/interactive-prompts.js +3 -1
- package/dist/src/yaml/meta-helpers.d.ts +9 -0
- package/dist/src/yaml/meta-helpers.d.ts.map +1 -1
- package/dist/src/yaml/meta-helpers.js +26 -0
- package/dist/src/yaml/step-validator-check-failure.test.d.ts +24 -0
- package/dist/src/yaml/step-validator-check-failure.test.d.ts.map +1 -0
- package/dist/src/yaml/step-validator-check-failure.test.js +130 -0
- package/dist/src/yaml/step-validator.d.ts +1 -0
- package/dist/src/yaml/step-validator.d.ts.map +1 -1
- package/dist/src/yaml/step-validator.js +24 -2
- package/dist/src/yaml/step-validator.test.d.ts +18 -0
- package/dist/src/yaml/step-validator.test.d.ts.map +1 -0
- package/dist/src/yaml/step-validator.test.js +140 -0
- package/dist/src/yaml/steps/android.d.ts.map +1 -1
- package/dist/src/yaml/steps/android.js +7 -3
- package/dist/src/yaml/steps/bitrise-android-tools.d.ts.map +1 -1
- package/dist/src/yaml/steps/bitrise-android-tools.js +4 -2
- package/package.json +1 -1
package/dist/src/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":";AASA,OAAO,uBAAuB,CAAC"}
|
package/dist/src/cli.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { resolve, extname } from "node:path";
|
|
3
3
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
4
|
+
// Static — see the note in `yaml/steps/android.ts`. `pkg` cannot evaluate a
|
|
5
|
+
// dynamic `import()` of a builtin.
|
|
6
|
+
import { createInterface } from "node:readline";
|
|
4
7
|
import prompts from "prompts";
|
|
5
8
|
import { SecretsManager } from "./yaml/secrets-manager.js";
|
|
6
9
|
import "./yaml/steps/index.js"; // Initialize step registry
|
|
@@ -255,7 +258,6 @@ Examples:
|
|
|
255
258
|
}
|
|
256
259
|
else {
|
|
257
260
|
// Collect value interactively via readline (handles multiline paste cleanly)
|
|
258
|
-
const { createInterface } = await import('node:readline');
|
|
259
261
|
console.log(`\n Enter value for ${varName}`);
|
|
260
262
|
console.log(' Paste content, then type . on its own line and press Enter:\n');
|
|
261
263
|
if (process.stdin.isTTY) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/commands/run.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,GAAE,UAAe,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/commands/run.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,GAAE,UAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAiJ3E"}
|
package/dist/src/commands/run.js
CHANGED
|
@@ -109,6 +109,13 @@ export async function handleRunCommand(opts = {}) {
|
|
|
109
109
|
await runner.runPipeline(pipeline, conversionResult.warnings, conversionResult.skippedSteps);
|
|
110
110
|
}
|
|
111
111
|
else if (isJavaScriptModule) {
|
|
112
|
+
// The one dynamic import that has to stay dynamic: the specifier is a
|
|
113
|
+
// user-supplied path, so no bundler can inline it. That means this
|
|
114
|
+
// branch does NOT work in the `pkg`-built macOS binary — it throws
|
|
115
|
+
// `Invalid host defined options` before the module loads. Nothing
|
|
116
|
+
// reaches it today: every curated template is YAML, and the runner only
|
|
117
|
+
// ever executes YAML pipelines. If JS pipelines are ever exposed as a
|
|
118
|
+
// supported path, they need a loader that does not rely on `import()`.
|
|
112
119
|
console.log(`Loading pipeline: ${pipelinePath}`);
|
|
113
120
|
const pipelineUrl = pathToFileURL(pipelinePath).href;
|
|
114
121
|
const pipelineModule = await import(pipelineUrl);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interactive-prompts.d.ts","sourceRoot":"","sources":["../../../src/yaml/interactive-prompts.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"interactive-prompts.d.ts","sourceRoot":"","sources":["../../../src/yaml/interactive-prompts.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,CAAS;gBAEd,cAAc,EAAE,cAAc,EAAE,QAAQ,CAAC,EAAE,MAAM;IAK7D;;;;;;OAMG;IACG,iBAAiB,CACrB,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,YAAY,CAAC;IA4FxB;;;;OAIG;IACH,OAAO,CAAC,mBAAmB;IAe3B;;;;OAIG;YACW,WAAW;IAWzB;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IA0BvB;;;;;OAKG;IACH,OAAO,CAAC,QAAQ;IAyBhB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAkBxB;;;OAGG;YACW,eAAe;IAqC7B;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Interactive prompts for missing environment variables
|
|
3
3
|
* Allows users to fill in missing values or cancel the build
|
|
4
4
|
*/
|
|
5
|
+
// Static — see the note in `steps/android.ts`. `pkg` cannot evaluate a
|
|
6
|
+
// dynamic `import()` of a builtin.
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
5
8
|
import prompts from 'prompts';
|
|
6
9
|
/**
|
|
7
10
|
* Interactive prompt manager for collecting missing environment variables
|
|
@@ -212,7 +215,6 @@ Finish with a short "Follow-up questions" numbered list only if clarification wo
|
|
|
212
215
|
* @param text Text to copy
|
|
213
216
|
*/
|
|
214
217
|
async copyToClipboard(text) {
|
|
215
|
-
const { spawn } = await import('child_process');
|
|
216
218
|
return new Promise((resolve, reject) => {
|
|
217
219
|
// Detect platform and use appropriate clipboard command
|
|
218
220
|
const platform = process.platform;
|
|
@@ -16,4 +16,13 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import type { YAMLCIBuildMeta, YAMLPipeline } from './types.js';
|
|
18
18
|
export declare function getInvarnMeta(pipeline: YAMLPipeline): YAMLCIBuildMeta | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the pipeline *declares* an app that spans both mobile platforms.
|
|
21
|
+
*
|
|
22
|
+
* Read only from the explicit declaration, never inferred from the steps.
|
|
23
|
+
* Inference would defeat the point: a workflow that mixes platforms by mistake
|
|
24
|
+
* is exactly what the gate exists to catch, and it looks identical to one that
|
|
25
|
+
* does so on purpose.
|
|
26
|
+
*/
|
|
27
|
+
export declare function declaresMultiplatformApp(pipeline: YAMLPipeline): boolean;
|
|
19
28
|
//# sourceMappingURL=meta-helpers.d.ts.map
|
|
@@ -1 +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"}
|
|
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;AAiBD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAGxE"}
|
|
@@ -17,4 +17,30 @@
|
|
|
17
17
|
export function getInvarnMeta(pipeline) {
|
|
18
18
|
return pipeline.meta?.invarn ?? pipeline.meta?.['cibuild.io'];
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* App types whose build is inherently both platforms in a single pass, and so
|
|
22
|
+
* whose one workflow legitimately holds both android and ios steps.
|
|
23
|
+
*
|
|
24
|
+
* A Kotlin Multiplatform build is the case that forced this: gradle assembles
|
|
25
|
+
* the XCFramework that `xcodebuild` then consumes, in that order, on one
|
|
26
|
+
* machine. It cannot be split — a workflow is one dispatch and two workflows
|
|
27
|
+
* share no filesystem, so the artifact would never reach the second half.
|
|
28
|
+
*
|
|
29
|
+
* `ios` and `android` are single-platform by definition. `other` means the app
|
|
30
|
+
* type was never classified, and an unclassified app has not declared anything
|
|
31
|
+
* — silence is not permission.
|
|
32
|
+
*/
|
|
33
|
+
const MULTIPLATFORM_APP_TYPES = new Set(['kmm']);
|
|
34
|
+
/**
|
|
35
|
+
* Whether the pipeline *declares* an app that spans both mobile platforms.
|
|
36
|
+
*
|
|
37
|
+
* Read only from the explicit declaration, never inferred from the steps.
|
|
38
|
+
* Inference would defeat the point: a workflow that mixes platforms by mistake
|
|
39
|
+
* is exactly what the gate exists to catch, and it looks identical to one that
|
|
40
|
+
* does so on purpose.
|
|
41
|
+
*/
|
|
42
|
+
export function declaresMultiplatformApp(pipeline) {
|
|
43
|
+
const platform = getInvarnMeta(pipeline)?.platform;
|
|
44
|
+
return platform !== undefined && MULTIPLATFORM_APP_TYPES.has(platform);
|
|
45
|
+
}
|
|
20
46
|
//# sourceMappingURL=meta-helpers.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for what happens when a pre-execution check cannot run.
|
|
3
|
+
*
|
|
4
|
+
* A check that *ran* and found nothing, and a check that *threw* before it
|
|
5
|
+
* tested anything, are different events. Until 2026-09-01 they were reported
|
|
6
|
+
* identically: the throw was wrapped in a `Check failed:` message and carried
|
|
7
|
+
* the requirement's own declared severity. For the Java JDK requirement that
|
|
8
|
+
* severity is `info` — chosen because the check looks in several places and a
|
|
9
|
+
* miss is not fatal — so a check that never looked anywhere at all was
|
|
10
|
+
* reported as information and read as noise.
|
|
11
|
+
*
|
|
12
|
+
* It was not noise. The released macOS binary is built with `pkg`, which
|
|
13
|
+
* cannot evaluate a dynamic `import()` of a module it did not bundle; the JDK
|
|
14
|
+
* check opened with two of them and threw `Invalid host defined options` on
|
|
15
|
+
* every macOS build. It went unseen for as long as the fleet has existed.
|
|
16
|
+
*
|
|
17
|
+
* Two rules are pinned here:
|
|
18
|
+
* 1. a throw is reported at `warning`, whatever the requirement declares;
|
|
19
|
+
* 2. no check may reach for a Node builtin through a dynamic import, because
|
|
20
|
+
* nothing else in the test suite can see that failure — under plain
|
|
21
|
+
* `node`, and in the npm bundle, the code works.
|
|
22
|
+
*/
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=step-validator-check-failure.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-validator-check-failure.test.d.ts","sourceRoot":"","sources":["../../../src/yaml/step-validator-check-failure.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for what happens when a pre-execution check cannot run.
|
|
3
|
+
*
|
|
4
|
+
* A check that *ran* and found nothing, and a check that *threw* before it
|
|
5
|
+
* tested anything, are different events. Until 2026-09-01 they were reported
|
|
6
|
+
* identically: the throw was wrapped in a `Check failed:` message and carried
|
|
7
|
+
* the requirement's own declared severity. For the Java JDK requirement that
|
|
8
|
+
* severity is `info` — chosen because the check looks in several places and a
|
|
9
|
+
* miss is not fatal — so a check that never looked anywhere at all was
|
|
10
|
+
* reported as information and read as noise.
|
|
11
|
+
*
|
|
12
|
+
* It was not noise. The released macOS binary is built with `pkg`, which
|
|
13
|
+
* cannot evaluate a dynamic `import()` of a module it did not bundle; the JDK
|
|
14
|
+
* check opened with two of them and threw `Invalid host defined options` on
|
|
15
|
+
* every macOS build. It went unseen for as long as the fleet has existed.
|
|
16
|
+
*
|
|
17
|
+
* Two rules are pinned here:
|
|
18
|
+
* 1. a throw is reported at `warning`, whatever the requirement declares;
|
|
19
|
+
* 2. no check may reach for a Node builtin through a dynamic import, because
|
|
20
|
+
* nothing else in the test suite can see that failure — under plain
|
|
21
|
+
* `node`, and in the npm bundle, the code works.
|
|
22
|
+
*/
|
|
23
|
+
import { describe, test, expect } from '@jest/globals';
|
|
24
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
import { registerStep } from './steps/registry.js';
|
|
27
|
+
import { BaseStepExecutor } from './steps/base.js';
|
|
28
|
+
import { StepValidator } from './step-validator.js';
|
|
29
|
+
import { loadConfig } from '../config.js';
|
|
30
|
+
const config = loadConfig();
|
|
31
|
+
/** A step whose single requirement behaves however the test needs it to. */
|
|
32
|
+
function stepWhoseCheck(severity, check) {
|
|
33
|
+
const name = `probe-${Math.random().toString(36).slice(2)}`;
|
|
34
|
+
class ProbeStep extends BaseStepExecutor {
|
|
35
|
+
async execute() {
|
|
36
|
+
return this.createScriptStep('echo probe', name);
|
|
37
|
+
}
|
|
38
|
+
getValidationRequirements() {
|
|
39
|
+
return [
|
|
40
|
+
{
|
|
41
|
+
category: 'command',
|
|
42
|
+
name: 'probe-tool',
|
|
43
|
+
description: 'A tool the probe step needs',
|
|
44
|
+
timing: 'pre-execution',
|
|
45
|
+
severity,
|
|
46
|
+
check,
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
registerStep(name, new ProbeStep());
|
|
52
|
+
return name;
|
|
53
|
+
}
|
|
54
|
+
async function validate(stepName) {
|
|
55
|
+
const validator = new StepValidator({
|
|
56
|
+
format_version: '1',
|
|
57
|
+
meta: { invarn: { stack: 'macos-xcode-26.4' } },
|
|
58
|
+
workflows: { primary: { steps: [{ [`${stepName}@1.0.0`]: { inputs: {} } }] } },
|
|
59
|
+
}, 'primary', config);
|
|
60
|
+
return validator.validateWorkflow();
|
|
61
|
+
}
|
|
62
|
+
describe('a check that could not run', () => {
|
|
63
|
+
test('is reported as a warning even though the requirement declares info', async () => {
|
|
64
|
+
const name = stepWhoseCheck('info', async () => {
|
|
65
|
+
throw new Error('Invalid host defined options');
|
|
66
|
+
});
|
|
67
|
+
const result = await validate(name);
|
|
68
|
+
expect(result.counts).toMatchObject({ errors: 0, warnings: 1, info: 0 });
|
|
69
|
+
expect(result.issues[0].requirement.severity).toBe('warning');
|
|
70
|
+
expect(result.issues[0].result.message).toBe('Check failed: Invalid host defined options');
|
|
71
|
+
});
|
|
72
|
+
test('does not turn a hard requirement into a soft one', async () => {
|
|
73
|
+
const name = stepWhoseCheck('error', async () => {
|
|
74
|
+
throw new Error('boom');
|
|
75
|
+
});
|
|
76
|
+
const result = await validate(name);
|
|
77
|
+
// An `error` requirement whose check throws must not be downgraded — that
|
|
78
|
+
// would let a pipeline proceed past a requirement nothing verified.
|
|
79
|
+
expect(result.counts).toMatchObject({ errors: 1, warnings: 0, info: 0 });
|
|
80
|
+
expect(result.valid).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
test('leaves a check that ran and failed at its declared severity', async () => {
|
|
83
|
+
const name = stepWhoseCheck('info', async () => ({
|
|
84
|
+
passed: false,
|
|
85
|
+
message: 'Java 17 not found',
|
|
86
|
+
}));
|
|
87
|
+
const result = await validate(name);
|
|
88
|
+
// The whole point of the distinction: this check *worked*. `info` is the
|
|
89
|
+
// right severity for it and must survive.
|
|
90
|
+
expect(result.counts).toMatchObject({ errors: 0, warnings: 0, info: 1 });
|
|
91
|
+
expect(result.issues[0].result.message).toBe('Java 17 not found');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* The structural half.
|
|
96
|
+
*
|
|
97
|
+
* esbuild leaves a dynamic `import()` of a Node builtin in the bundle — the
|
|
98
|
+
* builtins are external — and `pkg` then cannot evaluate it. Relative
|
|
99
|
+
* specifiers are inlined by the bundler and are unaffected, which is why
|
|
100
|
+
* `envman` and `fidelity-trim` work while the JDK check did not.
|
|
101
|
+
*
|
|
102
|
+
* There is no runtime test that can catch this: it reproduces only in a
|
|
103
|
+
* `pkg`-built binary, and `./test.sh` runs under plain `node`. So the rule is
|
|
104
|
+
* enforced on the source.
|
|
105
|
+
*/
|
|
106
|
+
describe('no source file reaches for a Node builtin through a dynamic import', () => {
|
|
107
|
+
const BUILTIN = /await import\(\s*['"](?:node:)?(fs|path|os|child_process|readline|crypto|util|url|http|https|net|stream|zlib|tty|events)(?:\/[\w-]+)?['"]\s*\)/;
|
|
108
|
+
function sourceFiles(dir) {
|
|
109
|
+
return readdirSync(dir).flatMap((entry) => {
|
|
110
|
+
const full = join(dir, entry);
|
|
111
|
+
if (statSync(full).isDirectory())
|
|
112
|
+
return sourceFiles(full);
|
|
113
|
+
if (!full.endsWith('.ts') || full.endsWith('.test.ts'))
|
|
114
|
+
return [];
|
|
115
|
+
return [full];
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
test('src is clean', () => {
|
|
119
|
+
// Jest runs from the package root; `src` is the whole surface that gets
|
|
120
|
+
// bundled into the released binary.
|
|
121
|
+
const offenders = sourceFiles(join(process.cwd(), 'src'))
|
|
122
|
+
.flatMap((file) => readFileSync(file, 'utf8')
|
|
123
|
+
.split('\n')
|
|
124
|
+
.map((line, i) => ({ file, line: line.trim(), n: i + 1 }))
|
|
125
|
+
.filter(({ line }) => BUILTIN.test(line)))
|
|
126
|
+
.map(({ file, n, line }) => `${file.replace(/.*\/src\//, 'src/')}:${n} ${line}`);
|
|
127
|
+
expect(offenders).toEqual([]);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
//# sourceMappingURL=step-validator-check-failure.test.js.map
|
|
@@ -22,6 +22,7 @@ export declare class StepValidator {
|
|
|
22
22
|
private yamlFilePath?;
|
|
23
23
|
private availableOutputs;
|
|
24
24
|
private establishedPlatform;
|
|
25
|
+
private readonly spansPlatforms;
|
|
25
26
|
constructor(pipeline: YAMLPipeline, workflowName: string, config: CIConfig, yamlFilePath?: string);
|
|
26
27
|
/**
|
|
27
28
|
* Validates all steps in the workflow.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"step-validator.d.ts","sourceRoot":"","sources":["../../../src/yaml/step-validator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"step-validator.d.ts","sourceRoot":"","sources":["../../../src/yaml/step-validator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAOH,OAAO,KAAK,EAAE,YAAY,EAA4B,MAAM,YAAY,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAGV,wBAAwB,EAGzB,MAAM,uBAAuB,CAAC;AAwB/B;;;;GAIG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,YAAY,CAAC,CAAS;IAG9B,OAAO,CAAC,gBAAgB,CAAoE;IAG5F,OAAO,CAAC,mBAAmB,CAA6B;IAKxD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;gBAGvC,QAAQ,EAAE,YAAY,EACtB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,QAAQ,EAChB,YAAY,CAAC,EAAE,MAAM;IA0BvB;;;OAGG;IACG,gBAAgB,IAAI,OAAO,CAAC,wBAAwB,CAAC;IAkN3D;;OAEG;YACW,mBAAmB;IAmEjC;;;;;;;;;OASG;IACH,OAAO,CAAC,yBAAyB;IAsBjC;;;;;;;;;OASG;IACH,OAAO,CAAC,mBAAmB;IAoB3B;;OAEG;IACH,OAAO,CAAC,SAAS;CA8BlB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,GAAG,MAAM,CA4D/E"}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { getStepExecutor, hasStep, getStepMetadata } from './steps/registry.js';
|
|
9
9
|
import { EnvResolver } from './env-resolver.js';
|
|
10
10
|
import { detectPlatformInfo } from './platform-detector.js';
|
|
11
|
+
import { declaresMultiplatformApp } from './meta-helpers.js';
|
|
11
12
|
// POSIX env vars the shell always provides — don't flag these as undeclared.
|
|
12
13
|
// The runner's guest shell seeds HOME, TMPDIR, PATH, etc.; requiring
|
|
13
14
|
// pipelines to redeclare them forces literal-path-only scripts.
|
|
@@ -45,6 +46,10 @@ export class StepValidator {
|
|
|
45
46
|
availableOutputs = new Map();
|
|
46
47
|
// Platform established by the first platform-specific step ('ios' or 'android')
|
|
47
48
|
establishedPlatform = null;
|
|
49
|
+
// True when the pipeline declares an app that is inherently both platforms
|
|
50
|
+
// (KMM today), so one workflow may legitimately hold android and ios steps.
|
|
51
|
+
// Read from the declaration only — see `declaresMultiplatformApp`.
|
|
52
|
+
spansPlatforms;
|
|
48
53
|
constructor(pipeline, workflowName, config, yamlFilePath) {
|
|
49
54
|
this.pipeline = pipeline;
|
|
50
55
|
this.workflowName = workflowName;
|
|
@@ -54,6 +59,7 @@ export class StepValidator {
|
|
|
54
59
|
if (!this.workflow) {
|
|
55
60
|
throw new Error(`Workflow '${workflowName}' not found in pipeline`);
|
|
56
61
|
}
|
|
62
|
+
this.spansPlatforms = declaresMultiplatformApp(pipeline);
|
|
57
63
|
// Create env resolver for variable interpolation
|
|
58
64
|
const platformInfo = detectPlatformInfo(pipeline, workflowName);
|
|
59
65
|
this.envResolver = new EnvResolver(pipeline, this.workflow, workflowName, platformInfo.platform, platformInfo.stack, yamlFilePath);
|
|
@@ -78,7 +84,11 @@ export class StepValidator {
|
|
|
78
84
|
// Platform compatibility check
|
|
79
85
|
const stepMetadata = getStepMetadata(parsedStep.name);
|
|
80
86
|
const stepPlatform = stepMetadata?.platform ?? 'all';
|
|
81
|
-
|
|
87
|
+
// A declared multiplatform app is exempt: its build spans both platforms
|
|
88
|
+
// by construction, so there is no single platform for a step to conflict
|
|
89
|
+
// with. Everything else — including an app that declares nothing, or
|
|
90
|
+
// declares one platform and then mixes — is still refused below.
|
|
91
|
+
if (stepPlatform !== 'all' && !this.spansPlatforms) {
|
|
82
92
|
if (this.establishedPlatform === null) {
|
|
83
93
|
// First platform-specific step establishes the workflow platform
|
|
84
94
|
this.establishedPlatform = stepPlatform;
|
|
@@ -270,10 +280,22 @@ export class StepValidator {
|
|
|
270
280
|
}
|
|
271
281
|
}
|
|
272
282
|
catch (error) {
|
|
283
|
+
// A check that threw did not run. That is a different event from a
|
|
284
|
+
// check that ran and found nothing, and it must not inherit the
|
|
285
|
+
// severity chosen for the latter: `info` is a reasonable verdict for
|
|
286
|
+
// "looked in several places, found no JDK" and a useless one for
|
|
287
|
+
// "never looked". Reported at `warning` so it is visible on the first
|
|
288
|
+
// build — a check whose own failure reads as information can stay
|
|
289
|
+
// broken for as long as nobody reads carefully.
|
|
290
|
+
//
|
|
291
|
+
// `error` is never downgraded. A hard requirement nothing could verify
|
|
292
|
+
// is not softer than one that was verified and failed.
|
|
273
293
|
return {
|
|
274
294
|
stepName,
|
|
275
295
|
stepIndex,
|
|
276
|
-
requirement
|
|
296
|
+
requirement: requirement.severity === 'error'
|
|
297
|
+
? requirement
|
|
298
|
+
: { ...requirement, severity: 'warning' },
|
|
277
299
|
result: {
|
|
278
300
|
passed: false,
|
|
279
301
|
message: `Check failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the pre-execution platform gate.
|
|
3
|
+
*
|
|
4
|
+
* A workflow's platform is established by its first platform-specific step,
|
|
5
|
+
* and any later step of a different platform is refused. That rule is right
|
|
6
|
+
* for an app that targets one platform and wrong for one that targets both:
|
|
7
|
+
* a Kotlin Multiplatform build is *inherently* android-then-ios — gradle
|
|
8
|
+
* assembles the XCFramework that `xcodebuild` then consumes — and there is no
|
|
9
|
+
* way to split it, because a workflow is one dispatch on one machine and two
|
|
10
|
+
* workflows share no filesystem.
|
|
11
|
+
*
|
|
12
|
+
* On 2026-09-01 that cost a real build: a generated KMM pipeline was refused
|
|
13
|
+
* before a single step ran. These tests pin the narrow relaxation that fixes
|
|
14
|
+
* it — permitted only where the pipeline *declares* a multiplatform app,
|
|
15
|
+
* never inferred from the steps themselves.
|
|
16
|
+
*/
|
|
17
|
+
import '../yaml/steps/index.js';
|
|
18
|
+
//# sourceMappingURL=step-validator.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"step-validator.test.d.ts","sourceRoot":"","sources":["../../../src/yaml/step-validator.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the pre-execution platform gate.
|
|
3
|
+
*
|
|
4
|
+
* A workflow's platform is established by its first platform-specific step,
|
|
5
|
+
* and any later step of a different platform is refused. That rule is right
|
|
6
|
+
* for an app that targets one platform and wrong for one that targets both:
|
|
7
|
+
* a Kotlin Multiplatform build is *inherently* android-then-ios — gradle
|
|
8
|
+
* assembles the XCFramework that `xcodebuild` then consumes — and there is no
|
|
9
|
+
* way to split it, because a workflow is one dispatch on one machine and two
|
|
10
|
+
* workflows share no filesystem.
|
|
11
|
+
*
|
|
12
|
+
* On 2026-09-01 that cost a real build: a generated KMM pipeline was refused
|
|
13
|
+
* before a single step ran. These tests pin the narrow relaxation that fixes
|
|
14
|
+
* it — permitted only where the pipeline *declares* a multiplatform app,
|
|
15
|
+
* never inferred from the steps themselves.
|
|
16
|
+
*/
|
|
17
|
+
import { describe, test, expect } from '@jest/globals';
|
|
18
|
+
import '../yaml/steps/index.js';
|
|
19
|
+
import { StepValidator } from './step-validator.js';
|
|
20
|
+
import { PlatformDetectionError } from './platform-detector.js';
|
|
21
|
+
import { loadConfig } from '../config.js';
|
|
22
|
+
const config = loadConfig();
|
|
23
|
+
/** What every curated template declares, and what the detector reads first. */
|
|
24
|
+
const STACK = 'macos-xcode-26.4';
|
|
25
|
+
/**
|
|
26
|
+
* A KMM build in one workflow: gradle establishes android, xcodebuild follows.
|
|
27
|
+
*
|
|
28
|
+
* The stack is not decoration. `PlatformDetector` resolves the host OS from it
|
|
29
|
+
* first and only falls back to reading the steps — where a mixed workflow
|
|
30
|
+
* throws before this validator ever runs. Every curated template declares a
|
|
31
|
+
* stack, so this is the gate a real pipeline actually meets.
|
|
32
|
+
*
|
|
33
|
+
* `getInvarnMeta` takes `meta.invarn` as a whole when it is present, so a test
|
|
34
|
+
* about the legacy block has to omit `meta.invarn` entirely.
|
|
35
|
+
*/
|
|
36
|
+
function kmmShapedPipeline({ invarn, legacy } = {}, { stack = true } = {}) {
|
|
37
|
+
const base = stack ? { stack: STACK } : {};
|
|
38
|
+
const meta = legacy && !invarn
|
|
39
|
+
? { 'cibuild.io': { ...base, ...legacy } }
|
|
40
|
+
: {
|
|
41
|
+
invarn: { ...base, ...invarn },
|
|
42
|
+
...(legacy ? { 'cibuild.io': { ...base, ...legacy } } : {}),
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
format_version: '1',
|
|
46
|
+
meta,
|
|
47
|
+
workflows: {
|
|
48
|
+
primary: {
|
|
49
|
+
steps: [
|
|
50
|
+
{ 'gradle-build@1.0.0': { inputs: { project_location: '.', gradle_task: 'assembleDebug' } } },
|
|
51
|
+
{
|
|
52
|
+
'xcodebuild@1.0.0': {
|
|
53
|
+
inputs: {
|
|
54
|
+
project_path: 'iosApp/iosApp.xcodeproj',
|
|
55
|
+
scheme: 'iosApp',
|
|
56
|
+
destination: 'generic/platform=iOS Simulator',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async function platformIssues(pipeline) {
|
|
66
|
+
const validator = new StepValidator(pipeline, 'primary', config);
|
|
67
|
+
const result = await validator.validateWorkflow();
|
|
68
|
+
return (result.issuesByCategory.get('platform') ?? []).map((i) => i.requirement.name);
|
|
69
|
+
}
|
|
70
|
+
describe('StepValidator platform gate', () => {
|
|
71
|
+
test('refuses a mixed workflow that declares no app platform', async () => {
|
|
72
|
+
expect(await platformIssues(kmmShapedPipeline())).toEqual(['xcodebuild']);
|
|
73
|
+
});
|
|
74
|
+
test('permits a mixed workflow that declares a KMM app', async () => {
|
|
75
|
+
expect(await platformIssues(kmmShapedPipeline({ invarn: { platform: 'kmm' } }))).toEqual([]);
|
|
76
|
+
});
|
|
77
|
+
test('permits it through the legacy meta.cibuild.io block too', async () => {
|
|
78
|
+
expect(await platformIssues(kmmShapedPipeline({ legacy: { platform: 'kmm' } }))).toEqual([]);
|
|
79
|
+
});
|
|
80
|
+
// meta.invarn wins as a whole when both blocks are present, so a stale
|
|
81
|
+
// `kmm` under the legacy key must not grant permission the current block
|
|
82
|
+
// withholds.
|
|
83
|
+
test('lets meta.invarn withhold what a stale meta.cibuild.io granted', async () => {
|
|
84
|
+
expect(await platformIssues(kmmShapedPipeline({ invarn: { platform: 'android' }, legacy: { platform: 'kmm' } }))).toEqual(['xcodebuild']);
|
|
85
|
+
});
|
|
86
|
+
// The regression this relaxation could introduce. A declared platform, never
|
|
87
|
+
// inference: an ios step in a workflow that says it is android stays refused.
|
|
88
|
+
test('still refuses an ios step in a declared-android workflow', async () => {
|
|
89
|
+
expect(await platformIssues(kmmShapedPipeline({ invarn: { platform: 'android' } }))).toEqual([
|
|
90
|
+
'xcodebuild',
|
|
91
|
+
]);
|
|
92
|
+
});
|
|
93
|
+
test('still refuses an android step in a declared-ios workflow', async () => {
|
|
94
|
+
const iosFirst = {
|
|
95
|
+
format_version: '1',
|
|
96
|
+
meta: { invarn: { stack: STACK, platform: 'ios' } },
|
|
97
|
+
workflows: {
|
|
98
|
+
primary: {
|
|
99
|
+
steps: [
|
|
100
|
+
{ 'xcodebuild@1.0.0': { inputs: { project_path: 'App.xcodeproj', scheme: 'App' } } },
|
|
101
|
+
{ 'gradle-build@1.0.0': { inputs: { project_location: '.', gradle_task: 'assembleDebug' } } },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
expect(await platformIssues(iosFirst)).toEqual(['gradle-build']);
|
|
107
|
+
});
|
|
108
|
+
// 'other' means the app type was not classified. An unclassified app has not
|
|
109
|
+
// declared that it spans platforms; silence is not permission.
|
|
110
|
+
test('still refuses when the declared platform is other', async () => {
|
|
111
|
+
expect(await platformIssues(kmmShapedPipeline({ invarn: { platform: 'other' } }))).toEqual([
|
|
112
|
+
'xcodebuild',
|
|
113
|
+
]);
|
|
114
|
+
});
|
|
115
|
+
// The declaration is about mixing, not about skipping the gate: a
|
|
116
|
+
// single-platform workflow was never at risk either way.
|
|
117
|
+
test('leaves a single-platform workflow alone whatever it declares', async () => {
|
|
118
|
+
const androidOnly = {
|
|
119
|
+
format_version: '1',
|
|
120
|
+
meta: { invarn: { stack: STACK, platform: 'kmm' } },
|
|
121
|
+
workflows: {
|
|
122
|
+
primary: {
|
|
123
|
+
steps: [
|
|
124
|
+
{ 'gradle-build@1.0.0': { inputs: { project_location: '.', gradle_task: 'assembleDebug' } } },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
expect(await platformIssues(androidOnly)).toEqual([]);
|
|
130
|
+
});
|
|
131
|
+
// Declaring the app's platform does not tell the detector which host OS to
|
|
132
|
+
// ask for, and a mixed workflow cannot be read off its steps. That refusal
|
|
133
|
+
// is `PlatformDetector`'s and comes first — it is not relaxed here, and its
|
|
134
|
+
// own message already says what to add.
|
|
135
|
+
test('does not substitute for the stack a mixed workflow still needs', async () => {
|
|
136
|
+
const noStack = kmmShapedPipeline({ invarn: { platform: 'kmm' } }, { stack: false });
|
|
137
|
+
expect(() => new StepValidator(noStack, 'primary', config)).toThrow(PlatformDetectionError);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
//# sourceMappingURL=step-validator.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/android.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"android.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/android.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Android step implementations (set-java-version, gradle-build, android-lint, android-unit-test)
|
|
3
3
|
*/
|
|
4
|
+
// Static, deliberately. The released macOS binary is built with `pkg`, which
|
|
5
|
+
// cannot evaluate a dynamic `import()` of a builtin — esbuild leaves those
|
|
6
|
+
// external, so they survive into the bundle as real imports and throw
|
|
7
|
+
// `Invalid host defined options` at runtime. A validation check that reaches
|
|
8
|
+
// for one never runs on a runner.
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { execSync } from 'node:child_process';
|
|
4
11
|
import { BaseStepExecutor } from './base.js';
|
|
5
12
|
/**
|
|
6
13
|
* Set Java version step executor
|
|
@@ -28,8 +35,6 @@ export class SetJavaVersionStepExecutor extends BaseStepExecutor {
|
|
|
28
35
|
* Matches the logic in execute() method
|
|
29
36
|
*/
|
|
30
37
|
async checkJavaVersionWithHomebrew(version) {
|
|
31
|
-
const { existsSync } = await import('node:fs');
|
|
32
|
-
const { execSync } = await import('node:child_process');
|
|
33
38
|
// Check Homebrew paths first (same order as execute())
|
|
34
39
|
const homebrewPaths = [
|
|
35
40
|
`/opt/homebrew/opt/openjdk@${version}`, // Apple Silicon
|
|
@@ -48,7 +53,6 @@ export class SetJavaVersionStepExecutor extends BaseStepExecutor {
|
|
|
48
53
|
const jbrPath = '/Applications/Android Studio.app/Contents/jbr/Contents/Home';
|
|
49
54
|
if (existsSync(jbrPath)) {
|
|
50
55
|
try {
|
|
51
|
-
const { readFileSync } = await import('node:fs');
|
|
52
56
|
const releaseContent = readFileSync(`${jbrPath}/release`, 'utf-8');
|
|
53
57
|
const jbrVersionMatch = /JAVA_VERSION="(\d+)/.exec(releaseContent);
|
|
54
58
|
if (jbrVersionMatch && jbrVersionMatch[1] === String(version)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bitrise-android-tools.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/bitrise-android-tools.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"bitrise-android-tools.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/bitrise-android-tools.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEhF;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,qBAAa,+BAAgC,SAAQ,gBAAgB;IACnE,yBAAyB,CACvB,OAAO,EAAE,yBAAyB,EAClC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IA+C1B,UAAU,IAAI,UAAU,EAAE;IAMpB,OAAO,CAAC,MAAM,EAAE,yBAAyB,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;CAyJlH"}
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* Bitrise install-missing-android-tools step implementation
|
|
3
3
|
* Maps Bitrise install-missing-android-tools@* to CI Build Android environment setup
|
|
4
4
|
*/
|
|
5
|
+
// Static — see the note in `android.ts`. A dynamic `import()` of a builtin
|
|
6
|
+
// throws under the `pkg`-built binary the macOS runners use.
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
5
9
|
import { BaseStepExecutor } from './base.js';
|
|
6
10
|
/**
|
|
7
11
|
* Bitrise install-missing-android-tools step executor (FR-3)
|
|
@@ -28,8 +32,6 @@ export class BitriseAndroidToolsStepExecutor extends BaseStepExecutor {
|
|
|
28
32
|
};
|
|
29
33
|
}
|
|
30
34
|
// Check if we can auto-detect
|
|
31
|
-
const { existsSync } = await import('node:fs');
|
|
32
|
-
const { homedir } = await import('node:os');
|
|
33
35
|
const homeDir = homedir();
|
|
34
36
|
const commonPaths = [
|
|
35
37
|
`${homeDir}/Library/Android/sdk`, // macOS
|