hotcodepush 0.0.1 → 0.2.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 (165) hide show
  1. package/README.md +3 -3
  2. package/dist/commands/app/create.js +49 -0
  3. package/dist/commands/app/delete.js +34 -0
  4. package/dist/commands/app/get.js +33 -0
  5. package/dist/commands/app/list.js +38 -0
  6. package/dist/commands/app/transfer.js +58 -0
  7. package/dist/commands/app/update.js +34 -0
  8. package/dist/commands/audience/get.js +69 -0
  9. package/dist/commands/audit-log/list.js +82 -0
  10. package/dist/commands/binary/create.js +191 -0
  11. package/dist/commands/binary/get.js +56 -0
  12. package/dist/commands/binary/list.js +58 -0
  13. package/dist/commands/bundle/delete.js +35 -0
  14. package/dist/commands/bundle/get.js +63 -0
  15. package/dist/commands/bundle/list.js +66 -0
  16. package/dist/commands/bundle/upload.js +177 -0
  17. package/dist/commands/channel/create.js +59 -0
  18. package/dist/commands/channel/delete.js +68 -0
  19. package/dist/commands/channel/get.js +45 -0
  20. package/dist/commands/channel/list.js +39 -0
  21. package/dist/commands/channel/pause.js +32 -0
  22. package/dist/commands/channel/resume.js +32 -0
  23. package/dist/commands/channel/update.js +40 -0
  24. package/dist/commands/checkout/create.js +25 -0
  25. package/dist/commands/completion.js +37 -0
  26. package/dist/commands/customer-portal-session/create.js +25 -0
  27. package/dist/commands/device/delete.js +29 -0
  28. package/dist/commands/device/get.js +60 -0
  29. package/dist/commands/device/list.js +97 -0
  30. package/dist/commands/device/probe.js +253 -0
  31. package/dist/commands/doctor.js +364 -0
  32. package/dist/commands/fingerprint/diff.js +134 -0
  33. package/dist/commands/fingerprint.js +42 -0
  34. package/dist/commands/init.js +493 -0
  35. package/dist/commands/invitation/accept.js +53 -0
  36. package/dist/commands/invitation/create.js +52 -0
  37. package/dist/commands/invitation/delete.js +60 -0
  38. package/dist/commands/invitation/list.js +40 -0
  39. package/dist/commands/limit/list.js +34 -0
  40. package/dist/commands/login.js +36 -0
  41. package/dist/commands/logout.js +81 -0
  42. package/dist/commands/member/delete.js +35 -0
  43. package/dist/commands/member/get.js +37 -0
  44. package/dist/commands/member/list.js +41 -0
  45. package/dist/commands/member/update.js +53 -0
  46. package/dist/commands/open.js +37 -0
  47. package/dist/commands/organization/create.js +30 -0
  48. package/dist/commands/organization/delete.js +39 -0
  49. package/dist/commands/organization/get.js +38 -0
  50. package/dist/commands/organization/list.js +41 -0
  51. package/dist/commands/organization/update.js +64 -0
  52. package/dist/commands/release/create.js +237 -0
  53. package/dist/commands/release/get.js +81 -0
  54. package/dist/commands/release/list.js +69 -0
  55. package/dist/commands/release/pause.js +36 -0
  56. package/dist/commands/release/resume.js +36 -0
  57. package/dist/commands/release/revoke.js +254 -0
  58. package/dist/commands/release/rollback.js +73 -0
  59. package/dist/commands/release/rollout.js +63 -0
  60. package/dist/commands/release/update.js +82 -0
  61. package/dist/commands/resource-file/write.js +78 -0
  62. package/dist/commands/signing-key/add.js +45 -0
  63. package/dist/commands/signing-key/create.js +84 -0
  64. package/dist/commands/signing-key/delete.js +67 -0
  65. package/dist/commands/signing-key/list.js +38 -0
  66. package/dist/commands/sso-provider/delete.js +32 -0
  67. package/dist/commands/sso-provider/get.js +63 -0
  68. package/dist/commands/sso-provider/set.js +161 -0
  69. package/dist/commands/sso-provider/verify.js +31 -0
  70. package/dist/commands/statistics/get.js +189 -0
  71. package/dist/commands/usage/get.js +31 -0
  72. package/dist/commands/usage/list.js +41 -0
  73. package/dist/commands/webhook/create.js +72 -0
  74. package/dist/commands/webhook/delete.js +32 -0
  75. package/dist/commands/webhook/get.js +34 -0
  76. package/dist/commands/webhook/list.js +40 -0
  77. package/dist/commands/webhook/test.js +32 -0
  78. package/dist/commands/webhook/update.js +56 -0
  79. package/dist/commands/webhook-delivery/list.js +63 -0
  80. package/dist/commands/webhook-delivery/redeliver.js +48 -0
  81. package/dist/commands/whoami.js +24 -0
  82. package/dist/completion/hotcodepush.bash +536 -0
  83. package/dist/completion/hotcodepush.fish +979 -0
  84. package/dist/completion/hotcodepush.zsh +1372 -0
  85. package/dist/config/consts.js +32 -0
  86. package/dist/index.js +5 -0
  87. package/dist/registry.js +83 -0
  88. package/dist/utils/api-client.js +17 -0
  89. package/dist/utils/audience.js +9 -0
  90. package/dist/utils/auth-client.js +35 -0
  91. package/dist/utils/authentication.js +60 -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 +80 -0
  99. package/dist/utils/command-registry.js +79 -0
  100. package/dist/utils/command.js +4 -0
  101. package/dist/utils/completion/bash.js +48 -0
  102. package/dist/utils/completion/command-tree.js +72 -0
  103. package/dist/utils/completion/fish.js +44 -0
  104. package/dist/utils/completion/posix-case-arms.js +9 -0
  105. package/dist/utils/completion/posix-quote.js +4 -0
  106. package/dist/utils/completion/script-files.js +26 -0
  107. package/dist/utils/completion/zsh.js +72 -0
  108. package/dist/utils/compressed-files.js +31 -0
  109. package/dist/utils/credential.js +15 -0
  110. package/dist/utils/device-resolution.js +17 -0
  111. package/dist/utils/diagnostics.js +20 -0
  112. package/dist/utils/duration.js +16 -0
  113. package/dist/utils/environment.js +22 -0
  114. package/dist/utils/error-mapping.js +75 -0
  115. package/dist/utils/errors.js +266 -0
  116. package/dist/utils/fingerprint.js +76 -0
  117. package/dist/utils/flag-name.js +4 -0
  118. package/dist/utils/fleet.js +9 -0
  119. package/dist/utils/framework.js +74 -0
  120. package/dist/utils/frameworks/binary-create-step.js +70 -0
  121. package/dist/utils/frameworks/capacitor.js +150 -0
  122. package/dist/utils/frameworks/cordova.js +110 -0
  123. package/dist/utils/frameworks/expo.js +152 -0
  124. package/dist/utils/frameworks/index.js +13 -0
  125. package/dist/utils/frameworks/native-glue.js +10 -0
  126. package/dist/utils/frameworks/native-projects.js +27 -0
  127. package/dist/utils/frameworks/react-native-build.js +182 -0
  128. package/dist/utils/frameworks/react-native.js +127 -0
  129. package/dist/utils/frameworks/sdk-package.js +63 -0
  130. package/dist/utils/git-provenance.js +71 -0
  131. package/dist/utils/global-options.js +34 -0
  132. package/dist/utils/hosts.js +53 -0
  133. package/dist/utils/init-steps.js +51 -0
  134. package/dist/utils/json-file.js +28 -0
  135. package/dist/utils/login.js +153 -0
  136. package/dist/utils/member-resolution.js +49 -0
  137. package/dist/utils/native-project-edit.js +41 -0
  138. package/dist/utils/nullable-field.js +10 -0
  139. package/dist/utils/outcome.js +24 -0
  140. package/dist/utils/output.js +38 -0
  141. package/dist/utils/pack.js +20 -0
  142. package/dist/utils/package-json.js +5 -0
  143. package/dist/utils/package-manager.js +50 -0
  144. package/dist/utils/pagination.js +29 -0
  145. package/dist/utils/progress.js +23 -0
  146. package/dist/utils/progression.js +119 -0
  147. package/dist/utils/project-config.js +61 -0
  148. package/dist/utils/prompts.js +53 -0
  149. package/dist/utils/react-native-project.js +119 -0
  150. package/dist/utils/release-conditions.js +79 -0
  151. package/dist/utils/release-output.js +13 -0
  152. package/dist/utils/release-resolution.js +83 -0
  153. package/dist/utils/resource-file.js +81 -0
  154. package/dist/utils/resource-resolution.js +107 -0
  155. package/dist/utils/signing-private-key.js +105 -0
  156. package/dist/utils/spending-cap.js +16 -0
  157. package/dist/utils/telemetry.js +172 -0
  158. package/dist/utils/token-store.js +63 -0
  159. package/dist/utils/update-notice.js +68 -0
  160. package/dist/utils/upload.js +148 -0
  161. package/dist/utils/usage-month.js +14 -0
  162. package/dist/utils/user-config.js +54 -0
  163. package/dist/utils/webhook-resolution.js +47 -0
  164. package/dist/utils/xcode-project.js +181 -0
  165. package/package.json +11 -9
@@ -0,0 +1,39 @@
1
+ import { createApiClient } from '../../utils/api-client.js';
2
+ import { defineCommand } from '../../utils/command.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
+ });
@@ -0,0 +1,32 @@
1
+ import { createApiClient } from '../../utils/api-client.js';
2
+ import { defineCommand } from '../../utils/command.js';
3
+ import { defineCommandOptions } from '../../utils/global-options.js';
4
+ import { printJson } from '../../utils/output.js';
5
+ import { confirmConsequence } from '../../utils/prompts.js';
6
+ import { channelOptionShape, fetchChannel, } from '../../utils/resource-resolution.js';
7
+ export default defineCommand({
8
+ description: 'Pause a channel, the kill switch: it serves nothing new until resumed.',
9
+ examples: [
10
+ 'hotcodepush channel pause --channel production',
11
+ 'hotcodepush channel pause --channel production --yes --json',
12
+ ],
13
+ options: defineCommandOptions(channelOptionShape),
14
+ action: async (options) => {
15
+ const hotCodePush = createApiClient();
16
+ const fetchedChannel = await fetchChannel(hotCodePush, options);
17
+ const isConfirmed = await confirmConsequence(`pauses channel ${fetchedChannel.name}: devices keep what they have and get nothing new`, options);
18
+ if (!isConfirmed) {
19
+ return;
20
+ }
21
+ const pausedChannel = await hotCodePush.apps.channels.pause({
22
+ appId: fetchedChannel.appId,
23
+ channelId: fetchedChannel.id,
24
+ });
25
+ if (options.json) {
26
+ printJson(pausedChannel);
27
+ }
28
+ else {
29
+ console.log(`Paused channel ${pausedChannel.name} (${pausedChannel.id}).`);
30
+ }
31
+ },
32
+ });
@@ -0,0 +1,32 @@
1
+ import { createApiClient } from '../../utils/api-client.js';
2
+ import { defineCommand } from '../../utils/command.js';
3
+ import { defineCommandOptions } from '../../utils/global-options.js';
4
+ import { printJson } from '../../utils/output.js';
5
+ import { confirmConsequence } from '../../utils/prompts.js';
6
+ import { channelOptionShape, fetchChannel, } from '../../utils/resource-resolution.js';
7
+ export default defineCommand({
8
+ description: 'Resume a paused channel: it serves its releases again.',
9
+ examples: [
10
+ 'hotcodepush channel resume --channel production',
11
+ 'hotcodepush channel resume --channel production --yes --json',
12
+ ],
13
+ options: defineCommandOptions(channelOptionShape),
14
+ action: async (options) => {
15
+ const hotCodePush = createApiClient();
16
+ const fetchedChannel = await fetchChannel(hotCodePush, options);
17
+ const isConfirmed = await confirmConsequence(`resumes channel ${fetchedChannel.name}: devices receive its releases again`, options);
18
+ if (!isConfirmed) {
19
+ return;
20
+ }
21
+ const resumedChannel = await hotCodePush.apps.channels.resume({
22
+ appId: fetchedChannel.appId,
23
+ channelId: fetchedChannel.id,
24
+ });
25
+ if (options.json) {
26
+ printJson(resumedChannel);
27
+ }
28
+ else {
29
+ console.log(`Resumed channel ${resumedChannel.name} (${resumedChannel.id}).`);
30
+ }
31
+ },
32
+ });
@@ -0,0 +1,40 @@
1
+ import { z } from 'zod';
2
+ import { createApiClient } from '../../utils/api-client.js';
3
+ import { channelFieldsClearShape, channelFieldsShape, resolveChannelFields, } from '../../utils/channel-fields.js';
4
+ import { defineCommand } from '../../utils/command.js';
5
+ import { defineCommandOptions } from '../../utils/global-options.js';
6
+ import { printJson } from '../../utils/output.js';
7
+ import { channelOptionShape, fetchChannel, } from '../../utils/resource-resolution.js';
8
+ export default defineCommand({
9
+ description: 'Rename a channel or change its flags, expiry and auto-pause policy.',
10
+ examples: [
11
+ 'hotcodepush channel update --channel staging --protected',
12
+ 'hotcodepush channel update --channel pr-42 --expires-in 7d --json',
13
+ ],
14
+ options: defineCommandOptions({
15
+ ...channelFieldsClearShape,
16
+ ...channelFieldsShape,
17
+ ...channelOptionShape,
18
+ name: z
19
+ .string()
20
+ .optional()
21
+ .describe("The channel's new name: letters, digits, - and _, one to 64 characters."),
22
+ }),
23
+ action: async (options) => {
24
+ const hotCodePush = createApiClient();
25
+ const channelFields = resolveChannelFields(options);
26
+ const fetchedChannel = await fetchChannel(hotCodePush, options);
27
+ const updatedChannel = await hotCodePush.apps.channels.update({
28
+ ...channelFields,
29
+ appId: fetchedChannel.appId,
30
+ channelId: fetchedChannel.id,
31
+ name: options.name,
32
+ });
33
+ if (options.json) {
34
+ printJson(updatedChannel);
35
+ }
36
+ else {
37
+ console.log(`Updated channel ${updatedChannel.name} (${updatedChannel.id}).`);
38
+ }
39
+ },
40
+ });
@@ -0,0 +1,25 @@
1
+ import { createApiClient } from '../../utils/api-client.js';
2
+ import { defineCommand } from '../../utils/command.js';
3
+ import { defineCommandOptions } from '../../utils/global-options.js';
4
+ import { printJson } from '../../utils/output.js';
5
+ import { fetchOrganizationId } from '../../utils/resource-resolution.js';
6
+ export default defineCommand({
7
+ description: 'Enable billing on a free organization: create the checkout for pay-as-you-go and print its URL to open.',
8
+ examples: [
9
+ 'hotcodepush checkout create',
10
+ 'hotcodepush checkout create --organization Acme --json',
11
+ ],
12
+ options: defineCommandOptions({}),
13
+ action: async (options) => {
14
+ const hotCodePush = createApiClient();
15
+ const createdCheckout = await hotCodePush.organizations.checkouts.create({
16
+ organizationId: await fetchOrganizationId(hotCodePush, options),
17
+ });
18
+ if (options.json) {
19
+ printJson(createdCheckout);
20
+ }
21
+ else {
22
+ console.log(`Open the checkout to enable billing: ${createdCheckout.url}`);
23
+ }
24
+ },
25
+ });
@@ -0,0 +1,37 @@
1
+ import { z } from 'zod';
2
+ import { defineCommand } from '../utils/command.js';
3
+ import { COMPLETION_SHELLS, readCompletionScript, } from '../utils/completion/script-files.js';
4
+ import { InvalidParameterError } from '../utils/errors.js';
5
+ import { defineCommandOptions } from '../utils/global-options.js';
6
+ import { printJson } from '../utils/output.js';
7
+ import { promptSelect } from '../utils/prompts.js';
8
+ // zodline reports an invalid argument as a plain error that would read as `unexpected`.
9
+ // So the action checks the shell itself.
10
+ const ShellSchema = z.enum(COMPLETION_SHELLS);
11
+ export default defineCommand({
12
+ description: 'Print the completion script for <shell>, bash, fish or zsh: eval "$(hotcodepush completion <shell>)" loads it from ~/.bashrc, or from ~/.zshrc after compinit, and fish loads it saved as ~/.config/fish/completions/hotcodepush.fish.',
13
+ authentication: 'optional',
14
+ examples: [
15
+ 'eval "$(hotcodepush completion zsh)"',
16
+ 'hotcodepush completion fish > ~/.config/fish/completions/hotcodepush.fish',
17
+ ],
18
+ args: z.array(z.string()),
19
+ options: defineCommandOptions({}),
20
+ action: async (options, args) => {
21
+ if (args.length > 1) {
22
+ throw new InvalidParameterError(`The completion command takes one shell, <shell>. It does not take ${args.slice(1).join(' ')}.`);
23
+ }
24
+ const shell = args[0] ??
25
+ (await promptSelect('<shell>', 'Which shell is the completion script for?', ShellSchema.options.map(value => ({ label: value, value })), options));
26
+ const parsedShell = ShellSchema.safeParse(shell);
27
+ if (!parsedShell.success) {
28
+ throw new InvalidParameterError(`The shell ${shell} has no completion script.`, 'Pass bash, fish or zsh as <shell>.');
29
+ }
30
+ const script = await readCompletionScript(parsedShell.data);
31
+ if (options.json) {
32
+ printJson({ script });
33
+ return;
34
+ }
35
+ console.log(script);
36
+ },
37
+ });
@@ -0,0 +1,25 @@
1
+ import { createApiClient } from '../../utils/api-client.js';
2
+ import { defineCommand } from '../../utils/command.js';
3
+ import { defineCommandOptions } from '../../utils/global-options.js';
4
+ import { printJson } from '../../utils/output.js';
5
+ import { fetchOrganizationId } from '../../utils/resource-resolution.js';
6
+ export default defineCommand({
7
+ description: "Create a session of the customer portal for an organization's invoices and payment method, and print its URL to open.",
8
+ examples: [
9
+ 'hotcodepush customer-portal-session create',
10
+ 'hotcodepush customer-portal-session create --organization Acme --json',
11
+ ],
12
+ options: defineCommandOptions({}),
13
+ action: async (options) => {
14
+ const hotCodePush = createApiClient();
15
+ const createdSession = await hotCodePush.organizations.customerPortalSessions.create({
16
+ organizationId: await fetchOrganizationId(hotCodePush, options),
17
+ });
18
+ if (options.json) {
19
+ printJson(createdSession);
20
+ }
21
+ else {
22
+ console.log(`Open the customer portal for the invoices and the payment method: ${createdSession.url}`);
23
+ }
24
+ },
25
+ });
@@ -0,0 +1,29 @@
1
+ import { createApiClient } from '../../utils/api-client.js';
2
+ import { defineCommand } from '../../utils/command.js';
3
+ import { deviceOptionShape, resolveDeviceReference, } from '../../utils/device-resolution.js';
4
+ import { defineCommandOptions } from '../../utils/global-options.js';
5
+ import { printJson } from '../../utils/output.js';
6
+ import { confirmConsequence } from '../../utils/prompts.js';
7
+ export default defineCommand({
8
+ description: "Delete a device's record, an erasure request. The device reports again with its next check.",
9
+ examples: [
10
+ 'hotcodepush device delete --device 6b1e9d37-2f5c-4a80-9c46-d8e3a1f7b259',
11
+ 'hotcodepush device delete --device 6b1e9d37-2f5c-4a80-9c46-d8e3a1f7b259 --yes --json',
12
+ ],
13
+ options: defineCommandOptions(deviceOptionShape),
14
+ action: async (options) => {
15
+ const hotCodePush = createApiClient();
16
+ const { appId, deviceId } = await resolveDeviceReference(hotCodePush, options);
17
+ const isConfirmed = await confirmConsequence(`deletes device ${deviceId}'s record for good, the erasure request. The device reports again with its next check`, options);
18
+ if (!isConfirmed) {
19
+ return;
20
+ }
21
+ await hotCodePush.apps.devices.delete({ appId, deviceId });
22
+ if (options.json) {
23
+ printJson({ id: deviceId, name: null });
24
+ }
25
+ else {
26
+ console.log(`Deleted device ${deviceId}.`);
27
+ }
28
+ },
29
+ });
@@ -0,0 +1,60 @@
1
+ import { createApiClient } from '../../utils/api-client.js';
2
+ import { defineCommand } from '../../utils/command.js';
3
+ import { deviceOptionShape, resolveDeviceReference, } from '../../utils/device-resolution.js';
4
+ import { defineCommandOptions } from '../../utils/global-options.js';
5
+ import { printDetails, printJson, printTable, resolveDate, } from '../../utils/output.js';
6
+ import { fetchAllPages } from '../../utils/pagination.js';
7
+ import { resolveBinaryText } from './list.js';
8
+ export default defineCommand({
9
+ description: 'Print what a device runs and reported last, with its outcome per release, newest first.',
10
+ examples: [
11
+ 'hotcodepush device get --device 6b1e9d37-2f5c-4a80-9c46-d8e3a1f7b259',
12
+ 'hotcodepush device get --device 6b1e9d37-2f5c-4a80-9c46-d8e3a1f7b259 --json',
13
+ ],
14
+ options: defineCommandOptions(deviceOptionShape),
15
+ action: async (options) => {
16
+ const hotCodePush = createApiClient();
17
+ const deviceReference = await resolveDeviceReference(hotCodePush, options);
18
+ const fetchedDevice = await hotCodePush.apps.devices.get({
19
+ ...deviceReference,
20
+ relations: ['channel'],
21
+ });
22
+ const fetchedEvents = await fetchAllPages(page => hotCodePush.apps.devices.events.list({ ...deviceReference, ...page }));
23
+ if (options.json) {
24
+ printJson({ ...fetchedDevice, events: fetchedEvents });
25
+ return;
26
+ }
27
+ printDetails([
28
+ ['ID', fetchedDevice.deviceId],
29
+ ['Platform', `${fetchedDevice.platform} ${fetchedDevice.osVersion}`],
30
+ ['Binary', resolveBinaryText(fetchedDevice)],
31
+ ['Fingerprint', fetchedDevice.fingerprint ?? 'none'],
32
+ [
33
+ 'Channel',
34
+ `${fetchedDevice.channel?.name ?? fetchedDevice.channelId ?? 'none'} (${fetchedDevice.channelSource})`,
35
+ ],
36
+ ['Release', fetchedDevice.currentReleaseId ?? 'the embedded bundle'],
37
+ ['SDK', fetchedDevice.sdkVersion],
38
+ [
39
+ 'Attributes',
40
+ Object.entries(fetchedDevice.attributes)
41
+ .map(([key, value]) => `${key}=${value}`)
42
+ .join(', ') || 'none',
43
+ ],
44
+ ['Country', fetchedDevice.country ?? 'unknown'],
45
+ ['Last seen', fetchedDevice.lastSeenAt],
46
+ ['First seen', fetchedDevice.createdAt],
47
+ ]);
48
+ printTable({
49
+ emptyText: 'No outcomes yet: the device has not checked a release.',
50
+ headers: ['RELEASE', 'OUTCOME', 'REASON', 'DATE'],
51
+ nextOffset: null,
52
+ rows: fetchedEvents.map(({ createdAt, reason, releaseId, type }) => [
53
+ releaseId,
54
+ type,
55
+ reason ?? '',
56
+ resolveDate(createdAt),
57
+ ]),
58
+ });
59
+ },
60
+ });
@@ -0,0 +1,97 @@
1
+ import { z } from 'zod';
2
+ import { createApiClient } from '../../utils/api-client.js';
3
+ import { defineCommand } from '../../utils/command.js';
4
+ import { resolveTimeBound } from '../../utils/duration.js';
5
+ import { defineCommandOptions } from '../../utils/global-options.js';
6
+ import { printJson, printTable, resolveDate } from '../../utils/output.js';
7
+ import { paginationShape, resolveNextOffset } from '../../utils/pagination.js';
8
+ import { readProjectConfig } from '../../utils/project-config.js';
9
+ import { fetchAppId, fetchChannels, fetchResourceId, } from '../../utils/resource-resolution.js';
10
+ export default defineCommand({
11
+ description: "List the app's devices, the newest report first, filtered by what they reported. It is for support and QA, never for a pipeline.",
12
+ examples: [
13
+ 'hotcodepush device list --attribute userId=42',
14
+ 'hotcodepush device list --platform ios --binary-version 2.4.1 --last-seen-since 2026-09-01T00:00:00Z --json',
15
+ ],
16
+ options: defineCommandOptions({
17
+ ...paginationShape,
18
+ attribute: z
19
+ .string()
20
+ .optional()
21
+ .describe('An attribute the app set, key=value, such as userId=42.'),
22
+ binaryBuild: z
23
+ .string()
24
+ .optional()
25
+ .describe('The build number the devices run.'),
26
+ binaryVersion: z
27
+ .string()
28
+ .optional()
29
+ .describe('The app version the devices run.'),
30
+ channel: z
31
+ .string()
32
+ .optional()
33
+ .describe('The channel the devices follow, by id or name.'),
34
+ fingerprint: z
35
+ .string()
36
+ .optional()
37
+ .describe('The native fingerprint the devices report.'),
38
+ lastSeenSince: z
39
+ .string()
40
+ .optional()
41
+ .describe('The earliest last report, inclusive: an ISO 8601 timestamp or a duration ago such as 2h or 7d.'),
42
+ lastSeenUntil: z
43
+ .string()
44
+ .optional()
45
+ .describe('The latest last report, inclusive: an ISO 8601 timestamp or a duration ago such as 2h or 7d.'),
46
+ platform: z
47
+ .enum(['android', 'ios'])
48
+ .optional()
49
+ .describe('The platform, ios or android.'),
50
+ sdkVersion: z
51
+ .string()
52
+ .optional()
53
+ .describe('The SDK version the devices run.'),
54
+ }),
55
+ action: async (options) => {
56
+ const hotCodePush = createApiClient();
57
+ const appId = await fetchAppId(hotCodePush, options, readProjectConfig(options.config));
58
+ const listedDevices = await hotCodePush.apps.devices.list({
59
+ appId,
60
+ attribute: options.attribute,
61
+ binaryBuild: options.binaryBuild,
62
+ binaryVersion: options.binaryVersion,
63
+ channelId: options.channel === undefined
64
+ ? undefined
65
+ : await fetchResourceId('channel', options.channel, () => fetchChannels(hotCodePush, appId)),
66
+ fingerprint: options.fingerprint,
67
+ lastSeenSince: resolveTimeBound(options.lastSeenSince),
68
+ lastSeenUntil: resolveTimeBound(options.lastSeenUntil),
69
+ limit: options.limit,
70
+ offset: options.offset,
71
+ platform: options.platform,
72
+ relations: ['channel'],
73
+ sdkVersion: options.sdkVersion,
74
+ });
75
+ const nextOffset = resolveNextOffset(listedDevices.length, options);
76
+ if (options.json) {
77
+ printJson({ devices: listedDevices, nextOffset });
78
+ return;
79
+ }
80
+ printTable({
81
+ emptyText: 'No devices match. A device appears with its first check.',
82
+ headers: ['ID', 'PLATFORM', 'BINARY', 'CHANNEL', 'SDK', 'LAST SEEN'],
83
+ nextOffset,
84
+ rows: listedDevices.map(device => [
85
+ device.deviceId,
86
+ device.platform,
87
+ resolveBinaryText(device),
88
+ device.channel?.name ?? device.channelId ?? 'none',
89
+ device.sdkVersion,
90
+ resolveDate(device.lastSeenAt),
91
+ ]),
92
+ });
93
+ },
94
+ });
95
+ export function resolveBinaryText({ binaryBuild, binaryVersion, }) {
96
+ return `${binaryVersion} (${binaryBuild})`;
97
+ }