c71663946e
## What Introduces a `packages/twenty-apps/public/` folder and moves the publicly publishable apps into it, then generalizes the apps CI workflow to cover both folders. ### Moves The following apps were moved from `packages/twenty-apps/internal/` to `packages/twenty-apps/public/` (via `git mv`, history preserved): - `people-data-labs` - `twenty-discord` - `twenty-exa` - `twenty-fireflies` - `twenty-last-contact` - `twenty-linear` - `twenty-meeting-bot` - `twenty-slack` These remain in `internal/`: `self-hosting`, `twenty-for-twenty`, `twenty-partners`. ### Workflow - Renamed `.github/workflows/ci-internal-apps.yaml` → `.github/workflows/ci-twenty-apps.yaml`. - The discover job now scans **both** `packages/twenty-apps/internal` and `packages/twenty-apps/public`: - the "no nested `.github`" guard checks both folders, - `changed-files` watches both globs, - the matrix builder iterates over both roots (guarded with `existsSync` so a missing folder is a no-op). - Each matrix entry still carries its own `path`, so the `ci` job works unchanged regardless of which folder an app lives in. ## Notes - The apps are standalone packages (own `yarn.lock`, not part of the root Nx workspaces), so no root `package.json` / `nx.json` / `tsconfig` changes were needed. - The companion publish workflow lives in `twentyhq/twenty-infra` (`publish-internal-apps.yaml` → `publish-public-apps.yaml`) and is updated in a paired PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- _Generated by [Claude Code](https://claude.ai/code/session_01Fmu3DWf1yTTkVW49eSkXwh)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22096?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
168 lines
5.5 KiB
YAML
168 lines
5.5 KiB
YAML
name: CI Twenty Apps
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
pull_request:
|
|
workflow_dispatch:
|
|
inputs:
|
|
application:
|
|
description: 'App folder name to test (e.g. "twenty-linear"). Leave empty to test all apps.'
|
|
required: false
|
|
default: ''
|
|
type: string
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
|
|
|
|
jobs:
|
|
discover:
|
|
timeout-minutes: 5
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
|
has_apps: ${{ steps.set-matrix.outputs.has_apps }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Ensure no .github folder inside app folders
|
|
run: |
|
|
offenders=$(find packages/twenty-apps/internal packages/twenty-apps/public -mindepth 2 -maxdepth 2 -type d -name .github)
|
|
if [ -n "$offenders" ]; then
|
|
echo "::error::Apps must not define their own .github folder. Offenders:"
|
|
echo "$offenders"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Detect changed files
|
|
id: changed-files
|
|
if: github.event_name != 'workflow_dispatch'
|
|
uses: tj-actions/changed-files@48d8f15b2aaa3d255ca5af3eba4870f807ce6b3c # v45.0.9
|
|
with:
|
|
json: true
|
|
escape_json: false
|
|
files: |
|
|
packages/twenty-apps/internal/**
|
|
packages/twenty-apps/public/**
|
|
|
|
- name: Build matrix of changed apps
|
|
id: set-matrix
|
|
env:
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
|
APPLICATION_INPUT: ${{ inputs.application }}
|
|
run: |
|
|
node <<'NODE'
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const roots = ['packages/twenty-apps/internal', 'packages/twenty-apps/public'];
|
|
const eventName = process.env.EVENT_NAME;
|
|
const changedFiles = JSON.parse(process.env.CHANGED_FILES || '[]');
|
|
const changedApps = new Set();
|
|
for (const file of changedFiles) {
|
|
const match = file.match(/^packages\/twenty-apps\/(?:internal|public)\/([^/]+)\//);
|
|
if (match) changedApps.add(match[1]);
|
|
}
|
|
const requestedApp = (process.env.APPLICATION_INPUT || '').trim();
|
|
const matrix = roots
|
|
.filter((root) => fs.existsSync(root))
|
|
.flatMap((root) =>
|
|
fs.readdirSync(root, { withFileTypes: true })
|
|
.filter((entry) => entry.isDirectory() && entry.name !== 'node_modules')
|
|
.map((entry) => entry.name)
|
|
.filter((name) => fs.existsSync(path.join(root, name, 'package.json')))
|
|
.filter((name) => {
|
|
if (eventName === 'workflow_dispatch') {
|
|
return requestedApp ? name === requestedApp : true;
|
|
}
|
|
return changedApps.has(name);
|
|
})
|
|
.map((name) => {
|
|
const appPath = path.join(root, name);
|
|
const scripts = JSON.parse(fs.readFileSync(path.join(appPath, 'package.json'), 'utf8')).scripts || {};
|
|
return {
|
|
name,
|
|
path: appPath,
|
|
hasTypecheck: Boolean(scripts.typecheck),
|
|
hasUnit: Boolean(scripts['test:unit']),
|
|
hasIntegration: Boolean(scripts.test),
|
|
};
|
|
})
|
|
);
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `matrix=${JSON.stringify(matrix)}\n`);
|
|
fs.appendFileSync(process.env.GITHUB_OUTPUT, `has_apps=${matrix.length > 0}\n`);
|
|
NODE
|
|
|
|
ci:
|
|
needs: discover
|
|
if: needs.discover.outputs.has_apps == 'true'
|
|
name: ${{ matrix.app.name }}
|
|
timeout-minutes: 30
|
|
runs-on: ubuntu-latest
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
app: ${{ fromJSON(needs.discover.outputs.matrix) }}
|
|
defaults:
|
|
run:
|
|
working-directory: ${{ matrix.app.path }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
|
|
|
|
- name: Enable Corepack
|
|
run: corepack enable
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
cache: yarn
|
|
cache-dependency-path: ${{ matrix.app.path }}/yarn.lock
|
|
|
|
- name: Install dependencies
|
|
run: yarn install --immutable
|
|
|
|
- name: Lint
|
|
run: yarn lint
|
|
|
|
- name: Typecheck
|
|
if: matrix.app.hasTypecheck
|
|
run: yarn typecheck
|
|
|
|
- name: Unit tests
|
|
if: matrix.app.hasUnit
|
|
run: yarn test:unit
|
|
|
|
- name: Spawn Twenty test instance
|
|
id: twenty
|
|
if: matrix.app.hasIntegration
|
|
uses: ./.github/actions/spawn-twenty-app-dev-test
|
|
with:
|
|
twenty-version: latest
|
|
|
|
- name: Integration tests
|
|
if: matrix.app.hasIntegration
|
|
run: yarn test
|
|
env:
|
|
TWENTY_API_URL: ${{ steps.twenty.outputs.server-url }}
|
|
TWENTY_API_KEY: ${{ steps.twenty.outputs.api-key }}
|
|
|
|
ci-twenty-apps-status-check:
|
|
if: always() && !cancelled()
|
|
timeout-minutes: 5
|
|
runs-on: ubuntu-latest
|
|
needs: [discover, ci]
|
|
steps:
|
|
- name: Fail job if any needs failed
|
|
if: contains(needs.*.result, 'failure')
|
|
run: exit 1
|