diff --git a/.changeset/quiet-files-travel.md b/.changeset/quiet-files-travel.md new file mode 100644 index 00000000..bb6518a0 --- /dev/null +++ b/.changeset/quiet-files-travel.md @@ -0,0 +1,5 @@ +--- +'@tanstack/create': patch +--- + +Remove `memfs` from the published runtime dependencies by reusing the internal in-memory environment. diff --git a/packages/create/package.json b/packages/create/package.json index 01f4ebb0..980f283a 100644 --- a/packages/create/package.json +++ b/packages/create/package.json @@ -73,7 +73,6 @@ "ejs": "^3.1.10", "execa": "^9.5.2", "ignore": "^7.0.3", - "memfs": "^4.17.0", "parse-gitignore": "^2.0.0", "prettier": "^3.5.0", "rimraf": "^6.0.1", @@ -85,6 +84,7 @@ "@types/parse-gitignore": "^1.0.2", "@vitest/coverage-v8": "4.1.5", "eslint": "^9.20.0", + "memfs": "4.17.0", "typescript": "^6.0.2", "vitest": "^4.1.5", "vitest-fetch-mock": "^0.4.5" diff --git a/packages/create/src/environment.ts b/packages/create/src/environment.ts index 2b955a51..46228942 100644 --- a/packages/create/src/environment.ts +++ b/packages/create/src/environment.ts @@ -8,24 +8,16 @@ import { writeFile, } from 'node:fs/promises' import { existsSync, statSync } from 'node:fs' -import { dirname } from 'node:path' +import { dirname, resolve } from 'node:path' import { execa } from 'execa' -import { memfs } from 'memfs' import { rimraf } from 'rimraf' -import { - cleanUpFileArray, - cleanUpFiles, - getBinaryFile, -} from './file-helpers.js' +import { createMemoryEnvironment as createEdgeMemoryEnvironment } from './edge-environment.js' +import { getBinaryFile } from './file-helpers.js' import type { Environment } from './types.js' -export interface MemoryEnvironmentOutput { - files: Record - deletedFiles: Array - commands: Array<{ command: string; args: Array }> -} +export type { MemoryEnvironmentOutput } from './edge-environment.js' export function createDefaultEnvironment(): Environment { let errors: Array = [] @@ -115,79 +107,43 @@ export function createDefaultEnvironment(): Environment { } export function createMemoryEnvironment(returnPathsRelativeTo: string = '') { - const environment = createDefaultEnvironment() + const { environment, output } = + createEdgeMemoryEnvironment(returnPathsRelativeTo) + const resolvePath = (path: string) => resolve(process.cwd(), path) - const output: MemoryEnvironmentOutput = { - files: {}, - commands: [], - deletedFiles: [], - } + const appendFile = environment.appendFile + environment.appendFile = (path, contents) => + appendFile(resolvePath(path), contents) - const { fs, vol } = memfs({}) + const copyFile = environment.copyFile + environment.copyFile = (from, to) => + copyFile(resolvePath(from), resolvePath(to)) - environment.appendFile = async (path: string, contents: string) => { - fs.mkdirSync(dirname(path), { recursive: true }) - await fs.appendFileSync(path, contents) - } - environment.copyFile = async (from: string, to: string) => { - fs.mkdirSync(dirname(to), { recursive: true }) - fs.copyFileSync(from, to) - return Promise.resolve() - } - environment.execute = async (command: string, args: Array) => { - output.commands.push({ - command, - args, - }) - return Promise.resolve({ stdout: '' }) - } - environment.readFile = async (path: string) => { - return Promise.resolve(fs.readFileSync(path, 'utf-8').toString()) - } - environment.writeFile = async (path: string, contents: string) => { - fs.mkdirSync(dirname(path), { recursive: true }) - await fs.writeFileSync(path, contents) - } - environment.writeFileBase64 = async (path: string, contents: string) => { - // For the in-memory file system, we are not converting the base64 to binary - // because it's not needed. - fs.mkdirSync(dirname(path), { recursive: true }) - await fs.writeFileSync(path, contents) - } - environment.deleteFile = async (path: string) => { - output.deletedFiles.push(path) - if (fs.existsSync(path)) { - await fs.unlinkSync(path) - } - } - environment.finishRun = () => { - output.files = vol.toJSON() as Record - for (const file of Object.keys(output.files)) { - if (fs.statSync(file).isDirectory()) { - delete output.files[file] - } - } - if (returnPathsRelativeTo.length) { - output.files = cleanUpFiles(output.files, returnPathsRelativeTo) - output.deletedFiles = cleanUpFileArray( - output.deletedFiles, - returnPathsRelativeTo, - ) - } - } - environment.exists = (path: string) => { - return fs.existsSync(path) - } - environment.isDirectory = (path: string) => { - return fs.statSync(path).isDirectory() - } - environment.readdir = async (path: string) => { - return Promise.resolve(fs.readdirSync(path).map((d) => d.toString())) - } - environment.rimraf = async () => {} + const writeFile = environment.writeFile + environment.writeFile = (path, contents) => + writeFile(resolvePath(path), contents) - return { - environment, - output, - } + const writeFileBase64 = environment.writeFileBase64 + environment.writeFileBase64 = (path, contents) => + writeFileBase64(resolvePath(path), contents) + + const deleteFile = environment.deleteFile + environment.deleteFile = (path) => deleteFile(resolvePath(path)) + + const readFile = environment.readFile + environment.readFile = (path) => readFile(resolvePath(path)) + + const exists = environment.exists + environment.exists = (path) => exists(resolvePath(path)) + + const isDirectory = environment.isDirectory + environment.isDirectory = (path) => isDirectory(resolvePath(path)) + + const readdir = environment.readdir + environment.readdir = (path) => readdir(resolvePath(path)) + + const rimraf = environment.rimraf + environment.rimraf = (path) => rimraf(resolvePath(path)) + + return { environment, output } } diff --git a/packages/create/src/file-helpers.ts b/packages/create/src/file-helpers.ts index 38149bf9..1a8d7521 100644 --- a/packages/create/src/file-helpers.ts +++ b/packages/create/src/file-helpers.ts @@ -48,7 +48,7 @@ export function toCleanPath(absolutePath: string, baseDir: string): string { if (normalizedPath.startsWith(normalizedBase)) { cleanPath = normalizedPath.slice(normalizedBase.length) } else if (hasDrive(normalizedPath) !== hasDrive(normalizedBase)) { - // Handle paths that are missing the Windows drive letter (e.g. memfs on Windows) + // Handle paths that are missing the Windows drive letter in memory. const pathNoDrive = stripDrive(normalizedPath) const baseNoDrive = stripDrive(normalizedBase) if (pathNoDrive.startsWith(baseNoDrive)) { diff --git a/packages/create/tests/index.test.ts b/packages/create/tests/index.test.ts index 8ba63104..52e8d296 100644 --- a/packages/create/tests/index.test.ts +++ b/packages/create/tests/index.test.ts @@ -1,9 +1,35 @@ -import { describe, expect, it } from 'vitest' +import { readFile } from 'node:fs/promises' +import { afterEach, describe, expect, it, vi } from 'vitest' -import { createApp } from '../src/index.js' +afterEach(() => { + vi.doUnmock('memfs') + vi.resetModules() +}) describe('index', () => { - it('should be a test', () => { + it('exports createApp', async () => { + const { createApp } = await import('../src/index.js') + + expect(createApp).toBeDefined() + }) + + it('does not import the test-only memory filesystem', async () => { + vi.resetModules() + vi.doMock('memfs', () => { + throw new Error('memfs is unavailable') + }) + + const { createApp } = await import('../src/index.js') + expect(createApp).toBeDefined() }) + + it('does not publish the test-only memory filesystem', async () => { + const packageJSON = JSON.parse( + await readFile(new URL('../package.json', import.meta.url), 'utf8'), + ) + + expect(packageJSON.dependencies).not.toHaveProperty('memfs') + expect(packageJSON.devDependencies).toHaveProperty('memfs') + }) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2b61719f..d8b7f054 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -257,9 +257,6 @@ importers: ignore: specifier: ^7.0.3 version: 7.0.5 - memfs: - specifier: ^4.17.0 - version: 4.17.0 parse-gitignore: specifier: ^2.0.0 version: 2.0.0 @@ -288,6 +285,9 @@ importers: eslint: specifier: ^9.20.0 version: 9.25.1(jiti@2.6.1) + memfs: + specifier: 4.17.0 + version: 4.17.0 typescript: specifier: ^6.0.2 version: 6.0.2