pob 38.11.0 → 38.13.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 +25 -5
- package/lib/generators/app/vite/AppViteGenerator.js +50 -3
- package/lib/generators/app/vite/templates/index.html.ejs +11 -0
- package/lib/generators/app/vite/templates/index.ts.ejs +1 -0
- package/lib/generators/app/vite/templates/vite.config.js.ejs +8 -0
- package/lib/generators/common/babel/templates/app.rollup.config.mjs.ejs +1 -0
- package/lib/generators/common/babel/templates/lib.rollup.config.mjs.ejs +1 -0
- package/lib/generators/common/e2e/CommonE2eGenerator.js +6 -1
- package/lib/generators/common/format-lint/CommonLintGenerator.js +141 -85
- package/lib/generators/common/format-lint/templates/oxfmtrc.jsonc.ejs +1 -0
- package/lib/generators/common/husky/templates/lint-staged.config.js.txt +1 -0
- package/lib/generators/common/release/templates/workflow-release.yml.ejs +21 -10
- package/lib/generators/common/testing/templates/vite.config.js.ejs +1 -0
- package/lib/generators/common/transpiler/templates/app.rollup.config.mjs.ejs +1 -0
- package/lib/generators/common/transpiler/templates/lib.rollup.config.mjs.ejs +1 -0
- package/lib/generators/common/typescript/CommonTypescriptGenerator.js +13 -1
- package/lib/generators/common/typescript/templates/tsconfig.check-js.json.ejs +1 -0
- package/lib/generators/common/typescript/templates/tsconfig.json.ejs +1 -0
- package/lib/generators/common/typescript/templates/tsconfig.tools.json.ejs +1 -0
- package/lib/generators/core/bun/templates/bunfig.toml.ejs +1 -0
- package/lib/generators/core/ci/templates/github-action-documentation-workflow.yml.ejs +2 -1
- package/lib/generators/core/ci/templates/github-action-push-workflow-split.yml.ejs +6 -5
- package/lib/generators/core/ci/templates/github-action-push-workflow.yml.ejs +2 -1
- package/lib/generators/core/claude/CoreClaudeGenerator.js +1 -1
- package/lib/generators/core/editorconfig/templates/editorconfig +1 -0
- package/lib/generators/core/git/CoreGitGenerator.js +72 -57
- package/lib/generators/core/git/generators/github/CoreGitGithubGenerator.js +192 -145
- package/lib/generators/core/gitignore/templates/gitignore.ejs +1 -0
- package/lib/generators/core/npm/templates/npmignore.ejs +1 -0
- package/lib/generators/core/package/CorePackageGenerator.js +21 -8
- package/lib/generators/core/pnpm/CorePnpmGenerator.js +30 -9
- package/lib/generators/core/vscode/templates/extensions.json.ejs +1 -0
- package/lib/generators/core/vscode/templates/settings.json.ejs +1 -0
- package/lib/generators/core/yarn/templates/yarn_gitignore.ejs +1 -0
- package/lib/generators/lib/PobLibGenerator.js +1 -0
- package/lib/generators/lib/doc/LibDocGenerator.js +3 -1
- package/lib/generators/lib/doc/templates/tsconfig.doc.json.ejs +1 -0
- package/lib/generators/lib/doc/templates/tsconfig.doc.json.lerna.ejs +1 -0
- package/lib/generators/monorepo/PobMonorepoGenerator.js +26 -5
- package/lib/generators/monorepo/templates/monorepo.rollup.config.mjs.ejs +1 -0
- package/lib/generators/monorepo/typescript/MonorepoTypescriptGenerator.js +21 -10
- package/lib/generators/monorepo/typescript/templates/tsconfig.json.ejs +1 -0
- package/lib/generators/monorepo/typescript/templates/tsconfig.tools.json.ejs +1 -0
- package/lib/generators/monorepo/workspaces/MonorepoWorkspacesGenerator.js +12 -4
- package/lib/generators/pob/PobBaseGenerator.js +1 -1
- package/lib/pob.js +49 -12
- package/lib/utils/git.js +34 -0
- package/lib/utils/git.test.js +71 -0
- package/lib/utils/workspaceUtils.js +10 -0
- package/lib/utils/workspaceUtils.test.js +15 -0
- package/package.json +13 -13
- package/lib/generators/common/format-lint/templates/eslint.config.js.ejs +0 -5
|
@@ -121,6 +121,18 @@ export default class CorePackageGenerator extends Generator {
|
|
|
121
121
|
pkg.type = this.options.packageType;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
// packages of a monorepo share the author, the license and the type of the
|
|
125
|
+
// root package.json
|
|
126
|
+
const rootPkg =
|
|
127
|
+
inMonorepo && !inMonorepo.root ? inMonorepo.rootMonorepoPkg : undefined;
|
|
128
|
+
if (rootPkg) {
|
|
129
|
+
if (!pkg.author && !pkg.authors && rootPkg.author) {
|
|
130
|
+
pkg.author = rootPkg.author;
|
|
131
|
+
}
|
|
132
|
+
if (!pkg.type && rootPkg.type) pkg.type = rootPkg.type;
|
|
133
|
+
}
|
|
134
|
+
const defaultLicense = rootPkg?.license;
|
|
135
|
+
|
|
124
136
|
let author = packageUtils.parsePkgAuthor(pkg);
|
|
125
137
|
|
|
126
138
|
const props = await this.prompt(
|
|
@@ -160,7 +172,7 @@ export default class CorePackageGenerator extends Generator {
|
|
|
160
172
|
message: "License Type",
|
|
161
173
|
type: "list",
|
|
162
174
|
choices: ["MIT", "ISC", "UNLICENSED"],
|
|
163
|
-
when: !pkg.license,
|
|
175
|
+
when: !pkg.license && !defaultLicense,
|
|
164
176
|
},
|
|
165
177
|
].filter(Boolean),
|
|
166
178
|
);
|
|
@@ -257,15 +269,16 @@ export default class CorePackageGenerator extends Generator {
|
|
|
257
269
|
}
|
|
258
270
|
|
|
259
271
|
if (!pkg.license) {
|
|
260
|
-
pkg.license = props.license;
|
|
261
|
-
this.
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
272
|
+
pkg.license = props.license || defaultLicense;
|
|
273
|
+
const licenseTemplatePath = this.templatePath(
|
|
274
|
+
`licenses/${pkg.license}.ejs`,
|
|
275
|
+
);
|
|
276
|
+
if (fs.existsSync(licenseTemplatePath)) {
|
|
277
|
+
this.fs.copyTpl(licenseTemplatePath, this.destinationPath("LICENSE"), {
|
|
265
278
|
year: new Date().getFullYear(),
|
|
266
279
|
author: pkg.author,
|
|
267
|
-
}
|
|
268
|
-
|
|
280
|
+
});
|
|
281
|
+
}
|
|
269
282
|
}
|
|
270
283
|
|
|
271
284
|
if (pkg.private) {
|
|
@@ -14,6 +14,8 @@ const minimumReleaseAgeExcludePackages = [
|
|
|
14
14
|
"nightingale-logger",
|
|
15
15
|
];
|
|
16
16
|
|
|
17
|
+
const trustedBuilds = ["esbuild"];
|
|
18
|
+
|
|
17
19
|
export default class CorePnpmGenerator extends Generator {
|
|
18
20
|
constructor(args, opts) {
|
|
19
21
|
super(args, opts);
|
|
@@ -39,7 +41,7 @@ export default class CorePnpmGenerator extends Generator {
|
|
|
39
41
|
if (
|
|
40
42
|
pkg.packageManager &&
|
|
41
43
|
(!pkg.packageManager.startsWith("pnpm@") ||
|
|
42
|
-
lt(pkg.packageManager.slice("pnpm@".length), "
|
|
44
|
+
lt(pkg.packageManager.slice("pnpm@".length), "12.0.0"))
|
|
43
45
|
) {
|
|
44
46
|
delete pkg.packageManager;
|
|
45
47
|
}
|
|
@@ -53,14 +55,25 @@ export default class CorePnpmGenerator extends Generator {
|
|
|
53
55
|
});
|
|
54
56
|
const config = loadedConfig ?? {};
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
// the file is read with FAILSAFE_SCHEMA: booleans are read as strings.
|
|
59
|
+
// Other values are placeholders written by pnpm for ignored builds.
|
|
60
|
+
const allowBuilds = Object.fromEntries(
|
|
61
|
+
Object.entries(config.allowBuilds ?? {}).map(([key, value]) => {
|
|
62
|
+
if (value === "true") return [key, true];
|
|
63
|
+
if (value === "false") return [key, false];
|
|
64
|
+
return [key, value];
|
|
65
|
+
}),
|
|
66
|
+
);
|
|
67
|
+
// build scripts of the dependencies pob adds itself. Set even before a
|
|
68
|
+
// package needs them: the root is generated before its packages, and an
|
|
69
|
+
// ignored build makes pnpm fail.
|
|
70
|
+
for (const name of trustedBuilds) {
|
|
71
|
+
if (typeof allowBuilds[name] !== "boolean") allowBuilds[name] = true;
|
|
63
72
|
}
|
|
73
|
+
config.allowBuilds = allowBuilds;
|
|
74
|
+
this.pendingAllowBuilds = Object.keys(allowBuilds).filter(
|
|
75
|
+
(name) => typeof allowBuilds[name] !== "boolean",
|
|
76
|
+
);
|
|
64
77
|
|
|
65
78
|
if (pkg.workspaces) {
|
|
66
79
|
config.packages = pkg.workspaces;
|
|
@@ -91,7 +104,15 @@ export default class CorePnpmGenerator extends Generator {
|
|
|
91
104
|
|
|
92
105
|
end() {
|
|
93
106
|
if (this.options.enable) {
|
|
94
|
-
this.
|
|
107
|
+
if (this.pendingAllowBuilds.length > 0) {
|
|
108
|
+
console.warn(
|
|
109
|
+
`pnpm-workspace.yaml: set allowBuilds to true or false for ${this.pendingAllowBuilds.join(", ")}`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
// pob just modified package.json: the lockfile must be allowed to update,
|
|
113
|
+
// in particular in the automatic update GitHub Actions workflow (CI
|
|
114
|
+
// defaults to frozen lockfile)
|
|
115
|
+
this.spawnSync("pnpm", ["install", "--no-frozen-lockfile"], {});
|
|
95
116
|
this.spawnSync("pnpm", ["dedupe"], {});
|
|
96
117
|
|
|
97
118
|
this.fs.delete("package-lock.json");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Generator from "yeoman-generator";
|
|
2
2
|
import inMonorepo from "../../../utils/inMonorepo.js";
|
|
3
|
+
import { readJSON5 } from "../../../utils/json5.js";
|
|
3
4
|
import removeLegacyGeneratedDocs from "../../../utils/legacyGeneratedDocs.js";
|
|
4
5
|
import * as packageUtils from "../../../utils/package.js";
|
|
5
6
|
import { copyAndFormatTpl } from "../../../utils/writeAndFormat.js";
|
|
@@ -54,7 +55,8 @@ export default class LibDocGenerator extends Generator {
|
|
|
54
55
|
: packageUtils.hasReact(pkg);
|
|
55
56
|
|
|
56
57
|
if (inMonorepo && inMonorepo.root) {
|
|
57
|
-
const existingConfig =
|
|
58
|
+
const existingConfig = readJSON5(
|
|
59
|
+
this.fs,
|
|
58
60
|
this.destinationPath("tsconfig.doc.json"),
|
|
59
61
|
{
|
|
60
62
|
typedocOptions: {},
|
|
@@ -255,6 +255,25 @@ export default class PobMonorepoGenerator extends Generator {
|
|
|
255
255
|
|
|
256
256
|
this.composeWith("pob:common:husky", {});
|
|
257
257
|
|
|
258
|
+
// the path of the package running the e2e tests. The prompt only asks
|
|
259
|
+
// whether there are e2e tests.
|
|
260
|
+
const e2eTesting = (() => {
|
|
261
|
+
const value = this.pobMonorepoConfig.e2eTesting;
|
|
262
|
+
if (!value || value === "false") return "";
|
|
263
|
+
if (value !== true && value !== "true") return value;
|
|
264
|
+
const e2eLocations = this.packageLocations.filter(
|
|
265
|
+
(location, index) =>
|
|
266
|
+
this.packageConfigs[index]?.app?.e2e ||
|
|
267
|
+
this.packageConfigs[index]?.lib?.e2e,
|
|
268
|
+
);
|
|
269
|
+
if (e2eLocations.length > 1) {
|
|
270
|
+
console.warn(
|
|
271
|
+
`monorepo.e2eTesting: several packages have e2e tests (${e2eLocations.join(", ")}), set the path of the one to run in ci in .yo-rc.json`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
return e2eLocations.length === 1 ? e2eLocations[0] : ".";
|
|
275
|
+
})();
|
|
276
|
+
|
|
258
277
|
const splitCIJobs = this.packageNames.length > 8;
|
|
259
278
|
|
|
260
279
|
// the root has a build script when there are typescript definitions or rollup
|
|
@@ -271,7 +290,7 @@ export default class PobMonorepoGenerator extends Generator {
|
|
|
271
290
|
runner: this.pobMonorepoConfig.testRunner,
|
|
272
291
|
disableYarnGitCache: this.options.disableYarnGitCache,
|
|
273
292
|
testing: this.pobMonorepoConfig.testing,
|
|
274
|
-
e2eTesting
|
|
293
|
+
e2eTesting,
|
|
275
294
|
build: hasRootBuild,
|
|
276
295
|
typescript: this.pobMonorepoConfig.typescript,
|
|
277
296
|
documentation: !!this.pobMonorepoConfig.documentation,
|
|
@@ -284,11 +303,10 @@ export default class PobMonorepoGenerator extends Generator {
|
|
|
284
303
|
splitCIJobs,
|
|
285
304
|
});
|
|
286
305
|
|
|
306
|
+
const e2eTestingPath = e2eTesting === "." ? "" : `/${e2eTesting}`;
|
|
287
307
|
const rootIgnorePaths = [
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
this.pobMonorepoConfig.e2eTesting &&
|
|
291
|
-
`${this.pobMonorepoConfig.e2eTesting === "." || this.pobMonorepoConfig.e2eTesting === true ? "" : `/${this.pobMonorepoConfig.e2eTesting}`}/test-results/`,
|
|
308
|
+
e2eTesting && `${e2eTestingPath}/playwright-report/`,
|
|
309
|
+
e2eTesting && `${e2eTestingPath}/test-results/`,
|
|
292
310
|
].filter(Boolean);
|
|
293
311
|
|
|
294
312
|
if (hasTamagui(this.packages, this.packageConfigs)) {
|
|
@@ -310,6 +328,9 @@ export default class PobMonorepoGenerator extends Generator {
|
|
|
310
328
|
packageManager: this.options.packageManager,
|
|
311
329
|
yarnNodeLinker: this.options.yarnNodeLinker,
|
|
312
330
|
appTypes: JSON.stringify(getAppTypes(this.packageConfigs)),
|
|
331
|
+
workspacesHaveReact: this.packages.some((workspacePkg) =>
|
|
332
|
+
packageUtils.hasReact(workspacePkg),
|
|
333
|
+
),
|
|
313
334
|
ignorePaths: [
|
|
314
335
|
...gitignorePaths.map((path) => `/${path}`),
|
|
315
336
|
hasDist(this.packages, this.packageConfigs) && "/dist",
|
|
@@ -67,6 +67,24 @@ export default class MonorepoTypescriptGenerator extends Generator {
|
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// non-published typescript run in place is not part of any package's
|
|
71
|
+
// emitting tsconfig, so it needs a dedicated noEmit project for type-aware
|
|
72
|
+
// linting and tsc -b: root config files (e.g. vitest.config.ts), a root
|
|
73
|
+
// scripts/ dir, and each package's scripts/ dir (matched via the workspace
|
|
74
|
+
// globs so all packages are covered)
|
|
75
|
+
hasTools() {
|
|
76
|
+
return (
|
|
77
|
+
hasRootConfigFiles(this.destinationPath()) ||
|
|
78
|
+
existsSync(this.destinationPath("scripts"))
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// tsc fails on a tsconfig.json without files nor references, like the one of
|
|
83
|
+
// a new monorepo without packages
|
|
84
|
+
hasTypescriptProjects() {
|
|
85
|
+
return JSON.parse(this.options.packagePaths).length > 0 || this.hasTools();
|
|
86
|
+
}
|
|
87
|
+
|
|
70
88
|
writing() {
|
|
71
89
|
if (this.fs.exists("flow-typed")) this.fs.delete("flow-typed");
|
|
72
90
|
if (this.fs.exists(this.destinationPath(".flowconfig"))) {
|
|
@@ -91,7 +109,7 @@ export default class MonorepoTypescriptGenerator extends Generator {
|
|
|
91
109
|
);
|
|
92
110
|
|
|
93
111
|
if (this.options.enable) {
|
|
94
|
-
packageUtils.
|
|
112
|
+
packageUtils.addOrRemoveScripts(pkg, this.hasTypescriptProjects(), {
|
|
95
113
|
tsc: "tsc -b",
|
|
96
114
|
});
|
|
97
115
|
packageUtils.addOrRemoveScripts(
|
|
@@ -143,7 +161,7 @@ export default class MonorepoTypescriptGenerator extends Generator {
|
|
|
143
161
|
this.fs.delete(this.destinationPath("tsconfig.root-configs.json"));
|
|
144
162
|
this.fs.delete(tsconfigTestPath);
|
|
145
163
|
|
|
146
|
-
if (!this.options.enable) {
|
|
164
|
+
if (!this.options.enable || !this.hasTypescriptProjects()) {
|
|
147
165
|
this.fs.delete(tsconfigPath);
|
|
148
166
|
this.fs.delete(tsconfigCheckPath);
|
|
149
167
|
this.fs.delete(tsconfigBuildPath);
|
|
@@ -152,15 +170,8 @@ export default class MonorepoTypescriptGenerator extends Generator {
|
|
|
152
170
|
const packagePaths = JSON.parse(this.options.packagePaths);
|
|
153
171
|
const pkg = this.fs.readJSON(this.destinationPath("package.json"));
|
|
154
172
|
|
|
155
|
-
// non-published typescript run in place is not part of any package's
|
|
156
|
-
// emitting tsconfig, so it needs a dedicated noEmit project for type-aware
|
|
157
|
-
// linting and tsc -b: root config files (e.g. vitest.config.ts), a root
|
|
158
|
-
// scripts/ dir, and each package's scripts/ dir (matched via the
|
|
159
|
-
// workspace globs so all packages are covered)
|
|
160
|
-
const hasRootConfigs = hasRootConfigFiles(this.destinationPath());
|
|
161
|
-
const hasRootScripts = existsSync(this.destinationPath("scripts"));
|
|
162
173
|
const workspaceGlobs = pkg.workspaces || [];
|
|
163
|
-
const hasTools =
|
|
174
|
+
const hasTools = this.hasTools();
|
|
164
175
|
if (hasTools) {
|
|
165
176
|
await copyAndFormatTpl(
|
|
166
177
|
this.fs,
|
|
@@ -86,7 +86,7 @@ export default class MonorepoWorkspacesGenerator extends Generator {
|
|
|
86
86
|
pkg.engines.npm = ">= 6.4.0";
|
|
87
87
|
} else if (this.options.packageManager === "pnpm") {
|
|
88
88
|
if (!pkg.engines) pkg.engines = {};
|
|
89
|
-
pkg.engines.pnpm = ">=
|
|
89
|
+
pkg.engines.pnpm = ">= 12.0.0";
|
|
90
90
|
delete pkg.engines.yarn;
|
|
91
91
|
} else if (pkg.engines) {
|
|
92
92
|
delete pkg.engines.yarn;
|
|
@@ -123,8 +123,16 @@ export default class MonorepoWorkspacesGenerator extends Generator {
|
|
|
123
123
|
if (this.options.isAppProject) {
|
|
124
124
|
packageUtils.addOrRemoveScripts(pkg, withBundler, {
|
|
125
125
|
build: workspacesRunTopological(packageManager, "build"),
|
|
126
|
-
watch: workspacesRunExcluding(packageManager, "watch", "*-example"),
|
|
127
126
|
});
|
|
127
|
+
// rollup apps have a start script, not a watch one
|
|
128
|
+
packageUtils.addOrRemoveScripts(
|
|
129
|
+
pkg,
|
|
130
|
+
withBundler &&
|
|
131
|
+
this.packages.some((packagePkg) => packagePkg.scripts?.watch),
|
|
132
|
+
{
|
|
133
|
+
watch: workspacesRunExcluding(packageManager, "watch", "*-example"),
|
|
134
|
+
},
|
|
135
|
+
);
|
|
128
136
|
}
|
|
129
137
|
|
|
130
138
|
// packageUtils.addOrRemoveScripts(pkg, withTypescript, {
|
|
@@ -212,8 +220,8 @@ export default class MonorepoWorkspacesGenerator extends Generator {
|
|
|
212
220
|
|
|
213
221
|
switch (this.options.packageManager) {
|
|
214
222
|
case "npm":
|
|
215
|
-
this.
|
|
216
|
-
this.
|
|
223
|
+
this.spawnSync("npm", ["install"]);
|
|
224
|
+
this.spawnSync("npm", ["run", "preversion"]);
|
|
217
225
|
break;
|
|
218
226
|
case "pnpm":
|
|
219
227
|
// see CorePnpmGenerator
|
package/lib/pob.js
CHANGED
|
@@ -47,11 +47,14 @@ import MonorepoTypescriptGenerator from "./generators/monorepo/typescript/Monore
|
|
|
47
47
|
import MonorepoWorkspacesGenerator from "./generators/monorepo/workspaces/MonorepoWorkspacesGenerator.js";
|
|
48
48
|
import PobBaseGenerator from "./generators/pob/PobBaseGenerator.js";
|
|
49
49
|
import { __dirname } from "./pob-dirname.cjs";
|
|
50
|
+
import { getNewPackagePath } from "./utils/workspaceUtils.js";
|
|
50
51
|
|
|
51
52
|
const printUsage = () => {
|
|
52
53
|
console.error("Usage: pob [monorepo] [lib|app|init]");
|
|
53
54
|
console.error(" pob [monorepo] update [--force]");
|
|
54
|
-
console.error(
|
|
55
|
+
console.error(
|
|
56
|
+
" pob add <packageName> [--app <appType> [--testing] [--e2e]]",
|
|
57
|
+
);
|
|
55
58
|
};
|
|
56
59
|
|
|
57
60
|
const readJson = (filepath) => {
|
|
@@ -279,21 +282,55 @@ if (action === "add") {
|
|
|
279
282
|
printUsage();
|
|
280
283
|
process.exit(1);
|
|
281
284
|
}
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
+
const packagePath = getNewPackagePath(projectPkg.workspaces, packageName);
|
|
286
|
+
|
|
287
|
+
if (existsSync(packagePath)) {
|
|
288
|
+
console.error(`${packagePath} already exists`);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// with --app, the package is generated without any prompt
|
|
293
|
+
const appType = argv.app;
|
|
294
|
+
const pobConfig = appType
|
|
295
|
+
? {
|
|
296
|
+
pob: {
|
|
297
|
+
project: { type: "app" },
|
|
298
|
+
app: {
|
|
299
|
+
type: appType,
|
|
300
|
+
testing: !!argv.testing,
|
|
301
|
+
e2e: !!argv.e2e,
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
}
|
|
305
|
+
: {};
|
|
285
306
|
|
|
286
|
-
fs.mkdirSync(
|
|
287
|
-
writeFileSync(`${packagesPath}/${packageName}/.yo-rc.json`, "{}");
|
|
307
|
+
fs.mkdirSync(packagePath, { recursive: true });
|
|
288
308
|
writeFileSync(
|
|
289
|
-
`${
|
|
290
|
-
JSON.stringify(
|
|
309
|
+
`${packagePath}/.yo-rc.json`,
|
|
310
|
+
JSON.stringify(pobConfig, null, 2),
|
|
311
|
+
);
|
|
312
|
+
writeFileSync(
|
|
313
|
+
`${packagePath}/package.json`,
|
|
314
|
+
JSON.stringify(
|
|
315
|
+
{
|
|
316
|
+
name: packageName,
|
|
317
|
+
version: "1.0.0-pre",
|
|
318
|
+
...(appType && { private: true }),
|
|
319
|
+
},
|
|
320
|
+
null,
|
|
321
|
+
2,
|
|
322
|
+
),
|
|
291
323
|
);
|
|
292
324
|
console.log("> Creating new Package");
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
325
|
+
// --force: the package is new, its package.json is expected to change
|
|
326
|
+
spawnSync(
|
|
327
|
+
process.argv[0],
|
|
328
|
+
[process.argv[1], ...(appType ? ["update"] : []), "--force"],
|
|
329
|
+
{
|
|
330
|
+
cwd: packagePath,
|
|
331
|
+
stdio: "inherit",
|
|
332
|
+
},
|
|
333
|
+
);
|
|
297
334
|
|
|
298
335
|
console.log("> Updating monorepo");
|
|
299
336
|
spawnSync(process.argv[0], [process.argv[1], "update"], {
|
package/lib/utils/git.js
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Repository name for a package name: without its scope, nor the "-monorepo"
|
|
5
|
+
* suffix of monorepo roots.
|
|
6
|
+
*/
|
|
7
|
+
export const getRepoName = (pkgName) =>
|
|
8
|
+
pkgName.replace(/^@[^/]+\//, "").replace(/-monorepo$/, "");
|
|
9
|
+
|
|
10
|
+
// characters allowed by GitHub in account and repository names
|
|
11
|
+
const validGitNameRegex = /^[\w.-]+$/;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parses a git remote url, or the "repository" field of a package.json.
|
|
15
|
+
* Returns undefined when the url cannot come from a real remote.
|
|
16
|
+
*/
|
|
17
|
+
export const parseRepositoryUrl = (repository) => {
|
|
18
|
+
const url = typeof repository === "string" ? repository : repository?.url;
|
|
19
|
+
if (!url || typeof url !== "string") return undefined;
|
|
20
|
+
|
|
21
|
+
const match = url.match(
|
|
22
|
+
/^(?:git\+)?(?:git@|ssh:\/\/git@|https?:\/\/)([^./:]+)(?:\.[a-z]+)?[/:]([^/]+)\/([^/]+?)(?:\.git)?\/?$/,
|
|
23
|
+
);
|
|
24
|
+
if (!match) return undefined;
|
|
25
|
+
|
|
26
|
+
const [, gitHost, gitAccount, repoName] = match;
|
|
27
|
+
if (
|
|
28
|
+
!validGitNameRegex.test(gitAccount) ||
|
|
29
|
+
!validGitNameRegex.test(repoName)
|
|
30
|
+
) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { gitHost, gitAccount, repoName };
|
|
35
|
+
};
|
|
36
|
+
|
|
3
37
|
export const isTrackedInGit = (cwd, path) => {
|
|
4
38
|
try {
|
|
5
39
|
return (
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { getRepoName, parseRepositoryUrl } from "./git.js";
|
|
3
|
+
|
|
4
|
+
describe("getRepoName", () => {
|
|
5
|
+
it("removes the scope and the monorepo suffix", () => {
|
|
6
|
+
expect(getRepoName("@chapplications/stories-monorepo")).toBe("stories");
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("removes the monorepo suffix", () => {
|
|
10
|
+
expect(getRepoName("pob-monorepo")).toBe("pob");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("keeps a plain name", () => {
|
|
14
|
+
expect(getRepoName("foo")).toBe("foo");
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("parseRepositoryUrl", () => {
|
|
19
|
+
it("parses an https url", () => {
|
|
20
|
+
expect(
|
|
21
|
+
parseRepositoryUrl("https://github.com/christophehurpeau/pob.git"),
|
|
22
|
+
).toEqual({
|
|
23
|
+
gitHost: "github",
|
|
24
|
+
gitAccount: "christophehurpeau",
|
|
25
|
+
repoName: "pob",
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("parses an ssh url", () => {
|
|
30
|
+
expect(
|
|
31
|
+
parseRepositoryUrl("git@github.com:christophehurpeau/pob.git"),
|
|
32
|
+
).toEqual({
|
|
33
|
+
gitHost: "github",
|
|
34
|
+
gitAccount: "christophehurpeau",
|
|
35
|
+
repoName: "pob",
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("parses a repository object and a name with a dot", () => {
|
|
40
|
+
expect(
|
|
41
|
+
parseRepositoryUrl({
|
|
42
|
+
type: "git",
|
|
43
|
+
url: "git+https://github.com/christophehurpeau/foo.js",
|
|
44
|
+
}),
|
|
45
|
+
).toEqual({
|
|
46
|
+
gitHost: "github",
|
|
47
|
+
gitAccount: "christophehurpeau",
|
|
48
|
+
repoName: "foo.js",
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("rejects a url built from a scoped package name", () => {
|
|
53
|
+
expect(
|
|
54
|
+
parseRepositoryUrl(
|
|
55
|
+
"https://github.com/christophehurpeau/@chapplications/stories-monorepo.git",
|
|
56
|
+
),
|
|
57
|
+
).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("rejects a url with an invalid repository name", () => {
|
|
61
|
+
expect(
|
|
62
|
+
parseRepositoryUrl(
|
|
63
|
+
"https://github.com/christophehurpeau/@chapplications",
|
|
64
|
+
),
|
|
65
|
+
).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("returns undefined without url", () => {
|
|
69
|
+
expect(parseRepositoryUrl(undefined)).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -8,8 +8,18 @@ export const getWorkspaceName = (workspace) => {
|
|
|
8
8
|
return path.basename(workspace.location) || "unnamed-workspace";
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Path of a package created by "pob add": in the first workspaces directory,
|
|
13
|
+
* named without the scope.
|
|
14
|
+
*/
|
|
15
|
+
export const getNewPackagePath = (workspaces, packageName) =>
|
|
16
|
+
`${workspaces[0].replace(/\/\*$/, "")}/${packageName.replace(/^@[^/]+\//, "")}`;
|
|
17
|
+
|
|
11
18
|
export const discoverWorkspaces = async (rootPath) => {
|
|
12
19
|
const rootPackageJSONPath = path.join(rootPath, "package.json");
|
|
20
|
+
if (!fs.existsSync(rootPackageJSONPath)) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
13
23
|
const rootPkg = JSON.parse(fs.readFileSync(rootPackageJSONPath));
|
|
14
24
|
|
|
15
25
|
let workspaceGlobs = [];
|
|
@@ -6,8 +6,23 @@ import {
|
|
|
6
6
|
buildDependenciesMaps,
|
|
7
7
|
buildTopologicalOrderBatches,
|
|
8
8
|
discoverWorkspaces,
|
|
9
|
+
getNewPackagePath,
|
|
9
10
|
} from "./workspaceUtils.js";
|
|
10
11
|
|
|
12
|
+
describe("getNewPackagePath", () => {
|
|
13
|
+
it("creates a scoped package in the workspaces directory", () => {
|
|
14
|
+
expect(getNewPackagePath(["packages/*"], "@scope/foo")).toBe(
|
|
15
|
+
"packages/foo",
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("creates an unscoped package in the workspaces directory", () => {
|
|
20
|
+
expect(getNewPackagePath(["packages/*", "@pob/*"], "foo")).toBe(
|
|
21
|
+
"packages/foo",
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
11
26
|
async function prepareMonorepo(tmpDir) {
|
|
12
27
|
await fs.mkdir(path.join(tmpDir, "packages"), { recursive: true });
|
|
13
28
|
const rootPkg = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pob",
|
|
3
|
-
"version": "38.
|
|
3
|
+
"version": "38.13.0",
|
|
4
4
|
"description": "Pile of bones, library generator with git/babel/typescript/typedoc/readme/vitest",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"skeleton"
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
"url": "https://github.com/christophehurpeau/pob.git",
|
|
17
17
|
"directory": "packages/pob"
|
|
18
18
|
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"pob": "./lib/pob.js"
|
|
21
|
+
},
|
|
19
22
|
"files": [
|
|
20
23
|
"lib",
|
|
21
24
|
"bin"
|
|
@@ -28,6 +31,10 @@
|
|
|
28
31
|
"./package.json": "./package.json"
|
|
29
32
|
},
|
|
30
33
|
"dependencies": {
|
|
34
|
+
"@pob/eslint-config": "67.11.0",
|
|
35
|
+
"@pob/eslint-config-typescript-react": "67.11.0",
|
|
36
|
+
"@pob/sort-object": "11.2.0",
|
|
37
|
+
"@pob/sort-pkg": "13.2.0",
|
|
31
38
|
"@types/inquirer": "9.0.10",
|
|
32
39
|
"@yeoman/adapter": "4.0.2",
|
|
33
40
|
"eslint": "10.8.1",
|
|
@@ -43,22 +50,18 @@
|
|
|
43
50
|
"minimist": "1.2.8",
|
|
44
51
|
"oxfmt": "0.54.0",
|
|
45
52
|
"parse-author": "2.0.0",
|
|
53
|
+
"pob-dependencies": "27.11.0",
|
|
46
54
|
"semver": "7.8.5",
|
|
47
55
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
48
56
|
"validate-npm-package-name": "^8.0.0",
|
|
49
57
|
"yeoman-environment": "6.1.0",
|
|
50
|
-
"yeoman-generator": "8.2.2"
|
|
51
|
-
"@pob/eslint-config": "67.10.0",
|
|
52
|
-
"@pob/sort-object": "11.2.0",
|
|
53
|
-
"@pob/sort-pkg": "13.2.0",
|
|
54
|
-
"pob-dependencies": "27.10.0",
|
|
55
|
-
"@pob/eslint-config-typescript-react": "67.10.0"
|
|
58
|
+
"yeoman-generator": "8.2.2"
|
|
56
59
|
},
|
|
57
60
|
"devDependencies": {
|
|
58
|
-
"@
|
|
61
|
+
"@pob/root": "27.13.0",
|
|
62
|
+
"@types/node": "24.19.1",
|
|
59
63
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
60
|
-
"@yeoman/types": "1.11.1"
|
|
61
|
-
"@pob/root": "27.11.0"
|
|
64
|
+
"@yeoman/types": "1.11.1"
|
|
62
65
|
},
|
|
63
66
|
"engines": {
|
|
64
67
|
"node": ">=22.18.0"
|
|
@@ -73,8 +76,5 @@
|
|
|
73
76
|
"lint:eslint": "pnpm --dir '../..' exec eslint --quiet 'packages/pob'",
|
|
74
77
|
"lint:oxlint": "pnpm --dir '../..' exec oxlint --quiet 'packages/pob'",
|
|
75
78
|
"tsc": "tsc"
|
|
76
|
-
},
|
|
77
|
-
"bin": {
|
|
78
|
-
"pob": "./lib/pob.js"
|
|
79
79
|
}
|
|
80
80
|
}
|