Skip to content

Commit 6ea8aa1

Browse files
author
settings-as-code-release
committed
docs(library): examples and smoke consumers use the Result API
Every fallible call now returns a neverthrow Result or ResultAsync with a typed Problem: the library page examples and the package-smoke consumer snippets read .isOk()/.isErr()/.value/.match and render problems with describeProblem; readSettingsFile takes the file role and the check example passes a SectionSelection.
1 parent 72fb0ea commit 6ea8aa1

2 files changed

Lines changed: 31 additions & 28 deletions

File tree

‎.github/scripts/package-smoke.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const PACKAGE = "@vivswan/github-settings-as-code";
2525
const NODE_CONSUMER = `import { SECTION_KEYS, validateSettings } from "${PACKAGE}";
2626
import schema from "${PACKAGE}/settings.schema.json" with { type: "json" };
2727
const result = validateSettings({ labels: [] });
28-
if (!result.ok) throw new Error("validateSettings rejected an empty labels list: " + result.error);
28+
if (result.isErr()) throw new Error("validateSettings rejected an empty labels list: " + result.error.code);
2929
if (!SECTION_KEYS.includes("labels")) throw new Error("SECTION_KEYS lacks labels");
3030
if (typeof schema.$schema !== "string") throw new Error("the schema subpath did not resolve to the JSON Schema");
3131
console.log("imported " + SECTION_KEYS.length + " section keys and the schema");
@@ -35,7 +35,7 @@ console.log("imported " + SECTION_KEYS.length + " section keys and the schema");
3535
const TS_CONSUMER = `import { SECTION_KEYS, type SectionKey, validateSettings } from "${PACKAGE}";
3636
const first: SectionKey | undefined = SECTION_KEYS[0];
3737
const result = validateSettings({ labels: [] });
38-
export const ok: boolean = result.ok && first === "repository";
38+
export const ok: boolean = result.isOk() && first === "repository";
3939
`;
4040

4141
/** Run a command to completion in `cwd`, streaming its output; a non-zero exit throws. */

‎docs/reference/library.md‎

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,21 @@ The package exports three paths: the entry (`.`), the committed settings.yml JSO
2121

2222
## The API by group
2323

24-
Every name below is exported from the entry, [src/index.ts](https://github.com/Vivswan/github-settings-as-code/blob/main/src/index.ts). Each group has one short example; the types beside each name are in the bundled declarations. The examples continue from one another: `result` is the Validate group's validated document, `client` the Client group's `GithubApi`, and `config` in the Io example a `SingleConfig` (the action's parsed inputs).
24+
Every name below is exported from the entry, [src/index.ts](https://github.com/Vivswan/github-settings-as-code/blob/main/src/index.ts). Each group has one short example; the types beside each name are in the bundled declarations. Every fallible call returns a [neverthrow](https://github.com/supermacro/neverthrow) `Result` (or `ResultAsync`) whose error is a typed `Problem`; `describeProblem` renders one as the message the action would print. The examples continue from one another: `settings` is the Validate group's validated document, `client` the Client group's `GithubApi`, and `config` in the Io example a `SingleConfig` (the action's parsed inputs).
2525

2626
### Validate and merge
2727

28-
`validateSettings` validates a parsed document; `mergeLayers`, `foldLayers`, and `readLayerFiles` fold layers as `mode: merge` does; `renderMergedYaml` prints the result the way the merged-file output is written; `readSettingsFile` and `parseSettingsDoc` read YAML.
28+
`validateSettings` validates a parsed document; `mergeLayers`, `foldLayers`, and `readLayerFiles` fold layers as `mode: merge` does; `renderMergedYaml` prints the result the way the merged-file output is written; `readSettingsFile` (given the file's role) and `parseSettingsDoc` read YAML.
2929

3030
```ts
31-
import { readSettingsFile, validateSettings } from "@vivswan/github-settings-as-code";
31+
import { describeProblem, readSettingsFile, validateSettings } from "@vivswan/github-settings-as-code";
3232

33-
const read = readSettingsFile(".github/settings.yml");
34-
if ("error" in read) throw new Error(read.error);
35-
const result = validateSettings(read.doc, { source: ".github/settings.yml" });
36-
if (!result.ok) throw new Error(result.error);
37-
console.log(result.warnings);
33+
const validated = readSettingsFile(".github/settings.yml", "settings-file").andThen((doc) =>
34+
validateSettings(doc, { source: ".github/settings.yml" }),
35+
);
36+
if (validated.isErr()) throw new Error(describeProblem(validated.error));
37+
const { settings, warnings } = validated.value;
38+
console.log(warnings);
3839
```
3940

4041
### Schema
@@ -61,33 +62,36 @@ const client = new GithubApi({ token: process.env.GITHUB_TOKEN ?? "" });
6162

6263
### Check and apply
6364

64-
`checkRepository` plans and diffs every active section without writing; `applyRepository` executes the plan. Both take the validated settings, the parsed `RepoRef` from `parseRepoSlug`, and the permission policy, and return the result plus the lines the run printed.
65+
`checkRepository` plans and diffs every active section without writing; `applyRepository` executes the plan. Both take the validated settings, the parsed `RepoRef` from `parseRepoSlug`, the permission policy, and a `SectionSelection` (which sections run and which must fully apply; `SectionSelection.ALL` runs every declared section), and return the result plus the lines the run printed.
6566

6667
```ts
67-
import { checkRepository, parseRepoSlug } from "@vivswan/github-settings-as-code";
68+
import { checkRepository, parseRepoSlug, SectionSelection } from "@vivswan/github-settings-as-code";
6869

6970
const repo = parseRepoSlug("octo-org/api");
70-
if (repo === null) throw new Error("not an owner/name slug");
71+
if (repo.isErr()) throw new Error(describeProblem(repo.error));
7172
const report = await checkRepository(client, {
72-
repo,
73-
settings: result.settings,
73+
repo: repo.value,
74+
settings,
7475
onMissingPermission: "warn",
75-
requiredSections: new Set(),
76-
onlySections: new Set(),
76+
sections: SectionSelection.ALL,
7777
});
7878
console.log(report.result, report.outcomes.map((o) => `${o.key}: ${o.status}`), report.log);
7979
```
8080

8181
### Flows
8282

83-
The run flows the action wraps: `runSingle` (one repository from a local file), `runMulti` (repos-dir, discovery, defaults file), `runMerge` (fold files into one), with `concludeRun` and `failRun` turning a finished run into outputs and an exit code. `SingleConfig`, `MultiConfig`, and `MergeConfig` are the inputs the action parses into.
83+
The run flows the action wraps: `runSingle` (one repository from a local file), `runMulti` (repos-dir, discovery, defaults file), `runMerge` (fold files into one), with `concludeRun`, `concludeMerge`, and `failRun` turning a finished run or its problem into outputs and an exit code. `SingleConfig`, `MultiConfig`, and `MergeConfig` are the inputs the action parses into; `parseConfig` builds one from an input reader and the environment the way the action does.
8484

8585
```ts
86-
import { runMerge, silentIo } from "@vivswan/github-settings-as-code";
86+
import { concludeMerge, failRun, runMerge, silentIo } from "@vivswan/github-settings-as-code";
8787

88+
const io = silentIo();
8889
const exitCode = runMerge(
8990
{ settingsFiles: ["base.yml", "team.yml"], mergedFile: "merged.yml", layering: "merge" },
90-
silentIo(),
91+
io,
92+
).match(
93+
(merged) => concludeMerge(io, merged),
94+
(problem) => failRun(io, problem),
9195
);
9296
```
9397

@@ -99,8 +103,8 @@ const exitCode = runMerge(
99103
import { DEFAULT_DISCOVERY_FILTERS, discoverRepos } from "@vivswan/github-settings-as-code";
100104

101105
const found = await discoverRepos(client, { ...DEFAULT_DISCOVERY_FILTERS, topics: ["managed"] });
102-
if ("error" in found) throw new Error(found.error);
103-
console.log(found.repos.map((r) => r.slug));
106+
if (found.isErr()) throw new Error(describeProblem(found.error));
107+
console.log(found.value.repos.map((r) => r.slug));
104108
```
105109

106110
### Report
@@ -112,7 +116,7 @@ import { encryptReport, parseRecipient } from "@vivswan/github-settings-as-code"
112116

113117
const recipient = process.env.REPORT_PUBLIC_KEY ?? "";
114118
const checked = parseRecipient(recipient);
115-
if (!checked.ok) throw new Error(checked.error);
119+
if (checked.isErr()) throw new Error(describeProblem(checked.error));
116120
const sealed = await encryptReport(recipient, "# report");
117121
```
118122

@@ -135,11 +139,10 @@ console.log(labels.key, Object.keys(labels.endpoints), sectionGrant(labels));
135139
import { collectingIo, concludeRun, failRun, runSingle } from "@vivswan/github-settings-as-code";
136140

137141
const collected = collectingIo();
138-
const run = await runSingle(client, config, collected.io);
139-
const exitCode =
140-
"fatal" in run
141-
? failRun(collected.io, run.fatal)
142-
: concludeRun(collected.io, { kind: "single", mode: config.mode, target: run.target });
142+
const exitCode = await runSingle(client, config, collected.io).match(
143+
(target) => concludeRun(collected.io, { kind: "single", mode: config.mode, target }),
144+
(problem) => failRun(collected.io, problem),
145+
);
143146
console.log(exitCode, collected.outputs.result, collected.lines.map((entry) => entry.line));
144147
```
145148

0 commit comments

Comments
 (0)