Skip to content

Commit

Permalink
chore(esm): convert @redwoodjs/cli-helpers to ESM (#9872)
Browse files Browse the repository at this point in the history
This PR converts `@redwoodjs/cli-helpers` to a dual ESM/CJS package like
#9870 did for
`@redwoodjs/project-config`. I didn't do anything differently so see
that PR for details.

As soon as I converted it, the Jest tests stopped working so
@Josh-Walker-GM and I bundled both changes into one PR here.

With this PR, we should be able to get all the tests in
#9863 passing (about ~15 (out
of 1000+) are skipped right now) because Vitest will be able to mock
`fs` since it's imported instead of required. That'll be really nice
since converting the CLI to ESM will then be possible. I'll confirm that
after making this PR. Even if they don't this'll still have been
worthwhile.

---------

Co-authored-by: Josh GM Walker <56300765+Josh-Walker-GM@users.noreply.github.com>
  • Loading branch information
jtoar and Josh-Walker-GM authored Jan 24, 2024
1 parent d0f036d commit e94dbf5
Show file tree
Hide file tree
Showing 27 changed files with 294 additions and 451 deletions.
1 change: 0 additions & 1 deletion packages/cli-helpers/.babelrc.js

This file was deleted.

222 changes: 3 additions & 219 deletions packages/cli-helpers/__mocks__/fs.js
Original file line number Diff line number Diff line change
@@ -1,220 +1,4 @@
import path from 'path'
import * as memfs from 'memfs'

const fs = {
...jest.requireActual('fs'),
}

let mockFiles = {}

const pathSeparator = path.sep

const getParentDir = (path) => {
return path.substring(0, path.lastIndexOf(pathSeparator))
}

const makeParentDirs = (path) => {
const parentDir = getParentDir(path)
if (parentDir && !(parentDir in mockFiles)) {
mockFiles[parentDir] = undefined
makeParentDirs(parentDir)
}
}

/**
* This is a custom function that our tests can use during setup to specify
* what the files on the "mock" filesystem should look like when any of the
* `fs` APIs are used.
*
* Sets the state of the mocked file system
* @param newMockFiles - {[filepath]: contents}
*/
fs.__setMockFiles = (newMockFiles) => {
mockFiles = { ...newMockFiles }

// Generate all the directories which implicitly exist
Object.keys(mockFiles).forEach((mockPath) => {
if (mockPath.includes(pathSeparator)) {
makeParentDirs(mockPath)
}
})
}

fs.__getMockFiles = () => {
return mockFiles
}

fs.readFileSync = (path) => {
// In prisma v4.3.0, prisma format uses a Wasm module. See https://github.com/prisma/prisma/releases/tag/4.3.0.
// We shouldn't mock this, so we'll use the real fs.readFileSync.
if (path.includes('prisma_fmt_build_bg.wasm')) {
return jest.requireActual('fs').readFileSync(path)
}

if (path in mockFiles) {
return mockFiles[path]
} else {
const fakeError = new Error(
`Error: ENOENT: no such file or directory, open '${path}'`
)
fakeError.errno = -2
fakeError.syscall = 'open'
fakeError.code = 'ENOENT'
fakeError.path = path
throw fakeError
}
}

fs.writeFileSync = (path, contents) => {
const parentDir = getParentDir(path)
if (parentDir && !fs.existsSync(parentDir)) {
const fakeError = new Error(
`Error: ENOENT: no such file or directory, open '${path}'`
)
fakeError.errno = -2
fakeError.syscall = 'open'
fakeError.code = 'ENOENT'
fakeError.path = path
throw fakeError
}
mockFiles[path] = contents
}

fs.appendFileSync = (path, contents) => {
if (path in mockFiles) {
mockFiles[path] = mockFiles[path] + contents
} else {
fs.writeFileSync(path, contents)
}
}

fs.rmSync = (path, options = {}) => {
if (fs.existsSync(path)) {
if (options.recursive) {
Object.keys(mockFiles).forEach((mockedPath) => {
if (mockedPath.startsWith(path)) {
delete mockFiles[mockedPath]
}
})
} else {
if (mockFiles[path] === undefined) {
const children = fs.readdirSync(path)
if (children.length !== 0) {
const fakeError = new Error(
`NodeError [SystemError]: Path is a directory: rm returned EISDIR (is a directory) ${path}`
)
fakeError.errno = 21
fakeError.syscall = 'rm'
fakeError.code = 'ERR_FS_EISDIR'
fakeError.path = path
throw fakeError
}
}
delete mockFiles[path]
}
} else {
const fakeError = new Error(
`Error: ENOENT: no such file or directory, stat '${path}'`
)
fakeError.errno = -2
fakeError.syscall = 'stat'
fakeError.code = 'ENOENT'
fakeError.path = path
throw fakeError
}
}

fs.unlinkSync = (path) => {
if (path in mockFiles) {
delete mockFiles[path]
} else {
const fakeError = new Error(
`Error: ENOENT: no such file or directory, stat '${path}'`
)
fakeError.errno = -2
fakeError.syscall = 'unlink'
fakeError.code = 'ENOENT'
fakeError.path = path
throw fakeError
}
}

fs.existsSync = (path) => {
return path in mockFiles
}

fs.copyFileSync = (src, dist) => {
fs.writeFileSync(dist, fs.readFileSync(src))
}

fs.readdirSync = (path) => {
if (!fs.existsSync(path)) {
const fakeError = new Error(
`Error: ENOENT: no such file or directory, scandir '${path}'`
)
fakeError.errno = -2
fakeError.syscall = 'scandir'
fakeError.code = 'ENOENT'
fakeError.path = path
throw fakeError
}

if (mockFiles[path] !== undefined) {
const fakeError = new Error(
`Error: ENOTDIR: not a directory, scandir '${path}'`
)
fakeError.errno = -20
fakeError.syscall = 'scandir'
fakeError.code = 'ENOTDIR'
fakeError.path = path
throw fakeError
}

const content = []
Object.keys(mockFiles).forEach((mockedPath) => {
const childPath = mockedPath.substring(path.length + 1)
if (
mockedPath.startsWith(path) &&
!childPath.includes(pathSeparator) &&
childPath
) {
content.push(childPath)
}
})
return content
}

fs.mkdirSync = (path, options = {}) => {
if (options.recursive) {
makeParentDirs(path)
}
// Directories are represented as paths with an "undefined" value
fs.writeFileSync(path, undefined)
}

fs.rmdirSync = (path, options = {}) => {
if (!fs.existsSync(path)) {
const fakeError = new Error(
`Error: ENOENT: no such file or directory, rmdir '${path}'`
)
fakeError.errno = -2
fakeError.syscall = 'rmdir'
fakeError.code = 'ENOENT'
fakeError.path = path
throw fakeError
}

if (mockFiles[path] !== undefined) {
const fakeError = new Error(
`Error: ENOTDIR: not a directory, rmdir '${path}'`
)
fakeError.errno = -20
fakeError.syscall = 'rmdir'
fakeError.code = 'ENOTDIR'
fakeError.path = path
throw fakeError
}

fs.rmSync(path, options)
}

module.exports = fs
export * from 'memfs'
export default memfs.fs
26 changes: 26 additions & 0 deletions packages/cli-helpers/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import * as esbuild from 'esbuild'

const options = {
entryPoints: ['./src/index.ts'],
outdir: 'dist',

platform: 'node',
target: ['node20'],
bundle: true,
packages: 'external',

logLevel: 'info',
metafile: true,
}

await esbuild.build({
...options,
format: 'esm',
outExtension: { '.js': '.mjs' },
})

await esbuild.build({
...options,
format: 'cjs',
outExtension: { '.js': '.cjs' },
})
4 changes: 0 additions & 4 deletions packages/cli-helpers/jest.config.js

This file was deleted.

21 changes: 11 additions & 10 deletions packages/cli-helpers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,32 @@
"directory": "packages/cli-helpers"
},
"license": "MIT",
"main": "./dist/index.js",
"type": "module",
"exports": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"default": "./dist/index.cjs"
},
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "yarn build:js && yarn build:types",
"build:js": "babel src -d dist --extensions \".js,.jsx,.ts,.tsx\"",
"build": "yarn node ./build.js && yarn build:types",
"build:pack": "yarn pack -o redwoodjs-cli-helpers.tgz",
"build:types": "tsc --build --verbose",
"build:watch": "nodemon --watch src --ext \"js,jsx,ts,tsx\" --ignore dist --exec \"yarn build\"",
"prepublishOnly": "NODE_ENV=production yarn build",
"test": "jest src",
"test:watch": "yarn test --watch"
"test": "vitest run",
"test:watch": "vitest watch"
},
"dependencies": {
"@babel/core": "^7.22.20",
"@babel/runtime-corejs3": "7.23.6",
"@iarna/toml": "2.2.5",
"@opentelemetry/api": "1.7.0",
"@redwoodjs/project-config": "6.0.7",
"@redwoodjs/telemetry": "6.0.7",
"chalk": "4.1.2",
"core-js": "3.34.0",
"dotenv": "16.3.1",
"execa": "5.1.1",
"listr2": "6.6.1",
Expand All @@ -41,12 +43,11 @@
"terminal-link": "2.1.1"
},
"devDependencies": {
"@babel/cli": "7.23.4",
"@types/lodash": "4.14.201",
"@types/pascalcase": "1.0.3",
"@types/yargs": "17.0.32",
"jest": "29.7.0",
"typescript": "5.3.3"
"typescript": "5.3.3",
"vitest": "1.2.1"
},
"gitHead": "3905ed045508b861b495f8d5630d76c7a157d8f1"
}
Loading

0 comments on commit e94dbf5

Please sign in to comment.