Skip to content

Commit 8742f93

Browse files
DavertMikclaude
andcommitted
ci(host): register globalThis.codeceptjs so deps don't need bare import
@codeceptjs/configure and @codeceptjs/expect-helper currently do top-level \`import 'codeceptjs'\`, which can't resolve inside this repo's CI (the project IS the codeceptjs package, so npm doesn't drop a node_modules entry to resolve to). The CI symlink works around it; the proper fix is to give those packages an in-process handle so they don't need to look up codeceptjs through Node's bare-specifier resolution. This change is the codeceptjs-side prep: - lib/host.js — sets globalThis.codeceptjs = { config, container, event, output, recorder, Helper } as a side-effect on import. Idempotent; matches the global @codeceptjs/helper already consults (\`global.codeceptjs || require('codeceptjs')\`). - lib/codecept.js + lib/plugin/browser.js — eager import of host.js so the registry is populated before the user's codecept.conf.js loads any companion package. Once @codeceptjs/configure and @codeceptjs/expect-helper ship versions that read from globalThis.codeceptjs (instead of \`import 'codeceptjs'\`), the CI symlink can be removed. Drop-in replacements for both packages are in scripts/upstream-patches/ — apply them upstream and bump versions in package.json. End-user projects are unaffected: globalThis.codeceptjs is set in normal runs too, so the new code path is the same code path everyone takes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent b2242de commit 8742f93

6 files changed

Lines changed: 111 additions & 0 deletions

File tree

lib/codecept.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import globalTimeoutListener from './listener/globalTimeout.js'
3232
import globalRetryListener from './listener/globalRetry.js'
3333
import exitListener from './listener/exit.js'
3434
import emptyRunListener from './listener/emptyRun.js'
35+
import './host.js'
3536

3637
/**
3738
* CodeceptJS runner

lib/host.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Helper from '@codeceptjs/helper'
2+
import Config from './config.js'
3+
import container from './container.js'
4+
import event from './event.js'
5+
import output from './output.js'
6+
import recorder from './recorder.js'
7+
8+
// Register an in-process handle on globalThis so companion packages
9+
// (@codeceptjs/helper, @codeceptjs/configure, @codeceptjs/expect-helper) can
10+
// reach back into the running codeceptjs instance without doing a top-level
11+
// `import 'codeceptjs'`. End-user projects don't need this — `codeceptjs` is
12+
// in their node_modules and the bare-specifier import resolves normally — but
13+
// inside this repo's CI the project IS the codeceptjs package and there is
14+
// no node_modules/codeceptjs to resolve to.
15+
if (!globalThis.codeceptjs) {
16+
globalThis.codeceptjs = { config: Config, container, event, output, recorder, Helper }
17+
}
18+
19+
export default globalThis.codeceptjs

lib/plugin/browser.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import output from '../output.js'
2+
import '../host.js'
23

34
/**
45
* Overrides browser helper config from the command line. Works for all browser helpers

scripts/upstream-patches/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Upstream patches
2+
3+
Two CodeceptJS companion packages — `@codeceptjs/configure` and
4+
`@codeceptjs/expect-helper` — currently do top-level `import 'codeceptjs'`.
5+
Inside the codeceptjs repo's own CI that import fails because the project
6+
**is** the codeceptjs package, so npm doesn't drop a `node_modules/codeceptjs/`
7+
to resolve to. We currently work around it with a one-line CI step:
8+
9+
```sh
10+
ln -sfn .. node_modules/codeceptjs
11+
```
12+
13+
These two patches replace the bare `import 'codeceptjs'` in each package with
14+
a lazy lookup against `globalThis.codeceptjs` — the same in-process registry
15+
that CodeceptJS sets up in `lib/host.js`. Once both patches ship as new
16+
betas of their packages and the version bumps land in `package.json`, the CI
17+
symlink can be removed.
18+
19+
Both patches are **purely additive for end-user projects**`globalThis.codeceptjs`
20+
is set in either case (the framework writes it during startup), so the
21+
runtime behavior in a normal user setup is unchanged.
22+
23+
## Apply
24+
25+
- `configure-codeceptjs.js``@codeceptjs/configure/codeceptjs.js`
26+
- `expect-helper-index.js.diff` → patch for `@codeceptjs/expect-helper/index.js`
27+
(the file is large; only the import block changes).
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Replacement for @codeceptjs/configure/codeceptjs.js
2+
//
3+
// CodeceptJS handle, looked up from the in-process registry the framework sets
4+
// on globalThis. Avoids a top-level `import 'codeceptjs'`, which fails to
5+
// resolve inside the codeceptjs repo's own CI (the project IS the codeceptjs
6+
// package). Each named export is a Proxy that forwards property access to the
7+
// live framework module at call time, so hooks register against the same
8+
// singletons the runner is using.
9+
10+
function lazy(name) {
11+
return new Proxy({}, {
12+
get(_, key) {
13+
const cjs = globalThis.codeceptjs
14+
if (!cjs) {
15+
throw new Error('CodeceptJS host not available — call configure functions from inside a codecept.conf.js / codecept run, or make sure CodeceptJS >= 4.0 is loaded first.')
16+
}
17+
const target = cjs[name]
18+
if (!target) {
19+
throw new Error(`CodeceptJS does not expose ${name} on the host registry.`)
20+
}
21+
const v = target[key]
22+
return typeof v === 'function' ? v.bind(target) : v
23+
},
24+
})
25+
}
26+
27+
const codeceptjs = new Proxy({}, {
28+
get(_, key) {
29+
const cjs = globalThis.codeceptjs
30+
if (!cjs) throw new Error('CodeceptJS host not available — call configure functions from inside a codecept.conf.js / codecept run.')
31+
return cjs[key]
32+
},
33+
})
34+
35+
export default codeceptjs
36+
export const config = lazy('config')
37+
export const container = lazy('container')
38+
export const event = lazy('event')
39+
export const output = lazy('output')
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--- a/index.js
2+
+++ b/index.js
3+
@@ -1,8 +1,21 @@
4+
import { use, expect, assert } from 'chai';
5+
import chaiExclude from 'chai-exclude';
6+
import chaiMatchPattern from 'chai-match-pattern';
7+
import chaiJsonSchemaAjv from 'chai-json-schema-ajv';
8+
-import { output } from 'codeceptjs';
9+
+
10+
+// CodeceptJS output module. Looked up lazily on each property access from the
11+
+// in-process handle the framework registers on globalThis — this avoids a
12+
+// top-level `import 'codeceptjs'`, which fails to resolve inside the codeceptjs
13+
+// repo's own CI (the project IS the codeceptjs package).
14+
+const output = new Proxy({}, {
15+
+ get(_, key) {
16+
+ const o = globalThis.codeceptjs?.output
17+
+ if (!o) throw new Error('CodeceptJS output module not available — make sure the test is running through the CodeceptJS runner.')
18+
+ const v = o[key]
19+
+ return typeof v === 'function' ? v.bind(o) : v
20+
+ }
21+
+})
22+
23+
use(chaiExclude);
24+
use(chaiMatchPattern);

0 commit comments

Comments
 (0)