@pithy-sh/core 0.7.3 → 0.8.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/dist/naming/domains.d.ts +43 -3
- package/dist/naming/domains.d.ts.map +1 -1
- package/dist/naming/domains.js +62 -4
- package/dist/naming/feature.d.ts +126 -11
- package/dist/naming/feature.d.ts.map +1 -1
- package/dist/naming/feature.js +184 -30
- package/dist/naming/limits.d.ts +23 -7
- package/dist/naming/limits.d.ts.map +1 -1
- package/dist/naming/limits.js +22 -9
- package/dist/naming/provisionScope.d.ts +25 -11
- package/dist/naming/provisionScope.d.ts.map +1 -1
- package/dist/naming/provisionScope.js +28 -11
- package/dist/seed/seed.d.ts +36 -7
- package/dist/seed/seed.d.ts.map +1 -1
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/worker/identity.d.ts +9 -0
- package/dist/worker/identity.d.ts.map +1 -1
- package/dist/worker/identity.js +10 -1
- package/dist/workflow/faults.d.ts +34 -2
- package/dist/workflow/faults.d.ts.map +1 -1
- package/dist/workflow/faults.js +42 -18
- package/dist/workflow/host.d.ts +7 -0
- package/dist/workflow/host.d.ts.map +1 -1
- package/dist/workflow/host.js +5 -3
- package/dist/workflow/naming.d.ts +8 -0
- package/dist/workflow/naming.d.ts.map +1 -1
- package/dist/workflow/naming.js +15 -0
- package/package.json +1 -1
- package/src/naming/domains.ts +75 -5
- package/src/naming/feature.ts +274 -38
- package/src/naming/limits.ts +25 -8
- package/src/naming/provisionScope.ts +39 -14
- package/src/seed/seed.ts +36 -7
- package/src/version.generated.ts +1 -1
- package/src/worker/identity.ts +10 -0
- package/src/workflow/faults.ts +61 -4
- package/src/workflow/host.ts +15 -3
- package/src/workflow/naming.ts +25 -0
package/dist/naming/domains.d.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { z } from "zod";
|
|
4
|
+
import { type AmbientEnv } from "../env/ambient";
|
|
5
|
+
/**
|
|
6
|
+
* Whether a string is a public hostname: dot-separated DNS labels, at least two, lowercase. No scheme, no
|
|
7
|
+
* port, no trailing dot, and nothing a URL parser decoded into one — `%2e%2e` is `..` once parsed, which is an
|
|
8
|
+
* empty label rather than a host (#643). `localhost` is not one: it has a single label, and it is this machine.
|
|
9
|
+
*
|
|
10
|
+
* The one test for "is this a host" that an origin read from a stamp and an origin typed on a command line
|
|
11
|
+
* both pass, so the two cannot accept different things.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isPublicHostname(hostname: string): boolean;
|
|
4
14
|
/** The environments a domain may be declared for — every managed one, never `dev`. */
|
|
5
15
|
export declare const DOMAIN_ENVIRONMENTS: ("prod" | "staging")[];
|
|
6
16
|
/** One environment's public address for one Worker. */
|
|
@@ -58,9 +68,32 @@ export interface ResolvedOrigin {
|
|
|
58
68
|
origin: string;
|
|
59
69
|
/** The hostname alone: a route pattern, and what Turnstile binds a widget to. */
|
|
60
70
|
hostname: string;
|
|
61
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Whether `domains` declared it. `false` means {@link LOCAL_ORIGIN} was substituted, or — for a feature
|
|
73
|
+
* environment only — that the address is the `workers.dev` one provisioning stamped ({@link featureOrigin}).
|
|
74
|
+
*/
|
|
62
75
|
declared: boolean;
|
|
63
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* **A feature environment's origin: the `workers.dev` address provisioning stamped, or null (#643).**
|
|
79
|
+
*
|
|
80
|
+
* A feature Worker answers on `https://<script>.<account subdomain>.workers.dev`. Nothing can declare that —
|
|
81
|
+
* `domains` has no feature key, because a branch's hostname is composed from the branch — and the Worker cannot
|
|
82
|
+
* derive it either, because the subdomain is the account's and only the Cloudflare API knows it. So `pithy
|
|
83
|
+
* provision --feature` derives it, through the CLI's one address resolver, and stamps it as the feature
|
|
84
|
+
* stanza's {@link BASE_URL_VAR}. This is the one reader of that stamp.
|
|
85
|
+
*
|
|
86
|
+
* **Only a `workers.dev` https origin is accepted, and that is the guard rather than a tidiness check.** A
|
|
87
|
+
* feature stanza is generated from the Worker's top level, so a `BASE_URL` somebody set by hand up there
|
|
88
|
+
* arrives in the feature too — and it is another environment's origin, most likely production's. Honoring it
|
|
89
|
+
* would send a branch's magic links into production, which is the one thing {@link resolveOrigin} exists to
|
|
90
|
+
* make impossible. A feature's address is `workers.dev` by construction, so anything else is not one and
|
|
91
|
+
* resolves to nothing. No path, no port, no credentials: an origin, or null.
|
|
92
|
+
*/
|
|
93
|
+
export declare function featureOrigin(stamped: string | undefined): {
|
|
94
|
+
origin: string;
|
|
95
|
+
hostname: string;
|
|
96
|
+
} | null;
|
|
64
97
|
/**
|
|
65
98
|
* **Where is this Worker publicly reachable, in this environment?** — asked once, answered once.
|
|
66
99
|
*
|
|
@@ -86,8 +119,10 @@ export interface ResolvedOrigin {
|
|
|
86
119
|
* checks that stored value, so a per-environment issuer would make a connection minted in staging
|
|
87
120
|
* unverifiable in production. That may be the better isolation, but it is a decision about trust rather
|
|
88
121
|
* than about reachability, and a helper whose job is "where am I reachable" must not sweep it up.
|
|
122
|
+
*
|
|
123
|
+
* **`stamped` is a feature environment's `BASE_URL`, and is read for nothing else** — see {@link featureOrigin}.
|
|
89
124
|
*/
|
|
90
|
-
export declare function resolveOrigin(environment: string | undefined, domains: WorkerDomains | undefined): ResolvedOrigin;
|
|
125
|
+
export declare function resolveOrigin(environment: string | undefined, domains: WorkerDomains | undefined, stamped?: string | undefined): ResolvedOrigin;
|
|
91
126
|
/**
|
|
92
127
|
* The one line an adopter writes: this Worker's public origin for the environment it is running in.
|
|
93
128
|
*
|
|
@@ -105,6 +140,11 @@ export declare function resolveOrigin(environment: string | undefined, domains:
|
|
|
105
140
|
* `AUTH_BASE_URL`, which is part of why `email` and `payments` were missed for days: the constant read
|
|
106
141
|
* as auth's private business when it is the Worker's address, and every capability that needs an origin
|
|
107
142
|
* needs this one.
|
|
143
|
+
*
|
|
144
|
+
* **A feature deployment is answered from its own vars (#643).** `env` is the ambient environment — inside a
|
|
145
|
+
* Worker, its own vars — and it is read for one thing: the `BASE_URL` a feature's provisioning stamped. So the
|
|
146
|
+
* same scaffolded line gives auth's base URL, email's links and payments' return URLs a feature's real
|
|
147
|
+
* `workers.dev` origin, rather than `http://localhost`, with nothing added to the adopter's config.
|
|
108
148
|
*/
|
|
109
|
-
export declare function originFor(environment: string | undefined, domains: WorkerDomains | undefined): string;
|
|
149
|
+
export declare function originFor(environment: string | undefined, domains: WorkerDomains | undefined, env?: AmbientEnv): string;
|
|
110
150
|
//# sourceMappingURL=domains.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domains.d.ts","sourceRoot":"","sources":["../../src/naming/domains.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"domains.d.ts","sourceRoot":"","sources":["../../src/naming/domains.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,KAAK,UAAU,EAAc,MAAM,gBAAgB,CAAC;AAuC7D;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,sFAAsF;AACtF,eAAO,MAAM,mBAAmB,wBAA8D,CAAC;AAE/F,uDAAuD;AACvD,eAAO,MAAM,YAAY;;;iBA+BrB,CAAC;AACL,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;;;GAKG;AACH,eAAO,MAAM,aAAa;;;;;;;;;iBAOvB,CAAC;AACJ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,aAAa,GAAG,SAAS,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,YAAY,GAAG,IAAI,CAKlH;AAED,gHAAgH;AAChH,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAEvD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,YAAY,qBAAqB,CAAC;AAE/C,yFAAyF;AACzF,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAetG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,aAAa,CAC3B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,GAC3B,cAAc,CAShB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,SAAS,CACvB,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,GAAG,GAAE,UAAyB,GAC7B,MAAM,CAER"}
|
package/dist/naming/domains.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { ENVIRONMENTS } from "./environment.js";
|
|
4
|
+
import { BASE_URL_VAR } from "../worker/identity.js";
|
|
5
|
+
import { ambientEnv } from "../env/ambient.js";
|
|
4
6
|
import { z } from "zod";
|
|
5
7
|
//#region src/naming/domains.ts
|
|
6
8
|
/**
|
|
@@ -36,6 +38,17 @@ import { z } from "zod";
|
|
|
36
38
|
*/
|
|
37
39
|
/** A hostname a Worker answers on. No scheme, no path, no port — wrangler's `routes` pattern is a host. */
|
|
38
40
|
const HOSTNAME_PATTERN = /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))+$/;
|
|
41
|
+
/**
|
|
42
|
+
* Whether a string is a public hostname: dot-separated DNS labels, at least two, lowercase. No scheme, no
|
|
43
|
+
* port, no trailing dot, and nothing a URL parser decoded into one — `%2e%2e` is `..` once parsed, which is an
|
|
44
|
+
* empty label rather than a host (#643). `localhost` is not one: it has a single label, and it is this machine.
|
|
45
|
+
*
|
|
46
|
+
* The one test for "is this a host" that an origin read from a stamp and an origin typed on a command line
|
|
47
|
+
* both pass, so the two cannot accept different things.
|
|
48
|
+
*/
|
|
49
|
+
function isPublicHostname(hostname) {
|
|
50
|
+
return hostname.length <= 253 && HOSTNAME_PATTERN.test(hostname);
|
|
51
|
+
}
|
|
39
52
|
/** The environments a domain may be declared for — every managed one, never `dev`. */
|
|
40
53
|
const DOMAIN_ENVIRONMENTS = ENVIRONMENTS.filter((environment) => environment !== "dev");
|
|
41
54
|
/** One environment's public address for one Worker. */
|
|
@@ -95,6 +108,39 @@ function baseUrlFor(domain) {
|
|
|
95
108
|
*/
|
|
96
109
|
const LOCAL_ORIGIN = "http://localhost";
|
|
97
110
|
/**
|
|
111
|
+
* **A feature environment's origin: the `workers.dev` address provisioning stamped, or null (#643).**
|
|
112
|
+
*
|
|
113
|
+
* A feature Worker answers on `https://<script>.<account subdomain>.workers.dev`. Nothing can declare that —
|
|
114
|
+
* `domains` has no feature key, because a branch's hostname is composed from the branch — and the Worker cannot
|
|
115
|
+
* derive it either, because the subdomain is the account's and only the Cloudflare API knows it. So `pithy
|
|
116
|
+
* provision --feature` derives it, through the CLI's one address resolver, and stamps it as the feature
|
|
117
|
+
* stanza's {@link BASE_URL_VAR}. This is the one reader of that stamp.
|
|
118
|
+
*
|
|
119
|
+
* **Only a `workers.dev` https origin is accepted, and that is the guard rather than a tidiness check.** A
|
|
120
|
+
* feature stanza is generated from the Worker's top level, so a `BASE_URL` somebody set by hand up there
|
|
121
|
+
* arrives in the feature too — and it is another environment's origin, most likely production's. Honoring it
|
|
122
|
+
* would send a branch's magic links into production, which is the one thing {@link resolveOrigin} exists to
|
|
123
|
+
* make impossible. A feature's address is `workers.dev` by construction, so anything else is not one and
|
|
124
|
+
* resolves to nothing. No path, no port, no credentials: an origin, or null.
|
|
125
|
+
*/
|
|
126
|
+
function featureOrigin(stamped) {
|
|
127
|
+
if (!stamped) return null;
|
|
128
|
+
let url;
|
|
129
|
+
try {
|
|
130
|
+
url = new URL(stamped);
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
if (url.protocol !== "https:" || url.port !== "" || url.username !== "" || url.password !== "") return null;
|
|
135
|
+
if (url.pathname !== "/" || url.search !== "" || url.hash !== "") return null;
|
|
136
|
+
if (!url.hostname.endsWith(".workers.dev") || url.hostname.split(".").length < 4) return null;
|
|
137
|
+
if (!isPublicHostname(url.hostname)) return null;
|
|
138
|
+
return {
|
|
139
|
+
origin: url.origin,
|
|
140
|
+
hostname: url.hostname
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
98
144
|
* **Where is this Worker publicly reachable, in this environment?** — asked once, answered once.
|
|
99
145
|
*
|
|
100
146
|
* The two halves already existed ({@link domainFor} and {@link baseUrlFor}) and nothing composed them,
|
|
@@ -119,14 +165,21 @@ const LOCAL_ORIGIN = "http://localhost";
|
|
|
119
165
|
* checks that stored value, so a per-environment issuer would make a connection minted in staging
|
|
120
166
|
* unverifiable in production. That may be the better isolation, but it is a decision about trust rather
|
|
121
167
|
* than about reachability, and a helper whose job is "where am I reachable" must not sweep it up.
|
|
168
|
+
*
|
|
169
|
+
* **`stamped` is a feature environment's `BASE_URL`, and is read for nothing else** — see {@link featureOrigin}.
|
|
122
170
|
*/
|
|
123
|
-
function resolveOrigin(environment, domains) {
|
|
171
|
+
function resolveOrigin(environment, domains, stamped) {
|
|
124
172
|
const domain = domainFor(domains, environment);
|
|
125
173
|
if (domain) return {
|
|
126
174
|
origin: baseUrlFor(domain),
|
|
127
175
|
hostname: domain.pattern,
|
|
128
176
|
declared: true
|
|
129
177
|
};
|
|
178
|
+
const feature = environment === "feature" ? featureOrigin(stamped) : null;
|
|
179
|
+
if (feature) return {
|
|
180
|
+
...feature,
|
|
181
|
+
declared: false
|
|
182
|
+
};
|
|
130
183
|
return {
|
|
131
184
|
origin: LOCAL_ORIGIN,
|
|
132
185
|
hostname: "localhost",
|
|
@@ -150,9 +203,14 @@ function resolveOrigin(environment, domains) {
|
|
|
150
203
|
* `AUTH_BASE_URL`, which is part of why `email` and `payments` were missed for days: the constant read
|
|
151
204
|
* as auth's private business when it is the Worker's address, and every capability that needs an origin
|
|
152
205
|
* needs this one.
|
|
206
|
+
*
|
|
207
|
+
* **A feature deployment is answered from its own vars (#643).** `env` is the ambient environment — inside a
|
|
208
|
+
* Worker, its own vars — and it is read for one thing: the `BASE_URL` a feature's provisioning stamped. So the
|
|
209
|
+
* same scaffolded line gives auth's base URL, email's links and payments' return URLs a feature's real
|
|
210
|
+
* `workers.dev` origin, rather than `http://localhost`, with nothing added to the adopter's config.
|
|
153
211
|
*/
|
|
154
|
-
function originFor(environment, domains) {
|
|
155
|
-
return resolveOrigin(environment, domains).origin;
|
|
212
|
+
function originFor(environment, domains, env = ambientEnv()) {
|
|
213
|
+
return resolveOrigin(environment, domains, env[BASE_URL_VAR]).origin;
|
|
156
214
|
}
|
|
157
215
|
//#endregion
|
|
158
|
-
export { DOMAIN_ENVIRONMENTS, LOCAL_ORIGIN, WorkerDomain, WorkerDomains, baseUrlFor, domainFor, originFor, resolveOrigin };
|
|
216
|
+
export { DOMAIN_ENVIRONMENTS, LOCAL_ORIGIN, WorkerDomain, WorkerDomains, baseUrlFor, domainFor, featureOrigin, isPublicHostname, originFor, resolveOrigin };
|
package/dist/naming/feature.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
/**
|
|
4
|
-
* The names a feature environment provisions under: `<project>-f<issue>-<slug
|
|
5
|
-
* resource, `<project>-f<issue>-<slug
|
|
4
|
+
* The names a feature environment provisions under: `<project>-f<issue>-<slug>--<binding>-<kind>` for a
|
|
5
|
+
* resource, `<project>-f<issue>-<slug>--<worker>` for a Worker script.
|
|
6
|
+
*
|
|
7
|
+
* **Two hyphens after the slug, and a project with no `f<digits>` segment (#643).** Together they make every
|
|
8
|
+
* feature name parse back to exactly one (project, issue, slug): the double hyphen is something no other name
|
|
9
|
+
* Pithy composes can hold ({@link FEATURE_TAIL_SEPARATOR}), and the first `f<digits>` segment is the issue's,
|
|
10
|
+
* because the project may carry none ({@link assertFeatureProject}). Before them, project `acme-f12-x`'s `prod`
|
|
11
|
+
* and project `acme`'s branch `feature/12-x-prod` composed every name between them, and a branch whose slug
|
|
12
|
+
* was a hyphen-prefix of a sibling's could compose the sibling's database. See {@link parseFeatureName}.
|
|
6
13
|
*
|
|
7
14
|
* **No environment segment, and no Worker segment.** A feature *is* an environment, and two Workers that
|
|
8
15
|
* declare the same binding are meant to share one resource — so the binding name, not the topology, is
|
|
@@ -10,9 +17,12 @@
|
|
|
10
17
|
*
|
|
11
18
|
* Every name here is **derived and recomputed**, never stored: `provision` and `destroy` both compute the
|
|
12
19
|
* same string from `(identity, binding, kind)`, which is what lets teardown reconcile exactly rather than
|
|
13
|
-
* scan a prefix a hyphenated sibling slug could ambiguously match.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
20
|
+
* scan a prefix a hyphenated sibling slug could ambiguously match.
|
|
21
|
+
*
|
|
22
|
+
* **Never truncated (#643).** These names used to fit a long slug with a hash, on the reasoning that a feature
|
|
23
|
+
* name addresses nothing that outlives the feature. But a short hash is a slug some sibling branch can have
|
|
24
|
+
* whole, so a fitted name was a name two features could compose. A slug that does not fit is refused instead,
|
|
25
|
+
* naming the maximum ({@link assertFeatureSlugFits}), and every feature name is exactly its feature's.
|
|
16
26
|
*
|
|
17
27
|
* It lives in `core`, beside the rest of the naming rule, so the CLI and the facade compose one
|
|
18
28
|
* implementation rather than two that drift.
|
|
@@ -31,7 +41,92 @@ export interface FeatureIdentity {
|
|
|
31
41
|
slug: string;
|
|
32
42
|
}
|
|
33
43
|
/**
|
|
34
|
-
* The
|
|
44
|
+
* **The issue as a name carries it: its digits, without leading zeros (#643).** `feature/0643-foo` and
|
|
45
|
+
* `feature/643-foo` are one issue, so they are one feature and compose one set of names. Read as strings they
|
|
46
|
+
* were two features, and their rate-limit namespaces collided every time. `0` stays `0`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function canonicalIssue(issue: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* The `f<digits>` segment a project name carries, or `null` — the shape a feature's issue takes (#643).
|
|
51
|
+
*
|
|
52
|
+
* Read after kebabbing, because the kebab is what reaches an account.
|
|
53
|
+
*/
|
|
54
|
+
export declare function featureMarkerInProjectName(project: string): string | null;
|
|
55
|
+
/**
|
|
56
|
+
* **Refuse features for a project whose name carries an `f<digits>` segment (#643).**
|
|
57
|
+
*
|
|
58
|
+
* A feature name is read left to right: the project, then the first `f<digits>` segment, which is the issue.
|
|
59
|
+
* A project named `acme-f12-x` puts that segment inside itself, and then its feature 3 on `y` and project `acme`'s
|
|
60
|
+
* feature 12 on `x-f3-y` are one string. Environment names already obey this rule ({@link isFeatureMarker}), so
|
|
61
|
+
* the project obeys it too, where the two shapes meet.
|
|
62
|
+
*
|
|
63
|
+
* **Refused here, not at `requireProjectName`.** Such a project's staging and prod are untouched: no name they
|
|
64
|
+
* compose holds {@link FEATURE_TAIL_SEPARATOR}, so no feature anywhere can compose one of theirs. Refusing the
|
|
65
|
+
* name everywhere would break every command of a project that is otherwise sound. `pithy init` refuses a new one,
|
|
66
|
+
* and `pithy doctor` names the segment an existing one carries. Nothing is renamed.
|
|
67
|
+
*/
|
|
68
|
+
export declare function assertFeatureProject(project: string): void;
|
|
69
|
+
/** A feature name, read back: which project, issue and slug it belongs to, and what it names. */
|
|
70
|
+
export interface ParsedFeatureName {
|
|
71
|
+
/** The project segment, kebabbed. */
|
|
72
|
+
project: string;
|
|
73
|
+
/** The issue, canonical. */
|
|
74
|
+
issue: string;
|
|
75
|
+
/** The slug segment: the whole slug, since no feature name is truncated. */
|
|
76
|
+
slug: string;
|
|
77
|
+
/** Everything after {@link FEATURE_TAIL_SEPARATOR}: the binding and kind, Worker, Workflow or entry. */
|
|
78
|
+
thing: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* **Read a feature name back into its owner, or `null` when it is not one (#643).**
|
|
82
|
+
*
|
|
83
|
+
* Exact, because the shape leaves one reading: a feature name holds {@link FEATURE_TAIL_SEPARATOR} exactly once,
|
|
84
|
+
* everything before it is `<project>-f<issue>-<slug>`, and the first `f<digits>` segment there is the issue,
|
|
85
|
+
* since a project may carry none. Nothing else Pithy composes holds the separator at all.
|
|
86
|
+
*/
|
|
87
|
+
export declare function parseFeatureName(name: string): ParsedFeatureName | null;
|
|
88
|
+
/**
|
|
89
|
+
* **Is this name one this feature composes?** The ownership check the isolation gates ask (#643).
|
|
90
|
+
*
|
|
91
|
+
* Parsed, never prefix-matched, and exact: the name's project, issue and slug are this feature's, character for
|
|
92
|
+
* character. `<project>-f<issue>-` is shared by every branch of one issue, so a prefix match reached siblings; and
|
|
93
|
+
* a fitted form of the slug, which this used to accept, is a short hash some sibling can have as its whole slug
|
|
94
|
+
* (`feature/12-login` owned `feature/12-c`). No feature name is fitted any more ({@link composeFeatureName}), so
|
|
95
|
+
* there is nothing but the slug itself to accept.
|
|
96
|
+
*/
|
|
97
|
+
export declare function isFeatureOwnedName(identity: FeatureIdentity, name: string): boolean;
|
|
98
|
+
/** A feature's project and issue: everything its names are derived from but the slug. */
|
|
99
|
+
export type FeatureHead = Pick<FeatureIdentity, "project" | "issue">;
|
|
100
|
+
/**
|
|
101
|
+
* **The longest slug one feature name leaves room for** — `limit` less `<project>-f<issue>-`, the
|
|
102
|
+
* {@link FEATURE_TAIL_SEPARATOR}, and `thing`, which is everything after it. Zero or less when `thing` leaves none.
|
|
103
|
+
*/
|
|
104
|
+
export declare function featureSlugRoom(parts: FeatureHead, thing: string, limit: number): number;
|
|
105
|
+
/** One kind of name a feature composes, as far as its slug budget reads it. */
|
|
106
|
+
export interface FeatureNameShape {
|
|
107
|
+
/** How an error names the namespace, e.g. `a Workflow name`. */
|
|
108
|
+
label: string;
|
|
109
|
+
/** The longest name the namespace takes. */
|
|
110
|
+
limit: number;
|
|
111
|
+
/** Everything after {@link FEATURE_TAIL_SEPARATOR}, e.g. `email-suppressions-d1`. */
|
|
112
|
+
thing: string;
|
|
113
|
+
}
|
|
114
|
+
/** The longest slug every one of `shapes` leaves room for, for this project and issue, and the shape that sets it. */
|
|
115
|
+
export declare function tightestFeatureShape(parts: FeatureHead, shapes: readonly FeatureNameShape[]): {
|
|
116
|
+
max: number;
|
|
117
|
+
shape: FeatureNameShape | null;
|
|
118
|
+
};
|
|
119
|
+
/** The longest slug a feature of this project and issue may have, across every name it composes. */
|
|
120
|
+
export declare function maxFeatureSlug(parts: FeatureHead, shapes: readonly FeatureNameShape[]): number;
|
|
121
|
+
/**
|
|
122
|
+
* **Refuse a branch whose slug cannot fit every name its feature composes (#643).** The one refusal
|
|
123
|
+
* `pithy feature create` and `pithy provision --feature` make, before a worktree or a resource exists, with the
|
|
124
|
+
* project's maximum in the message. {@link composeFeatureName} refuses the same slug one name at a time; this says
|
|
125
|
+
* it once, for all of them.
|
|
126
|
+
*/
|
|
127
|
+
export declare function assertFeatureSlugFits(identity: FeatureIdentity, shapes: readonly FeatureNameShape[]): void;
|
|
128
|
+
/**
|
|
129
|
+
* The full Cloudflare resource name for a feature's binding — `<project>-f<issue>-<slug>--<binding>-<kind>`.
|
|
35
130
|
*
|
|
36
131
|
* Held to **R2's 63**, the strictest of the three kinds a feature provisions, so one shape is legal for
|
|
37
132
|
* all of them: lowercase, hyphenated, alphanumeric at both ends. A D1 or KV name could be longer, but a
|
|
@@ -40,7 +135,7 @@ export interface FeatureIdentity {
|
|
|
40
135
|
export declare function featureResourceName(identity: FeatureIdentity, binding: string, kind: FeatureResourceKind): string;
|
|
41
136
|
/**
|
|
42
137
|
* The CF Secrets Store entry name holding one of a feature's **environment-scoped** secrets —
|
|
43
|
-
* `<project>-f<issue>-<slug
|
|
138
|
+
* `<project>-f<issue>-<slug>--<secret>`.
|
|
44
139
|
*
|
|
45
140
|
* A Cloudflare account has one Secrets Store, flat and unpartitionable, so the entry name is the only
|
|
46
141
|
* partition there is. A feature therefore needs its own names for the same reason it needs its own
|
|
@@ -50,12 +145,12 @@ export declare function featureResourceName(identity: FeatureIdentity, binding:
|
|
|
50
145
|
* no documented Cloudflare cap, and holding it to the strictest kind's would hash
|
|
51
146
|
* `secrets-encryption-keys` down to nothing for no reason.
|
|
52
147
|
*
|
|
53
|
-
* A `global` secret is
|
|
54
|
-
*
|
|
148
|
+
* A `global` secret is named here too since #643: a feature shares nothing with any other environment, so
|
|
149
|
+
* its entry for a global secret is its own rather than the project's `<project>-global-<secret>`.
|
|
55
150
|
*/
|
|
56
151
|
export declare function featureSecretEntryName(identity: FeatureIdentity, secret: string): string;
|
|
57
152
|
/**
|
|
58
|
-
* The Worker **script name** for one of a feature's workers — `<project>-f<issue>-<slug
|
|
153
|
+
* The Worker **script name** for one of a feature's workers — `<project>-f<issue>-<slug>--<worker>`.
|
|
59
154
|
*
|
|
60
155
|
* This is the name the feature's Workers deploy under, and therefore the name a sibling's `service`
|
|
61
156
|
* binding must target, so RPC inside a feature environment reaches that feature's deployment rather than
|
|
@@ -67,10 +162,30 @@ export declare function featureSecretEntryName(identity: FeatureIdentity, secret
|
|
|
67
162
|
* has a charset rule and no length rule, so nothing upstream was going to stop it either.
|
|
68
163
|
*
|
|
69
164
|
* **`app` is the `apps/<app>` directory, never the deploy name.** A scaffolded Worker deploys as
|
|
70
|
-
* `<project>-<app>`, and handing that here composed `<project>-f<issue>-<slug
|
|
165
|
+
* `<project>-<app>`, and handing that here composed `<project>-f<issue>-<slug>--<project>-<app>` — the
|
|
71
166
|
* project twice, spent out of the budget above (#587). This function takes a string and cannot tell the
|
|
72
167
|
* two apart, so the provisioning path reaches it only through `featureScope`, which is handed both names
|
|
73
168
|
* and picks.
|
|
74
169
|
*/
|
|
75
170
|
export declare function featureWorkerName(identity: FeatureIdentity, app: string): string;
|
|
171
|
+
/**
|
|
172
|
+
* The deployed name of one of a feature's **Workflows** — `<project>-f<issue>-<slug>--<capability>-<job>` (#643).
|
|
173
|
+
*
|
|
174
|
+
* A Workflow name is account-wide, like a Worker's, so a feature's email host cannot run
|
|
175
|
+
* `<project>-feature-email-send`: every open branch would deploy the same Workflow over every other's. It takes
|
|
176
|
+
* the feature's head instead, exactly as its host Worker does ({@link featureWorkerName}), so the Worker and the
|
|
177
|
+
* Workflows it hosts are recognizably one feature's and teardown recomputes both from the identity.
|
|
178
|
+
*
|
|
179
|
+
* Held to the **Workflow** limit ({@link NAMESPACE_LIMITS.workflow}), and fitted the way every feature name is:
|
|
180
|
+
* the slug gives way first, then the `<capability>-<job>` tail, deterministically.
|
|
181
|
+
*/
|
|
182
|
+
export declare function featureWorkflowName(identity: FeatureIdentity, capability: string, job: string): string;
|
|
183
|
+
/**
|
|
184
|
+
* The name of one of a feature's **Vectorize indexes** — `<project>-f<issue>-<slug>--<thing>` (#643).
|
|
185
|
+
*
|
|
186
|
+
* An index name is account-wide, so a feature's vector host cannot bind `<project>-feature-vector-<index>`: every
|
|
187
|
+
* open branch would read and write one index. Held to the Vectorize limit and fitted the way every feature name
|
|
188
|
+
* is, so teardown recomputes it from the identity.
|
|
189
|
+
*/
|
|
190
|
+
export declare function featureVectorizeIndexName(identity: FeatureIdentity, thing: string): string;
|
|
76
191
|
//# sourceMappingURL=feature.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../../src/naming/feature.ts"],"names":[],"mappings":"AAQA
|
|
1
|
+
{"version":3,"file":"feature.d.ts","sourceRoot":"","sources":["../../src/naming/feature.ts"],"names":[],"mappings":"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"}
|