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
81 changes: 69 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,10 @@ concurrency:
cancel-in-progress: true

jobs:
smoke:
name: Smoke (${{ matrix.os }})
runs-on: ${{ matrix.os }}
test:
name: Test (Linux)
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest

env:
DEVSPACE_ALLOWED_ROOTS: ${{ github.workspace }}
Expand All @@ -42,7 +35,6 @@ jobs:
run: npm ci

- name: Install Pi sandbox dependencies
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y ripgrep bubblewrap socat
Expand All @@ -55,11 +47,76 @@ jobs:

- name: Test
env:
DEVSPACE_REQUIRE_PI_SANDBOX: ${{ matrix.os == 'ubuntu-latest' && '1' || '0' }}
DEVSPACE_REQUIRE_PI_SANDBOX: 1
run: npm test

- name: Build
run: npm run build

- name: Doctor
run: node dist/cli.js doctor

platform:
name: Platform (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os:
- macos-latest
- windows-latest

env:
DEVSPACE_ALLOWED_ROOTS: ${{ github.workspace }}
DEVSPACE_OAUTH_OWNER_TOKEN: ci-owner-token-that-is-long-enough
DEVSPACE_PUBLIC_BASE_URL: http://127.0.0.1:7676

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Test platform boundaries
run: >-
npx tsx --test --test-concurrency=1
src/cli-workspace.test.ts
src/process-platform.test.ts
src/process-sessions.test.ts
src/roots.test.ts
src/workspaces.test.ts

- name: Build
run: npm run build

- name: Doctor
run: node dist/cli.js doctor

package:
name: Package (Node 26)
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 26
cache: npm

- name: Install dependencies
run: npm ci

- name: Test installed package
run: npm run test:package
219 changes: 219 additions & 0 deletions docs/testing-research.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"dev": "node scripts/dev-server.mjs",
"postinstall": "node scripts/fix-node-pty-permissions.mjs",
"start": "node dist/cli.js serve",
"test": "tsx src/config.test.ts && tsx src/onboarding.test.ts && tsx src/cli-workspace.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-config.test.ts && tsx src/local-agent-catalog.test.ts && tsx src/local-agent-presentation.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-daemon-lifecycle.test.ts && tsx src/local-agent-daemon-protocol.test.ts && tsx src/local-agent-daemon.test.ts && tsx src/local-agent-codex.test.ts && tsx src/local-agent-opencode.test.ts && tsx src/local-agent-acp.test.ts && tsx src/local-agent-grok.test.ts && tsx src/local-agent-pi-sandbox.test.ts && tsx src/local-agent-pi.test.ts && tsx src/local-agent-claude.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/local-agent-manager.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
"test": "tsx --test --test-concurrency=1 \"src/**/*.test.ts\"",
"test:package": "npm run build && node scripts/test-package.mjs",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"keywords": [],
Expand Down
77 changes: 77 additions & 0 deletions scripts/test-package.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";

const execFileAsync = promisify(execFile);
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const npmExecutable = process.platform === "win32" ? "npm.cmd" : "npm";
const requiredPackageFiles = [
"dist/cli.js",
"dist/server.js",
"dist/local-agent-daemon-main.js",
"dist/db/migrations.js",
"dist/ui/workspace-app.html",
"scripts/fix-node-pty-permissions.mjs",
"skills/subagents/SKILL.md",
];
Comment on lines +12 to +20

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Package test omits runtime UI assets

The package inventory checks workspace-app.html, but the installed server requires the Vite manifest and its referenced JavaScript and CSS assets. Because the smoke test runs only --version and doctor, it can pass without proving that the packaged MCP application is loadable.

Knowledge Base Used:


const temporaryRoot = await mkdtemp(join(tmpdir(), "devspace-package-test-"));

try {
const packageJson = JSON.parse(await readFile(join(repositoryRoot, "package.json"), "utf8"));
const packed = await runNpm(["pack", "--json", "--pack-destination", temporaryRoot], repositoryRoot);
const [packageResult] = JSON.parse(packed.stdout);
const packedPaths = new Set(packageResult.files.map(({ path }) => path));

for (const requiredPath of requiredPackageFiles) {
assert.ok(packedPaths.has(requiredPath), `${requiredPath} is missing from the npm package`);
}

const consumerRoot = join(temporaryRoot, "consumer");
await mkdir(consumerRoot);
await writeFile(
join(consumerRoot, "package.json"),
JSON.stringify({ name: "devspace-package-consumer", private: true }, null, 2),
);

const tarballPath = join(temporaryRoot, packageResult.filename);
await runNpm(["install", "--no-audit", "--no-fund", tarballPath], consumerRoot);

const executable = join(
consumerRoot,
"node_modules",
".bin",
process.platform === "win32" ? "devspace.cmd" : "devspace",
);
const version = await run(executable, ["--version"], consumerRoot);
assert.equal(version.stdout.trim(), packageJson.version);

await run(executable, ["doctor"], consumerRoot, {
...process.env,
DEVSPACE_ALLOWED_ROOTS: consumerRoot,
DEVSPACE_CONFIG_DIR: join(temporaryRoot, "config"),
DEVSPACE_OAUTH_OWNER_TOKEN: "package-test-owner-token-that-is-long-enough",
DEVSPACE_PUBLIC_BASE_URL: "http://127.0.0.1:7676",
});

console.log(`Installed and exercised ${packageResult.filename} as a consumer.`);
} finally {
await rm(temporaryRoot, { recursive: true, force: true });
}

function runNpm(args, cwd) {
return run(npmExecutable, args, cwd);
}

function run(file, args, cwd, env = process.env) {
return execFileAsync(file, args, {
cwd,
env,
encoding: "utf8",
maxBuffer: 10 * 1024 * 1024,
});
}
Loading
Loading