@mondaydotcomorg/z2h-cli 0.11.4 → 0.11.6

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/create-workspace.d.ts.map +1 -1
  2. package/dist/commands/create-workspace.js +5 -1
  3. package/dist/constants.d.ts.map +1 -1
  4. package/dist/constants.js +3 -6
  5. package/dist/esm/commands/create-workspace.d.ts.map +1 -1
  6. package/dist/esm/commands/create-workspace.mjs +6 -2
  7. package/dist/esm/constants.d.ts.map +1 -1
  8. package/dist/esm/constants.mjs +3 -6
  9. package/dist/esm/index.mjs +4 -1
  10. package/dist/esm/migrations/005-fix-shadow-toolkit-wildcard.d.ts +3 -0
  11. package/dist/esm/migrations/005-fix-shadow-toolkit-wildcard.d.ts.map +1 -0
  12. package/dist/esm/migrations/005-fix-shadow-toolkit-wildcard.mjs +35 -0
  13. package/dist/esm/shadow/merge-package-json.d.ts +1 -13
  14. package/dist/esm/shadow/merge-package-json.d.ts.map +1 -1
  15. package/dist/esm/shadow/merge-package-json.mjs +2 -14
  16. package/dist/esm/shadow/sync.d.ts +0 -6
  17. package/dist/esm/shadow/sync.d.ts.map +1 -1
  18. package/dist/esm/shadow/sync.mjs +49 -11
  19. package/dist/esm/util/git/repo.mjs +2 -2
  20. package/dist/index.js +4 -1
  21. package/dist/migrations/005-fix-shadow-toolkit-wildcard.d.ts +3 -0
  22. package/dist/migrations/005-fix-shadow-toolkit-wildcard.d.ts.map +1 -0
  23. package/dist/migrations/005-fix-shadow-toolkit-wildcard.js +37 -0
  24. package/dist/shadow/merge-package-json.d.ts +1 -13
  25. package/dist/shadow/merge-package-json.d.ts.map +1 -1
  26. package/dist/shadow/merge-package-json.js +2 -14
  27. package/dist/shadow/sync.d.ts +0 -6
  28. package/dist/shadow/sync.d.ts.map +1 -1
  29. package/dist/shadow/sync.js +52 -10
  30. package/dist/util/git/repo.js +2 -2
  31. package/migrations.json +5 -0
  32. package/package.json +1 -1
  33. package/src/commands/__tests__/create-workspace.test.ts +2 -2
  34. package/src/commands/create-workspace.ts +5 -1
  35. package/src/constants.ts +3 -6
  36. package/src/index.ts +4 -1
  37. package/src/migrations/005-fix-shadow-toolkit-wildcard.ts +37 -0
  38. package/src/shadow/__tests__/merge-package-json.test.ts +13 -1
  39. package/src/shadow/merge-package-json.ts +2 -14
  40. package/src/shadow/sync.ts +55 -12
  41. package/src/util/git/__tests__/repo.test.ts +2 -2
  42. package/src/util/git/repo.ts +2 -2
@@ -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;AAED,wBAAsB,sBAAsB,CAAC,IAAI,GAAE,sBAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C7F"}
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;AAED,wBAAsB,sBAAsB,CAAC,IAAI,GAAE,sBAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CA8C7F"}
@@ -22,7 +22,11 @@ async function createWorkspaceCommand(opts = {}) {
22
22
  if (await fs.pathExists(home)) {
23
23
  const entries = await fs.readdir(home);
24
24
  if (entries.length > 0) {
25
- throw new Error(`Workspace already exists at ${home}. Remove it and try again.`);
25
+ util_logger.output(`[z2h-cli] workspace already exists at ${home} — skipping`, {
26
+ ok: true,
27
+ workspace: home,
28
+ });
29
+ return;
26
30
  }
27
31
  }
28
32
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,eAAO,MAAM,eAAe,SAAS,CAAC;AAGtC,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGjD,CAAC;AAQF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAElD,CAAC;AAMF,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAG9B,eAAO,MAAM,iBAAiB,UAAY,CAAC;AAQ3C,eAAO,MAAM,iBAAiB,qCAAqC,CAAC;AACpE,eAAO,MAAM,MAAM,cAAc,CAAC;AAClC,eAAO,MAAM,gBAAgB,wEAA4D,CAAC;AAE1F,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAC7D,eAAO,MAAM,mBAAmB,+CAAgD,CAAC;AAKjF,eAAO,MAAM,YAAY,cAAc,CAAC;AACxC,eAAO,MAAM,iBAAiB,yDAA2C,CAAC;AAM1E,eAAO,MAAM,gBAAgB,QAA2B,CAAC;AAKzD,eAAO,MAAM,uBAAuB,qCAAqC,CAAC;AAE1E;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAID,wBAAgB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGvD;AAKD,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAG9C,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAGnD,eAAO,MAAM,uBAAuB,gCAAgC,CAAC;AAMrE,eAAO,MAAM,iBAAiB,EAAG,eAAwB,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,eAAO,MAAM,eAAe,SAAS,CAAC;AAGtC,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGjD,CAAC;AAKF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAElD,CAAC;AAMF,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAG9B,eAAO,MAAM,iBAAiB,UAAY,CAAC;AAQ3C,eAAO,MAAM,iBAAiB,qCAAqC,CAAC;AACpE,eAAO,MAAM,MAAM,cAAc,CAAC;AAClC,eAAO,MAAM,gBAAgB,wEAA4D,CAAC;AAE1F,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAC7D,eAAO,MAAM,mBAAmB,+CAAgD,CAAC;AAKjF,eAAO,MAAM,YAAY,cAAc,CAAC;AACxC,eAAO,MAAM,iBAAiB,yDAA2C,CAAC;AAM1E,eAAO,MAAM,gBAAgB,QAA2B,CAAC;AAKzD,eAAO,MAAM,uBAAuB,qCAAqC,CAAC;AAE1E;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAID,wBAAgB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGvD;AAKD,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAG9C,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAGnD,eAAO,MAAM,uBAAuB,gCAAgC,CAAC;AAMrE,eAAO,MAAM,iBAAiB,EAAG,eAAwB,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C"}
package/dist/constants.js CHANGED
@@ -12,13 +12,10 @@ const SHADOW_SCRIPTS = {
12
12
  build: 'trident build',
13
13
  };
14
14
  // ─────────── Shadow dev deps ───────
15
- // Yarn berry only wires a workspace member's `node_modules/.bin/` PATH
16
- // for packages that member explicitly declares. The shadow's scripts
17
- // invoke `trident`, so trident-toolkit must be declared here for the
18
- // bin to resolve. `*` is fine — yarn workspaces resolve the actual
19
- // version from the workspace root's pinned entry.
15
+ // Fallback versions used when the workspace root's package.json cannot be
16
+ // read. syncShadow prefers the workspace root's pinned ranges at runtime.
20
17
  const SHADOW_DEV_DEPS = {
21
- '@mondaydotcomorg/trident-toolkit': '*',
18
+ '@mondaydotcomorg/trident-toolkit': '^2.9.25',
22
19
  };
23
20
  // ─────────── Auto-allocated dev port range ────
24
21
  // High enough to dodge common dev defaults (3000 Next, 5173 Vite, 8080).
@@ -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;AAED,wBAAsB,sBAAsB,CAAC,IAAI,GAAE,sBAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CA0C7F"}
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;AAED,wBAAsB,sBAAsB,CAAC,IAAI,GAAE,sBAA2B,GAAG,OAAO,CAAC,IAAI,CAAC,CA8C7F"}
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { pathExists, readdir, ensureDir, writeFile, copy } from 'fs-extra';
4
- import { setOutputMode, info, output } from '../util/logger.mjs';
4
+ import { setOutputMode, output, info } from '../util/logger.mjs';
5
5
  import { getWorkspaceHome } from '../util/workspace.mjs';
6
6
  import { getTemplatesDir } from '../util/templates.mjs';
7
7
  import { runYarnInstall } from '../util/npm/install.mjs';
@@ -16,7 +16,11 @@ async function createWorkspaceCommand(opts = {}) {
16
16
  if (await pathExists(home)) {
17
17
  const entries = await readdir(home);
18
18
  if (entries.length > 0) {
19
- throw new Error(`Workspace already exists at ${home}. Remove it and try again.`);
19
+ output(`[z2h-cli] workspace already exists at ${home} — skipping`, {
20
+ ok: true,
21
+ workspace: home,
22
+ });
23
+ return;
20
24
  }
21
25
  }
22
26
  else {
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,eAAO,MAAM,eAAe,SAAS,CAAC;AAGtC,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGjD,CAAC;AAQF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAElD,CAAC;AAMF,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAG9B,eAAO,MAAM,iBAAiB,UAAY,CAAC;AAQ3C,eAAO,MAAM,iBAAiB,qCAAqC,CAAC;AACpE,eAAO,MAAM,MAAM,cAAc,CAAC;AAClC,eAAO,MAAM,gBAAgB,wEAA4D,CAAC;AAE1F,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAC7D,eAAO,MAAM,mBAAmB,+CAAgD,CAAC;AAKjF,eAAO,MAAM,YAAY,cAAc,CAAC;AACxC,eAAO,MAAM,iBAAiB,yDAA2C,CAAC;AAM1E,eAAO,MAAM,gBAAgB,QAA2B,CAAC;AAKzD,eAAO,MAAM,uBAAuB,qCAAqC,CAAC;AAE1E;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAID,wBAAgB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGvD;AAKD,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAG9C,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAGnD,eAAO,MAAM,uBAAuB,gCAAgC,CAAC;AAMrE,eAAO,MAAM,iBAAiB,EAAG,eAAwB,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,eAAO,MAAM,eAAe,SAAS,CAAC;AAGtC,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGjD,CAAC;AAKF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAElD,CAAC;AAMF,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAG9B,eAAO,MAAM,iBAAiB,UAAY,CAAC;AAQ3C,eAAO,MAAM,iBAAiB,qCAAqC,CAAC;AACpE,eAAO,MAAM,MAAM,cAAc,CAAC;AAClC,eAAO,MAAM,gBAAgB,wEAA4D,CAAC;AAE1F,eAAO,MAAM,oBAAoB,2BAA2B,CAAC;AAC7D,eAAO,MAAM,mBAAmB,+CAAgD,CAAC;AAKjF,eAAO,MAAM,YAAY,cAAc,CAAC;AACxC,eAAO,MAAM,iBAAiB,yDAA2C,CAAC;AAM1E,eAAO,MAAM,gBAAgB,QAA2B,CAAC;AAKzD,eAAO,MAAM,uBAAuB,qCAAqC,CAAC;AAE1E;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAID,wBAAgB,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGvD;AAKD,eAAO,MAAM,eAAe,iBAAiB,CAAC;AAG9C,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAGnD,eAAO,MAAM,uBAAuB,gCAAgC,CAAC;AAMrE,eAAO,MAAM,iBAAiB,EAAG,eAAwB,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C"}
@@ -10,13 +10,10 @@ const SHADOW_SCRIPTS = {
10
10
  build: 'trident build',
11
11
  };
12
12
  // ─────────── Shadow dev deps ───────
13
- // Yarn berry only wires a workspace member's `node_modules/.bin/` PATH
14
- // for packages that member explicitly declares. The shadow's scripts
15
- // invoke `trident`, so trident-toolkit must be declared here for the
16
- // bin to resolve. `*` is fine — yarn workspaces resolve the actual
17
- // version from the workspace root's pinned entry.
13
+ // Fallback versions used when the workspace root's package.json cannot be
14
+ // read. syncShadow prefers the workspace root's pinned ranges at runtime.
18
15
  const SHADOW_DEV_DEPS = {
19
- '@mondaydotcomorg/trident-toolkit': '*',
16
+ '@mondaydotcomorg/trident-toolkit': '^2.9.25',
20
17
  };
21
18
  // ─────────── Auto-allocated dev port range ────
22
19
  // High enough to dodge common dev defaults (3000 Next, 5173 Vite, 8080).
@@ -232,5 +232,8 @@ void main()
232
232
  process.exit(0);
233
233
  })
234
234
  .catch(async (err) => {
235
- await fail(`main failed: ${err.message}`, { command: 'cli-main' });
235
+ const errorMsg = err instanceof Error ? err.message : String(err);
236
+ await fail(`main failed, check you are on latest version.\nerror: ${errorMsg}`, {
237
+ command: 'cli-main',
238
+ });
236
239
  });
@@ -0,0 +1,3 @@
1
+ import type { Tree } from '@nx/devkit';
2
+ export default function fixShadowToolkitWildcard(tree: Tree): void;
3
+ //# sourceMappingURL=005-fix-shadow-toolkit-wildcard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"005-fix-shadow-toolkit-wildcard.d.ts","sourceRoot":"","sources":["../../../src/migrations/005-fix-shadow-toolkit-wildcard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAKvC,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CA+BjE"}
@@ -0,0 +1,35 @@
1
+ const TOOLKIT = '@mondaydotcomorg/trident-toolkit';
2
+ const FALLBACK_VERSION = '^2.9.25';
3
+ function fixShadowToolkitWildcard(tree) {
4
+ let targetVersion = FALLBACK_VERSION;
5
+ if (tree.exists('package.json')) {
6
+ try {
7
+ const pkg = JSON.parse(tree.read('package.json', 'utf-8'));
8
+ targetVersion = pkg.devDependencies?.[TOOLKIT] ?? pkg.dependencies?.[TOOLKIT] ?? FALLBACK_VERSION;
9
+ }
10
+ catch {
11
+ // fall through to FALLBACK_VERSION
12
+ }
13
+ }
14
+ for (const entry of tree.children('.')) {
15
+ if (entry.startsWith('.')) {
16
+ continue;
17
+ }
18
+ const shadowPkgPath = `${entry}/.zth/package.json`;
19
+ if (!tree.exists(shadowPkgPath)) {
20
+ continue;
21
+ }
22
+ try {
23
+ const pkg = JSON.parse(tree.read(shadowPkgPath, 'utf-8'));
24
+ if (pkg.devDependencies?.[TOOLKIT] === '*') {
25
+ pkg.devDependencies[TOOLKIT] = targetVersion;
26
+ tree.write(shadowPkgPath, JSON.stringify(pkg, null, 2) + '\n');
27
+ }
28
+ }
29
+ catch {
30
+ // skip unparseable shadow packages
31
+ }
32
+ }
33
+ }
34
+
35
+ export { fixShadowToolkitWildcard as default };
@@ -1,17 +1,5 @@
1
1
  import type { ConsumerPkg, ShadowPkg } from '../types';
2
- /**
3
- * Build the shadow package.json.
4
- *
5
- * Runtime deps live at the workspace root (`~/zth-projects/`) and are
6
- * hoisted in via yarn workspaces — no need to mirror them here. The shadow
7
- * only declares the toolchain it directly executes (`trident-toolkit`) so
8
- * yarn berry wires the `trident` bin into the shadow's script PATH.
9
- *
10
- * The `@trident/` scope is preserved so trident-toolkit strips it and the
11
- * emitted bundle becomes `MF-mf-<appName>`, matching the URL the host MF
12
- * routes to.
13
- */
14
- export declare function buildShadowPkg(consumerPkg: ConsumerPkg): ShadowPkg;
2
+ export declare function buildShadowPkg(consumerPkg: ConsumerPkg, pinnedVersions?: Record<string, string>): ShadowPkg;
15
3
  /**
16
4
  * Hash the consumer's declared deps. This is the sole input that decides
17
5
  * whether `yarn install` needs to run — the shadow package.json is
@@ -1 +1 @@
1
- {"version":3,"file":"merge-package-json.d.ts","sourceRoot":"","sources":["../../../src/shadow/merge-package-json.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEvD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,WAAW,GAAG,SAAS,CAQlE;AAgBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAW9D;AAED,wBAAsB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5F"}
1
+ {"version":3,"file":"merge-package-json.d.ts","sourceRoot":"","sources":["../../../src/shadow/merge-package-json.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEvD,wBAAgB,cAAc,CAAC,WAAW,EAAE,WAAW,EAAE,cAAc,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,SAAS,CAQ/G;AAgBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAW9D;AAED,wBAAsB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5F"}
@@ -3,25 +3,13 @@ import { writeJson } from 'fs-extra';
3
3
  import { SHADOW_SCRIPTS, SHADOW_DEV_DEPS } from '../constants.mjs';
4
4
  import { mfAppName } from '../util/app-name.mjs';
5
5
 
6
- /**
7
- * Build the shadow package.json.
8
- *
9
- * Runtime deps live at the workspace root (`~/zth-projects/`) and are
10
- * hoisted in via yarn workspaces — no need to mirror them here. The shadow
11
- * only declares the toolchain it directly executes (`trident-toolkit`) so
12
- * yarn berry wires the `trident` bin into the shadow's script PATH.
13
- *
14
- * The `@trident/` scope is preserved so trident-toolkit strips it and the
15
- * emitted bundle becomes `MF-mf-<appName>`, matching the URL the host MF
16
- * routes to.
17
- */
18
- function buildShadowPkg(consumerPkg) {
6
+ function buildShadowPkg(consumerPkg, pinnedVersions = {}) {
19
7
  return {
20
8
  name: `@trident/${mfAppName(consumerPkg.name)}`,
21
9
  version: '0.0.1',
22
10
  private: true,
23
11
  scripts: SHADOW_SCRIPTS,
24
- devDependencies: { ...SHADOW_DEV_DEPS },
12
+ devDependencies: { ...SHADOW_DEV_DEPS, ...pinnedVersions },
25
13
  };
26
14
  }
27
15
  /**
@@ -1,9 +1,3 @@
1
1
  import type { ConsumerPaths, ConsumerPkg } from '../types';
2
- /**
3
- * Regenerate the shadow package.json from the current CLI template and
4
- * run `yarn install` at the consumer dir iff the consumer's declared deps
5
- * have changed since the last install. Idempotent — safe to call on every
6
- * build, dev start, or consumer package.json edit.
7
- */
8
2
  export declare function syncShadow(paths: ConsumerPaths, consumerPkg: ConsumerPkg): Promise<void>;
9
3
  //# sourceMappingURL=sync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../src/shadow/sync.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE3D;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAe9F"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../src/shadow/sync.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAa,MAAM,UAAU,CAAC;AA6CtE,wBAAsB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB9F"}
@@ -1,24 +1,62 @@
1
- import { pathExists, readFile, writeFile } from 'fs-extra';
1
+ import path from 'node:path';
2
+ import { remove, pathExists, readFile, writeFile, readJson } from 'fs-extra';
2
3
  import { info } from '../util/logger.mjs';
4
+ import { SHADOW_DEV_DEPS } from '../constants.mjs';
3
5
  import { runYarnInstall } from '../util/npm/install.mjs';
4
6
  import { buildShadowPkg, writeShadowPkg, hashConsumerDeps } from './merge-package-json.mjs';
5
7
 
6
- /**
7
- * Regenerate the shadow package.json from the current CLI template and
8
- * run `yarn install` at the consumer dir iff the consumer's declared deps
9
- * have changed since the last install. Idempotent — safe to call on every
10
- * build, dev start, or consumer package.json edit.
11
- */
8
+ // Reads the workspace root's pinned versions for each package in SHADOW_DEV_DEPS.
9
+ // The workspace root is always one directory above the consumer app.
10
+ // Returns an empty map if the root package.json is missing or unreadable.
11
+ async function readWorkspacePinnedVersions(consumerDir) {
12
+ const wsPkgPath = path.join(path.dirname(consumerDir), 'package.json');
13
+ if (!(await pathExists(wsPkgPath))) {
14
+ return {};
15
+ }
16
+ try {
17
+ const wsPkg = (await readJson(wsPkgPath));
18
+ const overrides = {};
19
+ for (const name of Object.keys(SHADOW_DEV_DEPS)) {
20
+ const version = wsPkg.devDependencies?.[name] ?? wsPkg.dependencies?.[name];
21
+ if (version) {
22
+ overrides[name] = version;
23
+ }
24
+ }
25
+ return overrides;
26
+ }
27
+ catch {
28
+ return {};
29
+ }
30
+ }
31
+ async function shadowDevDepsChanged(shadowPkgPath, newDevDeps) {
32
+ if (!(await pathExists(shadowPkgPath))) {
33
+ return false;
34
+ }
35
+ try {
36
+ const existing = (await readJson(shadowPkgPath));
37
+ for (const [name, version] of Object.entries(newDevDeps)) {
38
+ if (existing.devDependencies?.[name] !== version) {
39
+ return true;
40
+ }
41
+ }
42
+ return false;
43
+ }
44
+ catch {
45
+ return false;
46
+ }
47
+ }
12
48
  async function syncShadow(paths, consumerPkg) {
13
- const shadow = buildShadowPkg(consumerPkg);
49
+ const pinnedVersions = await readWorkspacePinnedVersions(paths.consumerDir);
50
+ const shadow = buildShadowPkg(consumerPkg, pinnedVersions);
51
+ if (await shadowDevDepsChanged(paths.shadowPkg, shadow.devDependencies)) {
52
+ info('shadow devDependencies changed — forcing yarn install');
53
+ await remove(paths.shadowDepsHash);
54
+ }
14
55
  await writeShadowPkg(paths.shadowPkg, shadow);
15
56
  const newHash = hashConsumerDeps(consumerPkg);
16
57
  const oldHash = (await pathExists(paths.shadowDepsHash)) ? await readFile(paths.shadowDepsHash, 'utf8') : null;
17
58
  if (oldHash !== newHash) {
18
59
  info(oldHash ? 'consumer deps changed — yarn install' : 'first run — yarn install');
19
- // Yarn 4 is cache-first and non-interactive by default in non-TTY
20
- // contexts, so plain `yarn install` is all we need. runYarnInstall injects
21
- // the npm-broker token, preflights auth, and re-auths on a broker 401.
22
60
  await runYarnInstall({ cwd: paths.consumerDir });
23
61
  await writeFile(paths.shadowDepsHash, newHash);
24
62
  }
@@ -81,7 +81,7 @@ async function pullAndResolve(dir, env) {
81
81
  }
82
82
  await safeBackupIfDirty(dir);
83
83
  const info = await gatherConflictInfo(dir, gitEnv);
84
- throw new GitConflictError(JSON.stringify(info));
84
+ throw new GitConflictError(`Pull rejected: Git conflict detected: ${JSON.stringify(info)}`);
85
85
  }
86
86
  }
87
87
  /**
@@ -109,7 +109,7 @@ async function pushToRemote(dir, env) {
109
109
  }
110
110
  if (isRejected) {
111
111
  const info = await gatherConflictInfo(dir, gitEnv);
112
- throw new GitConflictError(JSON.stringify(info));
112
+ throw new GitConflictError(`Push rejected: Git conflict detected: ${JSON.stringify(info)}`);
113
113
  }
114
114
  throw err;
115
115
  }
package/dist/index.js CHANGED
@@ -238,5 +238,8 @@ void main()
238
238
  process.exit(0);
239
239
  })
240
240
  .catch(async (err) => {
241
- await util_logger.fail(`main failed: ${err.message}`, { command: 'cli-main' });
241
+ const errorMsg = err instanceof Error ? err.message : String(err);
242
+ await util_logger.fail(`main failed, check you are on latest version.\nerror: ${errorMsg}`, {
243
+ command: 'cli-main',
244
+ });
242
245
  });
@@ -0,0 +1,3 @@
1
+ import type { Tree } from '@nx/devkit';
2
+ export default function fixShadowToolkitWildcard(tree: Tree): void;
3
+ //# sourceMappingURL=005-fix-shadow-toolkit-wildcard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"005-fix-shadow-toolkit-wildcard.d.ts","sourceRoot":"","sources":["../../src/migrations/005-fix-shadow-toolkit-wildcard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAKvC,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CA+BjE"}
@@ -0,0 +1,37 @@
1
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
2
+
3
+ const TOOLKIT = '@mondaydotcomorg/trident-toolkit';
4
+ const FALLBACK_VERSION = '^2.9.25';
5
+ function fixShadowToolkitWildcard(tree) {
6
+ let targetVersion = FALLBACK_VERSION;
7
+ if (tree.exists('package.json')) {
8
+ try {
9
+ const pkg = JSON.parse(tree.read('package.json', 'utf-8'));
10
+ targetVersion = pkg.devDependencies?.[TOOLKIT] ?? pkg.dependencies?.[TOOLKIT] ?? FALLBACK_VERSION;
11
+ }
12
+ catch {
13
+ // fall through to FALLBACK_VERSION
14
+ }
15
+ }
16
+ for (const entry of tree.children('.')) {
17
+ if (entry.startsWith('.')) {
18
+ continue;
19
+ }
20
+ const shadowPkgPath = `${entry}/.zth/package.json`;
21
+ if (!tree.exists(shadowPkgPath)) {
22
+ continue;
23
+ }
24
+ try {
25
+ const pkg = JSON.parse(tree.read(shadowPkgPath, 'utf-8'));
26
+ if (pkg.devDependencies?.[TOOLKIT] === '*') {
27
+ pkg.devDependencies[TOOLKIT] = targetVersion;
28
+ tree.write(shadowPkgPath, JSON.stringify(pkg, null, 2) + '\n');
29
+ }
30
+ }
31
+ catch {
32
+ // skip unparseable shadow packages
33
+ }
34
+ }
35
+ }
36
+
37
+ exports.default = fixShadowToolkitWildcard;
@@ -1,17 +1,5 @@
1
1
  import type { ConsumerPkg, ShadowPkg } from '../types';
2
- /**
3
- * Build the shadow package.json.
4
- *
5
- * Runtime deps live at the workspace root (`~/zth-projects/`) and are
6
- * hoisted in via yarn workspaces — no need to mirror them here. The shadow
7
- * only declares the toolchain it directly executes (`trident-toolkit`) so
8
- * yarn berry wires the `trident` bin into the shadow's script PATH.
9
- *
10
- * The `@trident/` scope is preserved so trident-toolkit strips it and the
11
- * emitted bundle becomes `MF-mf-<appName>`, matching the URL the host MF
12
- * routes to.
13
- */
14
- export declare function buildShadowPkg(consumerPkg: ConsumerPkg): ShadowPkg;
2
+ export declare function buildShadowPkg(consumerPkg: ConsumerPkg, pinnedVersions?: Record<string, string>): ShadowPkg;
15
3
  /**
16
4
  * Hash the consumer's declared deps. This is the sole input that decides
17
5
  * whether `yarn install` needs to run — the shadow package.json is
@@ -1 +1 @@
1
- {"version":3,"file":"merge-package-json.d.ts","sourceRoot":"","sources":["../../src/shadow/merge-package-json.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEvD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,WAAW,GAAG,SAAS,CAQlE;AAgBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAW9D;AAED,wBAAsB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5F"}
1
+ {"version":3,"file":"merge-package-json.d.ts","sourceRoot":"","sources":["../../src/shadow/merge-package-json.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEvD,wBAAgB,cAAc,CAAC,WAAW,EAAE,WAAW,EAAE,cAAc,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,SAAS,CAQ/G;AAgBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,WAAW,GAAG,MAAM,CAW9D;AAED,wBAAsB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5F"}
@@ -9,25 +9,13 @@ const _interopDefault = e => e && e.__esModule ? e : { default: e };
9
9
 
10
10
  const crypto__default = /*#__PURE__*/_interopDefault(crypto);
11
11
 
12
- /**
13
- * Build the shadow package.json.
14
- *
15
- * Runtime deps live at the workspace root (`~/zth-projects/`) and are
16
- * hoisted in via yarn workspaces — no need to mirror them here. The shadow
17
- * only declares the toolchain it directly executes (`trident-toolkit`) so
18
- * yarn berry wires the `trident` bin into the shadow's script PATH.
19
- *
20
- * The `@trident/` scope is preserved so trident-toolkit strips it and the
21
- * emitted bundle becomes `MF-mf-<appName>`, matching the URL the host MF
22
- * routes to.
23
- */
24
- function buildShadowPkg(consumerPkg) {
12
+ function buildShadowPkg(consumerPkg, pinnedVersions = {}) {
25
13
  return {
26
14
  name: `@trident/${util_appName.mfAppName(consumerPkg.name)}`,
27
15
  version: '0.0.1',
28
16
  private: true,
29
17
  scripts: constants.SHADOW_SCRIPTS,
30
- devDependencies: { ...constants.SHADOW_DEV_DEPS },
18
+ devDependencies: { ...constants.SHADOW_DEV_DEPS, ...pinnedVersions },
31
19
  };
32
20
  }
33
21
  /**
@@ -1,9 +1,3 @@
1
1
  import type { ConsumerPaths, ConsumerPkg } from '../types';
2
- /**
3
- * Regenerate the shadow package.json from the current CLI template and
4
- * run `yarn install` at the consumer dir iff the consumer's declared deps
5
- * have changed since the last install. Idempotent — safe to call on every
6
- * build, dev start, or consumer package.json edit.
7
- */
8
2
  export declare function syncShadow(paths: ConsumerPaths, consumerPkg: ConsumerPkg): Promise<void>;
9
3
  //# sourceMappingURL=sync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/shadow/sync.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE3D;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAe9F"}
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/shadow/sync.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAa,MAAM,UAAU,CAAC;AA6CtE,wBAAsB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB9F"}
@@ -1,26 +1,68 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
+ const path = require('node:path');
3
4
  const fs = require('fs-extra');
4
5
  const util_logger = require('../util/logger.js');
6
+ const constants = require('../constants.js');
5
7
  const util_npm_install = require('../util/npm/install.js');
6
8
  const shadow_mergePackageJson = require('./merge-package-json.js');
7
9
 
8
- /**
9
- * Regenerate the shadow package.json from the current CLI template and
10
- * run `yarn install` at the consumer dir iff the consumer's declared deps
11
- * have changed since the last install. Idempotent — safe to call on every
12
- * build, dev start, or consumer package.json edit.
13
- */
10
+ const _interopDefault = e => e && e.__esModule ? e : { default: e };
11
+
12
+ const path__default = /*#__PURE__*/_interopDefault(path);
13
+
14
+ // Reads the workspace root's pinned versions for each package in SHADOW_DEV_DEPS.
15
+ // The workspace root is always one directory above the consumer app.
16
+ // Returns an empty map if the root package.json is missing or unreadable.
17
+ async function readWorkspacePinnedVersions(consumerDir) {
18
+ const wsPkgPath = path__default.default.join(path__default.default.dirname(consumerDir), 'package.json');
19
+ if (!(await fs.pathExists(wsPkgPath))) {
20
+ return {};
21
+ }
22
+ try {
23
+ const wsPkg = (await fs.readJson(wsPkgPath));
24
+ const overrides = {};
25
+ for (const name of Object.keys(constants.SHADOW_DEV_DEPS)) {
26
+ const version = wsPkg.devDependencies?.[name] ?? wsPkg.dependencies?.[name];
27
+ if (version) {
28
+ overrides[name] = version;
29
+ }
30
+ }
31
+ return overrides;
32
+ }
33
+ catch {
34
+ return {};
35
+ }
36
+ }
37
+ async function shadowDevDepsChanged(shadowPkgPath, newDevDeps) {
38
+ if (!(await fs.pathExists(shadowPkgPath))) {
39
+ return false;
40
+ }
41
+ try {
42
+ const existing = (await fs.readJson(shadowPkgPath));
43
+ for (const [name, version] of Object.entries(newDevDeps)) {
44
+ if (existing.devDependencies?.[name] !== version) {
45
+ return true;
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
14
54
  async function syncShadow(paths, consumerPkg) {
15
- const shadow = shadow_mergePackageJson.buildShadowPkg(consumerPkg);
55
+ const pinnedVersions = await readWorkspacePinnedVersions(paths.consumerDir);
56
+ const shadow = shadow_mergePackageJson.buildShadowPkg(consumerPkg, pinnedVersions);
57
+ if (await shadowDevDepsChanged(paths.shadowPkg, shadow.devDependencies)) {
58
+ util_logger.info('shadow devDependencies changed — forcing yarn install');
59
+ await fs.remove(paths.shadowDepsHash);
60
+ }
16
61
  await shadow_mergePackageJson.writeShadowPkg(paths.shadowPkg, shadow);
17
62
  const newHash = shadow_mergePackageJson.hashConsumerDeps(consumerPkg);
18
63
  const oldHash = (await fs.pathExists(paths.shadowDepsHash)) ? await fs.readFile(paths.shadowDepsHash, 'utf8') : null;
19
64
  if (oldHash !== newHash) {
20
65
  util_logger.info(oldHash ? 'consumer deps changed — yarn install' : 'first run — yarn install');
21
- // Yarn 4 is cache-first and non-interactive by default in non-TTY
22
- // contexts, so plain `yarn install` is all we need. runYarnInstall injects
23
- // the npm-broker token, preflights auth, and re-auths on a broker 401.
24
66
  await util_npm_install.runYarnInstall({ cwd: paths.consumerDir });
25
67
  await fs.writeFile(paths.shadowDepsHash, newHash);
26
68
  }
@@ -88,7 +88,7 @@ async function pullAndResolve(dir, env) {
88
88
  }
89
89
  await safeBackupIfDirty(dir);
90
90
  const info = await gatherConflictInfo(dir, gitEnv);
91
- throw new GitConflictError(JSON.stringify(info));
91
+ throw new GitConflictError(`Pull rejected: Git conflict detected: ${JSON.stringify(info)}`);
92
92
  }
93
93
  }
94
94
  /**
@@ -116,7 +116,7 @@ async function pushToRemote(dir, env) {
116
116
  }
117
117
  if (isRejected) {
118
118
  const info = await gatherConflictInfo(dir, gitEnv);
119
- throw new GitConflictError(JSON.stringify(info));
119
+ throw new GitConflictError(`Push rejected: Git conflict detected: ${JSON.stringify(info)}`);
120
120
  }
121
121
  throw err;
122
122
  }
package/migrations.json CHANGED
@@ -19,6 +19,11 @@
19
19
  "version": "0.11.4",
20
20
  "description": "Remove @mondaydotcomorg/z2h-cli from workspace deps (globally installed, must not be a local dep)",
21
21
  "migrate": "./dist/migrations/004-remove-z2h-cli-workspace-dep"
22
+ },
23
+ "fix-shadow-toolkit-wildcard": {
24
+ "version": "0.11.5",
25
+ "description": "Fix shadow .zth/package.json trident-toolkit \"*\" → workspace-root pinned range to prevent react version conflict",
26
+ "migrate": "./dist/migrations/005-fix-shadow-toolkit-wildcard"
22
27
  }
23
28
  }
24
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mondaydotcomorg/z2h-cli",
3
- "version": "0.11.4",
3
+ "version": "0.11.6",
4
4
  "bin": "./bin/z2h-cli.js",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -51,11 +51,11 @@ describe('create-workspace', () => {
51
51
  expect(runYarnInstall).toHaveBeenCalledWith(expect.objectContaining({ cwd: tmp }));
52
52
  });
53
53
 
54
- it('refuses if the target dir is non-empty', async () => {
54
+ it('skips gracefully if the target dir is non-empty', async () => {
55
55
  await ensureDir(tmp);
56
56
  await writeJson(path.join(tmp, 'somefile.json'), { hello: 'world' });
57
57
 
58
- await expect(createWorkspaceCommand()).rejects.toThrow(/Workspace already exists/);
58
+ await expect(createWorkspaceCommand()).resolves.toBeUndefined();
59
59
  expect(runYarnInstall).not.toHaveBeenCalled();
60
60
  });
61
61
 
@@ -22,7 +22,11 @@ export async function createWorkspaceCommand(opts: CreateWorkspaceOptions = {}):
22
22
  if (await pathExists(home)) {
23
23
  const entries = await readdir(home);
24
24
  if (entries.length > 0) {
25
- throw new Error(`Workspace already exists at ${home}. Remove it and try again.`);
25
+ output(`[z2h-cli] workspace already exists at ${home} — skipping`, {
26
+ ok: true,
27
+ workspace: home,
28
+ });
29
+ return;
26
30
  }
27
31
  } else {
28
32
  await ensureDir(home);
package/src/constants.ts CHANGED
@@ -13,13 +13,10 @@ export const SHADOW_SCRIPTS: Record<string, string> = {
13
13
  };
14
14
 
15
15
  // ─────────── Shadow dev deps ───────
16
- // Yarn berry only wires a workspace member's `node_modules/.bin/` PATH
17
- // for packages that member explicitly declares. The shadow's scripts
18
- // invoke `trident`, so trident-toolkit must be declared here for the
19
- // bin to resolve. `*` is fine — yarn workspaces resolve the actual
20
- // version from the workspace root's pinned entry.
16
+ // Fallback versions used when the workspace root's package.json cannot be
17
+ // read. syncShadow prefers the workspace root's pinned ranges at runtime.
21
18
  export const SHADOW_DEV_DEPS: Record<string, string> = {
22
- '@mondaydotcomorg/trident-toolkit': '*',
19
+ '@mondaydotcomorg/trident-toolkit': '^2.9.25',
23
20
  };
24
21
 
25
22
  // ─────────── Auto-allocated dev port range ────
package/src/index.ts CHANGED
@@ -258,5 +258,8 @@ void main()
258
258
  process.exit(0);
259
259
  })
260
260
  .catch(async (err: unknown) => {
261
- await fail(`main failed: ${(err as Error).message}`, { command: 'cli-main' });
261
+ const errorMsg = err instanceof Error ? err.message : String(err);
262
+ await fail(`main failed, check you are on latest version.\nerror: ${errorMsg}`, {
263
+ command: 'cli-main',
264
+ });
262
265
  });
@@ -0,0 +1,37 @@
1
+ import type { Tree } from '@nx/devkit';
2
+
3
+ const TOOLKIT = '@mondaydotcomorg/trident-toolkit';
4
+ const FALLBACK_VERSION = '^2.9.25';
5
+
6
+ export default function fixShadowToolkitWildcard(tree: Tree): void {
7
+ let targetVersion = FALLBACK_VERSION;
8
+ if (tree.exists('package.json')) {
9
+ try {
10
+ const pkg = JSON.parse(tree.read('package.json', 'utf-8')!);
11
+ targetVersion = pkg.devDependencies?.[TOOLKIT] ?? pkg.dependencies?.[TOOLKIT] ?? FALLBACK_VERSION;
12
+ } catch {
13
+ // fall through to FALLBACK_VERSION
14
+ }
15
+ }
16
+
17
+ for (const entry of tree.children('.')) {
18
+ if (entry.startsWith('.')) {
19
+ continue;
20
+ }
21
+
22
+ const shadowPkgPath = `${entry}/.zth/package.json`;
23
+ if (!tree.exists(shadowPkgPath)) {
24
+ continue;
25
+ }
26
+
27
+ try {
28
+ const pkg = JSON.parse(tree.read(shadowPkgPath, 'utf-8')!);
29
+ if (pkg.devDependencies?.[TOOLKIT] === '*') {
30
+ pkg.devDependencies[TOOLKIT] = targetVersion;
31
+ tree.write(shadowPkgPath, JSON.stringify(pkg, null, 2) + '\n');
32
+ }
33
+ } catch {
34
+ // skip unparseable shadow packages
35
+ }
36
+ }
37
+ }
@@ -14,12 +14,24 @@ describe('buildShadowPkg', () => {
14
14
 
15
15
  it('declares only trident-toolkit so the trident bin is on the shadow PATH', () => {
16
16
  const shadow = buildShadowPkg({ name: 'demo' });
17
- expect(shadow.devDependencies).toEqual({ '@mondaydotcomorg/trident-toolkit': '*' });
17
+ expect(shadow.devDependencies).toHaveProperty('@mondaydotcomorg/trident-toolkit');
18
+ expect(shadow.devDependencies['@mondaydotcomorg/trident-toolkit']).not.toBe('*');
18
19
  const asAny = shadow as unknown as Record<string, unknown>;
19
20
  expect(asAny.dependencies).toBeUndefined();
20
21
  expect(asAny.peerDependencies).toBeUndefined();
21
22
  });
22
23
 
24
+ it('overrides SHADOW_DEV_DEPS fallbacks with supplied pinnedVersions', () => {
25
+ const shadow = buildShadowPkg({ name: 'demo' }, { '@mondaydotcomorg/trident-toolkit': '^3.0.0' });
26
+ expect(shadow.devDependencies['@mondaydotcomorg/trident-toolkit']).toBe('^3.0.0');
27
+ });
28
+
29
+ it('falls back to SHADOW_DEV_DEPS when no pinnedVersions supplied', () => {
30
+ const shadow = buildShadowPkg({ name: 'demo' });
31
+ // Fallback must be a constrained range — never a bare wildcard
32
+ expect(shadow.devDependencies['@mondaydotcomorg/trident-toolkit']).toMatch(/^\^/);
33
+ });
34
+
23
35
  it('uses plain trident scripts (no `-T`); bin resolves via the local devDep', () => {
24
36
  const shadow = buildShadowPkg({ name: 'demo' });
25
37
  expect(shadow.scripts).toEqual({
@@ -4,25 +4,13 @@ import { SHADOW_SCRIPTS, SHADOW_DEV_DEPS } from '../constants';
4
4
  import { mfAppName } from '../util/app-name';
5
5
  import type { ConsumerPkg, ShadowPkg } from '../types';
6
6
 
7
- /**
8
- * Build the shadow package.json.
9
- *
10
- * Runtime deps live at the workspace root (`~/zth-projects/`) and are
11
- * hoisted in via yarn workspaces — no need to mirror them here. The shadow
12
- * only declares the toolchain it directly executes (`trident-toolkit`) so
13
- * yarn berry wires the `trident` bin into the shadow's script PATH.
14
- *
15
- * The `@trident/` scope is preserved so trident-toolkit strips it and the
16
- * emitted bundle becomes `MF-mf-<appName>`, matching the URL the host MF
17
- * routes to.
18
- */
19
- export function buildShadowPkg(consumerPkg: ConsumerPkg): ShadowPkg {
7
+ export function buildShadowPkg(consumerPkg: ConsumerPkg, pinnedVersions: Record<string, string> = {}): ShadowPkg {
20
8
  return {
21
9
  name: `@trident/${mfAppName(consumerPkg.name)}`,
22
10
  version: '0.0.1',
23
11
  private: true,
24
12
  scripts: SHADOW_SCRIPTS,
25
- devDependencies: { ...SHADOW_DEV_DEPS },
13
+ devDependencies: { ...SHADOW_DEV_DEPS, ...pinnedVersions },
26
14
  };
27
15
  }
28
16
 
@@ -1,17 +1,63 @@
1
- import { pathExists, readFile, writeFile } from 'fs-extra';
1
+ import path from 'node:path';
2
+ import { pathExists, readFile, readJson, remove, writeFile } from 'fs-extra';
2
3
  import { info } from '../util/logger';
4
+ import { SHADOW_DEV_DEPS } from '../constants';
3
5
  import { runYarnInstall } from '../util/npm/install';
4
6
  import { buildShadowPkg, hashConsumerDeps, writeShadowPkg } from './merge-package-json';
5
- import type { ConsumerPaths, ConsumerPkg } from '../types';
7
+ import type { ConsumerPaths, ConsumerPkg, ShadowPkg } from '../types';
8
+
9
+ // Reads the workspace root's pinned versions for each package in SHADOW_DEV_DEPS.
10
+ // The workspace root is always one directory above the consumer app.
11
+ // Returns an empty map if the root package.json is missing or unreadable.
12
+ async function readWorkspacePinnedVersions(consumerDir: string): Promise<Record<string, string>> {
13
+ const wsPkgPath = path.join(path.dirname(consumerDir), 'package.json');
14
+ if (!(await pathExists(wsPkgPath))) {
15
+ return {};
16
+ }
17
+ try {
18
+ const wsPkg = (await readJson(wsPkgPath)) as {
19
+ devDependencies?: Record<string, string>;
20
+ dependencies?: Record<string, string>;
21
+ };
22
+ const overrides: Record<string, string> = {};
23
+ for (const name of Object.keys(SHADOW_DEV_DEPS)) {
24
+ const version = wsPkg.devDependencies?.[name] ?? wsPkg.dependencies?.[name];
25
+ if (version) {
26
+ overrides[name] = version;
27
+ }
28
+ }
29
+ return overrides;
30
+ } catch {
31
+ return {};
32
+ }
33
+ }
34
+
35
+ async function shadowDevDepsChanged(shadowPkgPath: string, newDevDeps: Record<string, string>): Promise<boolean> {
36
+ if (!(await pathExists(shadowPkgPath))) {
37
+ return false;
38
+ }
39
+ try {
40
+ const existing = (await readJson(shadowPkgPath)) as ShadowPkg;
41
+ for (const [name, version] of Object.entries(newDevDeps)) {
42
+ if (existing.devDependencies?.[name] !== version) {
43
+ return true;
44
+ }
45
+ }
46
+ return false;
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
6
51
 
7
- /**
8
- * Regenerate the shadow package.json from the current CLI template and
9
- * run `yarn install` at the consumer dir iff the consumer's declared deps
10
- * have changed since the last install. Idempotent — safe to call on every
11
- * build, dev start, or consumer package.json edit.
12
- */
13
52
  export async function syncShadow(paths: ConsumerPaths, consumerPkg: ConsumerPkg): Promise<void> {
14
- const shadow = buildShadowPkg(consumerPkg);
53
+ const pinnedVersions = await readWorkspacePinnedVersions(paths.consumerDir);
54
+ const shadow = buildShadowPkg(consumerPkg, pinnedVersions);
55
+
56
+ if (await shadowDevDepsChanged(paths.shadowPkg, shadow.devDependencies)) {
57
+ info('shadow devDependencies changed — forcing yarn install');
58
+ await remove(paths.shadowDepsHash);
59
+ }
60
+
15
61
  await writeShadowPkg(paths.shadowPkg, shadow);
16
62
 
17
63
  const newHash = hashConsumerDeps(consumerPkg);
@@ -19,9 +65,6 @@ export async function syncShadow(paths: ConsumerPaths, consumerPkg: ConsumerPkg)
19
65
 
20
66
  if (oldHash !== newHash) {
21
67
  info(oldHash ? 'consumer deps changed — yarn install' : 'first run — yarn install');
22
- // Yarn 4 is cache-first and non-interactive by default in non-TTY
23
- // contexts, so plain `yarn install` is all we need. runYarnInstall injects
24
- // the npm-broker token, preflights auth, and re-auths on a broker 401.
25
68
  await runYarnInstall({ cwd: paths.consumerDir });
26
69
  await writeFile(paths.shadowDepsHash, newHash);
27
70
  }
@@ -196,7 +196,7 @@ describe('pullAndResolve', () => {
196
196
  const err = await pullAndResolve(DIR).catch(e => e);
197
197
 
198
198
  expect(err).toBeInstanceOf(GitConflictError);
199
- const info = JSON.parse(err.message);
199
+ const info = JSON.parse(err.message.split('Git conflict detected: ')[1]);
200
200
  expect(info.localCommits).toEqual(['abc local commit']);
201
201
  expect(info.remoteCommits).toEqual(['def remote commit']);
202
202
  expect(info.conflictingFiles).toEqual(['src/index.ts']);
@@ -225,7 +225,7 @@ describe('pushToRemote', () => {
225
225
  const err = await pushToRemote(DIR).catch(e => e);
226
226
 
227
227
  expect(err).toBeInstanceOf(GitConflictError);
228
- const info = JSON.parse(err.message);
228
+ const info = JSON.parse(err.message.split('Git conflict detected: ')[1]);
229
229
  expect(info.localCommits).toEqual(['abc local']);
230
230
  expect(info.remoteCommits).toEqual(['def remote']);
231
231
  expect(info.conflictingFiles).toEqual(['file.ts']);
@@ -97,7 +97,7 @@ export async function pullAndResolve(dir: string, env?: Record<string, string>):
97
97
  }
98
98
  await safeBackupIfDirty(dir);
99
99
  const info = await gatherConflictInfo(dir, gitEnv);
100
- throw new GitConflictError(JSON.stringify(info));
100
+ throw new GitConflictError(`Pull rejected: Git conflict detected: ${JSON.stringify(info)}`);
101
101
  }
102
102
  }
103
103
 
@@ -125,7 +125,7 @@ export async function pushToRemote(dir: string, env?: Record<string, string>): P
125
125
  }
126
126
  if (isRejected) {
127
127
  const info = await gatherConflictInfo(dir, gitEnv);
128
- throw new GitConflictError(JSON.stringify(info));
128
+ throw new GitConflictError(`Push rejected: Git conflict detected: ${JSON.stringify(info)}`);
129
129
  }
130
130
  throw err;
131
131
  }