pob 40.1.0 → 40.2.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.
@@ -5,6 +5,7 @@ import { removeFromGitIndex } from "../../utils/git.js";
5
5
  import inMonorepo from "../../utils/inMonorepo.js";
6
6
  import removeLegacyGeneratedDocs from "../../utils/legacyGeneratedDocs.js";
7
7
  import * as packageUtils from "../../utils/package.js";
8
+ import { detectStorybookReactNative } from "../../utils/storybook.js";
8
9
  import { appIgnorePaths } from "./ignorePaths.js";
9
10
 
10
11
  const appsWithTypescript = [
@@ -95,6 +96,13 @@ export default class PobAppGenerator extends Generator {
95
96
  delete config.ci;
96
97
  }
97
98
 
99
+ const storybookDefault = detectStorybookReactNative([
100
+ this.fs.readJSON(this.destinationPath("package.json"), {}),
101
+ ]);
102
+ if (config?.type === "expo" && config.storybook === undefined) {
103
+ config.storybook = storybookDefault;
104
+ }
105
+
98
106
  if (config && this.options.updateOnly) {
99
107
  this.config.set("app", this.appConfig);
100
108
  this.config.save();
@@ -151,7 +159,7 @@ export default class PobAppGenerator extends Generator {
151
159
  type: "confirm",
152
160
  name: "storybook",
153
161
  message: "Do you want storybook ?",
154
- default: (config && config.storybook) || false,
162
+ default: config?.storybook ?? storybookDefault,
155
163
  when: (values) => values.type === "expo",
156
164
  },
157
165
  ]);
@@ -315,7 +323,8 @@ export default class PobAppGenerator extends Generator {
315
323
  this.composeWith("pob:common:format-lint", {
316
324
  isApp: true,
317
325
  documentation: false,
318
- storybook: pkg?.devDependencies?.storybook,
326
+ // only asked for expo apps: the on-device react-native storybook
327
+ storybookReactNative: this.appConfig.storybook,
319
328
  testing: this.appConfig.testing,
320
329
  testRunner: this.appConfig.testRunner,
321
330
  babel,
@@ -5,6 +5,7 @@ import { runTransform } from "@pob/codemods";
5
5
  import { format } from "oxfmt";
6
6
  import semver from "semver";
7
7
  import Generator from "yeoman-generator";
8
+ import hasDesignSync from "../../../utils/designSync.js";
8
9
  import { quoteArg } from "../../../utils/execUtils.js";
9
10
  import inMonorepo from "../../../utils/inMonorepo.js";
10
11
  import * as packageUtils from "../../../utils/package.js";
@@ -160,6 +161,13 @@ export default class CommonFormatLintGenerator extends Generator {
160
161
  description: "Enable storybook",
161
162
  });
162
163
 
164
+ this.option("storybookReactNative", {
165
+ type: Boolean,
166
+ required: false,
167
+ default: false,
168
+ description: "Enable the on-device react-native storybook",
169
+ });
170
+
163
171
  this.option("rootAsSrc", {
164
172
  type: Boolean,
165
173
  required: false,
@@ -566,6 +574,20 @@ export default class CommonFormatLintGenerator extends Generator {
566
574
  ignorePatterns.add("/rollup.config.mjs");
567
575
  }
568
576
 
577
+ if (this.options.storybook) {
578
+ ignorePatterns.add("**/.storybook/");
579
+ ignorePatterns.add("**/storybook-static/");
580
+ }
581
+ if (this.options.storybookReactNative) {
582
+ ignorePatterns.add("**/.rnstorybook/");
583
+ }
584
+
585
+ if (hasDesignSync(this)) {
586
+ ignorePatterns.add("/.design-sync/");
587
+ ignorePatterns.add("/.ds-sync/");
588
+ ignorePatterns.add("/ds-bundle/");
589
+ }
590
+
569
591
  if (rootIgnorePaths) {
570
592
  rootIgnorePaths.forEach((ignorePath) => {
571
593
  if (ignorePath.startsWith("#")) return;
@@ -703,7 +725,7 @@ export default class CommonFormatLintGenerator extends Generator {
703
725
  ? `${packageManagerExecWithCwd(this.options.packageManager, "../..", "oxlint")} ${args} ${quoteArg(path.relative("../..", "."))}`
704
726
  : `oxlint ${args}`,
705
727
  lint: `${packageManagerRun(this.options.packageManager, "lint:oxlint")} && ${
706
- useTypescript && !composite ? "tsc && " : ""
728
+ useTypescript && !composite ? "tsrs && " : ""
707
729
  }${packageManagerRun(this.options.packageManager, "lint:eslint")}`,
708
730
  });
709
731
 
@@ -178,7 +178,7 @@ export default class CommonTranspilerGenerator extends Generator {
178
178
  if (bundler && bundler.startsWith("rollup")) {
179
179
  return `${packageManagerRun(this.options.packageManager, "clean:build")} && rollup --config rollup.config.mjs --watch`;
180
180
  }
181
- if (bundler === "tsc") return "tsc --watch";
181
+ if (bundler === "tsc") return "tsrs --watch";
182
182
  if (bundler === "esbuild") return "pob-esbuild-watch";
183
183
  })(),
184
184
  });
@@ -194,7 +194,7 @@ export default class CommonTranspilerGenerator extends Generator {
194
194
  if (bundler && bundler.startsWith("rollup")) {
195
195
  return `${packageManagerRun(this.options.packageManager, "clean:build")} && rollup --config rollup.config.mjs`;
196
196
  }
197
- if (bundler === "tsc") return "tsc";
197
+ if (bundler === "tsc") return "tsrs";
198
198
  if (bundler === "esbuild") return "pob-esbuild-build";
199
199
  })(),
200
200
  });
@@ -203,7 +203,7 @@ export default class CommonTranspilerGenerator extends Generator {
203
203
  !this.options.isApp && withTypescript && bundler !== "tsc";
204
204
 
205
205
  packageUtils.addOrRemoveScripts(pkg, shouldBuildDefinitions, {
206
- "build:definitions": "tsc -p tsconfig.json",
206
+ "build:definitions": "tsrs -p tsconfig.json",
207
207
  });
208
208
 
209
209
  if (shouldBuildDefinitions) {
@@ -218,7 +218,7 @@ export default class CommonTranspilerGenerator extends Generator {
218
218
  if (this.fs.exists(this.destinationPath("lib/index.d.ts"))) {
219
219
  packageUtils.addScripts(pkg, {
220
220
  "build:definitions":
221
- "tsc --lib esnext --noEmit --skipLibCheck ./lib/index.d.ts",
221
+ "tsrs --lib esnext --noEmit --skipLibCheck ./lib/index.d.ts",
222
222
  build: packageManagerRun(
223
223
  this.options.packageManager,
224
224
  "build:definitions",
@@ -229,7 +229,7 @@ export default class CommonTranspilerGenerator extends Generator {
229
229
  if (this.fs.exists(this.destinationPath("lib/index.ts"))) {
230
230
  packageUtils.addScripts(pkg, {
231
231
  "build:definitions":
232
- "tsc --lib esnext --noEmit --skipLibCheck ./lib/index.ts",
232
+ "tsrs --lib esnext --noEmit --skipLibCheck ./lib/index.ts",
233
233
  build: packageManagerRun(
234
234
  this.options.packageManager,
235
235
  "build:definitions",
@@ -740,7 +740,9 @@ export default class CommonTranspilerGenerator extends Generator {
740
740
  }
741
741
 
742
742
  end() {
743
- if (this.bundler) {
743
+ // when updated from the monorepo root, dependencies are not installed yet:
744
+ // the root builds after installing
745
+ if (this.bundler && !this.options.fromPob) {
744
746
  this.spawnCommandSync(
745
747
  packageManagerRun(this.options.packageManager, "build"),
746
748
  );
@@ -222,12 +222,13 @@ export default class CommonTypescriptGenerator extends Generator {
222
222
 
223
223
  // "typescript" is aliased to the typescript 6 api package, still required by
224
224
  // typescript-eslint and typedoc. "@typescript/native" is typescript 7, which
225
- // provides the "tsc" binary.
225
+ // provides the "tsc" binary, kept for the "tsc:go" script. "tsrs" is the
226
+ // rust port of typescript 7, used by all the other scripts.
226
227
  packageUtils.addOrRemoveDevDependencies(
227
228
  pkg,
228
229
  this.options.enable ||
229
230
  this.fs.exists(this.destinationPath("lib/index.d.ts")),
230
- ["typescript", "@typescript/native"],
231
+ ["typescript", "@typescript/native", "tsrs"],
231
232
  );
232
233
 
233
234
  const tsconfigPath = this.destinationPath("tsconfig.json");
@@ -401,11 +402,15 @@ export default class CommonTypescriptGenerator extends Generator {
401
402
  }
402
403
 
403
404
  if (!pkg.workspaces || !this.options.enable) {
405
+ // tsrs and tsc do not share their .tsbuildinfo: switching between them
406
+ // triggers a full rebuild
407
+ const tscCommand = (compiler) =>
408
+ inMonorepo || !this.options.enable
409
+ ? compiler
410
+ : `${compiler} && ${compiler} -p tsconfig.tools.json`;
404
411
  packageUtils.addOrRemoveScripts(pkg, this.options.enable, {
405
- tsc:
406
- inMonorepo || !this.options.enable
407
- ? "tsc"
408
- : "tsc && tsc -p tsconfig.tools.json",
412
+ tsc: tscCommand("tsrs"),
413
+ "tsc:go": tscCommand("tsc"),
409
414
  });
410
415
  }
411
416
 
@@ -1,4 +1,5 @@
1
1
  import Generator from "yeoman-generator";
2
+ import hasDesignSync from "../../../utils/designSync.js";
2
3
 
3
4
  export default class CoreGitignoreGenerator extends Generator {
4
5
  constructor(args, opts) {
@@ -82,6 +83,7 @@ export default class CoreGitignoreGenerator extends Generator {
82
83
  typescript: withBabel || this.options.typescript,
83
84
  paths: this.options.paths,
84
85
  playwright: this.options.playwright,
86
+ designSync: hasDesignSync(this),
85
87
  });
86
88
  }
87
89
 
@@ -44,6 +44,12 @@ yarn-error.log*
44
44
  /test-results
45
45
  /playwright-report
46
46
  <% } -%>
47
+ <% if (designSync) { -%>
48
+
49
+ # Claude Design sync
50
+ /.ds-sync/
51
+ /ds-bundle/
52
+ <% } -%>
47
53
 
48
54
  #*
49
55
  *$
@@ -69,6 +69,24 @@ export default class CoreVSCodeGenerator extends Generator {
69
69
  async writing() {
70
70
  if (this.options.root) {
71
71
  const pkg = this.fs.readJSON(this.destinationPath("package.json"));
72
+ // the tsdk script runs tsrs from node_modules, which pnp does not have
73
+ const withTsrs =
74
+ this.options.typescript && this.options.yarnNodeLinker !== "pnp";
75
+ const tsdkPath = this.destinationPath(".vscode/tsrs-tsdk/tsgo");
76
+
77
+ if (withTsrs) {
78
+ // the extension runs it directly, so it must be executable
79
+ this.fs.write(
80
+ tsdkPath,
81
+ this.fs.read(this.templatePath("tsrs-tsdk-tsgo")),
82
+ {
83
+ stat: /** @type {import("node:fs").Stats} */ ({ mode: 0o755 }),
84
+ },
85
+ );
86
+ } else {
87
+ this.fs.delete(tsdkPath);
88
+ }
89
+
72
90
  await copyAndFormatTpl(
73
91
  this.fs,
74
92
  this.templatePath("extensions.json.ejs"),
@@ -90,6 +108,7 @@ export default class CoreVSCodeGenerator extends Generator {
90
108
  bun: this.options.packageManager === "bun",
91
109
  pnpm: this.options.packageManager === "pnpm",
92
110
  typescript: this.options.typescript,
111
+ tsrs: withTsrs,
93
112
  testing: this.options.testing,
94
113
  testRunner: this.options.testRunner,
95
114
  module: pkg.type === "module",
@@ -17,6 +17,16 @@
17
17
  // typescript 7 is provided by the TypeScriptTeam.native-preview extension.
18
18
  // "typescript" resolves to the typescript 6 api package, which has no
19
19
  // tsserver, so it cannot be used as "typescript.tsdk".
20
+ <% if (tsrs) { -%>
21
+ // The extension runs tsrs, the same compiler as the "tsc" script, through
22
+ // .vscode/tsrs-tsdk/tsgo once its "TypeScript 7 tsdk" prompt is allowed.
23
+ // Without the prompt, use "Select TypeScript Version…", which offers the
24
+ // "additionalLocations" entry.
25
+ "js/ts.experimental.useTsgo": true,
26
+ "js/ts.tsdk.path": ".vscode/tsrs-tsdk",
27
+ "js/ts.tsdk.additionalLocations": [".vscode/tsrs-tsdk"],
28
+ "js/ts.enablePromptUseWorkspaceTsdk": true,
29
+ <% } -%>
20
30
  <% } -%>
21
31
 
22
32
  <% if (yarn) { -%>
@@ -0,0 +1,9 @@
1
+ #!/bin/sh
2
+ # Generated by pob: do not edit, it is overwritten on each pob run.
3
+ # "js/ts.tsdk.path" points to this directory, so that the TypeScript 7
4
+ # extension (TypeScriptTeam.native-preview) runs the project's tsrs instead of
5
+ # its bundled typescript 7. The extension silently falls back to the bundled
6
+ # one when this fails: tsrs not installed, no tsrs binary for the platform
7
+ # (Windows, Intel macOS) or node not in VS Code's PATH.
8
+ # Check with: ps -axo command | grep -- '--lsp --stdio'
9
+ exec node "$(dirname "$0")/../../node_modules/tsrs/bin/tsrs" "$@"
@@ -365,7 +365,6 @@ export default class PobLibGenerator extends Generator {
365
365
  documentation:
366
366
  !!this.pobjson.documentation ||
367
367
  !!(this.pobjson.testing && this.pobjson.testing.codecov),
368
- storybook: pkg?.devDependencies?.storybook,
369
368
  testing: !!this.pobjson.testing,
370
369
  testRunner: inMonorepo
371
370
  ? inMonorepo.pobMonorepoConfig.testRunner
@@ -9,6 +9,10 @@ import {
9
9
  workspacesRun,
10
10
  workspacesRunSequential,
11
11
  } from "../../utils/packageManagerWorkspacesUtils.js";
12
+ import {
13
+ detectStorybook,
14
+ detectStorybookReactNative,
15
+ } from "../../utils/storybook.js";
12
16
  import {
13
17
  buildDependenciesMaps,
14
18
  buildTopologicalOrderBatches,
@@ -152,7 +156,16 @@ export default class PobMonorepoGenerator extends Generator {
152
156
  }
153
157
 
154
158
  async prompting() {
155
- const config = this.config.get("monorepo");
159
+ const storedConfig = this.config.get("monorepo");
160
+ const allPackages = [
161
+ this.fs.readJSON(this.destinationPath("package.json"), {}),
162
+ ...this.packages,
163
+ ];
164
+ const config = storedConfig && {
165
+ storybook: detectStorybook(allPackages),
166
+ storybookReactNative: detectStorybookReactNative(allPackages),
167
+ ...storedConfig,
168
+ };
156
169
 
157
170
  if (this.options.updateOnly && config) {
158
171
  this.pobMonorepoConfig = config;
@@ -232,6 +245,20 @@ export default class PobMonorepoGenerator extends Generator {
232
245
  message: "Would you like eslint in monorepo ?",
233
246
  default: config ? config.eslint : true,
234
247
  },
248
+ {
249
+ type: "confirm",
250
+ name: "storybook",
251
+ message: "Would you like storybook ?",
252
+ default: config ? config.storybook : detectStorybook(allPackages),
253
+ },
254
+ {
255
+ type: "confirm",
256
+ name: "storybookReactNative",
257
+ message: "Would you like the on-device react-native storybook ?",
258
+ default: config
259
+ ? config.storybookReactNative
260
+ : detectStorybookReactNative(allPackages),
261
+ },
235
262
  ]);
236
263
  this.pobMonorepoConfig.packageNames = this.packageNames;
237
264
  this.config.set("monorepo", this.pobMonorepoConfig);
@@ -320,7 +347,8 @@ export default class PobMonorepoGenerator extends Generator {
320
347
  this.composeWith("pob:common:format-lint", {
321
348
  monorepo: true,
322
349
  documentation: this.pobMonorepoConfig.documentation,
323
- storybook: pkg?.devDependencies?.storybook,
350
+ storybook: this.pobMonorepoConfig.storybook,
351
+ storybookReactNative: this.pobMonorepoConfig.storybookReactNative,
324
352
  typescript: this.pobMonorepoConfig.typescript,
325
353
  build: hasRootBuild,
326
354
  testing: this.pobMonorepoConfig.testing,
@@ -101,22 +101,26 @@ export default class MonorepoTypescriptGenerator extends Generator {
101
101
 
102
102
  // "typescript" is aliased to the typescript 6 api package, still required by
103
103
  // typescript-eslint and typedoc. "@typescript/native" is typescript 7, which
104
- // provides the "tsc" binary.
104
+ // provides the "tsc" binary, kept for the "tsc:go" script. "tsrs" is the
105
+ // rust port of typescript 7, used by all the other scripts.
105
106
  packageUtils.addOrRemoveDevDependencies(
106
107
  pkg,
107
108
  pkg.name === "@pob/eslint-config-monorepo" || this.options.enable,
108
- ["typescript", "@typescript/native"],
109
+ ["typescript", "@typescript/native", "tsrs"],
109
110
  );
110
111
 
111
112
  if (this.options.enable) {
113
+ // tsrs and tsc do not share their .tsbuildinfo: switching between them
114
+ // triggers a full rebuild
112
115
  packageUtils.addOrRemoveScripts(pkg, this.#hasTypescriptProjects(), {
113
- tsc: "tsc -b",
116
+ tsc: "tsrs -b",
117
+ "tsc:go": "tsc -b",
114
118
  });
115
119
  packageUtils.addOrRemoveScripts(
116
120
  pkg,
117
121
  !this.options.isAppProject && !this.options.checkOnly,
118
122
  {
119
- "build:definitions": "tsc -b",
123
+ "build:definitions": "tsrs -b",
120
124
  },
121
125
  );
122
126
 
@@ -134,6 +138,7 @@ export default class MonorepoTypescriptGenerator extends Generator {
134
138
  }
135
139
  } else if (pkg.scripts) {
136
140
  delete pkg.scripts.tsc;
141
+ delete pkg.scripts["tsc:go"];
137
142
  if (
138
143
  pkg.scripts.postbuild === "tsc -b tsconfig.build.json" ||
139
144
  pkg.scripts.postbuild === "tsc -b"
@@ -186,7 +186,11 @@ export default class MonorepoWorkspacesGenerator extends Generator {
186
186
  );
187
187
  }
188
188
 
189
- end() {
189
+ // packages are updated in the install phase, after root files are written and
190
+ // before the root install of the end phase (see CorePnpmGenerator,
191
+ // CoreYarnGenerator, CoreBunGenerator), so the lockfile includes the
192
+ // dependencies updated in packages
193
+ install() {
190
194
  this.packagePaths.forEach((packagePath) => {
191
195
  if (
192
196
  !existsSync(`${packagePath}/.yo-rc.json`) &&
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Whether the project uses Claude Design sync: its tracked config lives in
3
+ * ".design-sync", next to the untracked ".ds-sync" cache and "ds-bundle"
4
+ * build output.
5
+ *
6
+ * @param {import("yeoman-generator").default} generator
7
+ */
8
+ export default function hasDesignSync(generator) {
9
+ return generator.fs.exists(
10
+ generator.destinationPath(".design-sync/config.json"),
11
+ );
12
+ }
@@ -0,0 +1,26 @@
1
+ // Fallbacks for the storybook options of a config missing them.
2
+
3
+ /**
4
+ * @param {Record<string, any>[]} pkgs
5
+ * @param {(name: string) => boolean} test
6
+ */
7
+ const someDependency = (pkgs, test) =>
8
+ pkgs.some((pkg) =>
9
+ [pkg.dependencies, pkg.devDependencies].some(
10
+ (dependencies) => dependencies && Object.keys(dependencies).some(test),
11
+ ),
12
+ );
13
+
14
+ // a web storybook depends on its framework (@storybook/react-vite,
15
+ // @storybook/nextjs...): "storybook" alone is also a dependency of the
16
+ // react-native one
17
+ const webFrameworkRegExp =
18
+ /^@storybook\/(?:.+-(?:vite|webpack5)|nextjs|angular|ember|sveltekit)$/;
19
+
20
+ /** @param {Record<string, any>[]} pkgs */
21
+ export const detectStorybook = (pkgs) =>
22
+ someDependency(pkgs, (name) => webFrameworkRegExp.test(name));
23
+
24
+ /** @param {Record<string, any>[]} pkgs */
25
+ export const detectStorybookReactNative = (pkgs) =>
26
+ someDependency(pkgs, (name) => name === "@storybook/react-native");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pob",
3
- "version": "40.1.0",
3
+ "version": "40.2.0",
4
4
  "description": "Pile of bones, library generator with git/babel/typescript/typedoc/readme/vitest",
5
5
  "keywords": [
6
6
  "skeleton"
@@ -31,7 +31,7 @@
31
31
  "./package.json": "./package.json"
32
32
  },
33
33
  "dependencies": {
34
- "@pob/codemods": "0.18.0",
34
+ "@pob/codemods": "0.18.1",
35
35
  "@pob/eslint-config": "68.0.0",
36
36
  "@pob/eslint-config-typescript-react": "68.0.0",
37
37
  "@pob/sort-object": "11.2.0",
@@ -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.1.0",
54
+ "pob-dependencies": "28.2.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,10 +59,11 @@
59
59
  "yeoman-generator": "10.0.1"
60
60
  },
61
61
  "devDependencies": {
62
- "@pob/root": "28.1.0",
62
+ "@pob/root": "28.2.0",
63
63
  "@types/node": "24.19.1",
64
64
  "@typescript/native": "npm:typescript@7.0.2",
65
- "@yeoman/types": "1.13.1"
65
+ "@yeoman/types": "1.13.1",
66
+ "tsrs": "0.18.0-ts7.1.0-dev.20260929"
66
67
  },
67
68
  "engines": {
68
69
  "node": ">=24.14.1"
@@ -72,10 +73,11 @@
72
73
  },
73
74
  "scripts": {
74
75
  "build": "pnpm run build:definitions",
75
- "build:definitions": "tsc -p tsconfig.json",
76
+ "build:definitions": "tsrs -p tsconfig.json",
76
77
  "lint": "pnpm run lint:oxlint && pnpm run lint:eslint",
77
78
  "lint:eslint": "pnpm --dir '../..' exec eslint --quiet 'packages/pob'",
78
79
  "lint:oxlint": "pnpm --dir '../..' exec oxlint --quiet 'packages/pob'",
79
- "tsc": "tsc"
80
+ "tsc": "tsrs",
81
+ "tsc:go": "tsc"
80
82
  }
81
83
  }