@theholocron/holocron-plugin-fern 5.0.0-alpha.43 → 5.0.0-alpha.430

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 CHANGED
@@ -1,3 +1,5 @@
1
+ <!-- alex disable host-hostess -->
2
+
1
3
  <!-- editorconfig-checker-disable-file -->
2
4
 
3
5
  # `@theholocron/holocron-plugin-fern`
@@ -83,7 +85,7 @@ running `holocron setup`.
83
85
 
84
86
  ## CI secrets
85
87
 
86
- Add `HOLOCRON_FERN_TOKEN` to your repo's GitHub Secrets. The `wiki.yml` thin caller
88
+ Add `HOLOCRON_FERN_TOKEN` to your repo's GitHub Secrets. The `publish-wiki` thin caller
87
89
  passes it to the Fern CLI during `fern generate --docs`.
88
90
 
89
91
  | Secret | Purpose |
package/dist/index.d.mts CHANGED
@@ -1,10 +1,15 @@
1
- import { AuthError, DnsRecordRequest, ResolveTokenInput, Wiki, WikiProvisionOpts, WikiProxyConfig } from "@theholocron/cli";
2
- //#region src/capabilities/wiki.d.ts
3
- declare const FERN_VERSION = "5.114.1";
4
- interface FernWikiOptions {
1
+ import { AuthError, DnsRecordRequest, PluginToken, ResolveTokenInput, Wiki, WikiProvisionOpts, WikiProxyConfig } from "@theholocron/cli";
2
+ /** Resolves the Fern token from `--token`, `HOLOCRON_FERN_TOKEN`, `FERN_TOKEN` or the keyring entry for `fern`, and throws `AuthError` when none is set. */
3
+ export declare const resolveToken: import("@theholocron/cli").TokenResolver;
4
+ /** Every token this plugin takes, for `holocron doctor secrets --verify`, with the layers that hold it. */
5
+ export declare const tokens: PluginToken[];
6
+ /** The Fern CLI version written into `fern/fern.config.json`. */
7
+ export declare const FERN_VERSION = "5.114.1";
8
+ /** Options for the Fern `wiki` capability. */
9
+ interface FernWikiOptions extends ResolveTokenInput {
5
10
  /** Absolute path to the working repo root. Injected by the loader from RuntimeContext. */
6
11
  repoRoot?: string;
7
- /** Active org name (e.g. "theholocron"). Injected by the loader from RuntimeContext. */
12
+ /** Active org name (e.g. "acme"). Injected by the loader from RuntimeContext. */
8
13
  org?: string;
9
14
  /**
10
15
  * "owner/name" repo coordinate. Injected by the loader from RuntimeContext.
@@ -12,15 +17,15 @@ interface FernWikiOptions {
12
17
  */
13
18
  repo?: string;
14
19
  /**
15
- * Base domain for the Fern docs site (e.g. "wiki.theholocron.dev").
20
+ * Base domain for the Fern docs site (e.g. "wiki.example.dev").
16
21
  *
17
22
  * When provided without a path, the repo name is appended automatically:
18
- * domain "wiki.theholocron.dev" + repo "owner/holocron"
19
- * → custom-domain: wiki.theholocron.dev/holocron
23
+ * domain "wiki.example.dev" + repo "owner/holocron"
24
+ * → custom-domain: wiki.example.dev/holocron
20
25
  * → multi-source: true
21
26
  *
22
27
  * You can also supply the full path explicitly:
23
- * domain "wiki.theholocron.dev/holocron"
28
+ * domain "wiki.example.dev/holocron"
24
29
  *
25
30
  * Note: password protection is configured in the Fern Dashboard
26
31
  * (https://dashboard.buildwithfern.com) — it is not configurable via docs.yml.
@@ -30,7 +35,7 @@ interface FernWikiOptions {
30
35
  * Fern workspace org slug. Defaults to `org` (the GitHub org name).
31
36
  * Set this explicitly when the Fern workspace name differs from the
32
37
  * GitHub org — e.g. the workspace was registered as "holocron" while
33
- * the GitHub org is "theholocron".
38
+ * the GitHub org is "acme".
34
39
  */
35
40
  fernOrg?: string;
36
41
  /**
@@ -45,27 +50,54 @@ interface FernWikiOptions {
45
50
  */
46
51
  icon?: string;
47
52
  }
48
- declare class FernWiki implements Wiki {
53
+ /** `wiki` capability for Fern: writes the Fern config and docs scaffold for a repo. */
54
+ export declare class FernWiki implements Wiki {
49
55
  private readonly opts;
56
+ /** Capability key; always `"wiki"`. */
50
57
  readonly key: "wiki";
58
+ /** Provider name; always `"fern"`. */
51
59
  readonly providerName = "fern";
52
60
  constructor(opts: FernWikiOptions);
53
61
  provision(callOpts?: WikiProvisionOpts): Promise<string>;
54
62
  proxyConfig(): WikiProxyConfig | null;
63
+ /**
64
+ * Confirms `HOLOCRON_FERN_TOKEN`/`FERN_TOKEN` resolves, or a Fern token
65
+ * is stored in the keyring — not a live check. Fern exposes no
66
+ * lightweight endpoint to verify a token actually works (the only
67
+ * token-consuming operation is a full `fern generate`, see
68
+ * holocron#1079); this only confirms one is configured, the same way
69
+ * CI's `fern generate --docs` step would find it.
70
+ *
71
+ * Deliberately ignores `cliToken` (`--token`): CI's step only ever sees
72
+ * the env var from a GitHub Actions secret, never a CLI flag — and a
73
+ * bare `--token <value>` meant for some OTHER provider is forwarded to
74
+ * every loaded plugin as a fallback (see AGENTS.md), so honoring it
75
+ * here would report "configured" even when no Fern-specific credential
76
+ * exists anywhere CI would actually look.
77
+ *
78
+ * Only `AuthError` (the specific "no token found" signal) is treated as
79
+ * `ok: false` — any other error (e.g. a real keyring failure) is a
80
+ * genuine problem and must surface as a failure, not get misreported as
81
+ * a missing token.
82
+ */
83
+ whoami(): Promise<{
84
+ ok: boolean;
85
+ }>;
55
86
  dnsRecord(): DnsRecordRequest | null;
56
87
  }
57
- //#endregion
58
- //#region src/auth.d.ts
59
- declare const resolveToken: (input?: ResolveTokenInput) => string;
60
- //#endregion
61
- //#region src/index.d.ts
62
- type FernPluginOptions = FernWikiOptions;
63
- declare function wiki(opts: FernPluginOptions): Wiki;
64
- declare function createPlugin(options?: FernPluginOptions): {
88
+ /** Options for the Fern plugin; the same as the `wiki` capability's. */
89
+ export type FernPluginOptions = FernWikiOptions;
90
+ /** Builds the `wiki` capability from the plugin options. */
91
+ export declare function wiki(opts: FernPluginOptions): Wiki;
92
+ /** Creates the Fern plugin: its `connection` and `token` probes and the `wiki` capability. */
93
+ export declare function createPlugin(options?: FernPluginOptions): {
65
94
  name: string;
95
+ probes: {
96
+ connection: import("@theholocron/cli").Probe;
97
+ token: import("@theholocron/cli").Probe;
98
+ };
66
99
  capabilities: {
67
100
  wiki: () => Wiki;
68
101
  };
69
102
  };
70
- //#endregion
71
- export { AuthError, FERN_VERSION, FernPluginOptions, FernWiki, type FernWikiOptions, type ResolveTokenInput, createPlugin, resolveToken, wiki };
103
+ export { AuthError, type FernWikiOptions, type ResolveTokenInput };
package/dist/index.mjs CHANGED
@@ -1,11 +1,37 @@
1
+ import { AuthError, connectionProbe, createResolveToken, tokenExpectations } from "@theholocron/cli";
1
2
  import { mkdir, readFile, writeFile } from "node:fs/promises";
2
3
  import { join } from "node:path";
3
- import { AuthError, createResolveToken } from "@theholocron/cli";
4
- //#region src/capabilities/wiki.ts
4
+ import { stringifyJson } from "@theholocron/json-utils";
5
+ // src/probes/token/expectations.ts
6
+ /**
7
+ * Where the Fern token is held: CI only (the `publish-wiki` workflow), so `ci` is the one layer declared. Fern has no
8
+ * endpoint that reports a token's permissions, so none are declared and the probe says so instead of guessing. This is
9
+ * the plugin's `tokens` declaration, read through `tokenExpectations()`; change it and the Fern note in
10
+ * `docs/src/content/docs/tokens.mdx` together.
11
+ */
12
+ const EXPECTED = { layers: ["ci"] };
13
+ // src/auth.ts
14
+ /** Resolves the Fern token from `--token`, `HOLOCRON_FERN_TOKEN`, `FERN_TOKEN` or the keyring entry for `fern`, and throws `AuthError` when none is set. */
15
+ const resolveToken = createResolveToken({
16
+ envName: "HOLOCRON_FERN_TOKEN",
17
+ vendorEnvName: "FERN_TOKEN",
18
+ keyringService: "fern",
19
+ errorMessage: "no Fern token found. Set HOLOCRON_FERN_TOKEN / FERN_TOKEN, or run: holocron new token fern <token>"
20
+ });
21
+ /** Every token this plugin takes, for `holocron doctor secrets --verify`, with the layers that hold it. */
22
+ const tokens = [{
23
+ ...resolveToken.token,
24
+ expect: EXPECTED
25
+ }];
26
+ // src/capabilities/wiki.ts
27
+ /** The Fern CLI version written into `fern/fern.config.json`. */
5
28
  const FERN_VERSION = "5.114.1";
29
+ /** `wiki` capability for Fern: writes the Fern config and docs scaffold for a repo. */
6
30
  var FernWiki = class {
7
31
  opts;
32
+ /** Capability key; always `"wiki"`. */
8
33
  key = "wiki";
34
+ /** Provider name; always `"fern"`. */
9
35
  providerName = "fern";
10
36
  constructor(opts) {
11
37
  this.opts = opts;
@@ -33,6 +59,38 @@ var FernWiki = class {
33
59
  headers: { "X-Fern-Host": this.opts.domain.split("/")[0] }
34
60
  };
35
61
  }
62
+ /**
63
+ * Confirms `HOLOCRON_FERN_TOKEN`/`FERN_TOKEN` resolves, or a Fern token
64
+ * is stored in the keyring — not a live check. Fern exposes no
65
+ * lightweight endpoint to verify a token actually works (the only
66
+ * token-consuming operation is a full `fern generate`, see
67
+ * holocron#1079); this only confirms one is configured, the same way
68
+ * CI's `fern generate --docs` step would find it.
69
+ *
70
+ * Deliberately ignores `cliToken` (`--token`): CI's step only ever sees
71
+ * the env var from a GitHub Actions secret, never a CLI flag — and a
72
+ * bare `--token <value>` meant for some OTHER provider is forwarded to
73
+ * every loaded plugin as a fallback (see AGENTS.md), so honoring it
74
+ * here would report "configured" even when no Fern-specific credential
75
+ * exists anywhere CI would actually look.
76
+ *
77
+ * Only `AuthError` (the specific "no token found" signal) is treated as
78
+ * `ok: false` — any other error (e.g. a real keyring failure) is a
79
+ * genuine problem and must surface as a failure, not get misreported as
80
+ * a missing token.
81
+ */
82
+ async whoami() {
83
+ try {
84
+ resolveToken({
85
+ ...this.opts,
86
+ cliToken: void 0
87
+ });
88
+ return { ok: true };
89
+ } catch (err) {
90
+ if (err instanceof AuthError) return { ok: false };
91
+ throw err;
92
+ }
93
+ }
36
94
  dnsRecord() {
37
95
  const { org, domain, fernOrg } = this.opts;
38
96
  if (!domain) return null;
@@ -52,10 +110,10 @@ async function writeFernConfig({ repoRoot, fernOrg }) {
52
110
  const fernDir = join(repoRoot, "fern");
53
111
  await mkdir(fernDir, { recursive: true });
54
112
  const org = fernOrg ?? "holocron";
55
- const content = JSON.stringify({
113
+ const content = stringifyJson({
56
114
  organization: org,
57
115
  version: FERN_VERSION
58
- }, null, 2) + "\n";
116
+ });
59
117
  await writeFile(join(fernDir, "fern.config.json"), content, "utf8");
60
118
  return `fern.config.json: org=${org}, version=${FERN_VERSION}`;
61
119
  }
@@ -130,24 +188,76 @@ function updateInstancesBlock(content, instanceUrl, customDomain, multiSource, r
130
188
  if (match[0] === newBlock) return content;
131
189
  return content.replace(blockRe, newBlock);
132
190
  }
133
- //#endregion
134
- //#region src/auth.ts
135
- const resolveToken = createResolveToken({
136
- envName: "HOLOCRON_FERN_TOKEN",
137
- vendorEnvName: "FERN_TOKEN",
138
- keyringService: "fern",
139
- errorMessage: "no Fern token found. Set HOLOCRON_FERN_TOKEN / FERN_TOKEN, or run: holocron auth set fern <token>"
140
- });
141
- //#endregion
142
- //#region src/index.ts
191
+ // src/probes/token/parse-layer.ts
192
+ /** Reads `--layer <word>` (or `--layer=<word>`) from the words after the probe name, which the CLI passes when `--environment` named a layer. */
193
+ function parseLayer(args) {
194
+ for (let i = 0; i < args.length; i++) {
195
+ const arg = args[i];
196
+ if (arg === "--layer") return args[i + 1];
197
+ if (arg.startsWith("--layer=")) return arg.slice(8);
198
+ }
199
+ }
200
+ // src/probes/token/token.ts
201
+ /**
202
+ * The `token` probe for Fern. Fern has no endpoint that reports who a token is or what it may do, so the probe cannot
203
+ * judge permissions: it checks that a token is set, says whether the layer holds it when `--layer` is passed (because
204
+ * `--environment` named one) against the plugin's `tokens` declaration, and states plainly that permissions are not
205
+ * checked. It makes no request and never prints the token.
206
+ */
207
+ function tokenProbe(ctx) {
208
+ return {
209
+ summary: "Check that a Fern token is set, and for a layer that it is one that should hold it. Fern cannot report a token's permissions, so those are not checked. Read-only, no request.",
210
+ writes: false,
211
+ acceptsToken: true,
212
+ async run(probe) {
213
+ probe.secret(ctx.token());
214
+ probe.say({
215
+ status: "ok",
216
+ label: "token",
217
+ detail: "set"
218
+ });
219
+ const layer = parseLayer(probe.args ?? []);
220
+ if (layer) {
221
+ const declared = tokenExpectations(tokens, { layer }).length > 0;
222
+ probe.say(declared ? {
223
+ status: "ok",
224
+ label: layer,
225
+ detail: "a layer that should hold this token"
226
+ } : {
227
+ status: "info",
228
+ label: layer,
229
+ detail: "not a layer this token is declared for, so it is only described"
230
+ });
231
+ }
232
+ probe.say({
233
+ status: "info",
234
+ label: "permissions",
235
+ detail: "Fern cannot report a token's permissions, so they are not checked"
236
+ });
237
+ }
238
+ };
239
+ }
240
+ // src/index.ts
241
+ /** Builds the `wiki` capability from the plugin options. */
143
242
  function wiki(opts) {
144
243
  return new FernWiki(opts);
145
244
  }
245
+ /** Creates the Fern plugin: its `connection` and `token` probes and the `wiki` capability. */
146
246
  function createPlugin(options = {}) {
147
247
  return {
148
248
  name: "@theholocron/holocron-plugin-fern",
249
+ probes: {
250
+ connection: connectionProbe({
251
+ provider: "Fern",
252
+ token: () => resolveToken(options),
253
+ verify: async () => ({
254
+ ok: true,
255
+ subject: "a token is set (Fern cannot check it from here)"
256
+ })
257
+ }),
258
+ token: tokenProbe({ token: () => resolveToken(options) })
259
+ },
149
260
  capabilities: { wiki: () => wiki(options) }
150
261
  };
151
262
  }
152
- //#endregion
153
- export { AuthError, FERN_VERSION, FernWiki, createPlugin, resolveToken, wiki };
263
+ export { AuthError, FERN_VERSION, FernWiki, createPlugin, resolveToken, tokens, wiki };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theholocron/holocron-plugin-fern",
3
- "version": "5.0.0-alpha.43",
3
+ "version": "5.0.0-alpha.430",
4
4
  "description": "Holocron plugin for Fern. Implements the wiki capability for engineering knowledge publishing.",
5
5
  "keywords": [
6
6
  "fern",
@@ -8,12 +8,12 @@
8
8
  "plugin",
9
9
  "wiki"
10
10
  ],
11
- "homepage": "https://github.com/theholocron/holocron/tree/main/packages/holocron-plugin-fern#readme",
11
+ "homepage": "https://docs.theholocron.dev/holocron/plugins/fern/",
12
12
  "bugs": "https://github.com/theholocron/holocron/issues",
13
13
  "repository": {
14
14
  "type": "git",
15
15
  "url": "git+https://github.com/theholocron/holocron.git",
16
- "directory": "packages/holocron-plugin-fern"
16
+ "directory": "plugins/holocron-plugin-fern"
17
17
  },
18
18
  "license": "MIT",
19
19
  "author": "Newton Koumantzelis",
@@ -29,25 +29,28 @@
29
29
  "files": [
30
30
  "dist"
31
31
  ],
32
+ "dependencies": {
33
+ "@theholocron/json-utils": "^1.17.0-alpha.4"
34
+ },
32
35
  "devDependencies": {
33
- "@theholocron/eslint-config": "^8.4.5",
34
- "@theholocron/tsconfig": "^8.4.5",
35
- "@theholocron/tsdown-config": "^8.4.5",
36
- "@theholocron/vitest-config": "^8.4.5",
37
36
  "@types/node": "^26",
38
- "@vitest/coverage-v8": "^4.1.11",
39
- "@vitest/eslint-plugin": "^1.6.27",
40
- "eslint": "^10.8.1",
41
- "eslint-plugin-n": "^18.3.0",
42
- "globals": "^17.11.0",
43
- "tsdown": "^0.22.14",
44
- "tsx": "4.23.12",
37
+ "@vitest/coverage-v8": "^5.1.0",
38
+ "@vitest/eslint-plugin": "^1.6.28",
39
+ "eslint": "^10.14.0",
40
+ "eslint-plugin-n": "^18.5.0",
41
+ "globals": "^17.14.0",
42
+ "tsdown": "^0.23.5",
43
+ "tsx": "4.23.15",
45
44
  "typescript": "^5.9.3",
46
- "vitest": "^4.1.11",
47
- "@theholocron/cli": "5.0.0-alpha.43"
45
+ "vitest": "^5.1.0",
46
+ "@theholocron/cli": "5.0.0-alpha.430",
47
+ "@theholocron/eslint-config": "^8.9.0-alpha.17",
48
+ "@theholocron/tsconfig": "^8.9.0-alpha.17",
49
+ "@theholocron/tsdown-config": "^8.9.0-alpha.17",
50
+ "@theholocron/vitest-config": "^8.9.0-alpha.17"
48
51
  },
49
52
  "peerDependencies": {
50
- "@theholocron/cli": "5.0.0-alpha.43"
53
+ "@theholocron/cli": "5.0.0-alpha.430"
51
54
  },
52
55
  "engines": {
53
56
  "node": ">=22"
@@ -56,10 +59,10 @@
56
59
  "access": "public"
57
60
  },
58
61
  "scripts": {
59
- "delivery.build": "tsdown",
60
- "sourceQuality.staticAnalysis": "holocron run sourceQuality.staticAnalysis --",
62
+ "build": "tsdown",
63
+ "lint:code": "eslint .",
64
+ "test:unit": "vitest run --coverage",
61
65
  "test:watch": "vitest",
62
- "verification.typeSafety": "holocron run verification.typeSafety --",
63
- "verification.unitTests": "holocron run verification.unitTests --"
66
+ "typecheck": "tsc --noEmit"
64
67
  }
65
68
  }