@invarn/cibuild 2.7.4 → 2.7.5
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 +219 -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 +291 -0
- package/dist/src/commands/build.d.ts +124 -1
- package/dist/src/commands/build.d.ts.map +1 -1
- package/dist/src/commands/build.js +251 -33
- 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 +3 -2
- 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-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 +18 -3
- 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
|
@@ -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,100 @@ 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
|
+
* A disagreement is declared under `CIBUILD_SW__<WORKFLOW>__<VAR>`: the name
|
|
143
|
+
* `ci secrets upload` already encodes a workflow-scoped value under, that
|
|
144
|
+
* `SecretsManager` already decodes, and that `EnvResolver` already resolves
|
|
145
|
+
* for the workflow being run. Invarn's secret scopes have no workflow column,
|
|
146
|
+
* but a flat key→value map carries a workflow perfectly well when the workflow
|
|
147
|
+
* is part of the key.
|
|
148
|
+
*
|
|
149
|
+
* Every workflow gets a slot, not only the odd one out. The encoded name is
|
|
150
|
+
* per workflow, so a workflow left without one would have neither a value nor
|
|
151
|
+
* the declaration `file@1.0.0` refuses over.
|
|
152
|
+
*
|
|
153
|
+
* The `file@` step's `var_name` stays unsuffixed either way. The decoding does
|
|
154
|
+
* the mapping; a second naming convention in the step would be a second way to
|
|
155
|
+
* say the same thing.
|
|
156
|
+
*/
|
|
157
|
+
export declare function credentialSlots(varName: string, discriminatorByWorkflow: Record<string, string | null | undefined>): CredentialSlot[];
|
|
158
|
+
/** One group of workflows a credential's value is collected for, and what they share. */
|
|
159
|
+
export interface CollectionGroup extends WorkflowGroup {
|
|
160
|
+
/** The value of whatever the credential follows, shared by this group's workflows. */
|
|
161
|
+
discriminator: string;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The groups `ci init` collects a credential's value in: one per distinct value
|
|
165
|
+
* of whatever that credential follows, in workflow order.
|
|
166
|
+
*
|
|
167
|
+
* The same discriminator `credentialSlots` splits the *declaration* by, and
|
|
168
|
+
* deliberately the same function computing it, so what `ci init` asks for and
|
|
169
|
+
* what the pipeline declares cannot drift apart. One group means one slot and
|
|
170
|
+
* one question; two groups mean one slot per workflow and a question per group.
|
|
171
|
+
*/
|
|
172
|
+
export declare function collectionGroups(discriminatorByWorkflow: Record<string, string>, label: (discriminator: string, workflows: string[]) => string): CollectionGroup[];
|
|
173
|
+
/**
|
|
174
|
+
* The groups the Secrets Gradle Plugin's properties file is collected in: one
|
|
175
|
+
* per flavor.
|
|
176
|
+
*
|
|
177
|
+
* Unlike `google-services.json`, nothing about this credential's target says
|
|
178
|
+
* the bytes differ — every workflow writes the same file to the same path.
|
|
179
|
+
* What differs is the content: the plugin turns the file into `BuildConfig`,
|
|
180
|
+
* and a flavor is an environment. Asking once and storing one value gives
|
|
181
|
+
* `prod` the CI backend's base URL, on a green build.
|
|
182
|
+
*/
|
|
183
|
+
export declare function propertiesFileGroups(workflowFlavors: Record<string, string | null>): CollectionGroup[];
|
|
184
|
+
export declare function detectSetupNeeds(warnings: BuildWarning[]): Omit<SetupOptions, "keystorePaths" | "googleServicesPaths" | "workflowFlavors">;
|
|
62
185
|
/**
|
|
63
186
|
* The `trigger_map` block appended to every generated pipeline.
|
|
64
187
|
*
|
|
@@ -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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"}
|
|
@@ -4,6 +4,7 @@ import prompts from "prompts";
|
|
|
4
4
|
import { scanAndroidProject, formatScanResult, runnableJavaVersion, javaVersionProblem, } from "./android-scanner.js";
|
|
5
5
|
import { scanIosProject, formatIosScanResult } from "./ios-scanner.js";
|
|
6
6
|
import { SecretsManager } from "../yaml/secrets-manager.js";
|
|
7
|
+
import { encodeSecretName } from "./secrets-upload.js";
|
|
7
8
|
import { collectFileSecret } from "./file-secret-collector.js";
|
|
8
9
|
import { generateGitHubActionsWorkflow } from "./github-workflow.js";
|
|
9
10
|
import { detectDefaultBranch, FALLBACK_DEFAULT_BRANCH } from "../shared/git.js";
|
|
@@ -135,6 +136,154 @@ export function secretsPropertiesFileFrom(warnings) {
|
|
|
135
136
|
}
|
|
136
137
|
return "local.properties";
|
|
137
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* The product flavor a variant builds, out of the flavors the scanner found —
|
|
141
|
+
* `ciDynamicDebug` is a build of `ciDynamic`, `prodRelease` of `prod`.
|
|
142
|
+
*
|
|
143
|
+
* An Android variant name is the flavor followed by the capitalised build
|
|
144
|
+
* type, so the character after the flavor has to be an upper-case one. That
|
|
145
|
+
* check is load-bearing: without it a flavor called `pro` would claim
|
|
146
|
+
* `prodRelease`. The longest match wins, so `ciDynamic` beats `ci` on a
|
|
147
|
+
* repository that declares both.
|
|
148
|
+
*/
|
|
149
|
+
export function flavorOfVariant(variant, productFlavors) {
|
|
150
|
+
const matches = productFlavors
|
|
151
|
+
.filter((flavor) => {
|
|
152
|
+
if (flavor.length === 0 || !variant.startsWith(flavor))
|
|
153
|
+
return false;
|
|
154
|
+
const head = variant.slice(flavor.length)[0];
|
|
155
|
+
return head !== undefined && head === head.toUpperCase() && head !== head.toLowerCase();
|
|
156
|
+
})
|
|
157
|
+
.sort((a, b) => b.length - a.length);
|
|
158
|
+
return matches[0] ?? null;
|
|
159
|
+
}
|
|
160
|
+
/** Where a flavor's `google-services.json` belongs inside the app module. */
|
|
161
|
+
export function googleServicesPathFor(flavor, module = "app") {
|
|
162
|
+
return flavor
|
|
163
|
+
? `${module}/src/${flavor}/google-services.json`
|
|
164
|
+
: `${module}/google-services.json`;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The per-workflow path and key maps for `SetupOptions`, from the variants the
|
|
168
|
+
* workflows build and the flavors the scanner found.
|
|
169
|
+
*
|
|
170
|
+
* `app/google-services.json` used to be hardcoded here. For a flavored build
|
|
171
|
+
* that is the plugin's *fallback* location, not the right one, and writing
|
|
172
|
+
* there is silently wrong rather than loudly wrong: the plugin finds a file,
|
|
173
|
+
* parses it, and fails on a package name — which reads as a Firebase
|
|
174
|
+
* misconfiguration rather than as the wrong file having been delivered. That
|
|
175
|
+
* is what `bld_8e60d90ab4e55f85d2f72459` spent 1292 Gradle tasks proving.
|
|
176
|
+
*/
|
|
177
|
+
export function googleServicesTargets(variants, productFlavors, module = "app") {
|
|
178
|
+
const paths = {};
|
|
179
|
+
for (const [workflow, config] of variantByWorkflow(variants)) {
|
|
180
|
+
const flavor = flavorOfVariant(config.variant, productFlavors);
|
|
181
|
+
paths[workflow] = googleServicesPathFor(flavor, module);
|
|
182
|
+
}
|
|
183
|
+
return paths;
|
|
184
|
+
}
|
|
185
|
+
/** The three workflows every generated pipeline has, in the order they are emitted. */
|
|
186
|
+
export const GENERATED_WORKFLOWS = ["primary", "pull-request", "release"];
|
|
187
|
+
/** The variant each workflow builds, keyed by the workflow's name in the YAML. */
|
|
188
|
+
function variantByWorkflow(variants) {
|
|
189
|
+
return [
|
|
190
|
+
["primary", variants.primary],
|
|
191
|
+
["pull-request", variants.pullRequest],
|
|
192
|
+
["release", variants.release],
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The `workflowFlavors` map for `SetupOptions`, from the variants the
|
|
197
|
+
* workflows build and the flavors the scanner found.
|
|
198
|
+
*/
|
|
199
|
+
export function variantFlavors(variants, productFlavors) {
|
|
200
|
+
const flavors = {};
|
|
201
|
+
for (const [workflow, config] of variantByWorkflow(variants)) {
|
|
202
|
+
flavors[workflow] = flavorOfVariant(config.variant, productFlavors);
|
|
203
|
+
}
|
|
204
|
+
return flavors;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* The `envs` slots one credential needs: one when every workflow that reads it
|
|
208
|
+
* wants the same bytes, one per workflow when they do not.
|
|
209
|
+
*
|
|
210
|
+
* What "the same bytes" means is the credential's own business — a keystore
|
|
211
|
+
* follows the target path the pipeline declares for it, `google-services.json`
|
|
212
|
+
* the flavor's source set, `local.properties` the flavor itself — so the
|
|
213
|
+
* caller passes the discriminator it already computed, one entry per workflow
|
|
214
|
+
* that reads the credential, and this decides only whether they agree. A
|
|
215
|
+
* credential only one workflow reads can never disagree, which is why the
|
|
216
|
+
* genuinely release-only ones keep one slot however flavored the repository
|
|
217
|
+
* is.
|
|
218
|
+
*
|
|
219
|
+
* A disagreement is declared under `CIBUILD_SW__<WORKFLOW>__<VAR>`: the name
|
|
220
|
+
* `ci secrets upload` already encodes a workflow-scoped value under, that
|
|
221
|
+
* `SecretsManager` already decodes, and that `EnvResolver` already resolves
|
|
222
|
+
* for the workflow being run. Invarn's secret scopes have no workflow column,
|
|
223
|
+
* but a flat key→value map carries a workflow perfectly well when the workflow
|
|
224
|
+
* is part of the key.
|
|
225
|
+
*
|
|
226
|
+
* Every workflow gets a slot, not only the odd one out. The encoded name is
|
|
227
|
+
* per workflow, so a workflow left without one would have neither a value nor
|
|
228
|
+
* the declaration `file@1.0.0` refuses over.
|
|
229
|
+
*
|
|
230
|
+
* The `file@` step's `var_name` stays unsuffixed either way. The decoding does
|
|
231
|
+
* the mapping; a second naming convention in the step would be a second way to
|
|
232
|
+
* say the same thing.
|
|
233
|
+
*/
|
|
234
|
+
export function credentialSlots(varName, discriminatorByWorkflow) {
|
|
235
|
+
const distinct = new Set(Object.values(discriminatorByWorkflow));
|
|
236
|
+
if (distinct.size <= 1)
|
|
237
|
+
return [{ slot: varName, workflow: null }];
|
|
238
|
+
return Object.keys(discriminatorByWorkflow).map((workflow) => ({
|
|
239
|
+
slot: encodeSecretName(varName, workflow),
|
|
240
|
+
workflow,
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* One discriminator per workflow, defaulted the way the step that writes the
|
|
245
|
+
* credential defaults its own input — so a repository whose scan resolved
|
|
246
|
+
* nothing does not read as three workflows that disagree.
|
|
247
|
+
*/
|
|
248
|
+
function discriminators(map, fallback) {
|
|
249
|
+
const out = {};
|
|
250
|
+
for (const workflow of GENERATED_WORKFLOWS)
|
|
251
|
+
out[workflow] = map[workflow] ?? fallback;
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* The groups `ci init` collects a credential's value in: one per distinct value
|
|
256
|
+
* of whatever that credential follows, in workflow order.
|
|
257
|
+
*
|
|
258
|
+
* The same discriminator `credentialSlots` splits the *declaration* by, and
|
|
259
|
+
* deliberately the same function computing it, so what `ci init` asks for and
|
|
260
|
+
* what the pipeline declares cannot drift apart. One group means one slot and
|
|
261
|
+
* one question; two groups mean one slot per workflow and a question per group.
|
|
262
|
+
*/
|
|
263
|
+
export function collectionGroups(discriminatorByWorkflow, label) {
|
|
264
|
+
const byDiscriminator = new Map();
|
|
265
|
+
for (const [workflow, value] of Object.entries(discriminatorByWorkflow)) {
|
|
266
|
+
byDiscriminator.set(value, [...(byDiscriminator.get(value) ?? []), workflow]);
|
|
267
|
+
}
|
|
268
|
+
return [...byDiscriminator].map(([discriminator, workflows]) => ({
|
|
269
|
+
label: label(discriminator, workflows),
|
|
270
|
+
workflows,
|
|
271
|
+
discriminator,
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* The groups the Secrets Gradle Plugin's properties file is collected in: one
|
|
276
|
+
* per flavor.
|
|
277
|
+
*
|
|
278
|
+
* Unlike `google-services.json`, nothing about this credential's target says
|
|
279
|
+
* the bytes differ — every workflow writes the same file to the same path.
|
|
280
|
+
* What differs is the content: the plugin turns the file into `BuildConfig`,
|
|
281
|
+
* and a flavor is an environment. Asking once and storing one value gives
|
|
282
|
+
* `prod` the CI backend's base URL, on a green build.
|
|
283
|
+
*/
|
|
284
|
+
export function propertiesFileGroups(workflowFlavors) {
|
|
285
|
+
return collectionGroups(discriminators(workflowFlavors, ""), (flavor, workflows) => flavor ? `the ${flavor} flavor (${workflows.join(", ")})` : "every workflow");
|
|
286
|
+
}
|
|
138
287
|
export function detectSetupNeeds(warnings) {
|
|
139
288
|
const secretsPropertiesPath = secretsPropertiesFileFrom(warnings);
|
|
140
289
|
return {
|
|
@@ -174,7 +323,7 @@ trigger_map:
|
|
|
174
323
|
workflow: pull-request
|
|
175
324
|
`;
|
|
176
325
|
}
|
|
177
|
-
export function generateAndroidPipeline(javaVersion = 17, setup = { keystore: false, keystoreProperties: false, googleServices: false, googlePlayDeploy: false, googlePlayPackageName: '', artifactType: 'apk', keystorePaths: {}, secretsProperties: false, secretsPropertiesPath: 'local.properties' }, variants = DEFAULT_VARIANTS, cacheTechnology = "gradle", metaNamespace = 'cibuild.io', defaultBranch = FALLBACK_DEFAULT_BRANCH) {
|
|
326
|
+
export function generateAndroidPipeline(javaVersion = 17, setup = { keystore: false, keystoreProperties: false, googleServices: false, googlePlayDeploy: false, googlePlayPackageName: '', artifactType: 'apk', keystorePaths: {}, googleServicesPaths: {}, workflowFlavors: {}, secretsProperties: false, secretsPropertiesPath: 'local.properties' }, variants = DEFAULT_VARIANTS, cacheTechnology = "gradle", metaNamespace = 'cibuild.io', defaultBranch = FALLBACK_DEFAULT_BRANCH) {
|
|
178
327
|
const keystoreStepFor = (wf) => {
|
|
179
328
|
if (!setup.keystore)
|
|
180
329
|
return "";
|
|
@@ -191,25 +340,41 @@ export function generateAndroidPipeline(javaVersion = 17, setup = { keystore: fa
|
|
|
191
340
|
};
|
|
192
341
|
// Add BUILD_TYPE override to workflow envs when it differs from the global default ("Debug")
|
|
193
342
|
const buildTypeEnvOverride = (v) => v.buildType !== "Debug" ? ` - BUILD_TYPE: ${v.buildType}\n` : "";
|
|
194
|
-
// Optional env var stubs (empty defaults — filled via ci secrets add)
|
|
343
|
+
// Optional env var stubs (empty defaults — filled via ci secrets add).
|
|
344
|
+
//
|
|
345
|
+
// Declared globally and on purpose: `unsetEnvKeys` — what `invarn setup`
|
|
346
|
+
// searches the developer's machine by — reads `envs`, never step inputs. An
|
|
347
|
+
// undeclared credential is never discovered and never offered for upload,
|
|
348
|
+
// however many steps name it.
|
|
349
|
+
//
|
|
350
|
+
// One slot per credential where every workflow that reads it wants the same
|
|
351
|
+
// bytes, and one per workflow where they do not — see `credentialSlots`.
|
|
352
|
+
// The `var_name` in the step below stays unsuffixed either way.
|
|
353
|
+
const declare = (varName, discriminatorByWorkflow, comment) => credentialSlots(varName, discriminatorByWorkflow)
|
|
354
|
+
.map(({ slot, workflow }) => ` - ${slot}: ""${comment(workflow)}\n`)
|
|
355
|
+
.join("");
|
|
195
356
|
const keystoreEnv = setup.keystore
|
|
196
|
-
?
|
|
357
|
+
? declare("KEYSTORE_BASE64", discriminators(setup.keystorePaths, "keystore.jks"), (wf) => wf
|
|
358
|
+
? ` # Set via: ci secrets add KEYSTORE_BASE64 -w ${wf}`
|
|
359
|
+
: ` # Set via: ci secrets add KEYSTORE_BASE64 -w primary`)
|
|
197
360
|
: "";
|
|
361
|
+
// Release-only, and legitimately so: nothing but `release` signs a release
|
|
362
|
+
// or talks to Google Play, so there is no second workflow to disagree with.
|
|
198
363
|
const keystorePropertiesEnv = setup.keystoreProperties
|
|
199
364
|
? ` - KEYSTORE_PROPERTIES: "" # Release signing — set via: ci secrets add KEYSTORE_PROPERTIES -w release\n`
|
|
200
365
|
: "";
|
|
201
366
|
const googleServicesEnv = setup.googleServices
|
|
202
|
-
?
|
|
367
|
+
? declare("GOOGLE_SERVICES_JSON", discriminators(setup.googleServicesPaths, "app/google-services.json"), (wf) => wf
|
|
368
|
+
? ` # Set via: ci secrets add GOOGLE_SERVICES_JSON -w ${wf}`
|
|
369
|
+
: ` # Set via: ci secrets add GOOGLE_SERVICES_JSON`)
|
|
203
370
|
: "";
|
|
204
371
|
const googlePlayEnv = setup.googlePlayDeploy
|
|
205
372
|
? ` - GOOGLE_PLAY_SERVICE_ACCOUNT_JSON: "" # Set via: ci secrets add GOOGLE_PLAY_SERVICE_ACCOUNT_JSON -w release\n`
|
|
206
373
|
: "";
|
|
207
|
-
// Declared globally and on purpose: `unsetEnvKeys` — what `invarn setup`
|
|
208
|
-
// searches the developer's machine by — reads `envs`, never step inputs. An
|
|
209
|
-
// undeclared LOCAL_PROPERTIES is never discovered and never offered for
|
|
210
|
-
// upload, however many steps name it.
|
|
211
374
|
const secretsPropertiesEnv = setup.secretsProperties
|
|
212
|
-
?
|
|
375
|
+
? declare("LOCAL_PROPERTIES", discriminators(setup.workflowFlavors, ""), (wf) => wf
|
|
376
|
+
? ` # Read by the Secrets Gradle Plugin — set via: ci secrets add LOCAL_PROPERTIES -w ${wf}`
|
|
377
|
+
: ` # Read by the Secrets Gradle Plugin — set via: ci secrets add LOCAL_PROPERTIES`)
|
|
213
378
|
: "";
|
|
214
379
|
/**
|
|
215
380
|
* The Secrets Gradle Plugin's properties file, written into every workflow
|
|
@@ -227,15 +392,28 @@ export function generateAndroidPipeline(javaVersion = 17, setup = { keystore: fa
|
|
|
227
392
|
var_name: LOCAL_PROPERTIES
|
|
228
393
|
`
|
|
229
394
|
: "";
|
|
230
|
-
|
|
231
|
-
|
|
395
|
+
/**
|
|
396
|
+
* Per workflow, exactly as the keystore is: the flavor a workflow builds
|
|
397
|
+
* decides where the file is written. A workflow whose variant names no
|
|
398
|
+
* flavor is unchanged — `app/google-services.json`.
|
|
399
|
+
*
|
|
400
|
+
* The `var_name` is the same in every workflow on purpose. Scoping a value
|
|
401
|
+
* to a workflow is the *secret's* job, not the step's: `CIBUILD_SW__<WF>__X`
|
|
402
|
+
* is decoded by `SecretsManager` and resolved by `EnvResolver` for the
|
|
403
|
+
* workflow being run, so one name here reads a different value there.
|
|
404
|
+
*/
|
|
405
|
+
const googleServicesStepFor = (wf) => {
|
|
406
|
+
if (!setup.googleServices)
|
|
407
|
+
return "";
|
|
408
|
+
const path = setup.googleServicesPaths[wf] ?? "app/google-services.json";
|
|
409
|
+
return `
|
|
232
410
|
- file@1.0.0:
|
|
233
411
|
is_skippable: true
|
|
234
412
|
inputs:
|
|
235
|
-
target_path:
|
|
413
|
+
target_path: ${path}
|
|
236
414
|
var_name: GOOGLE_SERVICES_JSON
|
|
237
|
-
|
|
238
|
-
|
|
415
|
+
`;
|
|
416
|
+
};
|
|
239
417
|
const googlePlayStep = setup.googlePlayDeploy
|
|
240
418
|
? `
|
|
241
419
|
- file@1.0.0:
|
|
@@ -297,7 +475,7 @@ workflows:
|
|
|
297
475
|
- VARIANT: ${pv.variant}
|
|
298
476
|
${buildTypeEnvOverride(pv)}
|
|
299
477
|
steps:
|
|
300
|
-
${keystoreStepFor("primary")}${
|
|
478
|
+
${keystoreStepFor("primary")}${googleServicesStepFor("primary")}${secretsPropertiesStep}
|
|
301
479
|
- set-java-version@1.0.0:
|
|
302
480
|
inputs:
|
|
303
481
|
java_version: '${javaVersion}'
|
|
@@ -337,7 +515,7 @@ ${keystoreStepFor("primary")}${googleServicesStep}${secretsPropertiesStep}
|
|
|
337
515
|
- VARIANT: ${prv.variant}
|
|
338
516
|
${buildTypeEnvOverride(prv)}
|
|
339
517
|
steps:
|
|
340
|
-
${keystoreStepFor("pull-request")}${
|
|
518
|
+
${keystoreStepFor("pull-request")}${googleServicesStepFor("pull-request")}${secretsPropertiesStep}
|
|
341
519
|
- set-java-version@1.0.0:
|
|
342
520
|
inputs:
|
|
343
521
|
java_version: '${javaVersion}'
|
|
@@ -376,7 +554,7 @@ ${keystoreStepFor("pull-request")}${googleServicesStep}${secretsPropertiesStep}
|
|
|
376
554
|
- VARIANT: ${rv.variant}
|
|
377
555
|
${buildTypeEnvOverride(rv)}
|
|
378
556
|
steps:
|
|
379
|
-
${keystoreStepFor("release")}${keystorePropertiesStep}${
|
|
557
|
+
${keystoreStepFor("release")}${keystorePropertiesStep}${googleServicesStepFor("release")}${secretsPropertiesStep}
|
|
380
558
|
- set-java-version@1.0.0:
|
|
381
559
|
inputs:
|
|
382
560
|
java_version: '${javaVersion}'
|
|
@@ -943,7 +1121,13 @@ async function handleAndroidBuildCommand(cwd, options, cacheTechnology) {
|
|
|
943
1121
|
}
|
|
944
1122
|
// 4. Prompt to generate setup steps for detected file issues
|
|
945
1123
|
const setupNeeds = detectSetupNeeds(scanResult.warnings);
|
|
946
|
-
|
|
1124
|
+
// Which flavor each workflow builds decides where its google-services.json
|
|
1125
|
+
// is written, and — with no path to give it away — whether the properties
|
|
1126
|
+
// file can be one value or has to be one per workflow. Both maps are
|
|
1127
|
+
// derived from the variants chosen just above.
|
|
1128
|
+
const googleServicesPaths = googleServicesTargets(variants, productFlavors);
|
|
1129
|
+
const workflowFlavors = variantFlavors(variants, productFlavors);
|
|
1130
|
+
const setupOptions = { keystore: false, keystoreProperties: false, googleServices: false, googlePlayDeploy: false, googlePlayPackageName: '', artifactType: 'apk', keystorePaths: {}, googleServicesPaths, workflowFlavors, secretsProperties: false, secretsPropertiesPath: setupNeeds.secretsPropertiesPath };
|
|
947
1131
|
const secretsManager = new SecretsManager();
|
|
948
1132
|
const keystoreWorkflowGroups = [
|
|
949
1133
|
{ label: "debug builds (primary, pull-request)", workflows: ["primary", "pull-request"] },
|
|
@@ -1035,16 +1219,38 @@ async function handleAndroidBuildCommand(cwd, options, cacheTechnology) {
|
|
|
1035
1219
|
setupOptions.googleServices = !!create;
|
|
1036
1220
|
}
|
|
1037
1221
|
if (setupOptions.googleServices) {
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1222
|
+
// One collection per distinct target path. Workflows building the same
|
|
1223
|
+
// flavor write to the same place and are collected together; workflows
|
|
1224
|
+
// building different flavors need different files, so each is asked
|
|
1225
|
+
// separately and pinned to its own source set — which is what lets a
|
|
1226
|
+
// flavored repository auto-select rather than guess.
|
|
1227
|
+
//
|
|
1228
|
+
// The secret name is the same every time, and the *value* is stored per
|
|
1229
|
+
// workflow. That is what `SecretsManager` has always done, what
|
|
1230
|
+
// `ci secrets upload` encodes as `CIBUILD_SW__<WF>__<VAR>`, and what
|
|
1231
|
+
// `EnvResolver` resolves for the workflow being run.
|
|
1232
|
+
const byTarget = collectionGroups(discriminators(setupOptions.googleServicesPaths, "app/google-services.json"), (_path, workflows) => workflows.join(", "));
|
|
1233
|
+
for (const group of byTarget) {
|
|
1234
|
+
const flavorDir = /\/src\/([^/]+)\//.exec(group.discriminator)?.[1] ?? null;
|
|
1235
|
+
await collectFileSecret({
|
|
1236
|
+
displayName: flavorDir
|
|
1237
|
+
? `google-services.json for the ${flavorDir} flavor`
|
|
1238
|
+
: "google-services.json",
|
|
1239
|
+
secretName: "GOOGLE_SERVICES_JSON",
|
|
1240
|
+
fileMatch: (name) => name === "google-services.json",
|
|
1241
|
+
// A flavored target takes the copy from that flavor's source set. An
|
|
1242
|
+
// unflavored one takes anything that is *not* a flavor's copy, so
|
|
1243
|
+
// the module root wins rather than an arbitrary source set.
|
|
1244
|
+
pathMatch: flavorDir
|
|
1245
|
+
? (rel) => rel.includes(`/src/${flavorDir}/`) || rel.startsWith(`src/${flavorDir}/`)
|
|
1246
|
+
: (rel) => !/(?:^|\/)src\/[^/]+\//.test(rel),
|
|
1247
|
+
readAs: "text",
|
|
1248
|
+
// One group per call, not all of them: each group narrows the search
|
|
1249
|
+
// to its own flavor's source set, which is what lets a flavored
|
|
1250
|
+
// repository auto-select rather than ask.
|
|
1251
|
+
workflowGroups: [group],
|
|
1252
|
+
}, cwd, secretsManager, ni);
|
|
1253
|
+
}
|
|
1048
1254
|
}
|
|
1049
1255
|
}
|
|
1050
1256
|
if (setupNeeds.secretsProperties) {
|
|
@@ -1064,14 +1270,18 @@ async function handleAndroidBuildCommand(cwd, options, cacheTechnology) {
|
|
|
1064
1270
|
if (setupOptions.secretsProperties) {
|
|
1065
1271
|
// Every workflow, not just release: the plugin's evaluation listener
|
|
1066
1272
|
// runs for every variant, so a debug build cannot configure without it.
|
|
1273
|
+
//
|
|
1274
|
+
// And one group per flavor, because the plugin turns this file into
|
|
1275
|
+
// BuildConfig and a flavor is an environment. Every group in one call,
|
|
1276
|
+
// unlike google-services.json above: there is no per-flavor path
|
|
1277
|
+
// convention for a properties file to narrow the search by, so the
|
|
1278
|
+
// groups differ only in which answer they take.
|
|
1067
1279
|
await collectFileSecret({
|
|
1068
1280
|
displayName: propertiesFile,
|
|
1069
1281
|
secretName: "LOCAL_PROPERTIES",
|
|
1070
1282
|
fileMatch: (name) => name === propertiesFile.split("/").pop(),
|
|
1071
1283
|
readAs: "text",
|
|
1072
|
-
workflowGroups:
|
|
1073
|
-
{ label: "every workflow", workflows: ["primary", "pull-request", "release"] },
|
|
1074
|
-
],
|
|
1284
|
+
workflowGroups: propertiesFileGroups(setupOptions.workflowFlavors),
|
|
1075
1285
|
}, cwd, secretsManager, ni);
|
|
1076
1286
|
}
|
|
1077
1287
|
}
|
|
@@ -1156,8 +1366,16 @@ async function handleAndroidBuildCommand(cwd, options, cacheTechnology) {
|
|
|
1156
1366
|
console.log(" • Setup Keystore (KEYSTORE_BASE64)");
|
|
1157
1367
|
if (setupOptions.keystoreProperties)
|
|
1158
1368
|
console.log(" • Setup keystore.properties (KEYSTORE_PROPERTIES) — release only");
|
|
1159
|
-
if (setupOptions.googleServices)
|
|
1160
|
-
|
|
1369
|
+
if (setupOptions.googleServices) {
|
|
1370
|
+
const seen = new Set();
|
|
1371
|
+
for (const wf of GENERATED_WORKFLOWS) {
|
|
1372
|
+
const path = setupOptions.googleServicesPaths[wf];
|
|
1373
|
+
if (!path || seen.has(path))
|
|
1374
|
+
continue;
|
|
1375
|
+
seen.add(path);
|
|
1376
|
+
console.log(` • Setup ${path} (GOOGLE_SERVICES_JSON)`);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1161
1379
|
if (setupOptions.googlePlayDeploy)
|
|
1162
1380
|
console.log(` • Google Play deploy (${setupOptions.googlePlayPackageName}) — release only`);
|
|
1163
1381
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ci init` asks once per flavor for the properties file the Secrets Gradle
|
|
3
|
+
* Plugin reads, and stores the answer against that flavor's workflows.
|
|
4
|
+
*
|
|
5
|
+
* The generator now declares this credential once per workflow when the
|
|
6
|
+
* workflows build different flavors, because two flavors are two environments
|
|
7
|
+
* and one value cannot serve both. Collection had not moved: `collectFileSecret`
|
|
8
|
+
* was called with a single group labelled *"every workflow"*, so `ci init`
|
|
9
|
+
* asked once and stored the same bytes against `primary`, `pull-request` and
|
|
10
|
+
* `release`. The declaration would then say three values are needed and the
|
|
11
|
+
* store would hold one, three times.
|
|
12
|
+
*
|
|
13
|
+
* The machinery for this was already there. `collectFileSecret` takes
|
|
14
|
+
* `workflowGroups`, prompts per group, and stores per workflow — that is how
|
|
15
|
+
* the keystore has always collected a debug key and a release key separately.
|
|
16
|
+
* The properties file was the one credential passing it a single group.
|
|
17
|
+
*
|
|
18
|
+
* What matters more than the split is that it is the *same* split: `ci init`
|
|
19
|
+
* groups by exactly the discriminator `credentialSlots` declares by, so the
|
|
20
|
+
* question asked and the slot declared cannot drift apart. One group means one
|
|
21
|
+
* slot and one question; two groups mean one slot per workflow and a question
|
|
22
|
+
* per group. That invariant is asserted directly rather than left to two
|
|
23
|
+
* copies of the same rule staying in step.
|
|
24
|
+
*/
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=each-flavor-is-asked-for-its-own-properties-file.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"each-flavor-is-asked-for-its-own-properties-file.test.d.ts","sourceRoot":"","sources":["../../../src/commands/each-flavor-is-asked-for-its-own-properties-file.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG"}
|