diff --git a/.source/workflows/bun-test.ts b/.source/workflows/bun-test.ts new file mode 100644 index 0000000000..a6fc09a001 --- /dev/null +++ b/.source/workflows/bun-test.ts @@ -0,0 +1,46 @@ +// .github/workflows/bun-test.yml on TanStack Source: the Bun-only tests of +// @tanstack/ai-isolate-quickjs-bun, which the Node test job skips. +import { defineWorkflow, pullRequest, setup } from '@tanstack-source/ci' + +export default defineWorkflow('bun-test', { + on: [ + pullRequest({ + paths: [ + 'packages/ai-isolate-quickjs-bun/**', + 'packages/ai-code-mode/**', + '.source/workflows/bun-test.ts', + ], + }), + ], +}).job('test', { + // The browser image has the Chromium of Playwright 1.57.0. `pnpm install` runs the + // `playwright install chromium` postinstall of testing/e2e, which then downloads nothing. + image: 'browser', + // Nx Cloud is off, so Nx uses the task cache of the forge, and the job needs no secret. + env: { NX_NO_CLOUD: 'true' }, + // npm for packages and pnpm, nodejs.org when .nvmrc names another Node than the image, + // and the Playwright CDNs when the locked Playwright changes. + egress: [ + 'registry.npmjs.org', + 'nodejs.org', + 'cdn.playwright.dev', + 'playwright.download.prss.microsoft.com', + ], + steps: [ + // The steps of TanStack/config/.github/setup: pnpm, Node from .nvmrc, then the install. + setup.node(), + setup.pnpm(), + { name: 'Install dependencies', run: 'pnpm install --frozen-lockfile' }, + // The repository has no .bun-version, so the job uses the bun of the image (1.4.4). + // The driver needs bun 1.3.14 or later. + setup.bun(), + { + name: 'Build package and its workspace dependencies', + run: 'pnpm exec nx build @tanstack/ai-isolate-quickjs-bun', + }, + { + name: 'Run Bun test suite', + run: 'pnpm --filter @tanstack/ai-isolate-quickjs-bun run test:bun', + }, + ], +}) diff --git a/.source/workflows/e2e.ts b/.source/workflows/e2e.ts new file mode 100644 index 0000000000..0f79824495 --- /dev/null +++ b/.source/workflows/e2e.ts @@ -0,0 +1,81 @@ +// .github/workflows/e2e.yml on TanStack Source: the E2E shards and the `E2E Tests` gate. +import { + artifact, + defineWorkflow, + pullRequest, + push, + setup, +} from '@tanstack-source/ci' + +export default defineWorkflow('e2e', { + on: [pullRequest(), push({ branches: ['main', 'alpha', 'beta', 'rc'] })], +}) + .job('e2e', { + // The browser image has the browsers of Playwright 1.57.0, the version in pnpm-lock.yaml. + // It replaces the Playwright browser cache of the GitHub workflow. + image: 'browser', + timeoutMinutes: 30, + checkout: { depth: 0 }, + // Each shard runs to the end, as with `fail-fast: false` on GitHub. + matrix: { shard: [1, 2, 3, 4] }, + // Nx Cloud is off, so Nx uses the task cache of the forge, and the job needs no secret. + env: { NX_NO_CLOUD: 'true' }, + // npm for packages and pnpm, nodejs.org when .nvmrc names another Node than the image, + // and the Playwright CDNs when the locked Playwright changes. + // The tests talk to aimock on 127.0.0.1 only, so they need no provider host and no API key. + egress: [ + 'registry.npmjs.org', + 'nodejs.org', + 'cdn.playwright.dev', + 'playwright.download.prss.microsoft.com', + ], + steps: [ + { + // First, so that the uploads are registered before a step can fail. + name: 'Upload results after the job', + do: (ctx) => { + const upload = (name: string, paths: string[]) => + ctx.afterJob( + (after) => + artifact({ + name: `${name}-${ctx.matrix.shard}`, + paths, + retentionDays: 14, + }).do(after), + { always: true }, + ) + upload('e2e-videos', ['testing/e2e/test-results/**/*.webm']) + upload('e2e-report', ['testing/e2e/playwright-report/']) + upload('e2e-traces', ['testing/e2e/test-results/**/*.zip']) + }, + }, + // The steps of TanStack/config/.github/setup: pnpm, Node from .nvmrc, then the install. + setup.node(), + setup.pnpm(), + { name: 'Install dependencies', run: 'pnpm install --frozen-lockfile' }, + { name: 'Build Packages', run: 'pnpm run build:all' }, + // Downloads nothing while the image has the browser of the locked Playwright version. + { + name: 'Install Playwright Chromium', + run: 'pnpm --filter @tanstack/ai-e2e exec playwright install chromium', + }, + { + name: 'Run E2E Tests', + do: (ctx) => + ctx.sh`SHARD=${ctx.matrix.shard} bash -ec ${'pnpm --filter @tanstack/ai-e2e test:e2e --shard="$SHARD/4"'}`, + }, + ], + }) + // One result for all shards, as the `E2E Tests` job on GitHub. + .job('e2e-result', { + needs: ['e2e'], + when: 'always', + size: 'small', + steps: [ + { + name: 'Check shard results', + do: (ctx) => + ctx.sh`RESULT=${ctx.needs.e2e.result} bash -ec ${'test "$RESULT" = success'}`, + }, + ], + }) diff --git a/.source/workflows/pr.ts b/.source/workflows/pr.ts new file mode 100644 index 0000000000..a350ff1758 --- /dev/null +++ b/.source/workflows/pr.ts @@ -0,0 +1,31 @@ +// The `Test` check of .github/workflows/pr.yml, on TanStack Source. +// The Coverage and Preview jobs of pr.yml stay on GitHub for now. +import { defineWorkflow, pullRequest, setup } from '@tanstack-source/ci' + +export default defineWorkflow('pr', { + on: [pullRequest()], +}).job('test', { + // The browser image has the Chromium of Playwright 1.57.0. `pnpm install` runs the + // `playwright install chromium` postinstall of testing/e2e, which then downloads nothing. + image: 'browser', + // `nx affected` compares with the base commit, so the job needs the history. + // nrwl/nx-set-shas is built in: NX_BASE and NX_HEAD come from ctx.base and ctx.head. + checkout: { depth: 0 }, + // Nx Cloud is off, so Nx uses the task cache of the forge, and the job needs no secret. + env: { NX_NO_CLOUD: 'true' }, + // npm for packages and pnpm, nodejs.org when .nvmrc names another Node than the image, + // and the Playwright CDNs when the locked Playwright changes. + egress: [ + 'registry.npmjs.org', + 'nodejs.org', + 'cdn.playwright.dev', + 'playwright.download.prss.microsoft.com', + ], + steps: [ + // The steps of TanStack/config/.github/setup: pnpm, Node from .nvmrc, then the install. + setup.node(), + setup.pnpm(), + { name: 'Install dependencies', run: 'pnpm install --frozen-lockfile' }, + { name: 'Run Checks', run: 'pnpm run test:pr' }, + ], +}) diff --git a/.source/workflows/sync-models.ts b/.source/workflows/sync-models.ts new file mode 100644 index 0000000000..4b678f0e34 --- /dev/null +++ b/.source/workflows/sync-models.ts @@ -0,0 +1,144 @@ +// Daily sync of model metadata. Ported from .github/workflows/sync-models.yml. +// It force-pushes the changes to automated/sync-models, opens a PR for them, +// and opens (or comments on) a `sync-models` issue when a sync step fails. +import { + defineWorkflow, + forge, + manual, + schedule, + setup, +} from '@tanstack-source/ci' + +const branch = 'automated/sync-models' + +const prBody = `Automated daily sync of model metadata from OpenRouter, Vercel AI Gateway, Lovable AI Gateway, LLM Gateway, Neon AI Gateway, and modelschemas (ElevenLabs text-to-speech, music, sound effects, and voice design). + +- Fetches the latest model list from OpenRouter (chat + \`GET /api/v1/videos/models\`) +- Fetches the latest model list from Vercel AI Gateway +- Fetches the latest model list from Lovable AI Gateway +- Fetches the latest model list from LLM Gateway (\`GET /v1/models\`) +- Fetches the latest model list from Neon AI Gateway +- Converts to the internal adapter format +- Syncs provider-specific model metadata for affected packages +- Inserts new ElevenLabs text-to-speech, music, sound-effect, and voice-design models from modelschemas +- Creates a patch changeset for all changed packages` + +// Each sync runs on its own, so one provider failing does not block the +// others. Whatever succeeded is still committed, then the job fails. +const syncs = { + fetch: 'pnpm generate:models:fetch', + convert: 'pnpm regenerate:models', + elevenlabs: 'pnpm generate:models:elevenlabs', + provider: 'pnpm exec tsx scripts/sync-provider-models.ts', +} + +export default defineWorkflow('sync-models', { + on: [schedule('0 6 * * *'), manual()], +}).job('sync', { + checkout: { depth: 0 }, + env: { NX_NO_CLOUD: 'true' }, + // Optional: the Vercel gateway answers without a key today. + secrets: { AI_GATEWAY_API_KEY: { optional: true } }, + permissions: { + push: [branch], + pulls: 'create', + issues: 'write', + comment: 'any-pr', + }, + egress: [ + 'registry.npmjs.org', + 'nodejs.org', + 'openrouter.ai', + 'ai-gateway.vercel.sh', + 'ai.gateway.lovable.dev', + 'api.llmgateway.io', + 'neon.com', + 'modelschemas.com', + ], + steps: [ + setup.node(), + setup.pnpm(), + { name: 'Install', run: 'pnpm install --frozen-lockfile' }, + { + name: 'Sync models', + do: async (ctx) => { + let succeeded = false + const outcomes: Array = [] + const key = ctx.secrets.AI_GATEWAY_API_KEY ?? '' + for (const [name, command] of Object.entries(syncs)) { + const { code } = await ctx.sh + .nothrow`AI_GATEWAY_API_KEY=${key} bash -c ${command}` + outcomes.push(`${name}=${code === 0 ? 'success' : 'failure'}`) + } + const failed = outcomes.filter((o) => o.endsWith('=failure')) + + // Open or comment on the failure issue, also when a later step fails. + ctx.afterJob( + async (after) => { + if (succeeded) return + const client = forge(after) + await client.labels.ensure({ + name: 'sync-models', + color: 'B60205', + description: 'Daily model sync failures', + }) + const date = new Date().toISOString().slice(0, 10) + const body = [ + `🚨 Sync Model Metadata failed on ${date}.`, + '', + `Job: ${after.env.TANSTACK_SOURCE_JOB_ID ?? 'unknown'}`, + `Step outcomes: ${outcomes.join(', ')}`, + '', + `Steps that passed were still committed to \`${branch}\`. Close this issue when the sync is green again.`, + ].join('\n') + const [issue] = await client.issues.list({ + label: 'sync-models', + state: 'open', + }) + if (issue) await client.comments.create(issue.number, body) + else + await client.issues.create({ + title: + '🚨🚨🚨 DAILY MODEL SYNC IS FAILING 🚨🚨🚨 Model updates are NOT landing', + body, + labels: ['sync-models'], + }) + }, + { name: 'Open or update failure issue', always: true }, + ) + + await ctx.sh`pnpm format` + + const changed = await ctx.sh.nothrow`git diff --quiet -- packages/` + if (changed.code !== 0) { + await ctx.sh`git config user.name ${'TanStack Source'}` + await ctx.sh`git config user.email ${'ci@source.tanstack.com'}` + await ctx.sh`git add packages/ scripts/openrouter.models.json scripts/openrouter.video-models.json scripts/vercel-gateway.models.json scripts/lovable-gateway.models.json scripts/llmgateway.models.json scripts/neon.models.json scripts/.sync-models-last-run .changeset/` + await ctx.sh`git commit -m ${'chore: sync model metadata'}` + await ctx.sh`git push --force origin ${`HEAD:${branch}`}` + + const client = forge(ctx) + const [open] = await client.pulls.list({ + head: branch, + state: 'open', + }) + if (open === undefined) { + await client.pulls.create({ + title: 'chore: sync model metadata', + head: branch, + base: 'main', + body: prBody, + }) + } + } + + if (failed.length > 0) { + throw new Error( + `A model sync step failed (${failed.join(', ')}). Passing steps were still committed.`, + ) + } + succeeded = true + }, + }, + ], +}) diff --git a/knip.json b/knip.json index 35d03df499..78bcc67dce 100644 --- a/knip.json +++ b/knip.json @@ -14,6 +14,7 @@ ], "ignore": [ "kiira.config.ts", + ".source/**", "scripts/**", "**/*.test-d.ts", "packages/ai-code-mode-snippets/test-cli/**",