From 4fda05da1e78782709c129665249718ccc686378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Pintos=20L=C3=B3pez?= Date: Mon, 14 Oct 2024 18:47:44 +0200 Subject: [PATCH] chore: add foundation-scripts package --- package.json | 1 + packages/foundation/tools/scripts/.gitignore | 2 + .../tools/scripts/.lintstagedrc.json | 5 ++ .../scripts/bin/getAffectedProjectsChunks.js | 60 +++++++++++++++++++ .../scripts/bin/writeCommonJsPackageJson.js | 43 +++++++++++++ .../tools/scripts/bin/writeEsmPackageJson.js | 43 +++++++++++++ .../foundation/tools/scripts/package.json | 21 +++++++ .../tools/scripts/prettier.config.mjs | 3 + .../tools/scripts/src/promisifiedExec.js | 42 +++++++++++++ 9 files changed, 220 insertions(+) create mode 100644 packages/foundation/tools/scripts/.gitignore create mode 100644 packages/foundation/tools/scripts/.lintstagedrc.json create mode 100755 packages/foundation/tools/scripts/bin/getAffectedProjectsChunks.js create mode 100755 packages/foundation/tools/scripts/bin/writeCommonJsPackageJson.js create mode 100755 packages/foundation/tools/scripts/bin/writeEsmPackageJson.js create mode 100644 packages/foundation/tools/scripts/package.json create mode 100644 packages/foundation/tools/scripts/prettier.config.mjs create mode 100644 packages/foundation/tools/scripts/src/promisifiedExec.js diff --git a/package.json b/package.json index b9841d21..97c6f128 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@inversifyjs/foundation-prettier-config": "workspace:*", "@inversifyjs/foundation-eslint-config": "workspace:*", "@inversifyjs/foundation-jest-config": "workspace:*", + "@inversifyjs/foundation-scripts": "workspace:*", "@inversifyjs/foundation-typescript-config": "workspace:*", "husky": "9.1.6", "lint-staged": "15.2.10", diff --git a/packages/foundation/tools/scripts/.gitignore b/packages/foundation/tools/scripts/.gitignore new file mode 100644 index 00000000..44a0d434 --- /dev/null +++ b/packages/foundation/tools/scripts/.gitignore @@ -0,0 +1,2 @@ +# npm installed packages +/node_modules/ diff --git a/packages/foundation/tools/scripts/.lintstagedrc.json b/packages/foundation/tools/scripts/.lintstagedrc.json new file mode 100644 index 00000000..6105c241 --- /dev/null +++ b/packages/foundation/tools/scripts/.lintstagedrc.json @@ -0,0 +1,5 @@ +{ + "*.js": [ + "prettier --write" + ] +} diff --git a/packages/foundation/tools/scripts/bin/getAffectedProjectsChunks.js b/packages/foundation/tools/scripts/bin/getAffectedProjectsChunks.js new file mode 100755 index 00000000..a8fd4001 --- /dev/null +++ b/packages/foundation/tools/scripts/bin/getAffectedProjectsChunks.js @@ -0,0 +1,60 @@ +#!/usr/bin/env node + +import process from 'node:process'; +import { promisifiedExec } from '../src/promisifiedExec.js'; + +/** + * @param {Array} elements Elements + * @param {number} chunks chunks amount + * @returns {Array.} + */ +function buildChunks(elements, chunks) { + const chunkSize = Math.ceil(elements.length / chunks); + + const chunksArray = []; + + for (let i = 0; i < elements.length; i += chunkSize) { + chunksArray.push(elements.slice(i, i + chunkSize)); + } + + return chunksArray; +} + +const TURBOREPO_ROOT_PACKAGE_NAME = '//'; +const TURBOREPO_TASK_NOT_FOUND_MAGIC_STRING = '\u003cNONEXISTENT\u003e'; + +const taskToDryRun = process.argv[2]; +const baseRef = process.argv[3]; +const chunks = parseInt(process.argv[4]); + +let execCommand = `pnpm exec turbo run ${taskToDryRun} --dry=json`; + +if (baseRef !== undefined) { + execCommand += ` --filter ...[${baseRef}]`; +} + +const stringifiedDryRun = (await promisifiedExec(execCommand)).trim(); + +const dryRunResult = JSON.parse(stringifiedDryRun); + +/** @type {Array.} */ +const dryResultPackageNames = dryRunResult.packages.filter( + (packageName) => packageName !== TURBOREPO_ROOT_PACKAGE_NAME, +); + +const tasks = dryRunResult.tasks.filter((task) => + dryResultPackageNames.some( + (packageName) => + task.taskId === `${packageName}#${taskToDryRun}` && + !task.command.includes(TURBOREPO_TASK_NOT_FOUND_MAGIC_STRING), + ), +); + +/** @type {Array.} */ +const packageNames = tasks.map((task) => task.package); + +const packageNameChunks = buildChunks(packageNames, chunks).filter( + (chunk) => chunk.length > 0, +); + +process.stdout.write(JSON.stringify(packageNameChunks)); diff --git a/packages/foundation/tools/scripts/bin/writeCommonJsPackageJson.js b/packages/foundation/tools/scripts/bin/writeCommonJsPackageJson.js new file mode 100755 index 00000000..c0414f38 --- /dev/null +++ b/packages/foundation/tools/scripts/bin/writeCommonJsPackageJson.js @@ -0,0 +1,43 @@ +#!/usr/bin/env node + +import fs from 'node:fs/promises'; +import { argv } from 'node:process'; +import path from 'node:path'; +import { writeFile } from 'node:fs/promises'; + +/** + * @param {string} path + * @returns {Promise} + */ +async function pathExists(path) { + try { + await fs.access(path); + return true; + } catch (_err) { + return false; + } +} + +const directory = argv[2]; + +if (directory === undefined) { + throw new Error('Expected a path'); +} + +const directoryExists = await pathExists(directory); + +if (!directoryExists) { + throw new Error(`Path ${directory} not found`); +} + +const filePath = path.join(directory, 'package.json'); + +const packageJsonFileContent = JSON.stringify( + { + type: 'commonjs', + }, + undefined, + 2, +); + +await writeFile(filePath, packageJsonFileContent); diff --git a/packages/foundation/tools/scripts/bin/writeEsmPackageJson.js b/packages/foundation/tools/scripts/bin/writeEsmPackageJson.js new file mode 100755 index 00000000..2941aab5 --- /dev/null +++ b/packages/foundation/tools/scripts/bin/writeEsmPackageJson.js @@ -0,0 +1,43 @@ +#!/usr/bin/env node + +import fs from 'node:fs/promises'; +import { argv } from 'node:process'; +import path from 'node:path'; +import { writeFile } from 'node:fs/promises'; + +/** + * @param {string} path + * @returns {Promise} + */ +async function pathExists(path) { + try { + await fs.access(path); + return true; + } catch (_err) { + return false; + } +} + +const directory = argv[2]; + +if (directory === undefined) { + throw new Error('Expected a path'); +} + +const directoryExists = await pathExists(directory); + +if (!directoryExists) { + throw new Error(`Path ${directory} not found`); +} + +const filePath = path.join(directory, 'package.json'); + +const packageJsonFileContent = JSON.stringify( + { + type: 'module', + }, + undefined, + 2, +); + +await writeFile(filePath, packageJsonFileContent); diff --git a/packages/foundation/tools/scripts/package.json b/packages/foundation/tools/scripts/package.json new file mode 100644 index 00000000..4abc1c8b --- /dev/null +++ b/packages/foundation/tools/scripts/package.json @@ -0,0 +1,21 @@ +{ + "name": "@inversifyjs/foundation-scripts", + "private": true, + "description": "Common scripts for monorepo packages", + "repository": { + "type": "git", + "url": "git+https://github.com/notaphplover/one-game.git" + }, + "author": "Multiple authors", + "license": "MIT", + "bugs": { + "url": "https://github.com/notaphplover/one-game/issues" + }, + "type": "module", + "homepage": "https://github.com/notaphplover/one-game#readme", + "bin": { + "foundation-get-affected-project-chunks": "./bin/getAffectedProjectsChunks.js", + "foundation-ts-package-cjs": "./bin/writeCommonJsPackageJson.js", + "foundation-ts-package-esm": "./bin/writeEsmPackageJson.js" + } +} diff --git a/packages/foundation/tools/scripts/prettier.config.mjs b/packages/foundation/tools/scripts/prettier.config.mjs new file mode 100644 index 00000000..70361db5 --- /dev/null +++ b/packages/foundation/tools/scripts/prettier.config.mjs @@ -0,0 +1,3 @@ +import config from '@inversifyjs/foundation-prettier-config'; + +export default config; diff --git a/packages/foundation/tools/scripts/src/promisifiedExec.js b/packages/foundation/tools/scripts/src/promisifiedExec.js new file mode 100644 index 00000000..6413ac80 --- /dev/null +++ b/packages/foundation/tools/scripts/src/promisifiedExec.js @@ -0,0 +1,42 @@ +import { exec } from 'node:child_process'; + +/** + * @param {string} command command + * @param {PromisifiedExecOptions} options true to pipe standard input output and error streams + * @returns {Promise} + */ +export async function promisifiedExec(command, options) { + options = { + cwd: options?.cwd, + interactive: options?.interactive ?? false, + }; + + return new Promise((resolve, reject) => { + const childProcess = exec( + command, + { + cwd: options.cwd, + }, + (error, stdout) => { + if (error === null) { + resolve(stdout); + } else { + reject(error); + } + }, + ); + + if (options.interactive) { + childProcess.stdout.pipe(process.stdout); + childProcess.stderr.pipe(process.stderr); + + process.stdin.pipe(childProcess.stdin); + } + }); +} + +/** + * @typedef {Object} PromisifiedExecOptions + * @property {boolean} interactive + * @property {string} cwd + */