@vivswan/github-settings-as-code 2.0.1-main.1072.gfded5f5 → 2.0.1-main.1074.gc449c5c

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.

Potentially problematic release.


This version of @vivswan/github-settings-as-code might be problematic. Click here for more details.

Files changed (2) hide show
  1. package/lib/pkg/cli.js +92 -25
  2. package/package.json +1 -1
package/lib/pkg/cli.js CHANGED
@@ -285,32 +285,98 @@ function inputsForMode(mode) {
285
285
  };
286
286
  return INPUT_NAMES.filter((name) => !hidden.includes(name) && reads(name));
287
287
  }
288
+ /** A mode's subcommand: its flags are the inputs the mode reads. */
289
+ function modeSubcommand(mode) {
290
+ return {
291
+ flags: new Set(inputsForMode(mode)),
292
+ mode
293
+ };
294
+ }
288
295
  /**
289
- * The init subcommand's flags: the snapshot inputs of one repository, with
290
- * settings-file as the destination in place of snapshot-file. Filtered from
291
- * the declarations so the help keeps their order.
296
+ * The init subcommand: the snapshot inputs of one repository, with
297
+ * settings-file as the destination in place of snapshot-file. No mode runs
298
+ * it, so the clauses restricted to modes leave its help.
292
299
  */
293
- const INIT_FLAGS = /* @__PURE__ */ new Set([
294
- "repository",
295
- "settings-file",
296
- "on-missing-permission",
297
- "sections",
298
- "api-version"
299
- ]);
300
- const INIT_INPUTS = INPUT_NAMES.filter((name) => INIT_FLAGS.has(name));
300
+ const INIT_SUBCOMMAND = {
301
+ flags: /* @__PURE__ */ new Set([
302
+ "repository",
303
+ "settings-file",
304
+ "on-missing-permission",
305
+ "sections",
306
+ "api-version"
307
+ ]),
308
+ mode: null
309
+ };
310
+ /** init's flags in declaration order, the order the help keeps. */
311
+ const INIT_INPUTS = INPUT_NAMES.filter((name) => INIT_SUBCOMMAND.flags.has(name));
301
312
  /**
302
313
  * init's one reworded flag: the declaration describes the file apply and check
303
314
  * READ, and init WRITES it; every other init flag keeps its declared text.
304
315
  */
305
316
  const INIT_SETTINGS_FILE_DESCRIPTION = "Where the settings document is written: the file apply and check read. One path; an existing file is kept unless --force is passed.";
317
+ /** `text` as the declaration spells it; a reworded declaration fails here rather than leave the help stale. */
318
+ function declared(input, text) {
319
+ if (!INPUT_DECLS[input].description.includes(text)) throw new Error(`BUG: the ${input} input's description no longer says "${text}"; reword the CLI's clause with it`);
320
+ return text;
321
+ }
322
+ const MULTI_REPO_FLAGS = ["repos", "repos-dir"];
323
+ const CLAUSES = [
324
+ {
325
+ input: "repository",
326
+ text: declared("repository", " Single-repo mode only; cannot be combined with repos or repos-dir."),
327
+ flags: MULTI_REPO_FLAGS
328
+ },
329
+ {
330
+ input: "settings-file",
331
+ text: declared("settings-file", " Single-repo and merge modes only; multi-repo targets read repos-dir files or each repository's own .github/settings.yml, so overriding it alongside repos or repos-dir fails the run."),
332
+ flags: MULTI_REPO_FLAGS
333
+ },
334
+ {
335
+ input: "snapshot-dir",
336
+ text: declared("snapshot-dir", "; defaults-file does not apply"),
337
+ flags: ["defaults-file"]
338
+ },
339
+ {
340
+ input: "sections",
341
+ text: declared("sections", " apply, check, and snapshot only: mode: merge writes every section its layers declare, so the allowlist belongs on the step that runs the merged document and fails the merge when set."),
342
+ modes: [
343
+ "apply",
344
+ "check",
345
+ "snapshot"
346
+ ]
347
+ },
348
+ {
349
+ input: "private-report",
350
+ text: declared("private-report", " Under artifact, those reports are concatenated, age-encrypted to report-public-key, and uploaded as one workflow artifact (settings-as-code-private-report) for readers who hold the key but no GitHub access to the targets; the artifact channel needs the Actions artifact service, so on GitHub Enterprise Server it warns and uploads nothing."),
351
+ flags: ["report-public-key"]
352
+ },
353
+ {
354
+ input: "private-report",
355
+ text: declared("private-report", "issue, issue-on-failure, or artifact."),
356
+ replacement: "issue, or issue-on-failure.",
357
+ flags: ["report-public-key"]
358
+ }
359
+ ];
360
+ function meets(subcommand, clause) {
361
+ const flags = (clause.flags ?? []).every((flag) => subcommand.flags.has(flag));
362
+ const mode = clause.modes === void 0 || subcommand.mode !== null && clause.modes.includes(subcommand.mode);
363
+ return flags && mode;
364
+ }
306
365
  /** The sentence the action's `repository` description spends on a default a terminal never has. */
307
- const ACTIONS_DEFAULT_SENTENCE = "Defaults to the current repository.";
308
- const CLI_REPOSITORY_SENTENCE = "Required unless repos or repos-dir is set (inside GitHub Actions, GITHUB_REPOSITORY supplies it).";
309
- if (!INPUT_DECLS.repository.description.includes(ACTIONS_DEFAULT_SENTENCE)) throw new Error(`BUG: the repository input's description no longer says "${ACTIONS_DEFAULT_SENTENCE}"; reword the CLI's replacement with it`);
310
- /** The flag's help text: the declaration's, reworded where it assumes the Actions runner. */
311
- function inputDescription(name) {
312
- const description = INPUT_DECLS[name].description;
313
- return name === "repository" ? description.replace(ACTIONS_DEFAULT_SENTENCE, CLI_REPOSITORY_SENTENCE) : description;
366
+ const ACTIONS_DEFAULT_SENTENCE = declared("repository", "Defaults to the current repository.");
367
+ /**
368
+ * A flag's help text under `subcommand`: the declaration's, minus the clauses
369
+ * about flags and modes the subcommand lacks, and reworded where it assumes
370
+ * the Actions runner.
371
+ */
372
+ function inputDescription(name, subcommand) {
373
+ let description = INPUT_DECLS[name].description;
374
+ for (const clause of CLAUSES) if (clause.input === name && !meets(subcommand, clause)) description = description.replace(clause.text, clause.replacement ?? "");
375
+ if (name === "repository") {
376
+ const unless = MULTI_REPO_FLAGS.every((flag) => subcommand.flags.has(flag)) ? " unless repos or repos-dir is set" : "";
377
+ description = description.replace(ACTIONS_DEFAULT_SENTENCE, `Required${unless} (inside GitHub Actions, GITHUB_REPOSITORY supplies it).`);
378
+ }
379
+ return description;
314
380
  }
315
381
  /** Whether the declaration is a list; read through InputDecl since only the list members carry the field. */
316
382
  function isList(name) {
@@ -328,16 +394,16 @@ function once(flag) {
328
394
  };
329
395
  }
330
396
  /**
331
- * The commander option for one input: `--<name> <value>`, repeatable when the
332
- * declaration is a list; `description` replaces the declaration's where a
333
- * subcommand reads the input for another purpose.
397
+ * The commander option for one input under `subcommand`: `--<name> <value>`,
398
+ * repeatable when the declaration is a list; `description` replaces the
399
+ * declaration's where the subcommand reads the input for another purpose.
334
400
  */
335
- function inputOption(name, description = inputDescription(name)) {
401
+ function inputOption(name, subcommand, description = inputDescription(name, subcommand)) {
336
402
  const parse = isList(name) ? accumulate : once(name);
337
403
  return new Option(`--${name} <value>`, description).argParser(parse);
338
404
  }
339
405
  /** Commander's attribute for each flag (camelCase of the name), read from commander itself. */
340
- const ATTRIBUTE = Object.fromEntries(INPUT_NAMES.map((name) => [name, inputOption(name).attributeName()]));
406
+ const ATTRIBUTE = Object.fromEntries(INPUT_NAMES.map((name) => [name, new Option(`--${name} <value>`).attributeName()]));
341
407
  /** A flag value as the runner would hand it over: trimmed, as @actions/core trims every input. */
342
408
  function inputValue(value) {
343
409
  return typeof value === "string" ? value.trim() : "";
@@ -534,7 +600,8 @@ function buildProgram(options) {
534
600
  };
535
601
  for (const [name, mode] of Object.entries(MODE_COMMANDS)) {
536
602
  const command = program.command(name).description(DESCRIPTION[name]);
537
- for (const input of inputsForMode(mode)) command.addOption(inputOption(input));
603
+ const subcommand = modeSubcommand(mode);
604
+ for (const input of subcommand.flags) command.addOption(inputOption(input, subcommand));
538
605
  command.action(async function() {
539
606
  const values = this.optsWithGlobals();
540
607
  const { io, flush } = openIo(values);
@@ -544,7 +611,7 @@ function buildProgram(options) {
544
611
  });
545
612
  }
546
613
  const init = program.command("init").description(DESCRIPTION.init);
547
- for (const input of INIT_INPUTS) init.addOption(input === "settings-file" ? inputOption(input, INIT_SETTINGS_FILE_DESCRIPTION) : inputOption(input));
614
+ for (const input of INIT_INPUTS) init.addOption(input === "settings-file" ? inputOption(input, INIT_SUBCOMMAND, INIT_SETTINGS_FILE_DESCRIPTION) : inputOption(input, INIT_SUBCOMMAND));
548
615
  init.option("--force", "Replace the settings file when it already exists; without it, init refuses").action(async function() {
549
616
  const values = this.optsWithGlobals();
550
617
  const { io } = openIo(values);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vivswan/github-settings-as-code",
3
- "version": "2.0.1-main.1072.gfded5f5",
3
+ "version": "2.0.1-main.1074.gc449c5c",
4
4
  "description": "GitHub Action applying declarative repository settings: rulesets, labels, branch protection, and more.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "type": "module",