diff --git a/eslint.config.js b/eslint.config.js index 63b9cc76a..a28169dbc 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -166,7 +166,7 @@ export default tseslint.config( }, { // in bin files, imports with side effects are allowed - files: ['**/bin/**/*.ts', '**/bin/**/*.js'], + files: ['**/bin/**/*.ts', '**/bin/**/*.js', '**/bin.js'], rules: { 'import/no-unassigned-import': 'off', }, diff --git a/nx.json b/nx.json index c77d9dff8..356ac5246 100644 --- a/nx.json +++ b/nx.json @@ -75,7 +75,7 @@ "outputPath": "{projectRoot}/dist", "main": "{projectRoot}/src/index.ts", "tsConfig": "{projectRoot}/tsconfig.lib.json", - "assets": ["{projectRoot}/*.md"] + "assets": ["{projectRoot}/*.md", "{projectRoot}/bin.js"] } }, "unit-test": { diff --git a/packages/cli/bin/index.js b/packages/cli/bin.js similarity index 93% rename from packages/cli/bin/index.js rename to packages/cli/bin.js index 59c545050..70395c206 100755 --- a/packages/cli/bin/index.js +++ b/packages/cli/bin.js @@ -8,4 +8,4 @@ * By tracking this file in git with executable permissions (+x), we ensure * the CLI remains executable after npm publish without needing post-install scripts. */ -import '../src/index.js'; +import './src/index.js'; diff --git a/packages/cli/package.json b/packages/cli/package.json index ed1c91f44..58d8d2f7b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -39,7 +39,7 @@ }, "type": "module", "bin": { - "code-pushup": "./bin/index.js" + "code-pushup": "./bin.js" }, "engines": { "node": ">=20" diff --git a/packages/cli/project.json b/packages/cli/project.json index e96eb2e2e..c92b12115 100644 --- a/packages/cli/project.json +++ b/packages/cli/project.json @@ -4,11 +4,7 @@ "sourceRoot": "packages/cli/src", "projectType": "library", "targets": { - "build": { - "options": { - "assets": ["{projectRoot}/*.md", "{projectRoot}/bin/*"] - } - }, + "build": {}, "lint": {}, "unit-test": {}, "int-test": {}, diff --git a/packages/create-cli/bin.js b/packages/create-cli/bin.js new file mode 100755 index 000000000..70395c206 --- /dev/null +++ b/packages/create-cli/bin.js @@ -0,0 +1,11 @@ +#!/usr/bin/env node + +/** + * This file serves as the CLI entry point. + * + * We use a separate bin file (instead of pointing directly to src/index.js) + * because TypeScript build processes don't preserve file permissions. + * By tracking this file in git with executable permissions (+x), we ensure + * the CLI remains executable after npm publish without needing post-install scripts. + */ +import './src/index.js'; diff --git a/packages/create-cli/package.json b/packages/create-cli/package.json index 6da6774cf..615f0eacd 100644 --- a/packages/create-cli/package.json +++ b/packages/create-cli/package.json @@ -2,7 +2,7 @@ "name": "@code-pushup/create-cli", "version": "0.119.0", "license": "MIT", - "bin": "index.js", + "bin": "./bin.js", "homepage": "https://github.com/code-pushup/cli/tree/main/packages/create-cli#readme", "bugs": { "url": "https://github.com/code-pushup/cli/issues?q=is%3Aissue%20state%3Aopen%20type%3ABug%20label%3A\"🧩%20create-cli\"" diff --git a/packages/create-cli/src/lib/setup/monorepo.ts b/packages/create-cli/src/lib/setup/monorepo.ts index 54f66ebab..026a0b661 100644 --- a/packages/create-cli/src/lib/setup/monorepo.ts +++ b/packages/create-cli/src/lib/setup/monorepo.ts @@ -1,4 +1,5 @@ import { select } from '@inquirer/prompts'; +import { createRequire } from 'node:module'; import path from 'node:path'; import { MONOREPO_TOOL_DETECTORS, @@ -8,9 +9,9 @@ import { hasScript, listPackages, listWorkspaces, - loadNxProjectGraph, logger, readPnpmWorkspacePatterns, + stringifyError, toUnixPath, } from '@code-pushup/utils'; import type { @@ -83,7 +84,15 @@ export async function listProjects( } async function listNxProjects(cwd: string): Promise { - const graph = await loadNxProjectGraph(); + const require = createRequire(path.join(cwd, 'package.json')); + const { readCachedProjectGraph, createProjectGraphAsync } = + require('@nx/devkit') as typeof import('@nx/devkit'); + + const graph = await loadProjectGraph( + readCachedProjectGraph, + createProjectGraphAsync, + ); + return Object.values(graph.nodes).map(({ name, data }) => ({ name, directory: path.join(cwd, data.root), @@ -133,7 +142,7 @@ async function addNxTarget( return false; } const config = JSON.parse(raw); - if (config.targets[TASK_NAME] != null) { + if (config.targets?.[TASK_NAME] != null) { return true; } const updated = { @@ -180,3 +189,17 @@ function toProject(cwd: string, pkg: WorkspacePackage): WizardProject { relativeDir: toUnixPath(path.relative(cwd, pkg.directory)), }; } + +async function loadProjectGraph( + readCached: typeof import('@nx/devkit').readCachedProjectGraph, + createAsync: typeof import('@nx/devkit').createProjectGraphAsync, +) { + try { + return readCached(); + } catch (error) { + logger.warn( + `Could not read cached project graph, falling back to async creation.\n${stringifyError(error)}`, + ); + return createAsync({ exitOnError: false }); + } +}