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.
- package/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/commands/app/create.js +49 -0
- package/dist/commands/app/delete.js +34 -0
- package/dist/commands/app/get.js +33 -0
- package/dist/commands/app/list.js +38 -0
- package/dist/commands/app/transfer.js +58 -0
- package/dist/commands/app/update.js +34 -0
- package/dist/commands/audience/get.js +69 -0
- package/dist/commands/audit-log/list.js +82 -0
- package/dist/commands/binary/create.js +189 -0
- package/dist/commands/binary/get.js +56 -0
- package/dist/commands/binary/list.js +58 -0
- package/dist/commands/bundle/delete.js +35 -0
- package/dist/commands/bundle/get.js +63 -0
- package/dist/commands/bundle/list.js +66 -0
- package/dist/commands/bundle/upload.js +176 -0
- package/dist/commands/channel/create.js +59 -0
- package/dist/commands/channel/delete.js +68 -0
- package/dist/commands/channel/get.js +45 -0
- package/dist/commands/channel/list.js +39 -0
- package/dist/commands/channel/pause.js +32 -0
- package/dist/commands/channel/resume.js +32 -0
- package/dist/commands/channel/update.js +40 -0
- package/dist/commands/checkout/create.js +25 -0
- package/dist/commands/completion.js +36 -0
- package/dist/commands/customer-portal-session/create.js +25 -0
- package/dist/commands/device/delete.js +29 -0
- package/dist/commands/device/get.js +60 -0
- package/dist/commands/device/list.js +97 -0
- package/dist/commands/device/probe.js +253 -0
- package/dist/commands/doctor.js +363 -0
- package/dist/commands/fingerprint/diff.js +133 -0
- package/dist/commands/fingerprint.js +41 -0
- package/dist/commands/init.js +492 -0
- package/dist/commands/invitation/accept.js +53 -0
- package/dist/commands/invitation/create.js +52 -0
- package/dist/commands/invitation/delete.js +60 -0
- package/dist/commands/invitation/list.js +40 -0
- package/dist/commands/limit/list.js +34 -0
- package/dist/commands/login.js +185 -0
- package/dist/commands/logout.js +79 -0
- package/dist/commands/member/delete.js +35 -0
- package/dist/commands/member/get.js +37 -0
- package/dist/commands/member/list.js +41 -0
- package/dist/commands/member/update.js +53 -0
- package/dist/commands/open.js +37 -0
- package/dist/commands/organization/create.js +30 -0
- package/dist/commands/organization/delete.js +39 -0
- package/dist/commands/organization/get.js +38 -0
- package/dist/commands/organization/list.js +41 -0
- package/dist/commands/organization/update.js +64 -0
- package/dist/commands/release/create.js +237 -0
- package/dist/commands/release/get.js +81 -0
- package/dist/commands/release/list.js +69 -0
- package/dist/commands/release/pause.js +36 -0
- package/dist/commands/release/resume.js +36 -0
- package/dist/commands/release/revoke.js +253 -0
- package/dist/commands/release/rollback.js +73 -0
- package/dist/commands/release/rollout.js +63 -0
- package/dist/commands/release/update.js +82 -0
- package/dist/commands/resource-file/write.js +76 -0
- package/dist/commands/signing-key/add.js +45 -0
- package/dist/commands/signing-key/create.js +84 -0
- package/dist/commands/signing-key/delete.js +67 -0
- package/dist/commands/signing-key/list.js +38 -0
- package/dist/commands/sso-provider/delete.js +32 -0
- package/dist/commands/sso-provider/get.js +63 -0
- package/dist/commands/sso-provider/set.js +161 -0
- package/dist/commands/sso-provider/verify.js +31 -0
- package/dist/commands/statistics/get.js +189 -0
- package/dist/commands/usage/get.js +31 -0
- package/dist/commands/usage/list.js +41 -0
- package/dist/commands/webhook/create.js +72 -0
- package/dist/commands/webhook/delete.js +32 -0
- package/dist/commands/webhook/get.js +34 -0
- package/dist/commands/webhook/list.js +40 -0
- package/dist/commands/webhook/test.js +32 -0
- package/dist/commands/webhook/update.js +56 -0
- package/dist/commands/webhook-delivery/list.js +63 -0
- package/dist/commands/webhook-delivery/redeliver.js +48 -0
- package/dist/commands/whoami.js +24 -0
- package/dist/completion/hotcodepush.bash +536 -0
- package/dist/completion/hotcodepush.fish +979 -0
- package/dist/completion/hotcodepush.zsh +1372 -0
- package/dist/config/consts.js +32 -0
- package/dist/index.js +5 -0
- package/dist/registry.js +83 -0
- package/dist/utils/api-client.js +17 -0
- package/dist/utils/audience.js +9 -0
- package/dist/utils/auth-client.js +35 -0
- package/dist/utils/boolean-flag.js +3 -0
- package/dist/utils/browser.js +6 -0
- package/dist/utils/build-step.js +104 -0
- package/dist/utils/bundle-files.js +80 -0
- package/dist/utils/bundle-resolution.js +46 -0
- package/dist/utils/channel-fields.js +130 -0
- package/dist/utils/cli.js +75 -0
- package/dist/utils/command-registry.js +79 -0
- package/dist/utils/completion/bash.js +48 -0
- package/dist/utils/completion/command-tree.js +72 -0
- package/dist/utils/completion/fish.js +44 -0
- package/dist/utils/completion/posix-case-arms.js +9 -0
- package/dist/utils/completion/posix-quote.js +4 -0
- package/dist/utils/completion/script-files.js +26 -0
- package/dist/utils/completion/zsh.js +72 -0
- package/dist/utils/compressed-files.js +31 -0
- package/dist/utils/credential.js +15 -0
- package/dist/utils/device-resolution.js +17 -0
- package/dist/utils/duration.js +16 -0
- package/dist/utils/environment.js +22 -0
- package/dist/utils/error-mapping.js +79 -0
- package/dist/utils/errors.js +266 -0
- package/dist/utils/fingerprint.js +76 -0
- package/dist/utils/flag-name.js +4 -0
- package/dist/utils/fleet.js +9 -0
- package/dist/utils/framework.js +74 -0
- package/dist/utils/frameworks/binary-create-step.js +70 -0
- package/dist/utils/frameworks/capacitor.js +150 -0
- package/dist/utils/frameworks/cordova.js +110 -0
- package/dist/utils/frameworks/expo.js +152 -0
- package/dist/utils/frameworks/index.js +13 -0
- package/dist/utils/frameworks/native-glue.js +10 -0
- package/dist/utils/frameworks/native-projects.js +27 -0
- package/dist/utils/frameworks/react-native-build.js +182 -0
- package/dist/utils/frameworks/react-native.js +127 -0
- package/dist/utils/frameworks/sdk-package.js +63 -0
- package/dist/utils/git-provenance.js +71 -0
- package/dist/utils/global-options.js +34 -0
- package/dist/utils/hosts.js +53 -0
- package/dist/utils/init-steps.js +51 -0
- package/dist/utils/json-file.js +28 -0
- package/dist/utils/member-resolution.js +49 -0
- package/dist/utils/native-project-edit.js +41 -0
- package/dist/utils/nullable-field.js +10 -0
- package/dist/utils/outcome.js +24 -0
- package/dist/utils/output.js +37 -0
- package/dist/utils/pack.js +20 -0
- package/dist/utils/package-json.js +5 -0
- package/dist/utils/package-manager.js +50 -0
- package/dist/utils/pagination.js +29 -0
- package/dist/utils/progress.js +23 -0
- package/dist/utils/progression.js +119 -0
- package/dist/utils/project-config.js +61 -0
- package/dist/utils/prompts.js +53 -0
- package/dist/utils/react-native-project.js +119 -0
- package/dist/utils/release-conditions.js +80 -0
- package/dist/utils/release-output.js +13 -0
- package/dist/utils/release-resolution.js +83 -0
- package/dist/utils/resource-file.js +81 -0
- package/dist/utils/resource-resolution.js +107 -0
- package/dist/utils/signing-private-key.js +105 -0
- package/dist/utils/spending-cap.js +16 -0
- package/dist/utils/telemetry.js +171 -0
- package/dist/utils/token-store.js +63 -0
- package/dist/utils/update-notice.js +67 -0
- package/dist/utils/upload.js +148 -0
- package/dist/utils/usage-month.js +14 -0
- package/dist/utils/user-config.js +46 -0
- package/dist/utils/webhook-resolution.js +47 -0
- package/dist/utils/xcode-project.js +181 -0
- package/package.json +68 -7
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Genz IT Solutions GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# hotcodepush
|
|
2
|
+
|
|
3
|
+
The HotCodePush CLI: set up, release and manage over-the-air (OTA) updates for React Native, Expo, Capacitor and Cordova apps from the terminal and CI.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
The CLI is not on npm yet. Install the preview build of a commit on `main` as a dev dependency, `<sha>` being the commit's short hash:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install --save-dev https://pkg.pr.new/hotcodepush-team/cli/hotcodepush@<sha>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npx hotcodepush login
|
|
17
|
+
npx hotcodepush init # wires the SDK and the build step into your project
|
|
18
|
+
npx hotcodepush release create --path dist # uploads the build and releases it to the project's channel
|
|
19
|
+
npx hotcodepush release create --channel staging --rollout-percent 10
|
|
20
|
+
npx hotcodepush doctor # checks the setup
|
|
21
|
+
npx hotcodepush --help # every command; <command> --help for its flags
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Every command takes `--json` for machine-readable output and `--yes` to skip confirmations.
|
|
25
|
+
In CI, set `HOTCODEPUSH_TOKEN` instead of logging in, and `HOTCODEPUSH_API_BASE_URL` when the token belongs to an API other than production.
|
|
26
|
+
|
|
27
|
+
### Telemetry
|
|
28
|
+
|
|
29
|
+
After each command, the CLI sends one anonymous event (`command`, `outcome`, `stopped_step` for `init`, `duration_ms`, `cli_version`, `is_ci`, `framework` and `source`) to PostHog, and unexpected errors without their message to Sentry.
|
|
30
|
+
No argument, path or name from your project is ever sent.
|
|
31
|
+
Set `HOTCODEPUSH_TELEMETRY_DISABLED=1` or `DO_NOT_TRACK=1` to turn it off.
|
|
32
|
+
|
|
33
|
+
## Documentation
|
|
34
|
+
|
|
35
|
+
See [hotcodepush.com/docs/cli](https://hotcodepush.com/docs/cli).
|
|
36
|
+
|
|
37
|
+
## Development
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
nvm use
|
|
41
|
+
npm ci
|
|
42
|
+
npm run build # compiles src/ into dist/; node dist/index.js --help runs it
|
|
43
|
+
npm test
|
|
44
|
+
npm run lint && npm run typecheck
|
|
45
|
+
npm run fmt # Prettier, run before every commit
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Tests live next to the code they test as `*.test.ts`.
|
|
49
|
+
`CLAUDE.md` holds the layout, the rules and how to add a command or a framework.
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
|
|
53
|
+
See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,49 @@
|
|
|
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 } from '../../utils/output.js';
|
|
6
|
+
import { promptSelect, promptText } from '../../utils/prompts.js';
|
|
7
|
+
import { fetchOrganizationId } from '../../utils/resource-resolution.js';
|
|
8
|
+
const FRAMEWORKS = [
|
|
9
|
+
'react_native',
|
|
10
|
+
'expo',
|
|
11
|
+
'capacitor',
|
|
12
|
+
'cordova',
|
|
13
|
+
];
|
|
14
|
+
export default defineCommand({
|
|
15
|
+
description: 'Create an app with its default channel, production.',
|
|
16
|
+
examples: [
|
|
17
|
+
'hotcodepush app create --name "My App" --framework capacitor',
|
|
18
|
+
'hotcodepush app create --organization Acme --name "My App" --framework expo --json',
|
|
19
|
+
],
|
|
20
|
+
options: defineCommandOptions({
|
|
21
|
+
framework: z
|
|
22
|
+
.enum(FRAMEWORKS)
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('The framework: react_native, expo, capacitor or cordova.'),
|
|
25
|
+
name: z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("The app's name, unique in the organization."),
|
|
29
|
+
}),
|
|
30
|
+
action: async (options) => {
|
|
31
|
+
const hotCodePush = createApiClient();
|
|
32
|
+
const organizationId = await fetchOrganizationId(hotCodePush, options);
|
|
33
|
+
const name = options.name ??
|
|
34
|
+
(await promptText('--name', 'What is the app called?', options));
|
|
35
|
+
const framework = options.framework ??
|
|
36
|
+
(await promptSelect('--framework', 'Which framework does the app use?', FRAMEWORKS.map(framework => ({ label: framework, value: framework })), options));
|
|
37
|
+
const createdApp = await hotCodePush.organizations.apps.create({
|
|
38
|
+
framework,
|
|
39
|
+
name,
|
|
40
|
+
organizationId,
|
|
41
|
+
});
|
|
42
|
+
if (options.json) {
|
|
43
|
+
printJson(createdApp);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.log(`Created app ${createdApp.name} (${createdApp.id}).`);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineCommand } from 'zodline';
|
|
2
|
+
import { createApiClient } from '../../utils/api-client.js';
|
|
3
|
+
import { defineCommandOptions } from '../../utils/global-options.js';
|
|
4
|
+
import { printJson, resolveQuantityText } from '../../utils/output.js';
|
|
5
|
+
import { readProjectConfig } from '../../utils/project-config.js';
|
|
6
|
+
import { confirmConsequence } from '../../utils/prompts.js';
|
|
7
|
+
import { fetchAppId, fetchChannels } from '../../utils/resource-resolution.js';
|
|
8
|
+
export default defineCommand({
|
|
9
|
+
description: 'Delete an app with its channels, bundles and releases; support can restore it for seven days.',
|
|
10
|
+
examples: [
|
|
11
|
+
'hotcodepush app delete --app "My App"',
|
|
12
|
+
'hotcodepush app delete --app 7c9e6679-7425-40de-944b-e07fc1f90ae7 --yes --json',
|
|
13
|
+
],
|
|
14
|
+
options: defineCommandOptions({}),
|
|
15
|
+
action: async (options) => {
|
|
16
|
+
const hotCodePush = createApiClient();
|
|
17
|
+
const appId = await fetchAppId(hotCodePush, options, readProjectConfig(options.config));
|
|
18
|
+
const [fetchedApp, fetchedChannels] = await Promise.all([
|
|
19
|
+
hotCodePush.apps.get({ appId }),
|
|
20
|
+
fetchChannels(hotCodePush, appId),
|
|
21
|
+
]);
|
|
22
|
+
const isConfirmed = await confirmConsequence(`deletes app ${fetchedApp.name} and its ${resolveQuantityText(fetchedChannels.length, 'channel')}`, options);
|
|
23
|
+
if (!isConfirmed) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
await hotCodePush.apps.delete({ appId });
|
|
27
|
+
if (options.json) {
|
|
28
|
+
printJson({ id: appId, name: fetchedApp.name });
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.log(`Deleted app ${fetchedApp.name} (${appId}).`);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineCommand } from 'zodline';
|
|
2
|
+
import { createApiClient } from '../../utils/api-client.js';
|
|
3
|
+
import { defineCommandOptions } from '../../utils/global-options.js';
|
|
4
|
+
import { printDetails, printJson } from '../../utils/output.js';
|
|
5
|
+
import { readProjectConfig } from '../../utils/project-config.js';
|
|
6
|
+
import { fetchAppId } from '../../utils/resource-resolution.js';
|
|
7
|
+
export default defineCommand({
|
|
8
|
+
description: "Print an app, hotcodepush.json's by default.",
|
|
9
|
+
examples: [
|
|
10
|
+
'hotcodepush app get',
|
|
11
|
+
'hotcodepush app get --app "My App" --organization Acme --json',
|
|
12
|
+
],
|
|
13
|
+
options: defineCommandOptions({}),
|
|
14
|
+
action: async (options) => {
|
|
15
|
+
const hotCodePush = createApiClient();
|
|
16
|
+
const fetchedApp = await hotCodePush.apps.get({
|
|
17
|
+
appId: await fetchAppId(hotCodePush, options, readProjectConfig(options.config)),
|
|
18
|
+
});
|
|
19
|
+
if (options.json) {
|
|
20
|
+
printJson(fetchedApp);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
printDetails([
|
|
24
|
+
['ID', fetchedApp.id],
|
|
25
|
+
['Name', fetchedApp.name],
|
|
26
|
+
['Framework', fetchedApp.framework],
|
|
27
|
+
['Organization', fetchedApp.organizationId],
|
|
28
|
+
['Default channel', fetchedApp.defaultChannelId ?? 'none'],
|
|
29
|
+
['Channel link template', fetchedApp.channelLinkTemplate ?? 'none'],
|
|
30
|
+
['Created', fetchedApp.createdAt],
|
|
31
|
+
]);
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
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 { fetchOrganizationId } from '../../utils/resource-resolution.js';
|
|
7
|
+
export default defineCommand({
|
|
8
|
+
description: "List an organization's apps, newest first.",
|
|
9
|
+
examples: [
|
|
10
|
+
'hotcodepush app list',
|
|
11
|
+
'hotcodepush app list --organization Acme --json',
|
|
12
|
+
],
|
|
13
|
+
options: defineCommandOptions(paginationShape),
|
|
14
|
+
action: async (options) => {
|
|
15
|
+
const hotCodePush = createApiClient();
|
|
16
|
+
const listedApps = await hotCodePush.organizations.apps.list({
|
|
17
|
+
limit: options.limit,
|
|
18
|
+
offset: options.offset,
|
|
19
|
+
organizationId: await fetchOrganizationId(hotCodePush, options),
|
|
20
|
+
});
|
|
21
|
+
const nextOffset = resolveNextOffset(listedApps.length, options);
|
|
22
|
+
if (options.json) {
|
|
23
|
+
printJson({ apps: listedApps, nextOffset });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
printTable({
|
|
27
|
+
emptyText: 'No apps.',
|
|
28
|
+
headers: ['ID', 'NAME', 'FRAMEWORK', 'CREATED'],
|
|
29
|
+
nextOffset,
|
|
30
|
+
rows: listedApps.map(({ createdAt, framework, id, name }) => [
|
|
31
|
+
id,
|
|
32
|
+
name,
|
|
33
|
+
framework,
|
|
34
|
+
resolveDate(createdAt),
|
|
35
|
+
]),
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
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 } from '../../utils/output.js';
|
|
6
|
+
import { readProjectConfig } from '../../utils/project-config.js';
|
|
7
|
+
import { confirmConsequence } from '../../utils/prompts.js';
|
|
8
|
+
import { fetchAppId, fetchOrganizations, fetchResourceId, promptResourceId, } from '../../utils/resource-resolution.js';
|
|
9
|
+
export default defineCommand({
|
|
10
|
+
description: 'Move an app to the organization --organization names, one you are an Admin of.',
|
|
11
|
+
examples: [
|
|
12
|
+
'hotcodepush app transfer --organization Globex',
|
|
13
|
+
'hotcodepush app transfer --app "My App" --organization Globex --yes --json',
|
|
14
|
+
],
|
|
15
|
+
options: defineCommandOptions({
|
|
16
|
+
organization: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('The organization the app moves to, by id or name.'),
|
|
20
|
+
}),
|
|
21
|
+
action: async (options) => {
|
|
22
|
+
const hotCodePush = createApiClient();
|
|
23
|
+
// Here `--organization` names the target, so the app name is looked up in every organization of the user.
|
|
24
|
+
const appId = await fetchAppId(hotCodePush, { ...options, organization: undefined }, readProjectConfig(options.config));
|
|
25
|
+
const [fetchedApp, fetchedOrganizations] = await Promise.all([
|
|
26
|
+
hotCodePush.apps.get({ appId }),
|
|
27
|
+
fetchOrganizations(hotCodePush),
|
|
28
|
+
]);
|
|
29
|
+
const targetOrganizationId = await pickTargetOrganizationId(fetchedApp, fetchedOrganizations, options);
|
|
30
|
+
const sourceOrganizationName = resolveOrganizationName(fetchedOrganizations, fetchedApp.organizationId);
|
|
31
|
+
const targetOrganizationName = resolveOrganizationName(fetchedOrganizations, targetOrganizationId);
|
|
32
|
+
const isConfirmed = await confirmConsequence(`moves app ${fetchedApp.name} from organization ${sourceOrganizationName} to organization ${targetOrganizationName}`, options);
|
|
33
|
+
if (!isConfirmed) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const transferredApp = await hotCodePush.apps.transfer({
|
|
37
|
+
appId,
|
|
38
|
+
organizationId: targetOrganizationId,
|
|
39
|
+
});
|
|
40
|
+
if (options.json) {
|
|
41
|
+
printJson(transferredApp);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.log(`Transferred app ${transferredApp.name} (${transferredApp.id}) to organization ${targetOrganizationName}.`);
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
function pickTargetOrganizationId(app, organizations, options) {
|
|
49
|
+
if (options.organization === undefined) {
|
|
50
|
+
return promptResourceId('organization', organizations.filter(({ id }) => id !== app.organizationId), options);
|
|
51
|
+
}
|
|
52
|
+
return fetchResourceId('organization', options.organization, async () => organizations);
|
|
53
|
+
}
|
|
54
|
+
// Falls back to the id when the organizations of the user do not include it.
|
|
55
|
+
function resolveOrganizationName(organizations, organizationId) {
|
|
56
|
+
return (organizations.find(({ id }) => id === organizationId)?.name ??
|
|
57
|
+
organizationId);
|
|
58
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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 } from '../../utils/output.js';
|
|
6
|
+
import { readProjectConfig } from '../../utils/project-config.js';
|
|
7
|
+
import { promptText } from '../../utils/prompts.js';
|
|
8
|
+
import { fetchAppId } from '../../utils/resource-resolution.js';
|
|
9
|
+
export default defineCommand({
|
|
10
|
+
description: "Rename an app, hotcodepush.json's by default.",
|
|
11
|
+
examples: [
|
|
12
|
+
'hotcodepush app update --name "My New App"',
|
|
13
|
+
'hotcodepush app update --app "My App" --name "My New App" --json',
|
|
14
|
+
],
|
|
15
|
+
options: defineCommandOptions({
|
|
16
|
+
name: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("The app's new name, unique in the organization."),
|
|
20
|
+
}),
|
|
21
|
+
action: async (options) => {
|
|
22
|
+
const hotCodePush = createApiClient();
|
|
23
|
+
const appId = await fetchAppId(hotCodePush, options, readProjectConfig(options.config));
|
|
24
|
+
const name = options.name ??
|
|
25
|
+
(await promptText('--name', 'What is the app called now?', options));
|
|
26
|
+
const updatedApp = await hotCodePush.apps.update({ appId, name });
|
|
27
|
+
if (options.json) {
|
|
28
|
+
printJson(updatedApp);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.log(`Updated app ${updatedApp.name} (${updatedApp.id}).`);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { defineCommand } from 'zodline';
|
|
3
|
+
import { createApiClient } from '../../utils/api-client.js';
|
|
4
|
+
import { resolveAudienceText } from '../../utils/audience.js';
|
|
5
|
+
import { hasLockfile, readFingerprint } from '../../utils/fingerprint.js';
|
|
6
|
+
import { defineCommandOptions } from '../../utils/global-options.js';
|
|
7
|
+
import { printJson, printTable, printWarnings } from '../../utils/output.js';
|
|
8
|
+
import { locateProjectConfig } from '../../utils/project-config.js';
|
|
9
|
+
import { buildAudienceQuery, conditionOptionShape, } from '../../utils/release-conditions.js';
|
|
10
|
+
import { channelOptionShape, fetchChannel, } from '../../utils/resource-resolution.js';
|
|
11
|
+
export default defineCommand({
|
|
12
|
+
description: "Preview the devices a release's conditions would reach in a channel, before it exists, the project's fingerprint among them; what release create --dry-run prints.",
|
|
13
|
+
examples: [
|
|
14
|
+
'hotcodepush audience get --binary-version ">=2.3.0" --rollout-percent 10',
|
|
15
|
+
'hotcodepush audience get --channel production --attribute tier=beta --device 6b1e9d37-2f5c-4a80-9c46-d8e3a1f7b259 --json',
|
|
16
|
+
],
|
|
17
|
+
options: defineCommandOptions({
|
|
18
|
+
...channelOptionShape,
|
|
19
|
+
...conditionOptionShape,
|
|
20
|
+
rolloutPercent: z.coerce
|
|
21
|
+
.number()
|
|
22
|
+
.int()
|
|
23
|
+
.min(0)
|
|
24
|
+
.max(100)
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('The rollout percentage to estimate, 0 to 100; 100 by default.'),
|
|
27
|
+
}),
|
|
28
|
+
action: async (options) => {
|
|
29
|
+
const hotCodePush = createApiClient();
|
|
30
|
+
const channel = await fetchChannel(hotCodePush, options);
|
|
31
|
+
const rolloutPercent = options.rolloutPercent ?? 100;
|
|
32
|
+
const audience = await hotCodePush.apps.channels.audience.get({
|
|
33
|
+
...buildAudienceQuery(options, await readProjectFingerprint(options.config), rolloutPercent),
|
|
34
|
+
appId: channel.appId,
|
|
35
|
+
channelId: channel.id,
|
|
36
|
+
});
|
|
37
|
+
printWarnings(audience.warnings);
|
|
38
|
+
if (options.json) {
|
|
39
|
+
printJson(audience);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const audienceText = resolveAudienceText(audience, channel.name, rolloutPercent);
|
|
43
|
+
console.log(`${audienceText.charAt(0).toUpperCase()}${audienceText.slice(1)}.`);
|
|
44
|
+
if (audience.reachedDeviceCount === 0) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
for (const [header, deviceCounts] of [
|
|
48
|
+
['PLATFORM', audience.platform],
|
|
49
|
+
['BINARY VERSION', audience.binaryVersion],
|
|
50
|
+
]) {
|
|
51
|
+
printTable({
|
|
52
|
+
emptyText: '',
|
|
53
|
+
headers: [header, 'DEVICES'],
|
|
54
|
+
nextOffset: null,
|
|
55
|
+
rows: deviceCounts.map(({ deviceCount, value }) => [
|
|
56
|
+
value ?? 'none',
|
|
57
|
+
String(deviceCount),
|
|
58
|
+
]),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
// Uses the fingerprint that `release create` would set, so the preview counts the same devices.
|
|
64
|
+
async function readProjectFingerprint(configPath) {
|
|
65
|
+
const { directoryPath, projectConfig } = locateProjectConfig(configPath);
|
|
66
|
+
return hasLockfile(directoryPath)
|
|
67
|
+
? readFingerprint(directoryPath, projectConfig?.extraFingerprintPaths ?? [])
|
|
68
|
+
: null;
|
|
69
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { defineCommand } from 'zodline';
|
|
3
|
+
import { createApiClient } from '../../utils/api-client.js';
|
|
4
|
+
import { resolveTimeBound } from '../../utils/duration.js';
|
|
5
|
+
import { defineCommandOptions } from '../../utils/global-options.js';
|
|
6
|
+
import { printJson, printTable } from '../../utils/output.js';
|
|
7
|
+
import { paginationShape, resolveNextOffset } from '../../utils/pagination.js';
|
|
8
|
+
import { fetchApps, fetchOrganizationId, fetchResourceId, } from '../../utils/resource-resolution.js';
|
|
9
|
+
const ACTOR_TYPES = ['system', 'user'];
|
|
10
|
+
export default defineCommand({
|
|
11
|
+
description: "List the organization's audit log, newest first, filtered by type, actor, app and time, on a plan that includes the audit log.",
|
|
12
|
+
examples: [
|
|
13
|
+
'hotcodepush audit-log list --type "channel.*" --created-since 7d',
|
|
14
|
+
'hotcodepush audit-log list --app "My App" --actor 4f2c8a1e-6b3d-4e9f-a7c5-2d8b1e6f3a90 --json',
|
|
15
|
+
],
|
|
16
|
+
options: defineCommandOptions({
|
|
17
|
+
...paginationShape,
|
|
18
|
+
actor: z.string().optional().describe('The user who acted, by id.'),
|
|
19
|
+
actorType: z
|
|
20
|
+
.enum(ACTOR_TYPES)
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('system for the rows no user wrote, user for the rest.'),
|
|
23
|
+
app: z
|
|
24
|
+
.string()
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("The app, by id or name, to narrow the log to; the whole organization by default, never hotcodepush.json's app."),
|
|
27
|
+
createdSince: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('The earliest row, inclusive: an ISO 8601 timestamp or a duration ago such as 2h or 7d.'),
|
|
31
|
+
createdUntil: z
|
|
32
|
+
.string()
|
|
33
|
+
.optional()
|
|
34
|
+
.describe('The latest row, inclusive: an ISO 8601 timestamp or a duration ago such as 2h or 7d.'),
|
|
35
|
+
type: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe('The type, such as channel.created, or channel.* for every action on the object.'),
|
|
39
|
+
}),
|
|
40
|
+
action: async (options) => {
|
|
41
|
+
const hotCodePush = createApiClient();
|
|
42
|
+
const organizationId = await fetchOrganizationId(hotCodePush, options);
|
|
43
|
+
// The log belongs to the organization, so only `--app` filters it, never the app in hotcodepush.json.
|
|
44
|
+
const listedAuditLogs = await hotCodePush.organizations.auditLogs.list({
|
|
45
|
+
actorType: options.actorType,
|
|
46
|
+
appId: options.app === undefined
|
|
47
|
+
? undefined
|
|
48
|
+
: await fetchResourceId('app', options.app, () => fetchApps(hotCodePush, organizationId)),
|
|
49
|
+
createdSince: resolveTimeBound(options.createdSince),
|
|
50
|
+
createdUntil: resolveTimeBound(options.createdUntil),
|
|
51
|
+
limit: options.limit,
|
|
52
|
+
offset: options.offset,
|
|
53
|
+
organizationId,
|
|
54
|
+
relations: ['user'],
|
|
55
|
+
type: options.type,
|
|
56
|
+
userId: options.actor,
|
|
57
|
+
});
|
|
58
|
+
const nextOffset = resolveNextOffset(listedAuditLogs.length, options);
|
|
59
|
+
if (options.json) {
|
|
60
|
+
printJson({ auditLogs: listedAuditLogs, nextOffset });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
printTable({
|
|
64
|
+
emptyText: 'No audit log rows match.',
|
|
65
|
+
headers: ['TIME', 'TYPE', 'ACTOR', 'OBJECT'],
|
|
66
|
+
nextOffset,
|
|
67
|
+
rows: listedAuditLogs.map(auditLog => [
|
|
68
|
+
auditLog.createdAt,
|
|
69
|
+
auditLog.type,
|
|
70
|
+
resolveActorText(auditLog),
|
|
71
|
+
auditLog.objectId ?? '',
|
|
72
|
+
]),
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
// Matches the console: the email while the user exists, the id after.
|
|
77
|
+
function resolveActorText({ actorType, user, userId }) {
|
|
78
|
+
if (actorType === 'system') {
|
|
79
|
+
return 'System';
|
|
80
|
+
}
|
|
81
|
+
return user?.email ?? userId ?? '';
|
|
82
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { defineCommand } from 'zodline';
|
|
3
|
+
import { createApiClient } from '../../utils/api-client.js';
|
|
4
|
+
import { buildStepShape, fetchChannelId, readBuildStep, resolveChannelIdByShape, resolveFailureText, resolveOfflineCause, writeBuildResourceFile, } from '../../utils/build-step.js';
|
|
5
|
+
import { compressFiles, withTemporaryDirectory, } from '../../utils/compressed-files.js';
|
|
6
|
+
import { isCi } from '../../utils/environment.js';
|
|
7
|
+
import { CliError, PipelineNotLoggedInError } from '../../utils/errors.js';
|
|
8
|
+
import { assertMissingParameter } from '../../utils/framework.js';
|
|
9
|
+
import { defineCommandOptions } from '../../utils/global-options.js';
|
|
10
|
+
import { printJson } from '../../utils/output.js';
|
|
11
|
+
import { createReporter, resolveByteText } from '../../utils/progress.js';
|
|
12
|
+
import { assertWithinBundleBytesLimit, resolveMissingSha256s, uploadMissingFiles, } from '../../utils/upload.js';
|
|
13
|
+
const NO_UPLOAD = { fileCount: 0, sizeBytes: 0 };
|
|
14
|
+
export default defineCommand({
|
|
15
|
+
description: 'The build step of a store build: creates the binary with the embedded bundle it ships and writes the resource file the SDK reads, naming that bundle; every other build runs resource-file write. HOTCODEPUSH_OFFLINE=1 builds without the API, naming no channel unless it is given by id and creating no binary.',
|
|
16
|
+
examples: [
|
|
17
|
+
'hotcodepush binary create --platform ios --embedded-bundle-path build/App.app/public --resource-file-path build/App.app/hotcodepush.json --binary-version 2.4.1 --binary-build 57',
|
|
18
|
+
'hotcodepush binary create --platform android --embedded-bundle-path build/assets/public --resource-file-path build/assets/hotcodepush.json --binary-version 2.4.1 --binary-build 57 --force',
|
|
19
|
+
],
|
|
20
|
+
options: defineCommandOptions({
|
|
21
|
+
...buildStepShape,
|
|
22
|
+
binaryBuild: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("The store build's build number, from the build's variables."),
|
|
26
|
+
binaryVersion: z
|
|
27
|
+
.string()
|
|
28
|
+
.optional()
|
|
29
|
+
.describe("The store build's version, from the build's variables."),
|
|
30
|
+
force: z
|
|
31
|
+
.boolean()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('Replace a binary whose fingerprint conflicts, for the deliberate pre-ship rebuild.'),
|
|
34
|
+
}),
|
|
35
|
+
action: async (options) => {
|
|
36
|
+
const buildStep = await readBuildStep(options);
|
|
37
|
+
const { embeddedFiles, platform, resourceFilePath } = buildStep;
|
|
38
|
+
if (embeddedFiles === undefined) {
|
|
39
|
+
writeBuildResourceFile(buildStep, resolveChannelIdByShape(buildStep.channelReference), null);
|
|
40
|
+
process.stderr.write(`No binary created: the ${platform} build bundled no JavaScript, as a debug build served by the development server does. Wrote ${resourceFilePath} without an embedded bundle: over-the-air (OTA) updates are off in this build.\n`);
|
|
41
|
+
if (options.json) {
|
|
42
|
+
printJson({ binary: null, resourceFilePath, upload: NO_UPLOAD });
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
assertWithinBundleBytesLimit(embeddedFiles);
|
|
47
|
+
const identity = resolveBinaryIdentity(options);
|
|
48
|
+
const binaryCreation = await createBuildBinary({
|
|
49
|
+
...identity,
|
|
50
|
+
appId: buildStep.projectConfig.appId,
|
|
51
|
+
fingerprint: buildStep.fingerprint,
|
|
52
|
+
force: options.force ?? false,
|
|
53
|
+
platform,
|
|
54
|
+
}, buildStep, embeddedFiles, createReporter(options));
|
|
55
|
+
writeBuildResourceFile(buildStep, binaryCreation.channelId, {
|
|
56
|
+
bundleVersion: identity.binaryVersion,
|
|
57
|
+
files: embeddedFiles,
|
|
58
|
+
id: binaryCreation.binary?.bundleId ?? null,
|
|
59
|
+
});
|
|
60
|
+
if (binaryCreation.pipelineFailure !== undefined) {
|
|
61
|
+
throw binaryCreation.pipelineFailure;
|
|
62
|
+
}
|
|
63
|
+
if (binaryCreation.skippedReason !== null) {
|
|
64
|
+
process.stderr.write(`Warning: ${binaryCreation.skippedReason}\n`);
|
|
65
|
+
}
|
|
66
|
+
if (options.json) {
|
|
67
|
+
printJson({
|
|
68
|
+
binary: binaryCreation.binary,
|
|
69
|
+
resourceFilePath,
|
|
70
|
+
upload: binaryCreation.upload,
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
console.log(`Wrote ${resourceFilePath} for ${platform}.`);
|
|
75
|
+
if (binaryCreation.binary !== null) {
|
|
76
|
+
console.log(`Created the binary ${platform} ${identity.binaryVersion} (${identity.binaryBuild}): ${binaryCreation.upload.fileCount} files uploaded, ${resolveByteText(binaryCreation.upload.sizeBytes)}.`);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
async function createBuildBinary(request, buildStep, files, reporter) {
|
|
81
|
+
const channelIdByShape = resolveChannelIdByShape(buildStep.channelReference);
|
|
82
|
+
const offlineCause = resolveOfflineCause();
|
|
83
|
+
if (offlineCause === 'no-token' && isCi()) {
|
|
84
|
+
return resolveFailedCreation(channelIdByShape, new PipelineNotLoggedInError());
|
|
85
|
+
}
|
|
86
|
+
if (offlineCause !== undefined) {
|
|
87
|
+
return {
|
|
88
|
+
binary: null,
|
|
89
|
+
channelId: channelIdByShape,
|
|
90
|
+
skippedReason: resolveOfflineText(offlineCause, channelIdByShape),
|
|
91
|
+
upload: NO_UPLOAD,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const hotCodePush = createApiClient();
|
|
95
|
+
let channelId;
|
|
96
|
+
try {
|
|
97
|
+
channelId =
|
|
98
|
+
channelIdByShape ?? (await fetchChannelId(hotCodePush, buildStep));
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error instanceof CliError) {
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
if (isCi()) {
|
|
105
|
+
return resolveFailedCreation(null, error);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
binary: null,
|
|
109
|
+
channelId: null,
|
|
110
|
+
skippedReason: `the channel could not be resolved, so the build names none and takes no OTA updates, and no binary was created: ${resolveFailureText(error)}`,
|
|
111
|
+
upload: NO_UPLOAD,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
return {
|
|
116
|
+
...(await createBinaryWithUploads(hotCodePush, request, files, reporter)),
|
|
117
|
+
channelId,
|
|
118
|
+
skippedReason: null,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
// A local build never breaks: a failed creation is one warning. CI fails with it, since what it ships must name its channel.
|
|
123
|
+
if (isCi()) {
|
|
124
|
+
return resolveFailedCreation(channelId, error);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
binary: null,
|
|
128
|
+
channelId,
|
|
129
|
+
skippedReason: `the binary was not created: ${resolveFailureText(error)}`,
|
|
130
|
+
upload: NO_UPLOAD,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function createBinaryWithUploads(hotCodePush, request, files, reporter) {
|
|
135
|
+
const { binaryBuild, binaryVersion, ...identityless } = request;
|
|
136
|
+
const createOptions = {
|
|
137
|
+
...identityless,
|
|
138
|
+
build: binaryBuild,
|
|
139
|
+
files: files.map(({ path, sha256, sizeBytes }) => ({
|
|
140
|
+
path,
|
|
141
|
+
sha256,
|
|
142
|
+
sizeBytes,
|
|
143
|
+
})),
|
|
144
|
+
version: binaryVersion,
|
|
145
|
+
};
|
|
146
|
+
try {
|
|
147
|
+
const binary = await hotCodePush.apps.binaries.create(createOptions);
|
|
148
|
+
return { binary, upload: NO_UPLOAD };
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
const missingSha256s = resolveMissingSha256s(error);
|
|
152
|
+
if (missingSha256s === undefined) {
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
reporter.report(`Uploading ${missingSha256s.length} files the app lacks…`);
|
|
156
|
+
const uploadedFiles = await withTemporaryDirectory(async (temporaryDirectoryPath) => uploadMissingFiles(hotCodePush, request.appId, missingSha256s, await compressFiles(files.filter(({ sha256 }) => missingSha256s.includes(sha256)), temporaryDirectoryPath), reporter));
|
|
157
|
+
const binary = await hotCodePush.apps.binaries.create(createOptions);
|
|
158
|
+
return { binary, upload: uploadedFiles };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// The CLI reads no project file for the identity.
|
|
162
|
+
function resolveBinaryIdentity(options) {
|
|
163
|
+
const binaryVersion = assertMissingParameter(options.binaryVersion, '--binary-version');
|
|
164
|
+
return {
|
|
165
|
+
binaryBuild: assertMissingParameter(options.binaryBuild, '--binary-build'),
|
|
166
|
+
binaryVersion,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function resolveFailedCreation(channelId, pipelineFailure) {
|
|
170
|
+
return {
|
|
171
|
+
binary: null,
|
|
172
|
+
channelId,
|
|
173
|
+
pipelineFailure,
|
|
174
|
+
skippedReason: null,
|
|
175
|
+
upload: NO_UPLOAD,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function resolveOfflineText(offlineCause, channelId) {
|
|
179
|
+
const causeText = offlineCause === 'offline-switch'
|
|
180
|
+
? 'HOTCODEPUSH_OFFLINE is set'
|
|
181
|
+
: 'not logged in';
|
|
182
|
+
const consequenceText = channelId === null
|
|
183
|
+
? ': it names no channel and takes no OTA updates until it is built with a token, and no binary was created'
|
|
184
|
+
: ' and no binary was created';
|
|
185
|
+
const fixText = offlineCause === 'offline-switch'
|
|
186
|
+
? ''
|
|
187
|
+
: '; run "hotcodepush login" or set HOTCODEPUSH_TOKEN';
|
|
188
|
+
return `${causeText}, so the build was made offline${consequenceText}${fixText}.`;
|
|
189
|
+
}
|