Commit 9d5b3e4

Anton Golub <antongolub@antongolub.com>
2024-03-29 15:36:32
feat: enhance support for multiline cmd args (#753)
* feat: enhance support for multiline cmd args closes #539 * chore: fix `raw` ref in tpl pieces normalizer * chore: lint
1 parent fe24b7a
src/core.ts
@@ -34,6 +34,7 @@ import {
   formatCmd,
   getCallerLocation,
   noop,
+  normalizeMultilinePieces,
   parseDuration,
   quote,
   quotePowerShell,
@@ -137,10 +138,9 @@ export const $: Shell & Options = new Proxy<Shell & Options>(
     const promise = new ProcessPromise((...args) => ([resolve, reject] = args))
     const cmd = buildCmd(
       $.quote,
-      pieces as TemplateStringsArray,
+      normalizeMultilinePieces(pieces as TemplateStringsArray),
       args
     ) as string
-
     const snapshot = getStore()
     const sync = snapshot[syncExec]
     const callback = () => promise.isHalted || promise.run()
src/util.ts
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-import { chalk } from './vendor.js'
+import { chalk, parseLine } from './vendor.js'
 
 export function noop() {}
 
@@ -24,6 +24,24 @@ export function isString(obj: any) {
   return typeof obj === 'string'
 }
 
+export function normalizeMultilinePieces(
+  pieces: TemplateStringsArray
+): TemplateStringsArray {
+  return Object.assign(
+    pieces.map((p, i) =>
+      p.trim()
+        ? parseLine(p)
+            .words.map(({ w, e }) => {
+              if (w === '\\') return ''
+              return w.trim() + (p[e + 1] === ' ' ? ' ' : '')
+            })
+            .join(' ')
+        : pieces[i]
+    ),
+    { raw: pieces.raw }
+  )
+}
+
 export function quote(arg: string) {
   if (/^[a-z0-9/_.\-@:=]+$/i.test(arg) || arg === '') {
     return arg
src/vendor.ts
@@ -56,3 +56,4 @@ export { default as chalk, type ChalkInstance } from 'chalk'
 export { default as which } from 'which'
 export { default as minimist } from 'minimist'
 export { default as ps } from '@webpod/ps'
+export { parseLine } from '@webpod/ingrid'
test/fixtures/js-project/package-lock.json
@@ -19,6 +19,7 @@
         "@types/minimist": "^1.2.5",
         "@types/node": ">=20.11.30",
         "@types/which": "^3.0.3",
+        "@webpod/ingrid": "^0.0.0-beta.3",
         "@webpod/ps": "^0.0.0-beta.2",
         "c8": "^9.1.0",
         "chalk": "^5.3.0",
test/core.test.js
@@ -48,6 +48,28 @@ describe('core', () => {
     assert.equal((await $`echo -n ${''}`).toString(), '')
   })
 
+  test('handles multiline literals', async () => {
+    assert.equal(
+      (
+        await $`echo foo
+     bar
+     "baz
+      qux"
+`
+      ).toString(),
+      'foo bar baz\n      qux\n'
+    )
+    assert.equal(
+      (
+        await $`echo foo \
+                     bar \
+                     baz \
+`
+      ).toString(),
+      'foo bar baz\n'
+    )
+  })
+
   test('can create a dir with a space in the name', async () => {
     let name = 'foo bar'
     try {
package-lock.json
@@ -16,6 +16,7 @@
         "@types/minimist": "^1.2.5",
         "@types/node": ">=20.11.30",
         "@types/which": "^3.0.3",
+        "@webpod/ingrid": "^0.0.0-beta.3",
         "@webpod/ps": "^0.0.0-beta.2",
         "c8": "^9.1.0",
         "chalk": "^5.3.0",
@@ -502,9 +503,9 @@
       }
     },
     "node_modules/@webpod/ingrid": {
-      "version": "0.0.0-beta.2",
-      "resolved": "https://registry.npmjs.org/@webpod/ingrid/-/ingrid-0.0.0-beta.2.tgz",
-      "integrity": "sha512-TaA6xC1+lCkvPHSdD55fMF1mKe3xLy5NZpwbjoq3Zi1n0LU6XSFF2sD5SHAgnEHEzDxx8hDArNPvzZbF6uApdg==",
+      "version": "0.0.0-beta.3",
+      "resolved": "https://registry.npmjs.org/@webpod/ingrid/-/ingrid-0.0.0-beta.3.tgz",
+      "integrity": "sha512-PkorwT+q/MiIF+It47ORX0wCYHumOeMKwp5KX5WbUvbCeOtSB6b5UUC5FvzlijdwK/YPR+sOitQzyVSsRrMmJA==",
       "dev": true
     },
     "node_modules/@webpod/ps": {
package.json
@@ -59,6 +59,7 @@
     "@types/node": ">=20.11.30",
     "@types/which": "^3.0.3",
     "@webpod/ps": "^0.0.0-beta.2",
+    "@webpod/ingrid": "^0.0.0-beta.3",
     "c8": "^9.1.0",
     "chalk": "^5.3.0",
     "depseek": "^0.4.1",