diff --git a/gdf.js b/gdf.js index 4f6eb87..4c66abc 100755 --- a/gdf.js +++ b/gdf.js @@ -836,6 +836,42 @@ export class GDF { return this.bun ? 'bunx' : 'npx' } + get bunStartCommand() { + const candidates = [] + + for (const entry of [this.#pj.module, this.#pj.main]) { + if (!entry) continue + + candidates.push(entry) + const extension = path.extname(entry) + const typescriptEntry = extension + ? `${entry.slice(0, -extension.length)}.ts` + : `${entry}.ts` + if (typescriptEntry !== entry) candidates.push(typescriptEntry) + } + + const devScript = this.#pj.scripts?.dev + if (devScript) { + const commands = new Set(['bun', 'bunx', 'node', 'npx', 'npm', 'pnpm', 'yarn', 'tsx', 'ts-node', 'run', 'exec']) + const entries = ShellQuote.parse(devScript) + .filter(entry => typeof entry === 'string' && !entry.startsWith('-') && !commands.has(entry)) + .reverse() + + for (const entry of entries) { + candidates.push(entry) + const extension = path.extname(entry) + const typescriptEntry = extension + ? `${entry.slice(0, -extension.length)}.ts` + : `${entry}.ts` + if (typescriptEntry !== entry) candidates.push(typescriptEntry) + } + } + + candidates.push('src/index.ts', 'index.ts') + const entry = candidates.find(filename => fs.existsSync(path.join(this._appdir, filename))) + return ['bun', entry || 'index.ts'] + } + // command to start the web server get startCommand() { if (this.options.cmd) return this.options.cmd @@ -882,6 +918,8 @@ export class GDF { return start } else if (this.#pj.scripts?.start) { return [this.packager, 'run', 'start'] + } else if (this.bun) { + return this.bunStartCommand } else if (this.#pj.type === 'module' && this.#pj.module) { return [this.packager === 'bun' ? 'bun' : 'node', this.#pj.module] } else if (this.#pj.main) { diff --git a/test/gdf.test.js b/test/gdf.test.js index fe074c5..8db7b87 100644 --- a/test/gdf.test.js +++ b/test/gdf.test.js @@ -656,6 +656,7 @@ describe('gdf: start command and port', function() { it('uses bun for module, main and the default entry point', async function() { sandbox().stub('bun', 'echo 1.1.20\n') sandbox().write('bun.lockb', '') + sandbox().write('app.ts', '') let gdf = await sandbox().run({ main: 'app.ts' }) expect(gdf.startCommand).to.deep.equal(['bun', 'app.ts']) @@ -664,6 +665,41 @@ describe('gdf: start command and port', function() { expect(gdf.startCommand).to.deep.equal(['bun', 'index.ts']) }) + it('resolves a Bun module entry point to its TypeScript file when type is unspecified', async function() { + sandbox().stub('bun', 'echo 1.1.20\n') + sandbox().write('bun.lockb', '') + sandbox().write('src/index.ts', '') + + const gdf = await sandbox().run({ module: 'src/index.js' }) + expect(gdf.startCommand).to.deep.equal(['bun', 'src/index.ts']) + }) + + it('uses the Bun dev entry point without --watch', async function() { + sandbox().stub('bun', 'echo 1.1.20\n') + sandbox().write('bun.lockb', '') + sandbox().write('src/server.ts', '') + + const gdf = await sandbox().run({ scripts: { dev: 'bun --watch src/server.ts' } }) + expect(gdf.startCommand).to.deep.equal(['bun', 'src/server.ts']) + }) + + it('runs a Bun start script when one is defined', async function() { + sandbox().stub('bun', 'echo 1.1.20\n') + sandbox().write('bun.lockb', '') + + const gdf = await sandbox().run({ scripts: { start: 'bun src/server.ts' } }) + expect(gdf.startCommand).to.deep.equal(['bun', 'run', 'start']) + }) + + it('uses src/index.ts when a Bun project has no module or dev entry point', async function() { + sandbox().stub('bun', 'echo 1.1.20\n') + sandbox().write('bun.lockb', '') + sandbox().write('src/index.ts', '') + + const gdf = await sandbox().run({}) + expect(gdf.startCommand).to.deep.equal(['bun', 'src/index.ts']) + }) + it('starts standalone next.js with server.js', async function() { for (const config of ['next.config.js', 'next.config.mjs']) { fs.rmSync('next.config.js', { force: true })