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,53 @@
1
+ import { z } from 'zod';
2
+ import { createApiClient } from '../../utils/api-client.js';
3
+ import { defineCommand } from '../../utils/command.js';
4
+ import { isInteractive } from '../../utils/environment.js';
5
+ import { MissingParameterError } from '../../utils/errors.js';
6
+ import { defineCommandOptions } from '../../utils/global-options.js';
7
+ import { printJson } from '../../utils/output.js';
8
+ import { promptSelect, promptText } from '../../utils/prompts.js';
9
+ export default defineCommand({
10
+ description: 'Accept an invitation to an organization with the token from its mail.',
11
+ examples: [
12
+ 'hotcodepush invitation accept',
13
+ 'hotcodepush invitation accept --invitation 4c8e2a6f-1d3b-4e97-a5c0-8f2d6b4a1e93 --token p8Xc2LmQ4rTzN7vB --json',
14
+ ],
15
+ options: defineCommandOptions({
16
+ invitation: z
17
+ .string()
18
+ .optional()
19
+ .describe('The invitation, by the id its mail carries.'),
20
+ token: z
21
+ .string()
22
+ .optional()
23
+ .describe('The token the invitation mail carries.'),
24
+ }),
25
+ action: async (options) => {
26
+ const hotCodePush = createApiClient();
27
+ const invitationId = options.invitation ?? (await promptInvitationId(hotCodePush, options));
28
+ const token = options.token ??
29
+ (await promptText('--token', 'What is the token from the invitation mail?', options));
30
+ const acceptedMember = await hotCodePush.invitations.accept({
31
+ invitationId,
32
+ token,
33
+ });
34
+ if (options.json) {
35
+ printJson(acceptedMember);
36
+ }
37
+ else {
38
+ console.log(`Joined organization ${acceptedMember.organizationId} with role ${acceptedMember.role}.`);
39
+ }
40
+ },
41
+ });
42
+ async function promptInvitationId(hotCodePush, options) {
43
+ if (!isInteractive(options)) {
44
+ throw new MissingParameterError('--invitation');
45
+ }
46
+ const fetchedInvitations = await hotCodePush.invitations.list({
47
+ relations: ['organization'],
48
+ });
49
+ return promptSelect('--invitation', 'Which invitation?', fetchedInvitations.map(({ id, organization, organizationId, role }) => ({
50
+ label: `${organization?.name ?? organizationId} with role ${role}`,
51
+ value: id,
52
+ })), options);
53
+ }
@@ -0,0 +1,52 @@
1
+ import { z } from 'zod';
2
+ import { createApiClient } from '../../utils/api-client.js';
3
+ import { defineCommand } from '../../utils/command.js';
4
+ import { defineCommandOptions } from '../../utils/global-options.js';
5
+ import { printJson } from '../../utils/output.js';
6
+ import { confirmConsequence, promptSelect, promptText, } from '../../utils/prompts.js';
7
+ import { fetchOrganizationId } from '../../utils/resource-resolution.js';
8
+ const INVITATION_ROLES = [
9
+ 'admin',
10
+ 'billing',
11
+ 'member',
12
+ ];
13
+ export default defineCommand({
14
+ description: 'Invite an address to the organization with a role. The invitation mail is valid for two days.',
15
+ examples: [
16
+ 'hotcodepush invitation create --email anna@example.com --role member',
17
+ 'hotcodepush invitation create --organization Acme --email anna@example.com --role admin --yes --json',
18
+ ],
19
+ options: defineCommandOptions({
20
+ email: z.string().optional().describe('The address to invite.'),
21
+ role: z
22
+ .enum(INVITATION_ROLES)
23
+ .optional()
24
+ .describe('The role the invitee gets: admin, billing or member.'),
25
+ }),
26
+ action: async (options) => {
27
+ const hotCodePush = createApiClient();
28
+ const organizationId = await fetchOrganizationId(hotCodePush, options);
29
+ const email = options.email ??
30
+ (await promptText('--email', 'Which address do you invite?', options));
31
+ const role = options.role ??
32
+ (await promptSelect('--role', 'Which role does the invitee get?', INVITATION_ROLES.map(role => ({ label: role, value: role })), options));
33
+ const fetchedOrganization = await hotCodePush.organizations.get({
34
+ organizationId,
35
+ });
36
+ const isConfirmed = await confirmConsequence(`invites ${email} to organization ${fetchedOrganization.name} with role ${role}`, options);
37
+ if (!isConfirmed) {
38
+ return;
39
+ }
40
+ const createdInvitation = await hotCodePush.organizations.invitations.create({
41
+ email,
42
+ organizationId,
43
+ role,
44
+ });
45
+ if (options.json) {
46
+ printJson(createdInvitation);
47
+ }
48
+ else {
49
+ console.log(`Invited ${createdInvitation.email} (${createdInvitation.id}) to organization ${fetchedOrganization.name} with role ${createdInvitation.role}.`);
50
+ }
51
+ },
52
+ });
@@ -0,0 +1,60 @@
1
+ import { z } from 'zod';
2
+ import { createApiClient } from '../../utils/api-client.js';
3
+ import { defineCommand } from '../../utils/command.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 { fetchAllPages } from '../../utils/pagination.js';
8
+ import { confirmConsequence, promptSelect } from '../../utils/prompts.js';
9
+ import { fetchOrganizationId } from '../../utils/resource-resolution.js';
10
+ export default defineCommand({
11
+ description: 'Withdraw a pending invitation. The link in its mail stops working at once.',
12
+ examples: [
13
+ 'hotcodepush invitation delete --invitation anna@example.com',
14
+ 'hotcodepush invitation delete --invitation 4c8e2a6f-1d3b-4e97-a5c0-8f2d6b4a1e93 --yes --json',
15
+ ],
16
+ options: defineCommandOptions({
17
+ invitation: z
18
+ .string()
19
+ .optional()
20
+ .describe('The pending invitation, by id or email.'),
21
+ }),
22
+ action: async (options) => {
23
+ const hotCodePush = createApiClient();
24
+ const organizationId = await fetchOrganizationId(hotCodePush, options);
25
+ // Invitations have no read by id, and only a pending one is unique by its email.
26
+ const pendingInvitations = await fetchAllPages(page => hotCodePush.organizations.invitations.list({
27
+ organizationId,
28
+ status: 'pending',
29
+ ...page,
30
+ }));
31
+ const invitation = resolveInvitation(pendingInvitations, options.invitation ??
32
+ (await promptSelect('--invitation', 'Which invitation?', pendingInvitations.map(({ email, id }) => ({
33
+ label: email,
34
+ value: id,
35
+ })), options)));
36
+ const isConfirmed = await confirmConsequence(`withdraws the invitation to ${invitation.email}: the link in its mail stops working`, options);
37
+ if (!isConfirmed) {
38
+ return;
39
+ }
40
+ await hotCodePush.organizations.invitations.delete({
41
+ invitationId: invitation.id,
42
+ organizationId,
43
+ });
44
+ if (options.json) {
45
+ printJson({ id: invitation.id, name: invitation.email });
46
+ }
47
+ else {
48
+ console.log(`Withdrew the invitation to ${invitation.email} (${invitation.id}).`);
49
+ }
50
+ },
51
+ });
52
+ // The API stores emails lowercased.
53
+ function resolveInvitation(invitations, reference) {
54
+ const lowercasedReference = reference.toLowerCase();
55
+ const invitation = invitations.find(({ email, id }) => id === reference || email.toLowerCase() === lowercasedReference);
56
+ if (invitation === undefined) {
57
+ throw new InvalidParameterError(`The organization has no pending invitation ${reference} from --invitation.`, 'Run "hotcodepush invitation list" for the ids and emails.');
58
+ }
59
+ return invitation;
60
+ }
@@ -0,0 +1,40 @@
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 { fetchOrganizationId } from '../../utils/resource-resolution.js';
7
+ export default defineCommand({
8
+ description: "List the organization's pending invitations, the ones a link can still accept, newest first.",
9
+ examples: [
10
+ 'hotcodepush invitation list --organization Acme',
11
+ 'hotcodepush invitation list --limit 10 --offset 10 --json',
12
+ ],
13
+ options: defineCommandOptions(paginationShape),
14
+ action: async (options) => {
15
+ const hotCodePush = createApiClient();
16
+ const listedInvitations = await hotCodePush.organizations.invitations.list({
17
+ limit: options.limit,
18
+ offset: options.offset,
19
+ organizationId: await fetchOrganizationId(hotCodePush, options),
20
+ status: 'pending',
21
+ });
22
+ const nextOffset = resolveNextOffset(listedInvitations.length, options);
23
+ if (options.json) {
24
+ printJson({ invitations: listedInvitations, nextOffset });
25
+ return;
26
+ }
27
+ printTable({
28
+ emptyText: 'No pending invitations.',
29
+ headers: ['ID', 'EMAIL', 'ROLE', 'EXPIRES', 'CREATED'],
30
+ nextOffset,
31
+ rows: listedInvitations.map(({ createdAt, email, expiresAt, id, role }) => [
32
+ id,
33
+ email,
34
+ role,
35
+ resolveDate(expiresAt),
36
+ resolveDate(createdAt),
37
+ ]),
38
+ });
39
+ },
40
+ });
@@ -0,0 +1,34 @@
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 } from '../../utils/output.js';
5
+ import { fetchOrganizationId } from '../../utils/resource-resolution.js';
6
+ export default defineCommand({
7
+ description: "List the limits in effect for an organization: each value, the plan's default and whether it is overridden.",
8
+ examples: [
9
+ 'hotcodepush limit list',
10
+ 'hotcodepush limit list --organization Acme --json',
11
+ ],
12
+ options: defineCommandOptions({}),
13
+ action: async (options) => {
14
+ const hotCodePush = createApiClient();
15
+ const fetchedLimits = await hotCodePush.organizations.limits.get({
16
+ organizationId: await fetchOrganizationId(hotCodePush, options),
17
+ });
18
+ if (options.json) {
19
+ printJson(fetchedLimits);
20
+ return;
21
+ }
22
+ printTable({
23
+ emptyText: 'No limits.',
24
+ headers: ['LIMIT', 'VALUE', 'DEFAULT', 'OVERRIDDEN'],
25
+ nextOffset: null,
26
+ rows: Object.entries(fetchedLimits).map(([key, limit]) => [
27
+ key,
28
+ String(limit.value),
29
+ String(limit.default),
30
+ limit.isOverridden ? 'yes' : 'no',
31
+ ]),
32
+ });
33
+ },
34
+ });
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+ import { defineCommand } from '../utils/command.js';
3
+ import { defineCommandOptions } from '../utils/global-options.js';
4
+ import { logIn } from '../utils/login.js';
5
+ import { printJson } from '../utils/output.js';
6
+ import { readUserConfig, writeUserConfig } from '../utils/user-config.js';
7
+ export default defineCommand({
8
+ description: 'Log in by approving a one-time code in the browser.',
9
+ authentication: 'optional',
10
+ examples: [
11
+ 'hotcodepush login',
12
+ 'hotcodepush login --api-url https://api.example.com --json',
13
+ ],
14
+ options: defineCommandOptions({
15
+ apiUrl: z
16
+ .url({
17
+ error: 'Expected an absolute http or https URL such as https://api.example.com',
18
+ protocol: z.regexes.httpProtocol,
19
+ })
20
+ .transform(apiUrl => apiUrl.replace(/\/+$/, ''))
21
+ .optional()
22
+ .describe('The API to log in to, kept in config.json for every later command. Defaults to the one kept there.'),
23
+ }),
24
+ action: async (options) => {
25
+ if (options.apiUrl !== undefined) {
26
+ writeUserConfig({ ...readUserConfig(), apiUrl: options.apiUrl });
27
+ }
28
+ const user = await logIn(options);
29
+ if (options.json) {
30
+ printJson({ user });
31
+ }
32
+ else {
33
+ console.log(`Logged in as ${user.name} (${user.email}).`);
34
+ }
35
+ },
36
+ });
@@ -0,0 +1,81 @@
1
+ import { createApiAuthClient } from '../utils/auth-client.js';
2
+ import { defineCommand } from '../utils/command.js';
3
+ import { resolveApiError } from '../utils/error-mapping.js';
4
+ import { NotLoggedInError } from '../utils/errors.js';
5
+ import { defineCommandOptions } from '../utils/global-options.js';
6
+ import { printJson } from '../utils/output.js';
7
+ import { deleteToken, readStoredToken } from '../utils/token-store.js';
8
+ import { readUserConfig, writeUserConfig } from '../utils/user-config.js';
9
+ const UNAUTHORIZED_STATUS = 401;
10
+ export default defineCommand({
11
+ description: 'End the stored session and remove its token. HOTCODEPUSH_TOKEN, when set, stays what it is.',
12
+ authentication: 'optional',
13
+ examples: ['hotcodepush logout', 'hotcodepush logout --json'],
14
+ options: defineCommandOptions({}),
15
+ action: async (options) => {
16
+ const storedToken = readStoredToken();
17
+ if (storedToken === undefined) {
18
+ if (!process.env.HOTCODEPUSH_TOKEN) {
19
+ throw new NotLoggedInError();
20
+ }
21
+ printEnded({ id: null, name: null }, options.json);
22
+ return;
23
+ }
24
+ const authClient = createApiAuthClient(storedToken);
25
+ const knownSession = await fetchKnownSession(authClient);
26
+ const { error } = await authClient.revokeSession({
27
+ token: resolveUnsignedToken(storedToken),
28
+ });
29
+ // A 401 means the session already ended, so only the stored copies are left to clear.
30
+ if (error !== null && error.status !== UNAUTHORIZED_STATUS) {
31
+ throw resolveApiError(error);
32
+ }
33
+ deleteToken();
34
+ const storedSessionId = deleteStoredSession();
35
+ printEnded({
36
+ id: knownSession?.session.id ?? storedSessionId ?? null,
37
+ name: knownSession?.user.name ?? null,
38
+ }, options.json);
39
+ },
40
+ });
41
+ function deleteStoredSession() {
42
+ const userConfig = readUserConfig();
43
+ const { sessionId } = userConfig;
44
+ if (sessionId === undefined && userConfig.sessionExpiresAt === undefined) {
45
+ return undefined;
46
+ }
47
+ delete userConfig.sessionExpiresAt;
48
+ delete userConfig.sessionId;
49
+ writeUserConfig(userConfig);
50
+ return sessionId;
51
+ }
52
+ async function fetchKnownSession(authClient) {
53
+ const { data, error } = await authClient.getSession();
54
+ if (error !== null) {
55
+ if (error.status === UNAUTHORIZED_STATUS) {
56
+ return null;
57
+ }
58
+ throw resolveApiError(error);
59
+ }
60
+ return data;
61
+ }
62
+ function printEnded(endedSession, isJson) {
63
+ if (isJson) {
64
+ printJson(endedSession);
65
+ return;
66
+ }
67
+ const variableText = process.env.HOTCODEPUSH_TOKEN
68
+ ? ' HOTCODEPUSH_TOKEN still authenticates.'
69
+ : '';
70
+ if (endedSession.id === null && endedSession.name === null) {
71
+ console.log(`No stored session to end.${variableText}`);
72
+ return;
73
+ }
74
+ const nameText = endedSession.name === null ? '' : ` ${endedSession.name}`;
75
+ console.log(`Logged out${nameText}.${variableText}`);
76
+ }
77
+ // A bearer can be signed, `<token>.<signature>`, but revoke-session looks up the token alone.
78
+ function resolveUnsignedToken(token) {
79
+ const [unsignedToken = token] = token.split('.');
80
+ return unsignedToken;
81
+ }
@@ -0,0 +1,35 @@
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 { fetchMember, memberOptionShape, resolveMemberEmail, } from '../../utils/member-resolution.js';
5
+ import { printJson } from '../../utils/output.js';
6
+ import { confirmConsequence } from '../../utils/prompts.js';
7
+ import { fetchOrganizationId } from '../../utils/resource-resolution.js';
8
+ export default defineCommand({
9
+ description: 'Remove a member from the organization, or yourself to leave it. The Owner leaves only after transferring the ownership.',
10
+ examples: [
11
+ 'hotcodepush member delete --member bob@example.com',
12
+ 'hotcodepush member delete --member 5e8a2c71-0b4d-4f39-9a6e-3c1d7b2f8e40 --yes --json',
13
+ ],
14
+ options: defineCommandOptions(memberOptionShape),
15
+ action: async (options) => {
16
+ const hotCodePush = createApiClient();
17
+ const organizationId = await fetchOrganizationId(hotCodePush, options);
18
+ const fetchedMember = await fetchMember(hotCodePush, organizationId, options);
19
+ const email = resolveMemberEmail(fetchedMember);
20
+ const isConfirmed = await confirmConsequence(`removes member ${email} from the organization`, options);
21
+ if (!isConfirmed) {
22
+ return;
23
+ }
24
+ await hotCodePush.organizations.members.delete({
25
+ memberId: fetchedMember.id,
26
+ organizationId,
27
+ });
28
+ if (options.json) {
29
+ printJson({ id: fetchedMember.id, name: email });
30
+ }
31
+ else {
32
+ console.log(`Removed member ${email} (${fetchedMember.id}).`);
33
+ }
34
+ },
35
+ });
@@ -0,0 +1,37 @@
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 { fetchMember, memberOptionShape, resolveMemberEmail, } from '../../utils/member-resolution.js';
5
+ import { printDetails, printJson } from '../../utils/output.js';
6
+ import { fetchOrganizationId } from '../../utils/resource-resolution.js';
7
+ export default defineCommand({
8
+ description: 'Print a member of the organization with their role and how they sign in.',
9
+ examples: [
10
+ 'hotcodepush member get --member anna@example.com',
11
+ 'hotcodepush member get --member 2a7d4e91-6c3b-4f58-8e0a-9b1c5d3f7e62 --json',
12
+ ],
13
+ options: defineCommandOptions(memberOptionShape),
14
+ action: async (options) => {
15
+ const hotCodePush = createApiClient();
16
+ const fetchedMember = await fetchMember(hotCodePush, await fetchOrganizationId(hotCodePush, options), options);
17
+ if (options.json) {
18
+ printJson(fetchedMember);
19
+ return;
20
+ }
21
+ printDetails([
22
+ ['ID', fetchedMember.id],
23
+ ['Name', fetchedMember.user?.name ?? ''],
24
+ ['Email', resolveMemberEmail(fetchedMember)],
25
+ ['Role', fetchedMember.role],
26
+ ['Password', hasPasswordAccount(fetchedMember) ? 'yes' : 'no'],
27
+ ['Two-factor', fetchedMember.user?.twoFactorEnabled ? 'yes' : 'no'],
28
+ ['Last seen', fetchedMember.lastSeenAt ?? 'never'],
29
+ ['Joined', fetchedMember.createdAt],
30
+ ]);
31
+ },
32
+ });
33
+ // Better Auth stores a password as an account of the provider `credential`.
34
+ function hasPasswordAccount({ user }) {
35
+ return (user?.accounts?.some(({ providerId }) => providerId === 'credential') ??
36
+ false);
37
+ }
@@ -0,0 +1,41 @@
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 { resolveMemberEmail } from '../../utils/member-resolution.js';
5
+ import { printJson, printTable, resolveDate } from '../../utils/output.js';
6
+ import { paginationShape, resolveNextOffset } from '../../utils/pagination.js';
7
+ import { fetchOrganizationId } from '../../utils/resource-resolution.js';
8
+ export default defineCommand({
9
+ description: "List the organization's members with their roles, newest first.",
10
+ examples: [
11
+ 'hotcodepush member list --organization Acme',
12
+ 'hotcodepush member list --limit 10 --offset 10 --json',
13
+ ],
14
+ options: defineCommandOptions(paginationShape),
15
+ action: async (options) => {
16
+ const hotCodePush = createApiClient();
17
+ const listedMembers = await hotCodePush.organizations.members.list({
18
+ limit: options.limit,
19
+ offset: options.offset,
20
+ organizationId: await fetchOrganizationId(hotCodePush, options),
21
+ relations: ['user', 'user.accounts'],
22
+ });
23
+ const nextOffset = resolveNextOffset(listedMembers.length, options);
24
+ if (options.json) {
25
+ printJson({ members: listedMembers, nextOffset });
26
+ return;
27
+ }
28
+ printTable({
29
+ emptyText: 'No members.',
30
+ headers: ['ID', 'NAME', 'EMAIL', 'ROLE', 'JOINED'],
31
+ nextOffset,
32
+ rows: listedMembers.map(member => [
33
+ member.id,
34
+ member.user?.name ?? '',
35
+ resolveMemberEmail(member),
36
+ member.role,
37
+ resolveDate(member.createdAt),
38
+ ]),
39
+ });
40
+ },
41
+ });
@@ -0,0 +1,53 @@
1
+ import { z } from 'zod';
2
+ import { createApiClient } from '../../utils/api-client.js';
3
+ import { defineCommand } from '../../utils/command.js';
4
+ import { defineCommandOptions } from '../../utils/global-options.js';
5
+ import { fetchMember, memberOptionShape, resolveMemberEmail, } from '../../utils/member-resolution.js';
6
+ import { printJson } from '../../utils/output.js';
7
+ import { confirmConsequence, promptSelect } from '../../utils/prompts.js';
8
+ import { fetchOrganizationId } from '../../utils/resource-resolution.js';
9
+ const MEMBER_ROLES = [
10
+ 'admin',
11
+ 'billing',
12
+ 'member',
13
+ 'owner',
14
+ ];
15
+ export default defineCommand({
16
+ description: "Change a member's role; --role owner transfers the ownership to them, makes you an admin and asks once, --yes confirming in scripts.",
17
+ examples: [
18
+ 'hotcodepush member update --member bob@example.com --role admin',
19
+ 'hotcodepush member update --member 5e8a2c71-0b4d-4f39-9a6e-3c1d7b2f8e40 --role owner --yes',
20
+ ],
21
+ options: defineCommandOptions({
22
+ ...memberOptionShape,
23
+ role: z
24
+ .enum(MEMBER_ROLES)
25
+ .optional()
26
+ .describe("The member's new role: admin, billing, member, or owner to transfer the ownership."),
27
+ }),
28
+ action: async (options) => {
29
+ const hotCodePush = createApiClient();
30
+ const organizationId = await fetchOrganizationId(hotCodePush, options);
31
+ const fetchedMember = await fetchMember(hotCodePush, organizationId, options);
32
+ const role = options.role ??
33
+ (await promptSelect('--role', 'Which role does the member get?', MEMBER_ROLES.map(role => ({ label: role, value: role })), options));
34
+ const email = resolveMemberEmail(fetchedMember);
35
+ if (role === 'owner') {
36
+ const isConfirmed = await confirmConsequence(`transfers the ownership of the organization to ${email} and makes you an admin, and only the new owner can transfer it back`, options);
37
+ if (!isConfirmed) {
38
+ return;
39
+ }
40
+ }
41
+ const updatedMember = await hotCodePush.organizations.members.update({
42
+ memberId: fetchedMember.id,
43
+ organizationId,
44
+ role,
45
+ });
46
+ if (options.json) {
47
+ printJson(updatedMember);
48
+ }
49
+ else {
50
+ console.log(`Updated member ${email} (${updatedMember.id}) to role ${updatedMember.role}.`);
51
+ }
52
+ },
53
+ });
@@ -0,0 +1,37 @@
1
+ import { createApiClient } from '../utils/api-client.js';
2
+ import { openBrowser } from '../utils/browser.js';
3
+ import { defineCommand } from '../utils/command.js';
4
+ import { defineCommandOptions } from '../utils/global-options.js';
5
+ import { resolveConsoleBaseUrl } from '../utils/hosts.js';
6
+ import { printJson } from '../utils/output.js';
7
+ import { assertProjectConfigAppId, readProjectConfig, } from '../utils/project-config.js';
8
+ import { channelOptionShape, fetchAppId, fetchChannel, } from '../utils/resource-resolution.js';
9
+ import { readApiUrl } from '../utils/user-config.js';
10
+ export default defineCommand({
11
+ description: "Open the app's console page in the browser, or the channel's, from hotcodepush.json.",
12
+ examples: ['hotcodepush open', 'hotcodepush open --channel staging'],
13
+ options: defineCommandOptions(channelOptionShape),
14
+ action: async (options) => {
15
+ const url = await resolveConsolePageUrl(options);
16
+ if (options.json) {
17
+ printJson({ url });
18
+ return;
19
+ }
20
+ console.log(`Opening ${url}`);
21
+ await openBrowser(url);
22
+ },
23
+ });
24
+ async function resolveConsolePageUrl(options) {
25
+ const consoleBaseUrl = resolveConsoleBaseUrl(readApiUrl());
26
+ if (options.channel !== undefined) {
27
+ const channel = await fetchChannel(createApiClient(), options);
28
+ return `${consoleBaseUrl}/apps/${channel.appId}/channels/${channel.id}`;
29
+ }
30
+ const projectConfig = readProjectConfig(options.config);
31
+ if (options.app === undefined && projectConfig?.appId !== undefined) {
32
+ assertProjectConfigAppId(projectConfig.appId);
33
+ return `${consoleBaseUrl}/apps/${projectConfig.appId}`;
34
+ }
35
+ const appId = await fetchAppId(createApiClient(), options, projectConfig);
36
+ return `${consoleBaseUrl}/apps/${appId}`;
37
+ }
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod';
2
+ import { createApiClient } from '../../utils/api-client.js';
3
+ import { defineCommand } from '../../utils/command.js';
4
+ import { defineCommandOptions } from '../../utils/global-options.js';
5
+ import { printJson } from '../../utils/output.js';
6
+ import { promptText } from '../../utils/prompts.js';
7
+ export default defineCommand({
8
+ description: 'Create an organization with you as its Owner.',
9
+ examples: [
10
+ 'hotcodepush organization create --name Acme',
11
+ 'hotcodepush organization create --name Acme --json',
12
+ ],
13
+ options: defineCommandOptions({
14
+ name: z.string().optional().describe("The organization's name."),
15
+ }),
16
+ action: async (options) => {
17
+ const hotCodePush = createApiClient();
18
+ const name = options.name ??
19
+ (await promptText('--name', 'What is the organization called?', options));
20
+ const createdOrganization = await hotCodePush.organizations.create({
21
+ name,
22
+ });
23
+ if (options.json) {
24
+ printJson(createdOrganization);
25
+ }
26
+ else {
27
+ console.log(`Created organization ${createdOrganization.name} (${createdOrganization.id}).`);
28
+ }
29
+ },
30
+ });
@@ -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, resolveQuantityText } from '../../utils/output.js';
5
+ import { confirmConsequence } from '../../utils/prompts.js';
6
+ import { fetchApps, fetchOrganizationId, } from '../../utils/resource-resolution.js';
7
+ export default defineCommand({
8
+ description: 'Delete an organization with everything in it. Support can restore it for seven days.',
9
+ examples: [
10
+ 'hotcodepush organization delete --organization Acme',
11
+ 'hotcodepush organization delete --organization Acme --yes --json',
12
+ ],
13
+ options: defineCommandOptions({}),
14
+ action: async (options) => {
15
+ const hotCodePush = createApiClient();
16
+ const organizationId = await fetchOrganizationId(hotCodePush, options);
17
+ const [fetchedOrganization, fetchedApps] = await Promise.all([
18
+ hotCodePush.organizations.get({ organizationId }),
19
+ fetchApps(hotCodePush, organizationId),
20
+ ]);
21
+ const isConfirmed = await confirmConsequence(resolveDeletionConsequence(fetchedOrganization.name, fetchedApps.length), options);
22
+ if (!isConfirmed) {
23
+ return;
24
+ }
25
+ await hotCodePush.organizations.delete({ organizationId });
26
+ if (options.json) {
27
+ printJson({ id: organizationId, name: fetchedOrganization.name });
28
+ }
29
+ else {
30
+ console.log(`Deleted organization ${fetchedOrganization.name} (${organizationId}).`);
31
+ }
32
+ },
33
+ });
34
+ function resolveDeletionConsequence(organizationName, appCount) {
35
+ const deletion = `deletes organization ${organizationName}`;
36
+ return appCount === 0
37
+ ? deletion
38
+ : `${deletion} and its ${resolveQuantityText(appCount, 'app')}`;
39
+ }