@patronage/factory-ci 0.1.3 → 0.2.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 +22 -17
- package/dist/index.d.ts +221 -25
- package/dist/index.js +519 -132
- package/package.json +1 -1
- package/src/index.ts +23 -5
- package/src/proof-reuse-gate.ts +664 -0
- package/src/hosted-docs-only-detector.ts +0 -192
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
const githubExpression = (expression: string): string =>
|
|
2
|
-
`\${{ ${expression} }}`;
|
|
3
|
-
|
|
4
|
-
const BASE_SHA_EXPRESSION = githubExpression(
|
|
5
|
-
"github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before"
|
|
6
|
-
);
|
|
7
|
-
const HEAD_SHA_EXPRESSION = githubExpression(
|
|
8
|
-
"github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha"
|
|
9
|
-
);
|
|
10
|
-
|
|
11
|
-
export interface HostedDocsOnlyClassificationPolicy {
|
|
12
|
-
readonly docsOnlyFiles?: readonly string[];
|
|
13
|
-
readonly docsOnlyMarkdownPrefixes?: readonly string[];
|
|
14
|
-
readonly docsOnlyPrefixes?: readonly string[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface NormalizedHostedDocsOnlyClassificationPolicy {
|
|
18
|
-
readonly docsOnlyFiles: readonly string[];
|
|
19
|
-
readonly docsOnlyMarkdownPrefixes: readonly string[];
|
|
20
|
-
readonly docsOnlyPrefixes: readonly string[];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface HostedDocsOnlyDetectorStep {
|
|
24
|
-
readonly continueOnError: true;
|
|
25
|
-
readonly env: Readonly<{
|
|
26
|
-
BASE_SHA: string;
|
|
27
|
-
HEAD_SHA: string;
|
|
28
|
-
}>;
|
|
29
|
-
readonly id: "docs-only";
|
|
30
|
-
readonly name: "Detect docs-only changes";
|
|
31
|
-
readonly outputs: readonly ["docs_only"];
|
|
32
|
-
readonly run: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const stringArray = (value: unknown): readonly string[] | undefined => {
|
|
36
|
-
if (value === undefined) {
|
|
37
|
-
return [];
|
|
38
|
-
}
|
|
39
|
-
if (
|
|
40
|
-
Array.isArray(value) &&
|
|
41
|
-
value.every((entry) => typeof entry === "string" && entry.length > 0)
|
|
42
|
-
) {
|
|
43
|
-
return value;
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const normalizePolicy = (
|
|
48
|
-
value: unknown
|
|
49
|
-
): NormalizedHostedDocsOnlyClassificationPolicy | undefined => {
|
|
50
|
-
if (!(value && typeof value === "object" && !Array.isArray(value))) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const policy = value as Record<string, unknown>;
|
|
55
|
-
const docsOnlyFiles = stringArray(policy.docsOnlyFiles);
|
|
56
|
-
const docsOnlyMarkdownPrefixes = stringArray(policy.docsOnlyMarkdownPrefixes);
|
|
57
|
-
const docsOnlyPrefixes = stringArray(policy.docsOnlyPrefixes);
|
|
58
|
-
|
|
59
|
-
if (!(docsOnlyFiles && docsOnlyMarkdownPrefixes && docsOnlyPrefixes)) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
docsOnlyFiles,
|
|
65
|
-
docsOnlyMarkdownPrefixes,
|
|
66
|
-
docsOnlyPrefixes,
|
|
67
|
-
};
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const shellQuoteSegment = (value: string): string =>
|
|
71
|
-
`'${value.replaceAll("'", String.raw`'\''`)}'`;
|
|
72
|
-
|
|
73
|
-
const shellQuote = (value: string): string =>
|
|
74
|
-
value.split("$").map(shellQuoteSegment).join("'$'");
|
|
75
|
-
|
|
76
|
-
const shellArray = (values: readonly string[]): string =>
|
|
77
|
-
`(${values.map(shellQuote).join(" ")})`;
|
|
78
|
-
|
|
79
|
-
const shellExpansion = (name: string): string => `\${${name}}`;
|
|
80
|
-
|
|
81
|
-
const malformedPolicyScript = String.raw`printf 'docs_only=false\n' >> "$GITHUB_OUTPUT"`;
|
|
82
|
-
|
|
83
|
-
const detectorScript = (
|
|
84
|
-
policy: NormalizedHostedDocsOnlyClassificationPolicy
|
|
85
|
-
): string => String.raw`docs_only=false
|
|
86
|
-
changed_entries_file=""
|
|
87
|
-
|
|
88
|
-
finish() {
|
|
89
|
-
detector_status=$?
|
|
90
|
-
if [ -n "$changed_entries_file" ]; then
|
|
91
|
-
rm -f "$changed_entries_file" || true
|
|
92
|
-
fi
|
|
93
|
-
if [ "$detector_status" -ne 0 ]; then
|
|
94
|
-
docs_only=false
|
|
95
|
-
fi
|
|
96
|
-
printf 'docs_only=%s\n' "$docs_only" >> "$GITHUB_OUTPUT"
|
|
97
|
-
exit 0
|
|
98
|
-
}
|
|
99
|
-
trap finish EXIT
|
|
100
|
-
set -eo pipefail
|
|
101
|
-
|
|
102
|
-
if [ -z "$BASE_SHA" ] || [ -z "$HEAD_SHA" ] ||
|
|
103
|
-
[ "$BASE_SHA" = "0000000000000000000000000000000000000000" ] ||
|
|
104
|
-
[ "$HEAD_SHA" = "0000000000000000000000000000000000000000" ]; then
|
|
105
|
-
exit 0
|
|
106
|
-
fi
|
|
107
|
-
|
|
108
|
-
changed_entries_file="$(mktemp)"
|
|
109
|
-
git diff --name-status -z --find-renames --find-copies-harder \
|
|
110
|
-
"$BASE_SHA...$HEAD_SHA" -- > "$changed_entries_file"
|
|
111
|
-
if [ ! -s "$changed_entries_file" ]; then
|
|
112
|
-
exit 0
|
|
113
|
-
fi
|
|
114
|
-
|
|
115
|
-
docs_only_files=${shellArray(policy.docsOnlyFiles)}
|
|
116
|
-
docs_only_prefixes=${shellArray(policy.docsOnlyPrefixes)}
|
|
117
|
-
docs_only_markdown_prefixes=${shellArray(policy.docsOnlyMarkdownPrefixes)}
|
|
118
|
-
|
|
119
|
-
is_docs_only_path() {
|
|
120
|
-
candidate="$1"
|
|
121
|
-
if [ "$candidate" = "software-factory.profile.json" ]; then
|
|
122
|
-
return 1
|
|
123
|
-
fi
|
|
124
|
-
for policy_path in "${shellExpansion("docs_only_files[@]")}"; do
|
|
125
|
-
if [ "$candidate" = "$policy_path" ]; then
|
|
126
|
-
return 0
|
|
127
|
-
fi
|
|
128
|
-
done
|
|
129
|
-
for policy_path in "${shellExpansion("docs_only_prefixes[@]")}"; do
|
|
130
|
-
if [[ "$candidate" == "$policy_path"* ]]; then
|
|
131
|
-
return 0
|
|
132
|
-
fi
|
|
133
|
-
done
|
|
134
|
-
if [[ "$candidate" == *.md ]]; then
|
|
135
|
-
for policy_path in "${shellExpansion("docs_only_markdown_prefixes[@]")}"; do
|
|
136
|
-
if [[ "$candidate" == "$policy_path"* ]]; then
|
|
137
|
-
return 0
|
|
138
|
-
fi
|
|
139
|
-
done
|
|
140
|
-
fi
|
|
141
|
-
return 1
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
entry_count=0
|
|
145
|
-
while IFS= read -r -d '' change_status <&3; do
|
|
146
|
-
entry_count=$((entry_count + 1))
|
|
147
|
-
case "$change_status" in
|
|
148
|
-
A|D|M)
|
|
149
|
-
IFS= read -r -d '' changed_path <&3 || exit 0
|
|
150
|
-
is_docs_only_path "$changed_path" || exit 0
|
|
151
|
-
;;
|
|
152
|
-
R*|C*)
|
|
153
|
-
if [[ ! "$change_status" =~ ^[RC][0-9]{1,3}$ ]]; then
|
|
154
|
-
exit 0
|
|
155
|
-
fi
|
|
156
|
-
IFS= read -r -d '' source_path <&3 || exit 0
|
|
157
|
-
IFS= read -r -d '' destination_path <&3 || exit 0
|
|
158
|
-
is_docs_only_path "$source_path" || exit 0
|
|
159
|
-
is_docs_only_path "$destination_path" || exit 0
|
|
160
|
-
;;
|
|
161
|
-
*)
|
|
162
|
-
exit 0
|
|
163
|
-
;;
|
|
164
|
-
esac
|
|
165
|
-
done 3< "$changed_entries_file"
|
|
166
|
-
|
|
167
|
-
if [ "$entry_count" -gt 0 ]; then
|
|
168
|
-
docs_only=true
|
|
169
|
-
fi`;
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Generate the pre-install hosted detector from a consumer's canonical
|
|
173
|
-
* classification policy. The returned object is structurally accepted by
|
|
174
|
-
* gagen's `step()` without adding a gagen runtime dependency.
|
|
175
|
-
*/
|
|
176
|
-
export const hostedDocsOnlyDetectorStep = (
|
|
177
|
-
policy: HostedDocsOnlyClassificationPolicy
|
|
178
|
-
): HostedDocsOnlyDetectorStep => {
|
|
179
|
-
const normalized = normalizePolicy(policy);
|
|
180
|
-
|
|
181
|
-
return Object.freeze({
|
|
182
|
-
continueOnError: true as const,
|
|
183
|
-
env: Object.freeze({
|
|
184
|
-
BASE_SHA: BASE_SHA_EXPRESSION,
|
|
185
|
-
HEAD_SHA: HEAD_SHA_EXPRESSION,
|
|
186
|
-
}),
|
|
187
|
-
id: "docs-only" as const,
|
|
188
|
-
name: "Detect docs-only changes" as const,
|
|
189
|
-
outputs: ["docs_only"] as const,
|
|
190
|
-
run: normalized ? detectorScript(normalized) : malformedPolicyScript,
|
|
191
|
-
});
|
|
192
|
-
};
|