@invarn/cibuild 1.4.0 → 1.4.2

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.
Files changed (39) hide show
  1. package/dist/cli.cjs +1 -1
  2. package/dist/src/cli.d.ts.map +1 -1
  3. package/dist/src/cli.js +56 -678
  4. package/dist/src/commands/detect-platform.d.ts +13 -0
  5. package/dist/src/commands/detect-platform.d.ts.map +1 -0
  6. package/dist/src/commands/detect-platform.js +51 -0
  7. package/dist/src/commands/detect-project.d.ts +7 -0
  8. package/dist/src/commands/detect-project.d.ts.map +1 -0
  9. package/dist/src/commands/detect-project.js +12 -0
  10. package/dist/src/commands/index.d.ts +26 -0
  11. package/dist/src/commands/index.d.ts.map +1 -0
  12. package/dist/src/commands/index.js +22 -0
  13. package/dist/src/commands/init.d.ts +13 -0
  14. package/dist/src/commands/init.d.ts.map +1 -0
  15. package/dist/src/commands/init.js +262 -0
  16. package/dist/src/commands/run.d.ts +22 -0
  17. package/dist/src/commands/run.d.ts.map +1 -0
  18. package/dist/src/commands/run.js +131 -0
  19. package/dist/src/commands/validate.d.ts +10 -0
  20. package/dist/src/commands/validate.d.ts.map +1 -0
  21. package/dist/src/commands/validate.js +46 -0
  22. package/dist/src/shared/detect-project.d.ts +11 -0
  23. package/dist/src/shared/detect-project.d.ts.map +1 -0
  24. package/dist/src/shared/detect-project.js +53 -0
  25. package/dist/src/shared/gitignore.d.ts +6 -0
  26. package/dist/src/shared/gitignore.d.ts.map +1 -0
  27. package/dist/src/shared/gitignore.js +26 -0
  28. package/dist/src/shared/prompts.d.ts +18 -0
  29. package/dist/src/shared/prompts.d.ts.map +1 -0
  30. package/dist/src/shared/prompts.js +96 -0
  31. package/dist/src/yaml/converter.d.ts.map +1 -1
  32. package/dist/src/yaml/converter.js +25 -1
  33. package/dist/src/yaml/converter.test.js +149 -0
  34. package/dist/src/yaml/steps/cache.d.ts +2 -0
  35. package/dist/src/yaml/steps/cache.d.ts.map +1 -1
  36. package/dist/src/yaml/steps/cache.js +84 -23
  37. package/dist/src/yaml/types.d.ts +1 -0
  38. package/dist/src/yaml/types.d.ts.map +1 -1
  39. package/package.json +7 -3
@@ -0,0 +1,13 @@
1
+ export interface DetectPlatformOptions {
2
+ pipelinePath: string;
3
+ workflow?: string;
4
+ }
5
+ /**
6
+ * Detect the target platform / stack / machine type for a workflow in a
7
+ * YAML pipeline. Mirrors `ci detect-platform <path>`.
8
+ *
9
+ * Exits the process on error or cancellation (matches legacy behavior so
10
+ * exit codes propagate cleanly to the caller).
11
+ */
12
+ export declare function handleDetectPlatformCommand(opts: DetectPlatformOptions): Promise<void>;
13
+ //# sourceMappingURL=detect-platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-platform.d.ts","sourceRoot":"","sources":["../../../src/commands/detect-platform.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA2Cf"}
@@ -0,0 +1,51 @@
1
+ import { existsSync } from "node:fs";
2
+ import { extname } from "node:path";
3
+ import { loadYAMLPipeline } from "../yaml/parser.js";
4
+ import { detectPlatformInfo } from "../yaml/platform-detector.js";
5
+ import { promptForWorkflow } from "../shared/prompts.js";
6
+ /**
7
+ * Detect the target platform / stack / machine type for a workflow in a
8
+ * YAML pipeline. Mirrors `ci detect-platform <path>`.
9
+ *
10
+ * Exits the process on error or cancellation (matches legacy behavior so
11
+ * exit codes propagate cleanly to the caller).
12
+ */
13
+ export async function handleDetectPlatformCommand(opts) {
14
+ const { pipelinePath } = opts;
15
+ if (!existsSync(pipelinePath)) {
16
+ console.error(`Error: Pipeline file not found: ${pipelinePath}`);
17
+ process.exit(1);
18
+ }
19
+ const fileExt = extname(pipelinePath).toLowerCase();
20
+ if (fileExt !== ".yml" && fileExt !== ".yaml") {
21
+ console.error("Error: detect-platform only works with YAML pipeline files (.yml, .yaml)");
22
+ process.exit(1);
23
+ }
24
+ try {
25
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
26
+ let selectedWorkflow = opts.workflow;
27
+ if (!selectedWorkflow) {
28
+ const workflows = Object.keys(yamlPipeline.workflows);
29
+ selectedWorkflow = await promptForWorkflow(workflows);
30
+ if (!selectedWorkflow) {
31
+ process.exit(1);
32
+ }
33
+ }
34
+ const platformInfo = detectPlatformInfo(yamlPipeline, selectedWorkflow);
35
+ console.log(`\nPlatform Detection Results:`);
36
+ console.log(`─────────────────────────────`);
37
+ console.log(`Pipeline: ${pipelinePath}`);
38
+ console.log(`Workflow: ${selectedWorkflow}`);
39
+ console.log(`Platform: ${platformInfo.platform}`);
40
+ console.log(`Stack: ${platformInfo.stack || "N/A"}`);
41
+ console.log(`Machine Type: ${platformInfo.machineType || "N/A"}`);
42
+ console.log();
43
+ process.exit(0);
44
+ }
45
+ catch (error) {
46
+ console.error("\n✗ Platform detection failed:");
47
+ console.error(error instanceof Error ? error.message : String(error));
48
+ process.exit(1);
49
+ }
50
+ }
51
+ //# sourceMappingURL=detect-platform.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Print the detected mobile project type (android | ios | kmm) to stdout,
3
+ * or `unknown` if no mobile project is detected. Exits 0 on success, 1
4
+ * when nothing matched.
5
+ */
6
+ export declare function handleDetectProjectCommand(): Promise<void>;
7
+ //# sourceMappingURL=detect-project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-project.d.ts","sourceRoot":"","sources":["../../../src/commands/detect-project.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC,CAIhE"}
@@ -0,0 +1,12 @@
1
+ import { detectMobileProjectRoot } from "../shared/detect-project.js";
2
+ /**
3
+ * Print the detected mobile project type (android | ios | kmm) to stdout,
4
+ * or `unknown` if no mobile project is detected. Exits 0 on success, 1
5
+ * when nothing matched.
6
+ */
7
+ export async function handleDetectProjectCommand() {
8
+ const projectType = detectMobileProjectRoot(process.cwd());
9
+ console.log(projectType ?? "unknown");
10
+ process.exit(projectType ? 0 : 1);
11
+ }
12
+ //# sourceMappingURL=detect-project.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @invarn/cibuild/commands — interactive CLI command handlers.
3
+ *
4
+ * Public entry point for callers that want to invoke cibuild's commands
5
+ * programmatically (e.g. the Invarn CLI wrapping these under `invarn
6
+ * local <cmd>`). Each handler owns its own process-exit semantics to
7
+ * preserve legacy behavior — wrap with a try/catch + process.exit in the
8
+ * caller if you need custom error handling.
9
+ *
10
+ * For non-interactive library primitives (parser, runner, step registry)
11
+ * import from `@invarn/cibuild/lib` instead.
12
+ */
13
+ export { handleInitCommand } from "./init.js";
14
+ export type { InitOptions } from "./init.js";
15
+ export { handleValidateCommand } from "./validate.js";
16
+ export type { ValidateOptions } from "./validate.js";
17
+ export { handleRunCommand } from "./run.js";
18
+ export type { RunOptions } from "./run.js";
19
+ export { handleDetectProjectCommand } from "./detect-project.js";
20
+ export { handleDetectPlatformCommand } from "./detect-platform.js";
21
+ export type { DetectPlatformOptions } from "./detect-platform.js";
22
+ export { handleBuildCommand } from "./build.js";
23
+ export { handleResetCommand } from "./reset.js";
24
+ export { detectMobileProjectRoot } from "../shared/detect-project.js";
25
+ export type { MobileProjectType } from "../shared/detect-project.js";
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AACnE,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGhD,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAC;AACtE,YAAY,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @invarn/cibuild/commands — interactive CLI command handlers.
3
+ *
4
+ * Public entry point for callers that want to invoke cibuild's commands
5
+ * programmatically (e.g. the Invarn CLI wrapping these under `invarn
6
+ * local <cmd>`). Each handler owns its own process-exit semantics to
7
+ * preserve legacy behavior — wrap with a try/catch + process.exit in the
8
+ * caller if you need custom error handling.
9
+ *
10
+ * For non-interactive library primitives (parser, runner, step registry)
11
+ * import from `@invarn/cibuild/lib` instead.
12
+ */
13
+ export { handleInitCommand } from "./init.js";
14
+ export { handleValidateCommand } from "./validate.js";
15
+ export { handleRunCommand } from "./run.js";
16
+ export { handleDetectProjectCommand } from "./detect-project.js";
17
+ export { handleDetectPlatformCommand } from "./detect-platform.js";
18
+ export { handleBuildCommand } from "./build.js";
19
+ export { handleResetCommand } from "./reset.js";
20
+ // Helpers useful for callers that implement their own variations.
21
+ export { detectMobileProjectRoot } from "../shared/detect-project.js";
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ export interface InitOptions {
2
+ importPath?: string;
3
+ create?: boolean;
4
+ }
5
+ /**
6
+ * Check dependencies, then either import an existing YAML pipeline or
7
+ * scaffold a new one via `handleBuildCommand`. Mirrors `ci init`.
8
+ *
9
+ * Exits the process with a non-zero code on any unrecoverable error
10
+ * (missing deps, unreadable project, cancelled prompts).
11
+ */
12
+ export declare function handleInitCommand(opts?: InitOptions): Promise<void>;
13
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAUD;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0Q7E"}
@@ -0,0 +1,262 @@
1
+ import { execSync } from "node:child_process";
2
+ import { copyFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { resolve } from "node:path";
5
+ import prompts from "prompts";
6
+ import { detectMobileProjectRoot } from "../shared/detect-project.js";
7
+ import { ensureCiBuildGitignoreEntries } from "../shared/gitignore.js";
8
+ import { loadYAMLPipeline } from "../yaml/parser.js";
9
+ import { handleBuildCommand } from "./build.js";
10
+ import { generateGitHubActionsWorkflow } from "./github-workflow.js";
11
+ /**
12
+ * Check dependencies, then either import an existing YAML pipeline or
13
+ * scaffold a new one via `handleBuildCommand`. Mirrors `ci init`.
14
+ *
15
+ * Exits the process with a non-zero code on any unrecoverable error
16
+ * (missing deps, unreadable project, cancelled prompts).
17
+ */
18
+ export async function handleInitCommand(opts = {}) {
19
+ const cwd = process.cwd();
20
+ const projectType = detectMobileProjectRoot(cwd);
21
+ if (!projectType) {
22
+ console.error("Error: Not a mobile project root.");
23
+ console.error("ci init must be run from the root folder of an Android, iOS, or KMM project.");
24
+ console.error("\nAndroid/KMM projects must contain one of:");
25
+ console.error(" build.gradle, build.gradle.kts, settings.gradle, settings.gradle.kts, gradlew");
26
+ console.error("\nKMM projects additionally need: iosApp/, shared/, or composeApp/ directory");
27
+ console.error("\niOS projects must contain one of:");
28
+ console.error(" *.xcodeproj, *.xcworkspace, Podfile");
29
+ process.exit(1);
30
+ }
31
+ const ciDir = resolve(cwd, ".ci");
32
+ if (existsSync(ciDir)) {
33
+ const entries = readdirSync(ciDir);
34
+ if (entries.length > 0) {
35
+ console.error("Already initialised. Run 'ci run' to execute your pipeline.");
36
+ process.exit(1);
37
+ }
38
+ }
39
+ const projectTypeLabel = projectType === "kmm"
40
+ ? "KMM (Kotlin Multiplatform)"
41
+ : projectType === "android"
42
+ ? "Android"
43
+ : "iOS";
44
+ console.log(`\nDetected project type: ${projectTypeLabel}`);
45
+ console.log(`
46
+ ╔══════════════════════════════════════════════════════════════╗
47
+ ║ CI Build - Dependency Check ║
48
+ ╚══════════════════════════════════════════════════════════════╝
49
+ `);
50
+ const commonDependencies = [
51
+ {
52
+ name: "Git",
53
+ command: "git --version",
54
+ required: true,
55
+ installInstructions: "Download from https://git-scm.com/",
56
+ },
57
+ ];
58
+ const androidDependencies = [
59
+ {
60
+ name: "Java (JDK)",
61
+ command: "java -version 2>&1 | head -1",
62
+ required: true,
63
+ installInstructions: "Download from https://adoptium.net/ or install via brew: brew install --cask temurin",
64
+ },
65
+ {
66
+ name: "Android SDK",
67
+ customCheck: () => {
68
+ const fromEnv = process.env.ANDROID_HOME;
69
+ if (fromEnv && existsSync(fromEnv)) {
70
+ return { found: true, message: `${fromEnv} (via ANDROID_HOME)` };
71
+ }
72
+ const commonPaths = [
73
+ `${homedir()}/Library/Android/sdk`,
74
+ `${homedir()}/Android/Sdk`,
75
+ "/usr/local/android-sdk",
76
+ ];
77
+ const detected = commonPaths.find((p) => existsSync(p));
78
+ if (detected) {
79
+ return { found: true, message: `${detected} (auto-detected)` };
80
+ }
81
+ return { found: false, message: "Android SDK not found" };
82
+ },
83
+ required: true,
84
+ installInstructions: "Install Android Studio from https://developer.android.com/studio or set ANDROID_HOME manually",
85
+ },
86
+ ];
87
+ const iosDependencies = [
88
+ {
89
+ name: "Xcode",
90
+ command: "xcodebuild -version 2>&1 | head -1",
91
+ required: true,
92
+ installInstructions: "Install Xcode from the Mac App Store",
93
+ },
94
+ {
95
+ name: "CocoaPods",
96
+ command: "pod --version",
97
+ required: false,
98
+ installInstructions: "Run: sudo gem install cocoapods",
99
+ },
100
+ ];
101
+ const dependencies = [
102
+ ...commonDependencies,
103
+ ...(projectType === "android"
104
+ ? androidDependencies
105
+ : projectType === "kmm"
106
+ ? [...androidDependencies, ...iosDependencies]
107
+ : iosDependencies),
108
+ ];
109
+ let allRequired = true;
110
+ let missingOptional = false;
111
+ for (const dep of dependencies) {
112
+ if (dep.customCheck) {
113
+ const result = dep.customCheck();
114
+ if (result.found) {
115
+ console.log(`✅ ${dep.name.padEnd(20)} ${result.message}`);
116
+ }
117
+ else if (dep.required) {
118
+ console.log(`❌ ${dep.name.padEnd(20)} Not found (REQUIRED)`);
119
+ console.log(` Install: ${dep.installInstructions}`);
120
+ allRequired = false;
121
+ }
122
+ else {
123
+ console.log(`⚠️ ${dep.name.padEnd(20)} Not found (optional)`);
124
+ console.log(` Install: ${dep.installInstructions}`);
125
+ missingOptional = true;
126
+ }
127
+ continue;
128
+ }
129
+ try {
130
+ const output = execSync(dep.command, {
131
+ encoding: "utf-8",
132
+ stdio: ["pipe", "pipe", "pipe"],
133
+ shell: "/bin/sh",
134
+ }).trim();
135
+ console.log(`✅ ${dep.name.padEnd(20)} ${output}`);
136
+ }
137
+ catch {
138
+ if (dep.required) {
139
+ console.log(`❌ ${dep.name.padEnd(20)} Not found (REQUIRED)`);
140
+ console.log(` Install: ${dep.installInstructions}`);
141
+ allRequired = false;
142
+ }
143
+ else {
144
+ console.log(`⚠️ ${dep.name.padEnd(20)} Not found (optional)`);
145
+ console.log(` Install: ${dep.installInstructions}`);
146
+ missingOptional = true;
147
+ }
148
+ }
149
+ }
150
+ console.log();
151
+ if (allRequired && !missingOptional) {
152
+ console.log("✨ All dependencies are installed and ready!");
153
+ }
154
+ else if (allRequired) {
155
+ console.log("✅ All required dependencies are installed!");
156
+ console.log("⚠️ Some optional dependencies are missing but you can still use CI Build.");
157
+ }
158
+ else {
159
+ console.log("❌ Please install the missing required dependencies above.");
160
+ process.exit(1);
161
+ }
162
+ console.log();
163
+ let action;
164
+ if (opts.importPath) {
165
+ action = "import";
166
+ }
167
+ else if (opts.create) {
168
+ action = "create";
169
+ }
170
+ else {
171
+ const response = await prompts({
172
+ type: "select",
173
+ name: "action",
174
+ message: "How would you like to set up your pipeline?",
175
+ choices: [
176
+ { title: "Import an existing YAML pipeline", value: "import" },
177
+ { title: "Create a new pipeline", value: "create" },
178
+ ],
179
+ });
180
+ action = response.action;
181
+ }
182
+ if (!action) {
183
+ console.log("\nCancelled.");
184
+ process.exit(0);
185
+ }
186
+ ensureCiBuildGitignoreEntries(cwd);
187
+ if (action === "create") {
188
+ await handleBuildCommand(detectMobileProjectRoot, {
189
+ createPipelinesDir: true,
190
+ nonInteractive: true,
191
+ });
192
+ process.exit(0);
193
+ }
194
+ // Import flow
195
+ let resolvedYaml;
196
+ if (opts.importPath) {
197
+ const trimmed = opts.importPath.trim();
198
+ if (!existsSync(trimmed)) {
199
+ console.error(`Error: File not found: ${trimmed}`);
200
+ process.exit(1);
201
+ }
202
+ const ext = trimmed.split(".").pop()?.toLowerCase();
203
+ if (ext !== "yml" && ext !== "yaml") {
204
+ console.error("Error: File must be a .yml or .yaml file");
205
+ process.exit(1);
206
+ }
207
+ resolvedYaml = trimmed;
208
+ }
209
+ else {
210
+ const { yamlPath } = await prompts({
211
+ type: "text",
212
+ name: "yamlPath",
213
+ message: "Enter the absolute path to your YAML pipeline file:",
214
+ validate: (value) => {
215
+ if (!value.trim())
216
+ return "Path is required";
217
+ if (!existsSync(value.trim()))
218
+ return `File not found: ${value.trim()}`;
219
+ const ext = value.trim().split(".").pop()?.toLowerCase();
220
+ if (ext !== "yml" && ext !== "yaml")
221
+ return "File must be a .yml or .yaml file";
222
+ return true;
223
+ },
224
+ });
225
+ if (!yamlPath) {
226
+ console.log("\nCancelled.");
227
+ process.exit(0);
228
+ }
229
+ resolvedYaml = yamlPath.trim();
230
+ }
231
+ const fileName = resolvedYaml.split("/").pop();
232
+ const pipelinesDir = resolve(ciDir, "pipelines");
233
+ const destPath = resolve(pipelinesDir, fileName);
234
+ mkdirSync(pipelinesDir, { recursive: true });
235
+ copyFileSync(resolvedYaml, destPath);
236
+ console.log(`\n✅ Created .ci/pipelines/`);
237
+ console.log(`✅ Copied pipeline: ${fileName}`);
238
+ let importedPlatform = null;
239
+ try {
240
+ const yamlPipeline = loadYAMLPipeline(destPath);
241
+ const meta = yamlPipeline.meta?.["cibuild.io"];
242
+ if (meta?.platform === "ios" || meta?.platform === "android" || meta?.platform === "kmm") {
243
+ importedPlatform = meta.platform === "kmm" ? "ios" : meta.platform;
244
+ }
245
+ }
246
+ catch {
247
+ // Ignore parse errors — default to macos-latest in generator.
248
+ }
249
+ generateGitHubActionsWorkflow({ platform: importedPlatform, cwd });
250
+ const hasSecrets = existsSync(resolve(cwd, ".cibuild-secrets.json"));
251
+ const hasWorkflow = existsSync(resolve(cwd, ".github", "workflows", "ci.yml"));
252
+ console.log("\nNext steps:");
253
+ console.log(" ci run .ci/pipelines/cibuild.yml -w primary # Run locally");
254
+ if (hasWorkflow) {
255
+ if (hasSecrets) {
256
+ console.log(" ci secrets upload # Upload secrets to GitHub");
257
+ }
258
+ console.log(" git add . && git push # Push to GitHub - CI runs automatically");
259
+ }
260
+ console.log("");
261
+ }
262
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1,22 @@
1
+ export interface RunOptions {
2
+ /**
3
+ * Absolute path to the pipeline file. If omitted, the handler looks
4
+ * inside `./.ci/pipelines/` and prompts when multiple files exist.
5
+ */
6
+ pipelinePath?: string;
7
+ workflow?: string;
8
+ validateOnly?: boolean;
9
+ production?: boolean;
10
+ skipValidation?: boolean;
11
+ local?: boolean;
12
+ }
13
+ /**
14
+ * Run a pipeline locally. Mirrors `ci run <path>`.
15
+ *
16
+ * - `.yml` / `.yaml` → parse, validate, optionally prompt for missing
17
+ * env vars, then execute via `PipelineRunner`.
18
+ * - `.ts` / `.js` → dynamic-import the module, use its default export
19
+ * as the `PipelineDef`, then execute.
20
+ */
21
+ export declare function handleRunCommand(opts?: RunOptions): Promise<void>;
22
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/commands/run.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,GAAE,UAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAuI3E"}
@@ -0,0 +1,131 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { extname, resolve } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import prompts from "prompts";
5
+ import { PipelineRunner } from "../runner.js";
6
+ import { loadConfig } from "../config.js";
7
+ import { loadYAMLPipeline } from "../yaml/parser.js";
8
+ import { convertYAMLWithSecrets } from "../yaml/pipeline-with-secrets.js";
9
+ import { promptForWorkflow, promptForMissingVariables, } from "../shared/prompts.js";
10
+ /**
11
+ * Run a pipeline locally. Mirrors `ci run <path>`.
12
+ *
13
+ * - `.yml` / `.yaml` → parse, validate, optionally prompt for missing
14
+ * env vars, then execute via `PipelineRunner`.
15
+ * - `.ts` / `.js` → dynamic-import the module, use its default export
16
+ * as the `PipelineDef`, then execute.
17
+ */
18
+ export async function handleRunCommand(opts = {}) {
19
+ const cwd = process.cwd();
20
+ // Resolve the pipeline path, optionally discovering from .ci/pipelines/.
21
+ let pipelinePath;
22
+ if (opts.pipelinePath) {
23
+ pipelinePath = resolve(cwd, opts.pipelinePath);
24
+ }
25
+ else {
26
+ const pipelinesDir = resolve(cwd, ".ci", "pipelines");
27
+ if (!existsSync(pipelinesDir)) {
28
+ console.error("Project not initialised. Run 'ci init' first.");
29
+ process.exit(1);
30
+ }
31
+ const yamlFiles = readdirSync(pipelinesDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
32
+ if (yamlFiles.length === 0) {
33
+ console.error("No pipelines found in .ci/pipelines/. Run 'ci init' to import one.");
34
+ process.exit(1);
35
+ }
36
+ if (yamlFiles.length === 1) {
37
+ pipelinePath = resolve(pipelinesDir, yamlFiles[0]);
38
+ console.log(`Using pipeline: ${yamlFiles[0]}`);
39
+ }
40
+ else {
41
+ const { selected } = await prompts({
42
+ type: "select",
43
+ name: "selected",
44
+ message: "Select a pipeline to run:",
45
+ choices: yamlFiles.map((f) => ({ title: f, value: resolve(pipelinesDir, f) })),
46
+ });
47
+ if (!selected) {
48
+ console.log("Cancelled.");
49
+ process.exit(0);
50
+ }
51
+ pipelinePath = selected;
52
+ }
53
+ }
54
+ if (!existsSync(pipelinePath)) {
55
+ console.error(`Error: Pipeline file not found: ${pipelinePath}`);
56
+ process.exit(1);
57
+ }
58
+ try {
59
+ const config = loadConfig();
60
+ config.local = opts.local ?? false;
61
+ const runner = new PipelineRunner(config);
62
+ const fileExt = extname(pipelinePath).toLowerCase();
63
+ const isYAML = fileExt === ".yml" || fileExt === ".yaml";
64
+ const isJavaScriptModule = fileExt === ".ts" || fileExt === ".js";
65
+ let pipeline;
66
+ if (isYAML) {
67
+ console.log(`Loading YAML pipeline: ${pipelinePath}`);
68
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
69
+ let selectedWorkflow = opts.workflow;
70
+ if (!selectedWorkflow) {
71
+ const workflows = Object.keys(yamlPipeline.workflows);
72
+ selectedWorkflow = await promptForWorkflow(workflows);
73
+ if (!selectedWorkflow) {
74
+ process.exit(1);
75
+ }
76
+ }
77
+ else {
78
+ console.log(`Using workflow: ${selectedWorkflow}`);
79
+ }
80
+ if (opts.skipValidation) {
81
+ console.log(`\n⏭️ Skipping validation (--skip-validation flag set)\n`);
82
+ }
83
+ else {
84
+ const canProceed = await promptForMissingVariables(yamlPipeline, selectedWorkflow, config, pipelinePath);
85
+ if (!canProceed) {
86
+ console.error("\n✗ Pipeline cannot run. Fix the errors above before continuing.");
87
+ process.exit(1);
88
+ }
89
+ }
90
+ if (opts.production) {
91
+ console.log("═".repeat(60));
92
+ console.log("🚀 PRODUCTION MODE");
93
+ console.log("═".repeat(60));
94
+ console.log("\nValidation passed. Pipeline would be dispatched to remote runner.");
95
+ console.log("(Remote runner integration not yet implemented)\n");
96
+ console.log("For now, use without --production flag to run locally.\n");
97
+ process.exit(0);
98
+ }
99
+ const conversionResult = await convertYAMLWithSecrets(yamlPipeline, {
100
+ config,
101
+ workflowName: selectedWorkflow,
102
+ yamlFilePath: pipelinePath,
103
+ interactive: true,
104
+ });
105
+ pipeline = conversionResult.pipeline;
106
+ await runner.runPipeline(pipeline, conversionResult.warnings, conversionResult.skippedSteps);
107
+ }
108
+ else if (isJavaScriptModule) {
109
+ console.log(`Loading pipeline: ${pipelinePath}`);
110
+ const pipelineUrl = pathToFileURL(pipelinePath).href;
111
+ const pipelineModule = await import(pipelineUrl);
112
+ pipeline = pipelineModule.default;
113
+ if (!pipeline || typeof pipeline !== "object") {
114
+ throw new Error("Pipeline file must export a default PipelineDef object");
115
+ }
116
+ await runner.runPipeline(pipeline);
117
+ }
118
+ else {
119
+ throw new Error(`Unsupported pipeline file format: ${fileExt}\n` +
120
+ `Supported formats: .yml, .yaml, .ts, .js`);
121
+ }
122
+ console.log("\n✓ All steps completed successfully!\n");
123
+ process.exit(0);
124
+ }
125
+ catch (error) {
126
+ console.error("\n✗ Pipeline failed:");
127
+ console.error(error instanceof Error ? error.message : String(error));
128
+ process.exit(1);
129
+ }
130
+ }
131
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1,10 @@
1
+ export interface ValidateOptions {
2
+ pipelinePath: string;
3
+ workflow?: string;
4
+ }
5
+ /**
6
+ * Validate a pipeline YAML's workflow against the step-validator rules.
7
+ * Mirrors `ci validate <path>`. Exits 0 when valid, 1 otherwise.
8
+ */
9
+ export declare function handleValidateCommand(opts: ValidateOptions): Promise<void>;
10
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/commands/validate.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAwChF"}
@@ -0,0 +1,46 @@
1
+ import { existsSync } from "node:fs";
2
+ import { extname } from "node:path";
3
+ import { loadConfig } from "../config.js";
4
+ import { loadYAMLPipeline } from "../yaml/parser.js";
5
+ import { StepValidator, formatValidationResult } from "../yaml/step-validator.js";
6
+ import { promptForWorkflow } from "../shared/prompts.js";
7
+ /**
8
+ * Validate a pipeline YAML's workflow against the step-validator rules.
9
+ * Mirrors `ci validate <path>`. Exits 0 when valid, 1 otherwise.
10
+ */
11
+ export async function handleValidateCommand(opts) {
12
+ const { pipelinePath } = opts;
13
+ if (!existsSync(pipelinePath)) {
14
+ console.error(`Error: Pipeline file not found: ${pipelinePath}`);
15
+ process.exit(1);
16
+ }
17
+ const fileExt = extname(pipelinePath).toLowerCase();
18
+ if (fileExt !== ".yml" && fileExt !== ".yaml") {
19
+ console.error("Error: validate only works with YAML pipeline files (.yml, .yaml)");
20
+ process.exit(1);
21
+ }
22
+ try {
23
+ const config = loadConfig();
24
+ const yamlPipeline = loadYAMLPipeline(pipelinePath);
25
+ let selectedWorkflow = opts.workflow;
26
+ if (!selectedWorkflow) {
27
+ const workflows = Object.keys(yamlPipeline.workflows);
28
+ selectedWorkflow = await promptForWorkflow(workflows);
29
+ if (!selectedWorkflow) {
30
+ process.exit(1);
31
+ }
32
+ }
33
+ console.log(`🔍 Validating workflow: ${selectedWorkflow}`);
34
+ console.log(` Pipeline: ${pipelinePath}\n`);
35
+ const validator = new StepValidator(yamlPipeline, selectedWorkflow, config, pipelinePath);
36
+ const result = await validator.validateWorkflow();
37
+ console.log(formatValidationResult(result));
38
+ process.exit(result.valid ? 0 : 1);
39
+ }
40
+ catch (error) {
41
+ console.error("\n✗ Validation failed:");
42
+ console.error(error instanceof Error ? error.message : String(error));
43
+ process.exit(1);
44
+ }
45
+ }
46
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1,11 @@
1
+ export type MobileProjectType = "android" | "ios" | "kmm";
2
+ /**
3
+ * Detects whether the given directory is the root of an Android, iOS, or
4
+ * KMM project. Returns the detected project type, or null if none match.
5
+ *
6
+ * KMM is checked before Android because both have Gradle files; KMM is
7
+ * distinguished by the presence of an `iosApp/`, `shared/`, or
8
+ * `composeApp/` directory alongside the Gradle files.
9
+ */
10
+ export declare function detectMobileProjectRoot(dir: string): MobileProjectType | null;
11
+ //# sourceMappingURL=detect-project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detect-project.d.ts","sourceRoot":"","sources":["../../../src/shared/detect-project.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,CAAC;AAE1D;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CA0C7E"}