@10stars/config 17.0.2 → 17.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@10stars/config",
3
- "version": "17.0.2",
3
+ "version": "17.0.3",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "config": "./src/index.ts"
@@ -30,7 +30,7 @@
30
30
  "oxfmt": "0.58.0",
31
31
  "oxlint": "^1.73.0",
32
32
  "oxlint-tsgolint": "^0.24.0",
33
- "tsx": "^4.23.0",
33
+ "tsx": "^4.23.1",
34
34
  "type-fest": "^5.8.0",
35
35
  "typescript": "7.0.2"
36
36
  }
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@ import fs from "node:fs/promises"
4
4
  import path from "node:path"
5
5
 
6
6
  import { resetColorIndex } from "./colors"
7
- import { checkIsMonorepo, dedupeSingletons, linkPackages } from "./link-packages"
7
+ import { checkIsMonorepo, dedupeSingletons, linkPackages, linkSelf } from "./link-packages"
8
8
  import { dedupeRemote, linkRemote } from "./link-remote"
9
9
  import { setupOverrides } from "./manage-overrides"
10
10
  import { runConcurrently, type CommandConfig } from "./runner"
@@ -47,6 +47,8 @@ const actions = {
47
47
  action: async () => {
48
48
  const veInstalled = await patchVanillaExtract(cwd) // patch VE before the CI guard: builds (incl. CI) need the `css.ts`-only filter in node_modules
49
49
 
50
+ await linkSelf(cwd) // before the CI guard: CI app builds also self-import `<pkg>/src/…`
51
+
50
52
  if (process.env.CI) return
51
53
 
52
54
  // we must copy it first, because the following steps might have formatting cmds
@@ -1,8 +1,20 @@
1
+ import { execFile } from "node:child_process"
1
2
  import fs from "node:fs/promises"
2
3
  import path from "node:path"
4
+ import { promisify } from "node:util"
3
5
 
4
6
  import type * as TF from "type-fest"
5
7
 
8
+ const execFileAsync = promisify(execFile)
9
+
10
+ // bun materializes a `file:.` self-dep sibling as a dir of self-referential symlinks (name -> name)
11
+ // over a nested node_modules; `fs.rm(recursive)` follows that into an ELOOP and hangs. Coreutils
12
+ // `rm -rf` uses cycle-detecting fts and clears it instantly. Windows keeps fs.rm (force skips ELOOP).
13
+ const removeDir = async (dir: string) => {
14
+ if (process.platform === `win32`) return fs.rm(dir, { recursive: true, force: true })
15
+ await execFileAsync(`rm`, [`-rf`, dir])
16
+ }
17
+
6
18
  const symlink = async (pathTarget: string, pathSymlink: string) => {
7
19
  const stats = await fs.lstat(pathSymlink).catch(() => null)
8
20
  if (!stats) return
@@ -12,12 +24,39 @@ const symlink = async (pathTarget: string, pathSymlink: string) => {
12
24
  if (pathTarget === symlinkRealpath) return
13
25
  }
14
26
 
15
- if (stats.isDirectory()) await fs.rm(pathSymlink, { recursive: true })
27
+ if (stats.isDirectory()) await removeDir(pathSymlink)
16
28
  else await fs.unlink(pathSymlink)
17
29
 
18
30
  await fs.symlink(pathTarget, pathSymlink)
19
31
  }
20
32
 
33
+ /**
34
+ * Create `node_modules/<self-name> -> <repo root>` so a single-package repo can import its own
35
+ * files by package name (`frontend-toolkit/src/…`) at runtime — replacing the old `file:.`
36
+ * self-dependency. A `file:.` self-dep makes bun's file: resolver recurse over the package's whole
37
+ * subtree on every consumer's install (multi-minute CPU spin, then a stack-overflow crash). This
38
+ * runs in postinstall (after `bun install`), so bun never resolves a self-dep, yet every tool
39
+ * (bun/node, Vite, Ladle, Vitest) still resolves self-imports via node_modules. tsconfig `paths`
40
+ * cover the same specifiers for typecheck. Unlike `symlink()` above this CREATES the link when
41
+ * absent (there's no self-dep for `bun install` to have materialized first). Skips workspace roots
42
+ * (their subpackages are separate) and unnamed packages.
43
+ */
44
+ export const linkSelf = async (cwd: string) => {
45
+ const pkgJson: TF.PackageJson = await import(path.join(cwd, `package.json`)).catch(() => null)
46
+ if (!pkgJson?.name || pkgJson.workspaces) return
47
+
48
+ const pathSymlink = path.join(cwd, `node_modules`, pkgJson.name)
49
+ await fs.mkdir(path.dirname(pathSymlink), { recursive: true })
50
+ const relTarget = path.relative(path.dirname(pathSymlink), cwd) // `..` (bare) / `../..` (scoped)
51
+
52
+ const stats = await fs.lstat(pathSymlink).catch(() => null)
53
+ if (stats?.isSymbolicLink() && (await fs.realpath(pathSymlink).catch(() => null)) === cwd) return
54
+ if (stats?.isDirectory()) await removeDir(pathSymlink)
55
+ else if (stats) await fs.unlink(pathSymlink)
56
+
57
+ await fs.symlink(relTarget, pathSymlink)
58
+ }
59
+
21
60
  export const checkIsMonorepo = async (pathPkg: string) => {
22
61
  const pkgJson = await import(path.join(pathPkg, `package.json`))
23
62
  return Boolean(pkgJson.workspaces)