-
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
Changes from all commits
cbe02f2
b556bd8
4bba9cd
47bc389
695acbc
5d4fd60
052e18c
e6ac40d
311679f
8f3b7fa
e9e4a3a
a1d0f9d
f1dd383
93d1d40
41c900b
60183db
ea73523
454960b
b368ac0
d5e05a4
2041093
bebae7c
f947823
752a97e
4e90518
a180d5c
41f9af2
9dff7c4
0f3ba3a
cefe54d
d9b453f
7b307d0
a2892ba
ff74241
fc990a6
b1875d3
371e70b
ab3f5ed
aa0950a
1d006f2
fe6d69b
57c13c6
39c112a
bdf038a
be19204
1f2712a
f7d8730
ff432bf
36faf27
1d05e8b
f3c5dd5
6bec56f
40d5fb3
0973f1c
51cd8c7
476adfa
0fea136
15da0ab
f1996ad
3880c00
04e96bc
bbc10e7
d86726d
36d8c2a
afe8f89
d3781cf
9a5eb15
4268ecc
aa6363d
37f2028
f0680e2
3b3d25d
beebe69
ff7d045
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| 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.
Show resolved
Hide resolved
|
||
| 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}"`; | ||
| } | ||
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.