UNPKG

@sap/cds-dk

Version:

Command line client and development toolkit for the SAP Cloud Application Programming Model

226 lines (205 loc) 7.01 kB
const os = require("os"); const path = require("path"); const cp = require("child_process"); const term = require("../../../util/term"); const checks = require("../../../lint/checks"); const io = require("../../../lint/io"); const readline = require("readline"); const { renderAndCopy } = require('../../template') const { LINT_DEV, LINT_GLOBAL } = require("../../constants").OPTIONS; const { COMMAND_INIT } = require("../../constants"); const cds = require("../../../cds"); const { mkdirp, exists } = cds.utils; const IS_WIN = os.platform() === "win32"; const VSCODE_FILE_ASSOCIATIONS = { "csv": ["csv", "csv (semicolon)", "tsv", "tab"] } module.exports = class LintTemplate extends require('../../plugin') { static help() { return 'configure cds lint' } constructor() { super(); this.customPath = path.join(cds.root, ".eslint"); this.docsPath = path.join(this.customPath, "docs"); this.rulesPath = path.join(this.customPath, "rules"); this.testsPath = path.join(this.customPath, "tests"); this.lintType = "local"; this.customRuleExample = false; this.missingNpmDependencies = { names: [], msgs: [] }; this.isTest = false; } async canRun() { const { lint } = cds.cli.options if (lint) { // If custom rule example is requested, also trigger // "custom" rule type of setup if (lint.has(LINT_DEV)) { this.customRuleExample = true; } if (lint.has(LINT_GLOBAL)) { this.lintType = "global"; } } return true; } async run() { // Require ESLint v>=8.0.0 if (!this.isTest) { this._checkEslint(); } // To enable custom lint rules ('lint' or 'lint:dev') if (this.lintType === "local") { // Require local ESLint plugin installation and corresponding config this._checkEslintPlugin(); await this._addEslintPluginConfig(); // Include custom lint sample rule and test ('lint:dev') if (this.customRuleExample) { // Generate custom lint rules dirs await this._generateEslintDirs(); if (!exists(".eslint/rules/no-entity-moo.js")) { await renderAndCopy(".eslint/rules", this.rulesPath); } if (!exists(".eslint/tests/no-entity-moo.test.js")) { await renderAndCopy(".eslint/tests", this.testsPath); } } // Generate VS Code extension settings const { getFileExtensions } = require("@sap/eslint-plugin-cds/lib/api"); const ALLOWED_FILE_EXTENSIONS = getFileExtensions(); const extensions = new Set(ALLOWED_FILE_EXTENSIONS.map((ext) => { return path.extname(ext).replace('.', ''); })) extensions.forEach((ext) => { if (ext in VSCODE_FILE_ASSOCIATIONS) { VSCODE_FILE_ASSOCIATIONS[ext].forEach((assoc) => { extensions.add(assoc) }) } }); await io.sanitizeVscodeSettings( path.join(cds.root, ".vscode/settings.json"), this.lintType, Array.from(extensions), this.customRuleExample ); } } async finalize() { if (this.lintType !== "global") { if (this.missingNpmDependencies["names"].length > 0) { const task = `npm install --save-dev ${this.missingNpmDependencies[ "names" ].join(" ")}`; if (cds.cli.command === COMMAND_INIT) { this._runTask(task + ' --package-lock-only'); return; } if (cds.cli.options.force) { console.log( `\n\nAlmost done - installing ${this.missingNpmDependencies["names"].length} npm dependencies:\n${task}` ); this._runTask(task); } else { console.log( `\n\nAlmost done - ${term.warn( `you are missing ${this.missingNpmDependencies["names"].length} npm dependencies` )}:\n` ); this.missingNpmDependencies["msgs"].forEach((msg, i) => { console.log(`(${i + 1}) ${msg}`); if (i === this.missingNpmDependencies["msgs"].length - 1) { console.log("\n"); } }); await this._promptInstallDependencies(task); } } } } _checkEslint() { const eslintCmd = checks.resolveEslint(cds.root, ''); if (!eslintCmd) { this.missingNpmDependencies["names"].push("eslint@^9"); this.missingNpmDependencies["msgs"].push("ESLint v>=9.0.0"); } return; } _checkEslintPlugin() { const eslintPluginLocal = checks.resolvePlugin( "@sap/eslint-plugin-cds", cds.root ); // Require locally installed ESLint CDS plugin if (!eslintPluginLocal) { this.missingNpmDependencies["names"].push("@sap/eslint-plugin-cds@^3"); this.missingNpmDependencies["msgs"].push("ESLint plugin for CDS v>=3.0.0"); } return; } async _generateEslintDirs() { await mkdirp(this.docsPath); await mkdirp(this.rulesPath); await mkdirp(this.testsPath); } async _addEslintPluginConfig() { const { getConfigPath } = require("@sap/eslint-plugin-cds/lib/api"); let configPath = getConfigPath(cds.root); if (!configPath) { configPath = path.join(cds.root, "eslint.config.mjs"); } await io.sanitizeEslintConfig(configPath, this.customRuleExample); } async _promptInstallDependencies(task) { if (!process.stdin.isTTY) { return; // non-interactive shell, like in tests } const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); // Do not install dependencies by default let received = "no"; const timerID = setTimeout(() => { rl.write("no" + os.EOL); }, 10000); // Ask user whether dependencies should be installed const question = `${term.warn("Install dependencies now")} ? [y/n] `; let todos = `${term.error( "Aborted!\nPlease install the following components manually:" )}\n\n${term.warn(task)}`; return new Promise((resolve, reject) => { rl.question(question, (answer) => { rl.close(); clearTimeout(timerID); received = answer.trim().toLowerCase(); // If user confirms or force flag is set, install dependencies if (["y", "yes"].includes(received) || cds.cli.options.force) { resolve(this._runTask(task)); // If use declines or question timeout is reached, print todos } else if (["n", "no"].includes(received.toLowerCase())) { console.log(todos); resolve(); } else { // Close and restart question (with new timeout) rl.close(); clearTimeout(timerID); this._promptInstallDependencies(task).then(resolve).catch(reject); } }); }) } _runTask(task) { let doneTask = true; try { cp.execSync(task, { cwd: cds.root, shell: IS_WIN, stdio: "inherit", }); } catch (err) { doneTask = false; } return doneTask; } };