@anselmdk/feature-spec-md 0.3.1 → 0.5.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 +161 -11
- package/dist/cli.js +29 -0
- package/dist/cli.js.map +1 -1
- package/dist/githubActionDiffReport.d.ts +4 -0
- package/dist/githubActionDiffReport.d.ts.map +1 -0
- package/dist/githubActionDiffReport.js +209 -0
- package/dist/githubActionDiffReport.js.map +1 -0
- package/dist/githubActionFtp.d.ts +25 -0
- package/dist/githubActionFtp.d.ts.map +1 -0
- package/dist/githubActionFtp.js +210 -0
- package/dist/githubActionFtp.js.map +1 -0
- package/dist/githubActionOutput.d.ts +3 -0
- package/dist/githubActionOutput.d.ts.map +1 -0
- package/dist/githubActionOutput.js +33 -0
- package/dist/githubActionOutput.js.map +1 -0
- package/dist/githubActionReport.d.ts +2 -1
- package/dist/githubActionReport.d.ts.map +1 -1
- package/dist/githubActionReport.js +15 -182
- package/dist/githubActionReport.js.map +1 -1
- package/dist/reportTemplate.d.ts +3 -0
- package/dist/reportTemplate.d.ts.map +1 -1
- package/dist/reportTemplate.js +86 -29
- package/dist/reportTemplate.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { mkdir, readdir } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, relative, sep } from "node:path";
|
|
4
|
+
export function ftpConfig(options) {
|
|
5
|
+
const host = required(options, "ftp-host", "FEATURE_SPEC_FTP_HOST");
|
|
6
|
+
const user = required(options, "ftp-user", "FEATURE_SPEC_FTP_USER");
|
|
7
|
+
const password = required(options, "ftp-password", "FEATURE_SPEC_FTP_PASSWORD");
|
|
8
|
+
const baseUrl = required(options, "base-url", "FEATURE_SPEC_REPORT_BASE_URL");
|
|
9
|
+
const remoteDir = value(options, "ftp-remote-dir", "FEATURE_SPEC_FTP_REMOTE_DIR") ?? "";
|
|
10
|
+
const buildNumber = value(options, "build-number", "FEATURE_SPEC_BUILD_NUMBER") ??
|
|
11
|
+
value(options, "build-number", "GITHUB_RUN_NUMBER") ??
|
|
12
|
+
"local";
|
|
13
|
+
const prNumber = value(options, "pr-number", "FEATURE_SPEC_PR_NUMBER");
|
|
14
|
+
const secure = booleanValue(value(options, "ftp-secure", "FEATURE_SPEC_FTP_SECURE"));
|
|
15
|
+
const port = value(options, "ftp-port", "FEATURE_SPEC_FTP_PORT");
|
|
16
|
+
if (!/^\d+$/.test(buildNumber)) {
|
|
17
|
+
throw new Error(`Build number must be numeric for FTP publishing: ${buildNumber}`);
|
|
18
|
+
}
|
|
19
|
+
if (prNumber !== undefined && !/^\d+$/.test(prNumber)) {
|
|
20
|
+
throw new Error(`Pull request number must be numeric for FTP publishing: ${prNumber}`);
|
|
21
|
+
}
|
|
22
|
+
return { host, user, password, port, secure, remoteDir, baseUrl, buildNumber, prNumber };
|
|
23
|
+
}
|
|
24
|
+
export async function uploadDirectory(localDir, remoteDir, config) {
|
|
25
|
+
const files = await walkFiles(localDir);
|
|
26
|
+
if (!files.length) {
|
|
27
|
+
throw new Error(`Report directory contains no files: ${localDir}`);
|
|
28
|
+
}
|
|
29
|
+
for (const file of files) {
|
|
30
|
+
const remotePath = pathJoin(remoteDir, relative(localDir, file).split(sep).join("/"));
|
|
31
|
+
await uploadFile(file, remotePath, config);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export async function uploadFile(localFile, remotePath, config) {
|
|
35
|
+
await runCurl([
|
|
36
|
+
"--silent",
|
|
37
|
+
"--show-error",
|
|
38
|
+
"--fail",
|
|
39
|
+
"--ftp-create-dirs",
|
|
40
|
+
"-u",
|
|
41
|
+
`${config.user}:${config.password}`,
|
|
42
|
+
"-T",
|
|
43
|
+
localFile,
|
|
44
|
+
ftpUrl(config, remotePath),
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
export async function downloadRemoteFile(remotePath, localFile, config) {
|
|
48
|
+
await mkdir(dirname(localFile), { recursive: true });
|
|
49
|
+
await runCurl([
|
|
50
|
+
"--silent",
|
|
51
|
+
"--show-error",
|
|
52
|
+
"--fail",
|
|
53
|
+
"-u",
|
|
54
|
+
`${config.user}:${config.password}`,
|
|
55
|
+
"-o",
|
|
56
|
+
localFile,
|
|
57
|
+
ftpUrl(config, remotePath),
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
export async function listRemoteDirectory(remoteDir, config) {
|
|
61
|
+
const directoryUrl = ftpUrl(config, asDirectoryPath(remoteDir));
|
|
62
|
+
const commonArgs = [
|
|
63
|
+
"--silent",
|
|
64
|
+
"--show-error",
|
|
65
|
+
"--fail",
|
|
66
|
+
"-u",
|
|
67
|
+
`${config.user}:${config.password}`,
|
|
68
|
+
];
|
|
69
|
+
try {
|
|
70
|
+
return await runCurl([...commonArgs, "--list-only", directoryUrl]);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return runCurl([...commonArgs, directoryUrl]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export async function listRemoteFilesRecursive(remoteDir, config) {
|
|
77
|
+
const files = [];
|
|
78
|
+
await visit(remoteDir);
|
|
79
|
+
return files.sort();
|
|
80
|
+
async function visit(dir) {
|
|
81
|
+
const listing = await listRemoteDirectory(dir, config);
|
|
82
|
+
const entries = parseDirectoryListing(listing);
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
if (entry === "." || entry === "..")
|
|
85
|
+
continue;
|
|
86
|
+
const child = pathJoin(dir, entry);
|
|
87
|
+
if (looksLikeFile(entry)) {
|
|
88
|
+
files.push(child);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
await visit(child);
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
files.push(child);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export function parseBuildNumbersFromDirectoryListing(listing) {
|
|
101
|
+
return listing
|
|
102
|
+
.split(/\r?\n/)
|
|
103
|
+
.flatMap((line) => {
|
|
104
|
+
const hrefBuilds = Array.from(line.matchAll(/href=["'][^"']*?(\d+)\/?["']/gi), (match) => match[1]);
|
|
105
|
+
const lastToken = line.trim().split(/\s+/).at(-1) ?? "";
|
|
106
|
+
return /^\d+$/.test(lastToken) ? [...hrefBuilds, lastToken] : hrefBuilds;
|
|
107
|
+
})
|
|
108
|
+
.filter((name) => /^\d+$/.test(name));
|
|
109
|
+
}
|
|
110
|
+
export async function listBuildNumbers(remoteDir, config) {
|
|
111
|
+
try {
|
|
112
|
+
const listing = await listRemoteDirectory(remoteDir, config);
|
|
113
|
+
return Array.from(new Set(parseBuildNumbersFromDirectoryListing(listing)));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export function pathJoin(...parts) {
|
|
120
|
+
const joined = parts
|
|
121
|
+
.flatMap((part) => part.split("/"))
|
|
122
|
+
.map((part) => part.trim())
|
|
123
|
+
.filter((part) => part && part !== ".")
|
|
124
|
+
.join("/");
|
|
125
|
+
return joined;
|
|
126
|
+
}
|
|
127
|
+
export function publicUrl(baseUrl, ...parts) {
|
|
128
|
+
const path = parts
|
|
129
|
+
.flatMap((part) => part.split("/"))
|
|
130
|
+
.filter(Boolean)
|
|
131
|
+
.map(encodeURIComponent)
|
|
132
|
+
.join("/");
|
|
133
|
+
return path ? `${trimTrailingSlash(baseUrl)}/${path}` : trimTrailingSlash(baseUrl);
|
|
134
|
+
}
|
|
135
|
+
export function trimTrailingSlash(value) {
|
|
136
|
+
return value.replace(/\/+$/, "");
|
|
137
|
+
}
|
|
138
|
+
export async function runCurl(args) {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const child = spawn("curl", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
141
|
+
let stdout = "";
|
|
142
|
+
let stderr = "";
|
|
143
|
+
child.stdout.on("data", (chunk) => {
|
|
144
|
+
stdout += chunk.toString();
|
|
145
|
+
});
|
|
146
|
+
child.stderr.on("data", (chunk) => {
|
|
147
|
+
stderr += chunk.toString();
|
|
148
|
+
});
|
|
149
|
+
child.on("error", reject);
|
|
150
|
+
child.on("close", (code) => {
|
|
151
|
+
if (code === 0) {
|
|
152
|
+
resolve(stdout);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
reject(new Error(stderr.trim() || `curl exited with status ${code}`));
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
async function walkFiles(dir) {
|
|
160
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
161
|
+
const files = await Promise.all(entries.map(async (entry) => {
|
|
162
|
+
const fullPath = join(dir, entry.name);
|
|
163
|
+
if (entry.isDirectory())
|
|
164
|
+
return walkFiles(fullPath);
|
|
165
|
+
if (entry.isFile())
|
|
166
|
+
return [fullPath];
|
|
167
|
+
return [];
|
|
168
|
+
}));
|
|
169
|
+
return files.flat();
|
|
170
|
+
}
|
|
171
|
+
function parseDirectoryListing(listing) {
|
|
172
|
+
return listing
|
|
173
|
+
.split(/\r?\n/)
|
|
174
|
+
.map((line) => line.trim())
|
|
175
|
+
.filter(Boolean)
|
|
176
|
+
.map((line) => line.split(/\s+/).at(-1) ?? "")
|
|
177
|
+
.filter(Boolean);
|
|
178
|
+
}
|
|
179
|
+
function looksLikeFile(name) {
|
|
180
|
+
return /\.[a-z0-9]+$/i.test(name);
|
|
181
|
+
}
|
|
182
|
+
function ftpUrl(config, remotePath) {
|
|
183
|
+
const protocol = config.secure ? "ftps" : "ftp";
|
|
184
|
+
const port = config.port ? `:${config.port}` : "";
|
|
185
|
+
const hasTrailingSlash = remotePath.endsWith("/");
|
|
186
|
+
const encodedPath = remotePath
|
|
187
|
+
.split("/")
|
|
188
|
+
.filter(Boolean)
|
|
189
|
+
.map(encodeURIComponent)
|
|
190
|
+
.join("/");
|
|
191
|
+
const directorySlash = hasTrailingSlash ? "/" : "";
|
|
192
|
+
return `${protocol}://${config.host}${port}/${encodedPath}${directorySlash}`;
|
|
193
|
+
}
|
|
194
|
+
function asDirectoryPath(remotePath) {
|
|
195
|
+
const path = pathJoin(remotePath);
|
|
196
|
+
return path ? `${path}/` : "";
|
|
197
|
+
}
|
|
198
|
+
function required(options, key, envKey) {
|
|
199
|
+
const result = value(options, key, envKey);
|
|
200
|
+
if (!result)
|
|
201
|
+
throw new Error(`Missing required option --${key} or ${envKey}.`);
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
function value(options, key, envKey) {
|
|
205
|
+
return options[key] ?? process.env[envKey];
|
|
206
|
+
}
|
|
207
|
+
function booleanValue(value) {
|
|
208
|
+
return value === "true" || value === "1" || value === "yes";
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=githubActionFtp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"githubActionFtp.js","sourceRoot":"","sources":["../src/githubActionFtp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"githubActionOutput.d.ts","sourceRoot":"","sources":["../src/githubActionOutput.ts"],"names":[],"mappings":"AAGA,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,iBAMxD;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,iBAMrE"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
export async function writeGithubSummary(markdown) {
|
|
4
|
+
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
5
|
+
if (!summaryPath)
|
|
6
|
+
return;
|
|
7
|
+
await mkdir(dirname(summaryPath), { recursive: true });
|
|
8
|
+
const existing = await readTextIfExists(summaryPath);
|
|
9
|
+
await writeFile(summaryPath, `${existing}${markdown}`, "utf8");
|
|
10
|
+
}
|
|
11
|
+
export async function writeGithubOutput(values) {
|
|
12
|
+
const outputPath = process.env.GITHUB_OUTPUT;
|
|
13
|
+
if (!outputPath)
|
|
14
|
+
return;
|
|
15
|
+
const lines = Object.entries(values).map(([key, value]) => outputLine(key, value));
|
|
16
|
+
const existing = await readTextIfExists(outputPath);
|
|
17
|
+
await writeFile(outputPath, `${existing}${lines.join("\n")}\n`, "utf8");
|
|
18
|
+
}
|
|
19
|
+
function outputLine(key, value) {
|
|
20
|
+
if (!value.includes("\n"))
|
|
21
|
+
return `${key}=${value}`;
|
|
22
|
+
const delimiter = `feature_spec_md_${Math.random().toString(36).slice(2)}`;
|
|
23
|
+
return `${key}<<${delimiter}\n${value}\n${delimiter}`;
|
|
24
|
+
}
|
|
25
|
+
async function readTextIfExists(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
return await readFile(filePath, "utf8");
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=githubActionOutput.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"githubActionOutput.js","sourceRoot":"","sources":["../src/githubActionOutput.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,QAAgB;IACvD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;IACpD,IAAI,CAAC,WAAW;QAAE,OAAO;IACzB,MAAM,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACrD,MAAM,SAAS,CAAC,WAAW,EAAE,GAAG,QAAQ,GAAG,QAAQ,EAAE,EAAE,MAAM,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAA8B;IACpE,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAC7C,IAAI,CAAC,UAAU;QAAE,OAAO;IACxB,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;IACnF,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,UAAU,CAAC,CAAC;IACpD,MAAM,SAAS,CAAC,UAAU,EAAE,GAAG,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,UAAU,CAAC,GAAW,EAAE,KAAa;IAC5C,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,GAAG,GAAG,IAAI,KAAK,EAAE,CAAC;IACpD,MAAM,SAAS,GAAG,mBAAmB,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3E,OAAO,GAAG,GAAG,KAAK,SAAS,KAAK,KAAK,KAAK,SAAS,EAAE,CAAC;AACxD,CAAC;AAED,KAAK,UAAU,gBAAgB,CAAC,QAAgB;IAC9C,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { type GithubActionOptions } from "./githubActionFtp.js";
|
|
2
|
+
export type GithubActionReportOptions = GithubActionOptions;
|
|
2
3
|
export declare function publishGithubActionReport(options: GithubActionReportOptions): Promise<void>;
|
|
3
4
|
//# sourceMappingURL=githubActionReport.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"githubActionReport.d.ts","sourceRoot":"","sources":["../src/githubActionReport.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"githubActionReport.d.ts","sourceRoot":"","sources":["../src/githubActionReport.ts"],"names":[],"mappings":"AAGA,OAAO,EAOL,KAAK,mBAAmB,EACzB,MAAM,sBAAsB,CAAC;AAG9B,MAAM,MAAM,yBAAyB,GAAG,mBAAmB,CAAC;AAI5D,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,yBAAyB,iBA8CnC"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { dirname, join, relative, sep } from "node:path";
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
4
3
|
import { tmpdir } from "node:os";
|
|
4
|
+
import { ftpConfig, listBuildNumbers, pathJoin, publicUrl, uploadDirectory, uploadFile, } from "./githubActionFtp.js";
|
|
5
|
+
import { writeGithubOutput, writeGithubSummary } from "./githubActionOutput.js";
|
|
5
6
|
export async function publishGithubActionReport(options) {
|
|
6
7
|
const reportDir = value(options, "report-dir", "FEATURE_SPEC_REPORT_DIR") ??
|
|
7
8
|
"test-results/spec-report";
|
|
@@ -15,13 +16,13 @@ export async function publishGithubActionReport(options) {
|
|
|
15
16
|
"## Feature Spec Report",
|
|
16
17
|
"",
|
|
17
18
|
`<p><strong>Hosted HTML report:</strong> <a href=\"${escapeHtml(reportUrl)}\">${escapeHtml(reportName)}</a></p>`,
|
|
18
|
-
`<p><strong>
|
|
19
|
+
`<p><strong>Build report index:</strong> <a href=\"${escapeHtml(buildIndexUrl(config.baseUrl))}\">all builds</a></p>`,
|
|
19
20
|
"",
|
|
20
21
|
].join("\n"));
|
|
21
22
|
await writeGithubOutput({
|
|
22
23
|
"upload-github-artifact": "false",
|
|
23
24
|
"report-url": reportUrl,
|
|
24
|
-
"index-url":
|
|
25
|
+
"index-url": buildIndexUrl(config.baseUrl),
|
|
25
26
|
});
|
|
26
27
|
console.log(`Feature spec report uploaded to ${reportUrl}`);
|
|
27
28
|
return;
|
|
@@ -39,165 +40,26 @@ export async function publishGithubActionReport(options) {
|
|
|
39
40
|
});
|
|
40
41
|
}
|
|
41
42
|
async function publishReportToFtp(reportDir, config) {
|
|
42
|
-
const
|
|
43
|
+
const buildsRoot = pathJoin(config.remoteDir, "build");
|
|
44
|
+
const buildRemoteDir = pathJoin(buildsRoot, config.buildNumber);
|
|
43
45
|
await uploadDirectory(reportDir, buildRemoteDir, config);
|
|
44
|
-
const builds = await listBuildNumbers(
|
|
46
|
+
const builds = await listBuildNumbers(buildsRoot, config);
|
|
45
47
|
if (!builds.includes(config.buildNumber))
|
|
46
48
|
builds.push(config.buildNumber);
|
|
47
49
|
builds.sort((a, b) => Number(b) - Number(a));
|
|
48
50
|
const localIndex = join(tmpdir(), `feature-spec-md-build-index-${process.pid}.html`);
|
|
49
51
|
await writeFile(localIndex, renderBuildIndex(config.baseUrl, builds), "utf8");
|
|
50
|
-
await uploadFile(localIndex, pathJoin(
|
|
52
|
+
await uploadFile(localIndex, pathJoin(buildsRoot, "index.html"), config);
|
|
51
53
|
return buildUrl(config.baseUrl, config.buildNumber);
|
|
52
54
|
}
|
|
53
|
-
async function uploadDirectory(localDir, remoteDir, config) {
|
|
54
|
-
const files = await walkFiles(localDir);
|
|
55
|
-
if (!files.length) {
|
|
56
|
-
throw new Error(`Report directory contains no files: ${localDir}`);
|
|
57
|
-
}
|
|
58
|
-
for (const file of files) {
|
|
59
|
-
const remotePath = pathJoin(remoteDir, relative(localDir, file).split(sep).join("/"));
|
|
60
|
-
await uploadFile(file, remotePath, config);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
async function uploadFile(localFile, remotePath, config) {
|
|
64
|
-
await runCurl([
|
|
65
|
-
"--silent",
|
|
66
|
-
"--show-error",
|
|
67
|
-
"--fail",
|
|
68
|
-
"--ftp-create-dirs",
|
|
69
|
-
"-u",
|
|
70
|
-
`${config.user}:${config.password}`,
|
|
71
|
-
"-T",
|
|
72
|
-
localFile,
|
|
73
|
-
ftpUrl(config, remotePath),
|
|
74
|
-
]);
|
|
75
|
-
}
|
|
76
|
-
async function listBuildNumbers(remoteDir, config) {
|
|
77
|
-
try {
|
|
78
|
-
const listing = await listRemoteDirectory(remoteDir, config);
|
|
79
|
-
return Array.from(new Set(parseBuildNumbersFromDirectoryListing(listing)));
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
return [];
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
async function listRemoteDirectory(remoteDir, config) {
|
|
86
|
-
const directoryUrl = ftpUrl(config, asDirectoryPath(remoteDir));
|
|
87
|
-
const commonArgs = [
|
|
88
|
-
"--silent",
|
|
89
|
-
"--show-error",
|
|
90
|
-
"--fail",
|
|
91
|
-
"-u",
|
|
92
|
-
`${config.user}:${config.password}`,
|
|
93
|
-
];
|
|
94
|
-
try {
|
|
95
|
-
return await runCurl([...commonArgs, "--list-only", directoryUrl]);
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
return runCurl([...commonArgs, directoryUrl]);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
function parseBuildNumbersFromDirectoryListing(listing) {
|
|
102
|
-
return listing
|
|
103
|
-
.split(/\r?\n/)
|
|
104
|
-
.flatMap((line) => {
|
|
105
|
-
const hrefBuilds = Array.from(line.matchAll(/href=["'][^"']*?(\d+)\/?["']/gi), (match) => match[1]);
|
|
106
|
-
const lastToken = line.trim().split(/\s+/).at(-1) ?? "";
|
|
107
|
-
return /^\d+$/.test(lastToken) ? [...hrefBuilds, lastToken] : hrefBuilds;
|
|
108
|
-
})
|
|
109
|
-
.filter((name) => /^\d+$/.test(name));
|
|
110
|
-
}
|
|
111
|
-
async function runCurl(args) {
|
|
112
|
-
return new Promise((resolve, reject) => {
|
|
113
|
-
const child = spawn("curl", args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
114
|
-
let stdout = "";
|
|
115
|
-
let stderr = "";
|
|
116
|
-
child.stdout.on("data", (chunk) => {
|
|
117
|
-
stdout += chunk.toString();
|
|
118
|
-
});
|
|
119
|
-
child.stderr.on("data", (chunk) => {
|
|
120
|
-
stderr += chunk.toString();
|
|
121
|
-
});
|
|
122
|
-
child.on("error", reject);
|
|
123
|
-
child.on("close", (code) => {
|
|
124
|
-
if (code === 0) {
|
|
125
|
-
resolve(stdout);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
reject(new Error(stderr.trim() || `curl exited with status ${code}`));
|
|
129
|
-
});
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
async function walkFiles(dir) {
|
|
133
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
134
|
-
const files = await Promise.all(entries.map(async (entry) => {
|
|
135
|
-
const fullPath = join(dir, entry.name);
|
|
136
|
-
if (entry.isDirectory())
|
|
137
|
-
return walkFiles(fullPath);
|
|
138
|
-
if (entry.isFile())
|
|
139
|
-
return [fullPath];
|
|
140
|
-
return [];
|
|
141
|
-
}));
|
|
142
|
-
return files.flat();
|
|
143
|
-
}
|
|
144
|
-
function ftpConfig(options) {
|
|
145
|
-
const host = required(options, "ftp-host", "FEATURE_SPEC_FTP_HOST");
|
|
146
|
-
const user = required(options, "ftp-user", "FEATURE_SPEC_FTP_USER");
|
|
147
|
-
const password = required(options, "ftp-password", "FEATURE_SPEC_FTP_PASSWORD");
|
|
148
|
-
const baseUrl = required(options, "base-url", "FEATURE_SPEC_REPORT_BASE_URL");
|
|
149
|
-
const remoteDir = value(options, "ftp-remote-dir", "FEATURE_SPEC_FTP_REMOTE_DIR") ?? "";
|
|
150
|
-
const buildNumber = value(options, "build-number", "GITHUB_RUN_NUMBER") ??
|
|
151
|
-
value(options, "build-number", "FEATURE_SPEC_BUILD_NUMBER") ??
|
|
152
|
-
"local";
|
|
153
|
-
const secure = booleanValue(value(options, "ftp-secure", "FEATURE_SPEC_FTP_SECURE"));
|
|
154
|
-
const port = value(options, "ftp-port", "FEATURE_SPEC_FTP_PORT");
|
|
155
|
-
if (!/^\d+$/.test(buildNumber)) {
|
|
156
|
-
throw new Error(`Build number must be numeric for FTP publishing: ${buildNumber}`);
|
|
157
|
-
}
|
|
158
|
-
return { host, user, password, port, secure, remoteDir, baseUrl, buildNumber };
|
|
159
|
-
}
|
|
160
55
|
function publishMode(value) {
|
|
161
56
|
if (value === "ftp" || value === "artifact")
|
|
162
57
|
return value;
|
|
163
58
|
throw new Error(`Unknown report publish mode: ${value}`);
|
|
164
59
|
}
|
|
165
|
-
function required(options, key, envKey) {
|
|
166
|
-
const result = value(options, key, envKey);
|
|
167
|
-
if (!result)
|
|
168
|
-
throw new Error(`Missing required option --${key} or ${envKey}.`);
|
|
169
|
-
return result;
|
|
170
|
-
}
|
|
171
60
|
function value(options, key, envKey) {
|
|
172
61
|
return options[key] ?? process.env[envKey];
|
|
173
62
|
}
|
|
174
|
-
function booleanValue(value) {
|
|
175
|
-
return value === "true" || value === "1" || value === "yes";
|
|
176
|
-
}
|
|
177
|
-
function ftpUrl(config, remotePath) {
|
|
178
|
-
const protocol = config.secure ? "ftps" : "ftp";
|
|
179
|
-
const port = config.port ? `:${config.port}` : "";
|
|
180
|
-
const hasTrailingSlash = remotePath.endsWith("/");
|
|
181
|
-
const encodedPath = remotePath
|
|
182
|
-
.split("/")
|
|
183
|
-
.filter(Boolean)
|
|
184
|
-
.map(encodeURIComponent)
|
|
185
|
-
.join("/");
|
|
186
|
-
const directorySlash = hasTrailingSlash ? "/" : "";
|
|
187
|
-
return `${protocol}://${config.host}${port}/${encodedPath}${directorySlash}`;
|
|
188
|
-
}
|
|
189
|
-
function asDirectoryPath(remotePath) {
|
|
190
|
-
const path = pathJoin(remotePath);
|
|
191
|
-
return path ? `${path}/` : "";
|
|
192
|
-
}
|
|
193
|
-
function pathJoin(...parts) {
|
|
194
|
-
const joined = parts
|
|
195
|
-
.flatMap((part) => part.split("/"))
|
|
196
|
-
.map((part) => part.trim())
|
|
197
|
-
.filter((part) => part && part !== ".")
|
|
198
|
-
.join("/");
|
|
199
|
-
return joined;
|
|
200
|
-
}
|
|
201
63
|
function renderBuildIndex(baseUrl, builds) {
|
|
202
64
|
const generatedAt = new Date().toISOString();
|
|
203
65
|
const rows = builds
|
|
@@ -208,14 +70,14 @@ function renderBuildIndex(baseUrl, builds) {
|
|
|
208
70
|
<head>
|
|
209
71
|
<meta charset=\"utf-8\">
|
|
210
72
|
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
|
|
211
|
-
<title>Feature spec reports</title>
|
|
73
|
+
<title>Feature spec build reports</title>
|
|
212
74
|
<style>
|
|
213
75
|
body { font-family: system-ui, sans-serif; max-width: 48rem; margin: 3rem auto; padding: 0 1rem; line-height: 1.5; }
|
|
214
76
|
li { margin: 0.35rem 0; }
|
|
215
77
|
</style>
|
|
216
78
|
</head>
|
|
217
79
|
<body>
|
|
218
|
-
<h1>Feature spec reports</h1>
|
|
80
|
+
<h1>Feature spec build reports</h1>
|
|
219
81
|
<p>Generated ${escapeHtml(generatedAt)}.</p>
|
|
220
82
|
<ol>
|
|
221
83
|
${rows}
|
|
@@ -225,13 +87,10 @@ ${rows}
|
|
|
225
87
|
`;
|
|
226
88
|
}
|
|
227
89
|
function buildUrl(baseUrl, build) {
|
|
228
|
-
return
|
|
229
|
-
}
|
|
230
|
-
function indexUrl(baseUrl) {
|
|
231
|
-
return `${trimTrailingSlash(baseUrl)}/`;
|
|
90
|
+
return publicUrl(baseUrl, "build", build, "");
|
|
232
91
|
}
|
|
233
|
-
function
|
|
234
|
-
return
|
|
92
|
+
function buildIndexUrl(baseUrl) {
|
|
93
|
+
return publicUrl(baseUrl, "build", "");
|
|
235
94
|
}
|
|
236
95
|
function escapeHtml(value) {
|
|
237
96
|
return value
|
|
@@ -240,30 +99,4 @@ function escapeHtml(value) {
|
|
|
240
99
|
.replace(/>/g, ">")
|
|
241
100
|
.replace(/\"/g, """);
|
|
242
101
|
}
|
|
243
|
-
async function writeGithubSummary(markdown) {
|
|
244
|
-
const summaryPath = process.env.GITHUB_STEP_SUMMARY;
|
|
245
|
-
if (!summaryPath)
|
|
246
|
-
return;
|
|
247
|
-
await mkdir(dirname(summaryPath), { recursive: true });
|
|
248
|
-
const existing = await readTextIfExists(summaryPath);
|
|
249
|
-
await writeFile(summaryPath, `${existing}${markdown}`, "utf8");
|
|
250
|
-
}
|
|
251
|
-
async function writeGithubOutput(values) {
|
|
252
|
-
const outputPath = process.env.GITHUB_OUTPUT;
|
|
253
|
-
if (!outputPath)
|
|
254
|
-
return;
|
|
255
|
-
const lines = Object.entries(values)
|
|
256
|
-
.map(([key, value]) => `${key}=${value}`)
|
|
257
|
-
.join("\n");
|
|
258
|
-
const existing = await readTextIfExists(outputPath);
|
|
259
|
-
await writeFile(outputPath, `${existing}${lines}\n`, "utf8");
|
|
260
|
-
}
|
|
261
|
-
async function readTextIfExists(filePath) {
|
|
262
|
-
try {
|
|
263
|
-
return await readFile(filePath, "utf8");
|
|
264
|
-
}
|
|
265
|
-
catch {
|
|
266
|
-
return "";
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
102
|
//# sourceMappingURL=githubActionReport.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"githubActionReport.js","sourceRoot":"","sources":["../src/githubActionReport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"githubActionReport.js","sourceRoot":"","sources":["../src/githubActionReport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,SAAS,EACT,eAAe,EACf,UAAU,GAEX,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAMhF,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,OAAkC;IAElC,MAAM,SAAS,GACb,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,yBAAyB,CAAC;QACvD,0BAA0B,CAAC;IAC7B,MAAM,UAAU,GACd,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,0BAA0B,CAAC;QAClD,qBAAqB,CAAC;IACxB,MAAM,IAAI,GAAG,WAAW,CACtB,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,6BAA6B,CAAC,IAAI,UAAU,CACvE,CAAC;IAEF,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;QAClC,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC9D,MAAM,kBAAkB,CACtB;YACE,wBAAwB;YACxB,EAAE;YACF,qDAAqD,UAAU,CAAC,SAAS,CAAC,MAAM,UAAU,CAAC,UAAU,CAAC,UAAU;YAChH,qDAAqD,UAAU,CAAC,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,uBAAuB;YACrH,EAAE;SACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;QACF,MAAM,iBAAiB,CAAC;YACtB,wBAAwB,EAAE,OAAO;YACjC,YAAY,EAAE,SAAS;YACvB,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC;SAC3C,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,mCAAmC,SAAS,EAAE,CAAC,CAAC;QAC5D,OAAO;IACT,CAAC;IAED,MAAM,kBAAkB,CACtB;QACE,wBAAwB;QACxB,EAAE;QACF,yEAAyE,UAAU,CAAC,UAAU,CAAC,gBAAgB;QAC/G,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;IACF,MAAM,iBAAiB,CAAC;QACtB,wBAAwB,EAAE,MAAM;QAChC,eAAe,EAAE,UAAU;QAC3B,eAAe,EAAE,SAAS;KAC3B,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,kBAAkB,CAC/B,SAAiB,EACjB,MAAoC;IAEpC,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACvD,MAAM,cAAc,GAAG,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IAChE,MAAM,eAAe,CAAC,SAAS,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;IAEzD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC1D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC1E,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7C,MAAM,UAAU,GAAG,IAAI,CACrB,MAAM,EAAE,EACR,+BAA+B,OAAO,CAAC,GAAG,OAAO,CAClD,CAAC;IACF,MAAM,SAAS,CAAC,UAAU,EAAE,gBAAgB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IAC9E,MAAM,UAAU,CAAC,UAAU,EAAE,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC;IAEzE,OAAO,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,WAAW,CAAC,KAAa;IAChC,IAAI,KAAK,KAAK,KAAK,IAAI,KAAK,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC;IAC1D,MAAM,IAAI,KAAK,CAAC,gCAAgC,KAAK,EAAE,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,KAAK,CACZ,OAAkC,EAClC,GAAW,EACX,MAAc;IAEd,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC7C,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAe,EAAE,MAAgB;IACzD,MAAM,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC7C,MAAM,IAAI,GAAG,MAAM;SAChB,GAAG,CACF,CAAC,KAAK,EAAE,EAAE,CACR,iBAAiB,UAAU,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,YAAY,UAAU,CAAC,KAAK,CAAC,WAAW,CAChG;SACA,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,OAAO;;;;;;;;;;;;;iBAaQ,UAAU,CAAC,WAAW,CAAC;;EAEtC,IAAI;;;;CAIL,CAAC;AACF,CAAC;AAED,SAAS,QAAQ,CAAC,OAAe,EAAE,KAAa;IAC9C,OAAO,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,aAAa,CAAC,OAAe;IACpC,OAAO,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AAC9B,CAAC"}
|
package/dist/reportTemplate.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ export type ReportOptions = {
|
|
|
8
8
|
validationIssues?: ValidationIssue[];
|
|
9
9
|
title?: string;
|
|
10
10
|
generatedAt?: string;
|
|
11
|
+
githubBaseUrl?: string;
|
|
12
|
+
githubRef?: string;
|
|
13
|
+
repositoryUrl?: string;
|
|
11
14
|
};
|
|
12
15
|
/** Render a complete self-contained feature spec report as HTML. */
|
|
13
16
|
export declare function renderHtmlReport(specs: FeatureSpec[], options?: ReportOptions): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reportTemplate.d.ts","sourceRoot":"","sources":["../src/reportTemplate.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEV,eAAe,EACf,UAAU,EACV,WAAW,EAEX,SAAS,EACT,cAAc,EACd,SAAS,
|
|
1
|
+
{"version":3,"file":"reportTemplate.d.ts","sourceRoot":"","sources":["../src/reportTemplate.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEV,eAAe,EACf,UAAU,EACV,WAAW,EAEX,SAAS,EACT,cAAc,EACd,SAAS,EAET,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,cAAc,EAAE,CAAC;IAC/B,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAYF,oEAAoE;AACpE,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,GAAE,aAAkB,UAuE5B"}
|