Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/cross-platform-final-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@prover-coder-ai/docker-git": patch
"@prover-coder-ai/docker-git-session-sync": patch
---

Add portable launch/build scripts and CI final-build verification across Linux, macOS, and Windows.
1 change: 1 addition & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ runs:
with:
node-version: ${{ inputs.node-version }}
- name: Install OpenSSH client
if: runner.os == 'Linux'
shell: bash
run: |
if command -v ssh >/dev/null 2>&1 && command -v ssh-keygen >/dev/null 2>&1; then
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/final-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Final Build

on:
workflow_dispatch:
pull_request:
branches: [main]

permissions:
contents: read

jobs:
final-build:
name: Final build (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v6
- name: Install dependencies
uses: ./.github/actions/setup
with:
bun-version: 1.3.11
node-version: 24.14.0
- name: Build final workspace packages
run: bun run build
- name: Verify docker-git CLI starts
run: bun ./packages/app/dist/src/docker-git/main.js --help
- name: Verify session sync CLI starts
run: bun ./packages/docker-git-session-sync/dist/docker-git-session-sync.js --help
- name: Prepare package artifacts directory
run: |
node -e "require('node:fs').mkdirSync('artifacts', { recursive: true })"
- name: Pack docker-git package
working-directory: packages/app
run: bun pm pack --quiet --ignore-scripts --destination ../../artifacts
- name: Pack session sync package
working-directory: packages/docker-git-session-sync
run: bun pm pack --quiet --ignore-scripts --destination ../../artifacts
- name: Upload final build artifacts
uses: actions/upload-artifact@v7
with:
name: final-build-${{ matrix.os }}
path: artifacts/*.tgz
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
"changeset": "changeset",
"changeset-publish": "bun -e \"if (!process.env.NPM_TOKEN) { console.log('Skipping publish: NPM_TOKEN is not set'); process.exit(0); }\" && changeset publish",
"changeset-version": "changeset version",
"clone": "bash -lc 'bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js clone \"$@\"' --",
"open": "bash -lc 'bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js open \"$@\"' --",
"docker-git": "bash -lc 'bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js \"$@\"' --",
"clone": "bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js clone",
"open": "bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js open",
"docker-git": "bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js",
"skiller:init": "git submodule update --init --checkout third_party/skiller-desktop-skills-manager && bun scripts/skiller-apply-docker-git-patches.mjs",
"skiller:install": "bun install --cwd third_party/skiller-desktop-skills-manager --frozen-lockfile",
"skiller:dev": "bun run --cwd third_party/skiller-desktop-skills-manager dev",
Expand All @@ -36,7 +36,7 @@
"e2e:login-context": "bash scripts/e2e/login-context.sh",
"e2e:runtime-volumes-ssh": "bash scripts/e2e/runtime-volumes-ssh.sh",
"e2e:opencode-autoconnect": "bash scripts/e2e/opencode-autoconnect.sh",
"list": "bash -lc 'bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js ps \"$@\"' --",
"list": "bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js ps",
"dev": "bun run --cwd packages/app dev",
"web:dev": "bun run --cwd packages/app dev:web",
"web:build": "bun run --cwd packages/app build:web",
Expand All @@ -47,7 +47,7 @@
"lint:effect": "bun run --filter @prover-coder-ai/docker-git lint:effect && bun run --filter @effect-template/lib lint:effect",
"test": "bun run --filter @prover-coder-ai/docker-git-session-sync test && bun run --filter @prover-coder-ai/docker-git test && bun run --filter @effect-template/lib test",
"typecheck": "bun run --filter @prover-coder-ai/docker-git-session-sync typecheck && bun run --filter @prover-coder-ai/docker-git typecheck && bun run --filter @effect-template/lib typecheck",
"start": "bash -lc 'bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js \"$@\"' --"
"start": "bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js"
},
"devDependencies": {
"@changesets/changelog-github": "^0.7.0",
Expand Down
10 changes: 5 additions & 5 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@
"prebuild:docker-git": "bun install --cwd ../.. && bun run --cwd ../docker-git-session-sync build && bun run --cwd ../lib build",
"build:docker-git": "vite build --config vite.docker-git.config.ts",
"check": "bun run typecheck",
"clone": "bash -lc 'bun run build:docker-git && bun dist/src/docker-git/main.js clone \"$@\"' --",
"open": "bash -lc 'bun run build:docker-git && bun dist/src/docker-git/main.js open \"$@\"' --",
"docker-git": "bash -lc 'bun run build:docker-git && bun dist/src/docker-git/main.js \"$@\"' --",
"list": "bash -lc 'bun run build:docker-git && bun dist/src/docker-git/main.js ps \"$@\"' --",
"clone": "bun run build:docker-git && bun dist/src/docker-git/main.js clone",
"open": "bun run build:docker-git && bun dist/src/docker-git/main.js open",
"docker-git": "bun run build:docker-git && bun dist/src/docker-git/main.js",
"list": "bun run build:docker-git && bun dist/src/docker-git/main.js ps",
"preview:web": "vite preview --config vite.web.config.ts",
"start": "bash -lc 'bun run build:docker-git && bun dist/src/docker-git/main.js \"$@\"' --",
"start": "bun run build:docker-git && bun dist/src/docker-git/main.js",
"pretest": "bun run --cwd ../docker-git-session-sync build && bun run --cwd ../lib build",
"test": "bun run lint:tests && vitest run",
"pretypecheck": "bun run --cwd ../docker-git-session-sync build && bun run --cwd ../lib build",
Expand Down
13 changes: 12 additions & 1 deletion packages/app/src/lib/core/templates-entrypoint/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,17 @@ const renderCloneAuthRepoUrl = (): string =>
AUTH_REPO_URL="$(printf "%s" "$REPO_URL" | sed "s#^https://#https://\${RESOLVED_GIT_AUTH_USER}:\${RESOLVED_GIT_AUTH_TOKEN}@#")"
fi`

// CHANGE: restrict clone-cache mirror refresh to branch and tag refs
// WHY: broad refs include hosted forge PR refs and make cache reuse proportional to every remote ref
// QUOTE(ТЗ): "Для тестов можно реализовать CI/CD workflow для Linux, MAC, Windows"
// REF: issue-278-ci-check-clone-cache
// SOURCE: n/a
// FORMAT THEOREM: forall r in refreshedRefs: r in refs/heads/* union refs/tags/*
// PURITY: CORE
// INVARIANT: clone-cache refresh never requests refs/pull/* or refs/merge-requests/*
// COMPLEXITY: O(|heads| + |tags|)
const cloneCacheRefreshRefspecs = "'+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*'"

const renderCloneCacheInit = (config: TemplateConfig): string =>
` CLONE_CACHE_ARGS=""
CACHE_REPO_DIR=""
Expand All @@ -135,7 +146,7 @@ const renderCloneCacheInit = (config: TemplateConfig): string =>
chown 1000:1000 "$CACHE_ROOT" || true
if [[ -d "$CACHE_REPO_DIR" ]]; then
if su - ${config.sshUser} -c "git --git-dir '$CACHE_REPO_DIR' rev-parse --is-bare-repository >/dev/null 2>&1"; then
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git --git-dir '$CACHE_REPO_DIR' fetch --progress --prune '$AUTH_REPO_URL' '+refs/*:refs/*'"; then
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git --git-dir '$CACHE_REPO_DIR' fetch --progress --prune '$AUTH_REPO_URL' ${cloneCacheRefreshRefspecs}"; then
echo "[clone-cache] mirror refresh failed for $REPO_URL"
fi
CLONE_CACHE_ARGS="--reference-if-able '$CACHE_REPO_DIR' --dissociate"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { describe, expect, it } from "@effect/vitest"

import rootPackage from "../../../../package.json" with { type: "json" }
import sessionSyncPackage from "../../../docker-git-session-sync/package.json" with { type: "json" }
import appPackage from "../../package.json" with { type: "json" }

const launchScripts: ReadonlyArray<Readonly<{ packageName: string; scriptName: string; script: string }>> = [
{ packageName: "workspace", scriptName: "clone", script: rootPackage.scripts.clone },
{ packageName: "workspace", scriptName: "open", script: rootPackage.scripts.open },
{ packageName: "workspace", scriptName: "docker-git", script: rootPackage.scripts["docker-git"] },
{ packageName: "workspace", scriptName: "list", script: rootPackage.scripts.list },
{ packageName: "workspace", scriptName: "start", script: rootPackage.scripts.start },
{ packageName: "@prover-coder-ai/docker-git", scriptName: "clone", script: appPackage.scripts.clone },
{ packageName: "@prover-coder-ai/docker-git", scriptName: "open", script: appPackage.scripts.open },
{
packageName: "@prover-coder-ai/docker-git",
scriptName: "docker-git",
script: appPackage.scripts["docker-git"]
},
{ packageName: "@prover-coder-ai/docker-git", scriptName: "list", script: appPackage.scripts.list },
{ packageName: "@prover-coder-ai/docker-git", scriptName: "start", script: appPackage.scripts.start }
]

describe("package scripts cross-platform contract", () => {
it("keeps user-facing launch scripts independent from bash", () => {
for (const entry of launchScripts) {
expect(entry.script, `${entry.packageName}:${entry.scriptName}`).not.toMatch(/\bbash(?:\.exe)?\b/u)
}
})

it("keeps final package build independent from raw chmod", () => {
expect(sessionSyncPackage.scripts.build).not.toMatch(/\bchmod\s+/u)
})
})
2 changes: 1 addition & 1 deletion packages/docker-git-session-sync/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"dist"
],
"scripts": {
"build": "vite build && chmod +x dist/docker-git-session-sync.js",
"build": "vite build && bun ../../scripts/mark-executable.mjs dist/docker-git-session-sync.js",
"check": "bun run typecheck",
"prepack": "bun run build",
"test": "vitest run --passWithNoTests",
Expand Down
13 changes: 12 additions & 1 deletion packages/lib/src/core/templates-entrypoint/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,17 @@ const renderCloneAuthRepoUrl = (): string =>
AUTH_REPO_URL="$(printf "%s" "$REPO_URL" | sed "s#^https://#https://\${RESOLVED_GIT_AUTH_USER}:\${RESOLVED_GIT_AUTH_TOKEN}@#")"
fi`

// CHANGE: restrict clone-cache mirror refresh to branch and tag refs
// WHY: broad refs include hosted forge PR refs and make cache reuse proportional to every remote ref
// QUOTE(ТЗ): "Для тестов можно реализовать CI/CD workflow для Linux, MAC, Windows"
// REF: issue-278-ci-check-clone-cache
// SOURCE: n/a
// FORMAT THEOREM: forall r in refreshedRefs: r in refs/heads/* union refs/tags/*
// PURITY: CORE
// INVARIANT: clone-cache refresh never requests refs/pull/* or refs/merge-requests/*
// COMPLEXITY: O(|heads| + |tags|)
const cloneCacheRefreshRefspecs = "'+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*'"

const renderCloneCacheInit = (config: TemplateConfig): string =>
` CLONE_CACHE_ARGS=""
CACHE_REPO_DIR=""
Expand All @@ -135,7 +146,7 @@ const renderCloneCacheInit = (config: TemplateConfig): string =>
chown 1000:1000 "$CACHE_ROOT" || true
if [[ -d "$CACHE_REPO_DIR" ]]; then
if su - ${config.sshUser} -c "git --git-dir '$CACHE_REPO_DIR' rev-parse --is-bare-repository >/dev/null 2>&1"; then
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git --git-dir '$CACHE_REPO_DIR' fetch --progress --prune '$AUTH_REPO_URL' '+refs/*:refs/*'"; then
if ! su - ${config.sshUser} -c "GIT_TERMINAL_PROMPT=0 git --git-dir '$CACHE_REPO_DIR' fetch --progress --prune '$AUTH_REPO_URL' ${cloneCacheRefreshRefspecs}"; then
echo "[clone-cache] mirror refresh failed for $REPO_URL"
fi
CLONE_CACHE_ARGS="--reference-if-able '$CACHE_REPO_DIR' --dissociate"
Expand Down
11 changes: 11 additions & 0 deletions packages/lib/tests/core/templates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@ describe("renderDockerfile", () => {
})
})

describe("renderEntrypoint clone cache", () => {
it("refreshes mirrors without broad remote refs", () => {
const entrypoint = renderEntrypoint(makeTemplateConfig())

expect(entrypoint).toContain("git --git-dir '$CACHE_REPO_DIR' fetch")
expect(entrypoint).toContain("'+refs/heads/*:refs/heads/*'")
expect(entrypoint).toContain("'+refs/tags/*:refs/tags/*'")
expect(entrypoint).not.toContain("'+refs/*:refs/*'")
})
})

describe("renderEntrypointGitHooks", () => {
it("installs pre-push protection checks and a global git post-push runtime", () => {
const hooks = renderEntrypointGitHooks()
Expand Down
23 changes: 23 additions & 0 deletions scripts/mark-executable.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bun

import { chmodSync } from "node:fs"
import { resolve } from "node:path"

// CHANGE: centralize executable-bit handling for generated CLI files.
// WHY: POSIX chmod is not available on Windows, while Linux/macOS package builds require executable bins.
// QUOTE(TZ): "run conveniently on Windows and Linux"
// REF: issue-278
// SOURCE: n/a
// FORMAT THEOREM: forall p in Paths: platform=win32 -> no_posix_chmod(p), platform!=win32 -> executable(p)
// PURITY: SHELL
// EFFECT: filesystem metadata update
// INVARIANT: missing target argument exits non-zero; Windows builds do not invoke POSIX chmod.
// COMPLEXITY: O(1)/O(1)
const target = process.argv[2]

if (target === undefined || target.length === 0) {
process.stderr.write("Usage: mark-executable <path>\n")
process.exitCode = 1
} else if (process.platform !== "win32") {
chmodSync(resolve(process.cwd(), target), 0o755)
}
Loading