primitive-admin 1.1.0-alpha.71 → 1.1.0-alpha.72
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/src/commands/apps.js +5 -2
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/database-types.js +13 -313
- package/dist/src/commands/database-types.js.map +1 -1
- package/dist/src/commands/email-templates.js +3 -3
- package/dist/src/commands/email-templates.js.map +1 -1
- package/dist/src/commands/env.js +17 -1
- package/dist/src/commands/env.js.map +1 -1
- package/dist/src/commands/init.d.ts +15 -0
- package/dist/src/commands/init.js +158 -22
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.js +30 -14
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/llm.js +9 -6
- package/dist/src/commands/llm.js.map +1 -1
- package/dist/src/commands/scripts.d.ts +10 -0
- package/dist/src/commands/scripts.js +137 -8
- package/dist/src/commands/scripts.js.map +1 -1
- package/dist/src/commands/settings.d.ts +18 -0
- package/dist/src/commands/settings.js +54 -0
- package/dist/src/commands/settings.js.map +1 -0
- package/dist/src/commands/sync.d.ts +1 -31
- package/dist/src/commands/sync.js +5 -79
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/lib/env-resolver-core.d.ts +40 -0
- package/dist/src/lib/env-resolver-core.js +60 -0
- package/dist/src/lib/env-resolver-core.js.map +1 -1
- package/dist/src/lib/generated-workflow-model-fields.d.ts +20 -0
- package/dist/src/lib/generated-workflow-model-fields.js +53 -0
- package/dist/src/lib/generated-workflow-model-fields.js.map +1 -0
- package/dist/src/lib/google-client-secret-status.d.ts +35 -0
- package/dist/src/lib/google-client-secret-status.js +56 -0
- package/dist/src/lib/google-client-secret-status.js.map +1 -0
- package/dist/src/lib/init-client-platforms.d.ts +14 -0
- package/dist/src/lib/init-client-platforms.js +71 -0
- package/dist/src/lib/init-client-platforms.js.map +1 -0
- package/dist/src/lib/init-email-redirect-uris.d.ts +37 -0
- package/dist/src/lib/init-email-redirect-uris.js +46 -0
- package/dist/src/lib/init-email-redirect-uris.js.map +1 -0
- package/dist/src/lib/init-ios-links.d.ts +40 -0
- package/dist/src/lib/init-ios-links.js +66 -1
- package/dist/src/lib/init-ios-links.js.map +1 -1
- package/dist/src/lib/integration-selector.d.ts +42 -0
- package/dist/src/lib/integration-selector.js +46 -0
- package/dist/src/lib/integration-selector.js.map +1 -0
- package/dist/src/lib/local-test-cases.d.ts +63 -0
- package/dist/src/lib/local-test-cases.js +135 -0
- package/dist/src/lib/local-test-cases.js.map +1 -0
- package/dist/src/lib/project-config.d.ts +8 -0
- package/dist/src/lib/project-config.js +17 -0
- package/dist/src/lib/project-config.js.map +1 -1
- package/dist/src/lib/test-case-file-names.d.ts +40 -0
- package/dist/src/lib/test-case-file-names.js +91 -0
- package/dist/src/lib/test-case-file-names.js.map +1 -0
- package/dist/src/lib/web-url.d.ts +40 -0
- package/dist/src/lib/web-url.js +76 -0
- package/dist/src/lib/web-url.js.map +1 -0
- package/dist/src/lib/workflow-field-descriptor.d.ts +91 -0
- package/dist/src/lib/workflow-field-descriptor.js +153 -0
- package/dist/src/lib/workflow-field-descriptor.js.map +1 -0
- package/package.json +1 -1
|
@@ -109,6 +109,26 @@ export function stampCallbackScheme(clientDir, scheme) {
|
|
|
109
109
|
writeFileSync(plistPath, text.slice(0, dictStart) + rewritten + text.slice(dictEnd));
|
|
110
110
|
return { outcome: "stamped" };
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* How to point an environment at the app's web origin — in the shape that
|
|
114
|
+
* actually works (#2982).
|
|
115
|
+
*
|
|
116
|
+
* `primitive env add` CREATES an environment: it requires `--api-url` and
|
|
117
|
+
* refuses a name the config already has. Every environment anyone reads this
|
|
118
|
+
* about already exists (init wrote `dev`; `prod` was added when the app was
|
|
119
|
+
* deployed), so the instruction for those has to be the file, which is also
|
|
120
|
+
* what the docs describe as the way to edit one. The flag is worth naming only
|
|
121
|
+
* where a new environment is being created.
|
|
122
|
+
*
|
|
123
|
+
* `origin` is the placeholder or real origin to show.
|
|
124
|
+
*/
|
|
125
|
+
export function setWebUrlInstruction(origin, indent = " ") {
|
|
126
|
+
return [
|
|
127
|
+
`${indent}add "webUrl": "${origin}" to that environment in .primitive/config.json`,
|
|
128
|
+
`${indent}(\`primitive env add <name> --api-url … --web-url <origin>\` does this only for an`,
|
|
129
|
+
`${indent}environment it CREATES — it cannot edit one that already exists).`,
|
|
130
|
+
];
|
|
131
|
+
}
|
|
112
132
|
/**
|
|
113
133
|
* What init prints about email sign-in on the iOS path: it already works, and
|
|
114
134
|
* this is the one opt-in that adds the link.
|
|
@@ -128,7 +148,11 @@ export function linkOptInGuidance(scheme, options = {}) {
|
|
|
128
148
|
const redirectUri = iosMagicLinkRedirectUri(scheme);
|
|
129
149
|
return [
|
|
130
150
|
"Email sign-in works now: the email carries a 6-digit code and needs no app settings.",
|
|
131
|
-
|
|
151
|
+
"When this app gets a web client, prefer the https link: set the environment's `webUrl` —",
|
|
152
|
+
...setWebUrlInstruction("https://<your-web-domain>"),
|
|
153
|
+
" The email then carries that origin's /oauth/callback instead — one link that opens this",
|
|
154
|
+
" app where it is installed and signs in through the browser anywhere else.",
|
|
155
|
+
`Until then, to ALSO send a sign-in link that opens this app, add "${redirectUri}" to the existing`,
|
|
132
156
|
" [auth].emailRedirectUris array in config/app.toml (merge it in — run",
|
|
133
157
|
" `primitive config pull --only app` first if that file is not there yet), then run",
|
|
134
158
|
" `primitive config push --only app` and set `authManager.sendsEmailSignInLink = true`.",
|
|
@@ -140,6 +164,47 @@ export function linkOptInGuidance(scheme, options = {}) {
|
|
|
140
164
|
" authentication.md docs page — 'Make the emailed sign-in link open your app'.",
|
|
141
165
|
];
|
|
142
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* What init prints for an app with BOTH clients: the emailed sign-in link is
|
|
169
|
+
* already the web app's https callback, and this is what it takes to make that
|
|
170
|
+
* link open the iOS app in production (#2982).
|
|
171
|
+
*
|
|
172
|
+
* Everything here is a post-deploy step by necessity — the production web
|
|
173
|
+
* domain does not exist at init time, and the deploy script never learns the
|
|
174
|
+
* URL it deployed to. The order is load-bearing: the `applinks:` entitlement
|
|
175
|
+
* is the ONLY thing that makes Apple fetch the association document, and
|
|
176
|
+
* Apple's CDN caches what it gets, so it goes last — after the deployed
|
|
177
|
+
* document has been verified.
|
|
178
|
+
*
|
|
179
|
+
* `webOrigin` is the dev origin the run seeded — set only when the run also
|
|
180
|
+
* confirmed that origin's `/oauth/callback` is on the app's allow-list, since
|
|
181
|
+
* that is what makes the seeded link work.
|
|
182
|
+
*
|
|
183
|
+
* `unverifiedDevCallback` is the opposite case: a combined app whose allow-list
|
|
184
|
+
* this run could not vouch for (an adopted app, or no credentials to ask with).
|
|
185
|
+
* Nothing was seeded — the app keeps its working code-only email — and the two
|
|
186
|
+
* steps that turn the link on are printed instead. Neither is set for an
|
|
187
|
+
* adopting run, which does not own the project config at all.
|
|
188
|
+
*/
|
|
189
|
+
export function universalLinkGuidance(options = {}) {
|
|
190
|
+
const lines = [
|
|
191
|
+
"This app has both an iOS and a web client, so its sign-in email carries ONE https link:",
|
|
192
|
+
" the web app's /oauth/callback. Tapped where the iOS app is installed it opens the app;",
|
|
193
|
+
" anywhere else it is an ordinary web sign-in. The 6-digit code still works everywhere.",
|
|
194
|
+
];
|
|
195
|
+
if (options.webOrigin) {
|
|
196
|
+
lines.push(`In dev that link is ${options.webOrigin}/oauth/callback — seeded as the environment's`, " `webUrl`, and allow-listed on the app. Nothing to do.");
|
|
197
|
+
}
|
|
198
|
+
else if (options.unverifiedDevCallback) {
|
|
199
|
+
const origin = options.unverifiedDevCallback.replace(/\/oauth\/callback$/, "");
|
|
200
|
+
lines.push("The link is NOT on yet in dev: this app already existed, and its sign-in link allow-list", ` does not (or could not be checked to) carry ${options.unverifiedDevCallback}. Nothing was`, " changed — the email stays code-only, which works. To turn the link on:", ` 1. Merge "${options.unverifiedDevCallback}" into the existing [auth].emailRedirectUris`, " array in config/app.toml (run `primitive config pull --only app` first if that file is", " not there yet), then `primitive config push --only app`.", ` 2. Then ${setWebUrlInstruction(origin, "")[0]}`, " — the same edit the production steps below describe.");
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
lines.push("Set `webUrl` on this app's environment to turn it on (this run did not write the", " project config):", ...setWebUrlInstruction("https://<your-web-domain>"), " Allow-list that origin's /oauth/callback in [auth].emailRedirectUris too.");
|
|
204
|
+
}
|
|
205
|
+
lines.push("For production, once the web app is deployed:", " 1. Set that environment's `webUrl` to the deployed origin —", ...setWebUrlInstruction("https://<your-web-domain>", " "), " 2. Merge \"https://<your-web-domain>/oauth/callback\" into the existing", " [auth].emailRedirectUris array in config/app.toml (run `primitive config pull --only app`", " first if that file is not there yet), then `primitive config push --only app`.", " 3. In the web client, fill your Apple team and bundle ids into", " public/.well-known/apple-app-site-association.example and rename it (drop .example).", " 4. Deploy, then verify: `curl -i https://<your-web-domain>/.well-known/apple-app-site-association`", " must return 200, application/json and the JSON body — HTML means the SPA fallback answered.", " 5. Only then enable the `applinks:<your-web-domain>` entitlement in the iOS client's", " project.yml (it needs a real DEVELOPMENT_TEAM) and reinstall the app.", " Full checklist: the web client's public/.well-known/README.md, or the authentication", " docs page — 'Make the emailed sign-in link open your app'.");
|
|
206
|
+
return lines;
|
|
207
|
+
}
|
|
143
208
|
/** What init says when the template predates the `PrimitiveAuth` URL type. */
|
|
144
209
|
export function missingUrlTypeGuidance(scheme) {
|
|
145
210
|
return [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init-ios-links.js","sourceRoot":"","sources":["../../../src/lib/init-ios-links.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"init-ios-links.js","sourceRoot":"","sources":["../../../src/lib/init-ios-links.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B;;;;GAIG;AACH,MAAM,CAAC,MAAM,4BAA4B,GAAG,eAAe,CAAC;AAE5D,wEAAwE;AACxE,MAAM,CAAC,MAAM,wBAAwB,GAAG,cAAc,CAAC;AAEvD,kEAAkE;AAClE,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAE7B,yEAAyE;AACzE,MAAM,oBAAoB,GAAG,gBAAgB,CAAC;AAE9C;;;GAGG;AACH,SAAS,cAAc,CAAC,KAAyB,EAAE,SAAiB;IAClE,MAAM,SAAS,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IACxE,sEAAsE;IACtE,MAAM,WAAW,GAAG,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IACrD,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAe,EACf,KAAa,EACb,aAAsB;IAEtB,MAAM,KAAK,GAAG,OAAO,KAAK,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAC9D,MAAM,MAAM,GACV,cAAc,CAAC,KAAK,EAAE,iBAAiB,CAAC;QACxC,cAAc,CAAC,aAAa,EAAE,iBAAiB,CAAC;QAChD,KAAK,CAAC;IACR,+DAA+D;IAC/D,MAAM,MAAM,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACvD,2EAA2E;IAC3E,8CAA8C;IAC9C,OAAO,MAAM,KAAK,wBAAwB,CAAC,CAAC,CAAC,GAAG,MAAM,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;AACxE,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,uBAAuB,CAAC,MAAc;IACpD,OAAO,GAAG,MAAM,oBAAoB,CAAC;AACvC,CAAC;AAOD;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAAiB,EAAE,MAAc;IACnE,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,CAAC;IACxD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IAE5D,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IAClC,CAAC;IAED,MAAM,MAAM,GAAG,WAAW,4BAA4B,WAAW,CAAC;IAClE,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,WAAW,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IAExD,4EAA4E;IAC5E,6EAA6E;IAC7E,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IACrD,IAAI,SAAS,KAAK,CAAC,CAAC,IAAI,OAAO,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IACxE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC7C,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,4BAA4B,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IAEnF,MAAM,cAAc,GAClB,0EAA0E,CAAC;IAC7E,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IAC5C,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,MAAM;QAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IAEvD,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,cAAc,EAAE,KAAK,MAAM,IAAI,CAAC,CAAC;IACjE,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IACrF,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AAChC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAc,EAAE,MAAM,GAAG,IAAI;IAChE,OAAO;QACL,GAAG,MAAM,kBAAkB,MAAM,iDAAiD;QAClF,GAAG,MAAM,oFAAoF;QAC7F,GAAG,MAAM,mEAAmE;KAC7E,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAc,EACd,UAAoC,EAAE;IAEtC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,KAAK,KAAK,CAAC;IAChD,MAAM,WAAW,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;IACpD,OAAO;QACL,sFAAsF;QACtF,0FAA0F;QAC1F,GAAG,oBAAoB,CAAC,2BAA2B,CAAC;QACpD,2FAA2F;QAC3F,6EAA6E;QAC7E,qEAAqE,WAAW,mBAAmB;QACnG,wEAAwE;QACxE,qFAAqF;QACrF,yFAAyF;QACzF,UAAU;YACR,CAAC,CAAC,0EAA0E;YAC5E,CAAC,CAAC,eAAe,MAAM,iEAAiE;kBACpF,QAAQ,4BAA4B,gDAAgD;QAC1F,kFAAkF;QAClF,gFAAgF;KACjF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,qBAAqB,CACnC,UAAkE,EAAE;IAEpE,MAAM,KAAK,GAAG;QACZ,yFAAyF;QACzF,0FAA0F;QAC1F,yFAAyF;KAC1F,CAAC;IACF,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CACR,uBAAuB,OAAO,CAAC,SAAS,+CAA+C,EACvF,yDAAyD,CAC1D,CAAC;IACJ,CAAC;SAAM,IAAI,OAAO,CAAC,qBAAqB,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC;QAC/E,KAAK,CAAC,IAAI,CACR,0FAA0F,EAC1F,iDAAiD,OAAO,CAAC,qBAAqB,eAAe,EAC7F,0EAA0E,EAC1E,eAAe,OAAO,CAAC,qBAAqB,8CAA8C,EAC1F,6FAA6F,EAC7F,+DAA+D,EAC/D,aAAa,oBAAoB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAClD,2DAA2D,CAC5D,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CACR,kFAAkF,EAClF,oBAAoB,EACpB,GAAG,oBAAoB,CAAC,2BAA2B,CAAC,EACpD,6EAA6E,CAC9E,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,IAAI,CACR,+CAA+C,EAC/C,+DAA+D,EAC/D,GAAG,oBAAoB,CAAC,2BAA2B,EAAE,OAAO,CAAC,EAC7D,2EAA2E,EAC3E,gGAAgG,EAChG,qFAAqF,EACrF,kEAAkE,EAClE,2FAA2F,EAC3F,sGAAsG,EACtG,kGAAkG,EAClG,wFAAwF,EACxF,4EAA4E,EAC5E,wFAAwF,EACxF,8DAA8D,CAC/D,CAAC;IACF,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,sBAAsB,CAAC,MAAc;IACnD,OAAO;QACL,mDAAmD,4BAA4B,mBAAmB;QAClG,aAAa,MAAM,qEAAqE;QACxF,wCAAwC,4BAA4B,gCAAgC;QACpG,mFAAmF;QACnF,gEAAgE;KACjE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the identifier the `integrations tests` verbs take (issue #3000).
|
|
3
|
+
*
|
|
4
|
+
* The block-testing endpoints key on the composite `integration#<blockId>` and
|
|
5
|
+
* never check that the block exists, so an identifier that names no
|
|
6
|
+
* integration came back as an empty page rather than a 404: `tests list`
|
|
7
|
+
* printed "No test cases found." and `tests run-all` printed
|
|
8
|
+
* `Total Tests: 0 / Failed: 0` and exited 0. That is success-shaped in CI and
|
|
9
|
+
* indistinguishable from an integration that genuinely has no cases, so a
|
|
10
|
+
* typo, an integration KEY, or an ID from another app all read as "nothing to
|
|
11
|
+
* run".
|
|
12
|
+
*
|
|
13
|
+
* Resolving here — against the app's own integration list — is what makes
|
|
14
|
+
* `Total Tests: 0` mean "this integration exists and has no cases". The list
|
|
15
|
+
* is the one call that can answer both halves: it maps a key to its ID (keys
|
|
16
|
+
* are stored lowercased, so the match is case-insensitive) and it proves the
|
|
17
|
+
* identifier belongs to THIS app. Archived integrations are included, because
|
|
18
|
+
* their cases stay inspectable.
|
|
19
|
+
*/
|
|
20
|
+
export interface ResolvedIntegration {
|
|
21
|
+
integrationId: string;
|
|
22
|
+
/** The integration's key, when the summary carries one. */
|
|
23
|
+
integrationKey?: string;
|
|
24
|
+
}
|
|
25
|
+
/** The one method this resolver needs from `ApiClient`. */
|
|
26
|
+
export interface IntegrationLister {
|
|
27
|
+
listIntegrations(appId: string, params?: {
|
|
28
|
+
status?: string;
|
|
29
|
+
cursor?: string;
|
|
30
|
+
limit?: number;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
items: any[];
|
|
33
|
+
nextCursor?: string | null;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Map an integration ID or key to its integration ID.
|
|
38
|
+
*
|
|
39
|
+
* Throws — naming the identifier — when nothing in the app matches, so the
|
|
40
|
+
* caller's existing catch prints the message and exits non-zero.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveIntegrationSelector(client: IntegrationLister, appId: string, selector: string): Promise<ResolvedIntegration>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the identifier the `integrations tests` verbs take (issue #3000).
|
|
3
|
+
*
|
|
4
|
+
* The block-testing endpoints key on the composite `integration#<blockId>` and
|
|
5
|
+
* never check that the block exists, so an identifier that names no
|
|
6
|
+
* integration came back as an empty page rather than a 404: `tests list`
|
|
7
|
+
* printed "No test cases found." and `tests run-all` printed
|
|
8
|
+
* `Total Tests: 0 / Failed: 0` and exited 0. That is success-shaped in CI and
|
|
9
|
+
* indistinguishable from an integration that genuinely has no cases, so a
|
|
10
|
+
* typo, an integration KEY, or an ID from another app all read as "nothing to
|
|
11
|
+
* run".
|
|
12
|
+
*
|
|
13
|
+
* Resolving here — against the app's own integration list — is what makes
|
|
14
|
+
* `Total Tests: 0` mean "this integration exists and has no cases". The list
|
|
15
|
+
* is the one call that can answer both halves: it maps a key to its ID (keys
|
|
16
|
+
* are stored lowercased, so the match is case-insensitive) and it proves the
|
|
17
|
+
* identifier belongs to THIS app. Archived integrations are included, because
|
|
18
|
+
* their cases stay inspectable.
|
|
19
|
+
*/
|
|
20
|
+
import { paginateAll } from "./paginate.js";
|
|
21
|
+
/**
|
|
22
|
+
* Map an integration ID or key to its integration ID.
|
|
23
|
+
*
|
|
24
|
+
* Throws — naming the identifier — when nothing in the app matches, so the
|
|
25
|
+
* caller's existing catch prints the message and exits non-zero.
|
|
26
|
+
*/
|
|
27
|
+
export async function resolveIntegrationSelector(client, appId, selector) {
|
|
28
|
+
const identifier = (selector ?? "").trim();
|
|
29
|
+
if (!identifier) {
|
|
30
|
+
throw new Error("An integration ID or key is required.");
|
|
31
|
+
}
|
|
32
|
+
const items = await paginateAll((cursor) => client.listIntegrations(appId, cursor ? { cursor } : {}));
|
|
33
|
+
const lowered = identifier.toLowerCase();
|
|
34
|
+
const match = items.find((item) => item?.integrationId === identifier) ??
|
|
35
|
+
items.find((item) => typeof item?.integrationKey === "string" &&
|
|
36
|
+
item.integrationKey.toLowerCase() === lowered);
|
|
37
|
+
if (!match) {
|
|
38
|
+
throw new Error(`No integration '${identifier}' in app ${appId}. ` +
|
|
39
|
+
`Pass an integration key or ID from 'primitive integrations list'.`);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
integrationId: match.integrationId ?? identifier,
|
|
43
|
+
...(match.integrationKey ? { integrationKey: match.integrationKey } : {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=integration-selector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration-selector.js","sourceRoot":"","sources":["../../../src/lib/integration-selector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAgB5C;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,MAAyB,EACzB,KAAa,EACb,QAAgB;IAEhB,MAAM,UAAU,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3C,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,WAAW,CAAM,CAAC,MAAM,EAAE,EAAE,CAC9C,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACzD,CAAC;IAEF,MAAM,OAAO,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;IACzC,MAAM,KAAK,GACT,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,UAAU,CAAC;QACxD,KAAK,CAAC,IAAI,CACR,CAAC,IAAI,EAAE,EAAE,CACP,OAAO,IAAI,EAAE,cAAc,KAAK,QAAQ;YACxC,IAAI,CAAC,cAAc,CAAC,WAAW,EAAE,KAAK,OAAO,CAChD,CAAC;IAEJ,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,mBAAmB,UAAU,YAAY,KAAK,IAAI;YAChD,mEAAmE,CACtE,CAAC;IACJ,CAAC;IAED,OAAO;QACL,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,UAAU;QAChD,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1E,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The case files on disk that the server has never been told about (#3061).
|
|
3
|
+
*
|
|
4
|
+
* A `<key>.tests/*.toml` sidecar is authored locally and becomes runnable only
|
|
5
|
+
* when `config push` registers it. The test verbs run the REGISTERED set, so a
|
|
6
|
+
* file nobody pushed contributes nothing to `Total Tests` — and until this
|
|
7
|
+
* module existed, nothing to the output either. `Total Tests: 3 / Failed: 0`
|
|
8
|
+
* beside four case files is a pass standing in for work that did not happen,
|
|
9
|
+
* and is indistinguishable from a case that genuinely ran.
|
|
10
|
+
*
|
|
11
|
+
* Naming those files is only useful if the naming is never wrong, so pairing
|
|
12
|
+
* here mirrors what push pairs with:
|
|
13
|
+
* 1. the sync manifest, which maps a file to the live case it manages even
|
|
14
|
+
* when the file has since been renamed;
|
|
15
|
+
* 2. the file name pull would give the case — its committed key, or the slug
|
|
16
|
+
* of an unkeyed case's name (web-admin, an older CLI or the raw API),
|
|
17
|
+
* collision suffixes included — from the one shared naming rule in
|
|
18
|
+
* `test-case-file-names.ts`.
|
|
19
|
+
* Both passes CLAIM: one live case accounts for one file, the way pull assigns
|
|
20
|
+
* one file per case and push's adoption pass pairs. Matching a name loosely
|
|
21
|
+
* instead would let a single registered "foo" excuse both `foo.toml` and a
|
|
22
|
+
* newly authored `foo-2.toml`, which is the silence this issue is about.
|
|
23
|
+
* Anything left over is a file no live case accounts for. The bias is toward
|
|
24
|
+
* silence: a pairing this module cannot make is a warning it does not print.
|
|
25
|
+
*
|
|
26
|
+
* Reads only — the scan never registers anything. `config push` stays the one
|
|
27
|
+
* verb that mutates the server, which is what the warning tells the user.
|
|
28
|
+
*/
|
|
29
|
+
/** What one suite's sidecar directory holds, against what is registered. */
|
|
30
|
+
export interface LocalTestCaseScan {
|
|
31
|
+
/** Every `*.toml` case file in the directory, basename order. */
|
|
32
|
+
localFiles: string[];
|
|
33
|
+
/** The subset no registered case accounts for, basename order. */
|
|
34
|
+
unregistered: string[];
|
|
35
|
+
}
|
|
36
|
+
/** The `.primitive-sync.json` rows that map a sidecar to a live case. */
|
|
37
|
+
export type ManagedTestCases = Record<string, {
|
|
38
|
+
id?: string;
|
|
39
|
+
} | undefined>;
|
|
40
|
+
/** Read the manifest's test-case rows, or nothing when it cannot be read. */
|
|
41
|
+
export declare function readManagedTestCases(configDir: string): ManagedTestCases;
|
|
42
|
+
/**
|
|
43
|
+
* The `*.toml` case files in a suite's sidecar directory, if it has one.
|
|
44
|
+
*
|
|
45
|
+
* An empty array means the directory is absent or holds no case file. A
|
|
46
|
+
* directory that cannot be READ throws instead of reporting nothing: this whole
|
|
47
|
+
* issue is about a silence indistinguishable from having no work, and callers
|
|
48
|
+
* turn the throw into a diagnostic that names the directory.
|
|
49
|
+
*/
|
|
50
|
+
export declare function listLocalTestCaseFiles(testsDir: string): string[];
|
|
51
|
+
/**
|
|
52
|
+
* Which of a suite's local case files no registered case accounts for.
|
|
53
|
+
*
|
|
54
|
+
* `managedPrefix` is the manifest's namespace for this block —
|
|
55
|
+
* `<blockType>/<blockKey>` — so a row that maps `foo.toml` to a live case is
|
|
56
|
+
* read only for the block that owns it.
|
|
57
|
+
*/
|
|
58
|
+
export declare function scanLocalTestCases(params: {
|
|
59
|
+
testsDir: string;
|
|
60
|
+
registered: any[];
|
|
61
|
+
managed?: ManagedTestCases;
|
|
62
|
+
managedPrefix?: string;
|
|
63
|
+
}): LocalTestCaseScan;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The case files on disk that the server has never been told about (#3061).
|
|
3
|
+
*
|
|
4
|
+
* A `<key>.tests/*.toml` sidecar is authored locally and becomes runnable only
|
|
5
|
+
* when `config push` registers it. The test verbs run the REGISTERED set, so a
|
|
6
|
+
* file nobody pushed contributes nothing to `Total Tests` — and until this
|
|
7
|
+
* module existed, nothing to the output either. `Total Tests: 3 / Failed: 0`
|
|
8
|
+
* beside four case files is a pass standing in for work that did not happen,
|
|
9
|
+
* and is indistinguishable from a case that genuinely ran.
|
|
10
|
+
*
|
|
11
|
+
* Naming those files is only useful if the naming is never wrong, so pairing
|
|
12
|
+
* here mirrors what push pairs with:
|
|
13
|
+
* 1. the sync manifest, which maps a file to the live case it manages even
|
|
14
|
+
* when the file has since been renamed;
|
|
15
|
+
* 2. the file name pull would give the case — its committed key, or the slug
|
|
16
|
+
* of an unkeyed case's name (web-admin, an older CLI or the raw API),
|
|
17
|
+
* collision suffixes included — from the one shared naming rule in
|
|
18
|
+
* `test-case-file-names.ts`.
|
|
19
|
+
* Both passes CLAIM: one live case accounts for one file, the way pull assigns
|
|
20
|
+
* one file per case and push's adoption pass pairs. Matching a name loosely
|
|
21
|
+
* instead would let a single registered "foo" excuse both `foo.toml` and a
|
|
22
|
+
* newly authored `foo-2.toml`, which is the silence this issue is about.
|
|
23
|
+
* Anything left over is a file no live case accounts for. The bias is toward
|
|
24
|
+
* silence: a pairing this module cannot make is a warning it does not print.
|
|
25
|
+
*
|
|
26
|
+
* Reads only — the scan never registers anything. `config push` stays the one
|
|
27
|
+
* verb that mutates the server, which is what the warning tells the user.
|
|
28
|
+
*/
|
|
29
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
30
|
+
import { basename, join } from "path";
|
|
31
|
+
import { testCaseFileBasenames } from "./test-case-file-names.js";
|
|
32
|
+
import { normalizeTestCaseKey } from "./test-case-keys.js";
|
|
33
|
+
/** Read the manifest's test-case rows, or nothing when it cannot be read. */
|
|
34
|
+
export function readManagedTestCases(configDir) {
|
|
35
|
+
const syncFile = join(configDir, ".primitive-sync.json");
|
|
36
|
+
if (!existsSync(syncFile))
|
|
37
|
+
return {};
|
|
38
|
+
try {
|
|
39
|
+
const state = JSON.parse(readFileSync(syncFile, "utf-8"));
|
|
40
|
+
const managed = state?.entities?.testCases;
|
|
41
|
+
return managed && typeof managed === "object" ? managed : {};
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// A manifest we cannot parse is a manifest we cannot pair with; the key and
|
|
45
|
+
// name passes still speak, and an unpairable file is left unnamed.
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The `*.toml` case files in a suite's sidecar directory, if it has one.
|
|
51
|
+
*
|
|
52
|
+
* An empty array means the directory is absent or holds no case file. A
|
|
53
|
+
* directory that cannot be READ throws instead of reporting nothing: this whole
|
|
54
|
+
* issue is about a silence indistinguishable from having no work, and callers
|
|
55
|
+
* turn the throw into a diagnostic that names the directory.
|
|
56
|
+
*/
|
|
57
|
+
export function listLocalTestCaseFiles(testsDir) {
|
|
58
|
+
if (!existsSync(testsDir))
|
|
59
|
+
return [];
|
|
60
|
+
if (!statSync(testsDir).isDirectory())
|
|
61
|
+
return [];
|
|
62
|
+
return readdirSync(testsDir)
|
|
63
|
+
.filter((entry) => entry.endsWith(".toml"))
|
|
64
|
+
.filter((entry) => {
|
|
65
|
+
// Attachment folders live beside the cases (`<case>/`); only files are
|
|
66
|
+
// cases, and a directory named `x.toml` is neither.
|
|
67
|
+
try {
|
|
68
|
+
return statSync(join(testsDir, entry)).isFile();
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
// Gone between the listing and the stat is gone — anything else is a
|
|
72
|
+
// directory we cannot read, which the caller has to hear about.
|
|
73
|
+
if (err?.code === "ENOENT")
|
|
74
|
+
return false;
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
.sort();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Which of a suite's local case files no registered case accounts for.
|
|
82
|
+
*
|
|
83
|
+
* `managedPrefix` is the manifest's namespace for this block —
|
|
84
|
+
* `<blockType>/<blockKey>` — so a row that maps `foo.toml` to a live case is
|
|
85
|
+
* read only for the block that owns it.
|
|
86
|
+
*/
|
|
87
|
+
export function scanLocalTestCases(params) {
|
|
88
|
+
const localFiles = listLocalTestCaseFiles(params.testsDir);
|
|
89
|
+
if (localFiles.length === 0)
|
|
90
|
+
return { localFiles, unregistered: [] };
|
|
91
|
+
const registered = (params.registered ?? []).filter((live) => live && typeof live === "object");
|
|
92
|
+
// The file each live case belongs in, allocated exactly as pull allocates it:
|
|
93
|
+
// keys claim theirs first, unkeyed names take what is left, and a collision
|
|
94
|
+
// suffix is assigned rather than accepted from any name that carries one.
|
|
95
|
+
const liveByFileName = new Map();
|
|
96
|
+
for (const [live, name] of testCaseFileBasenames(registered)) {
|
|
97
|
+
liveByFileName.set(normalizeTestCaseKey(name), live);
|
|
98
|
+
}
|
|
99
|
+
const liveById = new Map();
|
|
100
|
+
for (const live of registered) {
|
|
101
|
+
if (typeof live.testCaseId === "string" && live.testCaseId) {
|
|
102
|
+
liveById.set(live.testCaseId, live);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const managedLiveFor = (slug) => {
|
|
106
|
+
if (!params.managedPrefix)
|
|
107
|
+
return undefined;
|
|
108
|
+
const id = params.managed?.[`${params.managedPrefix}/${slug}`]?.id;
|
|
109
|
+
return id ? liveById.get(id) : undefined;
|
|
110
|
+
};
|
|
111
|
+
// A live case accounts for ONE file. Claiming is what keeps a single
|
|
112
|
+
// registered "foo" from excusing `foo.toml` and `foo-2.toml` alike.
|
|
113
|
+
const claimed = new Set();
|
|
114
|
+
const unclaimed = [];
|
|
115
|
+
// The manifest states identity outright, so it is read first: a file renamed
|
|
116
|
+
// since it was pulled keeps its case even where the naming rules would hand
|
|
117
|
+
// that case to the file still sitting under its old name.
|
|
118
|
+
for (const file of localFiles) {
|
|
119
|
+
const live = managedLiveFor(basename(file, ".toml"));
|
|
120
|
+
if (live && !claimed.has(live)) {
|
|
121
|
+
claimed.add(live);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
unclaimed.push(file);
|
|
125
|
+
}
|
|
126
|
+
const unregistered = unclaimed.filter((file) => {
|
|
127
|
+
const live = liveByFileName.get(normalizeTestCaseKey(basename(file, ".toml")));
|
|
128
|
+
if (!live || claimed.has(live))
|
|
129
|
+
return true;
|
|
130
|
+
claimed.add(live);
|
|
131
|
+
return false;
|
|
132
|
+
});
|
|
133
|
+
return { localFiles, unregistered };
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=local-test-cases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-test-cases.js","sourceRoot":"","sources":["../../../src/lib/local-test-cases.ts"],"names":[],"mappings":"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"}
|
|
@@ -34,6 +34,14 @@ export interface ProjectEnvironment {
|
|
|
34
34
|
apiUrl: string;
|
|
35
35
|
appId?: string;
|
|
36
36
|
appName?: string;
|
|
37
|
+
/**
|
|
38
|
+
* The app's web counterpart for this environment, as a normalized origin
|
|
39
|
+
* (`https://app.example.com`) — the deployment whose `/oauth/callback` the
|
|
40
|
+
* emailed sign-in link points at, and the origin an incoming universal link
|
|
41
|
+
* is trusted from (#2982). Per environment, because that deployment has to
|
|
42
|
+
* talk to the same backend and app as this entry.
|
|
43
|
+
*/
|
|
44
|
+
webUrl?: string;
|
|
37
45
|
/** Optional notes about this environment. */
|
|
38
46
|
description?: string;
|
|
39
47
|
}
|
|
@@ -20,6 +20,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
|
20
20
|
import { dirname, join, resolve } from "path";
|
|
21
21
|
import { detectInstall, installCommand, upgradeCommand, PRIMITIVE_CLI_PACKAGE, } from "./package-manager.js";
|
|
22
22
|
import { validateServerUrl } from "./server-url.js";
|
|
23
|
+
import { normalizeWebUrl, validateWebUrl } from "./web-url.js";
|
|
23
24
|
/**
|
|
24
25
|
* The command that upgrades the running CLI. Falls back to the first-time
|
|
25
26
|
* install command when the running copy can't be upgraded by a package
|
|
@@ -147,10 +148,26 @@ export function validateProjectConfig(raw, sourcePath) {
|
|
|
147
148
|
if (envObj.description !== undefined && typeof envObj.description !== "string") {
|
|
148
149
|
throw new ProjectConfigError(`Environment "${name}": "description" must be a string if set.`, sourcePath);
|
|
149
150
|
}
|
|
151
|
+
// An origin, not any URL: the emailed sign-in link is built from it, so a
|
|
152
|
+
// value the server's allow-list would refuse — or one whose callback path
|
|
153
|
+
// neither the web app nor the AASA component names — is a 400 or a dead
|
|
154
|
+
// link later rather than an error here (#2982).
|
|
155
|
+
if (envObj.webUrl !== undefined) {
|
|
156
|
+
if (typeof envObj.webUrl !== "string") {
|
|
157
|
+
throw new ProjectConfigError(`Environment "${name}": "webUrl" must be a string if set.`, sourcePath);
|
|
158
|
+
}
|
|
159
|
+
const problem = validateWebUrl(envObj.webUrl);
|
|
160
|
+
if (problem !== true) {
|
|
161
|
+
throw new ProjectConfigError(`Environment "${name}" has invalid webUrl: ${problem}`, sourcePath);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
150
164
|
environments[name] = {
|
|
151
165
|
apiUrl: envObj.apiUrl.replace(/\/$/, ""),
|
|
152
166
|
appId: envObj.appId,
|
|
153
167
|
appName: envObj.appName,
|
|
168
|
+
webUrl: envObj.webUrl === undefined
|
|
169
|
+
? undefined
|
|
170
|
+
: normalizeWebUrl(envObj.webUrl),
|
|
154
171
|
description: envObj.description,
|
|
155
172
|
};
|
|
156
173
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-config.js","sourceRoot":"","sources":["../../../src/lib/project-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,EACL,aAAa,EACb,cAAc,EACd,cAAc,EACd,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"project-config.js","sourceRoot":"","sources":["../../../src/lib/project-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a test case's sidecar file is called (#2896).
|
|
3
|
+
*
|
|
4
|
+
* `config pull` writes these names, `config push` pairs files back to live
|
|
5
|
+
* cases by them, `config diff` reports them, and `scripts tests` reads them to
|
|
6
|
+
* tell an authored case file apart from one the server already knows (#3061).
|
|
7
|
+
* They live here, in `lib/`, so all four read the SAME rule: a verb that named
|
|
8
|
+
* a case's file differently from pull would either miss a file that never runs
|
|
9
|
+
* or accuse one that does.
|
|
10
|
+
*/
|
|
11
|
+
export declare function slugifyTestCaseName(name: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* The name to use for `slug`, given the names already taken.
|
|
14
|
+
*
|
|
15
|
+
* `occupy` decides what "already taken" means, and `usedSlugs` holds names in
|
|
16
|
+
* that form (#2896): a file name is occupied CASE-INSENSITIVELY on the
|
|
17
|
+
* filesystems most of these trees live on, so `Foo` and `foo` are one file even
|
|
18
|
+
* though they are two strings. The returned name keeps its own casing — only
|
|
19
|
+
* the occupancy check is normalized.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveSlugCollisions(slug: string, usedSlugs: Set<string>, occupy?: (name: string) => string): string;
|
|
22
|
+
/**
|
|
23
|
+
* The file name each live test case belongs in (#2896).
|
|
24
|
+
*
|
|
25
|
+
* A KEYED case is named by its key: that is what the key is — the basename the
|
|
26
|
+
* committed tree carries. An unkeyed one (created by web-admin, an older CLI or
|
|
27
|
+
* the raw API) keeps the slug-of-name naming, with the collision suffixes
|
|
28
|
+
* resolved only AFTER the keys have claimed theirs, so a slug can never take a
|
|
29
|
+
* name a key owns.
|
|
30
|
+
*
|
|
31
|
+
* One function for pull, for pairing's adoption pass and for the diff's
|
|
32
|
+
* remote-only rows, because three commands that name a case's file differently
|
|
33
|
+
* is the disagreement this issue reports.
|
|
34
|
+
*
|
|
35
|
+
* Two names that differ only by CASE are one file on a case-insensitive
|
|
36
|
+
* filesystem, so occupancy is tracked the way the server's key constraint
|
|
37
|
+
* normalizes: a keyed `Foo` and an unkeyed case named "foo" get `Foo.toml` and
|
|
38
|
+
* `foo-2.toml`, rather than one pull silently overwriting the other's sidecar.
|
|
39
|
+
*/
|
|
40
|
+
export declare function testCaseFileBasenames(liveCases: any[], onUnusableKey?: (live: any, reason: string) => void, isSafeName?: (name: string) => boolean): Map<any, string>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a test case's sidecar file is called (#2896).
|
|
3
|
+
*
|
|
4
|
+
* `config pull` writes these names, `config push` pairs files back to live
|
|
5
|
+
* cases by them, `config diff` reports them, and `scripts tests` reads them to
|
|
6
|
+
* tell an authored case file apart from one the server already knows (#3061).
|
|
7
|
+
* They live here, in `lib/`, so all four read the SAME rule: a verb that named
|
|
8
|
+
* a case's file differently from pull would either miss a file that never runs
|
|
9
|
+
* or accuse one that does.
|
|
10
|
+
*/
|
|
11
|
+
import { normalizeTestCaseKey, testCaseKeyError } from "./test-case-keys.js";
|
|
12
|
+
// Test case helpers (exported for unit testing)
|
|
13
|
+
export function slugifyTestCaseName(name) {
|
|
14
|
+
return name
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
17
|
+
.replace(/^-+|-+$/g, "")
|
|
18
|
+
|| "test";
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The name to use for `slug`, given the names already taken.
|
|
22
|
+
*
|
|
23
|
+
* `occupy` decides what "already taken" means, and `usedSlugs` holds names in
|
|
24
|
+
* that form (#2896): a file name is occupied CASE-INSENSITIVELY on the
|
|
25
|
+
* filesystems most of these trees live on, so `Foo` and `foo` are one file even
|
|
26
|
+
* though they are two strings. The returned name keeps its own casing — only
|
|
27
|
+
* the occupancy check is normalized.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveSlugCollisions(slug, usedSlugs, occupy = (name) => name) {
|
|
30
|
+
const taken = (candidate) => usedSlugs.has(occupy(candidate));
|
|
31
|
+
if (!taken(slug)) {
|
|
32
|
+
usedSlugs.add(occupy(slug));
|
|
33
|
+
return slug;
|
|
34
|
+
}
|
|
35
|
+
let counter = 2;
|
|
36
|
+
while (taken(`${slug}-${counter}`)) {
|
|
37
|
+
counter++;
|
|
38
|
+
}
|
|
39
|
+
const resolved = `${slug}-${counter}`;
|
|
40
|
+
usedSlugs.add(occupy(resolved));
|
|
41
|
+
return resolved;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The file name each live test case belongs in (#2896).
|
|
45
|
+
*
|
|
46
|
+
* A KEYED case is named by its key: that is what the key is — the basename the
|
|
47
|
+
* committed tree carries. An unkeyed one (created by web-admin, an older CLI or
|
|
48
|
+
* the raw API) keeps the slug-of-name naming, with the collision suffixes
|
|
49
|
+
* resolved only AFTER the keys have claimed theirs, so a slug can never take a
|
|
50
|
+
* name a key owns.
|
|
51
|
+
*
|
|
52
|
+
* One function for pull, for pairing's adoption pass and for the diff's
|
|
53
|
+
* remote-only rows, because three commands that name a case's file differently
|
|
54
|
+
* is the disagreement this issue reports.
|
|
55
|
+
*
|
|
56
|
+
* Two names that differ only by CASE are one file on a case-insensitive
|
|
57
|
+
* filesystem, so occupancy is tracked the way the server's key constraint
|
|
58
|
+
* normalizes: a keyed `Foo` and an unkeyed case named "foo" get `Foo.toml` and
|
|
59
|
+
* `foo-2.toml`, rather than one pull silently overwriting the other's sidecar.
|
|
60
|
+
*/
|
|
61
|
+
export function testCaseFileBasenames(liveCases, onUnusableKey, isSafeName) {
|
|
62
|
+
const used = new Set();
|
|
63
|
+
const occupy = (name) => normalizeTestCaseKey(name);
|
|
64
|
+
const names = new Map();
|
|
65
|
+
const unkeyed = [];
|
|
66
|
+
for (const live of liveCases) {
|
|
67
|
+
const key = typeof live?.key === "string" ? live.key : "";
|
|
68
|
+
if (!key) {
|
|
69
|
+
unkeyed.push(live);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// The key is SERVER data on its way to becoming a path. A server that
|
|
73
|
+
// stored one this CLI would not have sent must not be able to name a file
|
|
74
|
+
// outside the sidecar directory.
|
|
75
|
+
const reason = testCaseKeyError(key) ??
|
|
76
|
+
(isSafeName && !isSafeName(key)
|
|
77
|
+
? `test case key "${key}" is not a safe file name here`
|
|
78
|
+
: null);
|
|
79
|
+
if (reason) {
|
|
80
|
+
onUnusableKey?.(live, reason);
|
|
81
|
+
unkeyed.push(live);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
names.set(live, resolveSlugCollisions(key, used, occupy));
|
|
85
|
+
}
|
|
86
|
+
for (const live of unkeyed) {
|
|
87
|
+
names.set(live, resolveSlugCollisions(slugifyTestCaseName(live?.name || "unnamed"), used, occupy));
|
|
88
|
+
}
|
|
89
|
+
return names;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=test-case-file-names.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test-case-file-names.js","sourceRoot":"","sources":["../../../src/lib/test-case-file-names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE7E,gDAAgD;AAChD,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,IAAI;SACR,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;WACrB,MAAM,CAAC;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CACnC,IAAY,EACZ,SAAsB,EACtB,SAAmC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI;IAEjD,MAAM,KAAK,GAAG,CAAC,SAAiB,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;IACtE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACjB,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,OAAO,KAAK,CAAC,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC,EAAE,CAAC;QACnC,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,QAAQ,GAAG,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC;IACtC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,qBAAqB,CACnC,SAAgB,EAChB,aAAmD,EACnD,UAAsC;IAEtC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAe,CAAC;IACrC,MAAM,OAAO,GAAU,EAAE,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,OAAO,IAAI,EAAE,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,SAAS;QACX,CAAC;QACD,sEAAsE;QACtE,0EAA0E;QAC1E,iCAAiC;QACjC,MAAM,MAAM,GACV,gBAAgB,CAAC,GAAG,CAAC;YACrB,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;gBAC7B,CAAC,CAAC,kBAAkB,GAAG,gCAAgC;gBACvD,CAAC,CAAC,IAAI,CAAC,CAAC;QACZ,IAAI,MAAM,EAAE,CAAC;YACX,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC9B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,SAAS;QACX,CAAC;QACD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,qBAAqB,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5D,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,KAAK,CAAC,GAAG,CACP,IAAI,EACJ,qBAAqB,CACnB,mBAAmB,CAAC,IAAI,EAAE,IAAI,IAAI,SAAS,CAAC,EAC5C,IAAI,EACJ,MAAM,CACP,CACF,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|