diff --git a/packages/cli-helpers/.babelrc.js b/packages/cli-helpers/.babelrc.js deleted file mode 100644 index 3b2c815712d9..000000000000 --- a/packages/cli-helpers/.babelrc.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = { extends: '../../babel.config.js' } diff --git a/packages/cli-helpers/__mocks__/fs.js b/packages/cli-helpers/__mocks__/fs.js index 99fec09d82ed..de739ddd902a 100644 --- a/packages/cli-helpers/__mocks__/fs.js +++ b/packages/cli-helpers/__mocks__/fs.js @@ -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 diff --git a/packages/cli-helpers/build.js b/packages/cli-helpers/build.js new file mode 100644 index 000000000000..3a01088401ee --- /dev/null +++ b/packages/cli-helpers/build.js @@ -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' }, +}) diff --git a/packages/cli-helpers/jest.config.js b/packages/cli-helpers/jest.config.js deleted file mode 100644 index 4b24969ced25..000000000000 --- a/packages/cli-helpers/jest.config.js +++ /dev/null @@ -1,4 +0,0 @@ -/** @type {import('@jest/types').Config.InitialOptions} */ -module.exports = { - testPathIgnorePatterns: ['fixtures', 'dist', 'mockFsFiles'], -} diff --git a/packages/cli-helpers/package.json b/packages/cli-helpers/package.json index 8bdc55d1ef3e..d7678c25d735 100644 --- a/packages/cli-helpers/package.json +++ b/packages/cli-helpers/package.json @@ -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", @@ -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" } diff --git a/packages/cli-helpers/src/auth/__tests__/__snapshots__/authTasks.test.ts.snap b/packages/cli-helpers/src/auth/__tests__/__snapshots__/authTasks.test.ts.snap index 585825351020..abc555315d2b 100644 --- a/packages/cli-helpers/src/auth/__tests__/__snapshots__/authTasks.test.ts.snap +++ b/packages/cli-helpers/src/auth/__tests__/__snapshots__/authTasks.test.ts.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`authTasks Components with props Should add useAuth on the same line for single line components, and separate line for multiline components 1`] = ` +exports[`authTasks > Components with props > Should add useAuth on the same line for single line components, and separate line for multiline components 1`] = ` "import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web' import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' @@ -27,7 +27,7 @@ export default App " `; -exports[`authTasks Components with props Should add useAuth on the same line for single line components, and separate line for multiline components 2`] = ` +exports[`authTasks > Components with props > Should add useAuth on the same line for single line components, and separate line for multiline components 2`] = ` "// In this file, all Page components from 'src/pages\` are auto-imported. Nested // directories are supported, and should be uppercase. Each subdirectory will be // prepended onto the component name. @@ -62,7 +62,7 @@ export default Routes " `; -exports[`authTasks Components with props Should not add useAuth if one already exists 1`] = ` +exports[`authTasks > Components with props > Should not add useAuth if one already exists 1`] = ` "import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web' import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' @@ -89,7 +89,7 @@ export default App " `; -exports[`authTasks Components with props Should not add useAuth if one already exists 2`] = ` +exports[`authTasks > Components with props > Should not add useAuth if one already exists 2`] = ` "// In this file, all Page components from 'src/pages\` are auto-imported. Nested // directories are supported, and should be uppercase. Each subdirectory will be // prepended onto the component name. @@ -128,7 +128,7 @@ export default Routes " `; -exports[`authTasks Customized App.js Should add auth config when using explicit return 1`] = ` +exports[`authTasks > Customized App.js > Should add auth config when using explicit return 1`] = ` "import { useEffect } from 'react' import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web' import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' @@ -166,7 +166,7 @@ export default App " `; -exports[`authTasks Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Auth0) 1`] = ` +exports[`authTasks > Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Auth0) 1`] = ` "import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web' import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' @@ -193,7 +193,7 @@ export default App " `; -exports[`authTasks Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Auth0) 2`] = ` +exports[`authTasks > Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Auth0) 2`] = ` "import { Auth0Client } from '@auth0/auth0-spa-js' import { createAuth } from '@redwoodjs/auth-auth0-web' @@ -221,7 +221,7 @@ export const { AuthProvider, useAuth } = createAuth(auth0) " `; -exports[`authTasks Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Auth0) 3`] = ` +exports[`authTasks > Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Auth0) 3`] = ` "// In this file, all Page components from 'src/pages\` are auto-imported. Nested // directories are supported, and should be uppercase. Each subdirectory will be // prepended onto the component name. @@ -247,7 +247,7 @@ export default Routes " `; -exports[`authTasks Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Clerk) 1`] = ` +exports[`authTasks > Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Clerk) 1`] = ` "import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web' import { RedwoodApolloProvider } from '@redwoodjs/web/apollo' @@ -274,7 +274,7 @@ export default App " `; -exports[`authTasks Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Clerk) 2`] = ` +exports[`authTasks > Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Clerk) 2`] = ` "import React, { useEffect } from 'react' import { ClerkLoaded, ClerkProvider, useUser } from '@clerk/clerk-react' @@ -327,7 +327,7 @@ export const AuthProvider = ({ children }: Props) => { " `; -exports[`authTasks Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Clerk) 3`] = ` +exports[`authTasks > Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Clerk) 3`] = ` "// In this file, all Page components from 'src/pages\` are auto-imported. Nested // directories are supported, and should be uppercase. Each subdirectory will be // prepended onto the component name. @@ -353,7 +353,7 @@ export default Routes " `; -exports[`authTasks Should update App.tsx for legacy apps 1`] = ` +exports[`authTasks > Should update App.tsx for legacy apps 1`] = ` "import netlifyIdentity from 'netlify-identity-widget' import { isBrowser } from '@redwoodjs/prerender/browserUtils' @@ -385,7 +385,7 @@ export default App " `; -exports[`authTasks Swapped out GraphQL client Should add auth config when app is missing RedwoodApolloProvider 1`] = ` +exports[`authTasks > Swapped out GraphQL client > Should add auth config when app is missing RedwoodApolloProvider 1`] = ` "import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs/web' import FatalErrorPage from 'src/pages/FatalErrorPage' @@ -415,7 +415,7 @@ export default App " `; -exports[`authTasks addApiConfig Adds authDecoder arg to default graphql.ts file 1`] = ` +exports[`authTasks > addApiConfig > Adds authDecoder arg to default graphql.ts file 1`] = ` "import { authDecoder } from 'test-auth-api' import { createGraphQLHandler } from '@redwoodjs/graphql-server' @@ -442,7 +442,7 @@ export const handler = createGraphQLHandler({ " `; -exports[`authTasks addApiConfig Doesn't add authDecoder arg if one already exists 1`] = ` +exports[`authTasks > addApiConfig > Doesn't add authDecoder arg if one already exists 1`] = ` "import { authDecoder } from 'test-auth-api' import { createGraphQLHandler } from '@redwoodjs/graphql-server' @@ -469,7 +469,7 @@ export const handler = createGraphQLHandler({ " `; -exports[`authTasks addApiConfig Doesn't add authDecoder arg if one already exists, even with a non-standard import name and arg placement 1`] = ` +exports[`authTasks > addApiConfig > Doesn't add authDecoder arg if one already exists, even with a non-standard import name and arg placement 1`] = ` "import { authDecoder } from 'test-auth-api' import { createGraphQLHandler } from '@redwoodjs/graphql-server' @@ -496,6 +496,6 @@ export const handler = createGraphQLHandler({ " `; -exports[`authTasks writes an auth.js file for JS projects 1`] = `"// web auth template"`; +exports[`authTasks > writes an auth.js file for JS projects 1`] = `"// web auth template"`; -exports[`authTasks writes an auth.ts file for TS projects 1`] = `"// web auth template"`; +exports[`authTasks > writes an auth.ts file for TS projects 1`] = `"// web auth template"`; diff --git a/packages/cli-helpers/src/auth/__tests__/authFiles.test.ts b/packages/cli-helpers/src/auth/__tests__/authFiles.test.ts index b7ca9a3d778e..2c4f6fe395d5 100644 --- a/packages/cli-helpers/src/auth/__tests__/authFiles.test.ts +++ b/packages/cli-helpers/src/auth/__tests__/authFiles.test.ts @@ -1,13 +1,14 @@ // Have to use `var` here to avoid "Temporal Dead Zone" issues let mockBasePath = '' -let mockIsTypeScriptProject = true globalThis.__dirname = __dirname -jest.mock('../../lib/paths', () => { +vi.mock('../../lib/paths', async (importOriginal) => { const path = require('path') + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const orginalPaths = await importOriginal() return { - ...jest.requireActual('../../lib/paths'), + ...orginalPaths, getPaths: () => { const base = mockBasePath || '/mock/base/path' @@ -22,17 +23,20 @@ jest.mock('../../lib/paths', () => { } }) -jest.mock('../../lib/project', () => ({ - isTypeScriptProject: () => mockIsTypeScriptProject, +vi.mock('../../lib/project', () => ({ + isTypeScriptProject: vi.fn(), })) import path from 'path' +import { vi, beforeEach, it, expect } from 'vitest' + import { getPaths } from '../../lib/paths' +import { isTypeScriptProject } from '../../lib/project' import { apiSideFiles, generateUniqueFileNames } from '../authFiles' beforeEach(() => { - mockIsTypeScriptProject = true + vi.mocked(isTypeScriptProject).mockReturnValue(true) }) it('generates a record of TS files', () => { @@ -51,7 +55,7 @@ it('generates a record of TS files', () => { }) it('generates a record of JS files', () => { - mockIsTypeScriptProject = false + vi.mocked(isTypeScriptProject).mockReturnValue(false) const filePaths = Object.keys( apiSideFiles({ diff --git a/packages/cli-helpers/src/auth/__tests__/authTasks.test.ts b/packages/cli-helpers/src/auth/__tests__/authTasks.test.ts index 70f6b77603e8..6d7f007b506b 100644 --- a/packages/cli-helpers/src/auth/__tests__/authTasks.test.ts +++ b/packages/cli-helpers/src/auth/__tests__/authTasks.test.ts @@ -1,72 +1,67 @@ -// Have to use `var` here to avoid "Temporal Dead Zone" issues -// eslint-disable-next-line -var mockIsTypeScriptProject = true - -jest.mock('../../lib/project', () => ({ - isTypeScriptProject: () => mockIsTypeScriptProject, -})) - -jest.mock('../../lib', () => ({ +vi.mock('../../lib', () => ({ transformTSToJS: (_path: string, data: string) => data, })) // mock Telemetry for CLI commands so they don't try to spawn a process -jest.mock('@redwoodjs/telemetry', () => { +vi.mock('@redwoodjs/telemetry', () => { return { - errorTelemetry: () => jest.fn(), - timedTelemetry: () => jest.fn(), + errorTelemetry: () => vi.fn(), + timedTelemetry: () => vi.fn(), } }) -jest.mock('../../lib/paths', () => { - const path = require('path') - const actualPaths = jest.requireActual('../../lib/paths') - const basedir = '/mock/setup/path' - const app = mockIsTypeScriptProject ? 'App.tsx' : 'App.jsx' - const routes = mockIsTypeScriptProject ? 'Routes.tsx' : 'Routes.jsx' - +vi.mock('../../lib/paths', () => { return { - resolveFile: actualPaths.resolveFile, - getPaths: () => ({ - api: { - functions: '', - src: '', - lib: '', - graphql: path.join(basedir, 'api/src/functions/graphql.ts'), - }, - web: { - src: path.join(basedir, 'web/src'), - app: path.join(basedir, `web/src/${app}`), - routes: path.join(basedir, `web/src/${routes}`), - }, - base: path.join(basedir), - }), + getPaths: vi.fn(), } }) -jest.mock('../../lib/project', () => { +vi.mock('../../lib/project', async () => { + const { getPaths } = await import('../../lib/paths') return { - isTypeScriptProject: () => mockIsTypeScriptProject, + isTypeScriptProject: vi.fn(), getGraphqlPath: () => { - const { getPaths } = require('../../lib/paths') return getPaths().api.graphql }, } }) // This will load packages/cli-helpers/__mocks__/fs.js -jest.mock('fs') +vi.mock('fs') +vi.mock('node:fs', async () => { + const memfs = await import('memfs') + return { + ...memfs.fs, + default: memfs.fs, + } +}) -const mockFS = fs as unknown as Omit, 'readdirSync'> & { - __setMockFiles: (files: Record) => void - __getMockFiles: () => Record - readdirSync: () => string[] +const mockedPathGenerator = (app: string, routes: string) => { + const basedir = '/mock/setup/path' + return { + api: { + functions: '', + src: '', + lib: '', + graphql: path.join(basedir, 'api/src/functions/graphql.ts'), + }, + web: { + src: path.join(basedir, 'web/src'), + app: path.join(basedir, `web/src/${app}`), + routes: path.join(basedir, `web/src/${routes}`), + }, + base: path.join(basedir), + } } import fs from 'fs' import path from 'path' +import { vol } from 'memfs' +import { vi, beforeEach, describe, it, expect, test } from 'vitest' + import { getPaths } from '../../lib/paths' +import { isTypeScriptProject } from '../../lib/project' import type { AuthGeneratorCtx } from '../authTasks' import { addApiConfig, @@ -98,10 +93,14 @@ function platformPath(filePath: string) { } beforeEach(() => { - mockIsTypeScriptProject = true - jest.restoreAllMocks() - - mockFS.__setMockFiles({ + vi.restoreAllMocks() + vi.mocked(isTypeScriptProject).mockReturnValue(true) + vi.mocked(getPaths).mockReturnValue( + // @ts-expect-error - We are not returning a full set of mock paths here + mockedPathGenerator('App.tsx', 'Routes.tsx') + ) + + vol.fromJSON({ [path.join( getPaths().base, platformPath('/templates/web/auth.ts.template') @@ -110,10 +109,6 @@ beforeEach(() => { [getPaths().api.graphql]: graphqlTs, [getPaths().web.routes]: routesTsx, }) - - mockFS.readdirSync = () => { - return ['auth.ts.template'] - } }) describe('authTasks', () => { @@ -123,8 +118,8 @@ describe('authTasks', () => { platformPath('/templates/web/auth.ts.template') ) - mockFS.__setMockFiles({ - ...mockFS.__getMockFiles(), + vol.fromJSON({ + ...vol.toJSON(), [templatePath]: auth0WebAuthTsTemplate, }) @@ -139,9 +134,9 @@ describe('authTasks', () => { const authTsPath = path.join(getPaths().web.src, 'auth.ts') - expect(fs.readFileSync(getPaths().web.app)).toMatchSnapshot() - expect(fs.readFileSync(authTsPath)).toMatchSnapshot() - expect(fs.readFileSync(getPaths().web.routes)).toMatchSnapshot() + expect(fs.readFileSync(getPaths().web.app, 'utf-8')).toMatchSnapshot() + expect(fs.readFileSync(authTsPath, 'utf-8')).toMatchSnapshot() + expect(fs.readFileSync(getPaths().web.routes, 'utf-8')).toMatchSnapshot() }) it('Should update App.{jsx,tsx}, Routes.{jsx,tsx} and add auth.ts (Clerk)', () => { @@ -150,13 +145,18 @@ describe('authTasks', () => { platformPath('/templates/web/auth.tsx.template') ) - mockFS.__setMockFiles({ - ...mockFS.__getMockFiles(), + // NOTE: We reset here because we had to remove the `auth.ts.template` + // file that would be here as a result of the `beforeEach` above. + // The previous implementation of this test was mocking the `fs` module to + // return only `auth.tsx.template` and not the `auth.ts.template` file even + // though it was on the mock filesystem. + vol.reset() + vol.fromJSON({ + [getPaths().web.app]: webAppTsx, + [getPaths().api.graphql]: graphqlTs, + [getPaths().web.routes]: routesTsx, [templatePath]: clerkWebAuthTsTemplate, }) - mockFS.readdirSync = () => { - return ['auth.tsx.template'] - } const ctx: AuthGeneratorCtx = { provider: 'clerk', @@ -169,14 +169,14 @@ describe('authTasks', () => { const authTsPath = path.join(getPaths().web.src, 'auth.tsx') - expect(fs.readFileSync(getPaths().web.app)).toMatchSnapshot() - expect(fs.readFileSync(authTsPath)).toMatchSnapshot() - expect(fs.readFileSync(getPaths().web.routes)).toMatchSnapshot() + expect(fs.readFileSync(getPaths().web.app, 'utf-8')).toMatchSnapshot() + expect(fs.readFileSync(authTsPath, 'utf-8')).toMatchSnapshot() + expect(fs.readFileSync(getPaths().web.routes, 'utf-8')).toMatchSnapshot() }) it('Should update App.tsx for legacy apps', () => { - mockFS.__setMockFiles({ - ...mockFS.__getMockFiles(), + vol.fromJSON({ + ...vol.toJSON(), [getPaths().web.app]: legacyAuthWebAppTsx, }) @@ -187,13 +187,13 @@ describe('authTasks', () => { addConfigToWebApp().task(ctx, {} as any) - expect(fs.readFileSync(getPaths().web.app)).toMatchSnapshot() + expect(fs.readFileSync(getPaths().web.app, 'utf-8')).toMatchSnapshot() }) describe('Components with props', () => { it('Should add useAuth on the same line for single line components, and separate line for multiline components', () => { - mockFS.__setMockFiles({ - ...mockFS.__getMockFiles(), + vol.fromJSON({ + ...vol.toJSON(), [getPaths().web.app]: customApolloAppTsx, [getPaths().web.routes]: customPropsRoutesTsx, }) @@ -206,13 +206,13 @@ describe('authTasks', () => { addConfigToWebApp().task(ctx, {} as any) addConfigToRoutes().task() - expect(fs.readFileSync(getPaths().web.app)).toMatchSnapshot() - expect(fs.readFileSync(getPaths().web.routes)).toMatchSnapshot() + expect(fs.readFileSync(getPaths().web.app, 'utf-8')).toMatchSnapshot() + expect(fs.readFileSync(getPaths().web.routes, 'utf-8')).toMatchSnapshot() }) it('Should not add useAuth if one already exists', () => { - mockFS.__setMockFiles({ - ...mockFS.__getMockFiles(), + vol.fromJSON({ + ...vol.toJSON(), [getPaths().web.app]: customApolloAppTsx, [getPaths().web.routes]: useAuthRoutesTsx, }) @@ -225,15 +225,15 @@ describe('authTasks', () => { addConfigToWebApp().task(ctx, {} as any) addConfigToRoutes().task() - expect(fs.readFileSync(getPaths().web.app)).toMatchSnapshot() - expect(fs.readFileSync(getPaths().web.routes)).toMatchSnapshot() + expect(fs.readFileSync(getPaths().web.app, 'utf-8')).toMatchSnapshot() + expect(fs.readFileSync(getPaths().web.routes, 'utf-8')).toMatchSnapshot() }) }) describe('Customized App.js', () => { it('Should add auth config when using explicit return', () => { - mockFS.__setMockFiles({ - ...mockFS.__getMockFiles(), + vol.fromJSON({ + ...vol.toJSON(), [getPaths().web.app]: explicitReturnAppTsx, }) @@ -244,14 +244,14 @@ describe('authTasks', () => { addConfigToWebApp().task(ctx, {} as any) - expect(fs.readFileSync(getPaths().web.app)).toMatchSnapshot() + expect(fs.readFileSync(getPaths().web.app, 'utf-8')).toMatchSnapshot() }) }) describe('Swapped out GraphQL client', () => { it('Should add auth config when app is missing RedwoodApolloProvider', () => { - mockFS.__setMockFiles({ - ...mockFS.__getMockFiles(), + vol.fromJSON({ + ...vol.toJSON(), [getPaths().web.app]: withoutRedwoodApolloAppTsx, }) @@ -264,7 +264,7 @@ describe('authTasks', () => { addConfigToWebApp().task(ctx, task) expect(task.output).toMatch(/GraphQL.*useAuth/) - expect(fs.readFileSync(getPaths().web.app)).toMatchSnapshot() + expect(fs.readFileSync(getPaths().web.app, 'utf-8')).toMatchSnapshot() }) }) @@ -275,12 +275,12 @@ describe('authTasks', () => { authDecoderImport: "import { authDecoder } from 'test-auth-api'", }) - expect(fs.readFileSync(getPaths().api.graphql)).toMatchSnapshot() + expect(fs.readFileSync(getPaths().api.graphql, 'utf-8')).toMatchSnapshot() }) it("Doesn't add authDecoder arg if one already exists", () => { - mockFS.__setMockFiles({ - ...mockFS.__getMockFiles(), + vol.fromJSON({ + ...vol.toJSON(), [getPaths().api.graphql]: withAuthDecoderGraphqlTs, }) @@ -289,12 +289,12 @@ describe('authTasks', () => { authDecoderImport: "import { authDecoder } from 'test-auth-api'", }) - expect(fs.readFileSync(getPaths().api.graphql)).toMatchSnapshot() + expect(fs.readFileSync(getPaths().api.graphql, 'utf-8')).toMatchSnapshot() }) it("Doesn't add authDecoder arg if one already exists, even with a non-standard import name and arg placement", () => { - mockFS.__setMockFiles({ - ...mockFS.__getMockFiles(), + vol.fromJSON({ + ...vol.toJSON(), [getPaths().api.graphql]: nonStandardAuthDecoderGraphqlTs, }) @@ -303,7 +303,7 @@ describe('authTasks', () => { authDecoderImport: "import { authDecoder } from 'test-auth-api'", }) - expect(fs.readFileSync(getPaths().api.graphql)).toMatchSnapshot() + expect(fs.readFileSync(getPaths().api.graphql, 'utf-8')).toMatchSnapshot() }) }) @@ -632,18 +632,23 @@ describe('authTasks', () => { provider: 'auth0', setupMode: 'FORCE', } + + // NOTE: The current fs related mocking leaves this file around from previous tests so we + // must delete it here. This should be fixed in a future refactoring of the entire test suite + fs.rmSync(path.join(getPaths().base, 'templates/web/auth.tsx.template')) + createWebAuth(getPaths().base, false).task(ctx) expect( - fs.readFileSync(path.join(getPaths().web.src, 'auth.ts')) + fs.readFileSync(path.join(getPaths().web.src, 'auth.ts'), 'utf-8') ).toMatchSnapshot() }) it('writes an auth.js file for JS projects', () => { - mockIsTypeScriptProject = false + vi.mocked(isTypeScriptProject).mockReturnValue(false) - mockFS.__setMockFiles({ - ...mockFS.__getMockFiles(), + vol.fromJSON({ + ...vol.toJSON(), [getPaths().web.app]: webAppTsx, }) @@ -654,7 +659,7 @@ describe('authTasks', () => { createWebAuth(getPaths().base, false).task(ctx) expect( - fs.readFileSync(path.join(getPaths().web.src, 'auth.js')) + fs.readFileSync(path.join(getPaths().web.src, 'auth.js'), 'utf-8') ).toMatchSnapshot() }) }) diff --git a/packages/cli-helpers/src/auth/__tests__/setupHelpers.test.ts b/packages/cli-helpers/src/auth/__tests__/setupHelpers.test.ts index eba30cdb464a..b549baf7a916 100644 --- a/packages/cli-helpers/src/auth/__tests__/setupHelpers.test.ts +++ b/packages/cli-helpers/src/auth/__tests__/setupHelpers.test.ts @@ -1,14 +1,14 @@ globalThis.__dirname = __dirname // mock Telemetry for CLI commands so they don't try to spawn a process -jest.mock('@redwoodjs/telemetry', () => { +vi.mock('@redwoodjs/telemetry', () => { return { - errorTelemetry: () => jest.fn(), - timedTelemetry: () => jest.fn(), + errorTelemetry: () => vi.fn(), + timedTelemetry: () => vi.fn(), } }) -jest.mock('../../lib/paths', () => { +vi.mock('../../lib/paths', () => { const path = require('path') const __dirname = path.resolve() @@ -28,42 +28,45 @@ jest.mock('../../lib/paths', () => { } }) -jest.mock('../../lib/project', () => ({ +vi.mock('../../lib/project', () => ({ isTypeScriptProject: () => true, })) -jest.mock('execa', () => {}) -jest.mock('listr2') -jest.mock('prompts', () => jest.fn(() => ({ answer: true }))) +vi.mock('execa') +vi.mock('listr2') +vi.mock('prompts', () => ({ + default: vi.fn(() => ({ answer: true })), +})) import fs from 'fs' import path from 'path' import { Listr } from 'listr2' import prompts from 'prompts' +import { vi, describe, afterEach, it, expect } from 'vitest' // import * as auth from '../auth' import { standardAuthHandler } from '../setupHelpers' describe('Auth generator tests', () => { - const processExitSpy = jest + const processExitSpy = vi .spyOn(process, 'exit') .mockImplementation((_code: any) => {}) - const mockListrRun = jest.fn() + const mockListrRun = vi.fn() - ;(Listr as jest.MockedFunction).mockImplementation(() => { + ;(Listr as vi.MockedFunction).mockImplementation(() => { return { run: mockListrRun, } }) - const fsSpy = jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {}) + const fsSpy = vi.spyOn(fs, 'writeFileSync').mockImplementation(() => {}) afterEach(() => { processExitSpy.mockReset() fsSpy.mockReset() - ;(prompts as unknown as jest.Mock).mockClear() + ;(prompts as unknown as vi.Mock).mockClear() mockListrRun.mockClear() }) diff --git a/packages/cli-helpers/src/auth/authFiles.ts b/packages/cli-helpers/src/auth/authFiles.ts index 85bd5f812075..c64f48f8141a 100644 --- a/packages/cli-helpers/src/auth/authFiles.ts +++ b/packages/cli-helpers/src/auth/authFiles.ts @@ -3,9 +3,9 @@ import path from 'path' import pascalcase from 'pascalcase' -import { transformTSToJS } from '../lib' -import { getPaths } from '../lib/paths' -import { isTypeScriptProject } from '../lib/project' +import { transformTSToJS } from '../lib/index.js' +import { getPaths } from '../lib/paths.js' +import { isTypeScriptProject } from '../lib/project.js' interface FilesArgs { basedir: string diff --git a/packages/cli-helpers/src/auth/authTasks.ts b/packages/cli-helpers/src/auth/authTasks.ts index 69d19239cb73..c4d515c7e6d2 100644 --- a/packages/cli-helpers/src/auth/authTasks.ts +++ b/packages/cli-helpers/src/auth/authTasks.ts @@ -5,17 +5,17 @@ import type { ListrRenderer, ListrTask, ListrTaskWrapper } from 'listr2' import { resolveFile } from '@redwoodjs/project-config' -import type { ExistingFiles } from '../lib' -import { transformTSToJS, writeFilesTask } from '../lib' -import { colors } from '../lib/colors' -import { getPaths } from '../lib/paths' +import { colors } from '../lib/colors.js' +import type { ExistingFiles } from '../lib/index.js' +import { transformTSToJS, writeFilesTask } from '../lib/index.js' +import { getPaths } from '../lib/paths.js' import { getGraphqlPath, graphFunctionDoesExist, isTypeScriptProject, -} from '../lib/project' +} from '../lib/project.js' -import { apiSideFiles, generateUniqueFileNames } from './authFiles' +import { apiSideFiles, generateUniqueFileNames } from './authFiles.js' const AUTH_PROVIDER_HOOK_IMPORT = `import { AuthProvider, useAuth } from './auth'` const AUTH_HOOK_IMPORT = `import { useAuth } from './auth'` diff --git a/packages/cli-helpers/src/auth/setupHelpers.ts b/packages/cli-helpers/src/auth/setupHelpers.ts index 5c572a7c6427..0daaeae51e3a 100644 --- a/packages/cli-helpers/src/auth/setupHelpers.ts +++ b/packages/cli-helpers/src/auth/setupHelpers.ts @@ -1,18 +1,18 @@ import type { ListrTask } from 'listr2' import { Listr } from 'listr2' import terminalLink from 'terminal-link' -import type yargs from 'yargs' +import type { Argv } from 'yargs' import { errorTelemetry } from '@redwoodjs/telemetry' -import { colors } from '../lib/colors' +import { colors } from '../lib/colors.js' import { addApiPackages, addWebPackages, installPackages, -} from '../lib/installHelpers' +} from '../lib/installHelpers.js' -import type { AuthGeneratorCtx } from './authTasks' +import type { AuthGeneratorCtx } from './authTasks.js' import { addAuthConfigToGqlApi, addConfigToRoutes, @@ -20,9 +20,9 @@ import { setAuthSetupMode, createWebAuth, generateAuthApiFiles, -} from './authTasks' +} from './authTasks.js' -export const standardAuthBuilder = (yargs: yargs.Argv) => { +export const standardAuthBuilder = (yargs: Argv) => { return yargs .option('force', { alias: 'f', diff --git a/packages/cli-helpers/src/index.ts b/packages/cli-helpers/src/index.ts index 04e8c7a96987..35f5d335f459 100644 --- a/packages/cli-helpers/src/index.ts +++ b/packages/cli-helpers/src/index.ts @@ -1,13 +1,13 @@ // @WARN: This export is going to cause memory problems in the CLI. // We need to split this into smaller packages, or use export aliasing (like in packages/testing/cache) -export * from './lib' -export * from './lib/colors' -export * from './lib/paths' -export * from './lib/project' -export * from './lib/version' -export * from './auth/setupHelpers' +export * from './lib/index.js' +export * from './lib/colors.js' +export * from './lib/paths.js' +export * from './lib/project.js' +export * from './lib/version.js' +export * from './auth/setupHelpers.js' -export * from './lib/installHelpers' +export * from './lib/installHelpers.js' -export * from './telemetry/index' +export * from './telemetry/index.js' diff --git a/packages/cli-helpers/src/lib/__tests__/__snapshots__/index.test.ts.snap b/packages/cli-helpers/src/lib/__tests__/__snapshots__/index.test.ts.snap index 1b01f64e6fa0..63ae56f46b8b 100644 --- a/packages/cli-helpers/src/lib/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/cli-helpers/src/lib/__tests__/__snapshots__/index.test.ts.snap @@ -1,4 +1,4 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`prettify formats tsx content 1`] = ` "import React from 'react' diff --git a/packages/cli-helpers/src/lib/__tests__/__snapshots__/project.test.ts.snap b/packages/cli-helpers/src/lib/__tests__/__snapshots__/project.test.ts.snap index 6da535eaf61e..c3d1be489a33 100644 --- a/packages/cli-helpers/src/lib/__tests__/__snapshots__/project.test.ts.snap +++ b/packages/cli-helpers/src/lib/__tests__/__snapshots__/project.test.ts.snap @@ -1,6 +1,6 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`addEnvVar addEnvVar adds environment variables as part of a setup task should add a comment that the existing environment variable value was not changed, but include its new value as a comment 1`] = ` +exports[`addEnvVar > addEnvVar adds environment variables as part of a setup task > should add a comment that the existing environment variable value was not changed, but include its new value as a comment 1`] = ` "EXISTING_VAR=value # CommentedVar=123 @@ -10,7 +10,7 @@ exports[`addEnvVar addEnvVar adds environment variables as part of a setup task " `; -exports[`addEnvVar addEnvVar adds environment variables as part of a setup task should add a new environment variable when it does not exist 1`] = ` +exports[`addEnvVar > addEnvVar adds environment variables as part of a setup task > should add a new environment variable when it does not exist 1`] = ` "EXISTING_VAR = value # CommentedVar = 123 @@ -19,7 +19,7 @@ NEW_VAR = new_value " `; -exports[`addEnvVar addEnvVar adds environment variables as part of a setup task should add a new environment variable when it does not exist when existing envars have no spacing 1`] = ` +exports[`addEnvVar > addEnvVar adds environment variables as part of a setup task > should add a new environment variable when it does not exist when existing envars have no spacing 1`] = ` "EXISTING_VAR=value # CommentedVar = 123 @@ -28,7 +28,7 @@ NEW_VAR = new_value " `; -exports[`addEnvVar addEnvVar adds environment variables as part of a setup task should handle existing environment variables and new value with quoted values by not updating the original value 1`] = ` +exports[`addEnvVar > addEnvVar adds environment variables as part of a setup task > should handle existing environment variables and new value with quoted values by not updating the original value 1`] = ` "EXISTING_VAR = "value" # CommentedVar = 123 @@ -38,19 +38,19 @@ exports[`addEnvVar addEnvVar adds environment variables as part of a setup task " `; -exports[`addEnvVar addEnvVar adds environment variables as part of a setup task should handle existing environment variables with quoted values 1`] = ` +exports[`addEnvVar > addEnvVar adds environment variables as part of a setup task > should handle existing environment variables with quoted values 1`] = ` "EXISTING_VAR = "value" # CommentedVar = 123 " `; -exports[`addEnvVar addEnvVar adds environment variables as part of a setup task should handle existing environment variables with quoted values and no spacing 1`] = ` +exports[`addEnvVar > addEnvVar adds environment variables as part of a setup task > should handle existing environment variables with quoted values and no spacing 1`] = ` "EXISTING_VAR="value" # CommentedVar=123 " `; -exports[`updateTomlConfig updateTomlConfig configures a new CLI plugin adds package but keeps autoInstall false 1`] = ` +exports[`updateTomlConfig > updateTomlConfig configures a new CLI plugin > adds package but keeps autoInstall false 1`] = ` "[web] title = "Redwood App" port = 8_910 @@ -69,7 +69,7 @@ enabled = true " `; -exports[`updateTomlConfig updateTomlConfig configures a new CLI plugin adds when experimental cli has some plugins configured 1`] = ` +exports[`updateTomlConfig > updateTomlConfig configures a new CLI plugin > adds when experimental cli has some plugins configured 1`] = ` "[web] title = "Redwood App" port = 8_910 @@ -91,7 +91,7 @@ enabled = true " `; -exports[`updateTomlConfig updateTomlConfig configures a new CLI plugin adds when experimental cli is not configured 1`] = ` +exports[`updateTomlConfig > updateTomlConfig configures a new CLI plugin > adds when experimental cli is not configured 1`] = ` "[web] title = "Redwood App" port = 8_910 @@ -110,7 +110,7 @@ autoInstall = true " `; -exports[`updateTomlConfig updateTomlConfig configures a new CLI plugin adds when experimental cli is setup but has no plugins configured 1`] = ` +exports[`updateTomlConfig > updateTomlConfig configures a new CLI plugin > adds when experimental cli is setup but has no plugins configured 1`] = ` "[web] title = "Redwood App" port = 8_910 @@ -129,7 +129,7 @@ enabled = true " `; -exports[`updateTomlConfig updateTomlConfig configures a new CLI plugin does not add duplicate place when experimental cli has that plugin configured 1`] = ` +exports[`updateTomlConfig > updateTomlConfig configures a new CLI plugin > does not add duplicate place when experimental cli has that plugin configured 1`] = ` "[web] title = "Redwood App" port = 8_910 diff --git a/packages/cli-helpers/src/lib/__tests__/index.test.ts b/packages/cli-helpers/src/lib/__tests__/index.test.ts index 3fae865c059f..3c9c608c1734 100644 --- a/packages/cli-helpers/src/lib/__tests__/index.test.ts +++ b/packages/cli-helpers/src/lib/__tests__/index.test.ts @@ -1,6 +1,8 @@ +import { vi, test, expect } from 'vitest' + import { prettify } from '../index' -jest.mock('../paths', () => { +vi.mock('../paths', () => { return { getPaths: () => { return { diff --git a/packages/cli-helpers/src/lib/__tests__/project.addTomlSetting.test.ts b/packages/cli-helpers/src/lib/__tests__/project.addTomlSetting.test.ts index a6ed682047e3..c46185cf261f 100644 --- a/packages/cli-helpers/src/lib/__tests__/project.addTomlSetting.test.ts +++ b/packages/cli-helpers/src/lib/__tests__/project.addTomlSetting.test.ts @@ -1,7 +1,14 @@ -jest.mock('fs', () => require('memfs').fs) -jest.mock('node:fs', () => require('memfs').fs) +vi.mock('fs') +vi.mock('node:fs', async () => { + const memfs = await import('memfs') + return { + ...memfs.fs, + default: memfs.fs, + } +}) import { vol } from 'memfs' +import { vi, beforeAll, afterAll, it, expect } from 'vitest' import { setTomlSetting } from '../project' @@ -16,8 +23,8 @@ beforeAll(() => { afterAll(() => { process.env.RWJS_CWD = original_RWJS_CWD - jest.restoreAllMocks() - jest.resetModules() + vi.restoreAllMocks() + vi.resetModules() }) it('should add `fragments = true` to empty redwood.toml', () => { diff --git a/packages/cli-helpers/src/lib/__tests__/project.test.ts b/packages/cli-helpers/src/lib/__tests__/project.test.ts index a8aa58145848..f5e45301e0ef 100644 --- a/packages/cli-helpers/src/lib/__tests__/project.test.ts +++ b/packages/cli-helpers/src/lib/__tests__/project.test.ts @@ -1,9 +1,16 @@ -jest.mock('fs') -jest.mock('node:fs') +vi.mock('fs') +vi.mock('node:fs', async () => { + const memfs = await import('memfs') + return { + ...memfs.fs, + default: memfs.fs, + } +}) import * as fs from 'node:fs' import * as toml from '@iarna/toml' +import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest' import { updateTomlConfig, addEnvVar } from '../project' @@ -23,7 +30,7 @@ const getRedwoodToml = () => { return defaultRedwoodToml } -jest.mock('@redwoodjs/project-config', () => { +vi.mock('@redwoodjs/project-config', () => { return { getPaths: () => { return { @@ -47,22 +54,22 @@ describe('addEnvVar', () => { describe('addEnvVar adds environment variables as part of a setup task', () => { beforeEach(() => { - jest.spyOn(fs, 'existsSync').mockImplementation(() => { + vi.spyOn(fs, 'existsSync').mockImplementation(() => { return true }) - jest.spyOn(fs, 'readFileSync').mockImplementation(() => { + vi.spyOn(fs, 'readFileSync').mockImplementation(() => { return envFileContent }) - jest.spyOn(fs, 'writeFileSync').mockImplementation((envPath, envFile) => { + vi.spyOn(fs, 'writeFileSync').mockImplementation((envPath, envFile) => { expect(envPath).toContain('.env') return envFile }) }) afterEach(() => { - jest.restoreAllMocks() + vi.restoreAllMocks() envFileContent = '' }) @@ -121,24 +128,22 @@ describe('addEnvVar', () => { describe('updateTomlConfig', () => { describe('updateTomlConfig configures a new CLI plugin', () => { beforeEach(() => { - jest.spyOn(fs, 'existsSync').mockImplementation(() => { + vi.spyOn(fs, 'existsSync').mockImplementation(() => { return true }) - jest.spyOn(fs, 'readFileSync').mockImplementation(() => { + vi.spyOn(fs, 'readFileSync').mockImplementation(() => { return toml.stringify(defaultRedwoodToml) }) - jest - .spyOn(fs, 'writeFileSync') - .mockImplementation((tomlPath, tomlFile) => { - expect(tomlPath).toContain('redwood.toml') - return tomlFile - }) + vi.spyOn(fs, 'writeFileSync').mockImplementation((tomlPath, tomlFile) => { + expect(tomlPath).toContain('redwood.toml') + return tomlFile + }) }) afterEach(() => { - jest.restoreAllMocks() + vi.restoreAllMocks() }) it('adds when experimental cli is not configured', () => { diff --git a/packages/cli-helpers/src/lib/__tests__/version.test.ts b/packages/cli-helpers/src/lib/__tests__/version.test.ts index cbcd8bb43e68..057e2463ccf8 100644 --- a/packages/cli-helpers/src/lib/__tests__/version.test.ts +++ b/packages/cli-helpers/src/lib/__tests__/version.test.ts @@ -1,4 +1,4 @@ -jest.mock('@redwoodjs/project-config', () => { +vi.mock('@redwoodjs/project-config', () => { return { getPaths: () => { return { @@ -7,10 +7,12 @@ jest.mock('@redwoodjs/project-config', () => { }, } }) -jest.mock('fs') +vi.mock('fs') import fs from 'fs' +import { vi, describe, test, expect, beforeEach } from 'vitest' + import { getCompatibilityData } from '../version' const EXAMPLE_PACKUMENT = { @@ -187,7 +189,7 @@ const EXAMPLE_PACKUMENT = { describe('version compatibility detection', () => { beforeEach(() => { - jest.spyOn(global, 'fetch').mockImplementation(() => { + vi.spyOn(global, 'fetch').mockImplementation(() => { return { json: () => { return EXAMPLE_PACKUMENT @@ -195,7 +197,7 @@ describe('version compatibility detection', () => { } as any }) - jest.spyOn(fs, 'readFileSync').mockImplementation(() => { + vi.spyOn(fs, 'readFileSync').mockImplementation(() => { return JSON.stringify({ devDependencies: { '@redwoodjs/core': '^6.0.0', @@ -206,15 +208,17 @@ describe('version compatibility detection', () => { test('throws for some fetch related error', async () => { // Mock the fetch function to throw an error - jest.spyOn(global, 'fetch').mockImplementation(() => { + vi.spyOn(global, 'fetch').mockImplementation(() => { throw new Error('Some fetch related error') }) await expect( getCompatibilityData('some-package', 'latest') - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Some fetch related error"`) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Some fetch related error]` + ) // Mock the json parsing to throw an error - jest.spyOn(global, 'fetch').mockImplementation(() => { + vi.spyOn(global, 'fetch').mockImplementation(() => { return { json: () => { throw new Error('Some json parsing error') @@ -224,11 +228,13 @@ describe('version compatibility detection', () => { await expect( getCompatibilityData('some-package', 'latest') - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Some json parsing error"`) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `[Error: Some json parsing error]` + ) }) test('throws for some packument related error', async () => { - jest.spyOn(global, 'fetch').mockImplementation(() => { + vi.spyOn(global, 'fetch').mockImplementation(() => { return { json: () => { return { @@ -241,7 +247,7 @@ describe('version compatibility detection', () => { await expect( getCompatibilityData('some-package', 'latest') ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Some packument related error"` + `[Error: Some packument related error]` ) }) @@ -249,7 +255,7 @@ describe('version compatibility detection', () => { await expect( getCompatibilityData('@scope/package-name', '0.0.4') ).rejects.toThrowErrorMatchingInlineSnapshot( - `"The package '@scope/package-name' does not have a version '0.0.4'"` + `[Error: The package '@scope/package-name' does not have a version '0.0.4']` ) }) @@ -257,12 +263,12 @@ describe('version compatibility detection', () => { await expect( getCompatibilityData('@scope/package-name', 'next') ).rejects.toThrowErrorMatchingInlineSnapshot( - `"The package '@scope/package-name' does not have a tag 'next'"` + `[Error: The package '@scope/package-name' does not have a tag 'next']` ) }) test('throws if no latest version could be found', async () => { - jest.spyOn(global, 'fetch').mockImplementation(() => { + vi.spyOn(global, 'fetch').mockImplementation(() => { return { json: () => { return { @@ -276,7 +282,7 @@ describe('version compatibility detection', () => { await expect( getCompatibilityData('@scope/package-name', 'latest') ).rejects.toThrowErrorMatchingInlineSnapshot( - `"The package '@scope/package-name' does not have a tag 'latest'"` + `[Error: The package '@scope/package-name' does not have a tag 'latest']` ) }) @@ -320,7 +326,7 @@ describe('version compatibility detection', () => { } ) - jest.spyOn(fs, 'readFileSync').mockImplementation(() => { + vi.spyOn(fs, 'readFileSync').mockImplementation(() => { return JSON.stringify({ devDependencies: { '@redwoodjs/core': '5.2.0', @@ -343,7 +349,7 @@ describe('version compatibility detection', () => { }) test('throws if no compatible version could be found', async () => { - jest.spyOn(fs, 'readFileSync').mockImplementation(() => { + vi.spyOn(fs, 'readFileSync').mockImplementation(() => { return JSON.stringify({ devDependencies: { '@redwoodjs/core': '7.0.0', @@ -354,7 +360,7 @@ describe('version compatibility detection', () => { expect( getCompatibilityData('@scope/package-name', 'latest') ).rejects.toThrowErrorMatchingInlineSnapshot( - `"No compatible version of '@scope/package-name' was found"` + `[Error: No compatible version of '@scope/package-name' was found]` ) }) }) diff --git a/packages/cli-helpers/src/lib/index.ts b/packages/cli-helpers/src/lib/index.ts index 1b294a27f9e5..1bc77ed61bea 100644 --- a/packages/cli-helpers/src/lib/index.ts +++ b/packages/cli-helpers/src/lib/index.ts @@ -10,8 +10,8 @@ import type { import { Listr } from 'listr2' import { format } from 'prettier' -import { colors } from './colors' -import { getPaths } from './paths' +import { colors } from './colors.js' +import { getPaths } from './paths.js' // TODO: Move this into `generateTemplate` when all templates have TS support /* diff --git a/packages/cli-helpers/src/lib/installHelpers.ts b/packages/cli-helpers/src/lib/installHelpers.ts index c8016953dee1..f70ae4e4c36f 100644 --- a/packages/cli-helpers/src/lib/installHelpers.ts +++ b/packages/cli-helpers/src/lib/installHelpers.ts @@ -1,6 +1,6 @@ import execa from 'execa' -import { getPaths } from './paths' +import { getPaths } from './paths.js' export const addWebPackages = (webPackages: string[]) => ({ title: 'Adding required web packages...', diff --git a/packages/cli-helpers/src/lib/paths.ts b/packages/cli-helpers/src/lib/paths.ts index 1f026d6ebc5c..7ad0f79cf755 100644 --- a/packages/cli-helpers/src/lib/paths.ts +++ b/packages/cli-helpers/src/lib/paths.ts @@ -1,6 +1,6 @@ import { getPaths as _getPaths } from '@redwoodjs/project-config' -import { colors } from './colors' +import { colors } from './colors.js' function isErrorWithMessage(e: any): e is { message: string } { return !!e.message diff --git a/packages/cli-helpers/src/lib/project.ts b/packages/cli-helpers/src/lib/project.ts index f1cd4d2b7225..8c79ffff5d29 100644 --- a/packages/cli-helpers/src/lib/project.ts +++ b/packages/cli-helpers/src/lib/project.ts @@ -13,8 +13,8 @@ import { resolveFile, } from '@redwoodjs/project-config' -import { colors } from './colors' -import { getPaths } from './paths' +import { colors } from './colors.js' +import { getPaths } from './paths.js' export const getGraphqlPath = () => { return resolveFile(path.join(getPaths().api.functions, 'graphql')) diff --git a/packages/cli-helpers/tsconfig.json b/packages/cli-helpers/tsconfig.json index 5e7f7fd919f2..e0e245aba844 100644 --- a/packages/cli-helpers/tsconfig.json +++ b/packages/cli-helpers/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../../tsconfig.compilerOption.json", "compilerOptions": { - "strict": true, + "moduleResolution": "NodeNext", + "module": "NodeNext", "baseUrl": ".", "rootDir": "src", "outDir": "dist" diff --git a/packages/cli-helpers/vitest.config.mts b/packages/cli-helpers/vitest.config.mts new file mode 100644 index 000000000000..55b4842e1875 --- /dev/null +++ b/packages/cli-helpers/vitest.config.mts @@ -0,0 +1,7 @@ +import { defineConfig, configDefaults } from 'vitest/config' + +export default defineConfig({ + test: { + exclude: [...configDefaults.exclude, '**/fixtures', '**/mockFsFiles'], + }, +}) diff --git a/packages/project-config/tsconfig.json b/packages/project-config/tsconfig.json index 78a31c197ddd..23e3bf19df5b 100644 --- a/packages/project-config/tsconfig.json +++ b/packages/project-config/tsconfig.json @@ -7,5 +7,5 @@ "rootDir": "src", "outDir": "dist", }, - "include": ["src/**/*"], + "include": ["src"], } diff --git a/yarn.lock b/yarn.lock index 6f8001bd4a4e..a9628c17d8eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8097,9 +8097,7 @@ __metadata: version: 0.0.0-use.local resolution: "@redwoodjs/cli-helpers@workspace:packages/cli-helpers" dependencies: - "@babel/cli": "npm:7.23.4" "@babel/core": "npm:^7.22.20" - "@babel/runtime-corejs3": "npm:7.23.6" "@iarna/toml": "npm:2.2.5" "@opentelemetry/api": "npm:1.7.0" "@redwoodjs/project-config": "npm:6.0.7" @@ -8108,10 +8106,8 @@ __metadata: "@types/pascalcase": "npm:1.0.3" "@types/yargs": "npm:17.0.32" chalk: "npm:4.1.2" - core-js: "npm:3.34.0" dotenv: "npm:16.3.1" execa: "npm:5.1.1" - jest: "npm:29.7.0" listr2: "npm:6.6.1" lodash: "npm:4.17.21" pascalcase: "npm:1.0.0" @@ -8119,6 +8115,7 @@ __metadata: prompts: "npm:2.4.2" terminal-link: "npm:2.1.1" typescript: "npm:5.3.3" + vitest: "npm:1.2.1" languageName: unknown linkType: soft