allure 3.20.1 → 3.22.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 +64 -3
- 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/ghWorkflow.d.ts +11 -0
- package/dist/commands/ghWorkflow.js +77 -0
- package/dist/commands/index.d.ts +4 -1
- package/dist/commands/index.js +4 -1
- package/dist/commands/jira/clear.js +3 -3
- package/dist/commands/migrate.d.ts +6 -0
- package/dist/commands/migrate.js +15 -0
- package/dist/commands/open.d.ts +3 -10
- package/dist/commands/open.js +20 -99
- 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/serve.d.ts +19 -0
- package/dist/commands/serve.js +168 -0
- 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 +32 -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/ghWorkflow.d.ts +10 -0
- package/dist/utils/ghWorkflow.js +75 -0
- 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 +35 -28
- /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
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { cwd as processCwd, exit } from "node:process";
|
|
6
|
+
import { readConfig } from "@allurereport/core";
|
|
7
|
+
import { serve } from "@allurereport/static-server";
|
|
8
|
+
import { Command, Option } from "clipanion";
|
|
9
|
+
import { red } from "yoctocolors";
|
|
10
|
+
import { findFilesByGlobs } from "./../utils/fileSystem.js";
|
|
11
|
+
import { resolveResultsPatterns } from "./../utils/resultsPatterns.js";
|
|
12
|
+
import { serverOptionsFromConfig } from "./../utils/serverOptions.js";
|
|
13
|
+
import { notifySignals, waitForAbort } from "./../utils/signals.js";
|
|
14
|
+
import { generate } from "./commons/generate.js";
|
|
15
|
+
const ESCALATION_DELAY = 3000;
|
|
16
|
+
export class BaseServeCommand extends Command {
|
|
17
|
+
constructor() {
|
|
18
|
+
super(...arguments);
|
|
19
|
+
this.resultsDir = Option.Rest({
|
|
20
|
+
name: "A report path to open, or results patterns. Overrides config.resultsDir. " +
|
|
21
|
+
"Empty Rest serves the configured output.",
|
|
22
|
+
});
|
|
23
|
+
this.config = Option.String("--config,-c", {
|
|
24
|
+
description: "Path to Allure configuration file",
|
|
25
|
+
});
|
|
26
|
+
this.port = Option.String("--port", {
|
|
27
|
+
description: "Port for the report server. Overrides the configured port; otherwise, a random available port is used",
|
|
28
|
+
});
|
|
29
|
+
this.host = Option.String("--host", {
|
|
30
|
+
description: "Host address to bind the report server to, e.g. 127.0.0.1 or 0.0.0.0. " +
|
|
31
|
+
"Overrides the configured host (default: all interfaces)",
|
|
32
|
+
});
|
|
33
|
+
this.cwd = Option.String("--cwd", {
|
|
34
|
+
description: "Working directory for the command (default: current working directory)",
|
|
35
|
+
});
|
|
36
|
+
this.hideLabels = Option.Array("--hide-labels", {
|
|
37
|
+
description: "Hide labels by exact name in generated reports. Repeat the option for multiple labels",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async execute() {
|
|
41
|
+
const cwd = this.cwd ?? processCwd();
|
|
42
|
+
const hideLabels = this.hideLabels?.length ? this.hideLabels : undefined;
|
|
43
|
+
const config = await readConfig(cwd, this.config, {
|
|
44
|
+
port: this.port,
|
|
45
|
+
host: this.host,
|
|
46
|
+
});
|
|
47
|
+
const servePath = this.resolveReportPath(cwd, this.resultsDir, config.output);
|
|
48
|
+
const resolvedPatterns = resolveResultsPatterns(this.resultsDir, config.resultsDir);
|
|
49
|
+
const onSignalRepeat = (signal) => {
|
|
50
|
+
const info = notifier.info();
|
|
51
|
+
if (signal !== "SIGINT" ||
|
|
52
|
+
info?.signal !== "SIGINT" ||
|
|
53
|
+
Date.now() >= (info?.receivedAt ?? 0) + ESCALATION_DELAY) {
|
|
54
|
+
exit(info?.code ?? 1);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const notifier = notifySignals(["SIGINT", "SIGTERM"], onSignalRepeat);
|
|
58
|
+
const signalExitCode = () => {
|
|
59
|
+
const info = notifier.info();
|
|
60
|
+
return info?.signal === "SIGINT" ? 0 : (info?.code ?? 0);
|
|
61
|
+
};
|
|
62
|
+
let server;
|
|
63
|
+
let tmpDir;
|
|
64
|
+
try {
|
|
65
|
+
if (await this.reportExists(servePath)) {
|
|
66
|
+
if (notifier.signal.aborted) {
|
|
67
|
+
return signalExitCode();
|
|
68
|
+
}
|
|
69
|
+
server = await serve({
|
|
70
|
+
...serverOptionsFromConfig(config),
|
|
71
|
+
servePath,
|
|
72
|
+
open: this.shouldOpen,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
else if (resolvedPatterns.length) {
|
|
76
|
+
tmpDir = await mkdtemp(join(tmpdir(), "allure-report-"));
|
|
77
|
+
const config = await readConfig(cwd, this.config, {
|
|
78
|
+
port: this.port,
|
|
79
|
+
host: this.host,
|
|
80
|
+
output: tmpDir,
|
|
81
|
+
hideLabels,
|
|
82
|
+
});
|
|
83
|
+
if (notifier.signal.aborted) {
|
|
84
|
+
return signalExitCode();
|
|
85
|
+
}
|
|
86
|
+
await generate({
|
|
87
|
+
resultsDir: this.resultsDir,
|
|
88
|
+
cwd,
|
|
89
|
+
config,
|
|
90
|
+
});
|
|
91
|
+
if (notifier.signal.aborted) {
|
|
92
|
+
return signalExitCode();
|
|
93
|
+
}
|
|
94
|
+
server = await serve({
|
|
95
|
+
...serverOptionsFromConfig(config),
|
|
96
|
+
servePath: config.output,
|
|
97
|
+
open: this.shouldOpen,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
console.error(red(`A report doesn't exist in ${servePath} and no input was provided to generate.`));
|
|
102
|
+
return 1;
|
|
103
|
+
}
|
|
104
|
+
await waitForAbort(notifier.signal);
|
|
105
|
+
console.info(`Allure is stopping.`);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
try {
|
|
109
|
+
await server?.stop();
|
|
110
|
+
}
|
|
111
|
+
catch { }
|
|
112
|
+
if (tmpDir) {
|
|
113
|
+
try {
|
|
114
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
115
|
+
}
|
|
116
|
+
catch { }
|
|
117
|
+
}
|
|
118
|
+
notifier.dispose();
|
|
119
|
+
}
|
|
120
|
+
return signalExitCode();
|
|
121
|
+
}
|
|
122
|
+
resolveReportPath(cwd, inputs, fallback = "allure-report") {
|
|
123
|
+
if (inputs.length <= 1) {
|
|
124
|
+
const [maybeRelativeReportPath = fallback] = inputs;
|
|
125
|
+
return resolve(cwd, maybeRelativeReportPath);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async reportExists(reportPath) {
|
|
129
|
+
if (reportPath && existsSync(reportPath)) {
|
|
130
|
+
const summaryFiles = await findFilesByGlobs(reportPath, [join("**", "summary.json")]);
|
|
131
|
+
return summaryFiles.length > 0;
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
BaseServeCommand.paths = [["serve"]];
|
|
137
|
+
BaseServeCommand.usage = Command.Usage({
|
|
138
|
+
description: "Serves an existing report or generates and serves a report from test results.",
|
|
139
|
+
details: "This command starts a report server without opening a browser. If test results are provided, it generates the" +
|
|
140
|
+
" report in a temporary directory before serving it. With no arguments, it serves the configured output " +
|
|
141
|
+
"directory or, if no report exists there, generates a report from the configured test results. Use --open " +
|
|
142
|
+
"to also open the report in the default browser.",
|
|
143
|
+
examples: [
|
|
144
|
+
["serve ./allure-results", "Generate and serve the report from the given test results directory"],
|
|
145
|
+
["serve --port 8080 ./allure-report", "Serve the report on port 8080"],
|
|
146
|
+
["serve --open ./allure-report", "Serve the report and open it in the default browser"],
|
|
147
|
+
["serve --host 127.0.0.1 ./allure-report", "Serve the report on the loopback interface only."],
|
|
148
|
+
[
|
|
149
|
+
"serve ./packages/*/allure-results",
|
|
150
|
+
"Generate and serve the report from all test results directories matching the pattern",
|
|
151
|
+
],
|
|
152
|
+
[
|
|
153
|
+
"serve ./packages/foo/allure-results ./packages/bar/allure-results",
|
|
154
|
+
"Generate and serve the report from two test results directories",
|
|
155
|
+
],
|
|
156
|
+
],
|
|
157
|
+
});
|
|
158
|
+
export class ServeCommand extends BaseServeCommand {
|
|
159
|
+
constructor() {
|
|
160
|
+
super(...arguments);
|
|
161
|
+
this.open = Option.Boolean("--open", {
|
|
162
|
+
description: "Open the report in the default browser (default: false)",
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
get shouldOpen() {
|
|
166
|
+
return Boolean(this.open);
|
|
167
|
+
}
|
|
168
|
+
}
|
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, GhWorkflowCommand, GitlabGenerateCommand, HistoryCommand, JiraClearCommand, LogCommand, MigrateCommand, OpenCommand, QualityGateCommand, ResultsPackCommand, ResultsUnpackCommand, RunCommand, ServeCommand, 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,
|
|
@@ -23,12 +26,15 @@ cli.register(ClassicCommand);
|
|
|
23
26
|
cli.register(CsvCommand);
|
|
24
27
|
cli.register(DashboardCommand);
|
|
25
28
|
cli.register(GenerateCommand);
|
|
29
|
+
cli.register(GhWorkflowCommand);
|
|
26
30
|
cli.register(HistoryCommand);
|
|
27
31
|
cli.register(JiraClearCommand);
|
|
28
32
|
cli.register(LogCommand);
|
|
33
|
+
cli.register(MigrateCommand);
|
|
29
34
|
cli.register(OpenCommand);
|
|
30
35
|
cli.register(QualityGateCommand);
|
|
31
36
|
cli.register(RunCommand);
|
|
37
|
+
cli.register(ServeCommand);
|
|
32
38
|
cli.register(SlackCommand);
|
|
33
39
|
cli.register(TestOpsPlanCommand);
|
|
34
40
|
cli.register(TestPlanCommand);
|
|
@@ -38,16 +44,33 @@ cli.register(ResultsUnpackCommand);
|
|
|
38
44
|
cli.register(GitlabGenerateCommand);
|
|
39
45
|
cli.register(Builtins.HelpCommand);
|
|
40
46
|
cli.register(Builtins.VersionCommand);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
const printAllure2Hint = (args) => {
|
|
48
|
+
try {
|
|
49
|
+
cli.process(args);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const hint = getAllure2Hint(args, error instanceof Error ? error.message : String(error));
|
|
53
|
+
if (hint) {
|
|
54
|
+
console.error(`\nComing from Allure 2? ${hint}\n${ALLURE2_MIGRATE_REMINDER}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const main = async () => {
|
|
59
|
+
const { args: notifierArgs, disabled } = parseUpdateNotifierArgs(rawArgs);
|
|
60
|
+
const { args, verbosity } = extractVerbosityFlags(notifierArgs);
|
|
61
|
+
applyVerbosity(verbosity);
|
|
62
|
+
void notifyAboutUpdate(args, disabled || verbosity === "quiet");
|
|
63
|
+
const exitCode = await cli.run(args);
|
|
44
64
|
if (exitCode === 0 && isAgentTaskMapHelpRequest(args)) {
|
|
45
65
|
process.stdout.write(`\n${AGENT_TASK_MAP_HELP}`);
|
|
46
66
|
}
|
|
67
|
+
if (exitCode !== 0) {
|
|
68
|
+
printAllure2Hint(args);
|
|
69
|
+
}
|
|
47
70
|
process.exitCode = exitCode;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
console.error(error);
|
|
71
|
+
};
|
|
72
|
+
void main().catch((error) => {
|
|
73
|
+
console.error(error instanceof UsageError ? `Usage Error: ${error.message}` : error);
|
|
51
74
|
process.exitCode = 1;
|
|
52
75
|
});
|
|
53
76
|
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);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { EnvironmentsConfig } from "@allurereport/core-api";
|
|
2
|
+
export type GhWorkflowOptions = {
|
|
3
|
+
environmentIds: readonly string[];
|
|
4
|
+
testCommand?: readonly string[];
|
|
5
|
+
config?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare const shellQuote: (value: string) => string;
|
|
8
|
+
export declare const normalizeEnvironmentIds: (values: readonly string[]) => string[];
|
|
9
|
+
export declare const configuredEnvironmentIds: (environments: EnvironmentsConfig | undefined) => string[];
|
|
10
|
+
export declare const buildGhWorkflowYaml: ({ environmentIds, testCommand, config, }: GhWorkflowOptions) => string;
|