@mondaydotcomorg/z2h-cli 0.25.8 → 0.26.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 (42) hide show
  1. package/dist/commands/backend.js +2 -2
  2. package/dist/commands/deploy.d.ts +1 -0
  3. package/dist/commands/deploy.d.ts.map +1 -1
  4. package/dist/commands/deploy.js +19 -3
  5. package/dist/commands/generate.d.ts +12 -0
  6. package/dist/commands/generate.d.ts.map +1 -0
  7. package/dist/commands/generate.js +190 -0
  8. package/dist/commands/templates.d.ts +7 -0
  9. package/dist/commands/templates.d.ts.map +1 -0
  10. package/dist/commands/templates.js +23 -0
  11. package/dist/esm/commands/deploy.d.ts +1 -0
  12. package/dist/esm/commands/deploy.d.ts.map +1 -1
  13. package/dist/esm/commands/deploy.mjs +19 -4
  14. package/dist/esm/commands/generate.d.ts +12 -0
  15. package/dist/esm/commands/generate.d.ts.map +1 -0
  16. package/dist/esm/commands/generate.mjs +182 -0
  17. package/dist/esm/commands/templates.d.ts +7 -0
  18. package/dist/esm/commands/templates.d.ts.map +1 -0
  19. package/dist/esm/commands/templates.mjs +21 -0
  20. package/dist/esm/index.mjs +16 -1
  21. package/dist/esm/util/dashboard-templates/load-catalog.d.ts +22 -0
  22. package/dist/esm/util/dashboard-templates/load-catalog.d.ts.map +1 -0
  23. package/dist/esm/util/dashboard-templates/load-catalog.mjs +55 -0
  24. package/dist/esm/util/dashboard-templates/patch-bundle.d.ts +12 -0
  25. package/dist/esm/util/dashboard-templates/patch-bundle.d.ts.map +1 -0
  26. package/dist/esm/util/dashboard-templates/patch-bundle.mjs +108 -0
  27. package/dist/index.js +16 -1
  28. package/dist/util/app-name.js +2 -2
  29. package/dist/util/dashboard-templates/load-catalog.d.ts +22 -0
  30. package/dist/util/dashboard-templates/load-catalog.d.ts.map +1 -0
  31. package/dist/util/dashboard-templates/load-catalog.js +64 -0
  32. package/dist/util/dashboard-templates/patch-bundle.d.ts +12 -0
  33. package/dist/util/dashboard-templates/patch-bundle.d.ts.map +1 -0
  34. package/dist/util/dashboard-templates/patch-bundle.js +116 -0
  35. package/handler-api/backend-runner/channels/snowflake-channel/run-query.ts +3 -1
  36. package/package.json +5 -3
  37. package/src/commands/deploy.ts +24 -5
  38. package/src/commands/generate.ts +225 -0
  39. package/src/commands/templates.ts +20 -0
  40. package/src/index.ts +36 -1
  41. package/src/util/dashboard-templates/load-catalog.ts +80 -0
  42. package/src/util/dashboard-templates/patch-bundle.ts +127 -0
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  const path = require('node:path');
4
- const node_fs = require('node:fs');
4
+ const fs = require('node:fs');
5
5
  const clientS3 = require('@aws-sdk/client-s3');
6
6
  const util_logger = require('../util/logger.js');
7
7
  const util_appName = require('../util/app-name.js');
@@ -29,7 +29,7 @@ function currentAppName() {
29
29
  * play.
30
30
  */
31
31
  function hasHandlers(appDir) {
32
- return node_fs.existsSync(path__default.default.join(appDir, 'backend', 'handlers'));
32
+ return fs.existsSync(path__default.default.join(appDir, 'backend', 'handlers'));
33
33
  }
34
34
  /**
35
35
  * Read handlers, detect declared integrations, and upload to `prefix` when there
@@ -6,6 +6,7 @@ export interface DeployOptions {
6
6
  preview?: boolean;
7
7
  open?: boolean;
8
8
  description?: string;
9
+ prebuiltDir?: string;
9
10
  }
10
11
  export declare function deployCommand(opts?: DeployOptions): Promise<void>;
11
12
  //# sourceMappingURL=deploy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,4BAA4B,CAAC;AAkBtG,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAWD,wBAAsB,aAAa,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB3E"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,4BAA4B,CAAC;AAkBtG,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAWD,wBAAsB,aAAa,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB3E"}
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  const os = require('node:os');
4
+ const path = require('node:path');
4
5
  const fs = require('fs-extra');
5
6
  const util_logger = require('../util/logger.js');
6
7
  const shadow_paths = require('../shadow/paths.js');
@@ -26,6 +27,7 @@ const commands_tag = require('./tag.js');
26
27
  const _interopDefault = e => e && e.__esModule ? e : { default: e };
27
28
 
28
29
  const os__default = /*#__PURE__*/_interopDefault(os);
30
+ const path__default = /*#__PURE__*/_interopDefault(path);
29
31
 
30
32
  /** Who is publishing this version — the machine identity, else the OS user. */
31
33
  function whoDeployed() {
@@ -64,7 +66,15 @@ async function deployCommand(opts = {}) {
64
66
  * asset manifest Trident emitted.
65
67
  */
66
68
  async function buildAndStamp(opts) {
67
- const { assetManifest } = await commands_build.buildCommand({ publicUrl: opts.publicUrl });
69
+ let assetManifest;
70
+ if (opts.prebuiltDir) {
71
+ util_logger.info(`skipping build — reusing prebuilt bundle from ${opts.prebuiltDir}`);
72
+ await fs.copy(opts.prebuiltDir, opts.paths.shadowBuild, { overwrite: true });
73
+ assetManifest = await fs.readJson(path__default.default.join(opts.paths.shadowBuild, 'asset-manifest.json'));
74
+ }
75
+ else {
76
+ ({ assetManifest } = await commands_build.buildCommand({ publicUrl: opts.publicUrl }));
77
+ }
68
78
  const deployedBy = whoDeployed();
69
79
  return {
70
80
  ...assetManifest,
@@ -109,7 +119,13 @@ async function deployPreview({ paths, consumerName, appName, token, opts }) {
109
119
  // and anything unclassified reports as `unknown` via reasonOf() in runCommand.
110
120
  const nextVersion = (await util_auth_brokerCredentialProvider.fetchDeployVersion(previewAppName)) + 1;
111
121
  util_logger.info('building');
112
- const entry = await buildAndStamp({ paths, publicUrl: url, nextVersion, gitRemote: '' });
122
+ const entry = await buildAndStamp({
123
+ paths,
124
+ publicUrl: url,
125
+ nextVersion,
126
+ gitRemote: '',
127
+ prebuiltDir: opts.prebuiltDir,
128
+ });
113
129
  util_logger.info('getting sts credentials');
114
130
  const creds = await util_auth_brokerCredentialProvider.brokerCredentialProvider({ appName: previewAppName, previewId })();
115
131
  util_s3_client.setupS3Client(creds);
@@ -182,7 +198,7 @@ async function deployLive({ paths, consumerName, appName, token, opts }) {
182
198
  const keyPrefix = `${appName}/${nextVersion}`;
183
199
  const url = `${constants.BUCKET_READ_DNS_URL}/${keyPrefix}`;
184
200
  util_logger.info('building');
185
- const entry = await buildAndStamp({ paths, publicUrl: url, nextVersion, gitRemote });
201
+ const entry = await buildAndStamp({ paths, publicUrl: url, nextVersion, gitRemote, prebuiltDir: opts.prebuiltDir });
186
202
  if (opts.dryRun) {
187
203
  util_logger.output(`[z2h-cli] dry-run: would upload to s3://${constants.BUCKET_WRITE_NAME}/${keyPrefix}/ as v${version}`, {
188
204
  ok: true,
@@ -0,0 +1,12 @@
1
+ export interface GenerateOptions {
2
+ template: string;
3
+ input: string;
4
+ }
5
+ /**
6
+ * Copies a warm @mondaydotcomorg/z2h-dashboard-templates entry, patches its already-built bundle with real
7
+ * config data, and previews it — no `z2h-cli build`, no `yarn install`, no live deploy. See
8
+ * systems/owned/z2h/specs/agent-dashboard-generation.md ("Separating Z2H from its consumers")
9
+ * in bb-foundations for why this lives here instead of as agent-side MCP tools.
10
+ */
11
+ export declare function generateCommand(appName: string, opts: GenerateOptions): Promise<void>;
12
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAwHD;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA6E3F"}
@@ -0,0 +1,190 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ const path = require('node:path');
4
+ const fs = require('node:fs');
5
+ const node_module = require('node:module');
6
+ const execa = require('execa');
7
+ const fs$1 = require('fs-extra');
8
+ const util_workspace = require('../util/workspace.js');
9
+ const util_appName = require('../util/app-name.js');
10
+ const util_broker_app = require('../util/broker/app.js');
11
+ const util_dashboardTemplates_patchBundle = require('../util/dashboard-templates/patch-bundle.js');
12
+ const util_dashboardTemplates_loadCatalog = require('../util/dashboard-templates/load-catalog.js');
13
+ const util_git_repo = require('../util/git/repo.js');
14
+ const util_logger = require('../util/logger.js');
15
+
16
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
17
+ const _interopDefault = e => e && e.__esModule ? e : { default: e };
18
+
19
+ const path__default = /*#__PURE__*/_interopDefault(path);
20
+ const fs__default = /*#__PURE__*/_interopDefault(fs);
21
+
22
+ // Must match zth-backend-runner's route pattern — fail at generate time, not at first invocation.
23
+ const HANDLER_NAME_RE = /^[a-z][a-z0-9_]{0,63}$/;
24
+ function createSetupAPI(handlersDir) {
25
+ return {
26
+ createBackend(handlerName, content) {
27
+ if (!HANDLER_NAME_RE.test(handlerName)) {
28
+ throw new Error(`setupBackend: invalid handler name "${handlerName}" — must be lowercase snake_case, matching ${HANDLER_NAME_RE}`);
29
+ }
30
+ fs__default.default.writeFileSync(path__default.default.join(handlersDir, `${handlerName}.js`), content);
31
+ },
32
+ };
33
+ }
34
+ // Shape is entirely template-defined — validated wholesale against the template's own zod schema,
35
+ // not against any fields z2h-cli itself knows about.
36
+ function parseInput(raw, template) {
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(raw);
40
+ }
41
+ catch {
42
+ throw new Error('--input must be valid JSON');
43
+ }
44
+ const result = template.validateConfig(parsed);
45
+ if (!result.success) {
46
+ throw new Error(`--input failed validation against template "${template.id}": ${result.error}`);
47
+ }
48
+ return result.data;
49
+ }
50
+ async function isDirEmpty(dir) {
51
+ if (!(await fs$1.pathExists(dir))) {
52
+ return true;
53
+ }
54
+ return (await fs$1.readdir(dir)).length === 0;
55
+ }
56
+ const DEPENDENCY_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
57
+ // A real npm publish already resolves a template's workspace:* deps to real versions before
58
+ // packaging (see trident-monorepo's replaceWorkspaceProtocolInPackage) — this is a no-op there.
59
+ // In this repo's own dev setup (reached via yarn link, unpublished), that never ran, so we resolve
60
+ // it ourselves via require.resolve, same as node would, anchored to this file not to cwd.
61
+ function resolveWorkspaceProtocolDeps(appDir) {
62
+ const pkgPath = path__default.default.join(appDir, 'package.json');
63
+ const pkg = JSON.parse(fs__default.default.readFileSync(pkgPath, 'utf8'));
64
+ const nodeRequire = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('commands/generate.js', document.baseURI).href)));
65
+ let changed = false;
66
+ for (const field of DEPENDENCY_FIELDS) {
67
+ const deps = pkg[field];
68
+ if (!deps) {
69
+ continue;
70
+ }
71
+ for (const [depName, range] of Object.entries(deps)) {
72
+ if (!range.startsWith('workspace:')) {
73
+ continue;
74
+ }
75
+ const protocol = range.slice('workspace:'.length);
76
+ const depPkgPath = nodeRequire.resolve(`${depName}/package.json`);
77
+ const actualVersion = JSON.parse(fs__default.default.readFileSync(depPkgPath, 'utf8')).version;
78
+ deps[depName] = protocol === '*' ? actualVersion : `${protocol}${actualVersion}`;
79
+ changed = true;
80
+ }
81
+ }
82
+ if (changed) {
83
+ fs__default.default.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
84
+ }
85
+ }
86
+ // Only the top-level package.json needs renaming — deployCommand() reads its "name" to derive
87
+ // the registered app name/URL slug. `.zth/package.json` is NOT patched here: `generate` never
88
+ // rebuilds (deploy runs with --prebuilt-dir), so it's never read; a real rebuild later would
89
+ // regenerate it from the top-level name anyway (see shadow/sync.ts's writeShadowPkg).
90
+ function patchPackageJson(appDir, appName) {
91
+ const pkgPath = path__default.default.join(appDir, 'package.json');
92
+ const pkg = JSON.parse(fs__default.default.readFileSync(pkgPath, 'utf8'));
93
+ pkg.name = appName;
94
+ delete pkg.devDependencies?.['@mondaydotcomorg/z2h-shared-utils'];
95
+ fs__default.default.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
96
+ resolveWorkspaceProtocolDeps(appDir);
97
+ }
98
+ // The copied template already ships src/templateConfig.ts as a placeholder — real, typed source
99
+ // kept buildable on its own so the template can be verified independently of the generator.
100
+ function patchConfigSource(appDir, frontendConfig) {
101
+ const configPath = path__default.default.join(appDir, 'src', 'templateConfig.ts');
102
+ const placeholder = fs__default.default.readFileSync(configPath, 'utf8');
103
+ const patched = util_dashboardTemplates_patchBundle.patchOneFile(placeholder, frontendConfig);
104
+ if (patched === null) {
105
+ throw new Error(`generate: ${configPath} has no Z2H_CONFIG_V1 anchor — template's placeholder is stale`);
106
+ }
107
+ fs__default.default.writeFileSync(configPath, patched);
108
+ }
109
+ // Optional per-template extension point, mirroring setup-backend.ts's contract: decides which
110
+ // fields of the raw, validated --input are safe to ship to the browser. Templates with nothing
111
+ // sensitive to strip (no sql, no secrets) can skip shipping this file entirely — the raw config
112
+ // is used as-is. Compiled from setup-frontend.ts into dist/setup-frontend.js by dashboard-templates'
113
+ // build script (scripts/build-templates.mjs) — a standalone tsc pass, separate from the browser
114
+ // MF build, since this hook is Node-side and never ships to the browser.
115
+ function toFrontendConfig(templateDir, rawConfig) {
116
+ const setupFrontendPath = path__default.default.join(templateDir, 'dist', 'setup-frontend.js');
117
+ if (!fs__default.default.existsSync(setupFrontendPath)) {
118
+ return rawConfig;
119
+ }
120
+ const templateRequire = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('commands/generate.js', document.baseURI).href)));
121
+ const { toFrontendConfig: transform } = templateRequire(setupFrontendPath);
122
+ return transform(rawConfig);
123
+ }
124
+ /**
125
+ * Copies a warm @mondaydotcomorg/z2h-dashboard-templates entry, patches its already-built bundle with real
126
+ * config data, and previews it — no `z2h-cli build`, no `yarn install`, no live deploy. See
127
+ * systems/owned/z2h/specs/agent-dashboard-generation.md ("Separating Z2H from its consumers")
128
+ * in bb-foundations for why this lives here instead of as agent-side MCP tools.
129
+ */
130
+ async function generateCommand(appName, opts) {
131
+ const workspace = await util_workspace.assertWorkspace();
132
+ util_appName.validateAppName(appName);
133
+ const template = util_dashboardTemplates_loadCatalog.findTemplate(opts.template);
134
+ const rawConfig = parseInput(opts.input, template);
135
+ const appDir = path__default.default.join(workspace, appName);
136
+ if (!(await isDirEmpty(appDir))) {
137
+ throw new Error(`${appDir} is non-empty. Pick a different name or wipe it first.`);
138
+ }
139
+ const existing = await util_broker_app.fetchApp(util_appName.mfAppName(appName));
140
+ if (existing) {
141
+ throw new Error(`An app named "${appName}" already exists in the Z2H registry. Pick a different name.`);
142
+ }
143
+ util_logger.info(`copying template "${template.id}" → ${appDir}`);
144
+ // Excludes node_modules (installed into templateDir itself by build-templates.mjs, never
145
+ // needed by the generated app), .build-shadow (a scratch dir that build-templates.mjs
146
+ // normally cleans up itself — only present here if a prior build crashed mid-way), and
147
+ // setup-backend/setup-frontend/template (source .ts at templateDir root, compiled .js in dist/ —
148
+ // generation-time-only files, required() straight from template.templateDir/dist above, never
149
+ // read again once the app exists, so neither form should end up in the generated app).
150
+ fs__default.default.cpSync(template.templateDir, appDir, {
151
+ recursive: true,
152
+ filter: src => !/[/\\](node_modules|\.build-shadow|setup-backend\.(ts|js)|setup-frontend\.(ts|js)|template\.(ts|js))([/\\]|$)/.test('/' + path__default.default.relative(template.templateDir, src)),
153
+ });
154
+ patchPackageJson(appDir, appName);
155
+ fs__default.default.appendFileSync(path__default.default.join(appDir, '.gitignore'), 'dist/\n');
156
+ const frontendConfig = toFrontendConfig(template.templateDir, rawConfig);
157
+ patchConfigSource(appDir, frontendConfig);
158
+ const patchedBuildDir = path__default.default.join(appDir, '.zth', 'build-patched');
159
+ util_logger.info('patching prebuilt bundle with real config data');
160
+ // Source is templateDir/dist/ — built by @mondaydotcomorg/z2h-dashboard-templates' own `build` script (see
161
+ // packages/dashboard-templates/scripts/build-templates.mjs), never committed to git.
162
+ util_dashboardTemplates_patchBundle.patchBuiltBundle(path__default.default.join(appDir, 'dist'), patchedBuildDir, frontendConfig);
163
+ const setupBackendPath = path__default.default.join(template.templateDir, 'dist', 'setup-backend.js');
164
+ if (fs__default.default.existsSync(setupBackendPath)) {
165
+ util_logger.info('generating backend handlers via template.setupBackend');
166
+ const templateRequire = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('commands/generate.js', document.baseURI).href)));
167
+ const { setupBackend } = templateRequire(setupBackendPath);
168
+ const handlersDir = path__default.default.join(appDir, 'backend', 'handlers');
169
+ fs__default.default.mkdirSync(handlersDir, { recursive: true });
170
+ setupBackend(rawConfig, createSetupAPI(handlersDir));
171
+ }
172
+ util_logger.info(`initializing git repo for ${appName}...`);
173
+ await util_git_repo.initRepo(appDir, util_git_repo.defaultS3Remote(appName));
174
+ // generate only ever produces a preview — a disposable, shareable synthetic slot. It never
175
+ // registers a live version; that's a separate, explicit `z2h-cli deploy` the user runs
176
+ // themselves once they're happy with the preview.
177
+ util_logger.info('deploying preview');
178
+ const previewArgs = ['deploy', '--preview', '--prebuilt-dir', patchedBuildDir, '--json', '--open'];
179
+ const { stdout } = await execa.execa('z2h-cli', previewArgs, { cwd: appDir });
180
+ const previewResult = JSON.parse(stdout.trim().split('\n').filter(Boolean).at(-1) ?? '{}');
181
+ util_logger.output(`[z2h-cli] generated "${appName}" from template "${template.id}" ✅\n preview: ${previewResult.previewUrl}`, {
182
+ ok: true,
183
+ appName: util_appName.cleanAppName(appName),
184
+ templateId: template.id,
185
+ previewUrl: previewResult.previewUrl,
186
+ version: previewResult.version,
187
+ });
188
+ }
189
+
190
+ exports.generateCommand = generateCommand;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Prints the @mondaydotcomorg/z2h-dashboard-templates catalog — id, title, description, and the
3
+ * config schema as JSON Schema (each template defines its own whole-config shape;
4
+ * `generate-app-template` validates `--input` against whichever one is named).
5
+ */
6
+ export declare function templatesCommand(): Promise<void>;
7
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/commands/templates.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAWtD"}
@@ -0,0 +1,23 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ const util_logger = require('../util/logger.js');
4
+ const util_dashboardTemplates_loadCatalog = require('../util/dashboard-templates/load-catalog.js');
5
+
6
+ /**
7
+ * Prints the @mondaydotcomorg/z2h-dashboard-templates catalog — id, title, description, and the
8
+ * config schema as JSON Schema (each template defines its own whole-config shape;
9
+ * `generate-app-template` validates `--input` against whichever one is named).
10
+ */
11
+ async function templatesCommand() {
12
+ const templates = util_dashboardTemplates_loadCatalog.loadDashboardTemplatesCatalog().map(({ id, title, description, configSchema }) => ({
13
+ id,
14
+ title,
15
+ description,
16
+ configSchema,
17
+ }));
18
+ const templatesInfo = templates.map(t => `${t.id} — ${t.title}\n ${t.description}`);
19
+ const hasInfo = templatesInfo.length > 0;
20
+ util_logger.output(hasInfo ? templatesInfo.join('\n') : '(no templates in catalog)', { ok: true, templates });
21
+ }
22
+
23
+ exports.templatesCommand = templatesCommand;
@@ -6,6 +6,7 @@ export interface DeployOptions {
6
6
  preview?: boolean;
7
7
  open?: boolean;
8
8
  description?: string;
9
+ prebuiltDir?: string;
9
10
  }
10
11
  export declare function deployCommand(opts?: DeployOptions): Promise<void>;
11
12
  //# sourceMappingURL=deploy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/commands/deploy.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,4BAA4B,CAAC;AAkBtG,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAWD,wBAAsB,aAAa,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB3E"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/commands/deploy.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,4BAA4B,CAAC;AAkBtG,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAWD,wBAAsB,aAAa,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB3E"}
@@ -1,5 +1,6 @@
1
1
  import os from 'node:os';
2
- import { readJson, ensureDir } from 'fs-extra';
2
+ import path from 'node:path';
3
+ import { readJson, ensureDir, copy } from 'fs-extra';
3
4
  import { info, output, getOutputMode, warn } from '../util/logger.mjs';
4
5
  import { resolveConsumerPaths } from '../shadow/paths.mjs';
5
6
  import { fetchAppTags, fetchTagCatalog, setAppTags } from '../util/tags.mjs';
@@ -58,7 +59,15 @@ async function deployCommand(opts = {}) {
58
59
  * asset manifest Trident emitted.
59
60
  */
60
61
  async function buildAndStamp(opts) {
61
- const { assetManifest } = await buildCommand({ publicUrl: opts.publicUrl });
62
+ let assetManifest;
63
+ if (opts.prebuiltDir) {
64
+ info(`skipping build — reusing prebuilt bundle from ${opts.prebuiltDir}`);
65
+ await copy(opts.prebuiltDir, opts.paths.shadowBuild, { overwrite: true });
66
+ assetManifest = await readJson(path.join(opts.paths.shadowBuild, 'asset-manifest.json'));
67
+ }
68
+ else {
69
+ ({ assetManifest } = await buildCommand({ publicUrl: opts.publicUrl }));
70
+ }
62
71
  const deployedBy = whoDeployed();
63
72
  return {
64
73
  ...assetManifest,
@@ -103,7 +112,13 @@ async function deployPreview({ paths, consumerName, appName, token, opts }) {
103
112
  // and anything unclassified reports as `unknown` via reasonOf() in runCommand.
104
113
  const nextVersion = (await fetchDeployVersion(previewAppName)) + 1;
105
114
  info('building');
106
- const entry = await buildAndStamp({ paths, publicUrl: url, nextVersion, gitRemote: '' });
115
+ const entry = await buildAndStamp({
116
+ paths,
117
+ publicUrl: url,
118
+ nextVersion,
119
+ gitRemote: '',
120
+ prebuiltDir: opts.prebuiltDir,
121
+ });
107
122
  info('getting sts credentials');
108
123
  const creds = await brokerCredentialProvider({ appName: previewAppName, previewId })();
109
124
  setupS3Client(creds);
@@ -176,7 +191,7 @@ async function deployLive({ paths, consumerName, appName, token, opts }) {
176
191
  const keyPrefix = `${appName}/${nextVersion}`;
177
192
  const url = `${BUCKET_READ_DNS_URL}/${keyPrefix}`;
178
193
  info('building');
179
- const entry = await buildAndStamp({ paths, publicUrl: url, nextVersion, gitRemote });
194
+ const entry = await buildAndStamp({ paths, publicUrl: url, nextVersion, gitRemote, prebuiltDir: opts.prebuiltDir });
180
195
  if (opts.dryRun) {
181
196
  output(`[z2h-cli] dry-run: would upload to s3://${BUCKET_WRITE_NAME}/${keyPrefix}/ as v${version}`, {
182
197
  ok: true,
@@ -0,0 +1,12 @@
1
+ export interface GenerateOptions {
2
+ template: string;
3
+ input: string;
4
+ }
5
+ /**
6
+ * Copies a warm @mondaydotcomorg/z2h-dashboard-templates entry, patches its already-built bundle with real
7
+ * config data, and previews it — no `z2h-cli build`, no `yarn install`, no live deploy. See
8
+ * systems/owned/z2h/specs/agent-dashboard-generation.md ("Separating Z2H from its consumers")
9
+ * in bb-foundations for why this lives here instead of as agent-side MCP tools.
10
+ */
11
+ export declare function generateCommand(appName: string, opts: GenerateOptions): Promise<void>;
12
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../src/commands/generate.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAwHD;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA6E3F"}
@@ -0,0 +1,182 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import { execa } from 'execa';
5
+ import { pathExists, readdir } from 'fs-extra';
6
+ import { assertWorkspace } from '../util/workspace.mjs';
7
+ import { validateAppName, mfAppName, cleanAppName } from '../util/app-name.mjs';
8
+ import { fetchApp } from '../util/broker/app.mjs';
9
+ import { patchBuiltBundle, patchOneFile } from '../util/dashboard-templates/patch-bundle.mjs';
10
+ import { findTemplate } from '../util/dashboard-templates/load-catalog.mjs';
11
+ import { initRepo, defaultS3Remote } from '../util/git/repo.mjs';
12
+ import { info, output } from '../util/logger.mjs';
13
+
14
+ // Must match zth-backend-runner's route pattern — fail at generate time, not at first invocation.
15
+ const HANDLER_NAME_RE = /^[a-z][a-z0-9_]{0,63}$/;
16
+ function createSetupAPI(handlersDir) {
17
+ return {
18
+ createBackend(handlerName, content) {
19
+ if (!HANDLER_NAME_RE.test(handlerName)) {
20
+ throw new Error(`setupBackend: invalid handler name "${handlerName}" — must be lowercase snake_case, matching ${HANDLER_NAME_RE}`);
21
+ }
22
+ fs.writeFileSync(path.join(handlersDir, `${handlerName}.js`), content);
23
+ },
24
+ };
25
+ }
26
+ // Shape is entirely template-defined — validated wholesale against the template's own zod schema,
27
+ // not against any fields z2h-cli itself knows about.
28
+ function parseInput(raw, template) {
29
+ let parsed;
30
+ try {
31
+ parsed = JSON.parse(raw);
32
+ }
33
+ catch {
34
+ throw new Error('--input must be valid JSON');
35
+ }
36
+ const result = template.validateConfig(parsed);
37
+ if (!result.success) {
38
+ throw new Error(`--input failed validation against template "${template.id}": ${result.error}`);
39
+ }
40
+ return result.data;
41
+ }
42
+ async function isDirEmpty(dir) {
43
+ if (!(await pathExists(dir))) {
44
+ return true;
45
+ }
46
+ return (await readdir(dir)).length === 0;
47
+ }
48
+ const DEPENDENCY_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
49
+ // A real npm publish already resolves a template's workspace:* deps to real versions before
50
+ // packaging (see trident-monorepo's replaceWorkspaceProtocolInPackage) — this is a no-op there.
51
+ // In this repo's own dev setup (reached via yarn link, unpublished), that never ran, so we resolve
52
+ // it ourselves via require.resolve, same as node would, anchored to this file not to cwd.
53
+ function resolveWorkspaceProtocolDeps(appDir) {
54
+ const pkgPath = path.join(appDir, 'package.json');
55
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
56
+ const nodeRequire = createRequire(import.meta.url);
57
+ let changed = false;
58
+ for (const field of DEPENDENCY_FIELDS) {
59
+ const deps = pkg[field];
60
+ if (!deps) {
61
+ continue;
62
+ }
63
+ for (const [depName, range] of Object.entries(deps)) {
64
+ if (!range.startsWith('workspace:')) {
65
+ continue;
66
+ }
67
+ const protocol = range.slice('workspace:'.length);
68
+ const depPkgPath = nodeRequire.resolve(`${depName}/package.json`);
69
+ const actualVersion = JSON.parse(fs.readFileSync(depPkgPath, 'utf8')).version;
70
+ deps[depName] = protocol === '*' ? actualVersion : `${protocol}${actualVersion}`;
71
+ changed = true;
72
+ }
73
+ }
74
+ if (changed) {
75
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
76
+ }
77
+ }
78
+ // Only the top-level package.json needs renaming — deployCommand() reads its "name" to derive
79
+ // the registered app name/URL slug. `.zth/package.json` is NOT patched here: `generate` never
80
+ // rebuilds (deploy runs with --prebuilt-dir), so it's never read; a real rebuild later would
81
+ // regenerate it from the top-level name anyway (see shadow/sync.ts's writeShadowPkg).
82
+ function patchPackageJson(appDir, appName) {
83
+ const pkgPath = path.join(appDir, 'package.json');
84
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
85
+ pkg.name = appName;
86
+ delete pkg.devDependencies?.['@mondaydotcomorg/z2h-shared-utils'];
87
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
88
+ resolveWorkspaceProtocolDeps(appDir);
89
+ }
90
+ // The copied template already ships src/templateConfig.ts as a placeholder — real, typed source
91
+ // kept buildable on its own so the template can be verified independently of the generator.
92
+ function patchConfigSource(appDir, frontendConfig) {
93
+ const configPath = path.join(appDir, 'src', 'templateConfig.ts');
94
+ const placeholder = fs.readFileSync(configPath, 'utf8');
95
+ const patched = patchOneFile(placeholder, frontendConfig);
96
+ if (patched === null) {
97
+ throw new Error(`generate: ${configPath} has no Z2H_CONFIG_V1 anchor — template's placeholder is stale`);
98
+ }
99
+ fs.writeFileSync(configPath, patched);
100
+ }
101
+ // Optional per-template extension point, mirroring setup-backend.ts's contract: decides which
102
+ // fields of the raw, validated --input are safe to ship to the browser. Templates with nothing
103
+ // sensitive to strip (no sql, no secrets) can skip shipping this file entirely — the raw config
104
+ // is used as-is. Compiled from setup-frontend.ts into dist/setup-frontend.js by dashboard-templates'
105
+ // build script (scripts/build-templates.mjs) — a standalone tsc pass, separate from the browser
106
+ // MF build, since this hook is Node-side and never ships to the browser.
107
+ function toFrontendConfig(templateDir, rawConfig) {
108
+ const setupFrontendPath = path.join(templateDir, 'dist', 'setup-frontend.js');
109
+ if (!fs.existsSync(setupFrontendPath)) {
110
+ return rawConfig;
111
+ }
112
+ const templateRequire = createRequire(import.meta.url);
113
+ const { toFrontendConfig: transform } = templateRequire(setupFrontendPath);
114
+ return transform(rawConfig);
115
+ }
116
+ /**
117
+ * Copies a warm @mondaydotcomorg/z2h-dashboard-templates entry, patches its already-built bundle with real
118
+ * config data, and previews it — no `z2h-cli build`, no `yarn install`, no live deploy. See
119
+ * systems/owned/z2h/specs/agent-dashboard-generation.md ("Separating Z2H from its consumers")
120
+ * in bb-foundations for why this lives here instead of as agent-side MCP tools.
121
+ */
122
+ async function generateCommand(appName, opts) {
123
+ const workspace = await assertWorkspace();
124
+ validateAppName(appName);
125
+ const template = findTemplate(opts.template);
126
+ const rawConfig = parseInput(opts.input, template);
127
+ const appDir = path.join(workspace, appName);
128
+ if (!(await isDirEmpty(appDir))) {
129
+ throw new Error(`${appDir} is non-empty. Pick a different name or wipe it first.`);
130
+ }
131
+ const existing = await fetchApp(mfAppName(appName));
132
+ if (existing) {
133
+ throw new Error(`An app named "${appName}" already exists in the Z2H registry. Pick a different name.`);
134
+ }
135
+ info(`copying template "${template.id}" → ${appDir}`);
136
+ // Excludes node_modules (installed into templateDir itself by build-templates.mjs, never
137
+ // needed by the generated app), .build-shadow (a scratch dir that build-templates.mjs
138
+ // normally cleans up itself — only present here if a prior build crashed mid-way), and
139
+ // setup-backend/setup-frontend/template (source .ts at templateDir root, compiled .js in dist/ —
140
+ // generation-time-only files, required() straight from template.templateDir/dist above, never
141
+ // read again once the app exists, so neither form should end up in the generated app).
142
+ fs.cpSync(template.templateDir, appDir, {
143
+ recursive: true,
144
+ filter: src => !/[/\\](node_modules|\.build-shadow|setup-backend\.(ts|js)|setup-frontend\.(ts|js)|template\.(ts|js))([/\\]|$)/.test('/' + path.relative(template.templateDir, src)),
145
+ });
146
+ patchPackageJson(appDir, appName);
147
+ fs.appendFileSync(path.join(appDir, '.gitignore'), 'dist/\n');
148
+ const frontendConfig = toFrontendConfig(template.templateDir, rawConfig);
149
+ patchConfigSource(appDir, frontendConfig);
150
+ const patchedBuildDir = path.join(appDir, '.zth', 'build-patched');
151
+ info('patching prebuilt bundle with real config data');
152
+ // Source is templateDir/dist/ — built by @mondaydotcomorg/z2h-dashboard-templates' own `build` script (see
153
+ // packages/dashboard-templates/scripts/build-templates.mjs), never committed to git.
154
+ patchBuiltBundle(path.join(appDir, 'dist'), patchedBuildDir, frontendConfig);
155
+ const setupBackendPath = path.join(template.templateDir, 'dist', 'setup-backend.js');
156
+ if (fs.existsSync(setupBackendPath)) {
157
+ info('generating backend handlers via template.setupBackend');
158
+ const templateRequire = createRequire(import.meta.url);
159
+ const { setupBackend } = templateRequire(setupBackendPath);
160
+ const handlersDir = path.join(appDir, 'backend', 'handlers');
161
+ fs.mkdirSync(handlersDir, { recursive: true });
162
+ setupBackend(rawConfig, createSetupAPI(handlersDir));
163
+ }
164
+ info(`initializing git repo for ${appName}...`);
165
+ await initRepo(appDir, defaultS3Remote(appName));
166
+ // generate only ever produces a preview — a disposable, shareable synthetic slot. It never
167
+ // registers a live version; that's a separate, explicit `z2h-cli deploy` the user runs
168
+ // themselves once they're happy with the preview.
169
+ info('deploying preview');
170
+ const previewArgs = ['deploy', '--preview', '--prebuilt-dir', patchedBuildDir, '--json', '--open'];
171
+ const { stdout } = await execa('z2h-cli', previewArgs, { cwd: appDir });
172
+ const previewResult = JSON.parse(stdout.trim().split('\n').filter(Boolean).at(-1) ?? '{}');
173
+ output(`[z2h-cli] generated "${appName}" from template "${template.id}" ✅\n preview: ${previewResult.previewUrl}`, {
174
+ ok: true,
175
+ appName: cleanAppName(appName),
176
+ templateId: template.id,
177
+ previewUrl: previewResult.previewUrl,
178
+ version: previewResult.version,
179
+ });
180
+ }
181
+
182
+ export { generateCommand };
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Prints the @mondaydotcomorg/z2h-dashboard-templates catalog — id, title, description, and the
3
+ * config schema as JSON Schema (each template defines its own whole-config shape;
4
+ * `generate-app-template` validates `--input` against whichever one is named).
5
+ */
6
+ export declare function templatesCommand(): Promise<void>;
7
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../../src/commands/templates.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAWtD"}
@@ -0,0 +1,21 @@
1
+ import { output } from '../util/logger.mjs';
2
+ import { loadDashboardTemplatesCatalog } from '../util/dashboard-templates/load-catalog.mjs';
3
+
4
+ /**
5
+ * Prints the @mondaydotcomorg/z2h-dashboard-templates catalog — id, title, description, and the
6
+ * config schema as JSON Schema (each template defines its own whole-config shape;
7
+ * `generate-app-template` validates `--input` against whichever one is named).
8
+ */
9
+ async function templatesCommand() {
10
+ const templates = loadDashboardTemplatesCatalog().map(({ id, title, description, configSchema }) => ({
11
+ id,
12
+ title,
13
+ description,
14
+ configSchema,
15
+ }));
16
+ const templatesInfo = templates.map(t => `${t.id} — ${t.title}\n ${t.description}`);
17
+ const hasInfo = templatesInfo.length > 0;
18
+ output(hasInfo ? templatesInfo.join('\n') : '(no templates in catalog)', { ok: true, templates });
19
+ }
20
+
21
+ export { templatesCommand };