Skip to content

Commit b075aa1

Browse files
committed
Bump Extension.js (canary)
1 parent 2410a23 commit b075aa1

5 files changed

Lines changed: 101 additions & 30 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
"@types/chrome": "^0.0.287",
9393
"@types/node": "^22.10.1",
9494
"eslint": "^9.16.0",
95-
"extension": "3.15.0-canary.local-30d4e851e6",
95+
"extension": "3.15.1-canary.local-fda8403864",
9696
"globals": "^15.13.0",
9797
"knip": "^5.33.0",
9898
"lint-staged": "16.1.0",

pnpm-lock.yaml

Lines changed: 19 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/assert-canary-runtime-installed.mjs

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,19 @@ import fs from 'node:fs'
1111
import os from 'node:os'
1212
import path from 'node:path'
1313
import {spawn} from 'node:child_process'
14-
import {fileURLToPath} from 'node:url'
14+
import {fileURLToPath, pathToFileURL} from 'node:url'
1515

1616
const __filename = fileURLToPath(import.meta.url)
1717
const __dirname = path.dirname(__filename)
1818
const repoRoot = path.resolve(__dirname, '..')
19-
const probePath = path.join(__dirname, 'probe-extension-develop-resolve.cjs')
19+
const probePathCjs = path.join(__dirname, 'probe-extension-develop-resolve.cjs')
20+
const probePathEsm = path.join(
21+
__dirname,
22+
'probe-extension-develop-resolve-esm.mjs'
23+
)
24+
// `--import` requires a URL form for absolute paths so the loader thread can
25+
// resolve the registration module the same way on POSIX and Windows.
26+
const probeEsmImportArg = pathToFileURL(probePathEsm).href
2027

2128
const installedExtensionBin = path.join(
2229
repoRoot,
@@ -65,7 +72,7 @@ const result = await new Promise((resolve) => {
6572
env: {
6673
...process.env,
6774
NODE_OPTIONS:
68-
`${process.env.NODE_OPTIONS ?? ''} --require ${probePath}`.trim(),
75+
`${process.env.NODE_OPTIONS ?? ''} --require ${probePathCjs} --import ${probeEsmImportArg}`.trim(),
6976
// Strip any stale env override that could mask resolver behavior.
7077
EXTENSION_DEVELOP_ROOT: ''
7178
},
@@ -75,20 +82,36 @@ const result = await new Promise((resolve) => {
7582

7683
let stdout = ''
7784
let stderr = ''
85+
let killedOnMarker = false
7886
child.stdout.on('data', (d) => (stdout += d.toString()))
79-
child.stderr.on('data', (d) => (stderr += d.toString()))
87+
child.stderr.on('data', (d) => {
88+
stderr += d.toString()
89+
// The ESM resolve hook lives in the loader worker thread and cannot
90+
// exit the main process directly, so kill from the parent as soon as
91+
// the marker line shows up. Avoids waiting for `extension dev` to
92+
// crash on the fake project (no package.json).
93+
if (!killedOnMarker && /__EXT_DEV_RESOLVED__::/.test(stderr)) {
94+
killedOnMarker = true
95+
child.kill('SIGKILL')
96+
}
97+
})
8098

8199
const killTimer = setTimeout(() => {
82100
child.kill('SIGKILL')
83101
}, 60_000)
84102

85103
child.on('close', (code) => {
86104
clearTimeout(killTimer)
87-
resolve({code: code ?? 1, stdout, stderr})
105+
resolve({code: code ?? 1, stdout, stderr, killedOnMarker})
88106
})
89107
child.on('error', (err) => {
90108
clearTimeout(killTimer)
91-
resolve({code: 1, stdout, stderr: String(err?.message || err)})
109+
resolve({
110+
code: 1,
111+
stdout,
112+
stderr: String(err?.message || err),
113+
killedOnMarker
114+
})
92115
})
93116
})
94117

@@ -121,16 +144,21 @@ if (!match) {
121144
process.exit(1)
122145
}
123146

124-
const resolved = match[1].trim()
147+
const resolvedRaw = match[1].trim()
148+
// CJS hook emits a filesystem path; ESM hook emits a `file://` URL. Normalize
149+
// both to a filesystem path before checking the local-install prefix.
150+
const resolvedPath = resolvedRaw.startsWith('file://')
151+
? fileURLToPath(resolvedRaw)
152+
: resolvedRaw
125153
const expectedPrefix = installedExtensionDevelopRoot + path.sep
126154

127-
if (!resolved.startsWith(expectedPrefix)) {
155+
if (!resolvedPath.startsWith(expectedPrefix)) {
128156
console.error(
129157
'✖ extension-develop resolved outside the local install:\n' +
130158
` expected prefix: ${expectedPrefix}\n` +
131-
` resolved: ${resolved}`
159+
` resolved: ${resolvedPath}`
132160
)
133161
process.exit(1)
134162
}
135163

136-
console.log(`✔ extension-develop resolved to ${resolved}`)
164+
console.log(`✔ extension-develop resolved to ${resolvedPath}`)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// ESM resolve hook (runs in Node's loader worker thread). Mirrors the CJS
2+
// probe-extension-develop-resolve.cjs: observes the first resolution that
3+
// matches the installed-runtime or workspace-runtime entry, prints a marker
4+
// line on stderr, and exits the *worker* thread cleanly. The main thread
5+
// then sees the marker land in stderr and the resolver returns normally.
6+
7+
import {writeSync} from 'node:fs'
8+
9+
const PATTERN =
10+
/(?:[\\/]extension-develop[\\/]|[\\/]programs[\\/]develop[\\/])dist[\\/]module\.[cm]?js$/
11+
12+
let observed = false
13+
14+
export async function resolve(specifier, context, nextResolve) {
15+
const result = await nextResolve(specifier, context)
16+
17+
if (
18+
!observed &&
19+
typeof result?.url === 'string' &&
20+
PATTERN.test(result.url)
21+
) {
22+
observed = true
23+
// writeSync to fd 2 because process.stderr in the loader thread is not
24+
// always wired the same way as on the main thread.
25+
writeSync(2, `__EXT_DEV_RESOLVED__::${result.url}\n`)
26+
// Signal main thread by triggering an unrecoverable error path is too
27+
// aggressive; instead we let the load proceed and rely on stderr capture.
28+
// The assert script's downstream dev() failure (no real package.json) is
29+
// expected and harmless once the marker has been observed.
30+
}
31+
32+
return result
33+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Loaded via `node --import` to register an ESM resolve hook that observes
2+
// which `extension-develop/dist/module.*` URL the canary CLI imports. Used
3+
// alongside the CJS probe (probe-extension-develop-resolve.cjs) so both
4+
// `Module._load` (CJS require) and `await import()` (ESM) paths are covered.
5+
6+
import {register} from 'node:module'
7+
8+
// `import.meta.url` is already a `file://` URL string; do NOT wrap it in
9+
// pathToFileURL (that would treat the URL as a file path and double-encode).
10+
register('./probe-extension-develop-resolve-esm-hook.mjs', import.meta.url)

0 commit comments

Comments
 (0)