@jterrazz/test 16.1.1 → 17.0.0
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.md +45 -171
- package/dist/browser/index.js +2 -2980
- package/dist/browser/services.js +2 -0
- package/dist/browser/stubs.js +2 -0
- package/dist/checker.d.ts +6 -1
- package/dist/checker.js +4534 -2628
- package/dist/config.d.ts +40 -0
- package/dist/config.js +309 -0
- package/dist/in-place.global-setup.d.ts +11 -0
- package/dist/in-place.global-setup.js +21 -0
- package/dist/index.d.ts +1653 -3004
- package/dist/index.js +5226 -3845
- package/dist/mobile-session.global-setup.d.ts +14 -0
- package/dist/mobile-session.global-setup.js +31 -0
- package/dist/oxlint.cjs +6069 -5634
- package/dist/oxlint.d.cts +43 -7212
- package/dist/oxlint.d.ts +43 -7212
- package/dist/oxlint.js +6127 -5693
- package/dist/route-coverage.global-setup.d.ts +42 -0
- package/dist/route-coverage.global-setup.js +2 -0
- package/dist/services.d.ts +3 -0
- package/dist/services.global-setup.d.ts +43 -0
- package/dist/services.global-setup.js +2 -0
- package/dist/services.js +597 -0
- package/dist/shared-appium.adapter.js +428 -0
- package/dist/shared-artifacts.js +32 -0
- package/dist/shared-chapter.js +7 -0
- package/dist/shared-container.port.d.ts +31 -0
- package/dist/shared-derived-projects.js +58 -0
- package/dist/shared-device.port.d.ts +189 -0
- package/dist/shared-exchange.js +447 -0
- package/dist/shared-in-place.js +172 -0
- package/dist/shared-index.browser.js +3191 -0
- package/dist/shared-index.services.d.ts +346 -0
- package/dist/shared-index.stubs.d.ts +287 -0
- package/dist/shared-json-shape.js +17 -0
- package/dist/shared-landmarks.js +71 -0
- package/dist/shared-ledger.js +68 -0
- package/dist/shared-literate-plugin.js +183 -0
- package/dist/shared-manifest.js +1321 -0
- package/dist/shared-pause.js +11 -0
- package/dist/shared-peer.js +88 -0
- package/dist/shared-playwright.adapter.js +330 -0
- package/dist/shared-process.adapter.js +74 -0
- package/dist/shared-refusal.js +35 -0
- package/dist/shared-resolve.js +24 -0
- package/dist/shared-route-coverage.global-setup.js +187 -0
- package/dist/shared-scenario-failure.js +198 -0
- package/dist/shared-serve.js +365 -0
- package/dist/shared-service-images.js +10 -0
- package/dist/shared-services.global-setup.js +179 -0
- package/dist/shared-session-ledger.js +301 -0
- package/dist/shared-setup-entry.js +12 -0
- package/dist/shared-source-scan.js +133 -0
- package/dist/shared-spec-document.js +365 -0
- package/dist/shared-specifications.js +26 -0
- package/dist/shared-stream-body.js +130 -0
- package/dist/shared-stub-server.js +190 -0
- package/dist/shared-temp-root.global-setup.js +80 -0
- package/dist/shared-test-baseline.js +26 -0
- package/dist/shared-test-section.js +58 -0
- package/dist/shared-testcontainers.adapter.js +203 -0
- package/dist/shared-tokens.js +55 -0
- package/dist/shared-yaml-document.js +119 -0
- package/dist/stubs.d.ts +2 -0
- package/dist/stubs.js +657 -0
- package/dist/temp-root.global-setup.d.ts +7 -0
- package/dist/temp-root.global-setup.js +2 -0
- package/dist/vitest.d.ts +97 -163
- package/dist/vitest.js +373 -525
- package/docs/README.md +82 -36
- package/docs/concepts/architecture.md +118 -0
- package/docs/concepts/conventions.md +333 -0
- package/docs/guides/api-specs.md +374 -0
- package/docs/guides/cli-specs.md +564 -0
- package/docs/guides/component-specs.md +252 -0
- package/docs/guides/develop.md +94 -0
- package/docs/guides/integration-specs.md +181 -0
- package/docs/guides/job-specs.md +180 -0
- package/docs/guides/lint-specs.md +174 -0
- package/docs/guides/mobile-specs.md +319 -0
- package/docs/guides/module-tests.md +149 -0
- package/docs/guides/release.md +43 -0
- package/docs/guides/spec-documents.md +189 -0
- package/docs/guides/website-specs.md +413 -0
- package/docs/reference/assertions.md +345 -0
- package/docs/reference/glossary.md +20 -0
- package/docs/reference/linting.md +483 -0
- package/docs/reference/services.md +236 -0
- package/docs/reference/stubs.md +498 -0
- package/docs/reference/time-and-doubles.md +137 -0
- package/docs/reference/tokens.md +258 -0
- package/docs/reference/visitor.md +221 -0
- package/docs/start/getting-started.md +187 -0
- package/docs/start/set-up-a-project.md +214 -0
- package/package.json +50 -30
- package/schema/spec.schema.json +43 -17
- package/dist/appium.adapter.js +0 -489
- package/dist/artifacts.js +0 -32
- package/dist/fixtures.js +0 -110
- package/dist/literate.js +0 -1747
- package/dist/match.js +0 -804
- package/dist/playwright.adapter.js +0 -438
- package/dist/server.js +0 -160
- package/dist/stream.js +0 -321
- package/dist/substring-warning.js +0 -282
- package/docs/01-architecture.md +0 -95
- package/docs/02-developing.md +0 -351
- package/docs/03-testing.md +0 -495
- package/docs/04-operating.md +0 -101
- package/docs/05-module-tests.md +0 -152
- package/docs/06-integration.md +0 -180
- package/docs/07-component.md +0 -246
- package/docs/08-website.md +0 -404
- package/docs/09-mobile.md +0 -270
- package/docs/10-api.md +0 -264
- package/docs/11-jobs.md +0 -157
- package/docs/12-cli.md +0 -677
- package/docs/13-elements.md +0 -248
- package/docs/14-assertions.md +0 -314
- package/docs/15-tokens.md +0 -253
- package/docs/16-contracts.md +0 -438
- package/docs/17-services.md +0 -188
- package/docs/18-conventions.md +0 -332
- package/docs/19-linting.md +0 -512
- package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
- package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
- package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
- package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
- package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
- package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
- package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
- package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
- package/docs/decisions/_template.md +0 -22
- package/docs/reference/classes/Matcher.md +0 -120
- package/docs/reference/classes/ProcessHandle.md +0 -368
- package/docs/reference/functions/defineContract.md +0 -39
- package/docs/reference/functions/defineContracts.md +0 -36
- package/docs/reference/functions/disabled.md +0 -28
- package/docs/reference/functions/enabled.md +0 -20
- package/docs/reference/functions/focused.md +0 -26
- package/docs/reference/functions/mockOf.md +0 -79
- package/docs/reference/functions/postgres.md +0 -31
- package/docs/reference/functions/process.md +0 -33
- package/docs/reference/functions/redis.md +0 -28
- package/docs/reference/functions/required.md +0 -41
- package/docs/reference/functions/selected.md +0 -26
- package/docs/reference/functions/sqlite.md +0 -33
- package/docs/reference/functions/testId.md +0 -20
- package/docs/reference/functions/text.md +0 -40
- package/docs/reference/functions/valued.md +0 -28
- package/docs/reference/functions/waitUntil.md +0 -46
- package/docs/reference/functions/within.md +0 -29
- package/docs/reference/index.md +0 -196
- package/docs/reference/interfaces/BaseResult.md +0 -87
- package/docs/reference/interfaces/CallResult.md +0 -160
- package/docs/reference/interfaces/CaptureScope.md +0 -82
- package/docs/reference/interfaces/CliResult.md +0 -253
- package/docs/reference/interfaces/ContainerAccessor.md +0 -166
- package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
- package/docs/reference/interfaces/FetchResult.md +0 -180
- package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
- package/docs/reference/interfaces/HttpResult.md +0 -126
- package/docs/reference/interfaces/JsonAccessor.md +0 -46
- package/docs/reference/interfaces/PageResult.md +0 -356
- package/docs/reference/interfaces/RenderResult.md +0 -72
- package/docs/reference/interfaces/RenderedText.md +0 -110
- package/docs/reference/interfaces/ResponseAccessor.md +0 -47
- package/docs/reference/interfaces/ScreenResult.md +0 -128
- package/docs/reference/interfaces/TableAccessor.md +0 -26
- package/docs/reference/interfaces/TextAccessor.md +0 -96
- package/docs/reference/type-aliases/ApiHandle.md +0 -68
- package/docs/reference/type-aliases/ApiSpecification.md +0 -213
- package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
- package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
- package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
- package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
- package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
- package/docs/reference/type-aliases/BrowserPage.md +0 -132
- package/docs/reference/type-aliases/BrowserPort.md +0 -51
- package/docs/reference/type-aliases/CliEnv.md +0 -11
- package/docs/reference/type-aliases/CliHandle.md +0 -80
- package/docs/reference/type-aliases/CliInput.md +0 -36
- package/docs/reference/type-aliases/CliOutput.md +0 -46
- package/docs/reference/type-aliases/CliPort.md +0 -58
- package/docs/reference/type-aliases/CliSpecification.md +0 -144
- package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
- package/docs/reference/type-aliases/ComponentChain.md +0 -156
- package/docs/reference/type-aliases/ComponentScenario.md +0 -20
- package/docs/reference/type-aliases/ComponentUi.md +0 -47
- package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
- package/docs/reference/type-aliases/ContainerPort.md +0 -113
- package/docs/reference/type-aliases/Contract.md +0 -68
- package/docs/reference/type-aliases/ContractInput.md +0 -13
- package/docs/reference/type-aliases/ContractRequest.md +0 -95
- package/docs/reference/type-aliases/ContractResponder.md +0 -23
- package/docs/reference/type-aliases/ContractResponse.md +0 -78
- package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
- package/docs/reference/type-aliases/Contracts.md +0 -48
- package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
- package/docs/reference/type-aliases/DatabasePort.md +0 -72
- package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
- package/docs/reference/type-aliases/DevicePort.md +0 -51
- package/docs/reference/type-aliases/DeviceScreen.md +0 -32
- package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
- package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
- package/docs/reference/type-aliases/DomMount.md +0 -21
- package/docs/reference/type-aliases/ElementKind.md +0 -27
- package/docs/reference/type-aliases/ElementMatch.md +0 -74
- package/docs/reference/type-aliases/ElementOptions.md +0 -29
- package/docs/reference/type-aliases/ElementRef.md +0 -128
- package/docs/reference/type-aliases/ExecOptions.md +0 -37
- package/docs/reference/type-aliases/FileAccessor.md +0 -54
- package/docs/reference/type-aliases/HonoApp.md +0 -31
- package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
- package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
- package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
- package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
- package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
- package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
- package/docs/reference/type-aliases/Intercept.md +0 -19
- package/docs/reference/type-aliases/InterceptOptions.md +0 -28
- package/docs/reference/type-aliases/InterceptScope.md +0 -11
- package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
- package/docs/reference/type-aliases/JobHandle.md +0 -34
- package/docs/reference/type-aliases/JobsHandle.md +0 -57
- package/docs/reference/type-aliases/JobsResult.md +0 -16
- package/docs/reference/type-aliases/JobsSpecification.md +0 -94
- package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
- package/docs/reference/type-aliases/LandmarkKind.md +0 -20
- package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
- package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
- package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
- package/docs/reference/type-aliases/MatchableRequest.md +0 -61
- package/docs/reference/type-aliases/MatcherKind.md +0 -34
- package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
- package/docs/reference/type-aliases/MobileElementKind.md +0 -14
- package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
- package/docs/reference/type-aliases/MobileElementRef.md +0 -20
- package/docs/reference/type-aliases/MobileHandle.md +0 -65
- package/docs/reference/type-aliases/MobileScenario.md +0 -20
- package/docs/reference/type-aliases/MobileSpecification.md +0 -51
- package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
- package/docs/reference/type-aliases/MobileVisitor.md +0 -81
- package/docs/reference/type-aliases/MockOfOptions.md +0 -26
- package/docs/reference/type-aliases/MockPort.md +0 -10
- package/docs/reference/type-aliases/PinnedClock.md +0 -10
- package/docs/reference/type-aliases/PostgresOptions.md +0 -32
- package/docs/reference/type-aliases/ProcessOptions.md +0 -112
- package/docs/reference/type-aliases/RedisOptions.md +0 -20
- package/docs/reference/type-aliases/RenderSubject.md +0 -12
- package/docs/reference/type-aliases/ScreenNode.md +0 -67
- package/docs/reference/type-aliases/ServerPort.md +0 -36
- package/docs/reference/type-aliases/ServerResponse.md +0 -46
- package/docs/reference/type-aliases/ServerSpec.md +0 -21
- package/docs/reference/type-aliases/ServiceHandle.md +0 -216
- package/docs/reference/type-aliases/ServiceRecord.md +0 -12
- package/docs/reference/type-aliases/SpecDocument.md +0 -84
- package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
- package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
- package/docs/reference/type-aliases/SpecFixture.md +0 -30
- package/docs/reference/type-aliases/SpecKind.md +0 -8
- package/docs/reference/type-aliases/SpecRun.md +0 -108
- package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
- package/docs/reference/type-aliases/SpecStream.md +0 -32
- package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
- package/docs/reference/type-aliases/SqliteOptions.md +0 -36
- package/docs/reference/type-aliases/SseEvent.md +0 -58
- package/docs/reference/type-aliases/StreamBody.md +0 -61
- package/docs/reference/type-aliases/TextFilter.md +0 -22
- package/docs/reference/type-aliases/VisitScenario.md +0 -20
- package/docs/reference/type-aliases/Visitor.md +0 -215
- package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
- package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
- package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
- package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
- package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
- package/docs/reference/variables/anthropic.md +0 -19
- package/docs/reference/variables/banner.md +0 -10
- package/docs/reference/variables/button.md +0 -10
- package/docs/reference/variables/clock.md +0 -16
- package/docs/reference/variables/complementary.md +0 -10
- package/docs/reference/variables/component.md +0 -10
- package/docs/reference/variables/content.md +0 -10
- package/docs/reference/variables/contentinfo.md +0 -10
- package/docs/reference/variables/dialog.md +0 -10
- package/docs/reference/variables/field.md +0 -10
- package/docs/reference/variables/form.md +0 -10
- package/docs/reference/variables/heading.md +0 -10
- package/docs/reference/variables/http.md +0 -40
- package/docs/reference/variables/intercept.md +0 -11
- package/docs/reference/variables/link.md +0 -10
- package/docs/reference/variables/listitem.md +0 -10
- package/docs/reference/variables/main.md +0 -10
- package/docs/reference/variables/match.md +0 -8
- package/docs/reference/variables/navigation.md +0 -10
- package/docs/reference/variables/openai.md +0 -21
- package/docs/reference/variables/option.md +0 -17
- package/docs/reference/variables/region.md +0 -10
- package/docs/reference/variables/row.md +0 -10
- package/docs/reference/variables/search.md +0 -10
- package/docs/reference/variables/specification.md +0 -8
- package/docs/reference/variables/status.md +0 -10
- package/docs/reference/variables/table.md +0 -10
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { n as refusal, t as invariant } from "./shared-refusal.js";
|
|
2
|
+
import { t as pause } from "./shared-pause.js";
|
|
3
|
+
import "node:module";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { execSync, spawn, spawnSync } from "node:child_process";
|
|
6
|
+
import { Socket, createServer } from "node:net";
|
|
7
|
+
import { once } from "node:events";
|
|
8
|
+
//#region \0rolldown/runtime.js
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __exportAll = (all, no_symbols) => {
|
|
11
|
+
let target = {};
|
|
12
|
+
for (var name in all) __defProp(target, name, {
|
|
13
|
+
get: all[name],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
17
|
+
return target;
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/services/process/listening.ts
|
|
21
|
+
async function findFreePort() {
|
|
22
|
+
return await new Promise((resolve, reject) => {
|
|
23
|
+
const server = createServer();
|
|
24
|
+
server.listen(0, () => {
|
|
25
|
+
const address = server.address();
|
|
26
|
+
if (address === null || typeof address === "string") {
|
|
27
|
+
server.close();
|
|
28
|
+
reject(invariant("a server listening on port 0 answered no port"));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const { port } = address;
|
|
32
|
+
server.close(() => {
|
|
33
|
+
resolve(port);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
server.on("error", reject);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async function firstAnswering(urls) {
|
|
40
|
+
return (await Promise.all(urls.map(async (url) => await answers(url) ? url : void 0))).find((url) => url !== void 0);
|
|
41
|
+
}
|
|
42
|
+
async function answers(url) {
|
|
43
|
+
try {
|
|
44
|
+
await fetch(url, { redirect: "manual" });
|
|
45
|
+
return true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/services/process/port-in-use.ts
|
|
52
|
+
const PORT_IN_USE = /EADDRINUSE|address already in use|port \d+ is already in use/iu;
|
|
53
|
+
function lessonOf(reason, output, port) {
|
|
54
|
+
if (!PORT_IN_USE.test(output)) return reason;
|
|
55
|
+
return {
|
|
56
|
+
fix: port === void 0 ? "have the command listen on the `PORT` it is handed, a free one, or stop the process holding the port it binds" : `stop the process holding port ${String(port)}, or drop the \`port\` option so a free one is picked`,
|
|
57
|
+
found: "the server could not bind its port, which another process already holds",
|
|
58
|
+
why: "A process is handed a free port unless `port` pins one, and a port another process holds is not free."
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/services/process/process-group.ts
|
|
63
|
+
const KILL_GRACE_MS = 2e3;
|
|
64
|
+
/** outside: Windows has no process groups to signal through a negative pid. */
|
|
65
|
+
const SIGNALS_GROUPS = process.platform !== "win32";
|
|
66
|
+
const GROUP_POLL_MS = 1e3;
|
|
67
|
+
const GROUP_SETTLE_MS = 50;
|
|
68
|
+
const STRAYS_SOURCE = `
|
|
69
|
+
const { execFileSync } = require('node:child_process');
|
|
70
|
+
const strays = JSON.parse(process.argv[1] ?? '[]');
|
|
71
|
+
const listed = (column) => {
|
|
72
|
+
let listing = '';
|
|
73
|
+
try { listing = execFileSync('ps', ['-A', '-o', 'pid=,' + column + '='], { encoding: 'utf8', maxBuffer: 67108864 }); } catch {}
|
|
74
|
+
return new Map(listing.split('\\n').flatMap((line) => {
|
|
75
|
+
const found = /^\\s*(\\d+)\\s(.*)$/u.exec(line);
|
|
76
|
+
return found === null ? [] : [[Number(found[1]), found[2]]];
|
|
77
|
+
}));
|
|
78
|
+
};
|
|
79
|
+
const names = (path, program) => path === program || path.endsWith('/' + program);
|
|
80
|
+
const strayPids = () => {
|
|
81
|
+
const commands = listed('command');
|
|
82
|
+
return [...listed('comm')]
|
|
83
|
+
.filter(([pid, executable]) => {
|
|
84
|
+
const command = commands.get(pid) ?? '';
|
|
85
|
+
return pid !== process.pid && strays.some((stray) =>
|
|
86
|
+
(names(executable, stray.program) || names(command.split(' ')[0], stray.program)) && stray.holds.every((word) => command.includes(word)));
|
|
87
|
+
})
|
|
88
|
+
.map(([pid]) => pid);
|
|
89
|
+
};
|
|
90
|
+
const signalStrays = (signal) => {
|
|
91
|
+
for (const pid of strays.length === 0 ? [] : strayPids()) {
|
|
92
|
+
try { process.kill(-pid, signal); } catch {}
|
|
93
|
+
try { process.kill(pid, signal); } catch {}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
`;
|
|
97
|
+
/** Its arguments are the strays, then the group. With strays to reap, it outlives the group: a stray outlives the group that spawned it. */
|
|
98
|
+
const WATCHDOG = `${STRAYS_SOURCE}
|
|
99
|
+
const group = -Number(process.argv[2]);
|
|
100
|
+
const gone = () => { try { process.kill(group, 0); return false; } catch { return true; } };
|
|
101
|
+
const end = () => {
|
|
102
|
+
try { process.kill(group, 'SIGTERM'); } catch { if (strays.length === 0) process.exit(0); }
|
|
103
|
+
signalStrays('SIGTERM');
|
|
104
|
+
setTimeout(() => { try { process.kill(group, 'SIGKILL'); } catch {} signalStrays('SIGKILL'); process.exit(0); }, ${KILL_GRACE_MS});
|
|
105
|
+
};
|
|
106
|
+
process.stdin.on('end', end);
|
|
107
|
+
process.stdin.on('error', end);
|
|
108
|
+
process.stdin.resume();
|
|
109
|
+
setInterval(() => { if (strays.length === 0 && gone()) process.exit(0); }, ${GROUP_POLL_MS});
|
|
110
|
+
`;
|
|
111
|
+
/** Its argument is the strays: each gets SIGTERM, and SIGKILL when it outlives the grace. */
|
|
112
|
+
const REAPER = `${STRAYS_SOURCE}
|
|
113
|
+
signalStrays('SIGTERM');
|
|
114
|
+
const deadline = Date.now() + ${KILL_GRACE_MS};
|
|
115
|
+
const settle = () => {
|
|
116
|
+
if (strayPids().length === 0) return;
|
|
117
|
+
if (Date.now() >= deadline) { signalStrays('SIGKILL'); return; }
|
|
118
|
+
setTimeout(settle, ${GROUP_SETTLE_MS});
|
|
119
|
+
};
|
|
120
|
+
settle();
|
|
121
|
+
`;
|
|
122
|
+
function groupRunning(group) {
|
|
123
|
+
try {
|
|
124
|
+
process.kill(group, 0);
|
|
125
|
+
return true;
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function endedBy(group, deadline) {
|
|
131
|
+
if (!groupRunning(group)) return true;
|
|
132
|
+
if (Date.now() >= deadline) return false;
|
|
133
|
+
await pause(GROUP_SETTLE_MS);
|
|
134
|
+
return await endedBy(group, deadline);
|
|
135
|
+
}
|
|
136
|
+
async function groupEnded(group, withinMs) {
|
|
137
|
+
return await endedBy(group, Date.now() + withinMs);
|
|
138
|
+
}
|
|
139
|
+
/** Resolves once the child is reaped, at once for one reaped already. */
|
|
140
|
+
async function reaped(child) {
|
|
141
|
+
if (child.exitCode !== null || child.signalCode !== null) return;
|
|
142
|
+
await once(child, "exit");
|
|
143
|
+
}
|
|
144
|
+
/** The child's whole group where groups are signalled, the child alone where they are not or the group is gone; nothing for a child that never started. */
|
|
145
|
+
function signalGroup(child, signal = "SIGTERM") {
|
|
146
|
+
if (child.pid === void 0) return false;
|
|
147
|
+
if (SIGNALS_GROUPS) try {
|
|
148
|
+
process.kill(-child.pid, signal);
|
|
149
|
+
return true;
|
|
150
|
+
} catch {}
|
|
151
|
+
return child.kill(signal);
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/services/process/serve.ts
|
|
155
|
+
var serve_exports = /* @__PURE__ */ __exportAll({
|
|
156
|
+
ServedProcess: () => ServedProcess,
|
|
157
|
+
runToCompletion: () => runToCompletion
|
|
158
|
+
});
|
|
159
|
+
const DEFAULT_READY_TIMEOUT = 3e4;
|
|
160
|
+
const READY_POLL_INTERVAL_MS = 250;
|
|
161
|
+
function watchGroup(groupId, strays) {
|
|
162
|
+
const args = [
|
|
163
|
+
"-e",
|
|
164
|
+
WATCHDOG,
|
|
165
|
+
JSON.stringify(strays),
|
|
166
|
+
String(groupId)
|
|
167
|
+
];
|
|
168
|
+
const watchdog = spawn(process.execPath, args, {
|
|
169
|
+
detached: true,
|
|
170
|
+
env: {
|
|
171
|
+
...process.env,
|
|
172
|
+
NODE_OPTIONS: void 0
|
|
173
|
+
},
|
|
174
|
+
stdio: [
|
|
175
|
+
"pipe",
|
|
176
|
+
"ignore",
|
|
177
|
+
"ignore"
|
|
178
|
+
]
|
|
179
|
+
});
|
|
180
|
+
watchdog.on("error", () => {
|
|
181
|
+
watchdog.stdin?.destroy();
|
|
182
|
+
});
|
|
183
|
+
watchdog.unref();
|
|
184
|
+
if (watchdog.stdin instanceof Socket) watchdog.stdin.unref();
|
|
185
|
+
return watchdog;
|
|
186
|
+
}
|
|
187
|
+
async function reapStrays(strays) {
|
|
188
|
+
const args = [
|
|
189
|
+
"-e",
|
|
190
|
+
REAPER,
|
|
191
|
+
JSON.stringify(strays)
|
|
192
|
+
];
|
|
193
|
+
await (strays.length > 0 ? reaped(spawn(process.execPath, args, { stdio: "ignore" })) : null);
|
|
194
|
+
}
|
|
195
|
+
function runToCompletion(command, env) {
|
|
196
|
+
execSync(command, {
|
|
197
|
+
env,
|
|
198
|
+
stdio: "pipe"
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/** Any HTTP status counts as ready. The child runs in its own process group so `stop()` kills a `next start` grandchild too, and a watchdog kills that group when the run dies before `stop()`; both reap the strays it declares, the processes it spawns outside its group. */
|
|
202
|
+
var ServedProcess = class {
|
|
203
|
+
port = null;
|
|
204
|
+
child = null;
|
|
205
|
+
extraEnv;
|
|
206
|
+
specification;
|
|
207
|
+
options;
|
|
208
|
+
exited = false;
|
|
209
|
+
output = "";
|
|
210
|
+
root;
|
|
211
|
+
strays;
|
|
212
|
+
watchdog = null;
|
|
213
|
+
constructor(options, root, specification = "website", extraEnv = {}) {
|
|
214
|
+
this.extraEnv = extraEnv;
|
|
215
|
+
this.strays = options.strays ?? [];
|
|
216
|
+
this.specification = specification;
|
|
217
|
+
this.options = options;
|
|
218
|
+
this.root = root;
|
|
219
|
+
}
|
|
220
|
+
async start() {
|
|
221
|
+
this.runBefore();
|
|
222
|
+
const banner = this.options.ready instanceof RegExp ? this.options.ready : null;
|
|
223
|
+
const port = banner ? null : this.options.port ?? await findFreePort();
|
|
224
|
+
this.spawnServer(port);
|
|
225
|
+
const deadline = Date.now() + this.timeout();
|
|
226
|
+
return (banner ? await this.awaitBanner(banner, deadline) : await this.awaitAnswer(port ?? 0, deadline)) ?? await this.refuse({
|
|
227
|
+
fix: "run the command by hand and correct what its output names",
|
|
228
|
+
found: "the server command exited before it was ready",
|
|
229
|
+
why: "A process is handed to the chain once it is ready, and one that exits never is."
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
environment(port) {
|
|
233
|
+
return {
|
|
234
|
+
...process.env,
|
|
235
|
+
ASTRO_DEV_BACKGROUND: "1",
|
|
236
|
+
ASTRO_PREVIEW_BACKGROUND: "1",
|
|
237
|
+
...typeof this.options.env === "function" ? {} : this.options.env,
|
|
238
|
+
...this.extraEnv,
|
|
239
|
+
...port === null ? {} : { PORT: String(port) }
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
spawnServer(port) {
|
|
243
|
+
const child = spawn(this.options.command, [], {
|
|
244
|
+
cwd: this.workingDirectory(),
|
|
245
|
+
detached: SIGNALS_GROUPS,
|
|
246
|
+
env: this.environment(port),
|
|
247
|
+
shell: true,
|
|
248
|
+
stdio: [
|
|
249
|
+
"ignore",
|
|
250
|
+
"pipe",
|
|
251
|
+
"pipe"
|
|
252
|
+
]
|
|
253
|
+
});
|
|
254
|
+
this.child = child;
|
|
255
|
+
this.exited = false;
|
|
256
|
+
if (child.pid !== void 0 && SIGNALS_GROUPS) this.watchdog = watchGroup(child.pid, this.strays);
|
|
257
|
+
const collect = (data) => {
|
|
258
|
+
this.output += String(data);
|
|
259
|
+
};
|
|
260
|
+
child.stdout?.on("data", collect);
|
|
261
|
+
child.stderr?.on("data", collect);
|
|
262
|
+
child.on("close", () => {
|
|
263
|
+
this.exited = true;
|
|
264
|
+
});
|
|
265
|
+
child.on("error", (error) => {
|
|
266
|
+
collect(`${String(error)}\n`);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
async awaitBanner(banner, deadline) {
|
|
270
|
+
while (!this.exited) {
|
|
271
|
+
banner.lastIndex = 0;
|
|
272
|
+
const found = banner.exec(this.output);
|
|
273
|
+
if (found) return await this.bannerPort(banner, found[1]);
|
|
274
|
+
if (Date.now() >= deadline) return await this.refuse({
|
|
275
|
+
fix: "correct the pattern to what the server prints, or raise `timeout`",
|
|
276
|
+
found: `the server did not print ${String(banner)} within ${this.timeout()}ms`,
|
|
277
|
+
why: "A process is ready once its output matches `ready`, within `timeout`."
|
|
278
|
+
});
|
|
279
|
+
await pause(READY_POLL_INTERVAL_MS);
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
async bannerPort(banner, group) {
|
|
284
|
+
const captured = Number(group);
|
|
285
|
+
if (!Number.isInteger(captured)) return await this.refuse({
|
|
286
|
+
fix: "capture the port alone in the pattern's one group, e.g. /listening on (\\d+)/",
|
|
287
|
+
found: `the ready pattern ${String(banner)} matched, and its capture group is not a port ("${group}")`,
|
|
288
|
+
why: "A RegExp `ready` is read over the output, and its one capture group is the port."
|
|
289
|
+
});
|
|
290
|
+
this.port = captured;
|
|
291
|
+
return `http://127.0.0.1:${captured}`;
|
|
292
|
+
}
|
|
293
|
+
async awaitAnswer(port, deadline) {
|
|
294
|
+
const baseUrls = [`http://127.0.0.1:${port}`, `http://[::1]:${port}`];
|
|
295
|
+
const path = typeof this.options.ready === "string" ? this.options.ready : "/";
|
|
296
|
+
while (!this.exited) {
|
|
297
|
+
const answering = await firstAnswering(baseUrls.map((baseUrl) => `${baseUrl}${path}`));
|
|
298
|
+
if (answering !== void 0) {
|
|
299
|
+
this.port = port;
|
|
300
|
+
return answering.slice(0, answering.length - path.length);
|
|
301
|
+
}
|
|
302
|
+
if (Date.now() >= deadline) return await this.refuse({
|
|
303
|
+
fix: "correct `ready` to a path the server answers, have it listen on `PORT`, or raise `timeout`",
|
|
304
|
+
found: `the server did not answer on ${baseUrls.map((baseUrl) => `${baseUrl}${path}`).join(" or ")} within ${this.timeout()}ms, whatever address it printed above`,
|
|
305
|
+
why: "A process is ready once its `ready` path answers, within `timeout`."
|
|
306
|
+
});
|
|
307
|
+
await pause(READY_POLL_INTERVAL_MS);
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
timeout() {
|
|
312
|
+
return this.options.timeout ?? DEFAULT_READY_TIMEOUT;
|
|
313
|
+
}
|
|
314
|
+
async refuse(reason) {
|
|
315
|
+
await this.stop();
|
|
316
|
+
const lesson = lessonOf(reason, this.output, this.options.port);
|
|
317
|
+
throw new Error(refusal({
|
|
318
|
+
fix: lesson.fix,
|
|
319
|
+
found: `specification.${this.specification}(): ${lesson.found}.\nCommand: ${this.options.command}\nOutput:\n${this.output}`,
|
|
320
|
+
read: "docs/reference/services.md#process-the-one-shape-an-external-process-takes",
|
|
321
|
+
why: lesson.why
|
|
322
|
+
}));
|
|
323
|
+
}
|
|
324
|
+
workingDirectory() {
|
|
325
|
+
const { cwd = "." } = this.options;
|
|
326
|
+
return resolve(this.root, typeof cwd === "function" ? cwd() : cwd);
|
|
327
|
+
}
|
|
328
|
+
/** Must exit 0: a failed build is reported as such, not as a later missing file. */
|
|
329
|
+
runBefore() {
|
|
330
|
+
const { before } = this.options;
|
|
331
|
+
if (before === void 0) return;
|
|
332
|
+
const outcome = spawnSync(before, [], {
|
|
333
|
+
cwd: this.workingDirectory(),
|
|
334
|
+
env: this.environment(null),
|
|
335
|
+
encoding: "utf8",
|
|
336
|
+
shell: true
|
|
337
|
+
});
|
|
338
|
+
if (outcome.status !== 0) throw new Error(refusal({
|
|
339
|
+
fix: "run the command by hand and correct what its output names",
|
|
340
|
+
found: `specification.${this.specification}(): \`before\` exited ${outcome.status ?? "without a status"}.\nCommand: ${before}\nOutput:\n${outcome.stdout + outcome.stderr}`,
|
|
341
|
+
read: "docs/reference/services.md#process-the-one-shape-an-external-process-takes",
|
|
342
|
+
why: "A process's `before` must exit 0 before the process starts."
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
async stop() {
|
|
346
|
+
const { child } = this;
|
|
347
|
+
if (!child) return;
|
|
348
|
+
this.child = null;
|
|
349
|
+
await endGroup(child);
|
|
350
|
+
await reapStrays(this.strays);
|
|
351
|
+
this.watchdog?.kill("SIGKILL");
|
|
352
|
+
this.watchdog = null;
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
async function endGroup(child) {
|
|
356
|
+
if (child.pid === void 0) return;
|
|
357
|
+
const leaderReaped = reaped(child);
|
|
358
|
+
signalGroup(child, "SIGTERM");
|
|
359
|
+
if (!await groupEnded(-child.pid, 2e3)) signalGroup(child, "SIGKILL");
|
|
360
|
+
await leaderReaped;
|
|
361
|
+
}
|
|
362
|
+
//#endregion
|
|
363
|
+
export { signalGroup as a, SIGNALS_GROUPS as i, serve_exports as n, KILL_GRACE_MS as r, ServedProcess as t };
|
|
364
|
+
|
|
365
|
+
//# sourceMappingURL=shared-serve.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/services/containers/service-images.ts
|
|
2
|
+
/** Pinned by the digest of the multi-platform index: a tag moves under the suite. Bumped by hand. */
|
|
3
|
+
const SERVICE_IMAGES = {
|
|
4
|
+
postgres: "postgres:17-alpine@sha256:b0f9560a2de083e2cc7382e75f808c7381a32852a7ec49117deedb300e552b24",
|
|
5
|
+
redis: "redis:7-alpine@sha256:858f009f9709ce576febc734aa78b8f6d624b82571f9ddb6bda4377c833b3499"
|
|
6
|
+
};
|
|
7
|
+
//#endregion
|
|
8
|
+
export { SERVICE_IMAGES as t };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=shared-service-images.js.map
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { n as refusal } from "./shared-refusal.js";
|
|
2
|
+
import { a as field, i as RunContainers, o as serveContainers, r as TestcontainersAdapter, t as OWNER_LABEL } from "./shared-testcontainers.adapter.js";
|
|
3
|
+
import { t as loadPeer } from "./shared-peer.js";
|
|
4
|
+
import { t as SERVICE_IMAGES } from "./shared-service-images.js";
|
|
5
|
+
import { t as setupEntry } from "./shared-setup-entry.js";
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { hostname } from "node:os";
|
|
9
|
+
//#region src/services/containers/orphans.ts
|
|
10
|
+
const SESSION_LABEL = "org.testcontainers.session-id";
|
|
11
|
+
const REAPER_LABEL = "org.testcontainers.ryuk";
|
|
12
|
+
const ENDED = /* @__PURE__ */ new Set(["dead", "exited"]);
|
|
13
|
+
/** Dead only when it names this host and no process answers its pid; another host's process is never taken for dead. */
|
|
14
|
+
function ownerAlive(owner, host = hostname(), probe = (pid) => process.kill(pid, 0)) {
|
|
15
|
+
const separator = owner.lastIndexOf(":");
|
|
16
|
+
const pid = Number(owner.slice(separator + 1));
|
|
17
|
+
if (separator === -1 || owner.slice(0, separator) !== host || !Number.isSafeInteger(pid) || pid <= 0) return true;
|
|
18
|
+
try {
|
|
19
|
+
probe(pid);
|
|
20
|
+
return true;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
return Reflect.get(new Object(error), "code") !== "ESRCH";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function orphans(containers, alive) {
|
|
26
|
+
const reaped = new Set(containers.filter((container) => container.State === "running" && container.Labels[REAPER_LABEL] === "true").map((reaper) => reaper.Labels[SESSION_LABEL]));
|
|
27
|
+
return containers.filter((container) => {
|
|
28
|
+
if (container.Labels[REAPER_LABEL] === "true") return false;
|
|
29
|
+
const owner = container.Labels[OWNER_LABEL];
|
|
30
|
+
if (owner !== void 0 && container.Labels["com.jterrazz.test.reuse"] !== "true" && !alive(owner)) return true;
|
|
31
|
+
const session = container.Labels[SESSION_LABEL];
|
|
32
|
+
return session !== void 0 && session !== "" && ENDED.has(container.State) && !reaped.has(session);
|
|
33
|
+
}).map((container) => container.Id);
|
|
34
|
+
}
|
|
35
|
+
/** A removal that fails is skipped: a sweep never fails the run. */
|
|
36
|
+
async function sweepOrphans(runtime, alive = ownerAlive) {
|
|
37
|
+
const lists = await Promise.all([SESSION_LABEL, OWNER_LABEL].map(async (label) => await runtime.listContainers({
|
|
38
|
+
all: true,
|
|
39
|
+
filters: { label: [label] }
|
|
40
|
+
})));
|
|
41
|
+
const found = orphans([...new Map(lists.flat().map((container) => [container.Id, container])).values()], alive);
|
|
42
|
+
return (await Promise.allSettled(found.map(async (id) => {
|
|
43
|
+
await runtime.getContainer(id).remove({
|
|
44
|
+
force: true,
|
|
45
|
+
v: true
|
|
46
|
+
});
|
|
47
|
+
return id;
|
|
48
|
+
}))).flatMap((outcome) => outcome.status === "fulfilled" ? [outcome.value] : []);
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/services/containers/services.global-setup.ts
|
|
52
|
+
/** Every project's setup joins the one run's containers. Default images are pulled in the background, so the first file waits on a pull at most once; a project declaring no service needs no Docker. */
|
|
53
|
+
const DOCKER_ABSENT = "no Docker daemon answered";
|
|
54
|
+
/** `connect` stands in for the runtime's own client lookup, so the refusal is reachable without a daemon to stop. */
|
|
55
|
+
async function prepareDocker(connect) {
|
|
56
|
+
const { getContainerRuntimeClient, ImageName } = await loadPeer("testcontainers", "a declared service", async () => await import("testcontainers"));
|
|
57
|
+
let client;
|
|
58
|
+
try {
|
|
59
|
+
client = await (connect ?? getContainerRuntimeClient)();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
62
|
+
throw new Error(refusal({
|
|
63
|
+
fix: "start Docker (Docker Desktop, OrbStack, colima or another) and run again",
|
|
64
|
+
found: `a declared service asked for Docker, and ${DOCKER_ABSENT} (${reason})`,
|
|
65
|
+
read: "docs/reference/services.md#one-container-per-run",
|
|
66
|
+
why: "A declared service runs in a container, so a project that declares one needs a Docker daemon."
|
|
67
|
+
}), { cause: error });
|
|
68
|
+
}
|
|
69
|
+
await Promise.all([sweepOrphans(client.container.dockerode).catch(() => []), ...Object.values(SERVICE_IMAGES).map(async (image) => {
|
|
70
|
+
const name = ImageName.fromString(image);
|
|
71
|
+
if (await client.image.exists(name)) return;
|
|
72
|
+
try {
|
|
73
|
+
await client.image.pull(name);
|
|
74
|
+
} catch {}
|
|
75
|
+
})]);
|
|
76
|
+
}
|
|
77
|
+
function servicesSetup() {
|
|
78
|
+
return setupEntry(import.meta.dirname, "services");
|
|
79
|
+
}
|
|
80
|
+
function packageVersion() {
|
|
81
|
+
let dir = import.meta.dirname;
|
|
82
|
+
while (!existsSync(join(dir, "package.json"))) {
|
|
83
|
+
const parent = dirname(dir);
|
|
84
|
+
if (parent === dir) return "unversioned";
|
|
85
|
+
dir = parent;
|
|
86
|
+
}
|
|
87
|
+
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
88
|
+
return String(field(manifest, "version"));
|
|
89
|
+
}
|
|
90
|
+
const RUN_SERVICES = Symbol.for(`@jterrazz/test.run-services@${packageVersion()}`);
|
|
91
|
+
function settled(prepare) {
|
|
92
|
+
const prepared = (async () => {
|
|
93
|
+
try {
|
|
94
|
+
await prepare();
|
|
95
|
+
return null;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return error instanceof Error ? error : new Error(refusal({
|
|
98
|
+
fix: "restart the Docker runtime, or reinstall the `testcontainers` peer",
|
|
99
|
+
found: `preparing Docker threw ${String(error)}, which is no Error`,
|
|
100
|
+
read: "docs/start/set-up-a-project.md#what-to-install",
|
|
101
|
+
why: "A service starts only on a Docker runtime that answers, through the peer that drives it."
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
})();
|
|
105
|
+
return async () => {
|
|
106
|
+
const refused = await prepared;
|
|
107
|
+
if (refused !== null) throw refused;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const DOCKER = {
|
|
111
|
+
create: (request) => new TestcontainersAdapter(request),
|
|
112
|
+
prepare: prepareDocker,
|
|
113
|
+
serve: serveContainers
|
|
114
|
+
};
|
|
115
|
+
async function open({ create, prepare, serve }) {
|
|
116
|
+
const containers = new RunContainers(create, settled(prepare));
|
|
117
|
+
const served = await serve(containers);
|
|
118
|
+
return {
|
|
119
|
+
close: async () => {
|
|
120
|
+
await served.close();
|
|
121
|
+
await containers.stop();
|
|
122
|
+
},
|
|
123
|
+
url: served.url
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function runServices(seams) {
|
|
127
|
+
let holders = 0;
|
|
128
|
+
let opened = null;
|
|
129
|
+
const forget = () => {
|
|
130
|
+
opened = null;
|
|
131
|
+
Reflect.deleteProperty(globalThis, RUN_SERVICES);
|
|
132
|
+
};
|
|
133
|
+
return { enter: async () => {
|
|
134
|
+
opened ??= open(seams);
|
|
135
|
+
const current = opened;
|
|
136
|
+
let served;
|
|
137
|
+
try {
|
|
138
|
+
served = await current;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
forget();
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
holders += 1;
|
|
144
|
+
let left = false;
|
|
145
|
+
return {
|
|
146
|
+
leave: async () => {
|
|
147
|
+
if (left) return;
|
|
148
|
+
left = true;
|
|
149
|
+
holders -= 1;
|
|
150
|
+
if (holders > 0) return;
|
|
151
|
+
forget();
|
|
152
|
+
await served.close();
|
|
153
|
+
},
|
|
154
|
+
url: served.url
|
|
155
|
+
};
|
|
156
|
+
} };
|
|
157
|
+
}
|
|
158
|
+
function isRunServices(value) {
|
|
159
|
+
return typeof field(value, "enter") === "function";
|
|
160
|
+
}
|
|
161
|
+
function theRun(seams) {
|
|
162
|
+
const found = Reflect.get(globalThis, RUN_SERVICES);
|
|
163
|
+
if (isRunServices(found)) return found;
|
|
164
|
+
const created = runServices(seams);
|
|
165
|
+
Reflect.set(globalThis, RUN_SERVICES, created);
|
|
166
|
+
return created;
|
|
167
|
+
}
|
|
168
|
+
async function setup(project, seams = {}) {
|
|
169
|
+
const joined = await theRun({
|
|
170
|
+
...DOCKER,
|
|
171
|
+
...seams
|
|
172
|
+
}).enter();
|
|
173
|
+
project.provide("serviceContainers", joined.url);
|
|
174
|
+
return joined.leave;
|
|
175
|
+
}
|
|
176
|
+
//#endregion
|
|
177
|
+
export { setup as i, prepareDocker as n, servicesSetup as r, DOCKER_ABSENT as t };
|
|
178
|
+
|
|
179
|
+
//# sourceMappingURL=shared-services.global-setup.js.map
|