docks-kit 0.9.0 → 0.10.1
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/AGENTS.md +9 -4
- package/README.md +21 -16
- package/cli/docs/install.md +11 -20
- package/cli/docs/platforms.md +16 -44
- package/cli/docs/toolchain.md +6 -6
- package/cli/src/commands/docs.ts +1 -1
- package/cli/src/commands/toolchain.ts +1 -1
- package/cli/src/engine-native/DESIGN.md +19 -27
- package/cli/src/engine-native/bun.ts +7 -25
- package/cli/src/engine-native/claudeRuntime.ts +6 -15
- package/cli/src/engine-native/claudeSync.ts +3 -34
- package/cli/src/engine-native/codexSync.ts +1 -1
- package/cli/src/engine-native/deps.ts +28 -49
- package/cli/src/engine-native/exec.ts +6 -13
- package/cli/src/engine-native/os.ts +7 -12
- package/cli/src/engine-native/services.ts +2 -4
- package/cli/src/engine-native/sessionRelayCli.ts +1 -1
- package/cli/src/engine-native/skillsSync.ts +13 -38
- package/cli/src/engine.ts +23 -15
- package/cli/src/generated/sotPayload.ts +5 -5
- package/docks-kit +11 -10
- package/package.json +5 -1
- package/cli/src/engine-native/powershell.ts +0 -11
|
@@ -112,34 +112,29 @@ const home = (): string => {
|
|
|
112
112
|
return envHome !== undefined && envHome !== "" ? envHome : homedir()
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
const absoluteWindowsExe = (path: string): boolean =>
|
|
116
|
-
/\.exe$/i.test(path) && (/^[A-Za-z]:[\\/]/.test(path) || /^\\\\/.test(path))
|
|
117
115
|
|
|
118
116
|
// The resolved path gets persisted into global direct-exec hooks, so a
|
|
119
117
|
// relative `which` hit (relative PATH entry, relative BUN_INSTALL) would
|
|
120
118
|
// break outside the sync working directory.
|
|
121
|
-
const absoluteExecutable = (path: string, platform: NodeJS.Platform): boolean =>
|
|
122
|
-
platform === "win32" ? absoluteWindowsExe(path) : isAbsolute(path)
|
|
123
119
|
|
|
124
|
-
const findBun = (exec: ProbeExecutor
|
|
120
|
+
const findBun = (exec: ProbeExecutor): { command: string; path: string } | undefined => {
|
|
125
121
|
const onPath = exec.which("bun")
|
|
126
|
-
if (onPath !== "" &&
|
|
127
|
-
return { command:
|
|
122
|
+
if (onPath !== "" && isAbsolute(onPath)) {
|
|
123
|
+
return { command: "bun", path: onPath }
|
|
128
124
|
}
|
|
129
125
|
const root =
|
|
130
126
|
process.env["BUN_INSTALL"] !== undefined && process.env["BUN_INSTALL"] !== ""
|
|
131
127
|
? process.env["BUN_INSTALL"]!
|
|
132
128
|
: p(home(), ".bun")
|
|
133
|
-
const
|
|
134
|
-
for (const candidate of [p(root, "bin", name), p(home(), ".bun", "bin", name)]) {
|
|
129
|
+
for (const candidate of [p(root, "bin", "bun"), p(home(), ".bun", "bin", "bun")]) {
|
|
135
130
|
const found = exec.which(candidate)
|
|
136
|
-
if (found !== "" &&
|
|
131
|
+
if (found !== "" && isAbsolute(found)) return { command: found, path: found }
|
|
137
132
|
}
|
|
138
133
|
return undefined
|
|
139
134
|
}
|
|
140
135
|
|
|
141
|
-
const resolveBun = (exec: ProbeExecutor
|
|
142
|
-
const bun = findBun(exec
|
|
136
|
+
const resolveBun = (exec: ProbeExecutor): ProbeResult => {
|
|
137
|
+
const bun = findBun(exec)
|
|
143
138
|
return bun === undefined
|
|
144
139
|
? { state: "missing" }
|
|
145
140
|
: { state: "present", path: bun.path }
|
|
@@ -161,28 +156,23 @@ const versionEffectSolutions = (exec: ProbeExecutor): string => {
|
|
|
161
156
|
return match?.[1] ?? ""
|
|
162
157
|
}
|
|
163
158
|
|
|
164
|
-
const locateEffectSolutions = (exec: ProbeExecutor
|
|
165
|
-
const strictBun = findBun(exec
|
|
159
|
+
const locateEffectSolutions = (exec: ProbeExecutor): DependencyLocation => {
|
|
160
|
+
const strictBun = findBun(exec)
|
|
166
161
|
const pathBun = exec.which("bun")
|
|
167
162
|
const bun = strictBun ?? (pathBun !== "" ? { command: "bun", path: pathBun } : undefined)
|
|
168
163
|
if (bun === undefined) return { path: "", binDir: "" }
|
|
169
164
|
const globalBin = exec.capture(bun.command, ["pm", "-g", "bin"])
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
: ["effect-solutions"]
|
|
174
|
-
const path = names.map((name) => p(globalBin, name)).find((candidate) => globalBin !== "" && exec.which(candidate) !== "")
|
|
175
|
-
return { path: path ?? "", binDir: globalBin }
|
|
165
|
+
const path = globalBin !== "" ? p(globalBin, "effect-solutions") : ""
|
|
166
|
+
const resolved = path !== "" && exec.which(path) !== "" ? path : ""
|
|
167
|
+
return { path: resolved, binDir: globalBin }
|
|
176
168
|
}
|
|
177
169
|
|
|
178
170
|
const resolveChrome = (exec: ProbeExecutor, platform: NodeJS.Platform): ProbeResult => {
|
|
179
171
|
const root = p(home(), ".agent-browser", "browsers")
|
|
180
172
|
const relative =
|
|
181
|
-
platform === "
|
|
182
|
-
? "
|
|
183
|
-
:
|
|
184
|
-
? "Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"
|
|
185
|
-
: "chrome"
|
|
173
|
+
platform === "darwin"
|
|
174
|
+
? "Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing"
|
|
175
|
+
: "chrome"
|
|
186
176
|
if (existsSync(root)) {
|
|
187
177
|
for (const directory of readdirSync(root).filter((name) => name.startsWith("chrome-")).sort().reverse()) {
|
|
188
178
|
const path = exec.which(p(root, directory, relative))
|
|
@@ -215,23 +205,19 @@ export const DEPENDENCIES: Record<ToolId, DependencySpec> = {
|
|
|
215
205
|
"git",
|
|
216
206
|
"optional",
|
|
217
207
|
(pf = rawPlatform()) =>
|
|
218
|
-
pf === "
|
|
219
|
-
? "
|
|
220
|
-
:
|
|
221
|
-
? "brew install git"
|
|
222
|
-
: "sudo apt install -y git (or your distro's package manager)",
|
|
208
|
+
pf === "darwin"
|
|
209
|
+
? "brew install git"
|
|
210
|
+
: "sudo apt install -y git (or your distro's package manager)",
|
|
223
211
|
{ version: versionProbe("git") }
|
|
224
212
|
),
|
|
225
213
|
jq: spec("jq", "optional", (pf = rawPlatform()) =>
|
|
226
|
-
pf === "
|
|
227
|
-
? "
|
|
228
|
-
:
|
|
229
|
-
? "brew install jq"
|
|
230
|
-
: "sudo apt install -y jq",
|
|
214
|
+
pf === "darwin"
|
|
215
|
+
? "brew install jq"
|
|
216
|
+
: "sudo apt install -y jq",
|
|
231
217
|
{ version: versionProbe("jq") }
|
|
232
218
|
),
|
|
233
219
|
curl: spec("curl", "optional", (pf = rawPlatform()) =>
|
|
234
|
-
pf === "
|
|
220
|
+
pf === "darwin" ? "brew install curl" : "sudo apt install -y curl",
|
|
235
221
|
{ version: versionProbe("curl") }
|
|
236
222
|
),
|
|
237
223
|
node: spec("node", "optional", () => "install Node.js via https://nodejs.org (or your package manager)", {
|
|
@@ -242,22 +228,16 @@ export const DEPENDENCIES: Record<ToolId, DependencySpec> = {
|
|
|
242
228
|
claude: spec(
|
|
243
229
|
"claude",
|
|
244
230
|
"optional",
|
|
245
|
-
(
|
|
246
|
-
pf === "win32"
|
|
247
|
-
? "winget install Anthropic.ClaudeCode"
|
|
248
|
-
: "curl -fsSL https://claude.ai/install.sh -o /tmp/claude-install.sh && bash /tmp/claude-install.sh",
|
|
231
|
+
() => "curl -fsSL https://claude.ai/install.sh -o /tmp/claude-install.sh && bash /tmp/claude-install.sh",
|
|
249
232
|
{ version: versionProbe("claude") }
|
|
250
233
|
),
|
|
251
234
|
codex: spec(
|
|
252
235
|
"codex",
|
|
253
236
|
"optional",
|
|
254
|
-
(
|
|
255
|
-
pf === "win32"
|
|
256
|
-
? `powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"`
|
|
257
|
-
: 'tmp=$(mktemp) && curl -fsSL https://chatgpt.com/codex/install.sh -o "$tmp" && CODEX_NON_INTERACTIVE=1 sh "$tmp"',
|
|
237
|
+
() => 'tmp=$(mktemp) && curl -fsSL https://chatgpt.com/codex/install.sh -o "$tmp" && CODEX_NON_INTERACTIVE=1 sh "$tmp"',
|
|
258
238
|
{ version: versionProbe("codex") }
|
|
259
239
|
),
|
|
260
|
-
rtk: spec("rtk", "optional", () => "see https://github.com/rtk-ai/rtk (kit auto-install is
|
|
240
|
+
rtk: spec("rtk", "optional", () => "see https://github.com/rtk-ai/rtk (kit auto-install is Linux/macOS-only)", {
|
|
261
241
|
version: versionProbe("rtk"),
|
|
262
242
|
latest: latestRtk
|
|
263
243
|
}),
|
|
@@ -269,12 +249,11 @@ export const DEPENDENCIES: Record<ToolId, DependencySpec> = {
|
|
|
269
249
|
bun: spec(
|
|
270
250
|
"bun",
|
|
271
251
|
"optional",
|
|
272
|
-
(
|
|
273
|
-
pf === "win32" ? `powershell -c "irm bun.sh/install.ps1 | iex"` : "curl -fsSL https://bun.sh/install | bash",
|
|
252
|
+
() => "curl -fsSL https://bun.sh/install | bash",
|
|
274
253
|
{
|
|
275
254
|
resolve: resolveBun,
|
|
276
255
|
version: (exec) => exec.capture(versionBunCommand(exec), ["--version"]),
|
|
277
|
-
locate: (exec
|
|
256
|
+
locate: (exec) => ({ path: findBun(exec)?.path ?? "", binDir: "" })
|
|
278
257
|
}
|
|
279
258
|
),
|
|
280
259
|
bwrap: spec("bwrap", "optional", () => "sudo apt install -y bubblewrap (or dnf/pacman/zypper equivalent)"),
|
|
@@ -298,7 +277,7 @@ export const DEPENDENCIES: Record<ToolId, DependencySpec> = {
|
|
|
298
277
|
"ffplay",
|
|
299
278
|
"optional",
|
|
300
279
|
(pf = rawPlatform()) =>
|
|
301
|
-
pf === "
|
|
280
|
+
pf === "darwin" ? "brew install ffmpeg" : "sudo apt install -y ffmpeg",
|
|
302
281
|
{ versionArgs: ["-version"], resolve: pathProbe("ffplay") }
|
|
303
282
|
),
|
|
304
283
|
intelephense: spec("intelephense", "optional", () => "npm install -g intelephense", {
|
|
@@ -7,11 +7,7 @@ import { spawnSync } from "node:child_process"
|
|
|
7
7
|
import { accessSync, chmodSync, constants, existsSync, readFileSync, statSync, writeFileSync } from "node:fs"
|
|
8
8
|
import { delimiter, isAbsolute, join } from "node:path"
|
|
9
9
|
|
|
10
|
-
/**
|
|
11
|
-
* Engine paths are built with "/" because they appear verbatim in output
|
|
12
|
-
* (dry-run lines, warns), where node:path.join would print "\" on Windows
|
|
13
|
-
* and break the golden contract. fs accepts "/" on every platform.
|
|
14
|
-
*/
|
|
10
|
+
/** Keep engine paths slash-separated so rendered output is host-stable. */
|
|
15
11
|
export function p(...parts: Array<string>): string {
|
|
16
12
|
return parts.join("/")
|
|
17
13
|
}
|
|
@@ -22,18 +18,15 @@ export function capture(cmd: string, args: ReadonlyArray<string>): string {
|
|
|
22
18
|
return (res.stdout ?? "").replace(/[\r\n]+$/, "")
|
|
23
19
|
}
|
|
24
20
|
|
|
25
|
-
/** `command -v` — resolve
|
|
21
|
+
/** `command -v` — resolve an executable name on PATH. */
|
|
26
22
|
export function which(name: string): string {
|
|
27
|
-
if (isAbsolute(name) || name.includes("/")
|
|
23
|
+
if (isAbsolute(name) || name.includes("/")) {
|
|
28
24
|
return isExecutable(name) ? name : ""
|
|
29
25
|
}
|
|
30
|
-
const exts = process.platform === "win32" ? (process.env["PATHEXT"] ?? ".EXE;.CMD;.BAT;.COM").split(";").concat("") : [""]
|
|
31
26
|
for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
|
|
32
27
|
if (dir === "") continue
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (isExecutable(cand)) return cand
|
|
36
|
-
}
|
|
28
|
+
const candidate = join(dir, name)
|
|
29
|
+
if (isExecutable(candidate)) return candidate
|
|
37
30
|
}
|
|
38
31
|
return ""
|
|
39
32
|
}
|
|
@@ -45,7 +38,7 @@ export function commandExists(name: string): boolean {
|
|
|
45
38
|
export function isExecutable(p: string): boolean {
|
|
46
39
|
try {
|
|
47
40
|
if (!statSync(p).isFile()) return false
|
|
48
|
-
|
|
41
|
+
accessSync(p, constants.X_OK)
|
|
49
42
|
return true
|
|
50
43
|
} catch {
|
|
51
44
|
return false
|
|
@@ -1,29 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Platform capability seam (Output Policy in DESIGN.md)
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* stays try-then-fallback at the call site (capability-driven, not predicted).
|
|
2
|
+
* Platform capability seam (Output Policy in DESIGN.md). Per-tool package
|
|
3
|
+
* identifiers stay in deps.ts; symlink handling stays try-then-fallback at
|
|
4
|
+
* the call site (capability-driven, not predicted).
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
export type PlatformName = "linux" | "darwin" | "
|
|
7
|
+
export type PlatformName = "linux" | "darwin" | "unknown"
|
|
9
8
|
|
|
10
9
|
export function rawPlatform(): NodeJS.Platform {
|
|
11
10
|
return process.platform
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
export function platformName(pf: NodeJS.Platform = rawPlatform()): PlatformName {
|
|
15
|
-
return pf === "linux" ? "linux" : pf === "darwin" ? "darwin" :
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function isWindows(): boolean {
|
|
19
|
-
return rawPlatform() === "win32"
|
|
14
|
+
return pf === "linux" ? "linux" : pf === "darwin" ? "darwin" : "unknown"
|
|
20
15
|
}
|
|
21
16
|
|
|
22
17
|
export function isLinux(): boolean {
|
|
23
18
|
return rawPlatform() === "linux"
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
/** Shell-rc exports
|
|
21
|
+
/** Shell-rc exports apply only on supported hosts. */
|
|
27
22
|
export function shellRcApplicable(): boolean {
|
|
28
|
-
return
|
|
23
|
+
return platformName() !== "unknown"
|
|
29
24
|
}
|
|
@@ -36,7 +36,6 @@ export interface DependencyManager {
|
|
|
36
36
|
export interface Platform {
|
|
37
37
|
readonly raw: () => NodeJS.Platform
|
|
38
38
|
readonly name: () => PlatformName
|
|
39
|
-
readonly isWindows: () => boolean
|
|
40
39
|
readonly isLinux: () => boolean
|
|
41
40
|
readonly shellRcApplicable: () => boolean
|
|
42
41
|
}
|
|
@@ -51,13 +50,12 @@ export interface EngineServiceOptions {
|
|
|
51
50
|
readonly sinks?: LoggerSinks
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
/** Platform view over an injectable platform id
|
|
53
|
+
/** Platform view over an injectable platform id. */
|
|
55
54
|
export const makePlatform = (pf: NodeJS.Platform = rawPlatform()): Platform => ({
|
|
56
55
|
raw: () => pf,
|
|
57
56
|
name: () => platformName(pf),
|
|
58
|
-
isWindows: () => pf === "win32",
|
|
59
57
|
isLinux: () => pf === "linux",
|
|
60
|
-
shellRcApplicable: () => pf
|
|
58
|
+
shellRcApplicable: () => pf === "linux" || pf === "darwin"
|
|
61
59
|
})
|
|
62
60
|
|
|
63
61
|
/** DependencyManager whose hints default to the INJECTED platform, not the host. */
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* and the snapshot write.
|
|
6
6
|
*/
|
|
7
7
|
import { spawnSync } from "node:child_process"
|
|
8
|
-
import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync,
|
|
8
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, rmSync, statSync, symlinkSync } from "node:fs"
|
|
9
9
|
import { p, writeFileIfChanged } from "./exec"
|
|
10
10
|
import { bunBootstrap } from "./bun"
|
|
11
11
|
import type { Ctx } from "./index"
|
|
12
12
|
import { compareCodepoints } from "./jq"
|
|
13
|
-
import type { EngineServices
|
|
13
|
+
import type { EngineServices } from "./services"
|
|
14
14
|
import { ensure, field } from "./toolchain"
|
|
15
15
|
import { payloadText } from "../payload"
|
|
16
16
|
|
|
@@ -139,9 +139,6 @@ function healClaudeSymlink(ctx: Ctx, skillsDir: string, base: string): boolean {
|
|
|
139
139
|
if (linkStat?.isSymbolicLink() === true) {
|
|
140
140
|
const current = safeReadlink(claudeLink)
|
|
141
141
|
if (current === relTarget) return false
|
|
142
|
-
// win32: `npx skills add` creates absolute symlinks/junctions — any link
|
|
143
|
-
// that RESOLVES to the canonical dir is healthy, not stale.
|
|
144
|
-
if (ctx.services.platform.isWindows() && realpathEquals(claudeLink, canonical)) return false
|
|
145
142
|
if (ctx.dryRun) {
|
|
146
143
|
echo(`[dry-run] would replace stale Claude symlink: ~/.claude/skills/${base} -> ${current} (correct: ${relTarget})`)
|
|
147
144
|
return true
|
|
@@ -178,36 +175,22 @@ function safeReadlink(path: string): string {
|
|
|
178
175
|
}
|
|
179
176
|
}
|
|
180
177
|
|
|
181
|
-
function realpathEquals(a: string, b: string): boolean {
|
|
182
|
-
try {
|
|
183
|
-
return realpathSync(a) === realpathSync(b)
|
|
184
|
-
} catch {
|
|
185
|
-
return false
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
178
|
|
|
189
|
-
/**
|
|
190
|
-
* Remove a symlink/junction without crashing: Bun's rmSync throws EFAULT on
|
|
191
|
-
* win32 directory symlinks. unlink handles file symlinks, rmdir handles
|
|
192
|
-
* dir symlinks/junctions, rmSync is the POSIX catch-all.
|
|
193
|
-
*/
|
|
179
|
+
/** Remove a symlink without touching a real directory. */
|
|
194
180
|
function removeLink(path: string): boolean {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// try the next removal shape
|
|
201
|
-
}
|
|
181
|
+
try {
|
|
182
|
+
rmSync(path, { force: true })
|
|
183
|
+
return true
|
|
184
|
+
} catch {
|
|
185
|
+
return lstat(path) === undefined
|
|
202
186
|
}
|
|
203
|
-
return lstat(path) === undefined
|
|
204
187
|
}
|
|
205
188
|
|
|
206
|
-
/** skills::_link_or_copy — real symlink preferred, copy fallback
|
|
207
|
-
export function linkOrCopy(target: string, link: string
|
|
189
|
+
/** skills::_link_or_copy — real symlink preferred, copy fallback. */
|
|
190
|
+
export function linkOrCopy(target: string, link: string): boolean {
|
|
208
191
|
removeLink(link)
|
|
209
192
|
try {
|
|
210
|
-
symlinkSync(target, link
|
|
193
|
+
symlinkSync(target, link)
|
|
211
194
|
} catch {
|
|
212
195
|
// fall through to the copy fallback below
|
|
213
196
|
}
|
|
@@ -223,11 +206,11 @@ export function linkOrCopy(target: string, link: string, platform: Platform): bo
|
|
|
223
206
|
}
|
|
224
207
|
|
|
225
208
|
function linkOrCopyWithWarnings(target: string, link: string, services: EngineServices): boolean {
|
|
226
|
-
const linked = linkOrCopy(target, link
|
|
209
|
+
const linked = linkOrCopy(target, link)
|
|
227
210
|
if (!linked) {
|
|
228
211
|
services.logger.warn(`could not create ${link} (symlink and copy both failed)`)
|
|
229
212
|
} else if (lstat(link)?.isSymbolicLink() !== true) {
|
|
230
|
-
services.logger.warn(`symlinks unsupported here — ${link} is a copy
|
|
213
|
+
services.logger.warn(`symlinks unsupported here — ${link} is a copy refreshed on sync`)
|
|
231
214
|
}
|
|
232
215
|
return linked
|
|
233
216
|
}
|
|
@@ -298,14 +281,6 @@ export function effectSolutionsInstall(
|
|
|
298
281
|
|
|
299
282
|
const location = services.deps.location("effect-solutions")
|
|
300
283
|
const gbin = location.binDir
|
|
301
|
-
// win32: bun writes an .exe shim (not the bare Unix name), and the
|
|
302
|
-
// ~/.local/bin link step below is Unix-only plumbing (non-interactive
|
|
303
|
-
// agent PATH) — bun's global bin is already the Windows PATH entry.
|
|
304
|
-
if (services.platform.isWindows()) {
|
|
305
|
-
if (location.path !== "") change(`effect-solutions CLI ready (${gbin})`)
|
|
306
|
-
else warn(`effect-solutions installed but no shim found under '${gbin !== "" ? gbin : "<unknown>"}' — check bun pm -g bin`)
|
|
307
|
-
return 0
|
|
308
|
-
}
|
|
309
284
|
if (location.path !== "") {
|
|
310
285
|
mkdirSync(p(ctx.home, ".local", "bin"), { recursive: true })
|
|
311
286
|
linkOrCopyWithWarnings(bun, p(ctx.home, ".local", "bin", "bun"), services)
|
package/cli/src/engine.ts
CHANGED
|
@@ -5,28 +5,32 @@ import { makeEngineServices } from "./engine-native/services"
|
|
|
5
5
|
import { kitHome } from "./kitHome"
|
|
6
6
|
import { DependencyManagerService, LoggerService, PlatformService } from "./services"
|
|
7
7
|
|
|
8
|
-
// Same factory as the Effect rim's live layers — this path runs outside the
|
|
9
|
-
// runtime (child-spawn capture), so it takes the services directly.
|
|
10
|
-
const services = makeEngineServices()
|
|
11
|
-
|
|
12
8
|
/**
|
|
13
9
|
* The single seam between the typed CLI and EngineNative. Engine execution
|
|
14
10
|
* stays in-process after @effect/cli has parsed pickers and flag spellings.
|
|
15
11
|
*/
|
|
16
12
|
const bashRemovedMessage = "bash engine removed — recover at tag bash-engine-final"
|
|
17
13
|
const bashEngineRequested = (): boolean => process.env["DOCKS_KIT_ENGINE"] === "bash"
|
|
14
|
+
const requireSupportedHost = () => {
|
|
15
|
+
const platform = process.platform
|
|
16
|
+
const arch = process.arch
|
|
17
|
+
return (platform === "linux" || platform === "darwin") && (arch === "x64" || arch === "arm64")
|
|
18
|
+
? Effect.void
|
|
19
|
+
: bail(
|
|
20
|
+
`unsupported host ${platform}/${arch}; docks-kit supports only Linux and macOS on x64 or arm64`,
|
|
21
|
+
2
|
|
22
|
+
)
|
|
23
|
+
}
|
|
18
24
|
|
|
19
|
-
// bun build --compile runs the embedded entry from a virtual path
|
|
20
|
-
// ("/$bunfs/root/…"
|
|
21
|
-
//
|
|
22
|
-
// The Windows check is anchored to the drive-rooted "~BUN" segment so a
|
|
23
|
-
// real checkout under a ~BUN-named directory can't false-positive.
|
|
25
|
+
// bun build --compile runs the embedded entry from a virtual POSIX path
|
|
26
|
+
// ("/$bunfs/root/…"). There process.execPath IS the CLI, so a re-spawn must
|
|
27
|
+
// not pass main.ts.
|
|
24
28
|
export const compiled =
|
|
25
|
-
process.argv[1] !== undefined &&
|
|
26
|
-
(process.argv[1].startsWith("/$bunfs/") || /^[A-Za-z]:[\\/]~BUN[\\/]/i.test(process.argv[1]))
|
|
29
|
+
process.argv[1] !== undefined && process.argv[1].startsWith("/$bunfs/")
|
|
27
30
|
|
|
28
31
|
export const engine = (args: ReadonlyArray<string>) =>
|
|
29
32
|
Effect.gen(function* () {
|
|
33
|
+
yield* requireSupportedHost()
|
|
30
34
|
if (bashEngineRequested()) {
|
|
31
35
|
yield* bail(bashRemovedMessage, 2)
|
|
32
36
|
}
|
|
@@ -41,9 +45,12 @@ export const engine = (args: ReadonlyArray<string>) =>
|
|
|
41
45
|
|
|
42
46
|
/** Run the engine capturing stdout (engine logs/warns go to stderr and pass through). */
|
|
43
47
|
export const engineCapture = (args: ReadonlyArray<string>) =>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
Effect.gen(function* () {
|
|
49
|
+
yield* requireSupportedHost()
|
|
50
|
+
if (bashEngineRequested()) {
|
|
51
|
+
return yield* bail(bashRemovedMessage, 2)
|
|
52
|
+
}
|
|
53
|
+
return yield* Effect.sync(() => {
|
|
47
54
|
// Child process (raw channel): runEngineNative writes straight to
|
|
48
55
|
// process.stdout, so in-process capture isn't possible.
|
|
49
56
|
const res = spawnSync(process.execPath, compiled ? [...args] : [`${kitHome()}/cli/src/main.ts`, ...args], {
|
|
@@ -52,10 +59,11 @@ export const engineCapture = (args: ReadonlyArray<string>) =>
|
|
|
52
59
|
stdio: ["ignore", "pipe", "inherit"]
|
|
53
60
|
})
|
|
54
61
|
if (res.error !== undefined || res.status !== 0) {
|
|
55
|
-
|
|
62
|
+
makeEngineServices().logger.warn(`engine capture failed (${args.join(" ")} exited ${res.status ?? "spawn-error"})`)
|
|
56
63
|
}
|
|
57
64
|
return res.stdout ?? ""
|
|
58
65
|
})
|
|
66
|
+
})
|
|
59
67
|
|
|
60
68
|
/** Print a message to stderr and exit — for CLI-side validation failures. */
|
|
61
69
|
export const bail = (message: string, code = 2) =>
|