@10stars/config 17.0.1 → 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 +2 -2
- package/src/index.ts +3 -1
- package/src/link-packages.ts +40 -1
- package/src/link-remote.ts +38 -26
- package/src/vanilla-extract/patch-vanilla-extract.ts +18 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@10stars/config",
|
|
3
|
-
"version": "17.0.
|
|
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.
|
|
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
|
package/src/link-packages.ts
CHANGED
|
@@ -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
|
|
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)
|
package/src/link-remote.ts
CHANGED
|
@@ -126,23 +126,15 @@ const bfsSiblings = async (cwd: string, visit: (sib: string) => Promise<void>):
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
|
-
*
|
|
130
|
-
* `ls-remote
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* as "No branch found".
|
|
129
|
+
* The first of `candidates` (priority order) that exists as a branch on the remote, else null.
|
|
130
|
+
* Queries only those specific refs in one `ls-remote` — no full head listing. A non-zero exit means
|
|
131
|
+
* the repo is unreadable (private + missing/unauthorized clone token → "Repository not found") —
|
|
132
|
+
* surfaced immediately rather than masquerading as a missing branch.
|
|
134
133
|
*/
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
process.env.GITHUB_HEAD_REF, // pull_request events
|
|
138
|
-
process.env.GITHUB_REF_NAME, // push events
|
|
139
|
-
`develop`,
|
|
140
|
-
`production`,
|
|
141
|
-
].filter((v): v is string => Boolean(v))
|
|
142
|
-
|
|
143
|
-
let heads: string
|
|
134
|
+
const matchRemoteBranch = (url: string, candidates: string[]): string | null => {
|
|
135
|
+
let out: string
|
|
144
136
|
try {
|
|
145
|
-
|
|
137
|
+
out = git([`ls-remote`, url, ...candidates.map((c) => `refs/heads/${c}`)])
|
|
146
138
|
} catch (err) {
|
|
147
139
|
const stderr = (err as { stderr?: string | Buffer })?.stderr
|
|
148
140
|
throw new Error(
|
|
@@ -151,18 +143,41 @@ const resolveRef = (url: string, repo: string): string => {
|
|
|
151
143
|
`with contents:read.\n${(stderr ?? (err as Error)?.message ?? ``).toString().trim()}`,
|
|
152
144
|
)
|
|
153
145
|
}
|
|
154
|
-
|
|
155
|
-
// Each line is "<sha>\trefs/heads/<branch>"; a branch may itself contain slashes (e.g. alice/x).
|
|
146
|
+
// Lines are "<sha>\trefs/heads/<branch>"; a branch may itself contain slashes (e.g. alice/x).
|
|
156
147
|
const present = new Set(
|
|
157
|
-
|
|
148
|
+
out
|
|
158
149
|
.split(`\n`)
|
|
159
150
|
.map((line) => line.split(`refs/heads/`)[1])
|
|
160
151
|
.filter(Boolean),
|
|
161
152
|
)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
153
|
+
return candidates.find((c) => present.has(c)) ?? null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Clone a missing sibling as a flat peer, returning the branch it landed on. Prefers a branch
|
|
158
|
+
* matching the consumer's coordinated branch (PR head, then push branch) when the sibling has one;
|
|
159
|
+
* otherwise clones the sibling's own default branch directly. A default-branch clone needs no
|
|
160
|
+
* `--branch`, so the `ls-remote` probe is skipped entirely when no coordinated branch is set — one
|
|
161
|
+
* fewer round-trip for local/manual runs (in GitHub Actions GITHUB_REF_NAME is always set).
|
|
162
|
+
*/
|
|
163
|
+
const cloneSibling = (url: string, repo: string, sib: string, dest: string): string | null => {
|
|
164
|
+
const coordinated = [
|
|
165
|
+
process.env.GITHUB_HEAD_REF, // pull_request events
|
|
166
|
+
process.env.GITHUB_REF_NAME, // push events
|
|
167
|
+
].filter((v): v is string => Boolean(v))
|
|
168
|
+
const ref = coordinated.length ? matchRemoteBranch(url, coordinated) : null
|
|
169
|
+
|
|
170
|
+
console.info(`linkRemote: cloning ${repo}@${ref ?? `(default)`} -> ${dest}`)
|
|
171
|
+
gitInherit([
|
|
172
|
+
`clone`,
|
|
173
|
+
`--depth`,
|
|
174
|
+
`1`,
|
|
175
|
+
`--single-branch`,
|
|
176
|
+
...(ref ? [`--branch`, ref] : []),
|
|
177
|
+
url,
|
|
178
|
+
sib,
|
|
179
|
+
])
|
|
180
|
+
return ref ?? currentRef(sib)
|
|
166
181
|
}
|
|
167
182
|
|
|
168
183
|
const headSha = (dir: string): string | null => tryGit([`rev-parse`, `HEAD`], dir)
|
|
@@ -194,10 +209,7 @@ const cloneClosureBranchFirst = async (cwd: string): Promise<Sibling[]> => {
|
|
|
194
209
|
ref = currentRef(sib)
|
|
195
210
|
} else {
|
|
196
211
|
const repo = path.basename(sib)
|
|
197
|
-
|
|
198
|
-
ref = resolveRef(url, repo)
|
|
199
|
-
console.info(`linkRemote: cloning ${repo}@${ref} -> ${path.relative(cwd, sib)}`)
|
|
200
|
-
gitInherit([`clone`, `--depth`, `1`, `--single-branch`, `--branch`, ref, url, sib])
|
|
212
|
+
ref = cloneSibling(repoUrl(repo), repo, sib, path.relative(cwd, sib))
|
|
201
213
|
}
|
|
202
214
|
order.push({ abs: sib, ref })
|
|
203
215
|
})
|
|
@@ -22,15 +22,29 @@ const PATCHED = String.raw`/(?:^|[/\\])css\.(js|cjs|mjs|jsx|ts|tsx)(\?used)?$/`
|
|
|
22
22
|
*/
|
|
23
23
|
export const patchVanillaExtract = async (cwd: string): Promise<boolean> => {
|
|
24
24
|
const nodeModules = path.join(cwd, `node_modules`)
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
// Recursively collect `@vanilla-extract/integration/dist/*.js` WITHOUT following directory
|
|
27
|
+
// symlinks. A `file:` self-dep (e.g. frontend-toolkit's `frontend-toolkit: file:.` creates
|
|
28
|
+
// node_modules/frontend-toolkit -> .) makes symlink-following recurse forever — Bun.Glob does,
|
|
29
|
+
// even with `followSymlinks: false`. A linked sibling patches its own VE during its own install,
|
|
30
|
+
// and this repo's own copy is hoisted as a real dir, so real-dir-only recursion is sufficient.
|
|
31
|
+
const integrationDist = /[/\\]@vanilla-extract[/\\]integration[/\\]dist[/\\][^/\\]+\.js$/
|
|
32
|
+
const findFiles = async (dir: string, out: string[] = []): Promise<string[]> => {
|
|
33
|
+
for (const entry of await fs.readdir(dir, { withFileTypes: true }).catch(() => [])) {
|
|
34
|
+
if (entry.isSymbolicLink()) continue
|
|
35
|
+
const full = path.join(dir, entry.name)
|
|
36
|
+
if (entry.isDirectory()) await findFiles(full, out)
|
|
37
|
+
else if (entry.isFile() && integrationDist.test(full)) out.push(full)
|
|
38
|
+
}
|
|
39
|
+
return out
|
|
40
|
+
}
|
|
26
41
|
|
|
27
42
|
let integrationFound = false
|
|
28
43
|
let definitionFound = false
|
|
29
44
|
const patched: string[] = []
|
|
30
45
|
|
|
31
|
-
for
|
|
46
|
+
for (const file of await findFiles(nodeModules)) {
|
|
32
47
|
integrationFound = true
|
|
33
|
-
const file = path.join(nodeModules, rel)
|
|
34
48
|
const content = await fs.readFile(file, `utf8`)
|
|
35
49
|
|
|
36
50
|
if (content.includes(PATCHED)) {
|
|
@@ -41,7 +55,7 @@ export const patchVanillaExtract = async (cwd: string): Promise<boolean> => {
|
|
|
41
55
|
|
|
42
56
|
definitionFound = true
|
|
43
57
|
await fs.writeFile(file, content.replaceAll(ORIGINAL, PATCHED))
|
|
44
|
-
patched.push(
|
|
58
|
+
patched.push(path.relative(nodeModules, file))
|
|
45
59
|
}
|
|
46
60
|
|
|
47
61
|
if (!integrationFound) return false // VE not installed here
|