@invarn/cibuild 2.7.4 → 2.7.6
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/a-credential-target-follows-the-flavor-source-set.test.d.ts +38 -0
- package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.d.ts.map +1 -0
- package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.js +222 -0
- package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.d.ts +41 -0
- package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.d.ts.map +1 -0
- package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.js +324 -0
- package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts +29 -0
- package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts.map +1 -0
- package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.js +160 -0
- package/dist/src/commands/android-scanner.d.ts +9 -1
- package/dist/src/commands/android-scanner.d.ts.map +1 -1
- package/dist/src/commands/android-scanner.js +103 -3
- package/dist/src/commands/build.d.ts +128 -1
- package/dist/src/commands/build.d.ts.map +1 -1
- package/dist/src/commands/build.js +286 -49
- package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.d.ts +26 -0
- package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.d.ts.map +1 -0
- package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.js +196 -0
- package/dist/src/commands/file-secret-collector.d.ts +13 -0
- package/dist/src/commands/file-secret-collector.d.ts.map +1 -1
- package/dist/src/commands/file-secret-collector.js +25 -7
- package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.d.ts +38 -0
- package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.d.ts.map +1 -0
- package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.js +300 -0
- package/dist/src/commands/local-properties-reaches-every-workflow.test.js +2 -0
- package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.d.ts +28 -0
- package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.d.ts.map +1 -0
- package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.js +223 -0
- package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.d.ts +37 -0
- package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.d.ts.map +1 -0
- package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.js +255 -0
- package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.d.ts +34 -0
- package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.d.ts.map +1 -0
- package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.js +218 -0
- package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.d.ts +27 -0
- package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.d.ts.map +1 -0
- package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.js +126 -0
- package/dist/src/yaml/converter.d.ts +48 -0
- package/dist/src/yaml/converter.d.ts.map +1 -1
- package/dist/src/yaml/converter.js +117 -0
- package/dist/src/yaml/env-resolver.d.ts +52 -0
- package/dist/src/yaml/env-resolver.d.ts.map +1 -1
- package/dist/src/yaml/env-resolver.js +99 -1
- package/dist/src/yaml/secrets-manager.d.ts +13 -3
- package/dist/src/yaml/secrets-manager.d.ts.map +1 -1
- package/dist/src/yaml/secrets-manager.js +25 -14
- package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.d.ts +22 -0
- package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.js +97 -0
- package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.d.ts +17 -0
- package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.js +132 -0
- package/dist/src/yaml/steps/index.d.ts.map +1 -1
- package/dist/src/yaml/steps/index.js +9 -0
- package/dist/src/yaml/steps/ios-deps.d.ts +35 -0
- package/dist/src/yaml/steps/ios-deps.d.ts.map +1 -1
- package/dist/src/yaml/steps/ios-deps.js +19 -4
- package/dist/src/yaml/steps/xcode-derived-data.test.d.ts +30 -0
- package/dist/src/yaml/steps/xcode-derived-data.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/xcode-derived-data.test.js +103 -0
- package/dist/src/yaml/steps/xcode.d.ts.map +1 -1
- package/dist/src/yaml/steps/xcode.js +73 -20
- package/package.json +1 -1
package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"an-already-stored-value-does-not-answer-the-target-question.test.d.ts","sourceRoot":"","sources":["../../../src/commands/an-already-stored-value-does-not-answer-the-target-question.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A value already in `.cibuild-secrets.json` answers "what are the bytes".
|
|
3
|
+
* It does not answer "where does each workflow write them", and
|
|
4
|
+
* `collectFileSecret` used to treat the two as one question.
|
|
5
|
+
*
|
|
6
|
+
* `pendingGroups` is empty when every group already has the secret — and a
|
|
7
|
+
* **global** entry satisfies `hasSecret(name, workflow)` for every workflow, so
|
|
8
|
+
* one `ci init` in the repository's past is enough. The call then returned an
|
|
9
|
+
* empty paths map, the caller assigned that to `setupOptions.keystorePaths`,
|
|
10
|
+
* and the fallback filled every workflow from one source: the `storeFile=`
|
|
11
|
+
* line of whatever `keystore.properties` was collected, or the first path
|
|
12
|
+
* Gradle mentioned. One path for three workflows is one discriminator, so
|
|
13
|
+
* `credentialSlots` declared a single flat `KEYSTORE_BASE64` and the whole
|
|
14
|
+
* per-workflow split was lost — silently, and on exactly the credential the
|
|
15
|
+
* split exists for.
|
|
16
|
+
*
|
|
17
|
+
* Measured on the real repository, 2026-09-09: the store held
|
|
18
|
+
* `KEYSTORE_BASE64` globally, the keystore prompt never appeared, and all
|
|
19
|
+
* three workflows were generated writing to `customer-android.jks` — the
|
|
20
|
+
* release key's path, over the two debug workflows. A fresh clone of the same
|
|
21
|
+
* repository prompts and splits correctly, which is why this never showed up
|
|
22
|
+
* in a test that builds its own fixture from nothing.
|
|
23
|
+
*
|
|
24
|
+
* The store records values and never paths, so the path has to be asked for
|
|
25
|
+
* whichever the value's state is. Only a caller that consumes the returned map
|
|
26
|
+
* is affected — `promptTargetPath` marks it — so nothing else gains a prompt.
|
|
27
|
+
*/
|
|
28
|
+
import { describe, test, expect, beforeEach, afterEach } from '@jest/globals';
|
|
29
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
30
|
+
import { tmpdir } from 'node:os';
|
|
31
|
+
import { join, resolve } from 'node:path';
|
|
32
|
+
import prompts from 'prompts';
|
|
33
|
+
import { collectFileSecret } from './file-secret-collector.js';
|
|
34
|
+
import { SecretsManager } from '../yaml/secrets-manager.js';
|
|
35
|
+
import { credentialSlots } from './build.js';
|
|
36
|
+
const KEYSTORE_GROUPS = [
|
|
37
|
+
{ label: 'debug builds (primary, pull-request)', workflows: ['primary', 'pull-request'] },
|
|
38
|
+
{ label: 'release', workflows: ['release'] },
|
|
39
|
+
];
|
|
40
|
+
let workdir;
|
|
41
|
+
let secretsManager;
|
|
42
|
+
// `prompts.inject` concatenates, so a sentinel one test leaves queued becomes
|
|
43
|
+
// the next test's answer. The queue itself is the only reset there is.
|
|
44
|
+
const clearInjected = () => {
|
|
45
|
+
prompts._injected = undefined;
|
|
46
|
+
};
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
clearInjected();
|
|
49
|
+
workdir = resolve(mkdtempSync(join(tmpdir(), 'cibuild-stored-value-target-')));
|
|
50
|
+
secretsManager = new SecretsManager(join(workdir, '.cibuild-secrets.json'));
|
|
51
|
+
});
|
|
52
|
+
afterEach(() => {
|
|
53
|
+
clearInjected();
|
|
54
|
+
rmSync(workdir, { recursive: true, force: true });
|
|
55
|
+
});
|
|
56
|
+
function put(relPath, contents) {
|
|
57
|
+
const full = join(workdir, relPath);
|
|
58
|
+
mkdirSync(join(full, '..'), { recursive: true });
|
|
59
|
+
writeFileSync(full, contents, 'utf-8');
|
|
60
|
+
return full;
|
|
61
|
+
}
|
|
62
|
+
/** The repository's shape: a committed debug key and an uncommitted release key. */
|
|
63
|
+
function twoKeystores() {
|
|
64
|
+
return {
|
|
65
|
+
debug: put('config/debug.keystore', 'debug-key-bytes'),
|
|
66
|
+
release: put('app/customer-android.jks', 'release-key-bytes'),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function collectKeystore() {
|
|
70
|
+
return collectFileSecret({
|
|
71
|
+
displayName: 'keystore',
|
|
72
|
+
secretName: 'KEYSTORE_BASE64',
|
|
73
|
+
fileMatch: (name) => name.endsWith('.jks') || name.endsWith('.keystore'),
|
|
74
|
+
readAs: 'base64',
|
|
75
|
+
workflowGroups: KEYSTORE_GROUPS,
|
|
76
|
+
promptTargetPath: true,
|
|
77
|
+
}, workdir, secretsManager);
|
|
78
|
+
}
|
|
79
|
+
describe('a keystore whose value the store already holds', () => {
|
|
80
|
+
test('is still asked where each workflow writes it', async () => {
|
|
81
|
+
const { debug, release } = twoKeystores();
|
|
82
|
+
// One `ci init` in this repository's past, filed globally — which is what
|
|
83
|
+
// the real repository had.
|
|
84
|
+
secretsManager.storeSecret('KEYSTORE_BASE64', 'already-here');
|
|
85
|
+
prompts.inject([debug, release]);
|
|
86
|
+
const paths = await collectKeystore();
|
|
87
|
+
expect(paths).toEqual({
|
|
88
|
+
primary: 'config/debug.keystore',
|
|
89
|
+
'pull-request': 'config/debug.keystore',
|
|
90
|
+
release: 'app/customer-android.jks',
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
test('so the credential still declares one slot per workflow', async () => {
|
|
94
|
+
const { debug, release } = twoKeystores();
|
|
95
|
+
secretsManager.storeSecret('KEYSTORE_BASE64', 'already-here');
|
|
96
|
+
prompts.inject([debug, release]);
|
|
97
|
+
const paths = await collectKeystore();
|
|
98
|
+
const discriminatorByWorkflow = Object.fromEntries(Object.entries(paths).map(([workflow, path]) => [workflow, path]));
|
|
99
|
+
// One declaration per workflow, each under the plain name — what varies is
|
|
100
|
+
// the `envs` block it is emitted into, not the key.
|
|
101
|
+
expect(credentialSlots('KEYSTORE_BASE64', discriminatorByWorkflow)).toEqual([
|
|
102
|
+
{ slot: 'KEYSTORE_BASE64', workflow: 'primary' },
|
|
103
|
+
{ slot: 'KEYSTORE_BASE64', workflow: 'pull-request' },
|
|
104
|
+
{ slot: 'KEYSTORE_BASE64', workflow: 'release' },
|
|
105
|
+
]);
|
|
106
|
+
});
|
|
107
|
+
test('and a workflow-scoped entry hides the question just as well as a global one', async () => {
|
|
108
|
+
const { debug, release } = twoKeystores();
|
|
109
|
+
for (const workflow of ['primary', 'pull-request', 'release']) {
|
|
110
|
+
secretsManager.storeSecret('KEYSTORE_BASE64', 'already-here', workflow);
|
|
111
|
+
}
|
|
112
|
+
prompts.inject([debug, release]);
|
|
113
|
+
expect(await collectKeystore()).toEqual({
|
|
114
|
+
primary: 'config/debug.keystore',
|
|
115
|
+
'pull-request': 'config/debug.keystore',
|
|
116
|
+
release: 'app/customer-android.jks',
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
test('and the value the user picks now replaces the one that was there', async () => {
|
|
120
|
+
const { debug, release } = twoKeystores();
|
|
121
|
+
secretsManager.storeSecret('KEYSTORE_BASE64', 'already-here');
|
|
122
|
+
prompts.inject([debug, release]);
|
|
123
|
+
await collectKeystore();
|
|
124
|
+
// Re-reading it is the point: a stale global value is what would otherwise
|
|
125
|
+
// be uploaded for all three workflows.
|
|
126
|
+
expect(secretsManager.getSecret('KEYSTORE_BASE64', 'primary')).toBe(Buffer.from('debug-key-bytes').toString('base64'));
|
|
127
|
+
expect(secretsManager.getSecret('KEYSTORE_BASE64', 'release')).toBe(Buffer.from('release-key-bytes').toString('base64'));
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe('a credential whose caller does not need a path back', () => {
|
|
131
|
+
test('still short-circuits on an already-stored value, and asks nothing', async () => {
|
|
132
|
+
// `google-services.json`, `local.properties` and `keystore.properties` take
|
|
133
|
+
// their targets from the pipeline's own flavors, not from this call, so the
|
|
134
|
+
// early return is right for them and must stay. No answer is injected: a
|
|
135
|
+
// prompt here would hang or take undefined, and either fails the test.
|
|
136
|
+
put('app/src/prod/google-services.json', '{}');
|
|
137
|
+
secretsManager.storeSecret('GOOGLE_SERVICES_JSON', 'already-here');
|
|
138
|
+
const paths = await collectFileSecret({
|
|
139
|
+
displayName: 'google-services.json',
|
|
140
|
+
secretName: 'GOOGLE_SERVICES_JSON',
|
|
141
|
+
fileMatch: (name) => name === 'google-services.json',
|
|
142
|
+
readAs: 'text',
|
|
143
|
+
workflowGroups: [{ label: 'release', workflows: ['release'] }],
|
|
144
|
+
}, workdir, secretsManager);
|
|
145
|
+
expect(paths).toEqual({});
|
|
146
|
+
expect(secretsManager.getSecret('GOOGLE_SERVICES_JSON', 'release')).toBe('already-here');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
describe('a keystore the store has never seen', () => {
|
|
150
|
+
test('behaves exactly as it did before', async () => {
|
|
151
|
+
const { debug, release } = twoKeystores();
|
|
152
|
+
prompts.inject([debug, release]);
|
|
153
|
+
expect(await collectKeystore()).toEqual({
|
|
154
|
+
primary: 'config/debug.keystore',
|
|
155
|
+
'pull-request': 'config/debug.keystore',
|
|
156
|
+
release: 'app/customer-android.jks',
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
//# sourceMappingURL=an-already-stored-value-does-not-answer-the-target-question.test.js.map
|
|
@@ -132,8 +132,16 @@ export declare function runnableJavaVersion(detectedJavaVersion: number | undefi
|
|
|
132
132
|
*/
|
|
133
133
|
export declare function javaVersionProblem(detectedJavaVersion: number | undefined, gradleVersion?: string): string | undefined;
|
|
134
134
|
/**
|
|
135
|
-
* Detects build types and product flavors from Gradle files by static parsing
|
|
135
|
+
* Detects build types and product flavors from Gradle files by static parsing,
|
|
136
|
+
* falling back to the module's source sets for the flavors when no Gradle file
|
|
137
|
+
* declares any (see `flavorsFromSourceSets`).
|
|
138
|
+
*
|
|
136
139
|
* Always includes "debug" and "release" as defaults.
|
|
140
|
+
*
|
|
141
|
+
* The fallback only ever runs when the literal scan found nothing, so a
|
|
142
|
+
* project whose Gradle files declare flavors is decided by those files exactly
|
|
143
|
+
* as before. Merging the two would let a leftover directory invent a flavor
|
|
144
|
+
* the build does not have.
|
|
137
145
|
*/
|
|
138
146
|
export declare function detectBuildVariants(gradleFiles: string[]): BuildVariants;
|
|
139
147
|
export declare function scanAndroidProject(projectRoot: string): Promise<ScanResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"android-scanner.d.ts","sourceRoot":"","sources":["../../../src/commands/android-scanner.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM,CAAC;AAEjD,MAAM,MAAM,eAAe,GACvB,cAAc,GACd,SAAS,GACT,iBAAiB,GACjB,gBAAgB,GAChB,cAAc,GACd,UAAU,GACV,gBAAgB,CAAC;AAErB,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,eAAe,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uFAAuF;IACvF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,2FAA2F;IAC3F,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,gGAAgG;IAChG,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,wFAAwF;IACxF,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,uFAAuF;IACvF,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAwKD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAcnE;AAyKD,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAO7D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,GACX;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CA8CjD;AA2BD;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG7E;AAQD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAsBvF;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAIrF;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,uBAAuB,UAAsB,CAAC;AAE3D;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,QAAuC,CAAC;AAElF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CACjC,mBAAmB,EAAE,MAAM,GAAG,SAAS,EACvC,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,CAQR;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,mBAAmB,EAAE,MAAM,GAAG,SAAS,EACvC,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,GAAG,SAAS,CAkBpB;
|
|
1
|
+
{"version":3,"file":"android-scanner.d.ts","sourceRoot":"","sources":["../../../src/commands/android-scanner.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,MAAM,CAAC;AAEjD,MAAM,MAAM,eAAe,GACvB,cAAc,GACd,SAAS,GACT,iBAAiB,GACjB,gBAAgB,GAChB,cAAc,GACd,UAAU,GACV,gBAAgB,CAAC;AAErB,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,eAAe,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uFAAuF;IACvF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,2FAA2F;IAC3F,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,gGAAgG;IAChG,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,wFAAwF;IACxF,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,uFAAuF;IACvF,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAwKD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAcnE;AAyKD,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAO7D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,GACX;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CA8CjD;AA2BD;;;;;;;;;;;GAWG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG7E;AAQD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAsBvF;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAIrF;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,uBAAuB,UAAsB,CAAC;AAE3D;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,QAAuC,CAAC;AAElF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CACjC,mBAAmB,EAAE,MAAM,GAAG,SAAS,EACvC,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,CAQR;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,mBAAmB,EAAE,MAAM,GAAG,SAAS,EACvC,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,GAAG,SAAS,CAkBpB;AAwLD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,aAAa,CAsCxE;AAMD,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAoQjF;AAgBD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAyD3D"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolve, relative } from "node:path";
|
|
1
|
+
import { resolve, join, relative, dirname } from "node:path";
|
|
2
2
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
3
|
import { parseSettingsModules } from "../shared/gradle-settings.js";
|
|
4
4
|
// ---------------------------------------------------------------------------
|
|
@@ -655,9 +655,107 @@ function extractDirectChildNames(block) {
|
|
|
655
655
|
}
|
|
656
656
|
return [...new Set(names)];
|
|
657
657
|
}
|
|
658
|
+
/** Source-set directories AGP defines itself, which never name a flavor. */
|
|
659
|
+
const SHARED_SOURCE_SETS = new Set(["main", "test", "androidTest", "testFixtures"]);
|
|
660
|
+
/** `testProd` and `androidTestProd` are `prod`'s test source sets, not flavors. */
|
|
661
|
+
const TEST_SOURCE_SET_PREFIXES = ["test", "androidTest"];
|
|
658
662
|
/**
|
|
659
|
-
*
|
|
663
|
+
* What a source set holds. A directory under `src/` carrying none of these is
|
|
664
|
+
* something a human left there, not a source set AGP would read.
|
|
665
|
+
*/
|
|
666
|
+
const SOURCE_SET_CONTENTS = new Set([
|
|
667
|
+
"java",
|
|
668
|
+
"kotlin",
|
|
669
|
+
"res",
|
|
670
|
+
"resources",
|
|
671
|
+
"assets",
|
|
672
|
+
"aidl",
|
|
673
|
+
"jni",
|
|
674
|
+
"jniLibs",
|
|
675
|
+
"rs",
|
|
676
|
+
"renderscript",
|
|
677
|
+
"shaders",
|
|
678
|
+
"ml",
|
|
679
|
+
"baselineProfiles",
|
|
680
|
+
"AndroidManifest.xml",
|
|
681
|
+
"google-services.json",
|
|
682
|
+
]);
|
|
683
|
+
function safeReaddir(dir) {
|
|
684
|
+
try {
|
|
685
|
+
return readdirSync(dir, { withFileTypes: true }).map((e) => ({
|
|
686
|
+
name: e.name,
|
|
687
|
+
isDirectory: e.isDirectory(),
|
|
688
|
+
}));
|
|
689
|
+
}
|
|
690
|
+
catch {
|
|
691
|
+
return [];
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/** `testProd` starts with the segment `test`; a flavor called `testing` does not. */
|
|
695
|
+
function startsWithSegment(name, segment) {
|
|
696
|
+
if (!name.startsWith(segment) || name.length === segment.length)
|
|
697
|
+
return false;
|
|
698
|
+
const next = name[segment.length];
|
|
699
|
+
return next === next.toUpperCase() && next !== next.toLowerCase();
|
|
700
|
+
}
|
|
701
|
+
/** `ciDynamicDebug` ends with the segment `debug`; `staging` does not. */
|
|
702
|
+
function endsWithSegment(name, segment) {
|
|
703
|
+
const capitalized = segment.charAt(0).toUpperCase() + segment.slice(1);
|
|
704
|
+
return name.length > capitalized.length && name.endsWith(capitalized);
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Product flavors read from the source sets a module has on disk.
|
|
708
|
+
*
|
|
709
|
+
* Modern Android declares its flavors in a convention plugin — an enum in
|
|
710
|
+
* Kotlin source, applied through `create(flavor.name)` — so there is no
|
|
711
|
+
* literal `productFlavors { … }` block anywhere to parse. The scan is static
|
|
712
|
+
* by design (running a Gradle configuration phase to list flavors would cost
|
|
713
|
+
* minutes on every `ci build`), so the evidence has to be something already on
|
|
714
|
+
* disk. A flavor has a source set, which is the same evidence `invarn setup`
|
|
715
|
+
* has always resolved `google-services.json` by.
|
|
716
|
+
*
|
|
717
|
+
* A directory under `<module>/src/` names a flavor when it is none of the
|
|
718
|
+
* things AGP creates for its own reasons: the shared and test source sets, a
|
|
719
|
+
* build type, a test source set for one flavor, or a variant — a flavor and a
|
|
720
|
+
* build type already joined, which taking for a flavor would cross with the
|
|
721
|
+
* build types a second time and offer `ciDynamicDebugDebug`.
|
|
722
|
+
*/
|
|
723
|
+
function flavorsFromSourceSets(gradleFiles, buildTypes) {
|
|
724
|
+
const found = new Set();
|
|
725
|
+
for (const filePath of gradleFiles) {
|
|
726
|
+
const srcDir = join(dirname(filePath), "src");
|
|
727
|
+
for (const entry of safeReaddir(srcDir)) {
|
|
728
|
+
if (!entry.isDirectory)
|
|
729
|
+
continue;
|
|
730
|
+
const name = entry.name;
|
|
731
|
+
if (SHARED_SOURCE_SETS.has(name))
|
|
732
|
+
continue;
|
|
733
|
+
if (buildTypes.includes(name))
|
|
734
|
+
continue;
|
|
735
|
+
if (TEST_SOURCE_SET_PREFIXES.some((p) => startsWithSegment(name, p)))
|
|
736
|
+
continue;
|
|
737
|
+
if (buildTypes.some((bt) => endsWithSegment(name, bt)))
|
|
738
|
+
continue;
|
|
739
|
+
const holds = safeReaddir(join(srcDir, name));
|
|
740
|
+
if (!holds.some((child) => SOURCE_SET_CONTENTS.has(child.name)))
|
|
741
|
+
continue;
|
|
742
|
+
found.add(name);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
// The filesystem's order is not a declaration order, so give it one.
|
|
746
|
+
return Array.from(found).sort();
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Detects build types and product flavors from Gradle files by static parsing,
|
|
750
|
+
* falling back to the module's source sets for the flavors when no Gradle file
|
|
751
|
+
* declares any (see `flavorsFromSourceSets`).
|
|
752
|
+
*
|
|
660
753
|
* Always includes "debug" and "release" as defaults.
|
|
754
|
+
*
|
|
755
|
+
* The fallback only ever runs when the literal scan found nothing, so a
|
|
756
|
+
* project whose Gradle files declare flavors is decided by those files exactly
|
|
757
|
+
* as before. Merging the two would let a leftover directory invent a flavor
|
|
758
|
+
* the build does not have.
|
|
661
759
|
*/
|
|
662
760
|
export function detectBuildVariants(gradleFiles) {
|
|
663
761
|
const buildTypeSet = new Set(["debug", "release"]);
|
|
@@ -678,7 +776,9 @@ export function detectBuildVariants(gradleFiles) {
|
|
|
678
776
|
}
|
|
679
777
|
}
|
|
680
778
|
const buildTypes = Array.from(buildTypeSet);
|
|
681
|
-
const productFlavors =
|
|
779
|
+
const productFlavors = productFlavorSet.size > 0
|
|
780
|
+
? Array.from(productFlavorSet)
|
|
781
|
+
: flavorsFromSourceSets(gradleFiles, buildTypes);
|
|
682
782
|
const variants = productFlavors.length === 0
|
|
683
783
|
? buildTypes
|
|
684
784
|
: productFlavors.flatMap((flavor) => buildTypes.map((bt) => `${flavor}${bt.charAt(0).toUpperCase()}${bt.slice(1)}`));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BuildWarning } from "./android-scanner.js";
|
|
2
|
+
import type { WorkflowGroup } from "./file-secret-collector.js";
|
|
2
3
|
export interface SetupOptions {
|
|
3
4
|
keystore: boolean;
|
|
4
5
|
keystoreProperties: boolean;
|
|
@@ -9,6 +10,35 @@ export interface SetupOptions {
|
|
|
9
10
|
artifactType: 'apk' | 'aab';
|
|
10
11
|
/** Resolved keystore file paths per workflow (relative to project root), if found on disk. */
|
|
11
12
|
keystorePaths: Record<string, string>;
|
|
13
|
+
/**
|
|
14
|
+
* Where each workflow's `google-services.json` is written. It follows the
|
|
15
|
+
* **flavor** the workflow builds: Android gives every product flavor its own
|
|
16
|
+
* source set, each with its own copy of the file, and the plugin resolves by
|
|
17
|
+
* specificity — variant, build type, flavor, module root. A workflow
|
|
18
|
+
* building `ciDynamicDebug` therefore wants
|
|
19
|
+
* `app/src/ciDynamic/google-services.json`, and a workflow building
|
|
20
|
+
* `prodRelease` wants a different file at a different path.
|
|
21
|
+
*
|
|
22
|
+
* The *value* varies per workflow too, and that needs no second map: a
|
|
23
|
+
* secret is scoped to a workflow by its name — `CIBUILD_SW__PRIMARY__X`
|
|
24
|
+
* beside `CIBUILD_SW__RELEASE__X` — which `SecretsManager` decodes and
|
|
25
|
+
* `EnvResolver` resolves for the workflow being run. One `var_name` in the
|
|
26
|
+
* step, one declared slot, and the workflow picks the value.
|
|
27
|
+
*/
|
|
28
|
+
googleServicesPaths: Record<string, string>;
|
|
29
|
+
/**
|
|
30
|
+
* The product flavor each workflow's variant builds, or null where the
|
|
31
|
+
* variant names none.
|
|
32
|
+
*
|
|
33
|
+
* `LOCAL_PROPERTIES` follows this rather than a target path, and it is the
|
|
34
|
+
* only credential that has to: every workflow writes the same
|
|
35
|
+
* `local.properties` to the same place, so nothing about the path says the
|
|
36
|
+
* bytes differ. What belongs *in* the file is the flavor's environment — a
|
|
37
|
+
* different base API URL plus twenty other keys the Secrets Gradle Plugin
|
|
38
|
+
* turns into `BuildConfig` — so two workflows on different flavors cannot
|
|
39
|
+
* share one value, and `credentialSlots` splits the slot when they disagree.
|
|
40
|
+
*/
|
|
41
|
+
workflowFlavors: Record<string, string | null>;
|
|
12
42
|
/**
|
|
13
43
|
* Whether a build file applies the Google Secrets Gradle Plugin and reads a
|
|
14
44
|
* properties file the repository does not carry. The plugin's evaluation
|
|
@@ -58,7 +88,104 @@ export interface IosSetupOptions {
|
|
|
58
88
|
* that applies the plugin* and names the properties file only in its message.
|
|
59
89
|
*/
|
|
60
90
|
export declare function secretsPropertiesFileFrom(warnings: BuildWarning[]): string | null;
|
|
61
|
-
|
|
91
|
+
/**
|
|
92
|
+
* The product flavor a variant builds, out of the flavors the scanner found —
|
|
93
|
+
* `ciDynamicDebug` is a build of `ciDynamic`, `prodRelease` of `prod`.
|
|
94
|
+
*
|
|
95
|
+
* An Android variant name is the flavor followed by the capitalised build
|
|
96
|
+
* type, so the character after the flavor has to be an upper-case one. That
|
|
97
|
+
* check is load-bearing: without it a flavor called `pro` would claim
|
|
98
|
+
* `prodRelease`. The longest match wins, so `ciDynamic` beats `ci` on a
|
|
99
|
+
* repository that declares both.
|
|
100
|
+
*/
|
|
101
|
+
export declare function flavorOfVariant(variant: string, productFlavors: string[]): string | null;
|
|
102
|
+
/** Where a flavor's `google-services.json` belongs inside the app module. */
|
|
103
|
+
export declare function googleServicesPathFor(flavor: string | null, module?: string): string;
|
|
104
|
+
/**
|
|
105
|
+
* The per-workflow path and key maps for `SetupOptions`, from the variants the
|
|
106
|
+
* workflows build and the flavors the scanner found.
|
|
107
|
+
*
|
|
108
|
+
* `app/google-services.json` used to be hardcoded here. For a flavored build
|
|
109
|
+
* that is the plugin's *fallback* location, not the right one, and writing
|
|
110
|
+
* there is silently wrong rather than loudly wrong: the plugin finds a file,
|
|
111
|
+
* parses it, and fails on a package name — which reads as a Firebase
|
|
112
|
+
* misconfiguration rather than as the wrong file having been delivered. That
|
|
113
|
+
* is what `bld_8e60d90ab4e55f85d2f72459` spent 1292 Gradle tasks proving.
|
|
114
|
+
*/
|
|
115
|
+
export declare function googleServicesTargets(variants: WorkflowVariants, productFlavors: string[], module?: string): Record<string, string>;
|
|
116
|
+
/** The three workflows every generated pipeline has, in the order they are emitted. */
|
|
117
|
+
export declare const GENERATED_WORKFLOWS: readonly ["primary", "pull-request", "release"];
|
|
118
|
+
/**
|
|
119
|
+
* The `workflowFlavors` map for `SetupOptions`, from the variants the
|
|
120
|
+
* workflows build and the flavors the scanner found.
|
|
121
|
+
*/
|
|
122
|
+
export declare function variantFlavors(variants: WorkflowVariants, productFlavors: string[]): Record<string, string | null>;
|
|
123
|
+
/** One `envs` slot to declare, and the workflow it belongs to. */
|
|
124
|
+
export interface CredentialSlot {
|
|
125
|
+
slot: string;
|
|
126
|
+
/** null when one slot serves every workflow that reads the credential. */
|
|
127
|
+
workflow: string | null;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The `envs` slots one credential needs: one when every workflow that reads it
|
|
131
|
+
* wants the same bytes, one per workflow when they do not.
|
|
132
|
+
*
|
|
133
|
+
* What "the same bytes" means is the credential's own business — a keystore
|
|
134
|
+
* follows the target path the pipeline declares for it, `google-services.json`
|
|
135
|
+
* the flavor's source set, `local.properties` the flavor itself — so the
|
|
136
|
+
* caller passes the discriminator it already computed, one entry per workflow
|
|
137
|
+
* that reads the credential, and this decides only whether they agree. A
|
|
138
|
+
* credential only one workflow reads can never disagree, which is why the
|
|
139
|
+
* genuinely release-only ones keep one slot however flavored the repository
|
|
140
|
+
* is.
|
|
141
|
+
*
|
|
142
|
+
* **The name never changes — the block it sits in does.** An agreed credential
|
|
143
|
+
* is declared once in `app.envs`; a disagreement is declared under the plain
|
|
144
|
+
* credential name in **each workflow's own `envs`**, which is where the YAML
|
|
145
|
+
* already says which workflow something belongs to. `EnvResolver` merges
|
|
146
|
+
* `app.envs` and then the running workflow's own, so `release`'s declaration is
|
|
147
|
+
* invisible to `primary` without anything being encoded.
|
|
148
|
+
*
|
|
149
|
+
* `CIBUILD_SW__<WORKFLOW>__<VAR>` is deliberately *not* used here. That prefix
|
|
150
|
+
* is the transport encoding `ci secrets upload` carries a workflow-scoped
|
|
151
|
+
* **value** under — Invarn's secret scopes have no workflow column, so the
|
|
152
|
+
* workflow travels in the key. A pipeline has structure instead, and spending
|
|
153
|
+
* the encoding there would be a second way to say what the block already says
|
|
154
|
+
* (Alex, 2026-09-10).
|
|
155
|
+
*
|
|
156
|
+
* Every workflow gets a slot, not only the odd one out: a workflow left without
|
|
157
|
+
* one would have neither a value nor the declaration `file@1.0.0` refuses over.
|
|
158
|
+
*
|
|
159
|
+
* The `file@` step's `var_name` is the same plain name either way.
|
|
160
|
+
*/
|
|
161
|
+
export declare function credentialSlots(varName: string, discriminatorByWorkflow: Record<string, string | null | undefined>): CredentialSlot[];
|
|
162
|
+
/** One group of workflows a credential's value is collected for, and what they share. */
|
|
163
|
+
export interface CollectionGroup extends WorkflowGroup {
|
|
164
|
+
/** The value of whatever the credential follows, shared by this group's workflows. */
|
|
165
|
+
discriminator: string;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The groups `ci init` collects a credential's value in: one per distinct value
|
|
169
|
+
* of whatever that credential follows, in workflow order.
|
|
170
|
+
*
|
|
171
|
+
* The same discriminator `credentialSlots` splits the *declaration* by, and
|
|
172
|
+
* deliberately the same function computing it, so what `ci init` asks for and
|
|
173
|
+
* what the pipeline declares cannot drift apart. One group means one slot and
|
|
174
|
+
* one question; two groups mean one slot per workflow and a question per group.
|
|
175
|
+
*/
|
|
176
|
+
export declare function collectionGroups(discriminatorByWorkflow: Record<string, string>, label: (discriminator: string, workflows: string[]) => string): CollectionGroup[];
|
|
177
|
+
/**
|
|
178
|
+
* The groups the Secrets Gradle Plugin's properties file is collected in: one
|
|
179
|
+
* per flavor.
|
|
180
|
+
*
|
|
181
|
+
* Unlike `google-services.json`, nothing about this credential's target says
|
|
182
|
+
* the bytes differ — every workflow writes the same file to the same path.
|
|
183
|
+
* What differs is the content: the plugin turns the file into `BuildConfig`,
|
|
184
|
+
* and a flavor is an environment. Asking once and storing one value gives
|
|
185
|
+
* `prod` the CI backend's base URL, on a green build.
|
|
186
|
+
*/
|
|
187
|
+
export declare function propertiesFileGroups(workflowFlavors: Record<string, string | null>): CollectionGroup[];
|
|
188
|
+
export declare function detectSetupNeeds(warnings: BuildWarning[]): Omit<SetupOptions, "keystorePaths" | "googleServicesPaths" | "workflowFlavors">;
|
|
62
189
|
/**
|
|
63
190
|
* The `trigger_map` block appended to every generated pipeline.
|
|
64
191
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAc,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"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"}
|