@invarn/cibuild 2.6.9 → 2.7.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/dist/cli.cjs +10 -10
- package/dist/src/commands/ios-scanner.d.ts +44 -8
- package/dist/src/commands/ios-scanner.d.ts.map +1 -1
- package/dist/src/commands/ios-scanner.js +52 -10
- package/dist/src/commands/ios-scheme-ranking.test.js +109 -2
- package/dist/src/yaml/env-resolver.d.ts.map +1 -1
- package/dist/src/yaml/env-resolver.js +14 -3
- package/dist/src/yaml/shell-assignments.d.ts +40 -0
- package/dist/src/yaml/shell-assignments.d.ts.map +1 -0
- package/dist/src/yaml/shell-assignments.js +77 -0
- package/dist/src/yaml/step-validator-self-assigned.test.js +63 -1
- package/dist/src/yaml/step-validator.d.ts +0 -32
- package/dist/src/yaml/step-validator.d.ts.map +1 -1
- package/dist/src/yaml/step-validator.js +1 -65
- package/package.json +1 -1
|
@@ -44,18 +44,54 @@ export interface IosScanResult {
|
|
|
44
44
|
*
|
|
45
45
|
* 1. named after the project or workspace — the commonest case, and
|
|
46
46
|
* unambiguous;
|
|
47
|
-
* 2. launches the product named after the project, and is not
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* 3. launches an `.app`, declares tests (the template runs them), and is not
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
* 4. launches an `.app` and is not named like a companion;
|
|
47
|
+
* 2. launches the product named after the project, and is not a companion.
|
|
48
|
+
* `thebaselab/codeapp` is why this outranks the next one: its `Code App`
|
|
49
|
+
* scheme launches `Code.app` and declares no tests at all, while `Code UI`
|
|
50
|
+
* has two test bundles and launches `CodeUI.app`;
|
|
51
|
+
* 3. launches an `.app`, declares tests (the template runs them), and is not a
|
|
52
|
+
* companion; among several, the iOS or Debug one, then the shortest;
|
|
53
|
+
* 4. launches an `.app` and is not a companion;
|
|
55
54
|
* 5. whatever order it came in.
|
|
56
55
|
*
|
|
57
56
|
* Every scheme stays in the list — this reorders, it does not filter, so a
|
|
58
57
|
* caller wanting the extension can still find it.
|
|
58
|
+
*
|
|
59
|
+
* **What "companion" has to mean, and why a name is not enough.**
|
|
60
|
+
* `mozilla-mobile/firefox-ios` broke tiers 1-4 without touching the directory
|
|
61
|
+
* order this function was written for. Its project is `Client.xcodeproj`, so
|
|
62
|
+
* `projectName` is `Client`. An app extension is launched *through its host
|
|
63
|
+
* app*, so the `Today` widget scheme's LaunchAction runnable is `Client.app` —
|
|
64
|
+
* identical to `Fennec`'s. Both landed in tier 1, neither name carries an `iOS`
|
|
65
|
+
* or `Debug` token, and the tie fell to name length:
|
|
66
|
+
*
|
|
67
|
+
* "Today" -> 5 characters <- won
|
|
68
|
+
* "Fennec" -> 6 characters
|
|
69
|
+
*
|
|
70
|
+
* The build then failed compiling `ActionExtension` rather than the browser
|
|
71
|
+
* (`bld_b1b38dda58f4111080415283`).
|
|
72
|
+
*
|
|
73
|
+
* Launching the app binary is therefore not sufficient, and the answer is not a
|
|
74
|
+
* longer name blocklist either — `Today` names nothing. Xcode states the fact
|
|
75
|
+
* outright: it writes `wasCreatedForAppExtension = "YES"` onto the `<Scheme>`
|
|
76
|
+
* element of a scheme it created for an extension target, so that is what is
|
|
77
|
+
* read.
|
|
78
|
+
*
|
|
79
|
+
* Measured over all 125 shared schemes in the thirty-app study's clones: 29
|
|
80
|
+
* carry the attribute, and **all 29 are extension schemes** — no false
|
|
81
|
+
* positive. It is also the only signal that would have worked. Two that were
|
|
82
|
+
* considered and are wrong on this same evidence:
|
|
83
|
+
*
|
|
84
|
+
* - *require an `.app` among the BuildAction entries.* 27 of the 29 extension
|
|
85
|
+
* schemes build the host app too — `Today` builds
|
|
86
|
+
* `["Today.appex", "Client.app"]` — so this demotes 2 of 29 and leaves
|
|
87
|
+
* firefox-ios picking `Today`.
|
|
88
|
+
* - *demote anything building an `.appex`.* `chenqi92/primuse`'s `Primuse`
|
|
89
|
+
* scheme is the real app scheme and builds two widget extensions alongside
|
|
90
|
+
* `Primuse.app`.
|
|
91
|
+
*
|
|
92
|
+
* The attribute is a sufficient signal, not a necessary one: a hand-written or
|
|
93
|
+
* pre-Xcode-10 scheme may omit it, and its absence changes nothing, so nothing
|
|
94
|
+
* that ranked before can regress on a scheme that does not carry it.
|
|
59
95
|
*/
|
|
60
96
|
export declare function rankSchemes(root: string, projectPath: string, schemes: string[]): string[];
|
|
61
97
|
/** The ranking, plus the sentence explaining its first entry. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ios-scanner.d.ts","sourceRoot":"","sources":["../../../src/commands/ios-scanner.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,SAAS,GAAG,gBAAgB,GAAG,WAAW,CAAC;AAE7F,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB;;;OAGG;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,YAAY,EAAE,OAAO,CAAC;IACtB,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iFAAiF;IACjF,eAAe,EAAE,MAAM,CAAC;CACzB;
|
|
1
|
+
{"version":3,"file":"ios-scanner.d.ts","sourceRoot":"","sources":["../../../src/commands/ios-scanner.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,SAAS,GAAG,gBAAgB,GAAG,WAAW,CAAC;AAE7F,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,MAAM,CAAC;AAEpD,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB;;;OAGG;IACH,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,uEAAuE;IACvE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,YAAY,EAAE,OAAO,CAAC;IACtB,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iFAAiF;IACjF,eAAe,EAAE,MAAM,CAAC;CACzB;AAuFD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,GAChB,MAAM,EAAE,CAEV;AAED,iEAAiE;AACjE,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EAAE,GAChB;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CA8CxC;AAmLD,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAgGhF;AAaD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAkDjE"}
|
|
@@ -60,9 +60,13 @@ function readSchemeFacts(root, name) {
|
|
|
60
60
|
if (!xml)
|
|
61
61
|
continue;
|
|
62
62
|
const launch = /<LaunchAction[\s\S]*?<\/LaunchAction>/.exec(xml)?.[0] ?? "";
|
|
63
|
+
// Attribute values carry no `>`, so the opening tag alone is a safe scope
|
|
64
|
+
// and no attribute deeper in the document can be mistaken for it.
|
|
65
|
+
const schemeTag = /<Scheme\b[^>]*>/.exec(xml)?.[0] ?? "";
|
|
63
66
|
return {
|
|
64
67
|
runnable: /BuildableName\s*=\s*"([^"]+)"/.exec(launch)?.[1],
|
|
65
68
|
testable: /<TestableReference/.test(xml),
|
|
69
|
+
appExtension: /wasCreatedForAppExtension\s*=\s*"YES"/.test(schemeTag),
|
|
66
70
|
};
|
|
67
71
|
}
|
|
68
72
|
return undefined;
|
|
@@ -84,18 +88,54 @@ function readSchemeFacts(root, name) {
|
|
|
84
88
|
*
|
|
85
89
|
* 1. named after the project or workspace — the commonest case, and
|
|
86
90
|
* unambiguous;
|
|
87
|
-
* 2. launches the product named after the project, and is not
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* 3. launches an `.app`, declares tests (the template runs them), and is not
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* 4. launches an `.app` and is not named like a companion;
|
|
91
|
+
* 2. launches the product named after the project, and is not a companion.
|
|
92
|
+
* `thebaselab/codeapp` is why this outranks the next one: its `Code App`
|
|
93
|
+
* scheme launches `Code.app` and declares no tests at all, while `Code UI`
|
|
94
|
+
* has two test bundles and launches `CodeUI.app`;
|
|
95
|
+
* 3. launches an `.app`, declares tests (the template runs them), and is not a
|
|
96
|
+
* companion; among several, the iOS or Debug one, then the shortest;
|
|
97
|
+
* 4. launches an `.app` and is not a companion;
|
|
95
98
|
* 5. whatever order it came in.
|
|
96
99
|
*
|
|
97
100
|
* Every scheme stays in the list — this reorders, it does not filter, so a
|
|
98
101
|
* caller wanting the extension can still find it.
|
|
102
|
+
*
|
|
103
|
+
* **What "companion" has to mean, and why a name is not enough.**
|
|
104
|
+
* `mozilla-mobile/firefox-ios` broke tiers 1-4 without touching the directory
|
|
105
|
+
* order this function was written for. Its project is `Client.xcodeproj`, so
|
|
106
|
+
* `projectName` is `Client`. An app extension is launched *through its host
|
|
107
|
+
* app*, so the `Today` widget scheme's LaunchAction runnable is `Client.app` —
|
|
108
|
+
* identical to `Fennec`'s. Both landed in tier 1, neither name carries an `iOS`
|
|
109
|
+
* or `Debug` token, and the tie fell to name length:
|
|
110
|
+
*
|
|
111
|
+
* "Today" -> 5 characters <- won
|
|
112
|
+
* "Fennec" -> 6 characters
|
|
113
|
+
*
|
|
114
|
+
* The build then failed compiling `ActionExtension` rather than the browser
|
|
115
|
+
* (`bld_b1b38dda58f4111080415283`).
|
|
116
|
+
*
|
|
117
|
+
* Launching the app binary is therefore not sufficient, and the answer is not a
|
|
118
|
+
* longer name blocklist either — `Today` names nothing. Xcode states the fact
|
|
119
|
+
* outright: it writes `wasCreatedForAppExtension = "YES"` onto the `<Scheme>`
|
|
120
|
+
* element of a scheme it created for an extension target, so that is what is
|
|
121
|
+
* read.
|
|
122
|
+
*
|
|
123
|
+
* Measured over all 125 shared schemes in the thirty-app study's clones: 29
|
|
124
|
+
* carry the attribute, and **all 29 are extension schemes** — no false
|
|
125
|
+
* positive. It is also the only signal that would have worked. Two that were
|
|
126
|
+
* considered and are wrong on this same evidence:
|
|
127
|
+
*
|
|
128
|
+
* - *require an `.app` among the BuildAction entries.* 27 of the 29 extension
|
|
129
|
+
* schemes build the host app too — `Today` builds
|
|
130
|
+
* `["Today.appex", "Client.app"]` — so this demotes 2 of 29 and leaves
|
|
131
|
+
* firefox-ios picking `Today`.
|
|
132
|
+
* - *demote anything building an `.appex`.* `chenqi92/primuse`'s `Primuse`
|
|
133
|
+
* scheme is the real app scheme and builds two widget extensions alongside
|
|
134
|
+
* `Primuse.app`.
|
|
135
|
+
*
|
|
136
|
+
* The attribute is a sufficient signal, not a necessary one: a hand-written or
|
|
137
|
+
* pre-Xcode-10 scheme may omit it, and its absence changes nothing, so nothing
|
|
138
|
+
* that ranked before can regress on a scheme that does not carry it.
|
|
99
139
|
*/
|
|
100
140
|
export function rankSchemes(root, projectPath, schemes) {
|
|
101
141
|
return rankSchemesWithReason(root, projectPath, schemes).schemes;
|
|
@@ -107,10 +147,12 @@ export function rankSchemesWithReason(root, projectPath, schemes) {
|
|
|
107
147
|
const projectName = projectPath.replace(/\.(xcodeproj|xcworkspace)$/, "").split("/").pop();
|
|
108
148
|
const scored = schemes.map((name, index) => {
|
|
109
149
|
const facts = readSchemeFacts(root, name);
|
|
110
|
-
|
|
150
|
+
// Two ways to be a companion: the name says so, or Xcode does. Xcode's
|
|
151
|
+
// word outranks a name match, so it disqualifies tier 0 as well.
|
|
152
|
+
const companion = COMPANION_SCHEME.test(name) || (facts?.appExtension ?? false);
|
|
111
153
|
const app = facts?.runnable?.endsWith(".app") ?? false;
|
|
112
154
|
let tier = 4;
|
|
113
|
-
if (name === projectName)
|
|
155
|
+
if (name === projectName && !facts?.appExtension)
|
|
114
156
|
tier = 0;
|
|
115
157
|
else if (facts?.runnable === `${projectName}.app` && !companion)
|
|
116
158
|
tier = 1;
|
|
@@ -27,11 +27,11 @@ afterEach(() => {
|
|
|
27
27
|
rmSync(root, { recursive: true, force: true });
|
|
28
28
|
});
|
|
29
29
|
/** An `.xcscheme` with the parts the ranking reads, in Xcode's own shape. */
|
|
30
|
-
function scheme(project, name, { runnable, testable }) {
|
|
30
|
+
function scheme(project, name, { runnable, testable, appExtension }) {
|
|
31
31
|
const dir = join(root, `${project}.xcodeproj`, "xcshareddata", "xcschemes");
|
|
32
32
|
mkdirSync(dir, { recursive: true });
|
|
33
33
|
writeFileSync(join(dir, `${name}.xcscheme`), `<?xml version="1.0" encoding="UTF-8"?>
|
|
34
|
-
<Scheme LastUpgradeVersion = "1600" version = "1.7">
|
|
34
|
+
<Scheme LastUpgradeVersion = "1600"${appExtension ? `\n wasCreatedForAppExtension = "YES"` : ""} version = "1.7">
|
|
35
35
|
<TestAction buildConfiguration = "Debug">
|
|
36
36
|
<Testables>${testable
|
|
37
37
|
? `
|
|
@@ -149,4 +149,111 @@ describe("rankSchemes", () => {
|
|
|
149
149
|
expect(pick("Solo.xcodeproj", ["Solo", "Other"])).toBe("Solo");
|
|
150
150
|
});
|
|
151
151
|
});
|
|
152
|
+
/**
|
|
153
|
+
* An app extension is launched *through its host app*, so an extension scheme's
|
|
154
|
+
* LaunchAction runnable is the host `.app` — the same value the app's own
|
|
155
|
+
* scheme carries. Launching the app binary is therefore not sufficient to be
|
|
156
|
+
* the app scheme, and no name blocklist reaches the case: `Today` names nothing.
|
|
157
|
+
*
|
|
158
|
+
* Xcode states the fact itself, on the `<Scheme>` element of any scheme it
|
|
159
|
+
* created for an extension target. Measured across all 125 shared schemes in
|
|
160
|
+
* the thirty-app study's clones: 29 carry `wasCreatedForAppExtension`, all 29
|
|
161
|
+
* are extension schemes, and 27 of them build the host `.app` too — which is
|
|
162
|
+
* why "require an `.app` among the BuildAction entries" does not separate them.
|
|
163
|
+
*/
|
|
164
|
+
describe("rankSchemes and app-extension schemes", () => {
|
|
165
|
+
/**
|
|
166
|
+
* `mozilla-mobile/firefox-ios`, all thirteen schemes of
|
|
167
|
+
* `firefox-ios/Client.xcodeproj`, read off the clone. The project is
|
|
168
|
+
* `Client.xcodeproj`, so five schemes launch `Client.app` and land in the
|
|
169
|
+
* same tier; none carries an `iOS` or `Debug` token, so the tie fell to name
|
|
170
|
+
* length and `Today` (5) beat `Fennec` (6). The build then failed compiling
|
|
171
|
+
* `ActionExtension` — `bld_b1b38dda58f4111080415283`.
|
|
172
|
+
*/
|
|
173
|
+
const FIREFOX_IOS = [
|
|
174
|
+
["Account", { runnable: "libAccount.a", testable: true }],
|
|
175
|
+
["Fennec", { runnable: "Client.app", testable: true }],
|
|
176
|
+
["Fennec_Enterprise", { runnable: "Client.app", testable: true }],
|
|
177
|
+
["Firefox", { runnable: "Client.app", testable: true }],
|
|
178
|
+
["FirefoxBeta", { runnable: "Client.app", testable: true }],
|
|
179
|
+
["FirefoxStaging", { runnable: "Client.app", testable: true }],
|
|
180
|
+
["L10nSnapshotTests", { runnable: "L10nSnapshotTests.xctest", testable: true }],
|
|
181
|
+
["Periphery", {}],
|
|
182
|
+
["ShareTo", { runnable: "ShareTo.appex", appExtension: true }],
|
|
183
|
+
["Storage", { runnable: "libStorage.a", testable: true }],
|
|
184
|
+
["Sync", { runnable: "Sync.framework", testable: true }],
|
|
185
|
+
["Telemetry", { runnable: "libSyncTelemetry.a", testable: true }],
|
|
186
|
+
["Today", { runnable: "Client.app", testable: true, appExtension: true }],
|
|
187
|
+
];
|
|
188
|
+
function firefoxIos() {
|
|
189
|
+
for (const [name, shape] of FIREFOX_IOS)
|
|
190
|
+
scheme("Client", name, shape);
|
|
191
|
+
return FIREFOX_IOS.map(([name]) => name);
|
|
192
|
+
}
|
|
193
|
+
test("picks the browser, not its widget", () => {
|
|
194
|
+
const names = firefoxIos();
|
|
195
|
+
expect(pick("Client.xcodeproj", names)).toBe("Fennec");
|
|
196
|
+
});
|
|
197
|
+
// The defect only became visible because a five-character extension name won
|
|
198
|
+
// a length tiebreak. The answer must not depend on the order either.
|
|
199
|
+
test("picks the browser whatever order the filesystem lists", () => {
|
|
200
|
+
const names = firefoxIos();
|
|
201
|
+
const orders = [
|
|
202
|
+
names,
|
|
203
|
+
[...names].reverse(),
|
|
204
|
+
["Today", ...names.filter((n) => n !== "Today")],
|
|
205
|
+
["ShareTo", "Today", ...names.filter((n) => n !== "Today" && n !== "ShareTo")],
|
|
206
|
+
];
|
|
207
|
+
for (const order of orders) {
|
|
208
|
+
expect([order[0], pick("Client.xcodeproj", order)]).toEqual([order[0], "Fennec"]);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
test("still keeps every scheme in the list", () => {
|
|
212
|
+
const names = firefoxIos();
|
|
213
|
+
expect([...rankSchemes(root, "Client.xcodeproj", names)].sort()).toEqual([...names].sort());
|
|
214
|
+
});
|
|
215
|
+
// The narrow statement, without the other twelve schemes around it.
|
|
216
|
+
test("an extension scheme launching the host app is not the app scheme", () => {
|
|
217
|
+
scheme("Client", "Fennec", { runnable: "Client.app", testable: true });
|
|
218
|
+
scheme("Client", "Today", { runnable: "Client.app", testable: true, appExtension: true });
|
|
219
|
+
expect(pick("Client.xcodeproj", ["Today", "Fennec"])).toBe("Fennec");
|
|
220
|
+
});
|
|
221
|
+
// Xcode's own word about the target outranks a name coincidence, so it
|
|
222
|
+
// disqualifies the name-match tier too.
|
|
223
|
+
test("outranks a scheme named exactly after the project", () => {
|
|
224
|
+
scheme("Widget", "Widget", { runnable: "Host.app", appExtension: true });
|
|
225
|
+
scheme("Widget", "HostApp", { runnable: "Host.app", testable: true });
|
|
226
|
+
expect(pick("Widget.xcodeproj", ["Widget", "HostApp"])).toBe("HostApp");
|
|
227
|
+
});
|
|
228
|
+
/**
|
|
229
|
+
* The attribute is a sufficient signal, not a necessary one — a hand-written
|
|
230
|
+
* or pre-Xcode-10 scheme may omit it. Its absence must change nothing, or
|
|
231
|
+
* every scheme that ranked correctly before this could regress.
|
|
232
|
+
*
|
|
233
|
+
* `chenqi92/primuse` is the row that makes the point concrete: its `Primuse`
|
|
234
|
+
* scheme is the real app scheme and builds two widget `.appex` bundles
|
|
235
|
+
* alongside `Primuse.app`, so "demote anything that builds an extension"
|
|
236
|
+
* would have taken the wrong one.
|
|
237
|
+
*/
|
|
238
|
+
test("an untagged app scheme ranks exactly as it did before", () => {
|
|
239
|
+
scheme("Primuse", "Primuse", { runnable: "Primuse.app", testable: true });
|
|
240
|
+
scheme("Primuse", "PrimuseWidgets", { runnable: "Primuse.app" });
|
|
241
|
+
expect(pick("Primuse.xcodeproj", ["PrimuseWidgets", "Primuse"])).toBe("Primuse");
|
|
242
|
+
});
|
|
243
|
+
// When every candidate is an extension scheme there is no app scheme to
|
|
244
|
+
// find. Both land in the bottom tier, nothing is filtered out, and the
|
|
245
|
+
// pre-existing within-tier rule decides — so the caller still gets a
|
|
246
|
+
// deterministic answer rather than one that depends on the directory order.
|
|
247
|
+
test("still answers deterministically when every scheme is an extension", () => {
|
|
248
|
+
scheme("Client", "ShareTo", { runnable: "ShareTo.appex", appExtension: true });
|
|
249
|
+
scheme("Client", "Today", { runnable: "Client.app", appExtension: true });
|
|
250
|
+
for (const order of [
|
|
251
|
+
["Today", "ShareTo"],
|
|
252
|
+
["ShareTo", "Today"],
|
|
253
|
+
]) {
|
|
254
|
+
expect([order[0], pick("Client.xcodeproj", order)]).toEqual([order[0], "Today"]);
|
|
255
|
+
}
|
|
256
|
+
expect(rankSchemes(root, "Client.xcodeproj", ["ShareTo", "Today"])).toHaveLength(2);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
152
259
|
//# sourceMappingURL=ios-scheme-ranking.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-resolver.d.ts","sourceRoot":"","sources":["../../../src/yaml/env-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAc,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"env-resolver.d.ts","sourceRoot":"","sources":["../../../src/yaml/env-resolver.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAc,QAAQ,EAAE,MAAM,YAAY,CAAC;AAiEnF,qBAAa,+BAAgC,SAAQ,KAAK;aAEtC,YAAY,EAAE,MAAM;aACpB,QAAQ,CAAC,EAAE,MAAM;aACjB,IAAI,CAAC,EAAE,MAAM;gBAFb,YAAY,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,YAAA,EACjB,IAAI,CAAC,EAAE,MAAM,YAAA;CAUhC;AAED;;;;;;;GAOG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;aAE/B,QAAQ,EAAE,MAAM;aAChB,QAAQ,CAAC,EAAE,MAAM;gBADjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE,MAAM,YAAA;CAKpC;AAED;;GAEG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,cAAc,CAAiB;gBAGrC,QAAQ,EAAE,YAAY,EACtB,QAAQ,EAAE,YAAY,EACtB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,QAAQ,EAClB,KAAK,CAAC,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM;IA0BvB;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKrC;;;;OAIG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B;;;;OAIG;IACH,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAMtC;;;OAGG;IACH,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAQhC;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM;IAqCnD;;;;;OAKG;IACH,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,GAAG;IAoBnD;;;OAGG;IACH,sBAAsB,IAAI,IAAI;IAQ9B;;;;;;OAMG;IACH,OAAO,CAAC,UAAU;IAalB;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IA0BvB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;IA4FtB;;;OAGG;IACH,OAAO,CAAC,YAAY;IAkBpB;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAuB9B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;CAS5B;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,YAAY,EACtB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,QAAQ,EAClB,KAAK,CAAC,EAAE,MAAM,GACb,WAAW,CAOb"}
|
|
@@ -8,6 +8,7 @@ import * as fs from 'fs';
|
|
|
8
8
|
import * as path from 'path';
|
|
9
9
|
import { execSync } from 'child_process';
|
|
10
10
|
import { SecretsManager } from './secrets-manager.js';
|
|
11
|
+
import { shellAssignedNamesIn } from './shell-assignments.js';
|
|
11
12
|
/**
|
|
12
13
|
* Hints for common variables that require user configuration
|
|
13
14
|
*/
|
|
@@ -174,21 +175,31 @@ export class EnvResolver {
|
|
|
174
175
|
*/
|
|
175
176
|
interpolate(str, stepName) {
|
|
176
177
|
let result = str;
|
|
178
|
+
// A name this very string assigns is the shell's own, and substituting it
|
|
179
|
+
// is worse than demanding it: `IOS_DIR=$(dirname "$IOS_PROJECT_PATH")`
|
|
180
|
+
// followed by `"$IOS_DIR"` would get a pipeline value pasted in instead of
|
|
181
|
+
// the one `dirname` had just computed, so a defined variable would corrupt
|
|
182
|
+
// the script and an undefined one would refuse the build. Left for the
|
|
183
|
+
// shell, which is the only thing that can evaluate it. Only the assignment
|
|
184
|
+
// target is spared — the right-hand side is still resolved here.
|
|
185
|
+
const selfAssigned = shellAssignedNamesIn(str);
|
|
177
186
|
// Format 1: {{checksum "file"}}
|
|
178
187
|
result = result.replace(/\{\{checksum "([^"]+)"\}\}/g, (match, filePath) => {
|
|
179
188
|
return this.computeChecksum(filePath, stepName);
|
|
180
189
|
});
|
|
181
|
-
// Format 2: {{getenv "VAR"}}
|
|
190
|
+
// Format 2: {{getenv "VAR"}} — an explicit request, so it is honoured even
|
|
191
|
+
// where the snippet also assigns that name. Asking by name is not the same
|
|
192
|
+
// as a bare `$VAR` the shell was always going to expand itself.
|
|
182
193
|
result = result.replace(/\{\{getenv "([^"]+)"\}\}/g, (match, varName) => {
|
|
183
194
|
return this.resolveVar(varName, stepName);
|
|
184
195
|
});
|
|
185
196
|
// Format 3: ${VAR}
|
|
186
197
|
result = result.replace(/\$\{([A-Z_][A-Z0-9_]*)\}/g, (match, varName) => {
|
|
187
|
-
return this.resolveVar(varName, stepName);
|
|
198
|
+
return selfAssigned.has(varName) ? match : this.resolveVar(varName, stepName);
|
|
188
199
|
});
|
|
189
200
|
// Format 4: $VAR (must not be followed by alphanumeric or underscore)
|
|
190
201
|
result = result.replace(/\$([A-Z_][A-Z0-9_]*)(?![A-Z0-9_])/g, (match, varName) => {
|
|
191
|
-
return this.resolveVar(varName, stepName);
|
|
202
|
+
return selfAssigned.has(varName) ? match : this.resolveVar(varName, stepName);
|
|
192
203
|
});
|
|
193
204
|
return result;
|
|
194
205
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which names a shell snippet binds for itself.
|
|
3
|
+
*
|
|
4
|
+
* Two different parts of this codebase scan a step's inputs for `$UPPERCASE`
|
|
5
|
+
* and act on every hit — the pre-execution validator, which demands each one
|
|
6
|
+
* be supplied, and the env resolver, which *substitutes* each one. For a
|
|
7
|
+
* `script` step the inputs are the shell body, so a variable the script
|
|
8
|
+
* computes is indistinguishable from one it needs handed to it. Both were
|
|
9
|
+
* wrong about the same thing, so they share one answer.
|
|
10
|
+
*
|
|
11
|
+
* A generated multiplatform pipeline made it plain. Its prepare step opens
|
|
12
|
+
*
|
|
13
|
+
* INTEGRATION="$KMM_IOS_INTEGRATION"
|
|
14
|
+
* IOS_DIR=$(dirname "$IOS_PROJECT_PATH")
|
|
15
|
+
* for WORKSPACE in "$IOS_DIR"/*.xcworkspace; do …
|
|
16
|
+
*
|
|
17
|
+
* and no build could start: the validator refused for three values nothing was
|
|
18
|
+
* meant to provide, and the resolver would have gone further and *rewritten*
|
|
19
|
+
* the body, replacing `"$IOS_DIR"` with a pipeline value instead of the one
|
|
20
|
+
* `dirname` had just computed. Substituting a shell local is not a missing
|
|
21
|
+
* value at all; it is a corrupted script.
|
|
22
|
+
*
|
|
23
|
+
* **Only the assignment target is collected, never the right-hand side.** That
|
|
24
|
+
* is what keeps this from being a hole: `INTEGRATION="$KMM_IOS_INTEGRATION"`
|
|
25
|
+
* still needs `KMM_IOS_INTEGRATION`, so a script cannot excuse itself from a
|
|
26
|
+
* value it reads by mentioning it.
|
|
27
|
+
*
|
|
28
|
+
* Assignment order is deliberately not modelled. A snippet that reads a
|
|
29
|
+
* variable before setting it is a bug in the snippet, and guessing about it
|
|
30
|
+
* here would trade one wrong answer for another; "does the script set this
|
|
31
|
+
* itself?" is the question worth asking.
|
|
32
|
+
*/
|
|
33
|
+
/** The names one snippet of shell assigns to itself. */
|
|
34
|
+
export declare function shellAssignedNamesIn(text: string): Set<string>;
|
|
35
|
+
/**
|
|
36
|
+
* The same question asked of a whole inputs object, walked recursively — what
|
|
37
|
+
* the validator needs, because it reasons about a step rather than a string.
|
|
38
|
+
*/
|
|
39
|
+
export declare function shellAssignedNames(obj: unknown): Set<string>;
|
|
40
|
+
//# sourceMappingURL=shell-assignments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-assignments.d.ts","sourceRoot":"","sources":["../../../src/yaml/shell-assignments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAaH,wDAAwD;AACxD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAa9D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,CAa5D"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which names a shell snippet binds for itself.
|
|
3
|
+
*
|
|
4
|
+
* Two different parts of this codebase scan a step's inputs for `$UPPERCASE`
|
|
5
|
+
* and act on every hit — the pre-execution validator, which demands each one
|
|
6
|
+
* be supplied, and the env resolver, which *substitutes* each one. For a
|
|
7
|
+
* `script` step the inputs are the shell body, so a variable the script
|
|
8
|
+
* computes is indistinguishable from one it needs handed to it. Both were
|
|
9
|
+
* wrong about the same thing, so they share one answer.
|
|
10
|
+
*
|
|
11
|
+
* A generated multiplatform pipeline made it plain. Its prepare step opens
|
|
12
|
+
*
|
|
13
|
+
* INTEGRATION="$KMM_IOS_INTEGRATION"
|
|
14
|
+
* IOS_DIR=$(dirname "$IOS_PROJECT_PATH")
|
|
15
|
+
* for WORKSPACE in "$IOS_DIR"/*.xcworkspace; do …
|
|
16
|
+
*
|
|
17
|
+
* and no build could start: the validator refused for three values nothing was
|
|
18
|
+
* meant to provide, and the resolver would have gone further and *rewritten*
|
|
19
|
+
* the body, replacing `"$IOS_DIR"` with a pipeline value instead of the one
|
|
20
|
+
* `dirname` had just computed. Substituting a shell local is not a missing
|
|
21
|
+
* value at all; it is a corrupted script.
|
|
22
|
+
*
|
|
23
|
+
* **Only the assignment target is collected, never the right-hand side.** That
|
|
24
|
+
* is what keeps this from being a hole: `INTEGRATION="$KMM_IOS_INTEGRATION"`
|
|
25
|
+
* still needs `KMM_IOS_INTEGRATION`, so a script cannot excuse itself from a
|
|
26
|
+
* value it reads by mentioning it.
|
|
27
|
+
*
|
|
28
|
+
* Assignment order is deliberately not modelled. A snippet that reads a
|
|
29
|
+
* variable before setting it is a bug in the snippet, and guessing about it
|
|
30
|
+
* here would trade one wrong answer for another; "does the script set this
|
|
31
|
+
* itself?" is the question worth asking.
|
|
32
|
+
*/
|
|
33
|
+
/** `NAME=…`, `export NAME=…`, `local/readonly/declare/typeset NAME=…`, `NAME+=…` */
|
|
34
|
+
const ASSIGNMENT = /(?:^|[\n;&|(]|\|\||&&)[ \t]*(?:(?:export|readonly|local|declare|typeset)[ \t]+)?([A-Z_][A-Z_0-9]*)\+?=/g;
|
|
35
|
+
/** `for NAME in …`, `select NAME in …` */
|
|
36
|
+
const LOOP = /(?:^|[\n;&|(])[ \t]*(?:for|select)[ \t]+([A-Z_][A-Z_0-9]*)[ \t]+in\b/g;
|
|
37
|
+
/** `read NAME`, `read -r NAME OTHER`, `while read -r NAME` */
|
|
38
|
+
const READ = /(?:^|[\n;&|(]|\|\||&&)[ \t]*(?:while[ \t]+)?read[ \t]+((?:-\S+[ \t]+)*)([A-Z_][A-Z_0-9]*(?:[ \t]+[A-Z_][A-Z_0-9]*)*)/g;
|
|
39
|
+
/** The names one snippet of shell assigns to itself. */
|
|
40
|
+
export function shellAssignedNamesIn(text) {
|
|
41
|
+
const names = new Set();
|
|
42
|
+
for (const re of [ASSIGNMENT, LOOP]) {
|
|
43
|
+
re.lastIndex = 0;
|
|
44
|
+
let m;
|
|
45
|
+
while ((m = re.exec(text)) !== null)
|
|
46
|
+
names.add(m[1]);
|
|
47
|
+
}
|
|
48
|
+
READ.lastIndex = 0;
|
|
49
|
+
let m;
|
|
50
|
+
while ((m = READ.exec(text)) !== null) {
|
|
51
|
+
for (const n of m[2].trim().split(/[ \t]+/))
|
|
52
|
+
names.add(n);
|
|
53
|
+
}
|
|
54
|
+
return names;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The same question asked of a whole inputs object, walked recursively — what
|
|
58
|
+
* the validator needs, because it reasons about a step rather than a string.
|
|
59
|
+
*/
|
|
60
|
+
export function shellAssignedNames(obj) {
|
|
61
|
+
const names = new Set();
|
|
62
|
+
const walk = (value) => {
|
|
63
|
+
if (typeof value === 'string') {
|
|
64
|
+
for (const n of shellAssignedNamesIn(value))
|
|
65
|
+
names.add(n);
|
|
66
|
+
}
|
|
67
|
+
else if (Array.isArray(value)) {
|
|
68
|
+
value.forEach(walk);
|
|
69
|
+
}
|
|
70
|
+
else if (value && typeof value === 'object') {
|
|
71
|
+
Object.values(value).forEach(walk);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
walk(obj);
|
|
75
|
+
return names;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=shell-assignments.js.map
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { describe, test, expect } from '@jest/globals';
|
|
23
23
|
import '../yaml/steps/index.js';
|
|
24
|
-
import { StepValidator
|
|
24
|
+
import { StepValidator } from './step-validator.js';
|
|
25
|
+
import { EnvResolver } from './env-resolver.js';
|
|
26
|
+
import { shellAssignedNames } from './shell-assignments.js';
|
|
25
27
|
import { loadConfig } from '../config.js';
|
|
26
28
|
const config = loadConfig();
|
|
27
29
|
/** A one-script workflow, so the only issues are about its variables. */
|
|
@@ -126,4 +128,64 @@ describe('pre-execution validation and self-assigned variables', () => {
|
|
|
126
128
|
]);
|
|
127
129
|
});
|
|
128
130
|
});
|
|
131
|
+
/**
|
|
132
|
+
* The resolver half, and the worse half.
|
|
133
|
+
*
|
|
134
|
+
* `StepValidator` only *demands* a script's own locals. `EnvResolver`
|
|
135
|
+
* SUBSTITUTES them — so a defined `IOS_DIR` would paste a pipeline value over
|
|
136
|
+
* the one `dirname` had just computed, and an undefined one refuses the build.
|
|
137
|
+
* Both are wrong; leaving the reference for the shell is the only correct
|
|
138
|
+
* answer, and it is the same thing `registerStepOutput` already does for a
|
|
139
|
+
* value a previous step exports.
|
|
140
|
+
*/
|
|
141
|
+
describe('EnvResolver leaves a script its own locals', () => {
|
|
142
|
+
const PREPARE = [
|
|
143
|
+
'INTEGRATION="$KMM_IOS_INTEGRATION"',
|
|
144
|
+
'IOS_DIR=$(dirname "$IOS_PROJECT_PATH")',
|
|
145
|
+
'echo "$INTEGRATION in $IOS_DIR"',
|
|
146
|
+
'for WORKSPACE in "$IOS_DIR"/*.xcworkspace; do echo "$WORKSPACE"; done',
|
|
147
|
+
].join('\n');
|
|
148
|
+
function resolver(envs) {
|
|
149
|
+
const pipeline = {
|
|
150
|
+
format_version: '1',
|
|
151
|
+
app: { envs: Object.entries(envs).map(([k, v]) => ({ [k]: v })) },
|
|
152
|
+
workflows: { primary: { steps: [] } },
|
|
153
|
+
};
|
|
154
|
+
return new EnvResolver(pipeline, pipeline.workflows.primary, 'primary', 'macos');
|
|
155
|
+
}
|
|
156
|
+
test('does not substitute or demand what the snippet assigns', () => {
|
|
157
|
+
const out = resolver({
|
|
158
|
+
KMM_IOS_INTEGRATION: 'cocoapods',
|
|
159
|
+
IOS_PROJECT_PATH: 'iosApp/iosApp.xcodeproj',
|
|
160
|
+
}).interpolate(PREPARE, 'Prepare the iOS build');
|
|
161
|
+
// The right-hand sides ARE resolved…
|
|
162
|
+
expect(out).toContain('INTEGRATION="cocoapods"');
|
|
163
|
+
expect(out).toContain('IOS_DIR=$(dirname "iosApp/iosApp.xcodeproj")');
|
|
164
|
+
// …and the locals are left for the shell, verbatim.
|
|
165
|
+
expect(out).toContain('echo "$INTEGRATION in $IOS_DIR"');
|
|
166
|
+
expect(out).toContain('for WORKSPACE in "$IOS_DIR"/*.xcworkspace');
|
|
167
|
+
expect(out).toContain('echo "$WORKSPACE"');
|
|
168
|
+
});
|
|
169
|
+
// Before the fix this threw MissingEnvironmentVariableError for INTEGRATION,
|
|
170
|
+
// which is what stopped every generated KMM and iOS pipeline before step 1.
|
|
171
|
+
test('does not throw for a local the snippet assigns', () => {
|
|
172
|
+
expect(() => resolver({
|
|
173
|
+
KMM_IOS_INTEGRATION: 'direct',
|
|
174
|
+
IOS_PROJECT_PATH: 'iosApp/iosApp.xcodeproj',
|
|
175
|
+
}).interpolate(PREPARE, 'Prepare the iOS build')).not.toThrow();
|
|
176
|
+
});
|
|
177
|
+
// The regression guard: a value the script only READS is still required.
|
|
178
|
+
test('still throws for a variable the snippet only reads', () => {
|
|
179
|
+
expect(() => resolver({}).interpolate(PREPARE, 'Prepare the iOS build')).toThrow(/Missing environment variable: 'KMM_IOS_INTEGRATION'/);
|
|
180
|
+
});
|
|
181
|
+
test('substitutes normally where the snippet assigns nothing', () => {
|
|
182
|
+
expect(resolver({ IOS_SCHEME: 'Hush iOS' }).interpolate('xcodebuild -scheme "$IOS_SCHEME"'))
|
|
183
|
+
.toBe('xcodebuild -scheme "Hush iOS"');
|
|
184
|
+
});
|
|
185
|
+
// `{{getenv "X"}}` is an explicit request by name, not a bare shell
|
|
186
|
+
// expansion, so it is honoured even where the snippet also assigns X.
|
|
187
|
+
test('still honours an explicit {{getenv}} for an assigned name', () => {
|
|
188
|
+
expect(resolver({ IOS_DIR: 'from-pipeline' }).interpolate('IOS_DIR=x\necho {{getenv "IOS_DIR"}}')).toContain('echo from-pipeline');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
129
191
|
//# sourceMappingURL=step-validator-self-assigned.test.js.map
|
|
@@ -8,38 +8,6 @@
|
|
|
8
8
|
import type { YAMLPipeline } from './types.js';
|
|
9
9
|
import type { CIConfig } from '../types.js';
|
|
10
10
|
import type { WorkflowValidationResult } from './validation-types.js';
|
|
11
|
-
/**
|
|
12
|
-
* Names a shell snippet assigns to itself, in every form a step script uses.
|
|
13
|
-
*
|
|
14
|
-
* The auto-discovery below treats every `$UPPERCASE` it finds in a step's
|
|
15
|
-
* inputs as a value the pipeline must supply. For a `script` step the inputs
|
|
16
|
-
* *are* the shell body, so a variable the script computes reads exactly like
|
|
17
|
-
* one it needs handed to it — and the run is refused for a value nothing was
|
|
18
|
-
* ever supposed to provide.
|
|
19
|
-
*
|
|
20
|
-
* That is not hypothetical. A generated multiplatform pipeline whose prepare
|
|
21
|
-
* step opens with
|
|
22
|
-
*
|
|
23
|
-
* INTEGRATION="$KMM_IOS_INTEGRATION"
|
|
24
|
-
* IOS_DIR=$(dirname "$IOS_PROJECT_PATH")
|
|
25
|
-
* for WORKSPACE in "$IOS_DIR"/*.xcworkspace; do …
|
|
26
|
-
*
|
|
27
|
-
* was refused before step 1 on "3 required value(s) missing — INTEGRATION,
|
|
28
|
-
* IOS_DIR, WORKSPACE", while the two variables it genuinely reads were both
|
|
29
|
-
* declared and present. The shell was correct; the reader was not.
|
|
30
|
-
*
|
|
31
|
-
* Only the assignment *target* is collected, never the right-hand side, so
|
|
32
|
-
* `INTEGRATION="$KMM_IOS_INTEGRATION"` still requires `KMM_IOS_INTEGRATION`.
|
|
33
|
-
* That is what keeps this from becoming a hole: a script cannot excuse itself
|
|
34
|
-
* from a value it reads by mentioning it.
|
|
35
|
-
*
|
|
36
|
-
* Assignment order is deliberately not modelled. A snippet that reads `$FOO`
|
|
37
|
-
* before setting it is a bug in the snippet, and guessing about it here would
|
|
38
|
-
* trade a false refusal for a different false refusal; "the script sets this
|
|
39
|
-
* somewhere" is the question worth asking, and erring toward letting the build
|
|
40
|
-
* run is the right direction for a check that cannot prompt.
|
|
41
|
-
*/
|
|
42
|
-
export declare function shellAssignedNames(obj: unknown): Set<string>;
|
|
43
11
|
/**
|
|
44
12
|
* StepValidator orchestrates pre-execution validation for a workflow.
|
|
45
13
|
* It iterates through all steps, collects validation requirements,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"step-validator.d.ts","sourceRoot":"","sources":["../../../src/yaml/step-validator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"step-validator.d.ts","sourceRoot":"","sources":["../../../src/yaml/step-validator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAAE,YAAY,EAA4B,MAAM,YAAY,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAGV,wBAAwB,EAGzB,MAAM,uBAAuB,CAAC;AAwB/B;;;;GAIG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,YAAY,CAAC,CAAS;IAG9B,OAAO,CAAC,gBAAgB,CAAoE;IAG5F,OAAO,CAAC,mBAAmB,CAA6B;IAKxD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;gBAGvC,QAAQ,EAAE,YAAY,EACtB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,QAAQ,EAChB,YAAY,CAAC,EAAE,MAAM;IA0BvB;;;OAGG;IACG,gBAAgB,IAAI,OAAO,CAAC,wBAAwB,CAAC;IAwN3D;;OAEG;YACW,mBAAmB;IAmEjC;;;;;;;;;OASG;IACH,OAAO,CAAC,yBAAyB;IAsBjC;;;;;;;;;OASG;IACH,OAAO,CAAC,mBAAmB;IAoB3B;;OAEG;IACH,OAAO,CAAC,SAAS;CA8BlB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,GAAG,MAAM,CA4D/E"}
|