@10stars/config 15.6.0 → 15.6.2
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 +1 -1
- package/src/index.ts +11 -2
- package/src/link-packages.ts +97 -33
- package/src/manage-overrides.ts +6 -0
package/package.json
CHANGED
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, linkPackages } from "./link-packages"
|
|
7
|
+
import { checkIsMonorepo, dedupeSingletons, linkPackages } from "./link-packages"
|
|
8
8
|
import { setupOverrides } from "./manage-overrides"
|
|
9
9
|
import { runConcurrently, type CommandConfig } from "./runner"
|
|
10
10
|
import { setupVSCode } from "./vscode-config"
|
|
@@ -13,6 +13,7 @@ const cwd = process.env.PWD!
|
|
|
13
13
|
|
|
14
14
|
interface Config {
|
|
15
15
|
linkPackages: string[]
|
|
16
|
+
dedupeSingletons?: boolean | string[]
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
const exec = (cmd: string) => {
|
|
@@ -55,7 +56,15 @@ const actions = {
|
|
|
55
56
|
await setupOverrides(cwd) // sync standardized package.json overrides
|
|
56
57
|
|
|
57
58
|
const config = await getConfig()
|
|
58
|
-
if (config?.linkPackages)
|
|
59
|
+
if (config?.linkPackages) {
|
|
60
|
+
const linkedPaths = await linkPackages(cwd, config.linkPackages)
|
|
61
|
+
if (config.dedupeSingletons !== false) {
|
|
62
|
+
const singletons = Array.isArray(config.dedupeSingletons)
|
|
63
|
+
? config.dedupeSingletons
|
|
64
|
+
: undefined
|
|
65
|
+
await dedupeSingletons(cwd, config.linkPackages, linkedPaths, singletons)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
59
68
|
},
|
|
60
69
|
},
|
|
61
70
|
typecheckWatch: {
|
package/src/link-packages.ts
CHANGED
|
@@ -24,6 +24,8 @@ export const checkIsMonorepo = async (pathPkg: string) => {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export const linkPackages = async (cwd: string, packagesToLink: string[]) => {
|
|
27
|
+
const linkedPaths: string[] = []
|
|
28
|
+
|
|
27
29
|
const linkPackage = async (pathPkg: string) => {
|
|
28
30
|
const pkgJson: TF.PackageJson = await import(path.join(pathPkg, `package.json`)).catch(
|
|
29
31
|
() => null,
|
|
@@ -33,42 +35,104 @@ export const linkPackages = async (cwd: string, packagesToLink: string[]) => {
|
|
|
33
35
|
const [orgName, pkgName] = pkgJson.name!.split(`/`)
|
|
34
36
|
|
|
35
37
|
const pathSymlink = path.join(cwd, `node_modules`, orgName, pkgName)
|
|
36
|
-
|
|
38
|
+
await symlink(pathPkg, pathSymlink)
|
|
39
|
+
linkedPaths.push(pathPkg)
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
promises.push(
|
|
43
|
-
checkIsMonorepo(pathPkg)
|
|
44
|
-
.catch(() => null) // relative path might point to a non-existing directory for a particular developer; skip linking a particular package in that case
|
|
45
|
-
.then(async (isMonorepo) => {
|
|
46
|
-
if (!isMonorepo) return promises.push(linkPackage(pathPkg))
|
|
47
|
-
|
|
48
|
-
const files = await fs.readdir(relativePath, { withFileTypes: true }).catch(() => null)
|
|
49
|
-
if (!files) return
|
|
50
|
-
|
|
51
|
-
const directories = files.filter((v) => v.isDirectory()).map((v) => v.name)
|
|
52
|
-
const packages = (
|
|
53
|
-
await Promise.all(
|
|
54
|
-
directories.map(async (v) => {
|
|
55
|
-
const pathPkg = path.join(cwd, relativePath, v)
|
|
56
|
-
const pkgJson: TF.PackageJson = await import(
|
|
57
|
-
path.join(pathPkg, `package.json`)
|
|
58
|
-
).catch(() => null)
|
|
59
|
-
if (pkgJson) return v
|
|
60
|
-
}),
|
|
61
|
-
)
|
|
62
|
-
).filter(Boolean)
|
|
63
|
-
for (const pkg of packages) {
|
|
64
|
-
const relativePathToPkg = path.join(cwd, relativePath, pkg!)
|
|
65
|
-
promises.push(linkPackage(relativePathToPkg))
|
|
66
|
-
}
|
|
67
|
-
}),
|
|
68
|
-
)
|
|
69
|
-
}
|
|
42
|
+
await Promise.all(
|
|
43
|
+
packagesToLink.map(async (relativePath) => {
|
|
44
|
+
const pathPkg = path.join(cwd, relativePath)
|
|
70
45
|
|
|
71
|
-
|
|
46
|
+
// relative path might point to a non-existing directory for a particular developer; skip linking that package in that case
|
|
47
|
+
const isMonorepo = await checkIsMonorepo(pathPkg).catch(() => null)
|
|
48
|
+
if (!isMonorepo) return linkPackage(pathPkg)
|
|
49
|
+
|
|
50
|
+
const files = await fs.readdir(relativePath, { withFileTypes: true }).catch(() => null)
|
|
51
|
+
if (!files) return
|
|
52
|
+
|
|
53
|
+
const directories = files.filter((v) => v.isDirectory()).map((v) => v.name)
|
|
54
|
+
await Promise.all(directories.map((dir) => linkPackage(path.join(cwd, relativePath, dir))))
|
|
55
|
+
}),
|
|
56
|
+
)
|
|
72
57
|
|
|
73
58
|
console.info(`Successfully linked local packages!`)
|
|
59
|
+
|
|
60
|
+
return linkedPaths
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const DEFAULT_SINGLETONS = [`@types/react`, `@types/react-dom`]
|
|
64
|
+
|
|
65
|
+
const readPkgVersion = async (pathPkg: string): Promise<string | null> => {
|
|
66
|
+
const pkgJson: TF.PackageJson = await import(path.join(pathPkg, `package.json`)).catch(() => null)
|
|
67
|
+
return pkgJson?.version ?? null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const majorOf = (version: string) => version.split(`.`)[0]
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Collapse React type singletons to one physical copy for locally-linked graphs.
|
|
74
|
+
*
|
|
75
|
+
* tsgo resolves symlinked deps at their realpath and ignores the consumer's tsconfig
|
|
76
|
+
* `paths`, so multiple physical `@types/react` copies become distinct type identities.
|
|
77
|
+
* Pointing every copy at the workspace-root copy replicates a hoisted monorepo (one type
|
|
78
|
+
* identity). Best-effort and idempotent: a plain `bun install` re-materializes real dirs and
|
|
79
|
+
* the next `prepare` re-establishes the symlinks.
|
|
80
|
+
*/
|
|
81
|
+
export const dedupeSingletons = async (
|
|
82
|
+
cwd: string,
|
|
83
|
+
packagesToLink: string[],
|
|
84
|
+
linkedPaths: string[],
|
|
85
|
+
singletons: string[] = DEFAULT_SINGLETONS,
|
|
86
|
+
) => {
|
|
87
|
+
// siblings share cwd's parent; the formula reduces to path.dirname(cwd) for `../pkg` entries
|
|
88
|
+
const roots = new Set(packagesToLink.map((rel) => path.resolve(cwd, rel, `..`)))
|
|
89
|
+
const workspaceRoot = roots.size === 1 ? [...roots][0]! : path.dirname(cwd)
|
|
90
|
+
|
|
91
|
+
for (const pkg of singletons) {
|
|
92
|
+
const canonical = path.join(workspaceRoot, `node_modules`, pkg)
|
|
93
|
+
const canonicalVersion = await readPkgVersion(canonical)
|
|
94
|
+
if (!canonicalVersion) {
|
|
95
|
+
console.warn(`Dedupe ${pkg}: canonical copy not found at ${canonical}; skipping`)
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
const canonicalReal = await fs.realpath(canonical).catch(() => canonical)
|
|
99
|
+
|
|
100
|
+
const targets = [
|
|
101
|
+
path.join(cwd, `node_modules`, pkg),
|
|
102
|
+
...linkedPaths.map((pathPkg) => path.join(pathPkg, `node_modules`, pkg)),
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
let relinked = 0
|
|
106
|
+
let skipped = 0
|
|
107
|
+
|
|
108
|
+
for (const target of targets) {
|
|
109
|
+
if (path.resolve(target) === path.resolve(canonical)) continue
|
|
110
|
+
|
|
111
|
+
const stats = await fs.lstat(target).catch(() => null)
|
|
112
|
+
if (!stats) continue
|
|
113
|
+
|
|
114
|
+
if (stats.isSymbolicLink()) {
|
|
115
|
+
const real = await fs.realpath(target).catch(() => null)
|
|
116
|
+
if (real === canonicalReal) continue // already deduped
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const targetVersion = await readPkgVersion(target)
|
|
120
|
+
if (!targetVersion) continue
|
|
121
|
+
|
|
122
|
+
if (majorOf(targetVersion) !== majorOf(canonicalVersion)) {
|
|
123
|
+
skipped++
|
|
124
|
+
console.warn(
|
|
125
|
+
`Dedupe ${pkg}: skipping ${target} (v${targetVersion}); major differs from canonical v${canonicalVersion}`,
|
|
126
|
+
)
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await symlink(canonicalReal, target)
|
|
131
|
+
relinked++
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.info(
|
|
135
|
+
`Dedupe ${pkg}@${canonicalVersion}: ${relinked} copy(ies) relinked, ${skipped} skipped`,
|
|
136
|
+
)
|
|
137
|
+
}
|
|
74
138
|
}
|
package/src/manage-overrides.ts
CHANGED
|
@@ -51,9 +51,15 @@ export const setupOverrides = async (cwd: string) => {
|
|
|
51
51
|
const merged = { ...pkgJson.overrides, ...managedOverrides }
|
|
52
52
|
const sorted = Object.fromEntries(Object.entries(merged).sort(([a], [b]) => a.localeCompare(b)))
|
|
53
53
|
|
|
54
|
+
const changed = JSON.stringify(pkgJson.overrides ?? {}) !== JSON.stringify(sorted)
|
|
55
|
+
if (!changed) return
|
|
56
|
+
|
|
54
57
|
pkgJson.overrides = sorted
|
|
55
58
|
|
|
56
59
|
await fs.writeFile(pkgPath, JSON.stringify(pkgJson, null, 2) + `\n`)
|
|
57
60
|
console.info(`Written: ${pkgPath}`)
|
|
58
61
|
execSync(`bunx oxfmt ${pkgPath}`, { cwd, stdio: `inherit` })
|
|
62
|
+
|
|
63
|
+
console.info(`Overrides changed, running "bun install"...`)
|
|
64
|
+
execSync(`bun install`, { cwd, stdio: `inherit` })
|
|
59
65
|
}
|