@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 @@
|
|
|
1
|
+
{"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../../src/commands/edit.ts"],"names":[],"mappings":"AAUA,OAAO,wBAAwB,CAAC;AAkYhC,wBAAsB,iBAAiB,CACrC,YAAY,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CA+Sf"}
|
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as readline from 'node:readline';
|
|
3
|
+
import { resolve, basename } from 'node:path';
|
|
4
|
+
import * as yaml from 'js-yaml';
|
|
5
|
+
import prompts from 'prompts';
|
|
6
|
+
import { loadYAMLPipeline } from '../yaml/parser.js';
|
|
7
|
+
import { SecretsManager } from '../yaml/secrets-manager.js';
|
|
8
|
+
import { getAvailableSteps, getStepMetadata } from '../yaml/steps/registry.js';
|
|
9
|
+
// Ensure the step registry is populated
|
|
10
|
+
import '../yaml/steps/index.js';
|
|
11
|
+
function getStepKey(step) {
|
|
12
|
+
return Object.keys(step)[0];
|
|
13
|
+
}
|
|
14
|
+
function getStepDisplayName(stepKey, step) {
|
|
15
|
+
const config = step[stepKey];
|
|
16
|
+
const baseName = stepKey.split('@')[0];
|
|
17
|
+
return config?.title ? `${baseName} (${config.title})` : baseName;
|
|
18
|
+
}
|
|
19
|
+
function formatValueForDisplay(value) {
|
|
20
|
+
if (value === null || value === undefined)
|
|
21
|
+
return '';
|
|
22
|
+
if (Array.isArray(value))
|
|
23
|
+
return value.map(String).join(', ');
|
|
24
|
+
const str = String(value);
|
|
25
|
+
const firstLine = str.split('\n')[0];
|
|
26
|
+
return str.includes('\n') ? `${firstLine}...` : firstLine;
|
|
27
|
+
}
|
|
28
|
+
function formatValueForEdit(value) {
|
|
29
|
+
if (value === null || value === undefined)
|
|
30
|
+
return '';
|
|
31
|
+
if (Array.isArray(value))
|
|
32
|
+
return value.map(String).join(', ');
|
|
33
|
+
return String(value);
|
|
34
|
+
}
|
|
35
|
+
function parseEditedValue(edited, originalValue) {
|
|
36
|
+
if (Array.isArray(originalValue)) {
|
|
37
|
+
return edited.split(/,\s*|\n/).map((s) => s.trim()).filter(Boolean);
|
|
38
|
+
}
|
|
39
|
+
if (typeof originalValue === 'number') {
|
|
40
|
+
const num = Number(edited);
|
|
41
|
+
return isNaN(num) ? edited : num;
|
|
42
|
+
}
|
|
43
|
+
return edited;
|
|
44
|
+
}
|
|
45
|
+
/** Returns all $VAR / ${VAR} names referenced in a value (recursively for arrays). */
|
|
46
|
+
function extractEnvRefs(value) {
|
|
47
|
+
if (Array.isArray(value))
|
|
48
|
+
return value.flatMap(extractEnvRefs);
|
|
49
|
+
if (typeof value !== 'string')
|
|
50
|
+
return [];
|
|
51
|
+
const refs = [];
|
|
52
|
+
const regex = /\$\{?([A-Z_][A-Z0-9_]*)\}?/g;
|
|
53
|
+
let match;
|
|
54
|
+
while ((match = regex.exec(value)) !== null) {
|
|
55
|
+
refs.push(match[1]);
|
|
56
|
+
}
|
|
57
|
+
return refs;
|
|
58
|
+
}
|
|
59
|
+
/** Returns true if varName is resolvable from pipeline envs or the secrets store. */
|
|
60
|
+
function isVarDefined(varName, pipeline, workflowName, secretsManager) {
|
|
61
|
+
const appEnvKeys = (pipeline.app?.envs ?? []).flatMap(Object.keys);
|
|
62
|
+
if (appEnvKeys.includes(varName))
|
|
63
|
+
return true;
|
|
64
|
+
const workflowEnvKeys = (pipeline.workflows[workflowName]?.envs ?? []).flatMap(Object.keys);
|
|
65
|
+
if (workflowEnvKeys.includes(varName))
|
|
66
|
+
return true;
|
|
67
|
+
return secretsManager.hasSecret(varName, workflowName);
|
|
68
|
+
}
|
|
69
|
+
function printPipelineSummary(fileName, selectedWorkflow, steps) {
|
|
70
|
+
console.log(`\nPipeline: ${fileName}`);
|
|
71
|
+
console.log('─'.repeat(50));
|
|
72
|
+
console.log(`Workflow: ${selectedWorkflow}`);
|
|
73
|
+
console.log(' Steps:');
|
|
74
|
+
steps.forEach((step, i) => {
|
|
75
|
+
const stepKey = getStepKey(step);
|
|
76
|
+
const config = step[stepKey];
|
|
77
|
+
const displayName = getStepDisplayName(stepKey, step);
|
|
78
|
+
console.log(` ${i + 1}. ${displayName}`);
|
|
79
|
+
if (config?.inputs && Object.keys(config.inputs).length > 0) {
|
|
80
|
+
for (const [key, value] of Object.entries(config.inputs)) {
|
|
81
|
+
const displayValue = formatValueForDisplay(value);
|
|
82
|
+
console.log(` ${key}: ${displayValue}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
console.log();
|
|
87
|
+
}
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Add step helpers
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
function suggestFromTransform(value, transform) {
|
|
92
|
+
if (transform === 'filename-to-env-var') {
|
|
93
|
+
return basename(value).replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Collects multiline content from the user via readline, bypassing the
|
|
99
|
+
* prompts library's paste-detection dialog which causes spurious auto-advances.
|
|
100
|
+
* The user terminates input by typing .done on its own line.
|
|
101
|
+
*/
|
|
102
|
+
function readMultilineContent(label) {
|
|
103
|
+
return new Promise((resolve) => {
|
|
104
|
+
console.log(`\n ${label}`);
|
|
105
|
+
console.log(' Paste content, then type .done on its own line and press Enter:\n');
|
|
106
|
+
if (process.stdin.isTTY) {
|
|
107
|
+
try {
|
|
108
|
+
process.stdin.setRawMode(false);
|
|
109
|
+
}
|
|
110
|
+
catch { /* ignore */ }
|
|
111
|
+
}
|
|
112
|
+
process.stdin.resume();
|
|
113
|
+
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
114
|
+
const lines = [];
|
|
115
|
+
rl.on('line', (line) => {
|
|
116
|
+
if (line.trim() === '.done') {
|
|
117
|
+
rl.close();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
lines.push(line);
|
|
121
|
+
});
|
|
122
|
+
rl.once('close', () => {
|
|
123
|
+
process.stdin.pause();
|
|
124
|
+
resolve(lines.length > 0 ? lines.join('\n') : undefined);
|
|
125
|
+
});
|
|
126
|
+
rl.on('SIGINT', () => { rl.close(); resolve(undefined); });
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
async function handleAddRegisteredStep(pipelinePath, workflowName, _pipeline, stepName, secretsManager) {
|
|
130
|
+
const meta = getStepMetadata(stepName);
|
|
131
|
+
if (!meta?.inputs) {
|
|
132
|
+
console.log(`\n⚠️ No input metadata for '${stepName}'. Add the step manually in the YAML.`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const yamlInputs = {};
|
|
136
|
+
// --- collect non-secret inputs first (in declaration order) ---
|
|
137
|
+
for (const [key, inputMeta] of Object.entries(meta.inputs)) {
|
|
138
|
+
if (inputMeta.isSetupSecret)
|
|
139
|
+
continue;
|
|
140
|
+
// Compute auto-suggestion if declared
|
|
141
|
+
let initial = inputMeta.default !== undefined ? String(inputMeta.default) : '';
|
|
142
|
+
if (inputMeta.deriveFrom && inputMeta.deriveTransform) {
|
|
143
|
+
const sourceVal = yamlInputs[inputMeta.deriveFrom];
|
|
144
|
+
if (typeof sourceVal === 'string' && sourceVal) {
|
|
145
|
+
initial = suggestFromTransform(sourceVal, inputMeta.deriveTransform);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const isMultiline = inputMeta.inputType === 'multiline';
|
|
149
|
+
const isPassword = inputMeta.inputType === 'password';
|
|
150
|
+
const label = inputMeta.required
|
|
151
|
+
? `${key} (${inputMeta.description})`
|
|
152
|
+
: `${key} (${inputMeta.description}) [optional, default: ${inputMeta.default ?? 'none'}]`;
|
|
153
|
+
if (isMultiline) {
|
|
154
|
+
const value = await readMultilineContent(label);
|
|
155
|
+
if (value === undefined) {
|
|
156
|
+
console.log('\nCancelled.');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (inputMeta.required && !value && !inputMeta.default)
|
|
160
|
+
continue;
|
|
161
|
+
if (value)
|
|
162
|
+
yamlInputs[key] = value;
|
|
163
|
+
else if (inputMeta.default !== undefined)
|
|
164
|
+
yamlInputs[key] = inputMeta.default;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const { value } = await prompts({
|
|
168
|
+
type: isPassword ? 'password' : 'text',
|
|
169
|
+
name: 'value',
|
|
170
|
+
message: label,
|
|
171
|
+
initial,
|
|
172
|
+
validate: inputMeta.required ? (v) => v.trim() !== '' || 'Required' : undefined,
|
|
173
|
+
});
|
|
174
|
+
if (value === undefined) {
|
|
175
|
+
console.log('\nCancelled.');
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (value !== '') {
|
|
179
|
+
yamlInputs[key] = inputMeta.inputType === 'path' || inputMeta.inputType === 'text' || !inputMeta.inputType
|
|
180
|
+
? value
|
|
181
|
+
: value;
|
|
182
|
+
}
|
|
183
|
+
else if (inputMeta.default !== undefined) {
|
|
184
|
+
yamlInputs[key] = inputMeta.default;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// --- collect setup-secret inputs ---
|
|
189
|
+
for (const [key, inputMeta] of Object.entries(meta.inputs)) {
|
|
190
|
+
if (!inputMeta.isSetupSecret)
|
|
191
|
+
continue;
|
|
192
|
+
const varName = inputMeta.secretKeyField ? String(yamlInputs[inputMeta.secretKeyField] ?? '') : key.toUpperCase();
|
|
193
|
+
if (!varName) {
|
|
194
|
+
console.log(`\n⚠️ Could not determine secret variable name for input '${key}'. Skipping.`);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const { secretInputMethod } = await prompts({
|
|
198
|
+
type: 'select',
|
|
199
|
+
name: 'secretInputMethod',
|
|
200
|
+
message: `How would you like to provide ${inputMeta.description}?`,
|
|
201
|
+
choices: [
|
|
202
|
+
{ title: 'Enter file path', value: 'path' },
|
|
203
|
+
{ title: 'Add content manually', value: 'manual' },
|
|
204
|
+
],
|
|
205
|
+
});
|
|
206
|
+
if (!secretInputMethod) {
|
|
207
|
+
console.log('\nCancelled.');
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
let content;
|
|
211
|
+
if (secretInputMethod === 'path') {
|
|
212
|
+
const { filePath } = await prompts({
|
|
213
|
+
type: 'text',
|
|
214
|
+
name: 'filePath',
|
|
215
|
+
message: `Enter absolute path to file:`,
|
|
216
|
+
});
|
|
217
|
+
if (!filePath?.trim()) {
|
|
218
|
+
console.log('\nCancelled.');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const expandedPath = filePath.trim().replace(/^~/, process.env.HOME ?? '~');
|
|
222
|
+
try {
|
|
223
|
+
content = fs.readFileSync(expandedPath, 'utf-8');
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
console.log(`\n Could not read ${expandedPath} — check the path and try again.`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
content = await readMultilineContent(`${inputMeta.description} → will be stored as secret $${varName}`);
|
|
232
|
+
if (!content) {
|
|
233
|
+
console.log('\nCancelled.');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
secretsManager.storeSecret(varName, content, workflowName);
|
|
238
|
+
// Add placeholder to workflow.envs (skip if already in app.envs)
|
|
239
|
+
const rawDoc = yaml.load(fs.readFileSync(pipelinePath, 'utf-8'));
|
|
240
|
+
const appEnvKeys = (rawDoc?.app?.envs ?? []).flatMap(Object.keys);
|
|
241
|
+
if (!appEnvKeys.includes(varName)) {
|
|
242
|
+
rawDoc.workflows[workflowName].envs ??= [];
|
|
243
|
+
const wfEnvKeys = rawDoc.workflows[workflowName].envs.flatMap(Object.keys);
|
|
244
|
+
if (!wfEnvKeys.includes(varName)) {
|
|
245
|
+
rawDoc.workflows[workflowName].envs.push({ [varName]: '' });
|
|
246
|
+
}
|
|
247
|
+
fs.writeFileSync(pipelinePath, yaml.dump(rawDoc, { indent: 2, lineWidth: -1, noRefs: true }), 'utf-8');
|
|
248
|
+
}
|
|
249
|
+
console.log(` ✅ Stored ${varName} in .cibuild-secrets.json`);
|
|
250
|
+
}
|
|
251
|
+
// --- position selector ---
|
|
252
|
+
const rawDoc = yaml.load(fs.readFileSync(pipelinePath, 'utf-8'));
|
|
253
|
+
const steps = rawDoc.workflows[workflowName].steps ?? [];
|
|
254
|
+
const positionChoices = [
|
|
255
|
+
{ title: '0. [top of workflow]', value: -1 },
|
|
256
|
+
...steps.map((s, i) => {
|
|
257
|
+
const key = Object.keys(s)[0];
|
|
258
|
+
const title = s[key]?.title ? `${i + 1}. ${key.split('@')[0]} (${s[key].title})` : `${i + 1}. ${key.split('@')[0]}`;
|
|
259
|
+
return { title, value: i };
|
|
260
|
+
}),
|
|
261
|
+
];
|
|
262
|
+
const { insertAfter } = await prompts({
|
|
263
|
+
type: 'select',
|
|
264
|
+
name: 'insertAfter',
|
|
265
|
+
message: 'Insert after step:',
|
|
266
|
+
choices: positionChoices,
|
|
267
|
+
});
|
|
268
|
+
if (insertAfter === undefined) {
|
|
269
|
+
console.log('\nCancelled.');
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
// --- build and insert the YAML step ---
|
|
273
|
+
const stepObj = {
|
|
274
|
+
[`${stepName}@1.0.0`]: {
|
|
275
|
+
is_skippable: true,
|
|
276
|
+
inputs: Object.keys(yamlInputs).length > 0 ? yamlInputs : undefined,
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
steps.splice(insertAfter + 1, 0, stepObj);
|
|
280
|
+
fs.writeFileSync(pipelinePath, yaml.dump(rawDoc, { indent: 2, lineWidth: -1, noRefs: true }), 'utf-8');
|
|
281
|
+
const insertedAfterLabel = insertAfter === -1
|
|
282
|
+
? 'top of workflow'
|
|
283
|
+
: positionChoices.find((c) => c.value === insertAfter)?.title ?? String(insertAfter + 1);
|
|
284
|
+
console.log(`\n✅ Inserted ${stepName}@1.0.0 after ${insertedAfterLabel}`);
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Detects the target platform of a workflow.
|
|
288
|
+
* Checks (in order):
|
|
289
|
+
* 1. meta.cibuild.io.platform — explicit declaration, most reliable
|
|
290
|
+
* 2. Existing steps in the workflow — fallback
|
|
291
|
+
* Returns 'ios' or 'android', or null if platform cannot be determined.
|
|
292
|
+
*/
|
|
293
|
+
function detectWorkflowPlatform(pipeline, workflowName) {
|
|
294
|
+
// 1. Explicit platform declaration in meta
|
|
295
|
+
const metaPlatform = pipeline.meta?.['cibuild.io']?.platform;
|
|
296
|
+
if (metaPlatform === 'ios' || metaPlatform === 'android')
|
|
297
|
+
return metaPlatform;
|
|
298
|
+
// 2. Scan existing steps as fallback
|
|
299
|
+
const workflow = pipeline.workflows[workflowName];
|
|
300
|
+
if (!workflow)
|
|
301
|
+
return null;
|
|
302
|
+
for (const yamlStep of workflow.steps) {
|
|
303
|
+
const stepKey = Object.keys(yamlStep)[0];
|
|
304
|
+
const stepName = stepKey.split('@')[0];
|
|
305
|
+
const m = getStepMetadata(stepName);
|
|
306
|
+
if (m?.platform === 'ios')
|
|
307
|
+
return 'ios';
|
|
308
|
+
if (m?.platform === 'android')
|
|
309
|
+
return 'android';
|
|
310
|
+
}
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
async function handleAddStep(pipelinePath, workflowName, pipeline, secretsManager) {
|
|
314
|
+
const workflowPlatform = detectWorkflowPlatform(pipeline, workflowName);
|
|
315
|
+
const stepNames = getAvailableSteps().filter((name) => {
|
|
316
|
+
const platform = getStepMetadata(name)?.platform ?? 'all';
|
|
317
|
+
if (platform === 'all')
|
|
318
|
+
return true;
|
|
319
|
+
if (workflowPlatform === null)
|
|
320
|
+
return true; // no platform established yet — show all
|
|
321
|
+
return platform === workflowPlatform;
|
|
322
|
+
});
|
|
323
|
+
if (workflowPlatform) {
|
|
324
|
+
console.log(`\nℹ️ Showing ${workflowPlatform} and platform-agnostic steps (workflow targets ${workflowPlatform})\n`);
|
|
325
|
+
}
|
|
326
|
+
const choices = stepNames.map((name) => {
|
|
327
|
+
const m = getStepMetadata(name);
|
|
328
|
+
return { title: `${name}${m ? ` — ${m.description}` : ''}`, value: name };
|
|
329
|
+
});
|
|
330
|
+
const { selectionMode } = await prompts({
|
|
331
|
+
type: 'select',
|
|
332
|
+
name: 'selectionMode',
|
|
333
|
+
message: 'How would you like to find a step?',
|
|
334
|
+
choices: [
|
|
335
|
+
{ title: 'Search — type to filter', value: 'search' },
|
|
336
|
+
{ title: 'Browse — scroll through list', value: 'browse' },
|
|
337
|
+
],
|
|
338
|
+
});
|
|
339
|
+
if (!selectionMode) {
|
|
340
|
+
console.log('\nCancelled.');
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
let stepName;
|
|
344
|
+
if (selectionMode === 'search') {
|
|
345
|
+
const result = await prompts({
|
|
346
|
+
type: 'autocomplete',
|
|
347
|
+
name: 'stepName',
|
|
348
|
+
message: 'Search step:',
|
|
349
|
+
choices,
|
|
350
|
+
suggest: async (input, _) => {
|
|
351
|
+
const q = input.toLowerCase();
|
|
352
|
+
return choices.filter((c) => c.title.toLowerCase().includes(q));
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
stepName = result.stepName;
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
const result = await prompts({
|
|
359
|
+
type: 'select',
|
|
360
|
+
name: 'stepName',
|
|
361
|
+
message: 'Select step type:',
|
|
362
|
+
choices,
|
|
363
|
+
});
|
|
364
|
+
stepName = result.stepName;
|
|
365
|
+
}
|
|
366
|
+
if (!stepName) {
|
|
367
|
+
console.log('\nCancelled.');
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
await handleAddRegisteredStep(pipelinePath, workflowName, pipeline, stepName, secretsManager);
|
|
371
|
+
}
|
|
372
|
+
// ---------------------------------------------------------------------------
|
|
373
|
+
export async function handleEditCommand(pipelinePath, workflowName) {
|
|
374
|
+
const absolutePath = resolve(pipelinePath);
|
|
375
|
+
const fileName = basename(absolutePath);
|
|
376
|
+
// Load and validate the pipeline
|
|
377
|
+
let pipeline;
|
|
378
|
+
try {
|
|
379
|
+
pipeline = loadYAMLPipeline(absolutePath);
|
|
380
|
+
}
|
|
381
|
+
catch (err) {
|
|
382
|
+
console.error(`\n✗ ${err instanceof Error ? err.message : String(err)}`);
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
const workflowNames = Object.keys(pipeline.workflows);
|
|
386
|
+
// Select workflow
|
|
387
|
+
let selectedWorkflow = workflowName;
|
|
388
|
+
if (!selectedWorkflow) {
|
|
389
|
+
if (workflowNames.length === 1) {
|
|
390
|
+
selectedWorkflow = workflowNames[0];
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
const { workflow } = await prompts({
|
|
394
|
+
type: 'select',
|
|
395
|
+
name: 'workflow',
|
|
396
|
+
message: 'Select a workflow:',
|
|
397
|
+
choices: workflowNames.map((name) => ({ title: name, value: name })),
|
|
398
|
+
initial: 0,
|
|
399
|
+
});
|
|
400
|
+
if (!workflow) {
|
|
401
|
+
console.log('\nCancelled.');
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
selectedWorkflow = workflow;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
// Narrowed to string — selection logic above guarantees this
|
|
408
|
+
const chosenWorkflow = selectedWorkflow;
|
|
409
|
+
const workflowDef = pipeline.workflows[chosenWorkflow];
|
|
410
|
+
if (!workflowDef) {
|
|
411
|
+
console.error(`\n✗ Workflow '${chosenWorkflow}' not found.`);
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
// Show pipeline summary
|
|
415
|
+
printPipelineSummary(fileName, chosenWorkflow, workflowDef.steps);
|
|
416
|
+
// Top-level action
|
|
417
|
+
const { action } = await prompts({
|
|
418
|
+
type: 'select',
|
|
419
|
+
name: 'action',
|
|
420
|
+
message: 'What would you like to do?',
|
|
421
|
+
choices: [
|
|
422
|
+
{ title: "Edit a step's inputs", value: 'edit' },
|
|
423
|
+
{ title: 'Add step', value: 'add' },
|
|
424
|
+
{ title: 'Remove step', value: 'remove' },
|
|
425
|
+
{ title: 'Exit', value: 'exit' },
|
|
426
|
+
],
|
|
427
|
+
});
|
|
428
|
+
if (!action || action === 'exit') {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (action === 'add') {
|
|
432
|
+
const secretsManager = new SecretsManager();
|
|
433
|
+
await handleAddStep(absolutePath, chosenWorkflow, pipeline, secretsManager);
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (action === 'remove') {
|
|
437
|
+
const rawDoc = yaml.load(fs.readFileSync(absolutePath, 'utf-8'));
|
|
438
|
+
const steps = rawDoc.workflows[chosenWorkflow].steps ?? [];
|
|
439
|
+
if (steps.length === 0) {
|
|
440
|
+
console.log('\nNo steps to remove.\n');
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const { stepIndex } = await prompts({
|
|
444
|
+
type: 'select',
|
|
445
|
+
name: 'stepIndex',
|
|
446
|
+
message: 'Select step to remove:',
|
|
447
|
+
choices: steps.map((s, i) => {
|
|
448
|
+
const key = Object.keys(s)[0];
|
|
449
|
+
const title = s[key]?.title
|
|
450
|
+
? `${i + 1}. ${key.split('@')[0]} (${s[key].title})`
|
|
451
|
+
: `${i + 1}. ${key.split('@')[0]}`;
|
|
452
|
+
return { title, value: i };
|
|
453
|
+
}),
|
|
454
|
+
});
|
|
455
|
+
if (stepIndex === undefined) {
|
|
456
|
+
console.log('\nCancelled.');
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const removedKey = Object.keys(steps[stepIndex])[0];
|
|
460
|
+
const { confirmed } = await prompts({
|
|
461
|
+
type: 'confirm',
|
|
462
|
+
name: 'confirmed',
|
|
463
|
+
message: `Remove '${removedKey.split('@')[0]}'?`,
|
|
464
|
+
initial: false,
|
|
465
|
+
});
|
|
466
|
+
if (!confirmed) {
|
|
467
|
+
console.log('\nCancelled.');
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
steps.splice(stepIndex, 1);
|
|
471
|
+
fs.writeFileSync(absolutePath, yaml.dump(rawDoc, { indent: 2, lineWidth: -1, noRefs: true }), 'utf-8');
|
|
472
|
+
console.log(`\n✅ Removed ${removedKey.split('@')[0]} from ${fileName}`);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
// Load raw YAML for write-back (preserves structure we don't touch)
|
|
476
|
+
const rawContent = fs.readFileSync(absolutePath, 'utf-8');
|
|
477
|
+
const doc = yaml.load(rawContent);
|
|
478
|
+
let madeChanges = false;
|
|
479
|
+
let continueEditing = true;
|
|
480
|
+
while (continueEditing) {
|
|
481
|
+
// Select step
|
|
482
|
+
const { stepIndex } = await prompts({
|
|
483
|
+
type: 'select',
|
|
484
|
+
name: 'stepIndex',
|
|
485
|
+
message: 'Select a step to edit:',
|
|
486
|
+
choices: workflowDef.steps.map((step, i) => {
|
|
487
|
+
const stepKey = getStepKey(step);
|
|
488
|
+
const config = step[stepKey];
|
|
489
|
+
const displayName = getStepDisplayName(stepKey, step);
|
|
490
|
+
const inputCount = config?.inputs ? Object.keys(config.inputs).length : 0;
|
|
491
|
+
return {
|
|
492
|
+
title: inputCount > 0
|
|
493
|
+
? `${displayName} (${inputCount} inputs)`
|
|
494
|
+
: `${displayName} (no inputs)`,
|
|
495
|
+
value: i,
|
|
496
|
+
};
|
|
497
|
+
}),
|
|
498
|
+
initial: 0,
|
|
499
|
+
});
|
|
500
|
+
if (stepIndex === undefined) {
|
|
501
|
+
console.log('\nCancelled.');
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
const selectedStep = workflowDef.steps[stepIndex];
|
|
505
|
+
const stepKey = getStepKey(selectedStep);
|
|
506
|
+
const stepConfig = selectedStep[stepKey];
|
|
507
|
+
const inputs = stepConfig?.inputs;
|
|
508
|
+
// Look up all available inputs from step metadata
|
|
509
|
+
const stepId = stepKey.split('@')[0];
|
|
510
|
+
const metadata = getStepMetadata(stepId);
|
|
511
|
+
const metadataInputs = metadata?.inputs ?? {};
|
|
512
|
+
// Merge: currently-set YAML inputs first, then unset metadata inputs
|
|
513
|
+
const setKeys = new Set(Object.keys(inputs ?? {}));
|
|
514
|
+
const allInputKeys = [
|
|
515
|
+
...Object.keys(inputs ?? {}),
|
|
516
|
+
...Object.keys(metadataInputs).filter(k => !setKeys.has(k)),
|
|
517
|
+
];
|
|
518
|
+
if (allInputKeys.length === 0) {
|
|
519
|
+
console.log('\nThis step has no inputs to edit.\n');
|
|
520
|
+
}
|
|
521
|
+
else {
|
|
522
|
+
console.log(`\nEditing: ${getStepDisplayName(stepKey, selectedStep)}`);
|
|
523
|
+
console.log('(Press Enter to keep current value, enter - to remove an input)\n');
|
|
524
|
+
const changes = {};
|
|
525
|
+
const deletions = new Set();
|
|
526
|
+
for (const key of allInputKeys) {
|
|
527
|
+
const isCurrentlySet = inputs != null && key in inputs;
|
|
528
|
+
const currentValue = isCurrentlySet ? inputs[key] : undefined;
|
|
529
|
+
const metaDefault = metadataInputs[key]?.default;
|
|
530
|
+
const displayCurrent = isCurrentlySet
|
|
531
|
+
? formatValueForDisplay(currentValue)
|
|
532
|
+
: metaDefault !== undefined
|
|
533
|
+
? `not set (default: ${metaDefault})`
|
|
534
|
+
: 'not set';
|
|
535
|
+
const isMultiline = typeof currentValue === 'string' && currentValue.includes('\n');
|
|
536
|
+
const editInitial = isCurrentlySet && !isMultiline ? formatValueForEdit(currentValue) : '';
|
|
537
|
+
const { newValue } = await prompts({
|
|
538
|
+
type: 'text',
|
|
539
|
+
name: 'newValue',
|
|
540
|
+
message: `${key} [${displayCurrent}]:`,
|
|
541
|
+
initial: editInitial,
|
|
542
|
+
});
|
|
543
|
+
if (newValue === undefined) {
|
|
544
|
+
console.log('\nCancelled.');
|
|
545
|
+
process.exit(0);
|
|
546
|
+
}
|
|
547
|
+
if (newValue === '-') {
|
|
548
|
+
if (isCurrentlySet)
|
|
549
|
+
deletions.add(key);
|
|
550
|
+
}
|
|
551
|
+
else if (newValue !== '' && newValue !== editInitial) {
|
|
552
|
+
changes[key] = parseEditedValue(newValue, currentValue ?? '');
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (Object.keys(changes).length === 0 && deletions.size === 0) {
|
|
556
|
+
console.log('\nNo changes made.\n');
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
console.log('\nChanges:');
|
|
560
|
+
for (const [key, newVal] of Object.entries(changes)) {
|
|
561
|
+
const oldDisplay = inputs && key in inputs ? formatValueForDisplay(inputs[key]) : 'not set';
|
|
562
|
+
const newDisplay = formatValueForDisplay(newVal);
|
|
563
|
+
console.log(` ${key}: ${oldDisplay} → ${newDisplay}`);
|
|
564
|
+
}
|
|
565
|
+
for (const key of deletions) {
|
|
566
|
+
console.log(` ${key}: ${formatValueForDisplay(inputs[key])} → (removed)`);
|
|
567
|
+
}
|
|
568
|
+
const { confirmSave } = await prompts({
|
|
569
|
+
type: 'confirm',
|
|
570
|
+
name: 'confirmSave',
|
|
571
|
+
message: 'Save changes?',
|
|
572
|
+
initial: true,
|
|
573
|
+
});
|
|
574
|
+
if (confirmSave) {
|
|
575
|
+
// Apply to the raw doc for write-back
|
|
576
|
+
const docStepConfig = doc.workflows[chosenWorkflow].steps[stepIndex][stepKey];
|
|
577
|
+
if (!docStepConfig.inputs)
|
|
578
|
+
docStepConfig.inputs = {};
|
|
579
|
+
for (const [key, val] of Object.entries(changes)) {
|
|
580
|
+
docStepConfig.inputs[key] = val;
|
|
581
|
+
if (!stepConfig.inputs)
|
|
582
|
+
stepConfig.inputs = {};
|
|
583
|
+
stepConfig.inputs[key] = val;
|
|
584
|
+
}
|
|
585
|
+
for (const key of deletions) {
|
|
586
|
+
delete docStepConfig.inputs[key];
|
|
587
|
+
if (inputs)
|
|
588
|
+
delete inputs[key];
|
|
589
|
+
}
|
|
590
|
+
const updatedYaml = yaml.dump(doc, {
|
|
591
|
+
indent: 2,
|
|
592
|
+
lineWidth: -1,
|
|
593
|
+
noRefs: true,
|
|
594
|
+
});
|
|
595
|
+
fs.writeFileSync(absolutePath, updatedYaml, 'utf-8');
|
|
596
|
+
madeChanges = true;
|
|
597
|
+
console.log(`\n✅ Saved to ${fileName}`);
|
|
598
|
+
// Guardrail: check for $VAR references in saved values that have no stored value
|
|
599
|
+
const secretsManager = new SecretsManager();
|
|
600
|
+
const unresolvedVars = new Set();
|
|
601
|
+
for (const val of Object.values(changes)) {
|
|
602
|
+
for (const ref of extractEnvRefs(val)) {
|
|
603
|
+
if (!isVarDefined(ref, pipeline, chosenWorkflow, secretsManager)) {
|
|
604
|
+
unresolvedVars.add(ref);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
for (const varName of unresolvedVars) {
|
|
609
|
+
console.log(`\n⚠️ $${varName} is referenced but has no stored value.`);
|
|
610
|
+
const { storeNow } = await prompts({
|
|
611
|
+
type: 'confirm',
|
|
612
|
+
name: 'storeNow',
|
|
613
|
+
message: `Store a value for ${varName} in .cibuild-secrets.json?`,
|
|
614
|
+
initial: true,
|
|
615
|
+
});
|
|
616
|
+
if (storeNow) {
|
|
617
|
+
const { secretValue } = await prompts({
|
|
618
|
+
type: 'password',
|
|
619
|
+
name: 'secretValue',
|
|
620
|
+
message: `Value for ${varName}:`,
|
|
621
|
+
validate: (v) => v.trim() === '' ? 'Value cannot be empty' : true,
|
|
622
|
+
});
|
|
623
|
+
if (secretValue) {
|
|
624
|
+
secretsManager.storeSecret(varName, secretValue, chosenWorkflow);
|
|
625
|
+
console.log(`✅ Stored ${varName} (workflow: ${chosenWorkflow})`);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
console.log(` You can add it later with: ci secrets add ${varName} ${pipelinePath} -w ${chosenWorkflow}`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
console.log();
|
|
636
|
+
const { again } = await prompts({
|
|
637
|
+
type: 'confirm',
|
|
638
|
+
name: 'again',
|
|
639
|
+
message: 'Edit another step?',
|
|
640
|
+
initial: false,
|
|
641
|
+
});
|
|
642
|
+
continueEditing = !!again;
|
|
643
|
+
}
|
|
644
|
+
if (madeChanges) {
|
|
645
|
+
console.log(`Next steps:`);
|
|
646
|
+
console.log(` ci validate ${pipelinePath}`);
|
|
647
|
+
console.log(` ci run ${pipelinePath} -w ${chosenWorkflow}`);
|
|
648
|
+
console.log();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
//# sourceMappingURL=edit.js.map
|