allure 3.21.0 → 3.22.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/README.md CHANGED
@@ -139,6 +139,23 @@ npx allure agent query --latest tests --status failed
139
139
  npx allure agent query --from ./agent-output findings --severity high
140
140
  ```
141
141
 
142
+ ### Generating GitHub Actions Jobs
143
+
144
+ Print GitHub Actions jobs that run your tests once per environment and build a single Allure report from all of them:
145
+
146
+ ```bash
147
+ npx allure gh-workflow --env foo --env bar --env baz -- npm test
148
+ ```
149
+
150
+ Paste the output under `jobs:` in a workflow file and fill in the `TODO` steps (runtime setup and dependencies). The output has two jobs:
151
+
152
+ - `test` runs `allure run --environment=<id> --dump=allure-results-<id> -- <test command>` for every environment in a matrix and uploads `allure-results-<id>.zip` as an artifact;
153
+ - `report` downloads all `allure-results-*` artifacts and runs `allure generate --dump="allure-results-*/*.zip"`.
154
+
155
+ The environment id, the dump file, the artifact name and the download pattern have to agree with each other; the command keeps them consistent.
156
+
157
+ Environments come from `--env` (repeatable, no config needed). Without `--env`, the ids declared under `environments` in your Allure config are used (integer-like ids come first, as JavaScript orders object keys). Pass the test command after `--` (default: `npm test`); `--config` is also added to the generated `allure` commands. The versions of the GitHub actions in the output are examples, pin the ones you use. Errors are written to stderr with a non-zero exit code, while command-line syntax errors are reported by the CLI parser on stdout.
158
+
142
159
  ### Generating Reports Manually
143
160
 
144
161
  If you already have test results and wish to generate a report manually, use the `generate` command:
@@ -165,6 +182,26 @@ npx allure open allure-report/awesome
165
182
 
166
183
  If the provided directory doesn't contain previously generated report, it will be treated as a source of results, and a new report will be generated on the fly and opened in the browser.
167
184
 
185
+ To stop serving the report, press CTRL+C.
186
+
187
+ #### Serving a report without opening
188
+
189
+ Use the `serve` command to start a local report server without opening a browser:
190
+
191
+ ```bash
192
+ npx allure serve <reportDir>
193
+ ```
194
+
195
+ Like `open`, this command uses the configured output directory when `<reportDir>` is omitted and generates a report on the fly when given test results. Open the URL printed in the console to view the report.
196
+
197
+ Use `--host` and `--port` to control the interfaces and port to listen:
198
+
199
+ ```bash
200
+ npx allure serve --port 8080 --host 127.0.0.1
201
+ ```
202
+
203
+ Press CTRL+C to stop serving.
204
+
168
205
  ### Real-time Report Monitoring
169
206
 
170
207
  When you need to monitor test execution live, the `watch` command provides dynamic report updates. It continuously monitors the specified results directory for changes and refreshes the report automatically:
@@ -205,7 +242,7 @@ tag. Unrecognized version formats are silently skipped.
205
242
  Allure 3 uses an `allurerc.ts`, `allurerc.mts`, `allurerc.cts`, `allurerc.mjs`, or `allurerc.js` configuration file to manage report settings, including the report name, output directory, and plugin options.
206
243
 
207
244
  > [!TIP]
208
- > We recommend using the **Awesome** plugin for the best experience.
245
+ > We recommend using the **Awesome** plugin for the best experience.
209
246
  > Support for Classic and Allure 2-style reports is currently experimental.
210
247
 
211
248
  ### Example Configuration File
@@ -272,7 +309,7 @@ For example, setting `"reportLanguage": "fr"` will render the report interface i
272
309
 
273
310
  You can also use `allurerc.json`, `allurerc.yaml`, or `allurerc.yml` files as a declarative way to configure Allure 3.
274
311
 
275
- > [!WARNING]
312
+ > [!WARNING]
276
313
  > Declarative formats don't support advanced features such as tests filtering or environments.
277
314
  > If you need these features, please consider using the `allurerc.ts`, `allurerc.mts`, `allurerc.cts`, `allurerc.mjs`, or `allurerc.js` files.
278
315
 
@@ -0,0 +1,11 @@
1
+ import { Command } from "clipanion";
2
+ export declare class GhWorkflowCommand extends Command {
3
+ #private;
4
+ static paths: string[][];
5
+ static usage: import("clipanion").Usage;
6
+ environments: string[] | undefined;
7
+ config: string | undefined;
8
+ cwd: string | undefined;
9
+ testCommand: string[];
10
+ execute(): Promise<0 | 1>;
11
+ }
@@ -0,0 +1,77 @@
1
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
+ };
6
+ var _GhWorkflowCommand_instances, _GhWorkflowCommand_readConfiguredEnvironmentIds;
7
+ import { realpath } from "node:fs/promises";
8
+ import process from "node:process";
9
+ import { readConfig } from "@allurereport/core";
10
+ import { Command, Option, UsageError } from "clipanion";
11
+ import { buildGhWorkflowYaml, configuredEnvironmentIds, normalizeEnvironmentIds } from "../utils/ghWorkflow.js";
12
+ const describeError = (error) => {
13
+ if (error instanceof UsageError) {
14
+ return error.message;
15
+ }
16
+ return error instanceof Error ? (error.stack ?? error.message) : String(error);
17
+ };
18
+ export class GhWorkflowCommand extends Command {
19
+ constructor() {
20
+ super(...arguments);
21
+ _GhWorkflowCommand_instances.add(this);
22
+ this.environments = Option.Array("--environment,--env", {
23
+ description: "Environment id to include. Can be repeated. Default: every environment from the config",
24
+ });
25
+ this.config = Option.String("--config,-c", {
26
+ description: "The path to Allure config file. Also passed to the generated allure commands",
27
+ });
28
+ this.cwd = Option.String("--cwd", {
29
+ description: "The working directory for the command to run (default: current working directory)",
30
+ });
31
+ this.testCommand = Option.Rest({
32
+ name: "Test command to run. Pass it after `--` (default: npm test)",
33
+ });
34
+ }
35
+ async execute() {
36
+ try {
37
+ const environmentIds = this.environments !== undefined && this.environments.length > 0
38
+ ? normalizeEnvironmentIds(this.environments)
39
+ : await __classPrivateFieldGet(this, _GhWorkflowCommand_instances, "m", _GhWorkflowCommand_readConfiguredEnvironmentIds).call(this);
40
+ const yaml = buildGhWorkflowYaml({
41
+ environmentIds,
42
+ testCommand: this.testCommand.length > 0 ? this.testCommand : undefined,
43
+ config: this.config,
44
+ });
45
+ this.context.stdout.write(yaml);
46
+ return 0;
47
+ }
48
+ catch (error) {
49
+ this.context.stderr.write(`${describeError(error)}\n`);
50
+ return 1;
51
+ }
52
+ }
53
+ }
54
+ _GhWorkflowCommand_instances = new WeakSet(), _GhWorkflowCommand_readConfiguredEnvironmentIds = async function _GhWorkflowCommand_readConfiguredEnvironmentIds() {
55
+ const cwd = await realpath(this.cwd ?? process.cwd());
56
+ const config = await readConfig(cwd, this.config, { plugins: {} });
57
+ return configuredEnvironmentIds(config.environments);
58
+ };
59
+ GhWorkflowCommand.paths = [["gh-workflow"]];
60
+ GhWorkflowCommand.usage = Command.Usage({
61
+ category: "Integrations",
62
+ description: "Prints GitHub Actions jobs that run tests per environment and build one Allure report",
63
+ details: `
64
+ Prints a \`test\` job (a matrix over the environments: \`allure run\` with a dump, then the dump is uploaded)
65
+ and a \`report\` job (downloads all dumps and runs \`allure generate\`). Paste the output under \`jobs:\` of a
66
+ workflow file and fill in the TODO steps. The names of the environment, the dump file, the artifact and the
67
+ download pattern are kept consistent for you.
68
+ Environments come from \`--env\`; without it, the \`environments\` declared in the Allure config are used.
69
+ Errors are written to stderr and the exit code is non-zero. Command-line syntax errors are reported by the CLI
70
+ parser on stdout. Versions of the GitHub actions in the output are examples, pin the ones you use.
71
+ `,
72
+ examples: [
73
+ ["gh-workflow --env foo --env bar --env baz", "Print the jobs for three environments (test command: npm test)"],
74
+ ["gh-workflow --env linux --env windows -- yarn test", "Use a custom test command"],
75
+ ["gh-workflow --config ./allurerc.mjs", "Use the environments declared in the config"],
76
+ ],
77
+ });
@@ -16,8 +16,10 @@ export * from "./open.js";
16
16
  export * from "./qualityGate.js";
17
17
  export * from "./log.js";
18
18
  export * from "./generate.js";
19
+ export * from "./ghWorkflow.js";
19
20
  export * from "./dashboard.js";
20
21
  export * from "./results/index.js";
21
22
  export * from "./jira/index.js";
22
23
  export * from "./gitlab/index.js";
23
24
  export * from "./migrate.js";
25
+ export * from "./serve.js";
@@ -16,8 +16,10 @@ export * from "./open.js";
16
16
  export * from "./qualityGate.js";
17
17
  export * from "./log.js";
18
18
  export * from "./generate.js";
19
+ export * from "./ghWorkflow.js";
19
20
  export * from "./dashboard.js";
20
21
  export * from "./results/index.js";
21
22
  export * from "./jira/index.js";
22
23
  export * from "./gitlab/index.js";
23
24
  export * from "./migrate.js";
25
+ export * from "./serve.js";
@@ -63,7 +63,7 @@ export class JiraClearCommand extends BaseJiraCommand {
63
63
  await jiraPlugin.clearAll(this.issues);
64
64
  this.context.stdout.write(green("All reports and test results have been unlinked from the specified Jira issue"));
65
65
  }
66
- catch (error) {
66
+ catch {
67
67
  this.context.stderr.write(red("Failed to unlink reports and test results from the specified Jira issue"));
68
68
  exit(1);
69
69
  }
@@ -73,7 +73,7 @@ export class JiraClearCommand extends BaseJiraCommand {
73
73
  await jiraPlugin.clearReports(this.issues);
74
74
  this.context.stdout.write(green("All reports have been unlinked from the specified Jira issue"));
75
75
  }
76
- catch (error) {
76
+ catch {
77
77
  this.context.stderr.write(red("Failed to unlink reports from the specified Jira issue"));
78
78
  exit(1);
79
79
  }
@@ -83,7 +83,7 @@ export class JiraClearCommand extends BaseJiraCommand {
83
83
  await jiraPlugin.clearResults(this.issues);
84
84
  this.context.stdout.write(green("All test results have been unlinked from the specified Jira issue"));
85
85
  }
86
- catch (error) {
86
+ catch {
87
87
  this.context.stderr.write(red("Failed to unlink test results from the specified Jira issue"));
88
88
  exit(1);
89
89
  }
@@ -1,14 +1,6 @@
1
- import { Command } from "clipanion";
2
- export declare class OpenCommand extends Command {
1
+ import { BaseServeCommand } from "./serve.js";
2
+ export declare class OpenCommand extends BaseServeCommand {
3
3
  static paths: string[][];
4
4
  static usage: import("clipanion").Usage;
5
- resultsDir: string[];
6
- config: string | undefined;
7
- port: string | undefined;
8
- host: string | undefined;
9
- cwd: string | undefined;
10
- hideLabels: string[] | undefined;
11
- execute(): Promise<void>;
12
- private resolveReportPath;
13
- private reportExists;
5
+ protected get shouldOpen(): boolean;
14
6
  }
@@ -1,120 +1,31 @@
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
- export class OpenCommand extends Command {
16
- constructor() {
17
- super(...arguments);
18
- this.resultsDir = Option.Rest({
19
- name: "A report path to open, or results patterns. Overrides config.resultsDir. Empty Rest serves the configured output.",
20
- });
21
- this.config = Option.String("--config,-c", {
22
- description: "The path to Allure config file",
23
- });
24
- this.port = Option.String("--port", {
25
- description: "The port to serve the reports on. If not set, the server starts on a random port",
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
- });
30
- this.cwd = Option.String("--cwd", {
31
- description: "The working directory for the command to run (default: current working directory)",
32
- });
33
- this.hideLabels = Option.Array("--hide-labels", {
34
- description: "Hide labels by exact name in generated reports. Repeat the option for multiple labels",
35
- });
36
- }
37
- async execute() {
38
- const cwd = this.cwd ?? processCwd();
39
- const hideLabels = this.hideLabels?.length ? this.hideLabels : undefined;
40
- const config = await readConfig(cwd, this.config, {
41
- port: this.port,
42
- host: this.host,
43
- });
44
- const servePath = this.resolveReportPath(cwd, this.resultsDir, config.output);
45
- const resolvedPatterns = resolveResultsPatterns(this.resultsDir, config.resultsDir);
46
- if (await this.reportExists(servePath)) {
47
- await serve({
48
- ...serverOptionsFromConfig(config),
49
- servePath,
50
- open: true,
51
- });
52
- }
53
- else if (resolvedPatterns.length) {
54
- const tmpDir = await mkdtemp(join(tmpdir(), "allure-report-"));
55
- const config = await readConfig(cwd, this.config, {
56
- port: this.port,
57
- host: this.host,
58
- output: tmpDir,
59
- hideLabels,
60
- });
61
- await generate({
62
- resultsDir: this.resultsDir,
63
- cwd,
64
- config,
65
- });
66
- const notifier = notifySignals(["SIGINT", "SIGTERM"]);
67
- void waitForAbort(notifier.signal).then(async () => {
68
- try {
69
- await rm(config.output, { recursive: true });
70
- }
71
- catch { }
72
- process.exit(notifier.info()?.code ?? 0);
73
- });
74
- await serve({
75
- ...serverOptionsFromConfig(config),
76
- servePath: config.output,
77
- open: true,
78
- });
79
- }
80
- else {
81
- console.error(red(`A report doesn't exist in ${servePath} and no input was provided to generate.`));
82
- exit(1);
83
- return;
84
- }
85
- }
86
- resolveReportPath(cwd, inputs, fallback = "allure-report") {
87
- if (inputs.length <= 1) {
88
- const [maybeRelativeReportPath = fallback] = inputs;
89
- return resolve(cwd, maybeRelativeReportPath);
90
- }
91
- }
92
- async reportExists(reportPath) {
93
- if (reportPath && existsSync(reportPath)) {
94
- const summaryFiles = await findFilesByGlobs(reportPath, [join("**", "summary.json")]);
95
- return summaryFiles.length > 0;
96
- }
97
- return false;
1
+ import { Command } from "clipanion";
2
+ import { BaseServeCommand } from "./serve.js";
3
+ export class OpenCommand extends BaseServeCommand {
4
+ get shouldOpen() {
5
+ return true;
98
6
  }
99
7
  }
100
- OpenCommand.paths = [["open"], ["serve"]];
8
+ OpenCommand.paths = [["open"]];
101
9
  OpenCommand.usage = Command.Usage({
102
- description: "Serves specified directory",
103
- details: "This command generates report with the given test results and opens it in the default browser.",
10
+ description: "Serves a report and opens it in the default browser, generating it from test results if necessary.",
11
+ details: "This command starts a report server and opens the report in the default browser. If test results are" +
12
+ " provided, it generates the report in a temporary directory before serving and opening it. With no arguments," +
13
+ " it serves the configured output directory or, if no report exists there, generates a report from the" +
14
+ " configured test results.",
104
15
  examples: [
105
- ["open ./allure-results", "Generate and serve the report based on given test results directory"],
106
- ["open --port 8080 ./allure-report", "Serve the report on port 8080"],
16
+ ["open ./allure-results", "Generate, serve, and open the report from the given test results directory"],
17
+ ["open --port 8080 ./allure-report", "Serve the report on port 8080 and open it"],
107
18
  [
108
- "open --host 0.0.0.0 ./allure-report",
109
- "Serve the report on all network interfaces, so it is reachable remotely",
19
+ "open --host 127.0.0.1 ./allure-report",
20
+ "Serve the report on the loopback interface only and open it in the default browser.",
110
21
  ],
111
22
  [
112
23
  "open ./packages/*/allure-results",
113
- "Generate and serve the report from all Allure result directories matching the pattern",
24
+ "Generate, serve, and open the report from all test results directories matching the pattern",
114
25
  ],
115
26
  [
116
- "open ./packages/foo/allure-results /packages/bar/allure-results",
117
- "Generate and serve the report from two Allure result directories",
27
+ "open ./packages/foo/allure-results ./packages/bar/allure-results",
28
+ "Generate, serve, and open the report from two test results directories",
118
29
  ],
119
30
  ],
120
31
  });
@@ -0,0 +1,19 @@
1
+ import { Command } from "clipanion";
2
+ export declare abstract class BaseServeCommand extends Command {
3
+ static paths: string[][];
4
+ static usage: import("clipanion").Usage;
5
+ resultsDir: string[];
6
+ config: string | undefined;
7
+ port: string | undefined;
8
+ host: string | undefined;
9
+ cwd: string | undefined;
10
+ hideLabels: string[] | undefined;
11
+ protected abstract get shouldOpen(): boolean;
12
+ execute(): Promise<number>;
13
+ private resolveReportPath;
14
+ private reportExists;
15
+ }
16
+ export declare class ServeCommand extends BaseServeCommand {
17
+ open: boolean | undefined;
18
+ protected get shouldOpen(): boolean;
19
+ }
@@ -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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import process, { argv } from "node:process";
3
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";
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
5
  import { ALLURE2_MIGRATE_REMINDER, getAllure2Hint } from "./utils/allure2Hints.js";
6
6
  import { notifyAboutUpdate, parseUpdateNotifierArgs } from "./utils/updateNotifier.js";
7
7
  import { applyVerbosity, extractVerbosityFlags } from "./utils/verbosity.js";
@@ -26,6 +26,7 @@ cli.register(ClassicCommand);
26
26
  cli.register(CsvCommand);
27
27
  cli.register(DashboardCommand);
28
28
  cli.register(GenerateCommand);
29
+ cli.register(GhWorkflowCommand);
29
30
  cli.register(HistoryCommand);
30
31
  cli.register(JiraClearCommand);
31
32
  cli.register(LogCommand);
@@ -33,6 +34,7 @@ cli.register(MigrateCommand);
33
34
  cli.register(OpenCommand);
34
35
  cli.register(QualityGateCommand);
35
36
  cli.register(RunCommand);
37
+ cli.register(ServeCommand);
36
38
  cli.register(SlackCommand);
37
39
  cli.register(TestOpsPlanCommand);
38
40
  cli.register(TestPlanCommand);
@@ -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;
@@ -0,0 +1,75 @@
1
+ import { UsageError } from "clipanion";
2
+ import { normalizeCommandEnvironmentOptions } from "./environment.js";
3
+ const SAFE_TOKEN = /^[A-Za-z0-9._/:@=-]+$/;
4
+ const INVISIBLE_CHARS = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u;
5
+ const CHECKOUT_ACTION = "actions/checkout@v6";
6
+ const UPLOAD_ARTIFACT_ACTION = "actions/upload-artifact@v7";
7
+ const DOWNLOAD_ARTIFACT_ACTION = "actions/download-artifact@v8";
8
+ const DEFAULT_TEST_COMMAND = ["npm", "test"];
9
+ const ENVIRONMENT = "${{ matrix.environment }}";
10
+ const NOT_CANCELLED = "${{ !cancelled() }}";
11
+ export const shellQuote = (value) => SAFE_TOKEN.test(value) && !value.startsWith("=") ? value : `'${value.replace(/'/g, `'\\''`)}'`;
12
+ const assertPrintable = (label, value) => {
13
+ if (INVISIBLE_CHARS.test(value)) {
14
+ throw new UsageError(`${label} must not contain control or invisible characters`);
15
+ }
16
+ return value;
17
+ };
18
+ export const normalizeEnvironmentIds = (values) => [
19
+ ...new Set(values.map((value) => normalizeCommandEnvironmentOptions({ environment: value }).environment)),
20
+ ];
21
+ export const configuredEnvironmentIds = (environments) => {
22
+ const ids = Object.keys(environments ?? {});
23
+ if (ids.length === 0) {
24
+ throw new UsageError("The Allure config has no environments (or no allurerc file was found). " +
25
+ "Declare `environments` in the config or pass environment ids with --env");
26
+ }
27
+ return ids;
28
+ };
29
+ export const buildGhWorkflowYaml = ({ environmentIds, testCommand = DEFAULT_TEST_COMMAND, config, }) => {
30
+ if (environmentIds.length === 0) {
31
+ throw new UsageError("At least one environment is required");
32
+ }
33
+ const testCommandLine = testCommand.map((token) => shellQuote(assertPrintable("The test command", token))).join(" ");
34
+ const configOption = config === undefined ? "" : ` --config=${shellQuote(assertPrintable("--config", config))}`;
35
+ const matrix = environmentIds.map((id) => JSON.stringify(id)).join(", ");
36
+ const results = `allure-results-${ENVIRONMENT}`;
37
+ return `# Generated by "allure gh-workflow". Paste it under "jobs:" and adjust the TODO steps.
38
+ test:
39
+ name: Test (${ENVIRONMENT})
40
+ runs-on: ubuntu-latest
41
+ strategy:
42
+ fail-fast: false
43
+ matrix:
44
+ environment: [${matrix}]
45
+ steps:
46
+ - uses: ${CHECKOUT_ACTION}
47
+ # TODO: set up your runtime and install dependencies
48
+ - name: Test
49
+ run: |
50
+ npx allure run${configOption} --environment=${ENVIRONMENT} --dump=${results} -- ${testCommandLine}
51
+ - name: Upload Allure results
52
+ if: ${NOT_CANCELLED}
53
+ uses: ${UPLOAD_ARTIFACT_ACTION}
54
+ with:
55
+ name: ${results}
56
+ path: ./${results}.zip
57
+
58
+ report:
59
+ name: Report
60
+ needs: [test]
61
+ if: ${NOT_CANCELLED}
62
+ runs-on: ubuntu-latest
63
+ steps:
64
+ - uses: ${CHECKOUT_ACTION}
65
+ # TODO: set up your runtime and install dependencies
66
+ - name: Download Allure results
67
+ uses: ${DOWNLOAD_ARTIFACT_ACTION}
68
+ with:
69
+ pattern: allure-results-*
70
+ path: ./
71
+ - name: Generate report
72
+ run: |
73
+ npx allure generate${configOption} --dump="allure-results-*/*.zip" --output=./allure-report
74
+ `;
75
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "allure",
3
- "version": "3.21.0",
3
+ "version": "3.22.1",
4
4
  "description": "Allure Commandline Tool",
5
5
  "keywords": [
6
6
  "allure",
@@ -27,35 +27,35 @@
27
27
  "build": "run clean && tsc --project ./tsconfig.json",
28
28
  "clean": "rimraf ./dist",
29
29
  "start": "node ./cli.js",
30
- "test": "vitest run",
30
+ "test": "rimraf out && vp test run",
31
31
  "test:unit": "yarn test",
32
- "lint": "oxlint --import-plugin src test features stories",
33
- "lint:fix": "oxlint --import-plugin --fix src test features stories"
32
+ "lint": "vp lint --import-plugin src test features stories",
33
+ "lint:fix": "vp lint --import-plugin --fix src test features stories"
34
34
  },
35
35
  "dependencies": {
36
- "@allurereport/charts-api": "3.21.0",
37
- "@allurereport/ci": "3.21.0",
38
- "@allurereport/cli-commons": "3.21.0",
39
- "@allurereport/core": "3.21.0",
40
- "@allurereport/core-api": "3.21.0",
41
- "@allurereport/directory-watcher": "3.21.0",
42
- "@allurereport/plugin-agent": "3.21.0",
43
- "@allurereport/plugin-allure2": "3.21.0",
44
- "@allurereport/plugin-api": "3.21.0",
45
- "@allurereport/plugin-awesome": "3.21.0",
46
- "@allurereport/plugin-classic": "3.21.0",
47
- "@allurereport/plugin-csv": "3.21.0",
48
- "@allurereport/plugin-dashboard": "3.21.0",
49
- "@allurereport/plugin-jira": "3.21.0",
50
- "@allurereport/plugin-log": "3.21.0",
51
- "@allurereport/plugin-metrics": "3.21.0",
52
- "@allurereport/plugin-progress": "3.21.0",
53
- "@allurereport/plugin-server-reload": "3.21.0",
54
- "@allurereport/plugin-slack": "3.21.0",
55
- "@allurereport/plugin-xray": "3.21.0",
56
- "@allurereport/reader-api": "3.21.0",
57
- "@allurereport/service": "3.21.0",
58
- "@allurereport/static-server": "3.21.0",
36
+ "@allurereport/charts-api": "3.22.1",
37
+ "@allurereport/ci": "3.22.1",
38
+ "@allurereport/cli-commons": "3.22.1",
39
+ "@allurereport/core": "3.22.1",
40
+ "@allurereport/core-api": "3.22.1",
41
+ "@allurereport/directory-watcher": "3.22.1",
42
+ "@allurereport/plugin-agent": "3.22.1",
43
+ "@allurereport/plugin-allure2": "3.22.1",
44
+ "@allurereport/plugin-api": "3.22.1",
45
+ "@allurereport/plugin-awesome": "3.22.1",
46
+ "@allurereport/plugin-classic": "3.22.1",
47
+ "@allurereport/plugin-csv": "3.22.1",
48
+ "@allurereport/plugin-dashboard": "3.22.1",
49
+ "@allurereport/plugin-jira": "3.22.1",
50
+ "@allurereport/plugin-log": "3.22.1",
51
+ "@allurereport/plugin-metrics": "3.22.1",
52
+ "@allurereport/plugin-progress": "3.22.1",
53
+ "@allurereport/plugin-server-reload": "3.22.1",
54
+ "@allurereport/plugin-slack": "3.22.1",
55
+ "@allurereport/plugin-xray": "3.22.1",
56
+ "@allurereport/reader-api": "3.22.1",
57
+ "@allurereport/service": "3.22.1",
58
+ "@allurereport/static-server": "3.22.1",
59
59
  "adm-zip": "0.6.1",
60
60
  "clipanion": "^4.0.0-rc.4",
61
61
  "glob": "^13.0.6",
@@ -65,18 +65,20 @@
65
65
  "yoctocolors": "^2.2.0"
66
66
  },
67
67
  "devDependencies": {
68
- "@allurereport/test-config": "3.21.0",
68
+ "@allurereport/common-config": "3.22.1",
69
69
  "@types/adm-zip": "^0",
70
70
  "@types/lodash.omit": "^4.5.9",
71
71
  "@types/node": "^20",
72
72
  "@types/prompts": "^2",
73
- "@vitest/coverage-istanbul": "^4.1.0",
73
+ "@vitest/coverage-istanbul": "5.0.4",
74
74
  "@vitest/runner": "^2",
75
- "@vitest/snapshot": "^5.0.1",
75
+ "@vitest/snapshot": "5.0.4",
76
76
  "allure-js-commons": "^3",
77
77
  "allure-vitest": "^3",
78
78
  "rimraf": "^6",
79
79
  "typescript": "^5",
80
- "vitest": "^4.1.0"
80
+ "vite": "@voidzero-dev/vite-plus-core@1.2.0",
81
+ "vite-plus": "1.2.0",
82
+ "vitest": "5.0.4"
81
83
  }
82
84
  }