From 8a3b03c568b69346da6ec4cbe3a2a1c12b3bc1cb Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Thu, 2 Nov 2023 10:40:26 +0900 Subject: [PATCH 01/14] Fix modules root in workerd --- packages/cli/src/lib/mini-oxygen/workerd.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/lib/mini-oxygen/workerd.ts b/packages/cli/src/lib/mini-oxygen/workerd.ts index 75e408668e..80a938f576 100644 --- a/packages/cli/src/lib/mini-oxygen/workerd.ts +++ b/packages/cli/src/lib/mini-oxygen/workerd.ts @@ -6,7 +6,7 @@ import { NoOpLog, type MiniflareOptions, } from 'miniflare'; -import {resolvePath} from '@shopify/cli-kit/node/path'; +import {dirname, resolvePath} from '@shopify/cli-kit/node/path'; import { glob, readFile, @@ -27,7 +27,7 @@ import { setConstructors, } from '../request-events.js'; -const DEFAULT_INSPECTOR_PORT = 8787; +const DEFAULT_INSPECTOR_PORT = 9229; export async function startWorkerdServer({ root, @@ -50,6 +50,8 @@ export async function startWorkerdServer({ setConstructors({Response}); + const absoluteBundlePath = resolvePath(root, buildPathWorkerFile); + const buildMiniOxygenOptions = async () => ({ cf: false, @@ -77,11 +79,12 @@ export async function startWorkerdServer({ }, { name: 'hydrogen', + modulesRoot: dirname(absoluteBundlePath), modules: [ { type: 'ESModule', - path: resolvePath(root, buildPathWorkerFile), - contents: await readFile(resolvePath(root, buildPathWorkerFile)), + path: absoluteBundlePath, + contents: await readFile(absoluteBundlePath), }, ], compatibilityFlags: ['streams_enable_constructors'], From a4533f80509f4780a363ba5b7519aabf1b6e0a6e Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Thu, 2 Nov 2023 10:49:22 +0900 Subject: [PATCH 02/14] Update workerd --- package-lock.json | 114 +++++++++++++++++++------------------- packages/cli/package.json | 2 +- 2 files changed, 58 insertions(+), 58 deletions(-) diff --git a/package-lock.json b/package-lock.json index d872272d0b..465f55d798 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2546,9 +2546,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20230904.0.tgz", - "integrity": "sha512-/GDlmxAFbDtrQwP4zOXFbqOfaPvkDxdsCoEa+KEBcAl5uR98+7WW5/b8naBHX+t26uS7p4bLlImM8J5F1ienRQ==", + "version": "1.20231016.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20231016.0.tgz", + "integrity": "sha512-rPAnF8Q25+eHEsAopihWeftPW/P0QapY9d7qaUmtOXztWdd6YPQ7JuiWVj4Nvjphge1BleehxAbo4I3Z4L2H1g==", "cpu": [ "x64" ], @@ -2561,9 +2561,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20230904.0.tgz", - "integrity": "sha512-x8WXNc2xnDqr5y1iirnNdyx8GZY3rL5xiF7ebK3mKQeB+jFjkhO71yuPTkDCzUWtOvw1Wfd4jbwy4wxacMX4mQ==", + "version": "1.20231016.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20231016.0.tgz", + "integrity": "sha512-MvydDdiLXt+jy57vrVZ2lU6EQwCdpieyZoN8uBXSWzfG3zR/6dxU1+okvPQPlHN0jtlufqPeHrpJyAqqgLHUKA==", "cpu": [ "arm64" ], @@ -2576,9 +2576,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20230904.0.tgz", - "integrity": "sha512-V58xyMS3oDpKO8Dpdh0r0BXm99OzoGgvWe9ufttVraj/1NTMGELwb6i9ySb8k3F1J9m/sO26+TV7pQc/bGC1VQ==", + "version": "1.20231016.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20231016.0.tgz", + "integrity": "sha512-y6Sj37yTzM8QbAghG9LRqoSBrsREnQz8NkcmpjSxeK6KMc2g0L5A/OemCdugNlIiv+zRv9BYX1aosaoxY5JbeQ==", "cpu": [ "x64" ], @@ -2591,9 +2591,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20230904.0.tgz", - "integrity": "sha512-VrDaW+pjb5IAKEnNWtEaFiG377kXKmk5Fu0Era4W+jKzPON2BW/qRb/4LNHXQ4yxg/2HLm7RiUTn7JZtt1qO6A==", + "version": "1.20231016.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20231016.0.tgz", + "integrity": "sha512-LqMIRUHD1YeRg2TPIfIQEhapSKMFSq561RypvJoXZvTwSbaROxGdW6Ku+PvButqTkEvuAtfzN/kGje7fvfQMHg==", "cpu": [ "arm64" ], @@ -2606,9 +2606,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20230904.0.tgz", - "integrity": "sha512-/R/dE8uy+8J2YeXfDhI8/Bg7YUirdbbjH5/l/Vv00ZRE0lC3nPLcYeyBXSwXIQ6/Xht3gN+lksLQgKd0ZWRd+Q==", + "version": "1.20231016.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20231016.0.tgz", + "integrity": "sha512-96ojBwIHyiUAbsWlzBqo9P/cvH8xUh8SuBboFXtwAeXcJ6/urwKN2AqPa/QzOGUTCdsurWYiieARHT5WWWPhKw==", "cpu": [ "x64" ], @@ -19489,9 +19489,9 @@ } }, "node_modules/miniflare": { - "version": "3.20230918.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20230918.0.tgz", - "integrity": "sha512-Dd29HB7ZlT1CXB2tPH8nW6fBOOXi/m7qFZHjKm2jGS+1OaGfrv0PkT5UspWW5jQi8rWI87xtordAUiIJkwWqRw==", + "version": "3.20231016.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20231016.0.tgz", + "integrity": "sha512-AmlqI89zsnBJfC+nKKZdCB/fuu0q/br24Kqt9NZwcT6yJEpO5NytNKfjl6nJROHROwuJSRQR1T3yopCtG1/0DA==", "dependencies": { "acorn": "^8.8.0", "acorn-walk": "^8.2.0", @@ -19501,7 +19501,7 @@ "source-map-support": "0.5.21", "stoppable": "^1.1.0", "undici": "^5.22.1", - "workerd": "1.20230904.0", + "workerd": "1.20231016.0", "ws": "^8.11.0", "youch": "^3.2.2", "zod": "^3.20.6" @@ -29587,9 +29587,9 @@ "license": "MIT" }, "node_modules/workerd": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20230904.0.tgz", - "integrity": "sha512-t9znszH0rQGK4mJGvF9L3nN0qKEaObAGx0JkywFtAwH8OkSn+YfQbHNZE+YsJ4qa1hOz1DCNEk08UDFRBaYq4g==", + "version": "1.20231016.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20231016.0.tgz", + "integrity": "sha512-v2GDb5XitSqgub/xm7EWHVAlAK4snxQu3itdMQxXstGtUG9hl79fQbXS/8fNFbmms2R2bAxUwSv47q8k5T5Erw==", "hasInstallScript": true, "bin": { "workerd": "bin/workerd" @@ -29598,11 +29598,11 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20230904.0", - "@cloudflare/workerd-darwin-arm64": "1.20230904.0", - "@cloudflare/workerd-linux-64": "1.20230904.0", - "@cloudflare/workerd-linux-arm64": "1.20230904.0", - "@cloudflare/workerd-windows-64": "1.20230904.0" + "@cloudflare/workerd-darwin-64": "1.20231016.0", + "@cloudflare/workerd-darwin-arm64": "1.20231016.0", + "@cloudflare/workerd-linux-64": "1.20231016.0", + "@cloudflare/workerd-linux-arm64": "1.20231016.0", + "@cloudflare/workerd-windows-64": "1.20231016.0" } }, "node_modules/worktop": { @@ -29991,7 +29991,7 @@ "fs-extra": "^11.1.0", "get-port": "^7.0.0", "gunzip-maybe": "^1.4.2", - "miniflare": "3.20230918.0", + "miniflare": "3.20231016.0", "prettier": "^2.8.4", "semver": "^7.5.3", "source-map": "^0.7.4", @@ -32569,33 +32569,33 @@ } }, "@cloudflare/workerd-darwin-64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20230904.0.tgz", - "integrity": "sha512-/GDlmxAFbDtrQwP4zOXFbqOfaPvkDxdsCoEa+KEBcAl5uR98+7WW5/b8naBHX+t26uS7p4bLlImM8J5F1ienRQ==", + "version": "1.20231016.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20231016.0.tgz", + "integrity": "sha512-rPAnF8Q25+eHEsAopihWeftPW/P0QapY9d7qaUmtOXztWdd6YPQ7JuiWVj4Nvjphge1BleehxAbo4I3Z4L2H1g==", "optional": true }, "@cloudflare/workerd-darwin-arm64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20230904.0.tgz", - "integrity": "sha512-x8WXNc2xnDqr5y1iirnNdyx8GZY3rL5xiF7ebK3mKQeB+jFjkhO71yuPTkDCzUWtOvw1Wfd4jbwy4wxacMX4mQ==", + "version": "1.20231016.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20231016.0.tgz", + "integrity": "sha512-MvydDdiLXt+jy57vrVZ2lU6EQwCdpieyZoN8uBXSWzfG3zR/6dxU1+okvPQPlHN0jtlufqPeHrpJyAqqgLHUKA==", "optional": true }, "@cloudflare/workerd-linux-64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20230904.0.tgz", - "integrity": "sha512-V58xyMS3oDpKO8Dpdh0r0BXm99OzoGgvWe9ufttVraj/1NTMGELwb6i9ySb8k3F1J9m/sO26+TV7pQc/bGC1VQ==", + "version": "1.20231016.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20231016.0.tgz", + "integrity": "sha512-y6Sj37yTzM8QbAghG9LRqoSBrsREnQz8NkcmpjSxeK6KMc2g0L5A/OemCdugNlIiv+zRv9BYX1aosaoxY5JbeQ==", "optional": true }, "@cloudflare/workerd-linux-arm64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20230904.0.tgz", - "integrity": "sha512-VrDaW+pjb5IAKEnNWtEaFiG377kXKmk5Fu0Era4W+jKzPON2BW/qRb/4LNHXQ4yxg/2HLm7RiUTn7JZtt1qO6A==", + "version": "1.20231016.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20231016.0.tgz", + "integrity": "sha512-LqMIRUHD1YeRg2TPIfIQEhapSKMFSq561RypvJoXZvTwSbaROxGdW6Ku+PvButqTkEvuAtfzN/kGje7fvfQMHg==", "optional": true }, "@cloudflare/workerd-windows-64": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20230904.0.tgz", - "integrity": "sha512-/R/dE8uy+8J2YeXfDhI8/Bg7YUirdbbjH5/l/Vv00ZRE0lC3nPLcYeyBXSwXIQ6/Xht3gN+lksLQgKd0ZWRd+Q==", + "version": "1.20231016.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20231016.0.tgz", + "integrity": "sha512-96ojBwIHyiUAbsWlzBqo9P/cvH8xUh8SuBboFXtwAeXcJ6/urwKN2AqPa/QzOGUTCdsurWYiieARHT5WWWPhKw==", "optional": true }, "@cspotcode/source-map-support": { @@ -35835,7 +35835,7 @@ "fs-extra": "^11.1.0", "get-port": "^7.0.0", "gunzip-maybe": "^1.4.2", - "miniflare": "3.20230918.0", + "miniflare": "3.20231016.0", "prettier": "^2.8.4", "semver": "^7.5.3", "source-map": "^0.7.4", @@ -44142,9 +44142,9 @@ "dev": true }, "miniflare": { - "version": "3.20230918.0", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20230918.0.tgz", - "integrity": "sha512-Dd29HB7ZlT1CXB2tPH8nW6fBOOXi/m7qFZHjKm2jGS+1OaGfrv0PkT5UspWW5jQi8rWI87xtordAUiIJkwWqRw==", + "version": "3.20231016.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20231016.0.tgz", + "integrity": "sha512-AmlqI89zsnBJfC+nKKZdCB/fuu0q/br24Kqt9NZwcT6yJEpO5NytNKfjl6nJROHROwuJSRQR1T3yopCtG1/0DA==", "requires": { "acorn": "^8.8.0", "acorn-walk": "^8.2.0", @@ -44154,7 +44154,7 @@ "source-map-support": "0.5.21", "stoppable": "^1.1.0", "undici": "^5.22.1", - "workerd": "1.20230904.0", + "workerd": "1.20231016.0", "ws": "^8.11.0", "youch": "^3.2.2", "zod": "^3.20.6" @@ -50642,15 +50642,15 @@ "version": "1.0.0" }, "workerd": { - "version": "1.20230904.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20230904.0.tgz", - "integrity": "sha512-t9znszH0rQGK4mJGvF9L3nN0qKEaObAGx0JkywFtAwH8OkSn+YfQbHNZE+YsJ4qa1hOz1DCNEk08UDFRBaYq4g==", - "requires": { - "@cloudflare/workerd-darwin-64": "1.20230904.0", - "@cloudflare/workerd-darwin-arm64": "1.20230904.0", - "@cloudflare/workerd-linux-64": "1.20230904.0", - "@cloudflare/workerd-linux-arm64": "1.20230904.0", - "@cloudflare/workerd-windows-64": "1.20230904.0" + "version": "1.20231016.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20231016.0.tgz", + "integrity": "sha512-v2GDb5XitSqgub/xm7EWHVAlAK4snxQu3itdMQxXstGtUG9hl79fQbXS/8fNFbmms2R2bAxUwSv47q8k5T5Erw==", + "requires": { + "@cloudflare/workerd-darwin-64": "1.20231016.0", + "@cloudflare/workerd-darwin-arm64": "1.20231016.0", + "@cloudflare/workerd-linux-64": "1.20231016.0", + "@cloudflare/workerd-linux-arm64": "1.20231016.0", + "@cloudflare/workerd-windows-64": "1.20231016.0" } }, "worktop": { diff --git a/packages/cli/package.json b/packages/cli/package.json index eca83e84a3..bbd4a7ab61 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -44,7 +44,7 @@ "fs-extra": "^11.1.0", "get-port": "^7.0.0", "gunzip-maybe": "^1.4.2", - "miniflare": "3.20230918.0", + "miniflare": "3.20231016.0", "prettier": "^2.8.4", "semver": "^7.5.3", "source-map": "^0.7.4", From 44257646f8a3e5ed352f42fe1852d9727a11a123 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Thu, 2 Nov 2023 11:04:52 +0900 Subject: [PATCH 03/14] Add script to run app from root --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 5a3abb69fa..4f780428c7 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "ci:checks": "turbo run lint test format:check typecheck", "dev": "npm run dev:pkg", "dev:pkg": "cross-env LOCAL_DEV=true turbo dev --parallel --filter=./packages/*", + "dev:app": "cd templates/skeleton && cross-env LOCAL_DEV=true npm run dev", "preview": "turbo build --filter=./packages/* && npm run preview -w demo-store", "lint": "eslint --no-error-on-unmatched-pattern --ext .js,.ts,.jsx,.tsx ./packages & npm run lint -w demo-store", "format": "prettier --write --ignore-unknown ./packages && npm run format -w demo-store", From b69cf4a7ea4ccae5d8725122f8b3a04f5d55e765 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Wed, 8 Nov 2023 13:11:40 +0900 Subject: [PATCH 04/14] Extract workerd inspector logs logic --- .../lib/mini-oxygen/workerd-inspector-logs.ts | 297 +++++++++++++++++ .../src/lib/mini-oxygen/workerd-inspector.ts | 306 +----------------- 2 files changed, 307 insertions(+), 296 deletions(-) create mode 100644 packages/cli/src/lib/mini-oxygen/workerd-inspector-logs.ts diff --git a/packages/cli/src/lib/mini-oxygen/workerd-inspector-logs.ts b/packages/cli/src/lib/mini-oxygen/workerd-inspector-logs.ts new file mode 100644 index 0000000000..0f397efa1b --- /dev/null +++ b/packages/cli/src/lib/mini-oxygen/workerd-inspector-logs.ts @@ -0,0 +1,297 @@ +import {type Protocol} from 'devtools-protocol'; +import {type ErrorProperties} from './workerd-inspector.js'; +import {SourceMapConsumer} from 'source-map'; +import {parse as parseStackTrace} from 'stack-trace'; + +/** + * This function converts a message serialised as a devtools event + * into arguments suitable to be called by a console method, and + * then actually calls the method with those arguments. Effectively, + * we're just doing a little bit of the work of the devtools console, + * directly in the terminal. + */ + +export const mapConsoleAPIMessageTypeToConsoleMethod: { + [key in Protocol.Runtime.ConsoleAPICalledEvent['type']]: Exclude< + keyof Console, + 'Console' + >; +} = { + log: 'log', + debug: 'debug', + info: 'info', + warning: 'warn', + error: 'error', + dir: 'dir', + dirxml: 'dirxml', + table: 'table', + trace: 'trace', + clear: 'clear', + count: 'count', + assert: 'assert', + profile: 'profile', + profileEnd: 'profileEnd', + timeEnd: 'timeEnd', + startGroup: 'group', + startGroupCollapsed: 'groupCollapsed', + endGroup: 'groupEnd', +}; + +export async function logConsoleMessage( + evt: Protocol.Runtime.ConsoleAPICalledEvent, + reconstructError: ( + initialProperties: ErrorProperties, + ro: Protocol.Runtime.RemoteObject, + ) => Promise, +) { + const args: Array = []; + for (const ro of evt.args) { + switch (ro.type) { + case 'string': + case 'number': + case 'boolean': + case 'undefined': + case 'symbol': + case 'bigint': + args.push(ro.value); + break; + case 'function': + args.push(`[Function: ${ro.description ?? ''}]`); + break; + case 'object': + if (!ro.preview) { + args.push( + ro.subtype === 'null' + ? 'null' + : ro.description ?? '', + ); + } else { + if (ro.preview.description) args.push(ro.preview.description); + + switch (ro.preview.subtype) { + case 'array': + args.push( + '[ ' + + ro.preview.properties + .map(({value}) => { + return value; + }) + .join(', ') + + (ro.preview.overflow ? '...' : '') + + ' ]', + ); + + break; + case 'weakmap': + case 'map': + ro.preview.entries === undefined + ? args.push('{}') + : args.push( + '{\n' + + ro.preview.entries + .map(({key, value}) => { + return ` ${key?.description ?? ''} => ${ + value.description + }`; + }) + .join(',\n') + + (ro.preview.overflow ? '\n ...' : '') + + '\n}', + ); + + break; + case 'weakset': + case 'set': + ro.preview.entries === undefined + ? args.push('{}') + : args.push( + '{ ' + + ro.preview.entries + .map(({value}) => { + return `${value.description}`; + }) + .join(', ') + + (ro.preview.overflow ? ', ...' : '') + + ' }', + ); + break; + case 'regexp': + break; + case 'date': + break; + case 'generator': + args.push(ro.preview?.properties[0]?.value || ''); + break; + case 'promise': + if (ro.preview?.properties[0]?.value === 'pending') { + args.push(`{<${ro.preview.properties[0].value}>}`); + } else { + args.push( + `{<${ro.preview?.properties[0]?.value}>: ${ro.preview?.properties[1]?.value}}`, + ); + } + break; + case 'node': + case 'iterator': + case 'proxy': + case 'typedarray': + case 'arraybuffer': + case 'dataview': + case 'webassemblymemory': + case 'wasmvalue': + break; + case 'error': + const errorProperties = { + message: + ro.preview.description + ?.split('\n') + .filter((line) => !/^\s+at\s/.test(line)) + .join('\n') ?? + ro.preview.properties.find(({name}) => name === 'message') + ?.value ?? + '', + stack: + ro.preview.description ?? + ro.description ?? + ro.preview.properties.find(({name}) => name === 'stack') + ?.value, + cause: ro.preview.properties.find(({name}) => name === 'cause') + ?.value as unknown, + }; + + // Even though we have gathered all the properties, they are likely + // truncated so we need to fetch their full version. + const error = await reconstructError(errorProperties, ro); + + // Replace its description in args + args.splice(-1, 1, error); + + break; + default: + args.push( + '{\n' + + ro.preview.properties + .map(({name, value}) => { + return ` ${name}: ${value}`; + }) + .join(',\n') + + (ro.preview.overflow ? '\n ...' : '') + + '\n}', + ); + } + } + break; + default: + args.push(ro.description || ro.unserializableValue || '🦋'); + break; + } + } + + const method = mapConsoleAPIMessageTypeToConsoleMethod[evt.type]; + + if (method in console) { + switch (method) { + case 'dir': + console.dir(args); + break; + case 'table': + console.table(args); + break; + default: + // @ts-expect-error + console[method].apply(console, args); + break; + } + } else { + console.warn(`Unsupported console method: ${method}`); + console.warn('console event:', evt); + } +} + +/** + * Converts a structured-error to a friendly, source-mapped error string. + * @param sourceMapConsumer source-map to use for mapping locations + * @param message first line of stack trace (e.g. `Error: message`) + * @param frames structured stack entries for error location + */ +export function formatStructuredError( + sourceMapConsumer: SourceMapConsumer, + message?: string, + frames?: Protocol.Runtime.CallFrame[], +): string { + const lines: string[] = []; + if (message !== undefined) lines.push(message); + // Pass each of the callframes into the consumer, and format the error + frames?.forEach(({functionName, lineNumber, columnNumber}, i) => { + try { + if (lineNumber) { + // `Protocol.Runtime.CallFrame` uses 0-indexed line and column + // numbers, whereas `source-map` expects 1-indexing for lines and + // 0-indexing for columns; + const pos = sourceMapConsumer.originalPositionFor({ + line: lineNumber + 1, + column: columnNumber, + }); + + // Print out line which caused error: + if (i === 0 && pos.source && pos.line) { + const fileSource = sourceMapConsumer.sourceContentFor(pos.source); + const fileSourceLine = fileSource?.split('\n')[pos.line - 1] || ''; + lines.push(fileSourceLine.trim()); + + // If we have a column, we can mark the position underneath + if (pos.column) { + lines.push( + `${' '.repeat(pos.column - fileSourceLine.search(/\S/))}^`, + ); + } + } + + // From the way esbuild implements the "names" field: + // > To save space, the original name is only recorded when it's different from the final name. + // however, source-map consumer does not handle this + if (pos && pos.line !== null && pos.column !== null) { + const convertedFnName = pos.name || functionName || ''; + let convertedLocation = `${pos.source}:${pos.line}:${pos.column + 1}`; + + if (convertedFnName === '') { + lines.push(` at ${convertedLocation}`); + } else { + lines.push(` at ${convertedFnName} (${convertedLocation})`); + } + } + } + } catch { + // Line failed to parse through the sourcemap consumer + // We should handle this better + } + }); + + return lines.join('\n'); +} + +/** + * Converts an unstructured-stack to a friendly, source-mapped error string. + * @param sourceMapConsumer source-map to use for mapping locations + * @param stack string stack trace from `Error#stack` + */ +export function formatStack( + sourceMapConsumer: SourceMapConsumer, + stack: string, +) { + const message = stack.split('\n')[0]; + // `stack-trace` requires an object with a `stack` property: + // https://github.com/felixge/node-stack-trace/blob/ba06dcdb50d465cd440d84a563836e293b360427/index.js#L21-L23 + const callSites = parseStackTrace({stack} as Error); + const frames = callSites.map((site) => ({ + functionName: site.getFunctionName() ?? '', + // `Protocol.Runtime.CallFrame`s line numbers are 0-indexed, hence `- 1` + lineNumber: (site.getLineNumber() ?? 1) - 1, + columnNumber: site.getColumnNumber() ?? 1, + // Unused by `formattedError` + scriptId: '', + url: '', + })); + + return formatStructuredError(sourceMapConsumer, message, frames); +} diff --git a/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts b/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts index 546e8beec2..547c707e91 100644 --- a/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts +++ b/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts @@ -6,9 +6,13 @@ import {dirname} from 'node:path'; import {readFile} from 'node:fs/promises'; import {SourceMapConsumer} from 'source-map'; -import {parse as parseStackTrace} from 'stack-trace'; -import WebSocket, {type MessageEvent} from 'ws'; -import {Protocol} from 'devtools-protocol'; +import {WebSocket, type MessageEvent} from 'ws'; +import {type Protocol} from 'devtools-protocol'; +import { + formatStack, + formatStructuredError, + logConsoleMessage, +} from './workerd-inspector-logs.js'; // https://chromedevtools.github.io/devtools-protocol/#endpoints interface InspectorWebSocketTarget { @@ -38,7 +42,7 @@ export async function findInspectorUrl(inspectorPort: number) { } } -interface InspectorProps { +interface InspectorOptions { /** * The websocket URL exposed by Workers that the inspector should connect to. */ @@ -49,7 +53,7 @@ interface InspectorProps { sourceMapPath?: string | undefined; } -interface ErrorProperties { +export interface ErrorProperties { message?: string; cause?: unknown; stack?: string; @@ -58,7 +62,7 @@ interface ErrorProperties { export function connectToInspector({ inspectorUrl, sourceMapPath, -}: InspectorProps) { +}: InspectorOptions) { /** * A simple decrementing id to attach to messages sent to DevTools. * Use negative ids to void collisions with DevTools messages. @@ -352,293 +356,3 @@ export function connectToInspector({ sourceMapAbortController.abort(); }; } - -/** - * This function converts a message serialised as a devtools event - * into arguments suitable to be called by a console method, and - * then actually calls the method with those arguments. Effectively, - * we're just doing a little bit of the work of the devtools console, - * directly in the terminal. - */ - -export const mapConsoleAPIMessageTypeToConsoleMethod: { - [key in Protocol.Runtime.ConsoleAPICalledEvent['type']]: Exclude< - keyof Console, - 'Console' - >; -} = { - log: 'log', - debug: 'debug', - info: 'info', - warning: 'warn', - error: 'error', - dir: 'dir', - dirxml: 'dirxml', - table: 'table', - trace: 'trace', - clear: 'clear', - count: 'count', - assert: 'assert', - profile: 'profile', - profileEnd: 'profileEnd', - timeEnd: 'timeEnd', - startGroup: 'group', - startGroupCollapsed: 'groupCollapsed', - endGroup: 'groupEnd', -}; - -async function logConsoleMessage( - evt: Protocol.Runtime.ConsoleAPICalledEvent, - reconstructError: ( - initialProperties: ErrorProperties, - ro: Protocol.Runtime.RemoteObject, - ) => Promise, -) { - const args: Array = []; - for (const ro of evt.args) { - switch (ro.type) { - case 'string': - case 'number': - case 'boolean': - case 'undefined': - case 'symbol': - case 'bigint': - args.push(ro.value); - break; - case 'function': - args.push(`[Function: ${ro.description ?? ''}]`); - break; - case 'object': - if (!ro.preview) { - args.push( - ro.subtype === 'null' - ? 'null' - : ro.description ?? '', - ); - } else { - if (ro.preview.description) args.push(ro.preview.description); - - switch (ro.preview.subtype) { - case 'array': - args.push( - '[ ' + - ro.preview.properties - .map(({value}) => { - return value; - }) - .join(', ') + - (ro.preview.overflow ? '...' : '') + - ' ]', - ); - - break; - case 'weakmap': - case 'map': - ro.preview.entries === undefined - ? args.push('{}') - : args.push( - '{\n' + - ro.preview.entries - .map(({key, value}) => { - return ` ${key?.description ?? ''} => ${ - value.description - }`; - }) - .join(',\n') + - (ro.preview.overflow ? '\n ...' : '') + - '\n}', - ); - - break; - case 'weakset': - case 'set': - ro.preview.entries === undefined - ? args.push('{}') - : args.push( - '{ ' + - ro.preview.entries - .map(({value}) => { - return `${value.description}`; - }) - .join(', ') + - (ro.preview.overflow ? ', ...' : '') + - ' }', - ); - break; - case 'regexp': - break; - case 'date': - break; - case 'generator': - args.push(ro.preview?.properties[0]?.value || ''); - break; - case 'promise': - if (ro.preview?.properties[0]?.value === 'pending') { - args.push(`{<${ro.preview.properties[0].value}>}`); - } else { - args.push( - `{<${ro.preview?.properties[0]?.value}>: ${ro.preview?.properties[1]?.value}}`, - ); - } - break; - case 'node': - case 'iterator': - case 'proxy': - case 'typedarray': - case 'arraybuffer': - case 'dataview': - case 'webassemblymemory': - case 'wasmvalue': - break; - case 'error': - const errorProperties = { - message: - ro.preview.description - ?.split('\n') - .filter((line) => !/^\s+at\s/.test(line)) - .join('\n') ?? - ro.preview.properties.find(({name}) => name === 'message') - ?.value ?? - '', - stack: - ro.preview.description ?? - ro.description ?? - ro.preview.properties.find(({name}) => name === 'stack') - ?.value, - cause: ro.preview.properties.find(({name}) => name === 'cause') - ?.value as unknown, - }; - - // Even though we have gathered all the properties, they are likely - // truncated so we need to fetch their full version. - const error = await reconstructError(errorProperties, ro); - - // Replace its description in args - args.splice(-1, 1, error); - - break; - default: - args.push( - '{\n' + - ro.preview.properties - .map(({name, value}) => { - return ` ${name}: ${value}`; - }) - .join(',\n') + - (ro.preview.overflow ? '\n ...' : '') + - '\n}', - ); - } - } - break; - default: - args.push(ro.description || ro.unserializableValue || '🦋'); - break; - } - } - - const method = mapConsoleAPIMessageTypeToConsoleMethod[evt.type]; - - if (method in console) { - switch (method) { - case 'dir': - console.dir(args); - break; - case 'table': - console.table(args); - break; - default: - // @ts-expect-error - console[method].apply(console, args); - break; - } - } else { - console.warn(`Unsupported console method: ${method}`); - console.warn('console event:', evt); - } -} - -/** - * Converts a structured-error to a friendly, source-mapped error string. - * @param sourceMapConsumer source-map to use for mapping locations - * @param message first line of stack trace (e.g. `Error: message`) - * @param frames structured stack entries for error location - */ -function formatStructuredError( - sourceMapConsumer: SourceMapConsumer, - message?: string, - frames?: Protocol.Runtime.CallFrame[], -): string { - const lines: string[] = []; - if (message !== undefined) lines.push(message); - // Pass each of the callframes into the consumer, and format the error - frames?.forEach(({functionName, lineNumber, columnNumber}, i) => { - try { - if (lineNumber) { - // `Protocol.Runtime.CallFrame` uses 0-indexed line and column - // numbers, whereas `source-map` expects 1-indexing for lines and - // 0-indexing for columns; - const pos = sourceMapConsumer.originalPositionFor({ - line: lineNumber + 1, - column: columnNumber, - }); - - // Print out line which caused error: - if (i === 0 && pos.source && pos.line) { - const fileSource = sourceMapConsumer.sourceContentFor(pos.source); - const fileSourceLine = fileSource?.split('\n')[pos.line - 1] || ''; - lines.push(fileSourceLine.trim()); - - // If we have a column, we can mark the position underneath - if (pos.column) { - lines.push( - `${' '.repeat(pos.column - fileSourceLine.search(/\S/))}^`, - ); - } - } - - // From the way esbuild implements the "names" field: - // > To save space, the original name is only recorded when it's different from the final name. - // however, source-map consumer does not handle this - if (pos && pos.line !== null && pos.column !== null) { - const convertedFnName = pos.name || functionName || ''; - let convertedLocation = `${pos.source}:${pos.line}:${pos.column + 1}`; - - if (convertedFnName === '') { - lines.push(` at ${convertedLocation}`); - } else { - lines.push(` at ${convertedFnName} (${convertedLocation})`); - } - } - } - } catch { - // Line failed to parse through the sourcemap consumer - // We should handle this better - } - }); - - return lines.join('\n'); -} - -/** - * Converts an unstructured-stack to a friendly, source-mapped error string. - * @param sourceMapConsumer source-map to use for mapping locations - * @param stack string stack trace from `Error#stack` - */ -function formatStack(sourceMapConsumer: SourceMapConsumer, stack: string) { - const message = stack.split('\n')[0]; - // `stack-trace` requires an object with a `stack` property: - // https://github.com/felixge/node-stack-trace/blob/ba06dcdb50d465cd440d84a563836e293b360427/index.js#L21-L23 - const callSites = parseStackTrace({stack} as Error); - const frames = callSites.map((site) => ({ - functionName: site.getFunctionName() ?? '', - // `Protocol.Runtime.CallFrame`s line numbers are 0-indexed, hence `- 1` - lineNumber: (site.getLineNumber() ?? 1) - 1, - columnNumber: site.getColumnNumber() ?? 1, - // Unused by `formattedError` - scriptId: '', - url: '', - })); - - return formatStructuredError(sourceMapConsumer, message, frames); -} From 0ab3fb6996a4576f98b40f2adf6ba749fca12e9a Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Wed, 8 Nov 2023 17:13:18 +0900 Subject: [PATCH 05/14] Support step debugging in workerd with a proxy server --- packages/cli/src/lib/mini-oxygen/common.ts | 2 + .../mini-oxygen/workerd-inspector-proxy.ts | 159 ++++++++++++++++++ .../src/lib/mini-oxygen/workerd-inspector.ts | 24 +-- packages/cli/src/lib/mini-oxygen/workerd.ts | 33 +++- 4 files changed, 199 insertions(+), 19 deletions(-) create mode 100644 packages/cli/src/lib/mini-oxygen/workerd-inspector-proxy.ts diff --git a/packages/cli/src/lib/mini-oxygen/common.ts b/packages/cli/src/lib/mini-oxygen/common.ts index ec2b8ddb74..2c5fe62130 100644 --- a/packages/cli/src/lib/mini-oxygen/common.ts +++ b/packages/cli/src/lib/mini-oxygen/common.ts @@ -6,6 +6,8 @@ import { import colors from '@shopify/cli-kit/node/colors'; import {DEV_ROUTES} from '../request-events.js'; +export const DEFAULT_INSPECTOR_PORT = 9222; + export function logRequestLine( // Minimal overlap between Fetch, Miniflare@2 and Miniflare@3 request types. request: Pick & { diff --git a/packages/cli/src/lib/mini-oxygen/workerd-inspector-proxy.ts b/packages/cli/src/lib/mini-oxygen/workerd-inspector-proxy.ts new file mode 100644 index 0000000000..8a9c2cdd26 --- /dev/null +++ b/packages/cli/src/lib/mini-oxygen/workerd-inspector-proxy.ts @@ -0,0 +1,159 @@ +import crypto from 'node:crypto'; +import { + createServer, + type IncomingMessage, + type ServerResponse, +} from 'node:http'; +import {WebSocketServer, type WebSocket, type MessageEvent} from 'ws'; +import {type Protocol} from 'devtools-protocol'; + +export function createInspectorProxy( + port: number, + inspectorConnection?: {ws?: WebSocket}, +) { + /** + * A unique identifier for this debugging session. + */ + const sessionId = crypto.randomUUID(); + /** + * WebSocket connection to the local debugger (e.g. VSCode, DevTools). + */ + let debuggerWs: WebSocket | undefined = undefined; + /** + * WebSocket connection to the Workerd inspector. + */ + let inspectorWs: WebSocket | undefined = inspectorConnection?.ws; + + const server = createServer((req: IncomingMessage, res: ServerResponse) => { + // Remove query params. E.g. `/json/list?for_tab` + const url = req.url?.split('?')[0] ?? ''; + + switch (url) { + // We implement a couple of well known end points + // that are queried for metadata by chrome://inspect + case '/json/version': + res.setHeader('Content-Type', 'application/json'); + res.end( + JSON.stringify({Browser: 'hydrogen/v2', 'Protocol-Version': '1.3'}), + ); + break; + case '/json': + case '/json/list': + { + res.setHeader('Content-Type', 'application/json'); + const localHost = `localhost:${port}/ws`; + const devtoolsFrontendUrl = `devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=${localHost}`; + const devtoolsFrontendUrlCompat = `devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=${localHost}`; + + res.end( + JSON.stringify([ + { + id: sessionId, + type: 'node', + webSocketDebuggerUrl: `ws://${localHost}`, + devtoolsFrontendUrl, + devtoolsFrontendUrlCompat, + // Below are fields that are visible in the DevTools UI. + title: 'Hydrogen / Oxygen Worker', + faviconUrl: + 'https://cdn.shopify.com/s/files/1/0598/4822/8886/files/favicon.svg', + url: + 'https://' + + (inspectorWs ? new URL(inspectorWs.url).host : localHost), + }, + ]), + ); + } + return; + default: + break; + } + }); + + const wsServer = new WebSocketServer({server, clientTracking: true}); + server.listen(port); + + /** + * Buffer inspector messages when there is no debugger connected. + * (e.g. initial console logs, etc.). + */ + let messageBuffer: MessageEvent[] = []; + + wsServer.on('connection', (ws, req) => { + if (wsServer.clients.size > 1) { + // Only support one active Devtools instance at a time. + console.error( + 'Tried to open a new devtools window when a previous one was already open.', + ); + + ws.close(1013, 'Too many clients; only one can be connected at a time'); + } else { + // Ensure debugger is restarted in workerd before connecting + // a new client to receive `Debugger.scriptParsed` events. + inspectorWs?.send( + JSON.stringify({id: 100_000_000, method: 'Debugger.disable'}), + ); + + debuggerWs?.removeEventListener('message', sendMessageToInspector); + debuggerWs = ws; + + debuggerWs.addEventListener('message', sendMessageToInspector); + debuggerWs.addEventListener('close', () => { + debuggerWs?.removeEventListener('message', sendMessageToInspector); + debuggerWs = undefined; + }); + + // Flush any buffered messages + messageBuffer.forEach(sendMessageToDebugger); + messageBuffer = []; + } + }); + + if (inspectorWs) onInspectorConnection(); + + function onInspectorConnection() { + inspectorWs?.addEventListener('message', sendMessageToDebugger); + + // In case this is a DevTools connection, send a warning + // message to the console to inform about reconnection. + // VSCode can reconnect automatically with `restart: true`. + debuggerWs?.send( + JSON.stringify({ + method: 'Runtime.consoleAPICalled', + params: { + type: 'warning', + args: [ + { + type: 'string', + value: + 'Source code changed. Please reload the DevTools to reconnect the debugger.', + }, + ], + executionContextId: Date.now(), + timestamp: Date.now(), + } satisfies Protocol.Runtime.ConsoleAPICalledEvent, + }), + ); + + debuggerWs?.close(1001, 'Source code changed'); + } + + function sendMessageToInspector(event: MessageEvent) { + inspectorWs?.send(event.data); + } + + function sendMessageToDebugger(event: MessageEvent) { + if (debuggerWs) { + debuggerWs.send(event.data); + } else { + messageBuffer.push(event); + } + } + + return { + updateInspectorConnection(newConnection?: {ws?: WebSocket}) { + inspectorWs = newConnection?.ws; + onInspectorConnection(); + }, + }; +} diff --git a/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts b/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts index 547c707e91..a1817f1635 100644 --- a/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts +++ b/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts @@ -342,17 +342,21 @@ export function connectToInspector({ sourceMapAbortController.abort(); }); - return () => { - clearInterval(keepAliveInterval); - - if (!isClosed()) { - try { - ws.close(); - } catch (err) { - // Closing before the websocket is ready will throw an error. + return { + ws, + close: () => { + clearInterval(keepAliveInterval); + + if (!isClosed()) { + try { + ws.removeAllListeners(); + ws.close(); + } catch (err) { + // Closing before the websocket is ready will throw an error. + } } - } - sourceMapAbortController.abort(); + sourceMapAbortController.abort(); + }, }; } diff --git a/packages/cli/src/lib/mini-oxygen/workerd.ts b/packages/cli/src/lib/mini-oxygen/workerd.ts index 80a938f576..eb9446c40e 100644 --- a/packages/cli/src/lib/mini-oxygen/workerd.ts +++ b/packages/cli/src/lib/mini-oxygen/workerd.ts @@ -1,3 +1,4 @@ +import crypto from 'node:crypto'; import { Miniflare, Request, @@ -16,10 +17,15 @@ import { import {renderSuccess} from '@shopify/cli-kit/node/ui'; import {lookupMimeType} from '@shopify/cli-kit/node/mimes'; import {connectToInspector, findInspectorUrl} from './workerd-inspector.js'; +import {createInspectorProxy} from './workerd-inspector-proxy.js'; import {DEFAULT_PORT} from '../flags.js'; import {findPort} from '../find-port.js'; import type {MiniOxygenInstance, MiniOxygenOptions} from './types.js'; -import {OXYGEN_HEADERS_MAP, logRequestLine} from './common.js'; +import { + DEFAULT_INSPECTOR_PORT, + OXYGEN_HEADERS_MAP, + logRequestLine, +} from './common.js'; import { H2O_BINDING_NAME, handleDebugNetworkRequest, @@ -27,7 +33,7 @@ import { setConstructors, } from '../request-events.js'; -const DEFAULT_INSPECTOR_PORT = 9229; +const PRIVATE_WORKERD_INSPECTOR_PORT = 9229; export async function startWorkerdServer({ root, @@ -38,7 +44,8 @@ export async function startWorkerdServer({ env, }: MiniOxygenOptions): Promise { const appPort = await findPort(port); - const inspectorPort = await findPort(DEFAULT_INSPECTOR_PORT); + const workerdInspectorPort = await findPort(PRIVATE_WORKERD_INSPECTOR_PORT); + const publicInspectorPort = await findPort(DEFAULT_INSPECTOR_PORT); const oxygenHeadersMap = Object.values(OXYGEN_HEADERS_MAP).reduce( (acc, item) => { @@ -57,7 +64,7 @@ export async function startWorkerdServer({ cf: false, verbose: false, port: appPort, - inspectorPort, + inspectorPort: workerdInspectorPort, log: new NoOpLog(), liveReload: watch, host: 'localhost', @@ -104,11 +111,17 @@ export async function startWorkerdServer({ const listeningAt = (await miniOxygen.ready).origin; const sourceMapPath = buildPathWorkerFile + '.map'; - let inspectorUrl = await findInspectorUrl(inspectorPort); - let cleanupInspector = inspectorUrl + + let inspectorUrl = await findInspectorUrl(workerdInspectorPort); + let inspectorConnection = inspectorUrl ? connectToInspector({inspectorUrl, sourceMapPath}) : undefined; + const inspectorProxy = createInspectorProxy( + publicInspectorPort, + inspectorConnection, + ); + return { port: appPort, listeningAt, @@ -125,12 +138,14 @@ export async function startWorkerdServer({ } } - cleanupInspector?.(); + inspectorConnection?.close(); + // @ts-expect-error await miniOxygen.setOptions(miniOxygenOptions); - inspectorUrl ??= await findInspectorUrl(inspectorPort); + inspectorUrl ??= await findInspectorUrl(workerdInspectorPort); if (inspectorUrl) { - cleanupInspector = connectToInspector({inspectorUrl, sourceMapPath}); + inspectorConnection = connectToInspector({inspectorUrl, sourceMapPath}); + inspectorProxy.updateInspectorConnection(inspectorConnection); } }, showBanner(options) { From 9a4759ec6bc42a3dd49d987ff7b8b126bc10c059 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Wed, 8 Nov 2023 17:34:46 +0900 Subject: [PATCH 06/14] Only serve inspector when flags are passed --- packages/cli/oclif.manifest.json | 9 ++++- packages/cli/src/commands/hydrogen/dev.ts | 43 ++++++++++++++------- packages/cli/src/lib/mini-oxygen/index.ts | 2 + packages/cli/src/lib/mini-oxygen/node.ts | 14 ++++++- packages/cli/src/lib/mini-oxygen/types.ts | 2 + packages/cli/src/lib/mini-oxygen/workerd.ts | 19 ++++++--- 6 files changed, 66 insertions(+), 23 deletions(-) diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 50ed43d48b..bf17acf8fd 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -286,9 +286,16 @@ "debug": { "name": "debug", "type": "boolean", - "description": "Attaches a Node inspector", + "description": "Enables inspector connections with a debugger.", "allowNo": false }, + "inspector-port": { + "name": "inspector-port", + "type": "option", + "description": "Port where the inspector will be available.", + "multiple": false, + "default": 9222 + }, "host": { "name": "host", "type": "option", diff --git a/packages/cli/src/commands/hydrogen/dev.ts b/packages/cli/src/commands/hydrogen/dev.ts index 5f1526aeed..7c38c1b446 100644 --- a/packages/cli/src/commands/hydrogen/dev.ts +++ b/packages/cli/src/commands/hydrogen/dev.ts @@ -22,7 +22,11 @@ import { } from '../../lib/flags.js'; import Command from '@shopify/cli-kit/node/base-command'; import {Flags} from '@oclif/core'; -import {type MiniOxygen, startMiniOxygen} from '../../lib/mini-oxygen/index.js'; +import { + type MiniOxygen, + startMiniOxygen, + DEFAULT_INSPECTOR_PORT, +} from '../../lib/mini-oxygen/index.js'; import {checkHydrogenVersion} from '../../lib/check-version.js'; import {addVirtualRoutes} from '../../lib/virtual-routes.js'; import {spawnCodegenProcess} from '../../lib/codegen.js'; @@ -56,10 +60,15 @@ export default class Dev extends Command { default: false, }), debug: Flags.boolean({ - description: 'Attaches a Node inspector', + description: 'Enables inspector connections with a debugger.', env: 'SHOPIFY_HYDROGEN_FLAG_DEBUG', default: false, }), + 'inspector-port': Flags.integer({ + description: 'Port where the inspector will be available.', + env: 'SHOPIFY_HYDROGEN_FLAG_INSPECTOR_PORT', + default: DEFAULT_INSPECTOR_PORT, + }), host: deprecated('--host')(), ['env-branch']: commonFlags.envBranch, }; @@ -77,6 +86,19 @@ export default class Dev extends Command { } } +type DevOptions = { + port?: number; + path?: string; + useCodegen?: boolean; + workerRuntime?: boolean; + codegenConfigPath?: string; + disableVirtualRoutes?: boolean; + envBranch?: string; + debug?: boolean; + sourcemap?: boolean; + inspectorPort?: number; +}; + async function runDev({ port: portFlag = DEFAULT_PORT, path: appPath, @@ -87,23 +109,12 @@ async function runDev({ envBranch, debug = false, sourcemap = true, -}: { - port?: number; - path?: string; - useCodegen?: boolean; - workerRuntime?: boolean; - codegenConfigPath?: string; - disableVirtualRoutes?: boolean; - envBranch?: string; - debug?: boolean; - sourcemap?: boolean; -}) { + inspectorPort, +}: DevOptions) { if (!process.env.NODE_ENV) process.env.NODE_ENV = 'development'; muteDevLogs(); - if (debug) (await import('node:inspector')).open(); - const {root, publicPath, buildPathClient, buildPathWorkerFile} = getProjectPaths(appPath); @@ -165,6 +176,8 @@ async function runDev({ miniOxygen = await startMiniOxygen( { root, + debug, + inspectorPort, port: portFlag, watch: !liveReload, buildPathWorkerFile, diff --git a/packages/cli/src/lib/mini-oxygen/index.ts b/packages/cli/src/lib/mini-oxygen/index.ts index b19bd1f6b1..fa643b6f47 100644 --- a/packages/cli/src/lib/mini-oxygen/index.ts +++ b/packages/cli/src/lib/mini-oxygen/index.ts @@ -2,6 +2,8 @@ import type {MiniOxygenInstance, MiniOxygenOptions} from './types.js'; export type MiniOxygen = MiniOxygenInstance; +export {DEFAULT_INSPECTOR_PORT} from './common.js'; + export async function startMiniOxygen( options: MiniOxygenOptions, useWorkerd = false, diff --git a/packages/cli/src/lib/mini-oxygen/node.ts b/packages/cli/src/lib/mini-oxygen/node.ts index ac7d3b7aec..40ba9c7577 100644 --- a/packages/cli/src/lib/mini-oxygen/node.ts +++ b/packages/cli/src/lib/mini-oxygen/node.ts @@ -9,12 +9,17 @@ import { } from '@shopify/mini-oxygen'; import {DEFAULT_PORT} from '../flags.js'; import type {MiniOxygenInstance, MiniOxygenOptions} from './types.js'; -import {OXYGEN_HEADERS_MAP, logRequestLine} from './common.js'; +import { + DEFAULT_INSPECTOR_PORT, + OXYGEN_HEADERS_MAP, + logRequestLine, +} from './common.js'; import { H2O_BINDING_NAME, logRequestEvent, handleDebugNetworkRequest, } from '../request-events.js'; +import {findPort} from '../find-port.js'; export async function startNodeServer({ port = DEFAULT_PORT, @@ -22,6 +27,8 @@ export async function startNodeServer({ buildPathWorkerFile, buildPathClient, env, + debug = false, + inspectorPort = DEFAULT_INSPECTOR_PORT, }: MiniOxygenOptions): Promise { const oxygenHeaders = Object.fromEntries( Object.entries(OXYGEN_HEADERS_MAP).map(([key, value]) => { @@ -45,6 +52,11 @@ export async function startNodeServer({ }, }; + if (debug) { + const publicInspectorPort = await findPort(inspectorPort); + (await import('node:inspector')).open(publicInspectorPort); + } + const miniOxygen = await startServer({ script: await readFile(buildPathWorkerFile), workerFile: buildPathWorkerFile, diff --git a/packages/cli/src/lib/mini-oxygen/types.ts b/packages/cli/src/lib/mini-oxygen/types.ts index 91aa3abdd9..2ef5096297 100644 --- a/packages/cli/src/lib/mini-oxygen/types.ts +++ b/packages/cli/src/lib/mini-oxygen/types.ts @@ -6,6 +6,8 @@ export type MiniOxygenOptions = { buildPathClient: string; buildPathWorkerFile: string; env: {[key: string]: string}; + debug?: boolean; + inspectorPort?: number; }; export type MiniOxygenInstance = { diff --git a/packages/cli/src/lib/mini-oxygen/workerd.ts b/packages/cli/src/lib/mini-oxygen/workerd.ts index eb9446c40e..14c3ef8858 100644 --- a/packages/cli/src/lib/mini-oxygen/workerd.ts +++ b/packages/cli/src/lib/mini-oxygen/workerd.ts @@ -38,6 +38,8 @@ const PRIVATE_WORKERD_INSPECTOR_PORT = 9229; export async function startWorkerdServer({ root, port = DEFAULT_PORT, + inspectorPort = DEFAULT_INSPECTOR_PORT, + debug = false, watch = false, buildPathWorkerFile, buildPathClient, @@ -45,7 +47,6 @@ export async function startWorkerdServer({ }: MiniOxygenOptions): Promise { const appPort = await findPort(port); const workerdInspectorPort = await findPort(PRIVATE_WORKERD_INSPECTOR_PORT); - const publicInspectorPort = await findPort(DEFAULT_INSPECTOR_PORT); const oxygenHeadersMap = Object.values(OXYGEN_HEADERS_MAP).reduce( (acc, item) => { @@ -117,10 +118,9 @@ export async function startWorkerdServer({ ? connectToInspector({inspectorUrl, sourceMapPath}) : undefined; - const inspectorProxy = createInspectorProxy( - publicInspectorPort, - inspectorConnection, - ); + const inspectorProxy = debug + ? createInspectorProxy(await findPort(inspectorPort), inspectorConnection) + : undefined; return { port: appPort, @@ -145,7 +145,7 @@ export async function startWorkerdServer({ inspectorUrl ??= await findInspectorUrl(workerdInspectorPort); if (inspectorUrl) { inspectorConnection = connectToInspector({inspectorUrl, sourceMapPath}); - inspectorProxy.updateInspectorConnection(inspectorConnection); + inspectorProxy?.updateInspectorConnection(inspectorConnection); } }, showBanner(options) { @@ -159,6 +159,13 @@ export async function startWorkerdServer({ body: [ `View ${options?.appName ?? 'Hydrogen'} app: ${listeningAt}`, ...(options?.extraLines ?? []), + ...(debug + ? [ + { + warn: `\n\nDebugger listening on ws://localhost:${inspectorPort}`, + }, + ] + : []), ], }); console.log(''); From d8f073e735813970a43849e1b694991536de7dcb Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Wed, 8 Nov 2023 17:38:49 +0900 Subject: [PATCH 07/14] Reuse type --- packages/cli/src/lib/mini-oxygen/workerd-inspector-proxy.ts | 3 ++- packages/cli/src/lib/mini-oxygen/workerd-inspector.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/lib/mini-oxygen/workerd-inspector-proxy.ts b/packages/cli/src/lib/mini-oxygen/workerd-inspector-proxy.ts index 8a9c2cdd26..bd5130d4ce 100644 --- a/packages/cli/src/lib/mini-oxygen/workerd-inspector-proxy.ts +++ b/packages/cli/src/lib/mini-oxygen/workerd-inspector-proxy.ts @@ -6,6 +6,7 @@ import { } from 'node:http'; import {WebSocketServer, type WebSocket, type MessageEvent} from 'ws'; import {type Protocol} from 'devtools-protocol'; +import {type InspectorWebSocketTarget} from './workerd-inspector.js'; export function createInspectorProxy( port: number, @@ -60,7 +61,7 @@ export function createInspectorProxy( url: 'https://' + (inspectorWs ? new URL(inspectorWs.url).host : localHost), - }, + } satisfies InspectorWebSocketTarget, ]), ); } diff --git a/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts b/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts index a1817f1635..364961f3bf 100644 --- a/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts +++ b/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts @@ -15,11 +15,11 @@ import { } from './workerd-inspector-logs.js'; // https://chromedevtools.github.io/devtools-protocol/#endpoints -interface InspectorWebSocketTarget { +export interface InspectorWebSocketTarget { id: string; title: string; type: 'node'; - description: string; + description?: string; webSocketDebuggerUrl: string; devtoolsFrontendUrl: string; devtoolsFrontendUrlCompat: string; From 51968816323f3bee46fd2f75beee0120cbe6add9 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Wed, 8 Nov 2023 19:02:54 +0900 Subject: [PATCH 08/14] Minor adjustments --- packages/cli/src/commands/hydrogen/dev.ts | 2 +- packages/cli/src/commands/hydrogen/preview.ts | 8 ++++++-- packages/cli/src/lib/mini-oxygen/index.ts | 5 +++++ packages/cli/src/lib/mini-oxygen/node.ts | 19 ++++++++++--------- packages/cli/src/lib/mini-oxygen/types.ts | 2 +- packages/cli/src/lib/mini-oxygen/workerd.ts | 12 ++++-------- 6 files changed, 27 insertions(+), 21 deletions(-) diff --git a/packages/cli/src/commands/hydrogen/dev.ts b/packages/cli/src/commands/hydrogen/dev.ts index 7c38c1b446..875a3519ec 100644 --- a/packages/cli/src/commands/hydrogen/dev.ts +++ b/packages/cli/src/commands/hydrogen/dev.ts @@ -96,7 +96,7 @@ type DevOptions = { envBranch?: string; debug?: boolean; sourcemap?: boolean; - inspectorPort?: number; + inspectorPort: number; }; async function runDev({ diff --git a/packages/cli/src/commands/hydrogen/preview.ts b/packages/cli/src/commands/hydrogen/preview.ts index 491ad17371..e4c1a50d6b 100644 --- a/packages/cli/src/commands/hydrogen/preview.ts +++ b/packages/cli/src/commands/hydrogen/preview.ts @@ -6,7 +6,10 @@ import { flagsToCamelObject, DEFAULT_PORT, } from '../../lib/flags.js'; -import {startMiniOxygen} from '../../lib/mini-oxygen/index.js'; +import { + DEFAULT_INSPECTOR_PORT, + startMiniOxygen, +} from '../../lib/mini-oxygen/index.js'; import {getAllEnvironmentVariables} from '../../lib/environment-variables.js'; import {getConfig} from '../../lib/shopify-config.js'; @@ -55,9 +58,10 @@ export async function runPreview({ { root, port, + env, buildPathClient, buildPathWorkerFile, - env, + inspectorPort: DEFAULT_INSPECTOR_PORT, }, workerRuntime, ); diff --git a/packages/cli/src/lib/mini-oxygen/index.ts b/packages/cli/src/lib/mini-oxygen/index.ts index fa643b6f47..469fb75418 100644 --- a/packages/cli/src/lib/mini-oxygen/index.ts +++ b/packages/cli/src/lib/mini-oxygen/index.ts @@ -1,4 +1,5 @@ import type {MiniOxygenInstance, MiniOxygenOptions} from './types.js'; +import {findPort} from '../find-port.js'; export type MiniOxygen = MiniOxygenInstance; @@ -8,6 +9,10 @@ export async function startMiniOxygen( options: MiniOxygenOptions, useWorkerd = false, ): Promise { + if (options.debug) { + options.inspectorPort = await findPort(options.inspectorPort); + } + if (useWorkerd) { const {startWorkerdServer} = await import('./workerd.js'); return startWorkerdServer(options); diff --git a/packages/cli/src/lib/mini-oxygen/node.ts b/packages/cli/src/lib/mini-oxygen/node.ts index 40ba9c7577..d73e6aae59 100644 --- a/packages/cli/src/lib/mini-oxygen/node.ts +++ b/packages/cli/src/lib/mini-oxygen/node.ts @@ -9,17 +9,12 @@ import { } from '@shopify/mini-oxygen'; import {DEFAULT_PORT} from '../flags.js'; import type {MiniOxygenInstance, MiniOxygenOptions} from './types.js'; -import { - DEFAULT_INSPECTOR_PORT, - OXYGEN_HEADERS_MAP, - logRequestLine, -} from './common.js'; +import {OXYGEN_HEADERS_MAP, logRequestLine} from './common.js'; import { H2O_BINDING_NAME, logRequestEvent, handleDebugNetworkRequest, } from '../request-events.js'; -import {findPort} from '../find-port.js'; export async function startNodeServer({ port = DEFAULT_PORT, @@ -28,7 +23,7 @@ export async function startNodeServer({ buildPathClient, env, debug = false, - inspectorPort = DEFAULT_INSPECTOR_PORT, + inspectorPort, }: MiniOxygenOptions): Promise { const oxygenHeaders = Object.fromEntries( Object.entries(OXYGEN_HEADERS_MAP).map(([key, value]) => { @@ -53,8 +48,7 @@ export async function startNodeServer({ }; if (debug) { - const publicInspectorPort = await findPort(inspectorPort); - (await import('node:inspector')).open(publicInspectorPort); + (await import('node:inspector')).open(inspectorPort); } const miniOxygen = await startServer({ @@ -130,6 +124,13 @@ export async function startNodeServer({ body: [ `View ${options?.appName ?? 'Hydrogen'} app: ${listeningAt}`, ...(options?.extraLines ?? []), + ...(debug + ? [ + { + warn: `\n\nDebugger listening on ws://localhost:${inspectorPort}`, + }, + ] + : []), ], }); console.log(''); diff --git a/packages/cli/src/lib/mini-oxygen/types.ts b/packages/cli/src/lib/mini-oxygen/types.ts index 2ef5096297..92b250db53 100644 --- a/packages/cli/src/lib/mini-oxygen/types.ts +++ b/packages/cli/src/lib/mini-oxygen/types.ts @@ -7,7 +7,7 @@ export type MiniOxygenOptions = { buildPathWorkerFile: string; env: {[key: string]: string}; debug?: boolean; - inspectorPort?: number; + inspectorPort: number; }; export type MiniOxygenInstance = { diff --git a/packages/cli/src/lib/mini-oxygen/workerd.ts b/packages/cli/src/lib/mini-oxygen/workerd.ts index 14c3ef8858..98962d76a6 100644 --- a/packages/cli/src/lib/mini-oxygen/workerd.ts +++ b/packages/cli/src/lib/mini-oxygen/workerd.ts @@ -21,11 +21,7 @@ import {createInspectorProxy} from './workerd-inspector-proxy.js'; import {DEFAULT_PORT} from '../flags.js'; import {findPort} from '../find-port.js'; import type {MiniOxygenInstance, MiniOxygenOptions} from './types.js'; -import { - DEFAULT_INSPECTOR_PORT, - OXYGEN_HEADERS_MAP, - logRequestLine, -} from './common.js'; +import {OXYGEN_HEADERS_MAP, logRequestLine} from './common.js'; import { H2O_BINDING_NAME, handleDebugNetworkRequest, @@ -38,7 +34,7 @@ const PRIVATE_WORKERD_INSPECTOR_PORT = 9229; export async function startWorkerdServer({ root, port = DEFAULT_PORT, - inspectorPort = DEFAULT_INSPECTOR_PORT, + inspectorPort: publicInspectorPort, debug = false, watch = false, buildPathWorkerFile, @@ -119,7 +115,7 @@ export async function startWorkerdServer({ : undefined; const inspectorProxy = debug - ? createInspectorProxy(await findPort(inspectorPort), inspectorConnection) + ? createInspectorProxy(publicInspectorPort, inspectorConnection) : undefined; return { @@ -162,7 +158,7 @@ export async function startWorkerdServer({ ...(debug ? [ { - warn: `\n\nDebugger listening on ws://localhost:${inspectorPort}`, + warn: `\n\nDebugger listening on ws://localhost:${publicInspectorPort}`, }, ] : []), From dfed6b0ffa4df96389b3d2f9cec0a7f749e5f564 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Wed, 8 Nov 2023 19:22:40 +0900 Subject: [PATCH 09/14] Avoid using global fetch for older versions of Node --- packages/cli/src/lib/mini-oxygen/workerd-inspector.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts b/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts index 364961f3bf..d0a5b5a263 100644 --- a/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts +++ b/packages/cli/src/lib/mini-oxygen/workerd-inspector.ts @@ -5,6 +5,7 @@ import {dirname} from 'node:path'; import {readFile} from 'node:fs/promises'; +import {fetch} from '@shopify/cli-kit/node/http'; import {SourceMapConsumer} from 'source-map'; import {WebSocket, type MessageEvent} from 'ws'; import {type Protocol} from 'devtools-protocol'; From 0f8760037506f58e45c4ccd734c3de8527696b26 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Thu, 9 Nov 2023 12:21:20 +0900 Subject: [PATCH 10/14] Changesets --- .changeset/silver-sheep-melt.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .changeset/silver-sheep-melt.md diff --git a/.changeset/silver-sheep-melt.md b/.changeset/silver-sheep-melt.md new file mode 100644 index 0000000000..d26a23a641 --- /dev/null +++ b/.changeset/silver-sheep-melt.md @@ -0,0 +1,31 @@ +--- +'@shopify/cli-hydrogen': minor +--- + +Enable debugger connections by passing `--debug` flag to the `h2 dev` command: + +- Current default runtime (Node.js sandbox): `h2 dev --debug`. +- New Worker runtime: `h2 dev --debug --worker-unstable`. + +You can then connect to the port `9222` (configurable with the new `--inspector-port` flag) to start step debugging. + +For example, in Chrome you can go to `chrome://inspect` and make sure the inspector port is added to the network targets. In VSCode, you can add the following to your `.vscode/launch.json`: + +``` +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Hydrogen", + "type": "node", + "request": "attach", + "port": 9222, + "cwd": "/", + "resolveSourceMapLocations": null, + "attachExistingChildren": false, + "autoAttachChildProcesses": false, + "restart": true + } + ] +} +``` From 3b2958dc7b94491371de7f2ada85316a6ffaac5b Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Thu, 9 Nov 2023 16:32:48 +0900 Subject: [PATCH 11/14] Enable debugging for preview command --- packages/cli/oclif.manifest.json | 13 ++++++ packages/cli/src/commands/hydrogen/dev.ts | 25 ++++------ packages/cli/src/commands/hydrogen/preview.ts | 46 +++++++++++-------- packages/cli/src/lib/flags.ts | 11 +++++ packages/cli/src/lib/mini-oxygen/index.ts | 5 -- packages/cli/src/lib/mini-oxygen/workerd.ts | 4 +- 6 files changed, 59 insertions(+), 45 deletions(-) diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index bf17acf8fd..f2137d7274 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -530,6 +530,19 @@ "char": "e", "description": "Specify an environment's branch name when using remote environment variables.", "multiple": false + }, + "inspector-port": { + "name": "inspector-port", + "type": "option", + "description": "Port where the inspector will be available.", + "multiple": false, + "default": 9222 + }, + "debug": { + "name": "debug", + "type": "boolean", + "description": "Enables inspector connections with a debugger.", + "allowNo": false } }, "args": {} diff --git a/packages/cli/src/commands/hydrogen/dev.ts b/packages/cli/src/commands/hydrogen/dev.ts index 875a3519ec..dfa421cda4 100644 --- a/packages/cli/src/commands/hydrogen/dev.ts +++ b/packages/cli/src/commands/hydrogen/dev.ts @@ -22,11 +22,7 @@ import { } from '../../lib/flags.js'; import Command from '@shopify/cli-kit/node/base-command'; import {Flags} from '@oclif/core'; -import { - type MiniOxygen, - startMiniOxygen, - DEFAULT_INSPECTOR_PORT, -} from '../../lib/mini-oxygen/index.js'; +import {type MiniOxygen, startMiniOxygen} from '../../lib/mini-oxygen/index.js'; import {checkHydrogenVersion} from '../../lib/check-version.js'; import {addVirtualRoutes} from '../../lib/virtual-routes.js'; import {spawnCodegenProcess} from '../../lib/codegen.js'; @@ -59,16 +55,8 @@ export default class Dev extends Command { env: 'SHOPIFY_HYDROGEN_FLAG_DISABLE_VIRTUAL_ROUTES', default: false, }), - debug: Flags.boolean({ - description: 'Enables inspector connections with a debugger.', - env: 'SHOPIFY_HYDROGEN_FLAG_DEBUG', - default: false, - }), - 'inspector-port': Flags.integer({ - description: 'Port where the inspector will be available.', - env: 'SHOPIFY_HYDROGEN_FLAG_INSPECTOR_PORT', - default: DEFAULT_INSPECTOR_PORT, - }), + debug: commonFlags.debug, + 'inspector-port': commonFlags.inspectorPort, host: deprecated('--host')(), ['env-branch']: commonFlags.envBranch, }; @@ -100,7 +88,7 @@ type DevOptions = { }; async function runDev({ - port: portFlag = DEFAULT_PORT, + port: appPort, path: appPath, useCodegen = false, workerRuntime = false, @@ -146,6 +134,9 @@ async function runDev({ const serverBundleExists = () => fileExists(buildPathWorkerFile); + inspectorPort = debug ? await findPort(inspectorPort) : inspectorPort; + appPort = workerRuntime ? await findPort(appPort) : appPort; // findPort is already called for Node sandbox + const [remixConfig, {shop, storefront}] = await Promise.all([ reloadConfig(), getConfig(root), @@ -178,7 +169,7 @@ async function runDev({ root, debug, inspectorPort, - port: portFlag, + port: appPort, watch: !liveReload, buildPathWorkerFile, buildPathClient, diff --git a/packages/cli/src/commands/hydrogen/preview.ts b/packages/cli/src/commands/hydrogen/preview.ts index e4c1a50d6b..0e8be9d53c 100644 --- a/packages/cli/src/commands/hydrogen/preview.ts +++ b/packages/cli/src/commands/hydrogen/preview.ts @@ -1,17 +1,11 @@ import Command from '@shopify/cli-kit/node/base-command'; import {muteDevLogs} from '../../lib/log.js'; import {getProjectPaths} from '../../lib/remix-config.js'; -import { - commonFlags, - flagsToCamelObject, - DEFAULT_PORT, -} from '../../lib/flags.js'; -import { - DEFAULT_INSPECTOR_PORT, - startMiniOxygen, -} from '../../lib/mini-oxygen/index.js'; +import {commonFlags, flagsToCamelObject} from '../../lib/flags.js'; +import {startMiniOxygen} from '../../lib/mini-oxygen/index.js'; import {getAllEnvironmentVariables} from '../../lib/environment-variables.js'; import {getConfig} from '../../lib/shopify-config.js'; +import {findPort} from '../../lib/find-port.js'; export default class Preview extends Command { static description = @@ -20,8 +14,10 @@ export default class Preview extends Command { static flags = { path: commonFlags.path, port: commonFlags.port, - ['worker-unstable']: commonFlags.workerRuntime, - ['env-branch']: commonFlags.envBranch, + 'worker-unstable': commonFlags.workerRuntime, + 'env-branch': commonFlags.envBranch, + 'inspector-port': commonFlags.inspectorPort, + debug: commonFlags.debug, }; async run(): Promise { @@ -34,17 +30,23 @@ export default class Preview extends Command { } } -export async function runPreview({ - port = DEFAULT_PORT, - path: appPath, - workerRuntime = false, - envBranch, -}: { +type PreviewOptions = { port?: number; path?: string; workerRuntime?: boolean; envBranch?: string; -}) { + inspectorPort: number; + debug: boolean; +}; + +export async function runPreview({ + port: appPort, + path: appPath, + workerRuntime = false, + envBranch, + inspectorPort, + debug, +}: PreviewOptions) { if (!process.env.NODE_ENV) process.env.NODE_ENV = 'production'; muteDevLogs({workerReload: false}); @@ -54,14 +56,18 @@ export async function runPreview({ const fetchRemote = !!shop && !!storefront?.id; const env = await getAllEnvironmentVariables({root, fetchRemote, envBranch}); + appPort = workerRuntime ? await findPort(appPort) : appPort; + inspectorPort = debug ? await findPort(inspectorPort) : inspectorPort; + const miniOxygen = await startMiniOxygen( { root, - port, + port: appPort, env, buildPathClient, buildPathWorkerFile, - inspectorPort: DEFAULT_INSPECTOR_PORT, + inspectorPort, + debug, }, workerRuntime, ); diff --git a/packages/cli/src/lib/flags.ts b/packages/cli/src/lib/flags.ts index 3b0610b240..62edc7f0dc 100644 --- a/packages/cli/src/lib/flags.ts +++ b/packages/cli/src/lib/flags.ts @@ -6,6 +6,7 @@ import colors from '@shopify/cli-kit/node/colors'; import type {CamelCasedProperties} from 'type-fest'; import {STYLING_CHOICES} from './setups/css/index.js'; import {I18N_CHOICES} from './setups/i18n/index.js'; +import {DEFAULT_INSPECTOR_PORT} from './mini-oxygen/common.js'; export const DEFAULT_PORT = 3000; @@ -89,6 +90,16 @@ export const commonFlags = { env: 'SHOPIFY_HYDROGEN_FLAG_SHORTCUT', allowNo: true, }), + debug: Flags.boolean({ + description: 'Enables inspector connections with a debugger.', + env: 'SHOPIFY_HYDROGEN_FLAG_DEBUG', + default: false, + }), + inspectorPort: Flags.integer({ + description: 'Port where the inspector will be available.', + env: 'SHOPIFY_HYDROGEN_FLAG_INSPECTOR_PORT', + default: DEFAULT_INSPECTOR_PORT, + }), }; export function flagsToCamelObject>(obj: T) { diff --git a/packages/cli/src/lib/mini-oxygen/index.ts b/packages/cli/src/lib/mini-oxygen/index.ts index 469fb75418..fa643b6f47 100644 --- a/packages/cli/src/lib/mini-oxygen/index.ts +++ b/packages/cli/src/lib/mini-oxygen/index.ts @@ -1,5 +1,4 @@ import type {MiniOxygenInstance, MiniOxygenOptions} from './types.js'; -import {findPort} from '../find-port.js'; export type MiniOxygen = MiniOxygenInstance; @@ -9,10 +8,6 @@ export async function startMiniOxygen( options: MiniOxygenOptions, useWorkerd = false, ): Promise { - if (options.debug) { - options.inspectorPort = await findPort(options.inspectorPort); - } - if (useWorkerd) { const {startWorkerdServer} = await import('./workerd.js'); return startWorkerdServer(options); diff --git a/packages/cli/src/lib/mini-oxygen/workerd.ts b/packages/cli/src/lib/mini-oxygen/workerd.ts index 98962d76a6..b284cf1363 100644 --- a/packages/cli/src/lib/mini-oxygen/workerd.ts +++ b/packages/cli/src/lib/mini-oxygen/workerd.ts @@ -18,7 +18,6 @@ import {renderSuccess} from '@shopify/cli-kit/node/ui'; import {lookupMimeType} from '@shopify/cli-kit/node/mimes'; import {connectToInspector, findInspectorUrl} from './workerd-inspector.js'; import {createInspectorProxy} from './workerd-inspector-proxy.js'; -import {DEFAULT_PORT} from '../flags.js'; import {findPort} from '../find-port.js'; import type {MiniOxygenInstance, MiniOxygenOptions} from './types.js'; import {OXYGEN_HEADERS_MAP, logRequestLine} from './common.js'; @@ -33,7 +32,7 @@ const PRIVATE_WORKERD_INSPECTOR_PORT = 9229; export async function startWorkerdServer({ root, - port = DEFAULT_PORT, + port: appPort, inspectorPort: publicInspectorPort, debug = false, watch = false, @@ -41,7 +40,6 @@ export async function startWorkerdServer({ buildPathClient, env, }: MiniOxygenOptions): Promise { - const appPort = await findPort(port); const workerdInspectorPort = await findPort(PRIVATE_WORKERD_INSPECTOR_PORT); const oxygenHeadersMap = Object.values(OXYGEN_HEADERS_MAP).reduce( From d9046e13f817a922c72d063b501e0ff49938f0f7 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Thu, 9 Nov 2023 18:29:28 +0900 Subject: [PATCH 12/14] Fix port type and imports --- packages/cli/src/commands/hydrogen/dev.ts | 3 ++- packages/cli/src/commands/hydrogen/preview.ts | 2 +- packages/cli/src/lib/mini-oxygen/types.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/commands/hydrogen/dev.ts b/packages/cli/src/commands/hydrogen/dev.ts index dfa421cda4..813bc487af 100644 --- a/packages/cli/src/commands/hydrogen/dev.ts +++ b/packages/cli/src/commands/hydrogen/dev.ts @@ -31,6 +31,7 @@ import {getConfig} from '../../lib/shopify-config.js'; import {setupLiveReload} from '../../lib/live-reload.js'; import {checkRemixVersions} from '../../lib/remix-version-check.js'; import {getGraphiQLUrl} from '../../lib/graphiql-url.js'; +import {findPort} from '../../lib/find-port.js'; const LOG_REBUILDING = '🧱 Rebuilding...'; const LOG_REBUILT = '🚀 Rebuilt'; @@ -75,7 +76,7 @@ export default class Dev extends Command { } type DevOptions = { - port?: number; + port: number; path?: string; useCodegen?: boolean; workerRuntime?: boolean; diff --git a/packages/cli/src/commands/hydrogen/preview.ts b/packages/cli/src/commands/hydrogen/preview.ts index 0e8be9d53c..5463e37159 100644 --- a/packages/cli/src/commands/hydrogen/preview.ts +++ b/packages/cli/src/commands/hydrogen/preview.ts @@ -31,7 +31,7 @@ export default class Preview extends Command { } type PreviewOptions = { - port?: number; + port: number; path?: string; workerRuntime?: boolean; envBranch?: string; diff --git a/packages/cli/src/lib/mini-oxygen/types.ts b/packages/cli/src/lib/mini-oxygen/types.ts index 92b250db53..e7cf4c7da0 100644 --- a/packages/cli/src/lib/mini-oxygen/types.ts +++ b/packages/cli/src/lib/mini-oxygen/types.ts @@ -1,6 +1,6 @@ export type MiniOxygenOptions = { root: string; - port?: number; + port: number; watch?: boolean; autoReload?: boolean; buildPathClient: string; From e2dc3a85bb10a85ccebb577ac9a7e52727314148 Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Wed, 15 Nov 2023 17:54:53 +0900 Subject: [PATCH 13/14] Swap ports --- packages/cli/oclif.manifest.json | 4 ++-- packages/cli/src/lib/mini-oxygen/common.ts | 3 ++- packages/cli/src/lib/mini-oxygen/workerd.ts | 4 +++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index f2137d7274..6f092c18c7 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -294,7 +294,7 @@ "type": "option", "description": "Port where the inspector will be available.", "multiple": false, - "default": 9222 + "default": 9229 }, "host": { "name": "host", @@ -536,7 +536,7 @@ "type": "option", "description": "Port where the inspector will be available.", "multiple": false, - "default": 9222 + "default": 9229 }, "debug": { "name": "debug", diff --git a/packages/cli/src/lib/mini-oxygen/common.ts b/packages/cli/src/lib/mini-oxygen/common.ts index 2c5fe62130..00c2e38e91 100644 --- a/packages/cli/src/lib/mini-oxygen/common.ts +++ b/packages/cli/src/lib/mini-oxygen/common.ts @@ -6,7 +6,8 @@ import { import colors from '@shopify/cli-kit/node/colors'; import {DEV_ROUTES} from '../request-events.js'; -export const DEFAULT_INSPECTOR_PORT = 9222; +// Default port used for debugging in VSCode and Chrome DevTools. +export const DEFAULT_INSPECTOR_PORT = 9229; export function logRequestLine( // Minimal overlap between Fetch, Miniflare@2 and Miniflare@3 request types. diff --git a/packages/cli/src/lib/mini-oxygen/workerd.ts b/packages/cli/src/lib/mini-oxygen/workerd.ts index b284cf1363..478e85d8a7 100644 --- a/packages/cli/src/lib/mini-oxygen/workerd.ts +++ b/packages/cli/src/lib/mini-oxygen/workerd.ts @@ -28,7 +28,9 @@ import { setConstructors, } from '../request-events.js'; -const PRIVATE_WORKERD_INSPECTOR_PORT = 9229; +// This should probably be `0` and let workerd find a free port, +// but at the moment we can't get the port from workerd (afaik?). +const PRIVATE_WORKERD_INSPECTOR_PORT = 9222; export async function startWorkerdServer({ root, From f1da5b8f5c1e7c2c3ff0b556b249d73394446c3f Mon Sep 17 00:00:00 2001 From: Fran Dios Date: Wed, 15 Nov 2023 19:31:12 +0900 Subject: [PATCH 14/14] Changesets --- .changeset/silver-sheep-melt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/silver-sheep-melt.md b/.changeset/silver-sheep-melt.md index d26a23a641..a008ca152c 100644 --- a/.changeset/silver-sheep-melt.md +++ b/.changeset/silver-sheep-melt.md @@ -7,7 +7,7 @@ Enable debugger connections by passing `--debug` flag to the `h2 dev` command: - Current default runtime (Node.js sandbox): `h2 dev --debug`. - New Worker runtime: `h2 dev --debug --worker-unstable`. -You can then connect to the port `9222` (configurable with the new `--inspector-port` flag) to start step debugging. +You can then connect to the port `9229` (configurable with the new `--inspector-port` flag) to start step debugging. For example, in Chrome you can go to `chrome://inspect` and make sure the inspector port is added to the network targets. In VSCode, you can add the following to your `.vscode/launch.json`: @@ -19,7 +19,7 @@ For example, in Chrome you can go to `chrome://inspect` and make sure the inspec "name": "Hydrogen", "type": "node", "request": "attach", - "port": 9222, + "port": 9229, "cwd": "/", "resolveSourceMapLocations": null, "attachExistingChildren": false,