From ee6d0e68b5557867389eb7ffc959cfd88a78cd29 Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 12 May 2026 09:48:40 +0000 Subject: [PATCH 1/3] Initial commit with task details Adding .gitkeep for PR creation (default mode). This file will be removed when the task is complete. Issue: https://github.com/ProverCoderAI/docker-git/issues/278 --- .gitkeep | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitkeep diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 00000000..f8bf83ed --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +# .gitkeep file auto-generated at 2026-05-12T09:48:40.302Z for PR creation at branch issue-278-d92a50df7e27 for issue https://github.com/ProverCoderAI/docker-git/issues/278 \ No newline at end of file From 87ede21d99ec83b04e758ac1791ad272f74d20e1 Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 12 May 2026 10:17:28 +0000 Subject: [PATCH 2/3] feat(ci): add cross-platform final build checks --- .changeset/cross-platform-final-build.md | 6 +++ .github/actions/setup/action.yml | 1 + .github/workflows/final-build.yml | 46 +++++++++++++++++++ .gitkeep | 1 - package.json | 10 ++-- packages/app/package.json | 10 ++-- .../package-scripts-cross-platform.test.ts | 34 ++++++++++++++ packages/docker-git-session-sync/package.json | 2 +- scripts/mark-executable.mjs | 23 ++++++++++ 9 files changed, 121 insertions(+), 12 deletions(-) create mode 100644 .changeset/cross-platform-final-build.md create mode 100644 .github/workflows/final-build.yml delete mode 100644 .gitkeep create mode 100644 packages/app/tests/docker-git/package-scripts-cross-platform.test.ts create mode 100644 scripts/mark-executable.mjs diff --git a/.changeset/cross-platform-final-build.md b/.changeset/cross-platform-final-build.md new file mode 100644 index 00000000..260c76bb --- /dev/null +++ b/.changeset/cross-platform-final-build.md @@ -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. diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index cb33036e..0b19744d 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -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 diff --git a/.github/workflows/final-build.yml b/.github/workflows/final-build.yml new file mode 100644 index 00000000..70e931f7 --- /dev/null +++ b/.github/workflows/final-build.yml @@ -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 diff --git a/.gitkeep b/.gitkeep deleted file mode 100644 index f8bf83ed..00000000 --- a/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# .gitkeep file auto-generated at 2026-05-12T09:48:40.302Z for PR creation at branch issue-278-d92a50df7e27 for issue https://github.com/ProverCoderAI/docker-git/issues/278 \ No newline at end of file diff --git a/package.json b/package.json index 294c7e68..e8ea3862 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", @@ -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", diff --git a/packages/app/package.json b/packages/app/package.json index 85adcd9b..bd2d7ba6 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -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", diff --git a/packages/app/tests/docker-git/package-scripts-cross-platform.test.ts b/packages/app/tests/docker-git/package-scripts-cross-platform.test.ts new file mode 100644 index 00000000..4d4596af --- /dev/null +++ b/packages/app/tests/docker-git/package-scripts-cross-platform.test.ts @@ -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> = [ + { 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) + }) +}) diff --git a/packages/docker-git-session-sync/package.json b/packages/docker-git-session-sync/package.json index 228ac33d..5b4b3725 100644 --- a/packages/docker-git-session-sync/package.json +++ b/packages/docker-git-session-sync/package.json @@ -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", diff --git a/scripts/mark-executable.mjs b/scripts/mark-executable.mjs new file mode 100644 index 00000000..d7a3befa --- /dev/null +++ b/scripts/mark-executable.mjs @@ -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 \n") + process.exitCode = 1 +} else if (process.platform !== "win32") { + chmodSync(resolve(process.cwd(), target), 0o755) +} From a872a79230edcea7405200a732154568f2051d81 Mon Sep 17 00:00:00 2001 From: konard Date: Tue, 12 May 2026 11:09:16 +0000 Subject: [PATCH 3/3] fix(ci): bound clone cache mirror refresh refs --- .../app/src/lib/core/templates-entrypoint/tasks.ts | 13 ++++++++++++- packages/lib/src/core/templates-entrypoint/tasks.ts | 13 ++++++++++++- packages/lib/tests/core/templates.test.ts | 11 +++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/app/src/lib/core/templates-entrypoint/tasks.ts b/packages/app/src/lib/core/templates-entrypoint/tasks.ts index d6264455..1889eb05 100644 --- a/packages/app/src/lib/core/templates-entrypoint/tasks.ts +++ b/packages/app/src/lib/core/templates-entrypoint/tasks.ts @@ -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="" @@ -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" diff --git a/packages/lib/src/core/templates-entrypoint/tasks.ts b/packages/lib/src/core/templates-entrypoint/tasks.ts index d6264455..1889eb05 100644 --- a/packages/lib/src/core/templates-entrypoint/tasks.ts +++ b/packages/lib/src/core/templates-entrypoint/tasks.ts @@ -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="" @@ -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" diff --git a/packages/lib/tests/core/templates.test.ts b/packages/lib/tests/core/templates.test.ts index 4de72f9e..2092a1ba 100644 --- a/packages/lib/tests/core/templates.test.ts +++ b/packages/lib/tests/core/templates.test.ts @@ -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()