@invarn/cibuild 1.3.15 → 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
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve, extname } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { existsSync, readdirSync, mkdirSync, copyFileSync, readFileSync, writeFileSync, appendFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { execSync } from "node:child_process";
|
|
7
|
+
import prompts from "prompts";
|
|
8
|
+
import { PipelineRunner } from "./runner.js";
|
|
9
|
+
import { loadConfig } from "./config.js";
|
|
10
|
+
import { loadYAMLPipeline } from "./yaml/parser.js";
|
|
11
|
+
import { convertYAMLWithSecrets } from "./yaml/pipeline-with-secrets.js";
|
|
12
|
+
import { detectPlatformInfo } from "./yaml/platform-detector.js";
|
|
13
|
+
import { StepValidator, formatValidationResult } from "./yaml/step-validator.js";
|
|
14
|
+
import { SecretsManager } from "./yaml/secrets-manager.js";
|
|
15
|
+
import { MissingEnvHandler } from "./yaml/missing-env-handler.js";
|
|
16
|
+
import { MissingEnvironmentVariableError } from "./yaml/env-resolver.js";
|
|
17
|
+
import "./yaml/steps/index.js"; // Initialize step registry
|
|
18
|
+
import { handleBuildCommand } from "./commands/build.js";
|
|
19
|
+
import { handleEditCommand } from "./commands/edit.js";
|
|
20
|
+
import { handleSecretsUploadCommand } from "./commands/secrets-upload.js";
|
|
21
|
+
import { handleSecretsSyncWorkflowCommand } from "./commands/secrets-sync-workflow.js";
|
|
22
|
+
import { handleResetCommand } from "./commands/reset.js";
|
|
23
|
+
import { generateGitHubActionsWorkflow } from "./commands/github-workflow.js";
|
|
24
|
+
/**
|
|
25
|
+
* Detects whether the current directory is the root of an Android or iOS project.
|
|
26
|
+
* Returns the detected project type, or null if neither is found.
|
|
27
|
+
*/
|
|
28
|
+
function detectMobileProjectRoot(dir) {
|
|
29
|
+
// Android: must have build.gradle or build.gradle.kts at root
|
|
30
|
+
const androidIndicators = [
|
|
31
|
+
"build.gradle",
|
|
32
|
+
"build.gradle.kts",
|
|
33
|
+
"settings.gradle",
|
|
34
|
+
"settings.gradle.kts",
|
|
35
|
+
"gradlew",
|
|
36
|
+
];
|
|
37
|
+
const hasAndroidIndicator = androidIndicators.some((f) => existsSync(resolve(dir, f)));
|
|
38
|
+
if (hasAndroidIndicator) {
|
|
39
|
+
return "android";
|
|
40
|
+
}
|
|
41
|
+
// iOS: must have a .xcodeproj or .xcworkspace directory, or a Podfile
|
|
42
|
+
const iosFileIndicators = ["Podfile"];
|
|
43
|
+
const hasIosFile = iosFileIndicators.some((f) => existsSync(resolve(dir, f)));
|
|
44
|
+
if (!hasIosFile) {
|
|
45
|
+
// Check for .xcodeproj / .xcworkspace directories
|
|
46
|
+
try {
|
|
47
|
+
const entries = readdirSync(dir);
|
|
48
|
+
const hasXcodeDir = entries.some((e) => e.endsWith(".xcodeproj") || e.endsWith(".xcworkspace"));
|
|
49
|
+
if (hasXcodeDir) {
|
|
50
|
+
return "ios";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// If we can't read the directory, fall through
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
return "ios";
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Ensures CI Build runtime files are listed in .gitignore.
|
|
64
|
+
* Safe to call multiple times — only appends entries that are missing.
|
|
65
|
+
*/
|
|
66
|
+
function ensureCiBuildGitignoreEntries(cwd) {
|
|
67
|
+
const gitignorePath = resolve(cwd, ".gitignore");
|
|
68
|
+
const gitignoreEntries = [".cibuild-secrets.json", ".ci/.envstore.json", ".ci/keys/", "build/"];
|
|
69
|
+
if (existsSync(gitignorePath)) {
|
|
70
|
+
const contents = readFileSync(gitignorePath, "utf-8");
|
|
71
|
+
const lines = contents.split("\n").map((l) => l.trim());
|
|
72
|
+
const toAdd = gitignoreEntries.filter((e) => !lines.includes(e));
|
|
73
|
+
if (toAdd.length > 0) {
|
|
74
|
+
appendFileSync(gitignorePath, `\n${toAdd.join("\n")}\n`);
|
|
75
|
+
for (const entry of toAdd) {
|
|
76
|
+
console.log(`✅ Added ${entry} to .gitignore`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
writeFileSync(gitignorePath, `${gitignoreEntries.join("\n")}\n`);
|
|
82
|
+
console.log(`✅ Created .gitignore with CI Build entries`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function handleInitCommand(opts = {}) {
|
|
86
|
+
const cwd = process.cwd();
|
|
87
|
+
// Verify we are inside a mobile project root before doing anything
|
|
88
|
+
const projectType = detectMobileProjectRoot(cwd);
|
|
89
|
+
if (!projectType) {
|
|
90
|
+
console.error("Error: Not a mobile project root.");
|
|
91
|
+
console.error("ci init must be run from the root folder of an Android or iOS project.");
|
|
92
|
+
console.error("\nAndroid projects must contain one of:");
|
|
93
|
+
console.error(" build.gradle, build.gradle.kts, settings.gradle, settings.gradle.kts, gradlew");
|
|
94
|
+
console.error("\niOS projects must contain one of:");
|
|
95
|
+
console.error(" *.xcodeproj, *.xcworkspace, Podfile");
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
// Bail out early if .ci already exists and has content
|
|
99
|
+
const ciDir = resolve(cwd, ".ci");
|
|
100
|
+
if (existsSync(ciDir)) {
|
|
101
|
+
const entries = readdirSync(ciDir);
|
|
102
|
+
if (entries.length > 0) {
|
|
103
|
+
console.error("Already initialised. Run 'ci run' to execute your pipeline.");
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
console.log(`\nDetected project type: ${projectType === "android" ? "Android" : "iOS"}`);
|
|
108
|
+
console.log(`
|
|
109
|
+
╔══════════════════════════════════════════════════════════════╗
|
|
110
|
+
║ CI Build - Dependency Check ║
|
|
111
|
+
╚══════════════════════════════════════════════════════════════╝
|
|
112
|
+
`);
|
|
113
|
+
const commonDependencies = [
|
|
114
|
+
{
|
|
115
|
+
name: "Git",
|
|
116
|
+
command: "git --version",
|
|
117
|
+
required: true,
|
|
118
|
+
installInstructions: "Download from https://git-scm.com/"
|
|
119
|
+
}
|
|
120
|
+
];
|
|
121
|
+
const androidDependencies = [
|
|
122
|
+
{
|
|
123
|
+
name: "Java (JDK)",
|
|
124
|
+
command: "java -version 2>&1 | head -1",
|
|
125
|
+
required: true,
|
|
126
|
+
installInstructions: "Download from https://adoptium.net/ or install via brew: brew install --cask temurin"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "Android SDK",
|
|
130
|
+
customCheck: () => {
|
|
131
|
+
const fromEnv = process.env.ANDROID_HOME;
|
|
132
|
+
if (fromEnv && existsSync(fromEnv)) {
|
|
133
|
+
return { found: true, message: `${fromEnv} (via ANDROID_HOME)` };
|
|
134
|
+
}
|
|
135
|
+
const commonPaths = [
|
|
136
|
+
`${homedir()}/Library/Android/sdk`, // macOS
|
|
137
|
+
`${homedir()}/Android/Sdk`, // Linux
|
|
138
|
+
"/usr/local/android-sdk",
|
|
139
|
+
];
|
|
140
|
+
const detected = commonPaths.find((p) => existsSync(p));
|
|
141
|
+
if (detected) {
|
|
142
|
+
return { found: true, message: `${detected} (auto-detected)` };
|
|
143
|
+
}
|
|
144
|
+
return { found: false, message: "Android SDK not found" };
|
|
145
|
+
},
|
|
146
|
+
required: true,
|
|
147
|
+
installInstructions: "Install Android Studio from https://developer.android.com/studio or set ANDROID_HOME manually"
|
|
148
|
+
}
|
|
149
|
+
];
|
|
150
|
+
const iosDependencies = [
|
|
151
|
+
{
|
|
152
|
+
name: "Xcode",
|
|
153
|
+
command: "xcodebuild -version 2>&1 | head -1",
|
|
154
|
+
required: true,
|
|
155
|
+
installInstructions: "Install Xcode from the Mac App Store"
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "CocoaPods",
|
|
159
|
+
command: "pod --version",
|
|
160
|
+
required: false,
|
|
161
|
+
installInstructions: "Run: sudo gem install cocoapods"
|
|
162
|
+
}
|
|
163
|
+
];
|
|
164
|
+
const dependencies = [
|
|
165
|
+
...commonDependencies,
|
|
166
|
+
...(projectType === "android" ? androidDependencies : iosDependencies),
|
|
167
|
+
];
|
|
168
|
+
let allRequired = true;
|
|
169
|
+
let missingOptional = false;
|
|
170
|
+
for (const dep of dependencies) {
|
|
171
|
+
if (dep.customCheck) {
|
|
172
|
+
const result = dep.customCheck();
|
|
173
|
+
if (result.found) {
|
|
174
|
+
console.log(`✅ ${dep.name.padEnd(20)} ${result.message}`);
|
|
175
|
+
}
|
|
176
|
+
else if (dep.required) {
|
|
177
|
+
console.log(`❌ ${dep.name.padEnd(20)} Not found (REQUIRED)`);
|
|
178
|
+
console.log(` Install: ${dep.installInstructions}`);
|
|
179
|
+
allRequired = false;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
console.log(`⚠️ ${dep.name.padEnd(20)} Not found (optional)`);
|
|
183
|
+
console.log(` Install: ${dep.installInstructions}`);
|
|
184
|
+
missingOptional = true;
|
|
185
|
+
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const output = execSync(dep.command, {
|
|
190
|
+
encoding: "utf-8",
|
|
191
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
192
|
+
shell: "/bin/sh",
|
|
193
|
+
}).trim();
|
|
194
|
+
console.log(`✅ ${dep.name.padEnd(20)} ${output}`);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
if (dep.required) {
|
|
198
|
+
console.log(`❌ ${dep.name.padEnd(20)} Not found (REQUIRED)`);
|
|
199
|
+
console.log(` Install: ${dep.installInstructions}`);
|
|
200
|
+
allRequired = false;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
console.log(`⚠️ ${dep.name.padEnd(20)} Not found (optional)`);
|
|
204
|
+
console.log(` Install: ${dep.installInstructions}`);
|
|
205
|
+
missingOptional = true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
console.log();
|
|
210
|
+
if (allRequired && !missingOptional) {
|
|
211
|
+
console.log("✨ All dependencies are installed and ready!");
|
|
212
|
+
}
|
|
213
|
+
else if (allRequired) {
|
|
214
|
+
console.log("✅ All required dependencies are installed!");
|
|
215
|
+
console.log("⚠️ Some optional dependencies are missing but you can still use CI Build.");
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
console.log("❌ Please install the missing required dependencies above.");
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
console.log();
|
|
222
|
+
// Determine action: non-interactive flags take priority over interactive prompts
|
|
223
|
+
let action;
|
|
224
|
+
if (opts.importPath) {
|
|
225
|
+
action = "import";
|
|
226
|
+
}
|
|
227
|
+
else if (opts.create) {
|
|
228
|
+
action = "create";
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
// Interactive mode — ask the user
|
|
232
|
+
const response = await prompts({
|
|
233
|
+
type: "select",
|
|
234
|
+
name: "action",
|
|
235
|
+
message: "How would you like to set up your pipeline?",
|
|
236
|
+
choices: [
|
|
237
|
+
{ title: "Import an existing YAML pipeline", value: "import" },
|
|
238
|
+
{ title: "Create a new pipeline", value: "create" },
|
|
239
|
+
],
|
|
240
|
+
});
|
|
241
|
+
action = response.action;
|
|
242
|
+
}
|
|
243
|
+
if (!action) {
|
|
244
|
+
console.log("\nCancelled.");
|
|
245
|
+
process.exit(0);
|
|
246
|
+
}
|
|
247
|
+
// Add CI Build runtime files to .gitignore (both create and import flows)
|
|
248
|
+
ensureCiBuildGitignoreEntries(cwd);
|
|
249
|
+
if (action === "create") {
|
|
250
|
+
await handleBuildCommand(detectMobileProjectRoot, { createPipelinesDir: true, nonInteractive: true });
|
|
251
|
+
process.exit(0);
|
|
252
|
+
}
|
|
253
|
+
// Import flow
|
|
254
|
+
let resolvedYaml;
|
|
255
|
+
if (opts.importPath) {
|
|
256
|
+
// Non-interactive: validate the provided path
|
|
257
|
+
const trimmed = opts.importPath.trim();
|
|
258
|
+
if (!existsSync(trimmed)) {
|
|
259
|
+
console.error(`Error: File not found: ${trimmed}`);
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
const ext = trimmed.split(".").pop()?.toLowerCase();
|
|
263
|
+
if (ext !== "yml" && ext !== "yaml") {
|
|
264
|
+
console.error("Error: File must be a .yml or .yaml file");
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
resolvedYaml = trimmed;
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
// Interactive: ask for the path
|
|
271
|
+
const { yamlPath } = await prompts({
|
|
272
|
+
type: "text",
|
|
273
|
+
name: "yamlPath",
|
|
274
|
+
message: "Enter the absolute path to your YAML pipeline file:",
|
|
275
|
+
validate: (value) => {
|
|
276
|
+
if (!value.trim())
|
|
277
|
+
return "Path is required";
|
|
278
|
+
if (!existsSync(value.trim()))
|
|
279
|
+
return `File not found: ${value.trim()}`;
|
|
280
|
+
const ext = value.trim().split(".").pop()?.toLowerCase();
|
|
281
|
+
if (ext !== "yml" && ext !== "yaml")
|
|
282
|
+
return "File must be a .yml or .yaml file";
|
|
283
|
+
return true;
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
if (!yamlPath) {
|
|
287
|
+
console.log("\nCancelled.");
|
|
288
|
+
process.exit(0);
|
|
289
|
+
}
|
|
290
|
+
resolvedYaml = yamlPath.trim();
|
|
291
|
+
}
|
|
292
|
+
const fileName = resolvedYaml.split("/").pop();
|
|
293
|
+
const pipelinesDir = resolve(ciDir, "pipelines");
|
|
294
|
+
const destPath = resolve(pipelinesDir, fileName);
|
|
295
|
+
mkdirSync(pipelinesDir, { recursive: true });
|
|
296
|
+
copyFileSync(resolvedYaml, destPath);
|
|
297
|
+
console.log(`\n✅ Created .ci/pipelines/`);
|
|
298
|
+
console.log(`✅ Copied pipeline: ${fileName}`);
|
|
299
|
+
// Generate GitHub Actions workflow — detect platform from imported YAML if possible
|
|
300
|
+
let importedPlatform = null;
|
|
301
|
+
try {
|
|
302
|
+
const yamlPipeline = loadYAMLPipeline(destPath);
|
|
303
|
+
const meta = yamlPipeline.meta?.["cibuild.io"];
|
|
304
|
+
if (meta?.platform === "ios" || meta?.platform === "android") {
|
|
305
|
+
importedPlatform = meta.platform;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch { /* ignore parse errors — default to macos-latest */ }
|
|
309
|
+
generateGitHubActionsWorkflow({ platform: importedPlatform, cwd });
|
|
310
|
+
const hasSecrets = existsSync(resolve(cwd, ".cibuild-secrets.json"));
|
|
311
|
+
const hasWorkflow = existsSync(resolve(cwd, ".github", "workflows", "ci.yml"));
|
|
312
|
+
console.log("\nNext steps:");
|
|
313
|
+
console.log(" ci run .ci/pipelines/cibuild.yml -w primary # Run locally");
|
|
314
|
+
if (hasWorkflow) {
|
|
315
|
+
if (hasSecrets) {
|
|
316
|
+
console.log(" ci secrets upload # Upload secrets to GitHub");
|
|
317
|
+
}
|
|
318
|
+
console.log(" git add . && git push # Push to GitHub - CI runs automatically");
|
|
319
|
+
}
|
|
320
|
+
console.log("");
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Shows an interactive workflow picker
|
|
324
|
+
* @param workflows Available workflow names
|
|
325
|
+
* @param pipelinePath Pipeline file path
|
|
326
|
+
* @returns Selected workflow name or undefined if cancelled
|
|
327
|
+
*/
|
|
328
|
+
async function promptForWorkflow(workflows) {
|
|
329
|
+
if (workflows.length === 0) {
|
|
330
|
+
console.error("Error: No workflows found in pipeline file");
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
333
|
+
if (workflows.length === 1) {
|
|
334
|
+
// Only one workflow, use it automatically
|
|
335
|
+
console.log(`Using workflow: ${workflows[0]}\n`);
|
|
336
|
+
return workflows[0];
|
|
337
|
+
}
|
|
338
|
+
// Multiple workflows, show interactive picker
|
|
339
|
+
const response = await prompts({
|
|
340
|
+
type: 'select',
|
|
341
|
+
name: 'workflow',
|
|
342
|
+
message: 'Select a workflow to run:',
|
|
343
|
+
choices: workflows.map((name) => ({ title: name, value: name })),
|
|
344
|
+
initial: 0,
|
|
345
|
+
});
|
|
346
|
+
if (!response.workflow) {
|
|
347
|
+
// User cancelled (Ctrl+C)
|
|
348
|
+
console.log('\nWorkflow selection cancelled');
|
|
349
|
+
return undefined;
|
|
350
|
+
}
|
|
351
|
+
console.log(); // Add blank line after selection
|
|
352
|
+
return response.workflow;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Runs pre-execution validation, separates issues into user-fillable (missing env vars)
|
|
356
|
+
* vs hard-blocking (missing commands, bad config), and collects all missing values
|
|
357
|
+
* upfront in a single form-like pass before execution begins.
|
|
358
|
+
*
|
|
359
|
+
* @returns true if it's safe to proceed, false if the user cancelled or hard errors exist
|
|
360
|
+
*/
|
|
361
|
+
async function promptForMissingVariables(yamlPipeline, workflowName, config, yamlFilePath) {
|
|
362
|
+
console.log(`\n🔍 Running pre-execution validation...`);
|
|
363
|
+
const validator = new StepValidator(yamlPipeline, workflowName, config, yamlFilePath);
|
|
364
|
+
const result = await validator.validateWorkflow();
|
|
365
|
+
const errorIssues = result.issues.filter((i) => i.requirement.severity === "error" && i.result && !i.result.passed);
|
|
366
|
+
const fillable = errorIssues.filter((i) => i.requirement.timing === "pre-execution" &&
|
|
367
|
+
(i.requirement.category === "environment" || i.requirement.category === "input"));
|
|
368
|
+
const blocking = errorIssues.filter((i) => !(i.requirement.timing === "pre-execution" &&
|
|
369
|
+
(i.requirement.category === "environment" || i.requirement.category === "input")));
|
|
370
|
+
// Hard errors that the user can't fix interactively (missing commands, etc.)
|
|
371
|
+
if (blocking.length > 0) {
|
|
372
|
+
console.log(formatValidationResult(result));
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
// Warnings and info only — nothing missing
|
|
376
|
+
if (fillable.length === 0) {
|
|
377
|
+
if (result.counts.warnings > 0 || result.counts.info > 0) {
|
|
378
|
+
console.log(formatValidationResult(result));
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
console.log("✅ Pre-execution validation passed\n");
|
|
382
|
+
}
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
// Show all missing variables upfront
|
|
386
|
+
console.log(`\n${fillable.length} required value(s) missing — provide them to continue:\n`);
|
|
387
|
+
for (const issue of fillable) {
|
|
388
|
+
const stepInfo = issue.stepName ? ` needed by: ${issue.stepName}` : "";
|
|
389
|
+
console.log(` • ${issue.requirement.name.padEnd(30)}${stepInfo}`);
|
|
390
|
+
}
|
|
391
|
+
console.log();
|
|
392
|
+
const handler = new MissingEnvHandler({
|
|
393
|
+
interactive: true,
|
|
394
|
+
workflow: workflowName,
|
|
395
|
+
});
|
|
396
|
+
for (let i = 0; i < fillable.length; i++) {
|
|
397
|
+
const issue = fillable[i];
|
|
398
|
+
const error = new MissingEnvironmentVariableError(issue.requirement.name, issue.stepName, issue.requirement.hint);
|
|
399
|
+
// Patch the counter into the box header label
|
|
400
|
+
const original = console.log;
|
|
401
|
+
const label = `(${i + 1} of ${fillable.length})`;
|
|
402
|
+
let patched = false;
|
|
403
|
+
console.log = (...args) => {
|
|
404
|
+
if (!patched && typeof args[0] === "string" && args[0].includes("MISSING REQUIRED")) {
|
|
405
|
+
original(`║ MISSING REQUIRED ENVIRONMENT VARIABLE ${label.padEnd(28)}║`);
|
|
406
|
+
patched = true;
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
original(...args);
|
|
410
|
+
};
|
|
411
|
+
const handleResult = await handler.handleMissingVariable(error);
|
|
412
|
+
console.log = original;
|
|
413
|
+
if (handleResult.cancelled) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
handler.close();
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
async function main() {
|
|
421
|
+
const args = process.argv.slice(2);
|
|
422
|
+
if (args[0] === "--version" || args[0] === "-v") {
|
|
423
|
+
console.log(CIBUILD_VERSION);
|
|
424
|
+
process.exit(0);
|
|
425
|
+
}
|
|
426
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
427
|
+
console.log(`
|
|
428
|
+
CI Build - Lightweight CI/CD Pipeline Runner
|
|
429
|
+
|
|
430
|
+
Usage:
|
|
431
|
+
ci init Check dependencies + set up pipeline (interactive)
|
|
432
|
+
ci init --create Check dependencies + create pipeline (non-interactive)
|
|
433
|
+
ci init --import <path> Check dependencies + import YAML pipeline (non-interactive)
|
|
434
|
+
ci build Generate a standard pipeline for the current project
|
|
435
|
+
ci run <path> [-w <name>] Run locally (development mode)
|
|
436
|
+
ci run <path> [-w <name>] --production Run on remote runner (production)
|
|
437
|
+
ci run <path> [-w <name>] --validate-only Validate only, don't execute
|
|
438
|
+
ci run <path> [-w <name>] --skip-validation Skip validation, run with interactive prompts
|
|
439
|
+
ci validate <path> [-w <name>] Validate pipeline (alias for --validate-only)
|
|
440
|
+
ci detect-platform <path> [-w <name>] Detect platform from YAML pipeline
|
|
441
|
+
ci reset [--force] Remove all cibuild files and folders
|
|
442
|
+
ci edit <path> [-w <name>] View pipeline and edit step inputs
|
|
443
|
+
ci secrets add <var_name> <path> [-w <name>] Add a secret (prompted interactively)
|
|
444
|
+
ci secrets add <var_name> <path> --file <file> [-w <name>] Add a secret from a file
|
|
445
|
+
ci secrets upload [--env <name>] [--repo <owner/repo>] [--dry-run] Upload secrets to GitHub environment
|
|
446
|
+
ci --version Show version
|
|
447
|
+
ci --help Show this help
|
|
448
|
+
|
|
449
|
+
Options:
|
|
450
|
+
--workflow, -w <name> Workflow name (YAML only, defaults to first workflow)
|
|
451
|
+
--production Execute on remote runner after validation (vs local)
|
|
452
|
+
--validate-only Run validation only, don't execute pipeline
|
|
453
|
+
--skip-validation Skip pre-execution validation (for development)
|
|
454
|
+
--help, -h Show this help message
|
|
455
|
+
|
|
456
|
+
Supported Formats:
|
|
457
|
+
.ts TypeScript pipeline files
|
|
458
|
+
.yml, .yaml YAML pipeline files
|
|
459
|
+
|
|
460
|
+
Examples:
|
|
461
|
+
ci init # Interactive setup
|
|
462
|
+
ci init --create # Non-interactive: create pipeline
|
|
463
|
+
ci init --import pipeline.yml # Non-interactive: import YAML
|
|
464
|
+
ci build # Generate standard pipeline
|
|
465
|
+
ci run examples/android-build.yml -w pull-request-java-17
|
|
466
|
+
ci run build.pipeline.ts
|
|
467
|
+
ci run ios-build.yml
|
|
468
|
+
ci run android.yml --workflow qa
|
|
469
|
+
ci run multi-platform.yaml -w ios-build
|
|
470
|
+
ci run android.yml --production # Run on remote runner
|
|
471
|
+
ci validate ios-build.yml # Validate only
|
|
472
|
+
ci detect-platform ios-build.yml
|
|
473
|
+
ci secrets add SLACK_CHANNEL bitrise.yml # Add secret (prompted)
|
|
474
|
+
ci secrets add GOOGLE_SERVICES_JSON bitrise.yml --file app/google-services.json
|
|
475
|
+
ci detect-platform multi-platform.yaml -w android
|
|
476
|
+
ci edit android-build.yml # View + edit step inputs
|
|
477
|
+
ci edit android-build.yml -w debug # Edit specific workflow
|
|
478
|
+
`);
|
|
479
|
+
process.exit(0);
|
|
480
|
+
}
|
|
481
|
+
// Handle envman subcommand — delegates to envman CLI
|
|
482
|
+
if (args[0] === "envman") {
|
|
483
|
+
// Re-write process.argv so envman sees its own args
|
|
484
|
+
process.argv = [process.argv[0], "envman", ...args.slice(1)];
|
|
485
|
+
await import("./envman/cli.js");
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
// Handle init command
|
|
489
|
+
if (args[0] === "init") {
|
|
490
|
+
const importIdx = args.indexOf("--import");
|
|
491
|
+
const importPath = importIdx !== -1 ? args[importIdx + 1] : undefined;
|
|
492
|
+
const createFlag = args.includes("--create");
|
|
493
|
+
await handleInitCommand({ importPath, create: createFlag });
|
|
494
|
+
process.exit(0);
|
|
495
|
+
}
|
|
496
|
+
// Handle reset command
|
|
497
|
+
if (args[0] === "reset") {
|
|
498
|
+
const force = args.includes("--force") || args.includes("-f");
|
|
499
|
+
await handleResetCommand({ force });
|
|
500
|
+
process.exit(0);
|
|
501
|
+
}
|
|
502
|
+
// Handle build command
|
|
503
|
+
if (args[0] === "build") {
|
|
504
|
+
await handleBuildCommand(detectMobileProjectRoot);
|
|
505
|
+
process.exit(0);
|
|
506
|
+
}
|
|
507
|
+
// Handle secrets command
|
|
508
|
+
if (args[0] === "secrets") {
|
|
509
|
+
const subCommand = args[1];
|
|
510
|
+
if (subCommand === "upload") {
|
|
511
|
+
const envFlagIdx = args.findIndex((a) => a === '--env' || a === '-e');
|
|
512
|
+
const envName = envFlagIdx !== -1 ? args[envFlagIdx + 1] : 'cibuild';
|
|
513
|
+
const repoFlagIdx = args.findIndex((a) => a === '--repo' || a === '-r');
|
|
514
|
+
const repo = repoFlagIdx !== -1 ? args[repoFlagIdx + 1] : undefined;
|
|
515
|
+
const dryRun = args.includes('--dry-run');
|
|
516
|
+
await handleSecretsUploadCommand({ envName, repo, dryRun });
|
|
517
|
+
process.exit(0);
|
|
518
|
+
}
|
|
519
|
+
if (subCommand === "sync-workflow") {
|
|
520
|
+
const pathFlagIdx = args.findIndex((a) => a === '--path' || a === '-p');
|
|
521
|
+
let workflowPath = pathFlagIdx !== -1 ? args[pathFlagIdx + 1] : undefined;
|
|
522
|
+
if (!workflowPath) {
|
|
523
|
+
// Auto-discover workflow files
|
|
524
|
+
const ghWorkflowDir = resolve(process.cwd(), '.github', 'workflows');
|
|
525
|
+
const discoveredFiles = [];
|
|
526
|
+
if (existsSync(ghWorkflowDir)) {
|
|
527
|
+
try {
|
|
528
|
+
const entries = readdirSync(ghWorkflowDir);
|
|
529
|
+
for (const entry of entries) {
|
|
530
|
+
if (entry.endsWith('.yml') || entry.endsWith('.yaml')) {
|
|
531
|
+
discoveredFiles.push(`.github/workflows/${entry}`);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
catch { /* ignore read errors */ }
|
|
536
|
+
}
|
|
537
|
+
const choices = discoveredFiles.map((f) => ({
|
|
538
|
+
title: f,
|
|
539
|
+
value: f,
|
|
540
|
+
}));
|
|
541
|
+
choices.push({ title: 'Enter path manually…', value: '__manual__' });
|
|
542
|
+
if (choices.length === 1) {
|
|
543
|
+
// Only the manual option — no files found
|
|
544
|
+
console.log('No workflow files found in .github/workflows/\n');
|
|
545
|
+
}
|
|
546
|
+
const { selected } = await prompts({
|
|
547
|
+
type: 'select',
|
|
548
|
+
name: 'selected',
|
|
549
|
+
message: 'Select a workflow file to sync secrets into:',
|
|
550
|
+
choices,
|
|
551
|
+
});
|
|
552
|
+
if (!selected) {
|
|
553
|
+
process.exit(0);
|
|
554
|
+
}
|
|
555
|
+
if (selected === '__manual__') {
|
|
556
|
+
const { manualPath } = await prompts({
|
|
557
|
+
type: 'text',
|
|
558
|
+
name: 'manualPath',
|
|
559
|
+
message: 'Enter the path to the workflow YAML file:',
|
|
560
|
+
validate: (v) => v.trim() ? true : 'Path is required',
|
|
561
|
+
});
|
|
562
|
+
if (!manualPath) {
|
|
563
|
+
process.exit(0);
|
|
564
|
+
}
|
|
565
|
+
workflowPath = manualPath;
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
workflowPath = selected;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const dryRun = args.includes('--dry-run');
|
|
572
|
+
await handleSecretsSyncWorkflowCommand({ workflowPath: resolve(process.cwd(), workflowPath), dryRun });
|
|
573
|
+
process.exit(0);
|
|
574
|
+
}
|
|
575
|
+
if (subCommand !== "add") {
|
|
576
|
+
console.error("Error: Unknown secrets subcommand. Available: add, upload, sync-workflow");
|
|
577
|
+
console.error("Usage: ci secrets add <var_name> <pipeline_path> [--file <file>] [--workflow <name>]");
|
|
578
|
+
console.error(" ci secrets upload [--env <name>] [--repo <owner/repo>] [--dry-run]");
|
|
579
|
+
console.error(" ci secrets sync-workflow --path <workflow.yml> [--dry-run]");
|
|
580
|
+
process.exit(1);
|
|
581
|
+
}
|
|
582
|
+
// ci secrets add <var_name> <pipeline_path> [--file <file>] [--workflow <name>]
|
|
583
|
+
const varName = args[2];
|
|
584
|
+
const pipelinePath = args[3];
|
|
585
|
+
if (!varName || !pipelinePath) {
|
|
586
|
+
console.error("Error: Variable name and pipeline path are required");
|
|
587
|
+
console.error("Usage: ci secrets add <var_name> <pipeline_path> [--file <file>] [-w <workflow>]");
|
|
588
|
+
console.error("\nExamples:");
|
|
589
|
+
console.error(" ci secrets add SLACK_CHANNEL bitrise.yml # prompted interactively");
|
|
590
|
+
console.error(" ci secrets add GOOGLE_SERVICES_JSON bitrise.yml --file google-services.json # read from file");
|
|
591
|
+
process.exit(1);
|
|
592
|
+
}
|
|
593
|
+
const resolvedPath = resolve(process.cwd(), pipelinePath);
|
|
594
|
+
if (!existsSync(resolvedPath)) {
|
|
595
|
+
console.error(`Error: Pipeline file not found: ${resolvedPath}`);
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
// Only works with YAML files
|
|
599
|
+
const fileExt = extname(resolvedPath).toLowerCase();
|
|
600
|
+
const isYAML = fileExt === '.yml' || fileExt === '.yaml';
|
|
601
|
+
if (!isYAML) {
|
|
602
|
+
console.error("Error: secrets command only works with YAML pipeline files (.yml, .yaml)");
|
|
603
|
+
process.exit(1);
|
|
604
|
+
}
|
|
605
|
+
// Parse --file / -f flag
|
|
606
|
+
const fileFlagIdx = args.findIndex((a) => a === '--file' || a === '-f');
|
|
607
|
+
const secretFilePath = fileFlagIdx !== -1 ? args[fileFlagIdx + 1] : undefined;
|
|
608
|
+
// Parse -w / --workflow flag
|
|
609
|
+
const workflowFlagIdx = args.findIndex((a) => a === '-w' || a === '--workflow');
|
|
610
|
+
const workflowArg = workflowFlagIdx !== -1 ? args[workflowFlagIdx + 1] : undefined;
|
|
611
|
+
try {
|
|
612
|
+
const secretsManager = new SecretsManager();
|
|
613
|
+
console.log('\n╔═══════════════════════════════════════════════════════════════════╗');
|
|
614
|
+
console.log('║ ADD SECRET ║');
|
|
615
|
+
console.log('╚═══════════════════════════════════════════════════════════════════╝\n');
|
|
616
|
+
console.log(`Variable: ${varName}`);
|
|
617
|
+
console.log(`Location: ${secretsManager.getSecretsFilePath()}`);
|
|
618
|
+
// Check if this variable already exists
|
|
619
|
+
const existingSecretId = secretsManager.getSecretIdByName(varName);
|
|
620
|
+
if (existingSecretId) {
|
|
621
|
+
console.log(`\nℹ️ This variable already exists in your secrets file.`);
|
|
622
|
+
const confirmResponse = await prompts({
|
|
623
|
+
type: 'confirm',
|
|
624
|
+
name: 'value',
|
|
625
|
+
message: 'Update existing value?',
|
|
626
|
+
initial: true,
|
|
627
|
+
});
|
|
628
|
+
if (!confirmResponse.value) {
|
|
629
|
+
console.log('\n❌ Cancelled');
|
|
630
|
+
process.exit(0);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
let secretValue;
|
|
634
|
+
if (secretFilePath) {
|
|
635
|
+
// Read value from file
|
|
636
|
+
const resolvedFilePath = resolve(process.cwd(), secretFilePath);
|
|
637
|
+
if (!existsSync(resolvedFilePath)) {
|
|
638
|
+
console.error(`\nError: File not found: ${resolvedFilePath}`);
|
|
639
|
+
process.exit(1);
|
|
640
|
+
}
|
|
641
|
+
secretValue = readFileSync(resolvedFilePath, 'utf-8');
|
|
642
|
+
console.log(`\nReading value from: ${secretFilePath}`);
|
|
643
|
+
}
|
|
644
|
+
else {
|
|
645
|
+
// Collect value interactively via readline (handles multiline paste cleanly)
|
|
646
|
+
const { createInterface } = await import('node:readline');
|
|
647
|
+
console.log(`\n Enter value for ${varName}`);
|
|
648
|
+
console.log(' Paste content, then type . on its own line and press Enter:\n');
|
|
649
|
+
if (process.stdin.isTTY) {
|
|
650
|
+
try {
|
|
651
|
+
process.stdin.setRawMode(false);
|
|
652
|
+
}
|
|
653
|
+
catch { /* ignore */ }
|
|
654
|
+
}
|
|
655
|
+
process.stdin.resume();
|
|
656
|
+
secretValue = await new Promise((res) => {
|
|
657
|
+
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
658
|
+
const lines = [];
|
|
659
|
+
rl.on('line', (line) => {
|
|
660
|
+
if (line.trim() === '.') {
|
|
661
|
+
rl.close();
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
lines.push(line);
|
|
665
|
+
});
|
|
666
|
+
rl.once('close', () => { process.stdin.pause(); res(lines.join('\n')); });
|
|
667
|
+
rl.on('SIGINT', () => { rl.close(); res(''); });
|
|
668
|
+
});
|
|
669
|
+
if (!secretValue.trim()) {
|
|
670
|
+
console.log('\n❌ Cancelled');
|
|
671
|
+
process.exit(0);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
secretsManager.storeSecret(varName, secretValue, workflowArg);
|
|
675
|
+
console.log(`\n✅ Secret stored: ${varName}${workflowArg ? ` (workflow: ${workflowArg})` : ' (global)'}`);
|
|
676
|
+
console.log(` Location: ${secretsManager.getSecretsFilePath()}`);
|
|
677
|
+
console.log(`\n💡 This value will be loaded automatically when you run the pipeline.\n`);
|
|
678
|
+
process.exit(0);
|
|
679
|
+
}
|
|
680
|
+
catch (error) {
|
|
681
|
+
console.error("\n✗ Failed to add secret:");
|
|
682
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
683
|
+
process.exit(1);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
// Handle edit command
|
|
687
|
+
if (args[0] === "edit") {
|
|
688
|
+
const pipelineIndex = args.findIndex((arg) => arg === "--pipeline" || arg === "-p");
|
|
689
|
+
let pipelinePath;
|
|
690
|
+
if (pipelineIndex !== -1 && args[pipelineIndex + 1]) {
|
|
691
|
+
pipelinePath = resolve(process.cwd(), args[pipelineIndex + 1]);
|
|
692
|
+
}
|
|
693
|
+
else if (args[1] && !args[1].startsWith("-")) {
|
|
694
|
+
pipelinePath = resolve(process.cwd(), args[1]);
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
console.error("Error: Pipeline path is required");
|
|
698
|
+
console.error("Usage: ci edit <path> [-w <name>]");
|
|
699
|
+
process.exit(1);
|
|
700
|
+
}
|
|
701
|
+
const workflowIndex = args.findIndex((arg) => arg === "--workflow" || arg === "-w");
|
|
702
|
+
const workflowName = workflowIndex !== -1 && args[workflowIndex + 1]
|
|
703
|
+
? args[workflowIndex + 1]
|
|
704
|
+
: undefined;
|
|
705
|
+
if (!existsSync(pipelinePath)) {
|
|
706
|
+
console.error(`Error: Pipeline file not found: ${pipelinePath}`);
|
|
707
|
+
process.exit(1);
|
|
708
|
+
}
|
|
709
|
+
const fileExt = extname(pipelinePath).toLowerCase();
|
|
710
|
+
if (fileExt !== ".yml" && fileExt !== ".yaml") {
|
|
711
|
+
console.error("Error: edit only works with YAML pipeline files (.yml, .yaml)");
|
|
712
|
+
process.exit(1);
|
|
713
|
+
}
|
|
714
|
+
try {
|
|
715
|
+
await handleEditCommand(pipelinePath, workflowName);
|
|
716
|
+
process.exit(0);
|
|
717
|
+
}
|
|
718
|
+
catch (error) {
|
|
719
|
+
console.error("\n✗ Edit failed:");
|
|
720
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
721
|
+
process.exit(1);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
// Check for validate command or --validate-only flag
|
|
725
|
+
const isValidateCommand = args[0] === "validate";
|
|
726
|
+
const hasValidateOnlyFlag = args.includes("--validate-only");
|
|
727
|
+
if (isValidateCommand || (args[0] === "run" && hasValidateOnlyFlag)) {
|
|
728
|
+
// Support both: ci validate <path> and ci validate --pipeline <path>
|
|
729
|
+
const pipelineIndex = args.findIndex((arg) => arg === "--pipeline" || arg === "-p");
|
|
730
|
+
let pipelinePath;
|
|
731
|
+
if (pipelineIndex !== -1 && args[pipelineIndex + 1]) {
|
|
732
|
+
pipelinePath = resolve(process.cwd(), args[pipelineIndex + 1]);
|
|
733
|
+
}
|
|
734
|
+
else if (args[1] && !args[1].startsWith("-")) {
|
|
735
|
+
// Second argument is the path (e.g., ci validate <path>)
|
|
736
|
+
pipelinePath = resolve(process.cwd(), args[1]);
|
|
737
|
+
}
|
|
738
|
+
else {
|
|
739
|
+
console.error("Error: Pipeline path is required");
|
|
740
|
+
console.error("Usage: ci validate <path> [--workflow <name>]");
|
|
741
|
+
console.error(" or: ci validate --pipeline <path> [--workflow <name>]");
|
|
742
|
+
process.exit(1);
|
|
743
|
+
}
|
|
744
|
+
const workflowIndex = args.findIndex((arg) => arg === "--workflow" || arg === "-w");
|
|
745
|
+
const workflowName = workflowIndex !== -1 && args[workflowIndex + 1]
|
|
746
|
+
? args[workflowIndex + 1]
|
|
747
|
+
: undefined;
|
|
748
|
+
if (!existsSync(pipelinePath)) {
|
|
749
|
+
console.error(`Error: Pipeline file not found: ${pipelinePath}`);
|
|
750
|
+
process.exit(1);
|
|
751
|
+
}
|
|
752
|
+
const fileExt = extname(pipelinePath).toLowerCase();
|
|
753
|
+
const isYAML = fileExt === '.yml' || fileExt === '.yaml';
|
|
754
|
+
if (!isYAML) {
|
|
755
|
+
console.error("Error: validate only works with YAML pipeline files (.yml, .yaml)");
|
|
756
|
+
process.exit(1);
|
|
757
|
+
}
|
|
758
|
+
try {
|
|
759
|
+
const config = loadConfig();
|
|
760
|
+
const yamlPipeline = loadYAMLPipeline(pipelinePath);
|
|
761
|
+
// Prompt for workflow if not specified
|
|
762
|
+
let selectedWorkflow = workflowName;
|
|
763
|
+
if (!selectedWorkflow) {
|
|
764
|
+
const workflows = Object.keys(yamlPipeline.workflows);
|
|
765
|
+
selectedWorkflow = await promptForWorkflow(workflows);
|
|
766
|
+
if (!selectedWorkflow) {
|
|
767
|
+
process.exit(1);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
console.log(`🔍 Validating workflow: ${selectedWorkflow}`);
|
|
771
|
+
console.log(` Pipeline: ${pipelinePath}\n`);
|
|
772
|
+
const validator = new StepValidator(yamlPipeline, selectedWorkflow, config, pipelinePath);
|
|
773
|
+
const result = await validator.validateWorkflow();
|
|
774
|
+
console.log(formatValidationResult(result));
|
|
775
|
+
process.exit(result.valid ? 0 : 1);
|
|
776
|
+
}
|
|
777
|
+
catch (error) {
|
|
778
|
+
console.error("\n✗ Validation failed:");
|
|
779
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
780
|
+
process.exit(1);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (args[0] === "detect-platform") {
|
|
784
|
+
// Support both: ci detect-platform <path> and ci detect-platform --pipeline <path>
|
|
785
|
+
const pipelineIndex = args.findIndex((arg) => arg === "--pipeline" || arg === "-p");
|
|
786
|
+
let pipelinePath;
|
|
787
|
+
if (pipelineIndex !== -1 && args[pipelineIndex + 1]) {
|
|
788
|
+
pipelinePath = resolve(process.cwd(), args[pipelineIndex + 1]);
|
|
789
|
+
}
|
|
790
|
+
else if (args[1] && !args[1].startsWith("-")) {
|
|
791
|
+
// Second argument is the path (e.g., ci detect-platform <path>)
|
|
792
|
+
pipelinePath = resolve(process.cwd(), args[1]);
|
|
793
|
+
}
|
|
794
|
+
else {
|
|
795
|
+
console.error("Error: Pipeline path is required");
|
|
796
|
+
console.error("Usage: ci detect-platform <path> [--workflow <name>]");
|
|
797
|
+
console.error(" or: ci detect-platform --pipeline <path> [--workflow <name>]");
|
|
798
|
+
process.exit(1);
|
|
799
|
+
}
|
|
800
|
+
// Check for --workflow parameter (optional)
|
|
801
|
+
const workflowIndex = args.findIndex((arg) => arg === "--workflow" || arg === "-w");
|
|
802
|
+
const workflowName = workflowIndex !== -1 && args[workflowIndex + 1]
|
|
803
|
+
? args[workflowIndex + 1]
|
|
804
|
+
: undefined;
|
|
805
|
+
if (!existsSync(pipelinePath)) {
|
|
806
|
+
console.error(`Error: Pipeline file not found: ${pipelinePath}`);
|
|
807
|
+
process.exit(1);
|
|
808
|
+
}
|
|
809
|
+
// Only works with YAML files
|
|
810
|
+
const fileExt = extname(pipelinePath).toLowerCase();
|
|
811
|
+
const isYAML = fileExt === '.yml' || fileExt === '.yaml';
|
|
812
|
+
if (!isYAML) {
|
|
813
|
+
console.error("Error: detect-platform only works with YAML pipeline files (.yml, .yaml)");
|
|
814
|
+
process.exit(1);
|
|
815
|
+
}
|
|
816
|
+
try {
|
|
817
|
+
const yamlPipeline = loadYAMLPipeline(pipelinePath);
|
|
818
|
+
// Prompt for workflow if not specified
|
|
819
|
+
let selectedWorkflow = workflowName;
|
|
820
|
+
if (!selectedWorkflow) {
|
|
821
|
+
const workflows = Object.keys(yamlPipeline.workflows);
|
|
822
|
+
selectedWorkflow = await promptForWorkflow(workflows);
|
|
823
|
+
if (!selectedWorkflow) {
|
|
824
|
+
process.exit(1);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
const platformInfo = detectPlatformInfo(yamlPipeline, selectedWorkflow);
|
|
828
|
+
console.log(`\nPlatform Detection Results:`);
|
|
829
|
+
console.log(`─────────────────────────────`);
|
|
830
|
+
console.log(`Pipeline: ${pipelinePath}`);
|
|
831
|
+
console.log(`Workflow: ${selectedWorkflow}`);
|
|
832
|
+
console.log(`Platform: ${platformInfo.platform}`);
|
|
833
|
+
console.log(`Stack: ${platformInfo.stack || 'N/A'}`);
|
|
834
|
+
console.log(`Machine Type: ${platformInfo.machineType || 'N/A'}`);
|
|
835
|
+
console.log();
|
|
836
|
+
process.exit(0);
|
|
837
|
+
}
|
|
838
|
+
catch (error) {
|
|
839
|
+
console.error("\n✗ Platform detection failed:");
|
|
840
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
841
|
+
process.exit(1);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
else if (args[0] === "run") {
|
|
845
|
+
// Support both: ci run <path> and ci run --pipeline <path>
|
|
846
|
+
const pipelineIndex = args.findIndex((arg) => arg === "--pipeline" || arg === "-p");
|
|
847
|
+
let pipelinePath;
|
|
848
|
+
if (pipelineIndex !== -1 && args[pipelineIndex + 1]) {
|
|
849
|
+
pipelinePath = resolve(process.cwd(), args[pipelineIndex + 1]);
|
|
850
|
+
}
|
|
851
|
+
else if (args[1] && !args[1].startsWith("-")) {
|
|
852
|
+
// Second argument is the path (e.g., ci run <path>)
|
|
853
|
+
pipelinePath = resolve(process.cwd(), args[1]);
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
// No path given — check for .ci/pipelines/
|
|
857
|
+
const pipelinesDir = resolve(process.cwd(), ".ci", "pipelines");
|
|
858
|
+
if (!existsSync(pipelinesDir)) {
|
|
859
|
+
console.error("Project not initialised. Run 'ci init' first.");
|
|
860
|
+
process.exit(1);
|
|
861
|
+
}
|
|
862
|
+
const yamlFiles = readdirSync(pipelinesDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
|
|
863
|
+
if (yamlFiles.length === 0) {
|
|
864
|
+
console.error("No pipelines found in .ci/pipelines/. Run 'ci init' to import one.");
|
|
865
|
+
process.exit(1);
|
|
866
|
+
}
|
|
867
|
+
if (yamlFiles.length === 1) {
|
|
868
|
+
pipelinePath = resolve(pipelinesDir, yamlFiles[0]);
|
|
869
|
+
console.log(`Using pipeline: ${yamlFiles[0]}`);
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
const { selected } = await prompts({
|
|
873
|
+
type: "select",
|
|
874
|
+
name: "selected",
|
|
875
|
+
message: "Select a pipeline to run:",
|
|
876
|
+
choices: yamlFiles.map((f) => ({ title: f, value: resolve(pipelinesDir, f) })),
|
|
877
|
+
});
|
|
878
|
+
if (!selected) {
|
|
879
|
+
console.log("Cancelled.");
|
|
880
|
+
process.exit(0);
|
|
881
|
+
}
|
|
882
|
+
pipelinePath = selected;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
// Check for --workflow parameter (optional, only used for YAML)
|
|
886
|
+
const workflowIndex = args.findIndex((arg) => arg === "--workflow" || arg === "-w");
|
|
887
|
+
const workflowName = workflowIndex !== -1 && args[workflowIndex + 1]
|
|
888
|
+
? args[workflowIndex + 1]
|
|
889
|
+
: undefined;
|
|
890
|
+
// Check for --production, --skip-validation, and --local flags
|
|
891
|
+
const isProduction = args.includes("--production");
|
|
892
|
+
const skipValidation = args.includes("--skip-validation");
|
|
893
|
+
const isLocal = args.includes("--local");
|
|
894
|
+
if (!existsSync(pipelinePath)) {
|
|
895
|
+
console.error(`Error: Pipeline file not found: ${pipelinePath}`);
|
|
896
|
+
process.exit(1);
|
|
897
|
+
}
|
|
898
|
+
try {
|
|
899
|
+
const config = loadConfig();
|
|
900
|
+
config.local = isLocal;
|
|
901
|
+
const runner = new PipelineRunner(config);
|
|
902
|
+
// Detect file type based on extension
|
|
903
|
+
const fileExt = extname(pipelinePath).toLowerCase();
|
|
904
|
+
const isYAML = fileExt === '.yml' || fileExt === '.yaml';
|
|
905
|
+
const isJavaScriptModule = fileExt === '.ts' || fileExt === '.js';
|
|
906
|
+
let pipeline;
|
|
907
|
+
if (isYAML) {
|
|
908
|
+
// Load YAML pipeline
|
|
909
|
+
console.log(`Loading YAML pipeline: ${pipelinePath}`);
|
|
910
|
+
const yamlPipeline = loadYAMLPipeline(pipelinePath);
|
|
911
|
+
// Prompt for workflow if not specified
|
|
912
|
+
let selectedWorkflow = workflowName;
|
|
913
|
+
if (!selectedWorkflow) {
|
|
914
|
+
const workflows = Object.keys(yamlPipeline.workflows);
|
|
915
|
+
selectedWorkflow = await promptForWorkflow(workflows);
|
|
916
|
+
if (!selectedWorkflow) {
|
|
917
|
+
process.exit(1);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
console.log(`Using workflow: ${workflowName}`);
|
|
922
|
+
}
|
|
923
|
+
// Run pre-execution validation and collect missing variables (unless skipped)
|
|
924
|
+
if (skipValidation) {
|
|
925
|
+
console.log(`\n⏭️ Skipping validation (--skip-validation flag set)\n`);
|
|
926
|
+
}
|
|
927
|
+
else {
|
|
928
|
+
const canProceed = await promptForMissingVariables(yamlPipeline, selectedWorkflow, config, pipelinePath);
|
|
929
|
+
if (!canProceed) {
|
|
930
|
+
console.error("\n✗ Pipeline cannot run. Fix the errors above before continuing.");
|
|
931
|
+
process.exit(1);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
// Check if production mode
|
|
935
|
+
if (isProduction) {
|
|
936
|
+
console.log("═".repeat(60));
|
|
937
|
+
console.log("🚀 PRODUCTION MODE");
|
|
938
|
+
console.log("═".repeat(60));
|
|
939
|
+
console.log("\nValidation passed. Pipeline would be dispatched to remote runner.");
|
|
940
|
+
console.log("(Remote runner integration not yet implemented)\n");
|
|
941
|
+
console.log("For now, use without --production flag to run locally.\n");
|
|
942
|
+
process.exit(0);
|
|
943
|
+
}
|
|
944
|
+
// Convert and run locally (development mode)
|
|
945
|
+
const conversionResult = await convertYAMLWithSecrets(yamlPipeline, {
|
|
946
|
+
config,
|
|
947
|
+
workflowName: selectedWorkflow,
|
|
948
|
+
yamlFilePath: pipelinePath,
|
|
949
|
+
interactive: true,
|
|
950
|
+
});
|
|
951
|
+
pipeline = conversionResult.pipeline;
|
|
952
|
+
// Run pipeline with warnings
|
|
953
|
+
await runner.runPipeline(pipeline, conversionResult.warnings, conversionResult.skippedSteps);
|
|
954
|
+
}
|
|
955
|
+
else if (isJavaScriptModule) {
|
|
956
|
+
// Load TypeScript/JavaScript pipeline module
|
|
957
|
+
console.log(`Loading pipeline: ${pipelinePath}`);
|
|
958
|
+
const pipelineUrl = pathToFileURL(pipelinePath).href;
|
|
959
|
+
const pipelineModule = await import(pipelineUrl);
|
|
960
|
+
pipeline = pipelineModule.default;
|
|
961
|
+
if (!pipeline || typeof pipeline !== "object") {
|
|
962
|
+
throw new Error("Pipeline file must export a default PipelineDef object");
|
|
963
|
+
}
|
|
964
|
+
// Run pipeline without warnings (TypeScript pipelines don't have warnings)
|
|
965
|
+
await runner.runPipeline(pipeline);
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
throw new Error(`Unsupported pipeline file format: ${fileExt}\n` +
|
|
969
|
+
`Supported formats: .yml, .yaml, .ts, .js`);
|
|
970
|
+
}
|
|
971
|
+
console.log("\n✓ All steps completed successfully!\n");
|
|
972
|
+
process.exit(0);
|
|
973
|
+
}
|
|
974
|
+
catch (error) {
|
|
975
|
+
console.error("\n✗ Pipeline failed:");
|
|
976
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
977
|
+
process.exit(1);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
console.error(`Unknown command: ${args[0]}`);
|
|
982
|
+
console.error("Run 'ci --help' for usage information");
|
|
983
|
+
process.exit(1);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
main();
|
|
987
|
+
//# sourceMappingURL=cli.js.map
|