diff --git a/scripts/bench/utils.ts b/scripts/bench/utils.ts index 57f53ebc56ac..2648ae91943b 100644 --- a/scripts/bench/utils.ts +++ b/scripts/bench/utils.ts @@ -1,6 +1,6 @@ /// -// eslint-disable-next-line depend/ban-dependencies -import { ensureDir, readJSON, readdir, writeJSON } from 'fs-extra'; +import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises'; + import { join } from 'path'; import type { Page } from 'playwright-core'; @@ -19,17 +19,23 @@ export const saveBench = async ( ) => { const dirName = join(options.rootDir || process.cwd(), 'bench'); const fileName = `${key}.json`; - const existing = await ensureDir(dirName).then(() => { - return readJSON(join(dirName, fileName)).catch(() => ({})); - }); - await writeJSON(join(dirName, fileName), { ...existing, ...data }, { spaces: 2 }); + await mkdir(dirName, { recursive: true }); + + const filePath = join(dirName, fileName); + const existing = await readFile(filePath, 'utf8') + .then((txt) => JSON.parse(txt)) + .catch(() => ({})); + + const merged = { ...existing, ...data }; + await writeFile(filePath, JSON.stringify(merged, null, 2), 'utf8'); }; export const loadBench = async (options: SaveBenchOptions): Promise> => { const dirName = join(options.rootDir || process.cwd(), 'bench'); const files = await readdir(dirName); return files.reduce(async (acc, fileName) => { - const data = await readJSON(join(dirName, fileName)); + const content = await readFile(join(dirName, fileName), 'utf8'); + const data = JSON.parse(content); return { ...(await acc), ...data }; }, Promise.resolve({})); // return readJSON(join(dirname, `bench.json`)); diff --git a/scripts/build-package.ts b/scripts/build-package.ts index 29531120d441..e04a01bed21e 100644 --- a/scripts/build-package.ts +++ b/scripts/build-package.ts @@ -11,11 +11,10 @@ * * When you pass no package names, you will be prompted to select which packages to build. */ +import { readFile } from 'node:fs/promises'; + import { exec } from 'child_process'; import { program } from 'commander'; -// eslint-disable-next-line depend/ban-dependencies -// eslint-disable-next-line depend/ban-dependencies -import { readJSON } from 'fs-extra'; import { posix, resolve, sep } from 'path'; import picocolors from 'picocolors'; import prompts from 'prompts'; @@ -166,9 +165,8 @@ async function run() { let lastName = ''; selection.forEach(async (v) => { - const command = (await readJSON(resolve('../code', v.location, 'package.json'))).scripts?.prep - .split(posix.sep) - .join(sep); + const content = await readFile(resolve('../code', v.location, 'package.json'), 'utf-8'); + const command = JSON.parse(content).scripts?.prep.split(posix.sep).join(sep); if (!command) { console.log(`No prep script found for ${v.name}`); diff --git a/scripts/check-package.ts b/scripts/check-package.ts index 035ae8a74ec6..4d683da69c35 100644 --- a/scripts/check-package.ts +++ b/scripts/check-package.ts @@ -1,11 +1,11 @@ // This script makes sure that we can support type checking, // without having to build dts files for all packages in the monorepo. // It is not implemented yet for angular, svelte and vue. +import { readFile } from 'node:fs/promises'; + import { program } from 'commander'; // eslint-disable-next-line depend/ban-dependencies import { execaCommand } from 'execa'; -// eslint-disable-next-line depend/ban-dependencies -import { readJSON } from 'fs-extra'; import { resolve } from 'path'; import picocolors from 'picocolors'; import prompts from 'prompts'; @@ -113,7 +113,8 @@ async function run() { } selection?.filter(Boolean).forEach(async (v) => { - const command = (await readJSON(resolve('../code', v.location, 'package.json'))).scripts.check; + const content = await readFile(resolve('../code', v.location, 'package.json'), 'utf-8'); + const command = JSON.parse(content).scripts.check; const cwd = resolve(__dirname, '..', 'code', v.location); const sub = execaCommand(`${command}${watchMode ? ' --watch' : ''}`, { cwd, diff --git a/scripts/combine-compodoc.ts b/scripts/combine-compodoc.ts index 8dd049bdaf30..dfb1cebac851 100755 --- a/scripts/combine-compodoc.ts +++ b/scripts/combine-compodoc.ts @@ -1,11 +1,11 @@ // Compodoc does not follow symlinks (it ignores them and their contents entirely) // So, we need to run a separate compodoc process on every symlink inside the project, // then combine the results into one large documentation.json +import { lstat, readFile, realpath, writeFile } from 'node:fs/promises'; + // eslint-disable-next-line depend/ban-dependencies import { execaCommand } from 'execa'; // eslint-disable-next-line depend/ban-dependencies -import { lstat, readFile, realpath, writeFile } from 'fs-extra'; -// eslint-disable-next-line depend/ban-dependencies import { globSync } from 'glob'; import { join, resolve } from 'path'; diff --git a/scripts/get-report-message.ts b/scripts/get-report-message.ts index 60f556b91014..9e931bc12a30 100644 --- a/scripts/get-report-message.ts +++ b/scripts/get-report-message.ts @@ -1,7 +1,7 @@ +import { readFile } from 'node:fs/promises'; + // eslint-disable-next-line depend/ban-dependencies import { execaCommand } from 'execa'; -// eslint-disable-next-line depend/ban-dependencies -import { readJson } from 'fs-extra'; import { join } from 'path'; import { CODE_DIRECTORY } from './utils/constants'; @@ -17,7 +17,8 @@ const getFooter = async (branch: Branch, workflow: Workflow, job: string) => { // The CI workflows can run on release branches and we should display the version number if (branch === 'next-release' || branch === 'latest-release') { - const packageJson = await readJson(join(CODE_DIRECTORY, 'package.json')); + const content = await readFile(join(CODE_DIRECTORY, 'package.json'), 'utf8'); + const packageJson = JSON.parse(content); // running in alpha branch we should just show the version which failed return `\n**Version: ${packageJson.version}**`; diff --git a/scripts/get-template.ts b/scripts/get-template.ts index cb351eab0410..edba3a3b071f 100644 --- a/scripts/get-template.ts +++ b/scripts/get-template.ts @@ -1,7 +1,6 @@ +import { access, readFile, readdir } from 'node:fs/promises'; + import { program } from 'commander'; -// eslint-disable-next-line depend/ban-dependencies -import { pathExists, readFile } from 'fs-extra'; -import { readdir } from 'fs/promises'; import picocolors from 'picocolors'; import { dedent } from 'ts-dedent'; import yaml from 'yaml'; @@ -28,6 +27,15 @@ async function getDirectories(source: string) { .map((entry) => entry.name); } +async function pathExists(path: string) { + try { + await access(path); + return true; + } catch { + return false; + } +} + export async function getTemplate( cadence: Cadence, scriptName: string, @@ -62,12 +70,12 @@ export async function getTemplate( if (potentialTemplateKeys.length !== total) { throw new Error(dedent`Circle parallelism set incorrectly. - + Parallelism is set to ${total}, but there are ${ potentialTemplateKeys.length } templates to run for the "${scriptName}" task: ${potentialTemplateKeys.map((v) => `- ${v}`).join('\n')} - + ${await checkParallelism(cadence)} `); } @@ -176,7 +184,7 @@ async function run({ cadence, task, check }: RunOptions) { if (check) { if (task && !tasks.includes(task)) { throw new Error( - dedent`The "${task}" task you provided is not valid. Valid tasks (found in .circleci/config.yml) are: + dedent`The "${task}" task you provided is not valid. Valid tasks (found in .circleci/config.yml) are: ${tasks.map((v) => `- ${v}`).join('\n')}` ); } diff --git a/scripts/package.json b/scripts/package.json index a2fcf6c82507..307b8c252be3 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -76,7 +76,6 @@ "@types/detect-port": "^1.3.5", "@types/ejs": "^3.1.5", "@types/escodegen": "^0.0.6", - "@types/fs-extra": "^11.0.4", "@types/http-server": "^0.12.4", "@types/jest": "^29.5.12", "@types/node": "^22.0.0", @@ -128,7 +127,6 @@ "fast-folder-size": "^2.2.0", "fast-glob": "^3.3.2", "find-up": "^5.0.0", - "fs-extra": "^11.2.0", "github-release-from-changelog": "^2.1.1", "glob": "^10.4.5", "http-server": "^14.1.1", diff --git a/scripts/release/__tests__/is-pr-frozen.test.ts b/scripts/release/__tests__/is-pr-frozen.test.ts index 2aa12c56855f..575c7ba4001d 100644 --- a/scripts/release/__tests__/is-pr-frozen.test.ts +++ b/scripts/release/__tests__/is-pr-frozen.test.ts @@ -1,12 +1,11 @@ +import * as fspImp from 'node:fs/promises'; import { join } from 'node:path'; import { describe, expect, it, vi } from 'vitest'; -// eslint-disable-next-line depend/ban-dependencies -import * as fsExtraImp from 'fs-extra'; import * as simpleGitImp from 'simple-git'; -import type * as MockedFSExtra from '../../../code/__mocks__/fs-extra'; +import type * as MockedFSPExtra from '../../../code/__mocks__/fs/promises'; import type * as MockedSimpleGit from '../../__mocks__/simple-git'; import { CODE_DIRECTORY } from '../../utils/constants'; import { run as isPrFrozen } from '../is-pr-frozen'; @@ -15,13 +14,13 @@ import { getPullInfoFromCommit } from '../utils/get-github-info'; vi.mock('../utils/get-github-info'); vi.mock('simple-git'); -vi.mock('fs-extra', async () => import('../../../code/__mocks__/fs-extra')); -const fsExtra = fsExtraImp as unknown as typeof MockedFSExtra; +vi.mock('node:fs/promises', async () => import('../../../code/__mocks__/fs/promises')); +const fsp = fspImp as unknown as typeof MockedFSPExtra; const simpleGit = simpleGitImp as unknown as typeof MockedSimpleGit; const CODE_PACKAGE_JSON_PATH = join(CODE_DIRECTORY, 'package.json'); -fsExtra.__setMockFiles({ +fsp.__setMockFiles({ [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), }); diff --git a/scripts/release/__tests__/version.test.ts b/scripts/release/__tests__/version.test.ts index b1890f39f26f..ba71e024a56e 100644 --- a/scripts/release/__tests__/version.test.ts +++ b/scripts/release/__tests__/version.test.ts @@ -1,17 +1,16 @@ +import * as fspImp from 'node:fs/promises'; import { join } from 'node:path'; import { describe, expect, it, vi } from 'vitest'; // eslint-disable-next-line depend/ban-dependencies import { execaCommand } from 'execa'; -// eslint-disable-next-line depend/ban-dependencies -import * as fsExtraImp from 'fs-extra'; -import type * as MockedFSToExtra from '../../../code/__mocks__/fs-extra'; +import type * as MockedFSPToExtra from '../../../code/__mocks__/fs/promises'; import { run as version } from '../version'; -vi.mock('fs-extra', async () => import('../../../code/__mocks__/fs-extra')); -const fsExtra = fsExtraImp as unknown as typeof MockedFSToExtra; +vi.mock('node:fs/promises', async () => import('../../../code/__mocks__/fs/promises')); +const fspExtra = fspImp as unknown as typeof MockedFSPToExtra; vi.mock('../../../code/core/src/common/src/versions', () => ({ '@storybook/addon-a11y': '7.1.0-alpha.29', @@ -40,7 +39,7 @@ describe('Version', () => { const A11Y_PACKAGE_JSON_PATH = join(CODE_DIR_PATH, 'addons', 'a11y', 'package.json'); it('should throw when release type is invalid', async () => { - fsExtra.__setMockFiles({ + fspExtra.__setMockFiles({ [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), [MANAGER_API_VERSION_PATH]: `export const version = "1.0.0";`, [VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "1.0.0" };`, @@ -70,7 +69,7 @@ describe('Version', () => { }); it('should throw when prerelease identifier is combined with non-pre release type', async () => { - fsExtra.__setMockFiles({ + fspExtra.__setMockFiles({ [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), [MANAGER_API_VERSION_PATH]: `export const version = "1.0.0";`, [VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "1.0.0" };`, @@ -89,7 +88,7 @@ describe('Version', () => { }); it('should throw when exact is combined with release type', async () => { - fsExtra.__setMockFiles({ + fspExtra.__setMockFiles({ [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), [MANAGER_API_VERSION_PATH]: `export const version = "1.0.0";`, [VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "1.0.0" };`, @@ -108,7 +107,7 @@ describe('Version', () => { }); it('should throw when exact is invalid semver', async () => { - fsExtra.__setMockFiles({ + fspExtra.__setMockFiles({ [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), [MANAGER_API_VERSION_PATH]: `export const version = "1.0.0";`, [VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "1.0.0" };`, @@ -128,7 +127,7 @@ describe('Version', () => { }); it('should throw when apply is combined with releaseType', async () => { - fsExtra.__setMockFiles({ + fspExtra.__setMockFiles({ [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), [MANAGER_API_VERSION_PATH]: `export const version = "1.0.0";`, [VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "1.0.0" };`, @@ -147,7 +146,7 @@ describe('Version', () => { }); it('should throw when apply is combined with exact', async () => { - fsExtra.__setMockFiles({ + fspExtra.__setMockFiles({ [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), [MANAGER_API_VERSION_PATH]: `export const version = "1.0.0";`, [VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "1.0.0" };`, @@ -166,7 +165,7 @@ describe('Version', () => { }); it('should throw when apply is combined with deferred', async () => { - fsExtra.__setMockFiles({ + fspExtra.__setMockFiles({ [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), [MANAGER_API_VERSION_PATH]: `export const version = "1.0.0";`, [VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "1.0.0" };`, @@ -185,7 +184,7 @@ describe('Version', () => { }); it('should throw when applying without a "deferredNextVersion" set', async () => { - fsExtra.__setMockFiles({ + fspExtra.__setMockFiles({ [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), }); @@ -193,8 +192,7 @@ describe('Version', () => { `[Error: The 'deferredNextVersion' property in code/package.json is unset. This is necessary to apply a deferred version bump]` ); - expect(fsExtra.writeJson).not.toHaveBeenCalled(); - expect(fsExtra.writeFile).not.toHaveBeenCalled(); + expect(fspExtra.writeFile).not.toHaveBeenCalled(); expect(execaCommand).not.toHaveBeenCalled(); }); @@ -238,7 +236,7 @@ describe('Version', () => { expectedVersion, deferredNextVersion, }) => { - fsExtra.__setMockFiles({ + fspExtra.__setMockFiles({ [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: currentVersion, deferredNextVersion }), [MANAGER_API_VERSION_PATH]: `export const version = "${currentVersion}";`, [VERSIONS_PATH]: `export default { "@storybook/addon-a11y": "${currentVersion}" };`, @@ -249,36 +247,37 @@ describe('Version', () => { }); await version({ releaseType, preId, exact, apply }); - expect(fsExtra.writeJson).toHaveBeenCalledTimes(apply ? 3 : 2); + expect(fspExtra.writeFile).toHaveBeenCalledTimes(apply ? 5 : 4); if (apply) { - expect(fsExtra.writeJson).toHaveBeenCalledWith( + expect(fspExtra.writeFile).toHaveBeenCalledWith( CODE_PACKAGE_JSON_PATH, // this call is the write that removes the "deferredNextVersion" property - { version: currentVersion }, - { spaces: 2 } + JSON.stringify({ version: currentVersion }, null, 2) ); } - expect(fsExtra.writeJson).toHaveBeenCalledWith( + expect(fspExtra.writeFile).toHaveBeenCalledWith( CODE_PACKAGE_JSON_PATH, - { version: expectedVersion }, - { spaces: 2 } + JSON.stringify({ version: expectedVersion }, null, 2) ); - expect(fsExtra.writeFile).toHaveBeenCalledWith( + expect(fspExtra.writeFile).toHaveBeenCalledWith( MANAGER_API_VERSION_PATH, `export const version = "${expectedVersion}";` ); - expect(fsExtra.writeFile).toHaveBeenCalledWith( + expect(fspExtra.writeFile).toHaveBeenCalledWith( VERSIONS_PATH, `export default { "@storybook/addon-a11y": "${expectedVersion}" };` ); - expect(fsExtra.writeJson).toHaveBeenCalledWith( + expect(fspExtra.writeFile).toHaveBeenCalledWith( A11Y_PACKAGE_JSON_PATH, - expect.objectContaining({ - // should update package version - version: expectedVersion, - }), - { spaces: 2 } + JSON.stringify( + { + // should update package version + version: expectedVersion, + }, + null, + 2 + ) ); expect(execaCommand).toHaveBeenCalledWith('yarn install --mode=update-lockfile', { cwd: join(CODE_DIR_PATH), @@ -289,19 +288,17 @@ describe('Version', () => { ); it('should only set version in "deferredNextVersion" when using --deferred', async () => { - fsExtra.__setMockFiles({ + fspExtra.__setMockFiles({ [CODE_PACKAGE_JSON_PATH]: JSON.stringify({ version: '1.0.0' }), }); await version({ releaseType: 'premajor', preId: 'beta', deferred: true }); - expect(fsExtra.writeJson).toHaveBeenCalledTimes(1); - expect(fsExtra.writeJson).toHaveBeenCalledWith( + expect(fspExtra.writeFile).toHaveBeenCalledTimes(1); + expect(fspExtra.writeFile).toHaveBeenCalledWith( CODE_PACKAGE_JSON_PATH, - { version: '1.0.0', deferredNextVersion: '2.0.0-beta.0' }, - { spaces: 2 } + JSON.stringify({ version: '1.0.0', deferredNextVersion: '2.0.0-beta.0' }, null, 2) ); - expect(fsExtra.writeFile).not.toHaveBeenCalled(); expect(execaCommand).not.toHaveBeenCalled(); }); }); diff --git a/scripts/release/__tests__/write-changelog.test.ts b/scripts/release/__tests__/write-changelog.test.ts index c93f4ea202e1..66a9b7d233ea 100644 --- a/scripts/release/__tests__/write-changelog.test.ts +++ b/scripts/release/__tests__/write-changelog.test.ts @@ -1,21 +1,20 @@ +import * as fspImp from 'node:fs/promises'; import { join } from 'node:path'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -// eslint-disable-next-line depend/ban-dependencies -import * as fsExtraImp from 'fs-extra'; import { dedent } from 'ts-dedent'; -import type * as MockedFSToExtra from '../../../code/__mocks__/fs-extra'; +import type * as MockedFSPToExtra from '../../../code/__mocks__/fs/promises'; import * as changesUtils_ from '../utils/get-changes'; import { run as writeChangelog } from '../write-changelog'; -vi.mock('fs-extra', async () => import('../../../code/__mocks__/fs-extra')); +vi.mock('node:fs/promises', async () => import('../../../code/__mocks__/fs/promises')); vi.mock('../utils/get-changes'); const changesUtils = vi.mocked(changesUtils_); -const fsExtra = fsExtraImp as unknown as typeof MockedFSToExtra; +const fsp = fspImp as unknown as typeof MockedFSPToExtra; beforeEach(() => { vi.restoreAllMocks(); @@ -24,7 +23,7 @@ beforeEach(() => { vi.spyOn(console, 'warn').mockImplementation(() => {}); vi.spyOn(console, 'error').mockImplementation(() => {}); - fsExtra.__setMockFiles({ + fsp.__setMockFiles({ [STABLE_CHANGELOG_PATH]: EXISTING_STABLE_CHANGELOG, [PRERELEASE_CHANGELOG_PATH]: EXISTING_PRERELEASE_CHANGELOG, }); @@ -55,9 +54,9 @@ describe('Write changelog', () => { await writeChangelog(['7.0.1'], {}); - expect(fsExtra.writeFile).toHaveBeenCalledTimes(1); - expect(fsExtra.writeFile.mock.calls[0][0]).toBe(STABLE_CHANGELOG_PATH); - expect(fsExtra.writeFile.mock.calls[0][1]).toMatchInlineSnapshot(` + expect(fsp.writeFile).toHaveBeenCalledTimes(2); + expect(fsp.writeFile.mock.calls[0][0]).toBe(STABLE_CHANGELOG_PATH); + expect(fsp.writeFile.mock.calls[0][1]).toMatchInlineSnapshot(` "## 7.0.1 - React: Make it reactive @@ -67,17 +66,10 @@ describe('Write changelog', () => { - Core: Some change" `); - expect(fsExtra.writeJson).toBeCalledTimes(1); - expect(fsExtra.writeJson.mock.calls[0][0]).toBe(LATEST_VERSION_PATH); - expect(fsExtra.writeJson.mock.calls[0][1]).toMatchInlineSnapshot(` - { - "info": { - "plain": "- React: Make it reactive - - CLI: Not UI", - }, - "version": "7.0.1", - } - `); + expect(fsp.writeFile.mock.calls[1][0]).toBe(LATEST_VERSION_PATH); + expect(fsp.writeFile.mock.calls[1][1]).toMatchInlineSnapshot( + `"{"version":"7.0.1","info":{"plain":"- React: Make it reactive\\n- CLI: Not UI"}}"` + ); }); it('should escape double quotes for json files', async () => { @@ -92,9 +84,9 @@ describe('Write changelog', () => { await writeChangelog(['7.0.1'], {}); - expect(fsExtra.writeFile).toHaveBeenCalledTimes(1); - expect(fsExtra.writeFile.mock.calls[0][0]).toBe(STABLE_CHANGELOG_PATH); - expect(fsExtra.writeFile.mock.calls[0][1]).toMatchInlineSnapshot(` + expect(fsp.writeFile).toHaveBeenCalledTimes(2); + expect(fsp.writeFile.mock.calls[0][0]).toBe(STABLE_CHANGELOG_PATH); + expect(fsp.writeFile.mock.calls[0][1]).toMatchInlineSnapshot(` "## 7.0.1 - React: Make it reactive @@ -105,21 +97,13 @@ describe('Write changelog', () => { - Core: Some change" `); - expect(fsExtra.writeJson).toBeCalledTimes(1); - expect(fsExtra.writeJson.mock.calls[0][0]).toBe(LATEST_VERSION_PATH); - expect(fsExtra.writeJson.mock.calls[0][1]).toMatchInlineSnapshot(` - { - "info": { - "plain": "- React: Make it reactive - - Revert \\"CLI: Not UI\\" - - CLI: Not UI", - }, - "version": "7.0.1", - } - `); + expect(fsp.writeFile.mock.calls[1][0]).toBe(LATEST_VERSION_PATH); + expect(fsp.writeFile.mock.calls[1][1]).toMatchInlineSnapshot( + `"{"version":"7.0.1","info":{"plain":"- React: Make it reactive\\n- Revert \\\\\\"CLI: Not UI\\\\\\"\\n- CLI: Not UI"}}"` + ); }); - it('should write to prerelase changelogs and version files in docs', async () => { + it('should write to prerelease changelogs and version files in docs', async () => { changesUtils.getChanges.mockResolvedValue({ changes: [], changelogText: `## 7.1.0-alpha.21 @@ -130,9 +114,9 @@ describe('Write changelog', () => { await writeChangelog(['7.1.0-alpha.21'], {}); - expect(fsExtra.writeFile).toHaveBeenCalledTimes(1); - expect(fsExtra.writeFile.mock.calls[0][0]).toBe(PRERELEASE_CHANGELOG_PATH); - expect(fsExtra.writeFile.mock.calls[0][1]).toMatchInlineSnapshot(` + expect(fsp.writeFile).toHaveBeenCalledTimes(2); + expect(fsp.writeFile.mock.calls[0][0]).toBe(PRERELEASE_CHANGELOG_PATH); + expect(fsp.writeFile.mock.calls[0][1]).toMatchInlineSnapshot(` "## 7.1.0-alpha.21 - React: Make it reactive @@ -142,16 +126,9 @@ describe('Write changelog', () => { - CLI: Super fast now" `); - expect(fsExtra.writeJson).toBeCalledTimes(1); - expect(fsExtra.writeJson.mock.calls[0][0]).toBe(NEXT_VERSION_PATH); - expect(fsExtra.writeJson.mock.calls[0][1]).toMatchInlineSnapshot(` - { - "info": { - "plain": "- React: Make it reactive - - CLI: Not UI", - }, - "version": "7.1.0-alpha.21", - } - `); + expect(fsp.writeFile.mock.calls[1][0]).toBe(NEXT_VERSION_PATH); + expect(fsp.writeFile.mock.calls[1][1]).toMatchInlineSnapshot( + `"{"version":"7.1.0-alpha.21","info":{"plain":"- React: Make it reactive\\n- CLI: Not UI"}}"` + ); }); }); diff --git a/scripts/release/get-changelog-from-file.ts b/scripts/release/get-changelog-from-file.ts index de8bc4e386e9..eb2c6cef41a4 100644 --- a/scripts/release/get-changelog-from-file.ts +++ b/scripts/release/get-changelog-from-file.ts @@ -1,9 +1,8 @@ +import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import { setOutput } from '@actions/core'; import { program } from 'commander'; -// eslint-disable-next-line depend/ban-dependencies -import { readFile } from 'fs-extra'; import picocolors from 'picocolors'; import semver from 'semver'; import { dedent } from 'ts-dedent'; diff --git a/scripts/release/get-current-version.ts b/scripts/release/get-current-version.ts index b0133cf03f7f..927ba698f1b9 100644 --- a/scripts/release/get-current-version.ts +++ b/scripts/release/get-current-version.ts @@ -1,8 +1,7 @@ +import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import { setOutput } from '@actions/core'; -// eslint-disable-next-line depend/ban-dependencies -import { readJson } from 'fs-extra'; import picocolors from 'picocolors'; import { esMain } from '../utils/esmain'; @@ -12,7 +11,8 @@ const CODE_PACKAGE_JSON_PATH = join(CODE_DIR_PATH, 'package.json'); export const getCurrentVersion = async () => { console.log(`📐 Reading current version of Storybook...`); - const { version } = (await readJson(CODE_PACKAGE_JSON_PATH)) as { version: string }; + const content = await readFile(CODE_PACKAGE_JSON_PATH, 'utf8'); + const { version } = JSON.parse(content) as { version: string }; if (process.env.GITHUB_ACTIONS === 'true') { setOutput('current-version', version); } diff --git a/scripts/release/is-pr-frozen.ts b/scripts/release/is-pr-frozen.ts index e198e89d8202..fce100d1f5fb 100644 --- a/scripts/release/is-pr-frozen.ts +++ b/scripts/release/is-pr-frozen.ts @@ -1,9 +1,8 @@ +import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import { setOutput } from '@actions/core'; import { program } from 'commander'; -// eslint-disable-next-line depend/ban-dependencies -import { readJson } from 'fs-extra'; import picocolors from 'picocolors'; import { esMain } from '../utils/esmain'; @@ -23,7 +22,8 @@ const CODE_PACKAGE_JSON_PATH = join(CODE_DIR_PATH, 'package.json'); const getCurrentVersion = async () => { console.log(`📐 Reading current version of Storybook...`); - const { version } = await readJson(CODE_PACKAGE_JSON_PATH); + const content = await readFile(CODE_PACKAGE_JSON_PATH, 'utf-8'); + const { version } = JSON.parse(content); return version; }; diff --git a/scripts/release/publish.ts b/scripts/release/publish.ts index 595879738b87..b4ace3a48e3f 100644 --- a/scripts/release/publish.ts +++ b/scripts/release/publish.ts @@ -1,10 +1,9 @@ +import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import { program } from 'commander'; // eslint-disable-next-line depend/ban-dependencies import { execaCommand } from 'execa'; -// eslint-disable-next-line depend/ban-dependencies -import { readJson } from 'fs-extra'; import pRetry from 'p-retry'; import picocolors from 'picocolors'; import semver from 'semver'; @@ -52,7 +51,8 @@ const getCurrentVersion = async (verbose?: boolean) => { if (verbose) { console.log(`📐 Reading current version of Storybook...`); } - const { version } = await readJson(CODE_PACKAGE_JSON_PATH); + const content = await readFile(CODE_PACKAGE_JSON_PATH, 'utf-8'); + const { version } = JSON.parse(content); console.log(`📐 Current version of Storybook is ${picocolors.green(version)}`); return version; }; diff --git a/scripts/release/version.ts b/scripts/release/version.ts index 7d18b99aeca5..a9cd2d72098c 100644 --- a/scripts/release/version.ts +++ b/scripts/release/version.ts @@ -1,11 +1,10 @@ +import { readFile, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { setOutput } from '@actions/core'; import { program } from 'commander'; // eslint-disable-next-line depend/ban-dependencies import { execaCommand } from 'execa'; -// eslint-disable-next-line depend/ban-dependencies -import { readFile, readJson, writeFile, writeJson } from 'fs-extra'; import picocolors from 'picocolors'; import semver from 'semver'; import { z } from 'zod'; @@ -111,6 +110,11 @@ const validateOptions = (options: { [key: string]: any }): options is Options => return true; }; +const readJson = async (path: string) => { + const content = await readFile(path, 'utf-8'); + return JSON.parse(content); +}; + const getCurrentVersion = async () => { console.log(`📐 Reading current version of Storybook...`); const { version } = await readJson(CODE_PACKAGE_JSON_PATH); @@ -123,7 +127,7 @@ const bumpCodeVersion = async (nextVersion: string) => { const codePkgJson = await readJson(CODE_PACKAGE_JSON_PATH); codePkgJson.version = nextVersion; - await writeJson(CODE_PACKAGE_JSON_PATH, codePkgJson, { spaces: 2 }); + await writeFile(CODE_PACKAGE_JSON_PATH, JSON.stringify(codePkgJson, null, 2)); console.log(`✅ Bumped version of ${picocolors.cyan('code')}'s package.json`); }; @@ -176,7 +180,7 @@ const bumpAllPackageJsons = async ({ ` Bumping ${picocolors.blue(pkg.name)}'s version to ${picocolors.yellow(nextVersion)}` ); } - await writeJson(packageJsonPath, packageJson, { spaces: 2 }); + await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); }) ); }; @@ -198,7 +202,7 @@ const bumpDeferred = async (nextVersion: string) => { } codePkgJson.deferredNextVersion = nextVersion; - await writeJson(CODE_PACKAGE_JSON_PATH, codePkgJson, { spaces: 2 }); + await writeFile(CODE_PACKAGE_JSON_PATH, JSON.stringify(codePkgJson, null, 2)); console.log(`✅ Set a ${picocolors.cyan('deferred')} version bump. Not bumping any packages.`); }; @@ -220,7 +224,7 @@ const applyDeferredVersionBump = async () => { } delete codePkgJson.deferredNextVersion; - await writeJson(CODE_PACKAGE_JSON_PATH, codePkgJson, { spaces: 2 }); + await writeFile(CODE_PACKAGE_JSON_PATH, JSON.stringify(codePkgJson, null, 2)); console.log( `✅ Extracted and removed deferred version ${picocolors.green( diff --git a/scripts/release/write-changelog.ts b/scripts/release/write-changelog.ts index 9e8b1e342cb2..71423483f984 100644 --- a/scripts/release/write-changelog.ts +++ b/scripts/release/write-changelog.ts @@ -1,8 +1,7 @@ +import { readFile, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { program } from 'commander'; -// eslint-disable-next-line depend/ban-dependencies -import { readFile, writeFile, writeJson } from 'fs-extra'; import picocolors from 'picocolors'; import semver from 'semver'; import { z } from 'zod'; @@ -106,7 +105,7 @@ const writeToDocsVersionFile = async ({ }, }; - await writeJson(filepath, content); + await writeFile(filepath, JSON.stringify(content)); }; export const run = async (args: unknown[], options: unknown) => { diff --git a/scripts/reset.js b/scripts/reset.js index d9049633d2bc..2586ececf6dd 100644 --- a/scripts/reset.js +++ b/scripts/reset.js @@ -1,8 +1,7 @@ import { spawn } from 'node:child_process'; import { appendFile, writeFileSync } from 'node:fs'; +import { rm } from 'node:fs/promises'; -// eslint-disable-next-line depend/ban-dependencies -import { remove } from 'fs-extra'; import trash from 'trash'; const logger = console; @@ -33,7 +32,7 @@ cleaningProcess.stdout.on('data', (data) => { uri.match(/\.cache/) || uri.match(/dll/) ) { - remove(uri).then(() => { + rm(uri, { force: true, recursive: true }).then(() => { logger.log(`deleted ${uri}`); }); } else { @@ -43,7 +42,9 @@ cleaningProcess.stdout.on('data', (data) => { }) .catch((e) => { logger.log('failed to trash, will try permanent delete'); - remove(uri); + rm(uri, { force: true, recursive: true }).then(() => { + logger.log(`deleted ${uri}`); + }); }); } } diff --git a/scripts/run-registry.ts b/scripts/run-registry.ts index 64002e327909..4dd99f8e3d48 100755 --- a/scripts/run-registry.ts +++ b/scripts/run-registry.ts @@ -1,5 +1,5 @@ import { exec } from 'node:child_process'; -import { mkdir, rm } from 'node:fs/promises'; +import { access, mkdir, readFile, rm } from 'node:fs/promises'; import http from 'node:http'; import type { Server } from 'node:http'; import { join, resolve as resolvePath } from 'node:path'; @@ -7,8 +7,6 @@ import { join, resolve as resolvePath } from 'node:path'; import { program } from 'commander'; // eslint-disable-next-line depend/ban-dependencies import { execa } from 'execa'; -// eslint-disable-next-line depend/ban-dependencies -import { pathExists, readJSON, remove } from 'fs-extra'; import pLimit from 'p-limit'; import picocolors from 'picocolors'; import { parseConfigFile, runServer } from 'verdaccio'; @@ -29,6 +27,15 @@ const root = resolvePath(__dirname, '..'); const opts = program.opts(); +const pathExists = async (p: string) => { + try { + await access(p); + return true; + } catch { + return false; + } +}; + const startVerdaccio = async () => { const ready = { proxy: false, @@ -99,7 +106,8 @@ const startVerdaccio = async () => { }; const currentVersion = async () => { - const { version } = await readJSON(join(__dirname, '..', 'code', 'package.json')); + const content = await readFile(join(__dirname, '..', 'code', 'package.json'), 'utf-8'); + const { version } = JSON.parse(content); return version; }; @@ -163,7 +171,7 @@ const run = async () => { const verdaccioCache = resolvePath(__dirname, '..', '.verdaccio-cache'); if (await pathExists(verdaccioCache)) { logger.log(`🗑 cleaning up cache`); - await remove(verdaccioCache); + await rm(verdaccioCache, { force: true, recursive: true }); } } diff --git a/scripts/sandbox/generate.ts b/scripts/sandbox/generate.ts index 5ee3c3c6d328..3dfda36a37e9 100755 --- a/scripts/sandbox/generate.ts +++ b/scripts/sandbox/generate.ts @@ -1,3 +1,5 @@ +import { cp, mkdir, readdir, rename, rm, writeFile } from 'node:fs/promises'; + import { readFile } from 'node:fs/promises'; import { join, relative } from 'node:path'; @@ -7,8 +9,6 @@ import { program } from 'commander'; import type { Options as ExecaOptions } from 'execa'; // eslint-disable-next-line depend/ban-dependencies import { execaCommand } from 'execa'; -// eslint-disable-next-line depend/ban-dependencies -import { copy, emptyDir, ensureDir, move, remove, writeFile } from 'fs-extra'; import pLimit from 'p-limit'; import prettyTime from 'pretty-hrtime'; import { dedent } from 'ts-dedent'; @@ -76,6 +76,13 @@ const withLocalRegistry = async ({ action, cwd, env, debug }: LocalRegistryProps } }; +const emptyDir = async (dir: string): Promise => { + await mkdir(dir, { recursive: true }); + + const names = await readdir(dir); + await Promise.all(names.map((name) => rm(join(dir, name), { recursive: true, force: true }))); +}; + const addStorybook = async ({ localRegistry, baseDir, @@ -95,7 +102,7 @@ const addStorybook = async ({ const tmpDir = await temporaryDirectory(); try { - await copy(beforeDir, tmpDir); + await cp(beforeDir, tmpDir, { recursive: true }); if (localRegistry) { await addResolutions(tmpDir); @@ -104,12 +111,12 @@ const addStorybook = async ({ await sbInit(tmpDir, env, [...flags, '--package-manager=yarn1'], debug); } catch (e) { console.log('error', e); - await remove(tmpDir); + await rm(tmpDir, { recursive: true, force: true }); throw e; } - await copy(tmpDir, afterDir); - await remove(tmpDir); + await cp(tmpDir, afterDir, { recursive: true }); + await rm(tmpDir, { recursive: true, force: true }); }; export const runCommand = async (script: string, options: ExecaOptions, debug = false) => { @@ -133,7 +140,7 @@ const addDocumentation = async ( const stackblitzConfigPath = join(__dirname, 'templates', '.stackblitzrc'); const readmePath = join(__dirname, 'templates', 'item.ejs'); - await copy(stackblitzConfigPath, join(afterDir, '.stackblitzrc')); + await cp(stackblitzConfigPath, join(afterDir, '.stackblitzrc')); const stackblitzUrl = getStackblitzUrl(dirName); const contents = await renderTemplate(readmePath, { @@ -215,7 +222,7 @@ const runGenerators = async ( debug ); } else { - await ensureDir(createBeforeDir); + await mkdir(createBeforeDir, { recursive: true }); await runCommand(script, { cwd: createBeforeDir, timeout: SCRIPT_TIMEOUT }, debug); } } catch (error) { @@ -233,10 +240,10 @@ const runGenerators = async ( await localizeYarnConfigFiles(createBaseDir, createBeforeDir); // Now move the created before dir into it's final location and add storybook - await move(createBeforeDir, beforeDir); + await rename(createBeforeDir, beforeDir); // Make sure there are no git projects in the folder - await remove(join(beforeDir, '.git')); + await rm(join(beforeDir, '.git'), { recursive: true, force: true }); try { await addStorybook({ baseDir, localRegistry, flags, debug, env }); @@ -269,9 +276,12 @@ const runGenerators = async ( // They're not uploaded to the git sandboxes repo anyway if (process.env.CLEANUP_SANDBOX_NODE_MODULES) { console.log(`🗑️ Removing ${join(beforeDir, 'node_modules')}`); - await remove(join(beforeDir, 'node_modules')); + await rm(join(beforeDir, 'node_modules'), { recursive: true, force: true }); console.log(`🗑️ Removing ${join(baseDir, AFTER_DIR_NAME, 'node_modules')}`); - await remove(join(baseDir, AFTER_DIR_NAME, 'node_modules')); + await rm(join(baseDir, AFTER_DIR_NAME, 'node_modules'), { + recursive: true, + force: true, + }); } } }) diff --git a/scripts/sandbox/publish.ts b/scripts/sandbox/publish.ts index f83048dff5cd..e6ec55b852b2 100755 --- a/scripts/sandbox/publish.ts +++ b/scripts/sandbox/publish.ts @@ -1,10 +1,10 @@ +import { cp, mkdir, readdir, rm, writeFile } from 'node:fs/promises'; + import { program } from 'commander'; // eslint-disable-next-line depend/ban-dependencies import { execaCommand } from 'execa'; import { existsSync } from 'fs'; // eslint-disable-next-line depend/ban-dependencies -import { copy, emptyDir, remove, writeFile } from 'fs-extra'; -// eslint-disable-next-line depend/ban-dependencies import { glob } from 'glob'; import { dirname, join, relative } from 'path'; @@ -15,6 +15,13 @@ import { getTemplatesData, renderTemplate } from './utils/template'; export const logger = console; +const emptyDir = async (dir: string): Promise => { + await mkdir(dir, { recursive: true }); + + const names = await readdir(dir); + await Promise.all(names.map((name) => rm(join(dir, name), { recursive: true, force: true }))); +}; + interface PublishOptions { remote?: string; push?: boolean; @@ -61,7 +68,7 @@ const publish = async (options: PublishOptions & { tmpFolder: string }) => { await writeFile(join(tmpFolder, 'README.md'), output); logger.log(`🚛 Moving all the repros into the repository`); - await copy(REPROS_DIRECTORY, tmpFolder); + await cp(REPROS_DIRECTORY, tmpFolder, { recursive: true }); await commitAllToGit({ cwd: tmpFolder, branch }); @@ -112,7 +119,7 @@ async function main() { if (existsSync(tmpFolder)) { logger.log('🚮 Removing the temporary folder..'); - await remove(tmpFolder); + await rm(tmpFolder, { force: true, recursive: true }); } process.exit(1); }); diff --git a/scripts/sandbox/utils/template.ts b/scripts/sandbox/utils/template.ts index 825cd6e58641..4bda17d4f5c2 100644 --- a/scripts/sandbox/utils/template.ts +++ b/scripts/sandbox/utils/template.ts @@ -1,6 +1,6 @@ +import { readFile } from 'node:fs/promises'; + import { render } from 'ejs'; -// eslint-disable-next-line depend/ban-dependencies -import { readFile } from 'fs-extra'; import prettier from 'prettier'; import { allTemplates as sandboxTemplates } from '../../../code/lib/cli-storybook/src/sandbox-templates'; diff --git a/scripts/sandbox/utils/yarn.ts b/scripts/sandbox/utils/yarn.ts index 1132bda1472d..4b209b2a3352 100644 --- a/scripts/sandbox/utils/yarn.ts +++ b/scripts/sandbox/utils/yarn.ts @@ -1,6 +1,5 @@ -import fs from 'fs'; -// eslint-disable-next-line depend/ban-dependencies -import { move, remove } from 'fs-extra'; +import { rename, rm, writeFile } from 'node:fs/promises'; + import { join } from 'path'; import { runCommand } from '../generate'; @@ -13,19 +12,19 @@ interface SetupYarnOptions { export async function setupYarn({ cwd, pnp = false, version = 'classic' }: SetupYarnOptions) { // force yarn - fs.writeFileSync(join(cwd, 'yarn.lock'), '', { flag: 'a' }); + await writeFile(join(cwd, 'yarn.lock'), '', { flag: 'a' }); await runCommand(`yarn set version ${version}`, { cwd }); if (version === 'berry' && !pnp) { await runCommand('yarn config set nodeLinker node-modules', { cwd }); } - await remove(join(cwd, 'package.json')); + await rm(join(cwd, 'package.json'), { force: true }); } export async function localizeYarnConfigFiles(baseDir: string, beforeDir: string) { await Promise.allSettled([ - fs.writeFileSync(join(beforeDir, 'yarn.lock'), '', { flag: 'a' }), - move(join(baseDir, '.yarn'), join(beforeDir, '.yarn')), - move(join(baseDir, '.yarnrc.yml'), join(beforeDir, '.yarnrc.yml')), - move(join(baseDir, '.yarnrc'), join(beforeDir, '.yarnrc')), + writeFile(join(beforeDir, 'yarn.lock'), '', { flag: 'a' }), + rename(join(baseDir, '.yarn'), join(beforeDir, '.yarn')), + rename(join(baseDir, '.yarnrc.yml'), join(beforeDir, '.yarnrc.yml')), + rename(join(baseDir, '.yarnrc'), join(beforeDir, '.yarnrc')), ]); } diff --git a/scripts/task.ts b/scripts/task.ts index 1d4925bfd528..c2e477953964 100644 --- a/scripts/task.ts +++ b/scripts/task.ts @@ -1,8 +1,8 @@ -// eslint-disable-next-line depend/ban-dependencies -import { outputFile, pathExists, readFile } from 'fs-extra'; +import { access, mkdir, readFile, writeFile } from 'node:fs/promises'; + import type { TestCase } from 'junit-xml'; import { getJunitXml } from 'junit-xml'; -import { join, resolve } from 'path'; +import { dirname, join, resolve } from 'path'; import picocolors from 'picocolors'; import { prompt } from 'prompts'; import invariant from 'tiny-invariant'; @@ -194,6 +194,20 @@ function getJunitFilename(taskKey: TaskKey) { return join(JUNIT_DIRECTORY, `${taskKey}.xml`); } +async function pathExists(path: string) { + try { + await access(path); + return true; + } catch { + return false; + } +} + +async function outputFile(file: string, data: string) { + await mkdir(dirname(file), { recursive: true }); + await writeFile(file, data); +} + async function writeJunitXml( taskKey: TaskKey, templateKey: TemplateKey, @@ -521,7 +535,7 @@ async function run() { startFrom: 'auto', }) )} - + Note this uses locally linking which in rare cases behaves differently to CI. For a closer match, add ${picocolors.bold('--no-link')} to the command above. `; diff --git a/scripts/tasks/build.ts b/scripts/tasks/build.ts index 2742a5bda7db..85967b804b65 100644 --- a/scripts/tasks/build.ts +++ b/scripts/tasks/build.ts @@ -1,14 +1,22 @@ +import { access } from 'node:fs/promises'; import { join } from 'node:path'; import { promisify } from 'node:util'; import dirSize from 'fast-folder-size'; -// eslint-disable-next-line depend/ban-dependencies -import { pathExists } from 'fs-extra'; import { now, saveBench } from '../bench/utils'; import type { Task } from '../task'; import { exec } from '../utils/exec'; +async function pathExists(path: string) { + try { + await access(path); + return true; + } catch { + return false; + } +} + export const build: Task = { description: 'Build the static version of the sandbox', dependsOn: ['sandbox'], diff --git a/scripts/tasks/compile.ts b/scripts/tasks/compile.ts index 5ef9a63838a8..a25274eb83f3 100644 --- a/scripts/tasks/compile.ts +++ b/scripts/tasks/compile.ts @@ -1,9 +1,6 @@ -import { rm } from 'node:fs/promises'; +import { readFile, rm } from 'node:fs/promises'; import { join, resolve } from 'node:path'; -// eslint-disable-next-line depend/ban-dependencies -import { readFile } from 'fs-extra'; - import type { Task } from '../task'; import { exec } from '../utils/exec'; import { maxConcurrentTasks } from '../utils/maxConcurrentTasks'; diff --git a/scripts/tasks/generate.ts b/scripts/tasks/generate.ts index 5736803a551d..97d02f15f1dc 100644 --- a/scripts/tasks/generate.ts +++ b/scripts/tasks/generate.ts @@ -1,5 +1,5 @@ -// eslint-disable-next-line depend/ban-dependencies -import { pathExists, remove } from 'fs-extra'; +import { access, rm } from 'node:fs/promises'; + import { join } from 'path'; import type { Task } from '../task'; @@ -7,6 +7,15 @@ import { REPROS_DIRECTORY } from '../utils/constants'; const logger = console; +const pathExists = async (path: string) => { + try { + await access(path); + return true; + } catch { + return false; + } +}; + export const generate: Task = { description: 'Create the template repro', dependsOn: ['run-registry'], @@ -24,7 +33,7 @@ export const generate: Task = { const reproDir = join(REPROS_DIRECTORY, details.key); if (await this.ready(details, options)) { logger.info('🗑 Removing old repro dir'); - await remove(reproDir); + await rm(reproDir, { force: true, recursive: true }); } // This uses an async import as it depends on `lib/cli` which requires `code` to be installed. diff --git a/scripts/tasks/install.ts b/scripts/tasks/install.ts index 0299c18d2fe4..faf5e11f69b5 100644 --- a/scripts/tasks/install.ts +++ b/scripts/tasks/install.ts @@ -1,10 +1,19 @@ -// eslint-disable-next-line depend/ban-dependencies -import { pathExists, remove } from 'fs-extra'; +import { access, rm } from 'node:fs/promises'; + import { join } from 'path'; import type { Task } from '../task'; import { checkDependencies } from '../utils/cli-utils'; +const pathExists = async (path: string) => { + try { + await access(path); + return true; + } catch { + return false; + } +}; + export const install: Task = { description: 'Install the dependencies of the monorepo', async ready({ codeDir }) { @@ -14,6 +23,6 @@ export const install: Task = { await checkDependencies(); // these are webpack4 types, we we should never use - await remove(join(codeDir, 'node_modules', '@types', 'webpack')); + await rm(join(codeDir, 'node_modules', '@types', 'webpack'), { force: true, recursive: true }); }, }; diff --git a/scripts/tasks/publish.ts b/scripts/tasks/publish.ts index 987286207dd1..cfb9f7bac6fb 100644 --- a/scripts/tasks/publish.ts +++ b/scripts/tasks/publish.ts @@ -1,5 +1,5 @@ -// eslint-disable-next-line depend/ban-dependencies -import { pathExists } from 'fs-extra'; +import { access } from 'node:fs/promises'; + import { resolve } from 'path'; import type { Task } from '../task'; @@ -7,6 +7,15 @@ import { exec } from '../utils/exec'; const verdaccioCacheDir = resolve(__dirname, '../../.verdaccio-cache'); +const pathExists = async (path: string) => { + try { + await access(path); + return true; + } catch { + return false; + } +}; + export const publish: Task = { description: 'Publish the packages of the monorepo to an internal npm server', dependsOn: ['compile'], diff --git a/scripts/tasks/sandbox-parts.ts b/scripts/tasks/sandbox-parts.ts index 3fb40d1708ba..6d354b340fc5 100644 --- a/scripts/tasks/sandbox-parts.ts +++ b/scripts/tasks/sandbox-parts.ts @@ -1,20 +1,10 @@ // This file requires many imports from `../code`, which requires both an install and bootstrap of // the repo to work properly. So we load it async in the task runner *after* those steps. +import { existsSync } from 'node:fs'; +import { access, cp, lstat, mkdir, readFile, symlink, writeFile } from 'node:fs/promises'; +import { dirname } from 'node:path'; + import { isFunction } from 'es-toolkit/predicate'; -// eslint-disable-next-line depend/ban-dependencies -import { - copy, - ensureDir, - ensureSymlink, - existsSync, - mkdir, - pathExists, - readFile, - readFileSync, - readJson, - writeFile, - writeJson, -} from 'fs-extra'; import JSON5 from 'json5'; import { createRequire } from 'module'; import { join, relative, resolve, sep } from 'path'; @@ -45,21 +35,50 @@ import { installYarn2, } from '../utils/yarn'; +async function ensureSymlink(src: string, dest: string): Promise { + await mkdir(dirname(dest), { recursive: true }); + + try { + await lstat(dest); + return; + } catch (e: any) { + if (e?.code !== 'ENOENT') { + throw e; + } + } + + await symlink(src, dest); +} + // Windows-compatible symlink function that falls back to copying async function ensureSymlinkOrCopy(source: string, target: string): Promise { try { await ensureSymlink(source, target); } catch (error: any) { - // If symlink fails (typically on Windows without admin privileges), fall back to copy + // If symlink fails (typically on Windows without admin privileges), fall back to cp if (error.code === 'EPERM' || error.code === 'EEXIST') { - logger.info(`Symlink failed for ${target}, falling back to copy`); - await copy(source, target, { overwrite: true }); + logger.info(`Symlink failed for ${target}, falling back to cp`); + await cp(source, target, { recursive: true, force: true }); } else { throw error; } } } +async function readJson(path: string) { + const content = await readFile(path, 'utf-8'); + return JSON.parse(content); +} + +async function pathExists(path: string) { + try { + await access(path); + return true; + } catch { + return false; + } +} + const logger = console; export const essentialsAddons = [ @@ -75,14 +94,14 @@ export const essentialsAddons = [ export const create: Task['run'] = async ({ key, template, sandboxDir }, { dryRun, debug }) => { const parentDir = resolve(sandboxDir, '..'); - await ensureDir(parentDir); + await mkdir(parentDir, { recursive: true }); if ('inDevelopment' in template && template.inDevelopment) { const srcDir = join(REPROS_DIRECTORY, key, 'after-storybook'); if (!existsSync(srcDir)) { throw new Error(`Missing repro directory '${srcDir}', did the generate task run?`); } - await copy(srcDir, sandboxDir); + await cp(srcDir, sandboxDir, { recursive: true }); } else { await executeCLIStep(steps.repro, { argument: key, @@ -408,7 +427,7 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio }; } - await writeJson(packageJsonPath, packageJson, { spaces: 2 }); + await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); const isVue = template.expected.renderer === '@storybook/vue3'; // const isAngular = template.expected.framework === '@storybook/angular'; @@ -451,7 +470,7 @@ export async function setupVitest(details: TemplateDetails, options: PassedOptio import * as templateAnnotations from '../template-stories/core/preview' import * as projectAnnotations from './preview' ${isVue ? 'import * as vueAnnotations from "../src/stories/renderers/vue3/preview.js"' : ''} - + setProjectAnnotations([ ${isVue ? 'vueAnnotations,' : ''} rendererDocsAnnotations, @@ -543,7 +562,9 @@ export async function addExtraDependencies({ } export const addGlobalMocks: Task['run'] = async ({ sandboxDir }) => { - await copy(join(CODE_DIRECTORY, 'core', 'template', '__mocks__'), join(sandboxDir, '__mocks__')); + await cp(join(CODE_DIRECTORY, 'core', 'template', '__mocks__'), join(sandboxDir, '__mocks__'), { + recursive: true, + }); }; export const addStories: Task['run'] = async ( @@ -846,7 +867,7 @@ export async function setImportMap(cwd: string) { }, }; - await writeJson(join(cwd, 'package.json'), packageJson, { spaces: 2 }); + await writeFile(join(cwd, 'package.json'), JSON.stringify(packageJson, null, 2)); } async function prepareReactNativeWebSandbox(cwd: string) { @@ -877,7 +898,7 @@ async function prepareAngularSandbox(cwd: string, templateName: string) { angularJson.projects[projectName].architect['build-storybook'].options.preserveSymlinks = true; }); - await writeJson(join(cwd, 'angular.json'), angularJson, { spaces: 2 }); + await writeFile(join(cwd, 'angular.json'), JSON.stringify(angularJson, null, 2)); const packageJsonPath = join(cwd, 'package.json'); const packageJson = await readJson(packageJsonPath); @@ -889,12 +910,12 @@ async function prepareAngularSandbox(cwd: string, templateName: string) { 'build-storybook': `yarn docs:json && ${packageJson.scripts['build-storybook']}`, }; - await writeJson(packageJsonPath, packageJson, { spaces: 2 }); + await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); // Set tsConfig compilerOptions const tsConfigPath = join(cwd, '.storybook', 'tsconfig.json'); - const tsConfigContent = readFileSync(tsConfigPath, { encoding: 'utf-8' }); + const tsConfigContent = await readFile(tsConfigPath, { encoding: 'utf-8' }); // This does not preserve comments, but that shouldn't be an issue for sandboxes const tsConfigJson = JSON5.parse(tsConfigContent); @@ -917,5 +938,5 @@ async function prepareAngularSandbox(cwd: string, templateName: string) { }; } - await writeJson(tsConfigPath, tsConfigJson, { spaces: 2 }); + await writeFile(tsConfigPath, JSON.stringify(tsConfigJson, null, 2)); } diff --git a/scripts/tasks/sandbox.ts b/scripts/tasks/sandbox.ts index 9b5f3da61617..9078d5c4be23 100644 --- a/scripts/tasks/sandbox.ts +++ b/scripts/tasks/sandbox.ts @@ -1,16 +1,23 @@ -import path from 'node:path'; -import { join } from 'node:path'; +import { access, rm } from 'node:fs/promises'; +import path, { join } from 'node:path'; import { promisify } from 'node:util'; import dirSize from 'fast-folder-size'; -// eslint-disable-next-line depend/ban-dependencies -import { pathExists, remove } from 'fs-extra'; import { now, saveBench } from '../bench/utils'; import type { Task, TaskKey } from '../task'; const logger = console; +const pathExists = async (path: string) => { + try { + await access(path); + return true; + } catch { + return false; + } +}; + export const sandbox: Task = { description: 'Create the sandbox from a template', dependsOn: ({ template }, { link }) => { @@ -54,7 +61,7 @@ export const sandbox: Task = { if (!(await this.ready(details, options))) { logger.info('🗑 Removing old sandbox dir'); - await remove(details.sandboxDir); + await rm(details.sandboxDir, { force: true, recursive: true }); } const { @@ -161,7 +168,7 @@ export const sandbox: Task = { const packageManager = JsPackageManagerFactory.getPackageManager({}, details.sandboxDir); - await remove(path.join(details.sandboxDir, 'node_modules')); + await rm(path.join(details.sandboxDir, 'node_modules'), { force: true, recursive: true }); await packageManager.installDependencies(); await runMigrations(details, options); diff --git a/scripts/utils/filterExistsInCodeDir.ts b/scripts/utils/filterExistsInCodeDir.ts index 673148f916eb..93c4f9af1aca 100644 --- a/scripts/utils/filterExistsInCodeDir.ts +++ b/scripts/utils/filterExistsInCodeDir.ts @@ -1,10 +1,17 @@ +import { access } from 'node:fs/promises'; import { join, resolve } from 'node:path'; -// eslint-disable-next-line depend/ban-dependencies -import { pathExists } from 'fs-extra'; - import { CODE_DIRECTORY } from './constants'; +const pathExists = async (path: string) => { + try { + await access(path); + return true; + } catch { + return false; + } +}; + // packageDirs of the form `lib/preview-api` // paths to check of the form 'template/stories' export const filterExistsInCodeDir = async (packageDirs: string[], pathToCheck: string) => diff --git a/scripts/utils/package-json.ts b/scripts/utils/package-json.ts index 16bdb67f7078..3c8fe455f779 100644 --- a/scripts/utils/package-json.ts +++ b/scripts/utils/package-json.ts @@ -1,10 +1,11 @@ -// eslint-disable-next-line depend/ban-dependencies -import { readJSON, writeJSON } from 'fs-extra'; +import { readFile, writeFile } from 'node:fs/promises'; + import { join } from 'path'; export async function updatePackageScripts({ cwd, prefix }: { cwd: string; prefix: string }) { const packageJsonPath = join(cwd, 'package.json'); - const packageJson = await readJSON(packageJsonPath); + const content = await readFile(packageJsonPath, 'utf-8'); + const packageJson = JSON.parse(content); packageJson.scripts = { ...packageJson.scripts, ...(packageJson.scripts.storybook && { @@ -12,5 +13,5 @@ export async function updatePackageScripts({ cwd, prefix }: { cwd: string; prefi 'build-storybook': `${prefix} ${packageJson.scripts['build-storybook']}`, }), }; - await writeJSON(packageJsonPath, packageJson, { spaces: 2 }); + await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); } diff --git a/scripts/utils/paths.ts b/scripts/utils/paths.ts index 7253fdff740c..187e3885e6e8 100644 --- a/scripts/utils/paths.ts +++ b/scripts/utils/paths.ts @@ -1,7 +1,16 @@ -// eslint-disable-next-line depend/ban-dependencies -import { pathExists } from 'fs-extra'; +import { access } from 'node:fs/promises'; + import { join } from 'path'; +const pathExists = async (path: string) => { + try { + await access(path); + return true; + } catch { + return false; + } +}; + export async function findFirstPath(paths: string[], { cwd }: { cwd: string }) { for (const filePath of paths) { if (await pathExists(join(cwd, filePath))) { diff --git a/scripts/utils/tools.ts b/scripts/utils/tools.ts index f6ee9d2a0fe0..47ed8559228e 100644 --- a/scripts/utils/tools.ts +++ b/scripts/utils/tools.ts @@ -1,4 +1,4 @@ -import { writeFile } from 'node:fs/promises'; +import { access, readFile, writeFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import * as process from 'node:process'; @@ -6,8 +6,6 @@ import { globalExternals } from '@fal-works/esbuild-plugin-global-externals'; import { spawn } from 'cross-spawn'; import * as esbuild from 'esbuild'; // eslint-disable-next-line depend/ban-dependencies -import { pathExists, readJson } from 'fs-extra'; -// eslint-disable-next-line depend/ban-dependencies import { glob } from 'glob'; import limit from 'p-limit'; import picocolors from 'picocolors'; @@ -26,6 +24,15 @@ import { CODE_DIRECTORY } from './constants'; export { globalExternals }; +const pathExists = async (path: string) => { + try { + await access(path); + return true; + } catch { + return false; + } +}; + export const dts = async (entry: string, externals: string[], tsconfig: string) => { const dir = dirname(entry).replace('src', 'dist'); const out = await rollup.rollup({ @@ -124,7 +131,8 @@ type PackageJson = typefest.PackageJson & Required> & { path: string }; export const getWorkspace = async (): Promise => { - const codePackage = await readJson(join(CODE_DIRECTORY, 'package.json')); + const content = await readFile(join(CODE_DIRECTORY, 'package.json'), 'utf-8'); + const codePackage = JSON.parse(content); const { workspaces: { packages: patterns }, } = codePackage; @@ -146,7 +154,8 @@ export const getWorkspace = async (): Promise => { ); return null; } - const pkg = await readJson(packageJsonPath); + const content = await readFile(packageJsonPath, 'utf-8'); + const pkg = JSON.parse(content); return { ...pkg, path: packagePath } as PackageJson; }) ).then((packages) => packages.filter((p) => p !== null)); diff --git a/scripts/utils/yarn.ts b/scripts/utils/yarn.ts index af4730294ac0..4c3967876d44 100644 --- a/scripts/utils/yarn.ts +++ b/scripts/utils/yarn.ts @@ -1,8 +1,6 @@ +import { access, readFile, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; -// eslint-disable-next-line depend/ban-dependencies -import { pathExists, readJSON, writeJSON } from 'fs-extra'; - // TODO -- should we generate this file a second time outside of CLI? import storybookVersions from '../../code/core/src/common/versions'; import type { TemplateKey } from '../get-template'; @@ -17,6 +15,15 @@ export type YarnOptions = { const logger = console; +const pathExists = async (path: string) => { + try { + await access(path); + return true; + } catch { + return false; + } +}; + export const addPackageResolutions = async ({ cwd, dryRun }: YarnOptions) => { logger.info(`🔢 Adding package resolutions:`); @@ -25,7 +32,8 @@ export const addPackageResolutions = async ({ cwd, dryRun }: YarnOptions) => { } const packageJsonPath = join(cwd, 'package.json'); - const packageJson = await readJSON(packageJsonPath); + const content = await readFile(packageJsonPath, 'utf-8'); + const packageJson = JSON.parse(content); packageJson.resolutions = { ...packageJson.resolutions, ...storybookVersions, @@ -35,7 +43,7 @@ export const addPackageResolutions = async ({ cwd, dryRun }: YarnOptions) => { '@playwright/test': '1.52.0', rollup: '4.44.2', }; - await writeJSON(packageJsonPath, packageJson, { spaces: 2 }); + await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); }; export const installYarn2 = async ({ cwd, dryRun, debug }: YarnOptions) => { @@ -79,7 +87,8 @@ export const addWorkaroundResolutions = async ({ } const packageJsonPath = join(cwd, 'package.json'); - const packageJson = await readJSON(packageJsonPath); + const content = await readFile(packageJsonPath, 'utf-8'); + const packageJson = JSON.parse(content); const additionalReact19Resolutions = ['nextjs/default-ts', 'nextjs/prerelease'].includes(key) ? { @@ -103,7 +112,7 @@ export const addWorkaroundResolutions = async ({ rollup: '4.44.2', }; - await writeJSON(packageJsonPath, packageJson, { spaces: 2 }); + await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); }; export const configureYarn2ForVerdaccio = async ({ diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 0a11337b145b..46b4c5d9da2e 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -1528,7 +1528,6 @@ __metadata: "@types/detect-port": "npm:^1.3.5" "@types/ejs": "npm:^3.1.5" "@types/escodegen": "npm:^0.0.6" - "@types/fs-extra": "npm:^11.0.4" "@types/http-server": "npm:^0.12.4" "@types/jest": "npm:^29.5.12" "@types/node": "npm:^22.0.0" @@ -1581,7 +1580,6 @@ __metadata: fast-folder-size: "npm:^2.2.0" fast-glob: "npm:^3.3.2" find-up: "npm:^5.0.0" - fs-extra: "npm:^11.2.0" github-release-from-changelog: "npm:^2.1.1" glob: "npm:^10.4.5" http-server: "npm:^14.1.1" @@ -1826,16 +1824,6 @@ __metadata: languageName: node linkType: hard -"@types/fs-extra@npm:^11.0.4": - 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/glob@npm:^7.1.1": version: 7.2.0 resolution: "@types/glob@npm:7.2.0" @@ -1911,15 +1899,6 @@ __metadata: languageName: node linkType: hard -"@types/jsonfile@npm:*": - version: 6.1.3 - resolution: "@types/jsonfile@npm:6.1.3" - dependencies: - "@types/node": "npm:*" - checksum: 10c0/2f974e33d2e2aa3e8b04af77ece343c980d495a5ad3318d302a6aa8ba221806096f664353d0f70f1f83007831f15a3a1d3c8d48cd4039efb0880b02865d01175 - languageName: node - linkType: hard - "@types/lodash@npm:^4.14.175": version: 4.17.6 resolution: "@types/lodash@npm:4.17.6" @@ -6087,17 +6066,6 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^11.2.0": - version: 11.2.0 - resolution: "fs-extra@npm:11.2.0" - dependencies: - graceful-fs: "npm:^4.2.0" - jsonfile: "npm:^6.0.1" - universalify: "npm:^2.0.0" - checksum: 10c0/d77a9a9efe60532d2e790e938c81a02c1b24904ef7a3efb3990b835514465ba720e99a6ea56fd5e2db53b4695319b644d76d5a0e9988a2beef80aa7b1da63398 - languageName: node - linkType: hard - "fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" @@ -6570,7 +6538,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.10, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.10, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: 10c0/386d011a553e02bc594ac2ca0bd6d9e4c22d7fa8cfbfc448a6d148c59ea881b092db9dbe3547ae4b88e55f1b01f7c4a2ecc53b310c042793e63aa44cf6c257f2 @@ -7850,19 +7818,6 @@ __metadata: languageName: node linkType: hard -"jsonfile@npm:^6.0.1": - version: 6.1.0 - resolution: "jsonfile@npm:6.1.0" - dependencies: - graceful-fs: "npm:^4.1.6" - universalify: "npm:^2.0.0" - dependenciesMeta: - graceful-fs: - optional: true - checksum: 10c0/4f95b5e8a5622b1e9e8f33c96b7ef3158122f595998114d1e7f03985649ea99cb3cd99ce1ed1831ae94c8c8543ab45ebd044207612f31a56fd08462140e46865 - languageName: node - linkType: hard - "jsonparse@npm:^1.2.0": version: 1.3.1 resolution: "jsonparse@npm:1.3.1" @@ -12513,13 +12468,6 @@ __metadata: languageName: node linkType: hard -"universalify@npm:^2.0.0": - version: 2.0.1 - resolution: "universalify@npm:2.0.1" - checksum: 10c0/73e8ee3809041ca8b818efb141801a1004e3fc0002727f1531f4de613ea281b494a40909596dae4a042a4fb6cd385af5d4db2e137b1362e0e91384b828effd3a - languageName: node - linkType: hard - "unix-crypt-td-js@npm:1.1.4": version: 1.1.4 resolution: "unix-crypt-td-js@npm:1.1.4"