@mondaydotcomorg/z2h-cli 0.25.4 → 0.25.7
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/auth.d.ts +2 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +2 -1
- package/dist/commands/__tests__/build.test.d.ts +2 -0
- package/dist/commands/__tests__/build.test.d.ts.map +1 -0
- package/dist/commands/backend.d.ts.map +1 -1
- package/dist/commands/backend.js +7 -1
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +10 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +42 -19
- package/dist/commands/grant.d.ts.map +1 -1
- package/dist/commands/grant.js +8 -7
- package/dist/esm/auth.d.ts +2 -1
- package/dist/esm/auth.d.ts.map +1 -1
- package/dist/esm/auth.mjs +2 -1
- package/dist/esm/commands/__tests__/build.test.d.ts +2 -0
- package/dist/esm/commands/__tests__/build.test.d.ts.map +1 -0
- package/dist/esm/commands/backend.d.ts.map +1 -1
- package/dist/esm/commands/backend.mjs +7 -1
- package/dist/esm/commands/build.d.ts.map +1 -1
- package/dist/esm/commands/build.mjs +10 -0
- package/dist/esm/commands/deploy.d.ts.map +1 -1
- package/dist/esm/commands/deploy.mjs +43 -20
- package/dist/esm/commands/grant.d.ts.map +1 -1
- package/dist/esm/commands/grant.mjs +8 -7
- package/dist/esm/index.mjs +41 -140
- package/dist/esm/types.d.ts +23 -0
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/util/__tests__/errors.test.d.ts +2 -0
- package/dist/esm/util/__tests__/errors.test.d.ts.map +1 -0
- package/dist/esm/util/__tests__/run-command.test.d.ts +2 -0
- package/dist/esm/util/__tests__/run-command.test.d.ts.map +1 -0
- package/dist/esm/util/auth/auth-store.d.ts +0 -4
- package/dist/esm/util/auth/auth-store.d.ts.map +1 -1
- package/dist/esm/util/auth/auth-store.mjs +2 -9
- package/dist/esm/util/auth/broker-credential-provider.d.ts.map +1 -1
- package/dist/esm/util/auth/broker-credential-provider.mjs +16 -16
- package/dist/esm/util/broker/app.d.ts.map +1 -1
- package/dist/esm/util/broker/app.mjs +17 -9
- package/dist/esm/util/errors.d.ts +48 -0
- package/dist/esm/util/errors.d.ts.map +1 -0
- package/dist/esm/util/errors.mjs +91 -0
- package/dist/esm/util/git/repo.d.ts +0 -3
- package/dist/esm/util/git/repo.d.ts.map +1 -1
- package/dist/esm/util/git/repo.mjs +4 -9
- package/dist/esm/util/logger.d.ts +4 -1
- package/dist/esm/util/logger.d.ts.map +1 -1
- package/dist/esm/util/logger.mjs +11 -3
- package/dist/esm/util/run-command.d.ts +16 -0
- package/dist/esm/util/run-command.d.ts.map +1 -0
- package/dist/esm/util/run-command.mjs +47 -0
- package/dist/esm/util/s3/upload.d.ts.map +1 -1
- package/dist/esm/util/s3/upload.mjs +22 -16
- package/dist/esm/util/tracker.d.ts +9 -2
- package/dist/esm/util/tracker.d.ts.map +1 -1
- package/dist/esm/util/tracker.mjs +18 -15
- package/dist/index.js +40 -139
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/util/__tests__/errors.test.d.ts +2 -0
- package/dist/util/__tests__/errors.test.d.ts.map +1 -0
- package/dist/util/__tests__/run-command.test.d.ts +2 -0
- package/dist/util/__tests__/run-command.test.d.ts.map +1 -0
- package/dist/util/auth/auth-store.d.ts +0 -4
- package/dist/util/auth/auth-store.d.ts.map +1 -1
- package/dist/util/auth/auth-store.js +2 -10
- package/dist/util/auth/broker-credential-provider.d.ts.map +1 -1
- package/dist/util/auth/broker-credential-provider.js +16 -16
- package/dist/util/broker/app.d.ts.map +1 -1
- package/dist/util/broker/app.js +17 -9
- package/dist/util/errors.d.ts +48 -0
- package/dist/util/errors.d.ts.map +1 -0
- package/dist/util/errors.js +102 -0
- package/dist/util/git/repo.d.ts +0 -3
- package/dist/util/git/repo.d.ts.map +1 -1
- package/dist/util/git/repo.js +4 -10
- package/dist/util/logger.d.ts +4 -1
- package/dist/util/logger.d.ts.map +1 -1
- package/dist/util/logger.js +11 -3
- package/dist/util/run-command.d.ts +16 -0
- package/dist/util/run-command.d.ts.map +1 -0
- package/dist/util/run-command.js +49 -0
- package/dist/util/s3/upload.d.ts.map +1 -1
- package/dist/util/s3/upload.js +22 -16
- package/dist/util/tracker.d.ts +9 -2
- package/dist/util/tracker.d.ts.map +1 -1
- package/dist/util/tracker.js +19 -14
- package/handler-api/backend-runner/channels/llm-channel/llm.channel.ts +3 -2
- package/handler-api/backend-runner/channels/snowflake-channel/run-query.ts +7 -6
- package/handler-api/backend-runner/channels/snowflake-channel/snowflake.channel.ts +1 -1
- package/handler-api/backend-runner/channels/snowflake-channel/stateless-part-b.ts +1 -4
- package/package.json +2 -2
- package/src/auth.ts +2 -1
- package/src/commands/__tests__/backend-deploy.test.ts +18 -0
- package/src/commands/__tests__/build.test.ts +83 -0
- package/src/commands/__tests__/deploy.test.ts +107 -0
- package/src/commands/backend.ts +6 -1
- package/src/commands/build.ts +10 -0
- package/src/commands/deploy.ts +42 -20
- package/src/commands/grant.ts +8 -7
- package/src/index.ts +84 -133
- package/src/types.ts +47 -0
- package/src/util/__tests__/errors.test.ts +90 -0
- package/src/util/__tests__/run-command.test.ts +98 -0
- package/src/util/auth/__tests__/auth-store.test.ts +2 -1
- package/src/util/auth/__tests__/broker-credential-provider.test.ts +44 -8
- package/src/util/auth/auth-store.ts +1 -7
- package/src/util/auth/broker-credential-provider.ts +18 -16
- package/src/util/broker/__tests__/app.test.ts +23 -20
- package/src/util/broker/app.ts +16 -9
- package/src/util/errors.ts +98 -0
- package/src/util/git/__tests__/repo.test.ts +5 -3
- package/src/util/git/repo.ts +3 -9
- package/src/util/logger.ts +16 -3
- package/src/util/run-command.ts +59 -0
- package/src/util/s3/upload.ts +27 -22
- package/src/util/tracker.ts +19 -19
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base for every error the CLI classifies. runCommand in ./run-command.ts reads `reason`
|
|
3
|
+
* and `data` straight off the error, so the throw site is the only place that needs to know
|
|
4
|
+
* why it failed. Nothing re-derives it from the error type at the top level.
|
|
5
|
+
*
|
|
6
|
+
* `exitCode` lets a throw site override the CLI's --help contract, 1 for user-resolvable
|
|
7
|
+
* and 2 for system failures, per error instead of per command. Nothing sets it yet. The
|
|
8
|
+
* command table still pins exit codes, which keeps today's behavior.
|
|
9
|
+
*/
|
|
10
|
+
class Z2hError extends Error {
|
|
11
|
+
reason;
|
|
12
|
+
data;
|
|
13
|
+
exitCode;
|
|
14
|
+
constructor(message, reason, data, exitCode = 1) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.reason = reason;
|
|
17
|
+
this.data = data;
|
|
18
|
+
this.exitCode = exitCode;
|
|
19
|
+
this.name = new.target.name;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Tags a deploy/preview stage failure with a bounded reason code instead of a freeform message. */
|
|
23
|
+
class DeployError extends Z2hError {
|
|
24
|
+
reason;
|
|
25
|
+
constructor(reason, message, data) {
|
|
26
|
+
super(message, reason, data);
|
|
27
|
+
this.reason = reason;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Tags a grant/revoke/transfer-owner failure with a bounded reason code instead of a freeform message.
|
|
32
|
+
* `reason` also accepts a raw HTTP status, which gets mapped to a bounded reason internally.
|
|
33
|
+
*/
|
|
34
|
+
class GrantError extends Z2hError {
|
|
35
|
+
constructor(reason, message, data) {
|
|
36
|
+
super(message, typeof reason === 'number' ? grantReasonForStatus(reason) : reason, data);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** No z2h JWT found — see auth-store.ts for the discovery order (env var, then ~/.zth/auth.json). */
|
|
40
|
+
class Z2hAuthMissingError extends Z2hError {
|
|
41
|
+
authFilePath;
|
|
42
|
+
constructor(authFilePath) {
|
|
43
|
+
super(`No Z2H deploy token found at ${authFilePath}. Please run 'z2h-cli generate-z2h-token' command.`, 'read_auth_token');
|
|
44
|
+
this.authFilePath = authFilePath;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** A git pull diverged or a push was rejected; `reason` distinguishes which stage hit it. */
|
|
48
|
+
class GitConflictError extends Z2hError {
|
|
49
|
+
constructor(message, reason = 'git_error') {
|
|
50
|
+
super(message, reason);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Maps an HTTP status to a bounded grant/revoke/transfer-owner failure reason. */
|
|
54
|
+
function grantReasonForStatus(status) {
|
|
55
|
+
// 401 is an expired/invalid token (re-auth fixes it); 403 is a real permission denial.
|
|
56
|
+
if (status === 401) {
|
|
57
|
+
return 'auth';
|
|
58
|
+
}
|
|
59
|
+
if (status === 403) {
|
|
60
|
+
return 'forbidden';
|
|
61
|
+
}
|
|
62
|
+
if (status === 404) {
|
|
63
|
+
return 'not_found';
|
|
64
|
+
}
|
|
65
|
+
if (status >= 500) {
|
|
66
|
+
return 'server_error';
|
|
67
|
+
}
|
|
68
|
+
return 'unknown';
|
|
69
|
+
}
|
|
70
|
+
/** Tags a failure with `reason`, unless `err` is already classified; `signal` overrides `reason` for a killed child process. */
|
|
71
|
+
function tagDeployError(reason, err) {
|
|
72
|
+
if (err instanceof Z2hError) {
|
|
73
|
+
return err;
|
|
74
|
+
}
|
|
75
|
+
const signal = err instanceof Error ? err.signal : undefined;
|
|
76
|
+
return new DeployError(signal ? 'interrupted' : reason, messageOf(err));
|
|
77
|
+
}
|
|
78
|
+
/** Reads the bounded reason off a classified error; anything unclassified is `unknown`. */
|
|
79
|
+
function reasonOf(err) {
|
|
80
|
+
return err instanceof Z2hError ? err.reason : 'unknown';
|
|
81
|
+
}
|
|
82
|
+
/** Extra telemetry dimensions the throw site attached, if any. */
|
|
83
|
+
function dataOf(err) {
|
|
84
|
+
return err instanceof Z2hError ? err.data ?? {} : {};
|
|
85
|
+
}
|
|
86
|
+
/** Safe message for any thrown value. A non-Error throw must not break the error path. */
|
|
87
|
+
function messageOf(err) {
|
|
88
|
+
return err instanceof Error ? err.message : String(err);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { DeployError, GitConflictError, GrantError, Z2hAuthMissingError, Z2hError, dataOf, grantReasonForStatus, messageOf, reasonOf, tagDeployError };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../../../src/util/git/repo.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../../../src/util/git/repo.ts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAKD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOzE;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAI3E;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAK/D;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5G;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAclE;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B7F;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB3F;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAO7D;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG5D"}
|
|
@@ -3,6 +3,7 @@ import fs from 'fs-extra';
|
|
|
3
3
|
import { execa } from 'execa';
|
|
4
4
|
import { BUCKET_WRITE_NAME } from '../../constants.mjs';
|
|
5
5
|
import { mfAppName } from '../app-name.mjs';
|
|
6
|
+
import { GitConflictError, DeployError, messageOf } from '../errors.mjs';
|
|
6
7
|
|
|
7
8
|
function execGit(args, dir, env) {
|
|
8
9
|
return execa('git', args, { cwd: dir, env });
|
|
@@ -12,12 +13,6 @@ async function execGitLines(args, dir, env) {
|
|
|
12
13
|
.then(r => r.stdout.split('\n').filter(Boolean))
|
|
13
14
|
.catch(() => []);
|
|
14
15
|
}
|
|
15
|
-
class GitConflictError extends Error {
|
|
16
|
-
constructor(message) {
|
|
17
|
-
super(message);
|
|
18
|
-
this.name = 'GitConflictError';
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
16
|
const REQUIRED_GITIGNORE_ENTRIES = ['node_modules/', '.zth/', 'build/'];
|
|
22
17
|
const LOCAL_HOOKS_DIR = '.zth/hooks';
|
|
23
18
|
function defaultS3Remote(app) {
|
|
@@ -119,9 +114,9 @@ async function pushToRemote(dir, env) {
|
|
|
119
114
|
}
|
|
120
115
|
if (isRejected) {
|
|
121
116
|
const info = await gatherConflictInfo(dir, gitEnv);
|
|
122
|
-
throw new GitConflictError(`Push rejected: Git conflict detected: ${JSON.stringify(info)}
|
|
117
|
+
throw new GitConflictError(`Push rejected: Git conflict detected: ${JSON.stringify(info)}`, 'push_error');
|
|
123
118
|
}
|
|
124
|
-
throw err;
|
|
119
|
+
throw new DeployError('push_error', messageOf(err));
|
|
125
120
|
}
|
|
126
121
|
}
|
|
127
122
|
async function remoteHead(dir) {
|
|
@@ -176,4 +171,4 @@ async function ensureGitignore(dir) {
|
|
|
176
171
|
await fs.outputFile(gitignorePath, content + separator + missing.join('\n') + '\n');
|
|
177
172
|
}
|
|
178
173
|
|
|
179
|
-
export {
|
|
174
|
+
export { cloneRepo, defaultS3Remote, getRemoteUrl, guardCleanTree, initRepo, localHead, pullAndResolve, pushToRemote, remoteHead, safeBackupIfDirty };
|
|
@@ -17,6 +17,9 @@ export declare function output(human: string, data: unknown): void;
|
|
|
17
17
|
/**
|
|
18
18
|
* Emit a structured failure payload (JSON mode) or a red error line (human mode),
|
|
19
19
|
* then exit with the given code.
|
|
20
|
+
*
|
|
21
|
+
* `track: false` skips the telemetry event but still prints and exits. Commands with no
|
|
22
|
+
* success event pass it, so their failures don't skew per-command error rates.
|
|
20
23
|
*/
|
|
21
|
-
export declare function fail(message: string, payload: Record<string, unknown>, exitCode?: number): Promise<never>;
|
|
24
|
+
export declare function fail(message: string, payload: Record<string, unknown>, exitCode?: number, track?: boolean): Promise<never>;
|
|
22
25
|
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/util/logger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/util/logger.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,MAAM,CAAC;AAI1C,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAEpD;AAED,wBAAgB,aAAa,IAAI,UAAU,CAE1C;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAItC;AAED,wBAAgB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAItC;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAI1C;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAMzD;AAED;;;;;;GAMG;AACH,wBAAsB,IAAI,CACxB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,SAAI,EACZ,KAAK,UAAO,GACX,OAAO,CAAC,KAAK,CAAC,CAehB"}
|
package/dist/esm/util/logger.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
|
+
import { Z2hCliEvents } from '@mondaydotcomorg/z2h-shared-utils/observability';
|
|
2
3
|
import { trackEvent } from './tracker.mjs';
|
|
3
4
|
|
|
4
5
|
let mode = 'human';
|
|
@@ -43,10 +44,15 @@ function output(human, data) {
|
|
|
43
44
|
/**
|
|
44
45
|
* Emit a structured failure payload (JSON mode) or a red error line (human mode),
|
|
45
46
|
* then exit with the given code.
|
|
47
|
+
*
|
|
48
|
+
* `track: false` skips the telemetry event but still prints and exits. Commands with no
|
|
49
|
+
* success event pass it, so their failures don't skew per-command error rates.
|
|
46
50
|
*/
|
|
47
|
-
async function fail(message, payload, exitCode = 1) {
|
|
51
|
+
async function fail(message, payload, exitCode = 1, track = true) {
|
|
52
|
+
// Canonical fields go last so a payload key can never shadow `error`/`action`.
|
|
53
|
+
const { command, ...rest } = payload;
|
|
48
54
|
if (mode === 'json') {
|
|
49
|
-
process.stdout.write(`${JSON.stringify({ ok: false, error: message
|
|
55
|
+
process.stdout.write(`${JSON.stringify({ ok: false, ...payload, error: message })}\n`);
|
|
50
56
|
}
|
|
51
57
|
else {
|
|
52
58
|
process.stderr.write(pc.red(`[z2h-cli] ${message}\n`));
|
|
@@ -54,7 +60,9 @@ async function fail(message, payload, exitCode = 1) {
|
|
|
54
60
|
process.stderr.write(pc.dim(` → ${payload.suggestedCommand}\n`));
|
|
55
61
|
}
|
|
56
62
|
}
|
|
57
|
-
|
|
63
|
+
if (track) {
|
|
64
|
+
await trackEvent(Z2hCliEvents.error, { ...rest, action: command ?? 'unknown', error: message });
|
|
65
|
+
}
|
|
58
66
|
process.exit(exitCode);
|
|
59
67
|
}
|
|
60
68
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { CommandSpec } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a command body with the whole telemetry and failure contract so no command
|
|
5
|
+
* hand-rolls it. It times the happy path. On a throw it reads `reason` and `data` straight
|
|
6
|
+
* off the error, defined in ./errors, instead of re-deriving them per command.
|
|
7
|
+
*
|
|
8
|
+
* Returns a commander action handler, chainable straight off `.command(...)` — commander always
|
|
9
|
+
* appends the Command instance as the action handler's last argument, so this splits it back off
|
|
10
|
+
* to read the command's own name/parent instead of asking the caller to pass it in.
|
|
11
|
+
*
|
|
12
|
+
* NoInfer keeps the arg types coming from `fn` alone. A `data` or `label` callback that
|
|
13
|
+
* only needs the first arg must not narrow the handler's own signature.
|
|
14
|
+
*/
|
|
15
|
+
export declare function runCommand<A extends unknown[]>(spec: CommandSpec<NoInfer<A>>, fn: (...args: A) => Promise<unknown>): (...args: [...A, Command]) => Promise<void>;
|
|
16
|
+
//# sourceMappingURL=run-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-command.d.ts","sourceRoot":"","sources":["../../../src/util/run-command.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,UAAU,CAAC;AAcrD;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,OAAO,EAAE,EAC5C,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAC7B,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,GACnC,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAyB7C"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { fail } from './logger.mjs';
|
|
2
|
+
import { successEvent, trackDuration } from './tracker.mjs';
|
|
3
|
+
import { reasonOf, Z2hError, dataOf, messageOf } from './errors.mjs';
|
|
4
|
+
|
|
5
|
+
/** A spec field is a constant, a callback over the args, or absent — in which case `fallback` applies. */
|
|
6
|
+
const resolveSpecField = (field, args, fallback) => {
|
|
7
|
+
if (field === undefined) {
|
|
8
|
+
return fallback;
|
|
9
|
+
}
|
|
10
|
+
return typeof field === 'function' ? field(args) : field;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Wraps a command body with the whole telemetry and failure contract so no command
|
|
14
|
+
* hand-rolls it. It times the happy path. On a throw it reads `reason` and `data` straight
|
|
15
|
+
* off the error, defined in ./errors, instead of re-deriving them per command.
|
|
16
|
+
*
|
|
17
|
+
* Returns a commander action handler, chainable straight off `.command(...)` — commander always
|
|
18
|
+
* appends the Command instance as the action handler's last argument, so this splits it back off
|
|
19
|
+
* to read the command's own name/parent instead of asking the caller to pass it in.
|
|
20
|
+
*
|
|
21
|
+
* NoInfer keeps the arg types coming from `fn` alone. A `data` or `label` callback that
|
|
22
|
+
* only needs the first arg must not narrow the handler's own signature.
|
|
23
|
+
*/
|
|
24
|
+
function runCommand(spec, fn) {
|
|
25
|
+
return async (...allArgs) => {
|
|
26
|
+
const cmd = allArgs[allArgs.length - 1];
|
|
27
|
+
const args = allArgs.slice(0, -1);
|
|
28
|
+
const name = cmd.parent ? `${cmd.parent.name()} ${cmd.name()}` : cmd.name();
|
|
29
|
+
const resolve = (v, fallback) => resolveSpecField(v, args, fallback);
|
|
30
|
+
const track = resolve(spec.track, true);
|
|
31
|
+
const label = resolve(spec.label, name);
|
|
32
|
+
const event = resolve(spec.event, successEvent(name));
|
|
33
|
+
const data = spec.data?.(args) ?? {};
|
|
34
|
+
try {
|
|
35
|
+
await (track ? trackDuration(event, () => fn(...args), data) : fn(...args));
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
const reason = reasonOf(err);
|
|
39
|
+
// A deploy/preview that failed because the consumer's own build broke isn't a CLI/infra
|
|
40
|
+
// problem — don't report it at all, not even as `unknown`.
|
|
41
|
+
const shouldTrack = track && reason !== 'build_error';
|
|
42
|
+
await fail(`${label} failed: ${messageOf(err)}`, { command: label, reason, ...data, ...dataOf(err) }, spec.exitCode ?? (err instanceof Z2hError ? err.exitCode : 1), shouldTrack);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { runCommand };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../../../src/util/s3/upload.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../../../src/util/s3/upload.ts"],"names":[],"mappings":"AAgBA,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,GAAE,MAA0B,GACjC,OAAO,CAAC,MAAM,EAAE,CAAC,CA8BnB"}
|
|
@@ -5,6 +5,7 @@ import { lookup } from 'mime-types';
|
|
|
5
5
|
import pMap from 'p-map';
|
|
6
6
|
import { PutObjectCommand } from '@aws-sdk/client-s3';
|
|
7
7
|
import { BUCKET_WRITE_NAME } from '../../constants.mjs';
|
|
8
|
+
import { tagDeployError } from '../errors.mjs';
|
|
8
9
|
import { getS3Client } from './client.mjs';
|
|
9
10
|
|
|
10
11
|
// Each deploy uploads to a version-prefixed path (<app>/<version>/...), so
|
|
@@ -12,22 +13,27 @@ import { getS3Client } from './client.mjs';
|
|
|
12
13
|
const FOREVER = 'public, immutable';
|
|
13
14
|
const UPLOAD_CONCURRENCY = 5;
|
|
14
15
|
async function uploadDir(localDir, keyPrefix, bucket = BUCKET_WRITE_NAME) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
16
|
+
try {
|
|
17
|
+
const entries = await readdir(localDir, { recursive: true, withFileTypes: true });
|
|
18
|
+
const files = entries.filter(e => e.isFile()).map(e => path.relative(localDir, path.join(e.parentPath, e.name)));
|
|
19
|
+
const s3 = getS3Client();
|
|
20
|
+
await pMap(files, async (rel) => {
|
|
21
|
+
const body = await readFile(path.join(localDir, rel));
|
|
22
|
+
const contentType = lookup(rel) || 'application/octet-stream';
|
|
23
|
+
const key = `${keyPrefix}/${rel.split(path.sep).join('/')}`;
|
|
24
|
+
await s3.send(new PutObjectCommand({
|
|
25
|
+
Bucket: bucket,
|
|
26
|
+
Key: key,
|
|
27
|
+
Body: body,
|
|
28
|
+
ContentType: contentType,
|
|
29
|
+
CacheControl: FOREVER,
|
|
30
|
+
}));
|
|
31
|
+
}, { concurrency: UPLOAD_CONCURRENCY });
|
|
32
|
+
return files.map(f => f.split(path.sep).join('/'));
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
throw tagDeployError('s3_error', err);
|
|
36
|
+
}
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
export { uploadDir };
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
-
export declare
|
|
2
|
-
|
|
1
|
+
export declare const trackEvent: (name: string, data: Record<string, unknown>) => Promise<void>, trackDuration: <T>(name: string, fn: () => Promise<T>, data?: Record<string, unknown>) => Promise<T>;
|
|
2
|
+
/**
|
|
3
|
+
* `z2h_cli_<command>_success` — the success-event name for every command, derived by
|
|
4
|
+
* util/run-command.ts's `runCommand()` so no command has to choose one. Nested commands join
|
|
5
|
+
* their path with `_` (`backend invoke` → `z2h_cli_backend_invoke_success`). The one place a
|
|
6
|
+
* command names its own event, `deploy --preview`, still goes through this function so the
|
|
7
|
+
* convention holds everywhere. See CommandSpec's `event` field in types.ts.
|
|
8
|
+
*/
|
|
9
|
+
export declare function successEvent(command: string): string;
|
|
3
10
|
//# sourceMappingURL=tracker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../../src/util/tracker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../../src/util/tracker.ts"],"names":[],"mappings":"AAcA,eAAO,MAAQ,UAAU,kEAAE,aAAa,uFAItC,CAAC;AAEH;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpD"}
|
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { createHttpTracker } from '@mondaydotcomorg/z2h-shared-utils/observability';
|
|
2
4
|
|
|
3
5
|
const DOORMAN_URL = 'https://track-int.bigbrain.me/prod/event';
|
|
4
6
|
const DOORMAN_URL_STAGING = 'https://track-int.bigbrainstaging.me/stage/event';
|
|
5
7
|
const username = os.userInfo().username;
|
|
8
|
+
const { version: cliVersion } = createRequire(import.meta.url)('@mondaydotcomorg/z2h-cli/package.json');
|
|
6
9
|
const isStaging = process.env.NODE_ENV === 'staging';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return
|
|
10
|
+
const { trackEvent, trackDuration } = createHttpTracker({
|
|
11
|
+
url: isStaging ? DOORMAN_URL_STAGING : DOORMAN_URL,
|
|
12
|
+
source: 'z2h-cli',
|
|
13
|
+
getContext: () => ({ user_name: username, cli_version: cliVersion }),
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* `z2h_cli_<command>_success` — the success-event name for every command, derived by
|
|
17
|
+
* util/run-command.ts's `runCommand()` so no command has to choose one. Nested commands join
|
|
18
|
+
* their path with `_` (`backend invoke` → `z2h_cli_backend_invoke_success`). The one place a
|
|
19
|
+
* command names its own event, `deploy --preview`, still goes through this function so the
|
|
20
|
+
* convention holds everywhere. See CommandSpec's `event` field in types.ts.
|
|
21
|
+
*/
|
|
22
|
+
function successEvent(command) {
|
|
23
|
+
return `z2h_cli_${command.replace(/[\s-]+/g, '_')}_success`;
|
|
21
24
|
}
|
|
22
25
|
|
|
23
|
-
export { trackDuration, trackEvent };
|
|
26
|
+
export { successEvent, trackDuration, trackEvent };
|