hotcodepush 0.1.0 → 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.
- package/dist/commands/app/create.js +1 -1
- package/dist/commands/app/delete.js +2 -2
- package/dist/commands/app/get.js +1 -1
- package/dist/commands/app/list.js +1 -1
- package/dist/commands/app/transfer.js +1 -1
- package/dist/commands/app/update.js +1 -1
- package/dist/commands/audience/get.js +2 -2
- package/dist/commands/audit-log/list.js +2 -2
- package/dist/commands/binary/create.js +11 -9
- package/dist/commands/binary/get.js +1 -1
- package/dist/commands/binary/list.js +2 -2
- package/dist/commands/bundle/delete.js +2 -2
- package/dist/commands/bundle/get.js +1 -1
- package/dist/commands/bundle/list.js +2 -2
- package/dist/commands/bundle/upload.js +7 -6
- package/dist/commands/channel/create.js +1 -1
- package/dist/commands/channel/delete.js +3 -3
- package/dist/commands/channel/get.js +1 -1
- package/dist/commands/channel/list.js +1 -1
- package/dist/commands/channel/pause.js +1 -1
- package/dist/commands/channel/resume.js +1 -1
- package/dist/commands/channel/update.js +1 -1
- package/dist/commands/checkout/create.js +1 -1
- package/dist/commands/completion.js +2 -1
- package/dist/commands/customer-portal-session/create.js +1 -1
- package/dist/commands/device/delete.js +2 -2
- package/dist/commands/device/get.js +1 -1
- package/dist/commands/device/list.js +3 -3
- package/dist/commands/device/probe.js +3 -3
- package/dist/commands/doctor.js +2 -1
- package/dist/commands/fingerprint/diff.js +3 -2
- package/dist/commands/fingerprint.js +3 -2
- package/dist/commands/init.js +4 -3
- package/dist/commands/invitation/accept.js +1 -1
- package/dist/commands/invitation/create.js +2 -2
- package/dist/commands/invitation/delete.js +2 -2
- package/dist/commands/invitation/list.js +1 -1
- package/dist/commands/limit/list.js +1 -1
- package/dist/commands/login.js +4 -153
- package/dist/commands/logout.js +7 -5
- package/dist/commands/member/delete.js +2 -2
- package/dist/commands/member/get.js +1 -1
- package/dist/commands/member/list.js +1 -1
- package/dist/commands/member/update.js +2 -2
- package/dist/commands/open.js +1 -1
- package/dist/commands/organization/create.js +1 -1
- package/dist/commands/organization/delete.js +2 -2
- package/dist/commands/organization/get.js +1 -1
- package/dist/commands/organization/list.js +1 -1
- package/dist/commands/organization/update.js +1 -1
- package/dist/commands/release/create.js +5 -5
- package/dist/commands/release/get.js +1 -1
- package/dist/commands/release/list.js +1 -1
- package/dist/commands/release/pause.js +1 -1
- package/dist/commands/release/resume.js +1 -1
- package/dist/commands/release/revoke.js +5 -4
- package/dist/commands/release/rollback.js +3 -3
- package/dist/commands/release/rollout.js +4 -4
- package/dist/commands/release/update.js +2 -2
- package/dist/commands/resource-file/write.js +8 -6
- package/dist/commands/signing-key/add.js +2 -2
- package/dist/commands/signing-key/create.js +2 -2
- package/dist/commands/signing-key/delete.js +1 -1
- package/dist/commands/signing-key/list.js +2 -2
- package/dist/commands/sso-provider/delete.js +1 -1
- package/dist/commands/sso-provider/get.js +1 -1
- package/dist/commands/sso-provider/set.js +2 -2
- package/dist/commands/sso-provider/verify.js +2 -2
- package/dist/commands/statistics/get.js +5 -5
- package/dist/commands/usage/get.js +1 -1
- package/dist/commands/usage/list.js +1 -1
- package/dist/commands/webhook/create.js +3 -3
- package/dist/commands/webhook/delete.js +1 -1
- package/dist/commands/webhook/get.js +2 -2
- package/dist/commands/webhook/list.js +1 -1
- package/dist/commands/webhook/test.js +1 -1
- package/dist/commands/webhook/update.js +3 -3
- package/dist/commands/webhook-delivery/list.js +2 -2
- package/dist/commands/webhook-delivery/redeliver.js +2 -2
- package/dist/commands/whoami.js +1 -1
- package/dist/completion/hotcodepush.fish +75 -75
- package/dist/completion/hotcodepush.zsh +75 -75
- package/dist/utils/audience.js +1 -1
- package/dist/utils/authentication.js +60 -0
- package/dist/utils/build-step.js +1 -1
- package/dist/utils/channel-fields.js +3 -3
- package/dist/utils/cli.js +5 -0
- package/dist/utils/command.js +4 -0
- package/dist/utils/diagnostics.js +20 -0
- package/dist/utils/error-mapping.js +10 -14
- package/dist/utils/login.js +153 -0
- package/dist/utils/output.js +2 -1
- package/dist/utils/release-conditions.js +3 -4
- package/dist/utils/release-output.js +1 -1
- package/dist/utils/resource-resolution.js +1 -1
- package/dist/utils/telemetry.js +3 -2
- package/dist/utils/update-notice.js +2 -1
- package/dist/utils/usage-month.js +1 -1
- package/dist/utils/user-config.js +8 -0
- package/dist/utils/webhook-resolution.js +1 -1
- package/package.json +3 -3
|
@@ -28,11 +28,11 @@ export const failurePolicyShape = {
|
|
|
28
28
|
export const channelFieldsShape = {
|
|
29
29
|
discoverable: booleanFlagSchema
|
|
30
30
|
.optional()
|
|
31
|
-
.describe("Whether the app's setChannel finds the channel by its name
|
|
31
|
+
.describe("Whether the app's setChannel finds the channel by its name. Pass false to turn it off."),
|
|
32
32
|
expiresAt: z
|
|
33
33
|
.string()
|
|
34
34
|
.optional()
|
|
35
|
-
.describe('When the preview channel expires, an ISO 8601 timestamp
|
|
35
|
+
.describe('When the preview channel expires, an ISO 8601 timestamp. Protected and default channels have none.'),
|
|
36
36
|
expiresIn: z
|
|
37
37
|
.string()
|
|
38
38
|
.regex(DURATION_PATTERN, 'Expected a number of days or hours such as 14d or 12h')
|
|
@@ -41,7 +41,7 @@ export const channelFieldsShape = {
|
|
|
41
41
|
...failurePolicyShape,
|
|
42
42
|
protected: booleanFlagSchema
|
|
43
43
|
.optional()
|
|
44
|
-
.describe('Whether only Owners and Admins change what the channel serves
|
|
44
|
+
.describe('Whether only Owners and Admins change what the channel serves. Pass false to turn it off.'),
|
|
45
45
|
};
|
|
46
46
|
export const channelFieldsClearShape = {
|
|
47
47
|
clearExpiresAt: z
|
package/dist/utils/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { stripVTControlCharacters } from 'node:util';
|
|
2
2
|
import { defineConfig, processConfig } from 'zodline';
|
|
3
|
+
import { ensureAuthentication } from './authentication.js';
|
|
3
4
|
import { loadCommands, resolveInvocation } from './command-registry.js';
|
|
4
5
|
import { isColorEnabled } from './environment.js';
|
|
5
6
|
import { printError, resolveCliError } from './error-mapping.js';
|
|
@@ -55,6 +56,10 @@ async function runCommandLine(commandRegistry, argv, meta) {
|
|
|
55
56
|
const { args, command, options } = isColorEnabled(process.stdout)
|
|
56
57
|
? processConfig(config, invocation.argv)
|
|
57
58
|
: processConfigWithoutColor(config, invocation.argv);
|
|
59
|
+
if (commandName !== undefined &&
|
|
60
|
+
commands[commandName]?.authentication === 'required') {
|
|
61
|
+
await ensureAuthentication(options);
|
|
62
|
+
}
|
|
58
63
|
await command.action(options, args);
|
|
59
64
|
return { cliError: undefined, commandName };
|
|
60
65
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
2
|
+
import { log } from '@clack/prompts';
|
|
3
|
+
import { isColorEnabled } from './environment.js';
|
|
4
|
+
// A diagnostic addresses the person, so it goes to stderr in the gutter the prompts use; stdout stays the command's result.
|
|
5
|
+
const STDERR = { output: process.stderr };
|
|
6
|
+
export function printError({ docsUrl, sentences }) {
|
|
7
|
+
const styledDocsUrl = isColorEnabled(process.stderr)
|
|
8
|
+
? styleText('dim', docsUrl, { validateStream: false })
|
|
9
|
+
: docsUrl;
|
|
10
|
+
log.error([...sentences, styledDocsUrl].join('\n'), STDERR);
|
|
11
|
+
}
|
|
12
|
+
export function printNotice(text) {
|
|
13
|
+
log.info(text, STDERR);
|
|
14
|
+
}
|
|
15
|
+
export function printSuccess(text) {
|
|
16
|
+
log.success(text, STDERR);
|
|
17
|
+
}
|
|
18
|
+
export function printWarning(text) {
|
|
19
|
+
log.warn(text, STDERR);
|
|
20
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { inspect, stripVTControlCharacters
|
|
1
|
+
import { inspect, stripVTControlCharacters } from 'node:util';
|
|
2
2
|
import { HotCodePushError } from '@hotcodepush/node';
|
|
3
3
|
import { ZodError } from 'zod';
|
|
4
4
|
import { ZodlineError } from 'zodline';
|
|
5
5
|
import { ERRORS_DOCS_URL } from '../config/consts.js';
|
|
6
|
-
import {
|
|
6
|
+
import { printError as printErrorDiagnostic } from './diagnostics.js';
|
|
7
7
|
import { ApiError, CliError, InvalidParameterError, UnexpectedError, UnexpectedResponseError, } from './errors.js';
|
|
8
8
|
import { resolveFlagName } from './flag-name.js';
|
|
9
9
|
import { printJson } from './output.js';
|
|
@@ -16,16 +16,12 @@ export function buildErrorJson({ code, fix, message }) {
|
|
|
16
16
|
export function resolveSentence(message) {
|
|
17
17
|
return SENTENCE_END_PATTERN.test(message) ? message : `${message}.`;
|
|
18
18
|
}
|
|
19
|
+
export function resolveErrorDocsUrl({ code }) {
|
|
20
|
+
return `${ERRORS_DOCS_URL}#${code}`;
|
|
21
|
+
}
|
|
19
22
|
// An API error message says what to do itself, so it has no fix.
|
|
20
|
-
export function
|
|
21
|
-
|
|
22
|
-
const styledSentence = options.isColorEnabled
|
|
23
|
-
? styleText('red', sentence, { validateStream: false })
|
|
24
|
-
: sentence;
|
|
25
|
-
return [
|
|
26
|
-
fix === null ? styledSentence : `${styledSentence} ${fix}`,
|
|
27
|
-
`${ERRORS_DOCS_URL}#${code}`,
|
|
28
|
-
];
|
|
23
|
+
export function resolveErrorSentences({ fix, message }) {
|
|
24
|
+
return fix === null ? [message] : [resolveSentence(message), fix];
|
|
29
25
|
}
|
|
30
26
|
export function printError(cliError, { isJson, isVerbose }) {
|
|
31
27
|
if (isVerbose) {
|
|
@@ -35,10 +31,10 @@ export function printError(cliError, { isJson, isVerbose }) {
|
|
|
35
31
|
printJson(buildErrorJson(cliError));
|
|
36
32
|
}
|
|
37
33
|
else {
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
printErrorDiagnostic({
|
|
35
|
+
docsUrl: resolveErrorDocsUrl(cliError),
|
|
36
|
+
sentences: resolveErrorSentences(cliError),
|
|
40
37
|
});
|
|
41
|
-
process.stderr.write(`${errorLines.join('\n')}\n`);
|
|
42
38
|
}
|
|
43
39
|
}
|
|
44
40
|
// A response without a code, such as a proxy HTML page, is not an error from the API catalog.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { setTimeout } from 'node:timers/promises';
|
|
2
|
+
import { CLI_CLIENT_ID } from '../config/consts.js';
|
|
3
|
+
import { createApiAuthClient, fetchSession, resolveResponseData, } from './auth-client.js';
|
|
4
|
+
import { openBrowser } from './browser.js';
|
|
5
|
+
import { printNotice } from './diagnostics.js';
|
|
6
|
+
import { isInteractive } from './environment.js';
|
|
7
|
+
import { resolveApiError } from './error-mapping.js';
|
|
8
|
+
import { LoginDeniedError, LoginExpiredError, NotLoggedInError, } from './errors.js';
|
|
9
|
+
import { writeToken } from './token-store.js';
|
|
10
|
+
import { readUserConfig, writeStoredSession, writeUserConfig, } from './user-config.js';
|
|
11
|
+
// RFC 8628, section 3.2: without an interval from the server, a client polls every five seconds.
|
|
12
|
+
const DEFAULT_INTERVAL_SECONDS = 5;
|
|
13
|
+
const DEVICE_CODE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
|
|
14
|
+
// RFC 8628, section 3.5: each slow_down adds five seconds to this and every later interval.
|
|
15
|
+
const SLOW_DOWN_INCREMENT_SECONDS = 5;
|
|
16
|
+
// The page and the code go to stderr, so the stdout of the calling command stays its own.
|
|
17
|
+
export async function logIn(options) {
|
|
18
|
+
const authClient = createApiAuthClient();
|
|
19
|
+
const sessionToken = await fetchSessionToken(authClient, isInteractive(options));
|
|
20
|
+
const { session, user } = await fetchSession(createApiAuthClient(sessionToken));
|
|
21
|
+
writeToken(sessionToken);
|
|
22
|
+
deletePendingDeviceAuthorization();
|
|
23
|
+
writeStoredSession(session);
|
|
24
|
+
return { email: user.email, id: user.id, name: user.name };
|
|
25
|
+
}
|
|
26
|
+
// A request without an answer or with a transient status is polled again, since the person may be approving meanwhile.
|
|
27
|
+
async function fetchApprovedSessionToken(authClient, deviceAuthorization) {
|
|
28
|
+
let currentIntervalSeconds = deviceAuthorization.intervalSeconds;
|
|
29
|
+
for (;;) {
|
|
30
|
+
await setTimeout(currentIntervalSeconds * 1000);
|
|
31
|
+
if (isDeviceAuthorizationExpired(deviceAuthorization)) {
|
|
32
|
+
throw new LoginExpiredError();
|
|
33
|
+
}
|
|
34
|
+
const response = await fetchDeviceToken(authClient, deviceAuthorization.deviceCode).catch(() => undefined);
|
|
35
|
+
if (response === undefined) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const { data, error } = response;
|
|
39
|
+
if (error === null) {
|
|
40
|
+
return data.access_token;
|
|
41
|
+
}
|
|
42
|
+
switch (error.error) {
|
|
43
|
+
case 'access_denied':
|
|
44
|
+
throw new LoginDeniedError();
|
|
45
|
+
case 'authorization_pending':
|
|
46
|
+
break;
|
|
47
|
+
case 'expired_token':
|
|
48
|
+
throw new LoginExpiredError();
|
|
49
|
+
case 'slow_down':
|
|
50
|
+
currentIntervalSeconds += SLOW_DOWN_INCREMENT_SECONDS;
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
if (!isTransientStatus(error.status)) {
|
|
54
|
+
throw resolveApiError(error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function fetchDeviceAuthorization(authClient) {
|
|
60
|
+
const { device_code, expires_in, interval, user_code, verification_uri_complete, } = resolveResponseData(await authClient.device.code({ client_id: CLI_CLIENT_ID }));
|
|
61
|
+
return {
|
|
62
|
+
deviceCode: device_code,
|
|
63
|
+
expiresAt: new Date(Date.now() + expires_in * 1000).toISOString(),
|
|
64
|
+
intervalSeconds: interval,
|
|
65
|
+
userCode: user_code,
|
|
66
|
+
verificationUrl: verification_uri_complete,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function fetchDeviceToken(authClient, deviceCode) {
|
|
70
|
+
return authClient.device.token({
|
|
71
|
+
client_id: CLI_CLIENT_ID,
|
|
72
|
+
device_code: deviceCode,
|
|
73
|
+
grant_type: DEVICE_CODE_GRANT_TYPE,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
// A code that a non-interactive login kept is redeemed first, with one token request.
|
|
77
|
+
// While pending it is shown again. Denied, expired or unknown, it gives way to a new one.
|
|
78
|
+
async function fetchSessionToken(authClient, isLoginInteractive) {
|
|
79
|
+
const pendingDeviceAuthorization = readPendingDeviceAuthorization();
|
|
80
|
+
if (pendingDeviceAuthorization !== undefined &&
|
|
81
|
+
!isDeviceAuthorizationExpired(pendingDeviceAuthorization)) {
|
|
82
|
+
const { data, error } = await fetchDeviceToken(authClient, pendingDeviceAuthorization.deviceCode);
|
|
83
|
+
if (error === null) {
|
|
84
|
+
return data.access_token;
|
|
85
|
+
}
|
|
86
|
+
switch (error.error) {
|
|
87
|
+
case 'access_denied':
|
|
88
|
+
case 'expired_token':
|
|
89
|
+
case 'invalid_grant':
|
|
90
|
+
break;
|
|
91
|
+
case 'authorization_pending':
|
|
92
|
+
case 'slow_down':
|
|
93
|
+
return fetchSessionTokenOnApproval(authClient, pendingDeviceAuthorization, isLoginInteractive);
|
|
94
|
+
default:
|
|
95
|
+
throw resolveApiError(error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
deletePendingDeviceAuthorization();
|
|
99
|
+
return fetchSessionTokenOnApproval(authClient, await fetchDeviceAuthorization(authClient), isLoginInteractive);
|
|
100
|
+
}
|
|
101
|
+
// The code is kept, so the next login redeems one that ended before the approval.
|
|
102
|
+
async function fetchSessionTokenOnApproval(authClient, deviceAuthorization, isLoginInteractive) {
|
|
103
|
+
writePendingDeviceAuthorization(deviceAuthorization);
|
|
104
|
+
if (!isLoginInteractive) {
|
|
105
|
+
throw new NotLoggedInError(deviceAuthorization);
|
|
106
|
+
}
|
|
107
|
+
printNotice(`Open ${deviceAuthorization.verificationUrl} and approve the code ${deviceAuthorization.userCode}.\nWaiting for the approval…`);
|
|
108
|
+
await openBrowser(deviceAuthorization.verificationUrl);
|
|
109
|
+
return fetchApprovedSessionToken(authClient, deviceAuthorization);
|
|
110
|
+
}
|
|
111
|
+
function deletePendingDeviceAuthorization() {
|
|
112
|
+
const userConfig = readUserConfig();
|
|
113
|
+
if (userConfig.pendingDeviceCode === undefined) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
delete userConfig.pendingDeviceCode;
|
|
117
|
+
delete userConfig.pendingDeviceCodeExpiresAt;
|
|
118
|
+
delete userConfig.pendingUserCode;
|
|
119
|
+
delete userConfig.pendingVerificationUrl;
|
|
120
|
+
writeUserConfig(userConfig);
|
|
121
|
+
}
|
|
122
|
+
function isDeviceAuthorizationExpired({ expiresAt, }) {
|
|
123
|
+
return new Date(expiresAt) <= new Date();
|
|
124
|
+
}
|
|
125
|
+
// The statuses the Node client also retries: a timeout, the rate limit or a server error.
|
|
126
|
+
function isTransientStatus(status) {
|
|
127
|
+
return status === 408 || status === 429 || status >= 500;
|
|
128
|
+
}
|
|
129
|
+
function readPendingDeviceAuthorization() {
|
|
130
|
+
const { pendingDeviceCode, pendingDeviceCodeExpiresAt, pendingUserCode, pendingVerificationUrl, } = readUserConfig();
|
|
131
|
+
if (pendingDeviceCode === undefined ||
|
|
132
|
+
pendingDeviceCodeExpiresAt === undefined ||
|
|
133
|
+
pendingUserCode === undefined ||
|
|
134
|
+
pendingVerificationUrl === undefined) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
deviceCode: pendingDeviceCode,
|
|
139
|
+
expiresAt: pendingDeviceCodeExpiresAt,
|
|
140
|
+
intervalSeconds: DEFAULT_INTERVAL_SECONDS,
|
|
141
|
+
userCode: pendingUserCode,
|
|
142
|
+
verificationUrl: pendingVerificationUrl,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function writePendingDeviceAuthorization({ deviceCode, expiresAt, userCode, verificationUrl, }) {
|
|
146
|
+
writeUserConfig({
|
|
147
|
+
...readUserConfig(),
|
|
148
|
+
pendingDeviceCode: deviceCode,
|
|
149
|
+
pendingDeviceCodeExpiresAt: expiresAt,
|
|
150
|
+
pendingUserCode: userCode,
|
|
151
|
+
pendingVerificationUrl: verificationUrl,
|
|
152
|
+
});
|
|
153
|
+
}
|
package/dist/utils/output.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { printWarning } from './diagnostics.js';
|
|
1
2
|
export function printDetails(details) {
|
|
2
3
|
const labelWidth = Math.max(...details.map(([label]) => label.length));
|
|
3
4
|
for (const [label, value] of details) {
|
|
@@ -10,7 +11,7 @@ export function printJson(data) {
|
|
|
10
11
|
// The API warns when a computed preview is likely not what was meant.
|
|
11
12
|
export function printWarnings(warnings) {
|
|
12
13
|
for (const { message } of warnings) {
|
|
13
|
-
|
|
14
|
+
printWarning(message);
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
17
|
export function printTable({ emptyText, headers, nextOffset, rows, }) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isValidAttributeKey, isValidAttributeValue, } from '@hotcodepush/protocol';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { InvalidParameterError } from './errors.js';
|
|
4
4
|
export const conditionOptionShape = {
|
|
@@ -30,14 +30,13 @@ export function buildAudienceQuery(options, fingerprint, rolloutPercent) {
|
|
|
30
30
|
rolloutPercent,
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
-
// Matches the index: attribute values and device ids are hashed, and all device ids share one condition.
|
|
34
33
|
export function buildReleaseConditions(options, fingerprint) {
|
|
35
34
|
const deviceIds = options.device ?? [];
|
|
36
35
|
return [
|
|
37
36
|
...resolveAttributePairs(options.attribute).map(({ key, value }) => ({
|
|
38
37
|
key,
|
|
39
38
|
type: 'attribute',
|
|
40
|
-
|
|
39
|
+
value,
|
|
41
40
|
})),
|
|
42
41
|
...resolveList(options.binaryVersion).map((range) => ({
|
|
43
42
|
range,
|
|
@@ -47,7 +46,7 @@ export function buildReleaseConditions(options, fingerprint) {
|
|
|
47
46
|
? []
|
|
48
47
|
: [
|
|
49
48
|
{
|
|
50
|
-
|
|
49
|
+
ids: deviceIds,
|
|
51
50
|
type: 'device',
|
|
52
51
|
},
|
|
53
52
|
]),
|
|
@@ -8,6 +8,6 @@ export function resolveProgressingText({ progression }) {
|
|
|
8
8
|
}
|
|
9
9
|
function resolveLiveText(release) {
|
|
10
10
|
return release.liveAt === null
|
|
11
|
-
? '
|
|
11
|
+
? '. Not live yet, and "release get" shows when it is'
|
|
12
12
|
: `, live since ${release.liveAt}`;
|
|
13
13
|
}
|
|
@@ -9,7 +9,7 @@ export const channelOptionShape = {
|
|
|
9
9
|
channel: z
|
|
10
10
|
.string()
|
|
11
11
|
.optional()
|
|
12
|
-
.describe("The channel, by id or name
|
|
12
|
+
.describe("The channel, by id or name. Defaults to hotcodepush.json's channel."),
|
|
13
13
|
};
|
|
14
14
|
// A name is looked up in `--organization` when given, otherwise in every organization of the user.
|
|
15
15
|
export async function fetchAppId(hotCodePush, options, projectConfig) {
|
package/dist/utils/telemetry.js
CHANGED
|
@@ -3,13 +3,14 @@ import { hostname } from 'node:os';
|
|
|
3
3
|
import { basename } from 'node:path/posix';
|
|
4
4
|
import { setTimeout } from 'node:timers/promises';
|
|
5
5
|
import { PACKAGE_JSON, POSTHOG_HOST, POSTHOG_PROJECT_KEY, SENTRY_DSN, } from '../config/consts.js';
|
|
6
|
+
import { printNotice } from './diagnostics.js';
|
|
6
7
|
import { isCi } from './environment.js';
|
|
7
8
|
import { ReportedFailureError, UnexpectedError, UnexpectedResponseError, } from './errors.js';
|
|
8
9
|
import { ignoreUserConfigErrors, readUserConfig, writeUserConfig, } from './user-config.js';
|
|
9
10
|
const COMMAND_FINISHED_EVENT = 'cli.command_finished';
|
|
10
11
|
// Matches `at <function> (<file>:<line>:<column>)`, or the same without the function and parentheses.
|
|
11
12
|
const STACK_FRAME_PATTERN = /^\s*at (?:(.+?) \()?(.+?):(\d+):(\d+)\)?$/;
|
|
12
|
-
const TELEMETRY_NOTICE = 'HotCodePush collects anonymous usage and error data to improve the CLI
|
|
13
|
+
const TELEMETRY_NOTICE = 'HotCodePush collects anonymous usage and error data to improve the CLI. Set HOTCODEPUSH_TELEMETRY_DISABLED=1 to opt out.';
|
|
13
14
|
// The longest telemetry may delay the exit, requests included.
|
|
14
15
|
const TELEMETRY_TIMEOUT_MS = 1500;
|
|
15
16
|
// Only the release build has keys, so every other build reports nothing.
|
|
@@ -27,7 +28,7 @@ export function printTelemetryNotice() {
|
|
|
27
28
|
if (userConfig.telemetryNoticeShownAt !== undefined) {
|
|
28
29
|
return;
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
+
printNotice(TELEMETRY_NOTICE);
|
|
31
32
|
writeUserConfig({
|
|
32
33
|
...userConfig,
|
|
33
34
|
telemetryNoticeShownAt: new Date().toISOString(),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PACKAGE_JSON } from '../config/consts.js';
|
|
2
|
+
import { printNotice } from './diagnostics.js';
|
|
2
3
|
import { isCi } from './environment.js';
|
|
3
4
|
import { ignoreUserConfigErrors, readUserConfig, writeUserConfig, } from './user-config.js';
|
|
4
5
|
const LATEST_VERSION_URL = `https://registry.npmjs.org/${PACKAGE_JSON.name}/latest`;
|
|
@@ -22,7 +23,7 @@ export function printUpdateNotice() {
|
|
|
22
23
|
const { latestKnownVersion } = readUserConfig();
|
|
23
24
|
if (latestKnownVersion !== undefined &&
|
|
24
25
|
isNewerVersion(latestKnownVersion, PACKAGE_JSON.version)) {
|
|
25
|
-
|
|
26
|
+
printNotice(`HotCodePush CLI ${latestKnownVersion} is available. You run ${PACKAGE_JSON.version}.`);
|
|
26
27
|
}
|
|
27
28
|
});
|
|
28
29
|
}
|
|
@@ -3,7 +3,7 @@ export const monthOptionShape = {
|
|
|
3
3
|
month: z
|
|
4
4
|
.string()
|
|
5
5
|
.optional()
|
|
6
|
-
.describe('The month, YYYY-MM
|
|
6
|
+
.describe('The month, YYYY-MM. Defaults to the current one.'),
|
|
7
7
|
};
|
|
8
8
|
export function resolveMonthText(month) {
|
|
9
9
|
return new Date(`${month}-01T00:00:00.000Z`).toLocaleString('en-US', {
|
|
@@ -31,6 +31,14 @@ export function resolveConfigDirectoryPath() {
|
|
|
31
31
|
: process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
|
|
32
32
|
return join(baseDirectoryPath, CONFIG_DIRECTORY_NAME);
|
|
33
33
|
}
|
|
34
|
+
// The expiry is the local hint that a session may have ended; the API's answer is the fact.
|
|
35
|
+
export function writeStoredSession({ expiresAt, id }) {
|
|
36
|
+
writeUserConfig({
|
|
37
|
+
...readUserConfig(),
|
|
38
|
+
sessionExpiresAt: new Date(expiresAt).toISOString(),
|
|
39
|
+
sessionId: id,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
34
42
|
export function writeUserConfig(userConfig) {
|
|
35
43
|
const filePath = resolveConfigFilePath();
|
|
36
44
|
mkdirSync(resolveConfigDirectoryPath(), { recursive: true });
|
|
@@ -7,7 +7,7 @@ export const webhookAppOptionShape = {
|
|
|
7
7
|
app: z
|
|
8
8
|
.string()
|
|
9
9
|
.optional()
|
|
10
|
-
.describe("The app, by id or name, whose events alone the webhook receives
|
|
10
|
+
.describe("The app, by id or name, whose events alone the webhook receives. Defaults to every app's and the organization's events, never to hotcodepush.json's app."),
|
|
11
11
|
};
|
|
12
12
|
export const webhookEventOptionShape = {
|
|
13
13
|
event: z
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hotcodepush",
|
|
3
3
|
"description": "The HotCodePush CLI: set up, release and manage over-the-air (OTA) updates from the terminal and CI",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://hotcodepush.com",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@clack/prompts": "1.8.1",
|
|
49
|
-
"@hotcodepush/node": "https://pkg.pr.new/hotcodepush-team/hotcodepush-node/@hotcodepush/node@
|
|
50
|
-
"@hotcodepush/protocol": "https://pkg.pr.new/hotcodepush-team/protocol/@hotcodepush/protocol@
|
|
49
|
+
"@hotcodepush/node": "https://pkg.pr.new/hotcodepush-team/hotcodepush-node/@hotcodepush/node@9e671df",
|
|
50
|
+
"@hotcodepush/protocol": "https://pkg.pr.new/hotcodepush-team/protocol/@hotcodepush/protocol@5b27c65",
|
|
51
51
|
"@napi-rs/keyring": "2.1.0",
|
|
52
52
|
"better-auth": "1.7.12",
|
|
53
53
|
"fast-xml-parser": "5.11.2",
|