@reventlessdev/reventless-seed 1.0.0-alpha.21 → 1.0.0-alpha.22
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/CHANGELOG.md +7 -0
- package/lib/bs/.compiler.log +2 -2
- package/lib/bs/compiler-info.json +1 -1
- package/lib/bs/src/Seed-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed_Client-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed_Client-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed_Connect-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed_Connect-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed_Prompt-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed_Prompt-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed_Prompt.ast +0 -0
- package/lib/bs/src/Seed_Prompt.res +1 -0
- package/lib/bs/src/Seed_Prompt.res.mjs +2 -1
- package/lib/bs/src/Seed_Runner-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed_Runner-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed_Upload-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed_Upload-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed_Users-ReventlessSeed.cmi +0 -0
- package/lib/bs/src/Seed_Users-ReventlessSeed.cmj +0 -0
- package/lib/bs/src/Seed_Users-ReventlessSeed.cmt +0 -0
- package/lib/bs/src/Seed_Users.ast +0 -0
- package/lib/bs/src/Seed_Users.res +16 -1
- package/lib/bs/src/Seed_Users.res.mjs +13 -1
- package/lib/bs/tests/Seed_ClientTest-ReventlessSeed.cmi +0 -0
- package/lib/bs/tests/Seed_ClientTest-ReventlessSeed.cmt +0 -0
- package/lib/bs/tests/Seed_ConnectTest-ReventlessSeed.cmi +0 -0
- package/lib/bs/tests/Seed_ConnectTest-ReventlessSeed.cmt +0 -0
- package/lib/bs/tests/Seed_PromptTest-ReventlessSeed.cmi +0 -0
- package/lib/bs/tests/Seed_PromptTest-ReventlessSeed.cmt +0 -0
- package/lib/bs/tests/Seed_UsersTest-ReventlessSeed.cmi +0 -0
- package/lib/bs/tests/Seed_UsersTest-ReventlessSeed.cmt +0 -0
- package/lib/bs/tests/Seed_UsersTest.ast +0 -0
- package/lib/bs/tests/Seed_UsersTest.res +39 -1
- package/lib/bs/tests/Seed_UsersTest.res.mjs +28 -3
- package/lib/ocaml/.compiler.log +2 -2
- package/lib/ocaml/Seed-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Client-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_Client-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_ClientTest-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_ClientTest-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Connect-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_Connect-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_ConnectTest-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_ConnectTest-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Prompt-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_Prompt-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Prompt.ast +0 -0
- package/lib/ocaml/Seed_Prompt.res +1 -0
- package/lib/ocaml/Seed_PromptTest-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_PromptTest-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Runner-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_Runner-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Upload-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_Upload-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Users-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_Users-ReventlessSeed.cmj +0 -0
- package/lib/ocaml/Seed_Users-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_Users.ast +0 -0
- package/lib/ocaml/Seed_Users.res +16 -1
- package/lib/ocaml/Seed_UsersTest-ReventlessSeed.cmi +0 -0
- package/lib/ocaml/Seed_UsersTest-ReventlessSeed.cmt +0 -0
- package/lib/ocaml/Seed_UsersTest.ast +0 -0
- package/lib/ocaml/Seed_UsersTest.res +39 -1
- package/package.json +2 -2
- package/src/Seed_Prompt.res +1 -0
- package/src/Seed_Prompt.res.mjs +2 -1
- package/src/Seed_Users.res +16 -1
- package/src/Seed_Users.res.mjs +13 -1
- package/tests/Seed_UsersTest.res +39 -1
- package/tests/Seed_UsersTest.res.mjs +28 -3
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 1.0.0-alpha.22 (2026-09-16)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **aws:** make the demo accounts work on a pool that signs in by email ([97b036f](https://github.com/ReventlessDev/reventless-core/commit/97b036f86979ba55dbbf93d460fe8076c35abf1a))
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
# 1.0.0-alpha.21 (2026-09-09)
|
|
7
14
|
|
|
8
15
|
### Bug Fixes
|
package/lib/bs/.compiler.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#Start(
|
|
2
|
-
#Done(
|
|
1
|
+
#Start(1789592211625)
|
|
2
|
+
#Done(1789592211686)
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
"bsc_hash": "75e3a59c95cc953608fd3dd6ea6ed68141bba4414a20a52f114261b8a48385fa",
|
|
5
5
|
"rescript_config_hash": "25cfff836d200084949fdfdfec2f6875901bfee1f04d84634eca252ef1ca1ef1",
|
|
6
6
|
"runtime_path": "/home/runner/work/reventless-core/reventless-core/node_modules/@rescript/runtime",
|
|
7
|
-
"generated_at": "
|
|
7
|
+
"generated_at": "1789592211689"
|
|
8
8
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -24,6 +24,7 @@ type user = {
|
|
|
24
24
|
password: string,
|
|
25
25
|
groups: array<string>,
|
|
26
26
|
userId: option<string>,
|
|
27
|
+
demoOwner: option<string>,
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
let asString = (json: JSON.t): option<string> =>
|
|
@@ -44,7 +45,13 @@ let userOf = (json: JSON.t): option<user> =>
|
|
|
44
45
|
| Some(JSON.Array(items)) => items->Array.filterMap(asString)
|
|
45
46
|
| _ => []
|
|
46
47
|
}
|
|
47
|
-
Some({
|
|
48
|
+
Some({
|
|
49
|
+
username,
|
|
50
|
+
password,
|
|
51
|
+
groups,
|
|
52
|
+
userId: obj->Dict.get("userId")->Option.flatMap(asString),
|
|
53
|
+
demoOwner: obj->Dict.get("demoOwner")->Option.flatMap(asString),
|
|
54
|
+
})
|
|
48
55
|
| _ => None
|
|
49
56
|
}
|
|
50
57
|
| _ => None
|
|
@@ -87,6 +94,14 @@ let load = (~path: option<string>=?): option<(string, array<user>)> => {
|
|
|
87
94
|
users->Array.length == 0 ? None : Some((file, users))
|
|
88
95
|
}
|
|
89
96
|
|
|
97
|
+
/** The account that plays `name` in a demo: the one whose `demoOwner` says so,
|
|
98
|
+
else the one named `name` — which is how a file without the field reads. */
|
|
99
|
+
let playing = (users: array<user>, name: string): option<user> =>
|
|
100
|
+
switch users->Array.find(u => u.demoOwner == Some(name)) {
|
|
101
|
+
| Some(_) as declared => declared
|
|
102
|
+
| None => users->Array.find(u => u.username == name)
|
|
103
|
+
}
|
|
104
|
+
|
|
90
105
|
/** Menu label: the username, plus whatever groups the file records for it. */
|
|
91
106
|
let label = (u: user): string =>
|
|
92
107
|
u.groups->Array.length == 0 ? u.username : `${u.username} [${u.groups->Array.join(", ")}]`
|
|
@@ -5,6 +5,7 @@ import * as Nodefs from "node:fs";
|
|
|
5
5
|
import * as Nodepath from "node:path";
|
|
6
6
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
7
7
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
8
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
8
9
|
|
|
9
10
|
function asString(json) {
|
|
10
11
|
if (typeof json === "string") {
|
|
@@ -32,7 +33,8 @@ function userOf(json) {
|
|
|
32
33
|
username: match,
|
|
33
34
|
password: match$1,
|
|
34
35
|
groups: groups,
|
|
35
|
-
userId: Stdlib_Option.flatMap(json["userId"], asString)
|
|
36
|
+
userId: Stdlib_Option.flatMap(json["userId"], asString),
|
|
37
|
+
demoOwner: Stdlib_Option.flatMap(json["demoOwner"], asString)
|
|
36
38
|
};
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -72,6 +74,15 @@ function load(path) {
|
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
function playing(users, name) {
|
|
78
|
+
let declared = users.find(u => Primitive_object.equal(u.demoOwner, name));
|
|
79
|
+
if (declared !== undefined) {
|
|
80
|
+
return declared;
|
|
81
|
+
} else {
|
|
82
|
+
return users.find(u => u.username === name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
75
86
|
function label(u) {
|
|
76
87
|
if (u.groups.length === 0) {
|
|
77
88
|
return u.username;
|
|
@@ -86,6 +97,7 @@ export {
|
|
|
86
97
|
parseString,
|
|
87
98
|
defaultPath,
|
|
88
99
|
load,
|
|
100
|
+
playing,
|
|
89
101
|
label,
|
|
90
102
|
}
|
|
91
103
|
/* yaml Not a pure module */
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -95,6 +95,36 @@ describe("Seed_Users.load:", () => {
|
|
|
95
95
|
})
|
|
96
96
|
})
|
|
97
97
|
|
|
98
|
+
// A pool that signs in on an email address takes `shopper@example.com`, not
|
|
99
|
+
// `shopper`, so a demo cannot find its accounts by username alone there.
|
|
100
|
+
describe("Seed_Users.playing:", () => {
|
|
101
|
+
let users = Seed_Users.parseString(`
|
|
102
|
+
- username: shopper@example.com
|
|
103
|
+
password: pw
|
|
104
|
+
groups: [Shopper]
|
|
105
|
+
demoOwner: shopper
|
|
106
|
+
- username: admin
|
|
107
|
+
password: pw
|
|
108
|
+
groups: [Admin]
|
|
109
|
+
`)
|
|
110
|
+
|
|
111
|
+
testSync("finds the account whose demoOwner names the person", () => {
|
|
112
|
+
expect(
|
|
113
|
+
Seed_Users.playing(users, "shopper")->Option.map(u => u.Seed_Users.username),
|
|
114
|
+
)->Expect.toEqual(Some("shopper@example.com"))
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
testSync("falls back to the username when no entry declares one", () => {
|
|
118
|
+
expect(
|
|
119
|
+
Seed_Users.playing(users, "admin")->Option.map(u => u.Seed_Users.username),
|
|
120
|
+
)->Expect.toEqual(Some("admin"))
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
testSync("finds nobody for a person no entry plays", () => {
|
|
124
|
+
expect(Seed_Users.playing(users, "merch"))->Expect.toEqual(None)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
98
128
|
describe("Seed_Users.label:", () => {
|
|
99
129
|
testSync("names the groups an account carries", () => {
|
|
100
130
|
expect(
|
|
@@ -103,13 +133,20 @@ describe("Seed_Users.label:", () => {
|
|
|
103
133
|
password: "x",
|
|
104
134
|
groups: ["Admin", "Shopper"],
|
|
105
135
|
userId: None,
|
|
136
|
+
demoOwner: None,
|
|
106
137
|
}),
|
|
107
138
|
)->Expect.toBe("admin [Admin, Shopper]")
|
|
108
139
|
})
|
|
109
140
|
|
|
110
141
|
testSync("is the bare username when the file records no groups", () => {
|
|
111
142
|
expect(
|
|
112
|
-
Seed_Users.label({
|
|
143
|
+
Seed_Users.label({
|
|
144
|
+
username: "admin",
|
|
145
|
+
password: "x",
|
|
146
|
+
groups: [],
|
|
147
|
+
userId: None,
|
|
148
|
+
demoOwner: None,
|
|
149
|
+
}),
|
|
113
150
|
)->Expect.toBe("admin")
|
|
114
151
|
})
|
|
115
152
|
|
|
@@ -123,6 +160,7 @@ describe("Seed_Users.label:", () => {
|
|
|
123
160
|
password: "x",
|
|
124
161
|
groups: ["Admin"],
|
|
125
162
|
userId: Some("4275e4a4-00c1-70e1-1cb5-363b06b992b5"),
|
|
163
|
+
demoOwner: None,
|
|
126
164
|
}),
|
|
127
165
|
)->Expect.toBe("admin [Admin]")
|
|
128
166
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
3
4
|
import * as Seed_Users$ReventlessSeed from "../src/Seed_Users.res.mjs";
|
|
4
5
|
|
|
5
6
|
let document = `
|
|
@@ -77,6 +78,27 @@ globalThis.describe("Seed_Users.load:", () => {
|
|
|
77
78
|
});
|
|
78
79
|
});
|
|
79
80
|
|
|
81
|
+
globalThis.describe("Seed_Users.playing:", () => {
|
|
82
|
+
let users = Seed_Users$ReventlessSeed.parseString(`
|
|
83
|
+
- username: shopper@example.com
|
|
84
|
+
password: pw
|
|
85
|
+
groups: [Shopper]
|
|
86
|
+
demoOwner: shopper
|
|
87
|
+
- username: admin
|
|
88
|
+
password: pw
|
|
89
|
+
groups: [Admin]
|
|
90
|
+
`);
|
|
91
|
+
globalThis.test("finds the account whose demoOwner names the person", () => {
|
|
92
|
+
globalThis.expect(Stdlib_Option.map(Seed_Users$ReventlessSeed.playing(users, "shopper"), u => u.username)).toEqual("shopper@example.com");
|
|
93
|
+
});
|
|
94
|
+
globalThis.test("falls back to the username when no entry declares one", () => {
|
|
95
|
+
globalThis.expect(Stdlib_Option.map(Seed_Users$ReventlessSeed.playing(users, "admin"), u => u.username)).toEqual("admin");
|
|
96
|
+
});
|
|
97
|
+
globalThis.test("finds nobody for a person no entry plays", () => {
|
|
98
|
+
globalThis.expect(Seed_Users$ReventlessSeed.playing(users, "merch")).toEqual(undefined);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
80
102
|
globalThis.describe("Seed_Users.label:", () => {
|
|
81
103
|
globalThis.test("names the groups an account carries", () => {
|
|
82
104
|
globalThis.expect(Seed_Users$ReventlessSeed.label({
|
|
@@ -86,7 +108,8 @@ globalThis.describe("Seed_Users.label:", () => {
|
|
|
86
108
|
"Admin",
|
|
87
109
|
"Shopper"
|
|
88
110
|
],
|
|
89
|
-
userId: undefined
|
|
111
|
+
userId: undefined,
|
|
112
|
+
demoOwner: undefined
|
|
90
113
|
})).toBe("admin [Admin, Shopper]");
|
|
91
114
|
});
|
|
92
115
|
globalThis.test("is the bare username when the file records no groups", () => {
|
|
@@ -94,7 +117,8 @@ globalThis.describe("Seed_Users.label:", () => {
|
|
|
94
117
|
username: "admin",
|
|
95
118
|
password: "x",
|
|
96
119
|
groups: [],
|
|
97
|
-
userId: undefined
|
|
120
|
+
userId: undefined,
|
|
121
|
+
demoOwner: undefined
|
|
98
122
|
})).toBe("admin");
|
|
99
123
|
});
|
|
100
124
|
globalThis.test("says nothing about the userId", () => {
|
|
@@ -102,7 +126,8 @@ globalThis.describe("Seed_Users.label:", () => {
|
|
|
102
126
|
username: "admin",
|
|
103
127
|
password: "x",
|
|
104
128
|
groups: ["Admin"],
|
|
105
|
-
userId: "4275e4a4-00c1-70e1-1cb5-363b06b992b5"
|
|
129
|
+
userId: "4275e4a4-00c1-70e1-1cb5-363b06b992b5",
|
|
130
|
+
demoOwner: undefined
|
|
106
131
|
})).toBe("admin [Admin]");
|
|
107
132
|
});
|
|
108
133
|
});
|
package/lib/ocaml/.compiler.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#Start(
|
|
2
|
-
#Done(
|
|
1
|
+
#Start(1789592211625)
|
|
2
|
+
#Done(1789592211686)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/ocaml/Seed_Users.ast
CHANGED
|
Binary file
|
package/lib/ocaml/Seed_Users.res
CHANGED
|
@@ -24,6 +24,7 @@ type user = {
|
|
|
24
24
|
password: string,
|
|
25
25
|
groups: array<string>,
|
|
26
26
|
userId: option<string>,
|
|
27
|
+
demoOwner: option<string>,
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
let asString = (json: JSON.t): option<string> =>
|
|
@@ -44,7 +45,13 @@ let userOf = (json: JSON.t): option<user> =>
|
|
|
44
45
|
| Some(JSON.Array(items)) => items->Array.filterMap(asString)
|
|
45
46
|
| _ => []
|
|
46
47
|
}
|
|
47
|
-
Some({
|
|
48
|
+
Some({
|
|
49
|
+
username,
|
|
50
|
+
password,
|
|
51
|
+
groups,
|
|
52
|
+
userId: obj->Dict.get("userId")->Option.flatMap(asString),
|
|
53
|
+
demoOwner: obj->Dict.get("demoOwner")->Option.flatMap(asString),
|
|
54
|
+
})
|
|
48
55
|
| _ => None
|
|
49
56
|
}
|
|
50
57
|
| _ => None
|
|
@@ -87,6 +94,14 @@ let load = (~path: option<string>=?): option<(string, array<user>)> => {
|
|
|
87
94
|
users->Array.length == 0 ? None : Some((file, users))
|
|
88
95
|
}
|
|
89
96
|
|
|
97
|
+
/** The account that plays `name` in a demo: the one whose `demoOwner` says so,
|
|
98
|
+
else the one named `name` — which is how a file without the field reads. */
|
|
99
|
+
let playing = (users: array<user>, name: string): option<user> =>
|
|
100
|
+
switch users->Array.find(u => u.demoOwner == Some(name)) {
|
|
101
|
+
| Some(_) as declared => declared
|
|
102
|
+
| None => users->Array.find(u => u.username == name)
|
|
103
|
+
}
|
|
104
|
+
|
|
90
105
|
/** Menu label: the username, plus whatever groups the file records for it. */
|
|
91
106
|
let label = (u: user): string =>
|
|
92
107
|
u.groups->Array.length == 0 ? u.username : `${u.username} [${u.groups->Array.join(", ")}]`
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -95,6 +95,36 @@ describe("Seed_Users.load:", () => {
|
|
|
95
95
|
})
|
|
96
96
|
})
|
|
97
97
|
|
|
98
|
+
// A pool that signs in on an email address takes `shopper@example.com`, not
|
|
99
|
+
// `shopper`, so a demo cannot find its accounts by username alone there.
|
|
100
|
+
describe("Seed_Users.playing:", () => {
|
|
101
|
+
let users = Seed_Users.parseString(`
|
|
102
|
+
- username: shopper@example.com
|
|
103
|
+
password: pw
|
|
104
|
+
groups: [Shopper]
|
|
105
|
+
demoOwner: shopper
|
|
106
|
+
- username: admin
|
|
107
|
+
password: pw
|
|
108
|
+
groups: [Admin]
|
|
109
|
+
`)
|
|
110
|
+
|
|
111
|
+
testSync("finds the account whose demoOwner names the person", () => {
|
|
112
|
+
expect(
|
|
113
|
+
Seed_Users.playing(users, "shopper")->Option.map(u => u.Seed_Users.username),
|
|
114
|
+
)->Expect.toEqual(Some("shopper@example.com"))
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
testSync("falls back to the username when no entry declares one", () => {
|
|
118
|
+
expect(
|
|
119
|
+
Seed_Users.playing(users, "admin")->Option.map(u => u.Seed_Users.username),
|
|
120
|
+
)->Expect.toEqual(Some("admin"))
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
testSync("finds nobody for a person no entry plays", () => {
|
|
124
|
+
expect(Seed_Users.playing(users, "merch"))->Expect.toEqual(None)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
98
128
|
describe("Seed_Users.label:", () => {
|
|
99
129
|
testSync("names the groups an account carries", () => {
|
|
100
130
|
expect(
|
|
@@ -103,13 +133,20 @@ describe("Seed_Users.label:", () => {
|
|
|
103
133
|
password: "x",
|
|
104
134
|
groups: ["Admin", "Shopper"],
|
|
105
135
|
userId: None,
|
|
136
|
+
demoOwner: None,
|
|
106
137
|
}),
|
|
107
138
|
)->Expect.toBe("admin [Admin, Shopper]")
|
|
108
139
|
})
|
|
109
140
|
|
|
110
141
|
testSync("is the bare username when the file records no groups", () => {
|
|
111
142
|
expect(
|
|
112
|
-
Seed_Users.label({
|
|
143
|
+
Seed_Users.label({
|
|
144
|
+
username: "admin",
|
|
145
|
+
password: "x",
|
|
146
|
+
groups: [],
|
|
147
|
+
userId: None,
|
|
148
|
+
demoOwner: None,
|
|
149
|
+
}),
|
|
113
150
|
)->Expect.toBe("admin")
|
|
114
151
|
})
|
|
115
152
|
|
|
@@ -123,6 +160,7 @@ describe("Seed_Users.label:", () => {
|
|
|
123
160
|
password: "x",
|
|
124
161
|
groups: ["Admin"],
|
|
125
162
|
userId: Some("4275e4a4-00c1-70e1-1cb5-363b06b992b5"),
|
|
163
|
+
demoOwner: None,
|
|
126
164
|
}),
|
|
127
165
|
)->Expect.toBe("admin [Admin]")
|
|
128
166
|
})
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-seed",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.22",
|
|
4
4
|
"description": "Reusable harness for seeding a Reventless platform through its public GraphQL command API",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
7
7
|
"yaml": "^2.8.3",
|
|
8
|
-
"@reventlessdev/rescript-node": "2.0.0-alpha.
|
|
8
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.12"
|
|
9
9
|
},
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"rescript": "12.3.0",
|
package/src/Seed_Prompt.res
CHANGED
package/src/Seed_Prompt.res.mjs
CHANGED
package/src/Seed_Users.res
CHANGED
|
@@ -24,6 +24,7 @@ type user = {
|
|
|
24
24
|
password: string,
|
|
25
25
|
groups: array<string>,
|
|
26
26
|
userId: option<string>,
|
|
27
|
+
demoOwner: option<string>,
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
let asString = (json: JSON.t): option<string> =>
|
|
@@ -44,7 +45,13 @@ let userOf = (json: JSON.t): option<user> =>
|
|
|
44
45
|
| Some(JSON.Array(items)) => items->Array.filterMap(asString)
|
|
45
46
|
| _ => []
|
|
46
47
|
}
|
|
47
|
-
Some({
|
|
48
|
+
Some({
|
|
49
|
+
username,
|
|
50
|
+
password,
|
|
51
|
+
groups,
|
|
52
|
+
userId: obj->Dict.get("userId")->Option.flatMap(asString),
|
|
53
|
+
demoOwner: obj->Dict.get("demoOwner")->Option.flatMap(asString),
|
|
54
|
+
})
|
|
48
55
|
| _ => None
|
|
49
56
|
}
|
|
50
57
|
| _ => None
|
|
@@ -87,6 +94,14 @@ let load = (~path: option<string>=?): option<(string, array<user>)> => {
|
|
|
87
94
|
users->Array.length == 0 ? None : Some((file, users))
|
|
88
95
|
}
|
|
89
96
|
|
|
97
|
+
/** The account that plays `name` in a demo: the one whose `demoOwner` says so,
|
|
98
|
+
else the one named `name` — which is how a file without the field reads. */
|
|
99
|
+
let playing = (users: array<user>, name: string): option<user> =>
|
|
100
|
+
switch users->Array.find(u => u.demoOwner == Some(name)) {
|
|
101
|
+
| Some(_) as declared => declared
|
|
102
|
+
| None => users->Array.find(u => u.username == name)
|
|
103
|
+
}
|
|
104
|
+
|
|
90
105
|
/** Menu label: the username, plus whatever groups the file records for it. */
|
|
91
106
|
let label = (u: user): string =>
|
|
92
107
|
u.groups->Array.length == 0 ? u.username : `${u.username} [${u.groups->Array.join(", ")}]`
|
package/src/Seed_Users.res.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import * as Nodefs from "node:fs";
|
|
|
5
5
|
import * as Nodepath from "node:path";
|
|
6
6
|
import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
7
7
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
8
|
+
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
8
9
|
|
|
9
10
|
function asString(json) {
|
|
10
11
|
if (typeof json === "string") {
|
|
@@ -32,7 +33,8 @@ function userOf(json) {
|
|
|
32
33
|
username: match,
|
|
33
34
|
password: match$1,
|
|
34
35
|
groups: groups,
|
|
35
|
-
userId: Stdlib_Option.flatMap(json["userId"], asString)
|
|
36
|
+
userId: Stdlib_Option.flatMap(json["userId"], asString),
|
|
37
|
+
demoOwner: Stdlib_Option.flatMap(json["demoOwner"], asString)
|
|
36
38
|
};
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -72,6 +74,15 @@ function load(path) {
|
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
function playing(users, name) {
|
|
78
|
+
let declared = users.find(u => Primitive_object.equal(u.demoOwner, name));
|
|
79
|
+
if (declared !== undefined) {
|
|
80
|
+
return declared;
|
|
81
|
+
} else {
|
|
82
|
+
return users.find(u => u.username === name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
75
86
|
function label(u) {
|
|
76
87
|
if (u.groups.length === 0) {
|
|
77
88
|
return u.username;
|
|
@@ -86,6 +97,7 @@ export {
|
|
|
86
97
|
parseString,
|
|
87
98
|
defaultPath,
|
|
88
99
|
load,
|
|
100
|
+
playing,
|
|
89
101
|
label,
|
|
90
102
|
}
|
|
91
103
|
/* yaml Not a pure module */
|
package/tests/Seed_UsersTest.res
CHANGED
|
@@ -95,6 +95,36 @@ describe("Seed_Users.load:", () => {
|
|
|
95
95
|
})
|
|
96
96
|
})
|
|
97
97
|
|
|
98
|
+
// A pool that signs in on an email address takes `shopper@example.com`, not
|
|
99
|
+
// `shopper`, so a demo cannot find its accounts by username alone there.
|
|
100
|
+
describe("Seed_Users.playing:", () => {
|
|
101
|
+
let users = Seed_Users.parseString(`
|
|
102
|
+
- username: shopper@example.com
|
|
103
|
+
password: pw
|
|
104
|
+
groups: [Shopper]
|
|
105
|
+
demoOwner: shopper
|
|
106
|
+
- username: admin
|
|
107
|
+
password: pw
|
|
108
|
+
groups: [Admin]
|
|
109
|
+
`)
|
|
110
|
+
|
|
111
|
+
testSync("finds the account whose demoOwner names the person", () => {
|
|
112
|
+
expect(
|
|
113
|
+
Seed_Users.playing(users, "shopper")->Option.map(u => u.Seed_Users.username),
|
|
114
|
+
)->Expect.toEqual(Some("shopper@example.com"))
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
testSync("falls back to the username when no entry declares one", () => {
|
|
118
|
+
expect(
|
|
119
|
+
Seed_Users.playing(users, "admin")->Option.map(u => u.Seed_Users.username),
|
|
120
|
+
)->Expect.toEqual(Some("admin"))
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
testSync("finds nobody for a person no entry plays", () => {
|
|
124
|
+
expect(Seed_Users.playing(users, "merch"))->Expect.toEqual(None)
|
|
125
|
+
})
|
|
126
|
+
})
|
|
127
|
+
|
|
98
128
|
describe("Seed_Users.label:", () => {
|
|
99
129
|
testSync("names the groups an account carries", () => {
|
|
100
130
|
expect(
|
|
@@ -103,13 +133,20 @@ describe("Seed_Users.label:", () => {
|
|
|
103
133
|
password: "x",
|
|
104
134
|
groups: ["Admin", "Shopper"],
|
|
105
135
|
userId: None,
|
|
136
|
+
demoOwner: None,
|
|
106
137
|
}),
|
|
107
138
|
)->Expect.toBe("admin [Admin, Shopper]")
|
|
108
139
|
})
|
|
109
140
|
|
|
110
141
|
testSync("is the bare username when the file records no groups", () => {
|
|
111
142
|
expect(
|
|
112
|
-
Seed_Users.label({
|
|
143
|
+
Seed_Users.label({
|
|
144
|
+
username: "admin",
|
|
145
|
+
password: "x",
|
|
146
|
+
groups: [],
|
|
147
|
+
userId: None,
|
|
148
|
+
demoOwner: None,
|
|
149
|
+
}),
|
|
113
150
|
)->Expect.toBe("admin")
|
|
114
151
|
})
|
|
115
152
|
|
|
@@ -123,6 +160,7 @@ describe("Seed_Users.label:", () => {
|
|
|
123
160
|
password: "x",
|
|
124
161
|
groups: ["Admin"],
|
|
125
162
|
userId: Some("4275e4a4-00c1-70e1-1cb5-363b06b992b5"),
|
|
163
|
+
demoOwner: None,
|
|
126
164
|
}),
|
|
127
165
|
)->Expect.toBe("admin [Admin]")
|
|
128
166
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
3
4
|
import * as Seed_Users$ReventlessSeed from "../src/Seed_Users.res.mjs";
|
|
4
5
|
|
|
5
6
|
let document = `
|
|
@@ -77,6 +78,27 @@ globalThis.describe("Seed_Users.load:", () => {
|
|
|
77
78
|
});
|
|
78
79
|
});
|
|
79
80
|
|
|
81
|
+
globalThis.describe("Seed_Users.playing:", () => {
|
|
82
|
+
let users = Seed_Users$ReventlessSeed.parseString(`
|
|
83
|
+
- username: shopper@example.com
|
|
84
|
+
password: pw
|
|
85
|
+
groups: [Shopper]
|
|
86
|
+
demoOwner: shopper
|
|
87
|
+
- username: admin
|
|
88
|
+
password: pw
|
|
89
|
+
groups: [Admin]
|
|
90
|
+
`);
|
|
91
|
+
globalThis.test("finds the account whose demoOwner names the person", () => {
|
|
92
|
+
globalThis.expect(Stdlib_Option.map(Seed_Users$ReventlessSeed.playing(users, "shopper"), u => u.username)).toEqual("shopper@example.com");
|
|
93
|
+
});
|
|
94
|
+
globalThis.test("falls back to the username when no entry declares one", () => {
|
|
95
|
+
globalThis.expect(Stdlib_Option.map(Seed_Users$ReventlessSeed.playing(users, "admin"), u => u.username)).toEqual("admin");
|
|
96
|
+
});
|
|
97
|
+
globalThis.test("finds nobody for a person no entry plays", () => {
|
|
98
|
+
globalThis.expect(Seed_Users$ReventlessSeed.playing(users, "merch")).toEqual(undefined);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
80
102
|
globalThis.describe("Seed_Users.label:", () => {
|
|
81
103
|
globalThis.test("names the groups an account carries", () => {
|
|
82
104
|
globalThis.expect(Seed_Users$ReventlessSeed.label({
|
|
@@ -86,7 +108,8 @@ globalThis.describe("Seed_Users.label:", () => {
|
|
|
86
108
|
"Admin",
|
|
87
109
|
"Shopper"
|
|
88
110
|
],
|
|
89
|
-
userId: undefined
|
|
111
|
+
userId: undefined,
|
|
112
|
+
demoOwner: undefined
|
|
90
113
|
})).toBe("admin [Admin, Shopper]");
|
|
91
114
|
});
|
|
92
115
|
globalThis.test("is the bare username when the file records no groups", () => {
|
|
@@ -94,7 +117,8 @@ globalThis.describe("Seed_Users.label:", () => {
|
|
|
94
117
|
username: "admin",
|
|
95
118
|
password: "x",
|
|
96
119
|
groups: [],
|
|
97
|
-
userId: undefined
|
|
120
|
+
userId: undefined,
|
|
121
|
+
demoOwner: undefined
|
|
98
122
|
})).toBe("admin");
|
|
99
123
|
});
|
|
100
124
|
globalThis.test("says nothing about the userId", () => {
|
|
@@ -102,7 +126,8 @@ globalThis.describe("Seed_Users.label:", () => {
|
|
|
102
126
|
username: "admin",
|
|
103
127
|
password: "x",
|
|
104
128
|
groups: ["Admin"],
|
|
105
|
-
userId: "4275e4a4-00c1-70e1-1cb5-363b06b992b5"
|
|
129
|
+
userId: "4275e4a4-00c1-70e1-1cb5-363b06b992b5",
|
|
130
|
+
demoOwner: undefined
|
|
106
131
|
})).toBe("admin [Admin]");
|
|
107
132
|
});
|
|
108
133
|
});
|