diff --git a/packages/next/lib/typescript/writeAppTypeDeclarations.ts b/packages/next/lib/typescript/writeAppTypeDeclarations.ts index 3a8942a3f790f8c..c6b64dc2b200775 100644 --- a/packages/next/lib/typescript/writeAppTypeDeclarations.ts +++ b/packages/next/lib/typescript/writeAppTypeDeclarations.ts @@ -1,6 +1,7 @@ import { promises as fs } from 'fs' import os from 'os' import path from 'path' +import { fileExists } from '../file-exists' export async function writeAppTypeDeclarations( baseDir: string, @@ -9,19 +10,27 @@ export async function writeAppTypeDeclarations( // Reference `next` types const appTypeDeclarations = path.join(baseDir, 'next-env.d.ts') - await fs.writeFile( - appTypeDeclarations, + const content = '/// ' + - os.EOL + - '/// ' + - os.EOL + - (imageImportsEnabled - ? '/// ' + os.EOL - : '') + - os.EOL + - '// NOTE: This file should not be edited' + - os.EOL + - '// see https://nextjs.org/docs/basic-features/typescript for more information.' + - os.EOL - ) + os.EOL + + '/// ' + + os.EOL + + (imageImportsEnabled + ? '/// ' + os.EOL + : '') + + os.EOL + + '// NOTE: This file should not be edited' + + os.EOL + + '// see https://nextjs.org/docs/basic-features/typescript for more information.' + + os.EOL + + // Avoids a write for read-only filesystems + if ( + (await fileExists(appTypeDeclarations)) && + (await fs.readFile(appTypeDeclarations, 'utf8')) === content + ) { + return + } + + await fs.writeFile(appTypeDeclarations, content) } diff --git a/test/integration/typescript-app-type-declarations/next-env.d.ts b/test/integration/typescript-app-type-declarations/next-env.d.ts new file mode 100644 index 000000000000000..9bc3dd46b9d9b21 --- /dev/null +++ b/test/integration/typescript-app-type-declarations/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/test/integration/typescript-app-type-declarations/pages/index.tsx b/test/integration/typescript-app-type-declarations/pages/index.tsx new file mode 100644 index 000000000000000..3618c0765503375 --- /dev/null +++ b/test/integration/typescript-app-type-declarations/pages/index.tsx @@ -0,0 +1,3 @@ +export default function Index() { + return
+} diff --git a/test/integration/typescript-app-type-declarations/test/index.test.js b/test/integration/typescript-app-type-declarations/test/index.test.js new file mode 100644 index 000000000000000..ad2ea6a74c03143 --- /dev/null +++ b/test/integration/typescript-app-type-declarations/test/index.test.js @@ -0,0 +1,61 @@ +/* eslint-env jest */ + +import { join } from 'path' +import { findPort, launchApp, killApp } from 'next-test-utils' +import { promises as fs } from 'fs' + +jest.setTimeout(1000 * 60 * 2) + +const appDir = join(__dirname, '..') +const appTypeDeclarations = join(appDir, 'next-env.d.ts') + +describe('TypeScript App Type Declarations', () => { + it('should write a new next-env.d.ts if none exist', async () => { + const prevContent = await fs.readFile(appTypeDeclarations, 'utf8') + try { + await fs.unlink(appTypeDeclarations) + const appPort = await findPort() + let app + try { + app = await launchApp(appDir, appPort, {}) + const content = await fs.readFile(appTypeDeclarations, 'utf8') + expect(content).toEqual(prevContent) + } finally { + await killApp(app) + } + } finally { + await fs.writeFile(appTypeDeclarations, prevContent) + } + }) + + it('should overwrite next-env.d.ts if an incorrect one exists', async () => { + const prevContent = await fs.readFile(appTypeDeclarations, 'utf8') + try { + await fs.writeFile(appTypeDeclarations, prevContent + 'modification') + const appPort = await findPort() + let app + try { + app = await launchApp(appDir, appPort, {}) + const content = await fs.readFile(appTypeDeclarations, 'utf8') + expect(content).toEqual(prevContent) + } finally { + await killApp(app) + } + } finally { + await fs.writeFile(appTypeDeclarations, prevContent) + } + }) + + it('should not touch an existing correct next-env.d.ts', async () => { + const prevStat = await fs.stat(appTypeDeclarations) + const appPort = await findPort() + let app + try { + app = await launchApp(appDir, appPort, {}) + const stat = await fs.stat(appTypeDeclarations) + expect(stat.mtime).toEqual(prevStat.mtime) + } finally { + await killApp(app) + } + }) +}) diff --git a/test/integration/typescript-app-type-declarations/tsconfig.json b/test/integration/typescript-app-type-declarations/tsconfig.json new file mode 100644 index 000000000000000..11bbfe8efba7aaa --- /dev/null +++ b/test/integration/typescript-app-type-declarations/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "esModuleInterop": true, + "module": "esnext", + "jsx": "preserve", + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true + }, + "exclude": ["node_modules"], + "include": ["next-env.d.ts", "components", "pages"] +}