primitive-admin 1.0.62 → 1.0.63
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/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/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/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/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/package.json +1 -1
|
@@ -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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app's web counterpart, as a normalized web origin (#2982).
|
|
3
|
+
*
|
|
4
|
+
* One environment value with two jobs on the iOS side: the emailed sign-in
|
|
5
|
+
* link points at `<webUrl>/oauth/callback`, and the same origin is what an
|
|
6
|
+
* incoming universal link is trusted from. Both jobs need an ORIGIN, not just
|
|
7
|
+
* a URL that parses:
|
|
8
|
+
*
|
|
9
|
+
* - https except loopback, the same rule the server applies to every
|
|
10
|
+
* redirect URI (`src/auth/redirect-uri.ts`) — an origin that cannot be
|
|
11
|
+
* allow-listed turns every sign-in request into a 400;
|
|
12
|
+
* - no path, query or fragment: the web client derives its callback from its
|
|
13
|
+
* own origin plus `/oauth/callback` and has no base-path mode, and the
|
|
14
|
+
* `apple-app-site-association` component matches that exact path;
|
|
15
|
+
* - no credentials, which have no business in a link that goes out by email.
|
|
16
|
+
*
|
|
17
|
+
* Returns the `string | true` shape `@inquirer/prompts` expects, like
|
|
18
|
+
* `validateServerUrl`, so the same function backs the flag check, the project
|
|
19
|
+
* config's field validation and any future prompt.
|
|
20
|
+
*
|
|
21
|
+
* This is the surface that EXPLAINS a rejection. The same rule is applied
|
|
22
|
+
* silently — value or nothing — by every reader that feeds `primitive.json`
|
|
23
|
+
* without going through the CLI: `normalizeWebOrigin` in `env-resolver-core.ts`
|
|
24
|
+
* (which cannot import this file; it is copied verbatim into a package that
|
|
25
|
+
* must not depend on the CLI), its bash/python3 port in the Swift template's
|
|
26
|
+
* `resolve-primitive-config.sh`, and `PrimitiveAppState.linksBaseURL` as the
|
|
27
|
+
* app's own floor. `web-url-parity` in the project-config tests pins that the
|
|
28
|
+
* two TypeScript halves accept and normalize exactly the same values.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Validates a web origin. Returns `true` when valid, otherwise the message to
|
|
32
|
+
* show the user.
|
|
33
|
+
*/
|
|
34
|
+
export declare function validateWebUrl(value: string): string | true;
|
|
35
|
+
/**
|
|
36
|
+
* The origin form of an already-validated web URL: lowercased host, explicit
|
|
37
|
+
* port kept, no trailing slash — so `https://app.example.com/` and
|
|
38
|
+
* `https://app.example.com` are stored, compared and joined identically.
|
|
39
|
+
*/
|
|
40
|
+
export declare function normalizeWebUrl(value: string): string;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The app's web counterpart, as a normalized web origin (#2982).
|
|
3
|
+
*
|
|
4
|
+
* One environment value with two jobs on the iOS side: the emailed sign-in
|
|
5
|
+
* link points at `<webUrl>/oauth/callback`, and the same origin is what an
|
|
6
|
+
* incoming universal link is trusted from. Both jobs need an ORIGIN, not just
|
|
7
|
+
* a URL that parses:
|
|
8
|
+
*
|
|
9
|
+
* - https except loopback, the same rule the server applies to every
|
|
10
|
+
* redirect URI (`src/auth/redirect-uri.ts`) — an origin that cannot be
|
|
11
|
+
* allow-listed turns every sign-in request into a 400;
|
|
12
|
+
* - no path, query or fragment: the web client derives its callback from its
|
|
13
|
+
* own origin plus `/oauth/callback` and has no base-path mode, and the
|
|
14
|
+
* `apple-app-site-association` component matches that exact path;
|
|
15
|
+
* - no credentials, which have no business in a link that goes out by email.
|
|
16
|
+
*
|
|
17
|
+
* Returns the `string | true` shape `@inquirer/prompts` expects, like
|
|
18
|
+
* `validateServerUrl`, so the same function backs the flag check, the project
|
|
19
|
+
* config's field validation and any future prompt.
|
|
20
|
+
*
|
|
21
|
+
* This is the surface that EXPLAINS a rejection. The same rule is applied
|
|
22
|
+
* silently — value or nothing — by every reader that feeds `primitive.json`
|
|
23
|
+
* without going through the CLI: `normalizeWebOrigin` in `env-resolver-core.ts`
|
|
24
|
+
* (which cannot import this file; it is copied verbatim into a package that
|
|
25
|
+
* must not depend on the CLI), its bash/python3 port in the Swift template's
|
|
26
|
+
* `resolve-primitive-config.sh`, and `PrimitiveAppState.linksBaseURL` as the
|
|
27
|
+
* app's own floor. `web-url-parity` in the project-config tests pins that the
|
|
28
|
+
* two TypeScript halves accept and normalize exactly the same values.
|
|
29
|
+
*/
|
|
30
|
+
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1"]);
|
|
31
|
+
/** The example every message ends with, so a rejection is actionable. */
|
|
32
|
+
const SHAPE = "e.g. https://app.example.com (or http://localhost:5173 in dev)";
|
|
33
|
+
/**
|
|
34
|
+
* Validates a web origin. Returns `true` when valid, otherwise the message to
|
|
35
|
+
* show the user.
|
|
36
|
+
*/
|
|
37
|
+
export function validateWebUrl(value) {
|
|
38
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
39
|
+
return `Web URL cannot be empty — ${SHAPE}`;
|
|
40
|
+
}
|
|
41
|
+
let url;
|
|
42
|
+
try {
|
|
43
|
+
url = new URL(value);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return `Please enter an absolute URL — ${SHAPE}`;
|
|
47
|
+
}
|
|
48
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
49
|
+
return `Web URL must be http(s) — ${SHAPE}`;
|
|
50
|
+
}
|
|
51
|
+
if (url.protocol === "http:" && !LOOPBACK_HOSTS.has(url.hostname)) {
|
|
52
|
+
return `Web URL must use https outside localhost — the server refuses a non-loopback http redirect URI (${SHAPE})`;
|
|
53
|
+
}
|
|
54
|
+
if (url.username || url.password) {
|
|
55
|
+
return `Web URL must not carry credentials — ${SHAPE}`;
|
|
56
|
+
}
|
|
57
|
+
if (url.pathname !== "/" && url.pathname !== "") {
|
|
58
|
+
return `Web URL must be an origin with no path — the web app serves its sign-in callback on its own origin (${SHAPE})`;
|
|
59
|
+
}
|
|
60
|
+
if (url.search) {
|
|
61
|
+
return `Web URL must not carry a query — ${SHAPE}`;
|
|
62
|
+
}
|
|
63
|
+
if (url.hash) {
|
|
64
|
+
return `Web URL must not carry a fragment — ${SHAPE}`;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The origin form of an already-validated web URL: lowercased host, explicit
|
|
70
|
+
* port kept, no trailing slash — so `https://app.example.com/` and
|
|
71
|
+
* `https://app.example.com` are stored, compared and joined identically.
|
|
72
|
+
*/
|
|
73
|
+
export function normalizeWebUrl(value) {
|
|
74
|
+
return new URL(value).origin;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=web-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-url.js","sourceRoot":"","sources":["../../../src/lib/web-url.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC;AAE3D,yEAAyE;AACzE,MAAM,KAAK,GAAG,gEAAgE,CAAC;AAE/E;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAC/C,OAAO,6BAA6B,KAAK,EAAE,CAAC;IAC9C,CAAC;IACD,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,kCAAkC,KAAK,EAAE,CAAC;IACnD,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC1D,OAAO,6BAA6B,KAAK,EAAE,CAAC;IAC9C,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QAClE,OAAO,mGAAmG,KAAK,GAAG,CAAC;IACrH,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;QACjC,OAAO,wCAAwC,KAAK,EAAE,CAAC;IACzD,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,KAAK,GAAG,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;QAChD,OAAO,uGAAuG,KAAK,GAAG,CAAC;IACzH,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACf,OAAO,oCAAoC,KAAK,EAAE,CAAC;IACrD,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QACb,OAAO,uCAAuC,KAAK,EAAE,CAAC;IACxD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;AAC/B,CAAC"}
|