@hs-x/cli 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.
Files changed (302) hide show
  1. package/README.md +1001 -0
  2. package/dist/account-store.d.ts +51 -0
  3. package/dist/account-store.d.ts.map +1 -0
  4. package/dist/account-store.js +138 -0
  5. package/dist/account-store.js.map +1 -0
  6. package/dist/bin/hs-x.d.ts +3 -0
  7. package/dist/bin/hs-x.d.ts.map +1 -0
  8. package/dist/bin/hs-x.js +47 -0
  9. package/dist/bin/hs-x.js.map +1 -0
  10. package/dist/cli/index.d.ts +3 -0
  11. package/dist/cli/index.d.ts.map +1 -0
  12. package/dist/cli/index.js +595 -0
  13. package/dist/cli/index.js.map +1 -0
  14. package/dist/cli-error.d.ts +36 -0
  15. package/dist/cli-error.d.ts.map +1 -0
  16. package/dist/cli-error.js +40 -0
  17. package/dist/cli-error.js.map +1 -0
  18. package/dist/cloudflare-auth.d.ts +25 -0
  19. package/dist/cloudflare-auth.d.ts.map +1 -0
  20. package/dist/cloudflare-auth.js +251 -0
  21. package/dist/cloudflare-auth.js.map +1 -0
  22. package/dist/cloudflare-kv.d.ts +23 -0
  23. package/dist/cloudflare-kv.d.ts.map +1 -0
  24. package/dist/cloudflare-kv.js +101 -0
  25. package/dist/cloudflare-kv.js.map +1 -0
  26. package/dist/cloudflare-oauth-store.d.ts +16 -0
  27. package/dist/cloudflare-oauth-store.d.ts.map +1 -0
  28. package/dist/cloudflare-oauth-store.js +80 -0
  29. package/dist/cloudflare-oauth-store.js.map +1 -0
  30. package/dist/cloudflare-oauth.d.ts +82 -0
  31. package/dist/cloudflare-oauth.d.ts.map +1 -0
  32. package/dist/cloudflare-oauth.js +336 -0
  33. package/dist/cloudflare-oauth.js.map +1 -0
  34. package/dist/cloudflare-pointer.d.ts +13 -0
  35. package/dist/cloudflare-pointer.d.ts.map +1 -0
  36. package/dist/cloudflare-pointer.js +46 -0
  37. package/dist/cloudflare-pointer.js.map +1 -0
  38. package/dist/command-history.d.ts +7 -0
  39. package/dist/command-history.d.ts.map +1 -0
  40. package/dist/command-history.js +34 -0
  41. package/dist/command-history.js.map +1 -0
  42. package/dist/commands/account.d.ts +7 -0
  43. package/dist/commands/account.d.ts.map +1 -0
  44. package/dist/commands/account.js +315 -0
  45. package/dist/commands/account.js.map +1 -0
  46. package/dist/commands/api.d.ts +36 -0
  47. package/dist/commands/api.d.ts.map +1 -0
  48. package/dist/commands/api.js +521 -0
  49. package/dist/commands/api.js.map +1 -0
  50. package/dist/commands/completion.d.ts +7 -0
  51. package/dist/commands/completion.d.ts.map +1 -0
  52. package/dist/commands/completion.js +121 -0
  53. package/dist/commands/completion.js.map +1 -0
  54. package/dist/commands/connect.d.ts +7 -0
  55. package/dist/commands/connect.d.ts.map +1 -0
  56. package/dist/commands/connect.js +1123 -0
  57. package/dist/commands/connect.js.map +1 -0
  58. package/dist/commands/control-plane-read.d.ts +22 -0
  59. package/dist/commands/control-plane-read.d.ts.map +1 -0
  60. package/dist/commands/control-plane-read.js +350 -0
  61. package/dist/commands/control-plane-read.js.map +1 -0
  62. package/dist/commands/deploy-promote.d.ts +14 -0
  63. package/dist/commands/deploy-promote.d.ts.map +1 -0
  64. package/dist/commands/deploy-promote.js +105 -0
  65. package/dist/commands/deploy-promote.js.map +1 -0
  66. package/dist/commands/deploy.d.ts +18 -0
  67. package/dist/commands/deploy.d.ts.map +1 -0
  68. package/dist/commands/deploy.js +2764 -0
  69. package/dist/commands/deploy.js.map +1 -0
  70. package/dist/commands/dev.d.ts +7 -0
  71. package/dist/commands/dev.d.ts.map +1 -0
  72. package/dist/commands/dev.js +913 -0
  73. package/dist/commands/dev.js.map +1 -0
  74. package/dist/commands/doctor.d.ts +8 -0
  75. package/dist/commands/doctor.d.ts.map +1 -0
  76. package/dist/commands/doctor.js +258 -0
  77. package/dist/commands/doctor.js.map +1 -0
  78. package/dist/commands/flags.d.ts +22 -0
  79. package/dist/commands/flags.d.ts.map +1 -0
  80. package/dist/commands/flags.js +185 -0
  81. package/dist/commands/flags.js.map +1 -0
  82. package/dist/commands/help-command.d.ts +13 -0
  83. package/dist/commands/help-command.d.ts.map +1 -0
  84. package/dist/commands/help-command.js +482 -0
  85. package/dist/commands/help-command.js.map +1 -0
  86. package/dist/commands/history.d.ts +6 -0
  87. package/dist/commands/history.d.ts.map +1 -0
  88. package/dist/commands/history.js +42 -0
  89. package/dist/commands/history.js.map +1 -0
  90. package/dist/commands/init.d.ts +8 -0
  91. package/dist/commands/init.d.ts.map +1 -0
  92. package/dist/commands/init.js +233 -0
  93. package/dist/commands/init.js.map +1 -0
  94. package/dist/commands/link.d.ts +26 -0
  95. package/dist/commands/link.d.ts.map +1 -0
  96. package/dist/commands/link.js +441 -0
  97. package/dist/commands/link.js.map +1 -0
  98. package/dist/commands/login.d.ts +8 -0
  99. package/dist/commands/login.d.ts.map +1 -0
  100. package/dist/commands/login.js +381 -0
  101. package/dist/commands/login.js.map +1 -0
  102. package/dist/commands/migrate.d.ts +8 -0
  103. package/dist/commands/migrate.d.ts.map +1 -0
  104. package/dist/commands/migrate.js +258 -0
  105. package/dist/commands/migrate.js.map +1 -0
  106. package/dist/commands/rollback.d.ts +21 -0
  107. package/dist/commands/rollback.d.ts.map +1 -0
  108. package/dist/commands/rollback.js +301 -0
  109. package/dist/commands/rollback.js.map +1 -0
  110. package/dist/commands/secrets.d.ts +7 -0
  111. package/dist/commands/secrets.d.ts.map +1 -0
  112. package/dist/commands/secrets.js +230 -0
  113. package/dist/commands/secrets.js.map +1 -0
  114. package/dist/commands/status.d.ts +7 -0
  115. package/dist/commands/status.d.ts.map +1 -0
  116. package/dist/commands/status.js +241 -0
  117. package/dist/commands/status.js.map +1 -0
  118. package/dist/commands/unlink.d.ts +21 -0
  119. package/dist/commands/unlink.d.ts.map +1 -0
  120. package/dist/commands/unlink.js +83 -0
  121. package/dist/commands/unlink.js.map +1 -0
  122. package/dist/commands/update.d.ts +11 -0
  123. package/dist/commands/update.d.ts.map +1 -0
  124. package/dist/commands/update.js +154 -0
  125. package/dist/commands/update.js.map +1 -0
  126. package/dist/commands/validate.d.ts +9 -0
  127. package/dist/commands/validate.d.ts.map +1 -0
  128. package/dist/commands/validate.js +39 -0
  129. package/dist/commands/validate.js.map +1 -0
  130. package/dist/config.d.ts +12 -0
  131. package/dist/config.d.ts.map +1 -0
  132. package/dist/config.js +64 -0
  133. package/dist/config.js.map +1 -0
  134. package/dist/constants.d.ts +4 -0
  135. package/dist/constants.d.ts.map +1 -0
  136. package/dist/constants.js +4 -0
  137. package/dist/constants.js.map +1 -0
  138. package/dist/control-plane-fetch.d.ts +34 -0
  139. package/dist/control-plane-fetch.d.ts.map +1 -0
  140. package/dist/control-plane-fetch.js +73 -0
  141. package/dist/control-plane-fetch.js.map +1 -0
  142. package/dist/control-plane-loader.d.ts +16 -0
  143. package/dist/control-plane-loader.d.ts.map +1 -0
  144. package/dist/control-plane-loader.js +24 -0
  145. package/dist/control-plane-loader.js.map +1 -0
  146. package/dist/dev/compat-shim.d.ts +40 -0
  147. package/dist/dev/compat-shim.d.ts.map +1 -0
  148. package/dist/dev/compat-shim.js +65 -0
  149. package/dist/dev/compat-shim.js.map +1 -0
  150. package/dist/dev/event-bus.d.ts +27 -0
  151. package/dist/dev/event-bus.d.ts.map +1 -0
  152. package/dist/dev/event-bus.js +32 -0
  153. package/dist/dev/event-bus.js.map +1 -0
  154. package/dist/dev/log-server.d.ts +52 -0
  155. package/dist/dev/log-server.d.ts.map +1 -0
  156. package/dist/dev/log-server.js +216 -0
  157. package/dist/dev/log-server.js.map +1 -0
  158. package/dist/dev/session-manager.d.ts +33 -0
  159. package/dist/dev/session-manager.d.ts.map +1 -0
  160. package/dist/dev/session-manager.js +132 -0
  161. package/dist/dev/session-manager.js.map +1 -0
  162. package/dist/dev/stream-renderer.d.ts +22 -0
  163. package/dist/dev/stream-renderer.d.ts.map +1 -0
  164. package/dist/dev/stream-renderer.js +65 -0
  165. package/dist/dev/stream-renderer.js.map +1 -0
  166. package/dist/dev/tunnel.d.ts +40 -0
  167. package/dist/dev/tunnel.d.ts.map +1 -0
  168. package/dist/dev/tunnel.js +139 -0
  169. package/dist/dev/tunnel.js.map +1 -0
  170. package/dist/effect-http.d.ts +10 -0
  171. package/dist/effect-http.d.ts.map +1 -0
  172. package/dist/effect-http.js +38 -0
  173. package/dist/effect-http.js.map +1 -0
  174. package/dist/errors-registry.d.ts +11 -0
  175. package/dist/errors-registry.d.ts.map +1 -0
  176. package/dist/errors-registry.js +554 -0
  177. package/dist/errors-registry.js.map +1 -0
  178. package/dist/errors.d.ts +58 -0
  179. package/dist/errors.d.ts.map +1 -0
  180. package/dist/errors.js +30 -0
  181. package/dist/errors.js.map +1 -0
  182. package/dist/help.d.ts +6 -0
  183. package/dist/help.d.ts.map +1 -0
  184. package/dist/help.js +100 -0
  185. package/dist/help.js.map +1 -0
  186. package/dist/history.d.ts +15 -0
  187. package/dist/history.d.ts.map +1 -0
  188. package/dist/history.js +69 -0
  189. package/dist/history.js.map +1 -0
  190. package/dist/hubspot-auth.d.ts +53 -0
  191. package/dist/hubspot-auth.d.ts.map +1 -0
  192. package/dist/hubspot-auth.js +301 -0
  193. package/dist/hubspot-auth.js.map +1 -0
  194. package/dist/hubspot-developer-client.d.ts +10 -0
  195. package/dist/hubspot-developer-client.d.ts.map +1 -0
  196. package/dist/hubspot-developer-client.js +212 -0
  197. package/dist/hubspot-developer-client.js.map +1 -0
  198. package/dist/index.d.ts +5 -0
  199. package/dist/index.d.ts.map +1 -0
  200. package/dist/index.js +4 -0
  201. package/dist/index.js.map +1 -0
  202. package/dist/init/templates.d.ts +18 -0
  203. package/dist/init/templates.d.ts.map +1 -0
  204. package/dist/init/templates.js +239 -0
  205. package/dist/init/templates.js.map +1 -0
  206. package/dist/load-env.d.ts +16 -0
  207. package/dist/load-env.d.ts.map +1 -0
  208. package/dist/load-env.js +69 -0
  209. package/dist/load-env.js.map +1 -0
  210. package/dist/machine-id.d.ts +3 -0
  211. package/dist/machine-id.d.ts.map +1 -0
  212. package/dist/machine-id.js +41 -0
  213. package/dist/machine-id.js.map +1 -0
  214. package/dist/paths.d.ts +4 -0
  215. package/dist/paths.d.ts.map +1 -0
  216. package/dist/paths.js +19 -0
  217. package/dist/paths.js.map +1 -0
  218. package/dist/prompt.d.ts +43 -0
  219. package/dist/prompt.d.ts.map +1 -0
  220. package/dist/prompt.js +379 -0
  221. package/dist/prompt.js.map +1 -0
  222. package/dist/reporter/human.d.ts +28 -0
  223. package/dist/reporter/human.d.ts.map +1 -0
  224. package/dist/reporter/human.js +126 -0
  225. package/dist/reporter/human.js.map +1 -0
  226. package/dist/reporter/index.d.ts +14 -0
  227. package/dist/reporter/index.d.ts.map +1 -0
  228. package/dist/reporter/index.js +37 -0
  229. package/dist/reporter/index.js.map +1 -0
  230. package/dist/reporter/json.d.ts +43 -0
  231. package/dist/reporter/json.d.ts.map +1 -0
  232. package/dist/reporter/json.js +146 -0
  233. package/dist/reporter/json.js.map +1 -0
  234. package/dist/reporter/style.d.ts +34 -0
  235. package/dist/reporter/style.d.ts.map +1 -0
  236. package/dist/reporter/style.js +97 -0
  237. package/dist/reporter/style.js.map +1 -0
  238. package/dist/reporter/types.d.ts +41 -0
  239. package/dist/reporter/types.d.ts.map +1 -0
  240. package/dist/reporter/types.js +2 -0
  241. package/dist/reporter/types.js.map +1 -0
  242. package/dist/result.d.ts +4 -0
  243. package/dist/result.d.ts.map +1 -0
  244. package/dist/result.js +2 -0
  245. package/dist/result.js.map +1 -0
  246. package/dist/services/account-store.d.ts +31 -0
  247. package/dist/services/account-store.d.ts.map +1 -0
  248. package/dist/services/account-store.js +135 -0
  249. package/dist/services/account-store.js.map +1 -0
  250. package/dist/services/app-paths.d.ts +25 -0
  251. package/dist/services/app-paths.d.ts.map +1 -0
  252. package/dist/services/app-paths.js +34 -0
  253. package/dist/services/app-paths.js.map +1 -0
  254. package/dist/services/cloudflare-auth.d.ts +83 -0
  255. package/dist/services/cloudflare-auth.d.ts.map +1 -0
  256. package/dist/services/cloudflare-auth.js +30 -0
  257. package/dist/services/cloudflare-auth.js.map +1 -0
  258. package/dist/services/cloudflare-kv.d.ts +45 -0
  259. package/dist/services/cloudflare-kv.d.ts.map +1 -0
  260. package/dist/services/cloudflare-kv.js +151 -0
  261. package/dist/services/cloudflare-kv.js.map +1 -0
  262. package/dist/services/command-history.d.ts +29 -0
  263. package/dist/services/command-history.d.ts.map +1 -0
  264. package/dist/services/command-history.js +62 -0
  265. package/dist/services/command-history.js.map +1 -0
  266. package/dist/services/control-plane.d.ts +32 -0
  267. package/dist/services/control-plane.d.ts.map +1 -0
  268. package/dist/services/control-plane.js +57 -0
  269. package/dist/services/control-plane.js.map +1 -0
  270. package/dist/services/env-loader.d.ts +18 -0
  271. package/dist/services/env-loader.d.ts.map +1 -0
  272. package/dist/services/env-loader.js +34 -0
  273. package/dist/services/env-loader.js.map +1 -0
  274. package/dist/services/http.d.ts +19 -0
  275. package/dist/services/http.d.ts.map +1 -0
  276. package/dist/services/http.js +9 -0
  277. package/dist/services/http.js.map +1 -0
  278. package/dist/services/live.d.ts +16 -0
  279. package/dist/services/live.d.ts.map +1 -0
  280. package/dist/services/live.js +26 -0
  281. package/dist/services/live.js.map +1 -0
  282. package/dist/services/machine-id.d.ts +18 -0
  283. package/dist/services/machine-id.d.ts.map +1 -0
  284. package/dist/services/machine-id.js +39 -0
  285. package/dist/services/machine-id.js.map +1 -0
  286. package/dist/services/reporter.d.ts +55 -0
  287. package/dist/services/reporter.d.ts.map +1 -0
  288. package/dist/services/reporter.js +49 -0
  289. package/dist/services/reporter.js.map +1 -0
  290. package/dist/state-store.d.ts +39 -0
  291. package/dist/state-store.d.ts.map +1 -0
  292. package/dist/state-store.js +89 -0
  293. package/dist/state-store.js.map +1 -0
  294. package/dist/telemetry.d.ts +13 -0
  295. package/dist/telemetry.d.ts.map +1 -0
  296. package/dist/telemetry.js +129 -0
  297. package/dist/telemetry.js.map +1 -0
  298. package/dist/tenant-state.d.ts +69 -0
  299. package/dist/tenant-state.d.ts.map +1 -0
  300. package/dist/tenant-state.js +161 -0
  301. package/dist/tenant-state.js.map +1 -0
  302. package/package.json +38 -0
@@ -0,0 +1,913 @@
1
+ import { readFile, readdir, stat } from 'node:fs/promises';
2
+ import { createServer } from 'node:http';
3
+ import { join } from 'node:path';
4
+ import { generateProjectArtifacts } from '@hs-x/codegen';
5
+ import { HttpClientRequest, Schema, schemas } from '@hs-x/types';
6
+ import { BRAND_ORANGE_SGR, CLI_VERSION } from '../constants.js';
7
+ import { controlPlaneAuthHeaders } from '../control-plane-fetch.js';
8
+ import { executeCliHttp } from '../effect-http.js';
9
+ import { formatConfigUrl, loadCliConfig } from '../config.js';
10
+ import { createReporter } from '../reporter/index.js';
11
+ import { isInteractive, promptMultiSelect, promptSelect, promptText } from '../prompt.js';
12
+ async function hostedHttp(input) {
13
+ const headers = { ...(input.headers ?? {}) };
14
+ let request = HttpClientRequest.make(input.method ?? 'GET')(input.url).pipe(HttpClientRequest.setHeaders(headers));
15
+ if (input.body !== undefined) {
16
+ headers['content-type'] = headers['content-type'] ?? 'application/json';
17
+ request = request.pipe(HttpClientRequest.setHeaders(headers), HttpClientRequest.bodyText(typeof input.body === 'string' ? input.body : JSON.stringify(input.body), headers['content-type']));
18
+ }
19
+ return executeCliHttp(request);
20
+ }
21
+ function isRecord(value) {
22
+ return typeof value === 'object' && value !== null;
23
+ }
24
+ function renderDevHeader(env, stderr, { subject, url }) {
25
+ const isTTY = Boolean(stderr.isTTY);
26
+ const color = isTTY && env.NO_COLOR !== '1';
27
+ const orange = (s) => (color ? `\x1b[${BRAND_ORANGE_SGR}m${s}\x1b[0m` : s);
28
+ const bold = (s) => (color ? `\x1b[1m${s}\x1b[0m` : s);
29
+ const dim = (s) => (color ? `\x1b[2m${s}\x1b[0m` : s);
30
+ const arrow = '▲';
31
+ const sep = '→';
32
+ stderr.write(`\n ${orange(arrow)} ${bold('hs-x dev')} ${bold(subject)} ${dim(sep)} ${url}\n\n`);
33
+ }
34
+ function makeDevRequestLogger(env, stderr, defaultAccountId) {
35
+ const isTTY = Boolean(stderr.isTTY);
36
+ const color = isTTY && env.NO_COLOR !== '1';
37
+ const dim = (s) => (color ? `\x1b[2m${s}\x1b[0m` : s);
38
+ const green = (s) => (color ? `\x1b[32m${s}\x1b[0m` : s);
39
+ const red = (s) => (color ? `\x1b[31m${s}\x1b[0m` : s);
40
+ const yellow = (s) => (color ? `\x1b[33m${s}\x1b[0m` : s);
41
+ const cyan = (s) => (color ? `\x1b[36m${s}\x1b[0m` : s);
42
+ const magenta = (s) => (color ? `\x1b[35m${s}\x1b[0m` : s);
43
+ return ({ method, path, status, durationMs, source = 'local', accountId, detail }) => {
44
+ const statusColor = status >= 500 ? red : status >= 400 ? yellow : green;
45
+ const symbol = status >= 400 ? '✗' : '✓';
46
+ const methodStr = method.padEnd(5);
47
+ const pathStr = path.length > 35 ? path : path.padEnd(35);
48
+ const statusStr = statusColor(String(status).padStart(3));
49
+ const durationStr = `${durationMs}ms`.padStart(7);
50
+ const effectiveAccountId = accountId ?? defaultAccountId;
51
+ const acct = effectiveAccountId ? ` ${dim(effectiveAccountId)}` : '';
52
+ const sourceColor = source === 'prod' ? magenta : cyan;
53
+ const sourceLabel = sourceColor(source.padEnd(5));
54
+ stderr.write(` ${methodStr} ${pathStr} ${statusStr} ${durationStr}${acct} ${statusColor(symbol)} ${sourceLabel}\n`);
55
+ if (detail) {
56
+ stderr.write(` ${dim('└')} ${dim(detail)}\n`);
57
+ }
58
+ };
59
+ }
60
+ async function resolveAccountIdOrPrompt(input) {
61
+ const fromFlag = resolveFlag(input.argv, '--account-id') ?? process.env.HSX_ACCOUNT_ID;
62
+ if (fromFlag && fromFlag.length > 0)
63
+ return { accountId: fromFlag, fromPrompt: false };
64
+ const { loadStore } = await import('../account-store.js');
65
+ const store = await loadStore();
66
+ const ids = Object.keys(store.accounts);
67
+ if (ids.length === 0)
68
+ return { fromPrompt: false };
69
+ const defaultId = store.defaultAccountId;
70
+ // Non-interactive, --json, or --yes: accept the configured default rather than
71
+ // silently degrading to "no account picked" — that path led deploy to exit 0
72
+ // after building artifacts only, looking like success.
73
+ const yes = input.argv.includes('--yes') || input.argv.includes('-y');
74
+ if (input.json || !isInteractive() || yes) {
75
+ if (defaultId && ids.includes(defaultId)) {
76
+ return { accountId: defaultId, fromPrompt: false };
77
+ }
78
+ return { fromPrompt: false };
79
+ }
80
+ const picked = await promptSelect({
81
+ message: `Which HS-X account for ${input.purpose}?`,
82
+ ...(defaultId && ids.includes(defaultId) ? { default: defaultId } : {}),
83
+ options: ids.map((id) => {
84
+ const a = store.accounts[id];
85
+ return {
86
+ value: id,
87
+ label: id,
88
+ description: a
89
+ ? `portal ${a.hubspotPortalId} — ${a.displayName}${id === defaultId ? ' (default)' : ''}`
90
+ : '',
91
+ };
92
+ }),
93
+ });
94
+ return picked ? { accountId: picked, fromPrompt: true } : { fromPrompt: false };
95
+ }
96
+ async function promptMissingText(message, current, validate) {
97
+ if (current && current.length > 0)
98
+ return current;
99
+ const answer = await promptText({
100
+ message,
101
+ ...(validate ? { validate } : {}),
102
+ });
103
+ return answer === undefined ? undefined : answer;
104
+ }
105
+ async function readHsxConfigProjectId(root) {
106
+ try {
107
+ const contents = await readFile(join(root, 'hsx.config.ts'), 'utf8');
108
+ const match = contents.match(/\bname\s*:\s*["'`]([^"'`]+)["'`]/);
109
+ return match?.[1] ? toProjectId(match[1]) : undefined;
110
+ }
111
+ catch {
112
+ return undefined;
113
+ }
114
+ }
115
+ function toProjectId(value) {
116
+ return value
117
+ .trim()
118
+ .toLowerCase()
119
+ .replace(/[^a-z0-9_-]+/g, '-')
120
+ .replace(/^-+|-+$/g, '');
121
+ }
122
+ function cancelledResult(argv, command) {
123
+ const reporter = createReporter({ command, argv });
124
+ reporter.info('Cancelled.');
125
+ return { exitCode: 130 };
126
+ }
127
+ export async function devCommand({ argv, root, json, }) {
128
+ if (argv[1] === 'cleanup') {
129
+ return await devCleanupCommand({ argv, json });
130
+ }
131
+ if (argv[1] === 'status') {
132
+ return await devStatusCommand({ argv, json });
133
+ }
134
+ const port = Number(resolveFlag(argv, '--port') ?? '8787');
135
+ const workers = await discoverWorkerManifests(root);
136
+ await generateProjectArtifacts({ root, workers });
137
+ let portalId = resolveFlag(argv, '--portal');
138
+ let targetOrigin = resolveFlag(argv, '--target-origin');
139
+ const resolvedDevAcct = await resolveAccountIdOrPrompt({
140
+ argv,
141
+ json,
142
+ purpose: 'this dev session',
143
+ });
144
+ const accountId = resolvedDevAcct.accountId;
145
+ const devCliConfig = loadCliConfig(argv);
146
+ // Portal id: HubSpot account ids in our store look like `portal-46993937`;
147
+ // strip the prefix when the user didn't pass --portal explicitly.
148
+ if (!portalId && accountId) {
149
+ const derived = accountId.replace(/^portal[-_]?/i, '');
150
+ if (/^\d+$/.test(derived))
151
+ portalId = derived;
152
+ }
153
+ // Project id: prefer flag/env, otherwise derive from `name` in hsx.config.ts.
154
+ // Developers shouldn't have to repeat what's already in the project file.
155
+ const projectId = devCliConfig.projectId ?? (await readHsxConfigProjectId(root));
156
+ // Control plane is a single deployed instance; default to it. Overrides
157
+ // (env or flag) exist for self-host / local-cp test runs only.
158
+ const controlPlaneUrl = formatConfigUrl(devCliConfig.controlPlaneUrl);
159
+ const developerId = resolveFlag(argv, '--developer-id') ??
160
+ devCliConfig.userId;
161
+ const sessionId = resolveFlag(argv, '--session-id') ??
162
+ `devsess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
163
+ let ttlSeconds = Number(resolveFlag(argv, '--ttl-seconds') ?? '7200');
164
+ const selectedCapabilities = resolveFlags(argv, '--capability');
165
+ const allCapabilities = workers.flatMap((worker) => worker.capabilities
166
+ .filter((capability) => capability.runtimeNeeds.includes('worker'))
167
+ .map((capability) => ({
168
+ id: capability.id,
169
+ kind: capability.kind,
170
+ worker: worker.name,
171
+ })));
172
+ const allCapabilityIds = allCapabilities.map((c) => c.id);
173
+ if (!json && isInteractive() && (controlPlaneUrl || portalId || targetOrigin)) {
174
+ portalId = await promptMissingText('HubSpot portal id for dev override', portalId, (value) => /^\d+$/.test(value) ? undefined : 'Portal id must be numeric.');
175
+ if (portalId === undefined)
176
+ return cancelledResult(argv, 'dev');
177
+ targetOrigin = await promptMissingText('Public dev target origin', targetOrigin, (value) => {
178
+ try {
179
+ const url = new URL(value);
180
+ return url.protocol === 'https:' || url.hostname === '127.0.0.1' || url.hostname === 'localhost'
181
+ ? undefined
182
+ : 'Use an https URL, localhost, or 127.0.0.1.';
183
+ }
184
+ catch {
185
+ return 'Enter a valid URL.';
186
+ }
187
+ });
188
+ if (targetOrigin === undefined)
189
+ return cancelledResult(argv, 'dev');
190
+ if (!resolveFlag(argv, '--ttl-seconds')) {
191
+ const ttl = await promptSelect({
192
+ message: 'Dev override TTL',
193
+ default: '7200',
194
+ options: [
195
+ { value: '3600', label: '1 hour', description: 'short local session' },
196
+ { value: '7200', label: '2 hours', description: 'default' },
197
+ { value: '14400', label: '4 hours', description: 'long pairing/debugging session' },
198
+ ],
199
+ });
200
+ if (ttl === undefined)
201
+ return cancelledResult(argv, 'dev');
202
+ ttlSeconds = Number(ttl);
203
+ }
204
+ }
205
+ // Probe the HubSpot project IR up front so the picker can show UI-extension
206
+ // nodes alongside HS-X capabilities. No-op if hsproject.json isn't present.
207
+ const uiExtensionNodes = await probeHubSpotUiNodes({
208
+ root,
209
+ portalIdFlag: portalId,
210
+ accountId,
211
+ });
212
+ let capabilityIds = selectedCapabilities.length > 0 ? selectedCapabilities : allCapabilityIds;
213
+ let selectedUiNodeUids = uiExtensionNodes && uiExtensionNodes.uids.length > 0 ? uiExtensionNodes.uids : undefined;
214
+ // Unified picker: ask once over HS-X capabilities + HubSpot UI nodes.
215
+ const totalCandidates = allCapabilities.length + (uiExtensionNodes?.uids.length ?? 0);
216
+ if (portalId &&
217
+ !json &&
218
+ selectedCapabilities.length === 0 &&
219
+ totalCandidates > 1 &&
220
+ isInteractive()) {
221
+ const CAP_PREFIX = 'cap:';
222
+ const UI_PREFIX = 'ui:';
223
+ const options = [
224
+ ...allCapabilities.map((c) => ({
225
+ value: `${CAP_PREFIX}${c.id}`,
226
+ label: c.id,
227
+ description: `${c.kind} · worker ${c.worker} → cloudflare override`,
228
+ })),
229
+ ...(uiExtensionNodes?.uids ?? []).map((uid) => ({
230
+ value: `${UI_PREFIX}${uid}`,
231
+ label: uid,
232
+ description: 'ui-extension · hubspot dev-session',
233
+ })),
234
+ ];
235
+ const initial = options.map((o) => o.value);
236
+ const picked = await promptMultiSelect({
237
+ message: `Which components should run in dev mode? (portal ${portalId})`,
238
+ initial,
239
+ minSelected: 1,
240
+ options,
241
+ });
242
+ if (picked === undefined)
243
+ return cancelledResult(argv, 'dev');
244
+ capabilityIds = picked
245
+ .filter((v) => v.startsWith(CAP_PREFIX))
246
+ .map((v) => v.slice(CAP_PREFIX.length));
247
+ selectedUiNodeUids = picked
248
+ .filter((v) => v.startsWith(UI_PREFIX))
249
+ .map((v) => v.slice(UI_PREFIX.length));
250
+ }
251
+ let devOverrideRegistration;
252
+ // We register a dev override whenever the user has a HubSpot portal in
253
+ // play. `hs-x connect` gives us the portal id implicitly; only edge cases
254
+ // (no connected account, no --portal flag) skip registration.
255
+ const shouldRegisterOverride = Boolean(portalId);
256
+ if (shouldRegisterOverride) {
257
+ if (!accountId) {
258
+ throw new Error('Dev override needs a connected HubSpot account. Run `hs-x connect` or pass --account-id.');
259
+ }
260
+ if (!projectId) {
261
+ throw new Error('Could not determine project id. Add `name: "..."` to `hsx.config.ts`, set `HSX_PROJECT_ID`, or pass --project-id.');
262
+ }
263
+ if (!Number.isFinite(ttlSeconds) || ttlSeconds <= 0) {
264
+ throw new Error('--ttl-seconds must be a positive number.');
265
+ }
266
+ if (capabilityIds.length === 0) {
267
+ throw new Error('No worker-backed capabilities were found for dev override registration.');
268
+ }
269
+ // In JSON / scripted mode we still register here (without an auto-managed
270
+ // tunnel — scripts pass --target-origin themselves). Interactive mode
271
+ // registers later, after starting the sidecar + auto-tunnel, so the
272
+ // telemetry URL can be plumbed through.
273
+ if (json) {
274
+ if (!targetOrigin) {
275
+ throw new Error('Dev override registration in --json mode requires --target-origin <url>.');
276
+ }
277
+ devOverrideRegistration = await requestDevOverrideRegistration({
278
+ controlPlaneUrl,
279
+ accountId,
280
+ userId: developerId,
281
+ request: Schema.decodeUnknownSync(schemas.DevOverrideRegisterRequest)({
282
+ projectId,
283
+ portalId,
284
+ targetOrigin,
285
+ developerId,
286
+ sessionId,
287
+ capabilityIds,
288
+ ttlSeconds,
289
+ }),
290
+ });
291
+ }
292
+ }
293
+ if (json) {
294
+ write(`${JSON.stringify({
295
+ ok: true,
296
+ command: 'dev',
297
+ url: `http://127.0.0.1:${port}`,
298
+ workers,
299
+ ...(devOverrideRegistration
300
+ ? { mode: 'control-plane-dev-override', devOverrideRegistration }
301
+ : {}),
302
+ }, null, 2)}\n`);
303
+ return { exitCode: 0 };
304
+ }
305
+ const startupBegan = Date.now();
306
+ const renderRequestLog = makeDevRequestLogger(process.env, process.stderr, accountId);
307
+ const server = createServer((request, response) => {
308
+ const requestStarted = Date.now();
309
+ const method = request.method ?? 'GET';
310
+ const path = request.url ?? '/';
311
+ response.on('finish', () => {
312
+ renderRequestLog({
313
+ method,
314
+ path,
315
+ status: response.statusCode,
316
+ durationMs: Date.now() - requestStarted,
317
+ source: 'local',
318
+ });
319
+ });
320
+ if (path === '/_hsx/health') {
321
+ response.writeHead(200, { 'content-type': 'application/json' });
322
+ response.end(JSON.stringify({ ok: true, cliVersion: CLI_VERSION }));
323
+ return;
324
+ }
325
+ if (path === '/_hsx/manifest') {
326
+ response.writeHead(200, { 'content-type': 'application/json' });
327
+ response.end(JSON.stringify({ workers }, null, 2));
328
+ return;
329
+ }
330
+ response.writeHead(404, { 'content-type': 'application/json' });
331
+ response.end(JSON.stringify({
332
+ ok: false,
333
+ error: 'The v0 dev server exposes /_hsx/health and /_hsx/manifest.',
334
+ }));
335
+ });
336
+ await new Promise((resolveListen) => server.listen(port, '127.0.0.1', resolveListen));
337
+ const elapsed = Date.now() - startupBegan;
338
+ const reporter = createReporter({ command: 'dev', argv, entry: true });
339
+ reporter.banner();
340
+ const capabilityCount = workers.reduce((c, w) => c + w.capabilities.length, 0);
341
+ const subject = devOverrideRegistration ? `portal ${devOverrideRegistration.portalId}` : 'local';
342
+ const url = `http://127.0.0.1:${port}`;
343
+ // Optional: start a HubSpot UI-extension dev session if this directory
344
+ // contains an hsproject.json AND the user selected at least one UI node.
345
+ const hubSpotDevSession = selectedUiNodeUids && selectedUiNodeUids.length > 0
346
+ ? await maybeStartHubSpotDevSession({
347
+ root,
348
+ argv,
349
+ json,
350
+ accountId,
351
+ reporter,
352
+ selectedNodeUids: selectedUiNodeUids,
353
+ })
354
+ : undefined;
355
+ // HS-X dev sidecar — receives frontend logs (and later: backend/request
356
+ // events) from inside the card iframe and surfaces them in the terminal.
357
+ // Stable default port 9099 so card code can hard-default the dev-log URL.
358
+ const hsxLogPort = Number(resolveFlag(argv, '--hsx-log-port') ?? '9099');
359
+ const devLogStream = await startHsxDevLogStream({
360
+ port: hsxLogPort,
361
+ reporter,
362
+ });
363
+ // Auto-managed Cloudflare quick tunnel — gives the deployed Worker a public
364
+ // URL to POST telemetry envelopes to the local sidecar. The developer must
365
+ // never have to run cloudflared themselves; that's a non-negotiable default.
366
+ const devTunnel = shouldRegisterOverride
367
+ ? await startHsxDevTunnel({ port: hsxLogPort, reporter })
368
+ : undefined;
369
+ // Now register the dev override. If the developer didn't pass
370
+ // `--target-origin`, default to observe mode and let the deployed Worker
371
+ // run as normal — we only need the tunnel for the telemetry tee.
372
+ if (shouldRegisterOverride && controlPlaneUrl && portalId && accountId && projectId) {
373
+ const overrideMode = targetOrigin ? 'proxy' : 'observe';
374
+ const effectiveTargetOrigin = targetOrigin ?? devTunnel?.url ?? `http://127.0.0.1:${hsxLogPort}`;
375
+ devOverrideRegistration = await requestDevOverrideRegistration({
376
+ controlPlaneUrl,
377
+ accountId,
378
+ userId: developerId,
379
+ request: Schema.decodeUnknownSync(schemas.DevOverrideRegisterRequest)({
380
+ projectId,
381
+ portalId,
382
+ targetOrigin: effectiveTargetOrigin,
383
+ developerId,
384
+ sessionId,
385
+ capabilityIds,
386
+ ttlSeconds,
387
+ mode: overrideMode,
388
+ ...(devTunnel ? { telemetryUrl: `${devTunnel.url}/__hsx/event` } : {}),
389
+ }),
390
+ });
391
+ }
392
+ // Vercel-style header: arrow, command, subject, → URL
393
+ renderDevHeader(process.env, process.stderr, { subject, url });
394
+ reporter.info(`Ready in ${elapsed}ms — ${workers.length} worker${workers.length === 1 ? '' : 's'} · ${capabilityCount} capabilit${capabilityCount === 1 ? 'y' : 'ies'}${devOverrideRegistration ? ` · portal ${devOverrideRegistration.portalId}` : ''}${hubSpotDevSession ? ` · ui-extension session ${hubSpotDevSession.sessionId}` : ''}${devLogStream ? ` · log stream ${devLogStream.url}` : ''}${devTunnel ? ` · tunnel ${devTunnel.url}` : ''}`);
395
+ reporter.info('');
396
+ reporter.info('Press Ctrl+C to stop');
397
+ reporter.info('');
398
+ await new Promise((resolveClose) => {
399
+ const close = async () => {
400
+ if (hubSpotDevSession) {
401
+ try {
402
+ await hubSpotDevSession.cleanup();
403
+ }
404
+ catch (error) {
405
+ renderRequestLog({
406
+ method: 'CLEANUP',
407
+ path: '/dev-session',
408
+ status: 500,
409
+ durationMs: 0,
410
+ source: 'local',
411
+ detail: error instanceof Error ? error.message : String(error),
412
+ });
413
+ }
414
+ }
415
+ if (devTunnel) {
416
+ try {
417
+ await devTunnel.cleanup();
418
+ }
419
+ catch {
420
+ // best-effort: tunnel cleanup failures should not block exit.
421
+ }
422
+ }
423
+ if (devLogStream) {
424
+ try {
425
+ await devLogStream.cleanup();
426
+ }
427
+ catch {
428
+ // best-effort: sidecar shutdown failures should not block exit.
429
+ }
430
+ }
431
+ server.close(() => resolveClose());
432
+ };
433
+ process.once('SIGINT', close);
434
+ process.once('SIGTERM', close);
435
+ });
436
+ return { exitCode: 0 };
437
+ }
438
+ async function startHsxDevLogStream(input) {
439
+ try {
440
+ const [{ createDevEventBus }, { startDevLogServer }, { startDevStreamRenderer }] = await Promise.all([
441
+ import('../dev/event-bus.js'),
442
+ import('../dev/log-server.js'),
443
+ import('../dev/stream-renderer.js'),
444
+ ]);
445
+ const bus = createDevEventBus();
446
+ const server = await startDevLogServer({ bus, port: input.port });
447
+ const renderer = startDevStreamRenderer({ bus });
448
+ return {
449
+ url: server.url,
450
+ async cleanup() {
451
+ renderer.stop();
452
+ await server.close();
453
+ },
454
+ };
455
+ }
456
+ catch (error) {
457
+ input.reporter.warn('HSX_W_DEV_LOG_STREAM_FAILED', `Could not start dev log stream: ${error instanceof Error ? error.message : String(error)}`);
458
+ return undefined;
459
+ }
460
+ }
461
+ async function startHsxDevTunnel(input) {
462
+ const step = input.reporter.step('Cloudflare quick tunnel');
463
+ try {
464
+ const { startCloudflaredTunnel } = await import('../dev/tunnel.js');
465
+ const tunnel = await startCloudflaredTunnel({ port: input.port });
466
+ step.ok(tunnel.url);
467
+ return tunnel;
468
+ }
469
+ catch (error) {
470
+ const msg = error instanceof Error ? error.message : String(error);
471
+ step.warn(msg);
472
+ return undefined;
473
+ }
474
+ }
475
+ async function probeHubSpotUiNodes(input) {
476
+ const { access } = await import('node:fs/promises');
477
+ const { join: pathJoin } = await import('node:path');
478
+ const projectFile = pathJoin(input.root, 'hsproject.json');
479
+ try {
480
+ await access(projectFile);
481
+ }
482
+ catch {
483
+ return undefined;
484
+ }
485
+ const portalId = Number.parseInt(input.portalIdFlag ?? input.accountId?.replace(/^portal[-_]?/i, '') ?? '', 10);
486
+ if (!Number.isFinite(portalId))
487
+ return undefined;
488
+ try {
489
+ const { loadDevStack } = await import('../dev/compat-shim.js');
490
+ const stack = await loadDevStack();
491
+ const { readFile: readProj } = await import('node:fs/promises');
492
+ const projectConfig = JSON.parse(await readProj(projectFile, 'utf8'));
493
+ const ir = await stack.translateForLocalDev({
494
+ projectSourceDir: pathJoin(input.root, projectConfig.srcDir),
495
+ platformVersion: projectConfig.platformVersion,
496
+ accountId: portalId,
497
+ }, {});
498
+ return { uids: Object.keys(ir.intermediateNodesIndexedByUid), portalId };
499
+ }
500
+ catch {
501
+ // IR translate failed — caller falls back to capability-only behavior.
502
+ return undefined;
503
+ }
504
+ }
505
+ async function maybeStartHubSpotDevSession(input) {
506
+ if (input.argv.includes('--no-hubspot') || input.argv.includes('--no-ui-extensions'))
507
+ return;
508
+ const portalFlag = resolveFlag(input.argv, '--portal');
509
+ const portalId = Number.parseInt(portalFlag ?? input.accountId?.replace(/^portal[-_]?/i, '') ?? '', 10);
510
+ if (!Number.isFinite(portalId)) {
511
+ input.reporter.warn('HSX_W_DEV_HUBSPOT_NO_PORTAL', 'Found hsproject.json but no HubSpot portal id — pass --portal <id> to enable the UI-extension dev session.');
512
+ return undefined;
513
+ }
514
+ try {
515
+ const { startDevSession } = await import('../dev/session-manager.js');
516
+ const session = await startDevSession({
517
+ accountId: portalId,
518
+ projectDir: input.root,
519
+ selectedNodeUids: input.selectedNodeUids,
520
+ });
521
+ input.reporter
522
+ .step('HubSpot UI-extension session')
523
+ .ok(`id ${session.sessionId} · ${session.nodeUids.length} component${session.nodeUids.length === 1 ? '' : 's'}`);
524
+ return session;
525
+ }
526
+ catch (error) {
527
+ input.reporter.warn('HSX_W_DEV_HUBSPOT_SESSION_FAILED', `Could not start UI-extension dev session: ${error instanceof Error ? error.message : String(error)}`);
528
+ return undefined;
529
+ }
530
+ }
531
+ async function devStatusCommand({ argv, json, }) {
532
+ const accountId = resolveFlag(argv, '--account-id') ?? process.env.HSX_ACCOUNT_ID;
533
+ const controlPlaneUrl = resolveFlag(argv, '--control-plane-url') ?? process.env.HSX_CONTROL_PLANE_URL;
534
+ const userId = resolveFlag(argv, '--user-id') ??
535
+ resolveFlag(argv, '--developer-id') ??
536
+ process.env.HSX_USER_ID ??
537
+ 'local-cli-user';
538
+ if (!accountId || !controlPlaneUrl) {
539
+ throw new Error('Dev status requires --control-plane-url and --account-id.');
540
+ }
541
+ const active = await requestDevOverrideStatus({ controlPlaneUrl, accountId, userId });
542
+ const result = {
543
+ ok: true,
544
+ command: 'dev status',
545
+ mode: 'control-plane-dev-override-status',
546
+ accountId,
547
+ active,
548
+ };
549
+ if (json) {
550
+ write(`${JSON.stringify(result, null, 2)}\n`);
551
+ }
552
+ else {
553
+ const reporter = createReporter({ command: 'dev status', argv });
554
+ reporter.header(accountId);
555
+ reporter.block(renderDevOverrides(active.overrides));
556
+ reporter.done(`${active.overrides.length} active`);
557
+ }
558
+ return { exitCode: 0 };
559
+ }
560
+ async function devCleanupCommand({ argv, json, }) {
561
+ const accountId = resolveFlag(argv, '--account-id') ?? process.env.HSX_ACCOUNT_ID;
562
+ const controlPlaneUrl = resolveFlag(argv, '--control-plane-url') ?? process.env.HSX_CONTROL_PLANE_URL;
563
+ const userId = resolveFlag(argv, '--user-id') ??
564
+ resolveFlag(argv, '--developer-id') ??
565
+ process.env.HSX_USER_ID ??
566
+ 'local-cli-user';
567
+ const sessionId = resolveFlag(argv, '--session-id');
568
+ if (!accountId || !controlPlaneUrl || !sessionId) {
569
+ throw new Error('Dev cleanup requires --control-plane-url, --account-id, and --session-id.');
570
+ }
571
+ const cleared = await requestDevOverrideCleanup({
572
+ controlPlaneUrl,
573
+ accountId,
574
+ userId,
575
+ sessionId,
576
+ });
577
+ const result = {
578
+ ok: true,
579
+ command: 'dev cleanup',
580
+ mode: 'control-plane-dev-override-cleanup',
581
+ accountId,
582
+ sessionId,
583
+ cleared,
584
+ };
585
+ if (json) {
586
+ write(`${JSON.stringify(result, null, 2)}\n`);
587
+ }
588
+ else {
589
+ const reporter = createReporter({ command: 'dev cleanup', argv });
590
+ reporter.header(sessionId);
591
+ reporter.step(`Cleared ${cleared.cleared.length} dev overrides`).ok();
592
+ reporter.done();
593
+ }
594
+ return { exitCode: 0 };
595
+ }
596
+ async function requestDevOverrideRegistration({ controlPlaneUrl, accountId, userId, request, }) {
597
+ const response = await hostedHttp({
598
+ url: new URL(`/v1/accounts/${encodeURIComponent(accountId)}/dev-overrides`, controlPlaneUrl),
599
+ method: 'POST',
600
+ headers: await controlPlaneAuthHeaders(userId),
601
+ body: request,
602
+ });
603
+ const body = await response.json();
604
+ if (!response.ok) {
605
+ throw new Error(isRecord(body) && typeof body.message === 'string'
606
+ ? body.message
607
+ : `Control-plane dev override registration failed with status ${response.status}`);
608
+ }
609
+ return Schema.decodeUnknownSync(schemas.DevOverrideRegisterResponse)(body);
610
+ }
611
+ async function requestDevOverrideStatus({ controlPlaneUrl, accountId, userId, }) {
612
+ const response = await hostedHttp({
613
+ url: new URL(`/v1/accounts/${encodeURIComponent(accountId)}/dev-overrides`, controlPlaneUrl),
614
+ headers: await controlPlaneAuthHeaders(userId),
615
+ });
616
+ const body = await response.json();
617
+ if (!response.ok) {
618
+ throw new Error(isRecord(body) && typeof body.message === 'string'
619
+ ? body.message
620
+ : `Control-plane dev override status failed with status ${response.status}`);
621
+ }
622
+ return Schema.decodeUnknownSync(schemas.DevOverrideListResponse)(body);
623
+ }
624
+ async function requestDevOverrideCleanup({ controlPlaneUrl, accountId, userId, sessionId, }) {
625
+ const response = await hostedHttp({
626
+ url: new URL(`/v1/accounts/${encodeURIComponent(accountId)}/dev-overrides/${encodeURIComponent(sessionId)}`, controlPlaneUrl),
627
+ method: 'DELETE',
628
+ headers: await controlPlaneAuthHeaders(userId),
629
+ });
630
+ const body = await response.json();
631
+ if (!response.ok) {
632
+ throw new Error(isRecord(body) && typeof body.message === 'string'
633
+ ? body.message
634
+ : `Control-plane dev override cleanup failed with status ${response.status}`);
635
+ }
636
+ return Schema.decodeUnknownSync(schemas.DevOverrideClearResponse)(body);
637
+ }
638
+ async function discoverWorkerManifests(root, options = {}) {
639
+ const files = await collectFiles(join(root, 'src'));
640
+ const workers = [];
641
+ for (const file of files) {
642
+ const source = await readFile(file, 'utf8');
643
+ const workerName = /defineWorker\s*\(\s*["'`]([^"'`]+)["'`]/.exec(source)?.[1];
644
+ if (!workerName) {
645
+ continue;
646
+ }
647
+ const toolCapabilities = [
648
+ ...source.matchAll(/(?:worker\.)?(?:tool|action)\s*\(\s*["'`]([^"'`]+)["'`]\s*,\s*\{([\s\S]*?)(?:async\s+)?handler\s*(?:[:(])/g),
649
+ ].map((match) => ({
650
+ kind: 'tool',
651
+ id: match[1] ?? 'unknown',
652
+ label: /label\s*:\s*["'`]([^"'`]+)["'`]/.exec(match[2] ?? '')?.[1] ?? match[1] ?? 'unknown',
653
+ objectType: /objectType\s*:\s*["'`]([^"'`]+)["'`]/.exec(match[2] ?? '')?.[1] ?? 'unknown',
654
+ ...objectFieldsProperty(match[2] ?? '', 'input'),
655
+ ...objectFieldsProperty(match[2] ?? '', 'output'),
656
+ runtimeNeeds: ['worker'],
657
+ }));
658
+ const cardBackendCapabilities = [
659
+ ...source.matchAll(/(?:worker\.)?cardBackend\s*\(\s*["'`]([^"'`]+)["'`]\s*,\s*\{([\s\S]*?)(?:async\s+)?handler\s*(?:[:(])/g),
660
+ ].map((match) => ({
661
+ kind: 'card-backend',
662
+ id: match[1] ?? 'unknown',
663
+ label: /label\s*:\s*["'`]([^"'`]+)["'`]/.exec(match[2] ?? '')?.[1] ?? match[1] ?? 'unknown',
664
+ runtimeNeeds: ['worker'],
665
+ }));
666
+ const sourceDefinitions = discoverSourceDefinitions(source);
667
+ const syncCapabilities = [
668
+ ...source.matchAll(/worker\.sync\s*\(\s*([^,]+)\s*,\s*\{([\s\S]*?)\}\s*\)/g),
669
+ ].map((match) => {
670
+ const sourceOrId = (match[1] ?? '').trim();
671
+ const definition = match[2] ?? '';
672
+ const id = /^["'`]([^"'`]+)["'`]$/.exec(sourceOrId)?.[1];
673
+ const sourceDefinition = sourceDefinitions.get(sourceOrId);
674
+ const sourceManifest = sourceDefinition?.kind === 'push'
675
+ ? {
676
+ kind: 'push',
677
+ name: sourceDefinition.name,
678
+ webhookPath: `/webhooks/${sourceDefinition.name}`,
679
+ ...(sourceDefinition.auth ? { auth: { type: sourceDefinition.auth } } : {}),
680
+ }
681
+ : sourceDefinition?.kind === 'pull'
682
+ ? {
683
+ kind: 'pull',
684
+ name: sourceDefinition.name,
685
+ ...(sourceDefinition.auth ? { auth: { type: sourceDefinition.auth } } : {}),
686
+ }
687
+ : undefined;
688
+ return {
689
+ kind: 'sync',
690
+ id: id ?? sourceManifest?.name ?? 'unknown',
691
+ ...(/label\s*:\s*["'`]([^"'`]+)["'`]/.exec(definition)?.[1]
692
+ ? { label: /label\s*:\s*["'`]([^"'`]+)["'`]/.exec(definition)?.[1] }
693
+ : {}),
694
+ schedule: /schedule\s*:\s*["'`]([^"'`]+)["'`]/.exec(definition)?.[1] ?? 'manual',
695
+ ...stringProperty(definition, 'into'),
696
+ ...schemaProperty(definition),
697
+ manageSchema: options.noManageSchema ? false : discoverManageSchemaMode(definition),
698
+ ...(sourceManifest ? { source: sourceManifest } : {}),
699
+ runtimeNeeds: sourceManifest?.kind === 'push'
700
+ ? ['worker', 'durable-object', 'queue']
701
+ : ['worker', 'durable-object'],
702
+ };
703
+ });
704
+ workers.push({
705
+ name: workerName,
706
+ capabilities: [...toolCapabilities, ...cardBackendCapabilities, ...syncCapabilities],
707
+ });
708
+ }
709
+ return workers;
710
+ }
711
+ function stringProperty(source, propertyName) {
712
+ const match = new RegExp(`${propertyName}\\s*:\\s*["'\`]([^"'\`]+)["'\`]`).exec(source)?.[1];
713
+ return match ? { [propertyName]: match } : {};
714
+ }
715
+ function schemaProperty(source) {
716
+ const body = /schema\s*:\s*\{([\s\S]*?)\}\s*(?:,|$)/.exec(source)?.[1];
717
+ if (!body) {
718
+ return {};
719
+ }
720
+ const schema = {};
721
+ for (const match of body.matchAll(/([A-Za-z_$][\w$]*)\s*:\s*["'`]([^"'`]+)["'`]/g)) {
722
+ if (match[1] && match[2]) {
723
+ schema[match[1]] = match[2];
724
+ }
725
+ }
726
+ return Object.keys(schema).length > 0 ? { schema } : {};
727
+ }
728
+ function objectFieldsProperty(source, propertyName) {
729
+ const body = objectLiteralBody(source, propertyName);
730
+ if (!body) {
731
+ return {};
732
+ }
733
+ const fields = {};
734
+ for (const match of body.matchAll(/([A-Za-z_$][\w$]*)\s*:\s*\{([\s\S]*?)\}\s*,?/g)) {
735
+ const fieldName = match[1];
736
+ const definition = match[2] ?? '';
737
+ if (!fieldName) {
738
+ continue;
739
+ }
740
+ fields[fieldName] = {
741
+ ...stringProperty(definition, 'type'),
742
+ ...stringProperty(definition, 'label'),
743
+ ...literalProperty(definition, 'default'),
744
+ };
745
+ }
746
+ return Object.keys(fields).length > 0 ? { [propertyName]: fields } : {};
747
+ }
748
+ function objectLiteralBody(source, propertyName) {
749
+ const start = new RegExp(`${propertyName}\\s*:\\s*\\{`).exec(source);
750
+ if (!start) {
751
+ return undefined;
752
+ }
753
+ let depth = 0;
754
+ const bodyStart = start.index + start[0].length;
755
+ for (let index = bodyStart; index < source.length; index += 1) {
756
+ const char = source[index];
757
+ if (char === '{')
758
+ depth += 1;
759
+ if (char === '}') {
760
+ if (depth === 0) {
761
+ return source.slice(bodyStart, index);
762
+ }
763
+ depth -= 1;
764
+ }
765
+ }
766
+ return undefined;
767
+ }
768
+ function literalProperty(source, propertyName) {
769
+ const match = new RegExp(`${propertyName}\\s*:\\s*([^,}\\n]+)`).exec(source)?.[1]?.trim();
770
+ if (!match) {
771
+ return {};
772
+ }
773
+ if (/^["'`]/.test(match)) {
774
+ return stringProperty(source, propertyName);
775
+ }
776
+ const numeric = Number(match);
777
+ if (Number.isFinite(numeric)) {
778
+ return { [propertyName]: numeric };
779
+ }
780
+ if (match === 'true')
781
+ return { [propertyName]: true };
782
+ if (match === 'false')
783
+ return { [propertyName]: false };
784
+ return {};
785
+ }
786
+ function discoverManageSchemaMode(definition) {
787
+ const literal = /manageSchema\s*:\s*["'`](properties|full)["'`]/.exec(definition)?.[1];
788
+ if (literal === 'properties' || literal === 'full') {
789
+ return literal;
790
+ }
791
+ return false;
792
+ }
793
+ function discoverSourceDefinitions(source) {
794
+ const definitions = new Map();
795
+ for (const match of source.matchAll(/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*defineSource(\.push)?\s*\(\s*\{([\s\S]*?)\}\s*\)/g)) {
796
+ const variableName = match[1];
797
+ const body = match[3] ?? '';
798
+ if (!variableName) {
799
+ continue;
800
+ }
801
+ const name = /name\s*:\s*["'`]([^"'`]+)["'`]/.exec(body)?.[1] ?? variableName;
802
+ const auth = /auth\s*:\s*\{\s*type\s*:\s*["'`](bearer|oauth2|basic|hmac)["'`]/.exec(body)?.[1];
803
+ definitions.set(variableName, {
804
+ kind: match[2] ? 'push' : 'pull',
805
+ name,
806
+ ...(auth ? { auth } : {}),
807
+ });
808
+ }
809
+ return definitions;
810
+ }
811
+ async function collectFiles(root) {
812
+ const files = [];
813
+ async function walk(dir) {
814
+ let entries;
815
+ try {
816
+ entries = await readdir(dir, { withFileTypes: true });
817
+ }
818
+ catch {
819
+ return;
820
+ }
821
+ for (const entry of entries) {
822
+ const path = join(dir, entry.name);
823
+ if (entry.isDirectory()) {
824
+ await walk(path);
825
+ }
826
+ else if (entry.isFile() && /\.(?:ts|tsx|js|jsx)$/.test(entry.name)) {
827
+ files.push(path);
828
+ }
829
+ }
830
+ }
831
+ try {
832
+ if ((await stat(root)).isDirectory()) {
833
+ await walk(root);
834
+ }
835
+ }
836
+ catch {
837
+ return [];
838
+ }
839
+ return files.sort();
840
+ }
841
+ function renderDevOverrides(overrides) {
842
+ if (overrides.length === 0) {
843
+ return 'No active dev overrides found.\n';
844
+ }
845
+ const now = new Date();
846
+ const portalWidth = Math.max('Portal'.length, ...overrides.map((o) => o.portalId.length));
847
+ const capabilityWidth = Math.max('Capability'.length, ...overrides.map((o) => o.capabilityId.length));
848
+ const sessionWidth = Math.max('Session'.length, ...overrides.map((o) => o.sessionId.length));
849
+ const lines = [
850
+ `${'Portal'.padEnd(portalWidth)} ${'Capability'.padEnd(capabilityWidth)} ${'Session'.padEnd(sessionWidth)} Expires Target`,
851
+ ];
852
+ for (const override of overrides) {
853
+ lines.push(`${override.portalId.padEnd(portalWidth)} ${override.capabilityId.padEnd(capabilityWidth)} ${override.sessionId.padEnd(sessionWidth)} ${formatDevExpiry(now, override.expiresAt).padEnd(7)} ${override.targetOrigin}`);
854
+ }
855
+ return `${lines.join('\n')}\n`;
856
+ }
857
+ function formatDevExpiry(now, expiresAt) {
858
+ const expires = Date.parse(expiresAt);
859
+ if (!Number.isFinite(expires))
860
+ return expiresAt;
861
+ const ms = expires - now.getTime();
862
+ if (ms <= 0)
863
+ return 'expired';
864
+ const minutes = Math.ceil(ms / 60_000);
865
+ if (minutes < 60)
866
+ return `${minutes}m`;
867
+ const hours = Math.ceil(minutes / 60);
868
+ if (hours < 48)
869
+ return `${hours}h`;
870
+ return `${Math.ceil(hours / 24)}d`;
871
+ }
872
+ function isFlagValue(value) {
873
+ return value !== undefined && value.length > 0 && !value.startsWith('-');
874
+ }
875
+ function resolveFlag(argv, flag) {
876
+ const index = argv.indexOf(flag);
877
+ if (index !== -1) {
878
+ const next = argv[index + 1];
879
+ if (isFlagValue(next))
880
+ return next;
881
+ }
882
+ const prefix = `${flag}=`;
883
+ const found = argv.find((arg) => arg.startsWith(prefix));
884
+ if (!found)
885
+ return undefined;
886
+ const value = found.slice(prefix.length);
887
+ return value.length > 0 ? value : undefined;
888
+ }
889
+ function resolveFlags(argv, flag) {
890
+ const values = [];
891
+ const prefix = `${flag}=`;
892
+ for (let index = 0; index < argv.length; index += 1) {
893
+ const arg = argv[index];
894
+ if (arg === flag) {
895
+ const next = argv[index + 1];
896
+ if (isFlagValue(next)) {
897
+ values.push(next);
898
+ index += 1;
899
+ }
900
+ continue;
901
+ }
902
+ if (arg?.startsWith(prefix)) {
903
+ const value = arg.slice(prefix.length);
904
+ if (value.length > 0)
905
+ values.push(value);
906
+ }
907
+ }
908
+ return values;
909
+ }
910
+ function write(message) {
911
+ process.stdout.write(message);
912
+ }
913
+ //# sourceMappingURL=dev.js.map