allure 3.20.1 → 3.21.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 +25 -1
- package/dist/commands/{agent.js → agents/index.js} +13 -109
- package/dist/commands/agents/lifecycle.d.ts +20 -0
- package/dist/commands/agents/lifecycle.js +91 -0
- package/dist/commands/{agent-run.d.ts → agents/run.d.ts} +1 -13
- package/dist/commands/{agent-run.js → agents/run.js} +12 -67
- package/dist/commands/commons/generate.d.ts +1 -0
- package/dist/commands/commons/generate.js +28 -2
- package/dist/commands/generate.d.ts +2 -0
- package/dist/commands/generate.js +19 -1
- package/dist/commands/index.d.ts +2 -1
- package/dist/commands/index.js +2 -1
- package/dist/commands/migrate.d.ts +6 -0
- package/dist/commands/migrate.js +15 -0
- package/dist/commands/open.d.ts +1 -0
- package/dist/commands/open.js +12 -2
- package/dist/commands/qualityGate.js +11 -1
- package/dist/commands/run.d.ts +4 -0
- package/dist/commands/run.js +27 -5
- package/dist/commands/slack.d.ts +3 -2
- package/dist/commands/slack.js +17 -5
- package/dist/commands/testopsPlan.js +4 -4
- package/dist/commands/watch.d.ts +1 -0
- package/dist/commands/watch.js +10 -1
- package/dist/index.js +30 -9
- package/dist/utils/allure2Hints.d.ts +8 -0
- package/dist/utils/allure2Hints.js +90 -0
- package/dist/utils/cleanOutput.d.ts +9 -0
- package/dist/utils/cleanOutput.js +62 -0
- package/dist/utils/fileSystem.d.ts +4 -1
- package/dist/utils/fileSystem.js +3 -3
- package/dist/utils/serverOptions.d.ts +5 -0
- package/dist/utils/serverOptions.js +4 -0
- package/dist/utils/updateNotifier.d.ts +6 -0
- package/dist/utils/updateNotifier.js +58 -0
- package/dist/utils/verbosity.d.ts +7 -0
- package/dist/utils/verbosity.js +32 -0
- package/package.json +28 -23
- /package/dist/commands/{agent-human-report.d.ts → agents/human-report.d.ts} +0 -0
- /package/dist/commands/{agent-human-report.js → agents/human-report.js} +0 -0
- /package/dist/commands/{agent.d.ts → agents/index.d.ts} +0 -0
package/dist/commands/open.d.ts
CHANGED
package/dist/commands/open.js
CHANGED
|
@@ -9,6 +9,7 @@ import { Command, Option } from "clipanion";
|
|
|
9
9
|
import { red } from "yoctocolors";
|
|
10
10
|
import { findFilesByGlobs } from "./../utils/fileSystem.js";
|
|
11
11
|
import { resolveResultsPatterns } from "./../utils/resultsPatterns.js";
|
|
12
|
+
import { serverOptionsFromConfig } from "./../utils/serverOptions.js";
|
|
12
13
|
import { notifySignals, waitForAbort } from "./../utils/signals.js";
|
|
13
14
|
import { generate } from "./commons/generate.js";
|
|
14
15
|
export class OpenCommand extends Command {
|
|
@@ -23,6 +24,9 @@ export class OpenCommand extends Command {
|
|
|
23
24
|
this.port = Option.String("--port", {
|
|
24
25
|
description: "The port to serve the reports on. If not set, the server starts on a random port",
|
|
25
26
|
});
|
|
27
|
+
this.host = Option.String("--host", {
|
|
28
|
+
description: "The host (network interface) to serve the reports on, e.g. 127.0.0.1 or 0.0.0.0 (default: all interfaces)",
|
|
29
|
+
});
|
|
26
30
|
this.cwd = Option.String("--cwd", {
|
|
27
31
|
description: "The working directory for the command to run (default: current working directory)",
|
|
28
32
|
});
|
|
@@ -35,12 +39,13 @@ export class OpenCommand extends Command {
|
|
|
35
39
|
const hideLabels = this.hideLabels?.length ? this.hideLabels : undefined;
|
|
36
40
|
const config = await readConfig(cwd, this.config, {
|
|
37
41
|
port: this.port,
|
|
42
|
+
host: this.host,
|
|
38
43
|
});
|
|
39
44
|
const servePath = this.resolveReportPath(cwd, this.resultsDir, config.output);
|
|
40
45
|
const resolvedPatterns = resolveResultsPatterns(this.resultsDir, config.resultsDir);
|
|
41
46
|
if (await this.reportExists(servePath)) {
|
|
42
47
|
await serve({
|
|
43
|
-
|
|
48
|
+
...serverOptionsFromConfig(config),
|
|
44
49
|
servePath,
|
|
45
50
|
open: true,
|
|
46
51
|
});
|
|
@@ -49,6 +54,7 @@ export class OpenCommand extends Command {
|
|
|
49
54
|
const tmpDir = await mkdtemp(join(tmpdir(), "allure-report-"));
|
|
50
55
|
const config = await readConfig(cwd, this.config, {
|
|
51
56
|
port: this.port,
|
|
57
|
+
host: this.host,
|
|
52
58
|
output: tmpDir,
|
|
53
59
|
hideLabels,
|
|
54
60
|
});
|
|
@@ -66,7 +72,7 @@ export class OpenCommand extends Command {
|
|
|
66
72
|
process.exit(notifier.info()?.code ?? 0);
|
|
67
73
|
});
|
|
68
74
|
await serve({
|
|
69
|
-
|
|
75
|
+
...serverOptionsFromConfig(config),
|
|
70
76
|
servePath: config.output,
|
|
71
77
|
open: true,
|
|
72
78
|
});
|
|
@@ -98,6 +104,10 @@ OpenCommand.usage = Command.Usage({
|
|
|
98
104
|
examples: [
|
|
99
105
|
["open ./allure-results", "Generate and serve the report based on given test results directory"],
|
|
100
106
|
["open --port 8080 ./allure-report", "Serve the report on port 8080"],
|
|
107
|
+
[
|
|
108
|
+
"open --host 0.0.0.0 ./allure-report",
|
|
109
|
+
"Serve the report on all network interfaces, so it is reachable remotely",
|
|
110
|
+
],
|
|
101
111
|
[
|
|
102
112
|
"open ./packages/*/allure-results",
|
|
103
113
|
"Generate and serve the report from all Allure result directories matching the pattern",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as console from "node:console";
|
|
2
2
|
import { realpath } from "node:fs/promises";
|
|
3
|
-
import { exit, cwd as processCwd } from "node:process";
|
|
3
|
+
import process, { exit, cwd as processCwd } from "node:process";
|
|
4
4
|
import { AllureReport, QualityGateState, filterFailedQualityGateResults, readConfig, stringifyQualityGateResults, } from "@allurereport/core";
|
|
5
5
|
import { Command, Option } from "clipanion";
|
|
6
6
|
import * as typanion from "typanion";
|
|
@@ -88,6 +88,14 @@ export class QualityGateCommand extends Command {
|
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
90
|
const state = new QualityGateState();
|
|
91
|
+
let validated = false;
|
|
92
|
+
process.once("beforeExit", () => {
|
|
93
|
+
if (validated || (process.exitCode ?? 0) !== 0) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
console.error(red("Quality gate hasn't been validated, the report generation has been interrupted"));
|
|
97
|
+
process.exitCode = 1;
|
|
98
|
+
});
|
|
91
99
|
allureReport.realtimeSubscriber.onTestResults(async (trsIds) => {
|
|
92
100
|
const trs = await Promise.all(trsIds.map((id) => allureReport.store.testResultById(id)));
|
|
93
101
|
const nonRetryTrs = trs.filter((tr) => !tr.isRetry);
|
|
@@ -99,6 +107,7 @@ export class QualityGateCommand extends Command {
|
|
|
99
107
|
if (!fastFailed) {
|
|
100
108
|
return;
|
|
101
109
|
}
|
|
110
|
+
validated = true;
|
|
102
111
|
console.error(stringifyQualityGateResults(results));
|
|
103
112
|
exit(1);
|
|
104
113
|
});
|
|
@@ -112,6 +121,7 @@ export class QualityGateCommand extends Command {
|
|
|
112
121
|
trs: allTrs,
|
|
113
122
|
environment: resolvedEnvironment?.id,
|
|
114
123
|
});
|
|
124
|
+
validated = true;
|
|
115
125
|
if (filterFailedQualityGateResults(validationResults.results).length === 0) {
|
|
116
126
|
exit(0);
|
|
117
127
|
return;
|
package/dist/commands/run.d.ts
CHANGED
|
@@ -7,9 +7,12 @@ export declare class RunCommand extends Command {
|
|
|
7
7
|
output: string | undefined;
|
|
8
8
|
open: boolean | undefined;
|
|
9
9
|
port: string | undefined;
|
|
10
|
+
host: string | undefined;
|
|
10
11
|
reportName: string | undefined;
|
|
11
12
|
rerun: string | undefined;
|
|
12
13
|
silent: boolean | undefined;
|
|
14
|
+
verbose: boolean | undefined;
|
|
15
|
+
quiet: boolean | undefined;
|
|
13
16
|
ignoreLogs: boolean | undefined;
|
|
14
17
|
dump: string | undefined;
|
|
15
18
|
environment: string | undefined;
|
|
@@ -20,6 +23,7 @@ export declare class RunCommand extends Command {
|
|
|
20
23
|
knownIssues: string | undefined;
|
|
21
24
|
resultsDir: string[] | undefined;
|
|
22
25
|
commandToRun: string[];
|
|
26
|
+
get silentOutput(): boolean;
|
|
23
27
|
get logs(): "pipe" | "inherit" | "ignore";
|
|
24
28
|
execute(): Promise<void>;
|
|
25
29
|
}
|
package/dist/commands/run.js
CHANGED
|
@@ -9,6 +9,8 @@ import { Command, Option, UsageError } from "clipanion";
|
|
|
9
9
|
import { environmentNameOption, environmentOption, normalizeCommandEnvironmentOptions, resolveCommandEnvironment, } from "../utils/environment.js";
|
|
10
10
|
import { createChildAllureCliEnvironment, getActiveAllureCliCommand } from "../utils/execution-context.js";
|
|
11
11
|
import { parseRunCommand, resolveResultsPatterns } from "../utils/resultsPatterns.js";
|
|
12
|
+
import { serverOptionsFromConfig } from "../utils/serverOptions.js";
|
|
13
|
+
import { isQuiet } from "../utils/verbosity.js";
|
|
12
14
|
import { executeAllureRun, executeNestedAllureCommand } from "./commons/run.js";
|
|
13
15
|
const missingRunCommandUsageError = () => new UsageError("expecting command to be specified after --, e.g. allure run -- npm run test");
|
|
14
16
|
const runLogger = new Logger("AllureRun");
|
|
@@ -30,6 +32,9 @@ export class RunCommand extends Command {
|
|
|
30
32
|
this.port = Option.String("--port", {
|
|
31
33
|
description: "The port to serve the reports on. If not set, the server starts on a random port",
|
|
32
34
|
});
|
|
35
|
+
this.host = Option.String("--host", {
|
|
36
|
+
description: "The host (network interface) to serve the reports on, e.g. 127.0.0.1 or 0.0.0.0 (default: all interfaces)",
|
|
37
|
+
});
|
|
33
38
|
this.reportName = Option.String("--report-name,--name", {
|
|
34
39
|
description: "The report name (default: Allure Report)",
|
|
35
40
|
});
|
|
@@ -39,6 +44,12 @@ export class RunCommand extends Command {
|
|
|
39
44
|
this.silent = Option.Boolean("--silent", {
|
|
40
45
|
description: "Don't pipe the process output logs to console (default: 0)",
|
|
41
46
|
});
|
|
47
|
+
this.verbose = Option.Boolean("--verbose,-v", {
|
|
48
|
+
description: "Print verbose Allure messages (global flag, cannot be combined with --quiet)",
|
|
49
|
+
});
|
|
50
|
+
this.quiet = Option.Boolean("--quiet,-q", {
|
|
51
|
+
description: "Print errors only and hide the test process output, implies --silent (global flag)",
|
|
52
|
+
});
|
|
42
53
|
this.ignoreLogs = Option.Boolean("--ignore-logs", {
|
|
43
54
|
description: "Prevent logs attaching to the report (default: false)",
|
|
44
55
|
});
|
|
@@ -64,8 +75,11 @@ export class RunCommand extends Command {
|
|
|
64
75
|
});
|
|
65
76
|
this.commandToRun = Option.Rest();
|
|
66
77
|
}
|
|
78
|
+
get silentOutput() {
|
|
79
|
+
return !!(this.silent || this.quiet) || isQuiet();
|
|
80
|
+
}
|
|
67
81
|
get logs() {
|
|
68
|
-
if (this.
|
|
82
|
+
if (this.silentOutput) {
|
|
69
83
|
return this.ignoreLogs ? "ignore" : "pipe";
|
|
70
84
|
}
|
|
71
85
|
return this.ignoreLogs ? "inherit" : "pipe";
|
|
@@ -84,7 +98,7 @@ export class RunCommand extends Command {
|
|
|
84
98
|
command,
|
|
85
99
|
commandArgs,
|
|
86
100
|
cwd,
|
|
87
|
-
silent: this.
|
|
101
|
+
silent: this.silentOutput,
|
|
88
102
|
});
|
|
89
103
|
exit(exitCode ?? -1);
|
|
90
104
|
return;
|
|
@@ -100,6 +114,7 @@ export class RunCommand extends Command {
|
|
|
100
114
|
name: this.reportName,
|
|
101
115
|
open: this.open,
|
|
102
116
|
port: this.port,
|
|
117
|
+
host: this.host,
|
|
103
118
|
hideLabels,
|
|
104
119
|
historyLimit: this.historyLimit !== undefined ? parseInt(this.historyLimit, 10) : undefined,
|
|
105
120
|
...(this.historyBaseUrl !== undefined ? { historyBaseUrl: this.historyBaseUrl } : {}),
|
|
@@ -146,7 +161,7 @@ export class RunCommand extends Command {
|
|
|
146
161
|
environment: resolvedEnvironment?.id,
|
|
147
162
|
withQualityGate,
|
|
148
163
|
logs: this.logs,
|
|
149
|
-
silent: this.
|
|
164
|
+
silent: this.silentOutput,
|
|
150
165
|
ignoreLogs: this.ignoreLogs,
|
|
151
166
|
maxRerun,
|
|
152
167
|
resultsPatterns,
|
|
@@ -155,7 +170,7 @@ export class RunCommand extends Command {
|
|
|
155
170
|
runLogger.info(`Completed with exit code ${finalExitCode} after ${formatDuration(Date.now() - before)}`);
|
|
156
171
|
if (config.open) {
|
|
157
172
|
await serve({
|
|
158
|
-
|
|
173
|
+
...serverOptionsFromConfig(config),
|
|
159
174
|
servePath: config.output,
|
|
160
175
|
open: true,
|
|
161
176
|
});
|
|
@@ -171,7 +186,12 @@ RunCommand.usage = Command.Usage({
|
|
|
171
186
|
details: "This command runs the specified command and collects Allure results. " +
|
|
172
187
|
"Override results discovery with repeated `--results-dir` (CLI overrides `config.resultsDir`). " +
|
|
173
188
|
"When neither is set, directories named `allure-results` are discovered dynamically. " +
|
|
174
|
-
"Quote globs in the shell so they are not expanded early
|
|
189
|
+
"Quote globs in the shell so they are not expanded early.\n\n" +
|
|
190
|
+
"Global flags: `--verbose`/`-v` prints verbose Allure messages, `--quiet`/`-q` prints errors only. " +
|
|
191
|
+
"They cannot be combined and must come before `--`. " +
|
|
192
|
+
"`--quiet` also implies `--silent`: the test process output is not printed, but is still attached to the report " +
|
|
193
|
+
"unless `--ignore-logs` is set. `--verbose` does not change `--silent` or `--ignore-logs`. " +
|
|
194
|
+
"Without either flag the log level comes from `ALLURE_LOG_LEVEL` (default: info).",
|
|
175
195
|
examples: [
|
|
176
196
|
["run -- npm run test", "Run npm run test and collect Allure results"],
|
|
177
197
|
["run --rerun 3 -- npm run test", "Run npm run test and rerun failed tests up to 3 times"],
|
|
@@ -179,6 +199,8 @@ RunCommand.usage = Command.Usage({
|
|
|
179
199
|
"run --dump=my-dump -- npm run test",
|
|
180
200
|
"Run npm run test and pack inner report state into my-dump.zip archive to restore the state in the next run",
|
|
181
201
|
],
|
|
202
|
+
["run --quiet -- npm run test", "Print only errors and hide the test process output"],
|
|
203
|
+
["run --verbose -- npm run test", "Print verbose Allure messages while running the tests"],
|
|
182
204
|
[
|
|
183
205
|
"run --results-dir './artifacts/**/allure-results' -- npm test",
|
|
184
206
|
"Override results discovery with a quoted glob",
|
package/dist/commands/slack.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ export declare class SlackCommand extends Command {
|
|
|
5
5
|
resultsDir: string[];
|
|
6
6
|
config: string | undefined;
|
|
7
7
|
cwd: string | undefined;
|
|
8
|
-
token: string;
|
|
9
|
-
channel: string;
|
|
8
|
+
token: string | undefined;
|
|
9
|
+
channel: string | undefined;
|
|
10
|
+
webhook: string | undefined;
|
|
10
11
|
execute(): Promise<void>;
|
|
11
12
|
}
|
package/dist/commands/slack.js
CHANGED
|
@@ -3,7 +3,7 @@ import { realpath } from "node:fs/promises";
|
|
|
3
3
|
import process, { exit } from "node:process";
|
|
4
4
|
import { AllureReport, readConfig } from "@allurereport/core";
|
|
5
5
|
import SlackPlugin from "@allurereport/plugin-slack";
|
|
6
|
-
import { Command, Option } from "clipanion";
|
|
6
|
+
import { Command, Option, UsageError } from "clipanion";
|
|
7
7
|
import { red } from "yoctocolors";
|
|
8
8
|
import { resolveAndFindResultsDirs } from "../utils/resultsPatterns.js";
|
|
9
9
|
export class SlackCommand extends Command {
|
|
@@ -19,20 +19,28 @@ export class SlackCommand extends Command {
|
|
|
19
19
|
description: "The working directory for the command to run (default: current working directory)",
|
|
20
20
|
});
|
|
21
21
|
this.token = Option.String("--token,-t", {
|
|
22
|
-
description: "Slack Bot User OAuth Token",
|
|
23
|
-
|
|
22
|
+
description: "Slack Bot User OAuth Token (default: ALLURE_SLACK_TOKEN environment variable)",
|
|
23
|
+
env: "ALLURE_SLACK_TOKEN",
|
|
24
24
|
});
|
|
25
25
|
this.channel = Option.String("--channel", {
|
|
26
|
-
description: "Slack channelId",
|
|
27
|
-
|
|
26
|
+
description: "Slack channelId (default: ALLURE_SLACK_CHANNEL environment variable)",
|
|
27
|
+
env: "ALLURE_SLACK_CHANNEL",
|
|
28
|
+
});
|
|
29
|
+
this.webhook = Option.String("--webhook", {
|
|
30
|
+
description: "Slack incoming webhook URL (default: ALLURE_SLACK_WEBHOOK environment variable)",
|
|
31
|
+
env: "ALLURE_SLACK_WEBHOOK",
|
|
28
32
|
});
|
|
29
33
|
}
|
|
30
34
|
async execute() {
|
|
35
|
+
if (!this.webhook && (!this.token || !this.channel)) {
|
|
36
|
+
throw new UsageError("Provide either --webhook or both --token and --channel");
|
|
37
|
+
}
|
|
31
38
|
const cwd = await realpath(this.cwd ?? process.cwd());
|
|
32
39
|
const before = new Date().getTime();
|
|
33
40
|
const defaultSlackOptions = {
|
|
34
41
|
token: this.token,
|
|
35
42
|
channel: this.channel,
|
|
43
|
+
webhook: this.webhook,
|
|
36
44
|
};
|
|
37
45
|
const config = await readConfig(cwd, this.config);
|
|
38
46
|
const { resultDirectories, patterns } = await resolveAndFindResultsDirs(cwd, this.resultsDir, config.resultsDir);
|
|
@@ -69,6 +77,10 @@ SlackCommand.usage = Command.Usage({
|
|
|
69
77
|
"slack ./allure-results --token xoxb-token --channel C12345",
|
|
70
78
|
"Post test results from the ./allure-results directory to the specified Slack channel",
|
|
71
79
|
],
|
|
80
|
+
[
|
|
81
|
+
"slack ./allure-results --webhook https://hooks.slack.com/services/...",
|
|
82
|
+
"Post test results using a Slack incoming webhook",
|
|
83
|
+
],
|
|
72
84
|
[
|
|
73
85
|
"slack ./packages/*/allure-results --token xoxb-token --channel C12345",
|
|
74
86
|
"Post test results from all Allure result directories matching the pattern",
|
|
@@ -23,7 +23,7 @@ export class TestOpsPlanCommand extends Command {
|
|
|
23
23
|
const endpoint = env.ALLURE_ENDPOINT;
|
|
24
24
|
const accessToken = env.ALLURE_TOKEN;
|
|
25
25
|
if (!endpoint || !accessToken) {
|
|
26
|
-
console.error(red("ALLURE_ENDPOINT and ALLURE_TOKEN are required to fetch a test plan from TestOps"));
|
|
26
|
+
console.error(red("ALLURE_ENDPOINT and ALLURE_TOKEN are required to fetch a test plan from Allure TestOps"));
|
|
27
27
|
exit(1);
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
@@ -54,10 +54,10 @@ export class TestOpsPlanCommand extends Command {
|
|
|
54
54
|
}
|
|
55
55
|
TestOpsPlanCommand.paths = [["testops-plan"]];
|
|
56
56
|
TestOpsPlanCommand.usage = Command.Usage({
|
|
57
|
-
description: "Fetches a testplan.json for the current TestOps job run",
|
|
57
|
+
description: "Fetches a testplan.json for the current Allure TestOps job run",
|
|
58
58
|
details: "Reads ALLURE_JOB_RUN_ID and, when it's set, downloads the test cases selected for that job run " +
|
|
59
|
-
"from TestOps and writes them as a testplan.json compatible with ALLURE_TESTPLAN_PATH. " +
|
|
60
|
-
"Does nothing when ALLURE_JOB_RUN_ID isn't set, which covers every run TestOps didn't trigger itself.",
|
|
59
|
+
"from Allure TestOps and writes them as a testplan.json compatible with ALLURE_TESTPLAN_PATH. " +
|
|
60
|
+
"Does nothing when ALLURE_JOB_RUN_ID isn't set, which covers every run Allure TestOps didn't trigger itself.",
|
|
61
61
|
examples: [
|
|
62
62
|
["testops-plan", "Write ./testplan.json from the current job run, if any"],
|
|
63
63
|
["testops-plan --output custom-testplan.json", "Write to a custom path instead"],
|
package/dist/commands/watch.d.ts
CHANGED
package/dist/commands/watch.js
CHANGED
|
@@ -12,6 +12,7 @@ import { PathResultFile } from "@allurereport/reader-api";
|
|
|
12
12
|
import { serve } from "@allurereport/static-server";
|
|
13
13
|
import { Command, Option } from "clipanion";
|
|
14
14
|
import { resolveResultsPatterns } from "../utils/resultsPatterns.js";
|
|
15
|
+
import { serverOptionsFromConfig } from "../utils/serverOptions.js";
|
|
15
16
|
import { boundedTerminationSignal, notifySignals, waitForAbort } from "../utils/signals.js";
|
|
16
17
|
import { allureResultsDirectoriesGlobWatcher } from "./commons/resultsDiscovery.js";
|
|
17
18
|
export class WatchCommand extends Command {
|
|
@@ -38,6 +39,9 @@ export class WatchCommand extends Command {
|
|
|
38
39
|
this.port = Option.String("--port", {
|
|
39
40
|
description: "The port to serve the reports on (default: random port)",
|
|
40
41
|
});
|
|
42
|
+
this.host = Option.String("--host", {
|
|
43
|
+
description: "The host (network interface) to serve the reports on, e.g. 127.0.0.1 or 0.0.0.0 (default: all interfaces)",
|
|
44
|
+
});
|
|
41
45
|
this.preserve = Option.Boolean("--preserve", {
|
|
42
46
|
description: "Don't clear terminal output on the data refresh",
|
|
43
47
|
});
|
|
@@ -57,6 +61,7 @@ export class WatchCommand extends Command {
|
|
|
57
61
|
name: this.reportName,
|
|
58
62
|
open: this.open,
|
|
59
63
|
port: this.port,
|
|
64
|
+
host: this.host,
|
|
60
65
|
});
|
|
61
66
|
const resultsPatterns = resolveResultsPatterns(this.resultsDir ?? [], config.resultsDir);
|
|
62
67
|
const useDynamicNameDiscovery = resultsPatterns.length === 0;
|
|
@@ -70,7 +75,7 @@ export class WatchCommand extends Command {
|
|
|
70
75
|
}
|
|
71
76
|
const server = await serve({
|
|
72
77
|
servePath: config.output,
|
|
73
|
-
|
|
78
|
+
...serverOptionsFromConfig(config),
|
|
74
79
|
live: false,
|
|
75
80
|
open: false,
|
|
76
81
|
});
|
|
@@ -181,6 +186,10 @@ WatchCommand.usage = Command.Usage({
|
|
|
181
186
|
"Quote globs in the shell so they are not expanded early.",
|
|
182
187
|
examples: [
|
|
183
188
|
["watch ./allure-results", "Watch for changes in the ./allure-results directory"],
|
|
189
|
+
[
|
|
190
|
+
"watch ./allure-results --host 127.0.0.1",
|
|
191
|
+
"Watch for changes in the ./allure-results directory and serve the report on the 127.0.0.1 interface only",
|
|
192
|
+
],
|
|
184
193
|
[
|
|
185
194
|
"watch ./allure-results --port 8080",
|
|
186
195
|
"Watch for changes in the ./allure-results directory and serve the report on port 8080",
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import process, { argv } from "node:process";
|
|
3
|
-
import { Builtins, Cli } from "clipanion";
|
|
4
|
-
import { AgentCommand, AGENT_TASK_MAP_HELP, AgentCapabilitiesCommand, AgentInspectCommand, AgentLatestCommand, AgentQueryCommand, AgentSelectCommand, AgentStateDirCommand, Allure2Command, AwesomeCommand, CheckCommand, ClassicCommand, CsvCommand, DashboardCommand, GenerateCommand, GitlabGenerateCommand, HistoryCommand, JiraClearCommand, LogCommand, OpenCommand, QualityGateCommand, ResultsPackCommand, ResultsUnpackCommand, RunCommand, SlackCommand, TestOpsPlanCommand, TestPlanCommand, WatchCommand, isAgentTaskMapHelpRequest, } from "./commands/index.js";
|
|
5
|
-
|
|
3
|
+
import { Builtins, Cli, UsageError } from "clipanion";
|
|
4
|
+
import { AgentCommand, AGENT_TASK_MAP_HELP, AgentCapabilitiesCommand, AgentInspectCommand, AgentLatestCommand, AgentQueryCommand, AgentSelectCommand, AgentStateDirCommand, Allure2Command, AwesomeCommand, CheckCommand, ClassicCommand, CsvCommand, DashboardCommand, GenerateCommand, GitlabGenerateCommand, HistoryCommand, JiraClearCommand, LogCommand, MigrateCommand, OpenCommand, QualityGateCommand, ResultsPackCommand, ResultsUnpackCommand, RunCommand, SlackCommand, TestOpsPlanCommand, TestPlanCommand, WatchCommand, isAgentTaskMapHelpRequest, } from "./commands/index.js";
|
|
5
|
+
import { ALLURE2_MIGRATE_REMINDER, getAllure2Hint } from "./utils/allure2Hints.js";
|
|
6
|
+
import { notifyAboutUpdate, parseUpdateNotifierArgs } from "./utils/updateNotifier.js";
|
|
7
|
+
import { applyVerbosity, extractVerbosityFlags } from "./utils/verbosity.js";
|
|
8
|
+
const [node, app, ...rawArgs] = argv;
|
|
6
9
|
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
7
10
|
const cli = new Cli({
|
|
8
11
|
binaryName: pkg.name,
|
|
@@ -26,6 +29,7 @@ cli.register(GenerateCommand);
|
|
|
26
29
|
cli.register(HistoryCommand);
|
|
27
30
|
cli.register(JiraClearCommand);
|
|
28
31
|
cli.register(LogCommand);
|
|
32
|
+
cli.register(MigrateCommand);
|
|
29
33
|
cli.register(OpenCommand);
|
|
30
34
|
cli.register(QualityGateCommand);
|
|
31
35
|
cli.register(RunCommand);
|
|
@@ -38,16 +42,33 @@ cli.register(ResultsUnpackCommand);
|
|
|
38
42
|
cli.register(GitlabGenerateCommand);
|
|
39
43
|
cli.register(Builtins.HelpCommand);
|
|
40
44
|
cli.register(Builtins.VersionCommand);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
const printAllure2Hint = (args) => {
|
|
46
|
+
try {
|
|
47
|
+
cli.process(args);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const hint = getAllure2Hint(args, error instanceof Error ? error.message : String(error));
|
|
51
|
+
if (hint) {
|
|
52
|
+
console.error(`\nComing from Allure 2? ${hint}\n${ALLURE2_MIGRATE_REMINDER}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const main = async () => {
|
|
57
|
+
const { args: notifierArgs, disabled } = parseUpdateNotifierArgs(rawArgs);
|
|
58
|
+
const { args, verbosity } = extractVerbosityFlags(notifierArgs);
|
|
59
|
+
applyVerbosity(verbosity);
|
|
60
|
+
void notifyAboutUpdate(args, disabled || verbosity === "quiet");
|
|
61
|
+
const exitCode = await cli.run(args);
|
|
44
62
|
if (exitCode === 0 && isAgentTaskMapHelpRequest(args)) {
|
|
45
63
|
process.stdout.write(`\n${AGENT_TASK_MAP_HELP}`);
|
|
46
64
|
}
|
|
65
|
+
if (exitCode !== 0) {
|
|
66
|
+
printAllure2Hint(args);
|
|
67
|
+
}
|
|
47
68
|
process.exitCode = exitCode;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
console.error(error);
|
|
69
|
+
};
|
|
70
|
+
void main().catch((error) => {
|
|
71
|
+
console.error(error instanceof UsageError ? `Usage Error: ${error.message}` : error);
|
|
51
72
|
process.exitCode = 1;
|
|
52
73
|
});
|
|
53
74
|
export { defineConfig } from "@allurereport/plugin-api";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const ALLURE2_COMMAND_MAP: readonly {
|
|
2
|
+
allure2: string;
|
|
3
|
+
allure3: string;
|
|
4
|
+
note: string;
|
|
5
|
+
}[];
|
|
6
|
+
export declare const renderAllure2CommandMap: () => string;
|
|
7
|
+
export declare const getAllure2Hint: (args: readonly string[], errorMessage: string) => string | undefined;
|
|
8
|
+
export declare const ALLURE2_MIGRATE_REMINDER = "Run `allure migrate` to see how Allure 2 commands map to Allure 3.";
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const OPTION_HINTS = {
|
|
2
|
+
"--clean": "`--clean` is available for `allure generate` only: it removes the output directory before generating. " +
|
|
3
|
+
"`allure run` recreates the output directory on every launch.",
|
|
4
|
+
"--profile": "Profiles are gone. Use a single allurerc file and pass it with `--config`.",
|
|
5
|
+
"--configDirectory": "The config directory is gone. Use a single allurerc file and pass it with `--config`.",
|
|
6
|
+
"--lang": "`generate` and `run` have no `--lang`. Set `reportLanguage` in the plugin options of allurerc, " +
|
|
7
|
+
"or use a plugin command like `allure awesome --lang <code>`.",
|
|
8
|
+
"--report-language": "`generate` and `run` have no `--report-language`. Set `reportLanguage` in the plugin options of allurerc, " +
|
|
9
|
+
"or use a plugin command like `allure awesome --report-language <code>`.",
|
|
10
|
+
"--single-file": "`generate` and `run` have no `--single-file`. Set `singleFile` in the plugin options of allurerc, " +
|
|
11
|
+
"or use a plugin command like `allure awesome --single-file`.",
|
|
12
|
+
};
|
|
13
|
+
export const ALLURE2_COMMAND_MAP = [
|
|
14
|
+
{
|
|
15
|
+
allure2: "allure serve <results>",
|
|
16
|
+
allure3: "allure open <results>",
|
|
17
|
+
note: "Generates the report into a temp directory and serves it. For live updates use `allure watch <results>`",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
allure2: "allure generate <results> -o <dir>",
|
|
21
|
+
allure3: "allure generate <results> -o <dir>",
|
|
22
|
+
note: "Same command. Add `--open` to serve the report afterwards",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
allure2: "allure generate <results> --clean",
|
|
26
|
+
allure3: "allure generate <results> --clean",
|
|
27
|
+
note: "Same flag: removes the output directory before generating. `allure run` recreates it on every launch",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
allure2: "allure open <report>",
|
|
31
|
+
allure3: "allure open <report>",
|
|
32
|
+
note: "Same command. Serves an already generated report",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
allure2: "allure serve|open --host <host> --port <port>",
|
|
36
|
+
allure3: "allure open --host <host> --port <port>",
|
|
37
|
+
note: "`--host` and `--port` are also available for `generate --open`, `run` and `watch`, and in allurerc",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
allure2: "run tests, then allure serve",
|
|
41
|
+
allure3: "allure run -- <test command>",
|
|
42
|
+
note: "Runs the tests, collects results into a report, and serves it with `--open`",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
allure2: "allure generate --single-file --lang <code>",
|
|
46
|
+
allure3: "allure awesome <results> --single-file --lang <code>",
|
|
47
|
+
note: "Or set `singleFile` and `reportLanguage` in the plugin options of allurerc",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
allure2: "--profile <name>, --configDirectory <dir>, allure.yml",
|
|
51
|
+
allure3: "--config <path to allurerc>",
|
|
52
|
+
note: "One config file with plugins, report options and quality gates instead of profiles",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
allure2: "allure plugin",
|
|
56
|
+
allure3: "`plugins` section of allurerc",
|
|
57
|
+
note: "Plugins are enabled and configured in the config file",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
allure2: "--verbose, --quiet",
|
|
61
|
+
allure3: "--verbose, --quiet (-v, -q)",
|
|
62
|
+
note: "Same global flags. For `allure run`, `--quiet` also implies `--silent`; `--ignore-logs` is separate",
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
export const renderAllure2CommandMap = () => [
|
|
66
|
+
"Allure 2 -> Allure 3",
|
|
67
|
+
"",
|
|
68
|
+
...ALLURE2_COMMAND_MAP.flatMap(({ allure2, allure3, note }) => [
|
|
69
|
+
` ${allure2}`,
|
|
70
|
+
` -> ${allure3}`,
|
|
71
|
+
` ${note}`,
|
|
72
|
+
"",
|
|
73
|
+
]),
|
|
74
|
+
].join("\n");
|
|
75
|
+
const COMMAND_HINTS = {
|
|
76
|
+
plugin: "There is no `allure plugin` command. Plugins are listed in the `plugins` section of allurerc.",
|
|
77
|
+
report: "Allure 3 has no `allure report` group. Use `allure generate` to build a report and `allure open` to serve it.",
|
|
78
|
+
};
|
|
79
|
+
const UNSUPPORTED_OPTION_RE = /Unsupported option name \("([^"]+)"\)/;
|
|
80
|
+
export const getAllure2Hint = (args, errorMessage) => {
|
|
81
|
+
const optionMatch = UNSUPPORTED_OPTION_RE.exec(errorMessage);
|
|
82
|
+
if (optionMatch) {
|
|
83
|
+
return OPTION_HINTS[optionMatch[1]];
|
|
84
|
+
}
|
|
85
|
+
if (/Command not found/i.test(errorMessage) && args[0] !== undefined) {
|
|
86
|
+
return COMMAND_HINTS[args[0]];
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
};
|
|
90
|
+
export const ALLURE2_MIGRATE_REMINDER = "Run `allure migrate` to see how Allure 2 commands map to Allure 3.";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type CleanOutputParams = {
|
|
2
|
+
output: string;
|
|
3
|
+
cwd: string;
|
|
4
|
+
resultsDirs?: string[];
|
|
5
|
+
inputs?: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare const getUnsafeCleanReason: (params: CleanOutputParams) => Promise<string | undefined>;
|
|
8
|
+
export declare const cleanOutputDirectory: (params: CleanOutputParams) => Promise<void>;
|
|
9
|
+
export declare const isOutputDirectoryNotEmpty: (cwd: string, output: string) => Promise<boolean>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readdir, realpath, rm, stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { cwd as processCwd } from "node:process";
|
|
5
|
+
const isSameOrInside = (parent, child) => {
|
|
6
|
+
const rel = relative(parent, child);
|
|
7
|
+
return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
|
|
8
|
+
};
|
|
9
|
+
const realResolve = async (path) => {
|
|
10
|
+
const absolute = resolve(path);
|
|
11
|
+
try {
|
|
12
|
+
return await realpath(absolute);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
const parent = dirname(absolute);
|
|
16
|
+
return parent === absolute ? absolute : join(await realResolve(parent), basename(absolute));
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export const getUnsafeCleanReason = async (params) => {
|
|
20
|
+
const cwd = resolve(params.cwd);
|
|
21
|
+
const output = await realResolve(resolve(cwd, params.output));
|
|
22
|
+
if (output === parse(output).root) {
|
|
23
|
+
return "it is a filesystem root";
|
|
24
|
+
}
|
|
25
|
+
if ((await stat(output).catch(() => undefined))?.isDirectory() === false) {
|
|
26
|
+
return "it is not a directory";
|
|
27
|
+
}
|
|
28
|
+
const mustSurvive = [
|
|
29
|
+
["the working directory", cwd],
|
|
30
|
+
["the process working directory", processCwd()],
|
|
31
|
+
["the home directory", homedir()],
|
|
32
|
+
...(params.resultsDirs ?? []).map((dir) => ["a results directory", resolve(cwd, dir)]),
|
|
33
|
+
...(params.inputs ?? []).map((file) => ["an input file", resolve(cwd, file)]),
|
|
34
|
+
];
|
|
35
|
+
for (const [label, path] of mustSurvive) {
|
|
36
|
+
if (isSameOrInside(output, await realResolve(path))) {
|
|
37
|
+
return `it is or contains ${label} (${path})`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (const dir of params.resultsDirs ?? []) {
|
|
41
|
+
if (isSameOrInside(await realResolve(resolve(cwd, dir)), output)) {
|
|
42
|
+
return `it is located inside a results directory (${resolve(cwd, dir)})`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
};
|
|
47
|
+
export const cleanOutputDirectory = async (params) => {
|
|
48
|
+
const output = resolve(params.cwd, params.output);
|
|
49
|
+
const reason = await getUnsafeCleanReason(params);
|
|
50
|
+
if (reason) {
|
|
51
|
+
throw new Error(`Refusing to clean the output directory ${output}: ${reason}`);
|
|
52
|
+
}
|
|
53
|
+
await rm(output, { recursive: true, force: true });
|
|
54
|
+
};
|
|
55
|
+
export const isOutputDirectoryNotEmpty = async (cwd, output) => {
|
|
56
|
+
try {
|
|
57
|
+
return (await readdir(resolve(cwd, output))).length > 0;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export declare const findFilesByGlobs: (basePath: string, patterns: readonly string[]
|
|
1
|
+
export declare const findFilesByGlobs: (basePath: string, patterns: readonly string[], { dot, windowsPathsNoEscape }?: {
|
|
2
|
+
dot?: boolean;
|
|
3
|
+
windowsPathsNoEscape?: boolean;
|
|
4
|
+
}) => Promise<string[]>;
|
|
2
5
|
export declare const findDirectoriesByGlobs: (basePath: string, patterns: readonly string[]) => Promise<string[]>;
|
|
3
6
|
export declare const findAllureResultDirectories: (basePath: string, patterns: readonly string[]) => Promise<{
|
|
4
7
|
patterns: readonly string[];
|
package/dist/utils/fileSystem.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { glob } from "glob";
|
|
2
|
-
export const findFilesByGlobs = async (basePath, patterns) => {
|
|
2
|
+
export const findFilesByGlobs = async (basePath, patterns, { dot = true, windowsPathsNoEscape = true } = {}) => {
|
|
3
3
|
const files = [];
|
|
4
4
|
for (const pattern of patterns) {
|
|
5
5
|
const searchResults = await glob(pattern, {
|
|
6
6
|
nodir: true,
|
|
7
|
-
dot
|
|
7
|
+
dot,
|
|
8
8
|
absolute: true,
|
|
9
|
-
windowsPathsNoEscape
|
|
9
|
+
windowsPathsNoEscape,
|
|
10
10
|
cwd: basePath,
|
|
11
11
|
});
|
|
12
12
|
files.push(...searchResults);
|