@invarn/cibuild 2.7.2 → 2.7.4
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/commands/build.d.ts +21 -0
- package/dist/src/commands/build.d.ts.map +1 -1
- package/dist/src/commands/build.js +84 -7
- package/dist/src/commands/local-properties-reaches-every-workflow.test.d.ts +30 -0
- package/dist/src/commands/local-properties-reaches-every-workflow.test.d.ts.map +1 -0
- package/dist/src/commands/local-properties-reaches-every-workflow.test.js +144 -0
- package/dist/src/yaml/a-missing-credential-is-loud.test.d.ts +32 -0
- package/dist/src/yaml/a-missing-credential-is-loud.test.d.ts.map +1 -0
- package/dist/src/yaml/a-missing-credential-is-loud.test.js +301 -0
- package/dist/src/yaml/steps/file.d.ts +25 -0
- package/dist/src/yaml/steps/file.d.ts.map +1 -1
- package/dist/src/yaml/steps/file.js +75 -2
- package/dist/src/yaml/steps/git-fetch-retry-exec.test.js +6 -0
- package/dist/src/yaml/steps/xcode-resolve-retry.test.js +36 -1
- package/dist/src/yaml/steps/xcode.d.ts.map +1 -1
- package/dist/src/yaml/steps/xcode.js +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A credential the pipeline declares and nobody supplied fails the build,
|
|
3
|
+
* naming itself, instead of being skipped with a warning.
|
|
4
|
+
*
|
|
5
|
+
* `file@1.0.0` carries `is_skippable: true` in every pipeline the Android
|
|
6
|
+
* generator emits, so an absent value printed `⚠️ X not set — skipping <file>`
|
|
7
|
+
* and returned success. The build then failed several steps later inside the
|
|
8
|
+
* project's own build tool, with an error naming the project rather than the
|
|
9
|
+
* value we dropped. Both failure modes in this defect were green, which is why
|
|
10
|
+
* it survived.
|
|
11
|
+
*
|
|
12
|
+
* The two absences are not the same absence, and the map the step already
|
|
13
|
+
* receives tells them apart by presence:
|
|
14
|
+
*
|
|
15
|
+
* - a `var_name` the pipeline **declares** and leaves empty is a key of the
|
|
16
|
+
* map with an empty value — the value was supposed to arrive as a secret
|
|
17
|
+
* and did not. After issue 01 that means it is in none of the four places
|
|
18
|
+
* a value can come from, so the diagnosis is not a guess.
|
|
19
|
+
* - a `var_name` **nothing declares** is not a key of the map at all — a
|
|
20
|
+
* release keystore on a debug workflow. Skipping is correct there.
|
|
21
|
+
*
|
|
22
|
+
* One case sits between them, and it decides the rule (issue 03 §2.3, Alex,
|
|
23
|
+
* 2026-09-08): the generator declares `KEYSTORE_BASE64` globally but emits its
|
|
24
|
+
* `file@` step in all three workflows, so a project that ran `ci init` with a
|
|
25
|
+
* keystore and never uploaded one would go from three green skips to three
|
|
26
|
+
* hard failures — including on `pull-request`, where nothing signs anything.
|
|
27
|
+
* So the question the step asks is the one that actually matters: **will the
|
|
28
|
+
* file be there when the build reads it?** A repository that carries the file
|
|
29
|
+
* keeps warning and building; only an absence with nothing behind it fails.
|
|
30
|
+
*/
|
|
31
|
+
import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
|
|
32
|
+
import { spawnSync } from 'node:child_process';
|
|
33
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync, existsSync, } from 'node:fs';
|
|
34
|
+
import { tmpdir } from 'node:os';
|
|
35
|
+
import { dirname, join } from 'node:path';
|
|
36
|
+
import { convertYAMLToPipelineDef } from './converter.js';
|
|
37
|
+
import { StepValidator } from './step-validator.js';
|
|
38
|
+
import { clearRegistry, registerStep } from './steps/registry.js';
|
|
39
|
+
import { FileStepExecutor } from './steps/file.js';
|
|
40
|
+
const mockConfig = {
|
|
41
|
+
artifactsDir: '/test/artifacts',
|
|
42
|
+
maxConcurrentJobs: 1,
|
|
43
|
+
paths: {
|
|
44
|
+
buildsDir: '/test/builds',
|
|
45
|
+
cacheDir: '/test/cache',
|
|
46
|
+
derivedDataDir: '/test/derived-data',
|
|
47
|
+
},
|
|
48
|
+
interpreters: {
|
|
49
|
+
bash: '/bin/bash',
|
|
50
|
+
python: 'python3',
|
|
51
|
+
ruby: 'ruby',
|
|
52
|
+
node: 'node',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
/** The 61-byte document from the PRD's reproduction. Synthetic. */
|
|
56
|
+
const JSON_CREDENTIAL = '{"project_info":{"project_id":"synthetic-probe"},"client":[]}';
|
|
57
|
+
const TARGET = 'app/google-services.json';
|
|
58
|
+
let workdir;
|
|
59
|
+
let previousCwd;
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
clearRegistry();
|
|
62
|
+
registerStep('file', new FileStepExecutor());
|
|
63
|
+
previousCwd = process.cwd();
|
|
64
|
+
workdir = mkdtempSync(join(tmpdir(), 'cibuild-missing-credential-'));
|
|
65
|
+
process.chdir(workdir);
|
|
66
|
+
// `workdir` is the pre-symlink path on macOS; the step resolves paths from
|
|
67
|
+
// CIBUILD_SOURCE_DIR, which is cwd, so read and write files the same way.
|
|
68
|
+
workdir = process.cwd();
|
|
69
|
+
delete process.env.GOOGLE_SERVICES_JSON;
|
|
70
|
+
delete process.env.KEYSTORE_BASE64;
|
|
71
|
+
delete process.env.OTHER_SECRET;
|
|
72
|
+
});
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
process.chdir(previousCwd);
|
|
75
|
+
rmSync(workdir, { recursive: true, force: true });
|
|
76
|
+
clearRegistry();
|
|
77
|
+
delete process.env.GOOGLE_SERVICES_JSON;
|
|
78
|
+
delete process.env.KEYSTORE_BASE64;
|
|
79
|
+
delete process.env.OTHER_SECRET;
|
|
80
|
+
});
|
|
81
|
+
/**
|
|
82
|
+
* The shape the Android generator emits: empty slots under `app.envs` and one
|
|
83
|
+
* skippable `file@1.0.0` step per credential, in declaration order.
|
|
84
|
+
*/
|
|
85
|
+
function probePipeline(declared, fileSteps, options = {}) {
|
|
86
|
+
const skippable = options.skippable ?? true;
|
|
87
|
+
const app = declared
|
|
88
|
+
? { app: { envs: Object.entries(declared).map(([k, v]) => ({ [k]: v })) } }
|
|
89
|
+
: {};
|
|
90
|
+
return {
|
|
91
|
+
format_version: '1',
|
|
92
|
+
...app,
|
|
93
|
+
workflows: {
|
|
94
|
+
probe: {
|
|
95
|
+
steps: fileSteps.map((inputs) => ({
|
|
96
|
+
'file@1.0.0': {
|
|
97
|
+
title: `Setup ${String(inputs.var_name)}`,
|
|
98
|
+
is_skippable: skippable,
|
|
99
|
+
inputs,
|
|
100
|
+
},
|
|
101
|
+
})),
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function writeSecretStore(global) {
|
|
107
|
+
writeFileSync(join(workdir, '.cibuild-secrets.json'), JSON.stringify({ global, workflows: {} }), { mode: 0o600 });
|
|
108
|
+
}
|
|
109
|
+
/** Puts a file in the checkout, the way a repository that carries one does. */
|
|
110
|
+
function commitToCheckout(relPath, contents) {
|
|
111
|
+
const target = join(workdir, relPath);
|
|
112
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
113
|
+
writeFileSync(target, contents, 'utf-8');
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Converts the pipeline and runs the generated steps the way the runner does,
|
|
117
|
+
* then reads back whatever landed at `target`.
|
|
118
|
+
*/
|
|
119
|
+
async function land(pipeline, target = TARGET) {
|
|
120
|
+
const result = await convertYAMLToPipelineDef(pipeline, mockConfig, 'probe');
|
|
121
|
+
const chunks = [...result.warnings];
|
|
122
|
+
for (const step of result.pipeline.steps) {
|
|
123
|
+
const scriptPath = join(workdir, `__step_${step.id}.sh`);
|
|
124
|
+
writeFileSync(scriptPath, step.script, 'utf-8');
|
|
125
|
+
const proc = spawnSync('bash', [scriptPath], {
|
|
126
|
+
cwd: workdir,
|
|
127
|
+
encoding: 'utf-8',
|
|
128
|
+
env: { ...process.env },
|
|
129
|
+
});
|
|
130
|
+
expect(proc.status).toBe(0);
|
|
131
|
+
chunks.push(proc.stdout ?? '', proc.stderr ?? '');
|
|
132
|
+
}
|
|
133
|
+
const landedAt = join(workdir, target);
|
|
134
|
+
return {
|
|
135
|
+
output: chunks.join('\n'),
|
|
136
|
+
bytes: existsSync(landedAt) ? readFileSync(landedAt) : undefined,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** The conversion's outcome, without letting a throw escape the assertion. */
|
|
140
|
+
async function convert(pipeline) {
|
|
141
|
+
try {
|
|
142
|
+
const result = await convertYAMLToPipelineDef(pipeline, mockConfig, 'probe');
|
|
143
|
+
return { steps: result.pipeline.steps };
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
return { error: error };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
describe('a declared credential nothing supplied', () => {
|
|
150
|
+
test('fails, naming the key and the target path', async () => {
|
|
151
|
+
const { error, steps } = await convert(probePipeline({ GOOGLE_SERVICES_JSON: '' }, [
|
|
152
|
+
{ target_path: TARGET, var_name: 'GOOGLE_SERVICES_JSON' },
|
|
153
|
+
]));
|
|
154
|
+
expect(error).toBeDefined();
|
|
155
|
+
expect(error.message).toContain('GOOGLE_SERVICES_JSON');
|
|
156
|
+
expect(error.message).toContain(TARGET);
|
|
157
|
+
// Not a stack trace and not a step that runs: conversion is what builds
|
|
158
|
+
// the step list, so nothing at all is handed to the runner.
|
|
159
|
+
expect(steps).toBeUndefined();
|
|
160
|
+
});
|
|
161
|
+
test('says both places the value can come from', async () => {
|
|
162
|
+
const { error } = await convert(probePipeline({ GOOGLE_SERVICES_JSON: '' }, [
|
|
163
|
+
{ target_path: TARGET, var_name: 'GOOGLE_SERVICES_JSON' },
|
|
164
|
+
]));
|
|
165
|
+
// A user reading this should not have to know that a slot, an upload and
|
|
166
|
+
// a `file@` step are three separate things.
|
|
167
|
+
expect(error.message).toContain('ci secrets add GOOGLE_SERVICES_JSON');
|
|
168
|
+
expect(error.message).toMatch(/organization or pipeline secret/);
|
|
169
|
+
});
|
|
170
|
+
test('is_skippable does not turn it into a pass', async () => {
|
|
171
|
+
// Every `file@` step the Android generator emits carries this, which is
|
|
172
|
+
// the whole reason the defect was invisible.
|
|
173
|
+
const skippable = await convert(probePipeline({ GOOGLE_SERVICES_JSON: '' }, [{ target_path: TARGET, var_name: 'GOOGLE_SERVICES_JSON' }], { skippable: true }));
|
|
174
|
+
const notSkippable = await convert(probePipeline({ GOOGLE_SERVICES_JSON: '' }, [{ target_path: TARGET, var_name: 'GOOGLE_SERVICES_JSON' }], { skippable: false }));
|
|
175
|
+
expect(skippable.error).toBeDefined();
|
|
176
|
+
expect(notSkippable.error).toBeDefined();
|
|
177
|
+
});
|
|
178
|
+
test('fails on the first unfilled slot, not the last and not all three', async () => {
|
|
179
|
+
// What a real Android pipeline carries: three slots, none filled.
|
|
180
|
+
const { error } = await convert(probePipeline({
|
|
181
|
+
KEYSTORE_BASE64: '',
|
|
182
|
+
KEYSTORE_PROPERTIES: '',
|
|
183
|
+
GOOGLE_SERVICES_JSON: '',
|
|
184
|
+
}, [
|
|
185
|
+
{
|
|
186
|
+
target_path: 'release.keystore',
|
|
187
|
+
var_name: 'KEYSTORE_BASE64',
|
|
188
|
+
base64_encoded: true,
|
|
189
|
+
},
|
|
190
|
+
{ target_path: 'keystore.properties', var_name: 'KEYSTORE_PROPERTIES' },
|
|
191
|
+
{ target_path: TARGET, var_name: 'GOOGLE_SERVICES_JSON' },
|
|
192
|
+
]));
|
|
193
|
+
expect(error).toBeDefined();
|
|
194
|
+
expect(error.message).toContain('KEYSTORE_BASE64');
|
|
195
|
+
expect(error.message).not.toContain('KEYSTORE_PROPERTIES');
|
|
196
|
+
expect(error.message).not.toContain('GOOGLE_SERVICES_JSON');
|
|
197
|
+
});
|
|
198
|
+
test('no secret value appears in the message', async () => {
|
|
199
|
+
// The failing slot has no value by definition, so the assertion that
|
|
200
|
+
// means anything is about the values the build *does* hold.
|
|
201
|
+
process.env.OTHER_SECRET = JSON_CREDENTIAL;
|
|
202
|
+
const { error } = await convert(probePipeline({ OTHER_SECRET: '', GOOGLE_SERVICES_JSON: '' }, [
|
|
203
|
+
{ target_path: TARGET, var_name: 'GOOGLE_SERVICES_JSON' },
|
|
204
|
+
]));
|
|
205
|
+
expect(error).toBeDefined();
|
|
206
|
+
expect(error.message).not.toContain(JSON_CREDENTIAL);
|
|
207
|
+
expect(error.message).not.toContain('synthetic-probe');
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
describe('a credential the repository already carries', () => {
|
|
211
|
+
test('warns and proceeds — the file will be there when the build reads it', async () => {
|
|
212
|
+
// The `pull-request` collision: the generator declares KEYSTORE_BASE64
|
|
213
|
+
// globally and emits its step on every workflow, so a project whose
|
|
214
|
+
// keystore is in the repository must not be refused a build over a value
|
|
215
|
+
// it never needed.
|
|
216
|
+
commitToCheckout('release.keystore', 'a keystore the repository carries');
|
|
217
|
+
const { output } = await land(probePipeline({ KEYSTORE_BASE64: '' }, [
|
|
218
|
+
{
|
|
219
|
+
target_path: 'release.keystore',
|
|
220
|
+
var_name: 'KEYSTORE_BASE64',
|
|
221
|
+
base64_encoded: true,
|
|
222
|
+
},
|
|
223
|
+
]), 'release.keystore');
|
|
224
|
+
expect(output).toContain('KEYSTORE_BASE64');
|
|
225
|
+
expect(output).toContain('release.keystore');
|
|
226
|
+
// The escape hatch has to be visible in the log, or a build that passes
|
|
227
|
+
// this way is unexplainable to whoever reads it later.
|
|
228
|
+
expect(output).toMatch(/already in the checkout/);
|
|
229
|
+
});
|
|
230
|
+
test('leaves the file it found exactly as it was', async () => {
|
|
231
|
+
const carried = 'the repository\'s own google-services.json';
|
|
232
|
+
commitToCheckout(TARGET, carried);
|
|
233
|
+
const { bytes } = await land(probePipeline({ GOOGLE_SERVICES_JSON: '' }, [
|
|
234
|
+
{ target_path: TARGET, var_name: 'GOOGLE_SERVICES_JSON' },
|
|
235
|
+
]));
|
|
236
|
+
expect(bytes?.toString('utf-8')).toBe(carried);
|
|
237
|
+
});
|
|
238
|
+
test('an uploaded value still wins over the one in the checkout', async () => {
|
|
239
|
+
// A repository carrying a placeholder is the ordinary case for
|
|
240
|
+
// google-services.json, and the point of the upload is to replace it.
|
|
241
|
+
commitToCheckout(TARGET, 'placeholder committed for local development');
|
|
242
|
+
process.env.GOOGLE_SERVICES_JSON = JSON_CREDENTIAL;
|
|
243
|
+
const { output, bytes } = await land(probePipeline({ GOOGLE_SERVICES_JSON: '' }, [
|
|
244
|
+
{ target_path: TARGET, var_name: 'GOOGLE_SERVICES_JSON' },
|
|
245
|
+
]));
|
|
246
|
+
expect(output).toContain('✓ Written google-services.json');
|
|
247
|
+
expect(bytes?.toString('utf-8')).toBe(JSON_CREDENTIAL);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
describe('what issue 03 must not change', () => {
|
|
251
|
+
test('an undeclared key still warns and passes, is_skippable and all', async () => {
|
|
252
|
+
// Issue 01's class-A case, and the one this change is most likely to
|
|
253
|
+
// break: HOME exists in every environment this runs in, and nothing
|
|
254
|
+
// declares it.
|
|
255
|
+
expect(process.env.HOME).toBeTruthy();
|
|
256
|
+
const { output, bytes } = await land(probePipeline({ GOOGLE_SERVICES_JSON: '' }, [
|
|
257
|
+
{ target_path: 'home.txt', var_name: 'HOME' },
|
|
258
|
+
]), 'home.txt');
|
|
259
|
+
expect(output).toContain('HOME not set — skipping home.txt');
|
|
260
|
+
expect(bytes).toBeUndefined();
|
|
261
|
+
});
|
|
262
|
+
test('a declared value from the local store still writes', async () => {
|
|
263
|
+
writeSecretStore({ GOOGLE_SERVICES_JSON: JSON_CREDENTIAL });
|
|
264
|
+
const { output, bytes } = await land(probePipeline({ GOOGLE_SERVICES_JSON: '' }, [
|
|
265
|
+
{ target_path: TARGET, var_name: 'GOOGLE_SERVICES_JSON' },
|
|
266
|
+
]));
|
|
267
|
+
expect(output).toContain('✓ Written google-services.json');
|
|
268
|
+
expect(bytes?.toString('utf-8')).toBe(JSON_CREDENTIAL);
|
|
269
|
+
});
|
|
270
|
+
test('a declared value from the dispatched environment still writes', async () => {
|
|
271
|
+
process.env.GOOGLE_SERVICES_JSON = JSON_CREDENTIAL;
|
|
272
|
+
const { output, bytes } = await land(probePipeline({ GOOGLE_SERVICES_JSON: '' }, [
|
|
273
|
+
{ target_path: TARGET, var_name: 'GOOGLE_SERVICES_JSON' },
|
|
274
|
+
]));
|
|
275
|
+
expect(output).toContain('✓ Written google-services.json');
|
|
276
|
+
expect(bytes?.toString('utf-8')).toBe(JSON_CREDENTIAL);
|
|
277
|
+
});
|
|
278
|
+
test('a value the YAML itself supplies still writes', async () => {
|
|
279
|
+
const { output, bytes } = await land(probePipeline({ GOOGLE_SERVICES_JSON: JSON_CREDENTIAL }, [
|
|
280
|
+
{ target_path: TARGET, var_name: 'GOOGLE_SERVICES_JSON' },
|
|
281
|
+
]));
|
|
282
|
+
expect(output).toContain('✓ Written google-services.json');
|
|
283
|
+
expect(bytes?.toString('utf-8')).toBe(JSON_CREDENTIAL);
|
|
284
|
+
});
|
|
285
|
+
test('ci validate still passes on a pipeline with empty slots', async () => {
|
|
286
|
+
// Option 3 was rejected so validation does not start refusing builds
|
|
287
|
+
// that are green today. This is the line that holds it.
|
|
288
|
+
const pipeline = probePipeline({ KEYSTORE_BASE64: '', GOOGLE_SERVICES_JSON: '' }, [
|
|
289
|
+
{
|
|
290
|
+
target_path: 'release.keystore',
|
|
291
|
+
var_name: 'KEYSTORE_BASE64',
|
|
292
|
+
base64_encoded: true,
|
|
293
|
+
},
|
|
294
|
+
{ target_path: TARGET, var_name: 'GOOGLE_SERVICES_JSON' },
|
|
295
|
+
]);
|
|
296
|
+
const validator = new StepValidator(pipeline, 'probe', mockConfig);
|
|
297
|
+
const result = await validator.validateWorkflow();
|
|
298
|
+
expect(result.valid).toBe(true);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
//# sourceMappingURL=a-missing-credential-is-loud.test.js.map
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { BaseStepExecutor } from './base.js';
|
|
2
2
|
import type { StepDef, CIConfig } from '../../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* A credential the pipeline declares, the build reads, and nobody supplied.
|
|
5
|
+
*
|
|
6
|
+
* Thrown at generation time, from the step that would have written the file,
|
|
7
|
+
* so the build stops before its own build tool fails for a reason that names
|
|
8
|
+
* the project instead of the value we dropped. Generation time is also the
|
|
9
|
+
* only place `is_skippable` cannot swallow it: the converter sets that flag on
|
|
10
|
+
* a StepDef *after* `execute()` returns, so a step which merely exited
|
|
11
|
+
* non-zero would be reported as "failed but marked skippable — continuing".
|
|
12
|
+
*/
|
|
13
|
+
export declare class MissingFileSecretError extends Error {
|
|
14
|
+
readonly varName: string;
|
|
15
|
+
readonly targetPath: string;
|
|
16
|
+
constructor(varName: string, targetPath: string);
|
|
17
|
+
}
|
|
3
18
|
export interface FileInputs {
|
|
4
19
|
/** Path where the file should be written, relative to the project root. */
|
|
5
20
|
target_path: string;
|
|
@@ -23,5 +38,15 @@ export interface FileInputs {
|
|
|
23
38
|
*/
|
|
24
39
|
export declare class FileStepExecutor extends BaseStepExecutor {
|
|
25
40
|
execute(inputs: FileInputs, env: Record<string, string>, _config: CIConfig): Promise<StepDef>;
|
|
41
|
+
/**
|
|
42
|
+
* Whether the build will find the file at `target_path` without this step.
|
|
43
|
+
*
|
|
44
|
+
* Asked at generation time, which is when the step decides, and answerable
|
|
45
|
+
* because generation happens inside the checkout — on the host path locally
|
|
46
|
+
* and on the sandbox path inside the guest. The path is composed exactly the
|
|
47
|
+
* way the generated script composes the one it writes to, so the two cannot
|
|
48
|
+
* disagree about which file is in question.
|
|
49
|
+
*/
|
|
50
|
+
private fileAlreadyInCheckout;
|
|
26
51
|
}
|
|
27
52
|
//# sourceMappingURL=file.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/file.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/file.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAExD;;;;;;;;;GASG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;CAyBhD;AAED,MAAM,WAAW,UAAU;IACzB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,qBAAa,gBAAiB,SAAQ,gBAAgB;IAC9C,OAAO,CACX,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3B,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,OAAO,CAAC;IAiDnB;;;;;;;;OAQG;IACH,OAAO,CAAC,qBAAqB;CAI9B"}
|
|
@@ -1,5 +1,43 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { basename, join } from 'node:path';
|
|
2
3
|
import { BaseStepExecutor } from './base.js';
|
|
4
|
+
/**
|
|
5
|
+
* A credential the pipeline declares, the build reads, and nobody supplied.
|
|
6
|
+
*
|
|
7
|
+
* Thrown at generation time, from the step that would have written the file,
|
|
8
|
+
* so the build stops before its own build tool fails for a reason that names
|
|
9
|
+
* the project instead of the value we dropped. Generation time is also the
|
|
10
|
+
* only place `is_skippable` cannot swallow it: the converter sets that flag on
|
|
11
|
+
* a StepDef *after* `execute()` returns, so a step which merely exited
|
|
12
|
+
* non-zero would be reported as "failed but marked skippable — continuing".
|
|
13
|
+
*/
|
|
14
|
+
export class MissingFileSecretError extends Error {
|
|
15
|
+
varName;
|
|
16
|
+
targetPath;
|
|
17
|
+
constructor(varName, targetPath) {
|
|
18
|
+
super(`file@1.0.0 cannot write ${targetPath}: ${varName} is declared in this ` +
|
|
19
|
+
`pipeline but no value reached the build.\n` +
|
|
20
|
+
`\n` +
|
|
21
|
+
`A slot declared and left empty means the value arrives as a secret. ` +
|
|
22
|
+
`None did:\n` +
|
|
23
|
+
`not the pipeline itself, not .cibuild-secrets.json, and not this ` +
|
|
24
|
+
`build's environment.\n` +
|
|
25
|
+
`\n` +
|
|
26
|
+
`Supply it in one of two places:\n` +
|
|
27
|
+
`\n` +
|
|
28
|
+
` locally ci secrets add ${varName}\n` +
|
|
29
|
+
` remote build add ${varName} as an organization or pipeline ` +
|
|
30
|
+
`secret, then\n` +
|
|
31
|
+
` run the build again\n` +
|
|
32
|
+
`\n` +
|
|
33
|
+
`If ${basename(targetPath)} belongs to the repository instead, commit ` +
|
|
34
|
+
`it at\n` +
|
|
35
|
+
`${targetPath} — a file already in the checkout is used as it is.`);
|
|
36
|
+
this.name = 'MissingFileSecretError';
|
|
37
|
+
this.varName = varName;
|
|
38
|
+
this.targetPath = targetPath;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
3
41
|
/**
|
|
4
42
|
* Writes a file to a target path at runtime.
|
|
5
43
|
*
|
|
@@ -16,7 +54,29 @@ export class FileStepExecutor extends BaseStepExecutor {
|
|
|
16
54
|
const filename = basename(targetPath);
|
|
17
55
|
const content = env[varName];
|
|
18
56
|
if (!content) {
|
|
19
|
-
|
|
57
|
+
// Two absences, and they mean opposite things. A `var_name` nothing
|
|
58
|
+
// declares is a genuinely optional credential — a release keystore on a
|
|
59
|
+
// debug workflow — and skipping it is correct. A `var_name` the pipeline
|
|
60
|
+
// declares and leaves empty is a value that was supposed to arrive as a
|
|
61
|
+
// secret and did not: after issue 01 it is in none of the four places a
|
|
62
|
+
// value can come from, so the build is guaranteed red later, for a
|
|
63
|
+
// reason nothing in the log connects to this step. `mergeEnvVars` puts a
|
|
64
|
+
// declared key in the map even when its value is the empty placeholder,
|
|
65
|
+
// and nothing else puts a key there, so presence is the whole test.
|
|
66
|
+
//
|
|
67
|
+
// Except when the repository already carries the file. The Android
|
|
68
|
+
// generator declares KEYSTORE_BASE64 once, globally, but emits its
|
|
69
|
+
// `file@` step on every workflow including `pull-request`, where nothing
|
|
70
|
+
// signs anything — so "declared and empty" alone would refuse builds
|
|
71
|
+
// that are green today and rightly so. The question worth asking is the
|
|
72
|
+
// one the build will ask: will the file be there when it is read?
|
|
73
|
+
if (varName in env && !this.fileAlreadyInCheckout(targetPath, env)) {
|
|
74
|
+
throw new MissingFileSecretError(varName, targetPath);
|
|
75
|
+
}
|
|
76
|
+
const reason = varName in env
|
|
77
|
+
? `${filename} is already in the checkout, using it`
|
|
78
|
+
: `skipping ${filename}`;
|
|
79
|
+
return this.createScriptStep(`echo "⚠️ ${varName} not set — ${reason}"`, 'file');
|
|
20
80
|
}
|
|
21
81
|
// Base64-encode the content so it can be embedded safely in the script
|
|
22
82
|
// regardless of quotes, newlines, dollar signs, or any other special characters.
|
|
@@ -31,5 +91,18 @@ printf '%s' '${encoded}' | base64 -d > "\${CIBUILD_SOURCE_DIR:-$PWD}/${targetPat
|
|
|
31
91
|
echo "✓ Written ${filename}"`;
|
|
32
92
|
return this.createScriptStep(this.createBashScript(scriptContent, 'file'), 'file');
|
|
33
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Whether the build will find the file at `target_path` without this step.
|
|
96
|
+
*
|
|
97
|
+
* Asked at generation time, which is when the step decides, and answerable
|
|
98
|
+
* because generation happens inside the checkout — on the host path locally
|
|
99
|
+
* and on the sandbox path inside the guest. The path is composed exactly the
|
|
100
|
+
* way the generated script composes the one it writes to, so the two cannot
|
|
101
|
+
* disagree about which file is in question.
|
|
102
|
+
*/
|
|
103
|
+
fileAlreadyInCheckout(targetPath, env) {
|
|
104
|
+
const sourceDir = env.CIBUILD_SOURCE_DIR || process.cwd();
|
|
105
|
+
return existsSync(join(sourceDir, targetPath));
|
|
106
|
+
}
|
|
34
107
|
}
|
|
35
108
|
//# sourceMappingURL=file.js.map
|
|
@@ -146,6 +146,12 @@ describe('the steps that embed it emit syntactically valid bash', () => {
|
|
|
146
146
|
const r = await new XcodeBuildStepExecutor().execute({ project_path: 'MyApp.xcodeproj', scheme: 'MyApp', is_clean_build: true }, {}, testConfig);
|
|
147
147
|
await parses(r.script);
|
|
148
148
|
});
|
|
149
|
+
test('xcode-build-for-simulator', async () => {
|
|
150
|
+
const { XcodeBuildForSimulatorStepExecutor } = await import('./xcode.js');
|
|
151
|
+
const { testConfig } = await import('./test-config.js');
|
|
152
|
+
const r = await new XcodeBuildForSimulatorStepExecutor().execute({ project_path: "My App.xcworkspace", scheme: "My Scheme", perform_clean_action: 'yes' }, {}, testConfig);
|
|
153
|
+
await parses(r.script);
|
|
154
|
+
});
|
|
149
155
|
test('cocoapods-install', async () => {
|
|
150
156
|
const { CocoapodsInstallStepExecutor } = await import('./ios-deps.js');
|
|
151
157
|
const { testConfig } = await import('./test-config.js');
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* expensive one. These assert the split actually lands in the emitted script.
|
|
7
7
|
*/
|
|
8
8
|
import { describe, test, expect } from '@jest/globals';
|
|
9
|
-
import { XcodeBuildStepExecutor, XcodeBuildForTestStepExecutor } from './xcode.js';
|
|
9
|
+
import { XcodeBuildStepExecutor, XcodeBuildForTestStepExecutor, XcodeBuildForSimulatorStepExecutor, } from './xcode.js';
|
|
10
10
|
import { CocoapodsInstallStepExecutor } from './ios-deps.js';
|
|
11
11
|
import { testConfig } from './test-config.js';
|
|
12
12
|
const countOccurrences = (haystack, needle) => haystack.split(needle).length - 1;
|
|
@@ -83,6 +83,41 @@ describe('xcodebuild resolves before it builds', () => {
|
|
|
83
83
|
expect(await build({ is_clean_build: true })).toContain('xcodebuild clean build');
|
|
84
84
|
});
|
|
85
85
|
});
|
|
86
|
+
describe('xcode-build-for-simulator resolves before it builds', () => {
|
|
87
|
+
// This is the step the iOS template's "Build App" is, and it is the first
|
|
88
|
+
// step in that pipeline that resolves a package graph — so it is the one a
|
|
89
|
+
// refused fetch actually lands on. It was missed on the first pass.
|
|
90
|
+
const build = async (inputs = {}) => {
|
|
91
|
+
const executor = new XcodeBuildForSimulatorStepExecutor();
|
|
92
|
+
const result = await executor.execute({ project_path: 'MyApp.xcodeproj', scheme: 'MyApp', ...inputs }, {}, testConfig);
|
|
93
|
+
return result.script;
|
|
94
|
+
};
|
|
95
|
+
test('resolves package dependencies before building', async () => {
|
|
96
|
+
const script = await build();
|
|
97
|
+
expect(script).toContain('xcodebuild -resolvePackageDependencies');
|
|
98
|
+
expect(script.indexOf('xcodebuild -resolvePackageDependencies')).toBeLessThan(script.indexOf('xcodebuild build '));
|
|
99
|
+
});
|
|
100
|
+
test('wraps the resolve in the refused-fetch retry', async () => {
|
|
101
|
+
const script = await build();
|
|
102
|
+
expect(script).toContain('cibuild_fetch_was_refused');
|
|
103
|
+
expect(script).toContain('CIBUILD_FETCH_MAX_ATTEMPTS');
|
|
104
|
+
});
|
|
105
|
+
test('leaves the build invocation itself unwrapped', async () => {
|
|
106
|
+
expect(countOccurrences(await build(), 'CIBUILD_FETCH_MAX_ATTEMPTS=')).toBe(1);
|
|
107
|
+
});
|
|
108
|
+
test('passes the same project type and scheme as the build', async () => {
|
|
109
|
+
const resolveLine = (await build())
|
|
110
|
+
.split('\n')
|
|
111
|
+
.find((l) => l.includes('-resolvePackageDependencies'));
|
|
112
|
+
expect(resolveLine).toContain('"$PROJECT_TYPE"');
|
|
113
|
+
expect(resolveLine).toContain("-scheme 'MyApp'");
|
|
114
|
+
});
|
|
115
|
+
test('still honours a clean action and still finds the .app', async () => {
|
|
116
|
+
const script = await build({ perform_clean_action: 'yes' });
|
|
117
|
+
expect(script).toContain('xcodebuild clean build');
|
|
118
|
+
expect(script).toContain('CIBUILD_APP_DIR_PATH');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
86
121
|
describe('cocoapods-install retries a refused pod source fetch', () => {
|
|
87
122
|
const build = async (inputs = {}) => {
|
|
88
123
|
const executor = new CocoapodsInstallStepExecutor();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xcode.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/xcode.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"xcode.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/xcode.ts"],"names":[],"mappings":"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"}
|
|
@@ -1011,6 +1011,19 @@ export class XcodeBuildForSimulatorStepExecutor extends BaseStepExecutor {
|
|
|
1011
1011
|
commands.push(' PROJECT_TYPE="-project"');
|
|
1012
1012
|
commands.push('fi');
|
|
1013
1013
|
commands.push('');
|
|
1014
|
+
// Resolve the package graph before building, under the refused-fetch
|
|
1015
|
+
// retry. This is the step an iOS app is normally built with, so it is the
|
|
1016
|
+
// one a refused dependency fetch lands on first. No -derivedDataPath: the
|
|
1017
|
+
// build below does not set one either, so both use the default location
|
|
1018
|
+
// and the build finds the graph this just resolved.
|
|
1019
|
+
commands.push('# Resolve package dependencies first — see git-fetch-retry');
|
|
1020
|
+
commands.push(...refusedFetchHelpers());
|
|
1021
|
+
commands.push(...retryOnRefusedFetch({
|
|
1022
|
+
label: 'Package resolution',
|
|
1023
|
+
command: 'xcodebuild -resolvePackageDependencies' +
|
|
1024
|
+
` "$PROJECT_TYPE" '${escapedPath}'` +
|
|
1025
|
+
` -scheme '${escapedScheme}'`,
|
|
1026
|
+
}));
|
|
1014
1027
|
// Build command
|
|
1015
1028
|
let cmd = 'xcodebuild';
|
|
1016
1029
|
if (performClean === 'yes') {
|