Skip to content
Merged
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
38 changes: 2 additions & 36 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,48 +108,14 @@ jobs:
# still run (close to) everything — but the many PRs that don't touch
# spec skip the bulk of the 75-package matrix.
# --concurrency=4: turbo's default (10) oversubscribes the 4-vCPU
# hosted runner. As the task graph grew past ~100 tasks (connector
# packages + qa gates), parallel vitest workers + tsup DTS builds
# started exhausting runner memory: plugin-audit#test died with ZERO
# output (kernel OOM-kill signature) deterministically on the runner
# while passing everywhere else — first seen on main @4f8c2d1,
# reproduced twice on #3037. Matching concurrency to the core count
# bounds peak memory; the job is CPU-bound anyway.
# ── TEMPORARY DIAGNOSTICS for #3071 — remove before merge ────────────
# plugin-audit#test dies on the hosted runner with zero captured output
# (3× deterministic; passes locally under Node 20/22 with the exact CI
# command). Run it ISOLATED first — fresh machine, no turbo grouping,
# verbose reporter — so its real output cannot be mangled by the GH log
# pipeline under burst. SIGQUIT before the timeout makes Node dump all
# thread stacks if it hangs.
- name: DIAG 3071 — plugin-audit isolated, verbose (temporary)
if: github.event_name == 'pull_request'
run: |
node --version
free -h || true
echo "── isolated vitest run ──"
timeout -s QUIT 240 pnpm --filter @objectstack/plugin-audit exec vitest run --reporter=verbose --no-file-parallelism; echo "exit=$?"
continue-on-error: true

# hosted runner; matching the core count bounds peak memory and the
# job is CPU-bound anyway.
- name: Run affected tests (PR)
if: github.event_name == 'pull_request'
env:
TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha }}
run: pnpm turbo run test --affected --concurrency=4

# ── TEMPORARY DIAGNOSTICS for #3071 — remove before merge ────────────
# If the turbo step above still kills plugin-audit silently, dmesg shows
# whether the kernel OOM-killer fired, and the second isolated re-run
# distinguishes "machine state after the full graph" from "fresh".
- name: DIAG 3071 — post-turbo dmesg + re-run (temporary)
if: github.event_name == 'pull_request' && always()
run: |
echo "── kernel OOM traces ──"
sudo dmesg | grep -iE "oom|killed process|out of memory" | tail -20 || echo "(no OOM traces)"
echo "── plugin-audit re-run on post-graph machine ──"
timeout -s QUIT 240 pnpm --filter @objectstack/plugin-audit exec vitest run --reporter=verbose --no-file-parallelism; echo "exit=$?"
continue-on-error: true

# Push to main: full run, but exclude spec's plain test task — the
# coverage step below executes the exact same 250-file spec suite once,
# with coverage. Previously the spec suite ran twice per push.
Expand Down
8 changes: 7 additions & 1 deletion packages/plugins/plugin-audit/src/audit-writers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,13 @@ describe('audit writers — enable.files server-side enforcement (#2727)', () =>
});
});

describe('audit writers — localized activity summaries (framework#3039)', () => {
// timeout: the FIRST localized case pays the one-off cost of dynamically
// importing @objectstack/core + the shipped translation bundle (and, with the
// #3071 src aliases, their vite transforms). On a shared 4-vCPU CI runner that
// cold start alone was measured at ~5s — right at vitest's default timeout —
// while every warmed case runs in ~1ms. 20s bounds the cold start without
// masking a real hang.
describe('audit writers — localized activity summaries (framework#3039)', { timeout: 20_000 }, () => {
// Real memory i18n (what the kernel registers as the 'i18n' fallback) loaded
// with this plugin's shipped bundle plus an app-contributed object label —
// exercises the actual key shapes (`messages.activityCreated`,
Expand Down
30 changes: 30 additions & 0 deletions packages/plugins/plugin-audit/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

// #3071: without these aliases, vitest resolves workspace deps through their
// package.json `exports` — i.e. `dist/` — so whether this package's tests
// even LOAD depends on turbo build ordering and cache-restore integrity on
// the runner (the deterministic zero-output CI failure). Aliasing to `src/`
// (the same convention plugin-hono-server et al. already use) removes the
// dist dependency entirely.
import { defineConfig } from 'vitest/config';
import path from 'path';

export default defineConfig({
test: {
globals: true,
environment: 'node',
},
resolve: {
alias: {
'@objectstack/core': path.resolve(__dirname, '../../core/src/index.ts'),
'@objectstack/platform-objects/audit': path.resolve(__dirname, '../../platform-objects/src/audit/index.ts'),
'@objectstack/spec/contracts': path.resolve(__dirname, '../../spec/src/contracts/index.ts'),
'@objectstack/spec/data': path.resolve(__dirname, '../../spec/src/data/index.ts'),
'@objectstack/spec/system': path.resolve(__dirname, '../../spec/src/system/index.ts'),
'@objectstack/spec/api': path.resolve(__dirname, '../../spec/src/api/index.ts'),
'@objectstack/spec/kernel': path.resolve(__dirname, '../../spec/src/kernel/index.ts'),
'@objectstack/spec': path.resolve(__dirname, '../../spec/src/index.ts'),
'@objectstack/types': path.resolve(__dirname, '../../types/src/index.ts'),
},
},
});
25 changes: 23 additions & 2 deletions packages/plugins/plugin-dev/src/dev-plugin.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,25 @@
import { describe, it, expect, vi } from 'vitest';
import { DevPlugin } from './dev-plugin';

// #3060: init()'s graceful-degradation path dynamically imports ~10 real
// workspace packages (objectql, runtime, plugin-auth, …). Under a fully
// parallel `pnpm test` those vite transforms alone can blow past the test
// timeout — the "handle missing deps" test flaked at 15s while passing in
// <100ms standalone. The test's INTENT is "peer deps missing", so make them
// genuinely missing: each factory throws the same shape an absent package
// produces. The degradation branch is exercised for real, with zero real
// module resolution on the hot path. (The stub-contract tests below disable
// all real services, so they never reach these imports.)
// (vi.mock calls are hoisted above any const, so the factories are inline.)
vi.mock('@objectstack/objectql', () => { throw Object.assign(new Error("Cannot find package '@objectstack/objectql'"), { code: 'ERR_MODULE_NOT_FOUND' }); });
vi.mock('@objectstack/runtime', () => { throw Object.assign(new Error("Cannot find package '@objectstack/runtime'"), { code: 'ERR_MODULE_NOT_FOUND' }); });
vi.mock('@objectstack/driver-memory', () => { throw Object.assign(new Error("Cannot find package '@objectstack/driver-memory'"), { code: 'ERR_MODULE_NOT_FOUND' }); });
vi.mock('@objectstack/service-i18n', () => { throw Object.assign(new Error("Cannot find package '@objectstack/service-i18n'"), { code: 'ERR_MODULE_NOT_FOUND' }); });
vi.mock('@objectstack/plugin-auth', () => { throw Object.assign(new Error("Cannot find package '@objectstack/plugin-auth'"), { code: 'ERR_MODULE_NOT_FOUND' }); });
vi.mock('@objectstack/plugin-security', () => { throw Object.assign(new Error("Cannot find package '@objectstack/plugin-security'"), { code: 'ERR_MODULE_NOT_FOUND' }); });
vi.mock('@objectstack/plugin-hono-server', () => { throw Object.assign(new Error("Cannot find package '@objectstack/plugin-hono-server'"), { code: 'ERR_MODULE_NOT_FOUND' }); });
vi.mock('@objectstack/rest', () => { throw Object.assign(new Error("Cannot find package '@objectstack/rest'"), { code: 'ERR_MODULE_NOT_FOUND' }); });

describe('DevPlugin', () => {
it('should have correct metadata', () => {
const plugin = new DevPlugin();
Expand Down Expand Up @@ -41,10 +60,12 @@ describe('DevPlugin', () => {
getKernel: vi.fn(),
};

// DevPlugin should not throw even if peer dependencies are missing
// DevPlugin should not throw even if peer dependencies are missing.
// Deps are mocked-away above (#3060), so the default timeout suffices —
// if someone removes the mocks, the slowness resurfaces loudly here.
const plugin = new DevPlugin({ seedAdminUser: false });
await expect(plugin.init(ctx)).resolves.not.toThrow();
}, 15_000);
});

it('should register contract-compliant dev stubs for all core services', async () => {
const registeredServices = new Map<string, any>();
Expand Down
28 changes: 28 additions & 0 deletions packages/plugins/plugin-dev/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

// #3071/#3060: alias workspace deps to `src/` so the tests load regardless of
// whether upstream `dist/` has been built — same convention as
// plugin-hono-server. Without this, vitest resolves @objectstack/core through
// its package.json `exports` (dist), which on a fresh tree / CI runner may
// not exist yet.
import { defineConfig } from 'vitest/config';
import path from 'path';

export default defineConfig({
test: {
globals: true,
environment: 'node',
},
resolve: {
alias: {
'@objectstack/core': path.resolve(__dirname, '../../core/src/index.ts'),
'@objectstack/types': path.resolve(__dirname, '../../types/src/index.ts'),
'@objectstack/spec/api': path.resolve(__dirname, '../../spec/src/api/index.ts'),
'@objectstack/spec/contracts': path.resolve(__dirname, '../../spec/src/contracts/index.ts'),
'@objectstack/spec/data': path.resolve(__dirname, '../../spec/src/data/index.ts'),
'@objectstack/spec/kernel': path.resolve(__dirname, '../../spec/src/kernel/index.ts'),
'@objectstack/spec/system': path.resolve(__dirname, '../../spec/src/system/index.ts'),
'@objectstack/spec': path.resolve(__dirname, '../../spec/src/index.ts'),
},
},
});