@mondaydotcomorg/z2h-cli 0.1.1 → 0.2.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 (43) hide show
  1. package/dist/commands/build.d.ts +1 -0
  2. package/dist/commands/build.d.ts.map +1 -1
  3. package/dist/commands/build.js +5 -1
  4. package/dist/commands/create-workspace.d.ts.map +1 -1
  5. package/dist/commands/create-workspace.js +1 -0
  6. package/dist/commands/create.d.ts.map +1 -1
  7. package/dist/commands/create.js +7 -0
  8. package/dist/commands/deploy.d.ts.map +1 -1
  9. package/dist/commands/deploy.js +13 -9
  10. package/dist/commands/dev.d.ts.map +1 -1
  11. package/dist/commands/dev.js +29 -0
  12. package/dist/esm/commands/build.d.ts +1 -0
  13. package/dist/esm/commands/build.d.ts.map +1 -1
  14. package/dist/esm/commands/build.mjs +5 -1
  15. package/dist/esm/commands/create-workspace.d.ts.map +1 -1
  16. package/dist/esm/commands/create-workspace.mjs +1 -0
  17. package/dist/esm/commands/create.d.ts.map +1 -1
  18. package/dist/esm/commands/create.mjs +7 -0
  19. package/dist/esm/commands/deploy.d.ts.map +1 -1
  20. package/dist/esm/commands/deploy.mjs +12 -9
  21. package/dist/esm/commands/dev.d.ts.map +1 -1
  22. package/dist/esm/commands/dev.mjs +25 -1
  23. package/dist/esm/shadow/scaffold.d.ts.map +1 -1
  24. package/dist/esm/shadow/scaffold.mjs +5 -0
  25. package/dist/esm/templates/app/claude-settings.mjs +15 -2
  26. package/dist/esm/util/manifest.d.ts +1 -0
  27. package/dist/esm/util/manifest.d.ts.map +1 -1
  28. package/dist/shadow/scaffold.d.ts.map +1 -1
  29. package/dist/shadow/scaffold.js +5 -0
  30. package/dist/templates/app/claude-settings.js +15 -1
  31. package/dist/util/manifest.d.ts +1 -0
  32. package/dist/util/manifest.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/src/commands/__tests__/create.test.ts +3 -0
  35. package/src/commands/build.ts +6 -1
  36. package/src/commands/create-workspace.ts +1 -0
  37. package/src/commands/create.ts +8 -0
  38. package/src/commands/deploy.ts +15 -11
  39. package/src/commands/dev.ts +26 -1
  40. package/src/shadow/scaffold.ts +5 -0
  41. package/src/templates/app/claude-settings.json +12 -0
  42. package/src/templates/workspace/yarnlock +0 -0
  43. package/src/util/manifest.ts +1 -0
@@ -1,5 +1,6 @@
1
1
  export interface BuildOptions {
2
2
  json?: boolean;
3
+ publicUrl?: string;
3
4
  }
4
5
  export declare function buildCommand(opts: BuildOptions): Promise<void>;
5
6
  //# sourceMappingURL=build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCpE"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCpE"}
@@ -22,7 +22,11 @@ async function buildCommand(opts) {
22
22
  stdio: 'inherit',
23
23
  });
24
24
  util_logger.info(`building "${ctx.appName}" via trident`);
25
- await execa.execa('yarn', ['build'], { cwd: ctx.paths.shadowDir, stdio: 'inherit' });
25
+ await execa.execa('yarn', ['build'], {
26
+ cwd: ctx.paths.shadowDir,
27
+ stdio: 'inherit',
28
+ env: { ...process.env, ...(opts.publicUrl ? { PUBLIC_URL: opts.publicUrl } : {}) },
29
+ });
26
30
  if (!(await fsExtra.pathExists(ctx.paths.shadowBuild))) {
27
31
  throw new Error(`Expected build output at ${ctx.paths.shadowBuild} — not found.`);
28
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"create-workspace.d.ts","sourceRoot":"","sources":["../../src/commands/create-workspace.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAiBD,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyDxF"}
1
+ {"version":3,"file":"create-workspace.d.ts","sourceRoot":"","sources":["../../src/commands/create-workspace.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAiBD,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0DxF"}
@@ -60,6 +60,7 @@ async function createWorkspaceCommand(opts) {
60
60
  fsExtra.writeFile(path__default.default.join(home, 'package.json'), util_templates.renderTemplate(pkgRaw, { __Z2H_CLI_VERSION__: cliVersion })),
61
61
  fsExtra.copy(path__default.default.join(wsTemplatesDir, 'gitignore'), path__default.default.join(home, '.gitignore')),
62
62
  fsExtra.copy(path__default.default.join(wsTemplatesDir, 'yarnrc.yml'), path__default.default.join(home, '.yarnrc.yml')),
63
+ fsExtra.copy(path__default.default.join(wsTemplatesDir, 'yarnlock'), path__default.default.join(home, 'yarn.lock')),
63
64
  fsExtra.copy(path__default.default.join(wsTemplatesDir, 'nvmrc'), path__default.default.join(home, '.nvmrc')),
64
65
  fsExtra.copy(path__default.default.join(wsTemplatesDir, 'npmrc'), path__default.default.join(home, '.npmrc')),
65
66
  fsExtra.copy(path__default.default.join(wsTemplatesDir, 'README.md'), path__default.default.join(home, 'README.md')),
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA0BD,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA0EvF"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA0BD,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgFvF"}
@@ -7,6 +7,8 @@ const execa = require('execa');
7
7
  const util_logger = require('../util/logger.js');
8
8
  const util_workspace = require('../util/workspace.js');
9
9
  const util_templates = require('../util/templates.js');
10
+ const util_manifest = require('../util/manifest.js');
11
+ const util_appName = require('../util/app-name.js');
10
12
 
11
13
  const _interopDefault = e => e && e.__esModule ? e : { default: e };
12
14
 
@@ -58,6 +60,11 @@ async function createCommand(appName, opts) {
58
60
  if (!(await isDirEmpty(appDir))) {
59
61
  throw new Error(`${appDir} is non-empty. Pick a different name or wipe it first.`);
60
62
  }
63
+ // Check for manifest collision (network call — after all local checks).
64
+ const manifest = await util_manifest.readManifest();
65
+ if (manifest[util_appName.mfAppName(appName)]) {
66
+ throw new Error(`An app named "${appName}" already exists in the Z2H registry. Pick a different name.`);
67
+ }
61
68
  // Compute substitutions.
62
69
  const vars = {
63
70
  __APP_NAME__: appName,
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAaD,wBAAsB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA6EtE"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAaD,wBAAsB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgFtE"}
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  const path = require('node:path');
4
+ const os = require('node:os');
4
5
  const fsExtra = require('fs-extra');
5
6
  require('execa');
6
7
  const util_logger = require('../util/logger.js');
@@ -14,6 +15,7 @@ const commands_build = require('./build.js');
14
15
  const _interopDefault = e => e && e.__esModule ? e : { default: e };
15
16
 
16
17
  const path__default = /*#__PURE__*/_interopDefault(path);
18
+ const os__default = /*#__PURE__*/_interopDefault(os);
17
19
 
18
20
  const APONO_BUNDLE = '<APONO_BUNDLE_NAME>'; // TODO: replace with actual bundle name once infra setup is complete
19
21
  async function requestAponoAccess() {
@@ -28,14 +30,19 @@ async function deployCommand(opts) {
28
30
  }
29
31
  util_logger.info('checking S3 bucket');
30
32
  await util_s3_client.assertBucketExists();
31
- util_logger.info('building');
32
- await commands_build.buildCommand({ json: opts.json });
33
33
  const paths = shadow_paths.resolveConsumerPaths(process.cwd());
34
34
  const consumerPkg = (await fsExtra.readJson(paths.consumerPkg));
35
35
  if (!consumerPkg.name) {
36
36
  throw new Error('Consumer package.json must have a "name" field');
37
37
  }
38
38
  const appName = util_appName.mfAppName(consumerPkg.name);
39
+ util_logger.info('reading current Z2H manifest');
40
+ const manifest = await util_manifest.readManifest();
41
+ const version = util_manifest.nextVersion(manifest, appName);
42
+ const keyPrefix = `${appName}/${version}`;
43
+ const url = `${util_s3_client.BUCKET_READ_DNS_URL}/${keyPrefix}`;
44
+ util_logger.info('building');
45
+ await commands_build.buildCommand({ json: opts.json, publicUrl: url });
39
46
  if (!(await fsExtra.pathExists(paths.shadowBuild))) {
40
47
  throw new Error(`No build output at ${paths.shadowBuild} after build step.`);
41
48
  }
@@ -45,11 +52,6 @@ async function deployCommand(opts) {
45
52
  throw new Error(`Missing asset-manifest.json at ${assetManifestPath}.`);
46
53
  }
47
54
  const assetManifest = (await fsExtra.readJson(assetManifestPath));
48
- util_logger.info('reading current Z2H manifest');
49
- const manifest = await util_manifest.readManifest();
50
- const version = util_manifest.nextVersion(manifest, appName);
51
- const keyPrefix = `${appName}/${version}`;
52
- const url = `${util_s3_client.BUCKET_READ_DNS_URL}/${keyPrefix}`;
53
55
  if (opts.dryRun) {
54
56
  util_logger.output(`[z2h-cli] dry-run: would upload to s3://${util_s3_client.BUCKET_WRITE_NAME}/${keyPrefix}/ as v${version}`, {
55
57
  ok: true,
@@ -64,13 +66,15 @@ async function deployCommand(opts) {
64
66
  return;
65
67
  }
66
68
  await requestAponoAccess();
67
- util_logger.info(`uploading build → s3://${util_s3_client.BUCKET_WRITE_NAME}/${keyPrefix}/`);
68
- await util_s3_upload.uploadDir(paths.shadowBuild, keyPrefix);
69
+ const deployedBy = os__default.default.userInfo().username;
69
70
  const entry = {
70
71
  ...assetManifest,
71
72
  version,
72
73
  url,
74
+ deployedBy,
73
75
  };
76
+ util_logger.info(`uploading build → s3://${util_s3_client.BUCKET_WRITE_NAME}/${keyPrefix}/`);
77
+ await util_s3_upload.uploadDir(paths.shadowBuild, keyPrefix);
74
78
  util_logger.info(`updating manifest entry "${appName}" → v${version}`);
75
79
  await util_manifest.writeManifest(util_manifest.upsertEntry(manifest, appName, entry));
76
80
  // The host MF parses the app name from the URL and prepends `mf-` itself
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAID,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgDhE"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAID,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEhE"}
@@ -1,5 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
+ const path = require('node:path');
4
+ const fs = require('node:fs');
3
5
  const fsExtra = require('fs-extra');
4
6
  const chokidar = require('chokidar');
5
7
  const execa = require('execa');
@@ -8,6 +10,11 @@ const shadow_prepare = require('../shadow/prepare.js');
8
10
  const shadow_consumerPkg = require('../shadow/consumer-pkg.js');
9
11
  const shadow_sync = require('../shadow/sync.js');
10
12
 
13
+ const _interopDefault = e => e && e.__esModule ? e : { default: e };
14
+
15
+ const path__default = /*#__PURE__*/_interopDefault(path);
16
+ const fs__default = /*#__PURE__*/_interopDefault(fs);
17
+
11
18
  const TRIDENT_READY_PATTERN = /Server started at/i;
12
19
  async function devCommand(opts) {
13
20
  if (opts.json) {
@@ -24,6 +31,28 @@ async function devCommand(opts) {
24
31
  cwd: ctx.paths.shadowDir,
25
32
  stdio: ['inherit', 'pipe', 'inherit'],
26
33
  });
34
+ const pidFile = path__default.default.join(ctx.paths.shadowDir, 'dev.pid');
35
+ if (server.pid) {
36
+ await fsExtra.writeFile(pidFile, String(server.pid));
37
+ }
38
+ const cleanupPid = () => {
39
+ try {
40
+ fs__default.default.unlinkSync(pidFile);
41
+ }
42
+ catch (_) {
43
+ // file already gone — ignore
44
+ }
45
+ };
46
+ server.on('exit', cleanupPid);
47
+ process.on('exit', cleanupPid);
48
+ process.on('SIGINT', () => {
49
+ server.kill();
50
+ process.exit(0);
51
+ });
52
+ process.on('SIGTERM', () => {
53
+ server.kill();
54
+ process.exit(0);
55
+ });
27
56
  let readyPrinted = false;
28
57
  server.stdout?.on('data', (chunk) => {
29
58
  process.stdout.write(chunk);
@@ -1,5 +1,6 @@
1
1
  export interface BuildOptions {
2
2
  json?: boolean;
3
+ publicUrl?: string;
3
4
  }
4
5
  export declare function buildCommand(opts: BuildOptions): Promise<void>;
5
6
  //# sourceMappingURL=build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCpE"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCpE"}
@@ -20,7 +20,11 @@ async function buildCommand(opts) {
20
20
  stdio: 'inherit',
21
21
  });
22
22
  info(`building "${ctx.appName}" via trident`);
23
- await execa('yarn', ['build'], { cwd: ctx.paths.shadowDir, stdio: 'inherit' });
23
+ await execa('yarn', ['build'], {
24
+ cwd: ctx.paths.shadowDir,
25
+ stdio: 'inherit',
26
+ env: { ...process.env, ...(opts.publicUrl ? { PUBLIC_URL: opts.publicUrl } : {}) },
27
+ });
24
28
  if (!(await pathExists(ctx.paths.shadowBuild))) {
25
29
  throw new Error(`Expected build output at ${ctx.paths.shadowBuild} — not found.`);
26
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"create-workspace.d.ts","sourceRoot":"","sources":["../../../src/commands/create-workspace.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAiBD,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyDxF"}
1
+ {"version":3,"file":"create-workspace.d.ts","sourceRoot":"","sources":["../../../src/commands/create-workspace.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAiBD,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0DxF"}
@@ -54,6 +54,7 @@ async function createWorkspaceCommand(opts) {
54
54
  writeFile(path.join(home, 'package.json'), renderTemplate(pkgRaw, { __Z2H_CLI_VERSION__: cliVersion })),
55
55
  copy(path.join(wsTemplatesDir, 'gitignore'), path.join(home, '.gitignore')),
56
56
  copy(path.join(wsTemplatesDir, 'yarnrc.yml'), path.join(home, '.yarnrc.yml')),
57
+ copy(path.join(wsTemplatesDir, 'yarnlock'), path.join(home, 'yarn.lock')),
57
58
  copy(path.join(wsTemplatesDir, 'nvmrc'), path.join(home, '.nvmrc')),
58
59
  copy(path.join(wsTemplatesDir, 'npmrc'), path.join(home, '.npmrc')),
59
60
  copy(path.join(wsTemplatesDir, 'README.md'), path.join(home, 'README.md')),
@@ -1 +1 @@
1
- {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/commands/create.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA0BD,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA0EvF"}
1
+ {"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../../../src/commands/create.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AA0BD,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgFvF"}
@@ -5,6 +5,8 @@ import { execa } from 'execa';
5
5
  import { setOutputMode, info, output } from '../util/logger.mjs';
6
6
  import { assertWorkspace } from '../util/workspace.mjs';
7
7
  import { APP_TEMPLATES, getTemplatesDir, renderTemplate } from '../util/templates.mjs';
8
+ import { readManifest } from '../util/manifest.mjs';
9
+ import { mfAppName } from '../util/app-name.mjs';
8
10
 
9
11
  const APP_NAME_RE = /^[a-z][a-z0-9-]{1,49}$/;
10
12
  /**
@@ -52,6 +54,11 @@ async function createCommand(appName, opts) {
52
54
  if (!(await isDirEmpty(appDir))) {
53
55
  throw new Error(`${appDir} is non-empty. Pick a different name or wipe it first.`);
54
56
  }
57
+ // Check for manifest collision (network call — after all local checks).
58
+ const manifest = await readManifest();
59
+ if (manifest[mfAppName(appName)]) {
60
+ throw new Error(`An app named "${appName}" already exists in the Z2H registry. Pick a different name.`);
61
+ }
55
62
  // Compute substitutions.
56
63
  const vars = {
57
64
  __APP_NAME__: appName,
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/commands/deploy.ts"],"names":[],"mappings":"AAoBA,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAaD,wBAAsB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA6EtE"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../../src/commands/deploy.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAaD,wBAAsB,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgFtE"}
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path';
2
+ import os from 'node:os';
2
3
  import { readJson, pathExists } from 'fs-extra';
3
4
  import 'execa';
4
5
  import { setOutputMode, info, output, warn } from '../util/logger.mjs';
@@ -22,14 +23,19 @@ async function deployCommand(opts) {
22
23
  }
23
24
  info('checking S3 bucket');
24
25
  await assertBucketExists();
25
- info('building');
26
- await buildCommand({ json: opts.json });
27
26
  const paths = resolveConsumerPaths(process.cwd());
28
27
  const consumerPkg = (await readJson(paths.consumerPkg));
29
28
  if (!consumerPkg.name) {
30
29
  throw new Error('Consumer package.json must have a "name" field');
31
30
  }
32
31
  const appName = mfAppName(consumerPkg.name);
32
+ info('reading current Z2H manifest');
33
+ const manifest = await readManifest();
34
+ const version = nextVersion(manifest, appName);
35
+ const keyPrefix = `${appName}/${version}`;
36
+ const url = `${BUCKET_READ_DNS_URL}/${keyPrefix}`;
37
+ info('building');
38
+ await buildCommand({ json: opts.json, publicUrl: url });
33
39
  if (!(await pathExists(paths.shadowBuild))) {
34
40
  throw new Error(`No build output at ${paths.shadowBuild} after build step.`);
35
41
  }
@@ -39,11 +45,6 @@ async function deployCommand(opts) {
39
45
  throw new Error(`Missing asset-manifest.json at ${assetManifestPath}.`);
40
46
  }
41
47
  const assetManifest = (await readJson(assetManifestPath));
42
- info('reading current Z2H manifest');
43
- const manifest = await readManifest();
44
- const version = nextVersion(manifest, appName);
45
- const keyPrefix = `${appName}/${version}`;
46
- const url = `${BUCKET_READ_DNS_URL}/${keyPrefix}`;
47
48
  if (opts.dryRun) {
48
49
  output(`[z2h-cli] dry-run: would upload to s3://${BUCKET_WRITE_NAME}/${keyPrefix}/ as v${version}`, {
49
50
  ok: true,
@@ -58,13 +59,15 @@ async function deployCommand(opts) {
58
59
  return;
59
60
  }
60
61
  await requestAponoAccess();
61
- info(`uploading build → s3://${BUCKET_WRITE_NAME}/${keyPrefix}/`);
62
- await uploadDir(paths.shadowBuild, keyPrefix);
62
+ const deployedBy = os.userInfo().username;
63
63
  const entry = {
64
64
  ...assetManifest,
65
65
  version,
66
66
  url,
67
+ deployedBy,
67
68
  };
69
+ info(`uploading build → s3://${BUCKET_WRITE_NAME}/${keyPrefix}/`);
70
+ await uploadDir(paths.shadowBuild, keyPrefix);
68
71
  info(`updating manifest entry "${appName}" → v${version}`);
69
72
  await writeManifest(upsertEntry(manifest, appName, entry));
70
73
  // The host MF parses the app name from the URL and prepends `mf-` itself
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../src/commands/dev.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAID,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgDhE"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../../src/commands/dev.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAID,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAuEhE"}
@@ -1,4 +1,6 @@
1
- import { readJson } from 'fs-extra';
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { writeFile, readJson } from 'fs-extra';
2
4
  import { watch } from 'chokidar';
3
5
  import { execa } from 'execa';
4
6
  import { setOutputMode, info, output, warn, errorLog } from '../util/logger.mjs';
@@ -22,6 +24,28 @@ async function devCommand(opts) {
22
24
  cwd: ctx.paths.shadowDir,
23
25
  stdio: ['inherit', 'pipe', 'inherit'],
24
26
  });
27
+ const pidFile = path.join(ctx.paths.shadowDir, 'dev.pid');
28
+ if (server.pid) {
29
+ await writeFile(pidFile, String(server.pid));
30
+ }
31
+ const cleanupPid = () => {
32
+ try {
33
+ fs.unlinkSync(pidFile);
34
+ }
35
+ catch (_) {
36
+ // file already gone — ignore
37
+ }
38
+ };
39
+ server.on('exit', cleanupPid);
40
+ process.on('exit', cleanupPid);
41
+ process.on('SIGINT', () => {
42
+ server.kill();
43
+ process.exit(0);
44
+ });
45
+ process.on('SIGTERM', () => {
46
+ server.kill();
47
+ process.exit(0);
48
+ });
25
49
  let readyPrinted = false;
26
50
  server.stdout?.on('data', (chunk) => {
27
51
  process.stdout.write(chunk);
@@ -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;AAqG9C;;;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":"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"}
@@ -73,6 +73,11 @@ declare module '*.wasm' {
73
73
  const e: string;
74
74
  export default e;
75
75
  }
76
+
77
+ declare module '*.html?url' {
78
+ const src: string;
79
+ export default src;
80
+ }
76
81
  `;
77
82
  const TSCONFIG_TEMPLATE = {
78
83
  compilerOptions: {
@@ -3,8 +3,21 @@ var permissions = {
3
3
  "Skill(z2h:*)"
4
4
  ]
5
5
  };
6
+ var hooks = {
7
+ Stop: [
8
+ {
9
+ hooks: [
10
+ {
11
+ type: "command",
12
+ command: "if [ -f .zth/dev.pid ]; then kill $(cat .zth/dev.pid) 2>/dev/null; rm .zth/dev.pid; fi"
13
+ }
14
+ ]
15
+ }
16
+ ]
17
+ };
6
18
  const claudeSettings = {
7
- permissions: permissions
19
+ permissions: permissions,
20
+ hooks: hooks
8
21
  };
9
22
 
10
- export { claudeSettings as default, permissions };
23
+ export { claudeSettings as default, hooks, permissions };
@@ -22,6 +22,7 @@ export interface AssetManifestFile {
22
22
  export interface ManifestEntry extends AssetManifestFile {
23
23
  version: number;
24
24
  url: string;
25
+ deployedBy: string;
25
26
  }
26
27
  export type Manifest = Record<string, ManifestEntry>;
27
28
  export declare function readManifest(): Promise<Manifest>;
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/util/manifest.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,OAAO,CAAC;IACpB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAErD,wBAAsB,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC,CAQtD;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,UAI1D;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,QAAQ,CAE3F;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrE"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/util/manifest.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,OAAO,CAAC;IACpB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAErD,wBAAsB,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC,CAQtD;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,UAI1D;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,QAAQ,CAE3F;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrE"}
@@ -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;AAqG9C;;;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":"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"}
@@ -79,6 +79,11 @@ declare module '*.wasm' {
79
79
  const e: string;
80
80
  export default e;
81
81
  }
82
+
83
+ declare module '*.html?url' {
84
+ const src: string;
85
+ export default src;
86
+ }
82
87
  `;
83
88
  const TSCONFIG_TEMPLATE = {
84
89
  compilerOptions: {
@@ -5,9 +5,23 @@ var permissions = {
5
5
  "Skill(z2h:*)"
6
6
  ]
7
7
  };
8
+ var hooks = {
9
+ Stop: [
10
+ {
11
+ hooks: [
12
+ {
13
+ type: "command",
14
+ command: "if [ -f .zth/dev.pid ]; then kill $(cat .zth/dev.pid) 2>/dev/null; rm .zth/dev.pid; fi"
15
+ }
16
+ ]
17
+ }
18
+ ]
19
+ };
8
20
  const claudeSettings = {
9
- permissions: permissions
21
+ permissions: permissions,
22
+ hooks: hooks
10
23
  };
11
24
 
12
25
  exports.default = claudeSettings;
26
+ exports.hooks = hooks;
13
27
  exports.permissions = permissions;
@@ -22,6 +22,7 @@ export interface AssetManifestFile {
22
22
  export interface ManifestEntry extends AssetManifestFile {
23
23
  version: number;
24
24
  url: string;
25
+ deployedBy: string;
25
26
  }
26
27
  export type Manifest = Record<string, ManifestEntry>;
27
28
  export declare function readManifest(): Promise<Manifest>;
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/util/manifest.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,OAAO,CAAC;IACpB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAErD,wBAAsB,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC,CAQtD;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,UAI1D;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,QAAQ,CAE3F;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrE"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/util/manifest.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,OAAO,CAAC;IACpB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;IACnC,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,aAAc,SAAQ,iBAAiB;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAErD,wBAAsB,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC,CAQtD;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,UAI1D;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,QAAQ,CAE3F;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mondaydotcomorg/z2h-cli",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "bin": "./bin/z2h-cli.js",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -5,11 +5,14 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
5
 
6
6
  import { execa } from 'execa';
7
7
  import { createCommand } from '../create';
8
+ import * as manifest from '../../util/manifest';
8
9
 
9
10
  vi.mock('execa', () => ({
10
11
  execa: vi.fn().mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 }),
11
12
  }));
12
13
 
14
+ vi.spyOn(manifest, 'readManifest').mockResolvedValue({});
15
+
13
16
  async function makeWorkspace(tmp: string): Promise<void> {
14
17
  await ensureDir(tmp);
15
18
  await writeJson(path.join(tmp, 'package.json'), {
@@ -5,6 +5,7 @@ import { prepareShadow } from '../shadow/prepare';
5
5
 
6
6
  export interface BuildOptions {
7
7
  json?: boolean;
8
+ publicUrl?: string;
8
9
  }
9
10
 
10
11
  export async function buildCommand(opts: BuildOptions): Promise<void> {
@@ -27,7 +28,11 @@ export async function buildCommand(opts: BuildOptions): Promise<void> {
27
28
  });
28
29
 
29
30
  info(`building "${ctx.appName}" via trident`);
30
- await execa('yarn', ['build'], { cwd: ctx.paths.shadowDir, stdio: 'inherit' });
31
+ await execa('yarn', ['build'], {
32
+ cwd: ctx.paths.shadowDir,
33
+ stdio: 'inherit',
34
+ env: { ...process.env, ...(opts.publicUrl ? { PUBLIC_URL: opts.publicUrl } : {}) },
35
+ });
31
36
 
32
37
  if (!(await pathExists(ctx.paths.shadowBuild))) {
33
38
  throw new Error(`Expected build output at ${ctx.paths.shadowBuild} — not found.`);
@@ -62,6 +62,7 @@ export async function createWorkspaceCommand(opts: CreateWorkspaceOptions): Prom
62
62
  writeFile(path.join(home, 'package.json'), renderTemplate(pkgRaw, { __Z2H_CLI_VERSION__: cliVersion })),
63
63
  copy(path.join(wsTemplatesDir, 'gitignore'), path.join(home, '.gitignore')),
64
64
  copy(path.join(wsTemplatesDir, 'yarnrc.yml'), path.join(home, '.yarnrc.yml')),
65
+ copy(path.join(wsTemplatesDir, 'yarnlock'), path.join(home, 'yarn.lock')),
65
66
  copy(path.join(wsTemplatesDir, 'nvmrc'), path.join(home, '.nvmrc')),
66
67
  copy(path.join(wsTemplatesDir, 'npmrc'), path.join(home, '.npmrc')),
67
68
  copy(path.join(wsTemplatesDir, 'README.md'), path.join(home, 'README.md')),
@@ -5,6 +5,8 @@ import { execa } from 'execa';
5
5
  import { info, output, setOutputMode } from '../util/logger';
6
6
  import { assertWorkspace } from '../util/workspace';
7
7
  import { getTemplatesDir, renderTemplate, APP_TEMPLATES, type AppTemplate } from '../util/templates';
8
+ import { readManifest } from '../util/manifest';
9
+ import { mfAppName } from '../util/app-name';
8
10
 
9
11
  export interface CreateOptions {
10
12
  template?: string;
@@ -69,6 +71,12 @@ export async function createCommand(appName: string, opts: CreateOptions): Promi
69
71
  throw new Error(`${appDir} is non-empty. Pick a different name or wipe it first.`);
70
72
  }
71
73
 
74
+ // Check for manifest collision (network call — after all local checks).
75
+ const manifest = await readManifest();
76
+ if (manifest[mfAppName(appName)]) {
77
+ throw new Error(`An app named "${appName}" already exists in the Z2H registry. Pick a different name.`);
78
+ }
79
+
72
80
  // Compute substitutions.
73
81
  const vars = {
74
82
  __APP_NAME__: appName,
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path';
2
+ import os from 'node:os';
2
3
  import { pathExists, readJson } from 'fs-extra';
3
4
  import { execa } from 'execa';
4
5
  import { info, warn, output, setOutputMode } from '../util/logger';
@@ -42,9 +43,6 @@ export async function deployCommand(opts: DeployOptions): Promise<void> {
42
43
  info('checking S3 bucket');
43
44
  await assertBucketExists();
44
45
 
45
- info('building');
46
- await buildCommand({ json: opts.json });
47
-
48
46
  const paths = resolveConsumerPaths(process.cwd());
49
47
  const consumerPkg = (await readJson(paths.consumerPkg)) as { name?: string };
50
48
  if (!consumerPkg.name) {
@@ -52,6 +50,15 @@ export async function deployCommand(opts: DeployOptions): Promise<void> {
52
50
  }
53
51
  const appName = mfAppName(consumerPkg.name);
54
52
 
53
+ info('reading current Z2H manifest');
54
+ const manifest = await readManifest();
55
+ const version = nextVersion(manifest, appName);
56
+ const keyPrefix = `${appName}/${version}`;
57
+ const url = `${BUCKET_READ_DNS_URL}/${keyPrefix}`;
58
+
59
+ info('building');
60
+ await buildCommand({ json: opts.json, publicUrl: url });
61
+
55
62
  if (!(await pathExists(paths.shadowBuild))) {
56
63
  throw new Error(`No build output at ${paths.shadowBuild} after build step.`);
57
64
  }
@@ -63,12 +70,6 @@ export async function deployCommand(opts: DeployOptions): Promise<void> {
63
70
  }
64
71
  const assetManifest = (await readJson(assetManifestPath)) as AssetManifestFile;
65
72
 
66
- info('reading current Z2H manifest');
67
- const manifest = await readManifest();
68
- const version = nextVersion(manifest, appName);
69
- const keyPrefix = `${appName}/${version}`;
70
- const url = `${BUCKET_READ_DNS_URL}/${keyPrefix}`;
71
-
72
73
  if (opts.dryRun) {
73
74
  output(`[z2h-cli] dry-run: would upload to s3://${BUCKET_WRITE_NAME}/${keyPrefix}/ as v${version}`, {
74
75
  ok: true,
@@ -85,15 +86,18 @@ export async function deployCommand(opts: DeployOptions): Promise<void> {
85
86
 
86
87
  await requestAponoAccess();
87
88
 
88
- info(`uploading build → s3://${BUCKET_WRITE_NAME}/${keyPrefix}/`);
89
- await uploadDir(paths.shadowBuild, keyPrefix);
89
+ const deployedBy = os.userInfo().username;
90
90
 
91
91
  const entry: ManifestEntry = {
92
92
  ...assetManifest,
93
93
  version,
94
94
  url,
95
+ deployedBy,
95
96
  };
96
97
 
98
+ info(`uploading build → s3://${BUCKET_WRITE_NAME}/${keyPrefix}/`);
99
+ await uploadDir(paths.shadowBuild, keyPrefix);
100
+
97
101
  info(`updating manifest entry "${appName}" → v${version}`);
98
102
  await writeManifest(upsertEntry(manifest, appName, entry));
99
103
 
@@ -1,4 +1,6 @@
1
- import { readJson } from 'fs-extra';
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { readJson, writeFile } from 'fs-extra';
2
4
  import { watch } from 'chokidar';
3
5
  import { execa } from 'execa';
4
6
  import { info, warn, errorLog, output, setOutputMode } from '../util/logger';
@@ -31,6 +33,29 @@ export async function devCommand(opts: DevOptions): Promise<void> {
31
33
  stdio: ['inherit', 'pipe', 'inherit'],
32
34
  });
33
35
 
36
+ const pidFile = path.join(ctx.paths.shadowDir, 'dev.pid');
37
+ if (server.pid) {
38
+ await writeFile(pidFile, String(server.pid));
39
+ }
40
+
41
+ const cleanupPid = () => {
42
+ try {
43
+ fs.unlinkSync(pidFile);
44
+ } catch (_) {
45
+ // file already gone — ignore
46
+ }
47
+ };
48
+ server.on('exit', cleanupPid);
49
+ process.on('exit', cleanupPid);
50
+ process.on('SIGINT', () => {
51
+ server.kill();
52
+ process.exit(0);
53
+ });
54
+ process.on('SIGTERM', () => {
55
+ server.kill();
56
+ process.exit(0);
57
+ });
58
+
34
59
  let readyPrinted = false;
35
60
  server.stdout?.on('data', (chunk: Buffer) => {
36
61
  process.stdout.write(chunk);
@@ -78,6 +78,11 @@ declare module '*.wasm' {
78
78
  const e: string;
79
79
  export default e;
80
80
  }
81
+
82
+ declare module '*.html?url' {
83
+ const src: string;
84
+ export default src;
85
+ }
81
86
  `;
82
87
 
83
88
  const TSCONFIG_TEMPLATE = {
@@ -3,5 +3,17 @@
3
3
  "allow": [
4
4
  "Skill(z2h:*)"
5
5
  ]
6
+ },
7
+ "hooks": {
8
+ "Stop": [
9
+ {
10
+ "hooks": [
11
+ {
12
+ "type": "command",
13
+ "command": "if [ -f .zth/dev.pid ]; then kill $(cat .zth/dev.pid) 2>/dev/null; rm .zth/dev.pid; fi"
14
+ }
15
+ ]
16
+ }
17
+ ]
6
18
  }
7
19
  }
File without changes
@@ -27,6 +27,7 @@ export interface AssetManifestFile {
27
27
  export interface ManifestEntry extends AssetManifestFile {
28
28
  version: number;
29
29
  url: string;
30
+ deployedBy: string;
30
31
  }
31
32
 
32
33
  export type Manifest = Record<string, ManifestEntry>;