catalyst-core 1.1.0 → 1.2.0-canary.2

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 (69) hide show
  1. package/bin/catalyst.js +54 -6
  2. package/changelog.md +27 -1
  3. package/dist/errors/registry.js +306 -18
  4. package/dist/errors-index.json +273 -70
  5. package/dist/native/androidProject/app/build.gradle.kts +53 -79
  6. package/dist/native/androidProject/app/src/main/AndroidManifest.xml +1 -1
  7. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CatalystConstants.kt +3 -1
  8. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +32 -4
  9. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/KeyboardUtil.kt +5 -1
  10. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/ConfigMappingTest.kt +25 -0
  11. package/dist/native/androidSetup.js +76 -170
  12. package/dist/native/androidToolchain.js +642 -0
  13. package/dist/native/appIcon.js +100 -0
  14. package/dist/native/buildAndroid/assets.js +29 -5
  15. package/dist/native/buildAndroid/build.js +110 -223
  16. package/dist/native/buildAndroid/config.js +57 -65
  17. package/dist/native/buildAndroid/devices.js +176 -0
  18. package/dist/native/buildAndroid/index.js +271 -46
  19. package/dist/native/buildAndroid/output.js +43 -0
  20. package/dist/native/buildAndroid/signing.js +456 -0
  21. package/dist/native/buildAppAndroid.js +27 -7
  22. package/dist/native/buildAppIos.js +139 -20
  23. package/dist/native/buildIos/assets.js +76 -4
  24. package/dist/native/buildIos/build.js +31 -181
  25. package/dist/native/buildIos/config.js +126 -11
  26. package/dist/native/buildIos/index.js +354 -34
  27. package/dist/native/buildIos/localhostCert.js +87 -0
  28. package/dist/native/buildIos/output.js +28 -0
  29. package/dist/native/buildIos/signing.js +596 -0
  30. package/dist/native/credentials.js +309 -0
  31. package/dist/native/exportCredentials.js +51 -0
  32. package/dist/native/iosToolchain.js +583 -0
  33. package/dist/native/iosnativeWebView/Configurations/Shared.xcconfig +1 -3
  34. package/dist/native/iosnativeWebView/iosnativeWebView/Info-Release.plist +2 -0
  35. package/dist/native/iosnativeWebView/iosnativeWebView/PrivacyInfo.xcprivacy +40 -0
  36. package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebView.entitlements +0 -2
  37. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +4 -4
  38. package/dist/native/localState.js +148 -0
  39. package/dist/native/nativeConfig.js +621 -0
  40. package/dist/native/releaseOutput.js +53 -0
  41. package/dist/native/renameAndroidProject.js +154 -369
  42. package/dist/native/setupEmulatorIos.js +58 -275
  43. package/dist/native/setupServer.js +520 -124
  44. package/dist/native/utils.js +418 -115
  45. package/dist/vite/resolveDevServerConfig.js +21 -0
  46. package/mcp_v2/knowledge-base.json +13 -11
  47. package/mcp_v2/test/config.test.ts +40 -0
  48. package/mcp_v2/tools/build.js +80 -104
  49. package/mcp_v2/tools/config.js +181 -174
  50. package/package.json +1 -1
  51. package/test/cli/nativeFlags.test.ts +97 -0
  52. package/test/native/androidDevices.test.ts +230 -0
  53. package/test/native/androidElfAlignment.test.ts +148 -0
  54. package/test/native/androidSigning.test.ts +646 -0
  55. package/test/native/androidToolchain.test.ts +766 -0
  56. package/test/native/appIcon.test.ts +124 -0
  57. package/test/native/credentials.test.ts +241 -0
  58. package/test/native/interaction.test.ts +179 -0
  59. package/test/native/iosConfigPhase.test.ts +611 -0
  60. package/test/native/iosSigning.test.ts +1060 -0
  61. package/test/native/iosToolchain.test.ts +821 -0
  62. package/test/native/localState.test.ts +80 -0
  63. package/test/native/nativeConfig.test.ts +602 -0
  64. package/test/native/setupServer.test.ts +529 -0
  65. package/test/native/utils.test.ts +102 -127
  66. package/test/vite/resolveDevServerConfig.test.js +25 -0
  67. package/dist/native/androidProject/keystore/Untitled +0 -0
  68. package/dist/native/androidProject/keystore/release-key.jks +0 -0
  69. package/dist/native/iosnativeWebView/iosnativeWebView/localhost.p12 +0 -0
@@ -5,9 +5,15 @@ const {
5
5
  displayPath
6
6
  } = require("../../cli/theme.js");
7
7
  const fs = require("fs");
8
+ const {
9
+ execFileSync
10
+ } = require("child_process");
8
11
  const {
9
12
  runCommand,
10
- runInteractiveCommand
13
+ runFile,
14
+ runInteractiveCommand,
15
+ isInteractive,
16
+ select
11
17
  } = require("../utils.js");
12
18
  const {
13
19
  buildFailure
@@ -23,18 +29,74 @@ const {
23
29
  const createConfigPhase = require("./config.js");
24
30
  const createAssetsPhase = require("./assets.js");
25
31
  const createBuildPhase = require("./build.js");
32
+ const {
33
+ resolveAndroidSigning,
34
+ exportUploadCertificate,
35
+ legacyKeystorePath
36
+ } = require("./signing.js");
37
+ const {
38
+ parseAdbDevices,
39
+ chooseAndroidTarget,
40
+ reachDevServer
41
+ } = require("./devices.js");
42
+ const {
43
+ releaseOutputDir,
44
+ releaseArtifactPaths
45
+ } = require("./output.js");
46
+ const {
47
+ DEFAULT_VERSION_NAME,
48
+ assertReleaseFolderFree
49
+ } = require("../releaseOutput.js");
50
+ const {
51
+ applyJavaHome,
52
+ ensureAndroidSdk,
53
+ ensureJava,
54
+ resolveSdkPath
55
+ } = require("../androidToolchain.js");
56
+ const {
57
+ getLastTarget,
58
+ saveLastTarget
59
+ } = require("../localState.js");
60
+ const {
61
+ ensureDevServer,
62
+ migrateLanAddresses,
63
+ devServerPorts,
64
+ readHmrPort
65
+ } = require("../setupServer.js");
26
66
  const catalystCorePath = path.dirname(require.resolve("catalyst-core/package.json"));
27
67
  const pwd = path.join(catalystCorePath, "dist/native");
28
68
  const ANDROID_PACKAGE = "io.yourname.androidproject";
29
69
  const steps = {
70
+ server: "Start dev server",
30
71
  config: "Initialize configuration",
31
- tools: "Validate Android tools",
32
- emulator: "Check and start emulator",
72
+ tools: "Check Java and the Android SDK",
73
+ signing: "Resolve upload key",
74
+ emulator: "Prepare the device",
33
75
  copyAssets: "Copy build assets",
34
76
  build: "Build and install application",
35
77
  aab: "Build signed AAB"
36
78
  };
37
- function createAndroidBuild(config) {
79
+
80
+ /**
81
+ * @param {object} config - { WEBVIEW_CONFIG, BUILD_OUTPUT_PATH } from config.json
82
+ * @param {object} [options] - parseNativeArgs()
83
+ * @param {boolean} [options.release] - `--release`: build the signed AAB whatever android.buildType says
84
+ * @param {string} [options.url] - `--url`: the release's production URL, over WEBVIEW_CONFIG.url
85
+ * @param {boolean} [options.device] - `--device`: run on the connected phone, not the emulator
86
+ * @param {boolean} [options.yes] - `--yes`: allow JDK/SDK/emulator installs without a terminal
87
+ * @param {boolean} [options.acceptLicenses] - `--accept-licenses`
88
+ */
89
+ function createAndroidBuild(config, {
90
+ release: releaseFlag = false,
91
+ url,
92
+ device: wantDevice = false,
93
+ yes = false,
94
+ acceptLicenses = false
95
+ } = {}) {
96
+ const flags = {
97
+ yes,
98
+ acceptLicenses
99
+ };
38
100
  const {
39
101
  WEBVIEW_CONFIG,
40
102
  BUILD_OUTPUT_PATH
@@ -53,6 +115,7 @@ function createAndroidBuild(config) {
53
115
  progress,
54
116
  ANDROID_PACKAGE,
55
117
  runCommand,
118
+ runFile,
56
119
  runInteractiveCommand
57
120
  };
58
121
  const {
@@ -69,12 +132,9 @@ function createAndroidBuild(config) {
69
132
  processNotifications
70
133
  } = createAssetsPhase(ctx);
71
134
  const {
72
- detectPhysicalDevice,
73
- testPhysicalDeviceInstallation,
74
135
  handleEmulatorSetup,
75
136
  buildApp,
76
137
  launchApp,
77
- moveApkToOutputPath,
78
138
  buildSignedAAB
79
139
  } = createBuildPhase(ctx);
80
140
  function syncAIPackageIfEnabled(wvConfig, progress) {
@@ -106,55 +166,178 @@ function createAndroidBuild(config) {
106
166
  entry.isDirectory() ? copyDirSync(s, d) : fs.copyFileSync(s, d);
107
167
  }
108
168
  }
169
+
170
+ /** adb on one SDK, with a bound: a wedged adb server never answers. */
171
+ function adbRunner(ADB_PATH) {
172
+ return args => execFileSync(ADB_PATH, args, {
173
+ encoding: "utf8",
174
+ timeout: 15000,
175
+ stdio: ["ignore", "pipe", "pipe"]
176
+ });
177
+ }
178
+ function listDevices(ADB_PATH) {
179
+ if (!fs.existsSync(ADB_PATH)) return [];
180
+ try {
181
+ return parseAdbDevices(adbRunner(ADB_PATH)(["devices", "-l"]));
182
+ } catch (error) {
183
+ progress.log(`adb devices failed: ${error.message}`, "warning");
184
+ return [];
185
+ }
186
+ }
109
187
  async function buildAndroidApp() {
110
188
  let androidConfig = null;
111
189
  let targetDevice = null;
190
+ let resolved = null;
191
+ let signing = null;
192
+ let artifactPath = null;
193
+ let certificatePath = null;
112
194
  try {
195
+ const log = (message, type) => progress.log(message, type);
196
+ const appRoot = process.cwd();
197
+ // Older setups saved this machine's LAN address in config.json.
198
+ // Put localhost back once, before the config is read (never for
199
+ // a release or in CI).
200
+ migrateLanAddresses({
201
+ cwd: appRoot,
202
+ release: releaseFlag,
203
+ platform: "android",
204
+ log
205
+ });
113
206
  progress.start("config");
207
+ resolved = await initializeConfig({
208
+ release: releaseFlag,
209
+ url
210
+ });
114
211
  const {
115
212
  WEBVIEW_CONFIG: wvConfig,
116
- BUILD_OUTPUT_PATH: bop
117
- } = await initializeConfig();
213
+ version,
214
+ applicationId
215
+ } = resolved;
118
216
  androidConfig = wvConfig.android;
119
- const buildType = androidConfig.buildType || "debug";
217
+ const release = resolved.release;
120
218
  const buildOptimisation = !!androidConfig.buildOptimisation || false;
219
+ progress.log(`Version ${version.versionName || DEFAULT_VERSION_NAME} (${version.buildNumber}), ${applicationId}`, "info");
220
+ // Before any tooling runs: a second release in the same minute
221
+ // would overwrite the first one's bundle.
222
+ if (release) assertReleaseFolderFree(releaseOutputDir({
223
+ appRoot,
224
+ androidConfig,
225
+ version
226
+ }), [".aab"]);
121
227
  progress.complete("config");
228
+
229
+ // One-command dev: a debug build starts the dev server when
230
+ // nothing answers on its port, and waits until it does.
231
+ if (resolved.usesDevServer) {
232
+ progress.start("server");
233
+ await ensureDevServer({
234
+ cwd: appRoot,
235
+ log
236
+ });
237
+ progress.complete("server");
238
+ }
122
239
  progress.start("tools");
240
+ // Gradle and keytool get JDK 17 to 23 through this process's
241
+ // environment; no shell profile is involved. Missing pieces are
242
+ // installed after a yes (or --yes); the SDK path is read, never written.
243
+ const java = await ensureJava({
244
+ flags,
245
+ log
246
+ });
247
+ applyJavaHome(java);
248
+ progress.log(`Java ${java.version} from ${java.source}`, "info");
249
+ androidConfig.sdkPath = resolveSdkPath(androidConfig);
250
+ const adbPath = path.join(androidConfig.sdkPath, "platform-tools", "adb");
251
+
252
+ // Where a debug build runs: the emulator unless --device, or a
253
+ // person here picks a connected phone.
254
+ const last = getLastTarget("android", {
255
+ cwd: appRoot
256
+ });
257
+ let target = null;
258
+ const pickTarget = async () => chooseAndroidTarget({
259
+ wantDevice,
260
+ devices: listDevices(adbPath),
261
+ last,
262
+ isTTY: isInteractive(),
263
+ select,
264
+ emulatorHint: androidConfig.emulatorName || (last?.target === "emulator" ? last.avd : undefined)
265
+ });
266
+ // Without --device the choice comes first, so a picked phone
267
+ // skips the emulator install. With it, adb may need installing first.
268
+ if (!release && !wantDevice) target = await pickTarget();
269
+ const needEmulator = !release && !wantDevice && target?.target === "emulator";
270
+ const sdk = await ensureAndroidSdk({
271
+ sdkPath: androidConfig.sdkPath,
272
+ emulatorName: androidConfig.emulatorName,
273
+ preferredAvd: last?.target === "emulator" ? last.avd : undefined,
274
+ needEmulator,
275
+ flags,
276
+ log
277
+ });
278
+ if (sdk.installed.length) progress.log(`Installed ${sdk.installed.join(", ")}`, "success");
279
+ if (sdk.createdAvd) progress.log(`Created the "${sdk.emulatorName}" emulator`, "success");
280
+ if (sdk.emulatorName) androidConfig.emulatorName = sdk.emulatorName;
123
281
  const {
124
282
  ADB_PATH,
125
283
  EMULATOR_PATH
126
- } = validateAndroidTools(androidConfig);
284
+ } = validateAndroidTools(androidConfig, {
285
+ needEmulator
286
+ });
287
+ if (!release && wantDevice) target = await pickTarget();
127
288
  progress.complete("tools");
128
- if (buildType !== "release") {
289
+ if (release) {
290
+ progress.start("signing");
291
+ signing = await resolveAndroidSigning({
292
+ appRoot,
293
+ androidConfig,
294
+ isTTY: isInteractive(),
295
+ log,
296
+ // Named from the config as written: appId and appName
297
+ // overwrite packageName and android.appName in androidConfig.
298
+ legacyKeystorePath: legacyKeystorePath(resolved.config?.WEBVIEW_CONFIG?.android, {
299
+ cwd: appRoot
300
+ }),
301
+ commonName: androidConfig.appName
302
+ });
303
+ progress.log(`Upload key: ${signing.source} (alias ${signing.keyAlias})`, "info");
304
+ progress.complete("signing");
305
+ } else {
129
306
  progress.start("emulator");
130
- const physicalDevice = await detectPhysicalDevice(ADB_PATH);
131
- if (physicalDevice) {
132
- progress.log(`Found physical device: ${physicalDevice.model} (${physicalDevice.id})`, "success");
133
- const canInstallOnPhysical = await testPhysicalDeviceInstallation(ADB_PATH, physicalDevice.id);
134
- if (canInstallOnPhysical) {
135
- targetDevice = {
136
- type: "physical",
137
- id: physicalDevice.id,
138
- model: physicalDevice.model
139
- };
140
- progress.log(`Using physical device: ${physicalDevice.model}`, "success");
141
- } else {
142
- progress.log("Physical device installation test failed, falling back to emulator", "warning");
143
- targetDevice = await handleEmulatorSetup(ADB_PATH, EMULATOR_PATH, androidConfig);
144
- }
307
+ if (target.target === "device") {
308
+ targetDevice = {
309
+ type: "physical",
310
+ id: target.serial,
311
+ model: target.model || target.serial
312
+ };
313
+ progress.log(`Using the connected phone ${targetDevice.model}`, "success");
314
+ saveLastTarget("android", {
315
+ target: "device",
316
+ serial: target.serial
317
+ }, {
318
+ cwd: appRoot
319
+ });
145
320
  } else {
146
- targetDevice = await handleEmulatorSetup(ADB_PATH, EMULATOR_PATH, androidConfig);
321
+ const emulator = await handleEmulatorSetup(ADB_PATH, EMULATOR_PATH, androidConfig);
322
+ targetDevice = {
323
+ ...emulator,
324
+ id: emulator.serial
325
+ };
326
+ saveLastTarget("android", {
327
+ target: "emulator",
328
+ avd: emulator.name
329
+ }, {
330
+ cwd: appRoot
331
+ });
147
332
  }
148
333
  progress.complete("emulator");
149
- } else {
150
- progress.log("Skipping device setup for release build", "info");
151
334
  }
152
335
  progress.start("copyAssets");
153
336
  await copyBuildAssets(androidConfig, buildOptimisation);
154
337
  await copySplashscreenAssets();
155
338
  await copyOfflinePage();
156
339
  await copySplashHtml();
157
- await copyIconAssets();
340
+ await copyIconAssets(androidConfig);
158
341
  await configureAppName(androidConfig);
159
342
  const pluginConfig = resolvePluginConfig(wvConfig);
160
343
  composeAndroidPlugins({
@@ -167,45 +350,81 @@ function createAndroidBuild(config) {
167
350
  progress.log(`Build optimization: ${buildOptimisation ? "Enabled" : "Disabled"}`, "info");
168
351
  syncAIPackageIfEnabled(wvConfig, progress);
169
352
  progress.complete("copyAssets");
170
- let movedApkPath = null;
171
- if (buildType === "release") {
353
+ const gradleOptions = {
354
+ configPath: resolved.resolvedConfigPath,
355
+ applicationId,
356
+ version
357
+ };
358
+ if (release) {
172
359
  progress.start("aab");
173
- await buildSignedAAB(androidConfig);
360
+ const outputDir = releaseOutputDir({
361
+ appRoot,
362
+ androidConfig,
363
+ version
364
+ });
365
+ const names = releaseArtifactPaths(outputDir, androidConfig.newProjectName || androidConfig.appName || "app");
366
+ const aab = await buildSignedAAB(androidConfig, {
367
+ ...gradleOptions,
368
+ signing,
369
+ buildOptimisation,
370
+ finalAabPath: names.aab
371
+ });
372
+ artifactPath = aab?.finalAabPath || names.aab;
373
+ // The upload certificate beside the bundle (#508): Play asks
374
+ // for it when an upload key is registered or reset.
375
+ certificatePath = exportUploadCertificate(signing, names.certificate);
174
376
  progress.complete("aab");
175
- movedApkPath = await moveApkToOutputPath(buildType, bop, androidConfig.appName);
176
377
  } else {
177
378
  progress.start("build");
178
- await buildApp(ADB_PATH, androidConfig, buildOptimisation, targetDevice);
179
- await launchApp(ADB_PATH, buildType, targetDevice);
379
+ await buildApp(androidConfig, buildOptimisation, targetDevice.id, gradleOptions);
380
+ // Debug builds load http://localhost:<port>; on the device that
381
+ // is the device itself. Forward every port the app uses to this
382
+ // machine, after the install and before the launch, every run.
383
+ const packageName = `${applicationId}.debug`;
384
+ const ports = devServerPorts(resolved.config, {
385
+ hmrPort: await readHmrPort({
386
+ cwd: appRoot
387
+ })
388
+ });
389
+ reachDevServer(adbRunner(ADB_PATH), targetDevice.id, packageName, ports);
390
+ progress.log(`Forwarded ports ${ports.join(", ")} to this machine (adb reverse)`, "info");
391
+ launchApp(ADB_PATH, targetDevice.id, packageName);
180
392
  progress.complete("build");
181
- movedApkPath = await moveApkToOutputPath(buildType, bop, androidConfig.appName);
182
393
  }
183
394
 
184
395
  // Aligned key/value rows, matching the Config tree the setup
185
396
  // commands print. printTreeContent draws the branch glyphs.
186
397
  const deviceName = targetDevice?.type === "physical" ? targetDevice.model : `${androidConfig.emulatorName} emulator`;
187
- const summaryItems = [{
398
+ const summaryItems = release ? [] : [{
188
399
  text: `device ${deviceName}`,
189
400
  color: "gray"
190
401
  }];
191
- if (movedApkPath) {
402
+ if (artifactPath) {
192
403
  summaryItems.push({
193
- text: `artifact ${displayPath(movedApkPath, process.cwd())}`,
404
+ text: `artifact ${displayPath(artifactPath, appRoot)}`,
194
405
  color: "gray"
195
406
  });
196
407
  }
197
- if (buildType === "release") {
408
+ if (certificatePath) {
198
409
  summaryItems.push({
199
- text: `output Signed AAB in build-output/`,
410
+ text: `cert ${displayPath(certificatePath, appRoot)}`,
200
411
  color: "gray"
201
412
  });
202
413
  }
203
414
  summaryItems.push({
204
- text: `build ${buildType}${buildOptimisation ? ", optimised" : ""}`,
415
+ text: `version ${version.versionName || DEFAULT_VERSION_NAME} (${version.buildNumber})`,
416
+ color: "gray"
417
+ });
418
+ summaryItems.push({
419
+ text: `build ${release ? "release" : "debug"}${buildOptimisation ? ", optimised" : ""}`,
205
420
  color: "gray"
206
421
  });
207
422
  progress.printTreeContent("Build", summaryItems);
208
- progress.summary(`Installed on ${deviceName}`, "Run catalyst start to serve the app");
423
+ if (release) {
424
+ progress.summary("Built the signed AAB", "Upload it to Google Play");
425
+ } else {
426
+ progress.summary(`Running on ${deviceName}`, "Edit your app; the dev server reloads it");
427
+ }
209
428
  } catch (error) {
210
429
  // Mark the step, then show what actually broke. The old handler
211
430
  // printed a generic "check your SDK" checklist here, which is
@@ -218,13 +437,19 @@ function createAndroidBuild(config) {
218
437
  code: "ANDROID-000"
219
438
  }));
220
439
  throw error;
440
+ } finally {
441
+ // The unpacked CI credentials and the resolved config are temp files.
442
+ signing?.cleanup();
443
+ resolved?.cleanup();
221
444
  }
222
445
  }
223
446
  async function buildAndroidForTesting() {
224
447
  try {
225
448
  const {
226
- WEBVIEW_CONFIG: wvConfig
449
+ WEBVIEW_CONFIG: wvConfig,
450
+ cleanup
227
451
  } = await initializeConfig();
452
+ cleanup();
228
453
  const androidConfig = wvConfig.android;
229
454
  const buildOptimisation = !!androidConfig.buildOptimisation || false;
230
455
  await copyBuildAssets(androidConfig, buildOptimisation);
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.releaseArtifactPaths = releaseArtifactPaths;
7
+ exports.releaseOutputDir = releaseOutputDir;
8
+ var _path = _interopRequireDefault(require("path"));
9
+ var _releaseOutput = require("../releaseOutput.js");
10
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ /**
12
+ * Where a release puts its files: build-output/android/<version>-<build>/
13
+ * in the app, or <android.outputPath>/<version>-<build>/ when the config
14
+ * sets an output path. One folder per build, so nothing is overwritten.
15
+ *
16
+ * @param {object} options
17
+ * @param {string} [options.appRoot]
18
+ * @param {{ outputPath?: string }} [options.androidConfig]
19
+ * @param {{ versionName?: string, buildNumber: number | string }} options.version - resolveNativeConfig().version
20
+ * @returns {string}
21
+ */
22
+ function releaseOutputDir({
23
+ appRoot = process.cwd(),
24
+ androidConfig = {},
25
+ version
26
+ }) {
27
+ const root = androidConfig.outputPath ? _path.default.resolve(appRoot, androidConfig.outputPath) : _path.default.join(appRoot, "build-output", "android");
28
+ return _path.default.join(root, (0, _releaseOutput.releaseFolderName)(version));
29
+ }
30
+
31
+ /**
32
+ * The release files in that folder: the bundle for Play and the upload
33
+ * certificate (PEM) that identifies the key that signed it.
34
+ *
35
+ * @param {string} dir - releaseOutputDir()
36
+ * @param {string} name - the app's name; made file-name safe here
37
+ */
38
+ function releaseArtifactPaths(dir, name) {
39
+ return {
40
+ aab: _path.default.join(dir, `${(0, _releaseOutput.artifactBaseName)(name)}.aab`),
41
+ certificate: _path.default.join(dir, "upload_certificate.pem")
42
+ };
43
+ }