diff --git a/packages/@n8n/node-cli/src/template/core.test.ts b/packages/@n8n/node-cli/src/template/core.test.ts index d1620f8381e..cd82ef1c2af 100644 --- a/packages/@n8n/node-cli/src/template/core.test.ts +++ b/packages/@n8n/node-cli/src/template/core.test.ts @@ -3,6 +3,7 @@ import handlebars from 'handlebars'; import * as fs from 'node:fs/promises'; import { + copyDefaultTemplateFilesToDestination, copyTemplateFilesToDestination, templateStaticFiles, createTemplate, @@ -53,6 +54,29 @@ describe('Templates > core', () => { }); }); + describe('copyDefaultTemplateFilesToDestination', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('copies the shared default template and restores renamed dotfiles', async () => { + mockCopyFolder.mockResolvedValue(); + mockFs.rename.mockResolvedValue(); + + await copyDefaultTemplateFilesToDestination(baseData); + + expect(mockCopyFolder).toHaveBeenCalledWith( + expect.objectContaining({ + destination: '/dest', + ignore: ['dist', 'node_modules'], + }), + ); + // npm strips `.gitignore` from published packages, so it's shipped as `gitignore` + // and renamed back at scaffold time. + expect(mockFs.rename).toHaveBeenCalledWith('/dest/gitignore', '/dest/.gitignore'); + }); + }); + describe('templateStaticFiles', () => { beforeEach(() => { vi.clearAllMocks(); @@ -96,6 +120,7 @@ describe('Templates > core', () => { mockFs.readFile.mockResolvedValue(''); mockHandlebars.compile.mockReturnValue(() => ''); mockFs.writeFile.mockResolvedValue(); + mockFs.rename.mockResolvedValue(); const wrapped = createTemplate(template); await wrapped.run(baseData); diff --git a/packages/@n8n/node-cli/src/template/core.ts b/packages/@n8n/node-cli/src/template/core.ts index 4d1617106ed..6fc18a48651 100644 --- a/packages/@n8n/node-cli/src/template/core.ts +++ b/packages/@n8n/node-cli/src/template/core.ts @@ -41,12 +41,25 @@ export async function copyTemplateFilesToDestination( }); } +// npm strips certain dotfiles (e.g. `.gitignore`) from published packages, so the +// template ships them under alternative names and we restore the originals after +// copying to the destination. Add more entries here as needed. +const RENAMED_DEFAULT_TEMPLATE_FILES: Record = { + gitignore: '.gitignore', +}; + export async function copyDefaultTemplateFilesToDestination(data: TemplateData) { await copyFolder({ source: path.resolve(__dirname, 'templates/shared/default'), destination: data.destinationPath, ignore: ['dist', 'node_modules'], }); + await Promise.all( + Object.entries(RENAMED_DEFAULT_TEMPLATE_FILES).map( + async ([from, to]) => + await fs.rename(path.join(data.destinationPath, from), path.join(data.destinationPath, to)), + ), + ); } export async function templateStaticFiles(data: TemplateData) { diff --git a/packages/@n8n/node-cli/src/template/templates/shared/default/.gitignore b/packages/@n8n/node-cli/src/template/templates/shared/default/gitignore similarity index 100% rename from packages/@n8n/node-cli/src/template/templates/shared/default/.gitignore rename to packages/@n8n/node-cli/src/template/templates/shared/default/gitignore