@invarn/cibuild 2.7.5 → 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.js +9 -6
- package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.js +71 -38
- 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 +16 -12
- package/dist/src/commands/build.d.ts.map +1 -1
- package/dist/src/commands/build.js +64 -45
- package/dist/src/commands/file-secret-collector.d.ts.map +1 -1
- package/dist/src/commands/file-secret-collector.js +24 -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/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/ios-deps.js +1 -1
- package/package.json +1 -1
|
@@ -160,13 +160,16 @@ describe('the pipeline a flavored repository gets', () => {
|
|
|
160
160
|
googleServices: true,
|
|
161
161
|
googleServicesPaths: googleServicesTargets(FLAVORED, FLAVORS),
|
|
162
162
|
}, FLAVORED);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
163
|
+
// One declaration per workflow, in that workflow's own `envs`, under the
|
|
164
|
+
// plain credential name — the block says which workflow, so nothing is
|
|
165
|
+
// encoded into the key.
|
|
166
|
+
const parsed = parse(yaml);
|
|
167
|
+
const declaringWorkflows = Object.keys(parsed.workflows ?? {}).filter((workflow) => 'GOOGLE_SERVICES_JSON' in
|
|
168
|
+
Object.assign({}, ...(parsed.workflows?.[workflow]?.envs ?? [])));
|
|
169
|
+
expect(declaringWorkflows).toEqual(['primary', 'pull-request', 'release']);
|
|
170
|
+
expect(envs(parsed)).not.toHaveProperty('GOOGLE_SERVICES_JSON');
|
|
169
171
|
expect(yaml).not.toMatch(/GOOGLE_SERVICES_JSON_[A-Z]/);
|
|
172
|
+
expect(yaml).not.toContain('CIBUILD_SW__');
|
|
170
173
|
});
|
|
171
174
|
});
|
|
172
175
|
describe('a repository with no product flavors', () => {
|
package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.js
CHANGED
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
import { describe, test, expect } from '@jest/globals';
|
|
41
41
|
import { load } from 'js-yaml';
|
|
42
42
|
import { generateAndroidPipeline, googleServicesTargets, variantFlavors, credentialSlots, GENERATED_WORKFLOWS, } from './build.js';
|
|
43
|
-
import { decodeSecretName } from './secrets-upload.js';
|
|
44
43
|
function variant(name) {
|
|
45
44
|
const buildType = name.charAt(0).toUpperCase() + name.slice(1);
|
|
46
45
|
return { variant: name, buildType, gradleTask: `assemble${buildType}` };
|
|
@@ -92,6 +91,19 @@ function parse(yaml) {
|
|
|
92
91
|
function envs(parsed) {
|
|
93
92
|
return Object.assign({}, ...(parsed.app?.envs ?? []));
|
|
94
93
|
}
|
|
94
|
+
/** What one workflow's own `envs` block declares. */
|
|
95
|
+
function workflowEnvs(parsed, workflow) {
|
|
96
|
+
return Object.assign({}, ...(parsed.workflows?.[workflow]?.envs ?? []));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The workflows whose own `envs` declare `varName`, in pipeline order.
|
|
100
|
+
*
|
|
101
|
+
* The name is the plain credential now, so *where* the declaration sits is the
|
|
102
|
+
* whole answer to which workflow it belongs to.
|
|
103
|
+
*/
|
|
104
|
+
function declaringWorkflows(parsed, varName) {
|
|
105
|
+
return Object.keys(parsed.workflows ?? {}).filter((workflow) => varName in workflowEnvs(parsed, workflow));
|
|
106
|
+
}
|
|
95
107
|
/** The `var_name` of every `file@` step in `workflow`, in order. */
|
|
96
108
|
function fileVarNames(parsed, workflow) {
|
|
97
109
|
return (parsed.workflows?.[workflow]?.steps ?? [])
|
|
@@ -136,15 +148,18 @@ describe('the slots one credential gets', () => {
|
|
|
136
148
|
release: 'ciDynamic',
|
|
137
149
|
})).toEqual([{ slot: 'LOCAL_PROPERTIES', workflow: null }]);
|
|
138
150
|
});
|
|
139
|
-
test('are one per workflow,
|
|
151
|
+
test('are one per workflow, under the plain name, when they do not', () => {
|
|
152
|
+
// The name never changes; the block it is declared in does.
|
|
153
|
+
// `CIBUILD_SW__<WF>__<VAR>` is the encoding `ci secrets upload` carries a
|
|
154
|
+
// workflow-scoped *value* under, and a pipeline has structure instead.
|
|
140
155
|
expect(credentialSlots('LOCAL_PROPERTIES', {
|
|
141
156
|
primary: 'ciDynamic',
|
|
142
157
|
'pull-request': 'ciDynamic',
|
|
143
158
|
release: 'prod',
|
|
144
159
|
})).toEqual([
|
|
145
|
-
{ slot: '
|
|
146
|
-
{ slot: '
|
|
147
|
-
{ slot: '
|
|
160
|
+
{ slot: 'LOCAL_PROPERTIES', workflow: 'primary' },
|
|
161
|
+
{ slot: 'LOCAL_PROPERTIES', workflow: 'pull-request' },
|
|
162
|
+
{ slot: 'LOCAL_PROPERTIES', workflow: 'release' },
|
|
148
163
|
]);
|
|
149
164
|
});
|
|
150
165
|
test('include the workflows that agree, not only the odd one out', () => {
|
|
@@ -161,26 +176,26 @@ describe('the slots one credential gets', () => {
|
|
|
161
176
|
});
|
|
162
177
|
});
|
|
163
178
|
describe('a flavored repository', () => {
|
|
164
|
-
test('declares the properties file
|
|
179
|
+
test('declares the properties file in each workflow, and not globally', () => {
|
|
165
180
|
const parsed = flavoredPipeline();
|
|
166
|
-
expect(
|
|
167
|
-
'
|
|
168
|
-
'
|
|
169
|
-
'
|
|
181
|
+
expect(declaringWorkflows(parsed, 'LOCAL_PROPERTIES')).toEqual([
|
|
182
|
+
'primary',
|
|
183
|
+
'pull-request',
|
|
184
|
+
'release',
|
|
170
185
|
]);
|
|
186
|
+
// Globally would be one value for three workflows, which is the bug.
|
|
187
|
+
expect(envs(parsed)).not.toHaveProperty('LOCAL_PROPERTIES');
|
|
171
188
|
});
|
|
172
189
|
test('declares google-services.json and the keystore per workflow too', () => {
|
|
173
190
|
const parsed = flavoredPipeline();
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
'CIBUILD_SW__RELEASE__KEYSTORE_BASE64',
|
|
183
|
-
]);
|
|
191
|
+
for (const varName of ['GOOGLE_SERVICES_JSON', 'KEYSTORE_BASE64']) {
|
|
192
|
+
expect(declaringWorkflows(parsed, varName)).toEqual([
|
|
193
|
+
'primary',
|
|
194
|
+
'pull-request',
|
|
195
|
+
'release',
|
|
196
|
+
]);
|
|
197
|
+
expect(envs(parsed)).not.toHaveProperty(varName);
|
|
198
|
+
}
|
|
184
199
|
});
|
|
185
200
|
test('keeps one slot for the credentials only `release` reads', () => {
|
|
186
201
|
// Genuinely release-only, and legitimately so: nothing else signs a
|
|
@@ -205,27 +220,45 @@ describe('a flavored repository', () => {
|
|
|
205
220
|
});
|
|
206
221
|
test('declares every slot empty, which is what `invarn setup` searches by', () => {
|
|
207
222
|
// `unsetEnvKeys` collects `envs` keys whose declared value is the empty
|
|
208
|
-
// string
|
|
209
|
-
// developer's machine and
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
// string — from `app.envs` and from each workflow's own. A slot with
|
|
224
|
+
// anything else in it is never discovered on the developer's machine and
|
|
225
|
+
// never offered for upload.
|
|
226
|
+
const parsed = flavoredPipeline();
|
|
227
|
+
const isCredential = (k) => /^(KEYSTORE|GOOGLE|LOCAL)_/.test(k);
|
|
228
|
+
const declared = [
|
|
229
|
+
...Object.entries(envs(parsed)),
|
|
230
|
+
...Object.keys(parsed.workflows ?? {}).flatMap((w) => Object.entries(workflowEnvs(parsed, w))),
|
|
231
|
+
].filter(([key]) => isCredential(key));
|
|
232
|
+
// Three per-workflow slots each for the properties file,
|
|
233
|
+
// google-services.json and the keystore; one flat slot each for the two
|
|
234
|
+
// release-only ones.
|
|
235
|
+
expect(declared.length).toBe(11);
|
|
236
|
+
for (const [, value] of declared)
|
|
237
|
+
expect(value).toBe('');
|
|
238
|
+
});
|
|
239
|
+
test('spends no encoding on what the block already says', () => {
|
|
240
|
+
// The workflow a declaration belongs to is the block it sits in. The
|
|
241
|
+
// `CIBUILD_SW__` prefix stays what it has always been: how `ci secrets
|
|
242
|
+
// upload` transports a workflow-scoped value into a flat key→value store
|
|
243
|
+
// that has no workflow column (Alex, 2026-09-10).
|
|
244
|
+
const yaml = generateAndroidPipeline(17, {
|
|
245
|
+
...BASE,
|
|
246
|
+
...ALL_CREDENTIALS,
|
|
247
|
+
keystorePaths: SPLIT_KEYSTORES,
|
|
248
|
+
googleServicesPaths: googleServicesTargets(FLAVORED, FLAVORS),
|
|
249
|
+
workflowFlavors: variantFlavors(FLAVORED, FLAVORS),
|
|
250
|
+
}, FLAVORED);
|
|
251
|
+
expect(yaml).not.toContain('CIBUILD_SW__');
|
|
252
|
+
expect(yaml).not.toContain('CIBUILD_S__');
|
|
217
253
|
});
|
|
218
|
-
test('
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
// reaches `pull-request` and not a workflow nobody runs.
|
|
254
|
+
test('puts a per-workflow declaration in a workflow the pipeline has', () => {
|
|
255
|
+
// Structural now, and that is the point: a declaration cannot name a
|
|
256
|
+
// workflow that does not exist, because it lives inside one.
|
|
222
257
|
const parsed = flavoredPipeline();
|
|
223
258
|
const present = Object.keys(parsed.workflows ?? {});
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
expect(scoped.length).toBe(9);
|
|
228
|
-
expect(scoped.every((w) => w !== undefined && present.includes(w))).toBe(true);
|
|
259
|
+
const perWorkflow = ['KEYSTORE_BASE64', 'GOOGLE_SERVICES_JSON', 'LOCAL_PROPERTIES'].flatMap((varName) => declaringWorkflows(parsed, varName));
|
|
260
|
+
expect(perWorkflow.length).toBe(9);
|
|
261
|
+
expect(perWorkflow.every((w) => present.includes(w))).toBe(true);
|
|
229
262
|
});
|
|
230
263
|
});
|
|
231
264
|
describe('a repository whose workflows all want the same bytes', () => {
|
package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=an-already-stored-value-does-not-answer-the-target-question.test.d.ts.map
|
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)}`));
|
|
@@ -139,20 +139,24 @@ export interface CredentialSlot {
|
|
|
139
139
|
* genuinely release-only ones keep one slot however flavored the repository
|
|
140
140
|
* is.
|
|
141
141
|
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
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
148
|
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
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).
|
|
152
155
|
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
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.
|
|
156
160
|
*/
|
|
157
161
|
export declare function credentialSlots(varName: string, discriminatorByWorkflow: Record<string, string | null | undefined>): CredentialSlot[];
|
|
158
162
|
/** One group of workflows a credential's value is collected for, and what they share. */
|
|
@@ -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;AAMrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAIhE,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;;;;;;;;;;;;;;OAcG;IACH,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C;;;;;;;;;;;OAWG;IACH,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;IAC/C;;;;;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;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CASxF;AAED,6EAA6E;AAC7E,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,SAAQ,GAAG,MAAM,CAInF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,gBAAgB,EAC1B,cAAc,EAAE,MAAM,EAAE,EACxB,MAAM,SAAQ,GACb,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAED,uFAAuF;AACvF,eAAO,MAAM,mBAAmB,iDAAkD,CAAC;AAWnF;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,gBAAgB,EAC1B,cAAc,EAAE,MAAM,EAAE,GACvB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAM/B;AAED,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"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"}
|