@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.
Files changed (22) hide show
  1. package/dist/cli.cjs +10 -10
  2. package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.js +9 -6
  3. package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.js +71 -38
  4. package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts +29 -0
  5. package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts.map +1 -0
  6. package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.js +160 -0
  7. package/dist/src/commands/android-scanner.d.ts +9 -1
  8. package/dist/src/commands/android-scanner.d.ts.map +1 -1
  9. package/dist/src/commands/android-scanner.js +103 -3
  10. package/dist/src/commands/build.d.ts +16 -12
  11. package/dist/src/commands/build.d.ts.map +1 -1
  12. package/dist/src/commands/build.js +64 -45
  13. package/dist/src/commands/file-secret-collector.d.ts.map +1 -1
  14. package/dist/src/commands/file-secret-collector.js +24 -7
  15. package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.d.ts +38 -0
  16. package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.d.ts.map +1 -0
  17. package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.js +300 -0
  18. package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.d.ts +22 -0
  19. package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.d.ts.map +1 -0
  20. package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.js +97 -0
  21. package/dist/src/yaml/steps/ios-deps.js +1 -1
  22. 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
- const declared = envs(parse(yaml));
164
- expect(Object.keys(declared).filter((k) => k.includes('GOOGLE_SERVICES_JSON'))).toEqual([
165
- 'CIBUILD_SW__PRIMARY__GOOGLE_SERVICES_JSON',
166
- 'CIBUILD_SW__PULL_REQUEST__GOOGLE_SERVICES_JSON',
167
- 'CIBUILD_SW__RELEASE__GOOGLE_SERVICES_JSON',
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', () => {
@@ -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, encoded, when they do not', () => {
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: 'CIBUILD_SW__PRIMARY__LOCAL_PROPERTIES', workflow: 'primary' },
146
- { slot: 'CIBUILD_SW__PULL_REQUEST__LOCAL_PROPERTIES', workflow: 'pull-request' },
147
- { slot: 'CIBUILD_SW__RELEASE__LOCAL_PROPERTIES', workflow: 'release' },
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 once per workflow', () => {
179
+ test('declares the properties file in each workflow, and not globally', () => {
165
180
  const parsed = flavoredPipeline();
166
- expect(slotsFor(parsed, 'LOCAL_PROPERTIES')).toEqual([
167
- 'CIBUILD_SW__PRIMARY__LOCAL_PROPERTIES',
168
- 'CIBUILD_SW__PULL_REQUEST__LOCAL_PROPERTIES',
169
- 'CIBUILD_SW__RELEASE__LOCAL_PROPERTIES',
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
- expect(slotsFor(parsed, 'GOOGLE_SERVICES_JSON')).toEqual([
175
- 'CIBUILD_SW__PRIMARY__GOOGLE_SERVICES_JSON',
176
- 'CIBUILD_SW__PULL_REQUEST__GOOGLE_SERVICES_JSON',
177
- 'CIBUILD_SW__RELEASE__GOOGLE_SERVICES_JSON',
178
- ]);
179
- expect(slotsFor(parsed, 'KEYSTORE_BASE64')).toEqual([
180
- 'CIBUILD_SW__PRIMARY__KEYSTORE_BASE64',
181
- 'CIBUILD_SW__PULL_REQUEST__KEYSTORE_BASE64',
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. A slot with anything else in it is never discovered on the
209
- // developer's machine and never offered for upload.
210
- const declared = envs(flavoredPipeline());
211
- const credentialSlotKeys = Object.keys(declared).filter((k) => k.startsWith('CIBUILD_SW__') || /^(KEYSTORE|GOOGLE|LOCAL)_/.test(k));
212
- // Three scoped slots each for the properties file, google-services.json
213
- // and the keystore; one flat slot each for the two release-only ones.
214
- expect(credentialSlotKeys.length).toBe(11);
215
- for (const key of credentialSlotKeys)
216
- expect(declared[key]).toBe('');
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('names only workflows the pipeline actually has', () => {
219
- // `decodeSecretName` lower-cases and turns `_` back into `-`, so the
220
- // round trip is the only thing that proves `CIBUILD_SW__PULL_REQUEST__X`
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 scoped = Object.keys(envs(parsed))
225
- .filter((k) => k.startsWith('CIBUILD_SW__'))
226
- .map((k) => decodeSecretName(k)?.workflow);
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', () => {
@@ -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
@@ -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)}`));
@@ -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
- * 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.
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
- * 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.
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
- * 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
+ * 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;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,GACjE,cAAc,EAAE,CAOlB;AAgBD,yFAAyF;AACzF,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,sFAAsF;IACtF,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/C,KAAK,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,MAAM,GAC5D,eAAe,EAAE,CAUnB;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAC7C,eAAe,EAAE,CAInB;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,YAAY,EAAE,GACvB,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,qBAAqB,GAAG,iBAAiB,CAAC,CAoBjF;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,aAAa,GAAE,MAAgC,GAAG,MAAM,CAcvF;AAED,wBAAgB,uBAAuB,CACrC,WAAW,GAAE,MAAW,EACxB,KAAK,GAAE,YAAmS,EAC1S,QAAQ,GAAE,gBAAmC,EAC7C,eAAe,GAAE,QAAQ,GAAG,KAAgB,EAC5C,aAAa,GAAE,QAAQ,GAAG,YAA2B,EACrD,aAAa,GAAE,MAAgC,GAC9C,MAAM,CAiSR;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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"}