pob 40.7.3 → 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.
- package/lib/generators/app/PobAppGenerator.js +2 -1
- package/lib/generators/common/e2e/CommonE2eGenerator.js +5 -1
- package/lib/generators/common/testing/CommonTestingGenerator.js +25 -3
- package/lib/generators/common/transpiler/CommonTranspilerGenerator.js +15 -14
- package/lib/generators/common/typescript/CommonTypescriptGenerator.js +10 -1
- package/lib/generators/core/ci/CoreCIGenerator.js +24 -0
- package/lib/generators/core/ci/templates/github-action-push-workflow-split.yml.ejs +13 -0
- package/lib/generators/core/ci/templates/github-action-push-workflow.yml.ejs +13 -0
- package/lib/generators/core/npm/CoreNpmGenerator.js +5 -1
- package/lib/generators/core/renovate/CoreRenovateGenerator.js +11 -12
- package/lib/generators/core/renovate/renovateExtends.js +14 -0
- package/lib/generators/core/renovate/renovateExtends.test.js +37 -0
- package/lib/generators/monorepo/PobMonorepoGenerator.js +18 -2
- package/lib/utils/appTsconfigTarget.js +5 -10
- package/lib/utils/expoApps.js +22 -0
- package/lib/utils/expoApps.test.js +81 -0
- package/lib/utils/inMonorepo.js +4 -2
- package/lib/utils/minimumReleaseAgeExcludes.test.js +3 -1
- package/lib/utils/nodeEngines.js +7 -5
- package/lib/utils/nodeEngines.test.js +23 -16
- package/lib/utils/package.js +10 -0
- package/lib/utils/packageEnvs.js +28 -0
- package/lib/utils/packageEnvs.test.js +48 -0
- package/lib/utils/packagejson.js +1 -1
- package/lib/utils/workspaceUtils.js +19 -2
- package/lib/utils/workspaceUtils.test.js +33 -0
- package/package.json +15 -15
|
@@ -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
|
|
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
|
|
345
|
-
pkg.pob.bundler === false
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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 =
|
|
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
|
-
?
|
|
409
|
+
? entryEnvs.find((env) => env.target === "node")
|
|
409
410
|
: undefined;
|
|
410
411
|
|
|
411
412
|
const defaultNodeEnvVersion = defaultNodeEnv && defaultNodeEnv.version;
|
|
412
413
|
|
|
413
|
-
if (
|
|
414
|
-
|
|
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
|
|
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
|
|
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
|
|
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;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import Generator from "yeoman-generator";
|
|
2
|
+
import { hasExpoApp } from "../../../utils/expoApps.js";
|
|
2
3
|
import inMonorepo from "../../../utils/inMonorepo.js";
|
|
3
4
|
import { writeAndFormatJson } from "../../../utils/writeAndFormat.js";
|
|
5
|
+
import { getRenovateExtends } from "./renovateExtends.js";
|
|
4
6
|
|
|
5
7
|
export default class CoreRenovateGenerator extends Generator {
|
|
6
8
|
constructor(args, opts) {
|
|
@@ -57,7 +59,7 @@ export default class CoreRenovateGenerator extends Generator {
|
|
|
57
59
|
this.enableRenovate = this.enableRenovateConfig.enable;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
writing() {
|
|
62
|
+
async writing() {
|
|
61
63
|
if (this.enableRenovate) {
|
|
62
64
|
const pkg = this.fs.readJSON(this.destinationPath("package.json"), {});
|
|
63
65
|
const renovateConfig = this.fs.readJSON(
|
|
@@ -68,18 +70,15 @@ export default class CoreRenovateGenerator extends Generator {
|
|
|
68
70
|
if (this.options.app) {
|
|
69
71
|
renovateConfig.$schema =
|
|
70
72
|
"https://docs.renovatebot.com/renovate-schema.json";
|
|
71
|
-
renovateConfig.extends = [
|
|
72
|
-
"config:js-app",
|
|
73
|
-
"github>christophehurpeau/renovate-presets",
|
|
74
|
-
];
|
|
75
|
-
} else {
|
|
76
|
-
renovateConfig.extends = [
|
|
77
|
-
"config:js-lib",
|
|
78
|
-
pkg.name === "pob-monorepo"
|
|
79
|
-
? undefined
|
|
80
|
-
: "github>christophehurpeau/renovate-presets",
|
|
81
|
-
].filter(Boolean);
|
|
82
73
|
}
|
|
74
|
+
renovateConfig.extends = getRenovateExtends({
|
|
75
|
+
app: this.options.app,
|
|
76
|
+
pkgName: pkg.name,
|
|
77
|
+
withExpo: await hasExpoApp({
|
|
78
|
+
rootPath: this.destinationPath(),
|
|
79
|
+
pobConfig: this.config.getAll(),
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
83
82
|
|
|
84
83
|
return writeAndFormatJson(
|
|
85
84
|
this.fs,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const presets = "github>christophehurpeau/renovate-presets";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The expo preset comes last so that it overrides the rules of the default
|
|
5
|
+
* preset matching the same packages.
|
|
6
|
+
*
|
|
7
|
+
* @param {{ app: boolean, pkgName: string | undefined, withExpo: boolean }} options
|
|
8
|
+
*/
|
|
9
|
+
export const getRenovateExtends = ({ app, pkgName, withExpo }) =>
|
|
10
|
+
[
|
|
11
|
+
app ? "config:js-app" : "config:js-lib",
|
|
12
|
+
!app && pkgName === "pob-monorepo" ? undefined : presets,
|
|
13
|
+
withExpo ? `${presets}:expo` : undefined,
|
|
14
|
+
].filter(Boolean);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { getRenovateExtends } from "./renovateExtends.js";
|
|
3
|
+
|
|
4
|
+
const presets = "github>christophehurpeau/renovate-presets";
|
|
5
|
+
|
|
6
|
+
describe("getRenovateExtends", () => {
|
|
7
|
+
it("extends the lib config and the presets for a library", () => {
|
|
8
|
+
expect(
|
|
9
|
+
getRenovateExtends({ app: false, pkgName: "lib", withExpo: false }),
|
|
10
|
+
).toEqual(["config:js-lib", presets]);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("extends the app config and the presets for an app", () => {
|
|
14
|
+
expect(
|
|
15
|
+
getRenovateExtends({ app: true, pkgName: "app", withExpo: false }),
|
|
16
|
+
).toEqual(["config:js-app", presets]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("does not extend the presets for pob-monorepo", () => {
|
|
20
|
+
expect(
|
|
21
|
+
getRenovateExtends({
|
|
22
|
+
app: false,
|
|
23
|
+
pkgName: "pob-monorepo",
|
|
24
|
+
withExpo: false,
|
|
25
|
+
}),
|
|
26
|
+
).toEqual(["config:js-lib"]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("extends the expo preset last with an expo app", () => {
|
|
30
|
+
expect(
|
|
31
|
+
getRenovateExtends({ app: true, pkgName: "mobile", withExpo: true }),
|
|
32
|
+
).toEqual(["config:js-app", presets, `${presets}:expo`]);
|
|
33
|
+
expect(
|
|
34
|
+
getRenovateExtends({ app: false, pkgName: "alouette", withExpo: true }),
|
|
35
|
+
).toEqual(["config:js-lib", presets, `${presets}:expo`]);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -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
|
|
|
@@ -152,7 +153,8 @@ export default class PobMonorepoGenerator extends Generator {
|
|
|
152
153
|
this.packageNames = this.packages.map((pkg) => pkg.name);
|
|
153
154
|
this.packageConfigs = this.packageLocations.map((location) => {
|
|
154
155
|
try {
|
|
155
|
-
return JSON.parse(fs.readFileSync(`${location}/.yo-rc.json
|
|
156
|
+
return JSON.parse(fs.readFileSync(`${location}/.yo-rc.json`, "utf8"))
|
|
157
|
+
.pob;
|
|
156
158
|
} catch {
|
|
157
159
|
console.warn(`warn: could not read pob config in ${location}`);
|
|
158
160
|
return {};
|
|
@@ -265,6 +267,11 @@ export default class PobMonorepoGenerator extends Generator {
|
|
|
265
267
|
: detectStorybookReactNative(allPackages),
|
|
266
268
|
},
|
|
267
269
|
]);
|
|
270
|
+
// set by hand in .yo-rc.json, never prompted
|
|
271
|
+
if (storedConfig?.mongodbBinaryCacheKeyFile) {
|
|
272
|
+
this.pobMonorepoConfig.mongodbBinaryCacheKeyFile =
|
|
273
|
+
storedConfig.mongodbBinaryCacheKeyFile;
|
|
274
|
+
}
|
|
268
275
|
this.pobMonorepoConfig.packageNames = this.packageNames;
|
|
269
276
|
this.config.set("monorepo", this.pobMonorepoConfig);
|
|
270
277
|
this.config.delete("pob-config");
|
|
@@ -333,6 +340,12 @@ export default class PobMonorepoGenerator extends Generator {
|
|
|
333
340
|
isApp: this.options.isAppProject,
|
|
334
341
|
onlyLatestLTS: this.options.onlyLatestLTS,
|
|
335
342
|
splitCIJobs,
|
|
343
|
+
playwrightBrowserTestingPath:
|
|
344
|
+
this.packageLocations.find((location, index) =>
|
|
345
|
+
packageUtils.hasVitestBrowserPlaywright(this.packages[index]),
|
|
346
|
+
) ?? "",
|
|
347
|
+
mongodbBinaryCacheKeyFile:
|
|
348
|
+
this.pobMonorepoConfig.mongodbBinaryCacheKeyFile ?? "",
|
|
336
349
|
});
|
|
337
350
|
|
|
338
351
|
const e2eTestingPath = e2eTesting === "." ? "" : `/${e2eTesting}`;
|
|
@@ -419,7 +432,10 @@ export default class PobMonorepoGenerator extends Generator {
|
|
|
419
432
|
this.composeWith("pob:common:release", {
|
|
420
433
|
enable: true,
|
|
421
434
|
packageManager: this.options.packageManager,
|
|
422
|
-
enablePublish:
|
|
435
|
+
enablePublish: isMonorepoPublishable({
|
|
436
|
+
isAppProject: this.options.isAppProject,
|
|
437
|
+
packages: this.packages,
|
|
438
|
+
}),
|
|
423
439
|
build: hasRootBuild,
|
|
424
440
|
isMonorepo: true,
|
|
425
441
|
ci: this.pobMonorepoConfig.ci,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
3
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
buildDependentsMaps,
|
|
4
|
+
discoverWorkspaces,
|
|
5
|
+
readPobConfig,
|
|
6
|
+
} from "./workspaceUtils.js";
|
|
4
7
|
|
|
5
8
|
export const appsWithBrowser = ["alp", "next.js", "vite", "vite-with-server"];
|
|
6
9
|
|
|
@@ -19,14 +22,6 @@ export const getAppTsconfigTarget = ({ appType, dom, withBun }) => {
|
|
|
19
22
|
return undefined;
|
|
20
23
|
};
|
|
21
24
|
|
|
22
|
-
const readPobConfig = (location) => {
|
|
23
|
-
try {
|
|
24
|
-
return JSON.parse(fs.readFileSync(path.join(location, ".yo-rc.json"))).pob;
|
|
25
|
-
} catch {
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
|
|
30
25
|
/**
|
|
31
26
|
* Target of the apps directly depending on the package, for packages compiled
|
|
32
27
|
* by the bundler of these apps (untranspiled libraries). Expo wins as the
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { discoverWorkspaces, readPobConfig } from "./workspaceUtils.js";
|
|
3
|
+
|
|
4
|
+
/** @param {Record<string, any> | undefined} pobConfig */
|
|
5
|
+
export const isExpoApp = (pobConfig) =>
|
|
6
|
+
pobConfig?.project?.type === "app" && pobConfig.app?.type === "expo";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Based on the pob config of the project and of its workspaces, like
|
|
10
|
+
* getAppTsconfigTarget, rather than on the package.json dependencies.
|
|
11
|
+
*
|
|
12
|
+
* @param {{ rootPath: string, pobConfig: Record<string, any> | undefined }} options
|
|
13
|
+
*/
|
|
14
|
+
export const hasExpoApp = async ({ rootPath, pobConfig }) => {
|
|
15
|
+
if (isExpoApp(pobConfig)) return true;
|
|
16
|
+
const workspaces = await discoverWorkspaces(rootPath);
|
|
17
|
+
return workspaces.some(
|
|
18
|
+
(workspace) =>
|
|
19
|
+
!workspace.isRoot &&
|
|
20
|
+
isExpoApp(readPobConfig(path.join(rootPath, workspace.location))),
|
|
21
|
+
);
|
|
22
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { hasExpoApp, isExpoApp } from "./expoApps.js";
|
|
6
|
+
|
|
7
|
+
const app = (type) => ({ project: { type: "app" }, app: { type } });
|
|
8
|
+
|
|
9
|
+
async function writeWorkspace(rootPath, location, pkg, pobConfig) {
|
|
10
|
+
const dir = path.join(rootPath, location);
|
|
11
|
+
await fs.mkdir(dir, { recursive: true });
|
|
12
|
+
await fs.writeFile(path.join(dir, "package.json"), JSON.stringify(pkg));
|
|
13
|
+
if (pobConfig) {
|
|
14
|
+
await fs.writeFile(
|
|
15
|
+
path.join(dir, ".yo-rc.json"),
|
|
16
|
+
JSON.stringify({ pob: pobConfig }),
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Monorepo with a workspace for each [name, pobConfig] pair */
|
|
22
|
+
async function prepareMonorepo(workspaces) {
|
|
23
|
+
const rootPath = await fs.mkdtemp(path.join(os.tmpdir(), "pob-"));
|
|
24
|
+
await writeWorkspace(rootPath, ".", {
|
|
25
|
+
name: "root",
|
|
26
|
+
workspaces: ["packages/*"],
|
|
27
|
+
});
|
|
28
|
+
for (const [name, pobConfig] of workspaces) {
|
|
29
|
+
await writeWorkspace(rootPath, `packages/${name}`, { name }, pobConfig);
|
|
30
|
+
}
|
|
31
|
+
return rootPath;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const rootPobConfig = { project: { type: "lib" } };
|
|
35
|
+
|
|
36
|
+
describe("isExpoApp", () => {
|
|
37
|
+
it("returns true for an expo app", () => {
|
|
38
|
+
expect(isExpoApp(app("expo"))).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("returns false for other apps, libraries and missing configs", () => {
|
|
42
|
+
expect(isExpoApp(app("vite"))).toBe(false);
|
|
43
|
+
expect(isExpoApp({ project: { type: "lib" }, app: { type: "expo" } })).toBe(
|
|
44
|
+
false,
|
|
45
|
+
);
|
|
46
|
+
expect(isExpoApp(undefined)).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("hasExpoApp", () => {
|
|
51
|
+
it("returns true when the project is an expo app", async () => {
|
|
52
|
+
const rootPath = await fs.mkdtemp(path.join(os.tmpdir(), "pob-"));
|
|
53
|
+
await writeWorkspace(rootPath, ".", { name: "mobile" });
|
|
54
|
+
expect(await hasExpoApp({ rootPath, pobConfig: app("expo") })).toBe(true);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("returns false for a project without workspaces", async () => {
|
|
58
|
+
const rootPath = await fs.mkdtemp(path.join(os.tmpdir(), "pob-"));
|
|
59
|
+
await writeWorkspace(rootPath, ".", { name: "web" });
|
|
60
|
+
expect(await hasExpoApp({ rootPath, pobConfig: app("vite") })).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("returns true when a workspace is an expo app", async () => {
|
|
64
|
+
const rootPath = await prepareMonorepo([
|
|
65
|
+
["lib", rootPobConfig],
|
|
66
|
+
["expo-demo-app", app("expo")],
|
|
67
|
+
]);
|
|
68
|
+
expect(await hasExpoApp({ rootPath, pobConfig: rootPobConfig })).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("returns false when no workspace is an expo app", async () => {
|
|
72
|
+
const rootPath = await prepareMonorepo([
|
|
73
|
+
["lib", rootPobConfig],
|
|
74
|
+
["web", app("vite")],
|
|
75
|
+
["no-pob-config", undefined],
|
|
76
|
+
]);
|
|
77
|
+
expect(await hasExpoApp({ rootPath, pobConfig: rootPobConfig })).toBe(
|
|
78
|
+
false,
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
});
|
package/lib/utils/inMonorepo.js
CHANGED
|
@@ -12,11 +12,13 @@ const rootMonorepo =
|
|
|
12
12
|
|
|
13
13
|
const rootMonorepoPkg =
|
|
14
14
|
rootMonorepo &&
|
|
15
|
-
JSON.parse(
|
|
15
|
+
JSON.parse(
|
|
16
|
+
fs.readFileSync(path.resolve(rootMonorepo, "package.json"), "utf8"),
|
|
17
|
+
);
|
|
16
18
|
|
|
17
19
|
const getInMonorepoThings = () => {
|
|
18
20
|
const rootYoConfig = JSON.parse(
|
|
19
|
-
fs.readFileSync(path.resolve(rootMonorepo, ".yo-rc.json")),
|
|
21
|
+
fs.readFileSync(path.resolve(rootMonorepo, ".yo-rc.json"), "utf8"),
|
|
20
22
|
);
|
|
21
23
|
const cwd = process.cwd();
|
|
22
24
|
|
|
@@ -15,7 +15,9 @@ const readPublishedPackageNames = (directory) =>
|
|
|
15
15
|
.filter((entry) => entry.isDirectory())
|
|
16
16
|
.map((entry) => path.join(rootPath, directory, entry.name, "package.json"))
|
|
17
17
|
.filter((packageJsonPath) => fs.existsSync(packageJsonPath))
|
|
18
|
-
.map((packageJsonPath) =>
|
|
18
|
+
.map((packageJsonPath) =>
|
|
19
|
+
JSON.parse(fs.readFileSync(packageJsonPath, "utf8")),
|
|
20
|
+
)
|
|
19
21
|
.filter((pkg) => !pkg.private)
|
|
20
22
|
.map((pkg) => pkg.name);
|
|
21
23
|
|
package/lib/utils/nodeEngines.js
CHANGED
|
@@ -3,12 +3,14 @@ import semver from "semver";
|
|
|
3
3
|
import { latestLTS, maintenanceLTS } from "./nodeVersions.js";
|
|
4
4
|
import * as packageUtils from "./package.js";
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
//
|
|
6
|
+
// Min versions are the October 2026 security releases (CVE-2026-105482,
|
|
7
|
+
// CVE-2026-111497...), above 22.18.0 which has strip typescript stable.
|
|
8
|
+
// 26.13.0 is the first 26 LTS, with the same code as security release 26.12.1.
|
|
9
|
+
// Odd majors are skipped: features backported to 22 are experimental in 23.
|
|
8
10
|
const enginesNodeForMajor = new Map([
|
|
9
|
-
["22", "^22.
|
|
10
|
-
["24", "
|
|
11
|
-
["26", ">=26.
|
|
11
|
+
["22", "^22.23.4 || ^24.22.1 || >=26.13.0"],
|
|
12
|
+
["24", "^24.22.1 || >=26.13.0"],
|
|
13
|
+
["26", ">=26.13.0"],
|
|
12
14
|
]);
|
|
13
15
|
|
|
14
16
|
const minVersionForMajor = (major) => {
|
|
@@ -6,12 +6,12 @@ import {
|
|
|
6
6
|
updateTypesNodeFromEngines,
|
|
7
7
|
} from "./nodeEngines.js";
|
|
8
8
|
|
|
9
|
-
const NODE_22 = "^22.
|
|
9
|
+
const NODE_22 = "^22.23.4 || ^24.22.1 || >=26.13.0";
|
|
10
10
|
|
|
11
11
|
describe("updateTypesNodeFromEngines", () => {
|
|
12
12
|
it("keeps pinned devDependencies pinned when lower than engines min major", () => {
|
|
13
13
|
const pkg = {
|
|
14
|
-
engines: { node: "
|
|
14
|
+
engines: { node: "^24.22.1 || >=26.13.0" },
|
|
15
15
|
devDependencies: { "@types/node": "22.5.0" },
|
|
16
16
|
};
|
|
17
17
|
updateTypesNodeFromEngines(pkg);
|
|
@@ -31,7 +31,7 @@ describe("updateTypesNodeFromEngines", () => {
|
|
|
31
31
|
|
|
32
32
|
it("keeps devDependencies with the same major", () => {
|
|
33
33
|
const pkg = {
|
|
34
|
-
engines: { node: "
|
|
34
|
+
engines: { node: "^24.22.1 || >=26.13.0" },
|
|
35
35
|
devDependencies: { "@types/node": "24.13.3" },
|
|
36
36
|
};
|
|
37
37
|
updateTypesNodeFromEngines(pkg);
|
|
@@ -49,7 +49,7 @@ describe("updateTypesNodeFromEngines", () => {
|
|
|
49
49
|
|
|
50
50
|
it("updates a devDependencies range", () => {
|
|
51
51
|
const pkg = {
|
|
52
|
-
engines: { node: "
|
|
52
|
+
engines: { node: "^24.22.1 || >=26.13.0" },
|
|
53
53
|
devDependencies: { "@types/node": ">=22.0.0" },
|
|
54
54
|
};
|
|
55
55
|
updateTypesNodeFromEngines(pkg);
|
|
@@ -66,7 +66,10 @@ describe("updateTypesNodeFromEngines", () => {
|
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
it("does not add @types/node", () => {
|
|
69
|
-
const pkg = {
|
|
69
|
+
const pkg = {
|
|
70
|
+
engines: { node: "^24.22.1 || >=26.13.0" },
|
|
71
|
+
devDependencies: {},
|
|
72
|
+
};
|
|
70
73
|
updateTypesNodeFromEngines(pkg);
|
|
71
74
|
expect(pkg.devDependencies["@types/node"]).toBeUndefined();
|
|
72
75
|
});
|
|
@@ -88,7 +91,7 @@ describe("updateTypesNodeFromEngines", () => {
|
|
|
88
91
|
"keeps unparseable version %s",
|
|
89
92
|
(version) => {
|
|
90
93
|
const pkg = {
|
|
91
|
-
engines: { node: "
|
|
94
|
+
engines: { node: "^24.22.1 || >=26.13.0" },
|
|
92
95
|
devDependencies: { "@types/node": version },
|
|
93
96
|
};
|
|
94
97
|
updateTypesNodeFromEngines(pkg);
|
|
@@ -101,8 +104,8 @@ describe("resolveEnginesNode", () => {
|
|
|
101
104
|
it.each([
|
|
102
105
|
["18", NODE_22],
|
|
103
106
|
["22", NODE_22],
|
|
104
|
-
["24", "
|
|
105
|
-
["26", ">=26.
|
|
107
|
+
["24", "^24.22.1 || >=26.13.0"],
|
|
108
|
+
["26", ">=26.13.0"],
|
|
106
109
|
])("resolves min major %s from envs", (minMajor, expected) => {
|
|
107
110
|
expect(resolveEnginesNode(">=22.18.0", minMajor, true)).toBe(expected);
|
|
108
111
|
});
|
|
@@ -126,18 +129,22 @@ describe("resolveEnginesNode", () => {
|
|
|
126
129
|
});
|
|
127
130
|
|
|
128
131
|
it("lets envs lower engines.node", () => {
|
|
129
|
-
expect(resolveEnginesNode("
|
|
132
|
+
expect(resolveEnginesNode("^24.22.1 || >=26.13.0", "22", true)).toBe(
|
|
133
|
+
NODE_22,
|
|
134
|
+
);
|
|
130
135
|
});
|
|
131
136
|
|
|
132
137
|
it.each([true, false])(
|
|
133
138
|
"keeps a higher min version of the same major (hasEnvs: %s)",
|
|
134
139
|
(hasEnvs) => {
|
|
135
|
-
expect(resolveEnginesNode(">=24.
|
|
140
|
+
expect(resolveEnginesNode(">=24.23.0", "24", hasEnvs)).toBe(">=24.23.0");
|
|
136
141
|
},
|
|
137
142
|
);
|
|
138
143
|
|
|
139
144
|
it("raises a lower min version of the same major", () => {
|
|
140
|
-
expect(resolveEnginesNode(">=24.0.0", "24", true)).toBe(
|
|
145
|
+
expect(resolveEnginesNode(">=24.0.0", "24", true)).toBe(
|
|
146
|
+
"^24.22.1 || >=26.13.0",
|
|
147
|
+
);
|
|
141
148
|
});
|
|
142
149
|
});
|
|
143
150
|
|
|
@@ -148,7 +155,7 @@ describe("updateNodeEngines", () => {
|
|
|
148
155
|
devDependencies: { "@types/node": "22.5.0" },
|
|
149
156
|
};
|
|
150
157
|
updateNodeEngines(pkg, {});
|
|
151
|
-
expect(pkg.engines.node).toBe("
|
|
158
|
+
expect(pkg.engines.node).toBe("^24.22.1 || >=26.13.0");
|
|
152
159
|
expect(pkg.devDependencies["@types/node"]).toBe(
|
|
153
160
|
pobDependencies["@types/node"],
|
|
154
161
|
);
|
|
@@ -157,7 +164,7 @@ describe("updateNodeEngines", () => {
|
|
|
157
164
|
it("supports babelEnvs", () => {
|
|
158
165
|
const pkg = { pob: { babelEnvs: [{ target: "node", version: "24" }] } };
|
|
159
166
|
updateNodeEngines(pkg, {});
|
|
160
|
-
expect(pkg.engines.node).toBe("
|
|
167
|
+
expect(pkg.engines.node).toBe("^24.22.1 || >=26.13.0");
|
|
161
168
|
});
|
|
162
169
|
|
|
163
170
|
it("uses the maintenance LTS without envs", () => {
|
|
@@ -169,7 +176,7 @@ describe("updateNodeEngines", () => {
|
|
|
169
176
|
it("uses the latest LTS without envs when onlyLatestLTS", () => {
|
|
170
177
|
const pkg = {};
|
|
171
178
|
updateNodeEngines(pkg, { onlyLatestLTS: true });
|
|
172
|
-
expect(pkg.engines.node).toBe("
|
|
179
|
+
expect(pkg.engines.node).toBe("^24.22.1 || >=26.13.0");
|
|
173
180
|
});
|
|
174
181
|
|
|
175
182
|
it("removes @types/node when there is no node target", () => {
|
|
@@ -196,9 +203,9 @@ describe("updateNodeEngines", () => {
|
|
|
196
203
|
it("keeps a higher engines.node when there is no node target", () => {
|
|
197
204
|
const pkg = {
|
|
198
205
|
pob: { envs: [{ target: "browser" }] },
|
|
199
|
-
engines: { node: "
|
|
206
|
+
engines: { node: "^24.22.1 || >=26.13.0" },
|
|
200
207
|
};
|
|
201
208
|
updateNodeEngines(pkg, {});
|
|
202
|
-
expect(pkg.engines.node).toBe("
|
|
209
|
+
expect(pkg.engines.node).toBe("^24.22.1 || >=26.13.0");
|
|
203
210
|
});
|
|
204
211
|
});
|
package/lib/utils/package.js
CHANGED
|
@@ -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
|
};
|
|
@@ -86,6 +95,7 @@ const getVersionFromDependencyName = (dependency) => {
|
|
|
86
95
|
// import.meta
|
|
87
96
|
// .resolve(`${dependency}/package.json`, import.meta.url)
|
|
88
97
|
),
|
|
98
|
+
"utf8",
|
|
89
99
|
),
|
|
90
100
|
);
|
|
91
101
|
return pkgJson.version;
|
|
@@ -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
|
+
});
|
package/lib/utils/packagejson.js
CHANGED
|
@@ -3,6 +3,16 @@ import { glob } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { PackageDependencyDescriptorUtils } from "./packageDependencyDescriptorUtils.js";
|
|
5
5
|
|
|
6
|
+
export const readPobConfig = (location) => {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(
|
|
9
|
+
fs.readFileSync(path.join(location, ".yo-rc.json"), "utf8"),
|
|
10
|
+
).pob;
|
|
11
|
+
} catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
6
16
|
export const getWorkspaceName = (workspace) => {
|
|
7
17
|
if (workspace?.manifest?.raw?.name) return workspace.manifest.raw.name;
|
|
8
18
|
return path.basename(workspace.location) || "unnamed-workspace";
|
|
@@ -15,12 +25,19 @@ export const getWorkspaceName = (workspace) => {
|
|
|
15
25
|
export const getNewPackagePath = (workspaces, packageName) =>
|
|
16
26
|
`${workspaces[0].replace(/\/\*$/, "")}/${packageName.replace(/^@[^/]+\//, "")}`;
|
|
17
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
|
+
|
|
18
35
|
export const discoverWorkspaces = async (rootPath) => {
|
|
19
36
|
const rootPackageJSONPath = path.join(rootPath, "package.json");
|
|
20
37
|
if (!fs.existsSync(rootPackageJSONPath)) {
|
|
21
38
|
return [];
|
|
22
39
|
}
|
|
23
|
-
const rootPkg = JSON.parse(fs.readFileSync(rootPackageJSONPath));
|
|
40
|
+
const rootPkg = JSON.parse(fs.readFileSync(rootPackageJSONPath, "utf8"));
|
|
24
41
|
|
|
25
42
|
let workspaceGlobs = [];
|
|
26
43
|
if (Array.isArray(rootPkg.workspaces)) {
|
|
@@ -48,7 +65,7 @@ export const discoverWorkspaces = async (rootPath) => {
|
|
|
48
65
|
if (found.has(match)) continue;
|
|
49
66
|
found.add(match);
|
|
50
67
|
const filePath = path.join(rootPath, match);
|
|
51
|
-
const content = JSON.parse(fs.readFileSync(filePath));
|
|
68
|
+
const content = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
52
69
|
const dir = path.dirname(match);
|
|
53
70
|
workspaces.push({
|
|
54
71
|
name: content.name,
|
|
@@ -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.
|
|
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,12 +31,12 @@
|
|
|
31
31
|
"./package.json": "./package.json"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@pob/codemods": "0.
|
|
35
|
-
"@pob/eslint-config": "68.
|
|
36
|
-
"@pob/eslint-config-typescript-react": "68.
|
|
37
|
-
"@pob/sort-object": "11.
|
|
38
|
-
"@pob/sort-pkg": "13.
|
|
39
|
-
"@yeoman/adapter": "
|
|
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
|
+
"@yeoman/adapter": "5.1.1",
|
|
40
40
|
"eslint": "10.14.0",
|
|
41
41
|
"findup-sync": "^5.0.0",
|
|
42
42
|
"git-remote-url": "^1.0.1",
|
|
@@ -45,27 +45,27 @@
|
|
|
45
45
|
"json5": "^2.2.3",
|
|
46
46
|
"lodash.camelcase": "^4.3.0",
|
|
47
47
|
"lodash.kebabcase": "^4.1.1",
|
|
48
|
-
"mem-fs": "
|
|
48
|
+
"mem-fs": "6.0.0",
|
|
49
49
|
"mem-fs-editor": "12.1.0",
|
|
50
50
|
"minimist": "1.2.8",
|
|
51
|
-
"oxfmt": "0.
|
|
51
|
+
"oxfmt": "0.78.0",
|
|
52
52
|
"parse-author": "2.0.0",
|
|
53
|
-
"pob-dependencies": "28.
|
|
54
|
-
"semver": "7.8.
|
|
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
|
-
"yeoman-environment": "
|
|
57
|
+
"yeoman-environment": "8.5.1",
|
|
58
58
|
"yeoman-generator": "10.3.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@pob/root": "28.
|
|
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.
|
|
65
|
+
"tsrs": "0.24.0-ts7.1.0-beta"
|
|
66
66
|
},
|
|
67
67
|
"engines": {
|
|
68
|
-
"node": "
|
|
68
|
+
"node": "^24.22.1 || >=26.13.0"
|
|
69
69
|
},
|
|
70
70
|
"pob": {
|
|
71
71
|
"typescript": "check-only"
|