@pithy-sh/core 0.6.1 → 0.7.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/capability/capability.d.ts.map +1 -1
- package/dist/capability/capability.js +2 -0
- package/dist/capability/settings.d.ts +15 -2
- package/dist/capability/settings.d.ts.map +1 -1
- package/dist/data/databases.d.ts +11 -0
- package/dist/data/databases.d.ts.map +1 -1
- package/dist/migrations/registry.d.ts +6 -0
- package/dist/migrations/registry.d.ts.map +1 -1
- package/dist/migrations/registry.js +2 -0
- package/dist/migrations/retained.d.ts +101 -0
- package/dist/migrations/retained.d.ts.map +1 -0
- package/dist/migrations/retained.js +243 -0
- package/dist/migrations/retainedDeclaration.d.ts +15 -0
- package/dist/migrations/retainedDeclaration.d.ts.map +1 -0
- package/dist/migrations/retainedDeclaration.js +49 -0
- package/dist/migrations/runner.d.ts +44 -7
- package/dist/migrations/runner.d.ts.map +1 -1
- package/dist/migrations/runner.js +41 -15
- package/dist/naming/environment.d.ts +12 -0
- package/dist/naming/environment.d.ts.map +1 -1
- package/dist/naming/environment.js +38 -1
- package/dist/naming/feature.d.ts +7 -1
- package/dist/naming/feature.d.ts.map +1 -1
- package/dist/naming/feature.js +10 -3
- package/dist/naming/provisionScope.d.ts +97 -9
- package/dist/naming/provisionScope.d.ts.map +1 -1
- package/dist/naming/provisionScope.js +87 -12
- package/dist/naming/resourceNames.d.ts +5 -2
- package/dist/naming/resourceNames.d.ts.map +1 -1
- package/dist/naming/resourceNames.js +1 -1
- package/dist/text/comments.d.ts +14 -0
- package/dist/text/comments.d.ts.map +1 -1
- package/dist/text/comments.js +28 -2
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/dist/workflow/host.d.ts +8 -0
- package/dist/workflow/host.d.ts.map +1 -1
- package/dist/workflow/host.js +5 -2
- package/dist/workflow/hostEnv.d.ts +1 -1
- package/dist/workflow/hostEnv.js +1 -1
- package/package.json +1 -1
- package/src/capability/capability.ts +6 -0
- package/src/capability/settings.ts +15 -2
- package/src/data/databases.ts +11 -0
- package/src/migrations/registry.ts +10 -0
- package/src/migrations/retained.ts +319 -0
- package/src/migrations/retainedDeclaration.ts +56 -0
- package/src/migrations/runner.ts +89 -15
- package/src/naming/environment.ts +43 -0
- package/src/naming/feature.ts +10 -3
- package/src/naming/provisionScope.ts +130 -13
- package/src/naming/resourceNames.ts +6 -3
- package/src/text/comments.ts +31 -1
- package/src/version.generated.ts +1 -1
- package/src/workflow/host.ts +14 -4
- package/src/workflow/hostEnv.ts +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// SPDX-FileCopyrightText: 2026 Pithy
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
|
-
import { InternalError } from "../error/pithyError.js";
|
|
4
|
-
import { FEATURE_ENVIRONMENT, assertValidEnvironment } from "./environment.js";
|
|
3
|
+
import { InternalError, ValidationError } from "../error/pithyError.js";
|
|
4
|
+
import { FEATURE_ENVIRONMENT, assertValidEnvironment, isFeatureMarker } from "./environment.js";
|
|
5
|
+
import { kebab } from "./resource.js";
|
|
5
6
|
import { featureResourceName, featureSecretEntryName, featureWorkerName } from "./feature.js";
|
|
6
7
|
import { resourceNames } from "./resourceNames.js";
|
|
7
8
|
//#region src/naming/provisionScope.ts
|
|
@@ -32,19 +33,52 @@ function bindingResourceName(project, binding, kind, naming, scoped) {
|
|
|
32
33
|
return scoped(thing);
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
36
|
+
* **Is this name inside a feature's namespace** — `<project>-f<issue>-…`, the project and then a feature's
|
|
37
|
+
* marker, with something after it?
|
|
38
|
+
*
|
|
39
|
+
* The namespace, not one feature's names, because that is what has to stay disjoint: `pithy feature destroy`
|
|
40
|
+
* deletes by exact name, and a feature Worker's name has no kind suffix, so a declared environment's name
|
|
41
|
+
* that lands anywhere in here is one some branch's teardown can delete (#587). Every name {@link featureScope}
|
|
42
|
+
* composes is inside; {@link environmentScope} refuses to name a Worker inside, and `isValidEnvironment`
|
|
43
|
+
* refuses every environment whose composed names could be.
|
|
44
|
+
*
|
|
45
|
+
* **What it does not see.** It is stated for one project. A second project on the same account whose name is
|
|
46
|
+
* this one's plus `-f<issue>` — `acme-f1` beside `acme` — composes names inside `acme`'s namespace from an
|
|
47
|
+
* ordinary environment, and nothing here can tell, because a project name carries no boundary a later hyphen
|
|
48
|
+
* cannot fake. That is the hyphen ambiguity every composed name carries, one segment further left.
|
|
49
|
+
*/
|
|
50
|
+
function isFeatureName(project, name) {
|
|
51
|
+
const head = `${kebab(project)}-`;
|
|
52
|
+
if (!name.startsWith(head)) return false;
|
|
53
|
+
const [marker, ...rest] = name.slice(head.length).split("-");
|
|
54
|
+
return isFeatureMarker(marker ?? "") && rest.length > 0;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
35
57
|
* A declared environment's scope — `staging`, `prod`, or whatever the root `pithy.config.ts` lists.
|
|
36
58
|
*
|
|
37
59
|
* The environment is validated here, once, before a single name exists: it is the middle segment of
|
|
38
60
|
* every name below, so an illegal one must fail before anything is created rather than at the fourth
|
|
39
61
|
* getter that happened to use it.
|
|
40
62
|
*
|
|
41
|
-
* **A
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
63
|
+
* **A declared name wins; the fallback is wrangler's own, `<script>-<env>`.**
|
|
64
|
+
*
|
|
65
|
+
* This reverses the rule that stood here until #580, and the reversal is worth reading rather than
|
|
66
|
+
* assuming. The old rule *recomputed* `<script>-<env>` on every provision and wrote it over whatever the
|
|
67
|
+
* stanza said, on the argument that wrangler has always appended the environment to the top-level `name`,
|
|
68
|
+
* so every project that ever deployed is already at that address.
|
|
69
|
+
*
|
|
70
|
+
* That argument is still true about the **fallback**, and the fallback is unchanged: a stanza that names
|
|
71
|
+
* nothing gets `<script>-<env>` and every existing adopter deploys exactly where they always did. What was
|
|
72
|
+
* wrong was recomputing over a name somebody wrote down. Since #580 the starter template writes one —
|
|
73
|
+
* `<project>-<env>-<worker>`, the shape every other resource here takes — so recompute would have made the
|
|
74
|
+
* template's stamp pointless, and worse: it would have silently renamed the Worker of every project that
|
|
75
|
+
* had taken the new shape, on their next `pithy provision`. A Worker script is a `refuse`-policy namespace
|
|
76
|
+
* precisely because renaming one orphans the deployment, its routes, and every `service` binding pointing
|
|
77
|
+
* at it. Doing that as a side effect of provisioning is the failure the old rule was written to prevent,
|
|
78
|
+
* arriving from the other direction.
|
|
79
|
+
*
|
|
80
|
+
* So the name is read, not composed, when the file already holds one — and written out when it does not,
|
|
81
|
+
* so a service binding and a deploy still agree on one string that is in the file.
|
|
48
82
|
*/
|
|
49
83
|
function environmentScope(project, environment) {
|
|
50
84
|
assertValidEnvironment(environment);
|
|
@@ -54,7 +88,15 @@ function environmentScope(project, environment) {
|
|
|
54
88
|
source: true,
|
|
55
89
|
honorsGlobal: true,
|
|
56
90
|
resource: (binding, kind, naming) => bindingResourceName(project, binding, kind, naming, (thing) => names[KIND_NAMER[kind]](thing)),
|
|
57
|
-
worker: (
|
|
91
|
+
worker: ({ script }, declared) => {
|
|
92
|
+
const name = declared ?? `${script}-${environment}`;
|
|
93
|
+
if (isFeatureName(project, name)) throw new ValidationError({
|
|
94
|
+
message: `The ${environment} Worker "${name}" is named the way a feature's Worker is.`,
|
|
95
|
+
action: `Rename it in env.${environment}.name, or the Worker's top-level name, so it does not follow ${kebab(project)}- with f and a number.`,
|
|
96
|
+
detail: `${declared === void 0 ? "Composed by wrangler from the deploy name" : `Declared in env.${environment}.name`}. A feature's Worker is <project>-f<issue>-<slug>-<app>, and \`pithy feature destroy\` deletes it by that exact name.`
|
|
97
|
+
});
|
|
98
|
+
return name;
|
|
99
|
+
},
|
|
58
100
|
secretEntry: (secret, secretScope) => secretEntryName(project, secret, secretScope, () => names.secretEntry(secret))
|
|
59
101
|
};
|
|
60
102
|
}
|
|
@@ -77,6 +119,12 @@ function environmentScope(project, environment) {
|
|
|
77
119
|
* `resource` is ignored for the same reason it is moot: a feature's `<thing>` segment is the binding plus
|
|
78
120
|
* the kind, and the whole name is recomputed on teardown rather than looked up, so nothing outside the
|
|
79
121
|
* feature reads the string.
|
|
122
|
+
*
|
|
123
|
+
* **And a declared Worker name is ignored here too — the other half of the #580 asymmetry.** A declared
|
|
124
|
+
* environment's script name is the adopter's, written in a tracked file, and provisioning reads it. A
|
|
125
|
+
* feature's is the kit's: it is composed from the branch, written into a generated config nobody commits,
|
|
126
|
+
* and **recomputed on teardown**. Honoring a declared one would leave `pithy feature destroy` computing a
|
|
127
|
+
* name the deployment does not answer to, which is a Worker nothing deletes.
|
|
80
128
|
*/
|
|
81
129
|
function featureScope(identity) {
|
|
82
130
|
return {
|
|
@@ -84,9 +132,36 @@ function featureScope(identity) {
|
|
|
84
132
|
source: false,
|
|
85
133
|
honorsGlobal: false,
|
|
86
134
|
resource: (binding, kind) => featureResourceName(identity, binding, kind),
|
|
87
|
-
worker: (
|
|
135
|
+
worker: ({ app }) => featureWorkerName(identity, app),
|
|
88
136
|
secretEntry: (secret, secretScope) => secretEntryName(identity.project, secret, secretScope, () => featureSecretEntryName(identity, secret))
|
|
89
137
|
};
|
|
90
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* **Every script name one Worker can be deployed under in a feature — what teardown looks for (#592).**
|
|
141
|
+
*
|
|
142
|
+
* The first is the name {@link featureScope} composes today, taken from the scope rather than composed a
|
|
143
|
+
* second time, so provisioning and teardown cannot disagree about it. The second is the one it composed
|
|
144
|
+
* until #587, from the deploy name: `<project>-f<issue>-<slug>-<project>-<app>`. A feature provisioned and
|
|
145
|
+
* deployed before that upgrade is still running under it, and after its next deploy it runs under both —
|
|
146
|
+
* so teardown looks for both. One entry when the two coincide, as they do for a Worker whose deploy name
|
|
147
|
+
* is its directory.
|
|
148
|
+
*
|
|
149
|
+
* **Exact names, never a prefix.** A prefix scan of `<project>-f<issue>-<slug>-` reaches a sibling whose
|
|
150
|
+
* slug extends this one's. The exact names can still meet a sibling in one shape: the doubled name for
|
|
151
|
+
* slug `demo` and deploy name `acme-api` is the current name for slug `demo-acme` and directory `api`, on
|
|
152
|
+
* the same issue number. That is the same hyphen ambiguity every feature name here carries.
|
|
153
|
+
*
|
|
154
|
+
* **They cannot meet a declared environment's Worker**, which has no suffix either and is `<project>-<env>-<app>`
|
|
155
|
+
* since #580: every name either shape yields is inside {@link isFeatureName}'s namespace, no environment may
|
|
156
|
+
* start with a feature's marker, and {@link environmentScope} refuses a declared or fallback Worker name inside
|
|
157
|
+
* it. What that does not reach is a name nothing in the kit chose — a stanza written by hand and deployed
|
|
158
|
+
* without `pithy provision --env` ever reading it, or another project on the account, as {@link isFeatureName}
|
|
159
|
+
* says.
|
|
160
|
+
*/
|
|
161
|
+
function featureWorkerScriptNames(identity, worker) {
|
|
162
|
+
const current = featureScope(identity).worker(worker);
|
|
163
|
+
const beforeSingleProject = featureWorkerName(identity, worker.script);
|
|
164
|
+
return current === beforeSingleProject ? [current] : [current, beforeSingleProject];
|
|
165
|
+
}
|
|
91
166
|
//#endregion
|
|
92
|
-
export { bindingResourceName, environmentScope, featureScope };
|
|
167
|
+
export { bindingResourceName, environmentScope, featureScope, featureWorkerScriptNames, isFeatureName };
|
|
@@ -60,8 +60,11 @@ export interface ScopedNames {
|
|
|
60
60
|
export interface FeatureNames {
|
|
61
61
|
/** A feature's D1, KV, or R2 resource — `<project>-f<issue>-<slug>-<binding>-<kind>`. */
|
|
62
62
|
resource(binding: string, kind: FeatureResourceKind): string;
|
|
63
|
-
/**
|
|
64
|
-
|
|
63
|
+
/**
|
|
64
|
+
* A feature's Worker script — `<project>-f<issue>-<slug>-<app>`, from the `apps/<app>` directory and
|
|
65
|
+
* never the deploy name, which already leads with the project (#587).
|
|
66
|
+
*/
|
|
67
|
+
worker(app: string): string;
|
|
65
68
|
}
|
|
66
69
|
/** Everything a project can be asked to name. Created once, from the project, by {@link resourceNames}. */
|
|
67
70
|
export interface ProjectNames {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resourceNames.d.ts","sourceRoot":"","sources":["../../src/naming/resourceNames.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,mBAAmB,EAA0C,MAAM,WAAW,CAAC;AAInH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,8FAA8F;AAC9F,MAAM,WAAW,WAAW;IAC1B,sGAAsG;IACtG,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,0GAA0G;IAC1G,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAClD,uFAAuF;IACvF,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,kFAAkF;IAClF,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,4FAA4F;IAC5F,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,kGAAkG;IAClG,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,iEAAiE;IACjE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC;AAED,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAAC;IAC7D
|
|
1
|
+
{"version":3,"file":"resourceNames.d.ts","sourceRoot":"","sources":["../../src/naming/resourceNames.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,mBAAmB,EAA0C,MAAM,WAAW,CAAC;AAInH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,8FAA8F;AAC9F,MAAM,WAAW,WAAW;IAC1B,sGAAsG;IACtG,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,0GAA0G;IAC1G,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IAClD,uFAAuF;IACvF,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,kFAAkF;IAClF,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,4FAA4F;IAC5F,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,kGAAkG;IAClG,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACtC,iEAAiE;IACjE,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC,wEAAwE;IACxE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CACnC;AAED,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,MAAM,CAAC;IAC7D;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CAC7B;AAED,2GAA2G;AAC3G,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8FAA8F;IAC9F,GAAG,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,CAAC;IACtC,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,wDAAwD;IACxD,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,YAAY,CAAC;CACnE;AA8DD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,CAmB3D"}
|
package/dist/text/comments.d.ts
CHANGED
|
@@ -61,4 +61,18 @@
|
|
|
61
61
|
* `worker-safety.test.ts` exists to enforce, so the gate now polices the stripper it reads with.
|
|
62
62
|
*/
|
|
63
63
|
export declare function blankComments(source: string): string;
|
|
64
|
+
/**
|
|
65
|
+
* `source` with every comment **and every string's contents** blanked — for a gate asking whether a name is
|
|
66
|
+
* *called*, where {@link blankComments}' own rule points the wrong way.
|
|
67
|
+
*
|
|
68
|
+
* `blankComments` keeps strings because its callers look for a forbidden read, and a string that merely contains
|
|
69
|
+
* one is a false positive, the safe direction. A gate looking for a *required* call inverts that: a validator named
|
|
70
|
+
* only inside an error message — `"requireEnvironment(args.env)"` — is a call that never runs, and counting it
|
|
71
|
+
* passes the file. So this blanks the contents too, delimiters kept, character for character.
|
|
72
|
+
*
|
|
73
|
+
* **A template literal goes whole, `${}` included.** A call written only inside an interpolation is not seen. For
|
|
74
|
+
* a presence check that is the safe direction again — the file is flagged, never passed — and a scan for a read
|
|
75
|
+
* belongs on {@link blankComments}, where the interpolation stays.
|
|
76
|
+
*/
|
|
77
|
+
export declare function blankCommentsAndStrings(source: string): string;
|
|
64
78
|
//# sourceMappingURL=comments.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../../src/text/comments.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../../src/text/comments.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE9D"}
|
package/dist/text/comments.js
CHANGED
|
@@ -62,6 +62,26 @@
|
|
|
62
62
|
* `worker-safety.test.ts` exists to enforce, so the gate now polices the stripper it reads with.
|
|
63
63
|
*/
|
|
64
64
|
function blankComments(source) {
|
|
65
|
+
return blank(source, false);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* `source` with every comment **and every string's contents** blanked — for a gate asking whether a name is
|
|
69
|
+
* *called*, where {@link blankComments}' own rule points the wrong way.
|
|
70
|
+
*
|
|
71
|
+
* `blankComments` keeps strings because its callers look for a forbidden read, and a string that merely contains
|
|
72
|
+
* one is a false positive, the safe direction. A gate looking for a *required* call inverts that: a validator named
|
|
73
|
+
* only inside an error message — `"requireEnvironment(args.env)"` — is a call that never runs, and counting it
|
|
74
|
+
* passes the file. So this blanks the contents too, delimiters kept, character for character.
|
|
75
|
+
*
|
|
76
|
+
* **A template literal goes whole, `${}` included.** A call written only inside an interpolation is not seen. For
|
|
77
|
+
* a presence check that is the safe direction again — the file is flagged, never passed — and a scan for a read
|
|
78
|
+
* belongs on {@link blankComments}, where the interpolation stays.
|
|
79
|
+
*/
|
|
80
|
+
function blankCommentsAndStrings(source) {
|
|
81
|
+
return blank(source, true);
|
|
82
|
+
}
|
|
83
|
+
/** The one walk behind both: comments always blanked, a quoted run kept or blanked inside its delimiters. */
|
|
84
|
+
function blank(source, strings) {
|
|
65
85
|
let out = "";
|
|
66
86
|
let previous = "";
|
|
67
87
|
let index = 0;
|
|
@@ -76,7 +96,8 @@ function blankComments(source) {
|
|
|
76
96
|
}
|
|
77
97
|
if (char === "\"" || char === "'" || char === "`") {
|
|
78
98
|
const stop = endOfQuoted(source, index, char, char !== "`");
|
|
79
|
-
|
|
99
|
+
const run = source.slice(index, stop);
|
|
100
|
+
out += strings ? blankInside(run, char) : run;
|
|
80
101
|
index = stop;
|
|
81
102
|
previous = char;
|
|
82
103
|
continue;
|
|
@@ -94,6 +115,11 @@ function blankComments(source) {
|
|
|
94
115
|
}
|
|
95
116
|
return out;
|
|
96
117
|
}
|
|
118
|
+
/** A quoted run with everything between its delimiters blanked, newlines kept. An unterminated run has no closer. */
|
|
119
|
+
function blankInside(run, quote) {
|
|
120
|
+
const closed = run.length > 1 && run.endsWith(quote);
|
|
121
|
+
return `${quote}${run.slice(1, closed ? -1 : void 0).replace(/[^\n]/g, " ")}${closed ? quote : ""}`;
|
|
122
|
+
}
|
|
97
123
|
/**
|
|
98
124
|
* After one of these, a `/` opens a regex literal; after anything else it divides. The empty string is
|
|
99
125
|
* the start of the file. A wrong guess is bounded — see {@link endOfRegex}.
|
|
@@ -160,4 +186,4 @@ function endOfRegex(source, start) {
|
|
|
160
186
|
return source.length;
|
|
161
187
|
}
|
|
162
188
|
//#endregion
|
|
163
|
-
export { blankComments };
|
|
189
|
+
export { blankComments, blankCommentsAndStrings };
|
|
@@ -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.
|
|
6
|
+
export declare const PACKAGE_VERSION = "0.7.0";
|
|
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
|
+
const PACKAGE_VERSION = "0.7.0";
|
|
8
8
|
//#endregion
|
|
9
9
|
export { PACKAGE_NAME, PACKAGE_VERSION };
|
package/dist/workflow/host.d.ts
CHANGED
|
@@ -127,6 +127,14 @@ export interface WorkflowHostParams {
|
|
|
127
127
|
* not depend on `@pithy-sh/secrets`, which owns that naming.
|
|
128
128
|
*/
|
|
129
129
|
masterKeySecretName?: string;
|
|
130
|
+
/**
|
|
131
|
+
* Secrets Store binding name → the entry it resolves to in this environment, for every store secret the
|
|
132
|
+
* host reads besides the master key. Filled **only** for the bindings listed, like
|
|
133
|
+
* {@link databaseNames}; the names come from the capability, which composes them with the same namer
|
|
134
|
+
* provisioning created the entries with. Core composes none of them itself, for the reason
|
|
135
|
+
* {@link masterKeySecretName} gives.
|
|
136
|
+
*/
|
|
137
|
+
secretNames?: Record<string, string>;
|
|
130
138
|
/** Vars merged over the template's, e.g. the capability's config serialized as one JSON blob. */
|
|
131
139
|
vars?: Record<string, string>;
|
|
132
140
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/workflow/host.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../../src/workflow/host.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,qBAAqB,EAAwC,MAAM,UAAU,CAAC;AAC5F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,uGAAuG;AACvG,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,8GAA8G;AAC9G,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,4GAA4G;AAC5G,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,kEAAkE;AAClE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,kGAAkG;AAClG,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,sGAAsG;AACtG,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,aAAa,EAAE,CAAC;IAC/B,aAAa,CAAC,EAAE,aAAa,EAAE,CAAC;IAChC,UAAU,CAAC,EAAE,aAAa,EAAE,CAAC;IAC7B,SAAS,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACnC,EAAE,CAAC,EAAE,aAAa,CAAC;IACnB,UAAU,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACpC,qBAAqB,CAAC,EAAE,uBAAuB,EAAE,CAAC;IAClD,SAAS,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAClC,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED,yGAAyG;AACzG,MAAM,WAAW,kBAAkB;IACjC;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,UAAU,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,sCAAsC;IACtC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,qCAAqC;IACrC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,2CAA2C;IAC3C,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,iGAAiG;IACjG,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B;;;OAGG;IACH,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC;;;OAGG;IACH,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;CAC5C;AAOD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,MAAM,EAAE,kBAAkB,GAAG,oBAAoB,CA2FpH;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,qBAAqB,GAC3B;IAAE,SAAS,EAAE,mBAAmB,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAsBvD"}
|
package/dist/workflow/host.js
CHANGED
|
@@ -59,11 +59,14 @@ function resolveWorkflowHost(template, params) {
|
|
|
59
59
|
if (resolved.send_email) resolved.send_email = resolved.send_email.map((entry) => withRemote(entry, entry.name, remote));
|
|
60
60
|
if (resolved.secrets_store_secrets && params.secretsStoreId !== void 0) {
|
|
61
61
|
const storeId = params.secretsStoreId;
|
|
62
|
-
const
|
|
62
|
+
const names = {
|
|
63
|
+
...params.masterKeySecretName !== void 0 ? { SECRETS_ENCRYPTION_KEYS: params.masterKeySecretName } : {},
|
|
64
|
+
...params.secretNames
|
|
65
|
+
};
|
|
63
66
|
resolved.secrets_store_secrets = resolved.secrets_store_secrets.map((entry) => ({
|
|
64
67
|
...entry,
|
|
65
68
|
store_id: storeId,
|
|
66
|
-
secret_name:
|
|
69
|
+
secret_name: Object.hasOwn(names, entry.binding) ? names[entry.binding] : entry.secret_name
|
|
67
70
|
}));
|
|
68
71
|
}
|
|
69
72
|
if (resolved.workflows) {
|
|
@@ -40,7 +40,7 @@ export type HostEnvProviderKind = z.infer<typeof HostEnvProviderKind>;
|
|
|
40
40
|
/**
|
|
41
41
|
* What provides one field. The whole point of the declaration: a report that says `BASE_URL is
|
|
42
42
|
* missing` sends a developer looking, and a report that says `Var BASE_URL in the host's
|
|
43
|
-
* wrangler.jsonc. Run pithy email provision
|
|
43
|
+
* wrangler.jsonc. Run pithy email provision.` ends the search.
|
|
44
44
|
*/
|
|
45
45
|
export declare const HostEnvProvider: z.ZodObject<{
|
|
46
46
|
kind: z.ZodEnum<{
|
package/dist/workflow/hostEnv.js
CHANGED
|
@@ -41,7 +41,7 @@ const HostEnvProviderKind = z.enum([
|
|
|
41
41
|
/**
|
|
42
42
|
* What provides one field. The whole point of the declaration: a report that says `BASE_URL is
|
|
43
43
|
* missing` sends a developer looking, and a report that says `Var BASE_URL in the host's
|
|
44
|
-
* wrangler.jsonc. Run pithy email provision
|
|
44
|
+
* wrangler.jsonc. Run pithy email provision.` ends the search.
|
|
45
45
|
*/
|
|
46
46
|
const HostEnvProvider = z.object({
|
|
47
47
|
kind: HostEnvProviderKind.describe("Which kind of thing fills this field."),
|
package/package.json
CHANGED
|
@@ -19,6 +19,7 @@ import type { LocaleContext } from "../i18n/locale";
|
|
|
19
19
|
import type { Translator } from "../i18n/translator";
|
|
20
20
|
import type { KvNamespaceSpecMap } from "../kv/namespaces";
|
|
21
21
|
import type { Logger } from "../logger/logger";
|
|
22
|
+
import { declareRetained } from "../migrations/retainedDeclaration";
|
|
22
23
|
import type { SeedSet } from "../seed/seed";
|
|
23
24
|
import type { WorkflowSpecMap } from "../workflow/spec";
|
|
24
25
|
import { BindingSpec, type BindingSpecInput } from "./bindings";
|
|
@@ -595,6 +596,11 @@ export function defineCapability<
|
|
|
595
596
|
const Name extends string = string,
|
|
596
597
|
const Workflows extends WorkflowSpecMap = Record<never, never>,
|
|
597
598
|
>(input: CapabilityInput<Databases, Namespaces, Name, Workflows>): Capability<Databases, Namespaces, Name, Workflows> {
|
|
599
|
+
// A retained table is declared on the `down`s that could drop it the moment the capability exists, so a
|
|
600
|
+
// migration the runner is handed carries it however its provider was built (#588).
|
|
601
|
+
for (const spec of Object.values(input.databases ?? {})) {
|
|
602
|
+
if (spec.migrations && spec.retained) declareRetained(spec.migrations, spec.retained);
|
|
603
|
+
}
|
|
598
604
|
return {
|
|
599
605
|
...input,
|
|
600
606
|
requiredBindings: input.requiredBindings.map((binding) => BindingSpec.parse(binding)),
|
|
@@ -112,8 +112,21 @@ export interface SettingsAccountReader {
|
|
|
112
112
|
d1Databases(): Promise<readonly string[]>;
|
|
113
113
|
/** Whether this account holds a Cloudflare zone covering the hostname — the prerequisite for onboarding it. */
|
|
114
114
|
zone(hostname: string): Promise<boolean>;
|
|
115
|
-
/**
|
|
116
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Whether that environment's **D1 vault** holds a value under this registry name — asked of its manager
|
|
117
|
+
* Worker, the only thing that can open it.
|
|
118
|
+
*
|
|
119
|
+
* Named for the store it asks, because it used to be `secret()` — "does this secret have a value" — and
|
|
120
|
+
* that question has two stores behind it (#596). A check asking it about a `cf-secrets-store` secret got a
|
|
121
|
+
* confident `false` from a vault the secret never lives in. Ask {@link storeEntry} about one of those.
|
|
122
|
+
*/
|
|
123
|
+
vaultSecret(request: { name: string; environment: string }): Promise<boolean>;
|
|
124
|
+
/**
|
|
125
|
+
* Whether the account's **Secrets Store** holds an entry of exactly this name. The name is the entry's, not
|
|
126
|
+
* the registry's: compose it with `environmentScope(project, env).secretEntry(...)`, the namer provisioning
|
|
127
|
+
* created it with, so the check looks where the value went.
|
|
128
|
+
*/
|
|
129
|
+
storeEntry(name: string): Promise<boolean>;
|
|
117
130
|
}
|
|
118
131
|
|
|
119
132
|
/** What the account tier is told: everything the local tier gets, plus the account it may ask. */
|
package/src/data/databases.ts
CHANGED
|
@@ -29,6 +29,17 @@ export interface DatabaseSpec<Tables extends SchemaMap = SchemaMap> {
|
|
|
29
29
|
migrations?: Record<string, Migration>;
|
|
30
30
|
/** Sort order within this database relative to other capabilities (core low, app high). Required with `migrations`. */
|
|
31
31
|
migrationOrder?: number;
|
|
32
|
+
/**
|
|
33
|
+
* The tables in this slice whose rows **exist nowhere else** — a vault's sealed credentials, a
|
|
34
|
+
* suppression list of people who asked not to be mailed. Keys of `tables`, declared beside the
|
|
35
|
+
* `migrations` that create them (required with them).
|
|
36
|
+
*
|
|
37
|
+
* A schema `down` is right to drop a table; for these it destroys the only copy. So while one of them
|
|
38
|
+
* holds rows, no `down` runs against this database — a rollback, a `seed --redo` reset, a
|
|
39
|
+
* `remove --drop` — unless the operator passes the exact row count (`--destroy-retained <n>`). The
|
|
40
|
+
* refusal lives in the migration runner, so a new command that reverses migrations inherits it (#588).
|
|
41
|
+
*/
|
|
42
|
+
retained?: readonly (keyof Tables & string)[];
|
|
32
43
|
}
|
|
33
44
|
|
|
34
45
|
/** A capability's databases: database name → {@link DatabaseSpec}. */
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import type { Migration, MigrationProvider } from "kysely/migration";
|
|
5
5
|
import { InternalError } from "../error/pithyError";
|
|
6
|
+
import { declareRetained } from "./retainedDeclaration";
|
|
6
7
|
|
|
7
8
|
/** Width of the zero-padded `order` prefix in a composed key — the stable anchor. */
|
|
8
9
|
const ORDER_DIGITS = 4;
|
|
@@ -29,6 +30,12 @@ export interface NamespacedMigrations {
|
|
|
29
30
|
order: number;
|
|
30
31
|
/** Stable, per-namespace keys, each `^\d{4}_[a-z0-9_]+$` (e.g. "0001_init"). */
|
|
31
32
|
migrations: Record<string, Migration>;
|
|
33
|
+
/**
|
|
34
|
+
* The table keys (camelCase, as the capability's `tables` declares them) this set's capability retains
|
|
35
|
+
* in this database — rows that exist nowhere else. Recorded on this set's `down`s, and the runner refuses
|
|
36
|
+
* any `down` against the database while one of them holds rows. See `./retained`.
|
|
37
|
+
*/
|
|
38
|
+
retained?: readonly string[];
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
// These guard build-time registry invariants — a capability author wired its migrations wrong.
|
|
@@ -85,6 +92,9 @@ function composeDatabase(database: string, sets: NamespacedMigrations[]): Migrat
|
|
|
85
92
|
}
|
|
86
93
|
}
|
|
87
94
|
|
|
95
|
+
// Recorded on each migration's own `down`, so the runner enforces it on this provider and on anything
|
|
96
|
+
// later built from these same migrations — no caller has to remember to pass it along (#588).
|
|
97
|
+
for (const set of sets) declareRetained(set.migrations, set.retained ?? []);
|
|
88
98
|
return {
|
|
89
99
|
getMigrations: async (): Promise<Record<string, Migration>> => composed,
|
|
90
100
|
};
|