-
Notifications
You must be signed in to change notification settings - Fork 661
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
feat!(cli-test): Use child_process spawn arguments properly, fixing JSON encoding on the command line on Windows #2090
Conversation
…pe JSON for datastore commands.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2090 +/- ##
==========================================
- Coverage 91.83% 91.65% -0.18%
==========================================
Files 38 38
Lines 10264 10311 +47
Branches 646 647 +1
==========================================
+ Hits 9426 9451 +25
- Misses 826 848 +22
Partials 12 12
Flags with carried forward coverage won't be shown. Click here to find out more. |
@@ -49,7 +49,7 @@ export class SlackCLIProcess { | |||
/** | |||
* @description The CLI command to invoke | |||
*/ | |||
public command: string; |
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.
One part of the breaking change: commands are now composed of an array of strings, representing the space-delimited shell invocation.
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.
I think this is a learning for all of our back-pockets. When dealing with CLI arguments in code, it's best to represent them as an array rather than a single string. Command-line frameworks like Cobra also always parse the raw command string into an array as well and it's likely because it's a more reliable way to interact with CLI arguments in code.
@@ -14,6 +14,10 @@ export interface DatastoreCommandArguments { | |||
queryExpressionValues: object; | |||
} | |||
|
|||
function escapeJSON(obj: Record<string, unknown>): string { | |||
return `"${JSON.stringify(obj).replace(/"/g, '\\"')}"`; |
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.
This in combination with using using child_process
spawn
et al's args
array of arguments helped with dealing with JSON encoding of command line arguments in a cross-platform way.
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.
It may be worthwhile leaving a code comment to explain that this is to help support cross-platform JSON encoding
// In windows, we actually spawn a command prompt and tell it to invoke the CLI command. | ||
// The combination of windows and node's child_process spawning is complicated: on windows, child_process strips quotes from arguments. This makes passing JSON difficult. | ||
// As a workaround, by telling Windows Command Prompt (cmd.exe) to execute a command to completion (/c) and leave spaces intact (/c), combined with feeding arguments as an argument array into child_process, we can get around this mess. | ||
const windowsArgs = ['/s', '/c'].concat([command]).concat(args); |
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.
This is the root of the Windows compatibility hack
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.
What are /s
and /c
? (What do they mean / do?)
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.
It's described in the comment - let me know if you have a suggestion on wording or formatting this differently to make it possibly clearer
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.
Oh I see 🤡 execute a command to completion (/c) and leave spaces intact (/s)
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.
I found it really helpful to see it spelled out that this means Windows will execute cmd.exe /c /s "slack cli command here"
and would suggest that get added to the comment as well; for a while I thought it was slack \c \s datastore etc.
, which didn't work when I tried it on my Windows laptop 😅
}, /this is bat country/); | ||
}); | ||
if (process.platform === 'win32') { |
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.
This fork in the test code is to test stuff on Windows vs. non-Windows properly, since we now include Windows-specific code.
This also explains why the code coverage dropped: since the code coverage is generated on a GitHub ubuntu runner, it won't exercise the Windows-specific code path, so the coverage reporter will report a lower coverage score. But, since our CI now runs tests on Windows, this code path is exercised (you can see that in the GitHub Action output for the windows check here)
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.
Pfff... amazing work @filmaj! That's some incredible detective work 🕵🏻 to uncover the right combination of flags to keep JSON intact on Windows Command Prompt.
I've left a few minor comments and suggestions from the code review. I'll let the others throw a proper ✅ on it but it looks good on my side.
await datastore.datastoreQuery({ | ||
appPath: '/some/path', | ||
datastoreName: 'datastore', | ||
queryExpression: 'id = :id', | ||
queryExpressionValues: expressObj, | ||
}); |
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.
:bliss: Much more readable
@@ -14,6 +14,10 @@ export interface DatastoreCommandArguments { | |||
queryExpressionValues: object; | |||
} | |||
|
|||
function escapeJSON(obj: Record<string, unknown>): string { | |||
return `"${JSON.stringify(obj).replace(/"/g, '\\"')}"`; |
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.
It may be worthwhile leaving a code comment to explain that this is to help support cross-platform JSON encoding
@@ -49,7 +49,7 @@ export class SlackCLIProcess { | |||
/** | |||
* @description The CLI command to invoke | |||
*/ | |||
public command: string; |
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.
I think this is a learning for all of our back-pockets. When dealing with CLI arguments in code, it's best to represent them as an array rather than a single string. Command-line frameworks like Cobra also always parse the raw command string into an array as well and it's likely because it's a more reliable way to interact with CLI arguments in code.
Co-authored-by: Michael Brooks <[email protected]>
Just wondering, how were the Windows e2e tests verified? (Are we able to run them in CircleCI with a given version of the cli-test package?) Edit: Ah, I see: https://github.com/slackapi/platform-devxp-test/pull/151 |
@vegeris I have a PR using this branch of The two most recent CircleCI runs for this branch show both the non-windows ( The |
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.
This is great!
I take it we're fine to merge this despite the general restrictions this week as an update to the testing package that won't impact customer-facing behaviour?
Summary
(FYI a majority of changes in this PR are relatively cosmetic - will post comments for areas of particular interest for the kind reviewer)
There is a (technically) breaking change in this PR: the string arguments passed to
SlackCLIProcess
should be arranged in an array now, and these are passed as the second argument intochild_process.spawn
. This, in combination with the following changes, also fixes thedatastore
commands on Windows:cmd.exe /c /s
invocation, which helps to manage how node will strip quotes out of arguments on Windows when passed tochild_process
APIs.TODO: