@mondaydotcomorg/z2h-cli 0.24.2 → 0.25.1

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":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/shadow/scaffold.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA0G9C;;;GAGG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvG"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/shadow/scaffold.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;GAGG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvG"}
@@ -1,104 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { ensureDir, writeFile, writeJson } from 'fs-extra';
3
+ import { WRAPPER_TEMPLATE, tridentrcTemplate, dtsFileTemplate, TSCONFIG_TEMPLATE } from '@mondaydotcomorg/z2h-shared-utils/mf-scaffold';
3
4
 
4
- const WRAPPER_TEMPLATE = `import '@vibe/core/tokens';
5
- import ConsumerApp from '../../src/index';
6
- import React from 'react';
7
- import * as ReactDOM from 'react-dom/client';
8
- import { ErrorBoundary } from 'react-error-boundary';
9
-
10
- const roots = new Map<HTMLElement, ReactDOM.Root>();
11
-
12
- export function init() {}
13
-
14
- export function render(element: HTMLElement) {
15
- const root = ReactDOM.createRoot(element);
16
- root.render(
17
- <ErrorBoundary fallback={<div style={{ padding: 16 }}>This app crashed.</div>}>
18
- <ConsumerApp />
19
- </ErrorBoundary>,
20
- );
21
- roots.set(element, root);
22
- }
23
-
24
- export function unmount({ element }: { element: HTMLElement }) {
25
- const root = roots.get(element);
26
- if (root) {
27
- root.unmount();
28
- roots.delete(element);
29
- }
30
- }
31
- `;
32
- const tridentrcTemplate = (appName, port) => `/** @type {import("@mondaydotcomorg/trident-user-config").MicroFrontendConfig} */
33
- const config = {
34
- appName: '${appName}',
35
- port: ${port},
36
- };
37
-
38
- module.exports = config;
39
- `;
40
- const dtsFileTemplate = () => `
41
- declare module '*.module.css' {
42
- const classNames: {
43
- [className: string]: string;
44
- };
45
- export = classNames;
46
- }
47
-
48
- declare module '*.module.scss' {
49
- const classNames: {
50
- [className: string]: string;
51
- };
52
- export = classNames;
53
- }
54
-
55
- declare module '*.jpg' {
56
- const e: string;
57
- export default e;
58
- }
59
- declare module '*.jpeg' {
60
- const e: string;
61
- export default e;
62
- }
63
- declare module '*.png' {
64
- const e: string;
65
- export default e;
66
- }
67
- declare module '*.riv' {
68
- const e: string;
69
- export default e;
70
- }
71
-
72
- declare module '*.wasm' {
73
- const e: string;
74
- export default e;
75
- }
76
-
77
- declare module '*.html?url' {
78
- const src: string;
79
- export default src;
80
- }
81
- `;
82
- const TSCONFIG_TEMPLATE = {
83
- compilerOptions: {
84
- target: 'es5',
85
- lib: ['dom', 'dom.iterable', 'esnext'],
86
- allowJs: false,
87
- skipLibCheck: true,
88
- esModuleInterop: true,
89
- allowSyntheticDefaultImports: true,
90
- strict: false,
91
- module: 'esnext',
92
- moduleResolution: 'bundler',
93
- resolveJsonModule: true,
94
- noImplicitAny: false,
95
- isolatedModules: true,
96
- baseUrl: 'src',
97
- noEmit: true,
98
- jsx: 'react-jsx',
99
- },
100
- include: ['src', '../src'],
101
- };
102
5
  /**
103
6
  * Write every generated file under .zth/. Overwrites on every run so the
104
7
  * shadow dir is a deterministic function of the CLI version — no drift.
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/shadow/scaffold.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA0G9C;;;GAGG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvG"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/shadow/scaffold.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;GAGG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvG"}
@@ -2,120 +2,23 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  const path = require('node:path');
4
4
  const fs = require('fs-extra');
5
+ const mfScaffold = require('@mondaydotcomorg/z2h-shared-utils/mf-scaffold');
5
6
 
6
7
  const _interopDefault = e => e && e.__esModule ? e : { default: e };
7
8
 
8
9
  const path__default = /*#__PURE__*/_interopDefault(path);
9
10
 
10
- const WRAPPER_TEMPLATE = `import '@vibe/core/tokens';
11
- import ConsumerApp from '../../src/index';
12
- import React from 'react';
13
- import * as ReactDOM from 'react-dom/client';
14
- import { ErrorBoundary } from 'react-error-boundary';
15
-
16
- const roots = new Map<HTMLElement, ReactDOM.Root>();
17
-
18
- export function init() {}
19
-
20
- export function render(element: HTMLElement) {
21
- const root = ReactDOM.createRoot(element);
22
- root.render(
23
- <ErrorBoundary fallback={<div style={{ padding: 16 }}>This app crashed.</div>}>
24
- <ConsumerApp />
25
- </ErrorBoundary>,
26
- );
27
- roots.set(element, root);
28
- }
29
-
30
- export function unmount({ element }: { element: HTMLElement }) {
31
- const root = roots.get(element);
32
- if (root) {
33
- root.unmount();
34
- roots.delete(element);
35
- }
36
- }
37
- `;
38
- const tridentrcTemplate = (appName, port) => `/** @type {import("@mondaydotcomorg/trident-user-config").MicroFrontendConfig} */
39
- const config = {
40
- appName: '${appName}',
41
- port: ${port},
42
- };
43
-
44
- module.exports = config;
45
- `;
46
- const dtsFileTemplate = () => `
47
- declare module '*.module.css' {
48
- const classNames: {
49
- [className: string]: string;
50
- };
51
- export = classNames;
52
- }
53
-
54
- declare module '*.module.scss' {
55
- const classNames: {
56
- [className: string]: string;
57
- };
58
- export = classNames;
59
- }
60
-
61
- declare module '*.jpg' {
62
- const e: string;
63
- export default e;
64
- }
65
- declare module '*.jpeg' {
66
- const e: string;
67
- export default e;
68
- }
69
- declare module '*.png' {
70
- const e: string;
71
- export default e;
72
- }
73
- declare module '*.riv' {
74
- const e: string;
75
- export default e;
76
- }
77
-
78
- declare module '*.wasm' {
79
- const e: string;
80
- export default e;
81
- }
82
-
83
- declare module '*.html?url' {
84
- const src: string;
85
- export default src;
86
- }
87
- `;
88
- const TSCONFIG_TEMPLATE = {
89
- compilerOptions: {
90
- target: 'es5',
91
- lib: ['dom', 'dom.iterable', 'esnext'],
92
- allowJs: false,
93
- skipLibCheck: true,
94
- esModuleInterop: true,
95
- allowSyntheticDefaultImports: true,
96
- strict: false,
97
- module: 'esnext',
98
- moduleResolution: 'bundler',
99
- resolveJsonModule: true,
100
- noImplicitAny: false,
101
- isolatedModules: true,
102
- baseUrl: 'src',
103
- noEmit: true,
104
- jsx: 'react-jsx',
105
- },
106
- include: ['src', '../src'],
107
- };
108
11
  /**
109
12
  * Write every generated file under .zth/. Overwrites on every run so the
110
13
  * shadow dir is a deterministic function of the CLI version — no drift.
111
14
  */
112
15
  async function scaffoldShadow(paths, appName, port) {
113
16
  await fs.ensureDir(paths.shadowSrc);
114
- await fs.writeFile(paths.shadowWrapper, WRAPPER_TEMPLATE);
115
- await fs.writeFile(paths.shadowTridentrc, tridentrcTemplate(appName, port));
17
+ await fs.writeFile(paths.shadowWrapper, mfScaffold.WRAPPER_TEMPLATE);
18
+ await fs.writeFile(paths.shadowTridentrc, mfScaffold.tridentrcTemplate(appName, port));
116
19
  await fs.writeFile(paths.shadowNvmrc, `${process.version}\n`);
117
- await fs.writeFile(paths.shadowDts, dtsFileTemplate());
118
- await fs.writeJson(paths.shadowTsconfig, TSCONFIG_TEMPLATE, { spaces: 2 });
20
+ await fs.writeFile(paths.shadowDts, mfScaffold.dtsFileTemplate());
21
+ await fs.writeJson(paths.shadowTsconfig, mfScaffold.TSCONFIG_TEMPLATE, { spaces: 2 });
119
22
  // Drop a stub README so contributors who open .zth/ know what they're looking at.
120
23
  await fs.writeFile(path__default.default.join(paths.shadowDir, 'README.md'), '# .zth/\nGenerated by z2h-cli. Do not edit — run `z2h-cli build` or `z2h-cli dev` to refresh.\n');
121
24
  }
@@ -57,6 +57,7 @@ const OAUTH_CHANNELS: Record<string, (caller: Caller) => ChannelMap> = {
57
57
  * setup) is wrong and retrying won't help; `502` means the upstream failed.
58
58
  *
59
59
  * Each key is wrapped with `z2h.channel.<key>` duration + error metrics, tagged by app.
60
+ * Failures also reach BI generically at the invoke level — see `runner.service.ts`.
60
61
  */
61
62
  export function buildChannels(appName: string, caller: Caller, declaredIntegrations: string[]): ChannelMap {
62
63
  const channels: ChannelMap = {
@@ -1,7 +1,9 @@
1
1
  import { logger } from '@mondaydotcomorg/trident-backend-runtime';
2
2
  import { createMondayModel } from '@mondaydotcomorg/kong-ai-gateway-sdk/langchain';
3
+ import { BackendRunnerEvents, trackEvent } from '@mondaydotcomorg/z2h-shared-utils/observability';
3
4
 
4
5
  import { ChannelError } from '../../backend-runner.errors';
6
+ import { BI_SOURCE } from '../../constants';
5
7
  import { ConfigurationKey, getConfiguration } from '../../../ignite-service';
6
8
  import { LLM_TIMEOUT_MS, withTimeout } from '../with-timeout';
7
9
  import type { ChannelMap, Caller } from '../../backend-runner.types';
@@ -207,6 +209,7 @@ export function makeLlmChannel(appName: string, caller: Caller) {
207
209
  LLM_TIMEOUT_MS,
208
210
  'LLM gateway',
209
211
  );
212
+ trackEvent(BackendRunnerEvents.llm.call, { appName, email: caller.email, modelId: model }, BI_SOURCE);
210
213
  return { content: extractText(response.content) };
211
214
  } catch (err) {
212
215
  throw toLlmChannelError(err);
@@ -1,9 +1,11 @@
1
1
  import { createHash } from 'node:crypto';
2
2
 
3
3
  import { context, logger } from '@mondaydotcomorg/trident-backend-runtime';
4
+ import { BackendRunnerEvents, trackEvent } from '@mondaydotcomorg/z2h-shared-utils/observability';
4
5
  import { getDefaultClient } from 'src/utils/elasticache';
5
6
 
6
7
  import { ChannelError } from '../../backend-runner.errors';
8
+ import { BI_SOURCE } from '../../constants';
7
9
  import { TIMEOUT_BACKSTOP_MS, withTimeout } from '../with-timeout';
8
10
  import { loadSnowflakeQuery } from './analytical-layer-loader';
9
11
  import { guardAndBindQuery, type SnowflakeParamValue } from './query-guard';
@@ -14,6 +16,7 @@ export const DEFAULT_TIMEOUT_MS = 60_000;
14
16
  export const MAX_TIMEOUT_MS = 300_000;
15
17
 
16
18
  const CACHE_PATTERN = 'z2h-snowflake-query' as const;
19
+ const LOG_TAG = 'z2h-snowflake';
17
20
 
18
21
  export interface SnowflakeRow {
19
22
  [column: string]: unknown;
@@ -131,8 +134,22 @@ export async function runSnowflakeQuery(
131
134
  const queryCacheKey = hashQuery(boundSql, params ?? {});
132
135
  const queryIdentifier = hashQueryShape(sql);
133
136
  const scope = cacheScope(useStateless);
137
+ const startMs = Date.now();
134
138
  const cached = scope ? await readCache(queryCacheKey, scope) : null;
135
139
  if (cached) {
140
+ const duration_ms = Date.now() - startMs;
141
+ trackEvent(BackendRunnerEvents.snowflake.queryExecuted, { appName, duration_ms, cache_hit: true }, BI_SOURCE);
142
+ logger.info(
143
+ {
144
+ tag: LOG_TAG,
145
+ queryHash: queryCacheKey,
146
+ queryIdentifier,
147
+ duration_ms,
148
+ rowCount: cached.length,
149
+ cache_hit: true,
150
+ },
151
+ 'query executed',
152
+ );
136
153
  return cached;
137
154
  }
138
155
 
@@ -164,12 +181,25 @@ export async function runSnowflakeQuery(
164
181
  'snowflake query',
165
182
  );
166
183
  } catch (err) {
184
+ // Failure BI tracking already happens generically at the invoke level
185
+ // (runner.service.ts's z2h_backend_invoke_failed, tagged with this
186
+ // ChannelError's `code`) — this stays a log only, for the queryHash/
187
+ // queryIdentifier that generic event can't know about.
188
+ const duration_ms = Date.now() - startMs;
189
+ const error = err instanceof Error ? err.message : String(err);
190
+ logger.error({ tag: LOG_TAG, queryHash: queryCacheKey, queryIdentifier, duration_ms, error }, 'query failed');
167
191
  throw toSnowflakeChannelError(err);
168
192
  }
169
193
  const rows = Array.isArray(data) ? data : [];
170
194
  if (scope) {
171
195
  await writeCache(queryCacheKey, scope, rows);
172
196
  }
197
+ const duration_ms = Date.now() - startMs;
198
+ trackEvent(BackendRunnerEvents.snowflake.queryExecuted, { appName, duration_ms, cache_hit: false }, BI_SOURCE);
199
+ logger.info(
200
+ { tag: LOG_TAG, queryHash: queryCacheKey, queryIdentifier, duration_ms, rowCount: rows.length, cache_hit: false },
201
+ 'query executed',
202
+ );
173
203
  return rows;
174
204
  }
175
205
 
@@ -184,7 +214,7 @@ async function readCache(queryCacheKey: string, scope: string): Promise<Snowflak
184
214
  const hit = await getDefaultClient().getCompressed(CACHE_PATTERN, { queryHash: queryCacheKey, scope });
185
215
  return hit ? (JSON.parse(hit.toString()) as SnowflakeRow[]) : null;
186
216
  } catch (err) {
187
- logger.warn({ tag: 'z2h-snowflake', error: err instanceof Error ? err.message : String(err) }, 'cache read failed');
217
+ logger.warn({ tag: LOG_TAG, error: err instanceof Error ? err.message : String(err) }, 'cache read failed');
188
218
  return null;
189
219
  }
190
220
  }
@@ -193,13 +223,29 @@ async function writeCache(queryCacheKey: string, scope: string, rows: SnowflakeR
193
223
  try {
194
224
  await getDefaultClient().setCompressed(CACHE_PATTERN, { queryHash: queryCacheKey, scope }, JSON.stringify(rows));
195
225
  } catch (err) {
196
- logger.warn(
197
- { tag: 'z2h-snowflake', error: err instanceof Error ? err.message : String(err) },
198
- 'cache write failed',
199
- );
226
+ logger.warn({ tag: LOG_TAG, error: err instanceof Error ? err.message : String(err) }, 'cache write failed');
200
227
  }
201
228
  }
202
229
 
230
+ /**
231
+ * The 4xx the analytical layer is known to return. Its own text is fetcher boilerplate
232
+ * ("Fetcher response returned NON-OK status=…") and carries no Snowflake detail, so these
233
+ * say what the app can do instead of forwarding it.
234
+ */
235
+ const UPSTREAM_4XX: Record<number, { code: string; message: string }> = {
236
+ 400: { code: 'snowflake_query_error', message: 'snowflake rejected the query — check the SQL' },
237
+ 401: { code: 'snowflake_unauthorized', message: 'snowflake authentication failed' },
238
+ 403: { code: 'snowflake_forbidden', message: 'snowflake authorization failed — the caller lacks grants' },
239
+ 413: {
240
+ code: 'snowflake_result_too_large',
241
+ message: 'snowflake result too large — add a LIMIT, select fewer columns, or aggregate in SQL',
242
+ },
243
+ 429: {
244
+ code: 'snowflake_rate_limited',
245
+ message: 'snowflake rate limit exceeded — too many queries in flight, retry shortly',
246
+ },
247
+ };
248
+
203
249
  /**
204
250
  * Map an analytical-layer failure to the right HTTP status by cause: the caller
205
251
  * lacking Snowflake grants vs. the app's SQL being wrong (compilation/syntax)
@@ -210,6 +256,37 @@ function toSnowflakeChannelError(err: unknown): ChannelError {
210
256
  return err;
211
257
  }
212
258
  const message = err instanceof Error ? err.message : String(err);
259
+
260
+ // The analytical layer rethrows monday-fetch's `HttpFetcherError` untouched, so
261
+ // an HTTP failure arrives with its real status on the error. Read it structurally
262
+ // rather than via `instanceof`: monday-fetch is a transitive dep and node_modules
263
+ // holds several copies, so the prototype chain is not reliably shared. Matches how
264
+ // the llm and monday channels already classify their upstream failures.
265
+ const status = (err as { status?: unknown })?.status;
266
+ if (typeof status === 'number') {
267
+ // Every 4xx the layer reports is about the request the app asked us to make, so it
268
+ // passes through with its own status — the app can act on it and it stays out of our
269
+ // 5xx alerting. Forwarding generically rather than enumerating means a status the
270
+ // layer adds later arrives intact instead of silently becoming a 502.
271
+ //
272
+ // 404 is the exception: it means our own call was misrouted, which the app can do
273
+ // nothing about, and forwarding it would be indistinguishable from
274
+ // BackendNotFoundError's "handler not found".
275
+ if (status >= 400 && status < 500 && status !== 404) {
276
+ const known = UPSTREAM_4XX[status];
277
+ return new ChannelError(
278
+ status,
279
+ known?.message ?? `snowflake rejected the query (${status})`,
280
+ known?.code ?? 'snowflake_request_rejected',
281
+ );
282
+ }
283
+ // 5xx (and that 404) are genuinely upstream failures. Returning here rather than
284
+ // falling through is deliberate — the heuristics below read the message, and an
285
+ // upstream 500 whose body happens to mention "compilation error" is still a 500.
286
+ return new ChannelError(502, `snowflake query failed: ${message}`, 'snowflake_upstream_error');
287
+ }
288
+
289
+ // No status: a driver-level error from the Snowflake SDK, classified by message.
213
290
  if (/insufficient privileg|not authorized|access denied|permission/i.test(message)) {
214
291
  return new ChannelError(403, `snowflake authorization error: ${message}`, 'snowflake_forbidden');
215
292
  }
@@ -62,6 +62,29 @@ export function makeSnowflakeChannel(appName: string) {
62
62
  * undefined,
63
63
  * { timeoutMs: 25_000 },
64
64
  * );
65
+ *
66
+ * @throws `400` `snowflake_invalid_argument` — `sql` was not a non-empty string.
67
+ * @throws `400` `snowflake_not_read_only` — the statement is not a SELECT.
68
+ * @throws `400` `snowflake_invalid_sql` — the statement could not be parsed.
69
+ * @throws `400` `snowflake_invalid_binding` — a `:name` placeholder has no matching entry in `params`.
70
+ * @throws `400` `snowflake_invalid_timeout` — `options.timeoutMs` is not a positive number.
71
+ * @throws `400` `snowflake_query_error` — the warehouse rejected the SQL (compilation error, unknown
72
+ * column, missing table). Fix the query; retrying is pointless.
73
+ * @throws `401` `snowflake_unauthorized` — the caller could not be authenticated to Snowflake.
74
+ * @throws `403` `snowflake_forbidden` — the caller is authenticated but lacks grants on what the
75
+ * query reads. Narrow the query or request access to the tables.
76
+ * @throws `413` `snowflake_result_too_large` — the result set is too big to serialize. Add a
77
+ * `LIMIT`, select fewer columns, or aggregate in SQL instead of returning raw rows.
78
+ * @throws `429` `snowflake_rate_limited` — too many queries in flight **for this user**, counted
79
+ * across every Z2H surface they have open. **This one is retryable** and it is the most common
80
+ * failure here. The cap is on concurrency, not on how much you query in total: `await` your
81
+ * queries in sequence, or batch them into one SQL statement, rather than issuing a screenful
82
+ * with `Promise.all`. Retrying immediately without backing off makes it worse for everyone.
83
+ * @throws `4xx` `snowflake_request_rejected` — any other rejection from the analytical layer,
84
+ * forwarded with its own status.
85
+ * @throws `502` `snowflake_upstream_error` — the analytical layer itself failed. Not the app's fault;
86
+ * retry later.
87
+ * @throws `504` `upstream_timeout` — the query exceeded its budget. Narrow it or raise `options.timeoutMs`.
65
88
  */
66
89
  'snowflake.query': async ([query, params, options]): Promise<SnowflakeRow[]> => {
67
90
  if (typeof query !== 'string' || query.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mondaydotcomorg/z2h-cli",
3
- "version": "0.24.2",
3
+ "version": "0.25.1",
4
4
  "bin": "./bin/z2h-cli.js",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -51,6 +51,7 @@
51
51
  ],
52
52
  "dependencies": {
53
53
  "@aws-sdk/client-s3": "^3.706.0",
54
+ "@mondaydotcomorg/z2h-shared-utils": "^0.3.1",
54
55
  "chokidar": "^3.6.0",
55
56
  "commander": "^12.1.0",
56
57
  "cors": "^2.8.6",
@@ -1,111 +1,13 @@
1
1
  import path from 'node:path';
2
2
  import { ensureDir, writeFile, writeJson } from 'fs-extra';
3
+ import {
4
+ WRAPPER_TEMPLATE,
5
+ tridentrcTemplate,
6
+ dtsFileTemplate,
7
+ TSCONFIG_TEMPLATE,
8
+ } from '@mondaydotcomorg/z2h-shared-utils/mf-scaffold';
3
9
  import type { ConsumerPaths } from '../types';
4
10
 
5
- const WRAPPER_TEMPLATE = `import '@vibe/core/tokens';
6
- import ConsumerApp from '../../src/index';
7
- import React from 'react';
8
- import * as ReactDOM from 'react-dom/client';
9
- import { ErrorBoundary } from 'react-error-boundary';
10
-
11
- const roots = new Map<HTMLElement, ReactDOM.Root>();
12
-
13
- export function init() {}
14
-
15
- export function render(element: HTMLElement) {
16
- const root = ReactDOM.createRoot(element);
17
- root.render(
18
- <ErrorBoundary fallback={<div style={{ padding: 16 }}>This app crashed.</div>}>
19
- <ConsumerApp />
20
- </ErrorBoundary>,
21
- );
22
- roots.set(element, root);
23
- }
24
-
25
- export function unmount({ element }: { element: HTMLElement }) {
26
- const root = roots.get(element);
27
- if (root) {
28
- root.unmount();
29
- roots.delete(element);
30
- }
31
- }
32
- `;
33
-
34
- const tridentrcTemplate = (appName: string, port: number): string =>
35
- `/** @type {import("@mondaydotcomorg/trident-user-config").MicroFrontendConfig} */
36
- const config = {
37
- appName: '${appName}',
38
- port: ${port},
39
- };
40
-
41
- module.exports = config;
42
- `;
43
-
44
- const dtsFileTemplate = (): string =>
45
- `
46
- declare module '*.module.css' {
47
- const classNames: {
48
- [className: string]: string;
49
- };
50
- export = classNames;
51
- }
52
-
53
- declare module '*.module.scss' {
54
- const classNames: {
55
- [className: string]: string;
56
- };
57
- export = classNames;
58
- }
59
-
60
- declare module '*.jpg' {
61
- const e: string;
62
- export default e;
63
- }
64
- declare module '*.jpeg' {
65
- const e: string;
66
- export default e;
67
- }
68
- declare module '*.png' {
69
- const e: string;
70
- export default e;
71
- }
72
- declare module '*.riv' {
73
- const e: string;
74
- export default e;
75
- }
76
-
77
- declare module '*.wasm' {
78
- const e: string;
79
- export default e;
80
- }
81
-
82
- declare module '*.html?url' {
83
- const src: string;
84
- export default src;
85
- }
86
- `;
87
-
88
- const TSCONFIG_TEMPLATE = {
89
- compilerOptions: {
90
- target: 'es5',
91
- lib: ['dom', 'dom.iterable', 'esnext'],
92
- allowJs: false,
93
- skipLibCheck: true,
94
- esModuleInterop: true,
95
- allowSyntheticDefaultImports: true,
96
- strict: false,
97
- module: 'esnext',
98
- moduleResolution: 'bundler',
99
- resolveJsonModule: true,
100
- noImplicitAny: false,
101
- isolatedModules: true,
102
- baseUrl: 'src',
103
- noEmit: true,
104
- jsx: 'react-jsx',
105
- },
106
- include: ['src', '../src'],
107
- };
108
-
109
11
  /**
110
12
  * Write every generated file under .zth/. Overwrites on every run so the
111
13
  * shadow dir is a deterministic function of the CLI version — no drift.