mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-30 08:17:06 +02:00
fix(core): Extract workflow-sdk examples to a writable cache dir (#30433)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
563089ba70
commit
6beed60969
|
|
@ -22,7 +22,7 @@
|
|||
*/
|
||||
|
||||
import type { Workspace } from '@mastra/core/workspace';
|
||||
import { getExampleFiles } from '@n8n/workflow-sdk/examples-loader';
|
||||
import { getExampleFiles, type ExampleFile } from '@n8n/workflow-sdk/examples-loader';
|
||||
import { createRequire } from 'node:module';
|
||||
|
||||
import type { Logger } from '../logger';
|
||||
|
|
@ -329,7 +329,17 @@ export async function getWorkspaceRoot(workspace: Workspace): Promise<string> {
|
|||
*/
|
||||
export async function writeCuratedExamples(workspace: Workspace, logger?: Logger): Promise<void> {
|
||||
const start = Date.now();
|
||||
const { files: exampleFiles, indexTxt } = getExampleFiles();
|
||||
// Examples are nice-to-have — never block the build when loading them fails.
|
||||
let exampleFiles: ExampleFile[];
|
||||
let indexTxt: string;
|
||||
try {
|
||||
({ files: exampleFiles, indexTxt } = getExampleFiles());
|
||||
} catch (error) {
|
||||
logger?.warn('[sandbox-setup] curated examples unavailable, continuing without', {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (exampleFiles.length === 0) return;
|
||||
|
||||
const root = await getWorkspaceRoot(workspace);
|
||||
|
|
|
|||
|
|
@ -15,14 +15,12 @@ import * as fs from 'fs';
|
|||
import * as path from 'path';
|
||||
|
||||
import { emitInstanceAi } from './codegen/emit-instance-ai';
|
||||
import { ensureExtracted } from './examples-zip';
|
||||
import { ensureExtracted, WORKFLOWS_CACHE_DIR } from './examples-zip';
|
||||
import type { WorkflowJSON } from './types/base';
|
||||
|
||||
// Resolve relative to this file. At runtime this lives at <package>/dist/examples-loader.js,
|
||||
// so `../examples` reaches <package>/examples/.
|
||||
// Manifest ships read-only in the package; workflows live in WORKFLOWS_CACHE_DIR.
|
||||
const EXAMPLES_DIR = path.resolve(__dirname, '..', 'examples');
|
||||
const MANIFEST_PATH = path.join(EXAMPLES_DIR, 'manifest.json');
|
||||
const WORKFLOWS_DIR = path.join(EXAMPLES_DIR, 'workflows');
|
||||
|
||||
const NODES_INLINE_LIMIT = 5;
|
||||
const INDEX_NODE_SEPARATOR = ',';
|
||||
|
|
@ -95,7 +93,7 @@ function loadFromDisk(): ExampleFilesBundle {
|
|||
const indexLines: string[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const wfPath = path.join(WORKFLOWS_DIR, `${entry.slug}.json`);
|
||||
const wfPath = path.join(WORKFLOWS_CACHE_DIR, `${entry.slug}.json`);
|
||||
if (!fs.existsSync(wfPath)) continue;
|
||||
// eslint-disable-next-line n8n-local-rules/no-uncaught-json-parse -- Internal workflow fixture
|
||||
const wf = JSON.parse(fs.readFileSync(wfPath, 'utf-8')) as WorkflowJSON;
|
||||
|
|
|
|||
|
|
@ -8,12 +8,35 @@
|
|||
*/
|
||||
import AdmZip from 'adm-zip';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
const EXAMPLES_DIR = path.resolve(__dirname, '..', 'examples');
|
||||
const ZIP_PATH = path.join(EXAMPLES_DIR, 'templates.zip');
|
||||
const MANIFEST_PATH = path.join(EXAMPLES_DIR, 'manifest.json');
|
||||
const WORKFLOWS_DIR = path.join(EXAMPLES_DIR, 'workflows');
|
||||
|
||||
function sdkVersion(): string {
|
||||
try {
|
||||
const pkgPath = path.resolve(__dirname, '..', 'package.json');
|
||||
// eslint-disable-next-line n8n-local-rules/no-uncaught-json-parse -- Own package.json
|
||||
return (
|
||||
(JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as { version?: string }).version ??
|
||||
'unversioned'
|
||||
);
|
||||
} catch {
|
||||
return 'unversioned';
|
||||
}
|
||||
}
|
||||
|
||||
// Tmp cache for unzipped workflows — keyed by SDK version so upgrades extract
|
||||
// fresh. We can't unzip back into the package because node_modules is
|
||||
// read-only inside n8n's Docker image.
|
||||
export const WORKFLOWS_CACHE_DIR = path.join(
|
||||
os.tmpdir(),
|
||||
'n8n-workflow-sdk',
|
||||
sdkVersion(),
|
||||
'workflows',
|
||||
);
|
||||
|
||||
interface ManifestEntry {
|
||||
slug: string;
|
||||
|
|
@ -41,7 +64,7 @@ export function needsExtraction(): boolean {
|
|||
const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf-8')) as ManifestFile;
|
||||
for (const entry of manifest.workflows ?? []) {
|
||||
if (!entry.success || entry.skip) continue;
|
||||
const filePath = path.join(WORKFLOWS_DIR, `${entry.slug}.json`);
|
||||
const filePath = path.join(WORKFLOWS_CACHE_DIR, `${entry.slug}.json`);
|
||||
if (!fs.existsSync(filePath)) return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -55,14 +78,14 @@ export function extractFromZip(): void {
|
|||
if (!fs.existsSync(ZIP_PATH)) {
|
||||
throw new Error(`Examples zip not found: ${ZIP_PATH}`);
|
||||
}
|
||||
if (!fs.existsSync(WORKFLOWS_DIR)) {
|
||||
fs.mkdirSync(WORKFLOWS_DIR, { recursive: true });
|
||||
if (!fs.existsSync(WORKFLOWS_CACHE_DIR)) {
|
||||
fs.mkdirSync(WORKFLOWS_CACHE_DIR, { recursive: true });
|
||||
}
|
||||
|
||||
const zip = new AdmZip(ZIP_PATH);
|
||||
for (const entry of zip.getEntries()) {
|
||||
if (entry.isDirectory) continue;
|
||||
zip.extractEntryTo(entry, WORKFLOWS_DIR, false, true);
|
||||
zip.extractEntryTo(entry, WORKFLOWS_CACHE_DIR, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user