Merge branch 'main' into next

# Conflicts:
#	package.json
#	yarn.lock
This commit is contained in:
fisker committed 2025-04-14 22:47:14 +08:00
commit 9697144462
331 files changed
+14280 -11478

No files matched your search

+44 -25
View File
@@ -3,10 +3,10 @@
import fs from "node:fs/promises";
import path from "node:path";
import url from "node:url";
import createEsmUtils from "esm-utils";
import { execa } from "execa";
import esbuild from "esbuild";
import fastGlob from "fast-glob";
import { format } from "../src/index.js";
import spawn from "nano-spawn";
import serialize from "serialize-javascript";
import {
copyFile,
DIST_DIR,
@@ -16,19 +16,22 @@ import {
writeJson,
} from "./utils/index.js";
const { require } = createEsmUtils(import.meta);
const runYarn = (command, args, options) =>
execa("yarn", [command, ...args], {
stdout: "inherit",
stderr: "inherit",
...options,
});
spawn("yarn", [command, ...args], { stdio: "inherit", ...options });
const IS_PULL_REQUEST = process.env.PULL_REQUEST === "true";
const PRETTIER_DIR = IS_PULL_REQUEST
? DIST_DIR
? path.join(DIST_DIR, "prettier")
: url.fileURLToPath(new URL("../node_modules/prettier", import.meta.url));
const PLAYGROUND_PRETTIER_DIR = path.join(WEBSITE_DIR, "static/lib");
async function writeScript(file, code) {
const { code: minified } = await esbuild.transform(code, {
loader: "js",
minify: true,
});
await writeFile(path.join(PLAYGROUND_PRETTIER_DIR, file), minified.trim());
}
async function buildPrettier() {
// --- Build prettier for PR ---
const packageJsonFile = path.join(PROJECT_ROOT, "package.json");
@@ -50,7 +53,7 @@ async function buildPrettier() {
}
async function buildPlaygroundFiles() {
const patterns = ["standalone.js", "plugins/*.js"];
const patterns = ["standalone.mjs", "plugins/*.mjs"];
const files = await fastGlob(patterns, {
cwd: PRETTIER_DIR,
@@ -64,31 +67,47 @@ async function buildPlaygroundFiles() {
const dist = path.join(PLAYGROUND_PRETTIER_DIR, fileName);
await copyFile(file, dist);
if (fileName === "standalone.js") {
if (fileName === "standalone.mjs") {
continue;
}
const pluginModule = require(dist);
const plugin = pluginModule.default ?? pluginModule;
const { parsers = {}, printers = {} } = plugin;
packageManifest.builtinPlugins.push({
const pluginModule = await import(url.pathToFileURL(dist));
const plugin = {
name: path.basename(fileName, ".js"),
file: fileName,
parsers: Object.keys(parsers),
printers: Object.keys(printers),
});
};
for (const property of ["languages", "options", "defaultOptions"]) {
const value = pluginModule[property];
if (value !== undefined) {
plugin[property] = value;
}
}
for (const property of ["parsers", "printers"]) {
const value = pluginModule[property];
if (value !== undefined) {
plugin[property] = Object.keys(value);
}
}
packageManifest.builtinPlugins.push(plugin);
}
await writeFile(
path.join(PLAYGROUND_PRETTIER_DIR, "package-manifest.js"),
await format(
const serialized = serialize(packageManifest, { space: 2 });
await Promise.all([
writeScript("package-manifest.mjs", `export default ${serialized};`),
writeScript(
"package-manifest.js",
/* Indent */ `
"use strict";
const prettierPackageManifest = ${JSON.stringify(packageManifest)};
self.prettierPackageManifest = ${serialized};
`,
{ parser: "meriyah" },
),
);
]);
}
if (IS_PULL_REQUEST) {
+10 -3
View File
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
import path from "node:path";
import { outdent } from "outdent";
import rollupPluginLicense from "rollup-plugin-license";
import { DIST_DIR, PROJECT_ROOT } from "../utils/index.js";
import { PROJECT_ROOT } from "../utils/index.js";
const separator = `\n${"-".repeat(40)}\n\n`;
@@ -120,7 +120,14 @@ function getLicenseText(dependencies) {
return [head, content].join("\n\n");
}
async function buildDependenciesLicense({ file, files, results, cliOptions }) {
async function buildDependenciesLicense({
packageConfig,
file,
results,
cliOptions,
}) {
const { distDirectory, files } = packageConfig;
const fileName = file.output.file;
if (files.at(-1) !== file) {
@@ -144,7 +151,7 @@ async function buildDependenciesLicense({ file, files, results, cliOptions }) {
const text = getLicenseText(dependencies);
await fs.writeFile(path.join(DIST_DIR, fileName), text);
await fs.writeFile(path.join(distDirectory, fileName), text);
}
export default buildDependenciesLicense;
+5 -3
View File
@@ -4,7 +4,7 @@ import browserslistToEsbuild from "browserslist-to-esbuild";
import esbuild from "esbuild";
import { nodeModulesPolyfillPlugin as esbuildPluginNodeModulePolyfills } from "esbuild-plugins-node-modules-polyfill";
import createEsmUtils from "esm-utils";
import { DIST_DIR, PROJECT_ROOT } from "../utils/index.js";
import { PROJECT_ROOT } from "../utils/index.js";
import esbuildPluginAddDefaultExport from "./esbuild-plugins/add-default-export.js";
import esbuildPluginEvaluate from "./esbuild-plugins/evaluate.js";
import esbuildPluginPrimitiveDefine from "./esbuild-plugins/primitive-define.js";
@@ -35,7 +35,9 @@ const getRelativePath = (from, to) => {
return relativePath;
};
function getEsbuildOptions({ file, files, cliOptions }) {
function getEsbuildOptions({ packageConfig, file, cliOptions }) {
const { distDirectory, files } = packageConfig;
// Save dependencies to file
file.dependencies = [];
@@ -232,7 +234,7 @@ function getEsbuildOptions({ file, files, cliOptions }) {
target: [...(buildOptions.target ?? ["node14"])],
logLevel: "error",
format: file.output.format,
outfile: path.join(DIST_DIR, cliOptions.saveAs ?? file.output.file),
outfile: path.join(distDirectory, cliOptions.saveAs ?? file.output.file),
// https://esbuild.github.io/api/#main-fields
mainFields: file.platform === "node" ? ["module", "main"] : undefined,
supported: {
+4 -3
View File
@@ -1,5 +1,5 @@
import path from "node:path";
import { DIST_DIR, PROJECT_ROOT, readJson, writeJson } from "../utils/index.js";
import { PROJECT_ROOT, readJson, writeJson } from "../utils/index.js";
const keysToKeep = [
"name",
@@ -20,7 +20,8 @@ const keysToKeep = [
"preferUnplugged",
];
async function buildPackageJson({ file, files }) {
async function buildPackageJson({ packageConfig, file }) {
const { distDirectory, files } = packageConfig;
const packageJson = await readJson(path.join(PROJECT_ROOT, file.input));
const bin = files.find(
@@ -105,7 +106,7 @@ async function buildPackageJson({ file, files }) {
};
await writeJson(
path.join(DIST_DIR, file.output.file),
path.join(distDirectory, file.output.file),
Object.assign(pick(packageJson, keysToKeep), overrides),
);
}
+11 -5
View File
@@ -3,9 +3,9 @@ import path from "node:path";
import url from "node:url";
import { isValidIdentifier } from "@babel/types";
import { outdent } from "outdent";
import { DIST_DIR, PROJECT_ROOT, writeFile } from "../utils/index.js";
import { PROJECT_ROOT, writeFile } from "../utils/index.js";
async function typesFileBuilder({ file }) {
async function typesFileBuilder({ packageConfig, file }) {
/**
* @typedef {{ from: string, to: string }} ImportPathReplacement
* @typedef {{ [input: string]: Array<ImportPathReplacement> }} ReplacementMap
@@ -20,14 +20,17 @@ async function typesFileBuilder({ file }) {
for (const { from, to } of replacements) {
text = text.replaceAll(` from "${from}";`, ` from "${to}";`);
}
await writeFile(path.join(DIST_DIR, file.output.file), text);
await writeFile(
path.join(packageConfig.distDirectory, file.output.file),
text,
);
}
function toPropertyKey(name) {
return isValidIdentifier(name) ? name : JSON.stringify(name);
}
async function buildPluginTypes({ file: { input, output } }) {
async function buildPluginTypes({ packageConfig, file: { input, output } }) {
const pluginModule = await import(
url.pathToFileURL(path.join(PROJECT_ROOT, input))
);
@@ -52,7 +55,10 @@ async function buildPluginTypes({ file: { input, output } }) {
};
`;
await writeFile(path.join(DIST_DIR, output.file), `${code}\n`);
await writeFile(
path.join(packageConfig.distDirectory, output.file),
`${code}\n`,
);
}
function buildTypes(options) {
+16 -8
View File
@@ -7,7 +7,7 @@ import createEsmUtils from "esm-utils";
import styleText from "node-style-text";
import prettyBytes from "pretty-bytes";
import { DIST_DIR } from "../utils/index.js";
import files from "./config.js";
import packageConfig from "./config.js";
import parseArguments from "./parse-arguments.js";
const { require } = createEsmUtils(import.meta);
@@ -44,7 +44,8 @@ const clear = () => {
readline.cursorTo(process.stdout, 0, null);
};
async function buildFile({ file, files, cliOptions, results }) {
async function buildFile({ packageConfig, file, cliOptions, results }) {
const { distDirectory } = packageConfig;
let displayName = file.output.file;
if (
(file.platform === "universal" && file.output.format !== "esm") ||
@@ -59,7 +60,9 @@ async function buildFile({ file, files, cliOptions, results }) {
if (
(cliOptions.files && !cliOptions.files.has(file.output.file)) ||
(cliOptions.playground &&
(file.output.format !== "umd" || file.output.file === "doc.js"))
(file.output.format !== "esm" ||
file.platform !== "universal" ||
file.output.file === "doc.mjs"))
) {
console.log(status.SKIPPED);
return;
@@ -67,7 +70,7 @@ async function buildFile({ file, files, cliOptions, results }) {
let result;
try {
result = await file.build({ file, files, cliOptions, results });
result = await file.build({ packageConfig, file, cliOptions, results });
} catch (error) {
console.log(status.FAIL + "\n");
console.error(error);
@@ -85,7 +88,7 @@ async function buildFile({ file, files, cliOptions, results }) {
const sizeMessages = [];
if (cliOptions.printSize) {
const { size } = await fs.stat(path.join(DIST_DIR, outputFile));
const { size } = await fs.stat(path.join(distDirectory, outputFile));
sizeMessages.push(prettyBytes(size));
}
@@ -101,7 +104,7 @@ async function buildFile({ file, files, cliOptions, results }) {
}
if (stableSize) {
const { size } = await fs.stat(path.join(DIST_DIR, outputFile));
const { size } = await fs.stat(path.join(distDirectory, outputFile));
const sizeDiff = size - stableSize;
const message = styleText[sizeDiff > 0 ? "yellow" : "green"](
prettyBytes(sizeDiff),
@@ -149,8 +152,13 @@ async function run() {
console.log(styleText.inverse(" Building packages "));
const results = [];
for (const file of files) {
const result = await buildFile({ file, files, cliOptions, results });
for (const file of packageConfig.files) {
const result = await buildFile({
packageConfig,
file,
cliOptions,
results,
});
results.push(result);
}
}
+12 -16
View File
@@ -19,10 +19,10 @@ const {
} = createEsmUtils(import.meta);
const resolveEsmModulePath = (specifier) =>
url.fileURLToPath(importMetaResolve(specifier));
const copyFileBuilder = ({ file }) =>
const copyFileBuilder = ({ packageConfig, file }) =>
copyFile(
path.join(PROJECT_ROOT, file.input),
path.join(DIST_DIR, file.output.file),
path.join(packageConfig.distDirectory, file.output.file),
);
function getTypesFileConfig({ input: jsFileInput, outputBaseName, isPlugin }) {
@@ -38,7 +38,7 @@ function getTypesFileConfig({ input: jsFileInput, outputBaseName, isPlugin }) {
return {
input,
output: {
file: outputBaseName + ".d.ts",
file: `${outputBaseName}.d.ts`,
},
kind: "types",
isPlugin,
@@ -682,9 +682,10 @@ const nonPluginUniversalFiles = [
return text;
},
},
// Smaller size
{
module: require.resolve("chalk"),
path: path.join(dirname, "./shims/chalk.cjs"),
module: getPackageFile("picocolors/picocolors.browser.js"),
path: path.join(dirname, "./shims/colors.js"),
},
],
},
@@ -760,15 +761,6 @@ const nodejsFiles = [
find: "const readBuffer = new Buffer(this.options.readChunk);",
replacement: "const readBuffer = Buffer.alloc(this.options.readChunk);",
},
// `@babel/code-frame` and `@babel/highlight` use compatible `chalk`, but they installed separately
{
module: require.resolve("chalk", {
paths: [require.resolve("@babel/highlight")],
}),
path: require.resolve("chalk", {
paths: [require.resolve("@babel/code-frame")],
}),
},
{
module: getPackageFile("js-yaml/dist/js-yaml.mjs"),
find: "var dump = dumper.dump;",
@@ -808,7 +800,7 @@ const nodejsFiles = [
{
input: "src/cli/index.js",
outputBaseName: "internal/cli",
external: ["benchmark"],
external: ["tinybench"],
// TODO: Remove this when we drop support for Node.js v16
replaceModule: [
{
@@ -876,4 +868,8 @@ const metaFiles = [
/** @type {Files[]} */
const files = [...nodejsFiles, ...universalFiles, ...metaFiles].filter(Boolean);
export default files;
export default {
packageName: "prettier",
distDirectory: path.join(DIST_DIR, "prettier"),
files,
};
@@ -1,6 +1,6 @@
import path from "node:path";
const namespace = "with-default-export";
const PLUGIN_NAMESPACE = "with-default-export";
export default function esbuildPluginAddDefaultExport() {
return {
name: "addDefaultExport",
@@ -10,31 +10,32 @@ export default function esbuildPluginAddDefaultExport() {
return;
}
let entry;
build.onResolve({ filter: /./u }, (module) => {
build.onResolve({ filter: /./, namespace: "file" }, (module) => {
if (module.kind === "entry-point") {
const relativePath = module.path
.slice(module.resolveDir.length + 1)
const file = module.path;
const relativePath = path
.relative(module.resolveDir, file)
.replaceAll("\\", "/");
entry = module.path;
return { path: relativePath, namespace };
return {
path: relativePath,
namespace: PLUGIN_NAMESPACE,
pluginData: { file },
};
}
});
build.onLoad({ filter: /./u, namespace }, () => {
const directory = path.dirname(entry);
const source = `./${path.basename(entry)}`;
build.onLoad({ filter: /./, namespace: PLUGIN_NAMESPACE }, (module) => {
const { file } = module.pluginData;
const source = JSON.stringify(`./${path.basename(file)}`);
return {
contents: /* indent */ `
import * as namespace from "${source}";
import * as namespace from ${source};
export * from "${source}";
export * from ${source};
export default namespace;
`,
resolveDir: directory,
resolveDir: path.dirname(file),
};
});
},
+29 -27
View File
@@ -1,41 +1,43 @@
import url from "node:url";
import { isValidIdentifier } from "@babel/types";
import createEsmUtils from "esm-utils";
import serialize from "serialize-javascript";
const { importModule } = createEsmUtils(import.meta);
export default function esbuildPluginEvaluate() {
return {
name: "evaluate",
setup(build) {
const { format } = build.initialOptions;
build.onLoad({ filter: /\.evaluate\.c?js$/u }, async ({ path }) => {
const module = await importModule(path);
const text = Object.entries(module)
.map(([specifier, value]) => {
const code =
value instanceof RegExp
? `/${value.source}/${value.flags}`
: serialize(value, { space: 2 });
build.onLoad(
{ filter: /\.evaluate\.c?js$/, namespace: "file" },
async ({ path }) => {
const module = await import(url.pathToFileURL(path));
const text = Object.entries(module)
.filter(([specifier]) => specifier !== "module.exports")
.map(([specifier, value]) => {
const code =
value instanceof RegExp
? `/${value.source}/${value.flags}`
: serialize(value, { space: 2 });
if (specifier === "default") {
return format === "cjs"
? `module.exports = ${code};`
: `export default ${code};`;
}
if (!isValidIdentifier(specifier)) {
throw new Error(`${specifier} is not a valid specifier`);
}
if (specifier === "default") {
return format === "cjs"
? `module.exports = ${code};`
: `export default ${code};`;
}
if (!isValidIdentifier(specifier)) {
throw new Error(`${specifier} is not a valid specifier`);
}
return format === "cjs"
? `exports.${specifier} = ${code};`
: `export const ${specifier} = ${code};`;
})
.join("\n");
return { contents: text };
});
? `exports.${specifier} = ${code};`
: `export const ${specifier} = ${code};`;
})
.join("\n");
return { contents: text };
},
);
},
};
}
@@ -1,8 +1,8 @@
import fs from "node:fs/promises";
const DEFAULT_ON_RESOLVE_CONCEPTS = { filter: /./u, namespace: "file" };
const DEFAULT_ON_RESOLVE_CONCEPTS = { filter: /./, namespace: "file" };
const DEFAULT_ON_LOAD_CONCEPTS = {
filter: /\.(?:js|json|mjs|cjs)$/u,
filter: /\.(?:js|json|mjs|cjs)$/,
namespace: "file",
};
@@ -2,8 +2,9 @@ export default function esbuildPluginStripNodeProtocol() {
return {
name: "strip-node-protocol",
setup(build) {
build.onResolve({ filter: /^node:/u }, ({ path, kind }) =>
build.resolve(path.slice(5), { kind }),
build.onResolve(
{ filter: /^node:/, namespace: "file" },
({ path, kind }) => build.resolve(path.slice(5), { kind }),
);
},
};
@@ -114,18 +114,6 @@ export default function esbuildPluginThrowWarnings({
continue;
}
if (
warning.id === "package.json" &&
warning.location.file.startsWith("node_modules/") &&
(warning.text ===
'The condition "default" here will never be used as it comes after both "import" and "require"' ||
// `lines-and-columns`
warning.text ===
'The condition "types" here will never be used as it comes after both "import" and "require"')
) {
continue;
}
console.log(warning);
throw new Error(warning.text);
}
+22
View File
@@ -121,6 +121,28 @@ function esmifyTypescriptEslint(text) {
`,
);
/**
```js
var FOO;
(function (FOO) {
})(FOO || (exports.FOO = FOO = {}));
```
->
```js
var FOO;
(function (FOO) {
})(FOO ?? {}));
export {FOO};
```
*/
text = text.replaceAll(
/(?<=\n\}\))\((?<name>\S+) \|\| \(exports\.\k<name> = \k<name> = \{\}\)\);/gu,
outdent`
($<name> ??= {});
export {$<name>};
`,
);
// Make sure ESBuild treat it as Module
text += "\nexport {};";
+4 -3
View File
@@ -15,10 +15,9 @@ function parseArguments() {
"save-as": { type: "string" },
report: { type: "string", multiple: true },
},
strict: true,
});
if (values.minify && values.noMinify) {
if (values.minify && values["no-minify"]) {
throw new Error("'--minify' and '--no-minify' can't be used together.");
}
@@ -40,7 +39,9 @@ function parseArguments() {
);
}
if (!path.join(DIST_DIR, result.saveAs).startsWith(DIST_DIR)) {
// TODO: Support package name
const distDirectory = path.join(DIST_DIR, "prettier");
if (!path.join(distDirectory, result.saveAs).startsWith(distDirectory)) {
throw new Error("'--save-as' can only relative path");
}
}
-5
View File
@@ -1,5 +0,0 @@
"use strict";
const chalk = new Proxy(String, { get: () => chalk });
module.exports = chalk;
+3
View File
@@ -0,0 +1,3 @@
const colors = new Proxy(String, { get: () => colors });
export default colors;
+19 -7
View File
@@ -1,9 +1,10 @@
#!/usr/bin/env node
import path from "node:path";
import minimist from "minimist";
import { parseArgs } from "node:util";
import semver from "semver";
import {
categories,
changelogUnreleasedDirPath,
changelogUnreleasedDirs,
getEntries,
@@ -11,11 +12,17 @@ import {
replaceVersions,
} from "./utils/changelog.js";
const { previousVersion, newVersion } = parseArgv();
const { previousVersion, newVersion } = parseArguments();
const entries = changelogUnreleasedDirs.flatMap((dir) => {
const dirPath = path.join(changelogUnreleasedDirPath, dir.name);
return getEntries(dirPath);
const { title } = categories.find((category) => category.dir === dir.name);
return getEntries(dirPath).map((entry) => {
const content =
entry.content.slice(0, 4) + ` ${title}:` + entry.content.slice(4);
return { ...entry, content };
});
});
console.log(
@@ -27,10 +34,15 @@ console.log(
),
);
function parseArgv() {
const argv = minimist(process.argv.slice(2));
const previousVersion = argv["prev-version"];
const newVersion = argv["new-version"];
function parseArguments() {
const {
values: { "prev-version": previousVersion, "new-version": newVersion },
} = parseArgs({
options: {
"prev-version": { type: "string" },
"new-version": { type: "string" },
},
});
if (
!previousVersion ||
!newVersion ||
+20 -22
View File
@@ -1,19 +1,13 @@
#!/usr/bin/env node
import fs from "node:fs/promises";
import { execa } from "execa";
import spawn from "nano-spawn";
const CSPELL_CONFIG_FILE = new URL("../cspell.json", import.meta.url);
const updateConfig = (config) =>
fs.writeFile(CSPELL_CONFIG_FILE, JSON.stringify(config, undefined, 4) + "\n");
const runSpellcheck = (options) => {
const { yarnArgs, args, execaOptions } = {
yarnArgs: [],
args: [],
...options,
};
return execa("yarn", [...yarnArgs, "lint:spellcheck", ...args], execaOptions);
};
const runSpellcheck = (args, options) =>
spawn("yarn", ["lint:spellcheck", ...args], options);
console.log("Empty words ...");
const config = JSON.parse(await fs.readFile(CSPELL_CONFIG_FILE));
@@ -21,20 +15,24 @@ const original = config.words;
await updateConfig({ ...config, words: [] });
console.log("Running spellcheck with empty words ...");
const { stdout } = await runSpellcheck({
args: ["--words-only", "--unique"],
execaOptions: { reject: false },
});
let stdout = "";
try {
await runSpellcheck(["--words-only", "--unique"]);
} catch (error) {
({ stdout } = error);
}
const words = stdout
.split("\n")
// Remove upper case word, if lower case one already exists
.filter((word, _, words) => {
const lowerCased = word.toLowerCase();
return lowerCased === word || !words.includes(lowerCased);
})
// Compare function from https://github.com/streetsidesoftware/vscode-spell-checker/blob/2fde3bc5c658ee51da5a56580aa1370bf8174070/packages/client/src/settings/CSpellSettings.ts#L78
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
? stdout
.split("\n")
// Remove upper case word, if lower case one already exists
.filter((word, _, words) => {
const lowerCased = word.toLowerCase();
return lowerCased === word || !words.includes(lowerCased);
})
// Compare function from https://github.com/streetsidesoftware/vscode-spell-checker/blob/2fde3bc5c658ee51da5a56580aa1370bf8174070/packages/client/src/settings/CSpellSettings.ts#L78
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
: [];
config.words = words;
const removed = original.filter((word) => !words.includes(word));
@@ -58,6 +56,6 @@ console.log("Updating words ...");
await updateConfig(config);
console.log("Running spellcheck with new words ...");
await runSpellcheck({ execaOptions: { stdout: "inherit" } });
await runSpellcheck([], { stdio: "inherit" });
console.log("CSpell config file updated.");
+2 -2
View File
@@ -7,7 +7,7 @@ Remove revs without actual file change
*/
import fs from "node:fs/promises";
import { execa } from "execa";
import spawn from "nano-spawn";
const FILE = new URL("../.git-blame-ignore-revs", import.meta.url);
const content = await fs.readFile(FILE, "utf8");
@@ -17,7 +17,7 @@ const revsToRemove = new Set();
const IGNORED_FILES = new Set(["package.json", "yarn.lock"]);
async function getChangedFiles(rev) {
const { stdout } = await execa("git", [
const { stdout } = await spawn("git", [
"show",
"--name-only",
"--pretty=",
+1 -22
View File
@@ -6,6 +6,7 @@ import createEsmUtils from "esm-utils";
import fg from "fast-glob";
import semver from "semver";
import {
categories,
changelogUnreleasedDirPath,
changelogUnreleasedDirs,
getEntries,
@@ -36,28 +37,6 @@ const postFile = path.join(
`${new Date().toISOString().replace(/T.+/u, "")}-${nextVersion}.md`,
);
const categories = [
{ dir: "javascript", title: "JavaScript" },
{ dir: "typescript", title: "TypeScript" },
{ dir: "flow", title: "Flow" },
{ dir: "json", title: "JSON" },
{ dir: "css", title: "CSS" },
{ dir: "scss", title: "SCSS" },
{ dir: "less", title: "Less" },
{ dir: "html", title: "HTML" },
{ dir: "vue", title: "Vue" },
{ dir: "angular", title: "Angular" },
{ dir: "lwc", title: "LWC" },
{ dir: "handlebars", title: "Ember / Handlebars" },
{ dir: "graphql", title: "GraphQL" },
{ dir: "markdown", title: "Markdown" },
{ dir: "mdx", title: "MDX" },
{ dir: "yaml", title: "YAML" },
{ dir: "api", title: "API" },
{ dir: "cli", title: "CLI" },
{ dir: "misc", title: "Miscellaneous" },
];
const categoriesByDir = new Map(
categories.map((category) => [category.dir, category]),
);
+2
View File
@@ -134,6 +134,7 @@ function getSyntaxFromCategory(category) {
case "angular":
case "html":
case "lwc":
case "mjml":
return "html";
case "cli":
return "sh";
@@ -150,6 +151,7 @@ function getSyntaxFromCategory(category) {
case "flow":
case "javascript":
case "api":
case "misc":
return "jsx";
case "typescript":
return "tsx";
+2 -3
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node
import fs from "node:fs";
import { LinesAndColumns } from "lines-and-columns";
import indexToPosition from "index-to-position";
import { outdent } from "outdent";
import { CHANGELOG_CATEGORIES } from "./utils/changelog-categories.js";
@@ -142,9 +142,8 @@ for (const category of CHANGELOG_CATEGORIES) {
function getCommentDescription(content, comment) {
const start = content.indexOf(comment);
const end = start + comment.length;
const linesAndColumns = new LinesAndColumns(content);
const [startLine, endLine] = [start, end].map(
(index) => linesAndColumns.locationForIndex(index).line + 1,
(index) => indexToPosition(content, index, { oneBased: true }).line,
);
if (startLine === endLine) {
+1 -1
View File
@@ -15,5 +15,5 @@
"engines": {
"node": ">=20"
},
"packageManager": "yarn@4.6.0"
"packageManager": "yarn@4.9.0"
}
+5 -1
View File
@@ -56,13 +56,17 @@ for (let step of [
},
params.manual ? steps.publishToNpm : steps.waitForBotRelease,
steps.showInstructionsAfterNpmPublish,
{
name: "Merge release notes PR",
process: steps.mergeBlogPost,
},
steps.updateDependentsCount,
steps.bumpPrettier,
{
name: "Cleaning changelog",
process: steps.cleanChangelog,
skip: params.dry || params.next,
},
steps.bumpPrettier,
steps.postPublishSteps,
]) {
if (typeof step === "function") {
+2 -2
View File
@@ -11,10 +11,10 @@ export default async function generateBundles({ dry, version, manual }) {
runYarn(["build", "--clean", "--print-size", "--compare-size"]),
);
const builtPkg = await readJson("dist/package.json");
const builtPkg = await readJson("dist/prettier/package.json");
if (!dry && builtPkg.version !== version) {
throw new Error(
`Expected ${version} in dist/package.json but found ${builtPkg.version}`,
`Expected ${version} in dist/prettier/package.json but found ${builtPkg.version}`,
);
}
+1
View File
@@ -5,6 +5,7 @@ export { default as cleanChangelog } from "./clean-changelog.js";
export { default as generateBundles } from "./generate-bundles.js";
export { default as installDependencies } from "./install-dependencies.js";
export { default as lintFiles } from "./lint-files.js";
export { default as mergeBlogPost } from "./merge-blog-post.js";
export { default as postPublishSteps } from "./post-publish-steps.js";
export { default as publishToNpm } from "./publish-to-npm.js";
export { default as pushToGit } from "./push-to-git.js";
+16 -6
View File
@@ -1,12 +1,22 @@
import spawn from "nano-spawn";
import fs from "node:fs/promises";
import { runGit, runYarn } from "../utils.js";
export default async function installDependencies() {
await spawn("rm", ["-rf", "node_modules"]);
await runYarn(["install"]);
const PROJECT_ROOT = new URL("../../../", import.meta.url);
await spawn("rm", ["-rf", "node_modules"], { cwd: "./website" });
await runYarn(["install"], { cwd: "./website" });
async function installDependenciesInDirectory(directory) {
await fs.rm(new URL("./node_modules/", directory), {
recursive: true,
force: true,
});
await runYarn("install", { cwd: directory });
}
export default async function installDependencies() {
await Promise.all(
[PROJECT_ROOT, new URL("./website/", PROJECT_ROOT)].map((directory) =>
installDependenciesInDirectory(directory),
),
);
const { stdout: status } = await runGit(["ls-files", "-m"]);
if (status) {
+25
View File
@@ -0,0 +1,25 @@
import styleText from "node-style-text";
import semver from "semver";
import { runGit, waitForEnter } from "../utils.js";
export default async function mergeBlogPost({
dry,
repo,
version,
previousVersion,
}) {
if (dry) {
return;
}
if (semver.diff(version, previousVersion) === "patch") {
return;
}
console.log(
styleText.yellow.bold("Please merge the release notes PR if exits."),
);
await waitForEnter();
await runGit(["pull", "--repo", repo]);
}
+1 -1
View File
@@ -24,7 +24,7 @@ export default async function publishToNpm({ dry }) {
args.push("--otp", otp);
}
await spawn("npm", args, { cwd: "./dist" });
await spawn("npm", args, { cwd: "./dist/prettier" });
};
/**
@@ -1,7 +1,7 @@
import styleText from "node-style-text";
import { fetchText, logPromise, processFile, runGit } from "../utils.js";
async function update() {
async function update({ repo }) {
const npmPage = await logPromise(
"Fetching npm dependents count",
fetchText("https://www.npmjs.com/package/prettier"),
@@ -56,7 +56,7 @@ async function update() {
await logPromise("Committing and pushing to remote", async () => {
await runGit(["add", "."]);
await runGit(["commit", "-m", "Update dependents count"]);
await runGit(["push"]);
await runGit(["push", "--repo", repo]);
});
}
}
@@ -71,13 +71,13 @@ function formatNumber(value) {
return Math.floor(value / 1e5) / 10 + " million";
}
export default async function updateDependentsCount({ dry, next }) {
export default async function updateDependentsCount({ dry, next, repo }) {
if (dry || next) {
return;
}
try {
await update();
await update({ repo });
} catch (error) {
console.log(styleText.red.bold(error.message));
}
+3 -3
View File
@@ -2,8 +2,7 @@ import { createRequire } from "node:module";
import path from "node:path";
import url from "node:url";
import webpack from "webpack";
import files from "../../build/config.js";
import { DIST_DIR } from "../../utils/index.js";
import packageConfig from "../../build/config.js";
function runWebpack(config) {
return new Promise((resolve, reject) => {
@@ -30,6 +29,7 @@ const TEMPORARY_DIRECTORY = url.fileURLToPath(
new URL("./.tmp", import.meta.url),
);
const { distDirectory, files } = packageConfig;
/* `require` in `parser-typescript.js`, #12338 */
for (const file of files) {
if (file.platform !== "universal") {
@@ -39,7 +39,7 @@ for (const file of files) {
const stats = await runWebpack({
mode: "production",
entry: path.join(DIST_DIR, file.output.file),
entry: path.join(distDirectory, file.output.file),
output: {
path: TEMPORARY_DIRECTORY,
filename: `${file.output.file}.[contenthash:7].${
+1 -1
View File
@@ -9,5 +9,5 @@
"scripts": {
"test": "node ./index.js"
},
"packageManager": "yarn@4.6.0"
"packageManager": "yarn@4.9.0"
}
@@ -11,5 +11,5 @@
"test": "node test.js",
"test-coverage": "npx nyc node test.js"
},
"packageManager": "yarn@4.6.0"
"packageManager": "yarn@4.9.0"
}
@@ -0,0 +1,55 @@
const messageId = "print-function-parameter-order";
const expectedParameters = ["path", "options", "print"];
export default {
meta: {
type: "suggestion",
messages: {
[messageId]:
"`{{functionName}}` function parameters should in order of `path`, `options` and `print`.",
},
},
create(context) {
const sourceCode = context.getSourceCode();
return {
":function[params.length>=3]"(node) {
const parameterNames = node.params.map((node) =>
node.type === "Identifier" ? node.name : "",
);
// `embed` function order is `textToDoc, print, path, options`
if (parameterNames.includes("textToDoc")) {
return;
}
// Only if all three parameters exists
if (expectedParameters.some((name) => !parameterNames.includes(name))) {
return;
}
// In correct order
if (
expectedParameters.every(
(name, index) => name === parameterNames[index],
)
) {
return;
}
const firstToken = sourceCode.getFirstToken(node);
const tokenBeforeBody = sourceCode.getTokenBefore(node.body);
const functionName = node.id ? node.id.name : "print*";
context.report({
node,
loc: {
start: firstToken.loc.start,
end: tokenBeforeBody.loc.end,
},
messageId,
data: { functionName },
});
},
};
},
};
@@ -640,6 +640,31 @@ test("prefer-is-non-empty-array", {
],
});
test("print-function-parameter-order", {
valid: [
"function printFoo(path, options, print) {}",
"function printFoo(path, opts, print) {}",
"function printFoo(path, options, printPath) {}",
"function printFoo() {}",
"function printFoo(path, print) {}",
"function printFoo(path, options) {}",
"function printFoo(print, options) {}",
"function embed(path, print, textToDoc, options) {}",
],
invalid: [
"function printFoo(path, print, options) {}",
"function printFoo(path, print, options, args) {}",
"function printFoo(options, print, path) {}",
"const printFoo = function (path, print, options) {}",
"const printFoo = function printFoo(path, print, options) {}",
"const printFoo = (path, print, options) => {}",
].map((code) => ({
code,
output: null,
errors: 1,
})),
});
test("no-empty-flat-contents-for-if-break", {
valid: [
"ifBreak('foo', 'bar')",
+1
View File
@@ -11,6 +11,7 @@ export const CHANGELOG_CATEGORIES = [
"json",
"less",
"lwc",
"mjml",
"markdown",
"mdx",
"scss",
+23
View File
@@ -10,6 +10,29 @@ export const changelogUnreleasedDirPath = path.join(
"../../changelog_unreleased",
);
export const categories = [
{ dir: "javascript", title: "JavaScript" },
{ dir: "typescript", title: "TypeScript" },
{ dir: "flow", title: "Flow" },
{ dir: "json", title: "JSON" },
{ dir: "css", title: "CSS" },
{ dir: "scss", title: "SCSS" },
{ dir: "less", title: "Less" },
{ dir: "html", title: "HTML" },
{ dir: "vue", title: "Vue" },
{ dir: "angular", title: "Angular" },
{ dir: "lwc", title: "LWC" },
{ dir: "mjml", title: "MJML" },
{ dir: "handlebars", title: "Ember / Handlebars" },
{ dir: "graphql", title: "GraphQL" },
{ dir: "markdown", title: "Markdown" },
{ dir: "mdx", title: "MDX" },
{ dir: "yaml", title: "YAML" },
{ dir: "api", title: "API" },
{ dir: "cli", title: "CLI" },
{ dir: "misc", title: "Miscellaneous" },
];
export const changelogUnreleasedDirs = fs
.readdirSync(changelogUnreleasedDirPath, {
withFileTypes: true,