pob 40.8.0 → 40.9.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.
@@ -8,6 +8,7 @@ import inMonorepo from "../../utils/inMonorepo.js";
8
8
  import removeLegacyGeneratedDocs from "../../utils/legacyGeneratedDocs.js";
9
9
  import { nodeAppTypes } from "../../utils/nodeApps.js";
10
10
  import * as packageUtils from "../../utils/package.js";
11
+ import { getPackageEnvs } from "../../utils/packageEnvs.js";
11
12
  import { detectStorybookReactNative } from "../../utils/storybook.js";
12
13
  import { appIgnorePaths } from "./ignorePaths.js";
13
14
 
@@ -236,7 +237,7 @@ export default class PobAppGenerator extends Generator {
236
237
  this.composeWith("pob:common:husky", {});
237
238
  }
238
239
 
239
- const envs = (pkg.pob && (pkg.pob.babelEnvs || pkg.pob.envs)) || [];
240
+ const envs = getPackageEnvs(pkg.pob) || [];
240
241
  const babel =
241
242
  envs.length > 0 || appsWithTypescript.includes(this.appConfig.type);
242
243
  const typescript = babel || pkg.pob?.typescript;
@@ -20,8 +20,12 @@ export default class CommonE2eGenerator extends Generator {
20
20
 
21
21
  packageUtils.addOrRemoveDevDependencies(pkg, this.options.enable, [
22
22
  "@playwright/test",
23
- "playwright",
24
23
  ]);
24
+ packageUtils.addOrRemoveDevDependencies(
25
+ pkg,
26
+ this.options.enable || packageUtils.hasVitestBrowserPlaywright(pkg),
27
+ ["playwright"],
28
+ );
25
29
 
26
30
  packageUtils.addOrRemoveScripts(pkg, this.options.enable, {
27
31
  // keeps the browsers added by hand, like webkit for iPhone projects
@@ -112,6 +112,20 @@ export default class CommonTestingGenerator extends Generator {
112
112
  description:
113
113
  "Disable git cache. See https://yarnpkg.com/features/caching#offline-mirror.",
114
114
  });
115
+
116
+ this.option("playwrightBrowserTestingPath", {
117
+ type: String,
118
+ default: "",
119
+ description:
120
+ "path of a package running vitest browser tests, to install playwright browsers in ci",
121
+ });
122
+
123
+ this.option("mongodbBinaryCacheKeyFile", {
124
+ type: String,
125
+ default: "",
126
+ description:
127
+ "file pinning the mongodb-memory-server binary version, to cache the binary in ci",
128
+ });
115
129
  }
116
130
 
117
131
  prompting() {
@@ -139,6 +153,8 @@ export default class CommonTestingGenerator extends Generator {
139
153
  isApp: this.options.isApp,
140
154
  splitJobs: this.options.splitCIJobs,
141
155
  onlyLatestLTS: this.options.onlyLatestLTS,
156
+ playwrightBrowserTestingPath: this.options.playwrightBrowserTestingPath,
157
+ mongodbBinaryCacheKeyFile: this.options.mongodbBinaryCacheKeyFile,
142
158
  });
143
159
  } else {
144
160
  this.composeWith("pob:core:ci", {
@@ -222,11 +238,13 @@ export default class CommonTestingGenerator extends Generator {
222
238
  },
223
239
  );
224
240
 
241
+ const withVitestBrowser = packageUtils.hasVitestBrowserPlaywright(pkg);
242
+
225
243
  if (!this.options.enable || (globalTesting && !enableForMonorepo)) {
226
244
  packageUtils.removeDevDependencies(pkg, [
227
245
  "jest",
228
246
  "@types/jest",
229
- "vitest",
247
+ ...(withVitestBrowser ? [] : ["vitest"]),
230
248
  "@vitest/coverage-v8",
231
249
  ]);
232
250
 
@@ -321,11 +339,15 @@ export default class CommonTestingGenerator extends Generator {
321
339
  this.options.enable &&
322
340
  (enableForMonorepo || !globalTesting) &&
323
341
  testRunner === "vitest";
342
+ packageUtils.addOrRemoveDevDependencies(
343
+ pkg,
344
+ isVitestUsed || withVitestBrowser,
345
+ ["vitest"],
346
+ );
324
347
  packageUtils.addOrRemoveDevDependencies(pkg, isVitestUsed, [
325
- "vitest",
326
348
  "@vitest/coverage-v8",
327
349
  ]);
328
- if (isVitestUsed) {
350
+ if (isVitestUsed || withVitestBrowser) {
329
351
  packageUtils.addDevDependencies(pkg, ["vite"]);
330
352
  }
331
353
 
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import Generator from "yeoman-generator";
3
3
  import { latestLTS, maintenanceLTS } from "../../../utils/nodeVersions.js";
4
4
  import * as packageUtils from "../../../utils/package.js";
5
+ import { getEntryEnvs, getPackageEnvs } from "../../../utils/packageEnvs.js";
5
6
  import { packageManagerRun } from "../../../utils/packageManagerUtils.js";
6
7
  import { copyAndFormatTpl } from "../../../utils/writeAndFormat.js";
7
8
 
@@ -341,17 +342,15 @@ export default class CommonTranspilerGenerator extends Generator {
341
342
 
342
343
  let envs = pkg.pob.babelEnvs || pkg.pob.envs;
343
344
 
344
- const envsWithDefault =
345
- pkg.pob.bundler === false
346
- ? []
347
- : envs || [
348
- {
349
- target: "node",
350
- version: "22",
351
- },
352
- ];
345
+ const getEntryEnvsWithDefault = (entry) =>
346
+ pkg.pob.bundler === false ? [] : getEntryEnvs(entry, envs);
347
+
348
+ const indexEntry =
349
+ (pkg.pob.entries ?? ["index"]).find(
350
+ (entry) => (typeof entry === "string" ? entry : entry.name) === "index",
351
+ ) ?? "index";
353
352
 
354
- const esAllBrowserEnv = envsWithDefault.find(
353
+ const esAllBrowserEnv = getEntryEnvsWithDefault(indexEntry).find(
355
354
  (env) =>
356
355
  env.target === "browser" &&
357
356
  env.version === undefined &&
@@ -403,15 +402,17 @@ export default class CommonTranspilerGenerator extends Generator {
403
402
  : `./${this.options.buildDirectory}/definitions/${entryDistName}.d.ts`,
404
403
  };
405
404
 
405
+ const entryEnvs = getEntryEnvsWithDefault(entryWithOptionalExt);
406
+
406
407
  const defaultNodeEnv =
407
408
  withBabel || (withTypescript && pkg.pob?.typescript !== "check-only")
408
- ? envsWithDefault.find((env) => env.target === "node")
409
+ ? entryEnvs.find((env) => env.target === "node")
409
410
  : undefined;
410
411
 
411
412
  const defaultNodeEnvVersion = defaultNodeEnv && defaultNodeEnv.version;
412
413
 
413
- if (envsWithDefault) {
414
- envsWithDefault.forEach(
414
+ if (entryEnvs) {
415
+ entryEnvs.forEach(
415
416
  ({
416
417
  target,
417
418
  version,
@@ -656,7 +657,7 @@ export default class CommonTranspilerGenerator extends Generator {
656
657
  async writing() {
657
658
  const pkg = this.fs.readJSON(this.destinationPath("package.json"));
658
659
  const entries = pkg.pob.entries || ["index"];
659
- const envs = pkg.pob.envs || pkg.pob.babelEnvs;
660
+ const envs = getPackageEnvs(pkg.pob);
660
661
 
661
662
  const hasTargetBrowser =
662
663
  envs && envs.some((env) => env.target === "browser");
@@ -7,6 +7,7 @@ import {
7
7
  import inMonorepo from "../../../utils/inMonorepo.js";
8
8
  import { latestLTS, maintenanceLTS } from "../../../utils/nodeVersions.js";
9
9
  import * as packageUtils from "../../../utils/package.js";
10
+ import { getPackageEnvs } from "../../../utils/packageEnvs.js";
10
11
  import { copyAndFormatTpl } from "../../../utils/writeAndFormat.js";
11
12
 
12
13
  export default class CommonTypescriptGenerator extends Generator {
@@ -169,7 +170,7 @@ export default class CommonTypescriptGenerator extends Generator {
169
170
  }
170
171
 
171
172
  const pkg = this.fs.readJSON(this.destinationPath("package.json"));
172
- const envs = pkg.pob?.envs || [
173
+ const envs = getPackageEnvs(pkg.pob) || [
173
174
  {
174
175
  target: "node",
175
176
  version: `${maintenanceLTS}`,
@@ -421,6 +422,14 @@ export default class CommonTypescriptGenerator extends Generator {
421
422
  ...(this.options.storybook
422
423
  ? [".storybook/*.ts", ".storybook/*.tsx"]
423
424
  : []),
425
+ // the web storybook rendered by vitest browser tests
426
+ ...(existsSync(this.destinationPath(".storybook-web"))
427
+ ? [".storybook-web/*.ts", ".storybook-web/*.tsx"]
428
+ : []),
429
+ // generated by react-native-css: types the className props
430
+ ...(existsSync(this.destinationPath("nativewind-env.d.ts"))
431
+ ? ["nativewind-env.d.ts"]
432
+ : []),
424
433
  ],
425
434
  node: withNode,
426
435
  presets,
@@ -4,6 +4,7 @@ import inMonorepo from "../../../utils/inMonorepo.js";
4
4
  import { latestLTS, maintenanceLTS } from "../../../utils/nodeVersions.js";
5
5
  import {
6
6
  packageManagerExec,
7
+ packageManagerExecWithCwd,
7
8
  packageManagerRun,
8
9
  } from "../../../utils/packageManagerUtils.js";
9
10
  import { copyAndFormatTpl } from "../../../utils/writeAndFormat.js";
@@ -104,6 +105,20 @@ export default class CoreCIGenerator extends Generator {
104
105
  description:
105
106
  "Disable git cache. See https://yarnpkg.com/features/caching#offline-mirror.",
106
107
  });
108
+
109
+ this.option("playwrightBrowserTestingPath", {
110
+ type: String,
111
+ default: "",
112
+ description:
113
+ "path of a package running vitest browser tests, to install playwright browsers",
114
+ });
115
+
116
+ this.option("mongodbBinaryCacheKeyFile", {
117
+ type: String,
118
+ default: "",
119
+ description:
120
+ "file pinning the mongodb-memory-server binary version, to cache the binary",
121
+ });
107
122
  }
108
123
 
109
124
  async default() {
@@ -155,6 +170,15 @@ export default class CoreCIGenerator extends Generator {
155
170
  inMonorepo.pobConfig?.project?.type === "lib",
156
171
  nodeLatestMajorVersion: latestLTS,
157
172
  nodeMaintenanceMajorVersion: maintenanceLTS,
173
+ installPlaywrightBrowserCommand: this.options
174
+ .playwrightBrowserTestingPath
175
+ ? packageManagerExecWithCwd(
176
+ this.options.packageManager,
177
+ this.options.playwrightBrowserTestingPath,
178
+ "playwright install --with-deps chromium",
179
+ )
180
+ : "",
181
+ mongodbBinaryCacheKeyFile: this.options.mongodbBinaryCacheKeyFile,
158
182
  packageManagerRun: (script, packagePath) => {
159
183
  return packageManagerRun(
160
184
  this.options.packageManager,
@@ -202,6 +202,19 @@ jobs:
202
202
  - name: Build
203
203
  run: <%= packageManagerRun("build") %>
204
204
  <% } -%>
205
+ <% if (installPlaywrightBrowserCommand) { -%>
206
+
207
+ - name: Install Playwright chromium
208
+ run: <%- installPlaywrightBrowserCommand %>
209
+ <% } -%>
210
+ <% if (mongodbBinaryCacheKeyFile) { -%>
211
+
212
+ - name: Cache mongod binary
213
+ uses: actions/cache@v6
214
+ with:
215
+ path: ~/.cache/mongodb-binaries
216
+ key: mongod-${{ runner.os }}-${{ hashFiles('<%= mongodbBinaryCacheKeyFile %>') }}
217
+ <% } -%>
205
218
 
206
219
  <% if (codecov) { -%>
207
220
  - name: Test
@@ -88,6 +88,19 @@ jobs:
88
88
  if: startsWith(matrix.node-version, '<%= nodeLatestMajorVersion %>.')
89
89
  run: <%= packageManagerExec('repository-check-dirty') %>
90
90
  <% } -%>
91
+ <% if ((codecov || testing) && installPlaywrightBrowserCommand) { -%>
92
+
93
+ - name: Install Playwright chromium
94
+ run: <%- installPlaywrightBrowserCommand %>
95
+ <% } -%>
96
+ <% if ((codecov || testing) && mongodbBinaryCacheKeyFile) { -%>
97
+
98
+ - name: Cache mongod binary
99
+ uses: actions/cache@v6
100
+ with:
101
+ path: ~/.cache/mongodb-binaries
102
+ key: mongod-${{ runner.os }}-${{ hashFiles('<%= mongodbBinaryCacheKeyFile %>') }}
103
+ <% } -%>
91
104
  <% if (codecov) { -%>
92
105
 
93
106
  - name: Generate Test Coverage
@@ -79,7 +79,11 @@ export default class CoreNpmGenerator extends Generator {
79
79
  });
80
80
  }
81
81
 
82
- pkg.files = [...files].filter(Boolean);
82
+ const exclusions = (pkg.files ?? []).filter((file) =>
83
+ file.startsWith("!"),
84
+ );
85
+
86
+ pkg.files = [...files, ...exclusions].filter(Boolean);
83
87
  }
84
88
  } else {
85
89
  delete pkg.files;
@@ -23,6 +23,7 @@ import {
23
23
  buildTopologicalOrderBatches,
24
24
  discoverWorkspaces,
25
25
  getWorkspaceName,
26
+ isMonorepoPublishable,
26
27
  } from "../../utils/workspaceUtils.js";
27
28
  import { copyAndFormatTpl } from "../../utils/writeAndFormat.js";
28
29
 
@@ -266,6 +267,11 @@ export default class PobMonorepoGenerator extends Generator {
266
267
  : detectStorybookReactNative(allPackages),
267
268
  },
268
269
  ]);
270
+ // set by hand in .yo-rc.json, never prompted
271
+ if (storedConfig?.mongodbBinaryCacheKeyFile) {
272
+ this.pobMonorepoConfig.mongodbBinaryCacheKeyFile =
273
+ storedConfig.mongodbBinaryCacheKeyFile;
274
+ }
269
275
  this.pobMonorepoConfig.packageNames = this.packageNames;
270
276
  this.config.set("monorepo", this.pobMonorepoConfig);
271
277
  this.config.delete("pob-config");
@@ -334,6 +340,12 @@ export default class PobMonorepoGenerator extends Generator {
334
340
  isApp: this.options.isAppProject,
335
341
  onlyLatestLTS: this.options.onlyLatestLTS,
336
342
  splitCIJobs,
343
+ playwrightBrowserTestingPath:
344
+ this.packageLocations.find((location, index) =>
345
+ packageUtils.hasVitestBrowserPlaywright(this.packages[index]),
346
+ ) ?? "",
347
+ mongodbBinaryCacheKeyFile:
348
+ this.pobMonorepoConfig.mongodbBinaryCacheKeyFile ?? "",
337
349
  });
338
350
 
339
351
  const e2eTestingPath = e2eTesting === "." ? "" : `/${e2eTesting}`;
@@ -420,7 +432,10 @@ export default class PobMonorepoGenerator extends Generator {
420
432
  this.composeWith("pob:common:release", {
421
433
  enable: true,
422
434
  packageManager: this.options.packageManager,
423
- enablePublish: !this.options.isAppProject,
435
+ enablePublish: isMonorepoPublishable({
436
+ isAppProject: this.options.isAppProject,
437
+ packages: this.packages,
438
+ }),
424
439
  build: hasRootBuild,
425
440
  isMonorepo: true,
426
441
  ci: this.pobMonorepoConfig.ci,
@@ -19,6 +19,15 @@ export const hasReact = (pkg) =>
19
19
  (pkg.peerDependencies && pkg.peerDependencies.react)
20
20
  );
21
21
 
22
+ /**
23
+ * A package running vitest browser tests (a storybook rendered in a browser)
24
+ * declares vitest and playwright itself, whatever the test runner of the root.
25
+ */
26
+ export const hasVitestBrowserPlaywright = (pkg) =>
27
+ [pkg.dependencies, pkg.devDependencies].some(
28
+ (dependencies) => dependencies?.["@vitest/browser-playwright"],
29
+ );
30
+
22
31
  export const sort = function sort(pkg) {
23
32
  return sortPkg(pkg);
24
33
  };
@@ -0,0 +1,28 @@
1
+ const defaultEnvs = [{ target: "node", version: "22" }];
2
+
3
+ /**
4
+ * The envs an entry is built for, resolved like @pob/rollup-esbuild does: the
5
+ * entry's own envs, else the package envs, else node.
6
+ */
7
+ export const getEntryEnvs = (entry, packageEnvs) =>
8
+ (typeof entry === "object" && entry.envs) || packageEnvs || defaultEnvs;
9
+
10
+ const envKey = (env) =>
11
+ JSON.stringify([env.target, env.version ?? null, env.formats ?? null]);
12
+
13
+ /**
14
+ * The envs a package is built for: its envs, or the union of the envs of its
15
+ * entries when only the entries declare them. Undefined when none is declared.
16
+ */
17
+ export const getPackageEnvs = (pob) => {
18
+ if (!pob) return undefined;
19
+ if (pob.babelEnvs || pob.envs) return pob.babelEnvs || pob.envs;
20
+
21
+ const entriesEnvs = (pob.entries ?? []).flatMap((entry) =>
22
+ typeof entry === "object" && entry.envs ? entry.envs : [],
23
+ );
24
+ if (entriesEnvs.length === 0) return undefined;
25
+
26
+ const envsByKey = new Map(entriesEnvs.map((env) => [envKey(env), env]));
27
+ return [...envsByKey.values()];
28
+ };
@@ -0,0 +1,48 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getEntryEnvs, getPackageEnvs } from "./packageEnvs.js";
3
+
4
+ const node24 = { target: "node", version: "24", omitVersionInFileName: true };
5
+ const browser = { target: "browser" };
6
+
7
+ describe("getEntryEnvs", () => {
8
+ it("uses the envs of the entry", () => {
9
+ expect(getEntryEnvs({ name: "git", envs: [node24] }, [browser])).toEqual([
10
+ node24,
11
+ ]);
12
+ });
13
+
14
+ it("falls back to the envs of the package", () => {
15
+ expect(getEntryEnvs("index", [browser])).toEqual([browser]);
16
+ expect(getEntryEnvs({ name: "index" }, [browser])).toEqual([browser]);
17
+ });
18
+
19
+ it("falls back to node", () => {
20
+ expect(getEntryEnvs("index", undefined)).toEqual([
21
+ { target: "node", version: "22" },
22
+ ]);
23
+ });
24
+ });
25
+
26
+ describe("getPackageEnvs", () => {
27
+ it("returns the envs of the package", () => {
28
+ expect(getPackageEnvs({ envs: [node24], entries: ["index"] })).toEqual([
29
+ node24,
30
+ ]);
31
+ });
32
+
33
+ it("returns the union of the envs of the entries", () => {
34
+ expect(
35
+ getPackageEnvs({
36
+ entries: [
37
+ { name: "index", envs: [node24, browser] },
38
+ { name: "git", envs: [node24] },
39
+ ],
40
+ }),
41
+ ).toEqual([node24, browser]);
42
+ });
43
+
44
+ it("returns undefined when no env is declared", () => {
45
+ expect(getPackageEnvs({ entries: ["index"] })).toBeUndefined();
46
+ expect(getPackageEnvs(undefined)).toBeUndefined();
47
+ });
48
+ });
@@ -25,6 +25,13 @@ export const getWorkspaceName = (workspace) => {
25
25
  export const getNewPackagePath = (workspaces, packageName) =>
26
26
  `${workspaces[0].replace(/\/\*$/, "")}/${packageName.replace(/^@[^/]+\//, "")}`;
27
27
 
28
+ /**
29
+ * A monorepo publishes to npm when it is not an app, or when an app monorepo
30
+ * has public workspaces, such as a cli shipped for npx.
31
+ */
32
+ export const isMonorepoPublishable = ({ isAppProject, packages }) =>
33
+ !isAppProject || packages.some((pkg) => !pkg.private);
34
+
28
35
  export const discoverWorkspaces = async (rootPath) => {
29
36
  const rootPackageJSONPath = path.join(rootPath, "package.json");
30
37
  if (!fs.existsSync(rootPackageJSONPath)) {
@@ -7,8 +7,41 @@ import {
7
7
  buildTopologicalOrderBatches,
8
8
  discoverWorkspaces,
9
9
  getNewPackagePath,
10
+ isMonorepoPublishable,
10
11
  } from "./workspaceUtils.js";
11
12
 
13
+ describe("isMonorepoPublishable", () => {
14
+ it("publishes a lib monorepo", () => {
15
+ expect(
16
+ isMonorepoPublishable({
17
+ isAppProject: false,
18
+ packages: [{ name: "a", private: true }],
19
+ }),
20
+ ).toBe(true);
21
+ });
22
+
23
+ it("publishes an app monorepo with a public workspace", () => {
24
+ expect(
25
+ isMonorepoPublishable({
26
+ isAppProject: true,
27
+ packages: [{ name: "server", private: true }, { name: "cli" }],
28
+ }),
29
+ ).toBe(true);
30
+ });
31
+
32
+ it("does not publish an app monorepo whose workspaces are all private", () => {
33
+ expect(
34
+ isMonorepoPublishable({
35
+ isAppProject: true,
36
+ packages: [
37
+ { name: "server", private: true },
38
+ { name: "web", private: true },
39
+ ],
40
+ }),
41
+ ).toBe(false);
42
+ });
43
+ });
44
+
12
45
  describe("getNewPackagePath", () => {
13
46
  it("creates a scoped package in the workspaces directory", () => {
14
47
  expect(getNewPackagePath(["packages/*"], "@scope/foo")).toBe(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pob",
3
- "version": "40.8.0",
3
+ "version": "40.9.0",
4
4
  "description": "Pile of bones, library generator with git/babel/typescript/typedoc/readme/vitest",
5
5
  "keywords": [
6
6
  "skeleton"
@@ -31,11 +31,11 @@
31
31
  "./package.json": "./package.json"
32
32
  },
33
33
  "dependencies": {
34
- "@pob/codemods": "0.18.3",
35
- "@pob/eslint-config": "68.4.0",
36
- "@pob/eslint-config-typescript-react": "68.5.0",
37
- "@pob/sort-object": "11.3.1",
38
- "@pob/sort-pkg": "13.3.1",
34
+ "@pob/codemods": "0.19.0",
35
+ "@pob/eslint-config": "68.5.0",
36
+ "@pob/eslint-config-typescript-react": "68.6.0",
37
+ "@pob/sort-object": "11.4.0",
38
+ "@pob/sort-pkg": "13.4.0",
39
39
  "@yeoman/adapter": "5.1.1",
40
40
  "eslint": "10.14.0",
41
41
  "findup-sync": "^5.0.0",
@@ -48,21 +48,21 @@
48
48
  "mem-fs": "6.0.0",
49
49
  "mem-fs-editor": "12.1.0",
50
50
  "minimist": "1.2.8",
51
- "oxfmt": "0.75.0",
51
+ "oxfmt": "0.78.0",
52
52
  "parse-author": "2.0.0",
53
- "pob-dependencies": "28.7.0",
54
- "semver": "7.8.5",
53
+ "pob-dependencies": "28.8.0",
54
+ "semver": "7.8.6",
55
55
  "typescript": "npm:@typescript/typescript6@6.0.2",
56
56
  "validate-npm-package-name": "^8.0.0",
57
57
  "yeoman-environment": "8.5.1",
58
58
  "yeoman-generator": "10.3.0"
59
59
  },
60
60
  "devDependencies": {
61
- "@pob/root": "28.7.0",
61
+ "@pob/root": "28.8.0",
62
62
  "@types/node": "24.19.1",
63
63
  "@typescript/native": "npm:typescript@7.0.2",
64
64
  "@yeoman/types": "1.13.1",
65
- "tsrs": "0.20.1-ts7.1.0-beta"
65
+ "tsrs": "0.24.0-ts7.1.0-beta"
66
66
  },
67
67
  "engines": {
68
68
  "node": "^24.22.1 || >=26.13.0"