@10stars/config 17.0.3 → 17.0.4
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/link-remote.ts +68 -42
package/package.json
CHANGED
package/src/link-remote.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process"
|
|
1
|
+
import { execFile, execFileSync } from "node:child_process"
|
|
2
2
|
import fs from "node:fs/promises"
|
|
3
3
|
import path from "node:path"
|
|
4
|
+
import { promisify } from "node:util"
|
|
4
5
|
|
|
5
6
|
import type * as TF from "type-fest"
|
|
6
7
|
|
|
@@ -39,9 +40,14 @@ type Sibling = { abs: string; ref: string | null }
|
|
|
39
40
|
const git = (args: string[], cwd?: string): string =>
|
|
40
41
|
execFileSync(`git`, args, { cwd, encoding: `utf8` }).trim()
|
|
41
42
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const execFileAsync = promisify(execFile)
|
|
44
|
+
|
|
45
|
+
// Async git so independent clones (a BFS level's siblings, or all pinned --from clones) run
|
|
46
|
+
// concurrently: the sync `git` helper's execFileSync would serialize them by blocking the event
|
|
47
|
+
// loop until each finishes. Output is captured (not inherited) so concurrent progress can't
|
|
48
|
+
// interleave into unreadable logs; callers log one line per repo.
|
|
49
|
+
const gitAsync = (args: string[], cwd?: string): Promise<unknown> =>
|
|
50
|
+
execFileAsync(`git`, args, { cwd })
|
|
45
51
|
|
|
46
52
|
const tryGit = (args: string[], cwd?: string): string | null => {
|
|
47
53
|
try {
|
|
@@ -107,22 +113,34 @@ const fileSiblingRoots = async (dir: string): Promise<string[]> => {
|
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
/**
|
|
110
|
-
* BFS the transitive `file:` sibling graph from `cwd
|
|
111
|
-
*
|
|
112
|
-
*
|
|
116
|
+
* BFS the transitive `file:` sibling graph from `cwd` in dependents-first order, invoking `visit`
|
|
117
|
+
* on each level's newly discovered siblings concurrently (`Promise.all`). Cloning a sibling in
|
|
118
|
+
* `visit` makes its own `file:` deps discoverable when the next level expands from it. Levels stay
|
|
119
|
+
* sequential (a child is only found after its parent is cloned) but siblings within a level are
|
|
120
|
+
* independent and clone in parallel. The `visited` set dedupes repos reached by more than one path,
|
|
121
|
+
* both across and within a level. Returns the deduped siblings in discovery (dependents-first) order.
|
|
113
122
|
*/
|
|
114
|
-
const bfsSiblings = async (
|
|
123
|
+
const bfsSiblings = async (
|
|
124
|
+
cwd: string,
|
|
125
|
+
visit: (sib: string) => Promise<void>,
|
|
126
|
+
): Promise<string[]> => {
|
|
115
127
|
const visited = new Set<string>([path.resolve(cwd)])
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
128
|
+
const order: string[] = []
|
|
129
|
+
let frontier = [path.resolve(cwd)]
|
|
130
|
+
while (frontier.length) {
|
|
131
|
+
const level: string[] = []
|
|
132
|
+
for (const dir of frontier) {
|
|
133
|
+
for (const sib of await fileSiblingRoots(dir)) {
|
|
134
|
+
if (visited.has(sib)) continue // dedupe repos reached via multiple file: paths
|
|
135
|
+
visited.add(sib)
|
|
136
|
+
level.push(sib)
|
|
137
|
+
}
|
|
124
138
|
}
|
|
139
|
+
await Promise.all(level.map(visit)) // clone this level's siblings in parallel
|
|
140
|
+
order.push(...level)
|
|
141
|
+
frontier = level
|
|
125
142
|
}
|
|
143
|
+
return order
|
|
126
144
|
}
|
|
127
145
|
|
|
128
146
|
/**
|
|
@@ -160,7 +178,12 @@ const matchRemoteBranch = (url: string, candidates: string[]): string | null =>
|
|
|
160
178
|
* `--branch`, so the `ls-remote` probe is skipped entirely when no coordinated branch is set — one
|
|
161
179
|
* fewer round-trip for local/manual runs (in GitHub Actions GITHUB_REF_NAME is always set).
|
|
162
180
|
*/
|
|
163
|
-
const cloneSibling = (
|
|
181
|
+
const cloneSibling = async (
|
|
182
|
+
url: string,
|
|
183
|
+
repo: string,
|
|
184
|
+
sib: string,
|
|
185
|
+
dest: string,
|
|
186
|
+
): Promise<string | null> => {
|
|
164
187
|
const coordinated = [
|
|
165
188
|
process.env.GITHUB_HEAD_REF, // pull_request events
|
|
166
189
|
process.env.GITHUB_REF_NAME, // push events
|
|
@@ -168,7 +191,7 @@ const cloneSibling = (url: string, repo: string, sib: string, dest: string): str
|
|
|
168
191
|
const ref = coordinated.length ? matchRemoteBranch(url, coordinated) : null
|
|
169
192
|
|
|
170
193
|
console.info(`linkRemote: cloning ${repo}@${ref ?? `(default)`} -> ${dest}`)
|
|
171
|
-
|
|
194
|
+
await gitAsync([
|
|
172
195
|
`clone`,
|
|
173
196
|
`--depth`,
|
|
174
197
|
`1`,
|
|
@@ -189,11 +212,11 @@ const currentRef = (dir: string): string | null => {
|
|
|
189
212
|
return branch === `HEAD` ? null : branch
|
|
190
213
|
}
|
|
191
214
|
|
|
192
|
-
const clonePinned = (url: string, sha: string, dir: string): void => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
215
|
+
const clonePinned = async (url: string, sha: string, dir: string): Promise<void> => {
|
|
216
|
+
await gitAsync([`init`, dir])
|
|
217
|
+
await gitAsync([`remote`, `add`, `origin`, url], dir)
|
|
218
|
+
await gitAsync([`fetch`, `--depth`, `1`, `origin`, sha], dir) // GitHub allows fetching a reachable SHA
|
|
219
|
+
await gitAsync([`checkout`, `FETCH_HEAD`], dir)
|
|
197
220
|
}
|
|
198
221
|
|
|
199
222
|
/**
|
|
@@ -201,19 +224,18 @@ const clonePinned = (url: string, sha: string, dir: string): void => {
|
|
|
201
224
|
* Returns siblings in discovery (BFS) order — dependents before their dependencies.
|
|
202
225
|
*/
|
|
203
226
|
const cloneClosureBranchFirst = async (cwd: string): Promise<Sibling[]> => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
227
|
+
// Parallel visits resolve out of order; key refs by abs path and reassemble in discovery order.
|
|
228
|
+
const refs = new Map<string, string | null>()
|
|
229
|
+
const discovered = await bfsSiblings(cwd, async (sib) => {
|
|
207
230
|
if (await exists(sib)) {
|
|
208
231
|
console.info(`linkRemote: ${path.relative(cwd, sib)} already present; not cloning`)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const repo = path.basename(sib)
|
|
212
|
-
ref = cloneSibling(repoUrl(repo), repo, sib, path.relative(cwd, sib))
|
|
232
|
+
refs.set(sib, currentRef(sib))
|
|
233
|
+
return
|
|
213
234
|
}
|
|
214
|
-
|
|
235
|
+
const repo = path.basename(sib)
|
|
236
|
+
refs.set(sib, await cloneSibling(repoUrl(repo), repo, sib, path.relative(cwd, sib)))
|
|
215
237
|
})
|
|
216
|
-
return
|
|
238
|
+
return discovered.map((abs) => ({ abs, ref: refs.get(abs) ?? null }))
|
|
217
239
|
}
|
|
218
240
|
|
|
219
241
|
const writeManifest = async (cwd: string, siblings: Sibling[], out: string): Promise<void> => {
|
|
@@ -244,16 +266,20 @@ const linkRemoteFromManifest = async (cwd: string, manifestPath: string): Promis
|
|
|
244
266
|
return
|
|
245
267
|
}
|
|
246
268
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
269
|
+
// Pinned clones are mutually independent (each at a recorded SHA, no discovery), so materialize
|
|
270
|
+
// them all in parallel; leaves-first install order is still enforced below.
|
|
271
|
+
await Promise.all(
|
|
272
|
+
manifest.siblings.map(async (sib) => {
|
|
273
|
+
const abs = path.resolve(cwd, sib.path)
|
|
274
|
+
if (await exists(abs)) {
|
|
275
|
+
console.info(`linkRemote --from: ${sib.path} already present; leaving as-is`)
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
if (!sib.sha) throw new Error(`Manifest entry "${sib.repo}" has no sha to pin`)
|
|
279
|
+
console.info(`linkRemote --from: pinning ${sib.repo}@${sib.sha} -> ${sib.path}`)
|
|
280
|
+
await clonePinned(repoUrl(sib.repo), sib.sha, abs)
|
|
281
|
+
}),
|
|
282
|
+
)
|
|
257
283
|
|
|
258
284
|
installLeavesFirst(
|
|
259
285
|
cwd,
|