@invarn/cibuild 1.3.16 → 1.3.17
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 +1 -1
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +987 -0
- package/dist/src/commands/android-scanner.d.ts +32 -0
- package/dist/src/commands/android-scanner.d.ts.map +1 -0
- package/dist/src/commands/android-scanner.js +667 -0
- package/dist/src/commands/build.d.ts +5 -0
- package/dist/src/commands/build.d.ts.map +1 -0
- package/dist/src/commands/build.js +1096 -0
- package/dist/src/commands/edit.d.ts +3 -0
- package/dist/src/commands/edit.d.ts.map +1 -0
- package/dist/src/commands/edit.js +651 -0
- package/dist/src/commands/file-secret-collector.d.ts +37 -0
- package/dist/src/commands/file-secret-collector.d.ts.map +1 -0
- package/dist/src/commands/file-secret-collector.js +199 -0
- package/dist/src/commands/github-workflow.d.ts +5 -0
- package/dist/src/commands/github-workflow.d.ts.map +1 -0
- package/dist/src/commands/github-workflow.js +45 -0
- package/dist/src/commands/ios-scanner.d.ts +27 -0
- package/dist/src/commands/ios-scanner.d.ts.map +1 -0
- package/dist/src/commands/ios-scanner.js +337 -0
- package/dist/src/commands/reset.d.ts +7 -0
- package/dist/src/commands/reset.d.ts.map +1 -0
- package/dist/src/commands/reset.js +81 -0
- package/dist/src/commands/secrets-sync-workflow.d.ts +15 -0
- package/dist/src/commands/secrets-sync-workflow.d.ts.map +1 -0
- package/dist/src/commands/secrets-sync-workflow.js +255 -0
- package/dist/src/commands/secrets-upload.d.ts +21 -0
- package/dist/src/commands/secrets-upload.d.ts.map +1 -0
- package/dist/src/commands/secrets-upload.js +177 -0
- package/dist/src/commands/secrets-upload.test.d.ts +5 -0
- package/dist/src/commands/secrets-upload.test.d.ts.map +1 -0
- package/dist/src/commands/secrets-upload.test.js +60 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +46 -0
- package/dist/src/envman/cli.d.ts +21 -0
- package/dist/src/envman/cli.d.ts.map +1 -0
- package/dist/src/envman/cli.js +240 -0
- package/dist/src/envman/envman.d.ts +83 -0
- package/dist/src/envman/envman.d.ts.map +1 -0
- package/dist/src/envman/envman.js +361 -0
- package/dist/src/envman/envman.test.d.ts +5 -0
- package/dist/src/envman/envman.test.d.ts.map +1 -0
- package/dist/src/envman/envman.test.js +236 -0
- package/dist/src/envman/index.d.ts +23 -0
- package/dist/src/envman/index.d.ts.map +1 -0
- package/dist/src/envman/index.js +23 -0
- package/dist/src/envman/types.d.ts +55 -0
- package/dist/src/envman/types.d.ts.map +1 -0
- package/dist/src/envman/types.js +12 -0
- package/dist/src/lib.d.ts +27 -0
- package/dist/src/lib.d.ts.map +1 -0
- package/dist/src/lib.js +32 -0
- package/dist/src/pipeline.d.ts +3 -0
- package/dist/src/pipeline.d.ts.map +1 -0
- package/dist/src/pipeline.js +57 -0
- package/dist/src/runner.d.ts +17 -0
- package/dist/src/runner.d.ts.map +1 -0
- package/dist/src/runner.js +234 -0
- package/dist/src/types.d.ts +57 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/yaml/bitrise-compat.d.ts +65 -0
- package/dist/src/yaml/bitrise-compat.d.ts.map +1 -0
- package/dist/src/yaml/bitrise-compat.js +206 -0
- package/dist/src/yaml/bitrise-compat.test.d.ts +5 -0
- package/dist/src/yaml/bitrise-compat.test.d.ts.map +1 -0
- package/dist/src/yaml/bitrise-compat.test.js +347 -0
- package/dist/src/yaml/converter.d.ts +33 -0
- package/dist/src/yaml/converter.d.ts.map +1 -0
- package/dist/src/yaml/converter.js +222 -0
- package/dist/src/yaml/converter.test.d.ts +5 -0
- package/dist/src/yaml/converter.test.d.ts.map +1 -0
- package/dist/src/yaml/converter.test.js +348 -0
- package/dist/src/yaml/e2e.test.d.ts +6 -0
- package/dist/src/yaml/e2e.test.d.ts.map +1 -0
- package/dist/src/yaml/e2e.test.js +446 -0
- package/dist/src/yaml/env-resolver.d.ts +120 -0
- package/dist/src/yaml/env-resolver.d.ts.map +1 -0
- package/dist/src/yaml/env-resolver.js +405 -0
- package/dist/src/yaml/env-resolver.test.d.ts +5 -0
- package/dist/src/yaml/env-resolver.test.d.ts.map +1 -0
- package/dist/src/yaml/env-resolver.test.js +502 -0
- package/dist/src/yaml/interactive-prompts.d.ts +71 -0
- package/dist/src/yaml/interactive-prompts.d.ts.map +1 -0
- package/dist/src/yaml/interactive-prompts.js +258 -0
- package/dist/src/yaml/missing-env-handler.d.ts +45 -0
- package/dist/src/yaml/missing-env-handler.d.ts.map +1 -0
- package/dist/src/yaml/missing-env-handler.js +64 -0
- package/dist/src/yaml/parser.d.ts +33 -0
- package/dist/src/yaml/parser.d.ts.map +1 -0
- package/dist/src/yaml/parser.js +145 -0
- package/dist/src/yaml/pipeline-with-secrets.d.ts +25 -0
- package/dist/src/yaml/pipeline-with-secrets.d.ts.map +1 -0
- package/dist/src/yaml/pipeline-with-secrets.js +76 -0
- package/dist/src/yaml/platform-detector.d.ts +83 -0
- package/dist/src/yaml/platform-detector.d.ts.map +1 -0
- package/dist/src/yaml/platform-detector.js +188 -0
- package/dist/src/yaml/platform-detector.test.d.ts +5 -0
- package/dist/src/yaml/platform-detector.test.d.ts.map +1 -0
- package/dist/src/yaml/platform-detector.test.js +414 -0
- package/dist/src/yaml/preflight-validation.d.ts +40 -0
- package/dist/src/yaml/preflight-validation.d.ts.map +1 -0
- package/dist/src/yaml/preflight-validation.js +152 -0
- package/dist/src/yaml/secrets-manager.d.ts +77 -0
- package/dist/src/yaml/secrets-manager.d.ts.map +1 -0
- package/dist/src/yaml/secrets-manager.js +219 -0
- package/dist/src/yaml/step-validator.d.ts +54 -0
- package/dist/src/yaml/step-validator.d.ts.map +1 -0
- package/dist/src/yaml/step-validator.js +403 -0
- package/dist/src/yaml/steps/android-sign.d.ts +35 -0
- package/dist/src/yaml/steps/android-sign.d.ts.map +1 -0
- package/dist/src/yaml/steps/android-sign.js +147 -0
- package/dist/src/yaml/steps/android-version.d.ts +26 -0
- package/dist/src/yaml/steps/android-version.d.ts.map +1 -0
- package/dist/src/yaml/steps/android-version.js +128 -0
- package/dist/src/yaml/steps/android-version.test.d.ts +5 -0
- package/dist/src/yaml/steps/android-version.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/android-version.test.js +196 -0
- package/dist/src/yaml/steps/android.d.ts +95 -0
- package/dist/src/yaml/steps/android.d.ts.map +1 -0
- package/dist/src/yaml/steps/android.js +916 -0
- package/dist/src/yaml/steps/app-store-deploy.d.ts +48 -0
- package/dist/src/yaml/steps/app-store-deploy.d.ts.map +1 -0
- package/dist/src/yaml/steps/app-store-deploy.js +162 -0
- package/dist/src/yaml/steps/base.d.ts +238 -0
- package/dist/src/yaml/steps/base.d.ts.map +1 -0
- package/dist/src/yaml/steps/base.js +345 -0
- package/dist/src/yaml/steps/bitrise-android-tools.d.ts +26 -0
- package/dist/src/yaml/steps/bitrise-android-tools.d.ts.map +1 -0
- package/dist/src/yaml/steps/bitrise-android-tools.js +198 -0
- package/dist/src/yaml/steps/bitrise-android-tools.test.d.ts +5 -0
- package/dist/src/yaml/steps/bitrise-android-tools.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/bitrise-android-tools.test.js +280 -0
- package/dist/src/yaml/steps/bitrise-apk-info.d.ts +22 -0
- package/dist/src/yaml/steps/bitrise-apk-info.d.ts.map +1 -0
- package/dist/src/yaml/steps/bitrise-apk-info.js +144 -0
- package/dist/src/yaml/steps/bitrise-apk-info.test.d.ts +5 -0
- package/dist/src/yaml/steps/bitrise-apk-info.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/bitrise-apk-info.test.js +331 -0
- package/dist/src/yaml/steps/bitrise-slack.d.ts +49 -0
- package/dist/src/yaml/steps/bitrise-slack.d.ts.map +1 -0
- package/dist/src/yaml/steps/bitrise-slack.js +280 -0
- package/dist/src/yaml/steps/bitrise-slack.test.d.ts +5 -0
- package/dist/src/yaml/steps/bitrise-slack.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/bitrise-slack.test.js +484 -0
- package/dist/src/yaml/steps/bitrise-ssh.d.ts +27 -0
- package/dist/src/yaml/steps/bitrise-ssh.d.ts.map +1 -0
- package/dist/src/yaml/steps/bitrise-ssh.js +134 -0
- package/dist/src/yaml/steps/bitrise-ssh.test.d.ts +5 -0
- package/dist/src/yaml/steps/bitrise-ssh.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/bitrise-ssh.test.js +205 -0
- package/dist/src/yaml/steps/cache.d.ts +52 -0
- package/dist/src/yaml/steps/cache.d.ts.map +1 -0
- package/dist/src/yaml/steps/cache.js +351 -0
- package/dist/src/yaml/steps/fastlane.d.ts +27 -0
- package/dist/src/yaml/steps/fastlane.d.ts.map +1 -0
- package/dist/src/yaml/steps/fastlane.js +79 -0
- package/dist/src/yaml/steps/file.d.ts +27 -0
- package/dist/src/yaml/steps/file.d.ts.map +1 -0
- package/dist/src/yaml/steps/file.js +35 -0
- package/dist/src/yaml/steps/flutter.d.ts +63 -0
- package/dist/src/yaml/steps/flutter.d.ts.map +1 -0
- package/dist/src/yaml/steps/flutter.js +215 -0
- package/dist/src/yaml/steps/git-clone.d.ts +26 -0
- package/dist/src/yaml/steps/git-clone.d.ts.map +1 -0
- package/dist/src/yaml/steps/git-clone.js +111 -0
- package/dist/src/yaml/steps/google-play-deploy.d.ts +37 -0
- package/dist/src/yaml/steps/google-play-deploy.d.ts.map +1 -0
- package/dist/src/yaml/steps/google-play-deploy.js +193 -0
- package/dist/src/yaml/steps/google-play-deploy.test.d.ts +5 -0
- package/dist/src/yaml/steps/google-play-deploy.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/google-play-deploy.test.js +310 -0
- package/dist/src/yaml/steps/index.d.ts +10 -0
- package/dist/src/yaml/steps/index.d.ts.map +1 -0
- package/dist/src/yaml/steps/index.js +1361 -0
- package/dist/src/yaml/steps/ios-deps.d.ts +43 -0
- package/dist/src/yaml/steps/ios-deps.d.ts.map +1 -0
- package/dist/src/yaml/steps/ios-deps.js +141 -0
- package/dist/src/yaml/steps/ios-deps.test.d.ts +5 -0
- package/dist/src/yaml/steps/ios-deps.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/ios-deps.test.js +90 -0
- package/dist/src/yaml/steps/ios-signing.d.ts +31 -0
- package/dist/src/yaml/steps/ios-signing.d.ts.map +1 -0
- package/dist/src/yaml/steps/ios-signing.js +144 -0
- package/dist/src/yaml/steps/ios-version.d.ts +47 -0
- package/dist/src/yaml/steps/ios-version.d.ts.map +1 -0
- package/dist/src/yaml/steps/ios-version.js +151 -0
- package/dist/src/yaml/steps/linting.d.ts +47 -0
- package/dist/src/yaml/steps/linting.d.ts.map +1 -0
- package/dist/src/yaml/steps/linting.js +148 -0
- package/dist/src/yaml/steps/phase2.test.d.ts +6 -0
- package/dist/src/yaml/steps/phase2.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/phase2.test.js +197 -0
- package/dist/src/yaml/steps/phase3.test.d.ts +5 -0
- package/dist/src/yaml/steps/phase3.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/phase3.test.js +144 -0
- package/dist/src/yaml/steps/phase4.test.d.ts +5 -0
- package/dist/src/yaml/steps/phase4.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/phase4.test.js +166 -0
- package/dist/src/yaml/steps/phase5.test.d.ts +6 -0
- package/dist/src/yaml/steps/phase5.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/phase5.test.js +263 -0
- package/dist/src/yaml/steps/registry.d.ts +88 -0
- package/dist/src/yaml/steps/registry.d.ts.map +1 -0
- package/dist/src/yaml/steps/registry.js +125 -0
- package/dist/src/yaml/steps/registry.test.d.ts +5 -0
- package/dist/src/yaml/steps/registry.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/registry.test.js +235 -0
- package/dist/src/yaml/steps/release.d.ts +50 -0
- package/dist/src/yaml/steps/release.d.ts.map +1 -0
- package/dist/src/yaml/steps/release.js +154 -0
- package/dist/src/yaml/steps/script.d.ts +23 -0
- package/dist/src/yaml/steps/script.d.ts.map +1 -0
- package/dist/src/yaml/steps/script.js +63 -0
- package/dist/src/yaml/steps/spec-validation.test.d.ts +6 -0
- package/dist/src/yaml/steps/spec-validation.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/spec-validation.test.js +130 -0
- package/dist/src/yaml/steps/steps.test.d.ts +6 -0
- package/dist/src/yaml/steps/steps.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/steps.test.js +474 -0
- package/dist/src/yaml/steps/test-config.d.ts +3 -0
- package/dist/src/yaml/steps/test-config.d.ts.map +1 -0
- package/dist/src/yaml/steps/test-config.js +16 -0
- package/dist/src/yaml/steps/xcode-new.test.d.ts +5 -0
- package/dist/src/yaml/steps/xcode-new.test.d.ts.map +1 -0
- package/dist/src/yaml/steps/xcode-new.test.js +211 -0
- package/dist/src/yaml/steps/xcode.d.ts +222 -0
- package/dist/src/yaml/steps/xcode.d.ts.map +1 -0
- package/dist/src/yaml/steps/xcode.js +999 -0
- package/dist/src/yaml/types.d.ts +68 -0
- package/dist/src/yaml/types.d.ts.map +1 -0
- package/dist/src/yaml/types.js +5 -0
- package/dist/src/yaml/validation-types.d.ts +96 -0
- package/dist/src/yaml/validation-types.d.ts.map +1 -0
- package/dist/src/yaml/validation-types.js +8 -0
- package/dist/src/yaml/yaml-updater.d.ts +24 -0
- package/dist/src/yaml/yaml-updater.d.ts.map +1 -0
- package/dist/src/yaml/yaml-updater.js +128 -0
- package/package.json +16 -4
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SecretsManager } from "../yaml/secrets-manager.js";
|
|
2
|
+
export interface WorkflowGroup {
|
|
3
|
+
label: string;
|
|
4
|
+
workflows: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface FileSecretConfig {
|
|
7
|
+
/** Display name used in prompts, e.g. "keystore" */
|
|
8
|
+
displayName: string;
|
|
9
|
+
/** The secret variable name, e.g. "KEYSTORE_BASE64" */
|
|
10
|
+
secretName: string;
|
|
11
|
+
/** Returns true if a filename matches (e.g. name === "google-services.json") */
|
|
12
|
+
fileMatch: (name: string) => boolean;
|
|
13
|
+
/** How to read the file into a string value. Defaults to "text". */
|
|
14
|
+
readAs?: "text" | "base64";
|
|
15
|
+
/** Workflow groups to scope the secret to */
|
|
16
|
+
workflowGroups: WorkflowGroup[];
|
|
17
|
+
/**
|
|
18
|
+
* When true, prompt for the relative project path where this file should
|
|
19
|
+
* be placed at runtime whenever content is provided manually (not picked
|
|
20
|
+
* from disk candidates). The answer is stored in the returned paths map.
|
|
21
|
+
*/
|
|
22
|
+
promptTargetPath?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Maps each workflow name to the relative path of the file that was selected
|
|
26
|
+
* or auto-detected from disk. Empty when the value was pasted manually.
|
|
27
|
+
*/
|
|
28
|
+
export type FileSecretPaths = Record<string, string>;
|
|
29
|
+
export declare function findProjectFiles(dir: string, match: (name: string) => boolean): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Finds, prompts for, and stores a file-based secret.
|
|
32
|
+
* Only prompts for workflow groups that don't already have the secret.
|
|
33
|
+
* Returns a map of workflow → relative file path for every workflow
|
|
34
|
+
* that was resolved from disk (not from a manual paste).
|
|
35
|
+
*/
|
|
36
|
+
export declare function collectFileSecret(config: FileSecretConfig, cwd: string, secretsManager: SecretsManager, nonInteractive?: boolean): Promise<FileSecretPaths>;
|
|
37
|
+
//# sourceMappingURL=file-secret-collector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-secret-collector.d.ts","sourceRoot":"","sources":["../../../src/commands/file-secret-collector.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAE5D,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IACrC,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC3B,6CAA6C;IAC7C,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAMrD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GAAG,MAAM,EAAE,CAkBxF;AAiDD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,gBAAgB,EACxB,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,cAAc,EAC9B,cAAc,UAAQ,GACrB,OAAO,CAAC,eAAe,CAAC,CAuH1B"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { resolve, relative } from "node:path";
|
|
2
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import * as readline from "node:readline";
|
|
4
|
+
import prompts from "prompts";
|
|
5
|
+
const SKIP_DIRS = new Set([
|
|
6
|
+
".git", ".ci", "build", "dist", "node_modules", ".gradle", ".idea",
|
|
7
|
+
]);
|
|
8
|
+
export function findProjectFiles(dir, match) {
|
|
9
|
+
const results = [];
|
|
10
|
+
let entries;
|
|
11
|
+
try {
|
|
12
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return results;
|
|
16
|
+
}
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
if (entry.isDirectory()) {
|
|
19
|
+
if (!SKIP_DIRS.has(entry.name)) {
|
|
20
|
+
results.push(...findProjectFiles(resolve(dir, entry.name), match));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else if (match(entry.name)) {
|
|
24
|
+
results.push(resolve(dir, entry.name));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return results;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Reads multiline content from stdin via readline.
|
|
31
|
+
* The user terminates input by typing .done on its own line.
|
|
32
|
+
*/
|
|
33
|
+
function readMultilineContent(label) {
|
|
34
|
+
return new Promise((resolvePromise) => {
|
|
35
|
+
console.log(`\n ${label}`);
|
|
36
|
+
console.log(" Paste content, then type .done on its own line and press Enter:\n");
|
|
37
|
+
if (process.stdin.isTTY) {
|
|
38
|
+
try {
|
|
39
|
+
process.stdin.setRawMode(false);
|
|
40
|
+
}
|
|
41
|
+
catch { /* ignore */ }
|
|
42
|
+
}
|
|
43
|
+
process.stdin.resume();
|
|
44
|
+
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
45
|
+
const lines = [];
|
|
46
|
+
rl.on("line", (line) => {
|
|
47
|
+
if (line.trim() === ".done") {
|
|
48
|
+
rl.close();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
lines.push(line);
|
|
52
|
+
});
|
|
53
|
+
rl.once("close", () => {
|
|
54
|
+
process.stdin.pause();
|
|
55
|
+
resolvePromise(lines.length > 0 ? lines.join("\n") : undefined);
|
|
56
|
+
});
|
|
57
|
+
rl.on("SIGINT", () => { rl.close(); resolvePromise(undefined); });
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Prompts the user for the relative path in the project where the file
|
|
62
|
+
* should be written at runtime (e.g. "app/release.jks").
|
|
63
|
+
*/
|
|
64
|
+
async function promptTargetPathInProject(displayName) {
|
|
65
|
+
const { targetPath } = await prompts({
|
|
66
|
+
type: "text",
|
|
67
|
+
name: "targetPath",
|
|
68
|
+
message: `Where should the ${displayName} be placed in the project? (e.g. app/release.jks)`,
|
|
69
|
+
});
|
|
70
|
+
return targetPath?.trim() || undefined;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Finds, prompts for, and stores a file-based secret.
|
|
74
|
+
* Only prompts for workflow groups that don't already have the secret.
|
|
75
|
+
* Returns a map of workflow → relative file path for every workflow
|
|
76
|
+
* that was resolved from disk (not from a manual paste).
|
|
77
|
+
*/
|
|
78
|
+
export async function collectFileSecret(config, cwd, secretsManager, nonInteractive = false) {
|
|
79
|
+
const paths = {};
|
|
80
|
+
// Only process groups where at least one workflow is missing the secret
|
|
81
|
+
const pendingGroups = config.workflowGroups.filter((g) => !g.workflows.every((wf) => secretsManager.hasSecret(config.secretName, wf)));
|
|
82
|
+
if (pendingGroups.length === 0) {
|
|
83
|
+
console.log(` ✅ ${config.secretName} already in .cibuild-secrets.json — will be used at runtime.`);
|
|
84
|
+
return paths;
|
|
85
|
+
}
|
|
86
|
+
const pendingWorkflows = pendingGroups.flatMap((g) => g.workflows);
|
|
87
|
+
const readContent = (p) => config.readAs === "base64"
|
|
88
|
+
? readFileSync(p).toString("base64")
|
|
89
|
+
: readFileSync(p, "utf-8");
|
|
90
|
+
const candidates = findProjectFiles(cwd, config.fileMatch);
|
|
91
|
+
if (candidates.length === 1 && config.workflowGroups.length === 1) {
|
|
92
|
+
// Single file, single-group config — auto-apply without prompting
|
|
93
|
+
const content = readContent(candidates[0]);
|
|
94
|
+
const relPath = relative(cwd, candidates[0]);
|
|
95
|
+
for (const wf of pendingWorkflows) {
|
|
96
|
+
secretsManager.storeSecret(config.secretName, content, wf);
|
|
97
|
+
paths[wf] = relPath;
|
|
98
|
+
}
|
|
99
|
+
console.log(` ✅ Read ${relPath} and stored as ${config.secretName} (workflows: ${pendingWorkflows.join(", ")})`);
|
|
100
|
+
}
|
|
101
|
+
else if (candidates.length > 0 && nonInteractive) {
|
|
102
|
+
// Non-interactive: auto-pick first candidate for all pending groups
|
|
103
|
+
const content = readContent(candidates[0]);
|
|
104
|
+
const relPath = relative(cwd, candidates[0]);
|
|
105
|
+
for (const wf of pendingWorkflows) {
|
|
106
|
+
secretsManager.storeSecret(config.secretName, content, wf);
|
|
107
|
+
paths[wf] = relPath;
|
|
108
|
+
}
|
|
109
|
+
console.log(` ✅ Auto-selected ${relPath} for ${config.secretName} (workflows: ${pendingWorkflows.join(", ")})`);
|
|
110
|
+
}
|
|
111
|
+
else if (candidates.length > 0) {
|
|
112
|
+
// One or more files found — prompt per pending group
|
|
113
|
+
const ENTER_PATH = "__ENTER_PATH__";
|
|
114
|
+
for (const group of pendingGroups) {
|
|
115
|
+
const fileChoices = candidates.map((p) => ({
|
|
116
|
+
title: relative(cwd, p),
|
|
117
|
+
value: p,
|
|
118
|
+
}));
|
|
119
|
+
fileChoices.push({ title: "Enter file path", value: ENTER_PATH });
|
|
120
|
+
fileChoices.push({ title: "Skip (add manually later)", value: null });
|
|
121
|
+
const { chosen } = await prompts({
|
|
122
|
+
type: "select",
|
|
123
|
+
name: "chosen",
|
|
124
|
+
message: `Select ${config.displayName} for ${group.label}:`,
|
|
125
|
+
choices: fileChoices,
|
|
126
|
+
});
|
|
127
|
+
if (chosen === ENTER_PATH) {
|
|
128
|
+
const { filePath } = await prompts({
|
|
129
|
+
type: "text",
|
|
130
|
+
name: "filePath",
|
|
131
|
+
message: `Enter path to ${config.displayName} file:`,
|
|
132
|
+
});
|
|
133
|
+
if (filePath?.trim()) {
|
|
134
|
+
const expandedPath = filePath.trim().replace(/^~/, process.env.HOME ?? "~");
|
|
135
|
+
try {
|
|
136
|
+
const content = readContent(expandedPath);
|
|
137
|
+
const targetPath = config.promptTargetPath
|
|
138
|
+
? await promptTargetPathInProject(config.displayName)
|
|
139
|
+
: undefined;
|
|
140
|
+
for (const wf of group.workflows) {
|
|
141
|
+
secretsManager.storeSecret(config.secretName, content, wf);
|
|
142
|
+
if (targetPath)
|
|
143
|
+
paths[wf] = targetPath;
|
|
144
|
+
}
|
|
145
|
+
console.log(` ✅ Stored ${config.secretName} for workflows: ${group.workflows.join(", ")}`);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
console.log(` Could not read ${expandedPath} — check the path and try again.`);
|
|
149
|
+
console.log(` Add later: ci secrets add ${config.secretName} .ci/pipelines/cibuild.yml -w ${group.workflows[0]}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log(` Add later: ci secrets add ${config.secretName} .ci/pipelines/cibuild.yml -w ${group.workflows[0]}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else if (chosen) {
|
|
157
|
+
const content = readContent(chosen);
|
|
158
|
+
const relPath = relative(cwd, chosen);
|
|
159
|
+
for (const wf of group.workflows) {
|
|
160
|
+
secretsManager.storeSecret(config.secretName, content, wf);
|
|
161
|
+
paths[wf] = relPath;
|
|
162
|
+
}
|
|
163
|
+
console.log(` ✅ Stored ${config.secretName} for workflows: ${group.workflows.join(", ")}`);
|
|
164
|
+
}
|
|
165
|
+
else if (chosen === null) {
|
|
166
|
+
console.log(` Add later: ci secrets add ${config.secretName} .ci/pipelines/cibuild.yml -w ${group.workflows[0]}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
else if (nonInteractive) {
|
|
171
|
+
// Non-interactive: no files found — skip with hint
|
|
172
|
+
console.log(` ⚠ No ${config.displayName} files found — add later: ci secrets add ${config.secretName}`);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// Nothing found on disk — prompt per pending group
|
|
176
|
+
for (const group of pendingGroups) {
|
|
177
|
+
const label = pendingGroups.length > 1
|
|
178
|
+
? `Enter ${config.displayName} content for ${group.label} (or type .done immediately to add later):`
|
|
179
|
+
: `Enter ${config.displayName} content (or type .done immediately to add later):`;
|
|
180
|
+
const value = await readMultilineContent(label);
|
|
181
|
+
if (value) {
|
|
182
|
+
const targetPath = config.promptTargetPath
|
|
183
|
+
? await promptTargetPathInProject(config.displayName)
|
|
184
|
+
: undefined;
|
|
185
|
+
for (const wf of group.workflows) {
|
|
186
|
+
secretsManager.storeSecret(config.secretName, value, wf);
|
|
187
|
+
if (targetPath)
|
|
188
|
+
paths[wf] = targetPath;
|
|
189
|
+
}
|
|
190
|
+
console.log(` ✅ ${config.secretName} stored (workflows: ${group.workflows.join(", ")})`);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
console.log(` Add later: ci secrets add ${config.secretName} .ci/pipelines/cibuild.yml -w ${group.workflows[0]}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return paths;
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=file-secret-collector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-workflow.d.ts","sourceRoot":"","sources":["../../../src/commands/github-workflow.ts"],"names":[],"mappings":"AAGA,wBAAgB,6BAA6B,CAAC,OAAO,EAAE;IACrD,QAAQ,EAAE,KAAK,GAAG,SAAS,GAAG,IAAI,CAAC;IACnC,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,OAAO,CA8CV"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
export function generateGitHubActionsWorkflow(options) {
|
|
4
|
+
const { platform, cwd } = options;
|
|
5
|
+
const workflowDir = resolve(cwd, ".github", "workflows");
|
|
6
|
+
const workflowPath = resolve(workflowDir, "ci.yml");
|
|
7
|
+
if (existsSync(workflowPath)) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const runner = platform === "android" ? "ubuntu-latest" : "macos-latest";
|
|
11
|
+
const yaml = `name: CI
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
push:
|
|
15
|
+
branches: [main]
|
|
16
|
+
tags: ['v*']
|
|
17
|
+
pull_request:
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
build:
|
|
21
|
+
if: "!startsWith(github.ref, 'refs/tags/')"
|
|
22
|
+
runs-on: ${runner}
|
|
23
|
+
environment: cibuild
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v4
|
|
26
|
+
- uses: invarnhq/cibuild@v1
|
|
27
|
+
with:
|
|
28
|
+
workflow: \${{ github.event_name == 'pull_request' && 'pull-request' || 'primary' }}
|
|
29
|
+
|
|
30
|
+
release:
|
|
31
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
32
|
+
runs-on: ${runner}
|
|
33
|
+
environment: cibuild
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/checkout@v4
|
|
36
|
+
- uses: invarnhq/cibuild@v1
|
|
37
|
+
with:
|
|
38
|
+
workflow: release
|
|
39
|
+
`;
|
|
40
|
+
mkdirSync(workflowDir, { recursive: true });
|
|
41
|
+
writeFileSync(workflowPath, yaml, "utf-8");
|
|
42
|
+
console.log(`✅ Generated .github/workflows/ci.yml (${runner})`);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=github-workflow.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type IosWarningCategory = "missing-file" | "env-var" | "signing-config" | "cocoapods";
|
|
2
|
+
export type IosWarningSeverity = "warning" | "info";
|
|
3
|
+
export interface IosWarning {
|
|
4
|
+
category: IosWarningCategory;
|
|
5
|
+
severity: IosWarningSeverity;
|
|
6
|
+
message: string;
|
|
7
|
+
hint?: string;
|
|
8
|
+
location?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface IosScanResult {
|
|
11
|
+
warnings: IosWarning[];
|
|
12
|
+
/** Schemes detected from .xcscheme files inside .xcodeproj. Falls back to project name. */
|
|
13
|
+
detectedSchemes: string[];
|
|
14
|
+
/** True if a Podfile exists in the project root. */
|
|
15
|
+
hasCocoaPods: boolean;
|
|
16
|
+
/** True if a Package.swift exists in the project root. */
|
|
17
|
+
hasSPM: boolean;
|
|
18
|
+
/** Relative path to .xcworkspace (preferred) or .xcodeproj. */
|
|
19
|
+
projectPath: string;
|
|
20
|
+
/** True if project.pbxproj indicates manual code signing. */
|
|
21
|
+
hasSigningConfig: boolean;
|
|
22
|
+
/** DEVELOPMENT_TEAM value from project.pbxproj, or empty string if not found. */
|
|
23
|
+
developmentTeam: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function scanIosProject(projectRoot: string): Promise<IosScanResult>;
|
|
26
|
+
export declare function formatIosScanResult(result: IosScanResult): string;
|
|
27
|
+
//# sourceMappingURL=ios-scanner.d.ts.map
|
|
@@ -0,0 +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,2FAA2F;IAC3F,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,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;AAuND,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAqFhF;AAaD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM,CAkDjE"}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { resolve, relative } from "node:path";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// File discovery
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
function safeRead(filePath) {
|
|
7
|
+
try {
|
|
8
|
+
return readFileSync(filePath, "utf-8");
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function relPath(root, filePath) {
|
|
15
|
+
return relative(root, filePath);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Finds the primary Xcode project path.
|
|
19
|
+
* Prefers .xcworkspace over .xcodeproj (CocoaPods projects use workspace).
|
|
20
|
+
* Returns the relative path from root, or empty string if not found.
|
|
21
|
+
*/
|
|
22
|
+
function findXcodeProjectPath(root) {
|
|
23
|
+
let entries;
|
|
24
|
+
try {
|
|
25
|
+
entries = readdirSync(root);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
const workspaces = entries.filter((e) => e.endsWith(".xcworkspace"));
|
|
31
|
+
const projects = entries.filter((e) => e.endsWith(".xcodeproj"));
|
|
32
|
+
// Prefer workspace (CocoaPods / multi-package setups use these)
|
|
33
|
+
if (workspaces.length > 0)
|
|
34
|
+
return workspaces[0];
|
|
35
|
+
if (projects.length > 0)
|
|
36
|
+
return projects[0];
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Detects Xcode schemes by reading *.xcscheme files from the
|
|
41
|
+
* xcshareddata/xcschemes/ directory inside a .xcodeproj bundle.
|
|
42
|
+
* Falls back to deriving the scheme name from the project file name.
|
|
43
|
+
*/
|
|
44
|
+
function detectSchemes(root) {
|
|
45
|
+
const schemes = new Set();
|
|
46
|
+
let entries;
|
|
47
|
+
try {
|
|
48
|
+
entries = readdirSync(root);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
if (!entry.endsWith(".xcodeproj"))
|
|
55
|
+
continue;
|
|
56
|
+
const schemesDir = resolve(root, entry, "xcshareddata", "xcschemes");
|
|
57
|
+
let schemeFiles;
|
|
58
|
+
try {
|
|
59
|
+
schemeFiles = readdirSync(schemesDir);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// No shared schemes — fall back to project name
|
|
63
|
+
const projectName = entry.replace(/\.xcodeproj$/, "");
|
|
64
|
+
schemes.add(projectName);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
for (const sf of schemeFiles) {
|
|
68
|
+
if (sf.endsWith(".xcscheme")) {
|
|
69
|
+
schemes.add(sf.replace(/\.xcscheme$/, ""));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return Array.from(schemes);
|
|
74
|
+
}
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// xcconfig env var detection
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
/**
|
|
79
|
+
* Recursively finds all *.xcconfig files under root, skipping build/DerivedData/Pods.
|
|
80
|
+
*/
|
|
81
|
+
const SKIP_XCCONFIG_DIRS = new Set(["build", "DerivedData", "Pods", ".git", ".ci", "node_modules"]);
|
|
82
|
+
function findXcconfigFiles(dir) {
|
|
83
|
+
const results = [];
|
|
84
|
+
let entries;
|
|
85
|
+
try {
|
|
86
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return results;
|
|
90
|
+
}
|
|
91
|
+
for (const entry of entries) {
|
|
92
|
+
if (entry.isDirectory()) {
|
|
93
|
+
if (!SKIP_XCCONFIG_DIRS.has(entry.name)) {
|
|
94
|
+
results.push(...findXcconfigFiles(resolve(dir, entry.name)));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (entry.name.endsWith(".xcconfig")) {
|
|
98
|
+
results.push(resolve(dir, entry.name));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return results;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Extract environment variable references from xcconfig content.
|
|
105
|
+
* xcconfig uses $(VAR_NAME) syntax.
|
|
106
|
+
*/
|
|
107
|
+
function extractXcconfigEnvRefs(content) {
|
|
108
|
+
const results = [];
|
|
109
|
+
// Match $(VAR_NAME) — only uppercase + underscore patterns (likely env vars)
|
|
110
|
+
const re = /\$\(([A-Z][A-Z0-9_]+)\)/g;
|
|
111
|
+
const known = new Set(["SRCROOT", "PROJECT_DIR", "CONFIGURATION", "PRODUCT_NAME", "TARGET_NAME", "BUILT_PRODUCTS_DIR"]);
|
|
112
|
+
let m;
|
|
113
|
+
while ((m = re.exec(content)) !== null) {
|
|
114
|
+
const name = m[1];
|
|
115
|
+
if (!known.has(name)) {
|
|
116
|
+
results.push(name);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return [...new Set(results)];
|
|
120
|
+
}
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Signing config detection
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
/**
|
|
125
|
+
* Returns true if project.pbxproj indicates manual (non-automatic) code signing.
|
|
126
|
+
* Automatic signing sets PROVISIONING_PROFILE_SPECIFIER = "" and
|
|
127
|
+
* CODE_SIGN_STYLE = Automatic.
|
|
128
|
+
*/
|
|
129
|
+
function detectManualSigning(root) {
|
|
130
|
+
let entries;
|
|
131
|
+
try {
|
|
132
|
+
entries = readdirSync(root);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
for (const entry of entries) {
|
|
138
|
+
if (!entry.endsWith(".xcodeproj"))
|
|
139
|
+
continue;
|
|
140
|
+
const pbxprojPath = resolve(root, entry, "project.pbxproj");
|
|
141
|
+
const content = safeRead(pbxprojPath);
|
|
142
|
+
if (!content)
|
|
143
|
+
continue;
|
|
144
|
+
// Automatic signing sets CODE_SIGN_STYLE = Automatic
|
|
145
|
+
const isAutomatic = /CODE_SIGN_STYLE\s*=\s*Automatic/.test(content);
|
|
146
|
+
// Manual signing has a non-empty PROVISIONING_PROFILE_SPECIFIER or explicit identity
|
|
147
|
+
const hasProvisioningSpecifier = /PROVISIONING_PROFILE_SPECIFIER\s*=\s*"[^"]+"\s*;/.test(content);
|
|
148
|
+
const hasExplicitIdentity = /CODE_SIGN_IDENTITY\s*=\s*"iPhone (Developer|Distribution)"/.test(content);
|
|
149
|
+
if (!isAutomatic && (hasProvisioningSpecifier || hasExplicitIdentity)) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Extracts the DEVELOPMENT_TEAM value from project.pbxproj.
|
|
157
|
+
* Returns the first non-empty team ID found, or empty string.
|
|
158
|
+
*/
|
|
159
|
+
function detectDevelopmentTeam(root) {
|
|
160
|
+
let entries;
|
|
161
|
+
try {
|
|
162
|
+
entries = readdirSync(root);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return "";
|
|
166
|
+
}
|
|
167
|
+
for (const entry of entries) {
|
|
168
|
+
if (!entry.endsWith(".xcodeproj"))
|
|
169
|
+
continue;
|
|
170
|
+
const pbxprojPath = resolve(root, entry, "project.pbxproj");
|
|
171
|
+
const content = safeRead(pbxprojPath);
|
|
172
|
+
if (!content)
|
|
173
|
+
continue;
|
|
174
|
+
const match = content.match(/DEVELOPMENT_TEAM\s*=\s*([A-Z0-9]{10})\s*;/);
|
|
175
|
+
if (match)
|
|
176
|
+
return match[1];
|
|
177
|
+
}
|
|
178
|
+
return "";
|
|
179
|
+
}
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// Gitignore check
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
const IOS_RELEVANT_GITIGNORE_PATTERNS = [
|
|
184
|
+
{ pattern: /^Pods\/$|^\/Pods\//, label: "Pods/ (CocoaPods dependencies)" },
|
|
185
|
+
{ pattern: /\*\.p12|\*\.cer/, label: "certificates (*.p12, *.cer)" },
|
|
186
|
+
{ pattern: /\*\.mobileprovision/, label: "provisioning profiles (*.mobileprovision)" },
|
|
187
|
+
{ pattern: /\.env$|\*\.env/, label: ".env files" },
|
|
188
|
+
];
|
|
189
|
+
function checkGitignore(root) {
|
|
190
|
+
const gitignorePath = resolve(root, ".gitignore");
|
|
191
|
+
if (!existsSync(gitignorePath))
|
|
192
|
+
return [];
|
|
193
|
+
const content = safeRead(gitignorePath);
|
|
194
|
+
const lines = content.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
195
|
+
const found = [];
|
|
196
|
+
for (const { pattern, label } of IOS_RELEVANT_GITIGNORE_PATTERNS) {
|
|
197
|
+
if (lines.some((l) => pattern.test(l))) {
|
|
198
|
+
found.push(label);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return found;
|
|
202
|
+
}
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
// Main scanner
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
export async function scanIosProject(projectRoot) {
|
|
207
|
+
const warnings = [];
|
|
208
|
+
// 1. Find project path
|
|
209
|
+
const projectPath = findXcodeProjectPath(projectRoot);
|
|
210
|
+
// 2. Detect schemes
|
|
211
|
+
const detectedSchemes = detectSchemes(projectRoot);
|
|
212
|
+
// 3. CocoaPods
|
|
213
|
+
const hasCocoaPods = existsSync(resolve(projectRoot, "Podfile"));
|
|
214
|
+
// 4. SPM
|
|
215
|
+
const hasSPM = existsSync(resolve(projectRoot, "Package.swift"));
|
|
216
|
+
// 5. Signing config + team ID
|
|
217
|
+
const hasSigningConfig = detectManualSigning(projectRoot);
|
|
218
|
+
const developmentTeam = detectDevelopmentTeam(projectRoot);
|
|
219
|
+
// 6. CocoaPods warnings
|
|
220
|
+
const gitignoreItems = checkGitignore(projectRoot);
|
|
221
|
+
const podsGitignored = gitignoreItems.some((i) => i.includes("Pods/"));
|
|
222
|
+
if (hasCocoaPods) {
|
|
223
|
+
if (podsGitignored) {
|
|
224
|
+
warnings.push({
|
|
225
|
+
category: "cocoapods",
|
|
226
|
+
severity: "info",
|
|
227
|
+
message: "Pods/ is gitignored — pod install will run on CI",
|
|
228
|
+
hint: "The generated pipeline includes a pod install step",
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (!existsSync(resolve(projectRoot, "Podfile.lock"))) {
|
|
232
|
+
warnings.push({
|
|
233
|
+
category: "cocoapods",
|
|
234
|
+
severity: "warning",
|
|
235
|
+
message: "Podfile.lock not found",
|
|
236
|
+
hint: "Run 'pod install' locally and commit Podfile.lock for reproducible builds",
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// 7. Signing warnings
|
|
241
|
+
if (hasSigningConfig) {
|
|
242
|
+
warnings.push({
|
|
243
|
+
category: "signing-config",
|
|
244
|
+
severity: "warning",
|
|
245
|
+
message: "Manual code signing detected in project.pbxproj",
|
|
246
|
+
hint: "Provide CERTIFICATE_P12, CERTIFICATE_PASSWORD, and PROVISIONING_PROFILE as secrets",
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
// 8. xcconfig env var warnings
|
|
250
|
+
const seenEnvVars = new Set();
|
|
251
|
+
const xcconfigFiles = findXcconfigFiles(projectRoot);
|
|
252
|
+
for (const xcconfigPath of xcconfigFiles) {
|
|
253
|
+
const content = safeRead(xcconfigPath);
|
|
254
|
+
const rel = relPath(projectRoot, xcconfigPath);
|
|
255
|
+
for (const varName of extractXcconfigEnvRefs(content)) {
|
|
256
|
+
if (!seenEnvVars.has(varName)) {
|
|
257
|
+
seenEnvVars.add(varName);
|
|
258
|
+
warnings.push({
|
|
259
|
+
category: "env-var",
|
|
260
|
+
severity: "warning",
|
|
261
|
+
message: `Environment variable referenced: ${varName}`,
|
|
262
|
+
hint: `Add via: ci secrets add ${varName}`,
|
|
263
|
+
location: rel,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
// 9. Gitignore cross-check for certs / profiles
|
|
269
|
+
for (const item of gitignoreItems) {
|
|
270
|
+
if (item.includes("certificate") || item.includes("provisioning")) {
|
|
271
|
+
warnings.push({
|
|
272
|
+
category: "missing-file",
|
|
273
|
+
severity: "info",
|
|
274
|
+
message: `${item} is in .gitignore — likely absent in CI`,
|
|
275
|
+
hint: "Provide as secrets (CERTIFICATE_P12, PROVISIONING_PROFILE)",
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return { warnings, detectedSchemes, hasCocoaPods, hasSPM, projectPath, hasSigningConfig, developmentTeam };
|
|
280
|
+
}
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
// Formatter
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
const CATEGORY_LABELS = {
|
|
285
|
+
"missing-file": "Missing files",
|
|
286
|
+
"env-var": "Environment variables referenced in xcconfig files",
|
|
287
|
+
"signing-config": "Code signing configuration",
|
|
288
|
+
"cocoapods": "CocoaPods",
|
|
289
|
+
};
|
|
290
|
+
export function formatIosScanResult(result) {
|
|
291
|
+
const lines = [];
|
|
292
|
+
const sep = "─".repeat(50);
|
|
293
|
+
if (result.detectedSchemes.length > 0) {
|
|
294
|
+
lines.push(`ℹ Detected schemes: ${result.detectedSchemes.join(", ")}`);
|
|
295
|
+
lines.push("");
|
|
296
|
+
}
|
|
297
|
+
if (result.hasSPM) {
|
|
298
|
+
lines.push("ℹ Swift Package Manager detected — dependencies resolved automatically by Xcode");
|
|
299
|
+
lines.push("");
|
|
300
|
+
}
|
|
301
|
+
if (result.warnings.length === 0) {
|
|
302
|
+
lines.push("✅ No potential unknowns detected.");
|
|
303
|
+
return lines.join("\n") + "\n";
|
|
304
|
+
}
|
|
305
|
+
lines.push("⚠ iOS Project Scan — Potential Unknowns");
|
|
306
|
+
lines.push(sep);
|
|
307
|
+
lines.push("");
|
|
308
|
+
const byCategory = new Map();
|
|
309
|
+
for (const w of result.warnings) {
|
|
310
|
+
if (!byCategory.has(w.category))
|
|
311
|
+
byCategory.set(w.category, []);
|
|
312
|
+
byCategory.get(w.category).push(w);
|
|
313
|
+
}
|
|
314
|
+
for (const [cat, items] of byCategory) {
|
|
315
|
+
lines.push(`${CATEGORY_LABELS[cat]}:`);
|
|
316
|
+
for (const w of items) {
|
|
317
|
+
const icon = w.severity === "warning" ? "⚠ " : "ℹ ";
|
|
318
|
+
const loc = w.location ? ` (${w.location})` : "";
|
|
319
|
+
lines.push(` ${icon} ${w.message}${loc}`);
|
|
320
|
+
if (w.hint) {
|
|
321
|
+
lines.push(` Hint: ${w.hint}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
lines.push("");
|
|
325
|
+
}
|
|
326
|
+
lines.push(sep);
|
|
327
|
+
const warnCount = result.warnings.filter((w) => w.severity === "warning").length;
|
|
328
|
+
const infoCount = result.warnings.filter((w) => w.severity === "info").length;
|
|
329
|
+
const parts = [];
|
|
330
|
+
if (warnCount > 0)
|
|
331
|
+
parts.push(`${warnCount} warning(s)`);
|
|
332
|
+
if (infoCount > 0)
|
|
333
|
+
parts.push(`${infoCount} info`);
|
|
334
|
+
lines.push(`${parts.join(", ")} found. Review before running on CI.`);
|
|
335
|
+
return lines.join("\n");
|
|
336
|
+
}
|
|
337
|
+
//# sourceMappingURL=ios-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../../../src/commands/reset.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsBH,wBAAsB,kBAAkB,CAAC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzF"}
|