@mondaydotcomorg/z2h-cli 0.24.1 → 0.25.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.
@@ -1 +1 @@
1
- {"version":3,"file":"create-workspace.d.ts","sourceRoot":"","sources":["../../src/commands/create-workspace.ts"],"names":[],"mappings":"AASA,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0C5D"}
1
+ {"version":3,"file":"create-workspace.d.ts","sourceRoot":"","sources":["../../src/commands/create-workspace.ts"],"names":[],"mappings":"AAaA,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAuC5D"}
@@ -13,37 +13,35 @@ const _interopDefault = e => e && e.__esModule ? e : { default: e };
13
13
 
14
14
  const path__default = /*#__PURE__*/_interopDefault(path);
15
15
 
16
+ async function isPopulated(dir) {
17
+ return (await fs.pathExists(dir)) && (await fs.readdir(dir)).length > 0;
18
+ }
16
19
  async function createWorkspaceCommand() {
17
20
  const home = util_workspace.getWorkspaceHome();
18
21
  const templatesDir = util_templates.getTemplatesDir();
19
22
  const wsTemplatesDir = path__default.default.join(templatesDir, 'workspace');
20
- if (await fs.pathExists(home)) {
21
- const entries = await fs.readdir(home);
22
- if (entries.length > 0) {
23
- util_logger.output(`[z2h-cli] workspace already exists at ${home} — skipping`, {
24
- ok: true,
25
- workspace: home,
26
- });
27
- return;
28
- }
23
+ await fs.ensureDir(home);
24
+ const scaffolded = await util_workspace.isZ2hWorkspace(home);
25
+ if (!scaffolded) {
26
+ util_logger.info(`setting up your Z2H workspace`);
27
+ const pkgRaw = await promises.readFile(path__default.default.join(wsTemplatesDir, 'package.json.tmpl'), 'utf8');
28
+ await Promise.all([
29
+ fs.writeFile(path__default.default.join(home, 'package.json'), pkgRaw),
30
+ fs.copy(path__default.default.join(wsTemplatesDir, 'gitignore'), path__default.default.join(home, '.gitignore')),
31
+ fs.writeFile(path__default.default.join(home, '.yarnrc.yml'), util_npm_brokerConfig.workspaceYarnrcContents()),
32
+ fs.copy(path__default.default.join(wsTemplatesDir, 'nx.json'), path__default.default.join(home, 'nx.json')),
33
+ fs.copy(path__default.default.join(wsTemplatesDir, 'yarnlock'), path__default.default.join(home, 'yarn.lock')),
34
+ fs.copy(path__default.default.join(wsTemplatesDir, 'nvmrc'), path__default.default.join(home, '.nvmrc')),
35
+ fs.writeFile(path__default.default.join(home, '.npmrc'), util_npm_brokerConfig.workspaceNpmrcContents()),
36
+ fs.copy(path__default.default.join(wsTemplatesDir, 'README.md'), path__default.default.join(home, 'README.md')),
37
+ fs.copy(path__default.default.join(wsTemplatesDir, 'yarn-releases', 'yarn-4.5.1.cjs'), path__default.default.join(home, '.yarn', 'releases', 'yarn-4.5.1.cjs')),
38
+ ]);
29
39
  }
30
- else {
31
- await fs.ensureDir(home);
40
+ const hasWorkspaceNodeModules = await isPopulated(path__default.default.join(home, 'node_modules'));
41
+ if (!scaffolded || !hasWorkspaceNodeModules) {
42
+ util_logger.info(`installing workspace dependencies — this may take a minute`);
43
+ await util_npm_install.runYarnInstall({ cwd: home });
32
44
  }
33
- const pkgRaw = await promises.readFile(path__default.default.join(wsTemplatesDir, 'package.json.tmpl'), 'utf8');
34
- await Promise.all([
35
- fs.writeFile(path__default.default.join(home, 'package.json'), pkgRaw),
36
- fs.copy(path__default.default.join(wsTemplatesDir, 'gitignore'), path__default.default.join(home, '.gitignore')),
37
- fs.writeFile(path__default.default.join(home, '.yarnrc.yml'), util_npm_brokerConfig.workspaceYarnrcContents()),
38
- fs.copy(path__default.default.join(wsTemplatesDir, 'nx.json'), path__default.default.join(home, 'nx.json')),
39
- fs.copy(path__default.default.join(wsTemplatesDir, 'yarnlock'), path__default.default.join(home, 'yarn.lock')),
40
- fs.copy(path__default.default.join(wsTemplatesDir, 'nvmrc'), path__default.default.join(home, '.nvmrc')),
41
- fs.writeFile(path__default.default.join(home, '.npmrc'), util_npm_brokerConfig.workspaceNpmrcContents()),
42
- fs.copy(path__default.default.join(wsTemplatesDir, 'README.md'), path__default.default.join(home, 'README.md')),
43
- fs.copy(path__default.default.join(wsTemplatesDir, 'yarn-releases', 'yarn-4.5.1.cjs'), path__default.default.join(home, '.yarn', 'releases', 'yarn-4.5.1.cjs')),
44
- ]);
45
- util_logger.info(`setting up your Z2H workspace — this may take a minute...`);
46
- await util_npm_install.runYarnInstall({ cwd: home });
47
45
  util_logger.output(`Your workspace is ready! 🚀 Apps will live at ${home}`, {
48
46
  ok: true,
49
47
  workspace: home,
@@ -1 +1 @@
1
- {"version":3,"file":"create-workspace.d.ts","sourceRoot":"","sources":["../../../src/commands/create-workspace.ts"],"names":[],"mappings":"AASA,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0C5D"}
1
+ {"version":3,"file":"create-workspace.d.ts","sourceRoot":"","sources":["../../../src/commands/create-workspace.ts"],"names":[],"mappings":"AAaA,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAuC5D"}
@@ -1,43 +1,41 @@
1
1
  import path from 'node:path';
2
2
  import { readFile } from 'node:fs/promises';
3
- import { pathExists, readdir, ensureDir, writeFile, copy } from 'fs-extra';
4
- import { output, info } from '../util/logger.mjs';
5
- import { getWorkspaceHome } from '../util/workspace.mjs';
3
+ import { ensureDir, writeFile, copy, pathExists, readdir } from 'fs-extra';
4
+ import { info, output } from '../util/logger.mjs';
5
+ import { getWorkspaceHome, isZ2hWorkspace } from '../util/workspace.mjs';
6
6
  import { getTemplatesDir } from '../util/templates.mjs';
7
7
  import { runYarnInstall } from '../util/npm/install.mjs';
8
8
  import { workspaceYarnrcContents, workspaceNpmrcContents } from '../util/npm/broker-config.mjs';
9
9
 
10
+ async function isPopulated(dir) {
11
+ return (await pathExists(dir)) && (await readdir(dir)).length > 0;
12
+ }
10
13
  async function createWorkspaceCommand() {
11
14
  const home = getWorkspaceHome();
12
15
  const templatesDir = getTemplatesDir();
13
16
  const wsTemplatesDir = path.join(templatesDir, 'workspace');
14
- if (await pathExists(home)) {
15
- const entries = await readdir(home);
16
- if (entries.length > 0) {
17
- output(`[z2h-cli] workspace already exists at ${home} — skipping`, {
18
- ok: true,
19
- workspace: home,
20
- });
21
- return;
22
- }
17
+ await ensureDir(home);
18
+ const scaffolded = await isZ2hWorkspace(home);
19
+ if (!scaffolded) {
20
+ info(`setting up your Z2H workspace`);
21
+ const pkgRaw = await readFile(path.join(wsTemplatesDir, 'package.json.tmpl'), 'utf8');
22
+ await Promise.all([
23
+ writeFile(path.join(home, 'package.json'), pkgRaw),
24
+ copy(path.join(wsTemplatesDir, 'gitignore'), path.join(home, '.gitignore')),
25
+ writeFile(path.join(home, '.yarnrc.yml'), workspaceYarnrcContents()),
26
+ copy(path.join(wsTemplatesDir, 'nx.json'), path.join(home, 'nx.json')),
27
+ copy(path.join(wsTemplatesDir, 'yarnlock'), path.join(home, 'yarn.lock')),
28
+ copy(path.join(wsTemplatesDir, 'nvmrc'), path.join(home, '.nvmrc')),
29
+ writeFile(path.join(home, '.npmrc'), workspaceNpmrcContents()),
30
+ copy(path.join(wsTemplatesDir, 'README.md'), path.join(home, 'README.md')),
31
+ copy(path.join(wsTemplatesDir, 'yarn-releases', 'yarn-4.5.1.cjs'), path.join(home, '.yarn', 'releases', 'yarn-4.5.1.cjs')),
32
+ ]);
23
33
  }
24
- else {
25
- await ensureDir(home);
34
+ const hasWorkspaceNodeModules = await isPopulated(path.join(home, 'node_modules'));
35
+ if (!scaffolded || !hasWorkspaceNodeModules) {
36
+ info(`installing workspace dependencies — this may take a minute`);
37
+ await runYarnInstall({ cwd: home });
26
38
  }
27
- const pkgRaw = await readFile(path.join(wsTemplatesDir, 'package.json.tmpl'), 'utf8');
28
- await Promise.all([
29
- writeFile(path.join(home, 'package.json'), pkgRaw),
30
- copy(path.join(wsTemplatesDir, 'gitignore'), path.join(home, '.gitignore')),
31
- writeFile(path.join(home, '.yarnrc.yml'), workspaceYarnrcContents()),
32
- copy(path.join(wsTemplatesDir, 'nx.json'), path.join(home, 'nx.json')),
33
- copy(path.join(wsTemplatesDir, 'yarnlock'), path.join(home, 'yarn.lock')),
34
- copy(path.join(wsTemplatesDir, 'nvmrc'), path.join(home, '.nvmrc')),
35
- writeFile(path.join(home, '.npmrc'), workspaceNpmrcContents()),
36
- copy(path.join(wsTemplatesDir, 'README.md'), path.join(home, 'README.md')),
37
- copy(path.join(wsTemplatesDir, 'yarn-releases', 'yarn-4.5.1.cjs'), path.join(home, '.yarn', 'releases', 'yarn-4.5.1.cjs')),
38
- ]);
39
- info(`setting up your Z2H workspace — this may take a minute...`);
40
- await runYarnInstall({ cwd: home });
41
39
  output(`Your workspace is ready! 🚀 Apps will live at ${home}`, {
42
40
  ok: true,
43
41
  workspace: home,
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/shadow/scaffold.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA0G9C;;;GAGG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvG"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/shadow/scaffold.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;GAGG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvG"}
@@ -1,104 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { ensureDir, writeFile, writeJson } from 'fs-extra';
3
+ import { WRAPPER_TEMPLATE, tridentrcTemplate, dtsFileTemplate, TSCONFIG_TEMPLATE } from '@mondaydotcomorg/z2h-shared-utils/mf-scaffold';
3
4
 
4
- const WRAPPER_TEMPLATE = `import '@vibe/core/tokens';
5
- import ConsumerApp from '../../src/index';
6
- import React from 'react';
7
- import * as ReactDOM from 'react-dom/client';
8
- import { ErrorBoundary } from 'react-error-boundary';
9
-
10
- const roots = new Map<HTMLElement, ReactDOM.Root>();
11
-
12
- export function init() {}
13
-
14
- export function render(element: HTMLElement) {
15
- const root = ReactDOM.createRoot(element);
16
- root.render(
17
- <ErrorBoundary fallback={<div style={{ padding: 16 }}>This app crashed.</div>}>
18
- <ConsumerApp />
19
- </ErrorBoundary>,
20
- );
21
- roots.set(element, root);
22
- }
23
-
24
- export function unmount({ element }: { element: HTMLElement }) {
25
- const root = roots.get(element);
26
- if (root) {
27
- root.unmount();
28
- roots.delete(element);
29
- }
30
- }
31
- `;
32
- const tridentrcTemplate = (appName, port) => `/** @type {import("@mondaydotcomorg/trident-user-config").MicroFrontendConfig} */
33
- const config = {
34
- appName: '${appName}',
35
- port: ${port},
36
- };
37
-
38
- module.exports = config;
39
- `;
40
- const dtsFileTemplate = () => `
41
- declare module '*.module.css' {
42
- const classNames: {
43
- [className: string]: string;
44
- };
45
- export = classNames;
46
- }
47
-
48
- declare module '*.module.scss' {
49
- const classNames: {
50
- [className: string]: string;
51
- };
52
- export = classNames;
53
- }
54
-
55
- declare module '*.jpg' {
56
- const e: string;
57
- export default e;
58
- }
59
- declare module '*.jpeg' {
60
- const e: string;
61
- export default e;
62
- }
63
- declare module '*.png' {
64
- const e: string;
65
- export default e;
66
- }
67
- declare module '*.riv' {
68
- const e: string;
69
- export default e;
70
- }
71
-
72
- declare module '*.wasm' {
73
- const e: string;
74
- export default e;
75
- }
76
-
77
- declare module '*.html?url' {
78
- const src: string;
79
- export default src;
80
- }
81
- `;
82
- const TSCONFIG_TEMPLATE = {
83
- compilerOptions: {
84
- target: 'es5',
85
- lib: ['dom', 'dom.iterable', 'esnext'],
86
- allowJs: false,
87
- skipLibCheck: true,
88
- esModuleInterop: true,
89
- allowSyntheticDefaultImports: true,
90
- strict: false,
91
- module: 'esnext',
92
- moduleResolution: 'bundler',
93
- resolveJsonModule: true,
94
- noImplicitAny: false,
95
- isolatedModules: true,
96
- baseUrl: 'src',
97
- noEmit: true,
98
- jsx: 'react-jsx',
99
- },
100
- include: ['src', '../src'],
101
- };
102
5
  /**
103
6
  * Write every generated file under .zth/. Overwrites on every run so the
104
7
  * shadow dir is a deterministic function of the CLI version — no drift.
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/shadow/scaffold.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA0G9C;;;GAGG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvG"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/shadow/scaffold.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C;;;GAGG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAYvG"}
@@ -2,120 +2,23 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  const path = require('node:path');
4
4
  const fs = require('fs-extra');
5
+ const mfScaffold = require('@mondaydotcomorg/z2h-shared-utils/mf-scaffold');
5
6
 
6
7
  const _interopDefault = e => e && e.__esModule ? e : { default: e };
7
8
 
8
9
  const path__default = /*#__PURE__*/_interopDefault(path);
9
10
 
10
- const WRAPPER_TEMPLATE = `import '@vibe/core/tokens';
11
- import ConsumerApp from '../../src/index';
12
- import React from 'react';
13
- import * as ReactDOM from 'react-dom/client';
14
- import { ErrorBoundary } from 'react-error-boundary';
15
-
16
- const roots = new Map<HTMLElement, ReactDOM.Root>();
17
-
18
- export function init() {}
19
-
20
- export function render(element: HTMLElement) {
21
- const root = ReactDOM.createRoot(element);
22
- root.render(
23
- <ErrorBoundary fallback={<div style={{ padding: 16 }}>This app crashed.</div>}>
24
- <ConsumerApp />
25
- </ErrorBoundary>,
26
- );
27
- roots.set(element, root);
28
- }
29
-
30
- export function unmount({ element }: { element: HTMLElement }) {
31
- const root = roots.get(element);
32
- if (root) {
33
- root.unmount();
34
- roots.delete(element);
35
- }
36
- }
37
- `;
38
- const tridentrcTemplate = (appName, port) => `/** @type {import("@mondaydotcomorg/trident-user-config").MicroFrontendConfig} */
39
- const config = {
40
- appName: '${appName}',
41
- port: ${port},
42
- };
43
-
44
- module.exports = config;
45
- `;
46
- const dtsFileTemplate = () => `
47
- declare module '*.module.css' {
48
- const classNames: {
49
- [className: string]: string;
50
- };
51
- export = classNames;
52
- }
53
-
54
- declare module '*.module.scss' {
55
- const classNames: {
56
- [className: string]: string;
57
- };
58
- export = classNames;
59
- }
60
-
61
- declare module '*.jpg' {
62
- const e: string;
63
- export default e;
64
- }
65
- declare module '*.jpeg' {
66
- const e: string;
67
- export default e;
68
- }
69
- declare module '*.png' {
70
- const e: string;
71
- export default e;
72
- }
73
- declare module '*.riv' {
74
- const e: string;
75
- export default e;
76
- }
77
-
78
- declare module '*.wasm' {
79
- const e: string;
80
- export default e;
81
- }
82
-
83
- declare module '*.html?url' {
84
- const src: string;
85
- export default src;
86
- }
87
- `;
88
- const TSCONFIG_TEMPLATE = {
89
- compilerOptions: {
90
- target: 'es5',
91
- lib: ['dom', 'dom.iterable', 'esnext'],
92
- allowJs: false,
93
- skipLibCheck: true,
94
- esModuleInterop: true,
95
- allowSyntheticDefaultImports: true,
96
- strict: false,
97
- module: 'esnext',
98
- moduleResolution: 'bundler',
99
- resolveJsonModule: true,
100
- noImplicitAny: false,
101
- isolatedModules: true,
102
- baseUrl: 'src',
103
- noEmit: true,
104
- jsx: 'react-jsx',
105
- },
106
- include: ['src', '../src'],
107
- };
108
11
  /**
109
12
  * Write every generated file under .zth/. Overwrites on every run so the
110
13
  * shadow dir is a deterministic function of the CLI version — no drift.
111
14
  */
112
15
  async function scaffoldShadow(paths, appName, port) {
113
16
  await fs.ensureDir(paths.shadowSrc);
114
- await fs.writeFile(paths.shadowWrapper, WRAPPER_TEMPLATE);
115
- await fs.writeFile(paths.shadowTridentrc, tridentrcTemplate(appName, port));
17
+ await fs.writeFile(paths.shadowWrapper, mfScaffold.WRAPPER_TEMPLATE);
18
+ await fs.writeFile(paths.shadowTridentrc, mfScaffold.tridentrcTemplate(appName, port));
116
19
  await fs.writeFile(paths.shadowNvmrc, `${process.version}\n`);
117
- await fs.writeFile(paths.shadowDts, dtsFileTemplate());
118
- await fs.writeJson(paths.shadowTsconfig, TSCONFIG_TEMPLATE, { spaces: 2 });
20
+ await fs.writeFile(paths.shadowDts, mfScaffold.dtsFileTemplate());
21
+ await fs.writeJson(paths.shadowTsconfig, mfScaffold.TSCONFIG_TEMPLATE, { spaces: 2 });
119
22
  // Drop a stub README so contributors who open .zth/ know what they're looking at.
120
23
  await fs.writeFile(path__default.default.join(paths.shadowDir, 'README.md'), '# .zth/\nGenerated by z2h-cli. Do not edit — run `z2h-cli build` or `z2h-cli dev` to refresh.\n');
121
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mondaydotcomorg/z2h-cli",
3
- "version": "0.24.1",
3
+ "version": "0.25.0",
4
4
  "bin": "./bin/z2h-cli.js",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -51,6 +51,7 @@
51
51
  ],
52
52
  "dependencies": {
53
53
  "@aws-sdk/client-s3": "^3.706.0",
54
+ "@mondaydotcomorg/z2h-shared-utils": "^0.3.0",
54
55
  "chokidar": "^3.6.0",
55
56
  "commander": "^12.1.0",
56
57
  "cors": "^2.8.6",
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import os from 'node:os';
3
- import { mkdtemp, rm, writeJson, readJson, ensureDir, pathExists } from 'fs-extra';
3
+ import { mkdtemp, rm, outputJson, readJson, ensureDir, pathExists } from 'fs-extra';
4
4
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
5
 
6
6
  import { runYarnInstall } from '../../util/npm/install';
@@ -51,14 +51,35 @@ describe('create-workspace', () => {
51
51
  expect(runYarnInstall).toHaveBeenCalledWith(expect.objectContaining({ cwd: tmp }));
52
52
  });
53
53
 
54
- it('skips gracefully if the target dir is non-empty', async () => {
54
+ it('scaffolds and installs even if the target dir has unrelated files (e.g. a mounted empty node_modules)', async () => {
55
55
  await ensureDir(tmp);
56
- await writeJson(path.join(tmp, 'somefile.json'), { hello: 'world' });
56
+ await ensureDir(path.join(tmp, 'node_modules'));
57
+
58
+ await createWorkspaceCommand();
59
+
60
+ expect(await pathExists(path.join(tmp, 'package.json'))).toBe(true);
61
+ expect(runYarnInstall).toHaveBeenCalledWith(expect.objectContaining({ cwd: tmp }));
62
+ });
63
+
64
+ it('skips install if package.json exists and node_modules is populated', async () => {
65
+ await createWorkspaceCommand();
66
+ vi.mocked(runYarnInstall).mockClear();
67
+ await outputJson(path.join(tmp, 'node_modules', 'some-dep', 'package.json'), { name: 'some-dep' });
68
+
69
+ await createWorkspaceCommand();
57
70
 
58
- await expect(createWorkspaceCommand()).resolves.toBeUndefined();
59
71
  expect(runYarnInstall).not.toHaveBeenCalled();
60
72
  });
61
73
 
74
+ it('reinstalls if package.json exists but node_modules is empty (e.g. wiped by an ephemeral mount)', async () => {
75
+ await createWorkspaceCommand();
76
+ vi.mocked(runYarnInstall).mockClear();
77
+
78
+ await createWorkspaceCommand();
79
+
80
+ expect(runYarnInstall).toHaveBeenCalledWith(expect.objectContaining({ cwd: tmp }));
81
+ });
82
+
62
83
  it('does not include z2h-cli in workspace devDeps (managed globally)', async () => {
63
84
  await createWorkspaceCommand();
64
85
 
@@ -2,48 +2,49 @@ import path from 'node:path';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { ensureDir, writeFile, pathExists, readdir, copy } from 'fs-extra';
4
4
  import { info, output } from '../util/logger';
5
- import { getWorkspaceHome } from '../util/workspace';
5
+ import { getWorkspaceHome, isZ2hWorkspace } from '../util/workspace';
6
6
  import { getTemplatesDir } from '../util/templates';
7
7
  import { runYarnInstall } from '../util/npm/install';
8
8
  import { workspaceNpmrcContents, workspaceYarnrcContents } from '../util/npm/broker-config';
9
9
 
10
+ async function isPopulated(dir: string): Promise<boolean> {
11
+ return (await pathExists(dir)) && (await readdir(dir)).length > 0;
12
+ }
13
+
10
14
  export async function createWorkspaceCommand(): Promise<void> {
11
15
  const home = getWorkspaceHome();
12
16
  const templatesDir = getTemplatesDir();
13
17
  const wsTemplatesDir = path.join(templatesDir, 'workspace');
14
18
 
15
- if (await pathExists(home)) {
16
- const entries = await readdir(home);
17
- if (entries.length > 0) {
18
- output(`[z2h-cli] workspace already exists at ${home} — skipping`, {
19
- ok: true,
20
- workspace: home,
21
- });
22
- return;
23
- }
24
- } else {
25
- await ensureDir(home);
19
+ await ensureDir(home);
20
+
21
+ const scaffolded = await isZ2hWorkspace(home);
22
+
23
+ if (!scaffolded) {
24
+ info(`setting up your Z2H workspace`);
25
+ const pkgRaw = await readFile(path.join(wsTemplatesDir, 'package.json.tmpl'), 'utf8');
26
+
27
+ await Promise.all([
28
+ writeFile(path.join(home, 'package.json'), pkgRaw),
29
+ copy(path.join(wsTemplatesDir, 'gitignore'), path.join(home, '.gitignore')),
30
+ writeFile(path.join(home, '.yarnrc.yml'), workspaceYarnrcContents()),
31
+ copy(path.join(wsTemplatesDir, 'nx.json'), path.join(home, 'nx.json')),
32
+ copy(path.join(wsTemplatesDir, 'yarnlock'), path.join(home, 'yarn.lock')),
33
+ copy(path.join(wsTemplatesDir, 'nvmrc'), path.join(home, '.nvmrc')),
34
+ writeFile(path.join(home, '.npmrc'), workspaceNpmrcContents()),
35
+ copy(path.join(wsTemplatesDir, 'README.md'), path.join(home, 'README.md')),
36
+ copy(
37
+ path.join(wsTemplatesDir, 'yarn-releases', 'yarn-4.5.1.cjs'),
38
+ path.join(home, '.yarn', 'releases', 'yarn-4.5.1.cjs')
39
+ ),
40
+ ]);
26
41
  }
27
42
 
28
- const pkgRaw = await readFile(path.join(wsTemplatesDir, 'package.json.tmpl'), 'utf8');
29
-
30
- await Promise.all([
31
- writeFile(path.join(home, 'package.json'), pkgRaw),
32
- copy(path.join(wsTemplatesDir, 'gitignore'), path.join(home, '.gitignore')),
33
- writeFile(path.join(home, '.yarnrc.yml'), workspaceYarnrcContents()),
34
- copy(path.join(wsTemplatesDir, 'nx.json'), path.join(home, 'nx.json')),
35
- copy(path.join(wsTemplatesDir, 'yarnlock'), path.join(home, 'yarn.lock')),
36
- copy(path.join(wsTemplatesDir, 'nvmrc'), path.join(home, '.nvmrc')),
37
- writeFile(path.join(home, '.npmrc'), workspaceNpmrcContents()),
38
- copy(path.join(wsTemplatesDir, 'README.md'), path.join(home, 'README.md')),
39
- copy(
40
- path.join(wsTemplatesDir, 'yarn-releases', 'yarn-4.5.1.cjs'),
41
- path.join(home, '.yarn', 'releases', 'yarn-4.5.1.cjs')
42
- ),
43
- ]);
44
-
45
- info(`setting up your Z2H workspace — this may take a minute...`);
46
- await runYarnInstall({ cwd: home });
43
+ const hasWorkspaceNodeModules = await isPopulated(path.join(home, 'node_modules'));
44
+ if (!scaffolded || !hasWorkspaceNodeModules) {
45
+ info(`installing workspace dependencies — this may take a minute`);
46
+ await runYarnInstall({ cwd: home });
47
+ }
47
48
 
48
49
  output(`Your workspace is ready! 🚀 Apps will live at ${home}`, {
49
50
  ok: true,
@@ -1,111 +1,13 @@
1
1
  import path from 'node:path';
2
2
  import { ensureDir, writeFile, writeJson } from 'fs-extra';
3
+ import {
4
+ WRAPPER_TEMPLATE,
5
+ tridentrcTemplate,
6
+ dtsFileTemplate,
7
+ TSCONFIG_TEMPLATE,
8
+ } from '@mondaydotcomorg/z2h-shared-utils/mf-scaffold';
3
9
  import type { ConsumerPaths } from '../types';
4
10
 
5
- const WRAPPER_TEMPLATE = `import '@vibe/core/tokens';
6
- import ConsumerApp from '../../src/index';
7
- import React from 'react';
8
- import * as ReactDOM from 'react-dom/client';
9
- import { ErrorBoundary } from 'react-error-boundary';
10
-
11
- const roots = new Map<HTMLElement, ReactDOM.Root>();
12
-
13
- export function init() {}
14
-
15
- export function render(element: HTMLElement) {
16
- const root = ReactDOM.createRoot(element);
17
- root.render(
18
- <ErrorBoundary fallback={<div style={{ padding: 16 }}>This app crashed.</div>}>
19
- <ConsumerApp />
20
- </ErrorBoundary>,
21
- );
22
- roots.set(element, root);
23
- }
24
-
25
- export function unmount({ element }: { element: HTMLElement }) {
26
- const root = roots.get(element);
27
- if (root) {
28
- root.unmount();
29
- roots.delete(element);
30
- }
31
- }
32
- `;
33
-
34
- const tridentrcTemplate = (appName: string, port: number): string =>
35
- `/** @type {import("@mondaydotcomorg/trident-user-config").MicroFrontendConfig} */
36
- const config = {
37
- appName: '${appName}',
38
- port: ${port},
39
- };
40
-
41
- module.exports = config;
42
- `;
43
-
44
- const dtsFileTemplate = (): string =>
45
- `
46
- declare module '*.module.css' {
47
- const classNames: {
48
- [className: string]: string;
49
- };
50
- export = classNames;
51
- }
52
-
53
- declare module '*.module.scss' {
54
- const classNames: {
55
- [className: string]: string;
56
- };
57
- export = classNames;
58
- }
59
-
60
- declare module '*.jpg' {
61
- const e: string;
62
- export default e;
63
- }
64
- declare module '*.jpeg' {
65
- const e: string;
66
- export default e;
67
- }
68
- declare module '*.png' {
69
- const e: string;
70
- export default e;
71
- }
72
- declare module '*.riv' {
73
- const e: string;
74
- export default e;
75
- }
76
-
77
- declare module '*.wasm' {
78
- const e: string;
79
- export default e;
80
- }
81
-
82
- declare module '*.html?url' {
83
- const src: string;
84
- export default src;
85
- }
86
- `;
87
-
88
- const TSCONFIG_TEMPLATE = {
89
- compilerOptions: {
90
- target: 'es5',
91
- lib: ['dom', 'dom.iterable', 'esnext'],
92
- allowJs: false,
93
- skipLibCheck: true,
94
- esModuleInterop: true,
95
- allowSyntheticDefaultImports: true,
96
- strict: false,
97
- module: 'esnext',
98
- moduleResolution: 'bundler',
99
- resolveJsonModule: true,
100
- noImplicitAny: false,
101
- isolatedModules: true,
102
- baseUrl: 'src',
103
- noEmit: true,
104
- jsx: 'react-jsx',
105
- },
106
- include: ['src', '../src'],
107
- };
108
-
109
11
  /**
110
12
  * Write every generated file under .zth/. Overwrites on every run so the
111
13
  * shadow dir is a deterministic function of the CLI version — no drift.