@jterrazz/test 17.0.0 → 18.0.1
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/dist/browser/index.js +1 -2
- package/dist/browser/services.js +1 -2
- package/dist/browser/stubs.js +1 -2
- package/dist/{shared-index.browser.js → browser-shared-index.browser.js} +82 -40
- package/dist/checker.js +15 -824
- package/dist/in-place.global-setup.d.ts +1 -0
- package/dist/in-place.global-setup.js +1 -1
- package/dist/index.d.ts +2 -14
- package/dist/index.js +83 -204
- package/dist/mobile-session.global-setup.d.ts +1 -0
- package/dist/oxlint.cjs +2 -2
- package/dist/oxlint.js +67 -2321
- package/dist/route-coverage.global-setup.d.ts +1 -0
- package/dist/route-coverage.global-setup.js +1 -2
- package/dist/services.global-setup.d.ts +1 -1
- package/dist/services.global-setup.js +1 -2
- package/dist/services.js +1 -1
- package/dist/shared-appium.adapter.js +14 -14
- package/dist/shared-artifacts.js +7 -5
- package/dist/shared-derived-projects.js +23 -8
- package/dist/shared-in-place.js +1 -1
- package/dist/shared-ledger.js +1 -1
- package/dist/shared-listened-port.js +19 -0
- package/dist/shared-literate-plugin.js +2 -1
- package/dist/shared-pause.js +12 -1
- package/dist/shared-playwright.adapter.js +17 -19
- package/dist/shared-process-alive.js +13 -0
- package/dist/shared-record-key.js +13 -0
- package/dist/shared-serve.js +180 -69
- package/dist/shared-services.global-setup.js +4 -9
- package/dist/shared-session-ledger.js +10 -6
- package/dist/shared-spec-document.js +2 -64
- package/dist/shared-spec-grammar.js +66 -0
- package/dist/shared-specs-modules.js +1033 -0
- package/dist/{shared-stream-body.js → shared-stub-reply.js} +40 -3
- package/dist/shared-stub-server.js +30 -78
- package/dist/shared-temp-root.global-setup.js +3 -10
- package/dist/shared-testcontainers.adapter.js +111 -8
- package/dist/temp-root.global-setup.js +1 -2
- package/dist/vitest.d.ts +17 -22
- package/dist/vitest.js +37 -53
- package/docs/concepts/architecture.md +20 -20
- package/docs/concepts/conventions.md +2 -2
- package/docs/guides/cli-specs.md +2 -0
- package/docs/guides/develop.md +20 -17
- package/docs/guides/mobile-specs.md +3 -3
- package/docs/guides/release.md +4 -3
- package/docs/guides/website-specs.md +1 -1
- package/docs/reference/linting.md +2 -2
- package/docs/reference/services.md +13 -9
- package/docs/reference/stubs.md +4 -2
- package/docs/start/set-up-a-project.md +31 -17
- package/package.json +10 -10
|
@@ -39,4 +39,5 @@ export declare function routeCoverageSetup(): string;
|
|
|
39
39
|
export declare function setupRoutes(project: RouteProject): () => Promise<void>;
|
|
40
40
|
export default function setup(project: TestProject): () => Promise<void>;
|
|
41
41
|
//#endregion
|
|
42
|
+
export {};
|
|
42
43
|
//# sourceMappingURL=route-coverage.global-setup.d.ts.map
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { setup as default, routeCoverageSetup, setupRoutes };
|
|
1
|
+
export { n as default, t as routeCoverageSetup, r as setupRoutes } from "./shared-route-coverage.global-setup.js";
|
|
@@ -26,7 +26,6 @@ declare function serveContainers(containers: RunContainers): Promise<ServedConta
|
|
|
26
26
|
//#endregion
|
|
27
27
|
//#region src/services/containers/services.global-setup.d.ts
|
|
28
28
|
/** 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. */
|
|
29
|
-
export declare const DOCKER_ABSENT = "no Docker daemon answered";
|
|
30
29
|
/** `connect` stands in for the runtime's own client lookup, so the refusal is reachable without a daemon to stop. */
|
|
31
30
|
export declare function prepareDocker(connect?: () => Promise<ContainerRuntimeClient>): Promise<void>;
|
|
32
31
|
export declare function servicesSetup(): string;
|
|
@@ -40,4 +39,5 @@ type RunSeams = {
|
|
|
40
39
|
};
|
|
41
40
|
export default function setup(project: RunProject, seams?: Partial<RunSeams>): Promise<() => Promise<void>>;
|
|
42
41
|
//#endregion
|
|
42
|
+
export {};
|
|
43
43
|
//# sourceMappingURL=services.global-setup.d.ts.map
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { DOCKER_ABSENT, setup as default, prepareDocker, servicesSetup };
|
|
1
|
+
export { r as default, t as prepareDocker, n as servicesSetup } from "./shared-services.global-setup.js";
|
package/dist/services.js
CHANGED
|
@@ -3,7 +3,7 @@ import { n as refusal, t as invariant } from "./shared-refusal.js";
|
|
|
3
3
|
import { t as pause } from "./shared-pause.js";
|
|
4
4
|
import { n as processService, t as ProcessHandle } from "./shared-process.adapter.js";
|
|
5
5
|
import { n as requireBuiltPeer, t as loadPeer } from "./shared-peer.js";
|
|
6
|
-
import {
|
|
6
|
+
import { l as SQLITE_TEMPLATE_DIR } from "./shared-artifacts.js";
|
|
7
7
|
import { t as SERVICE_IMAGES } from "./shared-service-images.js";
|
|
8
8
|
import { closeSync, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
9
9
|
import { join, resolve } from "node:path";
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { n as refusal } from "./shared-refusal.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { n as untilDeadline } from "./shared-pause.js";
|
|
3
|
+
import { d as evidenceFile } from "./shared-artifacts.js";
|
|
4
4
|
import { n as SERVER_CALL_TIMEOUT_MS } from "./shared-session-ledger.js";
|
|
5
5
|
import { c as scopeChain } from "./shared-landmarks.js";
|
|
6
6
|
import { a as ambiguityError, c as asWrittenUnscoped, i as shortened, s as asWritten, t as scenarioFailure } from "./shared-scenario-failure.js";
|
|
7
|
-
import {
|
|
8
|
-
import { resolve } from "node:path";
|
|
7
|
+
import { writeFileSync } from "node:fs";
|
|
9
8
|
//#region src/specifications/mobile/session/driver-session.ts
|
|
10
9
|
const IDLE_SESSION_BUDGET_S = 300;
|
|
11
10
|
const IOS_PREFIX = /^ios\s+/iu;
|
|
@@ -355,15 +354,17 @@ var AppiumAdapter = class {
|
|
|
355
354
|
async resolveOne(driver, target, verb) {
|
|
356
355
|
const chain = scopeChain(target);
|
|
357
356
|
const anyTarget = verb === "see";
|
|
358
|
-
const deadline = Date.now() + this.actionTimeoutMs;
|
|
359
357
|
let tryScroll = true;
|
|
360
|
-
|
|
361
|
-
const
|
|
358
|
+
const resolved = await untilDeadline(async () => {
|
|
359
|
+
const found = await this.resolveChain(driver, chain, anyTarget, tryScroll);
|
|
362
360
|
tryScroll = false;
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
361
|
+
return found ?? void 0;
|
|
362
|
+
}, {
|
|
363
|
+
interval: POLL_INTERVAL_MS,
|
|
364
|
+
timeout: this.actionTimeoutMs
|
|
365
|
+
});
|
|
366
|
+
if (resolved === void 0) throw await this.timeoutError(driver, target, verb);
|
|
367
|
+
return resolved;
|
|
367
368
|
}
|
|
368
369
|
async resolveChain(driver, chain, anyTarget, tryScroll) {
|
|
369
370
|
let scope = driver;
|
|
@@ -385,11 +386,10 @@ var AppiumAdapter = class {
|
|
|
385
386
|
}
|
|
386
387
|
return element;
|
|
387
388
|
}
|
|
388
|
-
/** Screenshot the failing state into
|
|
389
|
+
/** Screenshot the failing state into the evidence folder; never masks the original error. */
|
|
389
390
|
async captureEvidence(driver) {
|
|
390
391
|
try {
|
|
391
|
-
const
|
|
392
|
-
const path = resolve(dir, "failure.png");
|
|
392
|
+
const path = evidenceFile("mobile");
|
|
393
393
|
writeFileSync(path, Buffer.from(await driver.takeScreenshot(), "base64"));
|
|
394
394
|
return path;
|
|
395
395
|
} catch {
|
package/dist/shared-artifacts.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, mkdtempSync } from "node:fs";
|
|
2
2
|
import { basename, join, resolve } from "node:path";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
//#region lint/kit/project/artifacts.ts
|
|
@@ -11,14 +11,16 @@ const SQLITE_TEMPLATE_DIR = `${VITEST_ARTIFACTS_DIR}/sqlite`;
|
|
|
11
11
|
const ATTACHMENTS_DIR = `${VITEST_ARTIFACTS_DIR}/attachments`;
|
|
12
12
|
/** Under the project: the run's temp root is gone by the time the error naming the file is read. */
|
|
13
13
|
const EVIDENCE_DIR = `${VITEST_ARTIFACTS_DIR}/evidence`;
|
|
14
|
-
/**
|
|
15
|
-
function
|
|
14
|
+
/** Where a failing scenario's screenshot goes, in a folder of its own filed under the run's temp root name, `<pid>-...`: the next run's start sweeps every run that is no longer alive. */
|
|
15
|
+
function evidenceFile(kind) {
|
|
16
16
|
const parent = resolve(EVIDENCE_DIR, basename(tmpdir()));
|
|
17
17
|
mkdirSync(parent, { recursive: true });
|
|
18
|
-
return join(parent,
|
|
18
|
+
return join(mkdtempSync(join(parent, `${kind}-`)), "failure.png");
|
|
19
19
|
}
|
|
20
20
|
const SCREENSHOTS_DIR = `${VITEST_ARTIFACTS_DIR}/screenshots`;
|
|
21
21
|
const BLOB_DIR = `${VITEST_ARTIFACTS_DIR}/blob`;
|
|
22
|
+
/** Node's compile cache, which every Node process of a run reads and writes: keyed by content, so an edit is never served old code. */
|
|
23
|
+
const NODE_COMPILE_CACHE_DIR = `${ARTIFACTS_DIR}/node-compile-cache`;
|
|
22
24
|
/** Vitest 5's file-writing reporters, each pinned under the artifacts folder. */
|
|
23
25
|
const REPORTER_OUTPUT_FILES = {
|
|
24
26
|
blob: `${BLOB_DIR}/blob.json`,
|
|
@@ -27,6 +29,6 @@ const REPORTER_OUTPUT_FILES = {
|
|
|
27
29
|
junit: `${VITEST_ARTIFACTS_DIR}/junit/output.xml`
|
|
28
30
|
};
|
|
29
31
|
//#endregion
|
|
30
|
-
export { EVIDENCE_DIR as a,
|
|
32
|
+
export { EVIDENCE_DIR as a, SCREENSHOTS_DIR as c, evidenceFile as d, COVERAGE_DIR as i, SQLITE_TEMPLATE_DIR as l, ATTACHMENTS_DIR as n, NODE_COMPILE_CACHE_DIR as o, BLOB_DIR as r, REPORTER_OUTPUT_FILES as s, ARTIFACTS_DIR as t, VITEST_ARTIFACTS_DIR as u };
|
|
31
33
|
|
|
32
34
|
//# sourceMappingURL=shared-artifacts.js.map
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { t as SPECIFICATIONS } from "./shared-specifications.js";
|
|
2
2
|
import { TESTS_FOLDER } from "@jterrazz/typescript/rules";
|
|
3
|
-
import { existsSync, readdirSync } from "node:fs";
|
|
3
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
4
4
|
import { basename, join } from "node:path";
|
|
5
5
|
//#region lint/kit/project/derived-projects.ts
|
|
6
6
|
const SPECS = "specs";
|
|
7
7
|
const UNIT = "unit";
|
|
8
8
|
const FOLDERED = /* @__PURE__ */ new Set([...SPECIFICATIONS, "component"]);
|
|
9
|
-
const
|
|
9
|
+
const RUNNER_SHARED = /* @__PURE__ */ new Set([
|
|
10
10
|
"api",
|
|
11
|
+
"cli",
|
|
11
12
|
"integration",
|
|
12
|
-
"job"
|
|
13
|
+
"job",
|
|
14
|
+
"website"
|
|
13
15
|
]);
|
|
14
16
|
const SPECIFICATION_MODULE = ".specification.ts";
|
|
15
17
|
const MODULE_TEST = /\.test\.ts$/u;
|
|
@@ -23,14 +25,27 @@ function holds(path, pattern) {
|
|
|
23
25
|
});
|
|
24
26
|
}
|
|
25
27
|
const foldersIn = (path) => existsSync(path) ? readdirSync(path, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !isUnwalked(entry.name)).map(({ name }) => name).toSorted() : [];
|
|
28
|
+
function inImportOrder(directory, modules) {
|
|
29
|
+
const ordered = [];
|
|
30
|
+
const seen = /* @__PURE__ */ new Set();
|
|
31
|
+
const visit = (module) => {
|
|
32
|
+
if (seen.has(module)) return;
|
|
33
|
+
seen.add(module);
|
|
34
|
+
const text = readFileSync(join(directory, module), "utf8");
|
|
35
|
+
for (const imported of modules.filter((other) => text.includes(`'./${other.slice(0, -2)}js'`))) visit(imported);
|
|
36
|
+
ordered.push(module);
|
|
37
|
+
};
|
|
38
|
+
for (const module of modules) visit(module);
|
|
39
|
+
return ordered;
|
|
40
|
+
}
|
|
26
41
|
function runnerOf(root, folder, specification) {
|
|
27
|
-
if (!
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
return modules.length ===
|
|
42
|
+
if (!RUNNER_SHARED.has(specification)) return {};
|
|
43
|
+
const directory = join(root, folder);
|
|
44
|
+
const modules = readdirSync(directory).filter((name) => name.endsWith(SPECIFICATION_MODULE)).toSorted();
|
|
45
|
+
return modules.length === 0 ? {} : { runner: inImportOrder(directory, modules).map((name) => `./${folder}/${name}`) };
|
|
31
46
|
}
|
|
32
47
|
const isSpecsMember = (root) => basename(root) === SPECS && existsSync(join(root, "package.json"));
|
|
33
|
-
/** The projects a tree names, each with the options it derives: one per `specs/<specification>/` folder (
|
|
48
|
+
/** The projects a tree names, each with the options it derives: one per `specs/<specification>/` folder (one whose spec files meet their subject through a runner handed as its `runner` every specification module of the folder, `*.specification.ts`, a module after those it imports), `unit` over each root holding a module test (`specs/unit/` is one root among them), `component` over each holding a rendered one. */
|
|
34
49
|
function derivedProjects(root) {
|
|
35
50
|
const member = isSpecsMember(root);
|
|
36
51
|
const prefix = member ? "" : `${SPECS}/`;
|
package/dist/shared-in-place.js
CHANGED
|
@@ -167,6 +167,6 @@ function writeInPlace(site, render, folder = pendingFolder?.()) {
|
|
|
167
167
|
save(site.file, source, folder);
|
|
168
168
|
}
|
|
169
169
|
//#endregion
|
|
170
|
-
export {
|
|
170
|
+
export { getCallerDir as a, writePendingRewrites as i, deferRewritesTo as n, writeInPlace as r, callSiteOf as t };
|
|
171
171
|
|
|
172
172
|
//# sourceMappingURL=shared-in-place.js.map
|
package/dist/shared-ledger.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as isRecord } from "./shared-json-shape.js";
|
|
2
|
-
import {
|
|
2
|
+
import { u as VITEST_ARTIFACTS_DIR } from "./shared-artifacts.js";
|
|
3
3
|
import { mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { t as invariant } from "./shared-refusal.js";
|
|
2
|
+
//#region src/chain/listened-port.ts
|
|
3
|
+
/** Listens, on `host` when one is named, and answers the port it got; a port another server holds rejects with its error. */
|
|
4
|
+
async function listenedPort(server, { host, port = 0 } = {}) {
|
|
5
|
+
await new Promise((resolve, reject) => {
|
|
6
|
+
server.once("error", reject);
|
|
7
|
+
server.listen(port, host, () => {
|
|
8
|
+
server.off("error", reject);
|
|
9
|
+
resolve();
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
const address = server.address();
|
|
13
|
+
if (address === null || typeof address === "string") throw invariant("a server listening on a TCP port answered no port");
|
|
14
|
+
return address.port;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
export { listenedPort as t };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=shared-listened-port.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { n as refusal } from "./shared-refusal.js";
|
|
2
|
-
import {
|
|
2
|
+
import { c as isRelativePath, s as SpecSyntaxError } from "./shared-spec-grammar.js";
|
|
3
|
+
import { i as readSpecFile, o as dedent } from "./shared-spec-document.js";
|
|
3
4
|
import { readFileSync } from "node:fs";
|
|
4
5
|
import { basename, isAbsolute, resolve } from "node:path";
|
|
5
6
|
//#region src/disk/spec-path.ts
|
package/dist/shared-pause.js
CHANGED
|
@@ -5,7 +5,18 @@ async function pause(ms) {
|
|
|
5
5
|
setTimeout(resolve, ms);
|
|
6
6
|
});
|
|
7
7
|
}
|
|
8
|
+
/** Asks `probe` until it answers something other than `undefined`, which it hands back, or answers `undefined` once `timeout` has passed. Measured on `performance.now()`, so `clock.at()`, which fakes `Date`, cannot freeze it. */
|
|
9
|
+
async function untilDeadline(probe, { interval = 0, timeout, wait }) {
|
|
10
|
+
const deadline = performance.now() + timeout;
|
|
11
|
+
for (;;) {
|
|
12
|
+
const answer = await probe();
|
|
13
|
+
if (answer !== void 0) return answer;
|
|
14
|
+
const remaining = deadline - performance.now();
|
|
15
|
+
if (remaining <= 0) return;
|
|
16
|
+
await (wait === void 0 ? pause(interval) : wait(remaining));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
8
19
|
//#endregion
|
|
9
|
-
export { pause as t };
|
|
20
|
+
export { untilDeadline as n, pause as t };
|
|
10
21
|
|
|
11
22
|
//# sourceMappingURL=shared-pause.js.map
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { n as refusal } from "./shared-refusal.js";
|
|
2
|
-
import {
|
|
2
|
+
import { n as untilDeadline } from "./shared-pause.js";
|
|
3
|
+
import { d as evidenceFile } from "./shared-artifacts.js";
|
|
3
4
|
import { c as scopeChain } from "./shared-landmarks.js";
|
|
4
5
|
import { n as CANDIDATE_LIMITS, o as ambiguousTargetError, r as candidatesOf, s as asWritten, t as scenarioFailure } from "./shared-scenario-failure.js";
|
|
5
|
-
import { mkdtempSync } from "node:fs";
|
|
6
|
-
import { resolve } from "node:path";
|
|
7
|
-
import { setTimeout } from "node:timers/promises";
|
|
8
6
|
//#region src/specifications/website/browser/page-scripts.ts
|
|
9
7
|
function extractPage() {
|
|
10
8
|
const links = [...document.querySelectorAll("link")].map((link) => ({
|
|
@@ -135,20 +133,21 @@ async function currentValue(locator) {
|
|
|
135
133
|
return null;
|
|
136
134
|
}
|
|
137
135
|
}
|
|
138
|
-
async function waitForValue(locator, target, wanted) {
|
|
136
|
+
async function waitForValue(locator, target, wanted, timeout = VALUE_TIMEOUT_MS) {
|
|
139
137
|
const value = target.value ?? "";
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}))
|
|
150
|
-
|
|
151
|
-
|
|
138
|
+
let actual = null;
|
|
139
|
+
if (await untilDeadline(async () => {
|
|
140
|
+
actual = await currentValue(locator);
|
|
141
|
+
return actual === value === wanted || void 0;
|
|
142
|
+
}, {
|
|
143
|
+
interval: VALUE_POLL_MS,
|
|
144
|
+
timeout
|
|
145
|
+
}) === void 0) throw new Error(refusal({
|
|
146
|
+
fix: "correct the value the scenario states, or what the page puts in the field",
|
|
147
|
+
found: `${asWritten(target)} ${wanted ? "never held" : "still holds"} ${JSON.stringify(value)}${actual === null ? " (it is not on the page)" : `, it holds ${JSON.stringify(actual)}`}`,
|
|
148
|
+
read: "docs/reference/visitor.md#see-and-gone-are-the-synchronization",
|
|
149
|
+
why: "A verb waits for what it states to hold, within its budget, and never sleeps."
|
|
150
|
+
}));
|
|
152
151
|
}
|
|
153
152
|
async function settle(page, locator, target, present) {
|
|
154
153
|
if (target.value !== void 0) {
|
|
@@ -312,8 +311,7 @@ var PlaywrightAdapter = class {
|
|
|
312
311
|
/** Screenshot the failing state into the evidence folder; never masks the original error. */
|
|
313
312
|
async captureEvidence(page) {
|
|
314
313
|
try {
|
|
315
|
-
const
|
|
316
|
-
const path = resolve(dir, "failure.png");
|
|
314
|
+
const path = evidenceFile("website");
|
|
317
315
|
await page.screenshot({
|
|
318
316
|
fullPage: true,
|
|
319
317
|
path
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/chain/process-alive.ts
|
|
2
|
+
function processAlive(pid, probe = (target) => process.kill(target, 0)) {
|
|
3
|
+
try {
|
|
4
|
+
probe(pid);
|
|
5
|
+
return true;
|
|
6
|
+
} catch (error) {
|
|
7
|
+
return Reflect.get(new Object(error), "code") !== "ESRCH";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { processAlive as t };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=shared-process-alive.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region lint/kit/project/record-key.ts
|
|
2
|
+
/** `analyticsDb` gives `analytics-db`. */
|
|
3
|
+
function toKebabCase(key) {
|
|
4
|
+
return key.replaceAll(/(?<lower>[a-z0-9])(?<upper>[A-Z])/gu, "$<lower>-$<upper>").toLowerCase();
|
|
5
|
+
}
|
|
6
|
+
/** `analyticsDb` gives `ANALYTICS_DB`, `db-main` gives `DB_MAIN`. */
|
|
7
|
+
function toConstantCase(key) {
|
|
8
|
+
return key.replaceAll(/(?<lower>[a-z0-9])(?<upper>[A-Z])/gu, "$<lower>_$<upper>").replaceAll(/[^A-Za-z0-9]/gu, "_").toUpperCase();
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { toKebabCase as n, toConstantCase as t };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=shared-record-key.js.map
|