Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't require source files to be writeable in dev mode #30758

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/next/server/dev/hot-reloader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import onDemandEntryHandler, {
} from './on-demand-entry-handler'
import { denormalizePagePath, normalizePathSep } from '../normalize-page-path'
import getRouteFromEntrypoint from '../get-route-from-entrypoint'
import { isWriteable } from '../../build/is-writeable'
import { fileExists } from '../../lib/file-exists'
import { ClientPagesLoaderOptions } from '../../build/webpack/loaders/next-client-pages-loader'
import { ssrEntries } from '../../build/webpack/plugins/middleware-plugin'
import { stringify } from 'querystring'
Expand Down Expand Up @@ -452,7 +452,7 @@ export default class HotReloader {
}

const { bundlePath, absolutePagePath, dispose } = entries[pageKey]
const pageExists = !dispose && (await isWriteable(absolutePagePath))
const pageExists = !dispose && (await fileExists(absolutePagePath))
if (!pageExists) {
// page was removed or disposed
delete entries[pageKey]
Expand Down
3 changes: 3 additions & 0 deletions test/integration/read-only-source-hmr/pages/hello.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const Hello = () => <p>Hello World</p>

export default Hello
123 changes: 123 additions & 0 deletions test/integration/read-only-source-hmr/test/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* eslint-env jest */

import fs from 'fs-extra'
import {
check,
findPort,
getBrowserBodyText,
killApp,
launchApp,
} from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'

const READ_ONLY_PERMISSIONS = 0o444
const READ_WRITE_PERMISSIONS = 0o644

const appDir = join(__dirname, '..')
const pagePath = join(appDir, 'pages/hello.js')

let appPort
let app

async function writeReadOnlyFile(path, content) {
const exists = await fs
.access(path)
.then(() => true)
.catch(() => false)

if (exists) {
await fs.chmod(path, READ_WRITE_PERMISSIONS)
}

await fs.writeFile(path, content, 'utf8')
await fs.chmod(path, READ_ONLY_PERMISSIONS)
}

describe('Read-only source HMR', () => {
beforeAll(async () => {
await fs.chmod(pagePath, READ_ONLY_PERMISSIONS)

appPort = await findPort()
app = await launchApp(appDir, appPort, {
env: {
__NEXT_TEST_WITH_DEVTOOL: 1,
// Events can be finicky in CI. This switches to a more reliable
// polling method.
CHOKIDAR_USEPOLLING: 'true',
CHOKIDAR_INTERVAL: 500,
},
})
})

afterAll(async () => {
await fs.chmod(pagePath, READ_WRITE_PERMISSIONS)
await killApp(app)
})

it('should detect changes to a page', async () => {
let browser

try {
browser = await webdriver(appPort, '/hello')
await check(() => getBrowserBodyText(browser), /Hello World/)

const originalContent = await fs.readFile(pagePath, 'utf8')
const editedContent = originalContent.replace('Hello World', 'COOL page')

await writeReadOnlyFile(pagePath, editedContent)
await check(() => getBrowserBodyText(browser), /COOL page/)

await writeReadOnlyFile(pagePath, originalContent)
await check(() => getBrowserBodyText(browser), /Hello World/)
} finally {
if (browser) {
await browser.close()
}
}
})

it('should handle page deletion and subsequent recreation', async () => {
let browser

try {
browser = await webdriver(appPort, '/hello')
await check(() => getBrowserBodyText(browser), /Hello World/)

const originalContent = await fs.readFile(pagePath, 'utf8')

await fs.remove(pagePath)

await writeReadOnlyFile(pagePath, originalContent)
await check(() => getBrowserBodyText(browser), /Hello World/)
} finally {
if (browser) {
await browser.close()
}
}
})

it('should detect a new page', async () => {
let browser
const newPagePath = join(appDir, 'pages/new.js')

try {
await writeReadOnlyFile(
newPagePath,
`
const New = () => <p>New page</p>

export default New
`
)

browser = await webdriver(appPort, '/new')
await check(() => getBrowserBodyText(browser), /New page/)
} finally {
if (browser) {
await browser.close()
}
await fs.remove(newPagePath)
}
})
})