Skip to content

Commit 5229108

Browse files
committed
Add support for default export in dynamicInclude method
1 parent 5c870cc commit 5229108

File tree

4 files changed

+81
-15
lines changed

4 files changed

+81
-15
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@noxify/gitlab-ci-builder": patch
3+
---
4+
5+
Add support for default export in `dynamicInclude`. Config modules can now use either `export default function(config: Config)` or the existing `export function extendConfig(config: Config)`. Default export is preferred when both are present.

src/config.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -332,9 +332,9 @@ export class Config {
332332
* Dynamically include other TypeScript configuration modules by glob.
333333
*
334334
* For every file matched by `globs` (resolved relative to `cwd`), this method
335-
* will `import()` the file and call its exported `extendConfig` function with
336-
* this `Config` instance. The imported file must export an `extendConfig`
337-
* function; otherwise an error is thrown.
335+
* will `import()` the file and call its exported function with this `Config`
336+
* instance. The imported file must export either a default export function or
337+
* a named `extendConfig` function; otherwise an error is thrown.
338338
*
339339
* @param cwd - Base directory for the globs (use `process.cwd()` normally).
340340
* @param globs - Glob patterns to match files (tinyglobby semantics).
@@ -350,16 +350,22 @@ export class Config {
350350
for (const file of files) {
351351
// eslint-disable-next-line no-console
352352
console.log(`Include file "${file}..."`)
353-
const exported = (await import(file)) as { extendConfig?: ExtendConfigFunction } | undefined
354-
if (!exported?.extendConfig) {
355-
throw new Error(`Please export a function extendConfig which returns a Config instance!`)
353+
const exported = (await import(file)) as
354+
| { default?: ExtendConfigFunction; extendConfig?: ExtendConfigFunction }
355+
| undefined
356+
357+
// Prefer default export, fallback to named extendConfig
358+
const extendFn = exported?.default ?? exported?.extendConfig
359+
360+
if (!extendFn) {
361+
throw new Error(`Please export a default function or a named "extendConfig" function!`)
356362
}
357363

358-
if (!(exported.extendConfig instanceof Function)) {
359-
throw new Error(`The exported extendConfig is not a function!`)
364+
if (!(extendFn instanceof Function)) {
365+
throw new Error(`The exported function is not a function!`)
360366
}
361367

362-
await exported.extendConfig(this)
368+
await extendFn(this)
363369
}
364370
}
365371
}

tests/config.edgecases.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ describe("Config - edge cases and branches", () => {
1414
const fixtures = new URL("./fixtures", import.meta.url).pathname
1515

1616
await expect(cfg.dynamicInclude(fixtures, ["no-export.mjs"])).rejects.toThrow(
17-
/Please export a function extendConfig/i,
17+
/Please export a default function or a named "extendConfig" function!/i,
1818
)
1919
})
2020

2121
it("throws if extendConfig is not a function", async () => {
2222
const fixtures = new URL("./fixtures", import.meta.url).pathname
2323

2424
await expect(cfg.dynamicInclude(fixtures, ["extend-not-fn.mjs"])).rejects.toThrow(
25-
/The exported extendConfig is not a function/i,
25+
/The exported function is not a function!/i,
2626
)
2727
})
2828

tests/dynamic-include.test.ts

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@ describe("Config - include", () => {
1919
it("should throw error when extendConfig is not exported", async () => {
2020
vi.mocked(globSync).mockReturnValue(["/fake/file.ts"])
2121

22-
// Mock the import to return an empty object
23-
vi.doMock("/fake/file.ts", () => ({ default: {} }))
22+
// Mock the import to return an object without default or extendConfig
23+
vi.doMock("/fake/file.ts", () => ({
24+
default: undefined,
25+
extendConfig: undefined,
26+
someOtherExport: "value",
27+
}))
2428

2529
await expect(config.dynamicInclude(process.cwd(), ["*.ts"])).rejects.toThrow(
26-
/Please export a function extendConfig|No "extendConfig" export/,
30+
/Please export a default function or a named "extendConfig" function!/,
2731
)
2832

2933
vi.doUnmock("/fake/file.ts")
@@ -34,12 +38,15 @@ describe("Config - include", () => {
3438

3539
// Mock dynamic import with extendConfig as non-function
3640
vi.doMock("/fake/file.ts", () => ({
41+
default: undefined,
3742
extendConfig: "not-a-function",
3843
}))
3944

4045
await expect(config.dynamicInclude(process.cwd(), ["*.ts"])).rejects.toThrow(
41-
"The exported extendConfig is not a function!",
46+
"The exported function is not a function!",
4247
)
48+
49+
vi.doUnmock("/fake/file.ts")
4350
})
4451

4552
it("should call extendConfig for each matched file", async () => {
@@ -51,10 +58,12 @@ describe("Config - include", () => {
5158

5259
// Mock dynamic imports
5360
vi.doMock("/fake/file1.ts", () => ({
61+
default: undefined,
5462
extendConfig: mockExtendConfig,
5563
}))
5664

5765
vi.doMock("/fake/file2.ts", () => ({
66+
default: undefined,
5867
extendConfig: mockExtendConfig,
5968
}))
6069

@@ -63,6 +72,9 @@ describe("Config - include", () => {
6372
// extendConfig should be called for each file
6473
expect(mockExtendConfig).toHaveBeenCalledTimes(2)
6574
expect(mockExtendConfig).toHaveBeenCalledWith(config)
75+
76+
vi.doUnmock("/fake/file1.ts")
77+
vi.doUnmock("/fake/file2.ts")
6678
})
6779

6880
it("should pass correct glob options", async () => {
@@ -94,16 +106,59 @@ describe("Config - include", () => {
94106
.mockReturnValueOnce(["/fake/config2.ts"])
95107

96108
vi.doMock("/fake/config1.ts", () => ({
109+
default: undefined,
97110
extendConfig: mockExtendConfig,
98111
}))
99112

100113
vi.doMock("/fake/config2.ts", () => ({
114+
default: undefined,
101115
extendConfig: mockExtendConfig,
102116
}))
103117

104118
await config.dynamicInclude(process.cwd(), ["configs/*.ts", "pipelines/*.ts"])
105119

106120
expect(globSync).toHaveBeenCalledTimes(2)
107121
expect(mockExtendConfig).toHaveBeenCalledTimes(2)
122+
123+
vi.doUnmock("/fake/config1.ts")
124+
vi.doUnmock("/fake/config2.ts")
125+
})
126+
127+
it("should support default export", async () => {
128+
const mockExtendConfig = vi.fn((cfg: Config) => {
129+
cfg.job("default-job", { script: ["echo default"] })
130+
})
131+
132+
vi.mocked(globSync).mockReturnValue(["/fake/default.ts"])
133+
134+
vi.doMock("/fake/default.ts", () => ({
135+
default: mockExtendConfig,
136+
}))
137+
138+
await config.dynamicInclude(process.cwd(), ["*.ts"])
139+
140+
expect(mockExtendConfig).toHaveBeenCalledTimes(1)
141+
expect(mockExtendConfig).toHaveBeenCalledWith(config)
142+
143+
vi.doUnmock("/fake/default.ts")
144+
})
145+
146+
it("should prefer default export over named extendConfig", async () => {
147+
const mockDefault = vi.fn()
148+
const mockNamed = vi.fn()
149+
150+
vi.mocked(globSync).mockReturnValue(["/fake/both.ts"])
151+
152+
vi.doMock("/fake/both.ts", () => ({
153+
default: mockDefault,
154+
extendConfig: mockNamed,
155+
}))
156+
157+
await config.dynamicInclude(process.cwd(), ["*.ts"])
158+
159+
expect(mockDefault).toHaveBeenCalledTimes(1)
160+
expect(mockNamed).not.toHaveBeenCalled()
161+
162+
vi.doUnmock("/fake/both.ts")
108163
})
109164
})

0 commit comments

Comments
 (0)