hotcodepush 0.0.0 → 0.1.0

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 (162) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +53 -0
  3. package/dist/commands/app/create.js +49 -0
  4. package/dist/commands/app/delete.js +34 -0
  5. package/dist/commands/app/get.js +33 -0
  6. package/dist/commands/app/list.js +38 -0
  7. package/dist/commands/app/transfer.js +58 -0
  8. package/dist/commands/app/update.js +34 -0
  9. package/dist/commands/audience/get.js +69 -0
  10. package/dist/commands/audit-log/list.js +82 -0
  11. package/dist/commands/binary/create.js +189 -0
  12. package/dist/commands/binary/get.js +56 -0
  13. package/dist/commands/binary/list.js +58 -0
  14. package/dist/commands/bundle/delete.js +35 -0
  15. package/dist/commands/bundle/get.js +63 -0
  16. package/dist/commands/bundle/list.js +66 -0
  17. package/dist/commands/bundle/upload.js +176 -0
  18. package/dist/commands/channel/create.js +59 -0
  19. package/dist/commands/channel/delete.js +68 -0
  20. package/dist/commands/channel/get.js +45 -0
  21. package/dist/commands/channel/list.js +39 -0
  22. package/dist/commands/channel/pause.js +32 -0
  23. package/dist/commands/channel/resume.js +32 -0
  24. package/dist/commands/channel/update.js +40 -0
  25. package/dist/commands/checkout/create.js +25 -0
  26. package/dist/commands/completion.js +36 -0
  27. package/dist/commands/customer-portal-session/create.js +25 -0
  28. package/dist/commands/device/delete.js +29 -0
  29. package/dist/commands/device/get.js +60 -0
  30. package/dist/commands/device/list.js +97 -0
  31. package/dist/commands/device/probe.js +253 -0
  32. package/dist/commands/doctor.js +363 -0
  33. package/dist/commands/fingerprint/diff.js +133 -0
  34. package/dist/commands/fingerprint.js +41 -0
  35. package/dist/commands/init.js +492 -0
  36. package/dist/commands/invitation/accept.js +53 -0
  37. package/dist/commands/invitation/create.js +52 -0
  38. package/dist/commands/invitation/delete.js +60 -0
  39. package/dist/commands/invitation/list.js +40 -0
  40. package/dist/commands/limit/list.js +34 -0
  41. package/dist/commands/login.js +185 -0
  42. package/dist/commands/logout.js +79 -0
  43. package/dist/commands/member/delete.js +35 -0
  44. package/dist/commands/member/get.js +37 -0
  45. package/dist/commands/member/list.js +41 -0
  46. package/dist/commands/member/update.js +53 -0
  47. package/dist/commands/open.js +37 -0
  48. package/dist/commands/organization/create.js +30 -0
  49. package/dist/commands/organization/delete.js +39 -0
  50. package/dist/commands/organization/get.js +38 -0
  51. package/dist/commands/organization/list.js +41 -0
  52. package/dist/commands/organization/update.js +64 -0
  53. package/dist/commands/release/create.js +237 -0
  54. package/dist/commands/release/get.js +81 -0
  55. package/dist/commands/release/list.js +69 -0
  56. package/dist/commands/release/pause.js +36 -0
  57. package/dist/commands/release/resume.js +36 -0
  58. package/dist/commands/release/revoke.js +253 -0
  59. package/dist/commands/release/rollback.js +73 -0
  60. package/dist/commands/release/rollout.js +63 -0
  61. package/dist/commands/release/update.js +82 -0
  62. package/dist/commands/resource-file/write.js +76 -0
  63. package/dist/commands/signing-key/add.js +45 -0
  64. package/dist/commands/signing-key/create.js +84 -0
  65. package/dist/commands/signing-key/delete.js +67 -0
  66. package/dist/commands/signing-key/list.js +38 -0
  67. package/dist/commands/sso-provider/delete.js +32 -0
  68. package/dist/commands/sso-provider/get.js +63 -0
  69. package/dist/commands/sso-provider/set.js +161 -0
  70. package/dist/commands/sso-provider/verify.js +31 -0
  71. package/dist/commands/statistics/get.js +189 -0
  72. package/dist/commands/usage/get.js +31 -0
  73. package/dist/commands/usage/list.js +41 -0
  74. package/dist/commands/webhook/create.js +72 -0
  75. package/dist/commands/webhook/delete.js +32 -0
  76. package/dist/commands/webhook/get.js +34 -0
  77. package/dist/commands/webhook/list.js +40 -0
  78. package/dist/commands/webhook/test.js +32 -0
  79. package/dist/commands/webhook/update.js +56 -0
  80. package/dist/commands/webhook-delivery/list.js +63 -0
  81. package/dist/commands/webhook-delivery/redeliver.js +48 -0
  82. package/dist/commands/whoami.js +24 -0
  83. package/dist/completion/hotcodepush.bash +536 -0
  84. package/dist/completion/hotcodepush.fish +979 -0
  85. package/dist/completion/hotcodepush.zsh +1372 -0
  86. package/dist/config/consts.js +32 -0
  87. package/dist/index.js +5 -0
  88. package/dist/registry.js +83 -0
  89. package/dist/utils/api-client.js +17 -0
  90. package/dist/utils/audience.js +9 -0
  91. package/dist/utils/auth-client.js +35 -0
  92. package/dist/utils/boolean-flag.js +3 -0
  93. package/dist/utils/browser.js +6 -0
  94. package/dist/utils/build-step.js +104 -0
  95. package/dist/utils/bundle-files.js +80 -0
  96. package/dist/utils/bundle-resolution.js +46 -0
  97. package/dist/utils/channel-fields.js +130 -0
  98. package/dist/utils/cli.js +75 -0
  99. package/dist/utils/command-registry.js +79 -0
  100. package/dist/utils/completion/bash.js +48 -0
  101. package/dist/utils/completion/command-tree.js +72 -0
  102. package/dist/utils/completion/fish.js +44 -0
  103. package/dist/utils/completion/posix-case-arms.js +9 -0
  104. package/dist/utils/completion/posix-quote.js +4 -0
  105. package/dist/utils/completion/script-files.js +26 -0
  106. package/dist/utils/completion/zsh.js +72 -0
  107. package/dist/utils/compressed-files.js +31 -0
  108. package/dist/utils/credential.js +15 -0
  109. package/dist/utils/device-resolution.js +17 -0
  110. package/dist/utils/duration.js +16 -0
  111. package/dist/utils/environment.js +22 -0
  112. package/dist/utils/error-mapping.js +79 -0
  113. package/dist/utils/errors.js +266 -0
  114. package/dist/utils/fingerprint.js +76 -0
  115. package/dist/utils/flag-name.js +4 -0
  116. package/dist/utils/fleet.js +9 -0
  117. package/dist/utils/framework.js +74 -0
  118. package/dist/utils/frameworks/binary-create-step.js +70 -0
  119. package/dist/utils/frameworks/capacitor.js +150 -0
  120. package/dist/utils/frameworks/cordova.js +110 -0
  121. package/dist/utils/frameworks/expo.js +152 -0
  122. package/dist/utils/frameworks/index.js +13 -0
  123. package/dist/utils/frameworks/native-glue.js +10 -0
  124. package/dist/utils/frameworks/native-projects.js +27 -0
  125. package/dist/utils/frameworks/react-native-build.js +182 -0
  126. package/dist/utils/frameworks/react-native.js +127 -0
  127. package/dist/utils/frameworks/sdk-package.js +63 -0
  128. package/dist/utils/git-provenance.js +71 -0
  129. package/dist/utils/global-options.js +34 -0
  130. package/dist/utils/hosts.js +53 -0
  131. package/dist/utils/init-steps.js +51 -0
  132. package/dist/utils/json-file.js +28 -0
  133. package/dist/utils/member-resolution.js +49 -0
  134. package/dist/utils/native-project-edit.js +41 -0
  135. package/dist/utils/nullable-field.js +10 -0
  136. package/dist/utils/outcome.js +24 -0
  137. package/dist/utils/output.js +37 -0
  138. package/dist/utils/pack.js +20 -0
  139. package/dist/utils/package-json.js +5 -0
  140. package/dist/utils/package-manager.js +50 -0
  141. package/dist/utils/pagination.js +29 -0
  142. package/dist/utils/progress.js +23 -0
  143. package/dist/utils/progression.js +119 -0
  144. package/dist/utils/project-config.js +61 -0
  145. package/dist/utils/prompts.js +53 -0
  146. package/dist/utils/react-native-project.js +119 -0
  147. package/dist/utils/release-conditions.js +80 -0
  148. package/dist/utils/release-output.js +13 -0
  149. package/dist/utils/release-resolution.js +83 -0
  150. package/dist/utils/resource-file.js +81 -0
  151. package/dist/utils/resource-resolution.js +107 -0
  152. package/dist/utils/signing-private-key.js +105 -0
  153. package/dist/utils/spending-cap.js +16 -0
  154. package/dist/utils/telemetry.js +171 -0
  155. package/dist/utils/token-store.js +63 -0
  156. package/dist/utils/update-notice.js +67 -0
  157. package/dist/utils/upload.js +148 -0
  158. package/dist/utils/usage-month.js +14 -0
  159. package/dist/utils/user-config.js +46 -0
  160. package/dist/utils/webhook-resolution.js +47 -0
  161. package/dist/utils/xcode-project.js +181 -0
  162. package/package.json +68 -7
@@ -0,0 +1,56 @@
1
+ import { z } from 'zod';
2
+ import { defineCommand } from 'zodline';
3
+ import { createApiClient } from '../../utils/api-client.js';
4
+ import { isInteractive } from '../../utils/environment.js';
5
+ import { MissingParameterError } from '../../utils/errors.js';
6
+ import { resolveBinaryBucket, UNSEEN_IN_FLEET_WINDOW_TEXT, } from '../../utils/fleet.js';
7
+ import { defineCommandOptions } from '../../utils/global-options.js';
8
+ import { printDetails, printJson } from '../../utils/output.js';
9
+ import { fetchAllPages } from '../../utils/pagination.js';
10
+ import { readProjectConfig } from '../../utils/project-config.js';
11
+ import { promptSelect } from '../../utils/prompts.js';
12
+ import { fetchAppId } from '../../utils/resource-resolution.js';
13
+ export default defineCommand({
14
+ description: 'Print a binary, a store build whose build created it, with the bundle it ships and the devices running it.',
15
+ examples: [
16
+ 'hotcodepush binary get --binary 6ba7b810-9dad-41d1-80b4-00c04fd430c8',
17
+ 'hotcodepush binary get --binary 6ba7b810-9dad-41d1-80b4-00c04fd430c8 --json',
18
+ ],
19
+ options: defineCommandOptions({
20
+ binary: z.string().optional().describe('The binary, by id.'),
21
+ }),
22
+ action: async (options) => {
23
+ const hotCodePush = createApiClient();
24
+ const appId = await fetchAppId(hotCodePush, options, readProjectConfig(options.config));
25
+ const fetchedBinary = await hotCodePush.apps.binaries.get({
26
+ appId,
27
+ binaryId: options.binary ?? (await promptBinaryId(hotCodePush, appId, options)),
28
+ });
29
+ if (options.json) {
30
+ printJson(fetchedBinary);
31
+ return;
32
+ }
33
+ const binaryBucket = resolveBinaryBucket(await hotCodePush.apps.statistics.fleet.get({ appId }), fetchedBinary.id);
34
+ printDetails([
35
+ ['ID', fetchedBinary.id],
36
+ ['Platform', fetchedBinary.platform],
37
+ ['Version', fetchedBinary.version],
38
+ ['Build', fetchedBinary.build],
39
+ ['Fingerprint', fetchedBinary.fingerprint ?? 'none'],
40
+ ['Bundle', fetchedBinary.bundleId],
41
+ ['Devices', String(binaryBucket?.deviceCount ?? 0)],
42
+ ['Last seen', binaryBucket?.lastSeenAt ?? UNSEEN_IN_FLEET_WINDOW_TEXT],
43
+ ['Created', fetchedBinary.createdAt],
44
+ ]);
45
+ },
46
+ });
47
+ async function promptBinaryId(hotCodePush, appId, options) {
48
+ if (!isInteractive(options)) {
49
+ throw new MissingParameterError('--binary');
50
+ }
51
+ const binaries = await fetchAllPages(page => hotCodePush.apps.binaries.list({ appId, ...page }));
52
+ return promptSelect('--binary', 'Which store build?', binaries.map(({ build, id, platform, version }) => ({
53
+ label: `${platform} ${version} (${build})`,
54
+ value: id,
55
+ })), options);
56
+ }
@@ -0,0 +1,58 @@
1
+ import { defineCommand } from 'zodline';
2
+ import { createApiClient } from '../../utils/api-client.js';
3
+ import { resolveBinaryBucket, UNSEEN_IN_FLEET_WINDOW_TEXT, } from '../../utils/fleet.js';
4
+ import { defineCommandOptions } from '../../utils/global-options.js';
5
+ import { printJson, printTable, resolveDate } from '../../utils/output.js';
6
+ import { paginationShape, resolveNextOffset } from '../../utils/pagination.js';
7
+ import { readProjectConfig } from '../../utils/project-config.js';
8
+ import { fetchAppId } from '../../utils/resource-resolution.js';
9
+ export default defineCommand({
10
+ description: 'List the binaries, the store builds whose build created one, with the bundle each ships and the devices running it.',
11
+ examples: [
12
+ 'hotcodepush binary list',
13
+ 'hotcodepush binary list --app "My App" --json',
14
+ ],
15
+ options: defineCommandOptions(paginationShape),
16
+ action: async (options) => {
17
+ const hotCodePush = createApiClient();
18
+ const appId = await fetchAppId(hotCodePush, options, readProjectConfig(options.config));
19
+ const listedBinaries = await hotCodePush.apps.binaries.list({
20
+ appId,
21
+ limit: options.limit,
22
+ offset: options.offset,
23
+ });
24
+ const nextOffset = resolveNextOffset(listedBinaries.length, options);
25
+ if (options.json) {
26
+ printJson({ binaries: listedBinaries, nextOffset });
27
+ return;
28
+ }
29
+ const fleet = await hotCodePush.apps.statistics.fleet.get({ appId });
30
+ printTable({
31
+ emptyText: 'No binaries; a store build creates one.',
32
+ headers: [
33
+ 'ID',
34
+ 'VERSION',
35
+ 'BUILD',
36
+ 'PLATFORM',
37
+ 'BUNDLE',
38
+ 'DEVICES',
39
+ 'LAST SEEN',
40
+ ],
41
+ nextOffset,
42
+ rows: listedBinaries.map(({ build, bundleId, id, platform, version }) => {
43
+ const binaryBucket = resolveBinaryBucket(fleet, id);
44
+ return [
45
+ id,
46
+ version,
47
+ build,
48
+ platform,
49
+ bundleId,
50
+ String(binaryBucket?.deviceCount ?? 0),
51
+ binaryBucket === undefined
52
+ ? UNSEEN_IN_FLEET_WINDOW_TEXT
53
+ : resolveDate(binaryBucket.lastSeenAt),
54
+ ];
55
+ }),
56
+ });
57
+ },
58
+ });
@@ -0,0 +1,35 @@
1
+ import { defineCommand } from 'zodline';
2
+ import { createApiClient } from '../../utils/api-client.js';
3
+ import { bundleOptionShape, fetchBundle, resolveBundleLabel, } from '../../utils/bundle-resolution.js';
4
+ import { defineCommandOptions } from '../../utils/global-options.js';
5
+ import { printJson } from '../../utils/output.js';
6
+ import { readProjectConfig } from '../../utils/project-config.js';
7
+ import { confirmConsequence } from '../../utils/prompts.js';
8
+ import { fetchAppId } from '../../utils/resource-resolution.js';
9
+ export default defineCommand({
10
+ description: 'Delete a bundle now; one a release serves or a store build embeds is refused.',
11
+ examples: [
12
+ 'hotcodepush bundle delete --bundle 17',
13
+ 'hotcodepush bundle delete --bundle 17 --yes --json',
14
+ ],
15
+ options: defineCommandOptions(bundleOptionShape),
16
+ action: async (options) => {
17
+ const hotCodePush = createApiClient();
18
+ const appId = await fetchAppId(hotCodePush, options, readProjectConfig(options.config));
19
+ const fetchedBundle = await fetchBundle(hotCodePush, appId, options);
20
+ const isConfirmed = await confirmConsequence(`deletes bundle ${resolveBundleLabel(fetchedBundle)} and its files now, not by retention`, options);
21
+ if (!isConfirmed) {
22
+ return;
23
+ }
24
+ await hotCodePush.apps.bundles.delete({
25
+ appId,
26
+ bundleId: fetchedBundle.id,
27
+ });
28
+ if (options.json) {
29
+ printJson({ id: fetchedBundle.id, name: fetchedBundle.version });
30
+ }
31
+ else {
32
+ console.log(`Deleted bundle ${resolveBundleLabel(fetchedBundle)} (${fetchedBundle.id}).`);
33
+ }
34
+ },
35
+ });
@@ -0,0 +1,63 @@
1
+ import { defineCommand } from 'zodline';
2
+ import { createApiClient } from '../../utils/api-client.js';
3
+ import { bundleOptionShape, fetchBundle, } from '../../utils/bundle-resolution.js';
4
+ import { defineCommandOptions } from '../../utils/global-options.js';
5
+ import { printDetails, printJson, resolveQuantityText, } from '../../utils/output.js';
6
+ import { fetchAllPages } from '../../utils/pagination.js';
7
+ import { resolveByteText } from '../../utils/progress.js';
8
+ import { readProjectConfig } from '../../utils/project-config.js';
9
+ import { fetchAppId } from '../../utils/resource-resolution.js';
10
+ export default defineCommand({
11
+ description: 'Print a bundle, by its number or id, with its deltas.',
12
+ examples: [
13
+ 'hotcodepush bundle get --bundle 17',
14
+ 'hotcodepush bundle get --bundle 17 --json',
15
+ ],
16
+ options: defineCommandOptions(bundleOptionShape),
17
+ action: async (options) => {
18
+ const hotCodePush = createApiClient();
19
+ const appId = await fetchAppId(hotCodePush, options, readProjectConfig(options.config));
20
+ const fetchedBundle = await fetchBundle(hotCodePush, appId, options);
21
+ const fetchedDeltas = await fetchAllPages(page => hotCodePush.apps.bundles.deltas.list({
22
+ ...page,
23
+ appId,
24
+ bundleId: fetchedBundle.id,
25
+ relations: ['baseBundle'],
26
+ }));
27
+ if (options.json) {
28
+ printJson({ ...fetchedBundle, deltas: fetchedDeltas });
29
+ return;
30
+ }
31
+ printDetails([
32
+ ['ID', fetchedBundle.id],
33
+ [
34
+ 'Number',
35
+ fetchedBundle.number === null ? 'none' : `#${fetchedBundle.number}`,
36
+ ],
37
+ ['Source', fetchedBundle.source],
38
+ ['Version', fetchedBundle.version],
39
+ ['Status', fetchedBundle.status],
40
+ ['Platforms', fetchedBundle.platforms.join(', ')],
41
+ ['Size', resolveByteText(fetchedBundle.sizeBytes)],
42
+ ['Deltas', resolveDeltasText(fetchedDeltas)],
43
+ ['Fingerprint', fetchedBundle.fingerprint ?? 'none'],
44
+ ['Commit', fetchedBundle.gitSha ?? 'unknown'],
45
+ ['Expires', fetchedBundle.expiresAt ?? 'in use'],
46
+ ['Created', fetchedBundle.createdAt],
47
+ ]);
48
+ },
49
+ });
50
+ function resolveDeltasText(deltas) {
51
+ if (deltas.length === 0) {
52
+ return 'none';
53
+ }
54
+ return deltas.map(resolveDeltaText).join('; ');
55
+ }
56
+ function resolveDeltaText({ baseBundle, patchCount, sizeBytes, status, }) {
57
+ const baseBundleNumber = baseBundle?.number ?? null;
58
+ const baseText = baseBundleNumber === null ? 'embedded' : `#${baseBundleNumber}`;
59
+ if (patchCount === null || sizeBytes === null) {
60
+ return `${baseText}: ${status}`;
61
+ }
62
+ return `${baseText}: ${resolveByteText(sizeBytes)}, ${resolveQuantityText(patchCount, 'patch', 'patches')}, ${status}`;
63
+ }
@@ -0,0 +1,66 @@
1
+ import { z } from 'zod';
2
+ import { defineCommand } from 'zodline';
3
+ import { createApiClient } from '../../utils/api-client.js';
4
+ import { defineCommandOptions } from '../../utils/global-options.js';
5
+ import { printJson, printTable, resolveDate } from '../../utils/output.js';
6
+ import { paginationShape, resolveNextOffset } from '../../utils/pagination.js';
7
+ import { resolveByteText } from '../../utils/progress.js';
8
+ import { readProjectConfig } from '../../utils/project-config.js';
9
+ import { fetchAppId } from '../../utils/resource-resolution.js';
10
+ const BUNDLE_SOURCES = ['all', 'binary', 'upload'];
11
+ export default defineCommand({
12
+ description: "List an app's bundles, newest first: the uploaded ones by default, the ones binaries ship with --source binary, both with --source all.",
13
+ examples: [
14
+ 'hotcodepush bundle list',
15
+ 'hotcodepush bundle list --source all --bundle-version 1.4.2 --json',
16
+ ],
17
+ options: defineCommandOptions({
18
+ ...paginationShape,
19
+ bundleVersion: z
20
+ .string()
21
+ .optional()
22
+ .describe('Only the bundles carrying this version label.'),
23
+ source: z
24
+ .enum(BUNDLE_SOURCES)
25
+ .optional()
26
+ .describe('upload, the bundles a release can carry, by default; binary, the ones binaries ship, which have no number; or all.'),
27
+ }),
28
+ action: async (options) => {
29
+ const hotCodePush = createApiClient();
30
+ const source = options.source ?? 'upload';
31
+ const listedBundles = await hotCodePush.apps.bundles.list({
32
+ appId: await fetchAppId(hotCodePush, options, readProjectConfig(options.config)),
33
+ limit: options.limit,
34
+ offset: options.offset,
35
+ source: source === 'all' ? undefined : source,
36
+ version: options.bundleVersion,
37
+ });
38
+ const nextOffset = resolveNextOffset(listedBundles.length, options);
39
+ if (options.json) {
40
+ printJson({ bundles: listedBundles, nextOffset });
41
+ return;
42
+ }
43
+ printTable({
44
+ emptyText: 'No bundles.',
45
+ headers: [
46
+ 'NUMBER',
47
+ 'VERSION',
48
+ 'SOURCE',
49
+ 'STATUS',
50
+ 'PLATFORMS',
51
+ 'SIZE',
52
+ 'CREATED',
53
+ ],
54
+ nextOffset,
55
+ rows: listedBundles.map(({ createdAt, number, platforms, sizeBytes, source, status, version, }) => [
56
+ number === null ? '' : `#${number}`,
57
+ version,
58
+ source,
59
+ status,
60
+ platforms.join(','),
61
+ resolveByteText(sizeBytes),
62
+ resolveDate(createdAt),
63
+ ]),
64
+ });
65
+ },
66
+ });
@@ -0,0 +1,176 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { z } from 'zod';
4
+ import { defineCommand } from 'zodline';
5
+ import { createApiClient } from '../../utils/api-client.js';
6
+ import { resolveBundleLabel } from '../../utils/bundle-resolution.js';
7
+ import { withTemporaryDirectory } from '../../utils/compressed-files.js';
8
+ import { SigningKeyUnavailableError } from '../../utils/errors.js';
9
+ import { readFingerprint } from '../../utils/fingerprint.js';
10
+ import { detectFramework, resolveInputDirectoryPath, } from '../../utils/framework.js';
11
+ import { resolveFrameworkModule } from '../../utils/frameworks/index.js';
12
+ import { resolveGitProvenance } from '../../utils/git-provenance.js';
13
+ import { defineCommandOptions } from '../../utils/global-options.js';
14
+ import { printJson } from '../../utils/output.js';
15
+ import { readPackageJson } from '../../utils/package-json.js';
16
+ import { createReporter, resolveByteText } from '../../utils/progress.js';
17
+ import { locateProjectConfig } from '../../utils/project-config.js';
18
+ import { promptText } from '../../utils/prompts.js';
19
+ import { fetchAppId } from '../../utils/resource-resolution.js';
20
+ import { readSigningKeyPair } from '../../utils/signing-private-key.js';
21
+ import { uploadBundle } from '../../utils/upload.js';
22
+ const FINGERPRINT_UNKNOWN_TEXT = 'No binary of the app carries this fingerprint.';
23
+ const PLATFORMS = ['android', 'ios'];
24
+ // Validated in the schema, so a wrong platform is reported under the flag name.
25
+ const platformListSchema = z
26
+ .string()
27
+ .transform(value => value.split(',').map(platform => platform.trim()))
28
+ .pipe(z.array(z.enum(PLATFORMS)).min(1));
29
+ export const bundleUploadOptionShape = {
30
+ bundleVersion: z
31
+ .string()
32
+ .optional()
33
+ .describe("The bundle's version label; the project's package.json version by default."),
34
+ gitMessage: z
35
+ .string()
36
+ .optional()
37
+ .describe('The commit message subject to record, over the detected one.'),
38
+ gitRef: z
39
+ .string()
40
+ .optional()
41
+ .describe('The branch or tag to record, over the detected one.'),
42
+ gitSha: z
43
+ .string()
44
+ .optional()
45
+ .describe('The commit to record, over the detected one.'),
46
+ noGit: z
47
+ .boolean()
48
+ .optional()
49
+ .describe('Detect no git provenance; the typed --git-* flags still apply.'),
50
+ path: z
51
+ .string()
52
+ .optional()
53
+ .describe("The build to upload, the framework's build output by default: on React Native and Expo one platform's prepared bundle, Capacitor's webDir or Cordova's www."),
54
+ platform: platformListSchema
55
+ .optional()
56
+ .describe('The platforms the bundle serves, ios,android by default.'),
57
+ privateKeyPath: z
58
+ .string()
59
+ .optional()
60
+ .describe('The private key file to sign with, as signing-key create wrote it; HOTCODEPUSH_SIGNING_KEY holds its content in CI.'),
61
+ };
62
+ export default defineCommand({
63
+ description: 'Upload a build as a bundle, signed where a key is configured: only the files the app lacks move, then the full pack; nothing is released.',
64
+ examples: [
65
+ 'hotcodepush bundle upload',
66
+ 'hotcodepush bundle upload --path dist --bundle-version 1.4.2 --platform ios --json',
67
+ ],
68
+ options: defineCommandOptions(bundleUploadOptionShape),
69
+ action: async (options) => {
70
+ const hotCodePush = createApiClient();
71
+ const uploadedBundles = await withTemporaryDirectory(async (packagingDirectoryPath) => {
72
+ const { bundleUploads } = await resolveUploadBundleOptions(hotCodePush, options, packagingDirectoryPath);
73
+ const bundles = [];
74
+ for (const uploadBundleOptions of bundleUploads) {
75
+ bundles.push(await uploadBundle(hotCodePush, uploadBundleOptions));
76
+ }
77
+ // All platform bundles share the project fingerprint, so one check covers them.
78
+ const [sharedOptions] = bundleUploads;
79
+ if (sharedOptions !== undefined &&
80
+ (await fetchIsFingerprintUnknown(hotCodePush, sharedOptions))) {
81
+ process.stderr.write(`Warning: ${FINGERPRINT_UNKNOWN_TEXT}\n`);
82
+ }
83
+ return bundles;
84
+ });
85
+ printUploadedBundles(uploadedBundles, options.json);
86
+ },
87
+ });
88
+ // An app without any binary stays silent, since its first store build is still to come.
89
+ async function fetchIsFingerprintUnknown(hotCodePush, { appId, fingerprint }) {
90
+ const { count: fingerprintBinaryCount } = await hotCodePush.apps.binaries.count({ appId, fingerprint });
91
+ if (fingerprintBinaryCount > 0) {
92
+ return false;
93
+ }
94
+ const { count: binaryCount } = await hotCodePush.apps.binaries.count({
95
+ appId,
96
+ });
97
+ return binaryCount > 0;
98
+ }
99
+ // `--json` always prints an array, as `release create` prints its releases.
100
+ function printUploadedBundles(uploadedBundles, isJson) {
101
+ if (isJson) {
102
+ printJson(uploadedBundles.map(resolveUploadedBundleJson));
103
+ }
104
+ else {
105
+ uploadedBundles.forEach(printUploadedBundle);
106
+ }
107
+ }
108
+ function resolveUploadedBundleJson({ bundle, upload }) {
109
+ return { ...bundle, upload };
110
+ }
111
+ function printUploadedBundle({ bundle, upload }) {
112
+ console.log(`Uploaded bundle ${resolveBundleLabel(bundle)} (${bundle.id}): ${upload.fileCount} files moved, ${resolveByteText(upload.sizeBytes)}.`);
113
+ }
114
+ // Nothing is uploaded here, so `release create` can resolve before it confirms.
115
+ // Its dry run resolves without the private key.
116
+ export async function resolveUploadBundleOptions(hotCodePush, options, packagingDirectoryPath, { isDryRun = false } = {}) {
117
+ const { directoryPath, projectConfig } = locateProjectConfig(options.config);
118
+ const framework = resolveFrameworkModule(detectFramework(directoryPath));
119
+ const appId = await fetchAppId(hotCodePush, options, projectConfig);
120
+ const sharedOptions = {
121
+ appId,
122
+ bundleVersion: await resolveBundleVersion(options, directoryPath),
123
+ fingerprint: await readFingerprint(directoryPath, projectConfig?.extraFingerprintPaths ?? []),
124
+ gitProvenance: await resolveGitProvenance(directoryPath, options),
125
+ nativeGluePaths: framework.nativeGluePaths,
126
+ reporter: createReporter(options),
127
+ signingPrivateKey: await readSigningPrivateKey(appId, projectConfig, options.privateKeyPath, isDryRun),
128
+ };
129
+ const packagedBundles = framework.packageBundles === undefined
130
+ ? [
131
+ {
132
+ directoryPath: await resolveInputDirectoryPath(options, '--path', directoryPath, framework),
133
+ platforms: options.platform ?? PLATFORMS,
134
+ },
135
+ ]
136
+ : await framework.packageBundles({
137
+ packagingDirectoryPath,
138
+ path: options.path,
139
+ platforms: options.platform,
140
+ projectDirectoryPath: directoryPath,
141
+ });
142
+ return {
143
+ appId,
144
+ bundleUploads: packagedBundles.map(packagedBundle => ({
145
+ ...sharedOptions,
146
+ ...packagedBundle,
147
+ })),
148
+ };
149
+ }
150
+ // Signing is off when `hotcodepush.json` lists no public key or names another app than `--app` meant.
151
+ // A dry run signs nothing, so a given key is checked but none is required.
152
+ async function readSigningPrivateKey(appId, projectConfig, privateKeyPath, isDryRun) {
153
+ const publicKeys = projectConfig?.appId === appId ? (projectConfig.publicKeys ?? []) : [];
154
+ try {
155
+ return ((await readSigningKeyPair(publicKeys, privateKeyPath))?.privateKey ?? null);
156
+ }
157
+ catch (error) {
158
+ if (isDryRun && error instanceof SigningKeyUnavailableError) {
159
+ return null;
160
+ }
161
+ throw error;
162
+ }
163
+ }
164
+ // The package.json version is the label a team already keeps.
165
+ async function resolveBundleVersion(options, directoryPath) {
166
+ if (options.bundleVersion !== undefined) {
167
+ return options.bundleVersion;
168
+ }
169
+ if (existsSync(join(directoryPath, 'package.json'))) {
170
+ const { version } = readPackageJson(directoryPath);
171
+ if (version) {
172
+ return version;
173
+ }
174
+ }
175
+ return promptText('--bundle-version', 'Which version label does the bundle carry?', options);
176
+ }
@@ -0,0 +1,59 @@
1
+ import { HotCodePushError } from '@hotcodepush/node';
2
+ import { z } from 'zod';
3
+ import { defineCommand } from 'zodline';
4
+ import { createApiClient } from '../../utils/api-client.js';
5
+ import { channelFieldsShape, resolveChannelFields, } from '../../utils/channel-fields.js';
6
+ import { defineCommandOptions } from '../../utils/global-options.js';
7
+ import { printJson } from '../../utils/output.js';
8
+ import { readProjectConfig } from '../../utils/project-config.js';
9
+ import { promptText } from '../../utils/prompts.js';
10
+ import { fetchAppId, fetchChannels, resolveNamedResource, } from '../../utils/resource-resolution.js';
11
+ export default defineCommand({
12
+ description: 'Create a channel in an app, with an expiry for a preview channel and its auto-pause policy.',
13
+ examples: [
14
+ 'hotcodepush channel create --name staging --protected',
15
+ 'hotcodepush channel create --name pr-42 --expires-in 14d --if-not-exists --json',
16
+ ],
17
+ options: defineCommandOptions({
18
+ ...channelFieldsShape,
19
+ ifNotExists: z
20
+ .boolean()
21
+ .optional()
22
+ .describe('Return the channel of that name when it exists instead of failing, for re-run pipelines.'),
23
+ name: z
24
+ .string()
25
+ .optional()
26
+ .describe('The name: letters, digits, - and _, one to 64 characters, unique in the app.'),
27
+ }),
28
+ action: async (options) => {
29
+ const hotCodePush = createApiClient();
30
+ const appId = await fetchAppId(hotCodePush, options, readProjectConfig(options.config));
31
+ const name = options.name ??
32
+ (await promptText('--name', 'What is the channel called?', options));
33
+ try {
34
+ const createdChannel = await hotCodePush.apps.channels.create({
35
+ ...resolveChannelFields(options),
36
+ appId,
37
+ name,
38
+ });
39
+ printChannel(createdChannel, 'Created channel', options.json);
40
+ }
41
+ catch (error) {
42
+ if (!options.ifNotExists || !isChannelNameTaken(error)) {
43
+ throw error;
44
+ }
45
+ printChannel(resolveNamedResource('channel', name, await fetchChannels(hotCodePush, appId)), 'Found the existing channel', options.json);
46
+ }
47
+ },
48
+ });
49
+ function isChannelNameTaken(error) {
50
+ return (error instanceof HotCodePushError && error.code === 'channel_name_taken');
51
+ }
52
+ function printChannel(channel, sentencePrefix, isJson) {
53
+ if (isJson) {
54
+ printJson(channel);
55
+ }
56
+ else {
57
+ console.log(`${sentencePrefix} ${channel.name} (${channel.id}).`);
58
+ }
59
+ }
@@ -0,0 +1,68 @@
1
+ import { HotCodePushError } from '@hotcodepush/node';
2
+ import { z } from 'zod';
3
+ import { defineCommand } from 'zodline';
4
+ import { createApiClient } from '../../utils/api-client.js';
5
+ import { UnknownNameError } from '../../utils/errors.js';
6
+ import { defineCommandOptions } from '../../utils/global-options.js';
7
+ import { printJson } from '../../utils/output.js';
8
+ import { ID_SCHEMA } from '../../utils/project-config.js';
9
+ import { confirmConsequence } from '../../utils/prompts.js';
10
+ import { channelOptionShape, fetchChannel, } from '../../utils/resource-resolution.js';
11
+ export default defineCommand({
12
+ description: 'Delete a channel and its releases; devices on it keep what they have.',
13
+ examples: [
14
+ 'hotcodepush channel delete --channel staging',
15
+ 'hotcodepush channel delete --channel pr-42 --ignore-not-found --yes --json',
16
+ ],
17
+ options: defineCommandOptions({
18
+ ...channelOptionShape,
19
+ ignoreNotFound: z
20
+ .boolean()
21
+ .optional()
22
+ .describe('Succeed when the channel does not exist, for re-run pipelines.'),
23
+ }),
24
+ action: async (options) => {
25
+ const hotCodePush = createApiClient();
26
+ try {
27
+ await deleteChannel(hotCodePush, options);
28
+ }
29
+ catch (error) {
30
+ if (!options.ignoreNotFound || !isChannelGone(error)) {
31
+ throw error;
32
+ }
33
+ if (options.json) {
34
+ printJson(resolveAbsentChannel(options.channel));
35
+ }
36
+ else {
37
+ console.log('The channel does not exist; nothing to delete.');
38
+ }
39
+ }
40
+ },
41
+ });
42
+ async function deleteChannel(hotCodePush, options) {
43
+ const fetchedChannel = await fetchChannel(hotCodePush, options);
44
+ const isConfirmed = await confirmConsequence(`deletes channel ${fetchedChannel.name} and its releases: devices on it keep what they have`, options);
45
+ if (!isConfirmed) {
46
+ return;
47
+ }
48
+ await hotCodePush.apps.channels.delete({
49
+ appId: fetchedChannel.appId,
50
+ channelId: fetchedChannel.id,
51
+ });
52
+ if (options.json) {
53
+ printJson({ id: fetchedChannel.id, name: fetchedChannel.name });
54
+ }
55
+ else {
56
+ console.log(`Deleted channel ${fetchedChannel.name} (${fetchedChannel.id}).`);
57
+ }
58
+ }
59
+ // A deleted app has no channels either.
60
+ function isChannelGone(error) {
61
+ return (error instanceof UnknownNameError ||
62
+ (error instanceof HotCodePushError && error.code === 'not_found'));
63
+ }
64
+ function resolveAbsentChannel(channel) {
65
+ return channel !== undefined && ID_SCHEMA.safeParse(channel).success
66
+ ? { id: channel, name: null }
67
+ : { id: null, name: channel ?? null };
68
+ }
@@ -0,0 +1,45 @@
1
+ import { defineCommand } from 'zodline';
2
+ import { createApiClient } from '../../utils/api-client.js';
3
+ import { resolveEffectiveFailurePolicy, resolveFailurePolicyDetails, } from '../../utils/channel-fields.js';
4
+ import { resolveReleaseDeviceCount } from '../../utils/fleet.js';
5
+ import { defineCommandOptions } from '../../utils/global-options.js';
6
+ import { printDetails, printJson } from '../../utils/output.js';
7
+ import { channelOptionShape, fetchChannel, } from '../../utils/resource-resolution.js';
8
+ export default defineCommand({
9
+ description: "Print a channel with its device counts, hotcodepush.json's by default.",
10
+ examples: [
11
+ 'hotcodepush channel get',
12
+ 'hotcodepush channel get --channel staging --json',
13
+ ],
14
+ options: defineCommandOptions(channelOptionShape),
15
+ action: async (options) => {
16
+ const hotCodePush = createApiClient();
17
+ const fetchedChannel = await fetchChannel(hotCodePush, options);
18
+ if (options.json) {
19
+ printJson(fetchedChannel);
20
+ return;
21
+ }
22
+ const fleet = await hotCodePush.apps.statistics.fleet.get({
23
+ appId: fetchedChannel.appId,
24
+ channelId: fetchedChannel.id,
25
+ });
26
+ printDetails([
27
+ ['ID', fetchedChannel.id],
28
+ ['Name', fetchedChannel.name],
29
+ [
30
+ 'Status',
31
+ fetchedChannel.pausedAt === null
32
+ ? 'running'
33
+ : `paused since ${fetchedChannel.pausedAt}`,
34
+ ],
35
+ ['Protected', fetchedChannel.isProtected ? 'yes' : 'no'],
36
+ ['Discoverable', fetchedChannel.isDiscoverable ? 'yes' : 'no'],
37
+ ['Expires', fetchedChannel.expiresAt ?? 'never'],
38
+ ...resolveFailurePolicyDetails(resolveEffectiveFailurePolicy(fetchedChannel)),
39
+ ['Active devices', String(fleet.deviceCount)],
40
+ ['Current devices', String(fleet.currentDeviceCount)],
41
+ ['Embedded devices', String(resolveReleaseDeviceCount(fleet, null))],
42
+ ['Created', fetchedChannel.createdAt],
43
+ ]);
44
+ },
45
+ });
@@ -0,0 +1,39 @@
1
+ import { defineCommand } from 'zodline';
2
+ import { createApiClient } from '../../utils/api-client.js';
3
+ import { defineCommandOptions } from '../../utils/global-options.js';
4
+ import { printJson, printTable, resolveDate } from '../../utils/output.js';
5
+ import { paginationShape, resolveNextOffset } from '../../utils/pagination.js';
6
+ import { readProjectConfig } from '../../utils/project-config.js';
7
+ import { fetchAppId } from '../../utils/resource-resolution.js';
8
+ export default defineCommand({
9
+ description: "List an app's channels, newest first.",
10
+ examples: [
11
+ 'hotcodepush channel list',
12
+ 'hotcodepush channel list --app "My App" --json',
13
+ ],
14
+ options: defineCommandOptions(paginationShape),
15
+ action: async (options) => {
16
+ const hotCodePush = createApiClient();
17
+ const listedChannels = await hotCodePush.apps.channels.list({
18
+ appId: await fetchAppId(hotCodePush, options, readProjectConfig(options.config)),
19
+ limit: options.limit,
20
+ offset: options.offset,
21
+ });
22
+ const nextOffset = resolveNextOffset(listedChannels.length, options);
23
+ if (options.json) {
24
+ printJson({ channels: listedChannels, nextOffset });
25
+ return;
26
+ }
27
+ printTable({
28
+ emptyText: 'No channels.',
29
+ headers: ['ID', 'NAME', 'STATUS', 'CREATED'],
30
+ nextOffset,
31
+ rows: listedChannels.map(({ createdAt, id, name, pausedAt }) => [
32
+ id,
33
+ name,
34
+ pausedAt === null ? 'running' : 'paused',
35
+ resolveDate(createdAt),
36
+ ]),
37
+ });
38
+ },
39
+ });