From 6eaeec1ec6900d8054e2e623ac0a22e93ea75fda Mon Sep 17 00:00:00 2001 From: Bobby Christopher Date: Thu, 11 Jun 2026 13:57:02 -0400 Subject: [PATCH 1/2] Speed up CI builds for snapshot-based Genesis deploys (v2). Wire --skip-type-check through CI into deploy, skip redundant bun install when deps are already on disk, and honor skip typecheck in the v2 deploy build step for legacy Genesis projects. Co-authored-by: Cursor --- packages/cli/package.json | 2 +- packages/cli/src/cmd/build/ci.ts | 40 +++++++++++++++++++++----- packages/cli/src/cmd/build/index.ts | 1 + packages/cli/src/cmd/cloud/deploy.ts | 35 +++++++++++----------- packages/cli/src/types.ts | 1 + packages/cli/test/cmd/build/ci.test.ts | 37 ++++++++++++++++++++++-- 6 files changed, 90 insertions(+), 26 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 5949d4b97..5dff7e965 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@agentuity/cli", - "version": "2.0.27", + "version": "2.0.28", "license": "Apache-2.0", "author": "Agentuity employees and contributors", "type": "module", diff --git a/packages/cli/src/cmd/build/ci.ts b/packages/cli/src/cmd/build/ci.ts index f066f33bd..a5712535d 100644 --- a/packages/cli/src/cmd/build/ci.ts +++ b/packages/cli/src/cmd/build/ci.ts @@ -2,7 +2,7 @@ import type { Logger } from '@agentuity/core'; import { spawn } from 'bun'; import { mkdir, mkdtemp, readdir, realpath, rm, stat } from 'node:fs/promises'; import { tmpdir } from 'node:os'; -import { join } from 'node:path'; +import { dirname, join } from 'node:path'; import { ErrorCode } from '../../errors'; import * as tui from '../../tui'; @@ -21,6 +21,27 @@ export interface CIBuildOptions { pullRequestUrl?: string; logsUrl?: string; skipDnsValidation?: boolean; + skipTypeCheck?: boolean; +} + +export async function hasProjectDependenciesInstalled(projectDir: string): Promise { + let dir = projectDir; + for (;;) { + const bunStore = join(dir, 'node_modules', '.bun'); + const lockfile = join(dir, 'bun.lock'); + if ( + (await stat(bunStore).catch(() => null))?.isDirectory() && + (await Bun.file(lockfile).exists()) + ) { + return true; + } + + const parent = dirname(dir); + if (parent === dir) { + return false; + } + dir = parent; + } } async function runCommand(cmd: string[], cwd: string): Promise { @@ -127,6 +148,7 @@ export function buildDeployArgs(opts: CIBuildOptions): string[] { if (opts.pullRequestUrl) args.push('--pull-request-url', opts.pullRequestUrl); if (opts.logsUrl) args.push('--logs-url', opts.logsUrl); if (opts.skipDnsValidation) args.push('--skip-dns-validation'); + if (opts.skipTypeCheck) args.push('--skip-type-check'); return args; } @@ -210,12 +232,16 @@ export async function runCIBuild(opts: CIBuildOptions, _logger: Logger): Promise await Bun.write(join(projectDir, '.env'), `AGENTUITY_SDK_KEY=${sdkKey}\n`); } - tui.info('3️⃣ Installing your project dependencies...'); - const installExit = await runCommand(['bun', 'install'], projectDir); - if (installExit !== 0) { - tui.error(`Dependency installation failed (exit ${installExit})`); - pendingExitCode = installExit; - return; + if (await hasProjectDependenciesInstalled(projectDir)) { + tui.info('3️⃣ Using existing project dependencies (skipping install)...'); + } else { + tui.info('3️⃣ Installing your project dependencies...'); + const installExit = await runCommand(['bun', 'install'], projectDir); + if (installExit !== 0) { + tui.error(`Dependency installation failed (exit ${installExit})`); + pendingExitCode = installExit; + return; + } } const packageJsonPath = join(projectDir, 'package.json'); diff --git a/packages/cli/src/cmd/build/index.ts b/packages/cli/src/cmd/build/index.ts index a98c681a9..239bb8ae5 100644 --- a/packages/cli/src/cmd/build/index.ts +++ b/packages/cli/src/cmd/build/index.ts @@ -75,6 +75,7 @@ export const command = createCommand({ pullRequestUrl: opts.pullRequestUrl, logsUrl: opts.logsUrl, skipDnsValidation: opts.skipDnsValidation ?? true, + skipTypeCheck: opts.skipTypeCheck, }, ctx.logger ); diff --git a/packages/cli/src/cmd/cloud/deploy.ts b/packages/cli/src/cmd/cloud/deploy.ts index 06f3b89ca..8bd8a8b17 100644 --- a/packages/cli/src/cmd/cloud/deploy.ts +++ b/packages/cli/src/cmd/cloud/deploy.ts @@ -402,6 +402,7 @@ export const deploySubcommand = createSubcommand({ childArgs.push(`--pull-request-number=${opts.pullRequestNumber}`); if (opts.pullRequestUrl) childArgs.push(`--pull-request-url=${opts.pullRequestUrl}`); if (opts.skipDnsValidation) childArgs.push('--skip-dns-validation'); + if (opts.skipTypeCheck) childArgs.push('--skip-type-check'); const result = await runForkedDeploy({ projectDir, @@ -650,23 +651,25 @@ export const deploySubcommand = createSubcommand({ let capturedOutput: string[] = []; const rootDir = resolve(projectDir); - // Run typecheck with collector for error reporting - const endTypecheckDiagnostic = collector.startDiagnostic('typecheck'); - const started = Date.now(); - const typeResult = await typecheck(rootDir, { collector }); - endTypecheckDiagnostic(); - - if (typeResult.success) { - capturedOutput.push( - tui.muted(`✓ Typechecked in ${Date.now() - started}ms`) - ); - } else { - // Errors already added to collector by typecheck() - // Write report before returning error - if (opts.reportFile) { - await collector.forceWrite(); + if (!opts.skipTypeCheck) { + // Run typecheck with collector for error reporting + const endTypecheckDiagnostic = collector.startDiagnostic('typecheck'); + const started = Date.now(); + const typeResult = await typecheck(rootDir, { collector }); + endTypecheckDiagnostic(); + + if (typeResult.success) { + capturedOutput.push( + tui.muted(`✓ Typechecked in ${Date.now() - started}ms`) + ); + } else { + // Errors already added to collector by typecheck() + // Write report before returning error + if (opts.reportFile) { + await collector.forceWrite(); + } + return stepError('Typecheck failed\n\n' + typeResult.output); } - return stepError('Typecheck failed\n\n' + typeResult.output); } try { const bundleResult = await viteBundle({ diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index d4af18464..98a15ad4a 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -826,6 +826,7 @@ export const DeployOptionsSchema = zod .boolean() .optional() .describe('Skip custom domain DNS validation before deploying'), + skipTypeCheck: zod.boolean().optional().describe('Skip TypeScript validation during deploy'), }) .merge(GitOptionsSchema); diff --git a/packages/cli/test/cmd/build/ci.test.ts b/packages/cli/test/cmd/build/ci.test.ts index 5a0d97c3b..ebae7fcf3 100644 --- a/packages/cli/test/cmd/build/ci.test.ts +++ b/packages/cli/test/cmd/build/ci.test.ts @@ -1,9 +1,14 @@ import { describe, expect, test } from 'bun:test'; import { Buffer } from 'node:buffer'; -import { mkdtemp, readFile, rm } from 'node:fs/promises'; +import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; -import { buildDeployArgs, downloadSource, sourceDownloadHeaders } from '../../../src/cmd/build/ci'; +import { + buildDeployArgs, + downloadSource, + hasProjectDependenciesInstalled, + sourceDownloadHeaders, +} from '../../../src/cmd/build/ci'; describe('build ci', () => { test('passes skip DNS validation to nested deploy', () => { @@ -11,6 +16,34 @@ describe('build ci', () => { expect(args).toContain('--skip-dns-validation'); }); + test('passes skip typecheck to nested deploy', () => { + const args = buildDeployArgs({ skipTypeCheck: true }); + expect(args).toContain('--skip-type-check'); + }); + + test('detects existing dependencies from monorepo root', async () => { + const dir = await mkdtemp(join(tmpdir(), 'agentuity-ci-deps-test-')); + try { + await mkdir(join(dir, 'node_modules', '.bun'), { recursive: true }); + await writeFile(join(dir, 'bun.lock'), ''); + const appDir = join(dir, 'apps', 'web'); + await mkdir(appDir, { recursive: true }); + + expect(await hasProjectDependenciesInstalled(appDir)).toBe(true); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + + test('requires install when dependencies are missing', async () => { + const dir = await mkdtemp(join(tmpdir(), 'agentuity-ci-deps-test-')); + try { + expect(await hasProjectDependenciesInstalled(dir)).toBe(false); + } finally { + await rm(dir, { recursive: true, force: true }); + } + }); + test('adds GitHub archive token only for GitHub archive hosts', () => { const previous = process.env.GITHUB_ARCHIVE_TOKEN; process.env.GITHUB_ARCHIVE_TOKEN = 'ghs_test_token'; From df8636a58ad80187c3c0a76b83fad2769af11654 Mon Sep 17 00:00:00 2001 From: Bobby Christopher Date: Mon, 15 Jun 2026 15:17:32 -0400 Subject: [PATCH 2/2] Keep CLI at 2.0.27; version bump belongs in release PR. Co-authored-by: Cursor --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 5dff7e965..5949d4b97 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@agentuity/cli", - "version": "2.0.28", + "version": "2.0.27", "license": "Apache-2.0", "author": "Agentuity employees and contributors", "type": "module",