Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Update release process #16036

Merged
merged 4 commits into from Jun 23, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
230 changes: 48 additions & 182 deletions Makefile.js
Expand Up @@ -12,7 +12,6 @@

const checker = require("npm-license"),
ReleaseOps = require("eslint-release"),
dateformat = require("dateformat"),
fs = require("fs"),
glob = require("glob"),
marked = require("marked"),
Expand All @@ -33,7 +32,7 @@ require("shelljs/make");
* @see https://github.com/shelljs/shelljs/blob/124d3349af42cb794ae8f78fc9b0b538109f7ca7/make.js#L4
* @see https://github.com/DefinitelyTyped/DefinitelyTyped/blob/3aa2d09b6408380598cfb802743b07e1edb725f3/types/shelljs/make.d.ts#L8-L11
*/
const { cat, cd, cp, echo, exec, exit, find, ls, mkdir, pwd, rm, test } = require("shelljs");
const { cat, cd, echo, exec, exit, find, ls, mkdir, pwd, test } = require("shelljs");

//------------------------------------------------------------------------------
// Settings
Expand All @@ -60,8 +59,10 @@ const NODE = "node ", // intentional extra space
TEMP_DIR = "./tmp/",
DEBUG_DIR = "./debug/",
BUILD_DIR = "build",
DOCS_DIR = "../website/docs",
SITE_DIR = "../website/",
SITE_DIR = "../eslint.org",
DOCS_DIR = "./docs",
DOCS_SRC_DIR = path.join(DOCS_DIR, "src"),
DOCS_DATA_DIR = path.join(DOCS_SRC_DIR, "_data"),
PERF_TMP_DIR = path.join(TEMP_DIR, "eslint", "performance"),

// Utilities - intentional extra space at the end of each string
Expand Down Expand Up @@ -144,32 +145,27 @@ function generateBlogPost(releaseInfo, prereleaseMajorVersion) {
now = new Date(),
month = now.getMonth() + 1,
day = now.getDate(),
filename = `../website/_posts/${now.getFullYear()}-${
filename = path.join(SITE_DIR, `src/content/blog/${now.getFullYear()}-${
month < 10 ? `0${month}` : month}-${
day < 10 ? `0${day}` : day}-eslint-v${
releaseInfo.version}-released.md`;
releaseInfo.version}-released.md`);

output.to(filename);
}

/**
* Generates a doc page with formatter result examples
* @param {Object} formatterInfo Linting results from each formatter
* @param {string} [prereleaseVersion] The version used for a prerelease. This
* changes where the output is stored.
* @returns {void}
*/
function generateFormatterExamples(formatterInfo, prereleaseVersion) {
function generateFormatterExamples(formatterInfo) {
const output = ejs.render(cat("./templates/formatter-examples.md.ejs"), formatterInfo);
let filename = "../website/docs/user-guide/formatters/index.md",
htmlFilename = "../website/docs/user-guide/formatters/html-formatter-example.html";

if (prereleaseVersion) {
filename = filename.replace("/docs", `/docs/${prereleaseVersion}`);
htmlFilename = htmlFilename.replace("/docs", `/docs/${prereleaseVersion}`);
if (!test("-d", path.dirname(filename))) {
mkdir(path.dirname(filename));
}
const outputDir = path.join(DOCS_SRC_DIR, "user-guide/formatters/"),
filename = path.join(outputDir, "index.md"),
htmlFilename = path.join(outputDir, "html-formatter-example.html");

if (!test("-d", outputDir)) {
mkdir(outputDir);
}

output.to(filename);
Expand All @@ -181,9 +177,8 @@ function generateFormatterExamples(formatterInfo, prereleaseVersion) {
* @returns {void}
*/
function generateRuleIndexPage() {
const legacyWebsiteOutputFile = "../website/_data/rules.yml",
docsSiteOutputFile = "docs/src/_data/rules.json",
docsSiteMetaOutputFile = "docs/src/_data/rules_meta.json",
const docsSiteOutputFile = path.join(DOCS_DATA_DIR, "rules.json"),
docsSiteMetaOutputFile = path.join(DOCS_DATA_DIR, "rules_meta.json"),
ruleTypes = "conf/rule-type-list.json",
ruleTypesData = JSON.parse(cat(path.resolve(ruleTypes)));

Expand Down Expand Up @@ -240,9 +235,6 @@ function generateRuleIndexPage() {
JSON.stringify(ruleTypesData, null, 4).to(docsSiteOutputFile);
JSON.stringify(meta, null, 4).to(docsSiteMetaOutputFile);

const legacyOutput = yaml.dump(ruleTypesData, { sortKeys: true });

legacyOutput.to(legacyWebsiteOutputFile);
}

/**
Expand All @@ -256,26 +248,22 @@ function commitSiteToGit(tag) {

cd(SITE_DIR);
exec("git add -A .");
exec(`git commit -m "Autogenerated new docs and demo at ${dateformat(new Date())}"`);

if (tag) {
exec(`git tag ${tag}`);
}

exec("git fetch origin && git rebase origin/master");
exec(`git commit -m "Added release blog post for ${tag}"`);
exec(`git tag ${tag}`);
exec("git fetch origin && git rebase origin/main");
cd(currentDir);
}

/**
* Publishes the changes in an adjacent `website` repository to the remote. The
* Publishes the changes in an adjacent `eslint.org` repository to the remote. The
* site should already have local commits (e.g. from running `commitSiteToGit`).
* @returns {void}
*/
function publishSite() {
const currentDir = pwd();

cd(SITE_DIR);
exec("git push origin master --tags");
exec("git push origin HEAD --tags");
cd(currentDir);
}

Expand All @@ -294,7 +282,7 @@ function generateRelease() {
commitSiteToGit(`v${releaseInfo.version}`);

echo("Updating commit with docs data");
exec("git add docs/src/_data && git commit --amend --no-edit");
exec("git add docs/ && git commit --amend --no-edit");
exec(`git tag -a -f v${releaseInfo.version} -m ${releaseInfo.version}`);
}

Expand Down Expand Up @@ -636,60 +624,14 @@ target.test = function() {
target.checkLicenses();
};

target.gensite = function(prereleaseVersion) {
echo("Generating eslint.org");

let docFiles = [
"/rules/",
"/user-guide/",
"/maintainer-guide/",
"/developer-guide/",
"/about/"
];

// append version
if (prereleaseVersion) {
docFiles = docFiles.map(docFile => `/${prereleaseVersion}${docFile}`);
}

// 1. create temp and build directory
echo("> Creating a temporary directory (Step 1)");
if (!test("-d", TEMP_DIR)) {
mkdir(TEMP_DIR);
}

// 2. remove old files from the site
echo("> Removing old files (Step 2)");
docFiles.forEach(filePath => {
const fullPath = path.join(DOCS_DIR, filePath),
htmlFullPath = fullPath.replace(".md", ".html");

if (test("-f", fullPath)) {
rm("-rf", fullPath);
target.gensite = function() {
echo("Generating documentation");

if (filePath.includes(".md") && test("-f", htmlFullPath)) {
rm("-rf", htmlFullPath);
}
}
});

// 3. Copy docs folder to a temporary directory
echo("> Copying the docs folder (Step 3)");
docFiles.forEach(filePath => {
const pathToCopy = path.join("docs/src", filePath, "*"),
tempPath = path.join(TEMP_DIR, filePath);

if (!test("-d", tempPath)) {
mkdir(tempPath);
}

cp("-rf", pathToCopy, tempPath);
});
const DOCS_RULES_DIR = path.join(DOCS_SRC_DIR, "rules");
const RULE_VERSIONS_FILE = path.join(DOCS_SRC_DIR, "_data/rule_versions.json");

// special case (for now)
rm("-rf", path.join(TEMP_DIR, "pages"));

let versions = test("-f", "./versions.json") ? JSON.parse(cat("./versions.json")) : {};
// Set up rule version information
let versions = test("-f", RULE_VERSIONS_FILE) ? JSON.parse(cat(RULE_VERSIONS_FILE)) : {};

if (!versions.added) {
versions = {
Expand All @@ -698,117 +640,41 @@ target.gensite = function(prereleaseVersion) {
};
}

const { Linter } = require(".");
const rules = new Linter().getRules();

const RECOMMENDED_TEXT = "\n\n(recommended) The `\"extends\": \"eslint:recommended\"` property in a configuration file enables this rule.";
const FIXABLE_TEXT = "\n\n(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.";
const HAS_SUGGESTIONS_TEXT = "\n\n(hasSuggestions) Some problems reported by this rule are manually fixable by editor [suggestions](../developer-guide/working-with-rules#providing-suggestions).";

// 4. Loop through all files in temporary directory
process.stdout.write("> Updating files (Steps 4-9): 0/... - ...\r");
const tempFiles = find(TEMP_DIR);
const length = tempFiles.length;
// 1. Update rule meta data by checking rule docs - important to catch removed rules
echo("> Updating rule version meta data (Step 1)");
const ruleDocsFiles = find(DOCS_RULES_DIR);

tempFiles.forEach((filename, i) => {
ruleDocsFiles.forEach((filename, i) => {
if (test("-f", filename) && path.extname(filename) === ".md") {

const rulesUrl = "https://github.com/eslint/eslint/tree/HEAD/lib/rules/",
testsUrl = "https://github.com/eslint/eslint/tree/HEAD/tests/lib/rules/",
docsUrl = "https://github.com/eslint/eslint/tree/HEAD/docs/src/rules/",
baseName = path.basename(filename),
sourceBaseName = `${path.basename(filename, ".md")}.js`,
sourcePath = path.join("lib/rules", sourceBaseName),
ruleName = path.basename(filename, ".md"),
filePath = path.posix.join("docs", path.relative("tmp", filename));
let text = cat(filename);

process.stdout.write(`> Updating files (Steps 4-9): ${i}/${length} - ${filePath + " ".repeat(30)}\r`);

// 5. Prepend page title and layout variables at the top of rules
if (path.dirname(filename).includes("rules")) {

// Find out if the rule requires a special docs portion (e.g. if it is recommended and/or fixable)
const rule = rules.get(ruleName);
const isRecommended = rule && rule.meta.docs.recommended;
const isFixable = rule && rule.meta.fixable;
const hasSuggestions = rule && rule.meta.hasSuggestions;
echo(`> Updating rule version meta data (Step 1: ${i + 1}/${ruleDocsFiles.length}): ${filename}`);

text = text.replace("<!--FIXABLE-->", isFixable ? FIXABLE_TEXT : "")
.replace("<!--SUGGESTIONS-->", hasSuggestions ? HAS_SUGGESTIONS_TEXT : "")
.replace("<!--RECOMMENDED-->", isRecommended ? RECOMMENDED_TEXT : "");
const baseName = path.basename(filename, ".md"),
sourceBaseName = `${baseName}.js`,
sourcePath = path.join("lib/rules", sourceBaseName);

if (!versions.added[baseName]) {
versions.added[baseName] = getFirstVersionOfFile(sourcePath);
}

// 6. Remove .md extension for relative links and change README to empty string
text = text.replace(/\((?!https?:\/\/)([^)]*?)\.md(.*?)\)/gu, "($1$2)").replace("README.html", "");

// 7. Check if there's a trailing white line at the end of the file, if there isn't one, add it
if (!/\n$/u.test(text)) {
text = `${text}\n`;
if (!versions.removed[baseName] && !test("-f", sourcePath)) {
versions.removed[baseName] = getFirstVersionOfDeletion(sourcePath);
}

// 8. Append first version of ESLint rule was added at.
if (filename.includes("rules/")) {
if (!versions.added[baseName]) {
versions.added[baseName] = getFirstVersionOfFile(sourcePath);
}
const added = versions.added[baseName];

if (!versions.removed[baseName] && !test("-f", sourcePath)) {
versions.removed[baseName] = getFirstVersionOfDeletion(sourcePath);
}
const removed = versions.removed[baseName];

text += "\n## Version\n\n";
text += removed
? `This rule was introduced in ESLint ${added} and removed in ${removed}.\n`
: `This rule was introduced in ESLint ${added}.\n`;

text += "\n## Resources\n\n";
if (!removed) {
text += `* [Rule source](${rulesUrl}${sourceBaseName})\n`;
text += `* [Test source](${testsUrl}${sourceBaseName})\n`;
}
text += `* [Documentation source](${docsUrl}${baseName})\n`;
}

// 9. Update content of the file with changes
text.to(filename.replace("README.md", "index.md"));
}
});
JSON.stringify(versions).to("./versions.json");
echo(`> Updating files (Steps 4-9)${" ".repeat(50)}`);

// 10. Copy temporary directory to site's docs folder
echo("> Copying the temporary directory into the site's docs folder (Step 10)");
let outputDir = DOCS_DIR;

if (prereleaseVersion) {
outputDir += `/${prereleaseVersion}`;
if (!test("-d", outputDir)) {
mkdir(outputDir);
}
}
cp("-rf", `${TEMP_DIR}*`, outputDir);

// 11. Generate rules index page
if (prereleaseVersion) {
echo("> Skipping generating rules index page because this is a prerelease (Step 11)");
} else {
echo("> Generating the rules index page (Step 11)");
generateRuleIndexPage();
}
JSON.stringify(versions, null, 4).to(RULE_VERSIONS_FILE);

// 12. Delete temporary directory
echo("> Removing the temporary directory (Step 12)");
rm("-rf", TEMP_DIR);
// 2. Generate rules index page meta data
echo("> Generating the rules index page (Step 2)");
generateRuleIndexPage();

// 13. Create Example Formatter Output Page
echo("> Creating the formatter examples (Step 14)");
generateFormatterExamples(getFormatterResults(), prereleaseVersion);
// 3. Create Example Formatter Output Page
echo("> Creating the formatter examples (Step 3)");
generateFormatterExamples(getFormatterResults());

echo("Done generating eslint.org");
echo("Done generating documentation");
};

target.generateRuleIndexPage = generateRuleIndexPage;
Expand Down