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.
Files changed (61) hide show
  1. package/dist/src/commands/apps.js +5 -2
  2. package/dist/src/commands/apps.js.map +1 -1
  3. package/dist/src/commands/database-types.js +13 -313
  4. package/dist/src/commands/database-types.js.map +1 -1
  5. package/dist/src/commands/email-templates.js +3 -3
  6. package/dist/src/commands/email-templates.js.map +1 -1
  7. package/dist/src/commands/env.js +17 -1
  8. package/dist/src/commands/env.js.map +1 -1
  9. package/dist/src/commands/init.d.ts +15 -0
  10. package/dist/src/commands/init.js +158 -22
  11. package/dist/src/commands/init.js.map +1 -1
  12. package/dist/src/commands/integrations.js +30 -14
  13. package/dist/src/commands/integrations.js.map +1 -1
  14. package/dist/src/commands/llm.js +9 -6
  15. package/dist/src/commands/llm.js.map +1 -1
  16. package/dist/src/commands/scripts.d.ts +10 -0
  17. package/dist/src/commands/scripts.js +137 -8
  18. package/dist/src/commands/scripts.js.map +1 -1
  19. package/dist/src/commands/settings.d.ts +18 -0
  20. package/dist/src/commands/settings.js +54 -0
  21. package/dist/src/commands/settings.js.map +1 -0
  22. package/dist/src/commands/sync.d.ts +1 -31
  23. package/dist/src/commands/sync.js +5 -79
  24. package/dist/src/commands/sync.js.map +1 -1
  25. package/dist/src/lib/env-resolver-core.d.ts +40 -0
  26. package/dist/src/lib/env-resolver-core.js +60 -0
  27. package/dist/src/lib/env-resolver-core.js.map +1 -1
  28. package/dist/src/lib/generated-workflow-model-fields.d.ts +20 -0
  29. package/dist/src/lib/generated-workflow-model-fields.js +53 -0
  30. package/dist/src/lib/generated-workflow-model-fields.js.map +1 -0
  31. package/dist/src/lib/google-client-secret-status.d.ts +35 -0
  32. package/dist/src/lib/google-client-secret-status.js +56 -0
  33. package/dist/src/lib/google-client-secret-status.js.map +1 -0
  34. package/dist/src/lib/init-client-platforms.d.ts +14 -0
  35. package/dist/src/lib/init-client-platforms.js +71 -0
  36. package/dist/src/lib/init-client-platforms.js.map +1 -0
  37. package/dist/src/lib/init-email-redirect-uris.d.ts +37 -0
  38. package/dist/src/lib/init-email-redirect-uris.js +46 -0
  39. package/dist/src/lib/init-email-redirect-uris.js.map +1 -0
  40. package/dist/src/lib/init-ios-links.d.ts +40 -0
  41. package/dist/src/lib/init-ios-links.js +66 -1
  42. package/dist/src/lib/init-ios-links.js.map +1 -1
  43. package/dist/src/lib/integration-selector.d.ts +42 -0
  44. package/dist/src/lib/integration-selector.js +46 -0
  45. package/dist/src/lib/integration-selector.js.map +1 -0
  46. package/dist/src/lib/local-test-cases.d.ts +63 -0
  47. package/dist/src/lib/local-test-cases.js +135 -0
  48. package/dist/src/lib/local-test-cases.js.map +1 -0
  49. package/dist/src/lib/project-config.d.ts +8 -0
  50. package/dist/src/lib/project-config.js +17 -0
  51. package/dist/src/lib/project-config.js.map +1 -1
  52. package/dist/src/lib/test-case-file-names.d.ts +40 -0
  53. package/dist/src/lib/test-case-file-names.js +91 -0
  54. package/dist/src/lib/test-case-file-names.js.map +1 -0
  55. package/dist/src/lib/web-url.d.ts +40 -0
  56. package/dist/src/lib/web-url.js +76 -0
  57. package/dist/src/lib/web-url.js.map +1 -0
  58. package/dist/src/lib/workflow-field-descriptor.d.ts +91 -0
  59. package/dist/src/lib/workflow-field-descriptor.js +153 -0
  60. package/dist/src/lib/workflow-field-descriptor.js.map +1 -0
  61. 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
- `To ALSO send a sign-in link that opens this app, add "${redirectUri}" to the existing`,
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":"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"}
@@ -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":"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"}
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"}