@homeflare/site 0.1.0
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/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/checkout.d.ts +26 -0
- package/dist/checkout.d.ts.map +1 -0
- package/dist/decode.d.ts +26 -0
- package/dist/decode.d.ts.map +1 -0
- package/dist/derive.d.ts +66 -0
- package/dist/derive.d.ts.map +1 -0
- package/dist/errors.d.ts +45 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/guards.d.ts +19 -0
- package/dist/guards.d.ts.map +1 -0
- package/dist/identity.d.ts +33 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/index-dvzn0279.js +288 -0
- package/dist/index-dvzn0279.js.map +17 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +238 -0
- package/dist/index.js.map +14 -0
- package/dist/inventory.d.ts +12 -0
- package/dist/inventory.d.ts.map +1 -0
- package/dist/load.d.ts +34 -0
- package/dist/load.d.ts.map +1 -0
- package/dist/load.js +190 -0
- package/dist/load.js.map +12 -0
- package/dist/net.d.ts +5 -0
- package/dist/net.d.ts.map +1 -0
- package/dist/overrides.d.ts +45 -0
- package/dist/overrides.d.ts.map +1 -0
- package/dist/pins.d.ts +15 -0
- package/dist/pins.d.ts.map +1 -0
- package/dist/primitives.d.ts +57 -0
- package/dist/primitives.d.ts.map +1 -0
- package/dist/references.d.ts +6 -0
- package/dist/references.d.ts.map +1 -0
- package/dist/schema.d.ts +100 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/tokens.d.ts +24 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +41 -0
- package/site.example.json +81 -0
- package/src/checkout.ts +109 -0
- package/src/decode.ts +99 -0
- package/src/derive.ts +188 -0
- package/src/errors.ts +60 -0
- package/src/guards.ts +42 -0
- package/src/identity.ts +93 -0
- package/src/index.ts +43 -0
- package/src/inventory.ts +55 -0
- package/src/load.ts +186 -0
- package/src/net.ts +29 -0
- package/src/overrides.ts +79 -0
- package/src/pins.ts +40 -0
- package/src/primitives.ts +139 -0
- package/src/references.ts +82 -0
- package/src/schema.ts +144 -0
- package/src/tokens.ts +60 -0
- package/src/version.ts +2 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Timothy Schneider <tim@taslabs.net>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
10|furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
20|OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# @homeflare/site
|
|
2
|
+
|
|
3
|
+
One typed site config. The file holds **base values** (an apex, a few labels, networks,
|
|
4
|
+
hosts) and **pinned values**; `derive()` builds every hostname, address, zone suffix,
|
|
5
|
+
Access URL, OIDC redirect and vault mount name from it, the same way in every repo.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
bun add @homeflare/site effect@4.0.0-rc.115
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
⛔ **`effect` is a pinned peer.** Effect release candidates are not compatible with each
|
|
12
|
+
other, and the override behaviour this package relies on is measured per version.
|
|
13
|
+
|
|
14
|
+
★ **Why.** A public stack that types a hostname cannot be run by anyone else, and a stack
|
|
15
|
+
that re-derives a name its own way drifts from its siblings. Swapping the apex here
|
|
16
|
+
changes every derived name by substitution and nothing else — a test proves it.
|
|
17
|
+
|
|
18
|
+
## Load it
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { derive, assertStage } from '@homeflare/site';
|
|
22
|
+
import { loadSite } from '@homeflare/site/load'; // Node / Bun: reads a file, spawns git
|
|
23
|
+
|
|
24
|
+
const { site, overrides } = await loadSite({ siteDev: flags.siteDev });
|
|
25
|
+
if (overrides.length > 0) console.warn('site overrides:', overrides.join(', '));
|
|
26
|
+
assertStage(site, stage);
|
|
27
|
+
const d = derive(site);
|
|
28
|
+
d.vault.publicAddr; // https://api.v.example.com
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- `HF_SITE_FILE` names the file. ⛔ **There is no default path**: without it `loadSite`
|
|
32
|
+
refuses and points at `node_modules/@homeflare/site/site.example.json`. Copy that, set
|
|
33
|
+
`kind`, replace every value.
|
|
34
|
+
- **Plain JSON**, not JSONC — Nix, jq and Python read the same file.
|
|
35
|
+
- ⛔ **Reviewed values only.** Unless `siteDev` is set (your `--site-dev` flag), the file
|
|
36
|
+
must be committed, unmodified, on `main` (`branch` to change it). "Unmodified" is by
|
|
37
|
+
blob hash, so `--skip-worktree` cannot hide an edit, and a symlink is judged by the
|
|
38
|
+
file it points at. `readCheckout` and `checkoutProblem` expose the same check.
|
|
39
|
+
- `SITE_FILE_VAR` is `HF_SITE_FILE`; `SITE_EXAMPLE` is the example's path.
|
|
40
|
+
- Workers (no filesystem, no env): `decodeSite(json)` from the main entry. It runs
|
|
41
|
+
`validateSite` (references, then the derive-version guard) itself.
|
|
42
|
+
|
|
43
|
+
### Environment overrides
|
|
44
|
+
|
|
45
|
+
`ENV_OVERRIDES` lists the only `HF_SITE_*` variables accepted — scalars such as
|
|
46
|
+
`HF_SITE_APEX` or `HF_SITE_VAULT_PORT`. Anything else under `HF_SITE_` is refused.
|
|
47
|
+
⛔ **Overrides need `siteDev`**: an override is an unreviewed value, like an uncommitted
|
|
48
|
+
edit, so a stray `HF_SITE_APEX` in a shell refuses a reviewed load instead of renaming
|
|
49
|
+
every derived hostname.
|
|
50
|
+
|
|
51
|
+
⚠️ **Measured on effect 4.0.0-rc.115**, and each is a test:
|
|
52
|
+
|
|
53
|
+
- `nested('hf')` must come **before** `constantCase`. The other order looks up
|
|
54
|
+
`hf_SITE_APEX`, so `HF_SITE_APEX` is ignored silently.
|
|
55
|
+
- An override inside a record or list **replaces** the whole collection (and upper-cases
|
|
56
|
+
its key), so collections are never overridable.
|
|
57
|
+
- `HF_SITE_X_API` shadows `x`: a field named like another plus `_…` breaks the decode.
|
|
58
|
+
- ⛔ Guard fields — `version`, `deriveVersion`, `kind`, `vault.clusterName`,
|
|
59
|
+
`vault.namespace` — never: an overridden identity field moves the identity check's
|
|
60
|
+
expectation along with the client.
|
|
61
|
+
|
|
62
|
+
## Derived names
|
|
63
|
+
|
|
64
|
+
| call | example value |
|
|
65
|
+
| --------------------------------------- | ----------------------------------------------- |
|
|
66
|
+
| `d.vault.host` / `d.vault.apiHost` | `v.example.com` / `api.v.example.com` |
|
|
67
|
+
| `d.vault.meshAddr` / `d.vault.lanAddr` | `https://198.18.0.2:8200` / `http://hub.mgmt…` |
|
|
68
|
+
| `d.vault.oidcRedirects` | UI callback on the vault host, then `localhost` |
|
|
69
|
+
| `d.mgmtZone`, `d.zone('lab')` | `mgmt.example.com`, `lab.example.com` |
|
|
70
|
+
| `d.host('n2')`, `d.address('n2','lab')` | `n2.mgmt.example.com`, `198.51.100.12` |
|
|
71
|
+
| `d.access.teamDomain` | `https://example-team.cloudflareaccess.com` |
|
|
72
|
+
| `d.productHost('wiki')`, `d.productUrl` | `kb.example.com` (label), own `domain` if set |
|
|
73
|
+
| `d.serviceUrl('grafana')` | `http://hub.mgmt.example.com:3000` |
|
|
74
|
+
| `d.clusterMembers('c1')` | member FQDNs |
|
|
75
|
+
| `d.cloudflareMount('main','dns')` | `cloudflare-main-dns` (all: `cloudflareMounts`) |
|
|
76
|
+
|
|
77
|
+
⛔ **Unknown keys throw** (`SiteError` code `unknown-key`, listing what is declared). There
|
|
78
|
+
is no fallback host: a plausible default is how a typo becomes a DNS record.
|
|
79
|
+
|
|
80
|
+
`d.access.teamDomain` is exactly what `verifyAccessJwt` in `@homeflare/cloudflare` calls
|
|
81
|
+
`teamDomain`.
|
|
82
|
+
|
|
83
|
+
## Pinned, never derived
|
|
84
|
+
|
|
85
|
+
`pins(site)` reads `pinned.*` by key: `name`, `certificate`, `adopted`, `policy`,
|
|
86
|
+
`principals`.
|
|
87
|
+
|
|
88
|
+
- ⛔ **Physical names** (Workers, D1, KV, R2, Durable Objects): deriving the alerts D1
|
|
89
|
+
name would replace it and delete its data.
|
|
90
|
+
- ⛔ **Certificate hostnames** on adopted certificates: a change reissues and revokes the
|
|
91
|
+
certificate a host is still serving.
|
|
92
|
+
- **Adopted ids** (LB pool, WAF ruleset, Ceph / PBS names, PKI CNs), and the vault's
|
|
93
|
+
`clusterName`.
|
|
94
|
+
- **Policy and SSH principal lists** stay explicit. `unknownPrincipals`,
|
|
95
|
+
`pinnedPrincipalIssues` and `inventory` let a consumer test assert they are subsets of
|
|
96
|
+
the inventory — a break-glass principal must never vanish because a leg was renamed.
|
|
97
|
+
|
|
98
|
+
## Guards
|
|
99
|
+
|
|
100
|
+
- `decodeSite` / `loadSite` refuse a file whose `deriveVersion` is not the installed
|
|
101
|
+
version (`checkDeriveVersion`, code `derive-version`). Read the CHANGELOG, then bump it.
|
|
102
|
+
- `assertStage(site, 'live')` refuses unless `kind` is `live`.
|
|
103
|
+
- `expectedIdentity(site, { account })`, `compareIdentity`, `assertIdentity`: hand in the
|
|
104
|
+
observed `cluster_name` (from `sys/health`), namespace and Cloudflare account id; any
|
|
105
|
+
mismatch — or anything expected but not observed — refuses, and so does an expectation
|
|
106
|
+
naming no field (it would match any system). Pure: fetches nothing.
|
|
107
|
+
|
|
108
|
+
## Doc placeholders
|
|
109
|
+
|
|
110
|
+
`SITE_TOKENS` is the fixed set public docs use: `<apex>`, `<vault-host>`,
|
|
111
|
+
`<vault-api-host>`, `<mgmt-zone>`, `<access-team>`, `<github-owner>`, `<estate-root>`,
|
|
112
|
+
`<cluster>`. `tokenValues(site)` gives each single-valued token's value (a leak gate's
|
|
113
|
+
needles); `renderTokens(text, site)` substitutes them. `<cluster>` is a variable.
|
|
114
|
+
|
|
115
|
+
⛔ Placeholders use only RFC 2606 names and RFC 5737 / 2544 addresses — never `10/8` or
|
|
116
|
+
`100.64/10`, which would blind a leak gate to the ranges it must catch.
|
|
117
|
+
|
|
118
|
+
## Types and errors
|
|
119
|
+
|
|
120
|
+
`SiteSchema` (Effect Schema), `Site`, `SiteInput`, `SITE_FORMAT`, `Derived`, `SiteError`
|
|
121
|
+
with a stable `code` and `issues` naming each path, `VERSION`.
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT © Timothy Schneider
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface CheckoutState {
|
|
2
|
+
/** The checked-out branch, or `undefined` on a detached HEAD. */
|
|
3
|
+
readonly branch: string | undefined;
|
|
4
|
+
/** Git knows the file (⚠️ an ignored or untracked file was never reviewed). */
|
|
5
|
+
readonly tracked: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* The file's bytes differ from HEAD, staged or not.
|
|
8
|
+
* ⚠️ Compared by BLOB HASH, not only by `git status`. Measured 2026-09-21: after
|
|
9
|
+
* `git update-index --skip-worktree` (the usual way to keep a local config tweak),
|
|
10
|
+
* `status --porcelain` printed nothing for an edited site file, and a status-only
|
|
11
|
+
* guard loaded the edited apex as "clean, on main".
|
|
12
|
+
*/
|
|
13
|
+
readonly dirty: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The file's checkout state, or `undefined` when it is not inside a git work tree.
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ THE REAL FILE IS CHECKED, NOT THE LINK. Measured 2026-09-21: a committed symlink whose
|
|
19
|
+
* target lived outside the repository passed as "clean, on main" while the target was
|
|
20
|
+
* edited freely — git tracks the link's target PATH, not the bytes behind it. Resolving
|
|
21
|
+
* first makes that target "not inside a git checkout", which refuses.
|
|
22
|
+
*/
|
|
23
|
+
export declare function readCheckout(file: string): Promise<CheckoutState | undefined>;
|
|
24
|
+
/** Why `state` is not a reviewed checkout of `branch`, or `undefined` when it is. */
|
|
25
|
+
export declare function checkoutProblem(state: CheckoutState | undefined, branch: string): string | undefined;
|
|
26
|
+
//# sourceMappingURL=checkout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkout.d.ts","sourceRoot":"","sources":["../src/checkout.ts"],"names":[],"mappings":"AAqCA,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAoBD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAgBnF;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAC7B,KAAK,EAAE,aAAa,GAAG,SAAS,EAChC,MAAM,EAAE,MAAM,GACb,MAAM,GAAG,SAAS,CAQpB"}
|
package/dist/decode.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as SchemaIssue from 'effect/SchemaIssue';
|
|
2
|
+
import { type Site } from './schema.ts';
|
|
3
|
+
/**
|
|
4
|
+
* `pinned.certificates["vault.origin"].0` — a key that itself holds a dot is bracketed,
|
|
5
|
+
* or the path would read as two levels that do not exist.
|
|
6
|
+
*/
|
|
7
|
+
export declare function joinPath(segments: readonly string[]): string;
|
|
8
|
+
/** Render a schema issue as `path: problem` lines, dropping a leading `skip` segment. */
|
|
9
|
+
export declare function formatIssues(issue: SchemaIssue.Issue, skip?: string): readonly string[];
|
|
10
|
+
/** Schema-only decode. Throws `decode` listing every problem, each with its path. */
|
|
11
|
+
export declare function decodeStrict(input: unknown): Site;
|
|
12
|
+
export interface ValidateOptions {
|
|
13
|
+
/**
|
|
14
|
+
* The `@homeflare/site` version to hold `deriveVersion` against. Defaults to the
|
|
15
|
+
* installed one — which is the point; override only to test the refusal.
|
|
16
|
+
*/
|
|
17
|
+
readonly installed?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Cross-field references, then the derive-version guard. Returns the same site. */
|
|
20
|
+
export declare function validateSite(site: Site, options?: ValidateOptions): Site;
|
|
21
|
+
/**
|
|
22
|
+
* Decode and validate a site from plain data (already-parsed JSON).
|
|
23
|
+
* Throws {@link SiteError}: `decode`, `reference` or `derive-version`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function decodeSite(input: unknown, options?: ValidateOptions): Site;
|
|
26
|
+
//# sourceMappingURL=decode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode.d.ts","sourceRoot":"","sources":["../src/decode.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,WAAW,MAAM,oBAAoB,CAAC;AAIlD,OAAO,EAAE,KAAK,IAAI,EAAc,MAAM,aAAa,CAAC;AAgBpD;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAO5D;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAavF;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAYjD;AAED,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,eAAoB,GAAG,IAAI,CAO5E;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,eAAoB,GAAG,IAAI,CAE9E"}
|
package/dist/derive.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Site } from './schema.ts';
|
|
2
|
+
export interface DerivedHost {
|
|
3
|
+
/** `<key>.<zone>`; absent when the host declares no zone. */
|
|
4
|
+
readonly fqdn?: string;
|
|
5
|
+
/** Network key → address on that network. */
|
|
6
|
+
readonly addresses: Readonly<Record<string, string>>;
|
|
7
|
+
}
|
|
8
|
+
export interface DerivedVault {
|
|
9
|
+
/** `<vault-host>`: the browser / UI hostname. */
|
|
10
|
+
readonly host: string;
|
|
11
|
+
/** `<vault-api-host>`: the public API hostname. */
|
|
12
|
+
readonly apiHost: string;
|
|
13
|
+
/** `https://<vault-api-host>` — the public API. Humans; machines use Mesh or the LAN. */
|
|
14
|
+
readonly publicAddr: string;
|
|
15
|
+
/** `https://<mesh address>:<port>` — machines over the private Mesh path. */
|
|
16
|
+
readonly meshAddr: string;
|
|
17
|
+
/** The LAN pass-through proxy, as a `BAO_ADDR`. */
|
|
18
|
+
readonly lanAddr: string;
|
|
19
|
+
readonly namespace: string;
|
|
20
|
+
/** Browser callback on `<vault-host>`, then the CLI's localhost listener. */
|
|
21
|
+
readonly oidcRedirects: readonly string[];
|
|
22
|
+
}
|
|
23
|
+
export interface DerivedAccess {
|
|
24
|
+
readonly team: string;
|
|
25
|
+
/**
|
|
26
|
+
* `https://<team>.cloudflareaccess.com`. ★ Named for `verifyAccessJwt`'s `teamDomain`
|
|
27
|
+
* option in `@homeflare/cloudflare`, so it passes straight through.
|
|
28
|
+
*/
|
|
29
|
+
readonly teamDomain: string;
|
|
30
|
+
/** The JWKS endpoint Access signs with. */
|
|
31
|
+
readonly certsUrl: string;
|
|
32
|
+
}
|
|
33
|
+
export interface Derived {
|
|
34
|
+
readonly apex: string;
|
|
35
|
+
/** Zone key → FQDN (`mgmt` → `mgmt.<apex>`). */
|
|
36
|
+
readonly zones: Readonly<Record<string, string>>;
|
|
37
|
+
/** `<mgmt-zone>`. */
|
|
38
|
+
readonly mgmtZone: string;
|
|
39
|
+
readonly vault: DerivedVault;
|
|
40
|
+
readonly access: DerivedAccess;
|
|
41
|
+
readonly hosts: Readonly<Record<string, DerivedHost>>;
|
|
42
|
+
/** Product key → public hostname. */
|
|
43
|
+
readonly products: Readonly<Record<string, string>>;
|
|
44
|
+
/** Service key → URL. */
|
|
45
|
+
readonly services: Readonly<Record<string, string>>;
|
|
46
|
+
/** Cluster key → member FQDNs, in declared order. */
|
|
47
|
+
readonly clusters: Readonly<Record<string, readonly string[]>>;
|
|
48
|
+
/** Every `cloudflare-<alias>-<surface>` mount, in declared order. */
|
|
49
|
+
readonly cloudflareMounts: readonly string[];
|
|
50
|
+
/** A zone's FQDN. `"apex"` is the apex itself. */
|
|
51
|
+
zone(key: string): string;
|
|
52
|
+
/** A host's FQDN. Throws when the host is unknown or has no zone. */
|
|
53
|
+
host(key: string): string;
|
|
54
|
+
/** A host's address on a network. Throws when either is unknown or not a leg. */
|
|
55
|
+
address(host: string, network: string): string;
|
|
56
|
+
productHost(key: string): string;
|
|
57
|
+
/** `https://<productHost>`. */
|
|
58
|
+
productUrl(key: string): string;
|
|
59
|
+
serviceUrl(key: string): string;
|
|
60
|
+
clusterMembers(key: string): readonly string[];
|
|
61
|
+
/** Throws when the alias is unknown or that account does not list the surface. */
|
|
62
|
+
cloudflareMount(alias: string, surface: string): string;
|
|
63
|
+
accountId(alias: string): string;
|
|
64
|
+
}
|
|
65
|
+
export declare function derive(site: Site): Derived;
|
|
66
|
+
//# sourceMappingURL=derive.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../src/derive.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one error this package throws.
|
|
3
|
+
*
|
|
4
|
+
* ★ ONE CLASS, A `code` TO SWITCH ON. Callers mostly print the message and stop; the few
|
|
5
|
+
* that branch (a scaffold harness skipping `derive-version` in a dry run, say) branch on
|
|
6
|
+
* `code`, which is stable, rather than on message text, which is not.
|
|
7
|
+
*
|
|
8
|
+
* ⛔ Messages name paths, keys and the fix — never a value from a live file beyond the
|
|
9
|
+
* one being complained about. A site file is private, and an error string ends up in CI
|
|
10
|
+
* logs.
|
|
11
|
+
*/
|
|
12
|
+
export type SiteErrorCode =
|
|
13
|
+
/** The file does not match the schema. `issues` lists `path: problem`. */
|
|
14
|
+
'decode'
|
|
15
|
+
/** A value points at something the file does not declare (a zone, host, network). */
|
|
16
|
+
| 'reference'
|
|
17
|
+
/** A consumer asked for a product, service, zone, host or pin nobody declared. */
|
|
18
|
+
| 'unknown-key'
|
|
19
|
+
/** The file was reviewed against a different `@homeflare/site` version. */
|
|
20
|
+
| 'derive-version'
|
|
21
|
+
/** A non-live site was asked to plan stage `live`. */
|
|
22
|
+
| 'stage'
|
|
23
|
+
/** The observed vault or account is not the one the site describes. */
|
|
24
|
+
| 'identity'
|
|
25
|
+
/** The file could not be located, read or parsed. */
|
|
26
|
+
| 'load'
|
|
27
|
+
/** The file is not committed on `main` and `siteDev` was not set. */
|
|
28
|
+
| 'checkout'
|
|
29
|
+
/** An environment override was refused. */
|
|
30
|
+
| 'override';
|
|
31
|
+
export declare class SiteError extends Error {
|
|
32
|
+
readonly name = "SiteError";
|
|
33
|
+
readonly code: SiteErrorCode;
|
|
34
|
+
/** One line per problem, each naming its path. Empty when the message says it all. */
|
|
35
|
+
readonly issues: readonly string[];
|
|
36
|
+
constructor(code: SiteErrorCode, message: string, issues?: readonly string[]);
|
|
37
|
+
}
|
|
38
|
+
/** Throw `unknown-key`, listing what IS declared so the fix is one glance away. */
|
|
39
|
+
export declare function unknownKey(kind: string, key: string, declared: Iterable<string>): never;
|
|
40
|
+
/**
|
|
41
|
+
* Look a key up in a record, or throw `unknown-key`.
|
|
42
|
+
* ⛔ No fallback, ever. A plausible default host is how a typo becomes a live DNS record.
|
|
43
|
+
*/
|
|
44
|
+
export declare function lookup<V>(kind: string, record: Readonly<Record<string, V>>, key: string): V;
|
|
45
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,aAAa;AACvB,0EAA0E;AACxE,QAAQ;AACV,qFAAqF;GACnF,WAAW;AACb,kFAAkF;GAChF,aAAa;AACf,2EAA2E;GACzE,gBAAgB;AAClB,sDAAsD;GACpD,OAAO;AACT,uEAAuE;GACrE,UAAU;AACZ,qDAAqD;GACnD,MAAM;AACR,qEAAqE;GACnE,UAAU;AACZ,2CAA2C;GACzC,UAAU,CAAC;AAEf,qBAAa,SAAU,SAAQ,KAAK;IAClC,SAAkB,IAAI,eAAe;IACrC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,sFAAsF;IACtF,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAEnC,YAAY,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,SAAS,MAAM,EAAO,EAI/E;CACF;AAED,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,KAAK,CAIvF;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,CAG3F"}
|
package/dist/guards.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Site } from './schema.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Refuse a site reviewed against a different `@homeflare/site` than the one installed.
|
|
4
|
+
*
|
|
5
|
+
* ★ WHY EXACT EQUALITY. Derive rules are this package's behaviour. If a new version builds
|
|
6
|
+
* one hostname differently, every repo on it would plan a rename — a REPLACE — without
|
|
7
|
+
* anyone having looked. Holding `deriveVersion` equal to the installed version makes that
|
|
8
|
+
* a deliberate, reviewed one-line bump in the site file instead.
|
|
9
|
+
*/
|
|
10
|
+
export declare function checkDeriveVersion(site: Site, installed: string): void;
|
|
11
|
+
/** Stages as a deploy tool names them. Only `live` is special here. */
|
|
12
|
+
export type Stage = string;
|
|
13
|
+
/**
|
|
14
|
+
* ⛔ STAGE `live` ONLY WITH A LIVE SITE. An example or testing site can never plan
|
|
15
|
+
* against live state — its account ids and cluster name are placeholders, and a plan
|
|
16
|
+
* against real state would read every live object as "not declared: delete".
|
|
17
|
+
*/
|
|
18
|
+
export declare function assertStage(site: Site, stage: Stage): void;
|
|
19
|
+
//# sourceMappingURL=guards.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../src/guards.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAQtE;AAED,uEAAuE;AACvE,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC;AAE3B;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAO1D"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Site } from './schema.ts';
|
|
2
|
+
export interface Identity {
|
|
3
|
+
/** `cluster_name` from the vault's `sys/health`. */
|
|
4
|
+
readonly vaultClusterName?: string;
|
|
5
|
+
/** The namespace the client is using. `''` is the root namespace. */
|
|
6
|
+
readonly vaultNamespace?: string;
|
|
7
|
+
/** The Cloudflare account the credential resolves to. */
|
|
8
|
+
readonly cloudflareAccountId?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface IdentityMismatch {
|
|
11
|
+
readonly field: keyof Identity;
|
|
12
|
+
readonly expected: string;
|
|
13
|
+
/** `undefined` when the caller did not observe it — which is itself a mismatch. */
|
|
14
|
+
readonly observed: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
export interface ExpectedIdentityOptions {
|
|
17
|
+
/** The account alias the plan targets. Omit to leave the account out of the compare. */
|
|
18
|
+
readonly account?: string;
|
|
19
|
+
}
|
|
20
|
+
/** What the site says the live systems are. Throws `unknown-key` for an unknown alias. */
|
|
21
|
+
export declare function expectedIdentity(site: Site, options?: ExpectedIdentityOptions): Identity;
|
|
22
|
+
/**
|
|
23
|
+
* Every field the expectation sets and the observation does not match.
|
|
24
|
+
* ⛔ An expected field the caller did not observe COUNTS AS A MISMATCH. "We did not check"
|
|
25
|
+
* must never read the same as "it matched".
|
|
26
|
+
* ⛔ An expectation that sets NO field throws `identity`. Compared field by field, `{}`
|
|
27
|
+
* matches every system there is, so a caller that built it by mistake (a wrong spread, a
|
|
28
|
+
* renamed key) would pass the guard on any vault and any account.
|
|
29
|
+
*/
|
|
30
|
+
export declare function compareIdentity(expected: Identity, observed: Identity): readonly IdentityMismatch[];
|
|
31
|
+
/** {@link compareIdentity}, throwing `identity` with one line per mismatch. */
|
|
32
|
+
export declare function assertIdentity(expected: Identity, observed: Identity): void;
|
|
33
|
+
//# sourceMappingURL=identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,WAAW,QAAQ;IACvB,oDAAoD;IACpD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,qEAAqE;IACrE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,yDAAyD;IACzD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,QAAQ,CAAC;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mFAAmF;IACnF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,WAAW,uBAAuB;IACtC,wFAAwF;IACxF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,0FAA0F;AAC1F,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,uBAA4B,GAAG,QAAQ,CAU5F;AAID;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,GACjB,SAAS,gBAAgB,EAAE,CAe7B;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAW3E"}
|