mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-17 05:29:44 +02:00
build: Shorten monorepo build and typecheck critical paths
- turbo.json: drop ^typecheck from the typecheck task; all package typechecks are self-contained --noEmit runs, so the edge only serialized the pipeline - @n8n/chat: run vite-plugin-dts only on the lib pass; the bundle pass regenerated identical declarations (~12s per build) - @n8n/i18n: widen i18nInstance so vue-i18n stops resolving t() overloads against the ~7.7k-key en.json schema; key safety stays at the baseText(key: BaseTextKey) boundary - add scripts/turbo-critical-path.mjs and report the weighted critical path from turbo run summaries in PR/master CI job summaries Cold build 2m31s -> 1m47s; clean typecheck 3m36s -> 2m46s (11-core local). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
02e736ef4e
commit
5d74037805
5
.github/workflows/ci-master.yml
vendored
5
.github/workflows/ci-master.yml
vendored
|
|
@ -22,7 +22,10 @@ jobs:
|
|||
uses: ./.github/actions/setup-nodejs
|
||||
with:
|
||||
# The default is build without typechecking, we want both
|
||||
build-command: pnpm build
|
||||
build-command: pnpm build --summarize
|
||||
|
||||
- name: Report build critical path
|
||||
run: node scripts/turbo-critical-path.mjs >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
unit-test:
|
||||
name: Unit tests
|
||||
|
|
|
|||
6
.github/workflows/ci-pull-requests.yml
vendored
6
.github/workflows/ci-pull-requests.yml
vendored
|
|
@ -149,7 +149,11 @@ jobs:
|
|||
if: fromJSON(steps.ci-filter.outputs.results).ci || fromJSON(steps.ci-filter.outputs.results).e2e
|
||||
uses: ./.github/actions/setup-nodejs
|
||||
with:
|
||||
build-command: ${{ fromJSON(steps.ci-filter.outputs.results).ci && 'pnpm build' || 'pnpm turbo run build --filter=@n8n/playwright-janitor' }}
|
||||
build-command: ${{ fromJSON(steps.ci-filter.outputs.results).ci && 'pnpm build --summarize' || 'pnpm turbo run build --filter=@n8n/playwright-janitor' }}
|
||||
|
||||
- name: Report build critical path
|
||||
if: fromJSON(steps.ci-filter.outputs.results).ci
|
||||
run: node scripts/turbo-critical-path.mjs >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Run format check
|
||||
if: fromJSON(steps.ci-filter.outputs.results).ci
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ export default mergeConfig(
|
|||
compiler: 'vue3',
|
||||
autoInstall: true,
|
||||
}),
|
||||
dts(),
|
||||
// The bundle pass emits the same declarations as the lib pass — skip the ~12s duplicate run.
|
||||
...(includeVue ? [] : [dts()]),
|
||||
{
|
||||
name: 'rename-css-file',
|
||||
closeBundle() {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import type { INodeProperties, INodePropertyCollection, INodePropertyOptions } from 'n8n-workflow';
|
||||
import { ref } from 'vue';
|
||||
import { createI18n } from 'vue-i18n';
|
||||
import type { I18n } from 'vue-i18n';
|
||||
|
||||
import englishBaseText from './locales/en.json';
|
||||
import type { BaseTextKey, LocaleMessages, INodeTranslationHeaders } from './types';
|
||||
|
|
@ -14,7 +15,11 @@ import {
|
|||
|
||||
export type * from './types';
|
||||
|
||||
export const i18nInstance = createI18n({
|
||||
// Widened from the inferred type: vue-i18n otherwise resolves every t() call against a
|
||||
// schema of all ~7.7k en.json keys, costing tens of seconds of typecheck in each consumer.
|
||||
// Key safety is enforced at the baseText(key: BaseTextKey) boundary instead.
|
||||
type LooseSchema = Record<string, unknown>;
|
||||
export const i18nInstance: I18n<LooseSchema, LooseSchema, LooseSchema, string, false> = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en',
|
||||
fallbackLocale: 'en',
|
||||
|
|
|
|||
101
scripts/turbo-critical-path.mjs
Normal file
101
scripts/turbo-critical-path.mjs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Reports the build-time-weighted critical path of a Turborepo run, from a
|
||||
* `turbo run <task> --summarize` summary file. The critical path is the
|
||||
* dependency chain with the largest total duration — the lower bound on wall
|
||||
* time no amount of parallelism can beat. Use it to spot tasks worth
|
||||
* shrinking or edges worth removing.
|
||||
*
|
||||
* Usage: node scripts/turbo-critical-path.mjs [path-to-summary.json]
|
||||
* Defaults to the newest file in .turbo/runs/. Prints markdown; never fails.
|
||||
*/
|
||||
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const TOP_N = 10;
|
||||
|
||||
function newestRunSummary() {
|
||||
const dir = join(process.cwd(), '.turbo', 'runs');
|
||||
const files = readdirSync(dir)
|
||||
.filter((f) => f.endsWith('.json'))
|
||||
.map((f) => join(dir, f))
|
||||
.sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs);
|
||||
if (files.length === 0) throw new Error(`no run summaries in ${dir}`);
|
||||
return files[0];
|
||||
}
|
||||
|
||||
function fmt(seconds) {
|
||||
return seconds >= 100 ? `${Math.round(seconds)}s` : `${seconds.toFixed(1)}s`;
|
||||
}
|
||||
|
||||
try {
|
||||
const file = process.argv[2] ?? newestRunSummary();
|
||||
const summary = JSON.parse(readFileSync(file, 'utf8'));
|
||||
|
||||
const tasks = (summary.tasks ?? [])
|
||||
.filter((t) => t.execution?.startTime && t.execution?.endTime)
|
||||
.map((t) => ({
|
||||
id: t.taskId,
|
||||
dur: (t.execution.endTime - t.execution.startTime) / 1000,
|
||||
deps: t.dependencies ?? [],
|
||||
cached: t.cache?.status === 'HIT',
|
||||
}));
|
||||
if (tasks.length === 0) throw new Error(`no executed tasks in ${file}`);
|
||||
|
||||
const byId = new Map(tasks.map((t) => [t.id, t]));
|
||||
const memo = new Map();
|
||||
function heaviestChain(id) {
|
||||
if (memo.has(id)) return memo.get(id);
|
||||
const task = byId.get(id);
|
||||
if (!task) return { total: 0, path: [] };
|
||||
// turbo graphs are acyclic, so plain recursion terminates
|
||||
let best = { total: 0, path: [] };
|
||||
for (const dep of task.deps) {
|
||||
const chain = heaviestChain(dep);
|
||||
if (chain.total > best.total) best = chain;
|
||||
}
|
||||
const result = { total: best.total + task.dur, path: [...best.path, id] };
|
||||
memo.set(id, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
let critical = { total: 0, path: [] };
|
||||
for (const t of tasks) {
|
||||
const chain = heaviestChain(t.id);
|
||||
if (chain.total > critical.total) critical = chain;
|
||||
}
|
||||
|
||||
const cpu = tasks.reduce((sum, t) => sum + t.dur, 0);
|
||||
const wall =
|
||||
(Math.max(...summary.tasks.map((t) => t.execution?.endTime ?? 0)) -
|
||||
Math.min(
|
||||
...summary.tasks.filter((t) => t.execution?.startTime).map((t) => t.execution.startTime),
|
||||
)) /
|
||||
1000;
|
||||
const cachedCount = tasks.filter((t) => t.cached).length;
|
||||
|
||||
const label = (t) => `\`${t.id}\`${t.cached ? ' (cache hit)' : ''}`;
|
||||
|
||||
const lines = [
|
||||
`### Turbo critical path — \`${summary.execution?.command ?? 'run'}\``,
|
||||
'',
|
||||
`Wall ${fmt(wall)} · task CPU ${fmt(cpu)} · parallelism ${(cpu / wall).toFixed(1)}× · ${tasks.length} tasks (${cachedCount} cache hits)`,
|
||||
'',
|
||||
`**Critical path: ${fmt(critical.total)}** (${((critical.total / wall) * 100).toFixed(0)}% of wall time)`,
|
||||
'',
|
||||
...critical.path.map((id) => `- ${fmt(byId.get(id).dur)} ${label(byId.get(id))}`),
|
||||
'',
|
||||
`**Slowest tasks**`,
|
||||
'',
|
||||
...tasks
|
||||
.slice()
|
||||
.sort((a, b) => b.dur - a.dur)
|
||||
.slice(0, TOP_N)
|
||||
.map((t) => `- ${fmt(t.dur)} ${label(t)}`),
|
||||
'',
|
||||
];
|
||||
console.log(lines.join('\n'));
|
||||
} catch (error) {
|
||||
// Reporting only — never break the build over it.
|
||||
console.log(`turbo-critical-path: skipped (${error.message})`);
|
||||
}
|
||||
|
|
@ -27,7 +27,9 @@
|
|||
"env": ["RELEASE"]
|
||||
},
|
||||
"typecheck": {
|
||||
"dependsOn": ["^typecheck", "^build"]
|
||||
// Typechecks are all --noEmit and read upstream types from dist (^build) or source
|
||||
// (tsconfig paths) — depending on ^typecheck would only serialize the pipeline.
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"format": {},
|
||||
"format:check": {},
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user