@reventlessdev/reventless-seed 1.0.0-alpha.21 → 1.0.0-alpha.23
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 +14 -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.ast +0 -0
- package/lib/bs/src/Seed.res +1 -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_Runner.ast +0 -0
- package/lib/bs/src/Seed_Runner.res +8 -0
- package/lib/bs/src/Seed_Runner.res.mjs +4 -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.ast +0 -0
- package/lib/ocaml/Seed.res +1 -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_Runner.ast +0 -0
- package/lib/ocaml/Seed_Runner.res +8 -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.res +1 -0
- package/src/Seed_Prompt.res +1 -0
- package/src/Seed_Prompt.res.mjs +2 -1
- package/src/Seed_Runner.res +8 -0
- package/src/Seed_Runner.res.mjs +4 -0
- 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,20 @@
|
|
|
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.23 (2026-09-17)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **seed:** let a data set refuse to start before anything is sent ([6fc5135](https://github.com/ReventlessDev/reventless-core/commit/6fc5135107f9af3016a48423cfee625216babca1))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 1.0.0-alpha.22 (2026-09-16)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **aws:** make the demo accounts work on a pool that signs in by email ([97b036f](https://github.com/ReventlessDev/reventless-core/commit/97b036f86979ba55dbbf93d460fe8076c35abf1a))
|
|
18
|
+
|
|
19
|
+
|
|
6
20
|
# 1.0.0-alpha.21 (2026-09-09)
|
|
7
21
|
|
|
8
22
|
### Bug Fixes
|
package/lib/bs/.compiler.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#Start(
|
|
2
|
-
#Done(
|
|
1
|
+
#Start(1789645504278)
|
|
2
|
+
#Done(1789645504316)
|
|
@@ -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": "1789645504318"
|
|
8
8
|
}
|
|
Binary file
|
|
Binary file
|
package/lib/bs/src/Seed.ast
CHANGED
|
Binary file
|
package/lib/bs/src/Seed.res
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -151,6 +151,10 @@ type dataSet = {
|
|
|
151
151
|
// View names probed by the fresh-store guard before any command is sent; a
|
|
152
152
|
// non-empty one aborts the run at startup. Omit to skip the guard.
|
|
153
153
|
probeViews?: array<string>,
|
|
154
|
+
// Any other check that must pass before a command is sent — a set that adds to
|
|
155
|
+
// an existing store refuses an empty one here. Throw `Failed` to refuse; the
|
|
156
|
+
// run then reports that nothing was written.
|
|
157
|
+
preflight?: Seed_Connect.connection => promise<unit>,
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
/**
|
|
@@ -205,6 +209,10 @@ let seed = (~sets: array<dataSet>, ~connect: unit => promise<Seed_Connect.connec
|
|
|
205
209
|
// Runs inside this pre-seed `try`, so refusing a non-empty store (or a
|
|
206
210
|
// failed probe query) is reported as "did not start", not "half-seeded".
|
|
207
211
|
await assertStoreEmpty(connection.client, ~probeViews=chosen.probeViews->Option.getOr([]))
|
|
212
|
+
switch chosen.preflight {
|
|
213
|
+
| Some(check) => await check(connection)
|
|
214
|
+
| None => ()
|
|
215
|
+
}
|
|
208
216
|
Some((chosen, connection))
|
|
209
217
|
} catch {
|
|
210
218
|
| Failed(message) =>
|
|
@@ -163,6 +163,10 @@ function seed(sets, connect) {
|
|
|
163
163
|
let connection = await connect();
|
|
164
164
|
Seed_Prompt$ReventlessSeed.close();
|
|
165
165
|
await assertStoreEmpty(connection.client, Stdlib_Option.getOr(chosen.probeViews, []));
|
|
166
|
+
let check = chosen.preflight;
|
|
167
|
+
if (check !== undefined) {
|
|
168
|
+
await check(connection);
|
|
169
|
+
}
|
|
166
170
|
started = [
|
|
167
171
|
chosen,
|
|
168
172
|
connection
|
|
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(1789645504278)
|
|
2
|
+
#Done(1789645504316)
|
|
Binary file
|
|
Binary file
|
package/lib/ocaml/Seed.ast
CHANGED
|
Binary file
|
package/lib/ocaml/Seed.res
CHANGED
|
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
|
|
@@ -151,6 +151,10 @@ type dataSet = {
|
|
|
151
151
|
// View names probed by the fresh-store guard before any command is sent; a
|
|
152
152
|
// non-empty one aborts the run at startup. Omit to skip the guard.
|
|
153
153
|
probeViews?: array<string>,
|
|
154
|
+
// Any other check that must pass before a command is sent — a set that adds to
|
|
155
|
+
// an existing store refuses an empty one here. Throw `Failed` to refuse; the
|
|
156
|
+
// run then reports that nothing was written.
|
|
157
|
+
preflight?: Seed_Connect.connection => promise<unit>,
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
/**
|
|
@@ -205,6 +209,10 @@ let seed = (~sets: array<dataSet>, ~connect: unit => promise<Seed_Connect.connec
|
|
|
205
209
|
// Runs inside this pre-seed `try`, so refusing a non-empty store (or a
|
|
206
210
|
// failed probe query) is reported as "did not start", not "half-seeded".
|
|
207
211
|
await assertStoreEmpty(connection.client, ~probeViews=chosen.probeViews->Option.getOr([]))
|
|
212
|
+
switch chosen.preflight {
|
|
213
|
+
| Some(check) => await check(connection)
|
|
214
|
+
| None => ()
|
|
215
|
+
}
|
|
208
216
|
Some((chosen, connection))
|
|
209
217
|
} catch {
|
|
210
218
|
| Failed(message) =>
|
|
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.23",
|
|
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.res
CHANGED
package/src/Seed_Prompt.res
CHANGED
package/src/Seed_Prompt.res.mjs
CHANGED
package/src/Seed_Runner.res
CHANGED
|
@@ -151,6 +151,10 @@ type dataSet = {
|
|
|
151
151
|
// View names probed by the fresh-store guard before any command is sent; a
|
|
152
152
|
// non-empty one aborts the run at startup. Omit to skip the guard.
|
|
153
153
|
probeViews?: array<string>,
|
|
154
|
+
// Any other check that must pass before a command is sent — a set that adds to
|
|
155
|
+
// an existing store refuses an empty one here. Throw `Failed` to refuse; the
|
|
156
|
+
// run then reports that nothing was written.
|
|
157
|
+
preflight?: Seed_Connect.connection => promise<unit>,
|
|
154
158
|
}
|
|
155
159
|
|
|
156
160
|
/**
|
|
@@ -205,6 +209,10 @@ let seed = (~sets: array<dataSet>, ~connect: unit => promise<Seed_Connect.connec
|
|
|
205
209
|
// Runs inside this pre-seed `try`, so refusing a non-empty store (or a
|
|
206
210
|
// failed probe query) is reported as "did not start", not "half-seeded".
|
|
207
211
|
await assertStoreEmpty(connection.client, ~probeViews=chosen.probeViews->Option.getOr([]))
|
|
212
|
+
switch chosen.preflight {
|
|
213
|
+
| Some(check) => await check(connection)
|
|
214
|
+
| None => ()
|
|
215
|
+
}
|
|
208
216
|
Some((chosen, connection))
|
|
209
217
|
} catch {
|
|
210
218
|
| Failed(message) =>
|
package/src/Seed_Runner.res.mjs
CHANGED
|
@@ -163,6 +163,10 @@ function seed(sets, connect) {
|
|
|
163
163
|
let connection = await connect();
|
|
164
164
|
Seed_Prompt$ReventlessSeed.close();
|
|
165
165
|
await assertStoreEmpty(connection.client, Stdlib_Option.getOr(chosen.probeViews, []));
|
|
166
|
+
let check = chosen.preflight;
|
|
167
|
+
if (check !== undefined) {
|
|
168
|
+
await check(connection);
|
|
169
|
+
}
|
|
166
170
|
started = [
|
|
167
171
|
chosen,
|
|
168
172
|
connection
|
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
|
});
|