@pithy-sh/core 0.7.2 → 0.7.4
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/capability/capability.d.ts +22 -0
- package/dist/capability/capability.d.ts.map +1 -1
- package/dist/controlPlane/capability.d.ts.map +1 -1
- package/dist/controlPlane/capability.js +3 -1
- package/dist/controlPlane/discovery/adminRoute.d.ts +10 -0
- package/dist/controlPlane/discovery/adminRoute.d.ts.map +1 -1
- package/dist/controlPlane/discovery/adminRoute.js +10 -3
- package/dist/controlPlane/http/handlers.d.ts +1 -0
- package/dist/controlPlane/http/handlers.d.ts.map +1 -1
- 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 +11 -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/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/capability/capability.ts +22 -0
- package/src/controlPlane/capability.ts +11 -1
- package/src/controlPlane/discovery/adminRoute.ts +23 -3
- 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 +11 -7
- package/src/version.generated.ts +1 -1
- package/src/worker/identity.ts +10 -0
- package/src/workflow/host.ts +15 -3
- package/src/workflow/naming.ts +25 -0
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
/** This package's npm name — the join key against a release feed. */
|
|
4
4
|
export declare const PACKAGE_NAME = "@pithy-sh/core";
|
|
5
5
|
/** This package's version, stamped from its own package.json at generation time. */
|
|
6
|
-
export declare const PACKAGE_VERSION = "0.7.
|
|
6
|
+
export declare const PACKAGE_VERSION = "0.7.4";
|
|
7
7
|
//# sourceMappingURL=version.generated.d.ts.map
|
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
/** This package's npm name — the join key against a release feed. */
|
|
5
5
|
const PACKAGE_NAME = "@pithy-sh/core";
|
|
6
6
|
/** This package's version, stamped from its own package.json at generation time. */
|
|
7
|
-
const PACKAGE_VERSION = "0.7.
|
|
7
|
+
const PACKAGE_VERSION = "0.7.4";
|
|
8
8
|
//#endregion
|
|
9
9
|
export { PACKAGE_NAME, PACKAGE_VERSION };
|
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
export declare const PROJECT_VAR = "PROJECT";
|
|
25
25
|
/** The var naming the environment this deployment serves — `dev`, `staging`, or `prod`. */
|
|
26
26
|
export declare const ENVIRONMENT_VAR = "ENVIRONMENT";
|
|
27
|
+
/**
|
|
28
|
+
* The var naming where a deployed Worker answers — `https://<host>`, no path, no trailing slash.
|
|
29
|
+
*
|
|
30
|
+
* Generated, never hand-set: `pithy worker sync` writes it from a declared domain, and `pithy provision
|
|
31
|
+
* --feature` writes a feature's from its `workers.dev` address (#643). It is the one statement of a feature's
|
|
32
|
+
* origin a running Worker can read, because the account's `workers.dev` subdomain is something only the
|
|
33
|
+
* Cloudflare API knows, and a Worker has no business calling it at module scope.
|
|
34
|
+
*/
|
|
35
|
+
export declare const BASE_URL_VAR = "BASE_URL";
|
|
27
36
|
/**
|
|
28
37
|
* The var carrying a Worker's **own** dev origin, from `pithy dev` to whatever launches that Worker.
|
|
29
38
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/worker/identity.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,qFAAqF;AACrF,eAAO,MAAM,WAAW,YAAY,CAAC;AAErC,2FAA2F;AAC3F,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAEvD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,WAAW,CAAC;AAEnC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,wBAAwB,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC,qGAAqG;AACrG,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,wEAAwE;IACxE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,2EAA2E;IAC3E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;;;;;;OAQG;IACH,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,qBAAqB;IACpC,yGAAyG;IACzG,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAgBD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAW3D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,qBAAqB,CAazE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAEzD"}
|
|
1
|
+
{"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../../src/worker/identity.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,qFAAqF;AACrF,eAAO,MAAM,WAAW,YAAY,CAAC;AAErC,2FAA2F;AAC3F,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,aAAa,CAAC;AAEvC;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,wBAAwB,CAAC;AAEvD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,WAAW,CAAC;AAEnC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,wBAAwB,wBAAwB,CAAC;AAE9D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC,qGAAqG;AACrG,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,wEAAwE;IACxE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,2EAA2E;IAC3E,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;;;;;;;OAQG;IACH,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,qBAAqB;IACpC,yGAAyG;IACzG,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAgBD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,cAAc,CAW3D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,qBAAqB,CAazE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAEzD"}
|
package/dist/worker/identity.js
CHANGED
|
@@ -26,6 +26,15 @@ const PROJECT_VAR = "PROJECT";
|
|
|
26
26
|
/** The var naming the environment this deployment serves — `dev`, `staging`, or `prod`. */
|
|
27
27
|
const ENVIRONMENT_VAR = "ENVIRONMENT";
|
|
28
28
|
/**
|
|
29
|
+
* The var naming where a deployed Worker answers — `https://<host>`, no path, no trailing slash.
|
|
30
|
+
*
|
|
31
|
+
* Generated, never hand-set: `pithy worker sync` writes it from a declared domain, and `pithy provision
|
|
32
|
+
* --feature` writes a feature's from its `workers.dev` address (#643). It is the one statement of a feature's
|
|
33
|
+
* origin a running Worker can read, because the account's `workers.dev` subdomain is something only the
|
|
34
|
+
* Cloudflare API knows, and a Worker has no business calling it at module scope.
|
|
35
|
+
*/
|
|
36
|
+
const BASE_URL_VAR = "BASE_URL";
|
|
37
|
+
/**
|
|
29
38
|
* The var carrying a Worker's **own** dev origin, from `pithy dev` to whatever launches that Worker.
|
|
30
39
|
*
|
|
31
40
|
* Not stamped into `wrangler.jsonc` like the three above, and that is the whole point of it. A dev port
|
|
@@ -175,4 +184,4 @@ function workerVersion(env) {
|
|
|
175
184
|
return workerVersionMetadata(env).id;
|
|
176
185
|
}
|
|
177
186
|
//#endregion
|
|
178
|
-
export { ENVIRONMENT_VAR, PROJECT_VAR, SELF_BINDING, VERSION_METADATA_BINDING, WORKER_ORIGIN_VAR, WORKER_VAR, workerIdentity, workerVersion, workerVersionMetadata };
|
|
187
|
+
export { BASE_URL_VAR, ENVIRONMENT_VAR, PROJECT_VAR, SELF_BINDING, VERSION_METADATA_BINDING, WORKER_ORIGIN_VAR, WORKER_VAR, workerIdentity, workerVersion, workerVersionMetadata };
|
package/dist/workflow/host.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
+
import type { FeatureIdentity } from "../naming/feature";
|
|
3
4
|
import { type WorkflowHostNameParts } from "./naming";
|
|
4
5
|
import type { WorkflowRegistry } from "./spec";
|
|
5
6
|
/**
|
|
@@ -106,6 +107,12 @@ export interface WorkflowHostParams {
|
|
|
106
107
|
capability: string;
|
|
107
108
|
/** The target environment (`dev` | `staging` | `production`). */
|
|
108
109
|
env: string;
|
|
110
|
+
/**
|
|
111
|
+
* The feature this host serves, when it serves one (#643). Its name then takes the feature's head —
|
|
112
|
+
* `<project>-f<issue>-<slug>-<capability>` — as the Workflows derived with the same parts do; `env` still
|
|
113
|
+
* says `feature`, and that is what the host's `ENVIRONMENT` var is stamped with.
|
|
114
|
+
*/
|
|
115
|
+
feature?: FeatureIdentity;
|
|
109
116
|
/** D1 binding name → database id. An unlisted binding keeps the template's id. */
|
|
110
117
|
databaseIds?: Record<string, string>;
|
|
111
118
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/workflow/host.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/workflow/host.ts"],"names":[],"mappings":"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"}
|
package/dist/workflow/host.js
CHANGED
|
@@ -31,7 +31,8 @@ function resolveWorkflowHost(template, params) {
|
|
|
31
31
|
resolved.name = workflowHostName({
|
|
32
32
|
project: params.project,
|
|
33
33
|
capability: params.capability,
|
|
34
|
-
env: params.env
|
|
34
|
+
env: params.env,
|
|
35
|
+
...params.feature ? { feature: params.feature } : {}
|
|
35
36
|
});
|
|
36
37
|
if (resolved.d1_databases) resolved.d1_databases = resolved.d1_databases.map((entry) => ({
|
|
37
38
|
...entry,
|
|
@@ -91,7 +92,7 @@ function resolveWorkflowHost(template, params) {
|
|
|
91
92
|
* `className` cannot be hosted: there would be no class for wrangler to instantiate.
|
|
92
93
|
*/
|
|
93
94
|
function hostWorkflowsFor(registry, parts) {
|
|
94
|
-
const { project, capability, env } = parts;
|
|
95
|
+
const { project, capability, env, feature } = parts;
|
|
95
96
|
const owned = Object.values(registry).filter((entry) => entry.capability === capability);
|
|
96
97
|
const workflows = [];
|
|
97
98
|
const crons = [];
|
|
@@ -106,7 +107,8 @@ function hostWorkflowsFor(registry, parts) {
|
|
|
106
107
|
project,
|
|
107
108
|
capability,
|
|
108
109
|
job: entry.job,
|
|
109
|
-
env
|
|
110
|
+
env,
|
|
111
|
+
...feature ? { feature } : {}
|
|
110
112
|
}),
|
|
111
113
|
class_name: entry.spec.className
|
|
112
114
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
+
import { type FeatureIdentity } from "../naming/feature";
|
|
3
4
|
/**
|
|
4
5
|
* Every name a durable job answers to, in one module. The CLI writes these into a host worker's
|
|
5
6
|
* resolved `wrangler.jsonc`, the provisioner deploys under them, and the dispatcher resolves
|
|
@@ -43,6 +44,13 @@ export interface WorkflowHostNameParts {
|
|
|
43
44
|
capability: string;
|
|
44
45
|
/** The deployment environment (e.g. `staging`). */
|
|
45
46
|
env: string;
|
|
47
|
+
/**
|
|
48
|
+
* **The feature this deployment is, when it is one (#643).** A feature's host Worker and Workflows take the
|
|
49
|
+
* feature's head — `<project>-f<issue>-<slug>-…` — not `<project>-feature-…`, which every open branch would
|
|
50
|
+
* share: Worker and Workflow names are account-wide, so a second branch's deploy would replace the first's.
|
|
51
|
+
* `env` stays `feature`, which is what the host's `ENVIRONMENT` var says; only the names change.
|
|
52
|
+
*/
|
|
53
|
+
feature?: FeatureIdentity;
|
|
46
54
|
}
|
|
47
55
|
/** The identity of one durable job's Workflow in one environment. */
|
|
48
56
|
export interface WorkflowScriptNameParts extends WorkflowHostNameParts {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../../src/workflow/naming.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../../src/workflow/naming.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,eAAe,EAA0C,MAAM,mBAAmB,CAAC;AAKjG;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,IAAsC,CAAC;AAE3E;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,IAAoC,CAAC;AAkFvE,oEAAoE;AACpE,MAAM,WAAW,qBAAqB;IACpC,gHAAgH;IAChH,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED,qEAAqE;AACrE,MAAM,WAAW,uBAAwB,SAAQ,qBAAqB;IACpE,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,MAAM,CAMzE;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,CAIrE;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAInE"}
|
package/dist/workflow/naming.js
CHANGED
|
@@ -4,6 +4,7 @@ import { InternalError } from "../error/pithyError.js";
|
|
|
4
4
|
import { NAME_SEGMENT } from "../naming/segment.js";
|
|
5
5
|
import { NAMESPACE_LIMITS } from "../naming/limits.js";
|
|
6
6
|
import { assertValidProjectName, kebab } from "../naming/resource.js";
|
|
7
|
+
import { featureWorkerName, featureWorkflowName } from "../naming/feature.js";
|
|
7
8
|
//#region src/workflow/naming.ts
|
|
8
9
|
/**
|
|
9
10
|
* Every name a durable job answers to, in one module. The CLI writes these into a host worker's
|
|
@@ -96,6 +97,18 @@ function head(project, env) {
|
|
|
96
97
|
return `${kebab(project)}-${env}`;
|
|
97
98
|
}
|
|
98
99
|
/**
|
|
100
|
+
* The feature a name is composed for, held to the project the parts name — two projects in one set of parts is
|
|
101
|
+
* a name composed for somebody else's feature.
|
|
102
|
+
*/
|
|
103
|
+
function featureOf(parts) {
|
|
104
|
+
const feature = parts.feature;
|
|
105
|
+
if (feature.project !== parts.project) throw new InternalError({
|
|
106
|
+
message: "A feature's Workflow name was asked for with two different projects.",
|
|
107
|
+
detail: `parts name project "${parts.project}" and a feature of project "${feature.project}".`
|
|
108
|
+
});
|
|
109
|
+
return feature;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
99
112
|
* The deployed name of one job's Workflow in one environment — `<project>-<env>-<capability>-<job>`,
|
|
100
113
|
* e.g. `acme-staging-media-image-to-text`. This is what the CLI writes as the `name` of a
|
|
101
114
|
* `workflows` entry, and what `CloudflareWorkflowsClient` addresses when triggering from outside
|
|
@@ -109,6 +122,7 @@ function workflowScriptName(parts) {
|
|
|
109
122
|
assertSegment(parts.capability, "capability");
|
|
110
123
|
assertSegment(parts.job, "job");
|
|
111
124
|
assertCapabilityJob(parts.capability, parts.job);
|
|
125
|
+
if (parts.feature) return featureWorkflowName(featureOf(parts), parts.capability, parts.job);
|
|
112
126
|
return assertLength(`${head(parts.project, parts.env)}-${parts.capability}-${parts.job}`, MAX_WORKFLOW_NAME_BYTES);
|
|
113
127
|
}
|
|
114
128
|
/**
|
|
@@ -120,6 +134,7 @@ function workflowScriptName(parts) {
|
|
|
120
134
|
*/
|
|
121
135
|
function workflowHostName(parts) {
|
|
122
136
|
assertSegment(parts.capability, "capability");
|
|
137
|
+
if (parts.feature) return featureWorkerName(featureOf(parts), parts.capability);
|
|
123
138
|
return assertLength(`${head(parts.project, parts.env)}-${parts.capability}`, MAX_WORKER_NAME_BYTES);
|
|
124
139
|
}
|
|
125
140
|
/**
|
package/package.json
CHANGED
|
@@ -483,6 +483,28 @@ export interface Capability<
|
|
|
483
483
|
* question.
|
|
484
484
|
*/
|
|
485
485
|
version?: string | null;
|
|
486
|
+
/**
|
|
487
|
+
* The npm **package** that supplies this capability, or `null` where there is none.
|
|
488
|
+
*
|
|
489
|
+
* The join key {@link Capability.version} is useless without. A version joins against a release feed
|
|
490
|
+
* by package name, and this manifest reported the version alone until #626 — so a client had to guess
|
|
491
|
+
* `@pithy-sh/${name}`, which is right for most capabilities and wrong for the one that matters:
|
|
492
|
+
* `controlplane` ships inside `@pithy-sh/core`, so the guess reaches a package that has never been
|
|
493
|
+
* published and the join comes back empty, which is indistinguishable from "up to date".
|
|
494
|
+
*
|
|
495
|
+
* **A capability name and a package name are different kinds of thing.** One is what a composition
|
|
496
|
+
* calls a concept; the other is how that concept is distributed. A package may ship more than one
|
|
497
|
+
* capability — `core` already ships `controlplane` — so no convention makes the second derivable from
|
|
498
|
+
* the first, and the relationship is a fact only the producing package knows. Which is why **the
|
|
499
|
+
* framework never fills this in from `name`**: deriving it here would re-create the bug inside the fix.
|
|
500
|
+
*
|
|
501
|
+
* Every `@pithy-sh/*` capability sets it from its generated `PACKAGE_NAME` constant
|
|
502
|
+
* (`scripts/stampVersions.ts`), beside the version off the same stamp. **Nullable for the same reason
|
|
503
|
+
* `version` is**, and null in the same breath: the adopter's own `app` capability has a name, no
|
|
504
|
+
* package and no version. Neither alone is a state worth having — a package with no version says
|
|
505
|
+
* nothing joinable, and a version with no package is the defect this field closes.
|
|
506
|
+
*/
|
|
507
|
+
package?: string | null;
|
|
486
508
|
/**
|
|
487
509
|
* Optional startup hook, called once when {@link createBackend} assembles the backend, with every
|
|
488
510
|
* composed capability. Runs after binding and `dependsOn` validation, before middleware and routes
|
|
@@ -7,7 +7,7 @@ import type { BindingSpecInput } from "../capability/bindings";
|
|
|
7
7
|
import { type Capability, defineCapability, type PithyHonoEnv } from "../capability/capability";
|
|
8
8
|
import type { DatabaseSpecMap } from "../data/databases";
|
|
9
9
|
import type { KvNamespaceSpecMap, KvRegistry } from "../kv/namespaces";
|
|
10
|
-
import { PACKAGE_VERSION } from "../version.generated";
|
|
10
|
+
import { PACKAGE_NAME, PACKAGE_VERSION } from "../version.generated";
|
|
11
11
|
import { ENVIRONMENT_VAR } from "../worker/identity";
|
|
12
12
|
import { ControlPlaneConfig, type ControlPlaneConfigInput } from "./config/config";
|
|
13
13
|
import { ControlPlaneConnection } from "./data/connection";
|
|
@@ -138,6 +138,12 @@ export function controlplane(options: ControlPlaneOptions = {}): ControlPlaneCap
|
|
|
138
138
|
// package.json. The seam reports itself in the manifest beside every other composed capability, so a
|
|
139
139
|
// client can see which core an adopter is on without a separate question.
|
|
140
140
|
version: PACKAGE_VERSION,
|
|
141
|
+
// And the package that version belongs to — **the case no naming convention survives** (#626). This
|
|
142
|
+
// capability is called `controlplane` and ships inside `@pithy-sh/core`, so a client deriving
|
|
143
|
+
// `@pithy-sh/${name}` asks a release feed about a package that has never been published and is told
|
|
144
|
+
// nothing, which reads as up to date. Stamped from this package's own `package.json`, like the
|
|
145
|
+
// version above, because only the producing package knows which one it is.
|
|
146
|
+
package: PACKAGE_NAME,
|
|
141
147
|
// No `dependsOn`. The seam must work in a Worker composing neither auth nor audit nor secrets: it
|
|
142
148
|
// holds no secret, mints no session, and emits through a seam that is a no-op when absent.
|
|
143
149
|
requiredBindings,
|
|
@@ -166,6 +172,10 @@ export function controlplane(options: ControlPlaneOptions = {}): ControlPlaneCap
|
|
|
166
172
|
// `?? null`, never undefined: the adopter's own `app` capability has a name and no package
|
|
167
173
|
// version, and that must render as an explicit null rather than vanish from the JSON.
|
|
168
174
|
version: cap.version ?? null,
|
|
175
|
+
// The join key that version is worth nothing without (#626), **read off the capability and never
|
|
176
|
+
// derived from its name here**. `@pithy-sh/${cap.name}` would be right for most of them and
|
|
177
|
+
// wrong for the seam writing this line — which is the whole defect, rebuilt inside its fix.
|
|
178
|
+
package: cap.package ?? null,
|
|
169
179
|
adminRoutes: [...(cap.adminRoutes ?? [])],
|
|
170
180
|
// The vocabulary, always — a capability declaring a number this connection cannot see must be
|
|
171
181
|
// visible as a withheld number rather than as silence.
|
|
@@ -71,7 +71,17 @@ export const CapabilityDeclaration = z
|
|
|
71
71
|
.string()
|
|
72
72
|
.nullable()
|
|
73
73
|
.describe(
|
|
74
|
-
"The npm version of the package supplying this capability, or null where there is none — the adopter's own `app` capability has a name and no package. Reported per capability and never aggregated:
|
|
74
|
+
"The npm version of the package supplying this capability, or null where there is none — the adopter's own `app` capability has a name and no package. Reported per capability and never aggregated: a project composes some capabilities and not others, so only the intersection of what it composes and what changed is worth reporting. Joined against a release feed on the `package` beside it, never on `name` — the two are different kinds of thing.",
|
|
75
|
+
),
|
|
76
|
+
// **Defaulted, like every field added after the first manifest shipped**, and for the reason written
|
|
77
|
+
// out below against `healthKeys`: a Worker deployed before #626 sends no package, and a required
|
|
78
|
+
// field would cost that client the whole manifest rather than the one key it did not get.
|
|
79
|
+
package: z
|
|
80
|
+
.string()
|
|
81
|
+
.nullable()
|
|
82
|
+
.default(null)
|
|
83
|
+
.describe(
|
|
84
|
+
"The npm package supplying this capability — the join key against a release feed — or null where there is none, which is the adopter's own `app` capability and a Worker that predates this field. **Never derivable from `name`:** `controlplane` ships inside `@pithy-sh/core`, so a name-derived `@pithy-sh/<name>` reaches a package that has never been published and the join comes back empty, which reads exactly like up to date. Null together with `version`: a package with no version says nothing joinable, and a version with no package is the reason this field exists.",
|
|
75
85
|
),
|
|
76
86
|
adminRoutes: z
|
|
77
87
|
.array(AdminRoute)
|
|
@@ -103,7 +113,7 @@ export const CapabilityDeclaration = z
|
|
|
103
113
|
),
|
|
104
114
|
})
|
|
105
115
|
.describe(
|
|
106
|
-
"One capability this Worker composes, the version it is at, the admin surface it exposes, the summary it may report, and the configured facts a client must respect to call any of it.",
|
|
116
|
+
"One capability this Worker composes, the package and version it is at, the admin surface it exposes, the summary it may report, and the configured facts a client must respect to call any of it.",
|
|
107
117
|
);
|
|
108
118
|
export type CapabilityDeclaration = z.infer<typeof CapabilityDeclaration>;
|
|
109
119
|
|
|
@@ -163,8 +173,13 @@ export const CapabilityDescriptor = z
|
|
|
163
173
|
// Both fields, from the one value, in the one place. A handler cannot write the numbers and forget
|
|
164
174
|
// the flag, which is what makes `encode(decode(entry))` the entry again — the property the seam's
|
|
165
175
|
// response contract test rests on.
|
|
166
|
-
encode: ({ health, healthKeys, configKeys, config, ...declaration }) => ({
|
|
176
|
+
encode: ({ health, healthKeys, configKeys, config, package: suppliedBy, ...declaration }) => ({
|
|
167
177
|
...declaration,
|
|
178
|
+
// The package, explicitly, for the same reason the two below are explicit: it is defaulted, so it
|
|
179
|
+
// is optional on the way in and required on the way out, and absence and null mean the one thing —
|
|
180
|
+
// this capability comes from no npm package, which is the adopter's own app capability. Renamed on
|
|
181
|
+
// the way past because `package` is a reserved word and cannot be a binding name.
|
|
182
|
+
package: suppliedBy ?? null,
|
|
168
183
|
// Every defaulted field is optional on the way back out, and empty is the meaning absence already
|
|
169
184
|
// had: a capability that declares no summary, and one that states no configured fact.
|
|
170
185
|
//
|
|
@@ -198,6 +213,11 @@ export type CapabilityDescriptor = z.infer<typeof CapabilityDescriptor>;
|
|
|
198
213
|
* now — so a capability that changed its paths or its scopes reports the change directly, and a schema
|
|
199
214
|
* version would be a second source of truth to keep in sync with the first. That reasoning is unchanged.
|
|
200
215
|
*
|
|
216
|
+
* **And `capabilities[].version` travels with the package it belongs to** (#626). A version is only
|
|
217
|
+
* actionable joined against a release feed, a feed is keyed by package name, and a capability name is
|
|
218
|
+
* not one — `controlplane` ships inside `@pithy-sh/core`. Reporting the version alone left every client
|
|
219
|
+
* guessing the key, and the guess failed silently on the most frequently released package there is.
|
|
220
|
+
*
|
|
201
221
|
* What the manifest does carry is **identity**, not schema, and it carries two of them because they
|
|
202
222
|
* answer questions neither can answer alone. `version` is Cloudflare's opaque per-deploy id: it says
|
|
203
223
|
* *exactly which build* is running, which is what forensics needs, what reproduces a report, and what
|
package/src/naming/domains.ts
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import {
|
|
5
|
+
import { type AmbientEnv, ambientEnv } from "../env/ambient";
|
|
6
|
+
import { BASE_URL_VAR } from "../worker/identity";
|
|
7
|
+
import { ENVIRONMENTS, FEATURE_ENVIRONMENT } from "./environment";
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Where a Worker answers, declared once per Worker per environment.
|
|
@@ -39,6 +41,18 @@ import { ENVIRONMENTS } from "./environment";
|
|
|
39
41
|
/** A hostname a Worker answers on. No scheme, no path, no port — wrangler's `routes` pattern is a host. */
|
|
40
42
|
const HOSTNAME_PATTERN = /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))+$/;
|
|
41
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Whether a string is a public hostname: dot-separated DNS labels, at least two, lowercase. No scheme, no
|
|
46
|
+
* port, no trailing dot, and nothing a URL parser decoded into one — `%2e%2e` is `..` once parsed, which is an
|
|
47
|
+
* empty label rather than a host (#643). `localhost` is not one: it has a single label, and it is this machine.
|
|
48
|
+
*
|
|
49
|
+
* The one test for "is this a host" that an origin read from a stamp and an origin typed on a command line
|
|
50
|
+
* both pass, so the two cannot accept different things.
|
|
51
|
+
*/
|
|
52
|
+
export function isPublicHostname(hostname: string): boolean {
|
|
53
|
+
return hostname.length <= 253 && HOSTNAME_PATTERN.test(hostname);
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
/** The environments a domain may be declared for — every managed one, never `dev`. */
|
|
43
57
|
export const DOMAIN_ENVIRONMENTS = ENVIRONMENTS.filter((environment) => environment !== "dev");
|
|
44
58
|
|
|
@@ -135,10 +149,46 @@ export interface ResolvedOrigin {
|
|
|
135
149
|
origin: string;
|
|
136
150
|
/** The hostname alone: a route pattern, and what Turnstile binds a widget to. */
|
|
137
151
|
hostname: string;
|
|
138
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* Whether `domains` declared it. `false` means {@link LOCAL_ORIGIN} was substituted, or — for a feature
|
|
154
|
+
* environment only — that the address is the `workers.dev` one provisioning stamped ({@link featureOrigin}).
|
|
155
|
+
*/
|
|
139
156
|
declared: boolean;
|
|
140
157
|
}
|
|
141
158
|
|
|
159
|
+
/**
|
|
160
|
+
* **A feature environment's origin: the `workers.dev` address provisioning stamped, or null (#643).**
|
|
161
|
+
*
|
|
162
|
+
* A feature Worker answers on `https://<script>.<account subdomain>.workers.dev`. Nothing can declare that —
|
|
163
|
+
* `domains` has no feature key, because a branch's hostname is composed from the branch — and the Worker cannot
|
|
164
|
+
* derive it either, because the subdomain is the account's and only the Cloudflare API knows it. So `pithy
|
|
165
|
+
* provision --feature` derives it, through the CLI's one address resolver, and stamps it as the feature
|
|
166
|
+
* stanza's {@link BASE_URL_VAR}. This is the one reader of that stamp.
|
|
167
|
+
*
|
|
168
|
+
* **Only a `workers.dev` https origin is accepted, and that is the guard rather than a tidiness check.** A
|
|
169
|
+
* feature stanza is generated from the Worker's top level, so a `BASE_URL` somebody set by hand up there
|
|
170
|
+
* arrives in the feature too — and it is another environment's origin, most likely production's. Honoring it
|
|
171
|
+
* would send a branch's magic links into production, which is the one thing {@link resolveOrigin} exists to
|
|
172
|
+
* make impossible. A feature's address is `workers.dev` by construction, so anything else is not one and
|
|
173
|
+
* resolves to nothing. No path, no port, no credentials: an origin, or null.
|
|
174
|
+
*/
|
|
175
|
+
export function featureOrigin(stamped: string | undefined): { origin: string; hostname: string } | null {
|
|
176
|
+
if (!stamped) return null;
|
|
177
|
+
let url: URL;
|
|
178
|
+
try {
|
|
179
|
+
url = new URL(stamped);
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
if (url.protocol !== "https:" || url.port !== "" || url.username !== "" || url.password !== "") return null;
|
|
184
|
+
if (url.pathname !== "/" || url.search !== "" || url.hash !== "") return null;
|
|
185
|
+
// A script name and an account subdomain, at least: `workers.dev` alone, or `<sub>.workers.dev`, is not a Worker.
|
|
186
|
+
if (!url.hostname.endsWith(".workers.dev") || url.hostname.split(".").length < 4) return null;
|
|
187
|
+
// Every label a DNS label: the parser decodes `%2e%2e` into `..`, which ends in `.workers.dev` and is no host.
|
|
188
|
+
if (!isPublicHostname(url.hostname)) return null;
|
|
189
|
+
return { origin: url.origin, hostname: url.hostname };
|
|
190
|
+
}
|
|
191
|
+
|
|
142
192
|
/**
|
|
143
193
|
* **Where is this Worker publicly reachable, in this environment?** — asked once, answered once.
|
|
144
194
|
*
|
|
@@ -164,10 +214,21 @@ export interface ResolvedOrigin {
|
|
|
164
214
|
* checks that stored value, so a per-environment issuer would make a connection minted in staging
|
|
165
215
|
* unverifiable in production. That may be the better isolation, but it is a decision about trust rather
|
|
166
216
|
* than about reachability, and a helper whose job is "where am I reachable" must not sweep it up.
|
|
217
|
+
*
|
|
218
|
+
* **`stamped` is a feature environment's `BASE_URL`, and is read for nothing else** — see {@link featureOrigin}.
|
|
167
219
|
*/
|
|
168
|
-
export function resolveOrigin(
|
|
220
|
+
export function resolveOrigin(
|
|
221
|
+
environment: string | undefined,
|
|
222
|
+
domains: WorkerDomains | undefined,
|
|
223
|
+
stamped?: string | undefined,
|
|
224
|
+
): ResolvedOrigin {
|
|
169
225
|
const domain = domainFor(domains, environment);
|
|
170
226
|
if (domain) return { origin: baseUrlFor(domain), hostname: domain.pattern, declared: true };
|
|
227
|
+
// A feature environment, and only a feature environment, reads the address provisioning stamped. Every other
|
|
228
|
+
// environment ignores `stamped` outright: a declared one without a domain is unpublished, and `dev` resolves
|
|
229
|
+
// its address from the request.
|
|
230
|
+
const feature = environment === FEATURE_ENVIRONMENT ? featureOrigin(stamped) : null;
|
|
231
|
+
if (feature) return { ...feature, declared: false };
|
|
171
232
|
return { origin: LOCAL_ORIGIN, hostname: "localhost", declared: false };
|
|
172
233
|
}
|
|
173
234
|
|
|
@@ -188,7 +249,16 @@ export function resolveOrigin(environment: string | undefined, domains: WorkerDo
|
|
|
188
249
|
* `AUTH_BASE_URL`, which is part of why `email` and `payments` were missed for days: the constant read
|
|
189
250
|
* as auth's private business when it is the Worker's address, and every capability that needs an origin
|
|
190
251
|
* needs this one.
|
|
252
|
+
*
|
|
253
|
+
* **A feature deployment is answered from its own vars (#643).** `env` is the ambient environment — inside a
|
|
254
|
+
* Worker, its own vars — and it is read for one thing: the `BASE_URL` a feature's provisioning stamped. So the
|
|
255
|
+
* same scaffolded line gives auth's base URL, email's links and payments' return URLs a feature's real
|
|
256
|
+
* `workers.dev` origin, rather than `http://localhost`, with nothing added to the adopter's config.
|
|
191
257
|
*/
|
|
192
|
-
export function originFor(
|
|
193
|
-
|
|
258
|
+
export function originFor(
|
|
259
|
+
environment: string | undefined,
|
|
260
|
+
domains: WorkerDomains | undefined,
|
|
261
|
+
env: AmbientEnv = ambientEnv(),
|
|
262
|
+
): string {
|
|
263
|
+
return resolveOrigin(environment, domains, env[BASE_URL_VAR]).origin;
|
|
194
264
|
}
|