@plan2net/typo3-playwright-toolkit 0.1.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/LICENSE +338 -0
- package/README.md +404 -0
- package/dist/builders/content-builder.d.ts +30 -0
- package/dist/builders/content-builder.d.ts.map +1 -0
- package/dist/builders/content-builder.js +67 -0
- package/dist/builders/content-builder.js.map +1 -0
- package/dist/builders/content-factory.d.ts +10 -0
- package/dist/builders/content-factory.d.ts.map +1 -0
- package/dist/builders/content-factory.js +23 -0
- package/dist/builders/content-factory.js.map +1 -0
- package/dist/builders/core-content.d.ts +127 -0
- package/dist/builders/core-content.d.ts.map +1 -0
- package/dist/builders/core-content.js +223 -0
- package/dist/builders/core-content.js.map +1 -0
- package/dist/builders/fields.d.ts +7 -0
- package/dist/builders/fields.d.ts.map +1 -0
- package/dist/builders/fields.js +20 -0
- package/dist/builders/fields.js.map +1 -0
- package/dist/builders/identifier.d.ts +2 -0
- package/dist/builders/identifier.d.ts.map +1 -0
- package/dist/builders/identifier.js +11 -0
- package/dist/builders/identifier.js.map +1 -0
- package/dist/builders/page-builder.d.ts +40 -0
- package/dist/builders/page-builder.d.ts.map +1 -0
- package/dist/builders/page-builder.js +119 -0
- package/dist/builders/page-builder.js.map +1 -0
- package/dist/builders/request-context.d.ts +23 -0
- package/dist/builders/request-context.d.ts.map +1 -0
- package/dist/builders/request-context.js +32 -0
- package/dist/builders/request-context.js.map +1 -0
- package/dist/checks/accessibility.d.ts +34 -0
- package/dist/checks/accessibility.d.ts.map +1 -0
- package/dist/checks/accessibility.js +90 -0
- package/dist/checks/accessibility.js.map +1 -0
- package/dist/checks/csp.d.ts +39 -0
- package/dist/checks/csp.d.ts.map +1 -0
- package/dist/checks/csp.js +179 -0
- package/dist/checks/csp.js.map +1 -0
- package/dist/checks/screenshot.d.ts +18 -0
- package/dist/checks/screenshot.d.ts.map +1 -0
- package/dist/checks/screenshot.js +125 -0
- package/dist/checks/screenshot.js.map +1 -0
- package/dist/config.d.ts +100 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +68 -0
- package/dist/config.js.map +1 -0
- package/dist/contract.d.ts +9 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +25 -0
- package/dist/contract.js.map +1 -0
- package/dist/global-setup.d.ts +20 -0
- package/dist/global-setup.d.ts.map +1 -0
- package/dist/global-setup.js +103 -0
- package/dist/global-setup.js.map +1 -0
- package/dist/global-teardown.d.ts +34 -0
- package/dist/global-teardown.d.ts.map +1 -0
- package/dist/global-teardown.js +269 -0
- package/dist/global-teardown.js.map +1 -0
- package/dist/http/api-secret.d.ts +11 -0
- package/dist/http/api-secret.d.ts.map +1 -0
- package/dist/http/api-secret.js +27 -0
- package/dist/http/api-secret.js.map +1 -0
- package/dist/http/cleanup-client.d.ts +26 -0
- package/dist/http/cleanup-client.d.ts.map +1 -0
- package/dist/http/cleanup-client.js +91 -0
- package/dist/http/cleanup-client.js.map +1 -0
- package/dist/http/off-site-headers.d.ts +13 -0
- package/dist/http/off-site-headers.d.ts.map +1 -0
- package/dist/http/off-site-headers.js +27 -0
- package/dist/http/off-site-headers.js.map +1 -0
- package/dist/http/prepare-context.d.ts +4 -0
- package/dist/http/prepare-context.d.ts.map +1 -0
- package/dist/http/prepare-context.js +6 -0
- package/dist/http/prepare-context.js.map +1 -0
- package/dist/http/record-edit.d.ts +37 -0
- package/dist/http/record-edit.d.ts.map +1 -0
- package/dist/http/record-edit.js +86 -0
- package/dist/http/record-edit.js.map +1 -0
- package/dist/http/toolkit-request.d.ts +8 -0
- package/dist/http/toolkit-request.d.ts.map +1 -0
- package/dist/http/toolkit-request.js +45 -0
- package/dist/http/toolkit-request.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect/cli.d.ts +3 -0
- package/dist/inspect/cli.d.ts.map +1 -0
- package/dist/inspect/cli.js +36 -0
- package/dist/inspect/cli.js.map +1 -0
- package/dist/inspect/links.d.ts +10 -0
- package/dist/inspect/links.d.ts.map +1 -0
- package/dist/inspect/links.js +51 -0
- package/dist/inspect/links.js.map +1 -0
- package/dist/inspect/token.d.ts +5 -0
- package/dist/inspect/token.d.ts.map +1 -0
- package/dist/inspect/token.js +15 -0
- package/dist/inspect/token.js.map +1 -0
- package/dist/playwright/base-config.d.ts +4 -0
- package/dist/playwright/base-config.d.ts.map +1 -0
- package/dist/playwright/base-config.js +34 -0
- package/dist/playwright/base-config.js.map +1 -0
- package/dist/playwright/index.d.ts +3 -0
- package/dist/playwright/index.d.ts.map +1 -0
- package/dist/playwright/index.js +3 -0
- package/dist/playwright/index.js.map +1 -0
- package/dist/scenario.d.ts +46 -0
- package/dist/scenario.d.ts.map +1 -0
- package/dist/scenario.js +139 -0
- package/dist/scenario.js.map +1 -0
- package/dist/state/attempt-claim.d.ts +8 -0
- package/dist/state/attempt-claim.d.ts.map +1 -0
- package/dist/state/attempt-claim.js +47 -0
- package/dist/state/attempt-claim.js.map +1 -0
- package/dist/state/attempt-registry.d.ts +36 -0
- package/dist/state/attempt-registry.d.ts.map +1 -0
- package/dist/state/attempt-registry.js +68 -0
- package/dist/state/attempt-registry.js.map +1 -0
- package/dist/state/ensure-state.d.ts +42 -0
- package/dist/state/ensure-state.d.ts.map +1 -0
- package/dist/state/ensure-state.js +169 -0
- package/dist/state/ensure-state.js.map +1 -0
- package/dist/state/json-safe.d.ts +9 -0
- package/dist/state/json-safe.d.ts.map +1 -0
- package/dist/state/json-safe.js +18 -0
- package/dist/state/json-safe.js.map +1 -0
- package/dist/state/run-id.d.ts +6 -0
- package/dist/state/run-id.d.ts.map +1 -0
- package/dist/state/run-id.js +28 -0
- package/dist/state/run-id.js.map +1 -0
- package/dist/state/run-namespace.d.ts +33 -0
- package/dist/state/run-namespace.d.ts.map +1 -0
- package/dist/state/run-namespace.js +136 -0
- package/dist/state/run-namespace.js.map +1 -0
- package/dist/state/safe-paths.d.ts +10 -0
- package/dist/state/safe-paths.d.ts.map +1 -0
- package/dist/state/safe-paths.js +56 -0
- package/dist/state/safe-paths.js.map +1 -0
- package/dist/state/scenario-outcome.d.ts +9 -0
- package/dist/state/scenario-outcome.d.ts.map +1 -0
- package/dist/state/scenario-outcome.js +23 -0
- package/dist/state/scenario-outcome.js.map +1 -0
- package/dist/state/scenario-state.d.ts +41 -0
- package/dist/state/scenario-state.d.ts.map +1 -0
- package/dist/state/scenario-state.js +50 -0
- package/dist/state/scenario-state.js.map +1 -0
- package/dist/state/setup-lock.d.ts +22 -0
- package/dist/state/setup-lock.d.ts.map +1 -0
- package/dist/state/setup-lock.js +90 -0
- package/dist/state/setup-lock.js.map +1 -0
- package/dist/types/common.d.ts +17 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +2 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/content-builder.d.ts +16 -0
- package/dist/types/content-builder.d.ts.map +1 -0
- package/dist/types/content-builder.js +2 -0
- package/dist/types/content-builder.js.map +1 -0
- package/dist/types/playwright-extensions.d.ts +8 -0
- package/dist/types/playwright-extensions.d.ts.map +1 -0
- package/dist/types/playwright-extensions.js +2 -0
- package/dist/types/playwright-extensions.js.map +1 -0
- package/package.json +88 -0
- package/tsconfig.base.json +13 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { resolveRunId, RUN_ID_PATTERN } from './run-id.js';
|
|
5
|
+
export function runsRoot(stateDir) {
|
|
6
|
+
return path.join(stateDir, 'runs');
|
|
7
|
+
}
|
|
8
|
+
export function runPaths(config) {
|
|
9
|
+
const runId = resolveRunId(config.runId);
|
|
10
|
+
const runDir = path.join(runsRoot(config.paths.stateDir), runId);
|
|
11
|
+
return {
|
|
12
|
+
runId,
|
|
13
|
+
runDir,
|
|
14
|
+
scenariosDir: path.join(runDir, 'scenarios'),
|
|
15
|
+
failuresDir: path.join(runDir, 'failures'),
|
|
16
|
+
locksDir: path.join(runDir, 'locks'),
|
|
17
|
+
attemptsFile: path.join(runDir, 'attempts.jsonl'),
|
|
18
|
+
metaFile: path.join(runDir, 'meta.json'),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function ensureRunNamespace(config) {
|
|
22
|
+
const paths = runPaths(config);
|
|
23
|
+
for (const dir of [paths.runDir, paths.scenariosDir, paths.failuresDir, paths.locksDir]) {
|
|
24
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
return paths;
|
|
27
|
+
}
|
|
28
|
+
/** How long after its last sign of life a run still counts as running. */
|
|
29
|
+
const OWNER_ACTIVE_MS = 30_000;
|
|
30
|
+
export function prepareRun(config) {
|
|
31
|
+
const paths = ensureRunNamespace(config);
|
|
32
|
+
assertNamespaceIsFree(paths);
|
|
33
|
+
if (!fs.existsSync(paths.metaFile)) {
|
|
34
|
+
fs.writeFileSync(paths.metaFile, JSON.stringify({
|
|
35
|
+
runId: paths.runId,
|
|
36
|
+
startedAt: new Date().toISOString(),
|
|
37
|
+
ownerPid: process.pid,
|
|
38
|
+
testingURL: config.testingURL,
|
|
39
|
+
}, null, 2));
|
|
40
|
+
}
|
|
41
|
+
return paths;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Two runs with the same run ID share their state and databases, and each teardown
|
|
45
|
+
* deletes the other's records. Reusing the ID of a run that has finished is fine,
|
|
46
|
+
* and is what pinning PW_RUN_ID is for.
|
|
47
|
+
*/
|
|
48
|
+
function assertNamespaceIsFree(paths) {
|
|
49
|
+
const owner = readOwnerPid(paths.metaFile);
|
|
50
|
+
if (undefined === owner || owner === process.pid) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const liveness = path.join(paths.runDir, LIVENESS_FILE);
|
|
54
|
+
let lastSign = 0;
|
|
55
|
+
try {
|
|
56
|
+
lastSign = fs.statSync(liveness).mtimeMs;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (Date.now() - lastSign < OWNER_ACTIVE_MS) {
|
|
62
|
+
throw new Error(`[typo3-playwright-toolkit] Run "${paths.runId}" is already in use by process ${owner}. ` +
|
|
63
|
+
'Two runs in one namespace overwrite each other\'s state and drop each other\'s ' +
|
|
64
|
+
'databases. Unset PW_RUN_ID, or give this run its own value.');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function readOwnerPid(metaFile) {
|
|
68
|
+
try {
|
|
69
|
+
const meta = JSON.parse(fs.readFileSync(metaFile, 'utf-8'));
|
|
70
|
+
return 'number' === typeof meta.ownerPid ? meta.ownerPid : undefined;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const SALT_FILE = 'salt';
|
|
77
|
+
/**
|
|
78
|
+
* The random value every test ID of this run is built from. Without it the IDs
|
|
79
|
+
* would follow from the run ID, and a run ID can be set by hand and guessed. A
|
|
80
|
+
* test ID is enough to reach that test's database.
|
|
81
|
+
*
|
|
82
|
+
* Written with 'wx', so the first worker creates it and the others read it.
|
|
83
|
+
*/
|
|
84
|
+
export function runSalt(config) {
|
|
85
|
+
const saltFile = path.join(ensureRunNamespace(config).runDir, SALT_FILE);
|
|
86
|
+
try {
|
|
87
|
+
fs.writeFileSync(saltFile, randomBytes(16).toString('hex'), { flag: 'wx' });
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Another worker created it first.
|
|
91
|
+
}
|
|
92
|
+
return fs.readFileSync(saltFile, 'utf-8').trim();
|
|
93
|
+
}
|
|
94
|
+
export const LIVENESS_FILE = 'liveness';
|
|
95
|
+
export function touchRunLiveness(runDir) {
|
|
96
|
+
try {
|
|
97
|
+
fs.writeFileSync(path.join(runDir, LIVENESS_FILE), String(Date.now()));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// The run may be getting torn down elsewhere.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* When the run was last active. Writing to a file inside a directory does not
|
|
105
|
+
* update the directory's own time, so a busy run would look idle.
|
|
106
|
+
*/
|
|
107
|
+
export function runLastActiveMs(runDir) {
|
|
108
|
+
const candidates = [runDir, path.join(runDir, LIVENESS_FILE), path.join(runDir, 'attempts.jsonl')];
|
|
109
|
+
let newest = 0;
|
|
110
|
+
for (const candidate of candidates) {
|
|
111
|
+
try {
|
|
112
|
+
newest = Math.max(newest, fs.statSync(candidate).mtimeMs);
|
|
113
|
+
}
|
|
114
|
+
catch { }
|
|
115
|
+
}
|
|
116
|
+
return newest;
|
|
117
|
+
}
|
|
118
|
+
export function listRunIds(stateDir) {
|
|
119
|
+
const root = runsRoot(stateDir);
|
|
120
|
+
if (!fs.existsSync(root)) {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
// These names get joined into paths we delete, so screen them.
|
|
124
|
+
return fs
|
|
125
|
+
.readdirSync(root, { withFileTypes: true })
|
|
126
|
+
.filter((entry) => entry.isDirectory())
|
|
127
|
+
.map((entry) => entry.name)
|
|
128
|
+
.filter((name) => RUN_ID_PATTERN.test(name));
|
|
129
|
+
}
|
|
130
|
+
/** The hash suffix keeps two paths apart when sanitizing collapses them. */
|
|
131
|
+
export function sanitizeScenarioKey(key) {
|
|
132
|
+
const base = key.replace(/[^A-Za-z0-9_-]/g, '_');
|
|
133
|
+
const digest = createHash('sha256').update(key).digest('hex').slice(0, 8);
|
|
134
|
+
return `${base}-${digest}`;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=run-namespace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-namespace.js","sourceRoot":"","sources":["../../src/state/run-namespace.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Teardown deletes everything inside the directories the consumer names, so a typo
|
|
3
|
+
* deletes whatever that path points at. Only accept a directory inside the project.
|
|
4
|
+
*/
|
|
5
|
+
export declare function assertDeletableDirectory(field: string, target: string, consumerRoot: string): void;
|
|
6
|
+
/** True for a name that can only be a file directly inside a directory. */
|
|
7
|
+
export declare function isPlainSegment(name: string): boolean;
|
|
8
|
+
/** Adds one name to `base`, and refuses anything that points outside it. */
|
|
9
|
+
export declare function safeJoin(base: string, segment: string): string;
|
|
10
|
+
//# sourceMappingURL=safe-paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safe-paths.d.ts","sourceRoot":"","sources":["../../src/state/safe-paths.ts"],"names":[],"mappings":"AAgCA;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAmBlG;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAQ9D"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Keeps the part of the path that does not exist yet, so a directory that has not
|
|
5
|
+
* been created is checked at the place it will be, not at its closest parent.
|
|
6
|
+
*/
|
|
7
|
+
function resolveThroughSymlinks(target) {
|
|
8
|
+
let current = path.resolve(target);
|
|
9
|
+
const missing = [];
|
|
10
|
+
for (;;) {
|
|
11
|
+
if (fs.existsSync(current)) {
|
|
12
|
+
return path.join(fs.realpathSync(current), ...missing.reverse());
|
|
13
|
+
}
|
|
14
|
+
const parent = path.dirname(current);
|
|
15
|
+
if (parent === current) {
|
|
16
|
+
return path.join(current, ...missing.reverse());
|
|
17
|
+
}
|
|
18
|
+
missing.push(path.basename(current));
|
|
19
|
+
current = parent;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function isStrictlyInside(candidate, container) {
|
|
23
|
+
const relative = path.relative(container, candidate);
|
|
24
|
+
return '' !== relative && !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Teardown deletes everything inside the directories the consumer names, so a typo
|
|
28
|
+
* deletes whatever that path points at. Only accept a directory inside the project.
|
|
29
|
+
*/
|
|
30
|
+
export function assertDeletableDirectory(field, target, consumerRoot) {
|
|
31
|
+
if (!path.isAbsolute(target)) {
|
|
32
|
+
throw new Error(`[typo3-playwright-toolkit] paths.${field} must be an absolute path, got "${target}". ` +
|
|
33
|
+
'Teardown deletes inside it, and a relative path resolves against whatever ' +
|
|
34
|
+
'directory the run happens to start in.');
|
|
35
|
+
}
|
|
36
|
+
const root = resolveThroughSymlinks(consumerRoot);
|
|
37
|
+
const resolved = resolveThroughSymlinks(target);
|
|
38
|
+
if (!isStrictlyInside(resolved, root)) {
|
|
39
|
+
throw new Error(`[typo3-playwright-toolkit] paths.${field} ("${target}") must be inside ` +
|
|
40
|
+
`paths.consumerRoot ("${consumerRoot}"). It resolves to "${resolved}", and teardown ` +
|
|
41
|
+
'deletes inside it, so pointing it outside the project risks unrelated files.');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** True for a name that can only be a file directly inside a directory. */
|
|
45
|
+
export function isPlainSegment(name) {
|
|
46
|
+
return '' !== name && name === path.basename(name) && '.' !== name && '..' !== name;
|
|
47
|
+
}
|
|
48
|
+
/** Adds one name to `base`, and refuses anything that points outside it. */
|
|
49
|
+
export function safeJoin(base, segment) {
|
|
50
|
+
const joined = path.resolve(base, segment);
|
|
51
|
+
if (!isStrictlyInside(joined, path.resolve(base))) {
|
|
52
|
+
throw new Error(`[typo3-playwright-toolkit] "${segment}" escapes ${base}.`);
|
|
53
|
+
}
|
|
54
|
+
return joined;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=safe-paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safe-paths.js","sourceRoot":"","sources":["../../src/state/safe-paths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAE5B;;;GAGG;AACH,SAAS,sBAAsB,CAAC,MAAc;IAC1C,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAClC,MAAM,OAAO,GAAa,EAAE,CAAA;IAE5B,SAAS,CAAC;QACN,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;QACpE,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;QACpC,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;QACnD,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAA;QACpC,OAAO,GAAG,MAAM,CAAA;IACpB,CAAC;AACL,CAAC;AAED,SAAS,gBAAgB,CAAC,SAAiB,EAAE,SAAiB;IAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;IAEpD,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;AACtF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAAa,EAAE,MAAc,EAAE,YAAoB;IACxF,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACX,oCAAoC,KAAK,mCAAmC,MAAM,KAAK;YACnF,4EAA4E;YAC5E,wCAAwC,CAC/C,CAAA;IACL,CAAC;IAED,MAAM,IAAI,GAAG,sBAAsB,CAAC,YAAY,CAAC,CAAA;IACjD,MAAM,QAAQ,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAA;IAE/C,IAAI,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CACX,oCAAoC,KAAK,MAAM,MAAM,oBAAoB;YACrE,wBAAwB,YAAY,uBAAuB,QAAQ,kBAAkB;YACrF,8EAA8E,CACrF,CAAA;IACL,CAAC;AACL,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,IAAY;IACvC,OAAO,EAAE,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,CAAA;AACvF,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,OAAe;IAClD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAE1C,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,+BAA+B,OAAO,aAAa,IAAI,GAAG,CAAC,CAAA;IAC/E,CAAC;IAED,OAAO,MAAM,CAAA;AACjB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ToolkitConfig } from '../config.js';
|
|
2
|
+
import type { EnsureStateOutcome } from './ensure-state.js';
|
|
3
|
+
export declare function applyScenarioOutcome<S>(outcome: EnsureStateOutcome<S>, skip: (reason: string) => void): S;
|
|
4
|
+
/**
|
|
5
|
+
* Recorded so teardown preserves the databases of a scenario whose tests
|
|
6
|
+
* failed, the way it does for a failed setup.
|
|
7
|
+
*/
|
|
8
|
+
export declare function recordTestFailure(config: ToolkitConfig, key: string, error: string): void;
|
|
9
|
+
//# sourceMappingURL=scenario-outcome.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario-outcome.d.ts","sourceRoot":"","sources":["../../src/state/scenario-outcome.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AACjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAG3D,wBAAgB,oBAAoB,CAAC,CAAC,EAClC,OAAO,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAC9B,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAC/B,CAAC,CAUH;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAOzF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { recordScenarioFailure } from './scenario-state.js';
|
|
2
|
+
export function applyScenarioOutcome(outcome, skip) {
|
|
3
|
+
if (outcome.status === 'ready') {
|
|
4
|
+
return outcome.data;
|
|
5
|
+
}
|
|
6
|
+
skip(outcome.reason);
|
|
7
|
+
// Playwright's skip throws; anything else must not fall through into a test
|
|
8
|
+
// that has no state to work with.
|
|
9
|
+
throw new Error(`[typo3-playwright-toolkit] ${outcome.reason}`);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Recorded so teardown preserves the databases of a scenario whose tests
|
|
13
|
+
* failed, the way it does for a failed setup.
|
|
14
|
+
*/
|
|
15
|
+
export function recordTestFailure(config, key, error) {
|
|
16
|
+
recordScenarioFailure(config, {
|
|
17
|
+
key: `${key}__test`,
|
|
18
|
+
scenarioKey: key,
|
|
19
|
+
triggeringTestId: '',
|
|
20
|
+
attempts: [{ attempt: 1, testId: '', error, durationMs: 0 }],
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=scenario-outcome.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario-outcome.js","sourceRoot":"","sources":["../../src/state/scenario-outcome.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAE3D,MAAM,UAAU,oBAAoB,CAChC,OAA8B,EAC9B,IAA8B;IAE9B,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;QAC7B,OAAO,OAAO,CAAC,IAAI,CAAA;IACvB,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAEpB,4EAA4E;IAC5E,kCAAkC;IAClC,MAAM,IAAI,KAAK,CAAC,8BAA8B,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;AACnE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAqB,EAAE,GAAW,EAAE,KAAa;IAC/E,qBAAqB,CAAC,MAAM,EAAE;QAC1B,GAAG,EAAE,GAAG,GAAG,QAAQ;QACnB,WAAW,EAAE,GAAG;QAChB,gBAAgB,EAAE,EAAE;QACpB,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;KAC/D,CAAC,CAAA;AACN,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ToolkitConfig } from '../config.js';
|
|
2
|
+
export interface ScenarioStateRecord<S> {
|
|
3
|
+
runId: string;
|
|
4
|
+
key: string;
|
|
5
|
+
testId: string;
|
|
6
|
+
attempt: number;
|
|
7
|
+
committedAt: string;
|
|
8
|
+
setupMs: number;
|
|
9
|
+
data: S;
|
|
10
|
+
}
|
|
11
|
+
export interface ScenarioAttemptFailure {
|
|
12
|
+
attempt: number;
|
|
13
|
+
testId: string;
|
|
14
|
+
error: string;
|
|
15
|
+
durationMs: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ScenarioFailureRecord {
|
|
18
|
+
runId: string;
|
|
19
|
+
scenarioKey: string;
|
|
20
|
+
/** File name stem: a setup and a test failure of one scenario both need one. */
|
|
21
|
+
key: string;
|
|
22
|
+
triggeringTestId: string;
|
|
23
|
+
recordedAt: string;
|
|
24
|
+
attempts: ScenarioAttemptFailure[];
|
|
25
|
+
}
|
|
26
|
+
export declare function commitScenarioState<S>(config: ToolkitConfig, input: {
|
|
27
|
+
key: string;
|
|
28
|
+
testId: string;
|
|
29
|
+
attempt: number;
|
|
30
|
+
setupMs: number;
|
|
31
|
+
data: S;
|
|
32
|
+
}): void;
|
|
33
|
+
export declare function readScenarioState<S>(config: ToolkitConfig, key: string): ScenarioStateRecord<S> | undefined;
|
|
34
|
+
export declare function recordScenarioFailure(config: ToolkitConfig, input: {
|
|
35
|
+
key: string;
|
|
36
|
+
scenarioKey?: string;
|
|
37
|
+
triggeringTestId: string;
|
|
38
|
+
attempts: ScenarioAttemptFailure[];
|
|
39
|
+
}): void;
|
|
40
|
+
export declare function readScenarioFailure(config: ToolkitConfig, key: string): ScenarioFailureRecord | undefined;
|
|
41
|
+
//# sourceMappingURL=scenario-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario-state.d.ts","sourceRoot":"","sources":["../../src/state/scenario-state.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAIjD,MAAM,WAAW,mBAAmB,CAAC,CAAC;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,CAAC,CAAA;CACV;AAED,MAAM,WAAW,sBAAsB;IACnC,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,qBAAqB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,gFAAgF;IAChF,GAAG,EAAE,MAAM,CAAA;IACX,gBAAgB,EAAE,MAAM,CAAA;IACxB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,sBAAsB,EAAE,CAAA;CACrC;AAgBD,wBAAgB,mBAAmB,CAAC,CAAC,EACjC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAClF,IAAI,CAeN;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAAC,CAAC,CAAC,GAAG,SAAS,CAE3G;AAED,wBAAgB,qBAAqB,CACjC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,sBAAsB,EAAE,CAAA;CAAE,GAC3G,IAAI,CAYN;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,qBAAqB,GAAG,SAAS,CAEzG"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { assertJsonSafe } from './json-safe.js';
|
|
4
|
+
import { ensureRunNamespace } from './run-namespace.js';
|
|
5
|
+
function atomicWrite(target, content) {
|
|
6
|
+
const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
7
|
+
fs.writeFileSync(tmp, content);
|
|
8
|
+
fs.renameSync(tmp, target);
|
|
9
|
+
}
|
|
10
|
+
function readJson(file) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function commitScenarioState(config, input) {
|
|
19
|
+
assertJsonSafe(input.data, `setup state for "${input.key}"`);
|
|
20
|
+
const paths = ensureRunNamespace(config);
|
|
21
|
+
const record = {
|
|
22
|
+
runId: paths.runId,
|
|
23
|
+
key: input.key,
|
|
24
|
+
testId: input.testId,
|
|
25
|
+
attempt: input.attempt,
|
|
26
|
+
committedAt: new Date().toISOString(),
|
|
27
|
+
setupMs: input.setupMs,
|
|
28
|
+
data: input.data,
|
|
29
|
+
};
|
|
30
|
+
atomicWrite(path.join(paths.scenariosDir, `${input.key}.json`), JSON.stringify(record, null, 2));
|
|
31
|
+
}
|
|
32
|
+
export function readScenarioState(config, key) {
|
|
33
|
+
return readJson(path.join(ensureRunNamespace(config).scenariosDir, `${key}.json`));
|
|
34
|
+
}
|
|
35
|
+
export function recordScenarioFailure(config, input) {
|
|
36
|
+
const paths = ensureRunNamespace(config);
|
|
37
|
+
const record = {
|
|
38
|
+
runId: paths.runId,
|
|
39
|
+
scenarioKey: input.scenarioKey ?? input.key,
|
|
40
|
+
key: input.key,
|
|
41
|
+
triggeringTestId: input.triggeringTestId,
|
|
42
|
+
recordedAt: new Date().toISOString(),
|
|
43
|
+
attempts: input.attempts,
|
|
44
|
+
};
|
|
45
|
+
atomicWrite(path.join(paths.failuresDir, `${input.key}.json`), JSON.stringify(record, null, 2));
|
|
46
|
+
}
|
|
47
|
+
export function readScenarioFailure(config, key) {
|
|
48
|
+
return readJson(path.join(ensureRunNamespace(config).failuresDir, `${key}.json`));
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=scenario-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario-state.js","sourceRoot":"","sources":["../../src/state/scenario-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAE5B,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AA6BvD,SAAS,WAAW,CAAC,MAAc,EAAE,OAAe;IAChD,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,MAAM,CAAA;IACxD,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IAC9B,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;AAC9B,CAAC;AAED,SAAS,QAAQ,CAAI,IAAY;IAC7B,IAAI,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAM,CAAA;IAC1D,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,SAAS,CAAA;IACpB,CAAC;AACL,CAAC;AAED,MAAM,UAAU,mBAAmB,CAC/B,MAAqB,EACrB,KAAiF;IAEjF,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,oBAAoB,KAAK,CAAC,GAAG,GAAG,CAAC,CAAA;IAE5D,MAAM,KAAK,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAA;IACxC,MAAM,MAAM,GAA2B;QACnC,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrC,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI,EAAE,KAAK,CAAC,IAAI;KACnB,CAAA;IAED,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;AACpG,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAI,MAAqB,EAAE,GAAW;IACnE,OAAO,QAAQ,CAAyB,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,YAAY,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAA;AAC9G,CAAC;AAED,MAAM,UAAU,qBAAqB,CACjC,MAAqB,EACrB,KAA0G;IAE1G,MAAM,KAAK,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAA;IACxC,MAAM,MAAM,GAA0B;QAClC,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,GAAG;QAC3C,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACpC,QAAQ,EAAE,KAAK,CAAC,QAAQ;KAC3B,CAAA;IAED,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,GAAG,OAAO,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAA;AACnG,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,MAAqB,EAAE,GAAW;IAClE,OAAO,QAAQ,CAAwB,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC,CAAA;AAC5G,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface LockOwner {
|
|
2
|
+
nonce: string;
|
|
3
|
+
attempt: number;
|
|
4
|
+
pid: number;
|
|
5
|
+
startedAt: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function acquireSetupLock(locksDir: string, key: string, owner: {
|
|
8
|
+
nonce: string;
|
|
9
|
+
attempt: number;
|
|
10
|
+
}): boolean;
|
|
11
|
+
export declare function readLockOwner(locksDir: string, key: string): LockOwner | undefined;
|
|
12
|
+
/** False means another process took the lock, so nothing may be written. */
|
|
13
|
+
export declare function stillOwns(locksDir: string, key: string, nonce: string): boolean;
|
|
14
|
+
export declare function lockAgeMs(locksDir: string, key: string, now?: number): number | undefined;
|
|
15
|
+
export declare function heartbeatSetupLock(locksDir: string, key: string, nonce: string): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Takes over a lock whose owner stopped reporting. Renames the file instead of
|
|
18
|
+
* deleting it, because a rename can only succeed once, so only one process wins.
|
|
19
|
+
*/
|
|
20
|
+
export declare function stealSetupLock(locksDir: string, key: string, stealerNonce: string, staleMs: number, now?: number): boolean;
|
|
21
|
+
export declare function releaseSetupLock(locksDir: string, key: string, nonce: string): void;
|
|
22
|
+
//# sourceMappingURL=setup-lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-lock.d.ts","sourceRoot":"","sources":["../../src/state/setup-lock.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,SAAS;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;CACpB;AAMD,wBAAgB,gBAAgB,CAC5B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC1C,OAAO,CA0BT;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAMlF;AAED,4EAA4E;AAC5E,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE/E;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,GAAG,SAAS,CAMrG;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAaxF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC1B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,GAAG,GAAE,MAAmB,GACzB,OAAO,CAeT;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAKnF"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
function lockFile(locksDir, key) {
|
|
4
|
+
return path.join(locksDir, `${key}.lock`);
|
|
5
|
+
}
|
|
6
|
+
export function acquireSetupLock(locksDir, key, owner) {
|
|
7
|
+
fs.mkdirSync(locksDir, { recursive: true });
|
|
8
|
+
let handle;
|
|
9
|
+
try {
|
|
10
|
+
handle = fs.openSync(lockFile(locksDir, key), 'wx');
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (error.code === 'EEXIST') {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
const record = {
|
|
19
|
+
nonce: owner.nonce,
|
|
20
|
+
attempt: owner.attempt,
|
|
21
|
+
pid: process.pid,
|
|
22
|
+
startedAt: Date.now(),
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
fs.writeFileSync(handle, JSON.stringify(record));
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
fs.closeSync(handle);
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
export function readLockOwner(locksDir, key) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(fs.readFileSync(lockFile(locksDir, key), 'utf-8'));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** False means another process took the lock, so nothing may be written. */
|
|
41
|
+
export function stillOwns(locksDir, key, nonce) {
|
|
42
|
+
return readLockOwner(locksDir, key)?.nonce === nonce;
|
|
43
|
+
}
|
|
44
|
+
export function lockAgeMs(locksDir, key, now = Date.now()) {
|
|
45
|
+
try {
|
|
46
|
+
return now - fs.statSync(lockFile(locksDir, key)).mtimeMs;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function heartbeatSetupLock(locksDir, key, nonce) {
|
|
53
|
+
if (!stillOwns(locksDir, key, nonce)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const now = new Date();
|
|
57
|
+
try {
|
|
58
|
+
fs.utimesSync(lockFile(locksDir, key), now, now);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Takes over a lock whose owner stopped reporting. Renames the file instead of
|
|
67
|
+
* deleting it, because a rename can only succeed once, so only one process wins.
|
|
68
|
+
*/
|
|
69
|
+
export function stealSetupLock(locksDir, key, stealerNonce, staleMs, now = Date.now()) {
|
|
70
|
+
const age = lockAgeMs(locksDir, key, now);
|
|
71
|
+
if (age === undefined || age < staleMs) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const stolen = path.join(locksDir, `${key}.lock.stale-${stealerNonce}`);
|
|
75
|
+
try {
|
|
76
|
+
fs.renameSync(lockFile(locksDir, key), stolen);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
fs.rmSync(stolen, { force: true });
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
export function releaseSetupLock(locksDir, key, nonce) {
|
|
85
|
+
if (!stillOwns(locksDir, key, nonce)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
fs.rmSync(lockFile(locksDir, key), { force: true });
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=setup-lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-lock.js","sourceRoot":"","sources":["../../src/state/setup-lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type CropConfig = Record<string, {
|
|
2
|
+
cropArea: {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
};
|
|
8
|
+
selectedRatio: string;
|
|
9
|
+
/** null is what TYPO3 itself writes when no focus area is set. */
|
|
10
|
+
focusArea: {
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
} | null;
|
|
16
|
+
}>;
|
|
17
|
+
//# sourceMappingURL=common.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/types/common.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,MAAM,CAC3B,MAAM,EACN;IACI,QAAQ,EAAE;QACN,CAAC,EAAE,MAAM,CAAA;QACT,CAAC,EAAE,MAAM,CAAA;QACT,KAAK,EAAE,MAAM,CAAA;QACb,MAAM,EAAE,MAAM,CAAA;KACjB,CAAA;IACD,aAAa,EAAE,MAAM,CAAA;IACrB,kEAAkE;IAClE,SAAS,EAAE;QACP,CAAC,EAAE,MAAM,CAAA;QACT,CAAC,EAAE,MAAM,CAAA;QACT,KAAK,EAAE,MAAM,CAAA;QACb,MAAM,EAAE,MAAM,CAAA;KACjB,GAAG,IAAI,CAAA;CACX,CACJ,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/types/common.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CropConfig } from './common.js';
|
|
2
|
+
import type { RecordDataMap } from '../http/record-edit.js';
|
|
3
|
+
export interface ContentFields {
|
|
4
|
+
[key: string]: string | number | CropConfig | boolean | undefined;
|
|
5
|
+
}
|
|
6
|
+
export interface ContentBuilderInterface {
|
|
7
|
+
readonly type: string;
|
|
8
|
+
getFields(): ContentFields;
|
|
9
|
+
/**
|
|
10
|
+
* Extra records written in the same request — a file reference, a child
|
|
11
|
+
* record. Point at the content element by `contentIdentifier`; DataHandler
|
|
12
|
+
* substitutes the real uid once it has assigned one.
|
|
13
|
+
*/
|
|
14
|
+
getAdditionalRecords?(contentIdentifier: string, pageId: string): RecordDataMap;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=content-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-builder.d.ts","sourceRoot":"","sources":["../../src/types/content-builder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAE3D,MAAM,WAAW,aAAa;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAA;CACpE;AAED,MAAM,WAAW,uBAAuB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,SAAS,IAAI,aAAa,CAAA;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,CAAC,iBAAiB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,CAAA;CAClF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-builder.js","sourceRoot":"","sources":["../../src/types/content-builder.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Page, BrowserContext } from '@playwright/test';
|
|
2
|
+
export interface PageWithTestId extends Page {
|
|
3
|
+
testId?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ContextWithTestId extends BrowserContext {
|
|
6
|
+
testId?: string;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=playwright-extensions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwright-extensions.d.ts","sourceRoot":"","sources":["../../src/types/playwright-extensions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEvD,MAAM,WAAW,cAAe,SAAQ,IAAI;IACxC,MAAM,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,iBAAkB,SAAQ,cAAc;IACrD,MAAM,CAAC,EAAE,MAAM,CAAA;CAClB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwright-extensions.js","sourceRoot":"","sources":["../../src/types/playwright-extensions.ts"],"names":[],"mappings":""}
|