testdossier 0.1.0 → 0.1.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/.env.testdossier.example +3 -0
- package/README.md +44 -13
- package/lib/testdossier.mjs +47 -7
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,9 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
Upload a completed test report from a developer laptop, test machine, or CI job:
|
|
4
4
|
|
|
5
|
+
Create `.env.testdossier` in the project containing the test report:
|
|
6
|
+
|
|
7
|
+
```dotenv
|
|
8
|
+
TD_CI_TOKEN=replace_with_your_td_token
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Replace the placeholder with the CI-ingestion token shown once by TestDossier,
|
|
12
|
+
then:
|
|
13
|
+
|
|
5
14
|
```bash
|
|
6
|
-
|
|
7
|
-
npx testdossier upload playwright-results.json
|
|
15
|
+
chmod 600 .env.testdossier
|
|
16
|
+
npx testdossier upload playwright-results.json --env-file .env.testdossier
|
|
8
17
|
```
|
|
9
18
|
|
|
10
19
|
The CLI auto-detects Playwright JSON, Cypress JSON, Cucumber JSON, JUnit XML,
|
|
@@ -17,10 +26,12 @@ The uploader does one thing: read a completed report and make an outbound HTTPS
|
|
|
17
26
|
request to TestDossier. It does not run tests, accept remote commands, install a
|
|
18
27
|
background service, watch files, or keep a process running.
|
|
19
28
|
|
|
20
|
-
The access token is read
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
The access token is read from an explicitly selected env file or from the
|
|
30
|
+
`TD_CI_TOKEN` process environment. There is deliberately no `--token` option,
|
|
31
|
+
so a token cannot accidentally land in shell history or a process listing.
|
|
32
|
+
The CLI reads only `TD_CI_TOKEN` from the selected file; it does not import or
|
|
33
|
+
execute other entries. Redirects are rejected, and non-HTTPS destinations are
|
|
34
|
+
rejected except for localhost development.
|
|
24
35
|
|
|
25
36
|
Before sending anything, inspect what the CLI detected:
|
|
26
37
|
|
|
@@ -45,31 +56,47 @@ creates an ambiguous shape, pass `--format playwright`, `--format cypress`,
|
|
|
45
56
|
## Local setup
|
|
46
57
|
|
|
47
58
|
Create a project access token in TestDossier with the **CI ingestion**
|
|
48
|
-
capability
|
|
59
|
+
capability and copy it once. For repeat use, install and pin the CLI in the test
|
|
60
|
+
repository so its source and version are captured by the lockfile:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install --save-dev testdossier
|
|
64
|
+
cp node_modules/testdossier/.env.testdossier.example .env.testdossier
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Alternatively, create `.env.testdossier` yourself with the single placeholder
|
|
68
|
+
line shown at the top of this guide. Replace `replace_with_your_td_token`, then
|
|
69
|
+
ensure the project ignores and protects the real file:
|
|
70
|
+
|
|
71
|
+
```gitignore
|
|
72
|
+
.env.testdossier
|
|
73
|
+
```
|
|
49
74
|
|
|
50
75
|
```bash
|
|
51
|
-
|
|
76
|
+
chmod 600 .env.testdossier
|
|
52
77
|
```
|
|
53
78
|
|
|
54
|
-
|
|
55
|
-
version are captured by the lockfile:
|
|
79
|
+
Upload with:
|
|
56
80
|
|
|
57
81
|
```bash
|
|
58
|
-
|
|
59
|
-
npx testdossier upload playwright-results.json
|
|
82
|
+
npx testdossier upload playwright-results.json --env-file .env.testdossier
|
|
60
83
|
```
|
|
61
84
|
|
|
85
|
+
For a one-off session, setting `TD_CI_TOKEN` in the process environment still
|
|
86
|
+
works. An explicitly passed `--env-file` takes precedence.
|
|
87
|
+
|
|
62
88
|
The default destination is `https://testdossier.com`. A self-hosted or local
|
|
63
89
|
instance can be selected without putting the token on the command line:
|
|
64
90
|
|
|
65
91
|
```bash
|
|
66
|
-
TD_URL="https://dossier.example.com" npx testdossier upload junit-results.xml
|
|
92
|
+
TD_URL="https://dossier.example.com" npx testdossier upload junit-results.xml --env-file .env.testdossier
|
|
67
93
|
```
|
|
68
94
|
|
|
69
95
|
Useful metadata options:
|
|
70
96
|
|
|
71
97
|
```bash
|
|
72
98
|
npx testdossier upload junit-results.xml \
|
|
99
|
+
--env-file .env.testdossier \
|
|
73
100
|
--run-id "local-2026-07-23-1" \
|
|
74
101
|
--build "abc123" \
|
|
75
102
|
--branch "main"
|
|
@@ -81,3 +108,7 @@ one, the CLI creates a new local run ID. Transient network, rate-limit, conflict
|
|
|
81
108
|
and server errors are retried with the same run ID.
|
|
82
109
|
|
|
83
110
|
Run `npx testdossier --help` for every option.
|
|
111
|
+
|
|
112
|
+
If `.env.testdossier` is ever committed or shared, revoke that token in
|
|
113
|
+
TestDossier immediately and create a replacement. Hosted CI should use the
|
|
114
|
+
provider's encrypted secret storage instead of an env file.
|
package/lib/testdossier.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { readFile, stat } from "node:fs/promises";
|
|
3
3
|
|
|
4
|
-
export const VERSION = "0.1.
|
|
4
|
+
export const VERSION = "0.1.1";
|
|
5
5
|
const DEFAULT_ORIGIN = "https://testdossier.com";
|
|
6
6
|
const MAX_REPORT_BYTES = 25 * 1024 * 1024;
|
|
7
7
|
const RETRYABLE_STATUSES = new Set([408, 409, 425, 429, 500, 502, 503, 504]);
|
|
@@ -130,6 +130,7 @@ export function parseUploadArgs(args) {
|
|
|
130
130
|
branch: undefined,
|
|
131
131
|
provider: undefined,
|
|
132
132
|
ciUrl: undefined,
|
|
133
|
+
envFile: undefined,
|
|
133
134
|
dryRun: false,
|
|
134
135
|
reportPath: undefined,
|
|
135
136
|
};
|
|
@@ -141,6 +142,7 @@ export function parseUploadArgs(args) {
|
|
|
141
142
|
["--branch", "branch"],
|
|
142
143
|
["--provider", "provider"],
|
|
143
144
|
["--ci-url", "ciUrl"],
|
|
145
|
+
["--env-file", "envFile"],
|
|
144
146
|
]);
|
|
145
147
|
|
|
146
148
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -229,6 +231,41 @@ function integerEnv(value, fallback, min, max) {
|
|
|
229
231
|
return Number.isInteger(parsed) && parsed >= min && parsed <= max ? parsed : fallback;
|
|
230
232
|
}
|
|
231
233
|
|
|
234
|
+
export function parseTokenEnvFile(contents) {
|
|
235
|
+
let token;
|
|
236
|
+
const lines = String(contents).replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
237
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
238
|
+
const line = lines[index].trim();
|
|
239
|
+
if (!line || line.startsWith("#")) continue;
|
|
240
|
+
const match = line.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
241
|
+
if (!match || match[1] !== "TD_CI_TOKEN") continue;
|
|
242
|
+
if (token !== undefined) throw new CliError("env file contains TD_CI_TOKEN more than once.");
|
|
243
|
+
|
|
244
|
+
let value = match[2].trim();
|
|
245
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
246
|
+
value = value.slice(1, -1);
|
|
247
|
+
} else {
|
|
248
|
+
value = value.replace(/\s+#.*$/, "").trim();
|
|
249
|
+
}
|
|
250
|
+
if (!value) throw new CliError(`TD_CI_TOKEN is empty in the env file (line ${index + 1}).`);
|
|
251
|
+
token = value;
|
|
252
|
+
}
|
|
253
|
+
if (token === undefined) throw new CliError("env file does not define TD_CI_TOKEN.");
|
|
254
|
+
return token;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function tokenFromEnvFile(path, stderr) {
|
|
258
|
+
const fileStats = await stat(path).catch((error) => {
|
|
259
|
+
throw new CliError(`could not read env file: ${error.message}`);
|
|
260
|
+
});
|
|
261
|
+
if (!fileStats.isFile()) throw new CliError("env-file path must point to a file.");
|
|
262
|
+
if (fileStats.size > 64 * 1024) throw new CliError("env file is unexpectedly large (maximum 64 KiB).");
|
|
263
|
+
if (process.platform !== "win32" && (fileStats.mode & 0o077) !== 0) {
|
|
264
|
+
stderr(`Warning: ${path} is readable by other users; run chmod 600 "${path}".`);
|
|
265
|
+
}
|
|
266
|
+
return parseTokenEnvFile(await readFile(path, "utf8"));
|
|
267
|
+
}
|
|
268
|
+
|
|
232
269
|
function retryDelayMs(attempt, retryAfter, baseDelay) {
|
|
233
270
|
if (retryAfter) {
|
|
234
271
|
const seconds = Number(retryAfter);
|
|
@@ -303,18 +340,19 @@ Options:
|
|
|
303
340
|
--branch <value> Branch name
|
|
304
341
|
--provider <value> Provider label (defaults to local or detected CI)
|
|
305
342
|
--ci-url <url> Link to the originating CI run
|
|
343
|
+
--env-file <path> Read TD_CI_TOKEN from this explicit dotenv file
|
|
306
344
|
--dry-run Validate and detect only; send nothing
|
|
307
345
|
-h, --help Show help
|
|
308
346
|
--version Show version
|
|
309
347
|
|
|
310
348
|
Authentication:
|
|
311
|
-
Set TD_CI_TOKEN
|
|
312
|
-
accepted as command-line arguments, keeping
|
|
313
|
-
process listings.
|
|
349
|
+
Set TD_CI_TOKEN in the environment or pass --env-file .env.testdossier.
|
|
350
|
+
Tokens are intentionally not accepted as command-line arguments, keeping
|
|
351
|
+
them out of shell history and process listings.
|
|
314
352
|
|
|
315
353
|
Examples:
|
|
316
|
-
|
|
317
|
-
|
|
354
|
+
npx testdossier upload playwright-results.json --env-file .env.testdossier
|
|
355
|
+
npx testdossier upload junit-results.xml --env-file .env.testdossier --build abc123
|
|
318
356
|
npx testdossier upload testdossier-ci.json --dry-run`;
|
|
319
357
|
}
|
|
320
358
|
|
|
@@ -381,7 +419,9 @@ export async function runCli(
|
|
|
381
419
|
return 0;
|
|
382
420
|
}
|
|
383
421
|
|
|
384
|
-
const token =
|
|
422
|
+
const token = options.envFile
|
|
423
|
+
? await tokenFromEnvFile(options.envFile, stderr)
|
|
424
|
+
: env.TD_CI_TOKEN;
|
|
385
425
|
if (!token || !token.startsWith("td_")) {
|
|
386
426
|
throw new CliError("TD_CI_TOKEN is required and must be a TestDossier td_ access token.");
|
|
387
427
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testdossier",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Upload completed test reports to TestDossier from a local machine or CI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
11
|
"lib",
|
|
12
|
-
"README.md"
|
|
12
|
+
"README.md",
|
|
13
|
+
".env.testdossier.example"
|
|
13
14
|
],
|
|
14
15
|
"engines": {
|
|
15
16
|
"node": ">=18"
|