Skip to content

Commit da0bd31

Browse files
authored
fix(app): publish single docker-git dist without workspace lib runtime dep (#79)
* fix(app): bundle docker-git CLI and drop workspace lib runtime dep * test(e2e): verify local packed docker-git CLI via pnpm * chore(lockfile): sync app deps after packaging changes * fix(lib): split create command builder for lint stability
1 parent 9aeaf0c commit da0bd31

File tree

7 files changed

+196
-37
lines changed

7 files changed

+196
-37
lines changed

.github/workflows/check.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,17 @@ jobs:
8585
- name: Lint Effect-TS (lib)
8686
run: pnpm --filter ./packages/lib lint:effect
8787

88+
e2e-local-package:
89+
name: E2E (Local package CLI)
90+
runs-on: ubuntu-latest
91+
timeout-minutes: 15
92+
steps:
93+
- uses: actions/checkout@v6
94+
- name: Install dependencies
95+
uses: ./.github/actions/setup
96+
- name: Pack and run local package via pnpm
97+
run: bash scripts/e2e/local-package-cli.sh
98+
8899
e2e-opencode:
89100
name: E2E (OpenCode)
90101
runs-on: ubuntu-latest

packages/app/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@
1313
"prebuild": "pnpm -C ../lib build",
1414
"build": "pnpm run build:app && pnpm run build:docker-git",
1515
"build:app": "vite build --ssr src/app/main.ts",
16+
"prepack": "pnpm run build:docker-git",
1617
"dev": "vite build --watch --ssr src/app/main.ts",
1718
"prelint": "pnpm -C ../lib build",
1819
"lint": "PATH=../../scripts:$PATH vibecode-linter src/",
1920
"lint:tests": "PATH=../../scripts:$PATH vibecode-linter tests/",
2021
"lint:effect": "PATH=../../scripts:$PATH eslint --config eslint.effect-ts-check.config.mjs .",
2122
"prebuild:docker-git": "pnpm -C ../lib build",
22-
"build:docker-git": "tsc -p tsconfig.build.json",
23+
"build:docker-git": "vite build --config vite.docker-git.config.ts",
2324
"check": "pnpm run typecheck",
2425
"clone": "pnpm -C ../.. run clone",
2526
"docker-git": "node dist/src/docker-git/main.js",
@@ -53,7 +54,6 @@
5354
"homepage": "https://github.com/ProverCoderAI/docker-git#readme",
5455
"packageManager": "pnpm@10.28.0",
5556
"dependencies": {
56-
"@effect-template/lib": "workspace:*",
5757
"@effect/cli": "^0.73.0",
5858
"@effect/cluster": "^0.56.1",
5959
"@effect/experimental": "^0.58.0",
@@ -73,6 +73,7 @@
7373
"ts-morph": "^27.0.2"
7474
},
7575
"devDependencies": {
76+
"@effect-template/lib": "workspace:*",
7677
"@biomejs/biome": "^2.3.11",
7778
"@effect/eslint-plugin": "^0.3.2",
7879
"@effect/language-service": "latest",
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import path from "node:path"
2+
import { fileURLToPath } from "node:url"
3+
import { defineConfig } from "vite"
4+
import tsconfigPaths from "vite-tsconfig-paths"
5+
6+
const __filename = fileURLToPath(import.meta.url)
7+
const __dirname = path.dirname(__filename)
8+
9+
export default defineConfig({
10+
plugins: [tsconfigPaths()],
11+
publicDir: false,
12+
resolve: {
13+
alias: {
14+
"@": path.resolve(__dirname, "src"),
15+
"@effect-template/lib": path.resolve(__dirname, "../lib/src")
16+
}
17+
},
18+
build: {
19+
target: "node20",
20+
outDir: "dist",
21+
sourcemap: true,
22+
ssr: "src/docker-git/main.ts",
23+
rollupOptions: {
24+
output: {
25+
format: "es",
26+
entryFileNames: "src/docker-git/main.js",
27+
inlineDynamicImports: true
28+
}
29+
}
30+
},
31+
ssr: {
32+
target: "node"
33+
}
34+
})

packages/lib/src/core/command-builders.ts

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,69 @@ const resolvePaths = (
200200
}
201201
})
202202

203+
type CreateBehavior = {
204+
readonly runUp: boolean
205+
readonly openSsh: boolean
206+
readonly force: boolean
207+
readonly forceEnv: boolean
208+
readonly enableMcpPlaywright: boolean
209+
}
210+
211+
const resolveCreateBehavior = (raw: RawOptions): CreateBehavior => ({
212+
runUp: raw.up ?? true,
213+
openSsh: raw.openSsh ?? false,
214+
force: raw.force ?? false,
215+
forceEnv: raw.forceEnv ?? false,
216+
enableMcpPlaywright: raw.enableMcpPlaywright ?? false
217+
})
218+
219+
type BuildTemplateConfigInput = {
220+
readonly repo: RepoBasics
221+
readonly names: NameConfig
222+
readonly paths: PathConfig
223+
readonly dockerNetworkMode: CreateCommand["config"]["dockerNetworkMode"]
224+
readonly dockerSharedNetworkName: string
225+
readonly gitTokenLabel: string | undefined
226+
readonly codexAuthLabel: string | undefined
227+
readonly claudeAuthLabel: string | undefined
228+
readonly enableMcpPlaywright: boolean
229+
}
230+
231+
const buildTemplateConfig = ({
232+
claudeAuthLabel,
233+
codexAuthLabel,
234+
dockerNetworkMode,
235+
dockerSharedNetworkName,
236+
enableMcpPlaywright,
237+
gitTokenLabel,
238+
names,
239+
paths,
240+
repo
241+
}: BuildTemplateConfigInput): CreateCommand["config"] => ({
242+
containerName: names.containerName,
243+
serviceName: names.serviceName,
244+
sshUser: repo.sshUser,
245+
sshPort: repo.sshPort,
246+
repoUrl: repo.repoUrl,
247+
repoRef: repo.repoRef,
248+
gitTokenLabel,
249+
codexAuthLabel,
250+
claudeAuthLabel,
251+
targetDir: repo.targetDir,
252+
volumeName: names.volumeName,
253+
dockerGitPath: paths.dockerGitPath,
254+
authorizedKeysPath: paths.authorizedKeysPath,
255+
envGlobalPath: paths.envGlobalPath,
256+
envProjectPath: paths.envProjectPath,
257+
codexAuthPath: paths.codexAuthPath,
258+
codexSharedAuthPath: paths.codexSharedAuthPath,
259+
codexHome: paths.codexHome,
260+
dockerNetworkMode,
261+
dockerSharedNetworkName,
262+
enableMcpPlaywright,
263+
pnpmVersion: defaultTemplateConfig.pnpmVersion
264+
})
265+
203266
// CHANGE: build a typed create command from raw options (CLI or API)
204267
// WHY: share deterministic command construction across CLI and server
205268
// QUOTE(ТЗ): "В lib ты оставляешь бизнес логику, а все CLI морду хранишь в app"
@@ -217,11 +280,7 @@ export const buildCreateCommand = (
217280
const repo = yield* _(resolveRepoBasics(raw))
218281
const names = yield* _(resolveNames(raw, repo.projectSlug))
219282
const paths = yield* _(resolvePaths(raw, repo.repoPath))
220-
const runUp = raw.up ?? true
221-
const openSsh = raw.openSsh ?? false
222-
const force = raw.force ?? false
223-
const forceEnv = raw.forceEnv ?? false
224-
const enableMcpPlaywright = raw.enableMcpPlaywright ?? false
283+
const behavior = resolveCreateBehavior(raw)
225284
const gitTokenLabel = normalizeGitTokenLabel(raw.gitTokenLabel)
226285
const codexAuthLabel = normalizeAuthLabel(raw.codexTokenLabel)
227286
const claudeAuthLabel = normalizeAuthLabel(raw.claudeTokenLabel)
@@ -233,34 +292,21 @@ export const buildCreateCommand = (
233292
return {
234293
_tag: "Create",
235294
outDir: paths.outDir,
236-
runUp,
237-
openSsh,
238-
force,
239-
forceEnv,
295+
runUp: behavior.runUp,
296+
openSsh: behavior.openSsh,
297+
force: behavior.force,
298+
forceEnv: behavior.forceEnv,
240299
waitForClone: false,
241-
config: {
242-
containerName: names.containerName,
243-
serviceName: names.serviceName,
244-
sshUser: repo.sshUser,
245-
sshPort: repo.sshPort,
246-
repoUrl: repo.repoUrl,
247-
repoRef: repo.repoRef,
300+
config: buildTemplateConfig({
301+
repo,
302+
names,
303+
paths,
304+
dockerNetworkMode,
305+
dockerSharedNetworkName,
248306
gitTokenLabel,
249307
codexAuthLabel,
250308
claudeAuthLabel,
251-
targetDir: repo.targetDir,
252-
volumeName: names.volumeName,
253-
dockerGitPath: paths.dockerGitPath,
254-
authorizedKeysPath: paths.authorizedKeysPath,
255-
envGlobalPath: paths.envGlobalPath,
256-
envProjectPath: paths.envProjectPath,
257-
codexAuthPath: paths.codexAuthPath,
258-
codexSharedAuthPath: paths.codexSharedAuthPath,
259-
codexHome: paths.codexHome,
260-
dockerNetworkMode,
261-
dockerSharedNetworkName,
262-
enableMcpPlaywright,
263-
pnpmVersion: defaultTemplateConfig.pnpmVersion
264-
}
309+
enableMcpPlaywright: behavior.enableMcpPlaywright
310+
})
265311
}
266312
})

pnpm-lock.yaml

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/e2e/local-package-cli.sh

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
RUN_ID="$(date +%s)-$RANDOM"
5+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6+
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
7+
ROOT_BASE="${DOCKER_GIT_E2E_ROOT_BASE:-/tmp/docker-git-e2e-root}"
8+
mkdir -p "$ROOT_BASE"
9+
ROOT="$(mktemp -d "$ROOT_BASE/local-package-cli.XXXXXX")"
10+
KEEP="${KEEP:-0}"
11+
12+
PACK_LOG="$ROOT/npm-pack.log"
13+
HELP_LOG="$ROOT/docker-git-help.log"
14+
PACKED_TARBALL=""
15+
16+
fail() {
17+
echo "e2e/local-package-cli: $*" >&2
18+
exit 1
19+
}
20+
21+
on_error() {
22+
local line="$1"
23+
echo "e2e/local-package-cli: failed at line $line" >&2
24+
if [[ -f "$PACK_LOG" ]]; then
25+
echo "--- npm pack log ---" >&2
26+
cat "$PACK_LOG" >&2 || true
27+
fi
28+
if [[ -f "$HELP_LOG" ]]; then
29+
echo "--- docker-git --help log ---" >&2
30+
cat "$HELP_LOG" >&2 || true
31+
fi
32+
}
33+
34+
cleanup() {
35+
if [[ "$KEEP" == "1" ]]; then
36+
echo "e2e/local-package-cli: KEEP=1 set; preserving temp dir: $ROOT" >&2
37+
return
38+
fi
39+
if [[ -n "$PACKED_TARBALL" ]] && [[ -f "$PACKED_TARBALL" ]]; then
40+
rm -f "$PACKED_TARBALL" >/dev/null 2>&1 || true
41+
fi
42+
rm -rf "$ROOT" >/dev/null 2>&1 || true
43+
}
44+
45+
trap 'on_error $LINENO' ERR
46+
trap cleanup EXIT
47+
48+
cd "$REPO_ROOT/packages/app"
49+
npm pack --silent >"$PACK_LOG"
50+
tarball_name="$(tail -n 1 "$PACK_LOG" | tr -d '\r')"
51+
[[ -n "$tarball_name" ]] || fail "npm pack did not return tarball name"
52+
53+
PACKED_TARBALL="$REPO_ROOT/packages/app/$tarball_name"
54+
[[ -f "$PACKED_TARBALL" ]] || fail "packed tarball not found: $PACKED_TARBALL"
55+
56+
dep_keys="$(tar -xOf "$PACKED_TARBALL" package/package.json | node -e 'let s="";process.stdin.on("data",(c)=>{s+=c});process.stdin.on("end",()=>{const pkg=JSON.parse(s);const deps=Object.keys(pkg.dependencies ?? {});if (deps.includes("@effect-template/lib")) {console.error("@effect-template/lib must not be a runtime dependency in packed package");process.exit(1)}process.stdout.write(deps.join(","));});')"
57+
[[ "$dep_keys" == *"effect"* ]] || fail "packed dependency set looks invalid: $dep_keys"
58+
59+
mkdir -p "$ROOT/project"
60+
cd "$ROOT/project"
61+
npm init -y >/dev/null
62+
pnpm add "$PACKED_TARBALL" --silent --lockfile=false
63+
pnpm docker-git --help >"$HELP_LOG" 2>&1
64+
65+
grep -Fq -- "docker-git clone <url> [options]" "$HELP_LOG" \
66+
|| fail "expected docker-git help output from local packed package"
67+
68+
echo "e2e/local-package-cli: local tarball install + pnpm docker-git --help OK" >&2

scripts/e2e/run-all.sh

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
55

66
cases=("$@")
77
if [[ "${#cases[@]}" -eq 0 ]]; then
8-
cases=("clone-cache" "login-context" "opencode-autoconnect")
8+
cases=("local-package-cli" "clone-cache" "login-context" "opencode-autoconnect")
99
fi
1010

1111
for case_name in "${cases[@]}"; do
@@ -20,4 +20,3 @@ for case_name in "${cases[@]}"; do
2020
done
2121

2222
echo "e2e/run-all: all cases OK" >&2
23-

0 commit comments

Comments
 (0)