Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 16
refactor(utils): command helper#1113
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
cbe02f2b556bd84bba9cd47bc389695acbc5d4fd60052e18ce6ac40d311679f8f3b7fae9e4a3aa1d0f9df1dd38393d1d4041c900b60183dbea73523454960bb368ac0d5e05a42041093bebae7cf947823752a97e4e90518a180d5c41f9af29dff7c40f3ba3acefe54dd9b453f7b307d0a2892baff74241fc990a6b1875d3371e70bab3f5edaa0950a1d006f2fe6d69b57c13c639c112abdf038abe192041f2712af7d8730ff432bf36faf271d05e8bf3c5dd56bec56f40d5fb30973f1c51cd8c7476adfa0fea13615da0abf1996ad3880c0004e96bcbbc10e7d86726d36d8c2aafe8f89d3781cf9a5eb154268eccaa6363d37f2028f0680e23b3d25dbeebe69ff7d045File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -17,7 +17,9 @@ export default tseslint.config( | ||
| rules: { | ||
| '@nx/dependency-checks': [ | ||
| 'error', | ||
| { ignoredDependencies: ['esbuild'] }, // esbuild is a peer dependency of bundle-require | ||
| { | ||
| ignoredDependencies: ['esbuild', 'ora'], // esbuild is a peer dependency of bundle-require, ora has transitive dependencies with different versions | ||
Collaborator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The | ||
| }, | ||
| ], | ||
| }, | ||
| }, | ||
Collaborator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All these functions and tests are now duplicated. And I don't understand why. The |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| import ansis from 'ansis'; | ||
BioPhoton marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| import path from 'node:path'; | ||
| type ArgumentValue = number | string | boolean | string[] | undefined; | ||
| export type CliArgsObject<T extends object = Record<string, ArgumentValue>> = | ||
| T extends never | ||
| ? Record<string, ArgumentValue | undefined> | { _: string } | ||
| : T; | ||
| /** | ||
| * Escapes command line arguments that contain spaces, quotes, or other special characters. | ||
| * | ||
| * @param {string[]} args - Array of command arguments to escape. | ||
| * @returns {string[]} - Array of escaped arguments suitable for shell execution. | ||
| */ | ||
| export function escapeCliArgs(args: string[]): string[] { | ||
| return args.map(arg => { | ||
| if (arg.includes(' ') || arg.includes('"') || arg.includes("'")) { | ||
| return `"${arg.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`; | ||
| } | ||
| return arg; | ||
| }); | ||
| } | ||
| /** | ||
| * Formats environment variable values for display by stripping quotes and then escaping. | ||
| * | ||
| * @param {string} value - Environment variable value to format. | ||
| * @returns {string} - Formatted and escaped value suitable for display. | ||
| */ | ||
| export function formatEnvValue(value: string): string { | ||
| // Strip quotes from the value for display | ||
| const cleanValue = value.replace(/"/g, ''); | ||
| return escapeCliArgs([cleanValue])[0] ?? cleanValue; | ||
| } | ||
| /** | ||
| * Builds a command string by escaping arguments that contain spaces, quotes, or other special characters. | ||
| * | ||
| * @param {string} command - The base command to execute. | ||
| * @param {string[]} args - Array of command arguments. | ||
| * @returns {string} - The complete command string with properly escaped arguments. | ||
| */ | ||
| export function buildCommandString( | ||
| command: string, | ||
| args: string[] = [], | ||
| ): string { | ||
| if (args.length === 0) { | ||
| return command; | ||
| } | ||
| return `${command} ${escapeCliArgs(args).join(' ')}`; | ||
| } | ||
| /** | ||
| * Options for formatting a command log. | ||
| */ | ||
| export interface FormatCommandLogOptions { | ||
| command: string; | ||
| args?: string[]; | ||
| cwd?: string; | ||
| env?: Record<string, string>; | ||
| } | ||
| /** | ||
| * Formats a command string with optional cwd prefix, environment variables, and ANSI colors. | ||
| * | ||
| * @param {FormatCommandLogOptions} options - Command formatting options. | ||
| * @returns {string} - ANSI-colored formatted command string. | ||
| * | ||
| * @example | ||
| * | ||
| * formatCommandLog({cwd: 'tools/api', env: {API_KEY='•••' NODE_ENV='prod'}, command: 'node', args: ['cli.js', '--do', 'thing', 'fast']}) | ||
| * ┌─────────────────────────────────────────────────────────────────────────┐ | ||
| * │ tools/api $ API_KEY="•••" NODE_ENV="prod" node cli.js --do thing fast │ | ||
| * │ │ │ │ │ │ │ | ||
| * │ └ cwd │ │ │ └ args. │ | ||
| * │ │ │ └ command │ | ||
| * │ │ └ env variables │ | ||
| * │ └ prompt symbol ($) │ | ||
| * └─────────────────────────────────────────────────────────────────────────┘ | ||
| */ | ||
| export function formatCommandLog(options: FormatCommandLogOptions): string { | ||
| const { command, args = [], cwd = process.cwd(), env } = options; | ||
| const relativeDir = path.relative(process.cwd(), cwd); | ||
| return [ | ||
| ...(relativeDir && relativeDir !== '.' | ||
| ? [ansis.italic(ansis.gray(relativeDir))] | ||
| : []), | ||
| ansis.yellow('$'), | ||
| ...(env && Object.keys(env).length > 0 | ||
| ? Object.entries(env).map(([key, value]) => { | ||
| return ansis.gray(`${key}=${formatEnvValue(value)}`); | ||
| }) | ||
| : []), | ||
| ansis.gray(command), | ||
| ansis.gray(args.join(' ')), | ||
| ].join(' '); | ||
| } | ||
Comment on lines
+83
to
+100
Collaborator There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function was integrated into the new logger, and I removed this helper in #1137. | ||
| /** | ||
| * Converts an object with different types of values into an array of command-line arguments. | ||
| * | ||
| * @example | ||
| * const args = objectToCliArgs({ | ||
| * _: ['node', 'index.js'], // node index.js | ||
| * name: 'Juanita', // --name=Juanita | ||
| * formats: ['json', 'md'] // --format=json --format=md | ||
| * }); | ||
| */ | ||
| export function objectToCliArgs< | ||
| T extends object = Record<string, ArgumentValue>, | ||
| >(params?: CliArgsObject<T>): string[] { | ||
| if (!params) { | ||
| return []; | ||
| } | ||
| return Object.entries(params).flatMap(([key, value]) => { | ||
| // process/file/script | ||
| if (key === '_') { | ||
| return Array.isArray(value) ? value : [`${value}`]; | ||
| } | ||
| const prefix = key.length === 1 ? '-' : '--'; | ||
| // "-*" arguments (shorthands) | ||
| if (Array.isArray(value)) { | ||
| return value.map(v => `${prefix}${key}="${v}"`); | ||
| } | ||
| // "--*" arguments ========== | ||
| if (typeof value === 'object') { | ||
| return Object.entries(value as Record<string, ArgumentValue>).flatMap( | ||
| // transform nested objects to the dot notation `key.subkey` | ||
| ([k, v]) => objectToCliArgs({ [`${key}.${k}`]: v }), | ||
| ); | ||
| } | ||
| if (typeof value === 'string') { | ||
| return [`${prefix}${key}="${value}"`]; | ||
| } | ||
| if (typeof value === 'number') { | ||
| return [`${prefix}${key}=${value}`]; | ||
| } | ||
| if (typeof value === 'boolean') { | ||
| return [`${prefix}${value ? '' : 'no-'}${key}`]; | ||
| } | ||
| if (typeof value === 'undefined') { | ||
| return []; | ||
| } | ||
| throw new Error(`Unsupported type ${typeof value} for key ${key}`); | ||
| }); | ||
| } | ||
| /** | ||
| * Converts a file path to a CLI argument by wrapping it in quotes to handle spaces. | ||
| * | ||
| * @param {string} filePath - The file path to convert to a CLI argument. | ||
| * @returns {string} - The quoted file path suitable for CLI usage. | ||
| */ | ||
| export function filePathToCliArg(filePath: string): string { | ||
| // needs to be escaped if spaces included | ||
| return `"${filePath}"`; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dynamic import wouldn't be needed here if you imported
executeProcessfrom thepackages/nx-plugin/src/internal/execute-process.tsyou deleted (see previous comment).It's up to you, though. I don't really mind either way as long as the implementation is being dynamically imported. The local wrapper is just for convenience.