@mondaydotcomorg/z2h-cli 0.5.2 → 0.6.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.
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA6BD,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAqFvF"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA6BD,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAkFvF"}
@@ -96,10 +96,7 @@ async function createCommand(appName, opts) {
96
96
  await fsExtra.copy(opts.htmlFile, path__default.default.join(appDir, 'src', 'page.html'));
97
97
  }
98
98
  util_logger.info(`installing dependencies for ${appName}...`);
99
- await execa.execa('yarn', ['install'], {
100
- cwd: workspace,
101
- stdio: 'inherit',
102
- });
99
+ await execa.execa('yarn', ['install'], { cwd: workspace, stdio: 'inherit' });
103
100
  util_logger.output(`[z2h-cli] app ready at ${appDir}`, {
104
101
  ok: true,
105
102
  appDir,
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/commands/create.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA6BD,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAqFvF"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/commands/create.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA6BD,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAkFvF"}
@@ -90,10 +90,7 @@ async function createCommand(appName, opts) {
90
90
  await copy(opts.htmlFile, path.join(appDir, 'src', 'page.html'));
91
91
  }
92
92
  info(`installing dependencies for ${appName}...`);
93
- await execa('yarn', ['install'], {
94
- cwd: workspace,
95
- stdio: 'inherit',
96
- });
93
+ await execa('yarn', ['install'], { cwd: workspace, stdio: 'inherit' });
97
94
  output(`[z2h-cli] app ready at ${appDir}`, {
98
95
  ok: true,
99
96
  appDir,
@@ -1,5 +1,6 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { Command } from 'commander';
3
+ import { readFileSync } from 'node:fs';
3
4
  import { devCommand } from './commands/dev.mjs';
4
5
  import { buildCommand } from './commands/build.mjs';
5
6
  import { cleanCommand } from './commands/clean.mjs';
@@ -9,8 +10,17 @@ import { createCommand } from './commands/create.mjs';
9
10
  import { generateZ2HTokenCommand } from './commands/generate-z2h-token.mjs';
10
11
  import { migrateCommand } from './commands/migrate.mjs';
11
12
  import { errorLog, fail } from './util/logger.mjs';
13
+ import { trackDuration } from './util/tracker.mjs';
12
14
 
13
15
  const { version } = createRequire(import.meta.url)('../package.json');
16
+ function getCwdAppName() {
17
+ try {
18
+ return JSON.parse(readFileSync('package.json', 'utf8')).name;
19
+ }
20
+ catch {
21
+ return undefined;
22
+ }
23
+ }
14
24
  async function main() {
15
25
  const program = new Command();
16
26
  program
@@ -34,12 +44,9 @@ async function main() {
34
44
  .option('--json', 'emit structured JSON on stdout')
35
45
  .action(async (opts) => {
36
46
  try {
37
- await buildCommand(opts);
47
+ await trackDuration('z2h_app_build_success', () => buildCommand(opts), { app_name: getCwdAppName() });
38
48
  }
39
49
  catch (err) {
40
- // fail() emits a structured JSON error on stdout in --json mode and
41
- // a red line on stderr in human mode, then exits. errorLog alone is
42
- // silent in JSON mode, which leaves machine consumers with no signal.
43
50
  fail(`build failed: ${err.message}`, { command: 'build' }, 2);
44
51
  }
45
52
  });
@@ -74,7 +81,9 @@ async function main() {
74
81
  .option('--json', 'emit structured JSON on stdout')
75
82
  .action(async (opts) => {
76
83
  try {
77
- await deployCommand(opts);
84
+ await trackDuration('z2h_app_deploy_success', () => deployCommand(opts), {
85
+ app_name: getCwdAppName(),
86
+ });
78
87
  }
79
88
  catch (err) {
80
89
  fail(`deploy failed: ${err.message}`, { command: 'deploy' }, 2);
@@ -114,7 +123,10 @@ async function main() {
114
123
  .option('--json', 'emit structured JSON on stdout')
115
124
  .action(async (appName, opts) => {
116
125
  try {
117
- await createCommand(appName, opts);
126
+ await trackDuration('z2h_app_created', () => createCommand(appName, opts), {
127
+ app_name: appName,
128
+ template: opts.template ?? 'default',
129
+ });
118
130
  }
119
131
  catch (err) {
120
132
  fail(`create failed: ${err.message}`, { command: 'create' }, 1);
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
4
4
  import { pathExists, readJson, writeJson, writeFile } from 'fs-extra';
5
5
  import { execa } from 'execa';
6
6
  import semver from 'semver';
7
- import { warn, info } from '../util/logger.mjs';
7
+ import { warn, info, errorLog } from '../util/logger.mjs';
8
8
  import { getWorkspaceHome, isZ2hWorkspace } from '../util/workspace.mjs';
9
9
 
10
10
  const STATE_FILE = '.z2h-migrations-state.json';
@@ -151,9 +151,9 @@ async function autoMigrate(cliVersion, dryRun = false) {
151
151
  }
152
152
  }
153
153
  catch (err) {
154
- warn(` ✗ migration "${name}" failed: ${err.message}`);
155
- warn(' skipping remaining migrations. Run "z2h-cli migrate" to retry.');
156
- return;
154
+ const errorMessage = `migration "${name}" failed: ${err.message}`;
155
+ errorLog(`${errorMessage}\nSkipping remaining migrations — run "z2h-cli migrate" to retry.`);
156
+ throw new Error(errorMessage);
157
157
  }
158
158
  }
159
159
  if (needsInstall) {
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/util/logger.ts"],"names":[],"mappings":"AAEA,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;;;GAGG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,SAAI,GAAG,KAAK,CAU3F"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/util/logger.ts"],"names":[],"mappings":"AAGA,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;;;GAGG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,SAAI,GAAG,KAAK,CAW3F"}
@@ -1,4 +1,5 @@
1
1
  import pc from 'picocolors';
2
+ import { trackEvent } from './tracker.mjs';
2
3
 
3
4
  let mode = 'human';
4
5
  function setOutputMode(next) {
@@ -53,6 +54,7 @@ function fail(message, payload, exitCode = 1) {
53
54
  process.stderr.write(pc.dim(` → ${payload.suggestedCommand}\n`));
54
55
  }
55
56
  }
57
+ trackEvent('z2h_cli_error', { action: payload.command ?? 'unknown', error: message });
56
58
  process.exit(exitCode);
57
59
  }
58
60
 
@@ -0,0 +1,3 @@
1
+ export declare function trackEvent(name: string, data: Record<string, unknown>): void;
2
+ export declare function trackDuration<T>(eventName: string, fn: () => Promise<T>, data?: Record<string, unknown>): Promise<T>;
3
+ //# sourceMappingURL=tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../../src/util/tracker.ts"],"names":[],"mappings":"AAMA,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAO5E;AAED,wBAAsB,aAAa,CAAC,CAAC,EACnC,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,CAAC,CAAC,CAKZ"}
@@ -0,0 +1,20 @@
1
+ import os from 'node:os';
2
+
3
+ const DOORMAN_URL = 'https://track.bigbrain.me/prod/event';
4
+ const username = os.userInfo().username;
5
+ function trackEvent(name, data) {
6
+ void fetch(DOORMAN_URL, {
7
+ method: 'POST',
8
+ headers: { 'Content-Type': 'application/json' },
9
+ body: JSON.stringify({ name, source: 'z2h-cli', data: { username, ...data } }),
10
+ signal: AbortSignal.timeout(5000),
11
+ }).catch(() => { });
12
+ }
13
+ async function trackDuration(eventName, fn, data) {
14
+ const start = Date.now();
15
+ const result = await fn();
16
+ trackEvent(eventName, { ...data, duration_ms: Date.now() - start });
17
+ return result;
18
+ }
19
+
20
+ export { trackDuration, trackEvent };
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const node_module = require('node:module');
2
2
  const commander = require('commander');
3
+ const fs = require('node:fs');
3
4
  const commands_dev = require('./commands/dev.js');
4
5
  const commands_build = require('./commands/build.js');
5
6
  const commands_clean = require('./commands/clean.js');
@@ -9,9 +10,18 @@ const commands_create = require('./commands/create.js');
9
10
  const commands_generateZ2hToken = require('./commands/generate-z2h-token.js');
10
11
  const commands_migrate = require('./commands/migrate.js');
11
12
  const util_logger = require('./util/logger.js');
13
+ const util_tracker = require('./util/tracker.js');
12
14
 
13
15
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
14
16
  const { version } = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)))('../package.json');
17
+ function getCwdAppName() {
18
+ try {
19
+ return JSON.parse(fs.readFileSync('package.json', 'utf8')).name;
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ }
15
25
  async function main() {
16
26
  const program = new commander.Command();
17
27
  program
@@ -35,12 +45,9 @@ async function main() {
35
45
  .option('--json', 'emit structured JSON on stdout')
36
46
  .action(async (opts) => {
37
47
  try {
38
- await commands_build.buildCommand(opts);
48
+ await util_tracker.trackDuration('z2h_app_build_success', () => commands_build.buildCommand(opts), { app_name: getCwdAppName() });
39
49
  }
40
50
  catch (err) {
41
- // fail() emits a structured JSON error on stdout in --json mode and
42
- // a red line on stderr in human mode, then exits. errorLog alone is
43
- // silent in JSON mode, which leaves machine consumers with no signal.
44
51
  util_logger.fail(`build failed: ${err.message}`, { command: 'build' }, 2);
45
52
  }
46
53
  });
@@ -75,7 +82,9 @@ async function main() {
75
82
  .option('--json', 'emit structured JSON on stdout')
76
83
  .action(async (opts) => {
77
84
  try {
78
- await commands_deploy.deployCommand(opts);
85
+ await util_tracker.trackDuration('z2h_app_deploy_success', () => commands_deploy.deployCommand(opts), {
86
+ app_name: getCwdAppName(),
87
+ });
79
88
  }
80
89
  catch (err) {
81
90
  util_logger.fail(`deploy failed: ${err.message}`, { command: 'deploy' }, 2);
@@ -115,7 +124,10 @@ async function main() {
115
124
  .option('--json', 'emit structured JSON on stdout')
116
125
  .action(async (appName, opts) => {
117
126
  try {
118
- await commands_create.createCommand(appName, opts);
127
+ await util_tracker.trackDuration('z2h_app_created', () => commands_create.createCommand(appName, opts), {
128
+ app_name: appName,
129
+ template: opts.template ?? 'default',
130
+ });
119
131
  }
120
132
  catch (err) {
121
133
  util_logger.fail(`create failed: ${err.message}`, { command: 'create' }, 1);
@@ -159,9 +159,9 @@ async function autoMigrate(cliVersion, dryRun = false) {
159
159
  }
160
160
  }
161
161
  catch (err) {
162
- util_logger.warn(` ✗ migration "${name}" failed: ${err.message}`);
163
- util_logger.warn(' skipping remaining migrations. Run "z2h-cli migrate" to retry.');
164
- return;
162
+ const errorMessage = `migration "${name}" failed: ${err.message}`;
163
+ util_logger.errorLog(`${errorMessage}\nSkipping remaining migrations — run "z2h-cli migrate" to retry.`);
164
+ throw new Error(errorMessage);
165
165
  }
166
166
  }
167
167
  if (needsInstall) {
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/util/logger.ts"],"names":[],"mappings":"AAEA,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;;;GAGG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,SAAI,GAAG,KAAK,CAU3F"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/util/logger.ts"],"names":[],"mappings":"AAGA,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;;;GAGG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,SAAI,GAAG,KAAK,CAW3F"}
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  const pc = require('picocolors');
4
+ const util_tracker = require('./tracker.js');
4
5
 
5
6
  const _interopDefault = e => e && e.__esModule ? e : { default: e };
6
7
 
@@ -59,6 +60,7 @@ function fail(message, payload, exitCode = 1) {
59
60
  process.stderr.write(pc__default.default.dim(` → ${payload.suggestedCommand}\n`));
60
61
  }
61
62
  }
63
+ util_tracker.trackEvent('z2h_cli_error', { action: payload.command ?? 'unknown', error: message });
62
64
  process.exit(exitCode);
63
65
  }
64
66
 
@@ -0,0 +1,3 @@
1
+ export declare function trackEvent(name: string, data: Record<string, unknown>): void;
2
+ export declare function trackDuration<T>(eventName: string, fn: () => Promise<T>, data?: Record<string, unknown>): Promise<T>;
3
+ //# sourceMappingURL=tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tracker.d.ts","sourceRoot":"","sources":["../../src/util/tracker.ts"],"names":[],"mappings":"AAMA,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAO5E;AAED,wBAAsB,aAAa,CAAC,CAAC,EACnC,SAAS,EAAE,MAAM,EACjB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,CAAC,CAAC,CAKZ"}
@@ -0,0 +1,27 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ const os = require('node:os');
4
+
5
+ const _interopDefault = e => e && e.__esModule ? e : { default: e };
6
+
7
+ const os__default = /*#__PURE__*/_interopDefault(os);
8
+
9
+ const DOORMAN_URL = 'https://track.bigbrain.me/prod/event';
10
+ const username = os__default.default.userInfo().username;
11
+ function trackEvent(name, data) {
12
+ void fetch(DOORMAN_URL, {
13
+ method: 'POST',
14
+ headers: { 'Content-Type': 'application/json' },
15
+ body: JSON.stringify({ name, source: 'z2h-cli', data: { username, ...data } }),
16
+ signal: AbortSignal.timeout(5000),
17
+ }).catch(() => { });
18
+ }
19
+ async function trackDuration(eventName, fn, data) {
20
+ const start = Date.now();
21
+ const result = await fn();
22
+ trackEvent(eventName, { ...data, duration_ms: Date.now() - start });
23
+ return result;
24
+ }
25
+
26
+ exports.trackDuration = trackDuration;
27
+ exports.trackEvent = trackEvent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mondaydotcomorg/z2h-cli",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "bin": "./bin/z2h-cli.js",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -116,10 +116,7 @@ export async function createCommand(appName: string, opts: CreateOptions): Promi
116
116
  }
117
117
 
118
118
  info(`installing dependencies for ${appName}...`);
119
- await execa('yarn', ['install'], {
120
- cwd: workspace,
121
- stdio: 'inherit',
122
- });
119
+ await execa('yarn', ['install'], { cwd: workspace, stdio: 'inherit' });
123
120
 
124
121
  output(`[z2h-cli] app ready at ${appDir}`, {
125
122
  ok: true,
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import { Command } from 'commander';
3
+ import { readFileSync } from 'node:fs';
3
4
  import { devCommand } from './commands/dev';
4
5
  import { buildCommand } from './commands/build';
5
6
  import { cleanCommand } from './commands/clean';
@@ -9,9 +10,18 @@ import { createCommand } from './commands/create';
9
10
  import { generateZ2HTokenCommand } from './commands/generate-z2h-token';
10
11
  import { migrateCommand } from './commands/migrate';
11
12
  import { errorLog, fail } from './util/logger';
13
+ import { trackDuration } from './util/tracker';
12
14
 
13
15
  const { version } = createRequire(import.meta.url)('../package.json') as { version: string };
14
16
 
17
+ function getCwdAppName(): string | undefined {
18
+ try {
19
+ return (JSON.parse(readFileSync('package.json', 'utf8')) as { name?: string }).name;
20
+ } catch {
21
+ return undefined;
22
+ }
23
+ }
24
+
15
25
  async function main(): Promise<void> {
16
26
  const program = new Command();
17
27
  program
@@ -38,11 +48,8 @@ async function main(): Promise<void> {
38
48
  .option('--json', 'emit structured JSON on stdout')
39
49
  .action(async (opts: { json?: boolean }) => {
40
50
  try {
41
- await buildCommand(opts);
51
+ await trackDuration('z2h_app_build_success', () => buildCommand(opts), { app_name: getCwdAppName() });
42
52
  } catch (err) {
43
- // fail() emits a structured JSON error on stdout in --json mode and
44
- // a red line on stderr in human mode, then exits. errorLog alone is
45
- // silent in JSON mode, which leaves machine consumers with no signal.
46
53
  fail(`build failed: ${(err as Error).message}`, { command: 'build' }, 2);
47
54
  }
48
55
  });
@@ -78,7 +85,9 @@ async function main(): Promise<void> {
78
85
  .option('--json', 'emit structured JSON on stdout')
79
86
  .action(async (opts: { dryRun?: boolean; json?: boolean }) => {
80
87
  try {
81
- await deployCommand(opts);
88
+ await trackDuration('z2h_app_deploy_success', () => deployCommand(opts), {
89
+ app_name: getCwdAppName(),
90
+ });
82
91
  } catch (err) {
83
92
  fail(`deploy failed: ${(err as Error).message}`, { command: 'deploy' }, 2);
84
93
  }
@@ -120,7 +129,10 @@ async function main(): Promise<void> {
120
129
  .option('--json', 'emit structured JSON on stdout')
121
130
  .action(async (appName: string, opts: { template?: string; htmlFile?: string; json?: boolean }) => {
122
131
  try {
123
- await createCommand(appName, opts);
132
+ await trackDuration('z2h_app_created', () => createCommand(appName, opts), {
133
+ app_name: appName,
134
+ template: opts.template ?? 'default',
135
+ });
124
136
  } catch (err) {
125
137
  fail(`create failed: ${(err as Error).message}`, { command: 'create' }, 1);
126
138
  }
@@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url';
4
4
  import { pathExists, readJson, writeJson, writeFile } from 'fs-extra';
5
5
  import { execa } from 'execa';
6
6
  import semver from 'semver';
7
- import { info, warn } from '../util/logger';
7
+ import { info, warn, errorLog } from '../util/logger';
8
8
  import { getWorkspaceHome, isZ2hWorkspace } from '../util/workspace';
9
9
 
10
10
  const STATE_FILE = '.z2h-migrations-state.json';
@@ -199,9 +199,9 @@ export async function autoMigrate(cliVersion: string, dryRun = false): Promise<v
199
199
  info(` ○ ${entry.description} (no changes needed)`);
200
200
  }
201
201
  } catch (err) {
202
- warn(` ✗ migration "${name}" failed: ${(err as Error).message}`);
203
- warn(' skipping remaining migrations. Run "z2h-cli migrate" to retry.');
204
- return;
202
+ const errorMessage = `migration "${name}" failed: ${(err as Error).message}`;
203
+ errorLog(`${errorMessage}\nSkipping remaining migrations — run "z2h-cli migrate" to retry.`);
204
+ throw new Error(errorMessage);
205
205
  }
206
206
  }
207
207
 
@@ -1,4 +1,5 @@
1
1
  import pc from 'picocolors';
2
+ import { trackEvent } from './tracker';
2
3
 
3
4
  export type OutputMode = 'human' | 'json';
4
5
 
@@ -60,5 +61,6 @@ export function fail(message: string, payload: Record<string, unknown>, exitCode
60
61
  process.stderr.write(pc.dim(` → ${payload.suggestedCommand}\n`));
61
62
  }
62
63
  }
64
+ trackEvent('z2h_cli_error', { action: (payload.command as string) ?? 'unknown', error: message });
63
65
  process.exit(exitCode);
64
66
  }
@@ -0,0 +1,25 @@
1
+ import os from 'node:os';
2
+
3
+ const DOORMAN_URL = 'https://track.bigbrain.me/prod/event';
4
+
5
+ const username = os.userInfo().username;
6
+
7
+ export function trackEvent(name: string, data: Record<string, unknown>): void {
8
+ void fetch(DOORMAN_URL, {
9
+ method: 'POST',
10
+ headers: { 'Content-Type': 'application/json' },
11
+ body: JSON.stringify({ name, source: 'z2h-cli', data: { username, ...data } }),
12
+ signal: AbortSignal.timeout(5000),
13
+ }).catch(() => {});
14
+ }
15
+
16
+ export async function trackDuration<T>(
17
+ eventName: string,
18
+ fn: () => Promise<T>,
19
+ data?: Record<string, unknown>
20
+ ): Promise<T> {
21
+ const start = Date.now();
22
+ const result = await fn();
23
+ trackEvent(eventName, { ...data, duration_ms: Date.now() - start });
24
+ return result;
25
+ }