open-local-audit 0.62.0 → 0.63.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/CHANGELOG.md +6 -0
- package/README.md +12 -0
- package/dist/batch.d.ts +1 -0
- package/dist/batch.js +9 -3
- package/dist/batch.js.map +1 -1
- package/dist/cli.js +41 -6
- package/dist/cli.js.map +1 -1
- package/dist/discovery-runner.d.ts +1 -0
- package/dist/discovery-runner.js +9 -7
- package/dist/discovery-runner.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/output.d.ts +1 -0
- package/dist/output.js +20 -3
- package/dist/output.js.map +1 -1
- package/dist/shortlist-runner.js +4 -3
- package/dist/shortlist-runner.js.map +1 -1
- package/dist/workflow-output.d.ts +5 -0
- package/dist/workflow-output.js +70 -0
- package/dist/workflow-output.js.map +1 -0
- package/dist/workflow-paths.d.ts +19 -0
- package/dist/workflow-paths.js +111 -0
- package/dist/workflow-paths.js.map +1 -0
- package/dist/workflow-preflight.d.ts +38 -0
- package/dist/workflow-preflight.js +206 -0
- package/dist/workflow-preflight.js.map +1 -0
- package/dist/workflow.js +6 -27
- package/dist/workflow.js.map +1 -1
- package/docs/architecture/workflow-command.md +106 -0
- package/docs/architecture/workflow-preflight.md +131 -0
- package/package.json +4 -2
package/dist/shortlist-runner.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { mkdir, readFile
|
|
1
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { buildLeadShortlist, readShortlistReviewCsv, renderShortlistCsv, renderShortlistJson, renderShortlistMarkdown, renderShortlistSummaryJson } from "./shortlist.js";
|
|
4
|
+
import { writeWorkflowOutputFile } from "./workflow-output.js";
|
|
4
5
|
export async function runShortlistReport(options) {
|
|
5
6
|
const input = await readFile(options.input, "utf8");
|
|
6
7
|
const reviewRows = options.reviewCsv ? readShortlistReviewCsv(await readFile(options.reviewCsv, "utf8")) : [];
|
|
@@ -14,10 +15,10 @@ export async function runShortlistReport(options) {
|
|
|
14
15
|
? renderShortlistCsv(result)
|
|
15
16
|
: renderShortlistMarkdown(result);
|
|
16
17
|
await mkdir(dirname(options.out), { recursive: true });
|
|
17
|
-
await
|
|
18
|
+
await writeWorkflowOutputFile(options.out, output);
|
|
18
19
|
if (options.summaryJson) {
|
|
19
20
|
await mkdir(dirname(options.summaryJson), { recursive: true });
|
|
20
|
-
await
|
|
21
|
+
await writeWorkflowOutputFile(options.summaryJson, renderShortlistSummaryJson(result));
|
|
21
22
|
}
|
|
22
23
|
return result;
|
|
23
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shortlist-runner.js","sourceRoot":"","sources":["../src/shortlist-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"shortlist-runner.js","sourceRoot":"","sources":["../src/shortlist-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,0BAA0B,EAI3B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAW/D,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,OAA4B;IACnE,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACpD,MAAM,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,sBAAsB,CAAC,MAAM,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9G,MAAM,MAAM,GAAG,kBAAkB,CAAC,KAAK,EAAE;QACvC,GAAG,OAAO,CAAC,SAAS;QACpB,UAAU;KACX,CAAC,CAAC;IAEH,MAAM,MAAM,GACV,OAAO,CAAC,MAAM,KAAK,MAAM;QACvB,CAAC,CAAC,mBAAmB,CAAC,MAAM,CAAC;QAC7B,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,KAAK;YACxB,CAAC,CAAC,kBAAkB,CAAC,MAAM,CAAC;YAC5B,CAAC,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAExC,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,MAAM,uBAAuB,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAEnD,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACxB,MAAM,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/D,MAAM,uBAAuB,CAAC,OAAO,CAAC,WAAW,EAAE,0BAA0B,CAAC,MAAM,CAAC,CAAC,CAAC;IACzF,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { lstat, mkdir, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
function isMissingPath(error) {
|
|
5
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
6
|
+
}
|
|
7
|
+
function isContainedPath(root, path) {
|
|
8
|
+
const relativePath = relative(root, path);
|
|
9
|
+
return relativePath === "" || (!relativePath.startsWith(`..${sep}`) && relativePath !== ".." && !isAbsolute(relativePath));
|
|
10
|
+
}
|
|
11
|
+
async function ensureManagedOutputDirectory(directory, managedOutputRoot) {
|
|
12
|
+
const resolvedRoot = resolve(managedOutputRoot);
|
|
13
|
+
const resolvedDirectory = resolve(directory);
|
|
14
|
+
if (!isContainedPath(resolvedRoot, resolvedDirectory)) {
|
|
15
|
+
throw new Error("Managed output directory escapes managed output root");
|
|
16
|
+
}
|
|
17
|
+
async function inspectDirectory() {
|
|
18
|
+
const rootInfo = await lstat(resolvedRoot);
|
|
19
|
+
if (rootInfo.isSymbolicLink()) {
|
|
20
|
+
throw new Error("Managed output directory must not be linked");
|
|
21
|
+
}
|
|
22
|
+
if (!rootInfo.isDirectory()) {
|
|
23
|
+
throw new Error("Managed output directory must be a directory");
|
|
24
|
+
}
|
|
25
|
+
let directoryInfo;
|
|
26
|
+
try {
|
|
27
|
+
directoryInfo = await lstat(resolvedDirectory);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (isMissingPath(error)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
if (directoryInfo.isSymbolicLink()) {
|
|
36
|
+
throw new Error("Managed output directory must not be linked");
|
|
37
|
+
}
|
|
38
|
+
if (!directoryInfo.isDirectory()) {
|
|
39
|
+
throw new Error("Managed output directory must be a directory");
|
|
40
|
+
}
|
|
41
|
+
const [realRoot, realDirectory] = await Promise.all([realpath(resolvedRoot), realpath(resolvedDirectory)]);
|
|
42
|
+
if (!isContainedPath(realRoot, realDirectory)) {
|
|
43
|
+
throw new Error("Managed output directory escapes managed output root");
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (!(await inspectDirectory())) {
|
|
48
|
+
await mkdir(resolvedDirectory, { recursive: true });
|
|
49
|
+
await inspectDirectory();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export async function writeWorkflowOutputFile(path, content, options = {}) {
|
|
53
|
+
if (options.managedOutputRoot) {
|
|
54
|
+
await ensureManagedOutputDirectory(dirname(path), options.managedOutputRoot);
|
|
55
|
+
}
|
|
56
|
+
const temporaryPath = join(dirname(path), `.${basename(path)}-${randomUUID()}.tmp`);
|
|
57
|
+
try {
|
|
58
|
+
await writeFile(temporaryPath, content, "utf8");
|
|
59
|
+
await rename(temporaryPath, path);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
try {
|
|
63
|
+
await rm(temporaryPath, { force: true });
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Preserve the write or replacement failure when its temporary cleanup also fails.
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=workflow-output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-output.js","sourceRoot":"","sources":["../src/workflow-output.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { lstat } from "node:fs/promises";
|
|
2
|
+
import type { ResolvedWorkflowConfig } from "./workflow-config.js";
|
|
3
|
+
export type WorkflowPathIssueId = "output-linked" | "reports-linked" | "packages-linked" | "managed-file-linked" | "reports-escape" | "packages-escape";
|
|
4
|
+
export interface WorkflowPathIssue {
|
|
5
|
+
id: WorkflowPathIssueId;
|
|
6
|
+
message: string;
|
|
7
|
+
path: string;
|
|
8
|
+
}
|
|
9
|
+
export interface WorkflowPathInspection {
|
|
10
|
+
status: "safe" | "unsafe";
|
|
11
|
+
issues: WorkflowPathIssue[];
|
|
12
|
+
}
|
|
13
|
+
interface WorkflowPathDependencies {
|
|
14
|
+
lstat(path: string): ReturnType<typeof lstat>;
|
|
15
|
+
realpath(path: string): Promise<string>;
|
|
16
|
+
}
|
|
17
|
+
export declare function inspectWorkflowManagedPaths(config: ResolvedWorkflowConfig, dependencies?: Partial<WorkflowPathDependencies>): Promise<WorkflowPathInspection>;
|
|
18
|
+
export declare function prepareWorkflowManagedDirectories(config: ResolvedWorkflowConfig): Promise<void>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { lstat, mkdir, realpath } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, relative, sep } from "node:path";
|
|
3
|
+
const defaultDependencies = { lstat, realpath };
|
|
4
|
+
function isMissingPath(error) {
|
|
5
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
6
|
+
}
|
|
7
|
+
async function pathInfo(path, dependencies) {
|
|
8
|
+
try {
|
|
9
|
+
return await dependencies.lstat(path);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (isMissingPath(error)) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export async function inspectWorkflowManagedPaths(config, dependencies = {}) {
|
|
19
|
+
const resolvedDependencies = { ...defaultDependencies, ...dependencies };
|
|
20
|
+
const outputInfo = await pathInfo(config.outDir, resolvedDependencies);
|
|
21
|
+
if (!outputInfo) {
|
|
22
|
+
return { status: "safe", issues: [] };
|
|
23
|
+
}
|
|
24
|
+
if (outputInfo.isSymbolicLink()) {
|
|
25
|
+
return {
|
|
26
|
+
status: "unsafe",
|
|
27
|
+
issues: [
|
|
28
|
+
{
|
|
29
|
+
id: "output-linked",
|
|
30
|
+
message: "Managed output directory must not be linked",
|
|
31
|
+
path: config.outDir
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const realOutDir = await resolvedDependencies.realpath(config.outDir);
|
|
37
|
+
const directories = [
|
|
38
|
+
{
|
|
39
|
+
id: "reports",
|
|
40
|
+
path: config.paths.reportsDir
|
|
41
|
+
},
|
|
42
|
+
...(config.packageReports
|
|
43
|
+
? [
|
|
44
|
+
{
|
|
45
|
+
id: "packages",
|
|
46
|
+
path: config.paths.packagesDir
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
: [])
|
|
50
|
+
];
|
|
51
|
+
const issues = [];
|
|
52
|
+
for (const path of [
|
|
53
|
+
config.paths.leadsCsv,
|
|
54
|
+
config.paths.discoverySummaryJson,
|
|
55
|
+
config.paths.shortlistCsv,
|
|
56
|
+
config.paths.shortlistSummaryJson,
|
|
57
|
+
config.paths.reviewSummaryJson,
|
|
58
|
+
config.paths.workflowSummaryJson
|
|
59
|
+
]) {
|
|
60
|
+
const fileInfo = await pathInfo(path, resolvedDependencies);
|
|
61
|
+
if (fileInfo?.isSymbolicLink()) {
|
|
62
|
+
issues.push({
|
|
63
|
+
id: "managed-file-linked",
|
|
64
|
+
message: "Managed output file must not be linked",
|
|
65
|
+
path
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const directory of directories) {
|
|
70
|
+
const directoryInfo = await pathInfo(directory.path, resolvedDependencies);
|
|
71
|
+
if (!directoryInfo) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (directoryInfo.isSymbolicLink()) {
|
|
75
|
+
issues.push({
|
|
76
|
+
id: `${directory.id}-linked`,
|
|
77
|
+
message: `Managed ${directory.id} directory must not be linked`,
|
|
78
|
+
path: directory.path
|
|
79
|
+
});
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const realDirectory = await resolvedDependencies.realpath(directory.path);
|
|
83
|
+
const relativeDirectory = relative(realOutDir, realDirectory);
|
|
84
|
+
if (relativeDirectory === ".." ||
|
|
85
|
+
relativeDirectory.startsWith(`..${sep}`) ||
|
|
86
|
+
isAbsolute(relativeDirectory)) {
|
|
87
|
+
issues.push({
|
|
88
|
+
id: `${directory.id}-escape`,
|
|
89
|
+
message: `Managed ${directory.id} directory escapes output directory`,
|
|
90
|
+
path: directory.path
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return issues.length === 0 ? { status: "safe", issues } : { status: "unsafe", issues };
|
|
95
|
+
}
|
|
96
|
+
export async function prepareWorkflowManagedDirectories(config) {
|
|
97
|
+
await mkdir(config.outDir, { recursive: true });
|
|
98
|
+
const initialInspection = await inspectWorkflowManagedPaths(config);
|
|
99
|
+
if (initialInspection.status === "unsafe") {
|
|
100
|
+
throw new Error(initialInspection.issues[0].message);
|
|
101
|
+
}
|
|
102
|
+
await mkdir(config.paths.reportsDir, { recursive: true });
|
|
103
|
+
if (config.packageReports) {
|
|
104
|
+
await mkdir(config.paths.packagesDir, { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
const inspection = await inspectWorkflowManagedPaths(config);
|
|
107
|
+
if (inspection.status === "unsafe") {
|
|
108
|
+
throw new Error(inspection.issues[0].message);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=workflow-paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-paths.js","sourceRoot":"","sources":["../src/workflow-paths.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Stats } from "node:fs";
|
|
2
|
+
import type { ResolvedWorkflowConfig, WorkflowManagedPaths } from "./workflow-config.js";
|
|
3
|
+
import type { WorkflowPathInspection } from "./workflow-paths.js";
|
|
4
|
+
export type WorkflowPreflightStatus = "ready" | "blocked";
|
|
5
|
+
export type WorkflowPreflightCheckStatus = "pass" | "warn" | "fail";
|
|
6
|
+
export type WorkflowPreflightStage = "discovery" | "shortlist" | "review" | "packaging";
|
|
7
|
+
export type WorkflowPreflightCheckId = "configuration" | "discovery-input" | "google-api-key" | "review-csv" | "output-access" | "managed-paths";
|
|
8
|
+
export interface WorkflowPreflightCheck {
|
|
9
|
+
id: WorkflowPreflightCheckId;
|
|
10
|
+
status: WorkflowPreflightCheckStatus;
|
|
11
|
+
message: string;
|
|
12
|
+
path?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface WorkflowPreflightReport {
|
|
15
|
+
version: 1;
|
|
16
|
+
status: WorkflowPreflightStatus;
|
|
17
|
+
checks: WorkflowPreflightCheck[];
|
|
18
|
+
provider?: "manual-csv" | "google-places";
|
|
19
|
+
stages?: WorkflowPreflightStage[];
|
|
20
|
+
outputs?: {
|
|
21
|
+
outDir: string;
|
|
22
|
+
} & WorkflowManagedPaths;
|
|
23
|
+
limits?: {
|
|
24
|
+
maxCandidates: number | null;
|
|
25
|
+
maxAudits: number | null;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export interface WorkflowPreflightDependencies {
|
|
29
|
+
readWorkflowConfig(configPath: string): Promise<ResolvedWorkflowConfig>;
|
|
30
|
+
resolveGoogleMapsApiKey(): string | undefined;
|
|
31
|
+
lstat(path: string): Promise<Stats>;
|
|
32
|
+
access(path: string, mode?: number): Promise<void>;
|
|
33
|
+
inspectWorkflowManagedPaths(config: ResolvedWorkflowConfig): Promise<WorkflowPathInspection>;
|
|
34
|
+
}
|
|
35
|
+
export declare function renderWorkflowPreflightTerminal(report: WorkflowPreflightReport, configPath: string): string;
|
|
36
|
+
export declare function renderWorkflowPreflightJson(report: WorkflowPreflightReport): string;
|
|
37
|
+
export declare function runWorkflowPreflight(configPath: string): Promise<WorkflowPreflightReport>;
|
|
38
|
+
export declare function runWorkflowPreflightWithDependencies(configPath: string, overrides?: Partial<WorkflowPreflightDependencies>): Promise<WorkflowPreflightReport>;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access, lstat } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { ZodError } from "zod";
|
|
5
|
+
import { readWorkflowConfig } from "./workflow-config.js";
|
|
6
|
+
import { inspectWorkflowManagedPaths } from "./workflow-paths.js";
|
|
7
|
+
import { resolveGoogleMapsApiKey } from "./secrets.js";
|
|
8
|
+
const defaultDependencies = {
|
|
9
|
+
readWorkflowConfig,
|
|
10
|
+
resolveGoogleMapsApiKey,
|
|
11
|
+
lstat,
|
|
12
|
+
access,
|
|
13
|
+
inspectWorkflowManagedPaths
|
|
14
|
+
};
|
|
15
|
+
function hasErrorCode(error, code) {
|
|
16
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
|
|
17
|
+
}
|
|
18
|
+
function isFilesystemReadinessError(error) {
|
|
19
|
+
return ["ENOENT", "EACCES", "EPERM", "EISDIR", "ENOTDIR", "ELOOP", "ENAMETOOLONG", "EIO", "ESTALE"].some((code) => hasErrorCode(error, code));
|
|
20
|
+
}
|
|
21
|
+
function isInvalidWorkflowConfigJsonError(error) {
|
|
22
|
+
return Boolean(error &&
|
|
23
|
+
typeof error === "object" &&
|
|
24
|
+
"cause" in error &&
|
|
25
|
+
error.cause instanceof SyntaxError &&
|
|
26
|
+
error instanceof Error &&
|
|
27
|
+
error.message.startsWith("Workflow config ") &&
|
|
28
|
+
error.message.endsWith(" contains invalid JSON"));
|
|
29
|
+
}
|
|
30
|
+
function isExpectedWorkflowConfigError(error) {
|
|
31
|
+
return isFilesystemReadinessError(error) || error instanceof ZodError || isInvalidWorkflowConfigJsonError(error);
|
|
32
|
+
}
|
|
33
|
+
async function isReadableRegularFile(path, dependencies) {
|
|
34
|
+
try {
|
|
35
|
+
const info = await dependencies.lstat(path);
|
|
36
|
+
if (!info.isFile()) {
|
|
37
|
+
return "invalid";
|
|
38
|
+
}
|
|
39
|
+
await dependencies.access(path, constants.R_OK);
|
|
40
|
+
return "readable";
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (!isFilesystemReadinessError(error)) {
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
return hasErrorCode(error, "ENOENT") ? "missing" : "invalid";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function findWritableOutputAncestor(outDir, dependencies) {
|
|
50
|
+
let ancestor = outDir;
|
|
51
|
+
while (true) {
|
|
52
|
+
let info;
|
|
53
|
+
try {
|
|
54
|
+
info = await dependencies.lstat(ancestor);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (!hasErrorCode(error, "ENOENT")) {
|
|
58
|
+
if (isFilesystemReadinessError(error)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
const parent = dirname(ancestor);
|
|
64
|
+
if (parent === ancestor) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
ancestor = parent;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!info.isDirectory()) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await dependencies.access(ancestor, constants.W_OK);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (isFilesystemReadinessError(error)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function reportStatus(checks) {
|
|
86
|
+
return checks.some((check) => check.status === "fail") ? "blocked" : "ready";
|
|
87
|
+
}
|
|
88
|
+
export function renderWorkflowPreflightTerminal(report, configPath) {
|
|
89
|
+
const details = [
|
|
90
|
+
...(report.stages ? [`Stages: ${report.stages.join(" -> ")}`] : []),
|
|
91
|
+
...(report.outputs ? [`Managed output: ${report.outputs.outDir}`] : [])
|
|
92
|
+
];
|
|
93
|
+
const lines = [
|
|
94
|
+
`Workflow preflight: ${report.status.toUpperCase()}`,
|
|
95
|
+
`Config: ${configPath}`,
|
|
96
|
+
...(report.provider ? [`Provider: ${report.provider}`] : []),
|
|
97
|
+
"",
|
|
98
|
+
...report.checks.map((check) => `${check.status.toUpperCase().padEnd(5)} ${check.message}`),
|
|
99
|
+
...(details.length > 0 ? ["", ...details] : [])
|
|
100
|
+
];
|
|
101
|
+
return `${lines.join("\n")}\n`;
|
|
102
|
+
}
|
|
103
|
+
export function renderWorkflowPreflightJson(report) {
|
|
104
|
+
return `${JSON.stringify(report, null, 2)}\n`;
|
|
105
|
+
}
|
|
106
|
+
export async function runWorkflowPreflight(configPath) {
|
|
107
|
+
return runWorkflowPreflightWithDependencies(configPath);
|
|
108
|
+
}
|
|
109
|
+
export async function runWorkflowPreflightWithDependencies(configPath, overrides = {}) {
|
|
110
|
+
const dependencies = { ...defaultDependencies, ...overrides };
|
|
111
|
+
let config;
|
|
112
|
+
try {
|
|
113
|
+
config = await dependencies.readWorkflowConfig(configPath);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (!isExpectedWorkflowConfigError(error)) {
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
version: 1,
|
|
121
|
+
status: "blocked",
|
|
122
|
+
checks: [
|
|
123
|
+
{
|
|
124
|
+
id: "configuration",
|
|
125
|
+
status: "fail",
|
|
126
|
+
message: "Workflow configuration could not be read or validated"
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const checks = [
|
|
132
|
+
{ id: "configuration", status: "pass", message: "Workflow configuration is valid" }
|
|
133
|
+
];
|
|
134
|
+
if (config.discovery.provider === "manual-csv") {
|
|
135
|
+
const inputStatus = await isReadableRegularFile(config.discovery.input, dependencies);
|
|
136
|
+
checks.push({
|
|
137
|
+
id: "discovery-input",
|
|
138
|
+
status: inputStatus === "readable" ? "pass" : "fail",
|
|
139
|
+
message: inputStatus === "readable" ? "Discovery input is readable" : "Discovery input must be a readable regular file",
|
|
140
|
+
path: config.discovery.input
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
const hasApiKey = Boolean(dependencies.resolveGoogleMapsApiKey()?.trim());
|
|
145
|
+
checks.push({
|
|
146
|
+
id: "google-api-key",
|
|
147
|
+
status: hasApiKey ? "pass" : "fail",
|
|
148
|
+
message: hasApiKey ? "Google Maps API key is available" : "Google Maps API key is required"
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (config.review) {
|
|
152
|
+
const reviewStatus = await isReadableRegularFile(config.review.csv, dependencies);
|
|
153
|
+
checks.push({
|
|
154
|
+
id: "review-csv",
|
|
155
|
+
status: reviewStatus === "readable" ? "pass" : reviewStatus === "missing" ? "warn" : "fail",
|
|
156
|
+
message: reviewStatus === "readable"
|
|
157
|
+
? "Review CSV is readable"
|
|
158
|
+
: reviewStatus === "missing"
|
|
159
|
+
? "Review CSV does not exist and will be created"
|
|
160
|
+
: "Review CSV must be a readable regular file",
|
|
161
|
+
path: config.review.csv
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const outputWritable = await findWritableOutputAncestor(config.outDir, dependencies);
|
|
165
|
+
checks.push({
|
|
166
|
+
id: "output-access",
|
|
167
|
+
status: outputWritable ? "pass" : "fail",
|
|
168
|
+
message: outputWritable ? "Output location is writable" : "Output location is not writable",
|
|
169
|
+
path: config.outDir
|
|
170
|
+
});
|
|
171
|
+
try {
|
|
172
|
+
const inspection = await dependencies.inspectWorkflowManagedPaths(config);
|
|
173
|
+
for (const issue of inspection.issues) {
|
|
174
|
+
checks.push({ id: "managed-paths", status: "fail", message: issue.message, path: issue.path });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (!isFilesystemReadinessError(error)) {
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
checks.push({
|
|
182
|
+
id: "managed-paths",
|
|
183
|
+
status: "fail",
|
|
184
|
+
message: "Managed output paths could not be inspected safely"
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const stages = [
|
|
188
|
+
"discovery",
|
|
189
|
+
"shortlist",
|
|
190
|
+
...(config.review ? ["review"] : []),
|
|
191
|
+
...(config.packageReports ? ["packaging"] : [])
|
|
192
|
+
];
|
|
193
|
+
return {
|
|
194
|
+
version: 1,
|
|
195
|
+
status: reportStatus(checks),
|
|
196
|
+
checks,
|
|
197
|
+
provider: config.discovery.provider,
|
|
198
|
+
stages,
|
|
199
|
+
outputs: { outDir: config.outDir, ...config.paths },
|
|
200
|
+
limits: {
|
|
201
|
+
maxCandidates: config.discovery.provider === "google-places" ? config.discovery.limit : null,
|
|
202
|
+
maxAudits: config.discovery.maxAudits ?? null
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=workflow-preflight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-preflight.js","sourceRoot":"","sources":["../src/workflow-preflight.ts"],"names":[],"mappings":"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"}
|
package/dist/workflow.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { lstat, mkdir, mkdtemp, realpath, rename, rm
|
|
2
|
+
import { lstat, mkdir, mkdtemp, realpath, rename, rm } from "node:fs/promises";
|
|
3
3
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { runDiscovery } from "./discovery-runner.js";
|
|
5
5
|
import { packageReport } from "./report-pack.js";
|
|
@@ -7,6 +7,8 @@ import { summarizeReviewCsvFile } from "./review.js";
|
|
|
7
7
|
import { runShortlistReport } from "./shortlist-runner.js";
|
|
8
8
|
import { resolveGoogleMapsApiKey } from "./secrets.js";
|
|
9
9
|
import { readWorkflowConfig } from "./workflow-config.js";
|
|
10
|
+
import { prepareWorkflowManagedDirectories } from "./workflow-paths.js";
|
|
11
|
+
import { writeWorkflowOutputFile } from "./workflow-output.js";
|
|
10
12
|
export class WorkflowRunError extends Error {
|
|
11
13
|
summary;
|
|
12
14
|
constructor(summary) {
|
|
@@ -94,7 +96,7 @@ function createInitialSummary(config) {
|
|
|
94
96
|
}
|
|
95
97
|
async function writePrettyJson(path, value) {
|
|
96
98
|
await mkdir(dirname(path), { recursive: true });
|
|
97
|
-
await
|
|
99
|
+
await writeWorkflowOutputFile(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
98
100
|
}
|
|
99
101
|
async function writeWorkflowSummary(summary) {
|
|
100
102
|
await writePrettyJson(summary.outputs.workflowSummaryJson, summary);
|
|
@@ -120,30 +122,6 @@ function markStageFailure(summary, stage, message) {
|
|
|
120
122
|
message
|
|
121
123
|
};
|
|
122
124
|
}
|
|
123
|
-
async function prepareManagedDirectories(config) {
|
|
124
|
-
await mkdir(config.outDir, { recursive: true });
|
|
125
|
-
const outputInfo = await lstat(config.outDir);
|
|
126
|
-
if (outputInfo.isSymbolicLink()) {
|
|
127
|
-
throw new Error("Managed output directory must not be linked");
|
|
128
|
-
}
|
|
129
|
-
const realOutDir = await realpath(config.outDir);
|
|
130
|
-
const directories = [
|
|
131
|
-
{ label: "reports", path: config.paths.reportsDir },
|
|
132
|
-
...(config.packageReports ? [{ label: "packages", path: config.paths.packagesDir }] : [])
|
|
133
|
-
];
|
|
134
|
-
for (const directory of directories) {
|
|
135
|
-
await mkdir(directory.path, { recursive: true });
|
|
136
|
-
const directoryInfo = await lstat(directory.path);
|
|
137
|
-
if (directoryInfo.isSymbolicLink()) {
|
|
138
|
-
throw new Error(`Managed ${directory.label} directory must not be linked`);
|
|
139
|
-
}
|
|
140
|
-
const realDirectory = await realpath(directory.path);
|
|
141
|
-
const relativeDirectory = relative(realOutDir, realDirectory);
|
|
142
|
-
if (relativeDirectory.startsWith("..") || isAbsolute(relativeDirectory)) {
|
|
143
|
-
throw new Error(`Managed ${directory.label} directory escapes output directory`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
125
|
async function resolvePackageInputDir(reportsDir, reportPath) {
|
|
148
126
|
const resolvedReportPath = resolve(reportsDir, reportPath);
|
|
149
127
|
const relativePath = relative(reportsDir, resolvedReportPath);
|
|
@@ -271,7 +249,7 @@ export async function runResolvedWorkflow(config, dependencies = {}) {
|
|
|
271
249
|
...defaultDependencies,
|
|
272
250
|
...dependencies
|
|
273
251
|
};
|
|
274
|
-
await
|
|
252
|
+
await prepareWorkflowManagedDirectories(config);
|
|
275
253
|
const summary = createInitialSummary(config);
|
|
276
254
|
const knownSecrets = [];
|
|
277
255
|
try {
|
|
@@ -285,6 +263,7 @@ export async function runResolvedWorkflow(config, dependencies = {}) {
|
|
|
285
263
|
...(config.discovery.provider === "google-places" ? { query: config.discovery.query } : {}),
|
|
286
264
|
profile: config.discovery.profile,
|
|
287
265
|
outDir: config.paths.reportsDir,
|
|
266
|
+
managedOutputRoot: config.paths.reportsDir,
|
|
288
267
|
exportCsv: config.paths.leadsCsv,
|
|
289
268
|
summaryJson: config.paths.discoverySummaryJson,
|
|
290
269
|
reviewCsv: config.review?.csv,
|