diff --git a/.github/workflows/build-and-upload.yml b/.github/workflows/build-and-upload.yml index 0f016523..47870b3b 100644 --- a/.github/workflows/build-and-upload.yml +++ b/.github/workflows/build-and-upload.yml @@ -4,21 +4,33 @@ on: workflow_call: inputs: version: - description: "Version to apply to workspace packages" - required: true + description: "Version to apply to workspace packages (release builds)" + required: false + default: "" type: string tag: - description: "Git tag to upload assets to" - required: true + description: "Git tag to upload assets to (release builds)" + required: false + default: "" type: string release_name: description: "Release name (unused here, for context)" - required: true + required: false + default: "" type: string + upload: + description: "Upload built artifacts to the GitHub release" + required: false + default: true + type: boolean + set_versions: + description: "Run npm version to set workspace versions" + required: false + default: true + type: boolean -permissions: - id-token: write - contents: write +# Permissions are intentionally omitted here so callers can choose +# least-privilege (e.g. dev CI uses read-only; releases grant write). env: NODE_VERSION: 20 @@ -41,10 +53,11 @@ jobs: cache: npm - name: Set workspace versions + if: ${{ inputs.set_versions && inputs.version != '' }} run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version - name: Install dependencies - run: npm ci --workspaces + run: npm ci --workspaces --include=optional - name: Ensure rollup native binary run: npm install @rollup/rollup-darwin-x64 --no-save @@ -53,6 +66,7 @@ jobs: run: npm run build:mac --workspace @neuralnomads/codenomad-electron-app - name: Upload release assets + if: ${{ inputs.upload && inputs.tag != '' }} run: | set -euo pipefail shopt -s nullglob @@ -79,11 +93,12 @@ jobs: cache: npm - name: Set workspace versions + if: ${{ inputs.set_versions && inputs.version != '' }} run: npm version ${{ env.VERSION }} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version shell: bash - name: Install dependencies - run: npm ci --workspaces + run: npm ci --workspaces --include=optional - name: Ensure rollup native binary run: npm install @rollup/rollup-win32-x64-msvc --no-save @@ -92,6 +107,7 @@ jobs: run: npm run build:win --workspace @neuralnomads/codenomad-electron-app - name: Upload release assets + if: ${{ inputs.upload && inputs.tag != '' }} shell: pwsh run: | Get-ChildItem -Path "packages/electron-app/release" -Filter *.zip -File | ForEach-Object { @@ -116,10 +132,11 @@ jobs: cache: npm - name: Set workspace versions + if: ${{ inputs.set_versions && inputs.version != '' }} run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version - name: Install dependencies - run: npm ci --workspaces + run: npm ci --workspaces --include=optional - name: Ensure rollup native binary run: npm install @rollup/rollup-linux-x64-gnu --no-save @@ -128,6 +145,7 @@ jobs: run: npm run build:linux --workspace @neuralnomads/codenomad-electron-app - name: Upload release assets + if: ${{ inputs.upload && inputs.tag != '' }} run: | set -euo pipefail shopt -s nullglob @@ -157,18 +175,38 @@ jobs: uses: dtolnay/rust-toolchain@stable - name: Set workspace versions + if: ${{ inputs.set_versions && inputs.version != '' }} run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version - name: Install dependencies - run: npm ci --workspaces + run: npm ci --workspaces --include=optional - name: Ensure rollup native binary run: npm install @rollup/rollup-darwin-x64 --no-save + - name: Prebuild (Tauri) + run: npm run prebuild --workspace @codenomad/tauri-app + + - name: Ensure tauri native binary + working-directory: packages/tauri-app + run: | + set -euo pipefail + for attempt in 1 2 3; do + if [ "$attempt" -gt 1 ]; then + echo "Retrying Tauri CLI install (attempt $attempt)..." + fi + npm install @tauri-apps/cli@2.9.4 @tauri-apps/cli-darwin-x64@2.9.4 --no-save --no-audit --no-fund --workspaces=false + node -e "require('@tauri-apps/cli'); console.log('Tauri CLI loaded')" && exit 0 + done + echo "Tauri CLI failed to load after retries" >&2 + exit 1 + - name: Build macOS bundle (Tauri) - run: npm run build --workspace @codenomad/tauri-app + working-directory: packages/tauri-app + run: npm exec -- tauri build - name: Package Tauri artifacts (macOS) + if: ${{ inputs.upload }} run: | set -euo pipefail BUNDLE_ROOT="packages/tauri-app/target/release/bundle" @@ -180,6 +218,7 @@ jobs: fi - name: Upload Tauri release assets (macOS) + if: ${{ inputs.upload && inputs.tag != '' }} run: | set -euo pipefail shopt -s nullglob @@ -209,18 +248,38 @@ jobs: uses: dtolnay/rust-toolchain@stable - name: Set workspace versions + if: ${{ inputs.set_versions && inputs.version != '' }} run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version - name: Install dependencies - run: npm ci --workspaces + run: npm ci --workspaces --include=optional - name: Ensure rollup native binary run: npm install @rollup/rollup-darwin-arm64 --no-save + - name: Prebuild (Tauri) + run: npm run prebuild --workspace @codenomad/tauri-app + + - name: Ensure tauri native binary + working-directory: packages/tauri-app + run: | + set -euo pipefail + for attempt in 1 2 3; do + if [ "$attempt" -gt 1 ]; then + echo "Retrying Tauri CLI install (attempt $attempt)..." + fi + npm install @tauri-apps/cli@2.9.4 @tauri-apps/cli-darwin-arm64@2.9.4 --no-save --no-audit --no-fund --workspaces=false + node -e "require('@tauri-apps/cli'); console.log('Tauri CLI loaded')" && exit 0 + done + echo "Tauri CLI failed to load after retries" >&2 + exit 1 + - name: Build macOS bundle (Tauri, arm64) - run: npm run build --workspace @codenomad/tauri-app + working-directory: packages/tauri-app + run: npm exec -- tauri build - name: Package Tauri artifacts (macOS arm64) + if: ${{ inputs.upload }} run: | set -euo pipefail BUNDLE_ROOT="packages/tauri-app/target/release/bundle" @@ -232,6 +291,7 @@ jobs: fi - name: Upload Tauri release assets (macOS arm64) + if: ${{ inputs.upload && inputs.tag != '' }} run: | set -euo pipefail shopt -s nullglob @@ -261,19 +321,41 @@ jobs: uses: dtolnay/rust-toolchain@stable - name: Set workspace versions + if: ${{ inputs.set_versions && inputs.version != '' }} run: npm version ${{ env.VERSION }} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version shell: bash - name: Install dependencies - run: npm ci --workspaces + run: npm ci --workspaces --include=optional - name: Ensure rollup native binary run: npm install @rollup/rollup-win32-x64-msvc --no-save + - name: Prebuild (Tauri) + run: npm run prebuild --workspace @codenomad/tauri-app + + - name: Ensure tauri native binary + shell: bash + working-directory: packages/tauri-app + run: | + set -euo pipefail + for attempt in 1 2 3; do + if [ "$attempt" -gt 1 ]; then + echo "Retrying Tauri CLI install (attempt $attempt)..." + fi + npm install @tauri-apps/cli@2.9.4 @tauri-apps/cli-win32-x64-msvc@2.9.4 --no-save --no-audit --no-fund --workspaces=false + node -e "require('@tauri-apps/cli'); console.log('Tauri CLI loaded')" && exit 0 + done + echo "Tauri CLI failed to load after retries" >&2 + exit 1 + - name: Build Windows bundle (Tauri) - run: npm run build --workspace @codenomad/tauri-app + shell: bash + working-directory: packages/tauri-app + run: npm exec -- tauri build - name: Package Tauri artifacts (Windows) + if: ${{ inputs.upload }} shell: pwsh run: | $bundleRoot = "packages/tauri-app/target/release/bundle" @@ -287,6 +369,7 @@ jobs: } - name: Upload Tauri release assets (Windows) + if: ${{ inputs.upload && inputs.tag != '' }} shell: pwsh run: | if (Test-Path "packages/tauri-app/release-tauri") { @@ -329,18 +412,38 @@ jobs: librsvg2-dev - name: Set workspace versions + if: ${{ inputs.set_versions && inputs.version != '' }} run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version - name: Install dependencies - run: npm ci --workspaces + run: npm ci --workspaces --include=optional - name: Ensure rollup native binary run: npm install @rollup/rollup-linux-x64-gnu --no-save + - name: Prebuild (Tauri) + run: npm run prebuild --workspace @codenomad/tauri-app + + - name: Ensure tauri native binary + working-directory: packages/tauri-app + run: | + set -euo pipefail + for attempt in 1 2 3; do + if [ "$attempt" -gt 1 ]; then + echo "Retrying Tauri CLI install (attempt $attempt)..." + fi + npm install @tauri-apps/cli@2.9.4 @tauri-apps/cli-linux-x64-gnu@2.9.4 --no-save --no-audit --no-fund --workspaces=false + node -e "require('@tauri-apps/cli'); console.log('Tauri CLI loaded')" && exit 0 + done + echo "Tauri CLI failed to load after retries" >&2 + exit 1 + - name: Build Linux bundle (Tauri) - run: npm run build --workspace @codenomad/tauri-app + working-directory: packages/tauri-app + run: npm exec -- tauri build - name: Package Tauri artifacts (Linux) + if: ${{ inputs.upload }} run: | set -euo pipefail SEARCH_ROOT="packages/tauri-app/target" @@ -367,6 +470,7 @@ jobs: cp "$rpm" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.rpm" - name: Upload Tauri release assets (Linux) + if: ${{ inputs.upload && inputs.tag != '' }} run: | set -euo pipefail shopt -s nullglob @@ -429,7 +533,7 @@ jobs: run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version - name: Install dependencies - run: npm ci --workspaces + run: npm ci --workspaces --include=optional - name: Ensure rollup native binary run: npm install @rollup/rollup-linux-arm64-gnu --no-save @@ -497,10 +601,11 @@ jobs: sudo gem install --no-document fpm - name: Set workspace versions + if: ${{ inputs.set_versions && inputs.version != '' }} run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version - name: Install project dependencies - run: npm ci --workspaces + run: npm ci --workspaces --include=optional - name: Ensure rollup native binary run: npm install @rollup/rollup-linux-x64-gnu --no-save @@ -509,6 +614,7 @@ jobs: run: npm run build:linux-rpm --workspace @neuralnomads/codenomad-electron-app - name: Upload RPM release assets + if: ${{ inputs.upload && inputs.tag != '' }} run: | set -euo pipefail shopt -s nullglob diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml index aa245292..235e9bc0 100644 --- a/.github/workflows/dev-release.yml +++ b/.github/workflows/dev-release.yml @@ -1,16 +1,18 @@ -name: Dev Release +name: Dev CI on: + push: + branches: + - dev workflow_dispatch: permissions: - id-token: write - contents: write + contents: read jobs: - dev-release: - uses: ./.github/workflows/reusable-release.yml + dev-ci: + uses: ./.github/workflows/build-and-upload.yml with: - version_suffix: -dev - dist_tag: dev + upload: false + set_versions: false secrets: inherit diff --git a/package-lock.json b/package-lock.json index 4ea34889..a82b5c47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codenomad-workspace", - "version": "0.5.0", + "version": "0.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codenomad-workspace", - "version": "0.5.0", + "version": "0.5.1", "dependencies": { "7zip-bin": "^5.2.0", "google-auth-library": "^10.5.0" @@ -16,7 +16,10 @@ }, "workspaces": { "packages": [ - "packages/*" + "packages/server", + "packages/ui", + "packages/electron-app", + "packages/tauri-app" ] } }, @@ -1092,25 +1095,6 @@ "node": ">= 8" } }, - "node_modules/@opencode-ai/plugin": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.1.1.tgz", - "integrity": "sha512-OZGvpDal8YsSo6dnatHfwviSToGZ6mJJyEKZGxUyWDuGCP7VhcoPkoM16ktl7TCVHkDK+TdwY9tKzkzFqQNc5w==", - "license": "MIT", - "dependencies": { - "@opencode-ai/sdk": "1.1.1", - "zod": "4.1.8" - } - }, - "node_modules/@opencode-ai/plugin/node_modules/zod": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", - "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/@opencode-ai/sdk": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.1.1.tgz", @@ -5268,10 +5252,6 @@ "regex-recursion": "^6.0.2" } }, - "node_modules/opencode-config": { - "resolved": "packages/opencode-config", - "link": true - }, "node_modules/p-cancelable": { "version": "2.1.1", "dev": true, @@ -7409,7 +7389,7 @@ }, "packages/electron-app": { "name": "@neuralnomads/codenomad-electron-app", - "version": "0.5.0", + "version": "0.5.1", "dependencies": { "@codenomad/ui": "file:../ui", "@neuralnomads/codenomad": "file:../server" @@ -7434,14 +7414,16 @@ "license": "MIT" }, "packages/opencode-config": { + "name": "@codenomad/opencode-config", "version": "0.5.0", + "extraneous": true, "dependencies": { "@opencode-ai/plugin": "1.1.1" } }, "packages/server": { "name": "@neuralnomads/codenomad", - "version": "0.5.0", + "version": "0.5.1", "dependencies": { "@fastify/cors": "^8.5.0", "@fastify/reply-from": "^9.8.0", @@ -7476,14 +7458,14 @@ }, "packages/tauri-app": { "name": "@codenomad/tauri-app", - "version": "0.5.0", + "version": "0.5.1", "devDependencies": { "@tauri-apps/cli": "^2.9.4" } }, "packages/ui": { "name": "@codenomad/ui", - "version": "0.5.0", + "version": "0.5.1", "dependencies": { "@git-diff-view/solid": "^0.0.8", "@kobalte/core": "0.13.11", diff --git a/package.json b/package.json index 95179b72..dfe6d649 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,14 @@ { "name": "codenomad-workspace", - "version": "0.5.0", + "version": "0.5.1", "private": true, "description": "CodeNomad monorepo workspace", "workspaces": { "packages": [ - "packages/*" + "packages/server", + "packages/ui", + "packages/electron-app", + "packages/tauri-app" ] }, "scripts": { diff --git a/packages/electron-app/package.json b/packages/electron-app/package.json index d477d9ac..0aacc0a4 100644 --- a/packages/electron-app/package.json +++ b/packages/electron-app/package.json @@ -1,6 +1,6 @@ { "name": "@neuralnomads/codenomad-electron-app", - "version": "0.5.0", + "version": "0.5.1", "description": "CodeNomad - AI coding assistant", "author": { "name": "Neural Nomads", diff --git a/packages/electron-app/scripts/build.js b/packages/electron-app/scripts/build.js index 17f58d02..4cc52ce7 100644 --- a/packages/electron-app/scripts/build.js +++ b/packages/electron-app/scripts/build.js @@ -2,7 +2,7 @@ import { spawn } from "child_process" import { existsSync } from "fs" -import { join } from "path" +import path, { join } from "path" import { fileURLToPath } from "url" const __dirname = fileURLToPath(new URL(".", import.meta.url)) @@ -55,12 +55,22 @@ const platforms = { function run(command, args, options = {}) { return new Promise((resolve, reject) => { + const env = { ...process.env, NODE_PATH: nodeModulesPath, ...(options.env || {}) } + const pathKey = Object.keys(env).find((key) => key.toLowerCase() === "path") ?? "PATH" + + const binPaths = [ + join(nodeModulesPath, ".bin"), + join(workspaceNodeModulesPath, ".bin"), + ] + + env[pathKey] = `${binPaths.join(path.delimiter)}${path.delimiter}${env[pathKey] ?? ""}` + const spawnOptions = { cwd: appDir, stdio: "inherit", shell: process.platform === "win32", ...options, - env: { ...process.env, NODE_PATH: nodeModulesPath, ...(options.env || {}) }, + env, } const child = spawn(command, args, spawnOptions) diff --git a/packages/opencode-config/package.json b/packages/opencode-config/package.json new file mode 100644 index 00000000..9e990320 --- /dev/null +++ b/packages/opencode-config/package.json @@ -0,0 +1,8 @@ +{ + "name": "@codenomad/opencode-config", + "version": "0.5.0", + "private": true, + "dependencies": { + "@opencode-ai/plugin": "1.1.1" + } +} diff --git a/packages/server/package-lock.json b/packages/server/package-lock.json index 991f2cd7..031eefab 100644 --- a/packages/server/package-lock.json +++ b/packages/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "@neuralnomads/codenomad", - "version": "0.5.0", + "version": "0.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@neuralnomads/codenomad", - "version": "0.5.0", + "version": "0.5.1", "dependencies": { "@fastify/cors": "^8.5.0", "commander": "^12.1.0", diff --git a/packages/server/package.json b/packages/server/package.json index 39c6d089..a2e44461 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@neuralnomads/codenomad", - "version": "0.5.0", + "version": "0.5.1", "description": "CodeNomad Server", "author": { "name": "Neural Nomads", diff --git a/packages/server/scripts/copy-opencode-config.mjs b/packages/server/scripts/copy-opencode-config.mjs index ab952f9d..5e714395 100644 --- a/packages/server/scripts/copy-opencode-config.mjs +++ b/packages/server/scripts/copy-opencode-config.mjs @@ -10,7 +10,9 @@ const cliRoot = path.resolve(__dirname, "..") const sourceDir = path.resolve(cliRoot, "../opencode-config") const targetDir = path.resolve(cliRoot, "dist/opencode-config") const nodeModulesDir = path.resolve(sourceDir, "node_modules") -const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm" +const selfLinkDir = path.resolve(nodeModulesDir, "@codenomad", "opencode-config") +const npmExecPath = process.env.npm_execpath +const npmNodeExecPath = process.env.npm_node_execpath if (!existsSync(sourceDir)) { console.error(`[copy-opencode-config] Missing source directory at ${sourceDir}`) @@ -19,27 +21,39 @@ if (!existsSync(sourceDir)) { if (!existsSync(nodeModulesDir)) { console.log(`[copy-opencode-config] Installing opencode-config dependencies in ${sourceDir}`) - const result = spawnSync( - npmCmd, - [ - "install", - "--prefix", - sourceDir, - "--omit=dev", - "--ignore-scripts", - "--fund=false", - "--audit=false", - "--package-lock=false", - "--workspaces=false", - ], - { cwd: sourceDir, stdio: "inherit", env: { ...process.env, npm_config_workspaces: "false" } }, - ) + + const npmArgs = [ + "install", + "--prefix", + sourceDir, + "--omit=dev", + "--ignore-scripts", + "--fund=false", + "--audit=false", + "--package-lock=false", + "--workspaces=false", + ] + + const env = { ...process.env, npm_config_workspaces: "false" } + + const npmCli = npmExecPath && npmNodeExecPath ? [npmNodeExecPath, [npmExecPath, ...npmArgs]] : null + const result = npmCli + ? spawnSync(npmCli[0], npmCli[1], { cwd: sourceDir, stdio: "inherit", env }) + : spawnSync("npm", npmArgs, { cwd: sourceDir, stdio: "inherit", env, shell: process.platform === "win32" }) + if (result.status !== 0) { + if (result.error) { + console.error("[copy-opencode-config] npm install failed to start", result.error) + } console.error("[copy-opencode-config] Failed to install opencode-config dependencies") process.exit(result.status ?? 1) } } +// npm can create a self-referential link for scoped packages on Windows. +// That link causes recursive copies (ELOOP) during bundling. +rmSync(selfLinkDir, { recursive: true, force: true }) + rmSync(targetDir, { recursive: true, force: true }) mkdirSync(path.dirname(targetDir), { recursive: true }) cpSync(sourceDir, targetDir, { recursive: true }) diff --git a/packages/tauri-app/package.json b/packages/tauri-app/package.json index 49609ce0..ff917284 100644 --- a/packages/tauri-app/package.json +++ b/packages/tauri-app/package.json @@ -1,15 +1,15 @@ { "name": "@codenomad/tauri-app", - "version": "0.5.0", + "version": "0.5.1", "private": true, "scripts": { - "dev": "npx --yes @tauri-apps/cli@^2.9.4 dev", + "dev": "tauri dev", "dev:ui": "npm run dev --workspace @codenomad/ui", "dev:prep": "node ./scripts/dev-prep.js", "dev:bootstrap": "npm run dev:prep && npm run dev:ui", "prebuild": "node ./scripts/prebuild.js", "bundle:server": "npm run prebuild", - "build": "npx --yes @tauri-apps/cli@^2.9.4 build" + "build": "tauri build" }, "devDependencies": { "@tauri-apps/cli": "^2.9.4" diff --git a/packages/ui/package.json b/packages/ui/package.json index 877be66c..4c081afb 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@codenomad/ui", - "version": "0.5.0", + "version": "0.5.1", "private": true, "type": "module", "scripts": {