@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.
Files changed (64) hide show
  1. package/dist/cli.cjs +10 -10
  2. package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.d.ts +38 -0
  3. package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.d.ts.map +1 -0
  4. package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.js +222 -0
  5. package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.d.ts +41 -0
  6. package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.d.ts.map +1 -0
  7. package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.js +324 -0
  8. package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts +29 -0
  9. package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts.map +1 -0
  10. package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.js +160 -0
  11. package/dist/src/commands/android-scanner.d.ts +9 -1
  12. package/dist/src/commands/android-scanner.d.ts.map +1 -1
  13. package/dist/src/commands/android-scanner.js +103 -3
  14. package/dist/src/commands/build.d.ts +128 -1
  15. package/dist/src/commands/build.d.ts.map +1 -1
  16. package/dist/src/commands/build.js +286 -49
  17. package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.d.ts +26 -0
  18. package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.d.ts.map +1 -0
  19. package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.js +196 -0
  20. package/dist/src/commands/file-secret-collector.d.ts +13 -0
  21. package/dist/src/commands/file-secret-collector.d.ts.map +1 -1
  22. package/dist/src/commands/file-secret-collector.js +25 -7
  23. package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.d.ts +38 -0
  24. package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.d.ts.map +1 -0
  25. package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.js +300 -0
  26. package/dist/src/commands/local-properties-reaches-every-workflow.test.js +2 -0
  27. package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.d.ts +28 -0
  28. package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.d.ts.map +1 -0
  29. package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.js +223 -0
  30. package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.d.ts +37 -0
  31. package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.d.ts.map +1 -0
  32. package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.js +255 -0
  33. package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.d.ts +34 -0
  34. package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.d.ts.map +1 -0
  35. package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.js +218 -0
  36. package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.d.ts +27 -0
  37. package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.d.ts.map +1 -0
  38. package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.js +126 -0
  39. package/dist/src/yaml/converter.d.ts +48 -0
  40. package/dist/src/yaml/converter.d.ts.map +1 -1
  41. package/dist/src/yaml/converter.js +117 -0
  42. package/dist/src/yaml/env-resolver.d.ts +52 -0
  43. package/dist/src/yaml/env-resolver.d.ts.map +1 -1
  44. package/dist/src/yaml/env-resolver.js +99 -1
  45. package/dist/src/yaml/secrets-manager.d.ts +13 -3
  46. package/dist/src/yaml/secrets-manager.d.ts.map +1 -1
  47. package/dist/src/yaml/secrets-manager.js +25 -14
  48. package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.d.ts +22 -0
  49. package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.d.ts.map +1 -0
  50. package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.js +97 -0
  51. package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.d.ts +17 -0
  52. package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.d.ts.map +1 -0
  53. package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.js +132 -0
  54. package/dist/src/yaml/steps/index.d.ts.map +1 -1
  55. package/dist/src/yaml/steps/index.js +9 -0
  56. package/dist/src/yaml/steps/ios-deps.d.ts +35 -0
  57. package/dist/src/yaml/steps/ios-deps.d.ts.map +1 -1
  58. package/dist/src/yaml/steps/ios-deps.js +19 -4
  59. package/dist/src/yaml/steps/xcode-derived-data.test.d.ts +30 -0
  60. package/dist/src/yaml/steps/xcode-derived-data.test.d.ts.map +1 -0
  61. package/dist/src/yaml/steps/xcode-derived-data.test.js +103 -0
  62. package/dist/src/yaml/steps/xcode.d.ts.map +1 -1
  63. package/dist/src/yaml/steps/xcode.js +73 -20
  64. package/package.json +1 -1
@@ -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;AA6FD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,aAAa,CAmCxE;AAMD,wBAAsB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAoQjF;AAgBD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAyD3D"}
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
- * Detects build types and product flavors from Gradle files by static parsing.
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 = Array.from(productFlavorSet);
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
- export declare function detectSetupNeeds(warnings: BuildWarning[]): Omit<SetupOptions, "keystorePaths">;
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;AAQrE,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,oFAAoF;IACpF,YAAY,EAAE,KAAK,GAAG,KAAK,CAAC;IAC5B,8FAA8F;IAC9F,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC;;;;;OAKG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAC3B,oFAAoF;IACpF,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,8EAA8E;IAC9E,OAAO,EAAE,MAAM,CAAC;IAChB,+FAA+F;IAC/F,SAAS,EAAE,MAAM,CAAC;IAClB,6FAA6F;IAC7F,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,qBAAqB,CAAC;IAC/B,WAAW,EAAE,qBAAqB,CAAC;IACnC,OAAO,EAAE,qBAAqB,CAAC;CAChC;AAiBD,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,uBAAuB,CAAC;IACjC,WAAW,EAAE,uBAAuB,CAAC;IACrC,OAAO,EAAE,uBAAuB,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAiID;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,MAAM,GAAG,IAAI,CAYjF;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,CAAC,CAoB9F;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,aAAa,GAAE,MAAgC,GAAG,MAAM,CAcvF;AAED,wBAAgB,uBAAuB,CACrC,WAAW,GAAE,MAAW,EACxB,KAAK,GAAE,YAAqP,EAC5P,QAAQ,GAAE,gBAAmC,EAC7C,eAAe,GAAE,QAAQ,GAAG,KAAgB,EAC5C,aAAa,GAAE,QAAQ,GAAG,YAA2B,EACrD,aAAa,GAAE,MAAgC,GAC9C,MAAM,CAmPR;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,mBAAmB,EAC7B,aAAa,GAAE,QAAQ,GAAG,YAA2B,EACrD,aAAa,GAAE,MAAgC,GAC9C,MAAM,CA4LR;AAiVD,wBAAsB,kBAAkB,CACtC,uBAAuB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI,EAC1E,OAAO,GAAE;IACP,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,QAAQ,GAAG,YAAY,CAAC;CACpC,GACL,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"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"}