pob 40.3.3 → 40.4.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 +24 -4
- package/lib/generators/common/format-lint/CommonLintGenerator.js +14 -2
- package/lib/generators/common/husky/CommonHuskyGenerator.js +3 -1
- package/lib/generators/common/testing/CommonTestingGenerator.js +10 -2
- package/lib/generators/common/transpiler/CommonTranspilerGenerator.js +11 -9
- package/lib/generators/common/typescript/CommonTypescriptGenerator.js +110 -38
- package/lib/generators/core/bun/CoreBunGenerator.js +1 -1
- package/lib/generators/core/ci/CoreCIGenerator.js +4 -1
- package/lib/generators/core/git/CoreGitGenerator.js +2 -0
- package/lib/generators/core/git/generators/github/CoreGitGithubGenerator.js +1 -0
- package/lib/generators/core/package/CorePackageGenerator.js +12 -3
- package/lib/generators/core/pnpm/CorePnpmGenerator.js +8 -2
- package/lib/generators/core/vscode/CoreVSCodeGenerator.js +9 -0
- package/lib/generators/core/vscode/templates/extensions.json.ejs +2 -1
- package/lib/generators/core/vscode/templates/settings.json.ejs +5 -0
- package/lib/generators/core/yarn/CoreYarnGenerator.js +6 -2
- package/lib/generators/lib/PobLibGenerator.js +15 -13
- package/lib/generators/monorepo/PobMonorepoGenerator.js +18 -0
- package/lib/generators/monorepo/typescript/MonorepoTypescriptGenerator.js +13 -1
- package/lib/generators/monorepo/typescript/templates/tsconfig.tools.json.ejs +7 -0
- package/lib/generators/monorepo/workspaces/MonorepoWorkspacesGenerator.js +7 -2
- package/lib/generators/pob/PobBaseGenerator.js +4 -2
- package/lib/pob.js +75 -5
- package/lib/utils/alouette.js +18 -0
- package/lib/utils/appTsconfigTarget.js +60 -0
- package/lib/utils/appTsconfigTarget.test.js +169 -0
- package/lib/utils/nodeApps.js +8 -0
- package/package.json +5 -5
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import { platform } from "node:process";
|
|
3
3
|
import Generator from "yeoman-generator";
|
|
4
|
+
import { detectAlouette } from "../../utils/alouette.js";
|
|
5
|
+
import { appsWithBrowser } from "../../utils/appTsconfigTarget.js";
|
|
4
6
|
import { removeFromGitIndex } from "../../utils/git.js";
|
|
5
7
|
import inMonorepo from "../../utils/inMonorepo.js";
|
|
6
8
|
import removeLegacyGeneratedDocs from "../../utils/legacyGeneratedDocs.js";
|
|
9
|
+
import { nodeAppTypes } from "../../utils/nodeApps.js";
|
|
7
10
|
import * as packageUtils from "../../utils/package.js";
|
|
8
11
|
import { detectStorybookReactNative } from "../../utils/storybook.js";
|
|
9
12
|
import { appIgnorePaths } from "./ignorePaths.js";
|
|
@@ -16,7 +19,6 @@ const appsWithTypescript = [
|
|
|
16
19
|
"expo",
|
|
17
20
|
"yarn-plugin",
|
|
18
21
|
];
|
|
19
|
-
const appsWithBrowser = ["alp", "next.js", "vite", "vite-with-server"];
|
|
20
22
|
const shouldEnableHashSlash = (appType) =>
|
|
21
23
|
appType === "alp" ||
|
|
22
24
|
appType === "vite" ||
|
|
@@ -96,12 +98,15 @@ export default class PobAppGenerator extends Generator {
|
|
|
96
98
|
delete config.ci;
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
]);
|
|
101
|
+
const pkg = this.fs.readJSON(this.destinationPath("package.json"), {});
|
|
102
|
+
const storybookDefault = detectStorybookReactNative([pkg]);
|
|
102
103
|
if (config?.type === "expo" && config.storybook === undefined) {
|
|
103
104
|
config.storybook = storybookDefault;
|
|
104
105
|
}
|
|
106
|
+
const alouetteDefault = detectAlouette(pkg);
|
|
107
|
+
if (config?.type === "expo" && config.alouette === undefined) {
|
|
108
|
+
config.alouette = alouetteDefault;
|
|
109
|
+
}
|
|
105
110
|
|
|
106
111
|
if (config && this.options.updateOnly) {
|
|
107
112
|
this.config.set("app", this.appConfig);
|
|
@@ -162,6 +167,13 @@ export default class PobAppGenerator extends Generator {
|
|
|
162
167
|
default: config?.storybook ?? storybookDefault,
|
|
163
168
|
when: (values) => values.type === "expo",
|
|
164
169
|
},
|
|
170
|
+
{
|
|
171
|
+
type: "confirm",
|
|
172
|
+
name: "alouette",
|
|
173
|
+
message: "Do you use alouette ?",
|
|
174
|
+
default: config?.alouette ?? alouetteDefault,
|
|
175
|
+
when: (values) => values.type === "expo",
|
|
176
|
+
},
|
|
165
177
|
]);
|
|
166
178
|
|
|
167
179
|
this.config.set("app", this.appConfig);
|
|
@@ -262,10 +274,12 @@ export default class PobAppGenerator extends Generator {
|
|
|
262
274
|
inMonorepo.pobMonorepoConfig?.typescript === "check-only"),
|
|
263
275
|
isApp: true,
|
|
264
276
|
isAppLibrary,
|
|
277
|
+
appType: this.appConfig.type,
|
|
265
278
|
nextConfig: this.appConfig.type === "next.js",
|
|
266
279
|
// nextjs now supports src
|
|
267
280
|
rootDir: this.appConfig.type === "expo" ? "." : srcDirectory,
|
|
268
281
|
srcDirectory,
|
|
282
|
+
includeScripts: nodeAppTypes.includes(this.appConfig.type),
|
|
269
283
|
builddefs: false,
|
|
270
284
|
dom: browser,
|
|
271
285
|
jsx,
|
|
@@ -277,6 +291,8 @@ export default class PobAppGenerator extends Generator {
|
|
|
277
291
|
onlyLatestLTS: true,
|
|
278
292
|
packageManager: this.options.packageManager,
|
|
279
293
|
enableHashSlash,
|
|
294
|
+
// only asked for expo apps: the on-device react-native storybook
|
|
295
|
+
storybook: this.appConfig.storybook,
|
|
280
296
|
plugins: (() => {
|
|
281
297
|
if (this.appConfig.type === "next.js") {
|
|
282
298
|
return "next";
|
|
@@ -363,6 +379,8 @@ export default class PobAppGenerator extends Generator {
|
|
|
363
379
|
typescript,
|
|
364
380
|
testing: this.appConfig.testing,
|
|
365
381
|
testRunner: this.appConfig.testRunner,
|
|
382
|
+
// only asked for expo apps
|
|
383
|
+
tailwind: this.appConfig.alouette,
|
|
366
384
|
});
|
|
367
385
|
|
|
368
386
|
// only for gitignore
|
|
@@ -412,6 +430,8 @@ export default class PobAppGenerator extends Generator {
|
|
|
412
430
|
this.fs.delete(this.destinationPath("next-env.d.ts"));
|
|
413
431
|
}
|
|
414
432
|
|
|
433
|
+
if (this.options.check) return;
|
|
434
|
+
|
|
415
435
|
if (platform !== "win32") {
|
|
416
436
|
execSync(
|
|
417
437
|
`rm -Rf ${["lib-*", "coverage", "generated-docs", "dist"].filter(Boolean).join(" ")}`,
|
|
@@ -281,6 +281,10 @@ export default class CommonFormatLintGenerator extends Generator {
|
|
|
281
281
|
const rootIgnorePatterns = new Set(
|
|
282
282
|
this.options.rootIgnorePaths.split("\n").filter(Boolean),
|
|
283
283
|
);
|
|
284
|
+
if (this.options.storybookReactNative) {
|
|
285
|
+
// regenerated by metro on each start (sb-rn-get-stories)
|
|
286
|
+
rootIgnorePatterns.add("**/storybook.requires.ts");
|
|
287
|
+
}
|
|
284
288
|
const ignorePatterns = new Set(
|
|
285
289
|
this.options.ignorePaths.split("\n").filter(Boolean),
|
|
286
290
|
);
|
|
@@ -508,7 +512,8 @@ export default class CommonFormatLintGenerator extends Generator {
|
|
|
508
512
|
// tsconfig.json; point typed lint at that project. The monorepo root
|
|
509
513
|
// owns the shared tsconfig.tools.json (its **/scripts/** glob covers
|
|
510
514
|
// package scripts too), and single repos own their own; in-monorepo
|
|
511
|
-
// packages inherit the root config.
|
|
515
|
+
// packages inherit the root config. Node apps type-check them with
|
|
516
|
+
// their tsconfig.json instead, but still need the vitest resolution.
|
|
512
517
|
if ((isMonorepoRoot || !inMonorepo) && useTypescript) {
|
|
513
518
|
flatCascade.push("...pobConfig.configs.toolsProject");
|
|
514
519
|
}
|
|
@@ -575,11 +580,12 @@ export default class CommonFormatLintGenerator extends Generator {
|
|
|
575
580
|
}
|
|
576
581
|
|
|
577
582
|
if (this.options.storybook) {
|
|
578
|
-
ignorePatterns.add("**/.storybook/");
|
|
579
583
|
ignorePatterns.add("**/storybook-static/");
|
|
580
584
|
}
|
|
581
585
|
if (this.options.storybookReactNative) {
|
|
582
586
|
ignorePatterns.add("**/.rnstorybook/");
|
|
587
|
+
// generated in .storybook/ by sb-rn-get-stories
|
|
588
|
+
ignorePatterns.add("**/storybook.requires.ts");
|
|
583
589
|
}
|
|
584
590
|
|
|
585
591
|
if (hasDesignSync(this)) {
|
|
@@ -753,6 +759,12 @@ export default class CommonFormatLintGenerator extends Generator {
|
|
|
753
759
|
*/
|
|
754
760
|
async end() {
|
|
755
761
|
if (!this.runOxlintOnlyCodemods) return;
|
|
762
|
+
if (this.options.check) {
|
|
763
|
+
console.log(
|
|
764
|
+
"@pob/eslint-config 68: eslint-disable comments of rules checked by oxlint would be rewritten",
|
|
765
|
+
);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
756
768
|
const cwd = this.destinationPath();
|
|
757
769
|
const files = execFileSync(
|
|
758
770
|
"git",
|
|
@@ -16,7 +16,9 @@ export default class CommonHuskyGenerator extends Generator {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
writing() {
|
|
19
|
-
|
|
19
|
+
if (!this.options.check) {
|
|
20
|
+
rmSync("git-hooks", { recursive: true, force: true });
|
|
21
|
+
}
|
|
20
22
|
|
|
21
23
|
const gitHookDestination = this.destinationPath(".git/hooks/pre-commit");
|
|
22
24
|
let isSymlink;
|
|
@@ -523,8 +523,16 @@ export default class CommonTestingGenerator extends Generator {
|
|
|
523
523
|
}
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
-
// legacy jest babel config
|
|
527
|
-
|
|
526
|
+
// legacy jest babel config, only when generated by pob:
|
|
527
|
+
// apps (expo, react-native) can have their own, read by metro
|
|
528
|
+
const babelConfigPath = this.destinationPath("babel.config.cjs");
|
|
529
|
+
if (
|
|
530
|
+
this.fs
|
|
531
|
+
.read(babelConfigPath, { defaults: "" })
|
|
532
|
+
.includes("pob-babel/preset")
|
|
533
|
+
) {
|
|
534
|
+
this.fs.delete(babelConfigPath);
|
|
535
|
+
}
|
|
528
536
|
|
|
529
537
|
return writeAndFormatJson(
|
|
530
538
|
this.fs,
|
|
@@ -127,15 +127,17 @@ export default class CommonTranspilerGenerator extends Generator {
|
|
|
127
127
|
this.entries = pkg.pob.entries;
|
|
128
128
|
this.babelEnvs = pkg.pob.babelEnvs || [];
|
|
129
129
|
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
130
|
+
if (!this.options.check) {
|
|
131
|
+
if (
|
|
132
|
+
this.babelEnvs.length > 0 ||
|
|
133
|
+
pkg.pob.typescript === true ||
|
|
134
|
+
pkg.pob.bundler
|
|
135
|
+
) {
|
|
136
|
+
fs.mkdirSync(this.destinationPath("src"), { recursive: true });
|
|
137
|
+
} else {
|
|
138
|
+
// recursive does not throw if directory already exists
|
|
139
|
+
fs.mkdirSync(this.destinationPath("lib"), { recursive: true });
|
|
140
|
+
}
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import Generator from "yeoman-generator";
|
|
3
|
+
import {
|
|
4
|
+
getAppTsconfigTarget,
|
|
5
|
+
getConsumerAppsTsconfigTarget,
|
|
6
|
+
} from "../../../utils/appTsconfigTarget.js";
|
|
3
7
|
import inMonorepo from "../../../utils/inMonorepo.js";
|
|
4
8
|
import { latestLTS, maintenanceLTS } from "../../../utils/nodeVersions.js";
|
|
5
9
|
import * as packageUtils from "../../../utils/package.js";
|
|
@@ -27,6 +31,12 @@ export default class CommonTypescriptGenerator extends Generator {
|
|
|
27
31
|
default: false,
|
|
28
32
|
});
|
|
29
33
|
|
|
34
|
+
this.option("appType", {
|
|
35
|
+
type: String,
|
|
36
|
+
default: "",
|
|
37
|
+
description: "app type, when isApp",
|
|
38
|
+
});
|
|
39
|
+
|
|
30
40
|
this.option("rootDir", {
|
|
31
41
|
type: String,
|
|
32
42
|
default: "src",
|
|
@@ -39,6 +49,13 @@ export default class CommonTypescriptGenerator extends Generator {
|
|
|
39
49
|
description: "customize srcDirectory, if different than rootDir",
|
|
40
50
|
});
|
|
41
51
|
|
|
52
|
+
this.option("includeScripts", {
|
|
53
|
+
type: Boolean,
|
|
54
|
+
default: false,
|
|
55
|
+
description:
|
|
56
|
+
"type-check scripts and root config files with tsconfig.json instead of tsconfig.tools.json (node apps)",
|
|
57
|
+
});
|
|
58
|
+
|
|
42
59
|
this.option("jsx", {
|
|
43
60
|
type: Boolean,
|
|
44
61
|
default: true,
|
|
@@ -128,6 +145,13 @@ export default class CommonTypescriptGenerator extends Generator {
|
|
|
128
145
|
default: false,
|
|
129
146
|
description: "enable #/ in paths",
|
|
130
147
|
});
|
|
148
|
+
|
|
149
|
+
this.option("storybook", {
|
|
150
|
+
type: Boolean,
|
|
151
|
+
required: false,
|
|
152
|
+
default: false,
|
|
153
|
+
description: "Type-check the storybook config in .storybook",
|
|
154
|
+
});
|
|
131
155
|
}
|
|
132
156
|
|
|
133
157
|
default() {
|
|
@@ -154,7 +178,22 @@ export default class CommonTypescriptGenerator extends Generator {
|
|
|
154
178
|
const withNode = envs.some((env) => env.target === "node");
|
|
155
179
|
const withBun = this.options.packageManager === "bun";
|
|
156
180
|
|
|
181
|
+
// untranspiled libraries are never built on their own: they are compiled
|
|
182
|
+
// by the bundler of the apps importing them
|
|
183
|
+
const consumerAppsTarget =
|
|
184
|
+
this.options.appType === "untranspiled-library" &&
|
|
185
|
+
inMonorepo &&
|
|
186
|
+
!inMonorepo.root
|
|
187
|
+
? await getConsumerAppsTsconfigTarget({
|
|
188
|
+
rootPath: inMonorepo.rootPath,
|
|
189
|
+
packageName: pkg.name,
|
|
190
|
+
withBun,
|
|
191
|
+
})
|
|
192
|
+
: undefined;
|
|
193
|
+
|
|
157
194
|
const presets = (() => {
|
|
195
|
+
if (consumerAppsTarget) return [consumerAppsTarget];
|
|
196
|
+
|
|
158
197
|
const babelEnvs =
|
|
159
198
|
pkg.pob?.babelEnvs ||
|
|
160
199
|
(pkg.pob?.bundler === "rollup-babel" && pkg.pob.envs) ||
|
|
@@ -198,20 +237,12 @@ export default class CommonTypescriptGenerator extends Generator {
|
|
|
198
237
|
return ["@pob/root/tsconfigs/targets/rollup-es2015.json"];
|
|
199
238
|
}
|
|
200
239
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return ["@pob/root/tsconfigs/targets/vite.json"];
|
|
208
|
-
}
|
|
209
|
-
return ["@pob/root/tsconfigs/targets/webpack.json"];
|
|
210
|
-
}
|
|
211
|
-
if (withBun) {
|
|
212
|
-
return ["@pob/root/tsconfigs/targets/bun.json"];
|
|
213
|
-
}
|
|
214
|
-
return [];
|
|
240
|
+
const target = getAppTsconfigTarget({
|
|
241
|
+
appType: this.options.isApp ? this.options.appType : undefined,
|
|
242
|
+
dom: this.options.dom,
|
|
243
|
+
withBun,
|
|
244
|
+
});
|
|
245
|
+
return target ? [target] : [];
|
|
215
246
|
})();
|
|
216
247
|
|
|
217
248
|
packageUtils.removeDevDependencies(pkg, ["flow-bin"]);
|
|
@@ -237,12 +268,18 @@ export default class CommonTypescriptGenerator extends Generator {
|
|
|
237
268
|
const tsconfigBuildPath = this.destinationPath("tsconfig.build.json");
|
|
238
269
|
const tsconfigToolsPath = this.destinationPath("tsconfig.tools.json");
|
|
239
270
|
|
|
271
|
+
// the check-js template only covers the src directory
|
|
272
|
+
const includeScripts =
|
|
273
|
+
this.options.includeScripts && !this.options.onlyCheck;
|
|
274
|
+
|
|
240
275
|
if (this.options.enable) {
|
|
241
276
|
const { jsx, dom } = this.options;
|
|
242
277
|
let monorepoComposite;
|
|
243
278
|
let monorepoPackageReferences;
|
|
244
279
|
// let monorepoPackageBuildReferences;
|
|
245
280
|
let monorepoPackageSrcPaths;
|
|
281
|
+
// another workspace package depends on this one, so its tsconfig references it
|
|
282
|
+
let isMonorepoDependency = false;
|
|
246
283
|
|
|
247
284
|
if (inMonorepo && !inMonorepo.root) {
|
|
248
285
|
const yoConfig = inMonorepo.rootYoConfig;
|
|
@@ -253,24 +290,42 @@ export default class CommonTypescriptGenerator extends Generator {
|
|
|
253
290
|
yoConfig.pob.monorepo.typescript;
|
|
254
291
|
|
|
255
292
|
if (monorepoComposite) {
|
|
293
|
+
const dependsOn = (dependentPkg, packageName) =>
|
|
294
|
+
Boolean(
|
|
295
|
+
dependentPkg.dependencies?.[packageName] ||
|
|
296
|
+
dependentPkg.devDependencies?.[packageName] ||
|
|
297
|
+
dependentPkg.peerDependencies?.[packageName],
|
|
298
|
+
);
|
|
299
|
+
const getPackageLocation = (packageName) =>
|
|
300
|
+
`../../${
|
|
301
|
+
packageName[0] === "@"
|
|
302
|
+
? // oxlint-disable-next-line unicorn/no-nested-ternary
|
|
303
|
+
yoConfig.pob.project.type === "app"
|
|
304
|
+
? `packages/${packageName.slice(packageName.indexOf("/") + 1)}`
|
|
305
|
+
: packageName
|
|
306
|
+
: `packages/${packageName}`
|
|
307
|
+
}`;
|
|
308
|
+
|
|
309
|
+
isMonorepoDependency = yoConfig.pob.monorepo.packageNames.some(
|
|
310
|
+
(packageName) => {
|
|
311
|
+
if (packageName === pkg.name) return false;
|
|
312
|
+
const dependentPkgPath = `${getPackageLocation(packageName)}/package.json`;
|
|
313
|
+
return (
|
|
314
|
+
existsSync(dependentPkgPath) &&
|
|
315
|
+
dependsOn(
|
|
316
|
+
JSON.parse(readFileSync(dependentPkgPath, "utf8")),
|
|
317
|
+
pkg.name,
|
|
318
|
+
)
|
|
319
|
+
);
|
|
320
|
+
},
|
|
321
|
+
);
|
|
322
|
+
|
|
256
323
|
const packageLocations = new Map(
|
|
257
324
|
yoConfig.pob.monorepo.packageNames
|
|
258
|
-
.filter(
|
|
259
|
-
(packageName) =>
|
|
260
|
-
(pkg.dependencies && pkg.dependencies[packageName]) ||
|
|
261
|
-
(pkg.devDependencies && pkg.devDependencies[packageName]) ||
|
|
262
|
-
(pkg.peerDependencies && pkg.peerDependencies[packageName]),
|
|
263
|
-
)
|
|
325
|
+
.filter((packageName) => dependsOn(pkg, packageName))
|
|
264
326
|
.map((packageName) => [
|
|
265
327
|
packageName,
|
|
266
|
-
|
|
267
|
-
packageName[0] === "@"
|
|
268
|
-
? // oxlint-disable-next-line unicorn/no-nested-ternary
|
|
269
|
-
yoConfig.pob.project.type === "app"
|
|
270
|
-
? `packages/${packageName.slice(packageName.indexOf("/") + 1)}`
|
|
271
|
-
: packageName
|
|
272
|
-
: `packages/${packageName}`
|
|
273
|
-
}`,
|
|
328
|
+
getPackageLocation(packageName),
|
|
274
329
|
]),
|
|
275
330
|
);
|
|
276
331
|
|
|
@@ -304,6 +359,14 @@ export default class CommonTypescriptGenerator extends Generator {
|
|
|
304
359
|
}
|
|
305
360
|
}
|
|
306
361
|
|
|
362
|
+
// composite is meant for packages built and exported before the ones
|
|
363
|
+
// depending on them: libraries, and apps imported by other packages.
|
|
364
|
+
// Other apps only enable incremental.
|
|
365
|
+
const isReferenceable =
|
|
366
|
+
!this.options.isApp ||
|
|
367
|
+
this.options.isAppLibrary ||
|
|
368
|
+
isMonorepoDependency;
|
|
369
|
+
|
|
307
370
|
if (this.fs.exists(tsconfigEslintPath)) {
|
|
308
371
|
this.fs.delete(tsconfigEslintPath);
|
|
309
372
|
}
|
|
@@ -328,29 +391,37 @@ export default class CommonTypescriptGenerator extends Generator {
|
|
|
328
391
|
pkg.pob?.rollup === false ||
|
|
329
392
|
pkg.pob?.bundler === false ||
|
|
330
393
|
pkg.pob?.bundler === "tsc",
|
|
331
|
-
cacheEnabled:
|
|
394
|
+
cacheEnabled: isReferenceable,
|
|
332
395
|
monorepoPackageSrcPaths,
|
|
333
396
|
monorepoPackageReferences,
|
|
334
|
-
|
|
397
|
+
// "." also includes root *.ts files, like vitest.config.ts
|
|
398
|
+
rootDir: includeScripts ? "." : this.options.rootDir,
|
|
335
399
|
srcDirectory: this.options.srcDirectory || this.options.rootDir,
|
|
336
400
|
enableHashSlash: this.options.enableHashSlash,
|
|
337
401
|
jsx,
|
|
338
402
|
jsxPreserve: this.options.jsxPreserve,
|
|
339
403
|
nextConfig: this.options.nextConfig,
|
|
340
|
-
// for apps, only enable incremental. Composite is meant for libraries to build and export before the ones depending on them.
|
|
341
404
|
composite:
|
|
342
405
|
monorepoComposite &&
|
|
343
406
|
monorepoComposite !== "check-only" &&
|
|
344
|
-
|
|
407
|
+
isReferenceable,
|
|
345
408
|
incremental: monorepoComposite,
|
|
346
409
|
dom,
|
|
347
410
|
resolveJsonModule: this.options.resolveJsonModule,
|
|
348
411
|
forceExcludeNodeModules: this.options.forceExcludeNodeModules,
|
|
349
412
|
forceAllowJs: this.options.forceAllowJs,
|
|
350
413
|
plugins: this.options.plugins.split(",").filter(Boolean),
|
|
351
|
-
additionalIncludes:
|
|
352
|
-
.split(",")
|
|
353
|
-
|
|
414
|
+
additionalIncludes: [
|
|
415
|
+
...this.options.additionalIncludes.split(",").filter(Boolean),
|
|
416
|
+
...(includeScripts
|
|
417
|
+
? ["scripts/**/*.ts", "scripts/**/*.mts", "scripts/**/*.cts"]
|
|
418
|
+
: []),
|
|
419
|
+
// the app only imports the generated storybook.requires.ts, which
|
|
420
|
+
// loads ./preview with a require() that typescript does not follow
|
|
421
|
+
...(this.options.storybook
|
|
422
|
+
? [".storybook/*.ts", ".storybook/*.tsx"]
|
|
423
|
+
: []),
|
|
424
|
+
],
|
|
354
425
|
node: withNode,
|
|
355
426
|
presets,
|
|
356
427
|
},
|
|
@@ -381,7 +452,8 @@ export default class CommonTypescriptGenerator extends Generator {
|
|
|
381
452
|
// dedicated noEmit project so it is type-checked without emitting
|
|
382
453
|
// definitions nor violating rootDir. Monorepos handle this at the root
|
|
383
454
|
// via the shared tsconfig.tools.json, so only single repos get it here.
|
|
384
|
-
|
|
455
|
+
// Node apps include them in tsconfig.json instead.
|
|
456
|
+
if (inMonorepo || includeScripts) {
|
|
385
457
|
this.fs.delete(tsconfigToolsPath);
|
|
386
458
|
} else {
|
|
387
459
|
await copyAndFormatTpl(
|
|
@@ -405,7 +477,7 @@ export default class CommonTypescriptGenerator extends Generator {
|
|
|
405
477
|
// tsrs and tsc do not share their .tsbuildinfo: switching between them
|
|
406
478
|
// triggers a full rebuild
|
|
407
479
|
const tscCommand = (compiler) =>
|
|
408
|
-
inMonorepo || !this.options.enable
|
|
480
|
+
inMonorepo || includeScripts || !this.options.enable
|
|
409
481
|
? compiler
|
|
410
482
|
: `${compiler} && ${compiler} -p tsconfig.tools.json`;
|
|
411
483
|
packageUtils.addOrRemoveScripts(pkg, this.options.enable, {
|
|
@@ -41,7 +41,7 @@ export default class CoreBunGenerator extends Generator {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
end() {
|
|
44
|
-
if (this.options.enable) {
|
|
44
|
+
if (this.options.enable && !this.options.check) {
|
|
45
45
|
this.spawnSync("bun", ["install", "--save-text-lockfile"], {});
|
|
46
46
|
|
|
47
47
|
const pkg = this.fs.readJSON(this.destinationPath("package.json"));
|
|
@@ -107,7 +107,10 @@ export default class CoreCIGenerator extends Generator {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
async default() {
|
|
110
|
-
if (
|
|
110
|
+
if (
|
|
111
|
+
!this.options.check &&
|
|
112
|
+
fs.existsSync(this.destinationPath(".circleci"))
|
|
113
|
+
) {
|
|
111
114
|
fs.rmdirSync(this.destinationPath(".circleci"), { recursive: true });
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -264,6 +264,7 @@ export default class CoreGitGithubGenerator extends Generator {
|
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
async end() {
|
|
267
|
+
if (this.options.check) return;
|
|
267
268
|
const pkg = this.fs.readJSON(this.destinationPath("package.json"), {});
|
|
268
269
|
const owner = this.options.gitHostAccount;
|
|
269
270
|
const repo = this.options.repoName || getRepoName(pkg.name);
|
|
@@ -205,15 +205,24 @@ export default class CorePackageGenerator extends Generator {
|
|
|
205
205
|
if (pkg.private) delete pkg.bugs;
|
|
206
206
|
else if (rootMonorepoPkg.bugs) pkg.bugs = rootMonorepoPkg.bugs;
|
|
207
207
|
|
|
208
|
-
if (
|
|
208
|
+
if (
|
|
209
|
+
!this.options.check &&
|
|
210
|
+
this.fs.exists(this.destinationPath("yarn.lock"))
|
|
211
|
+
) {
|
|
209
212
|
fs.unlinkSync(this.destinationPath("yarn.lock"));
|
|
210
213
|
}
|
|
211
|
-
if (
|
|
214
|
+
if (
|
|
215
|
+
!this.options.check &&
|
|
216
|
+
this.fs.exists(this.destinationPath("pnpm-lock.yaml"))
|
|
217
|
+
) {
|
|
212
218
|
fs.unlinkSync(this.destinationPath("pnpm-lock.yaml"));
|
|
213
219
|
}
|
|
214
220
|
}
|
|
215
221
|
|
|
216
|
-
if (
|
|
222
|
+
if (
|
|
223
|
+
!this.options.check &&
|
|
224
|
+
this.fs.exists(this.destinationPath("yarn-error.log"))
|
|
225
|
+
) {
|
|
217
226
|
fs.unlinkSync(this.destinationPath("yarn-error.log"));
|
|
218
227
|
}
|
|
219
228
|
|
|
@@ -131,7 +131,13 @@ export default class CorePnpmGenerator extends Generator {
|
|
|
131
131
|
}
|
|
132
132
|
config.savePrefix = this.options.type === "app" ? "" : "^";
|
|
133
133
|
config.minimumReleaseAge = 1440 * 2.5; // 2.5 days in minutes
|
|
134
|
-
|
|
134
|
+
// keep the entries added by renovate for security updates, or manually
|
|
135
|
+
config.minimumReleaseAgeExclude = [
|
|
136
|
+
...new Set([
|
|
137
|
+
...minimumReleaseAgeExcludePackages,
|
|
138
|
+
...(config.minimumReleaseAgeExclude ?? []),
|
|
139
|
+
]),
|
|
140
|
+
];
|
|
135
141
|
config.dedupePeerDependents = true;
|
|
136
142
|
config.nodeLinker = "hoisted";
|
|
137
143
|
|
|
@@ -156,7 +162,7 @@ export default class CorePnpmGenerator extends Generator {
|
|
|
156
162
|
}
|
|
157
163
|
|
|
158
164
|
end() {
|
|
159
|
-
if (this.options.enable) {
|
|
165
|
+
if (this.options.enable && !this.options.check) {
|
|
160
166
|
if (this.pendingAllowBuilds.length > 0) {
|
|
161
167
|
console.warn(
|
|
162
168
|
`pnpm-workspace.yaml: set allowBuilds to true or false for ${this.pendingAllowBuilds.join(", ")}`,
|
|
@@ -55,6 +55,13 @@ export default class CoreVSCodeGenerator extends Generator {
|
|
|
55
55
|
description: "Typescript enabled",
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
this.option("tailwind", {
|
|
59
|
+
type: Boolean,
|
|
60
|
+
required: false,
|
|
61
|
+
default: false,
|
|
62
|
+
description: "Tailwind enabled, with tailwind-variants (alouette)",
|
|
63
|
+
});
|
|
64
|
+
|
|
58
65
|
this.option("packageNames", {
|
|
59
66
|
type: String,
|
|
60
67
|
required: false,
|
|
@@ -95,6 +102,7 @@ export default class CoreVSCodeGenerator extends Generator {
|
|
|
95
102
|
yarn: this.options.packageManager === "yarn",
|
|
96
103
|
pnp: this.options.yarnNodeLinker === "pnp",
|
|
97
104
|
typescript: this.options.typescript,
|
|
105
|
+
tailwind: this.options.tailwind,
|
|
98
106
|
},
|
|
99
107
|
);
|
|
100
108
|
await copyAndFormatTpl(
|
|
@@ -111,6 +119,7 @@ export default class CoreVSCodeGenerator extends Generator {
|
|
|
111
119
|
tsrs: withTsrs,
|
|
112
120
|
testing: this.options.testing,
|
|
113
121
|
testRunner: this.options.testRunner,
|
|
122
|
+
tailwind: this.options.tailwind,
|
|
114
123
|
module: pkg.type === "module",
|
|
115
124
|
},
|
|
116
125
|
);
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"dbaeumer.vscode-eslint",
|
|
12
12
|
"EditorConfig.EditorConfig",
|
|
13
13
|
"esbenp.prettier-vscode"<% if (typescript) { -%>,
|
|
14
|
-
"TypeScriptTeam.native-preview"<% }
|
|
14
|
+
"TypeScriptTeam.native-preview"<% } %><% if (tailwind) { -%>,
|
|
15
|
+
"bradlc.vscode-tailwindcss"<% } %>
|
|
15
16
|
],
|
|
16
17
|
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
|
17
18
|
"unwantedRecommendations": []
|
|
@@ -46,7 +46,7 @@ export default class CoreYarnGenerator extends Generator {
|
|
|
46
46
|
async initializing() {
|
|
47
47
|
if (this.options.enable) {
|
|
48
48
|
// dont use this.fs here, as it will cache the result
|
|
49
|
-
if (!fs.existsSync(".yarnrc.yml")) {
|
|
49
|
+
if (!this.options.check && !fs.existsSync(".yarnrc.yml")) {
|
|
50
50
|
// yarn 2 not yet installed
|
|
51
51
|
// https://yarnpkg.com/getting-started/install
|
|
52
52
|
this.spawnSync("yarn", ["set", "version", "stable"]);
|
|
@@ -103,6 +103,10 @@ export default class CoreYarnGenerator extends Generator {
|
|
|
103
103
|
// this.spawnSync("yarn", ["plugin", "import", nameOrUrl]);
|
|
104
104
|
// };
|
|
105
105
|
const removePlugin = (name) => {
|
|
106
|
+
if (this.options.check) {
|
|
107
|
+
console.log(`yarn plugin ${name} would be removed`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
106
110
|
this.spawnSync("yarn", ["plugin", "remove", name]);
|
|
107
111
|
};
|
|
108
112
|
|
|
@@ -224,7 +228,7 @@ export default class CoreYarnGenerator extends Generator {
|
|
|
224
228
|
|
|
225
229
|
end() {
|
|
226
230
|
this.fs.delete(this.destinationPath(".yarn/build-state.yml"));
|
|
227
|
-
if (this.options.enable) {
|
|
231
|
+
if (this.options.enable && !this.options.check) {
|
|
228
232
|
if (this.options.yarnNodeLinker === "pnp") {
|
|
229
233
|
this.spawnSync("yarn", ["dlx", "@yarnpkg/sdks", "vscode"]);
|
|
230
234
|
} else {
|
|
@@ -476,19 +476,21 @@ export default class PobLibGenerator extends Generator {
|
|
|
476
476
|
}
|
|
477
477
|
|
|
478
478
|
this.fs.writeJSON(this.destinationPath("package.json"), pkg);
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
479
|
+
if (!this.options.check) {
|
|
480
|
+
[
|
|
481
|
+
"lib-node14",
|
|
482
|
+
"lib-node16",
|
|
483
|
+
"coverage",
|
|
484
|
+
this.pobjson.documentation && "generated-docs",
|
|
485
|
+
!(withBabel || withTypescript) && "dist",
|
|
486
|
+
]
|
|
487
|
+
.filter(Boolean)
|
|
488
|
+
.forEach((path) => {
|
|
489
|
+
rmSync(path, { recursive: true, force: true });
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
removeLegacyGeneratedDocs((path) => this.destinationPath(path));
|
|
493
|
+
}
|
|
492
494
|
|
|
493
495
|
const { pobjson } = this;
|
|
494
496
|
|
|
@@ -2,7 +2,9 @@ import { execSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { platform } from "node:process";
|
|
4
4
|
import Generator from "yeoman-generator";
|
|
5
|
+
import { isAlouetteApp } from "../../utils/alouette.js";
|
|
5
6
|
import removeLegacyGeneratedDocs from "../../utils/legacyGeneratedDocs.js";
|
|
7
|
+
import { isNodeApp } from "../../utils/nodeApps.js";
|
|
6
8
|
import * as packageUtils from "../../utils/package.js";
|
|
7
9
|
import { packageManagerRun } from "../../utils/packageManagerUtils.js";
|
|
8
10
|
import {
|
|
@@ -389,6 +391,9 @@ export default class PobMonorepoGenerator extends Generator {
|
|
|
389
391
|
typescript: this.pobMonorepoConfig.typescript,
|
|
390
392
|
testing: this.pobMonorepoConfig.testing,
|
|
391
393
|
testRunner: this.pobMonorepoConfig.testRunner,
|
|
394
|
+
tailwind: this.packageConfigs.some((config, index) =>
|
|
395
|
+
isAlouetteApp(config, this.packages[index]),
|
|
396
|
+
),
|
|
392
397
|
packageNames: JSON.stringify(packageNames),
|
|
393
398
|
packageLocations: JSON.stringify(this.packageLocations),
|
|
394
399
|
});
|
|
@@ -428,6 +433,17 @@ export default class PobMonorepoGenerator extends Generator {
|
|
|
428
433
|
isAppProject: this.options.isAppProject,
|
|
429
434
|
packageNames: JSON.stringify(packageNames),
|
|
430
435
|
packagePaths: JSON.stringify(packagePaths),
|
|
436
|
+
// mirrors the includeScripts option given by the app generator
|
|
437
|
+
packagePathsWithScriptsIncluded: JSON.stringify(
|
|
438
|
+
this.packageLocations.filter(
|
|
439
|
+
(location, index) =>
|
|
440
|
+
this.pobMonorepoConfig.typescript !== "check-only" &&
|
|
441
|
+
this.packages[index].pob?.typescript !== "check-only" &&
|
|
442
|
+
isNodeApp(this.packageConfigs[index]) &&
|
|
443
|
+
packagePaths.includes(location) &&
|
|
444
|
+
fs.existsSync(`${location}/scripts`),
|
|
445
|
+
),
|
|
446
|
+
),
|
|
431
447
|
testRunner: this.pobMonorepoConfig.testRunner,
|
|
432
448
|
onlyLatestLTS: this.options.onlyLatestLTS,
|
|
433
449
|
packageManager: this.options.packageManager,
|
|
@@ -435,6 +451,8 @@ export default class PobMonorepoGenerator extends Generator {
|
|
|
435
451
|
|
|
436
452
|
this.fs.writeJSON(this.destinationPath("package.json"), pkg);
|
|
437
453
|
|
|
454
|
+
if (this.options.check) return;
|
|
455
|
+
|
|
438
456
|
if (platform !== "win32") {
|
|
439
457
|
execSync(
|
|
440
458
|
`rm -Rf ${["lib-*", "coverage", "generated-docs"].filter(Boolean).join(" ")}`,
|
|
@@ -57,6 +57,14 @@ export default class MonorepoTypescriptGenerator extends Generator {
|
|
|
57
57
|
required: true,
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
this.option("packagePathsWithScriptsIncluded", {
|
|
61
|
+
type: String,
|
|
62
|
+
required: false,
|
|
63
|
+
default: "[]",
|
|
64
|
+
description:
|
|
65
|
+
"packages whose tsconfig.json includes their scripts (node apps)",
|
|
66
|
+
});
|
|
67
|
+
|
|
60
68
|
this.option("testRunner", {
|
|
61
69
|
type: String,
|
|
62
70
|
required: false,
|
|
@@ -85,7 +93,8 @@ export default class MonorepoTypescriptGenerator extends Generator {
|
|
|
85
93
|
// emitting tsconfig, so it needs a dedicated noEmit project for type-aware
|
|
86
94
|
// linting and tsc -b: root config files (e.g. vitest.config.ts), a root
|
|
87
95
|
// scripts/ dir, and each package's scripts/ dir (matched via the workspace
|
|
88
|
-
// globs so all packages are covered
|
|
96
|
+
// globs so all packages are covered, except node apps which include them in
|
|
97
|
+
// their own tsconfig.json)
|
|
89
98
|
#hasTools() {
|
|
90
99
|
return (
|
|
91
100
|
hasRootConfigFiles(this.destinationPath()) ||
|
|
@@ -199,6 +208,9 @@ export default class MonorepoTypescriptGenerator extends Generator {
|
|
|
199
208
|
{
|
|
200
209
|
nodeVersion: latestLTS,
|
|
201
210
|
workspaceGlobs,
|
|
211
|
+
packagePathsWithScriptsIncluded: JSON.parse(
|
|
212
|
+
this.options.packagePathsWithScriptsIncluded,
|
|
213
|
+
),
|
|
202
214
|
},
|
|
203
215
|
);
|
|
204
216
|
} else {
|
|
@@ -21,6 +21,13 @@
|
|
|
21
21
|
"<%= workspaceGlob %>/e2e/**/*.cts"<%= index === workspaceGlobs.length - 1 ? '' : ',' %>
|
|
22
22
|
<% }) -%>
|
|
23
23
|
],
|
|
24
|
+
<% if (packagePathsWithScriptsIncluded.length > 0) { -%>
|
|
25
|
+
"exclude": [
|
|
26
|
+
<% packagePathsWithScriptsIncluded.forEach((packagePath, index) => { -%>
|
|
27
|
+
"<%= packagePath %>/scripts/**"<%= index === packagePathsWithScriptsIncluded.length - 1 ? '' : ',' %>
|
|
28
|
+
<% }) -%>
|
|
29
|
+
],
|
|
30
|
+
<% } -%>
|
|
24
31
|
"compilerOptions": {
|
|
25
32
|
"noEmit": true,
|
|
26
33
|
"tsBuildInfoFile": "node_modules/.cache/tsc/tsbuildinfo-tools",
|
|
@@ -199,21 +199,26 @@ export default class MonorepoWorkspacesGenerator extends Generator {
|
|
|
199
199
|
return;
|
|
200
200
|
}
|
|
201
201
|
console.log(`=> update ${packagePath}`);
|
|
202
|
-
spawnSync(
|
|
202
|
+
const { status } = spawnSync(
|
|
203
203
|
process.argv[0],
|
|
204
204
|
[
|
|
205
205
|
process.argv[1],
|
|
206
206
|
"update",
|
|
207
207
|
"from-pob",
|
|
208
|
-
this.options.
|
|
208
|
+
this.options.check && "--check",
|
|
209
|
+
!this.options.check && this.options.force && "--force",
|
|
209
210
|
].filter(Boolean),
|
|
210
211
|
{
|
|
211
212
|
cwd: packagePath,
|
|
212
213
|
stdio: "inherit",
|
|
213
214
|
},
|
|
214
215
|
);
|
|
216
|
+
// with --check, a package exits with 1 when a file would change
|
|
217
|
+
if (this.options.check && status !== 0) process.exitCode = 1;
|
|
215
218
|
});
|
|
216
219
|
|
|
220
|
+
if (this.options.check) return;
|
|
221
|
+
|
|
217
222
|
switch (this.options.packageManager) {
|
|
218
223
|
case "npm":
|
|
219
224
|
this.spawnSync("npm", ["install"]);
|
|
@@ -45,7 +45,9 @@ export default class PobBaseGenerator extends Generator {
|
|
|
45
45
|
|
|
46
46
|
async initializing() {
|
|
47
47
|
// format package.json to ensure diff is correct
|
|
48
|
-
|
|
48
|
+
if (!this.options.check) {
|
|
49
|
+
await ensureJsonFileFormatted(this.destinationPath("package.json"));
|
|
50
|
+
}
|
|
49
51
|
|
|
50
52
|
if (this.options.monorepo) {
|
|
51
53
|
this.isMonorepo = true;
|
|
@@ -296,7 +298,7 @@ export default class PobBaseGenerator extends Generator {
|
|
|
296
298
|
}
|
|
297
299
|
|
|
298
300
|
install() {
|
|
299
|
-
if (this.options.fromPob) return;
|
|
301
|
+
if (this.options.fromPob || this.options.check) return;
|
|
300
302
|
|
|
301
303
|
switch (this.#getPackageManager()) {
|
|
302
304
|
case "npm":
|
package/lib/pob.js
CHANGED
|
@@ -51,12 +51,42 @@ import { getNewPackagePath } from "./utils/workspaceUtils.js";
|
|
|
51
51
|
|
|
52
52
|
const printUsage = () => {
|
|
53
53
|
console.error("Usage: pob [monorepo] [lib|app|init]");
|
|
54
|
-
console.error(" pob [monorepo] update [--force]");
|
|
54
|
+
console.error(" pob [monorepo] update [--force|--check]");
|
|
55
55
|
console.error(
|
|
56
56
|
" pob add <packageName> [--app <appType> [--testing] [--e2e]]",
|
|
57
57
|
);
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
const printHelp = () => {
|
|
61
|
+
console.log(`Usage: pob [monorepo] [lib|app|init]
|
|
62
|
+
pob [monorepo] update [from-pob] [--force|--check]
|
|
63
|
+
pob add <packageName> [--app <appType> [--testing] [--e2e]]
|
|
64
|
+
pob migrate-to-monorepo
|
|
65
|
+
|
|
66
|
+
Commands:
|
|
67
|
+
lib Generate a library
|
|
68
|
+
app Generate an app
|
|
69
|
+
init Initialize pob config, prompting for project type
|
|
70
|
+
update Regenerate files from the existing .yo-rc.json, without prompts
|
|
71
|
+
update from-pob Same as update, without running install or build
|
|
72
|
+
add <packageName> Add a new package to the monorepo workspaces
|
|
73
|
+
migrate-to-monorepo Move the current project into packages/<name> and create a monorepo root
|
|
74
|
+
|
|
75
|
+
monorepo Prefix to force monorepo mode (auto-detected from workspaces or lerna.json)
|
|
76
|
+
|
|
77
|
+
Options:
|
|
78
|
+
--force Overwrite every file without asking
|
|
79
|
+
--check With update (implied when no command is given): print
|
|
80
|
+
the diffs without writing anything or running install,
|
|
81
|
+
exit with code 1 when a file would change
|
|
82
|
+
--app <appType> With add: generate an app package without prompts
|
|
83
|
+
(alp, vite, vite-with-server, next.js, node, node-library,
|
|
84
|
+
untranspiled-library, alp-node, expo, other)
|
|
85
|
+
--testing With add --app: enable testing
|
|
86
|
+
--e2e With add --app: enable e2e testing
|
|
87
|
+
-h, --help Show this help`);
|
|
88
|
+
};
|
|
89
|
+
|
|
60
90
|
const readJson = (filepath) => {
|
|
61
91
|
try {
|
|
62
92
|
return JSON.parse(readFileSync(filepath, "utf8"));
|
|
@@ -69,14 +99,39 @@ const readJson = (filepath) => {
|
|
|
69
99
|
// console.log(pkg.version);
|
|
70
100
|
// };
|
|
71
101
|
|
|
72
|
-
const argv = minimist(process.argv.slice(2));
|
|
102
|
+
const argv = minimist(process.argv.slice(2), { alias: { h: "help" } });
|
|
103
|
+
|
|
104
|
+
if (argv.help || argv._[0] === "help") {
|
|
105
|
+
printHelp();
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
73
108
|
|
|
74
109
|
if (argv.version) {
|
|
75
110
|
// printVersion();
|
|
76
111
|
process.exit(0);
|
|
77
112
|
}
|
|
78
113
|
|
|
79
|
-
const
|
|
114
|
+
const check = !!argv.check;
|
|
115
|
+
|
|
116
|
+
// dryRun: the conflicter prints the diffs and skips every write.
|
|
117
|
+
// sharedOptions.check: generators skip their side effects outside mem-fs.
|
|
118
|
+
const env = yeoman.createEnv(
|
|
119
|
+
check
|
|
120
|
+
? { dryRun: true, skipInstall: true, sharedOptions: { check: true } }
|
|
121
|
+
: {},
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// the conflicter clears its file statuses after each file: collect them as logged
|
|
125
|
+
const changedFiles = [];
|
|
126
|
+
if (check) {
|
|
127
|
+
for (const status of ["create", "conflict"]) {
|
|
128
|
+
const log = env.adapter.log[status];
|
|
129
|
+
env.adapter.log[status] = (filePath, ...args) => {
|
|
130
|
+
changedFiles.push(filePath);
|
|
131
|
+
return log.call(env.adapter.log, filePath, ...args);
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
80
135
|
|
|
81
136
|
env.on("error", (err) => {
|
|
82
137
|
console.error(err.stack || err.message || err);
|
|
@@ -266,7 +321,13 @@ env.registerStub(
|
|
|
266
321
|
const projectPkg = readJson(path.resolve("./package.json"));
|
|
267
322
|
const monorepoArg = argv._[0] === "lerna" || argv._[0] === "monorepo";
|
|
268
323
|
let monorepo = monorepoArg || !!(projectPkg && projectPkg.workspaces);
|
|
269
|
-
const action = monorepoArg ? argv._[1] : argv._[0];
|
|
324
|
+
const action = (monorepoArg ? argv._[1] : argv._[0]) ?? (check && "update");
|
|
325
|
+
|
|
326
|
+
if (check && action !== "update") {
|
|
327
|
+
console.error("--check can only be used with update");
|
|
328
|
+
printUsage();
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
270
331
|
|
|
271
332
|
if (action === "add") {
|
|
272
333
|
if (!projectPkg.workspaces) {
|
|
@@ -417,7 +478,8 @@ const options = {
|
|
|
417
478
|
updateOnly,
|
|
418
479
|
monorepo,
|
|
419
480
|
fromPob,
|
|
420
|
-
force
|
|
481
|
+
// force bypasses the conflicter diff
|
|
482
|
+
force: check ? false : argv.force,
|
|
421
483
|
};
|
|
422
484
|
|
|
423
485
|
try {
|
|
@@ -429,6 +491,14 @@ try {
|
|
|
429
491
|
}
|
|
430
492
|
}
|
|
431
493
|
|
|
494
|
+
if (check) {
|
|
495
|
+
if (changedFiles.length > 0) {
|
|
496
|
+
console.error(`pob --check: ${changedFiles.length} file(s) would change:`);
|
|
497
|
+
for (const file of changedFiles) console.error(` ${file}`);
|
|
498
|
+
process.exitCode = 1;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
432
502
|
// generator.on('error', (err) => {
|
|
433
503
|
// console.error(err.stack || err.message || err);
|
|
434
504
|
// process.exit(1);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// alouette styles components with tailwind (nativewind) and tailwind-variants
|
|
2
|
+
|
|
3
|
+
/** @param {Record<string, any>} pkg */
|
|
4
|
+
export const detectAlouette = (pkg) =>
|
|
5
|
+
[pkg.dependencies, pkg.devDependencies].some(
|
|
6
|
+
(dependencies) => dependencies?.alouette !== undefined,
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Fallback on the dependencies for an app config missing the option.
|
|
11
|
+
*
|
|
12
|
+
* @param {Record<string, any> | undefined} pobConfig
|
|
13
|
+
* @param {Record<string, any>} pkg
|
|
14
|
+
*/
|
|
15
|
+
export const isAlouetteApp = (pobConfig, pkg) =>
|
|
16
|
+
pobConfig?.project?.type === "app" &&
|
|
17
|
+
pobConfig.app?.type === "expo" &&
|
|
18
|
+
(pobConfig.app.alouette ?? detectAlouette(pkg));
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { buildDependentsMaps, discoverWorkspaces } from "./workspaceUtils.js";
|
|
4
|
+
|
|
5
|
+
export const appsWithBrowser = ["alp", "next.js", "vite", "vite-with-server"];
|
|
6
|
+
|
|
7
|
+
const expoTarget = "@pob/root/tsconfigs/targets/expo.json";
|
|
8
|
+
|
|
9
|
+
// based on the app type from the pob config rather than on the package.json
|
|
10
|
+
// dependencies, which can change without pob being run again
|
|
11
|
+
export const getAppTsconfigTarget = ({ appType, dom, withBun }) => {
|
|
12
|
+
if (appType === "expo") return expoTarget;
|
|
13
|
+
if (dom) {
|
|
14
|
+
return appType === "vite" || appType === "vite-with-server"
|
|
15
|
+
? "@pob/root/tsconfigs/targets/vite.json"
|
|
16
|
+
: "@pob/root/tsconfigs/targets/webpack.json";
|
|
17
|
+
}
|
|
18
|
+
if (withBun) return "@pob/root/tsconfigs/targets/bun.json";
|
|
19
|
+
return undefined;
|
|
20
|
+
};
|
|
21
|
+
|
|
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
|
+
/**
|
|
31
|
+
* Target of the apps directly depending on the package, for packages compiled
|
|
32
|
+
* by the bundler of these apps (untranspiled libraries). Expo wins as the
|
|
33
|
+
* widest target, otherwise the apps must agree on a single target.
|
|
34
|
+
*/
|
|
35
|
+
export const getConsumerAppsTsconfigTarget = async ({
|
|
36
|
+
rootPath,
|
|
37
|
+
packageName,
|
|
38
|
+
withBun,
|
|
39
|
+
}) => {
|
|
40
|
+
const workspaces = await discoverWorkspaces(rootPath);
|
|
41
|
+
const workspace = workspaces.find((w) => w.name === packageName);
|
|
42
|
+
if (!workspace) return undefined;
|
|
43
|
+
|
|
44
|
+
const dependents = buildDependentsMaps(workspaces).get(workspace) || [];
|
|
45
|
+
const targets = new Set();
|
|
46
|
+
for (const [dependent] of dependents) {
|
|
47
|
+
const pobConfig = readPobConfig(path.join(rootPath, dependent.location));
|
|
48
|
+
if (pobConfig?.project?.type !== "app") continue;
|
|
49
|
+
const appType = pobConfig.app?.type;
|
|
50
|
+
const target = getAppTsconfigTarget({
|
|
51
|
+
appType,
|
|
52
|
+
dom: appsWithBrowser.includes(appType),
|
|
53
|
+
withBun,
|
|
54
|
+
});
|
|
55
|
+
if (target) targets.add(target);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (targets.has(expoTarget)) return expoTarget;
|
|
59
|
+
return targets.size === 1 ? [...targets][0] : undefined;
|
|
60
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
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 {
|
|
6
|
+
getAppTsconfigTarget,
|
|
7
|
+
getConsumerAppsTsconfigTarget,
|
|
8
|
+
} from "./appTsconfigTarget.js";
|
|
9
|
+
|
|
10
|
+
const targets = {
|
|
11
|
+
expo: "@pob/root/tsconfigs/targets/expo.json",
|
|
12
|
+
vite: "@pob/root/tsconfigs/targets/vite.json",
|
|
13
|
+
webpack: "@pob/root/tsconfigs/targets/webpack.json",
|
|
14
|
+
bun: "@pob/root/tsconfigs/targets/bun.json",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe("getAppTsconfigTarget", () => {
|
|
18
|
+
it("returns expo for expo apps", () => {
|
|
19
|
+
expect(getAppTsconfigTarget({ appType: "expo", dom: false })).toBe(
|
|
20
|
+
targets.expo,
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("returns vite for vite apps", () => {
|
|
25
|
+
expect(getAppTsconfigTarget({ appType: "vite", dom: true })).toBe(
|
|
26
|
+
targets.vite,
|
|
27
|
+
);
|
|
28
|
+
expect(
|
|
29
|
+
getAppTsconfigTarget({ appType: "vite-with-server", dom: true }),
|
|
30
|
+
).toBe(targets.vite);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("returns webpack for other browser apps and dom libraries", () => {
|
|
34
|
+
expect(getAppTsconfigTarget({ appType: "alp", dom: true })).toBe(
|
|
35
|
+
targets.webpack,
|
|
36
|
+
);
|
|
37
|
+
expect(getAppTsconfigTarget({ appType: undefined, dom: true })).toBe(
|
|
38
|
+
targets.webpack,
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("returns bun without dom with bun", () => {
|
|
43
|
+
expect(
|
|
44
|
+
getAppTsconfigTarget({ appType: "node", dom: false, withBun: true }),
|
|
45
|
+
).toBe(targets.bun);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("returns undefined for node apps", () => {
|
|
49
|
+
expect(getAppTsconfigTarget({ appType: "node", dom: false })).toBe(
|
|
50
|
+
undefined,
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
async function writeWorkspace(rootPath, location, pkg, pobConfig) {
|
|
56
|
+
const dir = path.join(rootPath, location);
|
|
57
|
+
await fs.mkdir(dir, { recursive: true });
|
|
58
|
+
await fs.writeFile(path.join(dir, "package.json"), JSON.stringify(pkg));
|
|
59
|
+
if (pobConfig) {
|
|
60
|
+
await fs.writeFile(
|
|
61
|
+
path.join(dir, ".yo-rc.json"),
|
|
62
|
+
JSON.stringify({ pob: pobConfig }),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const app = (type) => ({ project: { type: "app" }, app: { type } });
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Monorepo with an untranspiled library "@ex/lib" and the given consumers,
|
|
71
|
+
* each one an [appType, pobConfig] pair depending on the library.
|
|
72
|
+
*/
|
|
73
|
+
async function prepareMonorepo(consumers) {
|
|
74
|
+
const rootPath = await fs.mkdtemp(path.join(os.tmpdir(), "pob-"));
|
|
75
|
+
await writeWorkspace(rootPath, ".", {
|
|
76
|
+
name: "root",
|
|
77
|
+
workspaces: ["packages/*"],
|
|
78
|
+
});
|
|
79
|
+
await writeWorkspace(
|
|
80
|
+
rootPath,
|
|
81
|
+
"packages/lib",
|
|
82
|
+
{ name: "@ex/lib" },
|
|
83
|
+
app("untranspiled-library"),
|
|
84
|
+
);
|
|
85
|
+
for (const [name, pobConfig] of consumers) {
|
|
86
|
+
await writeWorkspace(
|
|
87
|
+
rootPath,
|
|
88
|
+
`packages/${name}`,
|
|
89
|
+
{ name: `@ex/${name}`, dependencies: { "@ex/lib": "workspace:*" } },
|
|
90
|
+
pobConfig,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
return rootPath;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const getLibTarget = async (consumers, { withBun = false } = {}) =>
|
|
97
|
+
getConsumerAppsTsconfigTarget({
|
|
98
|
+
rootPath: await prepareMonorepo(consumers),
|
|
99
|
+
packageName: "@ex/lib",
|
|
100
|
+
withBun,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("getConsumerAppsTsconfigTarget", () => {
|
|
104
|
+
it("uses the target of a consumer expo app", async () => {
|
|
105
|
+
expect(await getLibTarget([["mobile", app("expo")]])).toBe(targets.expo);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("prefers expo when consumer apps disagree", async () => {
|
|
109
|
+
expect(
|
|
110
|
+
await getLibTarget([
|
|
111
|
+
["mobile", app("expo")],
|
|
112
|
+
["web", app("vite")],
|
|
113
|
+
["api", app("node")],
|
|
114
|
+
]),
|
|
115
|
+
).toBe(targets.expo);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("uses the target shared by all consumer apps", async () => {
|
|
119
|
+
expect(
|
|
120
|
+
await getLibTarget([
|
|
121
|
+
["web", app("vite")],
|
|
122
|
+
["api", app("node")],
|
|
123
|
+
]),
|
|
124
|
+
).toBe(targets.vite);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("returns undefined when consumer apps disagree without expo", async () => {
|
|
128
|
+
expect(
|
|
129
|
+
await getLibTarget([
|
|
130
|
+
["web", app("vite")],
|
|
131
|
+
["legacy", app("alp")],
|
|
132
|
+
]),
|
|
133
|
+
).toBe(undefined);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("returns undefined without consumer apps with a target", async () => {
|
|
137
|
+
expect(await getLibTarget([])).toBe(undefined);
|
|
138
|
+
expect(await getLibTarget([["api", app("node")]])).toBe(undefined);
|
|
139
|
+
expect(
|
|
140
|
+
await getLibTarget([["other-lib", { project: { type: "lib" } }]]),
|
|
141
|
+
).toBe(undefined);
|
|
142
|
+
expect(await getLibTarget([["no-config", undefined]])).toBe(undefined);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("uses bun for node consumer apps with bun", async () => {
|
|
146
|
+
expect(await getLibTarget([["api", app("node")]], { withBun: true })).toBe(
|
|
147
|
+
targets.bun,
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("ignores apps depending on the library through another library", async () => {
|
|
152
|
+
const rootPath = await prepareMonorepo([
|
|
153
|
+
["other-lib", app("untranspiled-library")],
|
|
154
|
+
]);
|
|
155
|
+
await writeWorkspace(
|
|
156
|
+
rootPath,
|
|
157
|
+
"packages/mobile",
|
|
158
|
+
{ name: "@ex/mobile", dependencies: { "@ex/other-lib": "workspace:*" } },
|
|
159
|
+
app("expo"),
|
|
160
|
+
);
|
|
161
|
+
expect(
|
|
162
|
+
await getConsumerAppsTsconfigTarget({
|
|
163
|
+
rootPath,
|
|
164
|
+
packageName: "@ex/lib",
|
|
165
|
+
withBun: false,
|
|
166
|
+
}),
|
|
167
|
+
).toBe(undefined);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// node backend apps: their scripts run in the same environment as their
|
|
2
|
+
// sources, so the package tsconfig.json type-checks them too instead of a
|
|
3
|
+
// separate tsconfig.tools.json
|
|
4
|
+
export const nodeAppTypes = ["node", "node-library", "alp-node"];
|
|
5
|
+
|
|
6
|
+
export const isNodeApp = (pobConfig) =>
|
|
7
|
+
pobConfig?.project?.type === "app" &&
|
|
8
|
+
nodeAppTypes.includes(pobConfig.app?.type);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pob",
|
|
3
|
-
"version": "40.
|
|
3
|
+
"version": "40.4.0",
|
|
4
4
|
"description": "Pile of bones, library generator with git/babel/typescript/typedoc/readme/vitest",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"skeleton"
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@pob/codemods": "0.18.2",
|
|
35
|
-
"@pob/eslint-config": "68.
|
|
36
|
-
"@pob/eslint-config-typescript-react": "68.
|
|
35
|
+
"@pob/eslint-config": "68.1.0",
|
|
36
|
+
"@pob/eslint-config-typescript-react": "68.1.0",
|
|
37
37
|
"@pob/sort-object": "11.2.0",
|
|
38
38
|
"@pob/sort-pkg": "13.2.0",
|
|
39
39
|
"@types/inquirer": "9.0.10",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"minimist": "1.2.8",
|
|
52
52
|
"oxfmt": "0.72.0",
|
|
53
53
|
"parse-author": "2.0.0",
|
|
54
|
-
"pob-dependencies": "28.
|
|
54
|
+
"pob-dependencies": "28.3.0",
|
|
55
55
|
"semver": "7.8.5",
|
|
56
56
|
"typescript": "npm:@typescript/typescript6@6.0.2",
|
|
57
57
|
"validate-npm-package-name": "^8.0.0",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"yeoman-generator": "10.3.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@pob/root": "28.
|
|
62
|
+
"@pob/root": "28.3.0",
|
|
63
63
|
"@types/node": "24.19.1",
|
|
64
64
|
"@typescript/native": "npm:typescript@7.0.2",
|
|
65
65
|
"@yeoman/types": "1.13.1",
|