Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | import { createProjectGraphAsync, type PromiseExecutor } from '@nx/devkit'; import { uniq } from 'lodash-es'; import { FsTree } from 'nx/src/generators/tree'; import { type TsConfigJson } from 'type-fest'; import { assert } from '@amalia/ext/typescript'; import { makeGitlabClient } from '@amalia/vendors/gitlab'; import { type StrictModeExecutorSchema } from './schema.d'; type PackageAndNonStrictDependencies = { package: string; root: string; isStrict: boolean; nonStrictDependencies: string[]; }; const getPackageEmoji = ({ isStrict, nonStrictDependencies, }: Pick<PackageAndNonStrictDependencies, 'isStrict' | 'nonStrictDependencies'>): string => isStrict ? (nonStrictDependencies.length ? '⚠️' : '✅') : nonStrictDependencies.length ? '❌' : '🔜'; const buildDepListItem = (dep: string): string => `<li>\`${dep}\`</li>`; const buildPackageTableRow = ({ package: pkg, isStrict, nonStrictDependencies, }: PackageAndNonStrictDependencies): string => `|${getPackageEmoji({ isStrict, nonStrictDependencies })} \`${pkg}\`|<ul>${nonStrictDependencies.map((dep) => buildDepListItem(dep)).join('')}</ul>|`; const buildPackagesTable = (packages: PackageAndNonStrictDependencies[]): string => `|Package|Non-strict dependencies|\n|---|---|\n${packages.map((dep) => buildPackageTableRow(dep)).join('\n')}`; const buildAllDetailsTable = (packages: PackageAndNonStrictDependencies[]): string => `<details><summary>All details</summary>\n<ul><li>✅: package is in strict mode.</li><li>⚠️: package is in strict mode but has non-strict dependencies.</li><li>🔜: package is not in strict mode but has no non-strict dependencies.</li><li>❌: package is not in strict mode and has non-strict dependencies.</li></ul>\n\n${buildPackagesTable(packages)}\n\n</details>`; const buildSummary = (packages: PackageAndNonStrictDependencies[]): string => { const nonStrictPackages = packages.filter(({ isStrict }) => !isStrict); const nonStrictPackagesWithNoNonStrictDependencies = nonStrictPackages.filter( ({ nonStrictDependencies }) => !nonStrictDependencies.length, ); if (!nonStrictPackages.length) { return '## Summary\n\nAll packages are in strict mode.'; } return `## Summary\n\n- Total packages: ${packages.length}.\n- Non-strict packages: ${nonStrictPackages.length}.\n- Non-strict packages with no non-strict dependencies (prioritize these packages): <ul>${nonStrictPackagesWithNoNonStrictDependencies.map((dep) => buildDepListItem(dep.package)).join('')}</ul>`; }; const buildMdOutput = (packages: PackageAndNonStrictDependencies[]): string => `${buildSummary(packages)}\n\n${buildAllDetailsTable(packages)}`; const isTsPackage = (pkgRoot: string, tree: FsTree): boolean => tree.exists(`${pkgRoot}/tsconfig.json`); const isPackageStrict = (pkgRoot: string, tree: FsTree): boolean => { if (isTsPackage(pkgRoot, tree)) { const tsConfig = JSON.parse(tree.read(`${pkgRoot}/tsconfig.json`, 'utf-8')!) as TsConfigJson; return !!tsConfig.compilerOptions?.strict; } return false; }; const runExecutor: PromiseExecutor<StrictModeExecutorSchema> = async ( { token: cliToken, projectId, issueIid }, context, ) => { const token = cliToken ?? process.env['GITLAB_API_TOKEN']; assert( token, 'You need to provide your GitLab personal access from --token CLI parameter or from GITLAB_API_TOKEN environment variable.', ); const tree = new FsTree(context.cwd, false); const projectGraph = await createProjectGraphAsync({ exitOnError: true }); const internalDependenciesPerPackage = Object.values(projectGraph.dependencies) .flat() .filter((dep) => !dep.target.startsWith('npm:') && dep.source !== '@amalia/monorepo') .reduce( (acc, dep) => { acc[dep.source] = dep.source in acc ? acc[dep.source] : []; acc[dep.source].push(dep.target); return acc; }, {} as Record<string, string[]>, ); const allPackages = Object.values(projectGraph.nodes) .map(({ data, name }) => ({ ...data, name, })) .filter(({ root }) => isTsPackage(root, tree)) .toSorted((a, b) => a.root.localeCompare(b.root)); const nonStrictPackages = new Set( allPackages.filter((data) => !isPackageStrict(data.root, tree)).map(({ name }) => name), ); const getNonStrictDependencies = (pkg: string): string[] => uniq( (pkg in internalDependenciesPerPackage ? internalDependenciesPerPackage[pkg] : []).flatMap((dep) => { if (nonStrictPackages.has(dep)) { return [dep, ...getNonStrictDependencies(dep)]; } return []; }), ); const packagesWithNonStrictDependencies = allPackages.map( (pkg): PackageAndNonStrictDependencies => ({ package: pkg.name, root: pkg.root, isStrict: !nonStrictPackages.has(pkg.name), nonStrictDependencies: getNonStrictDependencies(pkg.name), }), ); const gitlabClient = makeGitlabClient({ token }); const { web_url } = await gitlabClient.Issues.edit(projectId, issueIid, { description: buildMdOutput(packagesWithNonStrictDependencies), }); /* eslint-disable no-console */ console.log(`Updated description of issue ${web_url as string}.`); /* eslint-enable no-console */ return { success: true, }; }; export default runExecutor; |