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"