@theholocron/cli 5.0.0-alpha.180 → 5.0.0-alpha.182
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.mjs +416 -0
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/plugin/capabilities.d.mts +77 -1
- package/package.json +5 -5
package/dist/cli.mjs
CHANGED
|
@@ -407,6 +407,8 @@ const COMMAND_CONTEXTS = {
|
|
|
407
407
|
"repo setup": "workspace",
|
|
408
408
|
"secrets set": "workspace",
|
|
409
409
|
"secrets sync": "workspace",
|
|
410
|
+
"notifications channel add": "workspace",
|
|
411
|
+
"notifications setup": "workspace",
|
|
410
412
|
deploy: "workspace",
|
|
411
413
|
"deploy cleanup-preview": "workspace",
|
|
412
414
|
"monitoring check": "workspace",
|
|
@@ -2407,6 +2409,7 @@ async function runDeployFromFiles(input) {
|
|
|
2407
2409
|
* auth → `whoami()` (required on every provider)
|
|
2408
2410
|
* errors → `whoami()` (org reachability) or `describe()`
|
|
2409
2411
|
* logs → `whoami()` (dataset reachability) or `describe()`
|
|
2412
|
+
* wiki → `whoami()` (token presence only — not a live check, see holocron#1079)
|
|
2410
2413
|
* others → just "loaded" (no smoke endpoint defined yet)
|
|
2411
2414
|
*
|
|
2412
2415
|
* Output is plain text via a passed-in `print` function so tests can
|
|
@@ -2579,6 +2582,14 @@ async function runSmokeCheck(key, provider, impl) {
|
|
|
2579
2582
|
}
|
|
2580
2583
|
return mk(key, provider, "skip", "loaded (no smoke check defined for this capability)");
|
|
2581
2584
|
}
|
|
2585
|
+
case "wiki": {
|
|
2586
|
+
const wiki = impl;
|
|
2587
|
+
if (wiki.whoami) {
|
|
2588
|
+
const who = await wiki.whoami();
|
|
2589
|
+
return mk(key, provider, who.ok ? "ok" : "fail", who.ok ? "token configured" : "token not configured");
|
|
2590
|
+
}
|
|
2591
|
+
return mk(key, provider, "skip", "loaded (no smoke check defined for this capability)");
|
|
2592
|
+
}
|
|
2582
2593
|
default: return mk(key, provider, "skip", "loaded (no smoke check defined for this capability)");
|
|
2583
2594
|
}
|
|
2584
2595
|
}
|
|
@@ -3198,6 +3209,321 @@ async function runNew(input) {
|
|
|
3198
3209
|
};
|
|
3199
3210
|
}
|
|
3200
3211
|
//#endregion
|
|
3212
|
+
//#region src/commands/notifications-channel-add/notifications-channel-add.ts
|
|
3213
|
+
/**
|
|
3214
|
+
* `holocron notifications channel add` — create one channel (and, with
|
|
3215
|
+
* `--with-webhook`, an inbound webhook whose URL goes straight into a secret).
|
|
3216
|
+
*
|
|
3217
|
+
* Provider-neutral: it drives the `notifications` capability's `ensure*`
|
|
3218
|
+
* methods, so any provider that implements them works. Idempotent — an
|
|
3219
|
+
* existing channel or webhook is left alone, and a webhook URL is never
|
|
3220
|
+
* printed, only stored.
|
|
3221
|
+
*/
|
|
3222
|
+
/**
|
|
3223
|
+
* `HOLOCRON_NOTIFY_WEBHOOK_SYNC` for channel `sync`. Named after the job, not the
|
|
3224
|
+
* provider, so changing providers means re-pointing the same secrets at new
|
|
3225
|
+
* webhook URLs, with no workflow edits.
|
|
3226
|
+
*/
|
|
3227
|
+
function defaultSecretName(channel) {
|
|
3228
|
+
return `HOLOCRON_NOTIFY_WEBHOOK_${channel.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}`;
|
|
3229
|
+
}
|
|
3230
|
+
/** Why a capability is not loaded: the plugin's own load error when there is one (e.g. a missing token). */
|
|
3231
|
+
function whyMissing(loader, key, fallback) {
|
|
3232
|
+
const failure = loader.loadFailures().find((f) => f.key === key);
|
|
3233
|
+
return failure ? `${fallback} (\`${failure.provider}\` failed to load: ${failure.error.message})` : fallback;
|
|
3234
|
+
}
|
|
3235
|
+
/** Pick the provider that can provision; with several, `--provider` decides. */
|
|
3236
|
+
function pickProvisioner(providers, wanted) {
|
|
3237
|
+
const capable = providers.filter((p) => typeof p.ensureChannel === "function" || typeof p.ensureCategory === "function" || typeof p.ensureRole === "function");
|
|
3238
|
+
if (capable.length === 0) throw new Error("no `notifications` provider can provision — add one that implements it (for example discord) to holocron.config.ts");
|
|
3239
|
+
if (wanted) {
|
|
3240
|
+
const match = capable.find((p) => p.providerName === wanted);
|
|
3241
|
+
if (!match) throw new Error(`no provisioning \`notifications\` provider named \`${wanted}\` (have: ${names(capable)})`);
|
|
3242
|
+
return match;
|
|
3243
|
+
}
|
|
3244
|
+
if (capable.length > 1) throw new Error(`several \`notifications\` providers can provision (${names(capable)}) — pass --provider <name>`);
|
|
3245
|
+
return capable[0];
|
|
3246
|
+
}
|
|
3247
|
+
const names = (ps) => ps.map((p) => p.providerName).join(", ");
|
|
3248
|
+
/** Ensure one channel and, if asked, its webhook + secret. Shared with `notifications setup`. */
|
|
3249
|
+
async function provisionChannel(deps, spec) {
|
|
3250
|
+
const { notifications, secrets, scope, dryRun, print } = deps;
|
|
3251
|
+
const webhookCfg = spec.webhook || void 0;
|
|
3252
|
+
const secretName = webhookCfg?.secret ?? defaultSecretName(spec.name);
|
|
3253
|
+
const webhookName = webhookCfg?.name ?? spec.name;
|
|
3254
|
+
const where = spec.category ? ` in ${spec.category}` : "";
|
|
3255
|
+
if (!notifications.ensureChannel) throw new Error(`\`${notifications.providerName}\` cannot create channels`);
|
|
3256
|
+
if (webhookCfg && !notifications.ensureWebhook) throw new Error(`\`${notifications.providerName}\` cannot create webhooks`);
|
|
3257
|
+
if (webhookCfg && !dryRun && !secrets) throw new Error("storing a webhook URL needs a `secrets` provider — add one to holocron.config.ts");
|
|
3258
|
+
if (dryRun) {
|
|
3259
|
+
print(` … would ensure channel ${spec.name}${where}`);
|
|
3260
|
+
if (webhookCfg) print(` … would ensure webhook ${webhookName} and store its URL in ${secretName}`);
|
|
3261
|
+
return {
|
|
3262
|
+
name: spec.name,
|
|
3263
|
+
...spec.category ? { category: spec.category } : {},
|
|
3264
|
+
created: false
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
const channel = await notifications.ensureChannel({
|
|
3268
|
+
name: spec.name,
|
|
3269
|
+
...spec.category ? { category: spec.category } : {},
|
|
3270
|
+
...spec.topic ? { topic: spec.topic } : {}
|
|
3271
|
+
});
|
|
3272
|
+
print(` ${channel.created ? "✓ created" : "· exists "} channel ${spec.name}${where}`);
|
|
3273
|
+
const outcome = {
|
|
3274
|
+
name: spec.name,
|
|
3275
|
+
...spec.category ? { category: spec.category } : {},
|
|
3276
|
+
created: channel.created
|
|
3277
|
+
};
|
|
3278
|
+
if (!webhookCfg) return outcome;
|
|
3279
|
+
const hook = await notifications.ensureWebhook({
|
|
3280
|
+
channel: spec.name,
|
|
3281
|
+
name: webhookName,
|
|
3282
|
+
...spec.category ? { category: spec.category } : {}
|
|
3283
|
+
});
|
|
3284
|
+
if (hook.url) {
|
|
3285
|
+
try {
|
|
3286
|
+
await secrets.setSecret(scope, secretName, hook.url);
|
|
3287
|
+
} catch (err) {
|
|
3288
|
+
throw new Error(`webhook ${webhookName} was ${hook.created ? "created" : "found"} but storing its URL in ${secretName} failed (${errorMessage(err)}) — delete the webhook in the provider and re-run to get a fresh URL`, { cause: err });
|
|
3289
|
+
}
|
|
3290
|
+
print(` ✓ ${hook.created ? "created" : "found "} webhook ${webhookName}; URL stored in ${secretName}`);
|
|
3291
|
+
outcome.webhook = {
|
|
3292
|
+
name: webhookName,
|
|
3293
|
+
created: hook.created,
|
|
3294
|
+
secret: secretName,
|
|
3295
|
+
stored: true
|
|
3296
|
+
};
|
|
3297
|
+
} else {
|
|
3298
|
+
const note = `webhook ${webhookName} already exists and its URL cannot be read back — delete it in the provider and re-run, or set ${secretName} yourself`;
|
|
3299
|
+
print(` ! ${note}`);
|
|
3300
|
+
outcome.webhook = {
|
|
3301
|
+
name: webhookName,
|
|
3302
|
+
created: hook.created,
|
|
3303
|
+
secret: secretName,
|
|
3304
|
+
stored: false,
|
|
3305
|
+
note
|
|
3306
|
+
};
|
|
3307
|
+
}
|
|
3308
|
+
return outcome;
|
|
3309
|
+
}
|
|
3310
|
+
async function runNotificationsChannelAdd(input) {
|
|
3311
|
+
const print = input.print ?? ((line) => console.log(line));
|
|
3312
|
+
const logger = input.logger ?? getLogger();
|
|
3313
|
+
/* v8 ignore next -- the real loader is only built outside tests, which always inject one */
|
|
3314
|
+
const loader = input.loader ?? new PluginLoader(input.loaded.resolved, input.context);
|
|
3315
|
+
await loader.load();
|
|
3316
|
+
assertPluginsResolvable(loader, "notifications channel add");
|
|
3317
|
+
const dryRun = input.context.dryRun ?? false;
|
|
3318
|
+
print(`Holocron notifications channel add — ${input.name}${dryRun ? " (dry-run)" : ""}`);
|
|
3319
|
+
try {
|
|
3320
|
+
const notifications = pickProvisioner(loader.has("notifications") ? loader.get("notifications") : [], input.provider);
|
|
3321
|
+
let secrets;
|
|
3322
|
+
if (input.withWebhook && !dryRun) {
|
|
3323
|
+
if (!loader.has("secrets")) throw new Error(whyMissing(loader, "secrets", "`--with-webhook` needs a `secrets` capability — add a `secrets` provider to holocron.config.ts"));
|
|
3324
|
+
secrets = loader.get("secrets");
|
|
3325
|
+
}
|
|
3326
|
+
const org = input.context.org;
|
|
3327
|
+
const scope = input.scope ?? (org ? {
|
|
3328
|
+
kind: "organization",
|
|
3329
|
+
name: org
|
|
3330
|
+
} : void 0);
|
|
3331
|
+
if (input.withWebhook && !scope && !dryRun) throw new Error("no org to store the webhook secret under — pass --scope or set `org` in holocron.config.ts");
|
|
3332
|
+
const channel = await provisionChannel({
|
|
3333
|
+
notifications,
|
|
3334
|
+
...secrets ? { secrets } : {},
|
|
3335
|
+
scope: scope ?? { kind: "repo" },
|
|
3336
|
+
dryRun,
|
|
3337
|
+
print
|
|
3338
|
+
}, {
|
|
3339
|
+
name: input.name,
|
|
3340
|
+
...input.category ? { category: input.category } : {},
|
|
3341
|
+
...input.topic ? { topic: input.topic } : {},
|
|
3342
|
+
...input.withWebhook ? { webhook: {
|
|
3343
|
+
...input.webhookName ? { name: input.webhookName } : {},
|
|
3344
|
+
...input.secret ? { secret: input.secret } : {}
|
|
3345
|
+
} } : {}
|
|
3346
|
+
});
|
|
3347
|
+
logger.info({
|
|
3348
|
+
channel: input.name,
|
|
3349
|
+
created: channel.created,
|
|
3350
|
+
provider: notifications.providerName,
|
|
3351
|
+
status: "ok"
|
|
3352
|
+
}, `notifications channel add: ${input.name}`);
|
|
3353
|
+
if (channel.webhook && !channel.webhook.stored && !dryRun) {
|
|
3354
|
+
const message = channel.webhook.note;
|
|
3355
|
+
print(` ✗ ${message}`);
|
|
3356
|
+
return {
|
|
3357
|
+
status: "fail",
|
|
3358
|
+
channel,
|
|
3359
|
+
message
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
return {
|
|
3363
|
+
status: dryRun ? "dry-run" : "ok",
|
|
3364
|
+
channel
|
|
3365
|
+
};
|
|
3366
|
+
} catch (err) {
|
|
3367
|
+
const message = errorMessage(err);
|
|
3368
|
+
print(` ✗ ${message}`);
|
|
3369
|
+
logger.warn({
|
|
3370
|
+
channel: input.name,
|
|
3371
|
+
reason: message
|
|
3372
|
+
}, `notifications channel add: ${input.name}`);
|
|
3373
|
+
return {
|
|
3374
|
+
status: "fail",
|
|
3375
|
+
message
|
|
3376
|
+
};
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
//#endregion
|
|
3380
|
+
//#region src/commands/notifications-setup/notifications-setup.ts
|
|
3381
|
+
/**
|
|
3382
|
+
* `holocron notifications setup` — apply the server layout a `notifications`
|
|
3383
|
+
* provider declares in `holocron.config.ts` (categories, channels, roles, and
|
|
3384
|
+
* a webhook secret per channel that asks for one).
|
|
3385
|
+
*
|
|
3386
|
+
* Idempotent: every step looks things up by name and creates only what is
|
|
3387
|
+
* missing, so it is safe to re-run and `--dry-run` previews it. A failing step
|
|
3388
|
+
* is reported and the rest still run.
|
|
3389
|
+
*
|
|
3390
|
+
* Run it from ONE repo per server — a layout is org-level, and putting it in a
|
|
3391
|
+
* shared preset would apply it from every repo.
|
|
3392
|
+
*/
|
|
3393
|
+
async function runNotificationsSetup(input) {
|
|
3394
|
+
const print = input.print ?? ((line) => console.log(line));
|
|
3395
|
+
const logger = input.logger ?? getLogger();
|
|
3396
|
+
/* v8 ignore next -- the real loader is only built outside tests, which always inject one */
|
|
3397
|
+
const loader = input.loader ?? new PluginLoader(input.loaded.resolved, input.context);
|
|
3398
|
+
await loader.load();
|
|
3399
|
+
assertPluginsResolvable(loader, "notifications setup");
|
|
3400
|
+
const dryRun = input.context.dryRun ?? false;
|
|
3401
|
+
const report = {
|
|
3402
|
+
status: "ok",
|
|
3403
|
+
categories: [],
|
|
3404
|
+
channels: [],
|
|
3405
|
+
roles: [],
|
|
3406
|
+
failures: []
|
|
3407
|
+
};
|
|
3408
|
+
const refuse = (message) => {
|
|
3409
|
+
print(` ✗ ${message}`);
|
|
3410
|
+
logger.warn({ reason: message }, "notifications setup");
|
|
3411
|
+
return {
|
|
3412
|
+
...report,
|
|
3413
|
+
status: "fail",
|
|
3414
|
+
message
|
|
3415
|
+
};
|
|
3416
|
+
};
|
|
3417
|
+
let notifications;
|
|
3418
|
+
try {
|
|
3419
|
+
notifications = pickProvisioner(loader.has("notifications") ? loader.get("notifications") : [], input.provider);
|
|
3420
|
+
} catch (err) {
|
|
3421
|
+
return refuse(errorMessage(err));
|
|
3422
|
+
}
|
|
3423
|
+
const layout = notifications.layout;
|
|
3424
|
+
if (!layout) return refuse(`the \`${notifications.providerName}\` provider declares no layout — add \`layout\` to its options in holocron.config.ts`);
|
|
3425
|
+
const wantsWebhook = (layout.categories ?? []).some((c) => (c.channels ?? []).some((ch) => ch.webhook));
|
|
3426
|
+
let secrets;
|
|
3427
|
+
if (wantsWebhook && !dryRun) {
|
|
3428
|
+
if (!loader.has("secrets")) return refuse(whyMissing(loader, "secrets", "the layout asks for webhooks, which need a `secrets` capability — add a `secrets` provider"));
|
|
3429
|
+
secrets = loader.get("secrets");
|
|
3430
|
+
}
|
|
3431
|
+
const org = input.context.org;
|
|
3432
|
+
const scope = input.scope ?? (org ? {
|
|
3433
|
+
kind: "organization",
|
|
3434
|
+
name: org
|
|
3435
|
+
} : void 0);
|
|
3436
|
+
if (wantsWebhook && !scope && !dryRun) return refuse("no org to store webhook secrets under — pass --scope or set `org` in holocron.config.ts");
|
|
3437
|
+
const taken = /* @__PURE__ */ new Map();
|
|
3438
|
+
for (const category of layout.categories ?? []) for (const channel of category.channels ?? []) {
|
|
3439
|
+
if (!channel.webhook) continue;
|
|
3440
|
+
const secret = channel.webhook !== true && channel.webhook.secret || defaultSecretName(channel.name);
|
|
3441
|
+
const other = taken.get(secret);
|
|
3442
|
+
if (other) return refuse(`channels \`${other}\` and \`${channel.name}\` would both store their webhook in ${secret} — give one a distinct \`webhook.secret\``);
|
|
3443
|
+
taken.set(secret, channel.name);
|
|
3444
|
+
}
|
|
3445
|
+
print(`Holocron notifications setup — ${notifications.providerName}${dryRun ? " (dry-run)" : ""}`);
|
|
3446
|
+
const attempt = async (step, fn) => {
|
|
3447
|
+
try {
|
|
3448
|
+
await fn();
|
|
3449
|
+
} catch (err) {
|
|
3450
|
+
const message = errorMessage(err);
|
|
3451
|
+
print(` ✗ ${step}: ${message}`);
|
|
3452
|
+
report.failures.push({
|
|
3453
|
+
step,
|
|
3454
|
+
message
|
|
3455
|
+
});
|
|
3456
|
+
}
|
|
3457
|
+
};
|
|
3458
|
+
for (const category of layout.categories ?? []) {
|
|
3459
|
+
print(`\n${category.name}`);
|
|
3460
|
+
report.categories.push(category.name);
|
|
3461
|
+
await attempt(`category ${category.name}`, async () => {
|
|
3462
|
+
if (!notifications.ensureCategory) return;
|
|
3463
|
+
if (dryRun) {
|
|
3464
|
+
print(` … would ensure category ${category.name}`);
|
|
3465
|
+
return;
|
|
3466
|
+
}
|
|
3467
|
+
const made = await notifications.ensureCategory(category.name);
|
|
3468
|
+
print(` ${made.created ? "✓ created" : "· exists "} category ${category.name}`);
|
|
3469
|
+
});
|
|
3470
|
+
for (const channel of category.channels ?? []) await attempt(`channel ${channel.name}`, async () => {
|
|
3471
|
+
const outcome = await provisionChannel({
|
|
3472
|
+
notifications,
|
|
3473
|
+
...secrets ? { secrets } : {},
|
|
3474
|
+
scope: scope ?? { kind: "repo" },
|
|
3475
|
+
dryRun,
|
|
3476
|
+
print
|
|
3477
|
+
}, {
|
|
3478
|
+
name: channel.name,
|
|
3479
|
+
category: category.name,
|
|
3480
|
+
...channel.topic ? { topic: channel.topic } : {},
|
|
3481
|
+
...channel.webhook ? { webhook: channel.webhook === true ? {} : channel.webhook } : {}
|
|
3482
|
+
});
|
|
3483
|
+
report.channels.push(outcome);
|
|
3484
|
+
if (outcome.webhook && !outcome.webhook.stored) report.failures.push({
|
|
3485
|
+
step: `webhook ${outcome.webhook.name}`,
|
|
3486
|
+
message: outcome.webhook.note
|
|
3487
|
+
});
|
|
3488
|
+
});
|
|
3489
|
+
}
|
|
3490
|
+
if ((layout.roles ?? []).length > 0) print("\nRoles");
|
|
3491
|
+
for (const role of layout.roles ?? []) await attempt(`role ${role.name}`, async () => {
|
|
3492
|
+
if (!notifications.ensureRole) throw new Error(`\`${notifications.providerName}\` cannot manage roles`);
|
|
3493
|
+
if (dryRun) {
|
|
3494
|
+
print(` … would ensure role ${role.name}`);
|
|
3495
|
+
report.roles.push({
|
|
3496
|
+
name: role.name,
|
|
3497
|
+
created: false
|
|
3498
|
+
});
|
|
3499
|
+
return;
|
|
3500
|
+
}
|
|
3501
|
+
const made = await notifications.ensureRole({
|
|
3502
|
+
name: role.name,
|
|
3503
|
+
...role.mentionable !== void 0 ? { mentionable: role.mentionable } : {}
|
|
3504
|
+
});
|
|
3505
|
+
const outcome = {
|
|
3506
|
+
name: role.name,
|
|
3507
|
+
id: made.id,
|
|
3508
|
+
created: made.created
|
|
3509
|
+
};
|
|
3510
|
+
report.roles.push(outcome);
|
|
3511
|
+
print(` ${made.created ? "✓ created" : "· exists "} role ${role.name} (id ${made.id})`);
|
|
3512
|
+
});
|
|
3513
|
+
if (report.failures.length > 0) {
|
|
3514
|
+
report.status = "fail";
|
|
3515
|
+
print(`\n${report.failures.length} step(s) need attention`);
|
|
3516
|
+
} else if (dryRun) report.status = "dry-run";
|
|
3517
|
+
logger.info({
|
|
3518
|
+
provider: notifications.providerName,
|
|
3519
|
+
channels: report.channels.length,
|
|
3520
|
+
roles: report.roles.length,
|
|
3521
|
+
failures: report.failures.length,
|
|
3522
|
+
status: report.status
|
|
3523
|
+
}, "notifications setup");
|
|
3524
|
+
return report;
|
|
3525
|
+
}
|
|
3526
|
+
//#endregion
|
|
3201
3527
|
//#region src/commands/npm-bump-versions/npm-bump-versions.ts
|
|
3202
3528
|
async function runNpmBumpVersions(input) {
|
|
3203
3529
|
const print = input.print ?? ((line) => console.log(line));
|
|
@@ -9324,6 +9650,22 @@ const COMMAND_REGISTRY = [
|
|
|
9324
9650
|
}],
|
|
9325
9651
|
supportsDryRun: true
|
|
9326
9652
|
},
|
|
9653
|
+
{
|
|
9654
|
+
name: "notifications channel add",
|
|
9655
|
+
description: "Create a notification channel, optionally with a webhook whose URL is stored as a secret",
|
|
9656
|
+
positionals: [{
|
|
9657
|
+
key: "name",
|
|
9658
|
+
message: "Channel name:",
|
|
9659
|
+
type: "input"
|
|
9660
|
+
}],
|
|
9661
|
+
supportsDryRun: true
|
|
9662
|
+
},
|
|
9663
|
+
{
|
|
9664
|
+
name: "notifications setup",
|
|
9665
|
+
description: "Apply the server layout a notifications provider declares in holocron.config.ts",
|
|
9666
|
+
positionals: [],
|
|
9667
|
+
supportsDryRun: true
|
|
9668
|
+
},
|
|
9327
9669
|
{
|
|
9328
9670
|
name: "deploy",
|
|
9329
9671
|
description: "Trigger a deployment via the configured `deployment` capability",
|
|
@@ -10804,6 +11146,80 @@ try {
|
|
|
10804
11146
|
})).summary.fail > 0) process.exitCode = 1;
|
|
10805
11147
|
}).command("$0", false, () => {}, async (argv) => {
|
|
10806
11148
|
await launchGroupMenu("secrets", argv);
|
|
11149
|
+
}), () => {}).command("notifications", "Provision notification channels, webhooks and roles", (g) => g.command("channel", "Manage notification channels", (c) => c.command("add [name]", "Create a channel, optionally with a webhook whose URL is stored as a secret", (y) => y.positional("name", {
|
|
11150
|
+
type: "string",
|
|
11151
|
+
describe: "Channel name (e.g. alerts)"
|
|
11152
|
+
}).option("category", {
|
|
11153
|
+
type: "string",
|
|
11154
|
+
describe: "Put the channel under this category (created if missing)"
|
|
11155
|
+
}).option("topic", {
|
|
11156
|
+
type: "string",
|
|
11157
|
+
describe: "Channel topic"
|
|
11158
|
+
}).option("with-webhook", {
|
|
11159
|
+
type: "boolean",
|
|
11160
|
+
default: false,
|
|
11161
|
+
describe: "Also create a webhook and store its URL as a secret (the URL is never printed)"
|
|
11162
|
+
}).option("webhook-name", {
|
|
11163
|
+
type: "string",
|
|
11164
|
+
describe: "Webhook display name (default: the channel name)"
|
|
11165
|
+
}).option("secret", {
|
|
11166
|
+
type: "string",
|
|
11167
|
+
describe: "Secret to store the URL in (default: HOLOCRON_NOTIFY_WEBHOOK_<CHANNEL>)"
|
|
11168
|
+
}).option("scope", {
|
|
11169
|
+
type: "string",
|
|
11170
|
+
describe: "Secret scope: \"repo\", \"env=<name>\" or \"org=<name>\" (default: the org)"
|
|
11171
|
+
}).option("provider", {
|
|
11172
|
+
type: "string",
|
|
11173
|
+
describe: "Which notifications provider, when several can provision"
|
|
11174
|
+
}), async (argv) => {
|
|
11175
|
+
const tokens = tokenContext(argv.token);
|
|
11176
|
+
if (!tokens) return;
|
|
11177
|
+
const [name] = await promptForPositionals(getEntry("notifications channel add"), argv);
|
|
11178
|
+
const loaded = await loadConfig(argv.cwd);
|
|
11179
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
11180
|
+
if ((await runNotificationsChannelAdd({
|
|
11181
|
+
loaded,
|
|
11182
|
+
context: {
|
|
11183
|
+
repoRoot: argv.cwd,
|
|
11184
|
+
dryRun: argv.dryRun,
|
|
11185
|
+
...tokens,
|
|
11186
|
+
org: resolveOrg(argv, loaded.resolved)
|
|
11187
|
+
},
|
|
11188
|
+
name,
|
|
11189
|
+
...argv.category ? { category: argv.category } : {},
|
|
11190
|
+
...argv.topic ? { topic: argv.topic } : {},
|
|
11191
|
+
...argv.withWebhook ? { withWebhook: true } : {},
|
|
11192
|
+
...argv.webhookName ? { webhookName: argv.webhookName } : {},
|
|
11193
|
+
...argv.secret ? { secret: argv.secret } : {},
|
|
11194
|
+
...argv.scope ? { scope: parseScope(argv.scope) } : {},
|
|
11195
|
+
...argv.provider ? { provider: argv.provider } : {}
|
|
11196
|
+
})).status === "fail") process.exitCode = 1;
|
|
11197
|
+
}).command("$0", false, () => {}, async (argv) => {
|
|
11198
|
+
await launchGroupMenu("notifications channel", argv);
|
|
11199
|
+
}), () => {}).command("setup", "Apply the server layout a notifications provider declares in holocron.config.ts", (y) => y.option("scope", {
|
|
11200
|
+
type: "string",
|
|
11201
|
+
describe: "Webhook secret scope: \"repo\", \"env=<name>\" or \"org=<name>\" (default: the org)"
|
|
11202
|
+
}).option("provider", {
|
|
11203
|
+
type: "string",
|
|
11204
|
+
describe: "Which notifications provider, when several can provision"
|
|
11205
|
+
}), async (argv) => {
|
|
11206
|
+
const tokens = tokenContext(argv.token);
|
|
11207
|
+
if (!tokens) return;
|
|
11208
|
+
const loaded = await loadConfig(argv.cwd);
|
|
11209
|
+
applyResolvedConfig(argv, loaded.resolved);
|
|
11210
|
+
if ((await runNotificationsSetup({
|
|
11211
|
+
loaded,
|
|
11212
|
+
context: {
|
|
11213
|
+
repoRoot: argv.cwd,
|
|
11214
|
+
dryRun: argv.dryRun,
|
|
11215
|
+
...tokens,
|
|
11216
|
+
org: resolveOrg(argv, loaded.resolved)
|
|
11217
|
+
},
|
|
11218
|
+
...argv.scope ? { scope: parseScope(argv.scope) } : {},
|
|
11219
|
+
...argv.provider ? { provider: argv.provider } : {}
|
|
11220
|
+
})).status === "fail") process.exitCode = 1;
|
|
11221
|
+
}).command("$0", false, () => {}, async (argv) => {
|
|
11222
|
+
await launchGroupMenu("notifications", argv);
|
|
10807
11223
|
}), () => {}).command("sync", "Sync generated files to other repos", (g) => g.command("github", "Sync workflow templates and composite actions to theholocron/.github", (y) => y.option("repo", {
|
|
10808
11224
|
type: "string",
|
|
10809
11225
|
default: "theholocron/.github",
|