viberails 0.6.11 → 0.6.13
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/README.md +140 -0
- package/dist/index.cjs +169 -155
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +169 -155
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
package/dist/index.js
CHANGED
|
@@ -66,8 +66,8 @@ var FILE_NAMING_OPTIONS = [
|
|
|
66
66
|
{ value: "snake_case", label: "snake_case" }
|
|
67
67
|
];
|
|
68
68
|
var COMPONENT_NAMING_OPTIONS = [
|
|
69
|
-
{ value: "PascalCase", label: "PascalCase", hint: "MyComponent
|
|
70
|
-
{ value: "camelCase", label: "camelCase", hint: "myComponent
|
|
69
|
+
{ value: "PascalCase", label: "PascalCase", hint: "e.g. MyComponent, UserProfile" },
|
|
70
|
+
{ value: "camelCase", label: "camelCase", hint: "e.g. myComponent, userProfile" }
|
|
71
71
|
];
|
|
72
72
|
var HOOK_NAMING_OPTIONS = [
|
|
73
73
|
{ value: "useXxx", label: "useXxx", hint: "useAuth, useFormData" },
|
|
@@ -2987,7 +2987,169 @@ import * as clack11 from "@clack/prompts";
|
|
|
2987
2987
|
|
|
2988
2988
|
// src/utils/prompt-main-menu-handlers.ts
|
|
2989
2989
|
import * as clack10 from "@clack/prompts";
|
|
2990
|
+
import chalk13 from "chalk";
|
|
2991
|
+
|
|
2992
|
+
// src/utils/prompt-main-menu-hints.ts
|
|
2990
2993
|
import chalk12 from "chalk";
|
|
2994
|
+
function fileLimitsHint(config) {
|
|
2995
|
+
const max = config.rules.maxFileLines;
|
|
2996
|
+
const test = config.rules.maxTestFileLines;
|
|
2997
|
+
return test > 0 ? `${max} lines, tests ${test}` : `${max} lines`;
|
|
2998
|
+
}
|
|
2999
|
+
function getEffectiveFileNaming(config) {
|
|
3000
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3001
|
+
if (rootPkg.conventions?.fileNaming) {
|
|
3002
|
+
return { naming: rootPkg.conventions.fileNaming, source: "root" };
|
|
3003
|
+
}
|
|
3004
|
+
if (config.packages.length > 1) {
|
|
3005
|
+
const namingValues = config.packages.map((p) => p.conventions?.fileNaming).filter((n) => !!n);
|
|
3006
|
+
if (namingValues.length > 0 && new Set(namingValues).size === 1) {
|
|
3007
|
+
return { naming: namingValues[0], source: "consensus" };
|
|
3008
|
+
}
|
|
3009
|
+
}
|
|
3010
|
+
return void 0;
|
|
3011
|
+
}
|
|
3012
|
+
function fileNamingHint(config, scanResult) {
|
|
3013
|
+
if (!config.rules.enforceNaming) return "not enforced";
|
|
3014
|
+
const effective = getEffectiveFileNaming(config);
|
|
3015
|
+
if (effective) {
|
|
3016
|
+
const detected = scanResult.packages.some(
|
|
3017
|
+
(p) => p.conventions.fileNaming?.value === effective.naming && p.conventions.fileNaming.confidence !== "low"
|
|
3018
|
+
);
|
|
3019
|
+
return detected ? `${effective.naming} (detected)` : effective.naming;
|
|
3020
|
+
}
|
|
3021
|
+
return "not set \u2014 select to configure";
|
|
3022
|
+
}
|
|
3023
|
+
function fileNamingStatus(config) {
|
|
3024
|
+
if (!config.rules.enforceNaming) return "unconfigured";
|
|
3025
|
+
return getEffectiveFileNaming(config) ? "ok" : "needs-input";
|
|
3026
|
+
}
|
|
3027
|
+
function missingTestsHint(config) {
|
|
3028
|
+
if (!config.rules.enforceMissingTests) return "not enforced";
|
|
3029
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3030
|
+
const pattern = rootPkg.structure?.testPattern;
|
|
3031
|
+
return pattern ? `enforced (${pattern})` : "enforced";
|
|
3032
|
+
}
|
|
3033
|
+
function coverageHint(config, hasTestRunner) {
|
|
3034
|
+
if (config.rules.testCoverage === 0) return "disabled";
|
|
3035
|
+
if (!hasTestRunner)
|
|
3036
|
+
return `${config.rules.testCoverage}% target (inactive \u2014 no test runner)`;
|
|
3037
|
+
const isMonorepo = config.packages.length > 1;
|
|
3038
|
+
if (isMonorepo) {
|
|
3039
|
+
const withCov = config.packages.filter(
|
|
3040
|
+
(p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
|
|
3041
|
+
);
|
|
3042
|
+
const exempt = config.packages.length - withCov.length;
|
|
3043
|
+
return exempt > 0 ? `${config.rules.testCoverage}% (${withCov.length}/${config.packages.length} packages, ${exempt} exempt)` : `${config.rules.testCoverage}%`;
|
|
3044
|
+
}
|
|
3045
|
+
return `${config.rules.testCoverage}%`;
|
|
3046
|
+
}
|
|
3047
|
+
function aiContextHint(config) {
|
|
3048
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3049
|
+
const count = [
|
|
3050
|
+
rootPkg.conventions?.componentNaming,
|
|
3051
|
+
rootPkg.conventions?.hookNaming,
|
|
3052
|
+
rootPkg.conventions?.importAlias
|
|
3053
|
+
].filter(Boolean).length;
|
|
3054
|
+
if (count === 3) return "all set";
|
|
3055
|
+
if (count > 0) return `${count} of 3 conventions`;
|
|
3056
|
+
return "none set \u2014 optional AI guidelines";
|
|
3057
|
+
}
|
|
3058
|
+
function aiContextStatus(config) {
|
|
3059
|
+
const rootPkg = getRootPackage(config.packages);
|
|
3060
|
+
const count = [
|
|
3061
|
+
rootPkg.conventions?.componentNaming,
|
|
3062
|
+
rootPkg.conventions?.hookNaming,
|
|
3063
|
+
rootPkg.conventions?.importAlias
|
|
3064
|
+
].filter(Boolean).length;
|
|
3065
|
+
if (count === 3) return "ok";
|
|
3066
|
+
if (count > 0) return "partial";
|
|
3067
|
+
return "unconfigured";
|
|
3068
|
+
}
|
|
3069
|
+
function packageOverridesHint(config) {
|
|
3070
|
+
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3071
|
+
const editable = config.packages.filter((p) => p.path !== ".");
|
|
3072
|
+
const customized = editable.filter(
|
|
3073
|
+
(p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
|
|
3074
|
+
).length;
|
|
3075
|
+
return customized > 0 ? `${editable.length} packages (${customized} customized)` : `${editable.length} packages`;
|
|
3076
|
+
}
|
|
3077
|
+
function boundariesHint(config, state) {
|
|
3078
|
+
if (!state.visited.boundaries || !config.rules.enforceBoundaries) return "not enabled";
|
|
3079
|
+
const deny = config.boundaries?.deny;
|
|
3080
|
+
if (!deny) return "enabled";
|
|
3081
|
+
const ruleCount = Object.values(deny).reduce((s, a) => s + a.length, 0);
|
|
3082
|
+
const pkgCount = Object.keys(deny).length;
|
|
3083
|
+
return `${ruleCount} rules across ${pkgCount} packages`;
|
|
3084
|
+
}
|
|
3085
|
+
function packageOverridesStatus(config) {
|
|
3086
|
+
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3087
|
+
const editable = config.packages.filter((p) => p.path !== ".");
|
|
3088
|
+
const customized = editable.some(
|
|
3089
|
+
(p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
|
|
3090
|
+
);
|
|
3091
|
+
return customized ? "ok" : "unconfigured";
|
|
3092
|
+
}
|
|
3093
|
+
function statusIcon(status) {
|
|
3094
|
+
if (status === "ok") return chalk12.green("\u2713");
|
|
3095
|
+
if (status === "needs-input") return chalk12.yellow("?");
|
|
3096
|
+
if (status === "unconfigured") return chalk12.dim("-");
|
|
3097
|
+
return chalk12.yellow("~");
|
|
3098
|
+
}
|
|
3099
|
+
function buildMainMenuOptions(config, scanResult, state) {
|
|
3100
|
+
const namingStatus = fileNamingStatus(config);
|
|
3101
|
+
const coverageStatus = config.rules.testCoverage === 0 ? "unconfigured" : !state.hasTestRunner ? "partial" : "ok";
|
|
3102
|
+
const missingTestsStatus = config.rules.enforceMissingTests ? "ok" : "unconfigured";
|
|
3103
|
+
const options = [
|
|
3104
|
+
{
|
|
3105
|
+
value: "fileLimits",
|
|
3106
|
+
label: `${statusIcon("ok")} Max file size`,
|
|
3107
|
+
hint: fileLimitsHint(config)
|
|
3108
|
+
},
|
|
3109
|
+
{
|
|
3110
|
+
value: "fileNaming",
|
|
3111
|
+
label: `${statusIcon(namingStatus)} File naming`,
|
|
3112
|
+
hint: fileNamingHint(config, scanResult)
|
|
3113
|
+
},
|
|
3114
|
+
{
|
|
3115
|
+
value: "missingTests",
|
|
3116
|
+
label: `${statusIcon(missingTestsStatus)} Missing tests`,
|
|
3117
|
+
hint: missingTestsHint(config)
|
|
3118
|
+
},
|
|
3119
|
+
{
|
|
3120
|
+
value: "coverage",
|
|
3121
|
+
label: `${statusIcon(coverageStatus)} Coverage`,
|
|
3122
|
+
hint: coverageHint(config, state.hasTestRunner)
|
|
3123
|
+
},
|
|
3124
|
+
{
|
|
3125
|
+
value: "aiContext",
|
|
3126
|
+
label: `${statusIcon(aiContextStatus(config))} AI context`,
|
|
3127
|
+
hint: aiContextHint(config)
|
|
3128
|
+
}
|
|
3129
|
+
];
|
|
3130
|
+
if (config.packages.length > 1) {
|
|
3131
|
+
const bIcon = statusIcon(
|
|
3132
|
+
state.visited.boundaries && config.rules.enforceBoundaries ? "ok" : "unconfigured"
|
|
3133
|
+
);
|
|
3134
|
+
const poIcon = statusIcon(packageOverridesStatus(config));
|
|
3135
|
+
options.push(
|
|
3136
|
+
{
|
|
3137
|
+
value: "packageOverrides",
|
|
3138
|
+
label: `${poIcon} Per-package overrides`,
|
|
3139
|
+
hint: packageOverridesHint(config)
|
|
3140
|
+
},
|
|
3141
|
+
{ value: "boundaries", label: `${bIcon} Boundaries`, hint: boundariesHint(config, state) }
|
|
3142
|
+
);
|
|
3143
|
+
}
|
|
3144
|
+
options.push(
|
|
3145
|
+
{ value: "reset", label: " Reset all to defaults" },
|
|
3146
|
+
{ value: "review", label: " Review scan details", hint: "detected stack & conventions" },
|
|
3147
|
+
{ value: "done", label: " Done \u2014 write config" }
|
|
3148
|
+
);
|
|
3149
|
+
return options;
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
// src/utils/prompt-main-menu-handlers.ts
|
|
2991
3153
|
async function handleFileNaming(config, scanResult) {
|
|
2992
3154
|
const isMonorepo = config.packages.length > 1;
|
|
2993
3155
|
if (isMonorepo) {
|
|
@@ -3011,10 +3173,11 @@ async function handleFileNaming(config, scanResult) {
|
|
|
3011
3173
|
return { value: opt.value, label: opt.label };
|
|
3012
3174
|
});
|
|
3013
3175
|
const rootPkg = getRootPackage(config.packages);
|
|
3176
|
+
const effective = getEffectiveFileNaming(config);
|
|
3014
3177
|
const selected = await clack10.select({
|
|
3015
3178
|
message: isMonorepo ? "Default file naming convention" : "File naming convention",
|
|
3016
3179
|
options: [...namingOptions, { value: SENTINEL_SKIP, label: "Don't enforce" }],
|
|
3017
|
-
initialValue:
|
|
3180
|
+
initialValue: effective?.naming ?? SENTINEL_SKIP
|
|
3018
3181
|
});
|
|
3019
3182
|
if (isCancelled(selected)) return;
|
|
3020
3183
|
if (selected === SENTINEL_SKIP) {
|
|
@@ -3131,8 +3294,8 @@ async function handleAiContext(config) {
|
|
|
3131
3294
|
const rootPkg = getRootPackage(config.packages);
|
|
3132
3295
|
rootPkg.conventions = rootPkg.conventions ?? {};
|
|
3133
3296
|
while (true) {
|
|
3134
|
-
const ok =
|
|
3135
|
-
const unset =
|
|
3297
|
+
const ok = chalk13.green("\u2713");
|
|
3298
|
+
const unset = chalk13.dim("-");
|
|
3136
3299
|
const options = [
|
|
3137
3300
|
{
|
|
3138
3301
|
value: "componentNaming",
|
|
@@ -3214,155 +3377,6 @@ async function handleAiContext(config) {
|
|
|
3214
3377
|
}
|
|
3215
3378
|
}
|
|
3216
3379
|
|
|
3217
|
-
// src/utils/prompt-main-menu-hints.ts
|
|
3218
|
-
import chalk13 from "chalk";
|
|
3219
|
-
function fileLimitsHint(config) {
|
|
3220
|
-
const max = config.rules.maxFileLines;
|
|
3221
|
-
const test = config.rules.maxTestFileLines;
|
|
3222
|
-
return test > 0 ? `${max} lines, tests ${test}` : `${max} lines`;
|
|
3223
|
-
}
|
|
3224
|
-
function fileNamingHint(config, scanResult) {
|
|
3225
|
-
const rootPkg = getRootPackage(config.packages);
|
|
3226
|
-
const naming = rootPkg.conventions?.fileNaming;
|
|
3227
|
-
if (!config.rules.enforceNaming) return "not enforced";
|
|
3228
|
-
if (naming) {
|
|
3229
|
-
const detected = scanResult.packages.some(
|
|
3230
|
-
(p) => p.conventions.fileNaming?.value === naming && p.conventions.fileNaming.confidence === "high"
|
|
3231
|
-
);
|
|
3232
|
-
return detected ? `${naming} (detected)` : naming;
|
|
3233
|
-
}
|
|
3234
|
-
return "not set \u2014 select to configure";
|
|
3235
|
-
}
|
|
3236
|
-
function fileNamingStatus(config) {
|
|
3237
|
-
if (!config.rules.enforceNaming) return "unconfigured";
|
|
3238
|
-
const rootPkg = getRootPackage(config.packages);
|
|
3239
|
-
return rootPkg.conventions?.fileNaming ? "ok" : "needs-input";
|
|
3240
|
-
}
|
|
3241
|
-
function missingTestsHint(config) {
|
|
3242
|
-
if (!config.rules.enforceMissingTests) return "not enforced";
|
|
3243
|
-
const rootPkg = getRootPackage(config.packages);
|
|
3244
|
-
const pattern = rootPkg.structure?.testPattern;
|
|
3245
|
-
return pattern ? `enforced (${pattern})` : "enforced";
|
|
3246
|
-
}
|
|
3247
|
-
function coverageHint(config, hasTestRunner) {
|
|
3248
|
-
if (config.rules.testCoverage === 0) return "disabled";
|
|
3249
|
-
if (!hasTestRunner)
|
|
3250
|
-
return `${config.rules.testCoverage}% target (inactive \u2014 no test runner)`;
|
|
3251
|
-
const isMonorepo = config.packages.length > 1;
|
|
3252
|
-
if (isMonorepo) {
|
|
3253
|
-
const withCov = config.packages.filter(
|
|
3254
|
-
(p) => (p.rules?.testCoverage ?? config.rules.testCoverage) > 0
|
|
3255
|
-
);
|
|
3256
|
-
const exempt = config.packages.length - withCov.length;
|
|
3257
|
-
return exempt > 0 ? `${config.rules.testCoverage}% (${withCov.length}/${config.packages.length} packages, ${exempt} exempt)` : `${config.rules.testCoverage}%`;
|
|
3258
|
-
}
|
|
3259
|
-
return `${config.rules.testCoverage}%`;
|
|
3260
|
-
}
|
|
3261
|
-
function aiContextHint(config) {
|
|
3262
|
-
const rootPkg = getRootPackage(config.packages);
|
|
3263
|
-
const count = [
|
|
3264
|
-
rootPkg.conventions?.componentNaming,
|
|
3265
|
-
rootPkg.conventions?.hookNaming,
|
|
3266
|
-
rootPkg.conventions?.importAlias
|
|
3267
|
-
].filter(Boolean).length;
|
|
3268
|
-
if (count === 3) return "all set";
|
|
3269
|
-
if (count > 0) return `${count} of 3 conventions`;
|
|
3270
|
-
return "none set \u2014 optional AI guidelines";
|
|
3271
|
-
}
|
|
3272
|
-
function aiContextStatus(config) {
|
|
3273
|
-
const rootPkg = getRootPackage(config.packages);
|
|
3274
|
-
const count = [
|
|
3275
|
-
rootPkg.conventions?.componentNaming,
|
|
3276
|
-
rootPkg.conventions?.hookNaming,
|
|
3277
|
-
rootPkg.conventions?.importAlias
|
|
3278
|
-
].filter(Boolean).length;
|
|
3279
|
-
if (count === 3) return "ok";
|
|
3280
|
-
if (count > 0) return "partial";
|
|
3281
|
-
return "unconfigured";
|
|
3282
|
-
}
|
|
3283
|
-
function packageOverridesHint(config) {
|
|
3284
|
-
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3285
|
-
const editable = config.packages.filter((p) => p.path !== ".");
|
|
3286
|
-
const customized = editable.filter(
|
|
3287
|
-
(p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
|
|
3288
|
-
).length;
|
|
3289
|
-
return customized > 0 ? `${editable.length} packages (${customized} customized)` : `${editable.length} packages`;
|
|
3290
|
-
}
|
|
3291
|
-
function boundariesHint(config, state) {
|
|
3292
|
-
if (!state.visited.boundaries || !config.rules.enforceBoundaries) return "not enabled";
|
|
3293
|
-
const deny = config.boundaries?.deny;
|
|
3294
|
-
if (!deny) return "enabled";
|
|
3295
|
-
const ruleCount = Object.values(deny).reduce((s, a) => s + a.length, 0);
|
|
3296
|
-
const pkgCount = Object.keys(deny).length;
|
|
3297
|
-
return `${ruleCount} rules across ${pkgCount} packages`;
|
|
3298
|
-
}
|
|
3299
|
-
function packageOverridesStatus(config) {
|
|
3300
|
-
const rootNaming = getRootPackage(config.packages).conventions?.fileNaming;
|
|
3301
|
-
const editable = config.packages.filter((p) => p.path !== ".");
|
|
3302
|
-
const customized = editable.some(
|
|
3303
|
-
(p) => p.rules || p.coverage || p.conventions?.fileNaming !== void 0 && p.conventions.fileNaming !== rootNaming
|
|
3304
|
-
);
|
|
3305
|
-
return customized ? "ok" : "unconfigured";
|
|
3306
|
-
}
|
|
3307
|
-
function statusIcon(status) {
|
|
3308
|
-
if (status === "ok") return chalk13.green("\u2713");
|
|
3309
|
-
if (status === "needs-input") return chalk13.yellow("?");
|
|
3310
|
-
if (status === "unconfigured") return chalk13.dim("-");
|
|
3311
|
-
return chalk13.yellow("~");
|
|
3312
|
-
}
|
|
3313
|
-
function buildMainMenuOptions(config, scanResult, state) {
|
|
3314
|
-
const namingStatus = fileNamingStatus(config);
|
|
3315
|
-
const coverageStatus = config.rules.testCoverage === 0 ? "unconfigured" : !state.hasTestRunner ? "partial" : "ok";
|
|
3316
|
-
const missingTestsStatus = config.rules.enforceMissingTests ? "ok" : "unconfigured";
|
|
3317
|
-
const options = [
|
|
3318
|
-
{
|
|
3319
|
-
value: "fileLimits",
|
|
3320
|
-
label: `${statusIcon("ok")} Max file size`,
|
|
3321
|
-
hint: fileLimitsHint(config)
|
|
3322
|
-
},
|
|
3323
|
-
{
|
|
3324
|
-
value: "fileNaming",
|
|
3325
|
-
label: `${statusIcon(namingStatus)} File naming`,
|
|
3326
|
-
hint: fileNamingHint(config, scanResult)
|
|
3327
|
-
},
|
|
3328
|
-
{
|
|
3329
|
-
value: "missingTests",
|
|
3330
|
-
label: `${statusIcon(missingTestsStatus)} Missing tests`,
|
|
3331
|
-
hint: missingTestsHint(config)
|
|
3332
|
-
},
|
|
3333
|
-
{
|
|
3334
|
-
value: "coverage",
|
|
3335
|
-
label: `${statusIcon(coverageStatus)} Coverage`,
|
|
3336
|
-
hint: coverageHint(config, state.hasTestRunner)
|
|
3337
|
-
},
|
|
3338
|
-
{
|
|
3339
|
-
value: "aiContext",
|
|
3340
|
-
label: `${statusIcon(aiContextStatus(config))} AI context`,
|
|
3341
|
-
hint: aiContextHint(config)
|
|
3342
|
-
}
|
|
3343
|
-
];
|
|
3344
|
-
if (config.packages.length > 1) {
|
|
3345
|
-
const bIcon = statusIcon(
|
|
3346
|
-
state.visited.boundaries && config.rules.enforceBoundaries ? "ok" : "unconfigured"
|
|
3347
|
-
);
|
|
3348
|
-
const poIcon = statusIcon(packageOverridesStatus(config));
|
|
3349
|
-
options.push(
|
|
3350
|
-
{
|
|
3351
|
-
value: "packageOverrides",
|
|
3352
|
-
label: `${poIcon} Per-package overrides`,
|
|
3353
|
-
hint: packageOverridesHint(config)
|
|
3354
|
-
},
|
|
3355
|
-
{ value: "boundaries", label: `${bIcon} Boundaries`, hint: boundariesHint(config, state) }
|
|
3356
|
-
);
|
|
3357
|
-
}
|
|
3358
|
-
options.push(
|
|
3359
|
-
{ value: "reset", label: " Reset all to defaults" },
|
|
3360
|
-
{ value: "review", label: " Review scan details", hint: "detected stack & conventions" },
|
|
3361
|
-
{ value: "done", label: " Done \u2014 write config" }
|
|
3362
|
-
);
|
|
3363
|
-
return options;
|
|
3364
|
-
}
|
|
3365
|
-
|
|
3366
3380
|
// src/utils/prompt-main-menu.ts
|
|
3367
3381
|
async function promptMainMenu(config, scanResult, opts) {
|
|
3368
3382
|
const originalConfig = structuredClone(config);
|
|
@@ -4304,7 +4318,7 @@ ${chalk19.bold("Synced:")}`);
|
|
|
4304
4318
|
}
|
|
4305
4319
|
|
|
4306
4320
|
// src/index.ts
|
|
4307
|
-
var VERSION = "0.6.
|
|
4321
|
+
var VERSION = "0.6.13";
|
|
4308
4322
|
var program = new Command();
|
|
4309
4323
|
program.name("viberails").description("Guardrails for vibe coding").version(VERSION);
|
|
4310
4324
|
program.command("init", { isDefault: true }).description("Scan your project and set up enforcement guardrails").option("-y, --yes", "Non-interactive mode (use defaults, high-confidence only)").option("-f, --force", "Re-initialize, replacing existing config").action(async (options) => {
|