@sitepong/fastnode 0.1.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 +81 -0
- package/bin/fastnode.js +13 -0
- package/bin/fastnode.ts +5 -0
- package/dist/actions/index.cjs +7405 -0
- package/dist/actions/index.cjs.map +1 -0
- package/dist/actions/index.d.cts +15 -0
- package/dist/actions/index.d.ts +15 -0
- package/dist/actions/index.mjs +7377 -0
- package/dist/actions/index.mjs.map +1 -0
- package/dist/cert/index.cjs +219 -0
- package/dist/cert/index.cjs.map +1 -0
- package/dist/cert/index.d.cts +4 -0
- package/dist/cert/index.d.ts +4 -0
- package/dist/cert/index.mjs +191 -0
- package/dist/cert/index.mjs.map +1 -0
- package/dist/cert-BmJGtYtb.d.cts +27 -0
- package/dist/cert-owkOFNW8.d.ts +27 -0
- package/dist/cli/index.cjs +7998 -0
- package/dist/cli/index.cjs.map +1 -0
- package/dist/cli/index.d.cts +9 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.mjs +7972 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/client/index.cjs +2592 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +2 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.mjs +2582 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/context-CymLkfrS.d.cts +13 -0
- package/dist/context-CymLkfrS.d.ts +13 -0
- package/dist/core/index.cjs +281 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.mjs +254 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/deliver/index.cjs +328 -0
- package/dist/deliver/index.cjs.map +1 -0
- package/dist/deliver/index.d.cts +3 -0
- package/dist/deliver/index.d.ts +3 -0
- package/dist/deliver/index.mjs +322 -0
- package/dist/deliver/index.mjs.map +1 -0
- package/dist/enums-B609j2rb.d.cts +20 -0
- package/dist/enums-B609j2rb.d.ts +20 -0
- package/dist/frameit/index.cjs +247 -0
- package/dist/frameit/index.cjs.map +1 -0
- package/dist/frameit/index.d.cts +1 -0
- package/dist/frameit/index.d.ts +1 -0
- package/dist/frameit/index.mjs +242 -0
- package/dist/frameit/index.mjs.map +1 -0
- package/dist/gym/index.cjs +356 -0
- package/dist/gym/index.cjs.map +1 -0
- package/dist/gym/index.d.cts +2 -0
- package/dist/gym/index.d.ts +2 -0
- package/dist/gym/index.mjs +346 -0
- package/dist/gym/index.mjs.map +1 -0
- package/dist/index-1Vu2cRQ2.d.ts +99 -0
- package/dist/index-A5MimeQk.d.ts +72 -0
- package/dist/index-B0yhu_LZ.d.ts +76 -0
- package/dist/index-B9yXicrQ.d.ts +62 -0
- package/dist/index-BFdNOmPJ.d.cts +53 -0
- package/dist/index-BLubSlgK.d.cts +1513 -0
- package/dist/index-BN0ubxjZ.d.ts +70 -0
- package/dist/index-BQJIJlK7.d.cts +70 -0
- package/dist/index-BYonklf3.d.cts +84 -0
- package/dist/index-BYonklf3.d.ts +84 -0
- package/dist/index-BimqCmY8.d.cts +78 -0
- package/dist/index-BimqCmY8.d.ts +78 -0
- package/dist/index-BoU8qt8r.d.cts +59 -0
- package/dist/index-BoU8qt8r.d.ts +59 -0
- package/dist/index-C2yG3wXA.d.cts +95 -0
- package/dist/index-C2yG3wXA.d.ts +95 -0
- package/dist/index-CR3BFPIz.d.cts +77 -0
- package/dist/index-CR3BFPIz.d.ts +77 -0
- package/dist/index-CWNLnSOL.d.cts +99 -0
- package/dist/index-Cp6cjtd-.d.cts +65 -0
- package/dist/index-Cp6cjtd-.d.ts +65 -0
- package/dist/index-D1Izm7qt.d.ts +1513 -0
- package/dist/index-D5eihzwM.d.cts +76 -0
- package/dist/index-DImN9PWc.d.ts +155 -0
- package/dist/index-DfCUVJX1.d.cts +62 -0
- package/dist/index-DfirmFbC.d.ts +32 -0
- package/dist/index-DkvinY7_.d.ts +54 -0
- package/dist/index-DrUpRXRG.d.cts +155 -0
- package/dist/index-EcVvViW8.d.cts +72 -0
- package/dist/index-HRiaaX-Q.d.cts +54 -0
- package/dist/index-TfXqwuac.d.ts +53 -0
- package/dist/index-lt819Jgj.d.cts +32 -0
- package/dist/index.cjs +8464 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.mjs +8410 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lane/index.cjs +7641 -0
- package/dist/lane/index.cjs.map +1 -0
- package/dist/lane/index.d.cts +51 -0
- package/dist/lane/index.d.ts +51 -0
- package/dist/lane/index.mjs +7603 -0
- package/dist/lane/index.mjs.map +1 -0
- package/dist/match/index.cjs +942 -0
- package/dist/match/index.cjs.map +1 -0
- package/dist/match/index.d.cts +5 -0
- package/dist/match/index.d.ts +5 -0
- package/dist/match/index.mjs +913 -0
- package/dist/match/index.mjs.map +1 -0
- package/dist/pem/index.cjs +195 -0
- package/dist/pem/index.cjs.map +1 -0
- package/dist/pem/index.d.cts +3 -0
- package/dist/pem/index.d.ts +3 -0
- package/dist/pem/index.mjs +170 -0
- package/dist/pem/index.mjs.map +1 -0
- package/dist/pilot/index.cjs +206 -0
- package/dist/pilot/index.cjs.map +1 -0
- package/dist/pilot/index.d.cts +3 -0
- package/dist/pilot/index.d.ts +3 -0
- package/dist/pilot/index.mjs +196 -0
- package/dist/pilot/index.mjs.map +1 -0
- package/dist/precheck/index.cjs +277 -0
- package/dist/precheck/index.cjs.map +1 -0
- package/dist/precheck/index.d.cts +3 -0
- package/dist/precheck/index.d.ts +3 -0
- package/dist/precheck/index.mjs +274 -0
- package/dist/precheck/index.mjs.map +1 -0
- package/dist/produce/index.cjs +200 -0
- package/dist/produce/index.cjs.map +1 -0
- package/dist/produce/index.d.cts +3 -0
- package/dist/produce/index.d.ts +3 -0
- package/dist/produce/index.mjs +191 -0
- package/dist/produce/index.mjs.map +1 -0
- package/dist/scan/index.cjs +187 -0
- package/dist/scan/index.cjs.map +1 -0
- package/dist/scan/index.d.cts +1 -0
- package/dist/scan/index.d.ts +1 -0
- package/dist/scan/index.mjs +183 -0
- package/dist/scan/index.mjs.map +1 -0
- package/dist/sigh/index.cjs +203 -0
- package/dist/sigh/index.cjs.map +1 -0
- package/dist/sigh/index.d.cts +3 -0
- package/dist/sigh/index.d.ts +3 -0
- package/dist/sigh/index.mjs +198 -0
- package/dist/sigh/index.mjs.map +1 -0
- package/dist/snapshot/index.cjs +448 -0
- package/dist/snapshot/index.cjs.map +1 -0
- package/dist/snapshot/index.d.cts +1 -0
- package/dist/snapshot/index.d.ts +1 -0
- package/dist/snapshot/index.mjs +438 -0
- package/dist/snapshot/index.mjs.map +1 -0
- package/dist/trainer/index.cjs +395 -0
- package/dist/trainer/index.cjs.map +1 -0
- package/dist/trainer/index.d.cts +1 -0
- package/dist/trainer/index.d.ts +1 -0
- package/dist/trainer/index.mjs +372 -0
- package/dist/trainer/index.mjs.map +1 -0
- package/dist/xcode/index.cjs +188 -0
- package/dist/xcode/index.cjs.map +1 -0
- package/dist/xcode/index.d.cts +1 -0
- package/dist/xcode/index.d.ts +1 -0
- package/dist/xcode/index.mjs +176 -0
- package/dist/xcode/index.mjs.map +1 -0
- package/package.json +223 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { execFile } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import { existsSync, writeFileSync, readFileSync, readdirSync } from 'fs';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import * as plist from 'plist';
|
|
6
|
+
|
|
7
|
+
// src/trainer/index.ts
|
|
8
|
+
|
|
9
|
+
// src/xcode/platform.ts
|
|
10
|
+
function requireMacOS(feature) {
|
|
11
|
+
if (process.platform !== "darwin") {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`${feature} requires macOS. This operation uses Xcode command-line tools which are not available on ${process.platform}.`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
var execFileAsync = promisify(execFile);
|
|
18
|
+
async function parseTestResults(options) {
|
|
19
|
+
const inputPath = options.path ?? findTestResults();
|
|
20
|
+
const outputDir = options.outputDirectory ?? process.cwd();
|
|
21
|
+
const outputFilename = options.outputFilename ?? "report.junit";
|
|
22
|
+
const junitPath = join(outputDir, outputFilename);
|
|
23
|
+
if (!existsSync(inputPath)) {
|
|
24
|
+
throw new Error("Test results not found at: " + inputPath);
|
|
25
|
+
}
|
|
26
|
+
let testSuites;
|
|
27
|
+
if (inputPath.endsWith(".xcresult")) {
|
|
28
|
+
requireMacOS("xcresult parsing");
|
|
29
|
+
testSuites = await parseXCResult(inputPath);
|
|
30
|
+
} else if (inputPath.endsWith(".plist")) {
|
|
31
|
+
testSuites = parsePlistResults(inputPath);
|
|
32
|
+
} else {
|
|
33
|
+
const stat = await import('fs').then(
|
|
34
|
+
(fs) => fs.statSync(inputPath)
|
|
35
|
+
);
|
|
36
|
+
if (stat.isDirectory()) {
|
|
37
|
+
requireMacOS("xcresult parsing");
|
|
38
|
+
testSuites = await parseXCResult(inputPath);
|
|
39
|
+
} else {
|
|
40
|
+
testSuites = parsePlistResults(inputPath);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
let totalTests = 0;
|
|
44
|
+
let failures = 0;
|
|
45
|
+
let skipped = 0;
|
|
46
|
+
for (const suite of testSuites) {
|
|
47
|
+
totalTests += suite.tests;
|
|
48
|
+
failures += suite.failures;
|
|
49
|
+
for (const tc of suite.testCases) {
|
|
50
|
+
if (tc.time === 0 && tc.failure === null) ;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
for (const suite of testSuites) {
|
|
54
|
+
for (const tc of suite.testCases) {
|
|
55
|
+
if (tc.failure === "skipped" || tc.failure === "disabled") {
|
|
56
|
+
skipped++;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const { numberOfRetries, numberOfTestsExcludingRetries, numberOfFailuresExcludingRetries } = computeRetryStats(testSuites);
|
|
61
|
+
const junitXml = generateJUnitXml(testSuites);
|
|
62
|
+
writeFileSync(junitPath, junitXml, "utf-8");
|
|
63
|
+
const success = numberOfFailuresExcludingRetries === 0;
|
|
64
|
+
if (!success && options.failBuildOnError) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
"Tests failed: " + numberOfFailuresExcludingRetries + " failure(s) out of " + numberOfTestsExcludingRetries + " test(s)"
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
success,
|
|
71
|
+
totalTests,
|
|
72
|
+
failures,
|
|
73
|
+
skipped,
|
|
74
|
+
retries: numberOfRetries,
|
|
75
|
+
numberOfRetries,
|
|
76
|
+
numberOfTestsExcludingRetries,
|
|
77
|
+
numberOfFailuresExcludingRetries,
|
|
78
|
+
junitPath,
|
|
79
|
+
testSuites
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function computeRetryStats(testSuites) {
|
|
83
|
+
let numberOfRetries = 0;
|
|
84
|
+
let uniqueTests = 0;
|
|
85
|
+
let uniqueFailures = 0;
|
|
86
|
+
for (const suite of testSuites) {
|
|
87
|
+
const attemptsByKey = /* @__PURE__ */ new Map();
|
|
88
|
+
for (const tc of suite.testCases) {
|
|
89
|
+
const key = `${tc.className}::${tc.name}`;
|
|
90
|
+
const arr = attemptsByKey.get(key) ?? [];
|
|
91
|
+
arr.push(tc);
|
|
92
|
+
attemptsByKey.set(key, arr);
|
|
93
|
+
}
|
|
94
|
+
for (const attempts of attemptsByKey.values()) {
|
|
95
|
+
uniqueTests++;
|
|
96
|
+
numberOfRetries += Math.max(0, attempts.length - 1);
|
|
97
|
+
const last = attempts[attempts.length - 1];
|
|
98
|
+
if (last.failure !== null && last.failure !== "skipped" && last.failure !== "disabled") {
|
|
99
|
+
uniqueFailures++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
numberOfRetries,
|
|
105
|
+
numberOfTestsExcludingRetries: uniqueTests,
|
|
106
|
+
numberOfFailuresExcludingRetries: uniqueFailures
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
async function parseXCResult(xcresultPath) {
|
|
110
|
+
const rootJson = await xcresulttoolGet(xcresultPath);
|
|
111
|
+
const root = JSON.parse(rootJson);
|
|
112
|
+
const actions = root.actions?._values ?? [];
|
|
113
|
+
const testSuites = [];
|
|
114
|
+
for (const action of actions) {
|
|
115
|
+
const testsRefId = action.actionResult?.testsRef?.id?._value;
|
|
116
|
+
if (!testsRefId) continue;
|
|
117
|
+
const testsJson = await xcresulttoolGet(xcresultPath, testsRefId);
|
|
118
|
+
const testsRoot = JSON.parse(testsJson);
|
|
119
|
+
const topNodes = testsRoot.subtests?._values ?? [];
|
|
120
|
+
for (const node of topNodes) {
|
|
121
|
+
extractTestSuites(node, testSuites);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return testSuites;
|
|
125
|
+
}
|
|
126
|
+
function extractTestSuites(node, suites, parentName) {
|
|
127
|
+
const name = node.name?._value ?? node.identifier?._value ?? "Unknown";
|
|
128
|
+
const subtests = node.subtests?._values;
|
|
129
|
+
if (subtests && subtests.length > 0) {
|
|
130
|
+
const hasLeafTests = subtests.some(
|
|
131
|
+
(s) => !s.subtests || !s.subtests._values || s.subtests._values.length === 0
|
|
132
|
+
);
|
|
133
|
+
if (hasLeafTests) {
|
|
134
|
+
const testCases = [];
|
|
135
|
+
for (const sub of subtests) {
|
|
136
|
+
if (sub.subtests?._values && sub.subtests._values.length > 0) {
|
|
137
|
+
extractTestSuites(sub, suites, name);
|
|
138
|
+
} else {
|
|
139
|
+
testCases.push(extractTestCase(sub, name));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (testCases.length > 0) {
|
|
143
|
+
const failureCount = testCases.filter((tc) => tc.failure !== null && tc.failure !== "skipped").length;
|
|
144
|
+
const totalTime = testCases.reduce((acc, tc) => acc + tc.time, 0);
|
|
145
|
+
suites.push({
|
|
146
|
+
name: parentName ? parentName + "." + name : name,
|
|
147
|
+
tests: testCases.length,
|
|
148
|
+
failures: failureCount,
|
|
149
|
+
time: totalTime,
|
|
150
|
+
testCases
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
for (const sub of subtests) {
|
|
155
|
+
extractTestSuites(sub, suites, name);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function extractTestCase(node, className) {
|
|
161
|
+
const name = node.name?._value ?? node.identifier?._value ?? "Unknown";
|
|
162
|
+
const duration = node.duration?._value ? parseFloat(node.duration._value) : 0;
|
|
163
|
+
const status = node.testStatus?._value ?? "";
|
|
164
|
+
let failure = null;
|
|
165
|
+
if (status === "Failure") {
|
|
166
|
+
const summaries = node.summaries?._values ?? [];
|
|
167
|
+
const failureMessages = [];
|
|
168
|
+
for (const summary of summaries) {
|
|
169
|
+
const failureSummaries = summary.failureSummaries?._values ?? [];
|
|
170
|
+
for (const fs of failureSummaries) {
|
|
171
|
+
const msg = fs.message?._value ?? "Test failed";
|
|
172
|
+
const file = fs.fileName?._value ?? "";
|
|
173
|
+
const line = fs.lineNumber?._value ?? "";
|
|
174
|
+
if (file) {
|
|
175
|
+
failureMessages.push(msg + " (" + file + ":" + line + ")");
|
|
176
|
+
} else {
|
|
177
|
+
failureMessages.push(msg);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
failure = failureMessages.length > 0 ? failureMessages.join("\n") : "Test failed";
|
|
182
|
+
} else if (status === "Skipped") {
|
|
183
|
+
failure = "skipped";
|
|
184
|
+
}
|
|
185
|
+
return { name, className, time: duration, failure };
|
|
186
|
+
}
|
|
187
|
+
async function xcresulttoolGet(xcresultPath, id) {
|
|
188
|
+
const args = [
|
|
189
|
+
"xcresulttool",
|
|
190
|
+
"get",
|
|
191
|
+
"--format",
|
|
192
|
+
"json",
|
|
193
|
+
"--path",
|
|
194
|
+
xcresultPath
|
|
195
|
+
];
|
|
196
|
+
if (id) {
|
|
197
|
+
args.push("--id", id);
|
|
198
|
+
}
|
|
199
|
+
const { stdout } = await execFileAsync("xcrun", args, {
|
|
200
|
+
maxBuffer: 50 * 1024 * 1024
|
|
201
|
+
// 50MB buffer for large results
|
|
202
|
+
});
|
|
203
|
+
return stdout;
|
|
204
|
+
}
|
|
205
|
+
function parsePlistResults(plistPath) {
|
|
206
|
+
const raw = readFileSync(plistPath, "utf-8");
|
|
207
|
+
const parsed = plist.parse(raw);
|
|
208
|
+
const suites = [];
|
|
209
|
+
if (Array.isArray(parsed)) {
|
|
210
|
+
for (const entry of parsed) {
|
|
211
|
+
extractPlistSuites(entry, suites);
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
extractPlistSuites(parsed, suites);
|
|
215
|
+
}
|
|
216
|
+
return suites;
|
|
217
|
+
}
|
|
218
|
+
function extractPlistSuites(node, suites, parentName) {
|
|
219
|
+
const name = node.TestName ?? node.TestIdentifier ?? "Unknown";
|
|
220
|
+
const subtests = node.Subtests;
|
|
221
|
+
if (subtests && subtests.length > 0) {
|
|
222
|
+
const hasLeafTests = subtests.some(
|
|
223
|
+
(s) => !s.Subtests || s.Subtests.length === 0
|
|
224
|
+
);
|
|
225
|
+
if (hasLeafTests) {
|
|
226
|
+
const testCases = [];
|
|
227
|
+
for (const sub of subtests) {
|
|
228
|
+
if (sub.Subtests && sub.Subtests.length > 0) {
|
|
229
|
+
extractPlistSuites(sub, suites, name);
|
|
230
|
+
} else {
|
|
231
|
+
testCases.push(extractPlistTestCase(sub, name));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (testCases.length > 0) {
|
|
235
|
+
const failureCount = testCases.filter(
|
|
236
|
+
(tc) => tc.failure !== null && tc.failure !== "skipped"
|
|
237
|
+
).length;
|
|
238
|
+
const totalTime = testCases.reduce((acc, tc) => acc + tc.time, 0);
|
|
239
|
+
suites.push({
|
|
240
|
+
name: parentName ? parentName + "." + name : name,
|
|
241
|
+
tests: testCases.length,
|
|
242
|
+
failures: failureCount,
|
|
243
|
+
time: totalTime,
|
|
244
|
+
testCases
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
for (const sub of subtests) {
|
|
249
|
+
extractPlistSuites(sub, suites, name);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function extractPlistTestCase(node, className) {
|
|
255
|
+
const name = node.TestName ?? node.TestIdentifier ?? "Unknown";
|
|
256
|
+
const duration = node.Duration ?? 0;
|
|
257
|
+
const status = node.TestStatus ?? "";
|
|
258
|
+
let failure = null;
|
|
259
|
+
if (status === "Failure") {
|
|
260
|
+
const summaries = node.FailureSummaries ?? [];
|
|
261
|
+
const messages = summaries.map((s) => {
|
|
262
|
+
const msg = s.Message ?? "Test failed";
|
|
263
|
+
const file = s.FileName ?? "";
|
|
264
|
+
const line = s.LineNumber ?? "";
|
|
265
|
+
return file ? msg + " (" + file + ":" + line + ")" : msg;
|
|
266
|
+
});
|
|
267
|
+
failure = messages.length > 0 ? messages.join("\n") : "Test failed";
|
|
268
|
+
} else if (status === "Skipped") {
|
|
269
|
+
failure = "skipped";
|
|
270
|
+
}
|
|
271
|
+
return { name, className, time: duration, failure };
|
|
272
|
+
}
|
|
273
|
+
function generateJUnitXml(testSuites) {
|
|
274
|
+
const totalTests = testSuites.reduce((acc, s) => acc + s.tests, 0);
|
|
275
|
+
const totalFailures = testSuites.reduce((acc, s) => acc + s.failures, 0);
|
|
276
|
+
const totalTime = testSuites.reduce((acc, s) => acc + s.time, 0);
|
|
277
|
+
const lines = [];
|
|
278
|
+
lines.push('<?xml version="1.0" encoding="UTF-8"?>');
|
|
279
|
+
lines.push(
|
|
280
|
+
'<testsuites tests="' + totalTests + '" failures="' + totalFailures + '" time="' + totalTime.toFixed(3) + '">'
|
|
281
|
+
);
|
|
282
|
+
for (const suite of testSuites) {
|
|
283
|
+
lines.push(
|
|
284
|
+
' <testsuite name="' + escapeXmlAttr(suite.name) + '" tests="' + suite.tests + '" failures="' + suite.failures + '" time="' + suite.time.toFixed(3) + '">'
|
|
285
|
+
);
|
|
286
|
+
for (const tc of suite.testCases) {
|
|
287
|
+
if (tc.failure === "skipped") {
|
|
288
|
+
lines.push(
|
|
289
|
+
' <testcase classname="' + escapeXmlAttr(tc.className) + '" name="' + escapeXmlAttr(tc.name) + '" time="' + tc.time.toFixed(3) + '">'
|
|
290
|
+
);
|
|
291
|
+
lines.push(" <skipped/>");
|
|
292
|
+
lines.push(" </testcase>");
|
|
293
|
+
} else if (tc.failure !== null) {
|
|
294
|
+
lines.push(
|
|
295
|
+
' <testcase classname="' + escapeXmlAttr(tc.className) + '" name="' + escapeXmlAttr(tc.name) + '" time="' + tc.time.toFixed(3) + '">'
|
|
296
|
+
);
|
|
297
|
+
lines.push(
|
|
298
|
+
' <failure message="' + escapeXmlAttr(tc.failure) + '">'
|
|
299
|
+
);
|
|
300
|
+
lines.push(" " + escapeXmlContent(tc.failure));
|
|
301
|
+
lines.push(" </failure>");
|
|
302
|
+
lines.push(" </testcase>");
|
|
303
|
+
} else {
|
|
304
|
+
lines.push(
|
|
305
|
+
' <testcase classname="' + escapeXmlAttr(tc.className) + '" name="' + escapeXmlAttr(tc.name) + '" time="' + tc.time.toFixed(3) + '"/>'
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
lines.push(" </testsuite>");
|
|
310
|
+
}
|
|
311
|
+
lines.push("</testsuites>");
|
|
312
|
+
return lines.join("\n") + "\n";
|
|
313
|
+
}
|
|
314
|
+
function findTestResults() {
|
|
315
|
+
const cwd = process.cwd();
|
|
316
|
+
const derivedDataPath = join(
|
|
317
|
+
process.env.HOME ?? "/tmp",
|
|
318
|
+
"Library",
|
|
319
|
+
"Developer",
|
|
320
|
+
"Xcode",
|
|
321
|
+
"DerivedData"
|
|
322
|
+
);
|
|
323
|
+
if (existsSync(derivedDataPath)) {
|
|
324
|
+
try {
|
|
325
|
+
const projects = readdirSync(derivedDataPath);
|
|
326
|
+
for (const project of projects) {
|
|
327
|
+
const logsDir = join(
|
|
328
|
+
derivedDataPath,
|
|
329
|
+
project,
|
|
330
|
+
"Logs",
|
|
331
|
+
"Test"
|
|
332
|
+
);
|
|
333
|
+
if (!existsSync(logsDir)) continue;
|
|
334
|
+
const entries = readdirSync(logsDir);
|
|
335
|
+
const xcresults = entries.filter((e) => e.endsWith(".xcresult")).map((e) => ({
|
|
336
|
+
name: e,
|
|
337
|
+
path: join(logsDir, e)
|
|
338
|
+
}));
|
|
339
|
+
if (xcresults.length > 0) {
|
|
340
|
+
return xcresults[xcresults.length - 1].path;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} catch {
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const plistFiles = safeReaddir(cwd).filter(
|
|
347
|
+
(f) => f.endsWith(".plist") && f.includes("Test")
|
|
348
|
+
);
|
|
349
|
+
if (plistFiles.length > 0) {
|
|
350
|
+
return join(cwd, plistFiles[0]);
|
|
351
|
+
}
|
|
352
|
+
throw new Error(
|
|
353
|
+
"Could not auto-detect test results. Please specify the path option."
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
function safeReaddir(dir) {
|
|
357
|
+
try {
|
|
358
|
+
return readdirSync(dir);
|
|
359
|
+
} catch {
|
|
360
|
+
return [];
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function escapeXmlAttr(str) {
|
|
364
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
365
|
+
}
|
|
366
|
+
function escapeXmlContent(str) {
|
|
367
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export { generateJUnitXml, parseTestResults };
|
|
371
|
+
//# sourceMappingURL=index.mjs.map
|
|
372
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/xcode/platform.ts","../../src/trainer/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["export function requireMacOS(feature: string): void {\n if (process.platform !== \"darwin\") {\n throw new Error(\n `${feature} requires macOS. This operation uses Xcode command-line tools ` +\n `which are not available on ${process.platform}.`\n );\n }\n}\n\nexport function isMacOS(): boolean {\n return process.platform === \"darwin\";\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { existsSync, readFileSync, writeFileSync, readdirSync } from \"node:fs\";\nimport { join, extname, basename } from \"node:path\";\nimport { requireMacOS } from \"../xcode/platform.js\";\nimport * as plist from \"plist\";\n\nconst execFileAsync = promisify(execFile);\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface TrainerOptions {\n path?: string;\n outputDirectory?: string;\n outputFilename?: string;\n failBuildOnError?: boolean;\n xcpretty?: boolean;\n}\n\nexport interface JUnitResult {\n success: boolean;\n totalTests: number;\n failures: number;\n skipped: number;\n /** Total number of retried test attempts across all suites. */\n retries: number;\n numberOfRetries: number;\n /** Count of unique tests after collapsing retries. */\n numberOfTestsExcludingRetries: number;\n /** Count of unique failing tests after collapsing retries (last attempt wins). */\n numberOfFailuresExcludingRetries: number;\n junitPath: string;\n testSuites: TestSuite[];\n}\n\nexport interface TestSuite {\n name: string;\n tests: number;\n failures: number;\n time: number;\n testCases: TestCase[];\n}\n\nexport interface TestCase {\n name: string;\n className: string;\n time: number;\n failure: string | null;\n}\n\n// ── xcresulttool JSON shapes ───────────────────────────────────────────────\n\ninterface XCResultValue {\n _value?: string;\n _values?: XCResultValue[];\n _type?: { _name?: string };\n}\n\ninterface XCResultActionRecord {\n actionResult?: {\n testsRef?: { id?: XCResultValue };\n };\n}\n\ninterface XCResultActionsInvocation {\n actions?: {\n _values?: XCResultActionRecord[];\n };\n}\n\ninterface XCResultTestNode {\n name?: XCResultValue;\n identifier?: XCResultValue;\n duration?: XCResultValue;\n testStatus?: XCResultValue;\n subtests?: { _values?: XCResultTestNode[] };\n summaries?: {\n _values?: Array<{\n failureSummaries?: {\n _values?: Array<{\n message?: XCResultValue;\n fileName?: XCResultValue;\n lineNumber?: XCResultValue;\n }>;\n };\n }>;\n };\n}\n\ninterface XCResultTestPlan {\n subtests?: { _values?: XCResultTestNode[] };\n name?: XCResultValue;\n}\n\n// ── plist test results shape ───────────────────────────────────────────────\n\ninterface PlistTestSuite {\n TestName?: string;\n TestIdentifier?: string;\n Duration?: number;\n TestStatus?: string;\n Subtests?: PlistTestSuite[];\n FailureSummaries?: Array<{\n Message?: string;\n FileName?: string;\n LineNumber?: number;\n }>;\n}\n\n// ── Main Entry Point ───────────────────────────────────────────────────────\n\n/**\n * Parse test results from either .xcresult bundles or .plist files,\n * generate JUnit XML, and return structured results.\n *\n * Mirrors Fastlane trainer's test_parser.rb logic.\n */\nexport async function parseTestResults(\n options: TrainerOptions,\n): Promise<JUnitResult> {\n const inputPath = options.path ?? findTestResults();\n const outputDir = options.outputDirectory ?? process.cwd();\n const outputFilename = options.outputFilename ?? \"report.junit\";\n const junitPath = join(outputDir, outputFilename);\n\n if (!existsSync(inputPath)) {\n throw new Error(\"Test results not found at: \" + inputPath);\n }\n\n let testSuites: TestSuite[];\n\n if (inputPath.endsWith(\".xcresult\")) {\n requireMacOS(\"xcresult parsing\");\n testSuites = await parseXCResult(inputPath);\n } else if (inputPath.endsWith(\".plist\")) {\n testSuites = parsePlistResults(inputPath);\n } else {\n // Try to detect: if it's a directory, it might be an xcresult bundle\n const stat = await import(\"node:fs\").then((fs) =>\n fs.statSync(inputPath),\n );\n if (stat.isDirectory()) {\n requireMacOS(\"xcresult parsing\");\n testSuites = await parseXCResult(inputPath);\n } else {\n // Attempt plist parsing as fallback\n testSuites = parsePlistResults(inputPath);\n }\n }\n\n // Compute aggregated stats\n let totalTests = 0;\n let failures = 0;\n let skipped = 0;\n\n for (const suite of testSuites) {\n totalTests += suite.tests;\n failures += suite.failures;\n for (const tc of suite.testCases) {\n if (tc.time === 0 && tc.failure === null) {\n // Heuristic: zero-duration passing tests are often skipped\n }\n }\n }\n\n // Count skipped tests (tests with name containing \"skipped\" or with specific markers)\n for (const suite of testSuites) {\n for (const tc of suite.testCases) {\n if (tc.failure === \"skipped\" || tc.failure === \"disabled\") {\n skipped++;\n }\n }\n }\n\n // Retry detection: xcresult emits one TestCase per attempt of the same\n // (className, name). The final attempt wins for pass/fail. Count the\n // retry attempts that preceded it and compute retry-adjusted totals.\n const { numberOfRetries, numberOfTestsExcludingRetries, numberOfFailuresExcludingRetries } =\n computeRetryStats(testSuites);\n\n // Generate JUnit XML\n const junitXml = generateJUnitXml(testSuites);\n writeFileSync(junitPath, junitXml, \"utf-8\");\n\n const success = numberOfFailuresExcludingRetries === 0;\n\n if (!success && options.failBuildOnError) {\n throw new Error(\n \"Tests failed: \" + numberOfFailuresExcludingRetries + \" failure(s) out of \" +\n numberOfTestsExcludingRetries + \" test(s)\",\n );\n }\n\n return {\n success,\n totalTests,\n failures,\n skipped,\n retries: numberOfRetries,\n numberOfRetries,\n numberOfTestsExcludingRetries,\n numberOfFailuresExcludingRetries,\n junitPath,\n testSuites,\n };\n}\n\nfunction computeRetryStats(testSuites: TestSuite[]): {\n numberOfRetries: number;\n numberOfTestsExcludingRetries: number;\n numberOfFailuresExcludingRetries: number;\n} {\n let numberOfRetries = 0;\n let uniqueTests = 0;\n let uniqueFailures = 0;\n\n for (const suite of testSuites) {\n const attemptsByKey = new Map<string, TestCase[]>();\n for (const tc of suite.testCases) {\n const key = `${tc.className}::${tc.name}`;\n const arr = attemptsByKey.get(key) ?? [];\n arr.push(tc);\n attemptsByKey.set(key, arr);\n }\n for (const attempts of attemptsByKey.values()) {\n uniqueTests++;\n numberOfRetries += Math.max(0, attempts.length - 1);\n const last = attempts[attempts.length - 1];\n // Only real failures count — not \"skipped\" markers.\n if (last.failure !== null && last.failure !== \"skipped\" && last.failure !== \"disabled\") {\n uniqueFailures++;\n }\n }\n }\n\n return {\n numberOfRetries,\n numberOfTestsExcludingRetries: uniqueTests,\n numberOfFailuresExcludingRetries: uniqueFailures,\n };\n}\n\n// ── xcresult Parsing ───────────────────────────────────────────────────────\n\nasync function parseXCResult(xcresultPath: string): Promise<TestSuite[]> {\n // Step 1: Get the top-level invocation record\n const rootJson = await xcresulttoolGet(xcresultPath);\n const root = JSON.parse(rootJson) as XCResultActionsInvocation;\n\n const actions = root.actions?._values ?? [];\n const testSuites: TestSuite[] = [];\n\n for (const action of actions) {\n const testsRefId = action.actionResult?.testsRef?.id?._value;\n if (!testsRefId) continue;\n\n // Step 2: Get the test plan run summaries using the ref id\n const testsJson = await xcresulttoolGet(xcresultPath, testsRefId);\n const testsRoot = JSON.parse(testsJson) as XCResultTestPlan;\n\n // Step 3: Walk the test tree to extract suites and cases\n const topNodes = testsRoot.subtests?._values ?? [];\n for (const node of topNodes) {\n extractTestSuites(node, testSuites);\n }\n }\n\n return testSuites;\n}\n\nfunction extractTestSuites(\n node: XCResultTestNode,\n suites: TestSuite[],\n parentName?: string,\n): void {\n const name = node.name?._value ?? node.identifier?._value ?? \"Unknown\";\n const subtests = node.subtests?._values;\n\n if (subtests && subtests.length > 0) {\n // Check if children are leaf test cases or more groups\n const hasLeafTests = subtests.some(\n (s) => !s.subtests || !s.subtests._values || s.subtests._values.length === 0,\n );\n\n if (hasLeafTests) {\n // This node is a test suite containing test cases\n const testCases: TestCase[] = [];\n\n for (const sub of subtests) {\n if (sub.subtests?._values && sub.subtests._values.length > 0) {\n // Nested group -- recurse\n extractTestSuites(sub, suites, name);\n } else {\n // Leaf test case\n testCases.push(extractTestCase(sub, name));\n }\n }\n\n if (testCases.length > 0) {\n const failureCount = testCases.filter((tc) => tc.failure !== null && tc.failure !== \"skipped\").length;\n const totalTime = testCases.reduce((acc, tc) => acc + tc.time, 0);\n\n suites.push({\n name: parentName ? parentName + \".\" + name : name,\n tests: testCases.length,\n failures: failureCount,\n time: totalTime,\n testCases,\n });\n }\n } else {\n // All children are groups -- recurse deeper\n for (const sub of subtests) {\n extractTestSuites(sub, suites, name);\n }\n }\n }\n}\n\nfunction extractTestCase(\n node: XCResultTestNode,\n className: string,\n): TestCase {\n const name = node.name?._value ?? node.identifier?._value ?? \"Unknown\";\n const duration = node.duration?._value ? parseFloat(node.duration._value) : 0;\n const status = node.testStatus?._value ?? \"\";\n\n let failure: string | null = null;\n\n if (status === \"Failure\") {\n // Extract failure message from summaries\n const summaries = node.summaries?._values ?? [];\n const failureMessages: string[] = [];\n\n for (const summary of summaries) {\n const failureSummaries = summary.failureSummaries?._values ?? [];\n for (const fs of failureSummaries) {\n const msg = fs.message?._value ?? \"Test failed\";\n const file = fs.fileName?._value ?? \"\";\n const line = fs.lineNumber?._value ?? \"\";\n if (file) {\n failureMessages.push(msg + \" (\" + file + \":\" + line + \")\");\n } else {\n failureMessages.push(msg);\n }\n }\n }\n\n failure = failureMessages.length > 0\n ? failureMessages.join(\"\\n\")\n : \"Test failed\";\n } else if (status === \"Skipped\") {\n failure = \"skipped\";\n }\n\n return { name, className, time: duration, failure };\n}\n\nasync function xcresulttoolGet(\n xcresultPath: string,\n id?: string,\n): Promise<string> {\n const args = [\n \"xcresulttool\",\n \"get\",\n \"--format\",\n \"json\",\n \"--path\",\n xcresultPath,\n ];\n\n if (id) {\n args.push(\"--id\", id);\n }\n\n const { stdout } = await execFileAsync(\"xcrun\", args, {\n maxBuffer: 50 * 1024 * 1024, // 50MB buffer for large results\n });\n\n return stdout;\n}\n\n// ── Plist Parsing ──────────────────────────────────────────────────────────\n\nfunction parsePlistResults(plistPath: string): TestSuite[] {\n const raw = readFileSync(plistPath, \"utf-8\");\n const parsed = plist.parse(raw);\n\n // The plist can be either a single test suite dict or an array\n const suites: TestSuite[] = [];\n\n if (Array.isArray(parsed)) {\n for (const entry of parsed) {\n extractPlistSuites(entry as PlistTestSuite, suites);\n }\n } else {\n extractPlistSuites(parsed as PlistTestSuite, suites);\n }\n\n return suites;\n}\n\nfunction extractPlistSuites(\n node: PlistTestSuite,\n suites: TestSuite[],\n parentName?: string,\n): void {\n const name =\n node.TestName ?? node.TestIdentifier ?? \"Unknown\";\n const subtests = node.Subtests;\n\n if (subtests && subtests.length > 0) {\n const hasLeafTests = subtests.some(\n (s) => !s.Subtests || s.Subtests.length === 0,\n );\n\n if (hasLeafTests) {\n const testCases: TestCase[] = [];\n\n for (const sub of subtests) {\n if (sub.Subtests && sub.Subtests.length > 0) {\n extractPlistSuites(sub, suites, name);\n } else {\n testCases.push(extractPlistTestCase(sub, name));\n }\n }\n\n if (testCases.length > 0) {\n const failureCount = testCases.filter(\n (tc) => tc.failure !== null && tc.failure !== \"skipped\",\n ).length;\n const totalTime = testCases.reduce((acc, tc) => acc + tc.time, 0);\n\n suites.push({\n name: parentName ? parentName + \".\" + name : name,\n tests: testCases.length,\n failures: failureCount,\n time: totalTime,\n testCases,\n });\n }\n } else {\n for (const sub of subtests) {\n extractPlistSuites(sub, suites, name);\n }\n }\n }\n}\n\nfunction extractPlistTestCase(\n node: PlistTestSuite,\n className: string,\n): TestCase {\n const name =\n node.TestName ?? node.TestIdentifier ?? \"Unknown\";\n const duration = node.Duration ?? 0;\n const status = node.TestStatus ?? \"\";\n\n let failure: string | null = null;\n\n if (status === \"Failure\") {\n const summaries = node.FailureSummaries ?? [];\n const messages = summaries.map((s) => {\n const msg = s.Message ?? \"Test failed\";\n const file = s.FileName ?? \"\";\n const line = s.LineNumber ?? \"\";\n return file ? msg + \" (\" + file + \":\" + line + \")\" : msg;\n });\n failure = messages.length > 0 ? messages.join(\"\\n\") : \"Test failed\";\n } else if (status === \"Skipped\") {\n failure = \"skipped\";\n }\n\n return { name, className, time: duration, failure };\n}\n\n// ── JUnit XML Generation ───────────────────────────────────────────────────\n\n/**\n * Generate a JUnit XML string from test suites.\n * Mirrors Fastlane trainer's junit_generator.rb output format.\n */\nexport function generateJUnitXml(testSuites: TestSuite[]): string {\n const totalTests = testSuites.reduce((acc, s) => acc + s.tests, 0);\n const totalFailures = testSuites.reduce((acc, s) => acc + s.failures, 0);\n const totalTime = testSuites.reduce((acc, s) => acc + s.time, 0);\n\n const lines: string[] = [];\n lines.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>');\n lines.push(\n '<testsuites tests=\"' + totalTests +\n '\" failures=\"' + totalFailures +\n '\" time=\"' + totalTime.toFixed(3) + '\">',\n );\n\n for (const suite of testSuites) {\n lines.push(\n ' <testsuite name=\"' + escapeXmlAttr(suite.name) +\n '\" tests=\"' + suite.tests +\n '\" failures=\"' + suite.failures +\n '\" time=\"' + suite.time.toFixed(3) + '\">',\n );\n\n for (const tc of suite.testCases) {\n if (tc.failure === \"skipped\") {\n lines.push(\n ' <testcase classname=\"' + escapeXmlAttr(tc.className) +\n '\" name=\"' + escapeXmlAttr(tc.name) +\n '\" time=\"' + tc.time.toFixed(3) + '\">',\n );\n lines.push(\" <skipped/>\");\n lines.push(\" </testcase>\");\n } else if (tc.failure !== null) {\n lines.push(\n ' <testcase classname=\"' + escapeXmlAttr(tc.className) +\n '\" name=\"' + escapeXmlAttr(tc.name) +\n '\" time=\"' + tc.time.toFixed(3) + '\">',\n );\n lines.push(\n ' <failure message=\"' + escapeXmlAttr(tc.failure) + '\">',\n );\n lines.push(\" \" + escapeXmlContent(tc.failure));\n lines.push(\" </failure>\");\n lines.push(\" </testcase>\");\n } else {\n lines.push(\n ' <testcase classname=\"' + escapeXmlAttr(tc.className) +\n '\" name=\"' + escapeXmlAttr(tc.name) +\n '\" time=\"' + tc.time.toFixed(3) + '\"/>',\n );\n }\n }\n\n lines.push(\" </testsuite>\");\n }\n\n lines.push(\"</testsuites>\");\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\n/**\n * Auto-detect test results path. Searches common locations.\n */\nfunction findTestResults(): string {\n const cwd = process.cwd();\n\n // Check for xcresult in common derived data locations\n const derivedDataPath = join(\n process.env.HOME ?? \"/tmp\",\n \"Library\",\n \"Developer\",\n \"Xcode\",\n \"DerivedData\",\n );\n\n if (existsSync(derivedDataPath)) {\n try {\n const projects = readdirSync(derivedDataPath);\n for (const project of projects) {\n const logsDir = join(\n derivedDataPath,\n project,\n \"Logs\",\n \"Test\",\n );\n if (!existsSync(logsDir)) continue;\n\n const entries = readdirSync(logsDir);\n const xcresults = entries\n .filter((e) => e.endsWith(\".xcresult\"))\n .map((e) => ({\n name: e,\n path: join(logsDir, e),\n }));\n\n if (xcresults.length > 0) {\n // Return the most recently modified one\n return xcresults[xcresults.length - 1].path;\n }\n }\n } catch {\n // Ignore errors scanning derived data\n }\n }\n\n // Check for plist in current directory\n const plistFiles = safeReaddir(cwd).filter(\n (f) => f.endsWith(\".plist\") && f.includes(\"Test\"),\n );\n if (plistFiles.length > 0) {\n return join(cwd, plistFiles[0]);\n }\n\n throw new Error(\n \"Could not auto-detect test results. Please specify the path option.\",\n );\n}\n\nfunction safeReaddir(dir: string): string[] {\n try {\n return readdirSync(dir);\n } catch {\n return [];\n }\n}\n\nfunction escapeXmlAttr(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\nfunction escapeXmlContent(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\n}\n"]}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var child_process = require('child_process');
|
|
4
|
+
var util = require('util');
|
|
5
|
+
var promises = require('fs/promises');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
var os = require('os');
|
|
8
|
+
var crypto = require('crypto');
|
|
9
|
+
|
|
10
|
+
// src/xcode/platform.ts
|
|
11
|
+
function requireMacOS(feature) {
|
|
12
|
+
if (process.platform !== "darwin") {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`${feature} requires macOS. This operation uses Xcode command-line tools which are not available on ${process.platform}.`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function isMacOS() {
|
|
19
|
+
return process.platform === "darwin";
|
|
20
|
+
}
|
|
21
|
+
var execFileAsync = util.promisify(child_process.execFile);
|
|
22
|
+
async function createKeychain(options) {
|
|
23
|
+
requireMacOS("keychain");
|
|
24
|
+
const password = options.password ?? crypto.randomBytes(16).toString("hex");
|
|
25
|
+
const keychainName = `${options.name}.keychain-db`;
|
|
26
|
+
const keychainPath = path.join(os.homedir(), "Library", "Keychains", keychainName);
|
|
27
|
+
await execFileAsync("security", ["create-keychain", "-p", password, keychainPath]);
|
|
28
|
+
await execFileAsync("security", ["set-keychain-settings", "-lut", "21600", keychainPath]);
|
|
29
|
+
await execFileAsync("security", ["unlock-keychain", "-p", password, keychainPath]);
|
|
30
|
+
const { stdout: currentList } = await execFileAsync("security", ["list-keychains", "-d", "user"]);
|
|
31
|
+
const keychains = currentList.split("\n").map((k) => k.trim().replace(/"/g, "")).filter(Boolean);
|
|
32
|
+
const searchList = [keychainPath, ...keychains];
|
|
33
|
+
if (!searchList.includes("/Library/Keychains/System.keychain")) {
|
|
34
|
+
searchList.push("/Library/Keychains/System.keychain");
|
|
35
|
+
}
|
|
36
|
+
await execFileAsync("security", ["list-keychains", "-d", "user", "-s", ...searchList]);
|
|
37
|
+
try {
|
|
38
|
+
const wwdrPath = "/tmp/AppleWWDRCAG3.cer";
|
|
39
|
+
await execFileAsync("curl", ["-sfo", wwdrPath, "https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer"]);
|
|
40
|
+
await execFileAsync("security", ["import", wwdrPath, "-k", keychainPath, "-T", "/usr/bin/codesign"]);
|
|
41
|
+
} catch {
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
path: keychainPath,
|
|
45
|
+
password,
|
|
46
|
+
cleanup: () => cleanupKeychain({ path: keychainPath, password, cleanup: async () => {
|
|
47
|
+
} })
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async function importCertificate(keychainPath, certPath, password) {
|
|
51
|
+
requireMacOS("keychain");
|
|
52
|
+
const isCer = certPath.endsWith(".cer") || certPath.endsWith(".pem");
|
|
53
|
+
const args = isCer ? ["import", certPath, "-k", keychainPath, "-T", "/usr/bin/codesign", "-T", "/usr/bin/security"] : ["import", certPath, "-k", keychainPath, "-P", password ?? "", "-T", "/usr/bin/codesign", "-T", "/usr/bin/security"];
|
|
54
|
+
try {
|
|
55
|
+
await execFileAsync("security", args);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
58
|
+
if (!msg.includes("already exists")) throw err;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function installProfile(profilePath, uuid) {
|
|
62
|
+
const profilesDir = path.join(os.homedir(), "Library", "MobileDevice", "Provisioning Profiles");
|
|
63
|
+
await promises.mkdir(profilesDir, { recursive: true });
|
|
64
|
+
const dest = path.join(profilesDir, `${uuid}.mobileprovision`);
|
|
65
|
+
await promises.copyFile(profilePath, dest);
|
|
66
|
+
return dest;
|
|
67
|
+
}
|
|
68
|
+
async function cleanupKeychain(handle) {
|
|
69
|
+
try {
|
|
70
|
+
await execFileAsync("security", ["delete-keychain", handle.path]);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
var execFileAsync2 = util.promisify(child_process.execFile);
|
|
75
|
+
async function updateSigningSettings(options) {
|
|
76
|
+
const pbxprojPath = `${options.projectPath}/project.pbxproj`;
|
|
77
|
+
let content = await promises.readFile(pbxprojPath, "utf-8");
|
|
78
|
+
const style = options.automaticSigning ? "Automatic" : "Manual";
|
|
79
|
+
content = replaceBuildSetting(content, "CODE_SIGN_STYLE", style);
|
|
80
|
+
content = replaceBuildSetting(content, "DEVELOPMENT_TEAM", options.teamId);
|
|
81
|
+
content = replaceBuildSetting(content, "CODE_SIGN_IDENTITY", options.codeSignIdentity);
|
|
82
|
+
if (options.profileName) {
|
|
83
|
+
content = replaceBuildSetting(content, "PROVISIONING_PROFILE_SPECIFIER", options.profileName);
|
|
84
|
+
}
|
|
85
|
+
if (options.profileUuid) {
|
|
86
|
+
content = replaceBuildSetting(content, "PROVISIONING_PROFILE", options.profileUuid);
|
|
87
|
+
}
|
|
88
|
+
await promises.writeFile(pbxprojPath, content);
|
|
89
|
+
}
|
|
90
|
+
async function setBuildNumber(projectPath, buildNumber) {
|
|
91
|
+
const pbxprojPath = `${projectPath}/project.pbxproj`;
|
|
92
|
+
let content = await promises.readFile(pbxprojPath, "utf-8");
|
|
93
|
+
content = replaceBuildSetting(content, "CURRENT_PROJECT_VERSION", buildNumber);
|
|
94
|
+
await promises.writeFile(pbxprojPath, content);
|
|
95
|
+
}
|
|
96
|
+
async function setVersionNumber(projectPath, version) {
|
|
97
|
+
const pbxprojPath = `${projectPath}/project.pbxproj`;
|
|
98
|
+
let content = await promises.readFile(pbxprojPath, "utf-8");
|
|
99
|
+
content = replaceBuildSetting(content, "MARKETING_VERSION", version);
|
|
100
|
+
await promises.writeFile(pbxprojPath, content);
|
|
101
|
+
}
|
|
102
|
+
async function readBuildSettings(options) {
|
|
103
|
+
requireMacOS("readBuildSettings");
|
|
104
|
+
const args = ["-showBuildSettings"];
|
|
105
|
+
if (options.workspacePath) args.push("-workspace", options.workspacePath);
|
|
106
|
+
else if (options.projectPath) args.push("-project", options.projectPath);
|
|
107
|
+
args.push("-scheme", options.scheme);
|
|
108
|
+
if (options.configuration) args.push("-configuration", options.configuration);
|
|
109
|
+
const { stdout } = await execFileAsync2("xcodebuild", args);
|
|
110
|
+
const settings = {};
|
|
111
|
+
for (const line of stdout.split("\n")) {
|
|
112
|
+
const match = line.match(/^\s+(\w+)\s*=\s*(.*)$/);
|
|
113
|
+
if (match) {
|
|
114
|
+
settings[match[1]] = match[2].trim();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return settings;
|
|
118
|
+
}
|
|
119
|
+
function replaceBuildSetting(content, key, value) {
|
|
120
|
+
const regex = new RegExp(`(?<![A-Z_])(${key})\\s*=\\s*("[^"]*"|[^;]*);`, "g");
|
|
121
|
+
const needsQuotes = /[^A-Za-z0-9]/.test(value) || /\s/.test(value);
|
|
122
|
+
const quoted = needsQuotes ? `"${value}"` : value;
|
|
123
|
+
return content.replace(regex, `$1 = ${quoted};`);
|
|
124
|
+
}
|
|
125
|
+
async function uploadWithAltool(options) {
|
|
126
|
+
requireMacOS("altool upload");
|
|
127
|
+
let keyDir;
|
|
128
|
+
if (options.apiPrivateKey && !options.apiKeyPath) {
|
|
129
|
+
keyDir = await promises.mkdtemp(path.join(os.tmpdir(), "fastnode-altool-"));
|
|
130
|
+
const keyPath = path.join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
|
|
131
|
+
await promises.writeFile(keyPath, options.apiPrivateKey);
|
|
132
|
+
}
|
|
133
|
+
const args = [
|
|
134
|
+
"altool",
|
|
135
|
+
"--upload-app",
|
|
136
|
+
"-f",
|
|
137
|
+
options.filePath,
|
|
138
|
+
"-t",
|
|
139
|
+
options.platform ?? "ios",
|
|
140
|
+
"--apiKey",
|
|
141
|
+
options.apiKeyId,
|
|
142
|
+
"--apiIssuer",
|
|
143
|
+
options.apiIssuerId
|
|
144
|
+
];
|
|
145
|
+
const env = { ...process.env };
|
|
146
|
+
if (keyDir) {
|
|
147
|
+
env.API_PRIVATE_KEYS_DIR = keyDir;
|
|
148
|
+
}
|
|
149
|
+
return new Promise((resolve) => {
|
|
150
|
+
const child = child_process.spawn("xcrun", args, {
|
|
151
|
+
env,
|
|
152
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
153
|
+
});
|
|
154
|
+
let output = "";
|
|
155
|
+
child.stdout.on("data", (data) => {
|
|
156
|
+
const chunk = data.toString();
|
|
157
|
+
output += chunk;
|
|
158
|
+
options.onOutput?.(chunk);
|
|
159
|
+
});
|
|
160
|
+
child.stderr.on("data", (data) => {
|
|
161
|
+
const chunk = data.toString();
|
|
162
|
+
output += chunk;
|
|
163
|
+
options.onOutput?.(chunk);
|
|
164
|
+
});
|
|
165
|
+
child.on("close", async (code) => {
|
|
166
|
+
if (keyDir) {
|
|
167
|
+
const keyPath = path.join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
|
|
168
|
+
await promises.unlink(keyPath).catch(() => {
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
resolve({ success: code === 0, output });
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
exports.cleanupKeychain = cleanupKeychain;
|
|
177
|
+
exports.createKeychain = createKeychain;
|
|
178
|
+
exports.importCertificate = importCertificate;
|
|
179
|
+
exports.installProfile = installProfile;
|
|
180
|
+
exports.isMacOS = isMacOS;
|
|
181
|
+
exports.readBuildSettings = readBuildSettings;
|
|
182
|
+
exports.requireMacOS = requireMacOS;
|
|
183
|
+
exports.setBuildNumber = setBuildNumber;
|
|
184
|
+
exports.setVersionNumber = setVersionNumber;
|
|
185
|
+
exports.updateSigningSettings = updateSigningSettings;
|
|
186
|
+
exports.uploadWithAltool = uploadWithAltool;
|
|
187
|
+
//# sourceMappingURL=index.cjs.map
|
|
188
|
+
//# sourceMappingURL=index.cjs.map
|