diff --git a/.changeset/add-shebang-to-adapter-node-index.md b/.changeset/add-shebang-to-adapter-node-index.md new file mode 100644 index 000000000000..aed62f644cbb --- /dev/null +++ b/.changeset/add-shebang-to-adapter-node-index.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/adapter-node': patch +--- + +feat: add a shebang to `build/index.js` diff --git a/packages/adapter-node/index.js b/packages/adapter-node/index.js index 6c0087788398..0df75c91500f 100644 --- a/packages/adapter-node/index.js +++ b/packages/adapter-node/index.js @@ -75,7 +75,15 @@ export default function (opts = {}) { ].join('\n') ); - fs.writeFileSync(`${out}/index.js`, `export * from './server/adapter-index.js';\n`); + fs.writeFileSync( + `${out}/index.js`, + `#!/usr/bin/env node\nexport * from './server/adapter-index.js';\n` + ); + try { + fs.chmodSync(`${out}/index.js`, 0o755); + } catch { + // Ignore chmod errors on platforms/filesystems that do not support it + } fs.writeFileSync(`${out}/handler.js`, `export * from './server/handler.js';\n`); }, diff --git a/packages/adapter-node/src/adapt.spec.ts b/packages/adapter-node/src/adapt.spec.ts new file mode 100644 index 000000000000..cf666cf05cf8 --- /dev/null +++ b/packages/adapter-node/src/adapt.spec.ts @@ -0,0 +1,41 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { expect, test } from 'vitest'; +import adapter from '../index.js'; + +test('adapt writes index.js with a shebang', async () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'sveltekit-adapter-node-test-')); + try { + const out = path.join(tmp, 'build'); + const a = adapter({ out, precompress: false }); + + /** @type {any} */ + const builder = { + config: { paths: { base: '', origin: '' } }, + log: { minor: () => {} }, + writeClient: () => { + fs.mkdirSync(path.join(out, 'client'), { recursive: true }); + return []; + }, + writePrerendered: () => [], + compress: async () => [], + prerendered: { paths: [] }, + getServerDirectory: () => path.join(tmp, 'server'), + generateServerInstance: () => {}, + hasServerInstrumentationFile: () => false, + copy: () => {}, + getAppPath: () => '_app', + mimeTypes: {} + }; + + fs.mkdirSync(path.join(tmp, 'server'), { recursive: true }); + + await a.adapt(builder as any); + + const index_content = fs.readFileSync(path.join(out, 'index.js'), 'utf8'); + expect(index_content.startsWith('#!/usr/bin/env node\n')).toBe(true); + } finally { + fs.rmSync(tmp, { force: true, recursive: true }); + } +}); diff --git a/packages/adapter-node/test/apps/basic/build.test.js b/packages/adapter-node/test/apps/basic/build.test.js index 68507ac4a5c3..1e42f91553dd 100644 --- a/packages/adapter-node/test/apps/basic/build.test.js +++ b/packages/adapter-node/test/apps/basic/build.test.js @@ -30,3 +30,8 @@ test('dependencies are not bundled', () => { test('everything else is bundled', () => { expect(leaked_imports(app, 'build')).toEqual([]); }); + +test('build/index.js includes shebang', () => { + const content = fs.readFileSync(path.join(app, 'build/index.js'), 'utf8'); + expect(content.startsWith('#!/usr/bin/env node\n')).toBe(true); +});