@fluojs/testing 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +4 -2
- package/README.md +4 -2
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +119 -6
- package/dist/portability/http-adapter-portability.d.ts.map +1 -1
- package/dist/portability/http-adapter-portability.js +49 -61
- package/dist/vitest/tooling.d.ts +22 -0
- package/dist/vitest/tooling.d.ts.map +1 -0
- package/dist/vitest/tooling.js +90 -0
- package/package.json +16 -12
package/README.ko.md
CHANGED
|
@@ -79,6 +79,8 @@ const service = await module.resolve(UserService);
|
|
|
79
79
|
|
|
80
80
|
Testing builder는 route-pipeline 테스트에서 cross-cutting behavior를 교체할 수 있도록 `overrideGuard(...)`, `overrideInterceptor(...)`, `overrideFilter(...)`도 지원합니다.
|
|
81
81
|
|
|
82
|
+
`compile()`은 lifecycle hook이 있는 singleton provider에 대해 production module bootstrap과 같은 의미를 따릅니다. effective provider graph를 해석하고, testing module을 반환하기 전에 provider 순서대로 각 instance의 `onModuleInit()`을 실행한 뒤 `onApplicationBootstrap()`을 실행합니다. `get()`은 synchronous singleton 및 multi-provider 경로에서도 DI ownership 의미를 보존하므로, 반복 sync read는 같은 singleton contribution을 재사용하고 `module.container.dispose()`가 해당 instance를 계속 정리할 수 있습니다.
|
|
83
|
+
|
|
82
84
|
### `overrideModule()` 사용 시 모듈 identity 보존
|
|
83
85
|
|
|
84
86
|
`createTestingModule({ rootModule })`에는 명시적인 루트 모듈이 필요합니다. 그래야 테스트가 프로덕션 bootstrap과 같은 모듈 그래프 형태를 컴파일합니다. `overrideModule(source, replacement)`로 import된 모듈을 교체해도, 컴파일된 testing module은 provider 해석에 replacement import를 사용하면서 원래 `rootModule`과 컴파일된 `modules[].type` identity를 보존합니다. 따라서 diagnostics, graph assertion, module introspection 헬퍼는 테스트 전용 synthetic wrapper 클래스가 아니라 사용자가 작성한 애플리케이션 모듈 클래스에 계속 연결됩니다.
|
|
@@ -164,11 +166,11 @@ fluo는 테스트가 명시적인 `rootModule`을 이름으로 지정해야 한
|
|
|
164
166
|
## 공개 API
|
|
165
167
|
|
|
166
168
|
- **루트 패키지**: `createTestingModule(...)`, `createTestApp(...)`, 모듈 introspection 헬퍼, 공용 테스트 타입
|
|
167
|
-
- **서브패스**: `@fluojs/testing/app`, `@fluojs/testing/module`, `@fluojs/testing/http`, `@fluojs/testing/mock`, `@fluojs/testing/types`, `@fluojs/testing/vitest`
|
|
169
|
+
- **서브패스**: `@fluojs/testing/app`, `@fluojs/testing/module`, `@fluojs/testing/http`, `@fluojs/testing/mock`, `@fluojs/testing/types`, `@fluojs/testing/vitest`, `@fluojs/testing/vitest/tooling`
|
|
168
170
|
- **Mock 서브패스**: `@fluojs/testing/mock`
|
|
169
171
|
- **HTTP 헬퍼**: `@fluojs/testing/http`
|
|
170
172
|
- **하니스 서브패스**: `platform-conformance`, `http-adapter-portability`, `web-runtime-adapter-portability`, `fetch-style-websocket-conformance`
|
|
171
|
-
- **도구 지원**: `@fluojs/testing/vitest
|
|
173
|
+
- **도구 지원**: `@fluojs/testing/vitest`의 `fluoBabelDecoratorsPlugin()` 및 `@fluojs/testing/vitest/tooling`의 Vitest workspace config helper (`vitest`와 `@babel/core`를 함께 요구)
|
|
172
174
|
|
|
173
175
|
## 관련 패키지
|
|
174
176
|
|
package/README.md
CHANGED
|
@@ -77,6 +77,8 @@ const service = await module.resolve(UserService);
|
|
|
77
77
|
|
|
78
78
|
The testing builder also supports `overrideGuard(...)`, `overrideInterceptor(...)`, and `overrideFilter(...)` for route-pipeline tests that need to replace cross-cutting behavior.
|
|
79
79
|
|
|
80
|
+
`compile()` follows production module-bootstrap semantics for lifecycle-bearing singleton providers: it resolves the effective provider graph, runs `onModuleInit()` for each resolved instance, then runs `onApplicationBootstrap()` in the same provider order before the testing module is returned. `get()` keeps DI ownership semantics for synchronous singleton and multi-provider paths, so repeated sync reads reuse the same singleton contributions and `module.container.dispose()` can still clean them up.
|
|
81
|
+
|
|
80
82
|
### Preserve module identity with `overrideModule()`
|
|
81
83
|
|
|
82
84
|
`createTestingModule({ rootModule })` requires an explicit root module so tests compile the same module graph shape that production bootstrap uses. When `overrideModule(source, replacement)` swaps imported modules, the compiled testing module preserves the original `rootModule` and compiled `modules[].type` identities while using the replacement imports for provider resolution. This keeps diagnostics, graph assertions, and module-introspection helpers tied to the application module classes you authored instead of synthetic test-only wrapper classes.
|
|
@@ -162,11 +164,11 @@ fluo differs from NestJS by requiring tests to name an explicit `rootModule`. Th
|
|
|
162
164
|
## Public API
|
|
163
165
|
|
|
164
166
|
- **Root package**: `createTestingModule(...)`, `createTestApp(...)`, module introspection helpers, shared testing types
|
|
165
|
-
- **Subpaths**: `@fluojs/testing/app`, `@fluojs/testing/module`, `@fluojs/testing/http`, `@fluojs/testing/mock`, `@fluojs/testing/types`, `@fluojs/testing/vitest`
|
|
167
|
+
- **Subpaths**: `@fluojs/testing/app`, `@fluojs/testing/module`, `@fluojs/testing/http`, `@fluojs/testing/mock`, `@fluojs/testing/types`, `@fluojs/testing/vitest`, `@fluojs/testing/vitest/tooling`
|
|
166
168
|
- **Mock subpath**: `@fluojs/testing/mock`
|
|
167
169
|
- **HTTP helpers**: `@fluojs/testing/http`
|
|
168
170
|
- **Harness subpaths**: `platform-conformance`, `http-adapter-portability`, `web-runtime-adapter-portability`, `fetch-style-websocket-conformance`
|
|
169
|
-
- **Tooling**: `@fluojs/testing/vitest` with `fluoBabelDecoratorsPlugin()` (requires `vitest` and `@babel/core` in the consuming workspace)
|
|
171
|
+
- **Tooling**: `@fluojs/testing/vitest` with `fluoBabelDecoratorsPlugin()` and `@fluojs/testing/vitest/tooling` with Vitest workspace config helpers (requires `vitest` and `@babel/core` in the consuming workspace)
|
|
170
172
|
|
|
171
173
|
## Related Packages
|
|
172
174
|
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,SAAS,EAId,KAAK,QAAQ,
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,SAAS,EAId,KAAK,QAAQ,EAEd,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAqC,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMrF,OAAO,KAAK,EAA2B,oBAAoB,EAAE,oBAAoB,EAAoB,MAAM,YAAY,CAAC;AAExH;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,UAAU,GAAG,QAAQ,EAAE,CAQzE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,EAAE,CAQ5E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,EAAE,CAQzE;AAyuBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,oBAAoB,CAEvF;AAED;;GAEG;AACH,eAAO,MAAM,IAAI;;CAEhB,CAAC"}
|
package/dist/module.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getModuleMetadata } from '@fluojs/core';
|
|
2
|
-
import { isForwardRef, isOptionalToken } from '@fluojs/di';
|
|
2
|
+
import { isForwardRef, isOptionalToken, Scope } from '@fluojs/di';
|
|
3
3
|
import { bootstrapModule, defineModule } from '@fluojs/runtime';
|
|
4
4
|
import { createDispatcher, createHandlerMapping } from '@fluojs/http';
|
|
5
5
|
import { createTestRequestContextMiddleware, makeRequest } from './http.js';
|
|
@@ -97,7 +97,7 @@ function isContainerIntrospection(value) {
|
|
|
97
97
|
const candidate = value;
|
|
98
98
|
const parentValid = candidate.parent === undefined || isContainerIntrospection(candidate.parent);
|
|
99
99
|
const requestScopeValid = candidate.requestScopeEnabled === undefined || typeof candidate.requestScopeEnabled === 'boolean';
|
|
100
|
-
return candidate.registrations instanceof Map && candidate.multiRegistrations instanceof Map && candidate.singletonCache instanceof Map && parentValid && requestScopeValid;
|
|
100
|
+
return candidate.registrations instanceof Map && candidate.multiRegistrations instanceof Map && candidate.multiSingletonCache instanceof Map && candidate.singletonCache instanceof Map && parentValid && requestScopeValid;
|
|
101
101
|
}
|
|
102
102
|
function toContainerIntrospection(container) {
|
|
103
103
|
if (!isContainerIntrospection(container)) {
|
|
@@ -108,6 +108,77 @@ function toContainerIntrospection(container) {
|
|
|
108
108
|
function isPromiseLike(value) {
|
|
109
109
|
return (typeof value === 'object' || typeof value === 'function') && value !== null && typeof value.then === 'function';
|
|
110
110
|
}
|
|
111
|
+
function hasLifecycleHook(value, hookName) {
|
|
112
|
+
return (typeof value === 'object' || typeof value === 'function') && value !== null && typeof value[hookName] === 'function';
|
|
113
|
+
}
|
|
114
|
+
function hasAnyBootstrapLifecycleHook(value) {
|
|
115
|
+
return hasLifecycleHook(value, 'onModuleInit') || hasLifecycleHook(value, 'onApplicationBootstrap');
|
|
116
|
+
}
|
|
117
|
+
function providerToken(provider) {
|
|
118
|
+
return isProviderDescriptor(provider) ? provider.provide : provider;
|
|
119
|
+
}
|
|
120
|
+
function effectiveProvidersForToken(introspection, token) {
|
|
121
|
+
const multiProviders = collectMultiProviders(introspection, token);
|
|
122
|
+
if (multiProviders.length > 0) {
|
|
123
|
+
return multiProviders;
|
|
124
|
+
}
|
|
125
|
+
const provider = lookupProvider(introspection, token);
|
|
126
|
+
return provider ? [provider] : [];
|
|
127
|
+
}
|
|
128
|
+
function isSingletonLifecycleProvider(provider) {
|
|
129
|
+
if (provider.multi === true) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
if (provider.scope !== Scope.DEFAULT) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
return provider.type === 'class' || provider.type === 'value' && hasAnyBootstrapLifecycleHook(provider.useValue);
|
|
136
|
+
}
|
|
137
|
+
async function resolveTestingLifecycleInstances(bootstrapped, overrides = []) {
|
|
138
|
+
const lifecycleProviders = [...bootstrapped.effectiveProviders.runtimeProviders, ...bootstrapped.effectiveProviders.moduleProviders, ...overrides];
|
|
139
|
+
const instances = [];
|
|
140
|
+
const seenProviders = new Set();
|
|
141
|
+
const introspection = toContainerIntrospection(bootstrapped.container);
|
|
142
|
+
for (const provider of lifecycleProviders) {
|
|
143
|
+
const token = providerToken(provider);
|
|
144
|
+
const effectiveProviders = effectiveProvidersForToken(introspection, token);
|
|
145
|
+
for (const effectiveProvider of effectiveProviders) {
|
|
146
|
+
if (seenProviders.has(effectiveProvider)) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
seenProviders.add(effectiveProvider);
|
|
150
|
+
if (!isSingletonLifecycleProvider(effectiveProvider)) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (effectiveProvider.type === 'value') {
|
|
154
|
+
instances.push(effectiveProvider.useValue);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
instances.push(await bootstrapped.container.resolve(token));
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (error instanceof Error && error.message.includes('Request-scoped provider')) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return instances;
|
|
168
|
+
}
|
|
169
|
+
async function runTestingBootstrapLifecycle(bootstrapped, overrides = []) {
|
|
170
|
+
const instances = await resolveTestingLifecycleInstances(bootstrapped, overrides);
|
|
171
|
+
for (const instance of instances) {
|
|
172
|
+
if (hasLifecycleHook(instance, 'onModuleInit')) {
|
|
173
|
+
await instance.onModuleInit();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
for (const instance of instances) {
|
|
177
|
+
if (hasLifecycleHook(instance, 'onApplicationBootstrap')) {
|
|
178
|
+
await instance.onApplicationBootstrap();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
111
182
|
function rootContainerIntrospection(target) {
|
|
112
183
|
return target.parent ? rootContainerIntrospection(target.parent) : target;
|
|
113
184
|
}
|
|
@@ -190,6 +261,20 @@ function canPromoteCachedSingleton(state, token) {
|
|
|
190
261
|
const provider = lookupProvider(state.introspection, token);
|
|
191
262
|
return provider !== undefined && provider.scope !== 'request' && providerGraphIsSyncResolvable(state, token);
|
|
192
263
|
}
|
|
264
|
+
function providerGraphForProviderIsSyncResolvable(state, provider, visited = new Set()) {
|
|
265
|
+
if (provider.type === 'factory') {
|
|
266
|
+
return state.factoryResolutionKinds.get(provider) === 'sync';
|
|
267
|
+
}
|
|
268
|
+
if (provider.type === 'existing') {
|
|
269
|
+
return provider.useExisting !== undefined && providerGraphIsSyncResolvable(state, provider.useExisting, visited);
|
|
270
|
+
}
|
|
271
|
+
return provider.inject.every(entry => {
|
|
272
|
+
if (isOptionalToken(entry) && !hasToken(state, entry.token)) {
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
return providerGraphIsSyncResolvable(state, dependencyToken(entry), visited);
|
|
276
|
+
});
|
|
277
|
+
}
|
|
193
278
|
function resolveSyncDependency(entry, state) {
|
|
194
279
|
if (isOptionalToken(entry)) {
|
|
195
280
|
if (!hasToken(state, entry.token)) {
|
|
@@ -259,6 +344,24 @@ function resolveSyncProvider(provider, state) {
|
|
|
259
344
|
state.singletonCache.set(provider.provide, Promise.resolve(instance));
|
|
260
345
|
return instance;
|
|
261
346
|
}
|
|
347
|
+
function resolveSyncMultiProvider(provider, state) {
|
|
348
|
+
if (provider.scope === 'request' && !state.introspection.requestScopeEnabled) {
|
|
349
|
+
throw new Error(`Request-scoped provider ${String(provider.provide)} cannot be resolved outside request scope.`);
|
|
350
|
+
}
|
|
351
|
+
if (provider.scope === 'transient') {
|
|
352
|
+
return instantiateSyncProvider(provider, state);
|
|
353
|
+
}
|
|
354
|
+
if (state.syncMultiSingletonValues.has(provider)) {
|
|
355
|
+
return state.syncMultiSingletonValues.get(provider);
|
|
356
|
+
}
|
|
357
|
+
if (state.multiSingletonCache.has(provider)) {
|
|
358
|
+
throw new Error(`Token ${String(provider.provide)} was already resolved asynchronously. Use resolve() instead of get() for this provider.`);
|
|
359
|
+
}
|
|
360
|
+
const instance = instantiateSyncProvider(provider, state);
|
|
361
|
+
state.syncMultiSingletonValues.set(provider, instance);
|
|
362
|
+
state.multiSingletonCache.set(provider, Promise.resolve(instance));
|
|
363
|
+
return instance;
|
|
364
|
+
}
|
|
262
365
|
function resolveSyncToken(token, state) {
|
|
263
366
|
if (state.resolutionChain.has(token)) {
|
|
264
367
|
throw new Error(`Circular dependency detected while resolving token ${String(token)} via get().`);
|
|
@@ -267,7 +370,7 @@ function resolveSyncToken(token, state) {
|
|
|
267
370
|
try {
|
|
268
371
|
const multiProviders = collectMultiProviders(state.introspection, token);
|
|
269
372
|
if (multiProviders.length > 0) {
|
|
270
|
-
return multiProviders.map(provider =>
|
|
373
|
+
return multiProviders.map(provider => resolveSyncMultiProvider(provider, state));
|
|
271
374
|
}
|
|
272
375
|
const provider = lookupProvider(state.introspection, token);
|
|
273
376
|
if (!provider) {
|
|
@@ -285,8 +388,10 @@ function createSyncResolver(container) {
|
|
|
285
388
|
const state = {
|
|
286
389
|
factoryResolutionKinds,
|
|
287
390
|
introspection,
|
|
391
|
+
multiSingletonCache: rootContainerIntrospection(introspection).multiSingletonCache,
|
|
288
392
|
resolutionChain: new Set(),
|
|
289
393
|
singletonCache: rootContainerIntrospection(introspection).singletonCache,
|
|
394
|
+
syncMultiSingletonValues: new Map(),
|
|
290
395
|
syncSingletonValues: new Map()
|
|
291
396
|
};
|
|
292
397
|
return {
|
|
@@ -298,6 +403,12 @@ function createSyncResolver(container) {
|
|
|
298
403
|
}
|
|
299
404
|
state.syncSingletonValues.set(token, await promise);
|
|
300
405
|
}
|
|
406
|
+
for (const [provider, promise] of state.multiSingletonCache) {
|
|
407
|
+
if (!providerGraphForProviderIsSyncResolvable(state, provider)) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
state.syncMultiSingletonValues.set(provider, await promise);
|
|
411
|
+
}
|
|
301
412
|
}
|
|
302
413
|
};
|
|
303
414
|
}
|
|
@@ -392,7 +503,10 @@ class DefaultTestingModuleBuilder {
|
|
|
392
503
|
}
|
|
393
504
|
async compile() {
|
|
394
505
|
const bootstrapped = this.bootstrapTestingModule();
|
|
395
|
-
|
|
506
|
+
const syncResolver = createSyncResolver(bootstrapped.container);
|
|
507
|
+
await runTestingBootstrapLifecycle(bootstrapped, this.overrides);
|
|
508
|
+
await syncResolver.syncFromContainer();
|
|
509
|
+
return this.createTestingModuleRef(bootstrapped, syncResolver);
|
|
396
510
|
}
|
|
397
511
|
bootstrapTestingModule() {
|
|
398
512
|
const bootstrapped = this.bootstrapWithPatchedModuleImports();
|
|
@@ -411,9 +525,8 @@ class DefaultTestingModuleBuilder {
|
|
|
411
525
|
this.restorePatchedModuleImports();
|
|
412
526
|
}
|
|
413
527
|
}
|
|
414
|
-
createTestingModuleRef(bootstrapped) {
|
|
528
|
+
createTestingModuleRef(bootstrapped, syncResolver) {
|
|
415
529
|
const dispatcher = createTestingDispatcher(bootstrapped);
|
|
416
|
-
const syncResolver = createSyncResolver(bootstrapped.container);
|
|
417
530
|
return {
|
|
418
531
|
...bootstrapped,
|
|
419
532
|
has: token => bootstrapped.container.has(token),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-adapter-portability.d.ts","sourceRoot":"","sources":["../../src/portability/http-adapter-portability.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http-adapter-portability.d.ts","sourceRoot":"","sources":["../../src/portability/http-adapter-portability.ts"],"names":[],"mappings":"AAGA,OAAO,EAAwC,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE3G,OAAO,QAAQ,cAAc,CAAC;IAC5B,UAAU,gBAAgB;QACxB,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;QACvB,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB;CACF;AAED,KAAK,OAAO,GAAG;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB,CAAC;AAUF;;;;;;GAMG;AACH,MAAM,WAAW,oCAAoC,CACnD,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO;IAE9B;;;;;;OAMG;IACH,SAAS,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjF;;OAEG;IACH,2BAA2B,CAAC,EAAE,MAAM,CAAC;IAErC;;OAEG;IACH,2BAA2B,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElE;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;;;;;OAMG;IACH,GAAG,EAAE,CAAC,UAAU,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtE;AAgHD;;;;;;;GAOG;AACH,qBAAa,6BAA6B,CACxC,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO;IAOlB,OAAO,CAAC,QAAQ,CAAC,OAAO;IALpC;;;;OAIG;gBAC0B,OAAO,EAAE,oCAAoC,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC;IAEhH;;;OAGG;IACG,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IAiDrD,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IAqErD,wDAAwD,IAAI,OAAO,CAAC,IAAI,CAAC;IA8CzE,iCAAiC,IAAI,OAAO,CAAC,IAAI,CAAC;IAsDlD,8CAA8C,IAAI,OAAO,CAAC,IAAI,CAAC;IAqD/D,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC;IAkDjD;;;OAGG;IACG,mCAAmC,IAAI,OAAO,CAAC,IAAI,CAAC;IAyDpD,wCAAwC,IAAI,OAAO,CAAC,IAAI,CAAC;IAoDzD,4BAA4B,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAoDjF,8CAA8C,IAAI,OAAO,CAAC,IAAI,CAAC;CA2CtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,mCAAmC,CACjD,iBAAiB,SAAS,MAAM,EAChC,WAAW,SAAS,MAAM,EAC1B,IAAI,SAAS,OAAO,GAAG,OAAO,EAE9B,OAAO,EAAE,oCAAoC,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC,GAClF,6BAA6B,CAAC,iBAAiB,EAAE,WAAW,EAAE,IAAI,CAAC,CAErE"}
|
|
@@ -3,7 +3,6 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
|
|
|
3
3
|
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
4
4
|
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
|
|
5
5
|
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
|
|
6
|
-
import { createServer } from 'node:net';
|
|
7
6
|
import { request as httpsRequest } from 'node:https';
|
|
8
7
|
import { Controller, Get, Post, SseResponse } from '@fluojs/http';
|
|
9
8
|
import { defineModule } from '@fluojs/runtime';
|
|
@@ -16,25 +15,19 @@ import { defineModule } from '@fluojs/runtime';
|
|
|
16
15
|
* @template TApp - Type for the application instance.
|
|
17
16
|
*/
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
return
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
resolve(address.port);
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
});
|
|
18
|
+
function hasListenTarget(value) {
|
|
19
|
+
return typeof value === 'object' && value !== null && 'getListenTarget' in value && typeof value.getListenTarget === 'function';
|
|
20
|
+
}
|
|
21
|
+
function resolveListeningUrl(app, adapterName) {
|
|
22
|
+
const adapter = Reflect.get(app, 'adapter');
|
|
23
|
+
if (!hasListenTarget(adapter)) {
|
|
24
|
+
throw new Error(`${adapterName} adapter portability harness cannot resolve the listener URL after binding port 0.`);
|
|
25
|
+
}
|
|
26
|
+
const target = adapter.getListenTarget();
|
|
27
|
+
if (typeof target.url !== 'string' || target.url.length === 0) {
|
|
28
|
+
throw new Error(`${adapterName} adapter portability harness resolved an empty listener URL after binding port 0.`);
|
|
29
|
+
}
|
|
30
|
+
return target.url;
|
|
38
31
|
}
|
|
39
32
|
async function requestHttps(url) {
|
|
40
33
|
return await new Promise((resolve, reject) => {
|
|
@@ -78,6 +71,11 @@ async function runWithCleanup(app, adapterName, assertion) {
|
|
|
78
71
|
throw assertionError;
|
|
79
72
|
}
|
|
80
73
|
}
|
|
74
|
+
async function runWithListeningUrlCleanup(app, adapterName, assertion) {
|
|
75
|
+
await runWithCleanup(app, adapterName, async () => {
|
|
76
|
+
await assertion(resolveListeningUrl(app, adapterName));
|
|
77
|
+
});
|
|
78
|
+
}
|
|
81
79
|
async function prepareAndListenWithCleanup(app, adapterName, prepare) {
|
|
82
80
|
try {
|
|
83
81
|
await prepare?.();
|
|
@@ -138,14 +136,13 @@ export class HttpAdapterPortabilityHarness {
|
|
|
138
136
|
defineModule(AppModule, {
|
|
139
137
|
controllers: [_CookieController]
|
|
140
138
|
});
|
|
141
|
-
const port = await findAvailablePort();
|
|
142
139
|
const app = await this.options.bootstrap(AppModule, {
|
|
143
140
|
cors: false,
|
|
144
|
-
port
|
|
141
|
+
port: 0
|
|
145
142
|
});
|
|
146
143
|
await prepareAndListenWithCleanup(app, this.options.name);
|
|
147
|
-
await
|
|
148
|
-
const response = await fetch(
|
|
144
|
+
await runWithListeningUrlCleanup(app, this.options.name, async baseUrl => {
|
|
145
|
+
const response = await fetch(`${baseUrl}/cookies`, {
|
|
149
146
|
headers: {
|
|
150
147
|
cookie: 'good=hello%20world; bad=%E0%A4%A'
|
|
151
148
|
}
|
|
@@ -192,15 +189,14 @@ export class HttpAdapterPortabilityHarness {
|
|
|
192
189
|
defineModule(AppModule, {
|
|
193
190
|
controllers: [_WebhookController]
|
|
194
191
|
});
|
|
195
|
-
const port = await findAvailablePort();
|
|
196
192
|
const app = await this.options.bootstrap(AppModule, {
|
|
197
193
|
cors: false,
|
|
198
|
-
port,
|
|
194
|
+
port: 0,
|
|
199
195
|
rawBody: true
|
|
200
196
|
});
|
|
201
197
|
await prepareAndListenWithCleanup(app, this.options.name);
|
|
202
|
-
await
|
|
203
|
-
const [jsonResponse, textResponse] = await Promise.all([fetch(
|
|
198
|
+
await runWithListeningUrlCleanup(app, this.options.name, async baseUrl => {
|
|
199
|
+
const [jsonResponse, textResponse] = await Promise.all([fetch(`${baseUrl}/webhooks/json`, {
|
|
204
200
|
body: JSON.stringify({
|
|
205
201
|
provider: 'stripe'
|
|
206
202
|
}),
|
|
@@ -208,7 +204,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
208
204
|
'content-type': 'application/json'
|
|
209
205
|
},
|
|
210
206
|
method: 'POST'
|
|
211
|
-
}), fetch(
|
|
207
|
+
}), fetch(`${baseUrl}/webhooks/text`, {
|
|
212
208
|
body: 'ping=1',
|
|
213
209
|
headers: {
|
|
214
210
|
'content-type': 'text/plain; charset=utf-8'
|
|
@@ -261,19 +257,18 @@ export class HttpAdapterPortabilityHarness {
|
|
|
261
257
|
defineModule(AppModule, {
|
|
262
258
|
controllers: [_WebhookController2]
|
|
263
259
|
});
|
|
264
|
-
const port = await findAvailablePort();
|
|
265
260
|
const app = await this.options.bootstrap(AppModule, {
|
|
266
261
|
cors: false,
|
|
267
|
-
port,
|
|
262
|
+
port: 0,
|
|
268
263
|
rawBody: true
|
|
269
264
|
});
|
|
270
265
|
await prepareAndListenWithCleanup(app, this.options.name, async () => {
|
|
271
266
|
await this.options.prepareExactRawBodyByteTest?.(app);
|
|
272
267
|
});
|
|
273
|
-
await
|
|
268
|
+
await runWithListeningUrlCleanup(app, this.options.name, async baseUrl => {
|
|
274
269
|
const payload = Uint8Array.from([0xe9, 0x41]);
|
|
275
270
|
const contentType = this.options.exactRawBodyByteContentType ?? 'text/plain; charset=latin1';
|
|
276
|
-
const response = await fetch(
|
|
271
|
+
const response = await fetch(`${baseUrl}/webhooks/bytes`, {
|
|
277
272
|
body: payload,
|
|
278
273
|
headers: {
|
|
279
274
|
'content-type': contentType
|
|
@@ -319,20 +314,19 @@ export class HttpAdapterPortabilityHarness {
|
|
|
319
314
|
defineModule(AppModule, {
|
|
320
315
|
controllers: [_UploadController]
|
|
321
316
|
});
|
|
322
|
-
const port = await findAvailablePort();
|
|
323
317
|
const app = await this.options.bootstrap(AppModule, {
|
|
324
318
|
cors: false,
|
|
325
|
-
port,
|
|
319
|
+
port: 0,
|
|
326
320
|
rawBody: true
|
|
327
321
|
});
|
|
328
322
|
await prepareAndListenWithCleanup(app, this.options.name);
|
|
329
|
-
await
|
|
323
|
+
await runWithListeningUrlCleanup(app, this.options.name, async baseUrl => {
|
|
330
324
|
const form = new FormData();
|
|
331
325
|
form.set('name', 'Ada');
|
|
332
326
|
form.set('payload', new Blob(['hello'], {
|
|
333
327
|
type: 'text/plain'
|
|
334
328
|
}), 'payload.txt');
|
|
335
|
-
const response = await fetch(
|
|
329
|
+
const response = await fetch(`${baseUrl}/uploads`, {
|
|
336
330
|
body: form,
|
|
337
331
|
method: 'POST'
|
|
338
332
|
});
|
|
@@ -378,23 +372,22 @@ export class HttpAdapterPortabilityHarness {
|
|
|
378
372
|
defineModule(AppModule, {
|
|
379
373
|
controllers: [_UploadController2]
|
|
380
374
|
});
|
|
381
|
-
const port = await findAvailablePort();
|
|
382
375
|
const app = await this.options.bootstrap(AppModule, {
|
|
383
376
|
cors: false,
|
|
384
377
|
maxBodySize: 8,
|
|
385
378
|
multipart: {
|
|
386
379
|
maxFileSize: 1024
|
|
387
380
|
},
|
|
388
|
-
port
|
|
381
|
+
port: 0
|
|
389
382
|
});
|
|
390
383
|
await prepareAndListenWithCleanup(app, this.options.name);
|
|
391
|
-
await
|
|
384
|
+
await runWithListeningUrlCleanup(app, this.options.name, async baseUrl => {
|
|
392
385
|
const form = new FormData();
|
|
393
386
|
form.set('name', 'Ada');
|
|
394
387
|
form.set('payload', new Blob(['12345678'], {
|
|
395
388
|
type: 'text/plain'
|
|
396
389
|
}), 'payload.txt');
|
|
397
|
-
const response = await fetch(
|
|
390
|
+
const response = await fetch(`${baseUrl}/uploads`, {
|
|
398
391
|
body: form,
|
|
399
392
|
method: 'POST'
|
|
400
393
|
});
|
|
@@ -442,14 +435,13 @@ export class HttpAdapterPortabilityHarness {
|
|
|
442
435
|
defineModule(AppModule, {
|
|
443
436
|
controllers: [_EventsController]
|
|
444
437
|
});
|
|
445
|
-
const port = await findAvailablePort();
|
|
446
438
|
const app = await this.options.bootstrap(AppModule, {
|
|
447
439
|
cors: false,
|
|
448
|
-
port
|
|
440
|
+
port: 0
|
|
449
441
|
});
|
|
450
442
|
await prepareAndListenWithCleanup(app, this.options.name);
|
|
451
|
-
await
|
|
452
|
-
const response = await fetch(
|
|
443
|
+
await runWithListeningUrlCleanup(app, this.options.name, async baseUrl => {
|
|
444
|
+
const response = await fetch(`${baseUrl}/events`, {
|
|
453
445
|
headers: {
|
|
454
446
|
accept: 'text/event-stream'
|
|
455
447
|
}
|
|
@@ -510,14 +502,13 @@ export class HttpAdapterPortabilityHarness {
|
|
|
510
502
|
defineModule(AppModule, {
|
|
511
503
|
controllers: [_EventsController2]
|
|
512
504
|
});
|
|
513
|
-
const port = await findAvailablePort();
|
|
514
505
|
const app = await this.options.bootstrap(AppModule, {
|
|
515
506
|
cors: false,
|
|
516
|
-
port
|
|
507
|
+
port: 0
|
|
517
508
|
});
|
|
518
509
|
await prepareAndListenWithCleanup(app, this.options.name);
|
|
519
|
-
await
|
|
520
|
-
const response = await fetch(
|
|
510
|
+
await runWithListeningUrlCleanup(app, this.options.name, async baseUrl => {
|
|
511
|
+
const response = await fetch(`${baseUrl}/events`, {
|
|
521
512
|
headers: {
|
|
522
513
|
accept: 'text/event-stream'
|
|
523
514
|
}
|
|
@@ -566,15 +557,14 @@ export class HttpAdapterPortabilityHarness {
|
|
|
566
557
|
defineModule(AppModule, {
|
|
567
558
|
controllers: [_HealthController]
|
|
568
559
|
});
|
|
569
|
-
const port = await findAvailablePort();
|
|
570
560
|
const app = await this.options.run(AppModule, {
|
|
571
561
|
cors: false,
|
|
572
562
|
host: '127.0.0.1',
|
|
573
563
|
logger,
|
|
574
|
-
port
|
|
564
|
+
port: 0
|
|
575
565
|
});
|
|
576
|
-
await
|
|
577
|
-
const response = await fetch(
|
|
566
|
+
await runWithListeningUrlCleanup(app, this.options.name, async baseUrl => {
|
|
567
|
+
const response = await fetch(`${baseUrl}/health`);
|
|
578
568
|
if (response.status !== 200) {
|
|
579
569
|
throw new Error(`${this.options.name} adapter changed host-bound health response semantics.`);
|
|
580
570
|
}
|
|
@@ -584,7 +574,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
584
574
|
})) {
|
|
585
575
|
throw new Error(`${this.options.name} adapter changed host-bound response payload.`);
|
|
586
576
|
}
|
|
587
|
-
const expectedLog = `log:FluoFactory:Listening on
|
|
577
|
+
const expectedLog = `log:FluoFactory:Listening on ${baseUrl}`;
|
|
588
578
|
if (!loggerEvents.includes(expectedLog)) {
|
|
589
579
|
throw new Error(`${this.options.name} adapter changed startup host logging.`);
|
|
590
580
|
}
|
|
@@ -627,16 +617,15 @@ export class HttpAdapterPortabilityHarness {
|
|
|
627
617
|
defineModule(AppModule, {
|
|
628
618
|
controllers: [_HealthController2]
|
|
629
619
|
});
|
|
630
|
-
const port = await findAvailablePort();
|
|
631
620
|
const app = await this.options.run(AppModule, {
|
|
632
621
|
cors: false,
|
|
633
622
|
host: '127.0.0.1',
|
|
634
623
|
https,
|
|
635
624
|
logger,
|
|
636
|
-
port
|
|
625
|
+
port: 0
|
|
637
626
|
});
|
|
638
|
-
await
|
|
639
|
-
const response = await requestHttps(
|
|
627
|
+
await runWithListeningUrlCleanup(app, this.options.name, async baseUrl => {
|
|
628
|
+
const response = await requestHttps(`${baseUrl}/health`);
|
|
640
629
|
if (response.statusCode !== 200) {
|
|
641
630
|
throw new Error(`${this.options.name} adapter changed HTTPS response status semantics.`);
|
|
642
631
|
}
|
|
@@ -645,7 +634,7 @@ export class HttpAdapterPortabilityHarness {
|
|
|
645
634
|
})) {
|
|
646
635
|
throw new Error(`${this.options.name} adapter changed HTTPS response payload semantics.`);
|
|
647
636
|
}
|
|
648
|
-
const expectedLog = `log:FluoFactory:Listening on
|
|
637
|
+
const expectedLog = `log:FluoFactory:Listening on ${baseUrl}`;
|
|
649
638
|
if (!loggerEvents.includes(expectedLog)) {
|
|
650
639
|
throw new Error(`${this.options.name} adapter changed HTTPS startup logging.`);
|
|
651
640
|
}
|
|
@@ -685,11 +674,10 @@ export class HttpAdapterPortabilityHarness {
|
|
|
685
674
|
});
|
|
686
675
|
const signal = 'SIGTERM';
|
|
687
676
|
const listenersBefore = new Set(process.listeners(signal));
|
|
688
|
-
const port = await findAvailablePort();
|
|
689
677
|
const app = await this.options.run(AppModule, {
|
|
690
678
|
cors: false,
|
|
691
679
|
logger,
|
|
692
|
-
port,
|
|
680
|
+
port: 0,
|
|
693
681
|
shutdownSignals: [signal]
|
|
694
682
|
});
|
|
695
683
|
const registeredListeners = process.listeners(signal).filter(listener => !listenersBefore.has(listener));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collects source-file aliases for a fluo monorepo checkout.
|
|
3
|
+
*
|
|
4
|
+
* @param repoRootUrl - Repository root as a file URL or absolute path URL string.
|
|
5
|
+
* @returns A Vite/Vitest alias map that points public package imports at workspace source files.
|
|
6
|
+
*/
|
|
7
|
+
export declare function collectWorkspaceAliases(repoRootUrl: string | URL): Record<string, string>;
|
|
8
|
+
/**
|
|
9
|
+
* Creates the shared Vitest configuration used by fluo workspace packages.
|
|
10
|
+
*
|
|
11
|
+
* @param repoRootUrl - Repository root as a file URL or absolute path URL string.
|
|
12
|
+
* @param overrides - Optional Vitest config overrides merged after the fluo defaults.
|
|
13
|
+
* @returns A Vitest configuration with fluo decorator transforms and workspace aliases.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createFluoVitestWorkspaceConfig(repoRootUrl: string | URL, overrides?: {}): Record<string, any>;
|
|
16
|
+
/**
|
|
17
|
+
* Defines a Vitest config rooted at the current fluo repository checkout.
|
|
18
|
+
*
|
|
19
|
+
* @returns A Vitest configuration for repository-local package tests.
|
|
20
|
+
*/
|
|
21
|
+
export declare function defineFluoVitestConfig(): Record<string, any>;
|
|
22
|
+
//# sourceMappingURL=tooling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooling.d.ts","sourceRoot":"","sources":["../../src/vitest/tooling.ts"],"names":[],"mappings":"AA4EA;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAEzF;AAED;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,EAAE,SAAS,KAAK,uBAaxF;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,wBAErC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { extname, join, relative, sep } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { defineConfig, mergeConfig } from 'vitest/config';
|
|
5
|
+
import { fluoBabelDecoratorsPlugin } from '../vitest.js';
|
|
6
|
+
function collectSourceEntries(sourceRoot) {
|
|
7
|
+
const entries = [];
|
|
8
|
+
for (const directoryEntry of readdirSync(sourceRoot, {
|
|
9
|
+
withFileTypes: true
|
|
10
|
+
})) {
|
|
11
|
+
const entryPath = join(sourceRoot, directoryEntry.name);
|
|
12
|
+
if (directoryEntry.isDirectory()) {
|
|
13
|
+
entries.push(...collectSourceEntries(entryPath));
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (directoryEntry.isFile()) {
|
|
17
|
+
entries.push(entryPath);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return entries;
|
|
21
|
+
}
|
|
22
|
+
function collectWorkspaceAliasesFromRoot(repoRoot) {
|
|
23
|
+
const packagesRoot = join(repoRoot, 'packages');
|
|
24
|
+
const aliases = {};
|
|
25
|
+
for (const packageDirectoryName of readdirSync(packagesRoot)) {
|
|
26
|
+
const packageRoot = join(packagesRoot, packageDirectoryName);
|
|
27
|
+
const sourceRoot = join(packageRoot, 'src');
|
|
28
|
+
const manifestPath = join(packageRoot, 'package.json');
|
|
29
|
+
if (!existsSync(sourceRoot) || !existsSync(manifestPath)) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
33
|
+
const scopeName = manifest.name ?? `@fluojs/${packageDirectoryName}`;
|
|
34
|
+
for (const sourceEntryPath of collectSourceEntries(sourceRoot)) {
|
|
35
|
+
const relativeSourceEntry = relative(sourceRoot, sourceEntryPath);
|
|
36
|
+
if (extname(sourceEntryPath) !== '.ts' || relativeSourceEntry.endsWith('.test.ts') || relativeSourceEntry === 'index.ts') {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const subpath = relativeSourceEntry.slice(0, -3).split(sep).join('/');
|
|
40
|
+
aliases[`${scopeName}/${subpath}`] = sourceEntryPath;
|
|
41
|
+
}
|
|
42
|
+
const indexPath = join(sourceRoot, 'index.ts');
|
|
43
|
+
if (existsSync(indexPath)) {
|
|
44
|
+
aliases[scopeName] = indexPath;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
'@fluojs/runtime/internal/http-adapter': join(packagesRoot, 'runtime', 'src', 'internal-http-adapter.ts'),
|
|
49
|
+
'@fluojs/runtime/internal/request-response-factory': join(packagesRoot, 'runtime', 'src', 'internal-request-response-factory.ts'),
|
|
50
|
+
...aliases
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Collects source-file aliases for a fluo monorepo checkout.
|
|
56
|
+
*
|
|
57
|
+
* @param repoRootUrl - Repository root as a file URL or absolute path URL string.
|
|
58
|
+
* @returns A Vite/Vitest alias map that points public package imports at workspace source files.
|
|
59
|
+
*/
|
|
60
|
+
export function collectWorkspaceAliases(repoRootUrl) {
|
|
61
|
+
return collectWorkspaceAliasesFromRoot(fileURLToPath(repoRootUrl));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Creates the shared Vitest configuration used by fluo workspace packages.
|
|
66
|
+
*
|
|
67
|
+
* @param repoRootUrl - Repository root as a file URL or absolute path URL string.
|
|
68
|
+
* @param overrides - Optional Vitest config overrides merged after the fluo defaults.
|
|
69
|
+
* @returns A Vitest configuration with fluo decorator transforms and workspace aliases.
|
|
70
|
+
*/
|
|
71
|
+
export function createFluoVitestWorkspaceConfig(repoRootUrl, overrides = {}) {
|
|
72
|
+
return mergeConfig(defineConfig({
|
|
73
|
+
plugins: [fluoBabelDecoratorsPlugin()],
|
|
74
|
+
resolve: {
|
|
75
|
+
alias: collectWorkspaceAliases(repoRootUrl)
|
|
76
|
+
},
|
|
77
|
+
test: {
|
|
78
|
+
environment: 'node'
|
|
79
|
+
}
|
|
80
|
+
}), defineConfig(overrides));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Defines a Vitest config rooted at the current fluo repository checkout.
|
|
85
|
+
*
|
|
86
|
+
* @returns A Vitest configuration for repository-local package tests.
|
|
87
|
+
*/
|
|
88
|
+
export function defineFluoVitestConfig() {
|
|
89
|
+
return createFluoVitestWorkspaceConfig(new URL('../../../../', import.meta.url));
|
|
90
|
+
}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"override",
|
|
10
10
|
"module-builder"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0.
|
|
12
|
+
"version": "1.0.3",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -68,6 +68,10 @@
|
|
|
68
68
|
"./vitest": {
|
|
69
69
|
"types": "./dist/vitest.d.ts",
|
|
70
70
|
"import": "./dist/vitest.js"
|
|
71
|
+
},
|
|
72
|
+
"./vitest/tooling": {
|
|
73
|
+
"types": "./dist/vitest/tooling.d.ts",
|
|
74
|
+
"import": "./dist/vitest/tooling.js"
|
|
71
75
|
}
|
|
72
76
|
},
|
|
73
77
|
"main": "./dist/index.js",
|
|
@@ -76,11 +80,11 @@
|
|
|
76
80
|
"dist"
|
|
77
81
|
],
|
|
78
82
|
"dependencies": {
|
|
79
|
-
"@fluojs/
|
|
80
|
-
"@fluojs/
|
|
81
|
-
"@fluojs/http": "^1.
|
|
82
|
-
"@fluojs/di": "^1.0.
|
|
83
|
-
"@fluojs/runtime": "^1.
|
|
83
|
+
"@fluojs/core": "^1.0.3",
|
|
84
|
+
"@fluojs/config": "^1.0.2",
|
|
85
|
+
"@fluojs/http": "^1.1.0",
|
|
86
|
+
"@fluojs/di": "^1.0.3",
|
|
87
|
+
"@fluojs/runtime": "^1.1.1"
|
|
84
88
|
},
|
|
85
89
|
"peerDependencies": {
|
|
86
90
|
"@babel/core": ">=7.0.0",
|
|
@@ -88,12 +92,12 @@
|
|
|
88
92
|
},
|
|
89
93
|
"devDependencies": {
|
|
90
94
|
"vitest": "^3.2.4",
|
|
91
|
-
"@fluojs/platform-bun": "^1.0.
|
|
92
|
-
"@fluojs/platform-
|
|
93
|
-
"@fluojs/platform-
|
|
94
|
-
"@fluojs/platform-
|
|
95
|
-
"@fluojs/platform-
|
|
96
|
-
"@fluojs/platform-
|
|
95
|
+
"@fluojs/platform-bun": "^1.0.2",
|
|
96
|
+
"@fluojs/platform-deno": "^1.0.3",
|
|
97
|
+
"@fluojs/platform-nodejs": "^1.0.2",
|
|
98
|
+
"@fluojs/platform-express": "^1.0.2",
|
|
99
|
+
"@fluojs/platform-fastify": "^1.0.3",
|
|
100
|
+
"@fluojs/platform-cloudflare-workers": "^1.0.2"
|
|
97
101
|
},
|
|
98
102
|
"scripts": {
|
|
99
103
|
"prebuild": "node ../../tooling/scripts/clean-dist.mjs",
|