diff --git a/packages/@n8n/cli/docs/commands/package.md b/packages/@n8n/cli/docs/commands/package.md index 7ab35e41d7c..e76f8ce51dd 100644 --- a/packages/@n8n/cli/docs/commands/package.md +++ b/packages/@n8n/cli/docs/commands/package.md @@ -2,10 +2,7 @@ Export and import workflows as portable n8n packages (`.n8np` archives). -> **Beta — disabled by default.** These commands call beta endpoints that the -> target instance must opt into with `N8N_PUBLIC_API_PACKAGES_ENABLED=true`, and -> the n8n Packages feature must be licensed. While disabled, the instance returns -> `404` and the CLI prints a hint explaining how to enable it. +> **Beta feature:** n8n packages are still under development and there may be breaking changes on APIs. ## `package export` diff --git a/packages/@n8n/cli/src/__tests__/package-error.test.ts b/packages/@n8n/cli/src/__tests__/package-error.test.ts index 3c475378305..7c8f15496f8 100644 --- a/packages/@n8n/cli/src/__tests__/package-error.test.ts +++ b/packages/@n8n/cli/src/__tests__/package-error.test.ts @@ -9,14 +9,6 @@ describe('toPackagesError', () => { expect(toPackagesError(error)).toBe(error); }); - it('adds a beta-enablement hint for a 404', () => { - const result = toPackagesError(new ApiError(404, 'Not Found')); - - expect(result).toBeInstanceOf(ApiError); - expect((result as ApiError).statusCode).toBe(404); - expect((result as ApiError).hint).toContain('N8N_PUBLIC_API_PACKAGES_ENABLED=true'); - }); - it('lists workflow-conflict issues for a 409', () => { const result = toPackagesError( new ApiError(409, 'Import blocked', undefined, { diff --git a/packages/@n8n/cli/src/commands/package/shared.ts b/packages/@n8n/cli/src/commands/package/shared.ts index 864c875e125..39a1f7d64bc 100644 --- a/packages/@n8n/cli/src/commands/package/shared.ts +++ b/packages/@n8n/cli/src/commands/package/shared.ts @@ -1,9 +1,5 @@ import { ApiError } from '../../client'; -const BETA_HINT = - 'The n8n Packages API is beta and disabled by default. Enable it on the instance with ' + - 'N8N_PUBLIC_API_PACKAGES_ENABLED=true (requires the n8n Packages license).'; - type BlockingIssue = | { type: 'workflow-conflict'; @@ -35,9 +31,6 @@ function issuesHint(details: unknown): string | undefined { export function toPackagesError(error: unknown): unknown { if (!(error instanceof ApiError)) return error; - if (error.statusCode === 404) { - return new ApiError(error.statusCode, error.message, BETA_HINT, error.details); - } const hint = issuesHint(error.details); if (hint) { return new ApiError(error.statusCode, error.message, hint, error.details); diff --git a/packages/@n8n/config/src/configs/public-api.config.ts b/packages/@n8n/config/src/configs/public-api.config.ts index 06f0f08d685..e944db7dac3 100644 --- a/packages/@n8n/config/src/configs/public-api.config.ts +++ b/packages/@n8n/config/src/configs/public-api.config.ts @@ -13,11 +13,4 @@ export class PublicApiConfig { /** When true, the Swagger UI for the Public API is not served. */ @Env('N8N_PUBLIC_API_SWAGGERUI_DISABLED') swaggerUiDisabled: boolean = false; - - /** - * When true, the n8n-packages public API endpoints are enabled. This feature is - * currently in beta and disabled by default. - */ - @Env('N8N_PUBLIC_API_PACKAGES_ENABLED') - packagesEnabled: boolean = false; } diff --git a/packages/@n8n/config/test/config.test.ts b/packages/@n8n/config/test/config.test.ts index 0ae8fce54eb..64514ed3399 100644 --- a/packages/@n8n/config/test/config.test.ts +++ b/packages/@n8n/config/test/config.test.ts @@ -194,7 +194,6 @@ describe('GlobalConfig', () => { disabled: false, path: 'api', swaggerUiDisabled: false, - packagesEnabled: false, }, templates: { enabled: true, diff --git a/packages/cli/src/modules/n8n-packages/CLAUDE.md b/packages/cli/src/modules/n8n-packages/CLAUDE.md index 91e4c46797c..ecae6d6aa0d 100644 --- a/packages/cli/src/modules/n8n-packages/CLAUDE.md +++ b/packages/cli/src/modules/n8n-packages/CLAUDE.md @@ -64,9 +64,3 @@ The in-tree addition of `workflowIdPolicy` is the canonical example — it lande in the module types + importer, the DTO (+ form-fields list), the handler, and the CLI. Grep `workflowIdPolicy` to see every site a new import knob must touch. -## More context - -See the module structure and import-pipeline flow notes in the root -`AGENTS.md` and the package import/export RFC. Licensed behind -`feat:n8nPackages`; the public-API endpoints are beta and gated by -`N8N_PUBLIC_API_PACKAGES_ENABLED`. diff --git a/packages/cli/src/public-api/v1/handlers/n8n-packages/__tests__/n8n-packages.handler.test.ts b/packages/cli/src/public-api/v1/handlers/n8n-packages/__tests__/n8n-packages.handler.test.ts index 1bd87a51cfc..e34d27a2e18 100644 --- a/packages/cli/src/public-api/v1/handlers/n8n-packages/__tests__/n8n-packages.handler.test.ts +++ b/packages/cli/src/public-api/v1/handlers/n8n-packages/__tests__/n8n-packages.handler.test.ts @@ -1,5 +1,4 @@ import { mockInstance } from '@n8n/backend-test-utils'; -import { GlobalConfig } from '@n8n/config'; import type { AuthenticatedRequest } from '@n8n/db'; import { Container } from '@n8n/di'; import type { Response } from 'express'; @@ -8,7 +7,6 @@ import type { Mocked } from 'vitest'; import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; -import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { N8nPackagesService } from '@/modules/n8n-packages/n8n-packages.service'; import * as middlewares from '@/public-api/v1/shared/middlewares/global.middleware'; @@ -30,7 +28,6 @@ beforeAll(async () => { }); describe('n8n-packages handler', () => { - let packagesEnabled: boolean; let mockService: Mocked; function makeRequest( @@ -62,13 +59,9 @@ describe('n8n-packages handler', () => { beforeEach(() => { vi.clearAllMocks(); - packagesEnabled = true; mockService = mockInstance(N8nPackagesService); vi.spyOn(Container, 'get').mockImplementation((serviceClass) => { - if (serviceClass === GlobalConfig) { - return { publicApi: { packagesEnabled } } as never; - } if ( serviceClass === N8nPackagesService || (typeof serviceClass === 'function' && serviceClass.name === 'N8nPackagesService') @@ -79,18 +72,6 @@ describe('n8n-packages handler', () => { }); }); - it('throws NotFoundError when packages are disabled', async () => { - packagesEnabled = false; - - const caught = await run( - makeRequest({ workflowIds: ['wf-1'] }, ['workflow:export']), - makeResponse(), - ); - - expect(caught).toBeInstanceOf(NotFoundError); - expect(mockService.exportPackage).not.toHaveBeenCalled(); - }); - it('throws BadRequestError when the payload fails DTO validation', async () => { const caught = await run( makeRequest({ workflowIds: [''] }, ['workflow:export']), diff --git a/packages/cli/src/public-api/v1/handlers/n8n-packages/n8n-packages.handler.ts b/packages/cli/src/public-api/v1/handlers/n8n-packages/n8n-packages.handler.ts index f91e42eec96..11395e5de08 100644 --- a/packages/cli/src/public-api/v1/handlers/n8n-packages/n8n-packages.handler.ts +++ b/packages/cli/src/public-api/v1/handlers/n8n-packages/n8n-packages.handler.ts @@ -1,5 +1,4 @@ import { ExportPackageRequestDto, ImportPackageRequestDto } from '@n8n/api-types'; -import { GlobalConfig } from '@n8n/config'; import type { AuthenticatedRequest } from '@n8n/db'; import { Container } from '@n8n/di'; import type { ApiKeyScope } from '@n8n/permissions'; @@ -15,7 +14,6 @@ import { import { BadRequestError } from '@/errors/response-errors/bad-request.error'; import { ForbiddenError } from '@/errors/response-errors/forbidden.error'; -import { NotFoundError } from '@/errors/response-errors/not-found.error'; import { N8nPackagesService } from '@/modules/n8n-packages/n8n-packages.service'; import { resolveImportPackageUpload } from '@/modules/n8n-packages/utils/import-package-upload'; @@ -80,10 +78,6 @@ const n8nPackagesHandlers: N8nPackagesHandlers = { exportPackage: [ publicApiCompositeScope(PACKAGE_EXPORT_SCOPES), async (req, res) => { - if (!Container.get(GlobalConfig).publicApi.packagesEnabled) { - throw new NotFoundError('Not Found'); - } - const payload = ExportPackageRequestDto.safeParse(req.body); if (!payload.success) { throw new BadRequestError(payload.error.errors.map(({ message }) => message).join('; ')); @@ -113,10 +107,6 @@ const n8nPackagesHandlers: N8nPackagesHandlers = { importPackage: [ publicApiScope('workflow:import'), async (req, res) => { - if (!Container.get(GlobalConfig).publicApi.packagesEnabled) { - throw new NotFoundError('Not Found'); - } - const packageFile = resolveImportPackageUpload(req); const payload = ImportPackageRequestDto.safeParse(req.body ?? {}); diff --git a/packages/cli/src/public-api/v1/handlers/n8n-packages/spec/paths/n8n-packages.export.yml b/packages/cli/src/public-api/v1/handlers/n8n-packages/spec/paths/n8n-packages.export.yml index 9301367ecc7..d4f54a230ae 100644 --- a/packages/cli/src/public-api/v1/handlers/n8n-packages/spec/paths/n8n-packages.export.yml +++ b/packages/cli/src/public-api/v1/handlers/n8n-packages/spec/paths/n8n-packages.export.yml @@ -6,8 +6,7 @@ post: - N8nPackage summary: 'Beta: Export workflows and projects as an n8n package' description: | - **Beta — disabled by default.** Set `N8N_PUBLIC_API_PACKAGES_ENABLED=true` on the - instance to enable this endpoint. While disabled, requests return `404`. + **Beta** — breaking changes may still occur without major version bump. Export either workflows or projects as a gzipped tar archive (.n8np). Provide `workflowIds` or `projectIds`, but not both. Empty projects export diff --git a/packages/cli/src/public-api/v1/handlers/n8n-packages/spec/paths/n8n-packages.import.yml b/packages/cli/src/public-api/v1/handlers/n8n-packages/spec/paths/n8n-packages.import.yml index 6bfccdd3194..da3c82ea3e4 100644 --- a/packages/cli/src/public-api/v1/handlers/n8n-packages/spec/paths/n8n-packages.import.yml +++ b/packages/cli/src/public-api/v1/handlers/n8n-packages/spec/paths/n8n-packages.import.yml @@ -6,8 +6,7 @@ post: - N8nPackage summary: 'Beta: Import an n8n package into a project' description: | - **Beta — disabled by default.** Set `N8N_PUBLIC_API_PACKAGES_ENABLED=true` on the - instance to enable this endpoint. While disabled, requests return `404`. + **Beta** — breaking changes may still occur without major version bump. Imports a gzip-compressed tar package (`.n8np`) into the target project. Send the archive as the multipart field `package`. Optional routing uses form fields diff --git a/packages/cli/src/public-api/v1/openapi.yml b/packages/cli/src/public-api/v1/openapi.yml index f98e5a6f717..e66b6a92e8d 100644 --- a/packages/cli/src/public-api/v1/openapi.yml +++ b/packages/cli/src/public-api/v1/openapi.yml @@ -45,7 +45,7 @@ tags: - name: Folders description: Operations about folders - name: N8nPackage - description: Beta — operations about n8n packages. Disabled by default; enable with `N8N_PUBLIC_API_PACKAGES_ENABLED=true`. + description: Beta — breaking changes may still occur without major version bump. paths: /audit: diff --git a/packages/cli/test/integration/public-api/n8n-packages-export.test.ts b/packages/cli/test/integration/public-api/n8n-packages-export.test.ts index baa1d7fa88b..be5316728c3 100644 --- a/packages/cli/test/integration/public-api/n8n-packages-export.test.ts +++ b/packages/cli/test/integration/public-api/n8n-packages-export.test.ts @@ -1,5 +1,4 @@ import { testDb } from '@n8n/backend-test-utils'; -import { GlobalConfig } from '@n8n/config'; import type { User } from '@n8n/db'; import { Container } from '@n8n/di'; import { InstanceSettings } from 'n8n-core'; @@ -20,11 +19,9 @@ beforeAll(async () => { beforeEach(async () => { authOwnerAgent = testServer.publicApiAgentFor(owner); - Container.get(GlobalConfig).publicApi.packagesEnabled = true; }); afterEach(async () => { - Container.get(GlobalConfig).publicApi.packagesEnabled = false; await testDb.truncate(['WorkflowEntity', 'SharedWorkflow', 'ProjectRelation', 'Project']); }); diff --git a/packages/cli/test/integration/public-api/n8n-packages-import.test.ts b/packages/cli/test/integration/public-api/n8n-packages-import.test.ts index ec52d4be6ca..c74dcd02edf 100644 --- a/packages/cli/test/integration/public-api/n8n-packages-import.test.ts +++ b/packages/cli/test/integration/public-api/n8n-packages-import.test.ts @@ -1,5 +1,4 @@ import { mockInstance, testDb } from '@n8n/backend-test-utils'; -import { GlobalConfig } from '@n8n/config'; import type { Project, User } from '@n8n/db'; import { ProjectRepository } from '@n8n/db'; import { Container } from '@n8n/di'; @@ -44,11 +43,6 @@ beforeEach(async () => { 'SharedCredentials', ]); authOwnerAgent = testServer.publicApiAgentFor(owner); - Container.get(GlobalConfig).publicApi.packagesEnabled = true; -}); - -afterEach(() => { - Container.get(GlobalConfig).publicApi.packagesEnabled = false; }); const testWithAPIKey = (method: 'post', url: string, apiKey: string | null) => async () => {