From 247efff2d76bb2cda236b3aa3d06a07e9b7bc263 Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 00:14:30 +0200 Subject: [PATCH 01/28] Remove `fs-extra` from `presets/server` --- code/renderers/server/package.json | 2 -- code/renderers/server/src/preset.ts | 7 ++++--- code/yarn.lock | 2 -- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/code/renderers/server/package.json b/code/renderers/server/package.json index 8214690cb7de..67d3343a72b3 100644 --- a/code/renderers/server/package.json +++ b/code/renderers/server/package.json @@ -51,8 +51,6 @@ "@storybook/manager-api": "workspace:^", "@storybook/preview-api": "workspace:^", "@storybook/theming": "workspace:^", - "@types/fs-extra": "^11.0.1", - "fs-extra": "^11.1.0", "ts-dedent": "^2.0.0", "yaml": "^2.3.1" }, diff --git a/code/renderers/server/src/preset.ts b/code/renderers/server/src/preset.ts index 018f50623eee..04937a0b8c14 100644 --- a/code/renderers/server/src/preset.ts +++ b/code/renderers/server/src/preset.ts @@ -1,8 +1,8 @@ +import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import type { ComponentTitle, PresetProperty, StoryName, Tag } from 'storybook/internal/types'; -import fs from 'fs-extra'; import yaml from 'yaml'; type FileContent = { @@ -18,9 +18,10 @@ export const experimental_indexers: PresetProperty<'experimental_indexers'> = ( { test: /(stories|story)\.(json|ya?ml)$/, createIndex: async (fileName) => { + const rawFile = await readFile(fileName, { encoding: 'utf-8' }); const content: FileContent = fileName.endsWith('.json') - ? await fs.readJson(fileName, 'utf-8') - : yaml.parse((await fs.readFile(fileName, 'utf-8')).toString()); + ? JSON.parse(rawFile) + : yaml.parse(rawFile); return content.stories.map((story) => { const tags = Array.from(new Set([...(content.tags ?? []), ...(story.tags ?? [])])); diff --git a/code/yarn.lock b/code/yarn.lock index 52f251440bb3..57d65cf7988c 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6984,8 +6984,6 @@ __metadata: "@storybook/manager-api": "workspace:^" "@storybook/preview-api": "workspace:^" "@storybook/theming": "workspace:^" - "@types/fs-extra": "npm:^11.0.1" - fs-extra: "npm:^11.1.0" ts-dedent: "npm:^2.0.0" typescript: "npm:^5.3.2" yaml: "npm:^2.3.1" From 2ac99b3addc2842434563686e6e4c1f0a6838ca4 Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 00:17:40 +0200 Subject: [PATCH 02/28] Remove `fs-extra` from `presets/react-webpack` --- code/presets/react-webpack/package.json | 1 - code/yarn.lock | 1 - 2 files changed, 2 deletions(-) diff --git a/code/presets/react-webpack/package.json b/code/presets/react-webpack/package.json index d22f61a94932..8f3e04e671b7 100644 --- a/code/presets/react-webpack/package.json +++ b/code/presets/react-webpack/package.json @@ -70,7 +70,6 @@ "@types/node": "^22.0.0", "@types/semver": "^7.3.4", "find-up": "^5.0.0", - "fs-extra": "^11.1.0", "magic-string": "^0.30.5", "react-docgen": "^7.0.0", "resolve": "^1.22.8", diff --git a/code/yarn.lock b/code/yarn.lock index 57d65cf7988c..9a9b1cedaa13 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6583,7 +6583,6 @@ __metadata: "@types/node": "npm:^22.0.0" "@types/semver": "npm:^7.3.4" find-up: "npm:^5.0.0" - fs-extra: "npm:^11.1.0" magic-string: "npm:^0.30.5" react-docgen: "npm:^7.0.0" resolve: "npm:^1.22.8" From ce73ddb77c3bf76f6f4c39d64a79ea2570693e4c Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 12:20:06 +0200 Subject: [PATCH 03/28] Remove `fs-extra` from `lib/create-storybook` --- code/lib/create-storybook/package.json | 1 - .../src/generators/baseGenerator.ts | 6 +++-- .../src/generators/configure.test.ts | 23 +++++++++++-------- .../src/generators/configure.ts | 16 +++++++++---- .../src/scaffold-new-project.ts | 6 +++-- code/yarn.lock | 1 - 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/code/lib/create-storybook/package.json b/code/lib/create-storybook/package.json index 2a08fc5deed2..612b559bcae2 100644 --- a/code/lib/create-storybook/package.json +++ b/code/lib/create-storybook/package.json @@ -61,7 +61,6 @@ "execa": "^5.0.0", "fd-package-json": "^1.2.0", "find-up": "^5.0.0", - "fs-extra": "^11.1.0", "ora": "^5.4.1", "prettier": "^3.1.1", "prompts": "^2.4.0", diff --git a/code/lib/create-storybook/src/generators/baseGenerator.ts b/code/lib/create-storybook/src/generators/baseGenerator.ts index 20318b78a68e..f5d0c6ed828c 100644 --- a/code/lib/create-storybook/src/generators/baseGenerator.ts +++ b/code/lib/create-storybook/src/generators/baseGenerator.ts @@ -1,3 +1,4 @@ +import { mkdir } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import type { NpmOptions } from 'storybook/internal/cli'; @@ -10,7 +11,6 @@ import type { JsPackageManager } from 'storybook/internal/common'; import { getPackageDetails, versions as packageVersions } from 'storybook/internal/common'; import type { SupportedFrameworks } from 'storybook/internal/types'; -import fse from 'fs-extra'; import ora from 'ora'; import invariant from 'tiny-invariant'; import { dedent } from 'ts-dedent'; @@ -328,7 +328,9 @@ export async function baseGenerator( addDependenciesSpinner.succeed(); } - await fse.ensureDir(`./${storybookConfigFolder}`); + // Passing `recursive: true` ensures that the method doesn't throw when + // the directory already exists. + await mkdir(`./${storybookConfigFolder}`, { recursive: true }); if (addMainFile) { const prefixes = shouldApplyRequireWrapperOnPackageNames diff --git a/code/lib/create-storybook/src/generators/configure.test.ts b/code/lib/create-storybook/src/generators/configure.test.ts index b1ce1005d230..db099baf5590 100644 --- a/code/lib/create-storybook/src/generators/configure.test.ts +++ b/code/lib/create-storybook/src/generators/configure.test.ts @@ -1,17 +1,20 @@ +import type { Stats } from 'node:fs'; +import * as fsp from 'node:fs/promises'; + import { beforeAll, describe, expect, it, vi } from 'vitest'; import { SupportedLanguage } from 'storybook/internal/cli'; -import fse from 'fs-extra'; import { dedent } from 'ts-dedent'; import { configureMain, configurePreview } from './configure'; -vi.mock('fs-extra'); +vi.mock('node:fs/promises'); describe('configureMain', () => { beforeAll(() => { vi.clearAllMocks(); + vi.mocked(fsp.stat).mockRejectedValue({}); }); it('should generate main.js', async () => { @@ -25,7 +28,7 @@ describe('configureMain', () => { }, }); - const { calls } = vi.mocked(fse.writeFile).mock; + const { calls } = vi.mocked(fsp.writeFile).mock; const [mainConfigPath, mainConfigContent] = calls[0]; expect(mainConfigPath).toEqual('./.storybook/main.js'); @@ -54,7 +57,7 @@ describe('configureMain', () => { }, }); - const { calls } = vi.mocked(fse.writeFile).mock; + const { calls } = vi.mocked(fsp.writeFile).mock; const [mainConfigPath, mainConfigContent] = calls[0]; expect(mainConfigPath).toEqual('./.storybook/main.ts'); @@ -89,7 +92,7 @@ describe('configureMain', () => { }, }); - const { calls } = vi.mocked(fse.writeFile).mock; + const { calls } = vi.mocked(fsp.writeFile).mock; const [mainConfigPath, mainConfigContent] = calls[0]; expect(mainConfigPath).toEqual('./.storybook/main.js'); @@ -123,7 +126,7 @@ describe('configurePreview', () => { rendererId: 'react', }); - const { calls } = vi.mocked(fse.writeFile).mock; + const { calls } = vi.mocked(fsp.writeFile).mock; const [previewConfigPath, previewConfigContent] = calls[0]; expect(previewConfigPath).toEqual('./.storybook/preview.js'); @@ -152,7 +155,7 @@ describe('configurePreview', () => { rendererId: 'react', }); - const { calls } = vi.mocked(fse.writeFile).mock; + const { calls } = vi.mocked(fsp.writeFile).mock; const [previewConfigPath, previewConfigContent] = calls[0]; expect(previewConfigPath).toEqual('./.storybook/preview.ts'); @@ -176,13 +179,13 @@ describe('configurePreview', () => { }); it('should not do anything if the framework template already included a preview', async () => { - vi.mocked(fse.pathExists).mockImplementationOnce(() => Promise.resolve(true)); + vi.mocked(fsp.stat).mockResolvedValueOnce({} as Stats); await configurePreview({ language: SupportedLanguage.TYPESCRIPT_4_9, storybookConfigFolder: '.storybook', rendererId: 'react', }); - expect(fse.writeFile).not.toHaveBeenCalled(); + expect(fsp.writeFile).not.toHaveBeenCalled(); }); it('should add prefix if frameworkParts are passed', async () => { @@ -199,7 +202,7 @@ describe('configurePreview', () => { }, }); - const { calls } = vi.mocked(fse.writeFile).mock; + const { calls } = vi.mocked(fsp.writeFile).mock; const [previewConfigPath, previewConfigContent] = calls[0]; expect(previewConfigPath).toEqual('./.storybook/preview.ts'); diff --git a/code/lib/create-storybook/src/generators/configure.ts b/code/lib/create-storybook/src/generators/configure.ts index 94304f4c2d06..c7002c58c045 100644 --- a/code/lib/create-storybook/src/generators/configure.ts +++ b/code/lib/create-storybook/src/generators/configure.ts @@ -1,9 +1,9 @@ +import { stat, writeFile } from 'node:fs/promises'; import { resolve } from 'node:path'; import { SupportedLanguage, externalFrameworks } from 'storybook/internal/cli'; import { logger } from 'storybook/internal/node-logger'; -import fse from 'fs-extra'; import { dedent } from 'ts-dedent'; interface ConfigureMainOptions { @@ -35,6 +35,12 @@ interface ConfigurePreviewOptions { rendererId: string; } +const pathExists = async (path: string) => { + return stat(path) + .then(() => true) + .catch(() => false); +}; + /** * We need to clean up the paths in case of pnp input: * `path.dirname(require.resolve(path.join('@storybook/react-webpack5', 'package.json')))` output: @@ -59,7 +65,7 @@ export async function configureMain({ ...custom }: ConfigureMainOptions) { const srcPath = resolve(storybookConfigFolder, '../src'); - const prefix = (await fse.pathExists(srcPath)) ? '../src' : '../stories'; + const prefix = (await pathExists(srcPath)) ? '../src' : '../stories'; const config = { stories: [`${prefix}/**/*.mdx`, `${prefix}/**/*.stories.@(${extensions.join('|')})`], addons, @@ -114,7 +120,7 @@ export async function configureMain({ logger.verbose(`Failed to prettify ${mainPath}`); } - await fse.writeFile(mainPath, mainJsContents, { encoding: 'utf8' }); + await writeFile(mainPath, mainJsContents, { encoding: 'utf8' }); } export async function configurePreview(options: ConfigurePreviewOptions) { @@ -134,7 +140,7 @@ export async function configurePreview(options: ConfigurePreviewOptions) { const previewPath = `./${options.storybookConfigFolder}/preview.${isTypescript ? 'ts' : 'js'}`; // If the framework template included a preview then we have nothing to do - if (await fse.pathExists(previewPath)) { + if (await pathExists(previewPath)) { return; } @@ -177,5 +183,5 @@ export async function configurePreview(options: ConfigurePreviewOptions) { logger.verbose(`Failed to prettify ${previewPath}`); } - await fse.writeFile(previewPath, preview, { encoding: 'utf8' }); + await writeFile(previewPath, preview, { encoding: 'utf8' }); } diff --git a/code/lib/create-storybook/src/scaffold-new-project.ts b/code/lib/create-storybook/src/scaffold-new-project.ts index f80ff2281f23..f2d64fa794bd 100644 --- a/code/lib/create-storybook/src/scaffold-new-project.ts +++ b/code/lib/create-storybook/src/scaffold-new-project.ts @@ -1,3 +1,6 @@ +import { readdirSync } from 'node:fs'; +import { rm } from 'node:fs/promises'; + import type { PackageManagerName } from 'storybook/internal/common'; import { logger } from 'storybook/internal/node-logger'; import { GenerateNewProjectOnInitError } from 'storybook/internal/server-errors'; @@ -6,7 +9,6 @@ import { telemetry } from 'storybook/internal/telemetry'; import boxen from 'boxen'; import chalk from 'chalk'; import execa from 'execa'; -import { readdirSync, remove } from 'fs-extra'; import prompts from 'prompts'; import { dedent } from 'ts-dedent'; @@ -173,7 +175,7 @@ export const scaffoldNewProject = async ( try { // If target directory has a .cache folder, remove it // so that it does not block the creation of the new project - await remove(`${targetDir}/.cache`); + await rm(`${targetDir}/.cache`, { recursive: true, force: true }); // Create new project in temp directory await execa.command(createScript, { diff --git a/code/yarn.lock b/code/yarn.lock index 9a9b1cedaa13..ce362b0e899b 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -12498,7 +12498,6 @@ __metadata: execa: "npm:^5.0.0" fd-package-json: "npm:^1.2.0" find-up: "npm:^5.0.0" - fs-extra: "npm:^11.1.0" ora: "npm:^5.4.1" prettier: "npm:^3.1.1" prompts: "npm:^2.4.0" From 5ed3ab439808616329a22e1959d9f8573d2ee0cf Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 13:31:23 +0200 Subject: [PATCH 04/28] Remove `fs-extra` from `lib/cli-storybook` --- code/__mocks__/fs/promises.ts | 34 +++++++++++++++++++ code/lib/cli-storybook/package.json | 1 - .../automigrate/fixes/eslint-plugin.test.ts | 6 ++-- .../automigrate/fixes/initial-globals.test.ts | 7 ++-- .../src/automigrate/fixes/initial-globals.ts | 3 +- .../src/automigrate/fixes/mdx-1-to-3.ts | 6 ++-- .../fixes/remove-global-client-apis.test.ts | 7 ++-- .../fixes/remove-global-client-apis.ts | 5 +-- .../cli-storybook/src/automigrate/index.ts | 7 ++-- code/lib/cli-storybook/src/doctor/index.ts | 7 ++-- code/lib/cli-storybook/src/link.ts | 12 ++++--- code/lib/cli-storybook/src/sandbox.ts | 3 +- code/yarn.lock | 1 - 13 files changed, 69 insertions(+), 30 deletions(-) create mode 100644 code/__mocks__/fs/promises.ts diff --git a/code/__mocks__/fs/promises.ts b/code/__mocks__/fs/promises.ts new file mode 100644 index 000000000000..44a0bbd1a516 --- /dev/null +++ b/code/__mocks__/fs/promises.ts @@ -0,0 +1,34 @@ +import { vi } from 'vitest'; + +// 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. +let mockFiles = Object.create(null); + +// eslint-disable-next-line no-underscore-dangle, @typescript-eslint/naming-convention +export function __setMockFiles(newMockFiles: Record) { + mockFiles = newMockFiles; +} + +// A custom version of `readdirSync` that reads from the special mocked out +// file list set via __setMockFiles +export const writeFile = vi.fn(async (filePath: string, content: string) => { + mockFiles[filePath] = content; +}); +export const readFile = vi.fn(async (filePath: string) => mockFiles[filePath]); +export const lstat = vi.fn(async (filePath: string) => ({ + isFile: () => !!mockFiles[filePath], +})); +export const readdir = vi.fn(); +export const readlink = vi.fn(); +export const realpath = vi.fn(); + +export default { + __setMockFiles, + writeFile, + readFile, + lstat, + readdir, + readlink, + realpath, +}; diff --git a/code/lib/cli-storybook/package.json b/code/lib/cli-storybook/package.json index 5e13a89d5b30..7623c68b2f4d 100644 --- a/code/lib/cli-storybook/package.json +++ b/code/lib/cli-storybook/package.json @@ -51,7 +51,6 @@ "envinfo": "^7.7.3", "fd-package-json": "^1.2.0", "find-up": "^5.0.0", - "fs-extra": "^11.1.0", "giget": "^1.0.0", "glob": "^10.0.0", "globby": "^14.0.1", diff --git a/code/lib/cli-storybook/src/automigrate/fixes/eslint-plugin.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/eslint-plugin.test.ts index 2120abd7098f..5c8bee41c2f0 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/eslint-plugin.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/eslint-plugin.test.ts @@ -1,17 +1,17 @@ /* eslint-disable no-underscore-dangle */ import * as fs from 'node:fs'; +import * as fsp from 'node:fs/promises'; import { describe, expect, it, vi } from 'vitest'; import type { PackageJson } from 'storybook/internal/common'; -import * as fsExtra from 'fs-extra'; import { dedent } from 'ts-dedent'; import { makePackageManager } from '../helpers/testing-helpers'; import { eslintPlugin } from './eslint-plugin'; -vi.mock('fs-extra', async () => import('../../../../../__mocks__/fs-extra')); +vi.mock('node:fs/promises', async () => import('../../../../../__mocks__/fs/promises')); vi.mock('fs'); const checkEslint = async ({ @@ -23,7 +23,7 @@ const checkEslint = async ({ hasEslint?: boolean; eslintExtension?: string; }) => { - vi.mocked(fsExtra as any).__setMockFiles({ + vi.mocked(fsp as any).__setMockFiles({ [`.eslintrc.${eslintExtension}`]: !hasEslint ? null : dedent(` diff --git a/code/lib/cli-storybook/src/automigrate/fixes/initial-globals.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/initial-globals.test.ts index e74d5ce810da..b78a80fe79b3 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/initial-globals.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/initial-globals.test.ts @@ -1,17 +1,16 @@ /* eslint-disable no-underscore-dangle */ +import * as fsp from 'node:fs/promises'; import { join } from 'node:path'; import { expect, it, vi } from 'vitest'; -import * as fsExtra from 'fs-extra'; - import { initialGlobals } from './initial-globals'; -vi.mock('fs-extra', async () => import('../../../../../__mocks__/fs-extra')); +vi.mock('node:fs/promises', async () => import('../../../../../__mocks__/fs/promises')); const previewConfigPath = join('.storybook', 'preview.js'); const check = async (previewContents: string) => { - vi.mocked(fsExtra as any).__setMockFiles({ + vi.mocked(fsp as any).__setMockFiles({ [previewConfigPath]: previewContents, }); return initialGlobals.check({ diff --git a/code/lib/cli-storybook/src/automigrate/fixes/initial-globals.ts b/code/lib/cli-storybook/src/automigrate/fixes/initial-globals.ts index d4a96033a8c5..fe40749ffa16 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/initial-globals.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/initial-globals.ts @@ -1,9 +1,10 @@ +import { readFile, writeFile } from 'node:fs/promises'; + import type { ConfigFile } from 'storybook/internal/csf-tools'; import { formatConfig, loadConfig } from 'storybook/internal/csf-tools'; import type { Expression } from '@babel/types'; import chalk from 'chalk'; -import { readFile, writeFile } from 'fs-extra'; import { dedent } from 'ts-dedent'; import type { Fix } from '../types'; diff --git a/code/lib/cli-storybook/src/automigrate/fixes/mdx-1-to-3.ts b/code/lib/cli-storybook/src/automigrate/fixes/mdx-1-to-3.ts index 221edd9298e0..d5277a5e8c92 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/mdx-1-to-3.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/mdx-1-to-3.ts @@ -1,7 +1,7 @@ +import { readFile, writeFile } from 'node:fs/promises'; import { basename } from 'node:path'; import chalk from 'chalk'; -import fse from 'fs-extra'; import { dedent } from 'ts-dedent'; import type { Fix } from '../types'; @@ -74,14 +74,14 @@ export const mdx1to3: Fix = { async run({ result: { storiesMdxFiles }, dryRun }) { await Promise.all([ ...storiesMdxFiles.map(async (fname) => { - const contents = await fse.readFile(fname, 'utf-8'); + const contents = await readFile(fname, { encoding: 'utf-8' }); const updated = fixMdxComments(fixMdxStyleTags(contents)); if (updated === contents) { logger.info(`🆗 Unmodified ${basename(fname)}`); } else { logger.info(`✅ Modified ${basename(fname)}`); if (!dryRun) { - await fse.writeFile(fname, updated); + await writeFile(fname, updated); } } }), diff --git a/code/lib/cli-storybook/src/automigrate/fixes/remove-global-client-apis.test.ts b/code/lib/cli-storybook/src/automigrate/fixes/remove-global-client-apis.test.ts index 6ae48ad95018..3e1af96c38f6 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/remove-global-client-apis.test.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/remove-global-client-apis.test.ts @@ -1,19 +1,18 @@ /* eslint-disable no-underscore-dangle */ +import * as fsp from 'node:fs/promises'; import { join } from 'node:path'; import { describe, expect, it, vi } from 'vitest'; import type { JsPackageManager } from 'storybook/internal/common'; -import * as fsExtra from 'fs-extra'; - import { RemovedAPIs, removedGlobalClientAPIs as migration } from './remove-global-client-apis'; -vi.mock('fs-extra', async () => import('../../../../../__mocks__/fs-extra')); +vi.mock('node:fs/promises', async () => import('../../../../../__mocks__/fs/promises')); const check = async ({ contents, previewConfigPath }: any) => { if (contents) { - vi.mocked(fsExtra as any).__setMockFiles({ + vi.mocked(fsp as any).__setMockFiles({ [join('.storybook', 'preview.js')]: contents, }); } diff --git a/code/lib/cli-storybook/src/automigrate/fixes/remove-global-client-apis.ts b/code/lib/cli-storybook/src/automigrate/fixes/remove-global-client-apis.ts index 232ac4fea188..e7caf9027d4b 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/remove-global-client-apis.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/remove-global-client-apis.ts @@ -1,5 +1,6 @@ +import { readFile } from 'node:fs/promises'; + import chalk from 'chalk'; -import { readFile } from 'fs-extra'; import { dedent } from 'ts-dedent'; import type { Fix } from '../types'; @@ -26,7 +27,7 @@ export const removedGlobalClientAPIs: Fix = { async check({ previewConfigPath }) { if (previewConfigPath) { - const contents = await readFile(previewConfigPath, 'utf8'); + const contents = await readFile(previewConfigPath, { encoding: 'utf8' }); const usedAPIs = Object.values(RemovedAPIs).reduce((acc, item) => { if (contents.includes(item)) { diff --git a/code/lib/cli-storybook/src/automigrate/index.ts b/code/lib/cli-storybook/src/automigrate/index.ts index 690c3d1b15f7..ff727783dff3 100644 --- a/code/lib/cli-storybook/src/automigrate/index.ts +++ b/code/lib/cli-storybook/src/automigrate/index.ts @@ -1,3 +1,5 @@ +import { createWriteStream } from 'node:fs'; +import { rename, rm } from 'node:fs/promises'; import { join } from 'node:path'; import { @@ -10,7 +12,6 @@ import { import boxen from 'boxen'; import chalk from 'chalk'; -import { createWriteStream, move, remove } from 'fs-extra'; import prompts from 'prompts'; import semver from 'semver'; import invariant from 'tiny-invariant'; @@ -182,9 +183,9 @@ export const automigrate = async ({ // if migration failed, display a log file in the users cwd if (hasFailures) { - await move(TEMP_LOG_FILE_PATH, join(process.cwd(), LOG_FILE_NAME), { overwrite: true }); + await rename(TEMP_LOG_FILE_PATH, join(process.cwd(), LOG_FILE_NAME)); } else { - await remove(TEMP_LOG_FILE_PATH); + await rm(TEMP_LOG_FILE_PATH, { recursive: true, force: true }); } if (!hideMigrationSummary) { diff --git a/code/lib/cli-storybook/src/doctor/index.ts b/code/lib/cli-storybook/src/doctor/index.ts index 08e798288753..53439e244f00 100644 --- a/code/lib/cli-storybook/src/doctor/index.ts +++ b/code/lib/cli-storybook/src/doctor/index.ts @@ -1,3 +1,5 @@ +import { createWriteStream } from 'node:fs'; +import { rename, rm } from 'node:fs/promises'; import { join } from 'node:path'; import { JsPackageManagerFactory, temporaryFile } from 'storybook/internal/common'; @@ -5,7 +7,6 @@ import type { PackageManagerName } from 'storybook/internal/common'; import boxen from 'boxen'; import chalk from 'chalk'; -import { createWriteStream, move, remove } from 'fs-extra'; import { dedent } from 'ts-dedent'; import { cleanLog } from '../automigrate/helpers/cleanLog'; @@ -161,11 +162,11 @@ export const doctor = async ({ logger.info(`Full logs are available in ${chalk.cyan(LOG_FILE_PATH)}`); - await move(TEMP_LOG_FILE_PATH, join(process.cwd(), LOG_FILE_NAME), { overwrite: true }); + await rename(TEMP_LOG_FILE_PATH, join(process.cwd(), LOG_FILE_NAME)); } else { logger.info(`🥳 Your Storybook project looks good!`); logger.info(commandMessage); - await remove(TEMP_LOG_FILE_PATH); + await rm(TEMP_LOG_FILE_PATH, { recursive: true, force: true }); } logger.info(); diff --git a/code/lib/cli-storybook/src/link.ts b/code/lib/cli-storybook/src/link.ts index 369d668d2c2a..a858847e26b2 100644 --- a/code/lib/cli-storybook/src/link.ts +++ b/code/lib/cli-storybook/src/link.ts @@ -1,10 +1,10 @@ +import { mkdir, readFile } from 'node:fs/promises'; import { basename, extname, join } from 'node:path'; import { logger } from 'storybook/internal/node-logger'; import chalk from 'chalk'; import { spawn as spawnAsync, sync as spawnSync } from 'cross-spawn'; -import fse from 'fs-extra'; type ExecOptions = Parameters[2]; @@ -61,7 +61,7 @@ export const exec = async ( export const link = async ({ target, local, start }: LinkOptions) => { const storybookDir = process.cwd(); try { - const packageJson = await fse.readJSON('package.json'); + const packageJson = JSON.parse(await readFile('package.json', { encoding: 'utf-8' })); if (packageJson.name !== '@storybook/root') { throw new Error(); } @@ -75,7 +75,9 @@ export const link = async ({ target, local, start }: LinkOptions) => { if (!local) { const reprosDir = join(storybookDir, '../storybook-repros'); logger.info(`Ensuring directory ${reprosDir}`); - await fse.ensureDir(reprosDir); + // Passing `recursive: true` ensures that the method doesn't throw when + // the directory already exists. + await mkdir(reprosDir, { recursive: true }); logger.info(`Cloning ${target}`); await exec(`git clone ${target}`, { cwd: reprosDir }); @@ -84,7 +86,9 @@ export const link = async ({ target, local, start }: LinkOptions) => { reproDir = join(reprosDir, reproName); } - const reproPackageJson = await fse.readJSON(join(reproDir, 'package.json')); + const reproPackageJson = JSON.parse( + await readFile(join(reproDir, 'package.json'), { encoding: 'utf-8' }) + ); const version = spawnSync('yarn', ['--version'], { cwd: reproDir, diff --git a/code/lib/cli-storybook/src/sandbox.ts b/code/lib/cli-storybook/src/sandbox.ts index a6b4fe40f043..4bbe097d7d16 100644 --- a/code/lib/cli-storybook/src/sandbox.ts +++ b/code/lib/cli-storybook/src/sandbox.ts @@ -1,3 +1,5 @@ +import { existsSync } from 'node:fs'; +import { readdir } from 'node:fs/promises'; import { isAbsolute, join } from 'node:path'; import type { PackageManagerName } from 'storybook/internal/common'; @@ -7,7 +9,6 @@ import { versions } from 'storybook/internal/common'; import boxen from 'boxen'; import chalk from 'chalk'; import { initiate } from 'create-storybook'; -import { existsSync, readdir } from 'fs-extra'; import { downloadTemplate } from 'giget'; import prompts from 'prompts'; import { lt, prerelease } from 'semver'; diff --git a/code/yarn.lock b/code/yarn.lock index ce362b0e899b..997e639d2e70 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5896,7 +5896,6 @@ __metadata: envinfo: "npm:^7.7.3" fd-package-json: "npm:^1.2.0" find-up: "npm:^5.0.0" - fs-extra: "npm:^11.1.0" giget: "npm:^1.0.0" glob: "npm:^10.0.0" globby: "npm:^14.0.1" From 2e89262e63c10c41d5b24484dc1a0139efc1496f Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 13:32:21 +0200 Subject: [PATCH 05/28] Remove `fs-extra` from `frameworks/nextjs` --- code/frameworks/nextjs/package.json | 1 - code/yarn.lock | 1 - 2 files changed, 2 deletions(-) diff --git a/code/frameworks/nextjs/package.json b/code/frameworks/nextjs/package.json index 856982c811dd..6b28e51f8e7b 100644 --- a/code/frameworks/nextjs/package.json +++ b/code/frameworks/nextjs/package.json @@ -148,7 +148,6 @@ "babel-loader": "^9.1.3", "css-loader": "^6.7.3", "find-up": "^5.0.0", - "fs-extra": "^11.1.0", "image-size": "^1.0.0", "loader-utils": "^3.2.1", "node-polyfill-webpack-plugin": "^2.0.1", diff --git a/code/yarn.lock b/code/yarn.lock index 997e639d2e70..3115e30df1dd 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6433,7 +6433,6 @@ __metadata: babel-loader: "npm:^9.1.3" css-loader: "npm:^6.7.3" find-up: "npm:^5.0.0" - fs-extra: "npm:^11.1.0" image-size: "npm:^1.0.0" loader-utils: "npm:^3.2.1" next: "npm:^14.1.0" From b080e7153ac422cb9667dd6adacee405929e67e8 Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 13:38:45 +0200 Subject: [PATCH 06/28] Remove `fs-extra` from `builders/builder-webpack5` --- code/builders/builder-webpack5/package.json | 1 - code/builders/builder-webpack5/src/index.ts | 6 ++++-- code/yarn.lock | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/code/builders/builder-webpack5/package.json b/code/builders/builder-webpack5/package.json index 913991d99689..2d4c5330bd01 100644 --- a/code/builders/builder-webpack5/package.json +++ b/code/builders/builder-webpack5/package.json @@ -74,7 +74,6 @@ "es-module-lexer": "^1.5.0", "express": "^4.19.2", "fork-ts-checker-webpack-plugin": "^8.0.0", - "fs-extra": "^11.1.0", "html-webpack-plugin": "^5.5.0", "magic-string": "^0.30.5", "path-browserify": "^1.0.1", diff --git a/code/builders/builder-webpack5/src/index.ts b/code/builders/builder-webpack5/src/index.ts index f9a83f8efaf2..7d12dabb4b6f 100644 --- a/code/builders/builder-webpack5/src/index.ts +++ b/code/builders/builder-webpack5/src/index.ts @@ -1,3 +1,4 @@ +import { cp } from 'node:fs/promises'; import { join, parse } from 'node:path'; import { PREVIEW_BUILDER_PROGRESS } from 'storybook/internal/core-events'; @@ -12,7 +13,6 @@ import type { Builder, Options } from 'storybook/internal/types'; import { checkWebpackVersion } from '@storybook/core-webpack'; import express from 'express'; -import fs from 'fs-extra'; import prettyTime from 'pretty-hrtime'; import { corePath } from 'storybook/core-path'; import type { Configuration, Stats, StatsOptions } from 'webpack'; @@ -292,7 +292,9 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, const previewDirOrigin = previewResolvedDir; const previewDirTarget = join(options.outputDir || '', `sb-preview`); - const previewFiles = fs.copy(previewDirOrigin, previewDirTarget, { + // TODO: `fsPromises.cp` is marked as experimental in Node 16-21. Ask in the PR whether we should + // use it anyway or stick to `fs-extra` for now. + const previewFiles = cp(previewDirOrigin, previewDirTarget, { filter: (src) => { const { ext } = parse(src); if (ext) { diff --git a/code/yarn.lock b/code/yarn.lock index 3115e30df1dd..66d1f39375f7 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5843,7 +5843,6 @@ __metadata: es-module-lexer: "npm:^1.5.0" express: "npm:^4.19.2" fork-ts-checker-webpack-plugin: "npm:^8.0.0" - fs-extra: "npm:^11.1.0" html-webpack-plugin: "npm:^5.5.0" magic-string: "npm:^0.30.5" path-browserify: "npm:^1.0.1" From b9c600ad157e6e949b0111336b90036d01dbb5ef Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 13:44:12 +0200 Subject: [PATCH 07/28] Remove `fs-extra` from `builders/builder-vite` --- code/builders/builder-vite/package.json | 1 - code/builders/builder-vite/src/index.ts | 13 +++++++------ .../src/plugins/external-globals-plugin.ts | 8 ++++++-- code/yarn.lock | 1 - 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/code/builders/builder-vite/package.json b/code/builders/builder-vite/package.json index 5c8bd9b47ea1..94fde0bf8cfb 100644 --- a/code/builders/builder-vite/package.json +++ b/code/builders/builder-vite/package.json @@ -49,7 +49,6 @@ "es-module-lexer": "^1.5.0", "express": "^4.19.2", "find-cache-dir": "^3.0.0", - "fs-extra": "^11.1.0", "magic-string": "^0.30.0", "ts-dedent": "^2.0.0" }, diff --git a/code/builders/builder-vite/src/index.ts b/code/builders/builder-vite/src/index.ts index a8a775a13344..fb841b1c473d 100644 --- a/code/builders/builder-vite/src/index.ts +++ b/code/builders/builder-vite/src/index.ts @@ -1,4 +1,5 @@ // noinspection JSUnusedGlobalSymbols +import { cp, readFile } from 'node:fs/promises'; import { join, parse } from 'node:path'; import { NoStatsForViteDevError } from 'storybook/internal/server-errors'; @@ -6,7 +7,6 @@ import type { Options } from 'storybook/internal/types'; import type { RequestHandler } from 'express'; import express from 'express'; -import * as fs from 'fs-extra'; import { corePath } from 'storybook/core-path'; import type { ViteDevServer } from 'vite'; @@ -34,10 +34,9 @@ function iframeMiddleware(options: Options, server: ViteDevServer): RequestHandl return; } - const indexHtml = await fs.readFile( - require.resolve('@storybook/builder-vite/input/iframe.html'), - 'utf-8' - ); + const indexHtml = await readFile(require.resolve('@storybook/builder-vite/input/iframe.html'), { + encoding: 'utf-8', + }); const generated = await transformIframeHtml(indexHtml, options); const transformed = await server.transformIndexHtml('/iframe.html', generated); res.setHeader('Content-Type', 'text/html'); @@ -85,7 +84,9 @@ export const build: ViteBuilder['build'] = async ({ options }) => { const previewDirOrigin = previewResolvedDir; const previewDirTarget = join(options.outputDir || '', `sb-preview`); - const previewFiles = fs.copy(previewDirOrigin, previewDirTarget, { + // TODO: `fsPromises.cp` is marked as experimental in Node 16-21. Ask in the PR whether we should + // use it anyway or stick to `fs-extra` for now. + const previewFiles = cp(previewDirOrigin, previewDirTarget, { filter: (src) => { const { ext } = parse(src); if (ext) { diff --git a/code/builders/builder-vite/src/plugins/external-globals-plugin.ts b/code/builders/builder-vite/src/plugins/external-globals-plugin.ts index a71f9f3b0fe8..f7798ccff136 100644 --- a/code/builders/builder-vite/src/plugins/external-globals-plugin.ts +++ b/code/builders/builder-vite/src/plugins/external-globals-plugin.ts @@ -1,8 +1,10 @@ +import { existsSync, openSync } from 'node:fs'; +import { writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { init, parse } from 'es-module-lexer'; import findCacheDirectory from 'find-cache-dir'; -import { ensureFile, writeFile } from 'fs-extra'; +import { ensureFile } from 'fs-extra'; import MagicString from 'magic-string'; import type { Alias, Plugin } from 'vite'; @@ -59,7 +61,9 @@ export async function externalGlobalsPlugin(externals: Record) { (Object.keys(externals) as Array).map(async (externalKey) => { const externalCachePath = join(cachePath, `${externalKey}.js`); newAlias.push({ find: new RegExp(`^${externalKey}$`), replacement: externalCachePath }); - await ensureFile(externalCachePath); + if (!existsSync(externalCachePath)) { + openSync(externalCachePath, 'w'); + } await writeFile(externalCachePath, `module.exports = ${externals[externalKey]};`); }) ); diff --git a/code/yarn.lock b/code/yarn.lock index 66d1f39375f7..99ad7d5bdb79 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5802,7 +5802,6 @@ __metadata: es-module-lexer: "npm:^1.5.0" express: "npm:^4.19.2" find-cache-dir: "npm:^3.0.0" - fs-extra: "npm:^11.1.0" glob: "npm:^10.0.0" magic-string: "npm:^0.30.0" slash: "npm:^5.0.0" From 67dbe11f02d5c4b8978769e586adb315069572b6 Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 13:45:28 +0200 Subject: [PATCH 08/28] Remove `fs-extra` from `addons/docs` --- code/addons/docs/package.json | 1 - code/yarn.lock | 1 - 2 files changed, 2 deletions(-) diff --git a/code/addons/docs/package.json b/code/addons/docs/package.json index d905b004df47..7e38a4d00383 100644 --- a/code/addons/docs/package.json +++ b/code/addons/docs/package.json @@ -103,7 +103,6 @@ "@storybook/global": "^5.0.0", "@storybook/react-dom-shim": "workspace:*", "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "fs-extra": "^11.1.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", "rehype-external-links": "^3.0.0", diff --git a/code/yarn.lock b/code/yarn.lock index 99ad7d5bdb79..70a5397e0d27 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5437,7 +5437,6 @@ __metadata: "@storybook/global": "npm:^5.0.0" "@storybook/react-dom-shim": "workspace:*" "@types/react": "npm:^16.8.0 || ^17.0.0 || ^18.0.0" - fs-extra: "npm:^11.1.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" rehype-external-links: "npm:^3.0.0" From cf80eaa1889424ccf0db50a156ef69ac02c4bdde Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 13:46:23 +0200 Subject: [PATCH 09/28] Remove rogue import in `builders/builder-vite` --- .../builders/builder-vite/src/plugins/external-globals-plugin.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/code/builders/builder-vite/src/plugins/external-globals-plugin.ts b/code/builders/builder-vite/src/plugins/external-globals-plugin.ts index f7798ccff136..7813e1c50030 100644 --- a/code/builders/builder-vite/src/plugins/external-globals-plugin.ts +++ b/code/builders/builder-vite/src/plugins/external-globals-plugin.ts @@ -4,7 +4,6 @@ import { join } from 'node:path'; import { init, parse } from 'es-module-lexer'; import findCacheDirectory from 'find-cache-dir'; -import { ensureFile } from 'fs-extra'; import MagicString from 'magic-string'; import type { Alias, Plugin } from 'vite'; From 353dfb10a28b64902d0c163e5863d913504fd35b Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 14:05:40 +0200 Subject: [PATCH 10/28] Remove rogue comment --- code/__mocks__/fs/promises.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/code/__mocks__/fs/promises.ts b/code/__mocks__/fs/promises.ts index 44a0bbd1a516..d6673a26e648 100644 --- a/code/__mocks__/fs/promises.ts +++ b/code/__mocks__/fs/promises.ts @@ -10,8 +10,6 @@ export function __setMockFiles(newMockFiles: Record) { mockFiles = newMockFiles; } -// A custom version of `readdirSync` that reads from the special mocked out -// file list set via __setMockFiles export const writeFile = vi.fn(async (filePath: string, content: string) => { mockFiles[filePath] = content; }); From 08c6eef3ffeea4081e67acbec140919b3bbcdd9c Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 18:18:49 +0200 Subject: [PATCH 11/28] Remove `fs-extra` from `presets/server-webpack` --- code/presets/server-webpack/package.json | 1 - .../src/lib/compiler/json-to-csf-compiler.test.ts | 7 ++++--- code/yarn.lock | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/code/presets/server-webpack/package.json b/code/presets/server-webpack/package.json index 48fc4ac5927c..0dcf0288a02e 100644 --- a/code/presets/server-webpack/package.json +++ b/code/presets/server-webpack/package.json @@ -63,7 +63,6 @@ "yaml-loader": "^0.8.0" }, "devDependencies": { - "fs-extra": "^11.1.0", "typescript": "^5.3.2", "yaml": "^2.3.1" }, diff --git a/code/presets/server-webpack/src/lib/compiler/json-to-csf-compiler.test.ts b/code/presets/server-webpack/src/lib/compiler/json-to-csf-compiler.test.ts index 0c44665bdc25..f0d8237215cb 100644 --- a/code/presets/server-webpack/src/lib/compiler/json-to-csf-compiler.test.ts +++ b/code/presets/server-webpack/src/lib/compiler/json-to-csf-compiler.test.ts @@ -1,14 +1,15 @@ +import { readdirSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; -import fs from 'fs-extra'; import YAML from 'yaml'; import { compileCsfModule } from '.'; async function generate(filePath: string) { - const content = await fs.readFile(filePath, 'utf8'); + const content = await readFile(filePath, { encoding: 'utf8' }); const parsed = filePath.endsWith('.json') ? JSON.parse(content) : YAML.parse(content); return compileCsfModule(parsed); } @@ -18,7 +19,7 @@ async function generate(filePath: string) { describe(`${fileType}-to-csf-compiler`, () => { const transformFixturesDir = join(__dirname, '__testfixtures__'); - fs.readdirSync(transformFixturesDir) + readdirSync(transformFixturesDir) .filter((fileName: string) => inputRegExp.test(fileName)) .forEach((fixtureFile: string) => { it(`${fixtureFile}`, async () => { diff --git a/code/yarn.lock b/code/yarn.lock index f8bffd7aee76..2b753fe187ab 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6604,7 +6604,6 @@ __metadata: "@storybook/global": "npm:^5.0.0" "@storybook/server": "workspace:*" "@types/node": "npm:^22.0.0" - fs-extra: "npm:^11.1.0" safe-identifier: "npm:^0.4.1" ts-dedent: "npm:^2.0.0" typescript: "npm:^5.3.2" From a09abbc1c8d25d12f36cd4333ba6c5adf57a471a Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 18:32:26 +0200 Subject: [PATCH 12/28] Remove `fs-extra` from `renderers/svelte` VS Code complains about not being able to find Node types, but building the package works. Need to investigate. --- code/renderers/svelte/package.json | 1 - code/renderers/svelte/scripts/copy-unbundled-to-dist.ts | 5 +++-- code/yarn.lock | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/code/renderers/svelte/package.json b/code/renderers/svelte/package.json index aa804d3d0ce6..5972c0ef2ac8 100644 --- a/code/renderers/svelte/package.json +++ b/code/renderers/svelte/package.json @@ -68,7 +68,6 @@ "@sveltejs/vite-plugin-svelte": "^3.0.2", "@testing-library/svelte": "patch:@testing-library/svelte@npm%3A4.1.0#~/.yarn/patches/@testing-library-svelte-npm-4.1.0-34b7037bc0.patch", "expect-type": "^0.15.0", - "fs-extra": "^11.1.0", "svelte": "^5.0.0-next.65", "svelte-check": "^3.6.4", "typescript": "^5.3.2" diff --git a/code/renderers/svelte/scripts/copy-unbundled-to-dist.ts b/code/renderers/svelte/scripts/copy-unbundled-to-dist.ts index a82964679960..0444a5960183 100644 --- a/code/renderers/svelte/scripts/copy-unbundled-to-dist.ts +++ b/code/renderers/svelte/scripts/copy-unbundled-to-dist.ts @@ -1,4 +1,5 @@ -import { copy } from 'fs-extra'; +import { cp } from 'node:fs/promises'; + import { join } from 'path'; const src = join(__dirname, '..', 'src'); @@ -11,7 +12,7 @@ const run = async () => { console.log('Copying unbundled files to dist...'); await Promise.all( PATHS_TO_COPY.map((pathToCopy) => - copy(join(src, pathToCopy), join(dist, pathToCopy), { overwrite: true }) + cp(join(src, pathToCopy), join(dist, pathToCopy), { recursive: true, force: true }) ) ); console.log('Done!'); diff --git a/code/yarn.lock b/code/yarn.lock index 2b753fe187ab..a3672777a19b 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -7052,7 +7052,6 @@ __metadata: "@sveltejs/vite-plugin-svelte": "npm:^3.0.2" "@testing-library/svelte": "patch:@testing-library/svelte@npm%3A4.1.0#~/.yarn/patches/@testing-library-svelte-npm-4.1.0-34b7037bc0.patch" expect-type: "npm:^0.15.0" - fs-extra: "npm:^11.1.0" svelte: "npm:^5.0.0-next.65" svelte-check: "npm:^3.6.4" sveltedoc-parser: "npm:^4.2.1" From 93b866b4a1ee7c345421568f7ab13eb4b07213f2 Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 18:37:15 +0200 Subject: [PATCH 13/28] Remove `fs-extra` from `addons/links` VS Code complains about not being able to find Node types, but building the package works. Need to investigate. --- code/addons/links/package.json | 1 - code/addons/links/scripts/fix-preview-api-reference.ts | 2 +- code/yarn.lock | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/code/addons/links/package.json b/code/addons/links/package.json index c11fa3e056b3..ca95baea88ac 100644 --- a/code/addons/links/package.json +++ b/code/addons/links/package.json @@ -71,7 +71,6 @@ "ts-dedent": "^2.0.0" }, "devDependencies": { - "fs-extra": "^11.1.0", "typescript": "^5.3.2" }, "peerDependencies": { diff --git a/code/addons/links/scripts/fix-preview-api-reference.ts b/code/addons/links/scripts/fix-preview-api-reference.ts index 71ca667999e2..90d54704768d 100644 --- a/code/addons/links/scripts/fix-preview-api-reference.ts +++ b/code/addons/links/scripts/fix-preview-api-reference.ts @@ -1,4 +1,4 @@ -import { readFile, writeFile } from 'fs-extra'; +import { readFile, writeFile } from 'node:fs/promises'; /* I wish this wasn't needed.. * There seems to be some bug in tsup / the unlaying lib that does DTS bundling diff --git a/code/yarn.lock b/code/yarn.lock index a3672777a19b..e477552d26d5 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -5527,7 +5527,6 @@ __metadata: dependencies: "@storybook/csf": "npm:^0.1.11" "@storybook/global": "npm:^5.0.0" - fs-extra: "npm:^11.1.0" ts-dedent: "npm:^2.0.0" typescript: "npm:^5.3.2" peerDependencies: From 7c14a13412fd5bed24074145b7ef79d372319b43 Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 18:59:18 +0200 Subject: [PATCH 14/28] core: Remove `fs-extra` from `src/telemetry` --- code/__mocks__/fs.js | 29 ----------------- code/__mocks__/fs.ts | 32 +++++++++++++++++++ .../src/telemetry/get-monorepo-type.test.ts | 7 ++-- code/core/src/telemetry/get-monorepo-type.ts | 7 ++-- code/core/src/telemetry/package-json.ts | 5 ++- 5 files changed, 41 insertions(+), 39 deletions(-) delete mode 100644 code/__mocks__/fs.js create mode 100644 code/__mocks__/fs.ts diff --git a/code/__mocks__/fs.js b/code/__mocks__/fs.js deleted file mode 100644 index 9e608c3ff038..000000000000 --- a/code/__mocks__/fs.js +++ /dev/null @@ -1,29 +0,0 @@ -import { vi } from 'vitest'; - -const fs = vi.createMockFromModule('fs'); - -// 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. -let mockFiles = Object.create(null); - -// eslint-disable-next-line no-underscore-dangle, @typescript-eslint/naming-convention -function __setMockFiles(newMockFiles) { - mockFiles = newMockFiles; -} - -// A custom version of `readdirSync` that reads from the special mocked out -// file list set via __setMockFiles -const readFileSync = (filePath = '') => mockFiles[filePath]; -const existsSync = (filePath) => !!mockFiles[filePath]; -const lstatSync = (filePath) => ({ - isFile: () => !!mockFiles[filePath], -}); - -// eslint-disable-next-line no-underscore-dangle -fs.__setMockFiles = __setMockFiles; -fs.readFileSync = readFileSync; -fs.existsSync = existsSync; -fs.lstatSync = lstatSync; - -module.exports = fs; diff --git a/code/__mocks__/fs.ts b/code/__mocks__/fs.ts new file mode 100644 index 000000000000..50e34e8e4ca1 --- /dev/null +++ b/code/__mocks__/fs.ts @@ -0,0 +1,32 @@ +import { vi } from 'vitest'; + +// 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. +let mockFiles = Object.create(null); + +// eslint-disable-next-line no-underscore-dangle, @typescript-eslint/naming-convention +export function __setMockFiles(newMockFiles: Record) { + mockFiles = newMockFiles; +} + +export const readFileSync = (filePath = '') => mockFiles[filePath]; +export const existsSync = (filePath: string) => !!mockFiles[filePath]; +export const lstatSync = (filePath: string) => ({ + isFile: () => !!mockFiles[filePath], +}); +export const realpathSync = vi.fn(); +export const readdir = vi.fn(); +export const readdirSync = vi.fn(); +export const readlinkSync = vi.fn(); + +export default { + __setMockFiles, + readFileSync, + existsSync, + lstatSync, + realpathSync, + readdir, + readdirSync, + readlinkSync, +}; diff --git a/code/core/src/telemetry/get-monorepo-type.test.ts b/code/core/src/telemetry/get-monorepo-type.test.ts index 530da9f4abc9..3f8ecfca723a 100644 --- a/code/core/src/telemetry/get-monorepo-type.test.ts +++ b/code/core/src/telemetry/get-monorepo-type.test.ts @@ -1,13 +1,12 @@ /* eslint-disable no-underscore-dangle */ +import * as fs from 'node:fs'; import { join } from 'node:path'; import { describe, expect, it, vi } from 'vitest'; -import * as fsExtra from 'fs-extra'; - import { getMonorepoType, monorepoConfigs } from './get-monorepo-type'; -vi.mock('fs-extra', async () => import('../../../__mocks__/fs-extra')); +vi.mock('node:fs', async () => import('../../../__mocks__/fs')); vi.mock('@storybook/core/common', async (importOriginal) => { return { @@ -25,7 +24,7 @@ const checkMonorepoType = ({ monorepoConfigFile, isYarnWorkspace = false }: any) mockFiles[join('root', monorepoConfigFile)] = '{}'; } - vi.mocked(fsExtra as any).__setMockFiles(mockFiles); + vi.mocked(fs as any).__setMockFiles(mockFiles); return getMonorepoType(); }; diff --git a/code/core/src/telemetry/get-monorepo-type.ts b/code/core/src/telemetry/get-monorepo-type.ts index 41dcc0087044..e004365fcbb0 100644 --- a/code/core/src/telemetry/get-monorepo-type.ts +++ b/code/core/src/telemetry/get-monorepo-type.ts @@ -1,10 +1,9 @@ +import { existsSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; import { getProjectRoot } from '@storybook/core/common'; import type { PackageJson } from '@storybook/core/types'; -import { existsSync, readJsonSync } from 'fs-extra'; - export const monorepoConfigs = { Nx: 'nx.json', Turborepo: 'turbo.json', @@ -36,7 +35,9 @@ export const getMonorepoType = (): MonorepoType => { return undefined; } - const packageJson = readJsonSync(join(projectRootPath, 'package.json')) as PackageJson; + const packageJson = JSON.parse( + readFileSync(join(projectRootPath, 'package.json'), { encoding: 'utf-8' }) + ) as PackageJson; if (packageJson?.workspaces) { return 'Workspaces'; diff --git a/code/core/src/telemetry/package-json.ts b/code/core/src/telemetry/package-json.ts index b88ebc237ddc..3bf006115c4a 100644 --- a/code/core/src/telemetry/package-json.ts +++ b/code/core/src/telemetry/package-json.ts @@ -1,7 +1,6 @@ +import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { readJson } from 'fs-extra'; - import type { Dependency } from './types'; export const getActualPackageVersions = async (packages: Record>) => { @@ -25,6 +24,6 @@ export const getActualPackageJson = async (packageName: string) => { const resolvedPackageJson = require.resolve(join(packageName, 'package.json'), { paths: [process.cwd()], }); - const packageJson = await readJson(resolvedPackageJson); + const packageJson = JSON.parse(await readFile(resolvedPackageJson, { encoding: 'utf-8' })); return packageJson; }; From 7816541a57b21879837664261bc0deb3e0d33a44 Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 19:12:08 +0200 Subject: [PATCH 15/28] core: Remove `fs-extra` from `src/common/utils` --- .../src/common/js-package-manager/PNPMProxy.ts | 3 +-- code/core/src/common/utils/cli.ts | 16 +++++++--------- code/core/src/common/utils/get-storybook-info.ts | 7 ++----- code/core/src/common/utils/get-storybook-refs.ts | 8 +++++--- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/code/core/src/common/js-package-manager/PNPMProxy.ts b/code/core/src/common/js-package-manager/PNPMProxy.ts index f8df230d0fe6..3f4073c6f35e 100644 --- a/code/core/src/common/js-package-manager/PNPMProxy.ts +++ b/code/core/src/common/js-package-manager/PNPMProxy.ts @@ -4,7 +4,6 @@ import { join } from 'node:path'; import { FindPackageVersionsError } from '@storybook/core/server-errors'; import { findUpSync } from 'find-up'; -import { pathExistsSync } from 'fs-extra'; import { dedent } from 'ts-dedent'; import { createLogStream } from '../utils/cli'; @@ -42,7 +41,7 @@ export class PNPMProxy extends JsPackageManager { const CWD = process.cwd(); const pnpmWorkspaceYaml = `${CWD}/pnpm-workspace.yaml`; - return pathExistsSync(pnpmWorkspaceYaml); + return existsSync(pnpmWorkspaceYaml); } async initPackageJson() { diff --git a/code/core/src/common/utils/cli.ts b/code/core/src/common/utils/cli.ts index 00b831133d2a..05f4704360ea 100644 --- a/code/core/src/common/utils/cli.ts +++ b/code/core/src/common/utils/cli.ts @@ -1,9 +1,9 @@ -import { realpath } from 'node:fs/promises'; +import type { WriteStream } from 'node:fs'; +import { createWriteStream, mkdirSync } from 'node:fs'; +import { readFile, realpath, rename, rm, writeFile } from 'node:fs/promises'; import os from 'node:os'; import { join } from 'node:path'; -import type { WriteStream } from 'fs-extra'; -import { createWriteStream, mkdirSync, move, readFile, remove, writeFile } from 'fs-extra'; import { type MergeExclusive } from 'type-fest'; import uniqueString from 'unique-string'; @@ -17,7 +17,7 @@ const getPath = async (prefix = '') => join(await tempDir(), prefix + uniqueStri export async function temporaryDirectory({ prefix = '' } = {}) { const directory = await getPath(prefix); - await mkdirSync(directory); + mkdirSync(directory); return directory; } @@ -146,12 +146,10 @@ export const createLogStream = async ( return new Promise((resolve, reject) => { logStream.once('open', () => { - const moveLogFile = async () => move(temporaryLogPath, finalLogPath, { overwrite: true }); + const moveLogFile = async () => rename(temporaryLogPath, finalLogPath); const clearLogFile = async () => writeFile(temporaryLogPath, ''); - const removeLogFile = async () => remove(temporaryLogPath); - const readLogFile = async () => { - return readFile(temporaryLogPath, 'utf8'); - }; + const removeLogFile = async () => rm(temporaryLogPath, { recursive: true, force: true }); + const readLogFile = async () => readFile(temporaryLogPath, { encoding: 'utf8' }); resolve({ logStream, moveLogFile, clearLogFile, removeLogFile, readLogFile }); }); logStream.once('error', reject); diff --git a/code/core/src/common/utils/get-storybook-info.ts b/code/core/src/common/utils/get-storybook-info.ts index af65546949eb..a7e887a87d63 100644 --- a/code/core/src/common/utils/get-storybook-info.ts +++ b/code/core/src/common/utils/get-storybook-info.ts @@ -1,10 +1,9 @@ +import { existsSync } from 'node:fs'; import { join } from 'node:path'; import type { SupportedFrameworks } from '@storybook/core/types'; import type { CoreCommon_StorybookInfo, PackageJson } from '@storybook/core/types'; -import { pathExistsSync } from 'fs-extra'; - import { getStorybookConfiguration } from './get-storybook-configuration'; export const rendererPackages: Record = { @@ -92,9 +91,7 @@ const validConfigExtensions = ['ts', 'js', 'tsx', 'jsx', 'mjs', 'cjs']; export const findConfigFile = (prefix: string, configDir: string) => { const filePrefix = join(configDir, prefix); - const extension = validConfigExtensions.find((ext: string) => - pathExistsSync(`${filePrefix}.${ext}`) - ); + const extension = validConfigExtensions.find((ext: string) => existsSync(`${filePrefix}.${ext}`)); return extension ? `${filePrefix}.${extension}` : null; }; diff --git a/code/core/src/common/utils/get-storybook-refs.ts b/code/core/src/common/utils/get-storybook-refs.ts index 053ae17abc93..c733febf6aea 100644 --- a/code/core/src/common/utils/get-storybook-refs.ts +++ b/code/core/src/common/utils/get-storybook-refs.ts @@ -1,3 +1,4 @@ +import { readFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import type { Options, Ref } from '@storybook/core/types'; @@ -5,7 +6,6 @@ import type { Options, Ref } from '@storybook/core/types'; import { logger } from '@storybook/core/node-logger'; import { findUp } from 'find-up'; -import { readJSON } from 'fs-extra'; import resolveFrom from 'resolve-from'; export const getAutoRefs = async (options: Options): Promise> => { @@ -15,7 +15,8 @@ export const getAutoRefs = async (options: Options): Promise } const directory = dirname(location); - const { dependencies = [], devDependencies = [] } = (await readJSON(location)) || {}; + const { dependencies = [], devDependencies = [] } = + JSON.parse(await readFile(location, { encoding: 'utf-8' })) || {}; const deps = Object.keys({ ...dependencies, ...devDependencies }); const list = await Promise.all( @@ -23,7 +24,8 @@ export const getAutoRefs = async (options: Options): Promise try { const l = resolveFrom(directory, join(d, 'package.json')); - const { storybook, name, version } = (await readJSON(l)) || {}; + const { storybook, name, version } = + JSON.parse(await readFile(l, { encoding: 'utf-8' })) || {}; if (storybook?.url) { return { id: name, ...storybook, version }; From fb2455bfbab954d570e165c50f6a79eb7e16dd37 Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 19:31:03 +0200 Subject: [PATCH 16/28] core: Remove `fs-extra` from `src/core-server/utils` --- .../src/core-server/presets/common-preset.ts | 11 +++---- .../src/core-server/presets/favicon.test.ts | 30 ++++++++----------- .../core-server/utils/StoryIndexGenerator.ts | 11 +++---- .../utils/__tests__/server-statics.test.ts | 13 ++++---- .../utils/copy-all-static-files.ts | 10 +++++-- code/core/src/core-server/utils/metadata.ts | 5 ++-- .../src/core-server/utils/output-stats.ts | 4 +-- .../core/src/core-server/utils/server-init.ts | 9 +++--- .../src/core-server/utils/server-statics.ts | 4 +-- .../src/core-server/utils/stories-json.ts | 4 +-- .../utils/warnWhenUsingArgTypesRegex.ts | 7 +++-- code/core/src/core-server/utils/whats-new.ts | 5 ++-- 12 files changed, 58 insertions(+), 55 deletions(-) diff --git a/code/core/src/core-server/presets/common-preset.ts b/code/core/src/core-server/presets/common-preset.ts index bca9a1c9cfe6..9a32187c11f6 100644 --- a/code/core/src/core-server/presets/common-preset.ts +++ b/code/core/src/core-server/presets/common-preset.ts @@ -1,3 +1,5 @@ +import { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; import { dirname, isAbsolute, join } from 'node:path'; import type { Channel } from '@storybook/core/channels'; @@ -21,7 +23,6 @@ import type { import { readCsf } from '@storybook/core/csf-tools'; import { logger } from '@storybook/core/node-logger'; -import { pathExists, readFile } from 'fs-extra'; import { dedent } from 'ts-dedent'; import { initCreateNewStoryChannel } from '../server-channel/create-new-story-channel'; @@ -75,14 +76,14 @@ export const favicon = async ( if (targetEndpoint === '/') { const url = 'favicon.svg'; const path = join(staticPath, url); - if (await pathExists(path)) { + if (existsSync(path)) { results.push(path); } } if (targetEndpoint === '/') { const url = 'favicon.ico'; const path = join(staticPath, url); - if (await pathExists(path)) { + if (existsSync(path)) { results.push(path); } } @@ -256,8 +257,8 @@ export const docs: PresetProperty<'docs'> = (docsOptions, { docs: docsMode }: CL export const managerHead = async (_: any, options: Options) => { const location = join(options.configDir, 'manager-head.html'); - if (await pathExists(location)) { - const contents = readFile(location, 'utf-8'); + if (existsSync(location)) { + const contents = readFile(location, { encoding: 'utf-8' }); const interpolations = options.presets.apply>('env'); return interpolate(await contents, await interpolations); diff --git a/code/core/src/core-server/presets/favicon.test.ts b/code/core/src/core-server/presets/favicon.test.ts index 7af2bfbfb34d..4e6c72cc0618 100644 --- a/code/core/src/core-server/presets/favicon.test.ts +++ b/code/core/src/core-server/presets/favicon.test.ts @@ -1,11 +1,10 @@ +import * as fs from 'node:fs'; import { dirname, join } from 'node:path'; import { expect, it, vi } from 'vitest'; import { logger } from '@storybook/core/node-logger'; -import * as fs from 'fs-extra'; - import * as m from './common-preset'; const defaultFavicon = join( @@ -30,17 +29,6 @@ const createOptions = (locations: string[]): Parameters[1] => }, }); -vi.mock('fs-extra', () => { - return { - pathExists: vi.fn((p: string) => { - return false; - }), - existsSync: vi.fn((p: string) => { - return false; - }), - }; -}); - vi.mock('@storybook/core/node-logger', () => { return { logger: { @@ -49,7 +37,13 @@ vi.mock('@storybook/core/node-logger', () => { }; }); -const pathExists = vi.mocked(fs.pathExists); +vi.mock('node:fs', async (importOriginal) => ({ + ...(await importOriginal()), + existsSync: vi.fn((p: string) => { + return false; + }), +})); +const existsSyncMock = vi.mocked(fs.existsSync); it('with no staticDirs favicon should return default', async () => { const options = createOptions([]); @@ -59,7 +53,7 @@ it('with no staticDirs favicon should return default', async () => { it('with staticDirs containing a single favicon.ico should return the found favicon', async () => { const location = 'static'; - pathExists.mockImplementation((p: string) => { + existsSyncMock.mockImplementation((p: string | Buffer | URL) => { if (p === createPath(location)) { return true; } @@ -75,7 +69,7 @@ it('with staticDirs containing a single favicon.ico should return the found favi it('with staticDirs containing a single favicon.svg should return the found favicon', async () => { const location = 'static'; - pathExists.mockImplementation((p: string) => { + existsSyncMock.mockImplementation((p: string | Buffer | URL) => { if (p === createPath(location)) { return true; } @@ -91,7 +85,7 @@ it('with staticDirs containing a single favicon.svg should return the found favi it('with staticDirs containing a multiple favicons should return the first favicon and warn', async () => { const location = 'static'; - pathExists.mockImplementation((p: string) => { + existsSyncMock.mockImplementation((p: string | Buffer | URL) => { if (p === createPath(location)) { return true; } @@ -113,7 +107,7 @@ it('with staticDirs containing a multiple favicons should return the first favic it('with multiple staticDirs containing a multiple favicons should return the first favicon and warn', async () => { const locationA = 'static-a'; const locationB = 'static-b'; - pathExists.mockImplementation((p: string) => { + existsSyncMock.mockImplementation((p: string | Buffer | URL) => { if (p === createPath(locationA)) { return true; } diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.ts b/code/core/src/core-server/utils/StoryIndexGenerator.ts index 0c5338debf88..b3bc3239b983 100644 --- a/code/core/src/core-server/utils/StoryIndexGenerator.ts +++ b/code/core/src/core-server/utils/StoryIndexGenerator.ts @@ -1,4 +1,6 @@ /* eslint-disable no-underscore-dangle */ +import { existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; import { dirname, extname, join, normalize, relative, resolve, sep } from 'node:path'; import { commonGlobOptions, normalizeStoryPath } from '@storybook/core/common'; @@ -23,7 +25,6 @@ import { sortStoriesV7, userOrAutoTitleFromSpecifier } from '@storybook/core/pre import chalk from 'chalk'; import { findUp } from 'find-up'; -import fs from 'fs-extra'; import slash from 'slash'; import invariant from 'tiny-invariant'; import { dedent } from 'ts-dedent'; @@ -324,7 +325,7 @@ export class StoryIndexGenerator { const absoluteComponentPath = resolve(dirname(absolutePath), rawPath); const existing = ['', '.js', '.ts', '.jsx', '.tsx', '.mjs', '.mts'] .map((ext) => `${absoluteComponentPath}${ext}`) - .find((candidate) => fs.existsSync(candidate)); + .find((candidate) => existsSync(candidate)); if (existing) { const relativePath = relative(this.options.workingDir, existing); return slash(normalizeStoryPath(relativePath)); @@ -432,7 +433,7 @@ export class StoryIndexGenerator { const normalizedPath = normalizeStoryPath(relativePath); const importPath = slash(normalizedPath); - const content = await fs.readFile(absolutePath, 'utf8'); + const content = await readFile(absolutePath, { encoding: 'utf8' }); const { analyze } = await import('@storybook/docs-mdx'); const result = await analyze(content); @@ -753,9 +754,9 @@ export class StoryIndexGenerator { async getPreviewCode() { const previewFile = ['js', 'jsx', 'ts', 'tsx', 'mjs', 'cjs', 'mts'] .map((ext) => join(this.options.configDir, `preview.${ext}`)) - .find((fname) => fs.existsSync(fname)); + .find((fname) => existsSync(fname)); - return previewFile && (await fs.readFile(previewFile, 'utf-8')).toString(); + return previewFile && (await readFile(previewFile, { encoding: 'utf-8' })).toString(); } getProjectTags(previewCode?: string) { diff --git a/code/core/src/core-server/utils/__tests__/server-statics.test.ts b/code/core/src/core-server/utils/__tests__/server-statics.test.ts index ca532a8b04f9..0dfaea67f5df 100644 --- a/code/core/src/core-server/utils/__tests__/server-statics.test.ts +++ b/code/core/src/core-server/utils/__tests__/server-statics.test.ts @@ -1,19 +1,17 @@ +import fs from 'node:fs'; import { resolve } from 'node:path'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import fs from 'fs-extra'; - import { onlyWindows, skipWindows } from '../../../../../vitest.helpers'; import { parseStaticDir } from '../server-statics'; -vi.mock('fs-extra'); -const pathExistsMock = vi.mocked(fs.pathExists); +vi.mock('node:fs'); +const existsSyncMock = vi.mocked(fs.existsSync); describe('parseStaticDir', () => { beforeEach(() => { - // @ts-expect-error for some reason vitest does not match the return type with one of the overloads from pathExists - pathExistsMock.mockResolvedValue(true); + existsSyncMock.mockReturnValue(true); }); it('returns the static dir/path and default target', async () => { @@ -57,8 +55,7 @@ describe('parseStaticDir', () => { }); it('checks that the path exists', async () => { - // @ts-expect-error for some reason vitest does not match the return type with one of the overloads from pathExists - pathExistsMock.mockResolvedValue(false); + existsSyncMock.mockReturnValueOnce(false); await expect(parseStaticDir('nonexistent')).rejects.toThrow(resolve('nonexistent')); }); diff --git a/code/core/src/core-server/utils/copy-all-static-files.ts b/code/core/src/core-server/utils/copy-all-static-files.ts index bc9a515731ef..f0b3d331033d 100644 --- a/code/core/src/core-server/utils/copy-all-static-files.ts +++ b/code/core/src/core-server/utils/copy-all-static-files.ts @@ -1,3 +1,4 @@ +import { cp } from 'node:fs/promises'; import { join, relative } from 'node:path'; import { getDirectoryFromWorkingDir } from '@storybook/core/common'; @@ -5,7 +6,6 @@ import { getDirectoryFromWorkingDir } from '@storybook/core/common'; import { logger } from '@storybook/core/node-logger'; import chalk from 'chalk'; -import fs from 'fs-extra'; import { parseStaticDir } from './server-statics'; @@ -26,10 +26,13 @@ export async function copyAllStaticFiles(staticDirs: any[] | undefined, outputDi // Storybook's own files should not be overwritten, so we skip such files if we find them const skipPaths = ['index.html', 'iframe.html'].map((f) => join(targetPath, f)); - await fs.copy(staticPath, targetPath, { + // TODO: `fsPromises.cp` is marked as experimental in Node 16-21. Ask in the PR whether we should + // use it anyway or stick to `fs-extra` for now. + await cp(staticPath, targetPath, { dereference: true, preserveTimestamps: true, filter: (_, dest) => !skipPaths.includes(dest), + recursive: true, }); } catch (e) { if (e instanceof Error) { @@ -68,10 +71,11 @@ export async function copyAllStaticFilesRelativeToMain( `=> Copying static files: ${chalk.cyan(print(from))} at ${chalk.cyan(print(targetPath))}` ); } - await fs.copy(from, targetPath, { + await cp(from, targetPath, { dereference: true, preserveTimestamps: true, filter: (_, dest) => !skipPaths.includes(dest), + recursive: true, }); }, Promise.resolve()); } diff --git a/code/core/src/core-server/utils/metadata.ts b/code/core/src/core-server/utils/metadata.ts index cc047d841768..a617b3e1f52c 100644 --- a/code/core/src/core-server/utils/metadata.ts +++ b/code/core/src/core-server/utils/metadata.ts @@ -1,12 +1,13 @@ +import { writeFile } from 'node:fs/promises'; + import { getStorybookMetadata } from '@storybook/core/telemetry'; import type { Request, Response, Router } from 'express'; -import { writeJSON } from 'fs-extra'; export async function extractStorybookMetadata(outputFile: string, configDir: string) { const storybookMetadata = await getStorybookMetadata(configDir); - await writeJSON(outputFile, storybookMetadata); + await writeFile(outputFile, JSON.stringify(storybookMetadata)); } export function useStorybookMetadata(router: Router, configDir?: string) { diff --git a/code/core/src/core-server/utils/output-stats.ts b/code/core/src/core-server/utils/output-stats.ts index c3a5b9f8aad9..1d5047862247 100644 --- a/code/core/src/core-server/utils/output-stats.ts +++ b/code/core/src/core-server/utils/output-stats.ts @@ -1,3 +1,4 @@ +import { createWriteStream } from 'node:fs'; import { join } from 'node:path'; import type { Stats } from '@storybook/core/types'; @@ -6,7 +7,6 @@ import { logger } from '@storybook/core/node-logger'; import { stringifyStream } from '@discoveryjs/json-ext'; import chalk from 'chalk'; -import fs from 'fs-extra'; export async function outputStats(directory: string, previewStats?: any, managerStats?: any) { if (previewStats) { @@ -25,7 +25,7 @@ export const writeStats = async (directory: string, name: string, stats: Stats) await new Promise((resolve, reject) => { stringifyStream(data, null, 2) .on('error', reject) - .pipe(fs.createWriteStream(filePath)) + .pipe(createWriteStream(filePath)) .on('error', reject) .on('finish', resolve); }); diff --git a/code/core/src/core-server/utils/server-init.ts b/code/core/src/core-server/utils/server-init.ts index b63d663e6857..cdcd3c920543 100644 --- a/code/core/src/core-server/utils/server-init.ts +++ b/code/core/src/core-server/utils/server-init.ts @@ -1,7 +1,8 @@ +import { readFile } from 'node:fs/promises'; + import { logger } from '@storybook/core/node-logger'; import type { Express } from 'express'; -import { readFile } from 'fs-extra'; import http from 'http'; import https from 'https'; @@ -29,9 +30,9 @@ export async function getServer( } const sslOptions = { - ca: await Promise.all((options.sslCa || []).map((ca) => readFile(ca, 'utf-8'))), - cert: await readFile(options.sslCert, 'utf-8'), - key: await readFile(options.sslKey, 'utf-8'), + ca: await Promise.all((options.sslCa || []).map((ca) => readFile(ca, { encoding: 'utf-8' }))), + cert: await readFile(options.sslCert, { encoding: 'utf-8' }), + key: await readFile(options.sslKey, { encoding: 'utf-8' }), }; return https.createServer(sslOptions, app); diff --git a/code/core/src/core-server/utils/server-statics.ts b/code/core/src/core-server/utils/server-statics.ts index b10a573e7d90..edd1a4f45fb2 100644 --- a/code/core/src/core-server/utils/server-statics.ts +++ b/code/core/src/core-server/utils/server-statics.ts @@ -1,3 +1,4 @@ +import { existsSync } from 'node:fs'; import { basename, isAbsolute, posix, resolve, sep, win32 } from 'node:path'; import { getDirectoryFromWorkingDir } from '@storybook/core/common'; @@ -8,7 +9,6 @@ import { logger } from '@storybook/core/node-logger'; import chalk from 'chalk'; import type { Router } from 'express'; import express from 'express'; -import { pathExists } from 'fs-extra'; import { dedent } from 'ts-dedent'; export async function useStatics(router: Router, options: Options) { @@ -69,7 +69,7 @@ export const parseStaticDir = async (arg: string) => { const targetDir = target.replace(/^\/?/, './'); const targetEndpoint = targetDir.substring(1); - if (!(await pathExists(staticPath))) { + if (!existsSync(staticPath)) { throw new Error( dedent` Failed to load static files, no such directory: ${chalk.cyan(staticPath)} diff --git a/code/core/src/core-server/utils/stories-json.ts b/code/core/src/core-server/utils/stories-json.ts index 8956a4ed7ab1..d34ba9d72186 100644 --- a/code/core/src/core-server/utils/stories-json.ts +++ b/code/core/src/core-server/utils/stories-json.ts @@ -1,3 +1,4 @@ +import { writeFile } from 'node:fs/promises'; import { basename } from 'node:path'; import type { NormalizedStoriesSpecifier, StoryIndex } from '@storybook/core/types'; @@ -5,7 +6,6 @@ import type { NormalizedStoriesSpecifier, StoryIndex } from '@storybook/core/typ import { STORY_INDEX_INVALIDATED } from '@storybook/core/core-events'; import type { Request, Response, Router } from 'express'; -import { writeJSON } from 'fs-extra'; import debounce from 'lodash/debounce.js'; import type { StoryIndexGenerator } from './StoryIndexGenerator'; @@ -22,7 +22,7 @@ export async function extractStoriesJson( ) { const generator = await initializedStoryIndexGenerator; const storyIndex = await generator.getIndex(); - await writeJSON(outputFile, transform ? transform(storyIndex) : storyIndex); + await writeFile(outputFile, JSON.stringify(transform ? transform(storyIndex) : storyIndex)); } export function useStoriesJson({ diff --git a/code/core/src/core-server/utils/warnWhenUsingArgTypesRegex.ts b/code/core/src/core-server/utils/warnWhenUsingArgTypesRegex.ts index dd543bff54bc..d8fa4dcc521c 100644 --- a/code/core/src/core-server/utils/warnWhenUsingArgTypesRegex.ts +++ b/code/core/src/core-server/utils/warnWhenUsingArgTypesRegex.ts @@ -1,17 +1,20 @@ +import { readFile } from 'node:fs/promises'; + import { type BabelFile, core } from '@storybook/core/babel'; import type { StorybookConfig } from '@storybook/core/types'; import { babelParse } from '@storybook/core/csf-tools'; import chalk from 'chalk'; -import { readFile } from 'fs-extra'; import { dedent } from 'ts-dedent'; export async function warnWhenUsingArgTypesRegex( previewConfigPath: string | undefined, config: StorybookConfig ) { - const previewContent = previewConfigPath ? await readFile(previewConfigPath, 'utf8') : ''; + const previewContent = previewConfigPath + ? await readFile(previewConfigPath, { encoding: 'utf8' }) + : ''; const hasVisualTestAddon = config?.addons?.some((it) => diff --git a/code/core/src/core-server/utils/whats-new.ts b/code/core/src/core-server/utils/whats-new.ts index 1091d77ab74f..cb523f78318e 100644 --- a/code/core/src/core-server/utils/whats-new.ts +++ b/code/core/src/core-server/utils/whats-new.ts @@ -1,3 +1,5 @@ +import { writeFile } from 'node:fs/promises'; + import type { Channel } from '@storybook/core/channels'; import { findConfigFile } from '@storybook/core/common'; import { telemetry } from '@storybook/core/telemetry'; @@ -14,7 +16,6 @@ import { import { printConfig, readConfig } from '@storybook/core/csf-tools'; import { logger } from '@storybook/core/node-logger'; -import fs from 'fs-extra'; import invariant from 'tiny-invariant'; import { sendTelemetryError } from '../withTelemetry'; @@ -93,7 +94,7 @@ export function initializeWhatsNew( invariant(mainPath, `unable to find storybook main file in ${options.configDir}`); const main = await readConfig(mainPath); main.setFieldValue(['core', 'disableWhatsNewNotifications'], disableWhatsNewNotifications); - await fs.writeFile(mainPath, printConfig(main).code); + await writeFile(mainPath, printConfig(main).code); if (isTelemetryEnabled) { await telemetry('core-config', { disableWhatsNewNotifications }); } From 2dcc7b75e719487e6bb6c27cf844c8bb51b68d4c Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 19:38:58 +0200 Subject: [PATCH 17/28] Add `recursive: true` to `cp` calls --- code/builders/builder-vite/src/index.ts | 1 + code/builders/builder-webpack5/src/index.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/code/builders/builder-vite/src/index.ts b/code/builders/builder-vite/src/index.ts index fb841b1c473d..7a8771a5efe9 100644 --- a/code/builders/builder-vite/src/index.ts +++ b/code/builders/builder-vite/src/index.ts @@ -94,6 +94,7 @@ export const build: ViteBuilder['build'] = async ({ options }) => { } return true; }, + recursive: true, }); const [out] = await Promise.all([viteCompilation, previewFiles]); diff --git a/code/builders/builder-webpack5/src/index.ts b/code/builders/builder-webpack5/src/index.ts index 7d12dabb4b6f..bf87a0700131 100644 --- a/code/builders/builder-webpack5/src/index.ts +++ b/code/builders/builder-webpack5/src/index.ts @@ -302,6 +302,7 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, } return true; }, + recursive: true, }); const [webpackCompilationOutput] = await Promise.all([webpackCompilation, previewFiles]); From ca39982d8873141a36b8c56a2594af9d3027599d Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 19:39:31 +0200 Subject: [PATCH 18/28] core: Remove `fs-extra` from `src/core-server` --- code/core/src/core-server/build-dev.ts | 4 ++-- code/core/src/core-server/build-static.ts | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/code/core/src/core-server/build-dev.ts b/code/core/src/core-server/build-dev.ts index 5c87a7d9ccd8..2f1e08703a21 100644 --- a/code/core/src/core-server/build-dev.ts +++ b/code/core/src/core-server/build-dev.ts @@ -1,3 +1,4 @@ +import { readFile } from 'node:fs/promises'; import { join, relative, resolve } from 'node:path'; import { @@ -18,7 +19,6 @@ import { global } from '@storybook/global'; import { deprecate } from '@storybook/core/node-logger'; import { MissingBuilderError, NoStatsForViteDevError } from '@storybook/core/server-errors'; -import { readFile } from 'fs-extra'; import prompts from 'prompts'; import invariant from 'tiny-invariant'; import { dedent } from 'ts-dedent'; @@ -156,7 +156,7 @@ export async function buildDevStandalone( if (/\.c[jt]s$/.test(mainJsPath)) { deprecate(deprecationMessage); } - const mainJsContent = await readFile(mainJsPath, 'utf-8'); + const mainJsContent = await readFile(mainJsPath, { encoding: 'utf-8' }); // Regex that matches any CommonJS-specific syntax, stolen from Vite: https://github.com/vitejs/vite/blob/91a18c2f7da796ff8217417a4bf189ddda719895/packages/vite/src/node/ssr/ssrExternal.ts#L87 const CJS_CONTENT_REGEX = /\bmodule\.exports\b|\bexports[.[]|\brequire\s*\(|\bObject\.(?:defineProperty|defineProperties|assign)\s*\(\s*exports\b/; diff --git a/code/core/src/core-server/build-static.ts b/code/core/src/core-server/build-static.ts index 248b2746ea8c..d0835d0d8a67 100644 --- a/code/core/src/core-server/build-static.ts +++ b/code/core/src/core-server/build-static.ts @@ -1,3 +1,5 @@ +import { cp, mkdir, readdir } from 'node:fs/promises'; +import { rm } from 'node:fs/promises'; import { dirname, join, relative, resolve } from 'node:path'; import { @@ -14,7 +16,6 @@ import { global } from '@storybook/global'; import { logger } from '@storybook/core/node-logger'; import chalk from 'chalk'; -import { copy, emptyDir, ensureDir } from 'fs-extra'; import { StoryIndexGenerator } from './utils/StoryIndexGenerator'; import { buildOrThrow } from './utils/build-or-throw'; @@ -43,8 +44,12 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption if (options.outputDir === '/') { throw new Error("Won't remove directory '/'. Check your outputDir!"); } - await emptyDir(options.outputDir); - await ensureDir(options.outputDir); + + const outputDirFiles = await readdir(options.outputDir); + if (outputDirFiles.length > 0) { + await rm(options.outputDir, { recursive: true, force: true }); + await mkdir(options.outputDir, { recursive: true }); + } const config = await loadMainConfig(options); const { framework } = config; @@ -127,7 +132,9 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption dirname(require.resolve('@storybook/core/package.json')), 'assets/browser' ); - effects.push(copy(coreServerPublicDir, options.outputDir)); + // TODO: `fsPromises.cp` is marked as experimental in Node 16-21. Ask in the PR whether we should + // use it anyway or stick to `fs-extra` for now. + effects.push(cp(coreServerPublicDir, options.outputDir, { recursive: true })); let initializedStoryIndexGenerator: Promise = Promise.resolve(undefined); From a42ff7e9590c5747395c379c8c39c7fe0af980f1 Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 19:53:13 +0200 Subject: [PATCH 19/28] Close the file descriptor --- .../builder-vite/src/plugins/external-globals-plugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/builders/builder-vite/src/plugins/external-globals-plugin.ts b/code/builders/builder-vite/src/plugins/external-globals-plugin.ts index 7813e1c50030..6477ade92b4a 100644 --- a/code/builders/builder-vite/src/plugins/external-globals-plugin.ts +++ b/code/builders/builder-vite/src/plugins/external-globals-plugin.ts @@ -1,4 +1,4 @@ -import { existsSync, openSync } from 'node:fs'; +import { closeSync, existsSync, openSync } from 'node:fs'; import { writeFile } from 'node:fs/promises'; import { join } from 'node:path'; @@ -61,7 +61,7 @@ export async function externalGlobalsPlugin(externals: Record) { const externalCachePath = join(cachePath, `${externalKey}.js`); newAlias.push({ find: new RegExp(`^${externalKey}$`), replacement: externalCachePath }); if (!existsSync(externalCachePath)) { - openSync(externalCachePath, 'w'); + closeSync(openSync(externalCachePath, 'w')); } await writeFile(externalCachePath, `module.exports = ${externals[externalKey]};`); }) From 174868afbaa4775e2fc9621665b0d651fbe93dee Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 19:57:46 +0200 Subject: [PATCH 20/28] core: Remove `fs-extra` from `src/builder-manager` --- code/core/src/builder-manager/index.ts | 15 +++--- code/core/src/builder-manager/utils/files.ts | 9 ++-- .../builder-manager/utils/managerEntries.ts | 9 ++-- .../src/builder-manager/utils/template.ts | 4 +- code/core/src/cli/detect.test.ts | 4 -- code/core/src/cli/eslintPlugin.ts | 10 ++-- code/core/src/cli/helpers.test.ts | 46 ++++++++----------- code/core/src/cli/helpers.ts | 30 ++++++------ 8 files changed, 64 insertions(+), 63 deletions(-) diff --git a/code/core/src/builder-manager/index.ts b/code/core/src/builder-manager/index.ts index 1e246b5d1cb1..a1589af27b23 100644 --- a/code/core/src/builder-manager/index.ts +++ b/code/core/src/builder-manager/index.ts @@ -1,3 +1,4 @@ +import { cp, rm, writeFile } from 'node:fs/promises'; import { dirname, join, parse } from 'node:path'; import { stringifyProcessEnvs } from '@storybook/core/common'; @@ -9,7 +10,6 @@ import { globalExternals } from '@fal-works/esbuild-plugin-global-externals'; import { pnpPlugin } from '@yarnpkg/esbuild-plugin-pnp'; import aliasPlugin from 'esbuild-plugin-alias'; import express from 'express'; -import fs from 'fs-extra'; import type { BuilderBuildResult, @@ -149,7 +149,7 @@ const starter: StarterFunction = async function* starterGeneratorFn({ // make sure we clear output directory of addons dir before starting // this could cause caching issues where addons are loaded when they shouldn't const addonsDir = config.outdir; - await fs.remove(addonsDir); + await rm(addonsDir, { recursive: true, force: true }); yield; @@ -256,7 +256,9 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, yield; - const managerFiles = fs.copy(coreDirOrigin, coreDirTarget, { + // TODO: `fsPromises.cp` is marked as experimental in Node 16-21. Ask in the PR whether we should + // use it anyway or stick to `fs-extra` for now. + const managerFiles = cp(coreDirOrigin, coreDirTarget, { filter: (src) => { const { ext } = parse(src); if (ext) { @@ -264,6 +266,7 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, } return true; }, + recursive: true, }); const { cssFiles, jsFiles } = await readOrderedFiles(addonsDir, compilation?.outputFiles); @@ -288,11 +291,7 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, globals ); - await Promise.all([ - // - fs.writeFile(join(options.outputDir, 'index.html'), html), - managerFiles, - ]); + await Promise.all([writeFile(join(options.outputDir, 'index.html'), html), managerFiles]); logger.trace({ message: '=> Manager built', time: process.hrtime(startTime) }); diff --git a/code/core/src/builder-manager/utils/files.ts b/code/core/src/builder-manager/utils/files.ts index 94ec31677c4a..b83cb2e7a13e 100644 --- a/code/core/src/builder-manager/utils/files.ts +++ b/code/core/src/builder-manager/utils/files.ts @@ -1,7 +1,8 @@ +import { closeSync, existsSync, openSync } from 'node:fs'; +import { writeFile } from 'node:fs/promises'; import { join, normalize } from 'node:path'; import type { OutputFile } from 'esbuild'; -import fs from 'fs-extra'; import slash from 'slash'; import type { Compilation } from '../types'; @@ -15,8 +16,10 @@ export async function readOrderedFiles( // convert deeply nested paths to a single level, also remove special characters const { location, url } = sanitizePath(file, addonsDir); - await fs.ensureFile(location); - await fs.writeFile(location, file.contents); + if (!existsSync(location)) { + closeSync(openSync(location, 'w')); + } + await writeFile(location, file.contents); return url; }) || [] ); diff --git a/code/core/src/builder-manager/utils/managerEntries.ts b/code/core/src/builder-manager/utils/managerEntries.ts index d808596f52e4..3cb2f1d3aea9 100644 --- a/code/core/src/builder-manager/utils/managerEntries.ts +++ b/code/core/src/builder-manager/utils/managerEntries.ts @@ -1,8 +1,9 @@ +import { closeSync, existsSync, openSync } from 'node:fs'; +import { writeFile } from 'node:fs/promises'; import { join, parse, relative, sep } from 'node:path'; import { resolvePathInStorybookCache } from '@storybook/core/common'; -import fs from 'fs-extra'; import slash from 'slash'; const sanitizeBase = (path: string) => { @@ -55,8 +56,10 @@ export async function wrapManagerEntries(entrypoints: string[], uniqueId?: strin sanitizeFinal(join(`${sanitizeBase(base)}-${i}`, `${sanitizeBase(name)}-bundle.js`)) ); - await fs.ensureFile(location); - await fs.writeFile(location, `import '${slash(entry)}';`); + if (!existsSync(location)) { + closeSync(openSync(location, 'w')); + } + await writeFile(location, `import '${slash(entry)}';`); return location; }) diff --git a/code/core/src/builder-manager/utils/template.ts b/code/core/src/builder-manager/utils/template.ts index b264a3d5e02e..1ca99e07e2e6 100644 --- a/code/core/src/builder-manager/utils/template.ts +++ b/code/core/src/builder-manager/utils/template.ts @@ -1,9 +1,9 @@ +import { readFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import type { DocsOptions, Options, Ref, TagsOptions } from '@storybook/core/types'; import { render } from 'ejs'; -import fs from 'fs-extra'; export const getTemplatePath = async (template: string) => { return join(dirname(require.resolve('@storybook/core/package.json')), 'assets/server', template); @@ -12,7 +12,7 @@ export const getTemplatePath = async (template: string) => { export const readTemplate = async (template: string) => { const path = await getTemplatePath(template); - return fs.readFile(path, 'utf8'); + return readFile(path, { encoding: 'utf8' }); }; export async function getManagerMainTemplate() { diff --git a/code/core/src/cli/detect.test.ts b/code/core/src/cli/detect.test.ts index f12ece4314fa..95c1b126dca3 100644 --- a/code/core/src/cli/detect.test.ts +++ b/code/core/src/cli/detect.test.ts @@ -26,10 +26,6 @@ vi.mock('fs', () => ({ default: vi.fn(), })); -vi.mock('fs-extra', () => ({ - pathExistsSync: vi.fn(() => true), -})); - vi.mock('@storybook/core/node-logger'); const MOCK_FRAMEWORK_FILES: { diff --git a/code/core/src/cli/eslintPlugin.ts b/code/core/src/cli/eslintPlugin.ts index 4b8d47b293a2..e6a37adfd64e 100644 --- a/code/core/src/cli/eslintPlugin.ts +++ b/code/core/src/cli/eslintPlugin.ts @@ -1,4 +1,5 @@ import { existsSync } from 'node:fs'; +import { readFile, writeFile } from 'node:fs/promises'; import type { JsPackageManager } from '@storybook/core/common'; import { paddedLog } from '@storybook/core/common'; @@ -7,7 +8,6 @@ import { readConfig, writeConfig } from '@storybook/core/csf-tools'; import chalk from 'chalk'; import detectIndent from 'detect-indent'; -import { readFile, readJson, writeJson } from 'fs-extra'; import prompts from 'prompts'; import { dedent } from 'ts-dedent'; @@ -72,13 +72,15 @@ export async function configureEslintPlugin( if (eslintFile) { paddedLog(`Configuring Storybook ESLint plugin at ${eslintFile}`); if (eslintFile.endsWith('json')) { - const eslintConfig = (await readJson(eslintFile)) as { extends?: string[] }; + const eslintConfig = JSON.parse(await readFile(eslintFile, { encoding: 'utf-8' })) as { + extends?: string[]; + }; const existingExtends = normalizeExtends(eslintConfig.extends).filter(Boolean); eslintConfig.extends = [...existingExtends, 'plugin:storybook/recommended'] as string[]; - const eslintFileContents = await readFile(eslintFile, 'utf8'); + const eslintFileContents = await readFile(eslintFile, { encoding: 'utf8' }); const spaces = detectIndent(eslintFileContents).amount || 2; - await writeJson(eslintFile, eslintConfig, { spaces }); + await writeFile(eslintFile, JSON.stringify(eslintConfig, undefined, spaces)); } else { const eslint = await readConfig(eslintFile); const existingExtends = normalizeExtends(eslint.getFieldValue(['extends'])).filter(Boolean); diff --git a/code/core/src/cli/helpers.test.ts b/code/core/src/cli/helpers.test.ts index cff797a8f505..4615f0cfdd84 100644 --- a/code/core/src/cli/helpers.test.ts +++ b/code/core/src/cli/helpers.test.ts @@ -1,8 +1,10 @@ +import fs from 'node:fs'; +import fsp from 'node:fs/promises'; + import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { JsPackageManager } from '@storybook/core/common'; -import fse from 'fs-extra'; import { sep } from 'path'; import { IS_WINDOWS } from '../../../vitest.helpers'; @@ -13,21 +15,18 @@ import { SupportedLanguage } from './project_types'; const normalizePath = (path: string) => (IS_WINDOWS ? path.replace(/\//g, sep) : path); const fsMocks = vi.hoisted(() => ({ + cpSync: vi.fn(() => ({})), existsSync: vi.fn(), })); -const fseMocks = vi.hoisted(() => ({ - copySync: vi.fn(() => ({})), - copy: vi.fn(() => ({})), - ensureDir: vi.fn(() => {}), - existsSync: vi.fn(), - pathExists: vi.fn(), +const fspMocks = vi.hoisted(() => ({ + cp: vi.fn(() => ({})), readFile: vi.fn(() => ''), writeFile: vi.fn(), })); -vi.mock('fs', async (importOriginal) => { - const actual = await importOriginal(); +vi.mock('node:fs', async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, ...fsMocks, @@ -42,14 +41,14 @@ vi.mock('./dirs', () => ({ normalizePath(`@storybook/${renderer}`), })); -vi.mock('fs-extra', async (importOriginal) => { - const actual = await importOriginal(); +vi.mock('node:fs/promises', async (importOriginal) => { + const actual = await importOriginal(); return { ...actual, - ...fseMocks, + ...fspMocks, default: { ...actual, - ...fseMocks, + ...fspMocks, }, }; }); @@ -83,7 +82,7 @@ describe('Helpers', () => { helpers.copyTemplate(''); - expect(fse.copySync).toHaveBeenCalledWith( + expect(fs.cpSync).toHaveBeenCalledWith( expect.stringMatching(csfDirectory), expect.anything(), expect.anything() @@ -115,7 +114,7 @@ describe('Helpers', () => { const componentsDirectory = exists.map((folder: string) => normalizePath(`@storybook/react/template/cli/${folder}`) ); - fseMocks.pathExists.mockImplementation( + fsMocks.existsSync.mockImplementation( (filePath) => componentsDirectory.includes(filePath) || filePath === normalizePath('@storybook/react/template/cli') @@ -127,7 +126,7 @@ describe('Helpers', () => { commonAssetsDir: normalizePath('create-storybook/rendererAssets/common'), }); - expect(fse.copy).toHaveBeenNthCalledWith( + expect(fsp.cp).toHaveBeenNthCalledWith( 1, normalizePath('create-storybook/rendererAssets/common'), './stories', @@ -135,17 +134,12 @@ describe('Helpers', () => { ); const expectedDirectory = normalizePath(`@storybook/react/template/cli${expected}`); - expect(fse.copy).toHaveBeenNthCalledWith( - 2, - expectedDirectory, - './stories', - expect.anything() - ); + expect(fsp.cp).toHaveBeenNthCalledWith(2, expectedDirectory, './stories', expect.anything()); } ); it(`should copy to src folder when exists`, async () => { - vi.mocked(fse.pathExists).mockImplementation((filePath) => { + vi.mocked(fs.existsSync).mockImplementation((filePath) => { return filePath === normalizePath('@storybook/react/template/cli') || filePath === './src'; }); await helpers.copyTemplateFiles({ @@ -153,11 +147,11 @@ describe('Helpers', () => { language: SupportedLanguage.JAVASCRIPT, packageManager: packageManagerMock, }); - expect(fse.copy).toHaveBeenCalledWith(expect.anything(), './src/stories', expect.anything()); + expect(fsp.cp).toHaveBeenCalledWith(expect.anything(), './src/stories', expect.anything()); }); it(`should copy to root folder when src doesn't exist`, async () => { - vi.mocked(fse.pathExists).mockImplementation((filePath) => { + vi.mocked(fs.existsSync).mockImplementation((filePath) => { return filePath === normalizePath('@storybook/react/template/cli'); }); await helpers.copyTemplateFiles({ @@ -165,7 +159,7 @@ describe('Helpers', () => { language: SupportedLanguage.JAVASCRIPT, packageManager: packageManagerMock, }); - expect(fse.copy).toHaveBeenCalledWith(expect.anything(), './stories', expect.anything()); + expect(fsp.cp).toHaveBeenCalledWith(expect.anything(), './stories', expect.anything()); }); it(`should throw an error for unsupported renderer`, async () => { diff --git a/code/core/src/cli/helpers.ts b/code/core/src/cli/helpers.ts index 355e142cece1..1b1448e7b469 100644 --- a/code/core/src/cli/helpers.ts +++ b/code/core/src/cli/helpers.ts @@ -1,4 +1,5 @@ -import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { cpSync, existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { cp, readFile, writeFile } from 'node:fs/promises'; import { join, resolve } from 'node:path'; import { @@ -12,7 +13,6 @@ import type { SupportedFrameworks, SupportedRenderers } from '@storybook/core/ty import chalk from 'chalk'; import { findUpSync } from 'find-up'; -import { copy, copySync, pathExists, readFile, writeFile } from 'fs-extra'; import { coerce, satisfies } from 'semver'; import stripJsonComments from 'strip-json-comments'; import invariant from 'tiny-invariant'; @@ -130,7 +130,9 @@ export function copyTemplate(templateRoot: string, destination = '.') { throw new Error(`Couldn't find template dir`); } - copySync(templateDir, destination, { overwrite: true }); + // TODO: `fsPromises.cpSync` is marked as experimental in Node 16-21. Ask in the PR whether we + // should use it anyway or stick to `fs-extra` for now. + cpSync(templateDir, destination, { recursive: true }); } type CopyTemplateFilesOptions = { @@ -197,42 +199,44 @@ export async function copyTemplateFiles({ const assetsTS38 = join(assetsDir, languageFolderMapping[SupportedLanguage.TYPESCRIPT_3_8]); // Ideally use the assets that match the language & version. - if (await pathExists(assetsLanguage)) { + if (existsSync(assetsLanguage)) { return assetsLanguage; } // Use fallback typescript 3.8 assets if new ones aren't available - if (language === SupportedLanguage.TYPESCRIPT_4_9 && (await pathExists(assetsTS38))) { + if (language === SupportedLanguage.TYPESCRIPT_4_9 && existsSync(assetsTS38)) { return assetsTS38; } // Fallback further to TS (for backwards compatibility purposes) - if (await pathExists(assetsTS)) { + if (existsSync(assetsTS)) { return assetsTS; } // Fallback further to JS - if (await pathExists(assetsJS)) { + if (existsSync(assetsJS)) { return assetsJS; } // As a last resort, look for the root of the asset directory - if (await pathExists(assetsDir)) { + if (existsSync(assetsDir)) { return assetsDir; } throw new Error(`Unsupported renderer: ${renderer} (${baseDir})`); }; const targetPath = async () => { - if (await pathExists('./src')) { + if (existsSync('./src')) { return './src/stories'; } return './stories'; }; const destinationPath = destination ?? (await targetPath()); + // TODO: `fsPromises.cp` is marked as experimental in Node 16-21. Ask in the PR whether we should + // use it anyway or stick to `fs-extra` for now. if (commonAssetsDir) { - await copy(commonAssetsDir, destinationPath, { - overwrite: true, + await cp(commonAssetsDir, destinationPath, { + recursive: true, }); } - await copy(await templatePath(), destinationPath, { overwrite: true }); + await cp(await templatePath(), destinationPath, { recursive: true }); if (commonAssetsDir) { let rendererType = frameworkToRenderer[renderer] || 'react'; @@ -248,7 +252,7 @@ export async function copyTemplateFiles({ export async function adjustTemplate(templatePath: string, templateData: Record) { // for now, we're just doing a simple string replace // in the future we might replace this with a proper templating engine - let template = await readFile(templatePath, 'utf8'); + let template = await readFile(templatePath, { encoding: 'utf8' }); Object.keys(templateData).forEach((key) => { template = template.replaceAll(`{{${key}}}`, `${templateData[key]}`); From 643acbfc2a03faa240fa01693e11bd1c54a293e2 Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 20:01:00 +0200 Subject: [PATCH 21/28] core: Remove `fs-extra` from `scripts` --- code/core/package.json | 2 -- code/core/scripts/helpers/dependencies.ts | 7 ++++--- code/core/scripts/helpers/generatePackageJsonFile.ts | 5 ++--- code/core/scripts/helpers/generateTypesMapperFiles.ts | 7 ++++--- code/core/scripts/prep.ts | 8 ++++---- code/yarn.lock | 2 -- 6 files changed, 14 insertions(+), 17 deletions(-) diff --git a/code/core/package.json b/code/core/package.json index 181b04312985..e9464c5339b6 100644 --- a/code/core/package.json +++ b/code/core/package.json @@ -320,7 +320,6 @@ "@types/diff": "^5.0.9", "@types/ejs": "^3.1.1", "@types/find-cache-dir": "^5.0.0", - "@types/fs-extra": "^11.0.1", "@types/js-yaml": "^4.0.5", "@types/lodash": "^4.14.167", "@types/node": "^22.0.0", @@ -371,7 +370,6 @@ "find-cache-dir": "^5.0.0", "find-up": "^7.0.0", "flush-promises": "^1.0.2", - "fs-extra": "^11.1.0", "fuse.js": "^3.6.1", "get-npm-tarball-url": "^2.0.3", "glob": "^10.0.0", diff --git a/code/core/scripts/helpers/dependencies.ts b/code/core/scripts/helpers/dependencies.ts index 82b22a022ad4..e84e80931e75 100644 --- a/code/core/scripts/helpers/dependencies.ts +++ b/code/core/scripts/helpers/dependencies.ts @@ -1,7 +1,6 @@ +import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { readJson } from 'fs-extra'; - export async function flattenDependencies( list: string[], output: string[] = [], @@ -18,7 +17,9 @@ export async function flattenDependencies( console.log(dep + ' not found'); return; } - const { dependencies = {}, peerDependencies = {} } = await readJson(path); + const { dependencies = {}, peerDependencies = {} } = JSON.parse( + await readFile(path, { encoding: 'utf-8' }) + ); const all: string[] = [ ...new Set([...Object.keys(dependencies), ...Object.keys(peerDependencies)]), ] diff --git a/code/core/scripts/helpers/generatePackageJsonFile.ts b/code/core/scripts/helpers/generatePackageJsonFile.ts index 9ecf2f7679e5..1d14e141504a 100644 --- a/code/core/scripts/helpers/generatePackageJsonFile.ts +++ b/code/core/scripts/helpers/generatePackageJsonFile.ts @@ -1,7 +1,6 @@ -import { writeFile } from 'node:fs/promises'; +import { readFile, writeFile } from 'node:fs/promises'; import { join, relative } from 'node:path'; -import { readJSON } from 'fs-extra'; import slash from 'slash'; import { sortPackageJson } from '../../../../scripts/prepare/tools'; @@ -11,7 +10,7 @@ const cwd = process.cwd(); export async function generatePackageJsonFile(entries: ReturnType) { const location = join(cwd, 'package.json'); - const pkgJson = await readJSON(location); + const pkgJson = JSON.parse(await readFile(location, { encoding: 'utf-8' })); /** * Re-create the `exports` field in `code/core/package.json` This way we only need to update the diff --git a/code/core/scripts/helpers/generateTypesMapperFiles.ts b/code/core/scripts/helpers/generateTypesMapperFiles.ts index 2f79f80643d8..11845a5e3239 100644 --- a/code/core/scripts/helpers/generateTypesMapperFiles.ts +++ b/code/core/scripts/helpers/generateTypesMapperFiles.ts @@ -1,8 +1,7 @@ +import { closeSync, existsSync, openSync } from 'node:fs'; import { writeFile } from 'node:fs/promises'; import { join, relative } from 'node:path'; -import { ensureFile } from 'fs-extra'; - import { dedent } from '../../../../scripts/prepare/tools'; import type { getEntries } from '../entries'; @@ -34,7 +33,9 @@ export async function generateTypesMapperFiles(entries: ReturnType { const location = filePath.replace('src', 'dist').replace(/\.tsx?/, '.d.ts'); - await ensureFile(location); + if (!existsSync(location)) { + closeSync(openSync(location, 'w')); + } await writeFile(location, await generateTypesMapperContent(filePath)); }) ); diff --git a/code/core/scripts/prep.ts b/code/core/scripts/prep.ts index 49c2bd00cfa0..20c979ebd9aa 100644 --- a/code/core/scripts/prep.ts +++ b/code/core/scripts/prep.ts @@ -1,10 +1,8 @@ /* eslint-disable local-rules/no-uncategorized-errors */ -import { watch } from 'node:fs'; +import { existsSync, mkdirSync, watch } from 'node:fs'; import { mkdir, rm, writeFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; -import { ensureDir } from 'fs-extra'; - import { chalk, dedent, @@ -320,7 +318,9 @@ async function run() { const outName = keys.length === 1 ? dirname(keys[0]).replace('dist/', '') : `meta-${format}-${index}`; - await ensureDir('report'); + if (!existsSync('report')) { + mkdirSync('report'); + } await writeFile(`report/${outName}.json`, JSON.stringify(out.metafile, null, 2)); await writeFile( `report/${outName}.txt`, diff --git a/code/yarn.lock b/code/yarn.lock index e477552d26d5..544c7fa773c5 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6034,7 +6034,6 @@ __metadata: "@types/ejs": "npm:^3.1.1" "@types/express": "npm:^4.17.21" "@types/find-cache-dir": "npm:^5.0.0" - "@types/fs-extra": "npm:^11.0.1" "@types/js-yaml": "npm:^4.0.5" "@types/lodash": "npm:^4.14.167" "@types/node": "npm:^22.0.0" @@ -6087,7 +6086,6 @@ __metadata: find-cache-dir: "npm:^5.0.0" find-up: "npm:^7.0.0" flush-promises: "npm:^1.0.2" - fs-extra: "npm:^11.1.0" fuse.js: "npm:^3.6.1" get-npm-tarball-url: "npm:^2.0.3" glob: "npm:^10.0.0" From 89cdff3bec640b6bd8d8831d578cf4b9cc9858af Mon Sep 17 00:00:00 2001 From: ziebam Date: Sat, 14 Sep 2024 20:04:38 +0200 Subject: [PATCH 22/28] Remove `fs-extra` from `code` --- code/package.json | 1 - code/yarn.lock | 1 - 2 files changed, 2 deletions(-) diff --git a/code/package.json b/code/package.json index e348ef665900..000f4d270a0b 100644 --- a/code/package.json +++ b/code/package.json @@ -194,7 +194,6 @@ "eslint-plugin-local-rules": "portal:../scripts/eslint-plugin-local-rules", "eslint-plugin-playwright": "^1.6.2", "eslint-plugin-storybook": "^0.8.0", - "fs-extra": "^11.1.0", "github-release-from-changelog": "^2.1.1", "glob": "^10.0.0", "happy-dom": "^14.12.0", diff --git a/code/yarn.lock b/code/yarn.lock index 544c7fa773c5..ae4ac70a6765 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6893,7 +6893,6 @@ __metadata: eslint-plugin-local-rules: "portal:../scripts/eslint-plugin-local-rules" eslint-plugin-playwright: "npm:^1.6.2" eslint-plugin-storybook: "npm:^0.8.0" - fs-extra: "npm:^11.1.0" github-release-from-changelog: "npm:^2.1.1" glob: "npm:^10.0.0" happy-dom: "npm:^14.12.0" From 0d10b9586305315994a2b2e8716d18492481fc0a Mon Sep 17 00:00:00 2001 From: ziebam Date: Sun, 15 Sep 2024 11:40:41 +0200 Subject: [PATCH 23/28] Replace 'utf-8' with 'utf8' for consistency with Node docs --- code/builders/builder-vite/src/index.ts | 2 +- code/core/scripts/helpers/dependencies.ts | 2 +- code/core/scripts/helpers/generatePackageJsonFile.ts | 2 +- code/core/src/cli/eslintPlugin.ts | 2 +- code/core/src/common/utils/get-storybook-refs.ts | 4 ++-- code/core/src/core-server/build-dev.ts | 2 +- code/core/src/core-server/presets/common-preset.ts | 2 +- code/core/src/core-server/utils/StoryIndexGenerator.ts | 2 +- code/core/src/core-server/utils/server-init.ts | 6 +++--- code/core/src/telemetry/get-monorepo-type.ts | 2 +- code/core/src/telemetry/package-json.ts | 2 +- code/lib/cli-storybook/src/automigrate/fixes/mdx-1-to-3.ts | 2 +- code/lib/cli-storybook/src/link.ts | 4 ++-- code/renderers/server/src/preset.ts | 2 +- 14 files changed, 18 insertions(+), 18 deletions(-) diff --git a/code/builders/builder-vite/src/index.ts b/code/builders/builder-vite/src/index.ts index 7a8771a5efe9..cdd4ed972eef 100644 --- a/code/builders/builder-vite/src/index.ts +++ b/code/builders/builder-vite/src/index.ts @@ -35,7 +35,7 @@ function iframeMiddleware(options: Options, server: ViteDevServer): RequestHandl } const indexHtml = await readFile(require.resolve('@storybook/builder-vite/input/iframe.html'), { - encoding: 'utf-8', + encoding: 'utf8', }); const generated = await transformIframeHtml(indexHtml, options); const transformed = await server.transformIndexHtml('/iframe.html', generated); diff --git a/code/core/scripts/helpers/dependencies.ts b/code/core/scripts/helpers/dependencies.ts index e84e80931e75..89e67fc554f3 100644 --- a/code/core/scripts/helpers/dependencies.ts +++ b/code/core/scripts/helpers/dependencies.ts @@ -18,7 +18,7 @@ export async function flattenDependencies( return; } const { dependencies = {}, peerDependencies = {} } = JSON.parse( - await readFile(path, { encoding: 'utf-8' }) + await readFile(path, { encoding: 'utf8' }) ); const all: string[] = [ ...new Set([...Object.keys(dependencies), ...Object.keys(peerDependencies)]), diff --git a/code/core/scripts/helpers/generatePackageJsonFile.ts b/code/core/scripts/helpers/generatePackageJsonFile.ts index 1d14e141504a..271f775cba23 100644 --- a/code/core/scripts/helpers/generatePackageJsonFile.ts +++ b/code/core/scripts/helpers/generatePackageJsonFile.ts @@ -10,7 +10,7 @@ const cwd = process.cwd(); export async function generatePackageJsonFile(entries: ReturnType) { const location = join(cwd, 'package.json'); - const pkgJson = JSON.parse(await readFile(location, { encoding: 'utf-8' })); + const pkgJson = JSON.parse(await readFile(location, { encoding: 'utf8' })); /** * Re-create the `exports` field in `code/core/package.json` This way we only need to update the diff --git a/code/core/src/cli/eslintPlugin.ts b/code/core/src/cli/eslintPlugin.ts index e6a37adfd64e..fafb91b2652a 100644 --- a/code/core/src/cli/eslintPlugin.ts +++ b/code/core/src/cli/eslintPlugin.ts @@ -72,7 +72,7 @@ export async function configureEslintPlugin( if (eslintFile) { paddedLog(`Configuring Storybook ESLint plugin at ${eslintFile}`); if (eslintFile.endsWith('json')) { - const eslintConfig = JSON.parse(await readFile(eslintFile, { encoding: 'utf-8' })) as { + const eslintConfig = JSON.parse(await readFile(eslintFile, { encoding: 'utf8' })) as { extends?: string[]; }; const existingExtends = normalizeExtends(eslintConfig.extends).filter(Boolean); diff --git a/code/core/src/common/utils/get-storybook-refs.ts b/code/core/src/common/utils/get-storybook-refs.ts index c733febf6aea..4108ba969412 100644 --- a/code/core/src/common/utils/get-storybook-refs.ts +++ b/code/core/src/common/utils/get-storybook-refs.ts @@ -16,7 +16,7 @@ export const getAutoRefs = async (options: Options): Promise const directory = dirname(location); const { dependencies = [], devDependencies = [] } = - JSON.parse(await readFile(location, { encoding: 'utf-8' })) || {}; + JSON.parse(await readFile(location, { encoding: 'utf8' })) || {}; const deps = Object.keys({ ...dependencies, ...devDependencies }); const list = await Promise.all( @@ -25,7 +25,7 @@ export const getAutoRefs = async (options: Options): Promise const l = resolveFrom(directory, join(d, 'package.json')); const { storybook, name, version } = - JSON.parse(await readFile(l, { encoding: 'utf-8' })) || {}; + JSON.parse(await readFile(l, { encoding: 'utf8' })) || {}; if (storybook?.url) { return { id: name, ...storybook, version }; diff --git a/code/core/src/core-server/build-dev.ts b/code/core/src/core-server/build-dev.ts index 2f1e08703a21..c8b336a22e09 100644 --- a/code/core/src/core-server/build-dev.ts +++ b/code/core/src/core-server/build-dev.ts @@ -156,7 +156,7 @@ export async function buildDevStandalone( if (/\.c[jt]s$/.test(mainJsPath)) { deprecate(deprecationMessage); } - const mainJsContent = await readFile(mainJsPath, { encoding: 'utf-8' }); + const mainJsContent = await readFile(mainJsPath, { encoding: 'utf8' }); // Regex that matches any CommonJS-specific syntax, stolen from Vite: https://github.com/vitejs/vite/blob/91a18c2f7da796ff8217417a4bf189ddda719895/packages/vite/src/node/ssr/ssrExternal.ts#L87 const CJS_CONTENT_REGEX = /\bmodule\.exports\b|\bexports[.[]|\brequire\s*\(|\bObject\.(?:defineProperty|defineProperties|assign)\s*\(\s*exports\b/; diff --git a/code/core/src/core-server/presets/common-preset.ts b/code/core/src/core-server/presets/common-preset.ts index 9a32187c11f6..10f8be05614f 100644 --- a/code/core/src/core-server/presets/common-preset.ts +++ b/code/core/src/core-server/presets/common-preset.ts @@ -258,7 +258,7 @@ export const docs: PresetProperty<'docs'> = (docsOptions, { docs: docsMode }: CL export const managerHead = async (_: any, options: Options) => { const location = join(options.configDir, 'manager-head.html'); if (existsSync(location)) { - const contents = readFile(location, { encoding: 'utf-8' }); + const contents = readFile(location, { encoding: 'utf8' }); const interpolations = options.presets.apply>('env'); return interpolate(await contents, await interpolations); diff --git a/code/core/src/core-server/utils/StoryIndexGenerator.ts b/code/core/src/core-server/utils/StoryIndexGenerator.ts index b3bc3239b983..fb2a0f4b887c 100644 --- a/code/core/src/core-server/utils/StoryIndexGenerator.ts +++ b/code/core/src/core-server/utils/StoryIndexGenerator.ts @@ -756,7 +756,7 @@ export class StoryIndexGenerator { .map((ext) => join(this.options.configDir, `preview.${ext}`)) .find((fname) => existsSync(fname)); - return previewFile && (await readFile(previewFile, { encoding: 'utf-8' })).toString(); + return previewFile && (await readFile(previewFile, { encoding: 'utf8' })).toString(); } getProjectTags(previewCode?: string) { diff --git a/code/core/src/core-server/utils/server-init.ts b/code/core/src/core-server/utils/server-init.ts index cdcd3c920543..60710cb19cfb 100644 --- a/code/core/src/core-server/utils/server-init.ts +++ b/code/core/src/core-server/utils/server-init.ts @@ -30,9 +30,9 @@ export async function getServer( } const sslOptions = { - ca: await Promise.all((options.sslCa || []).map((ca) => readFile(ca, { encoding: 'utf-8' }))), - cert: await readFile(options.sslCert, { encoding: 'utf-8' }), - key: await readFile(options.sslKey, { encoding: 'utf-8' }), + ca: await Promise.all((options.sslCa || []).map((ca) => readFile(ca, { encoding: 'utf8' }))), + cert: await readFile(options.sslCert, { encoding: 'utf8' }), + key: await readFile(options.sslKey, { encoding: 'utf8' }), }; return https.createServer(sslOptions, app); diff --git a/code/core/src/telemetry/get-monorepo-type.ts b/code/core/src/telemetry/get-monorepo-type.ts index e004365fcbb0..143241b0a214 100644 --- a/code/core/src/telemetry/get-monorepo-type.ts +++ b/code/core/src/telemetry/get-monorepo-type.ts @@ -36,7 +36,7 @@ export const getMonorepoType = (): MonorepoType => { } const packageJson = JSON.parse( - readFileSync(join(projectRootPath, 'package.json'), { encoding: 'utf-8' }) + readFileSync(join(projectRootPath, 'package.json'), { encoding: 'utf8' }) ) as PackageJson; if (packageJson?.workspaces) { diff --git a/code/core/src/telemetry/package-json.ts b/code/core/src/telemetry/package-json.ts index 3bf006115c4a..4104425fc2d0 100644 --- a/code/core/src/telemetry/package-json.ts +++ b/code/core/src/telemetry/package-json.ts @@ -24,6 +24,6 @@ export const getActualPackageJson = async (packageName: string) => { const resolvedPackageJson = require.resolve(join(packageName, 'package.json'), { paths: [process.cwd()], }); - const packageJson = JSON.parse(await readFile(resolvedPackageJson, { encoding: 'utf-8' })); + const packageJson = JSON.parse(await readFile(resolvedPackageJson, { encoding: 'utf8' })); return packageJson; }; diff --git a/code/lib/cli-storybook/src/automigrate/fixes/mdx-1-to-3.ts b/code/lib/cli-storybook/src/automigrate/fixes/mdx-1-to-3.ts index d5277a5e8c92..bf87b5fed74f 100644 --- a/code/lib/cli-storybook/src/automigrate/fixes/mdx-1-to-3.ts +++ b/code/lib/cli-storybook/src/automigrate/fixes/mdx-1-to-3.ts @@ -74,7 +74,7 @@ export const mdx1to3: Fix = { async run({ result: { storiesMdxFiles }, dryRun }) { await Promise.all([ ...storiesMdxFiles.map(async (fname) => { - const contents = await readFile(fname, { encoding: 'utf-8' }); + const contents = await readFile(fname, { encoding: 'utf8' }); const updated = fixMdxComments(fixMdxStyleTags(contents)); if (updated === contents) { logger.info(`🆗 Unmodified ${basename(fname)}`); diff --git a/code/lib/cli-storybook/src/link.ts b/code/lib/cli-storybook/src/link.ts index a858847e26b2..8f0b2ed6718c 100644 --- a/code/lib/cli-storybook/src/link.ts +++ b/code/lib/cli-storybook/src/link.ts @@ -61,7 +61,7 @@ export const exec = async ( export const link = async ({ target, local, start }: LinkOptions) => { const storybookDir = process.cwd(); try { - const packageJson = JSON.parse(await readFile('package.json', { encoding: 'utf-8' })); + const packageJson = JSON.parse(await readFile('package.json', { encoding: 'utf8' })); if (packageJson.name !== '@storybook/root') { throw new Error(); } @@ -87,7 +87,7 @@ export const link = async ({ target, local, start }: LinkOptions) => { } const reproPackageJson = JSON.parse( - await readFile(join(reproDir, 'package.json'), { encoding: 'utf-8' }) + await readFile(join(reproDir, 'package.json'), { encoding: 'utf8' }) ); const version = spawnSync('yarn', ['--version'], { diff --git a/code/renderers/server/src/preset.ts b/code/renderers/server/src/preset.ts index 04937a0b8c14..197a303bb15b 100644 --- a/code/renderers/server/src/preset.ts +++ b/code/renderers/server/src/preset.ts @@ -18,7 +18,7 @@ export const experimental_indexers: PresetProperty<'experimental_indexers'> = ( { test: /(stories|story)\.(json|ya?ml)$/, createIndex: async (fileName) => { - const rawFile = await readFile(fileName, { encoding: 'utf-8' }); + const rawFile = await readFile(fileName, { encoding: 'utf8' }); const content: FileContent = fileName.endsWith('.json') ? JSON.parse(rawFile) : yaml.parse(rawFile); From 8346b85a547f37c801d68e3df9a6c5c0a70b8f43 Mon Sep 17 00:00:00 2001 From: ziebam Date: Mon, 16 Sep 2024 20:25:26 +0200 Subject: [PATCH 24/28] Remove the TODOs regarding `fsPromises.cp` --- code/builders/builder-vite/src/index.ts | 2 -- code/builders/builder-webpack5/src/index.ts | 2 -- code/core/src/builder-manager/index.ts | 2 -- code/core/src/cli/helpers.ts | 4 ---- code/core/src/core-server/build-static.ts | 2 -- code/core/src/core-server/utils/copy-all-static-files.ts | 2 -- 6 files changed, 14 deletions(-) diff --git a/code/builders/builder-vite/src/index.ts b/code/builders/builder-vite/src/index.ts index cdd4ed972eef..0962e1676e4e 100644 --- a/code/builders/builder-vite/src/index.ts +++ b/code/builders/builder-vite/src/index.ts @@ -84,8 +84,6 @@ export const build: ViteBuilder['build'] = async ({ options }) => { const previewDirOrigin = previewResolvedDir; const previewDirTarget = join(options.outputDir || '', `sb-preview`); - // TODO: `fsPromises.cp` is marked as experimental in Node 16-21. Ask in the PR whether we should - // use it anyway or stick to `fs-extra` for now. const previewFiles = cp(previewDirOrigin, previewDirTarget, { filter: (src) => { const { ext } = parse(src); diff --git a/code/builders/builder-webpack5/src/index.ts b/code/builders/builder-webpack5/src/index.ts index bf87a0700131..ffc11812562b 100644 --- a/code/builders/builder-webpack5/src/index.ts +++ b/code/builders/builder-webpack5/src/index.ts @@ -292,8 +292,6 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, const previewDirOrigin = previewResolvedDir; const previewDirTarget = join(options.outputDir || '', `sb-preview`); - // TODO: `fsPromises.cp` is marked as experimental in Node 16-21. Ask in the PR whether we should - // use it anyway or stick to `fs-extra` for now. const previewFiles = cp(previewDirOrigin, previewDirTarget, { filter: (src) => { const { ext } = parse(src); diff --git a/code/core/src/builder-manager/index.ts b/code/core/src/builder-manager/index.ts index a1589af27b23..b068caf206c7 100644 --- a/code/core/src/builder-manager/index.ts +++ b/code/core/src/builder-manager/index.ts @@ -256,8 +256,6 @@ const builder: BuilderFunction = async function* builderGeneratorFn({ startTime, yield; - // TODO: `fsPromises.cp` is marked as experimental in Node 16-21. Ask in the PR whether we should - // use it anyway or stick to `fs-extra` for now. const managerFiles = cp(coreDirOrigin, coreDirTarget, { filter: (src) => { const { ext } = parse(src); diff --git a/code/core/src/cli/helpers.ts b/code/core/src/cli/helpers.ts index 1b1448e7b469..5f567ebe5cbc 100644 --- a/code/core/src/cli/helpers.ts +++ b/code/core/src/cli/helpers.ts @@ -130,8 +130,6 @@ export function copyTemplate(templateRoot: string, destination = '.') { throw new Error(`Couldn't find template dir`); } - // TODO: `fsPromises.cpSync` is marked as experimental in Node 16-21. Ask in the PR whether we - // should use it anyway or stick to `fs-extra` for now. cpSync(templateDir, destination, { recursive: true }); } @@ -229,8 +227,6 @@ export async function copyTemplateFiles({ }; const destinationPath = destination ?? (await targetPath()); - // TODO: `fsPromises.cp` is marked as experimental in Node 16-21. Ask in the PR whether we should - // use it anyway or stick to `fs-extra` for now. if (commonAssetsDir) { await cp(commonAssetsDir, destinationPath, { recursive: true, diff --git a/code/core/src/core-server/build-static.ts b/code/core/src/core-server/build-static.ts index d0835d0d8a67..f1c9f9625910 100644 --- a/code/core/src/core-server/build-static.ts +++ b/code/core/src/core-server/build-static.ts @@ -132,8 +132,6 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption dirname(require.resolve('@storybook/core/package.json')), 'assets/browser' ); - // TODO: `fsPromises.cp` is marked as experimental in Node 16-21. Ask in the PR whether we should - // use it anyway or stick to `fs-extra` for now. effects.push(cp(coreServerPublicDir, options.outputDir, { recursive: true })); let initializedStoryIndexGenerator: Promise = diff --git a/code/core/src/core-server/utils/copy-all-static-files.ts b/code/core/src/core-server/utils/copy-all-static-files.ts index f0b3d331033d..66a0194cfd94 100644 --- a/code/core/src/core-server/utils/copy-all-static-files.ts +++ b/code/core/src/core-server/utils/copy-all-static-files.ts @@ -26,8 +26,6 @@ export async function copyAllStaticFiles(staticDirs: any[] | undefined, outputDi // Storybook's own files should not be overwritten, so we skip such files if we find them const skipPaths = ['index.html', 'iframe.html'].map((f) => join(targetPath, f)); - // TODO: `fsPromises.cp` is marked as experimental in Node 16-21. Ask in the PR whether we should - // use it anyway or stick to `fs-extra` for now. await cp(staticPath, targetPath, { dereference: true, preserveTimestamps: true, From f0593f9e9ef1de714ce91400eab373703bfee6fc Mon Sep 17 00:00:00 2001 From: ziebam Date: Mon, 16 Sep 2024 21:49:11 +0200 Subject: [PATCH 25/28] Rewrite how `ensureFile` is replaced --- .../builder-vite/src/plugins/external-globals-plugin.ts | 9 +++++---- code/core/scripts/helpers/generateTypesMapperFiles.ts | 9 +++++---- code/core/src/builder-manager/utils/files.ts | 9 +++++---- code/core/src/builder-manager/utils/managerEntries.ts | 9 +++++---- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/code/builders/builder-vite/src/plugins/external-globals-plugin.ts b/code/builders/builder-vite/src/plugins/external-globals-plugin.ts index 6477ade92b4a..d6767372a64f 100644 --- a/code/builders/builder-vite/src/plugins/external-globals-plugin.ts +++ b/code/builders/builder-vite/src/plugins/external-globals-plugin.ts @@ -1,6 +1,6 @@ -import { closeSync, existsSync, openSync } from 'node:fs'; -import { writeFile } from 'node:fs/promises'; -import { join } from 'node:path'; +import { existsSync } from 'node:fs'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; import { init, parse } from 'es-module-lexer'; import findCacheDirectory from 'find-cache-dir'; @@ -61,7 +61,8 @@ export async function externalGlobalsPlugin(externals: Record) { const externalCachePath = join(cachePath, `${externalKey}.js`); newAlias.push({ find: new RegExp(`^${externalKey}$`), replacement: externalCachePath }); if (!existsSync(externalCachePath)) { - closeSync(openSync(externalCachePath, 'w')); + const directory = dirname(externalCachePath); + await mkdir(directory, { recursive: true }); } await writeFile(externalCachePath, `module.exports = ${externals[externalKey]};`); }) diff --git a/code/core/scripts/helpers/generateTypesMapperFiles.ts b/code/core/scripts/helpers/generateTypesMapperFiles.ts index 11845a5e3239..5283321c84c0 100644 --- a/code/core/scripts/helpers/generateTypesMapperFiles.ts +++ b/code/core/scripts/helpers/generateTypesMapperFiles.ts @@ -1,6 +1,6 @@ -import { closeSync, existsSync, openSync } from 'node:fs'; -import { writeFile } from 'node:fs/promises'; -import { join, relative } from 'node:path'; +import { existsSync } from 'node:fs'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { dirname, join, relative } from 'node:path'; import { dedent } from '../../../../scripts/prepare/tools'; import type { getEntries } from '../entries'; @@ -34,7 +34,8 @@ export async function generateTypesMapperFiles(entries: ReturnType { const location = filePath.replace('src', 'dist').replace(/\.tsx?/, '.d.ts'); if (!existsSync(location)) { - closeSync(openSync(location, 'w')); + const directory = dirname(location); + await mkdir(directory, { recursive: true }); } await writeFile(location, await generateTypesMapperContent(filePath)); }) diff --git a/code/core/src/builder-manager/utils/files.ts b/code/core/src/builder-manager/utils/files.ts index b83cb2e7a13e..352d558dd981 100644 --- a/code/core/src/builder-manager/utils/files.ts +++ b/code/core/src/builder-manager/utils/files.ts @@ -1,6 +1,6 @@ -import { closeSync, existsSync, openSync } from 'node:fs'; -import { writeFile } from 'node:fs/promises'; -import { join, normalize } from 'node:path'; +import { existsSync } from 'node:fs'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { dirname, join, normalize } from 'node:path'; import type { OutputFile } from 'esbuild'; import slash from 'slash'; @@ -17,7 +17,8 @@ export async function readOrderedFiles( const { location, url } = sanitizePath(file, addonsDir); if (!existsSync(location)) { - closeSync(openSync(location, 'w')); + const directory = dirname(location); + await mkdir(directory, { recursive: true }); } await writeFile(location, file.contents); return url; diff --git a/code/core/src/builder-manager/utils/managerEntries.ts b/code/core/src/builder-manager/utils/managerEntries.ts index 3cb2f1d3aea9..787116d05c0e 100644 --- a/code/core/src/builder-manager/utils/managerEntries.ts +++ b/code/core/src/builder-manager/utils/managerEntries.ts @@ -1,6 +1,6 @@ -import { closeSync, existsSync, openSync } from 'node:fs'; -import { writeFile } from 'node:fs/promises'; -import { join, parse, relative, sep } from 'node:path'; +import { existsSync } from 'node:fs'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { dirname, join, parse, relative, sep } from 'node:path'; import { resolvePathInStorybookCache } from '@storybook/core/common'; @@ -57,7 +57,8 @@ export async function wrapManagerEntries(entrypoints: string[], uniqueId?: strin ); if (!existsSync(location)) { - closeSync(openSync(location, 'w')); + const directory = dirname(location); + await mkdir(directory, { recursive: true }); } await writeFile(location, `import '${slash(entry)}';`); From ea609bfdadefc78d4f241b38166c83c407c2ba40 Mon Sep 17 00:00:00 2001 From: ziebam Date: Mon, 16 Sep 2024 22:10:51 +0200 Subject: [PATCH 26/28] Remove `fs-extra` `from lib/cli/scripts` --- code/lib/cli/scripts/update-core-portal.ts | 11 +++++++---- code/lib/cli/scripts/utils.ts | 10 ++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/code/lib/cli/scripts/update-core-portal.ts b/code/lib/cli/scripts/update-core-portal.ts index b5896549366d..520bc3587848 100644 --- a/code/lib/cli/scripts/update-core-portal.ts +++ b/code/lib/cli/scripts/update-core-portal.ts @@ -1,7 +1,6 @@ +import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { readJSON } from 'fs-extra'; - import { sortPackageJson } from '../../../../scripts/node_modules/sort-package-json'; import { generateMapperContent, mapCoreExportToSelf, write } from './utils'; @@ -22,8 +21,12 @@ import { generateMapperContent, mapCoreExportToSelf, write } from './utils'; * remove those manually here. */ async function run() { - const selfPackageJson = await readJSON(join(__dirname, '../package.json')); - const corePackageJson = await readJSON(join(__dirname, '../../../core/package.json')); + const selfPackageJson = JSON.parse( + await readFile(join(__dirname, '../package.json'), { encoding: 'utf8' }) + ); + const corePackageJson = await JSON.parse( + await readFile(join(__dirname, '../../../core/package.json'), { encoding: 'utf8' }) + ); await Promise.all( Object.entries>(corePackageJson.exports) diff --git a/code/lib/cli/scripts/utils.ts b/code/lib/cli/scripts/utils.ts index e453988fad64..1c27080075c5 100644 --- a/code/lib/cli/scripts/utils.ts +++ b/code/lib/cli/scripts/utils.ts @@ -1,8 +1,14 @@ -import { ensureFile, writeFile } from 'fs-extra'; +import { existsSync } from 'node:fs'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { dirname } from 'node:path'; + import { dedent } from 'ts-dedent'; export const write = async (location: string, data: string) => { - await ensureFile(location); + if (!existsSync(location)) { + const directory = dirname(location); + await mkdir(directory, { recursive: true }); + } return writeFile(location, data); }; From b034203200861887eb5d8f0eba28b30047f7a2c7 Mon Sep 17 00:00:00 2001 From: ziebam Date: Mon, 16 Sep 2024 22:23:35 +0200 Subject: [PATCH 27/28] Fix the `emptyDir` + `ensureDir` replacement --- code/core/src/core-server/build-static.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/code/core/src/core-server/build-static.ts b/code/core/src/core-server/build-static.ts index f1c9f9625910..45ba7c7bc6d3 100644 --- a/code/core/src/core-server/build-static.ts +++ b/code/core/src/core-server/build-static.ts @@ -1,3 +1,4 @@ +import { existsSync } from 'node:fs'; import { cp, mkdir, readdir } from 'node:fs/promises'; import { rm } from 'node:fs/promises'; import { dirname, join, relative, resolve } from 'node:path'; @@ -45,8 +46,18 @@ export async function buildStaticStandalone(options: BuildStaticStandaloneOption throw new Error("Won't remove directory '/'. Check your outputDir!"); } - const outputDirFiles = await readdir(options.outputDir); - if (outputDirFiles.length > 0) { + try { + const outputDirFiles = await readdir(options.outputDir); + for (const file of outputDirFiles) { + await rm(file, { recursive: true, force: true }); + } + } catch { + await mkdir(options.outputDir, { recursive: true }); + } + + if (!existsSync(options.outputDir)) { + await mkdir(options.outputDir, { recursive: true }); + } else if ((await readdir(options.outputDir)).length > 0) { await rm(options.outputDir, { recursive: true, force: true }); await mkdir(options.outputDir, { recursive: true }); } From 8115974dbd56af2de7a7c9ffa3b91583a7b7d9b2 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Tue, 17 Sep 2024 10:19:22 +0200 Subject: [PATCH 28/28] remove @types/fs-extra --- code/lib/cli-storybook/src/sandbox.ts | 2 +- code/package.json | 1 - code/yarn.lock | 11 ----------- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/code/lib/cli-storybook/src/sandbox.ts b/code/lib/cli-storybook/src/sandbox.ts index 4bbe097d7d16..9b02ca82dd4e 100644 --- a/code/lib/cli-storybook/src/sandbox.ts +++ b/code/lib/cli-storybook/src/sandbox.ts @@ -207,7 +207,7 @@ export const sandbox = async ({ force: true, dir: templateDestination, }); - // throw an error if templateDestination is an empty directory using fs-extra + // throw an error if templateDestination is an empty directory if ((await readdir(templateDestination)).length === 0) { const selected = chalk.yellow(templateId); throw new Error(dedent` diff --git a/code/package.json b/code/package.json index 000f4d270a0b..af2a344764ec 100644 --- a/code/package.json +++ b/code/package.json @@ -168,7 +168,6 @@ "@testing-library/user-event": "^14.5.2", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/express": "^4.17.21", - "@types/fs-extra": "^11.0.1", "@types/lodash": "^4.14.167", "@types/mock-require": "^2.0.3", "@types/node": "^22.0.0", diff --git a/code/yarn.lock b/code/yarn.lock index f4822025b30a..33d11fccd4f5 100644 --- a/code/yarn.lock +++ b/code/yarn.lock @@ -6867,7 +6867,6 @@ __metadata: "@testing-library/user-event": "npm:^14.5.2" "@trivago/prettier-plugin-sort-imports": "npm:^4.3.0" "@types/express": "npm:^4.17.21" - "@types/fs-extra": "npm:^11.0.1" "@types/lodash": "npm:^4.14.167" "@types/mock-require": "npm:^2.0.3" "@types/node": "npm:^22.0.0" @@ -7855,16 +7854,6 @@ __metadata: languageName: node linkType: hard -"@types/fs-extra@npm:^11.0.1": - version: 11.0.4 - resolution: "@types/fs-extra@npm:11.0.4" - dependencies: - "@types/jsonfile": "npm:*" - "@types/node": "npm:*" - checksum: 10c0/9e34f9b24ea464f3c0b18c3f8a82aefc36dc524cc720fc2b886e5465abc66486ff4e439ea3fb2c0acebf91f6d3f74e514f9983b1f02d4243706bdbb7511796ad - languageName: node - linkType: hard - "@types/fs-extra@npm:^5.0.5": version: 5.1.0 resolution: "@types/fs-extra@npm:5.1.0"