@reventlessdev/reventless-local 3.0.0-alpha.260 → 3.0.0-alpha.262
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 +21 -0
- package/package.json +12 -12
- package/src/EmitCapabilities.res +53 -42
- package/src/EmitCapabilities.res.mjs +50 -19
- package/src/LocalPlatformRegistry.res +2 -2
- package/tests/EmitCapabilitiesTest.res +45 -0
- package/tests/EmitCapabilitiesTest.res.mjs +61 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,27 @@
|
|
|
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
|
+
# 3.0.0-alpha.262 (2026-09-23)
|
|
7
|
+
|
|
8
|
+
* feat(node)!: every command-line tool reads its arguments through parseArgs ([2e41979](https://github.com/ReventlessDev/reventless-core/commit/2e41979b73646c46356e197e17eb4a3da0154c73))
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* NodeProcess.onSignal takes the new NodeProcess.signal
|
|
13
|
+
variant (SIGINT | SIGTERM | SIGHUP) instead of a polymorphic variant:
|
|
14
|
+
write onSignal(SIGINT, …) for onSignal(#SIGINT, …). Node receives the
|
|
15
|
+
same string.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# 3.0.0-alpha.261 (2026-09-23)
|
|
20
|
+
|
|
21
|
+
**Note:** Version bump only for package @reventlessdev/reventless-local
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
6
27
|
# 3.0.0-alpha.260 (2026-09-22)
|
|
7
28
|
|
|
8
29
|
**Note:** Version bump only for package @reventlessdev/reventless-local
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-local",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.262",
|
|
4
4
|
"description": "Local platform for Reventless (in-memory or SQLite backend, for development and testing without AWS)",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -34,24 +34,24 @@
|
|
|
34
34
|
"graphql-yoga": "^5.21.0",
|
|
35
35
|
"sury": "11.0.0",
|
|
36
36
|
"ws": "^8.18.0",
|
|
37
|
-
"@reventlessdev/rescript-effect": "0.1.0-alpha.34",
|
|
38
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
39
37
|
"@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.29",
|
|
38
|
+
"@reventlessdev/rescript-effect": "0.1.0-alpha.34",
|
|
40
39
|
"@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.23",
|
|
41
|
-
"@reventlessdev/rescript-
|
|
40
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
41
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.15",
|
|
42
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.272",
|
|
43
|
+
"@reventlessdev/reventless-graphql-server": "1.0.0-alpha.120",
|
|
42
44
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.22",
|
|
43
|
-
"@reventlessdev/reventless-
|
|
44
|
-
"@reventlessdev/reventless-
|
|
45
|
-
"@reventlessdev/reventless-
|
|
46
|
-
"@reventlessdev/reventless-
|
|
47
|
-
"@reventlessdev/reventless-
|
|
48
|
-
"@reventlessdev/reventless-seed": "1.0.0-alpha.24",
|
|
49
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.142"
|
|
45
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.172",
|
|
46
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.136",
|
|
47
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.144",
|
|
48
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.219",
|
|
49
|
+
"@reventlessdev/reventless-seed": "1.0.0-alpha.26"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"rescript": "12.3.1",
|
|
53
53
|
"sury-ppx": "11.0.0",
|
|
54
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
54
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.89"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"rescript": "^12.3.1"
|
package/src/EmitCapabilities.res
CHANGED
|
@@ -16,9 +16,6 @@
|
|
|
16
16
|
// expecting the platform *value*, and a ReScript module is not one. Importing
|
|
17
17
|
// it puts both sides on the same footing, and it keeps `LOG_LEVEL` (set below)
|
|
18
18
|
// in force before the platform's logger initialises.
|
|
19
|
-
//
|
|
20
|
-
// Usage: emit-capabilities <srcDir> [<compositionModule>]
|
|
21
|
-
// (run from the plugin package, after `rescript build`)
|
|
22
19
|
|
|
23
20
|
// Emitted by ReScript as a literal `import(...)` expression — the composition
|
|
24
21
|
// root's path is only known at run time, so it cannot be a static binding.
|
|
@@ -56,45 +53,59 @@ let fail = (message: string) => {
|
|
|
56
53
|
NodeProcess.exit(1)
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
let
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
let usage = `Usage: emit-capabilities <srcDir> [<compositionModule>]
|
|
57
|
+
|
|
58
|
+
<srcDir> the plugin's sources; capabilities.json is written there
|
|
59
|
+
<compositionModule> the composition root, when it is not the generated
|
|
60
|
+
Plugin.res: a module name inside <srcDir>, or a path
|
|
61
|
+
|
|
62
|
+
Run from the plugin package, after rescript build.`
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
type args = {srcDir: string, compositionModule: option<string>}
|
|
65
|
+
|
|
66
|
+
let parseArgs = (argv: array<string>): result<args, string> =>
|
|
67
|
+
Reventless.CliArgs.parse(argv)
|
|
68
|
+
->Result.flatMap(a => a->Reventless.CliArgs.atMost(2))
|
|
69
|
+
->Result.flatMap(a => {
|
|
70
|
+
let positionals = a->Reventless.CliArgs.positionals
|
|
71
|
+
switch positionals->Array.get(0) {
|
|
72
|
+
| None | Some("") => Error("<srcDir> is required.")
|
|
73
|
+
| Some(srcDir) => Ok({srcDir, compositionModule: positionals->Array.get(1)})
|
|
70
74
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
let platform = platformModule["Make"]()
|
|
82
|
-
|
|
83
|
-
let composition: compositionExports = await dynImport(
|
|
84
|
-
NodeUrl.pathToFileURL(modulePath)["href"],
|
|
85
|
-
)
|
|
86
|
-
let built = composition["Make"](platform)
|
|
87
|
-
|
|
88
|
-
let manifestPath = NodePath.join([srcDir, "capabilities.json"])
|
|
89
|
-
NodeFs.writeFileSync(
|
|
90
|
-
manifestPath,
|
|
91
|
-
Reventless.CapabilityManifest.renderForStructure(built["pluginStructure"]),
|
|
92
|
-
)
|
|
93
|
-
Console.log("Generated: " ++ manifestPath)
|
|
94
|
-
|
|
95
|
-
// Applying the platform functor wires in-process infrastructure; exit
|
|
96
|
-
// explicitly so no lingering handle keeps the build step alive.
|
|
97
|
-
NodeProcess.exit(0)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
let cli: Reventless.CliArgs.cli<args> = {bin: "emit-capabilities", usage, parse: parseArgs}
|
|
78
|
+
|
|
79
|
+
let main = () =>
|
|
80
|
+
Reventless.CliArgs.run(cli, async ({srcDir: srcDirArg, compositionModule}) => {
|
|
81
|
+
// The local platform defaults to Debug-level logging; a build step should not.
|
|
82
|
+
switch NodeProcess.env->Dict.get("LOG_LEVEL") {
|
|
83
|
+
| Some(_) => ()
|
|
84
|
+
| None => NodeProcess.env->Dict.set("LOG_LEVEL", "warn")
|
|
98
85
|
}
|
|
99
|
-
|
|
100
|
-
|
|
86
|
+
|
|
87
|
+
let srcDir = NodePath.resolve([srcDirArg])
|
|
88
|
+
let modulePath = compositionModulePath(~srcDir, ~moduleArg=compositionModule)
|
|
89
|
+
if !NodeFs.existsSync(modulePath) {
|
|
90
|
+
fail(`${modulePath} not found — run \`rescript build\` first.`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Relative specifier: resolved against this module, so it finds the
|
|
94
|
+
// sibling compiled platform whatever the working directory is.
|
|
95
|
+
let platformModule: localPlatformExports = await dynImport("./Platform.res.mjs")
|
|
96
|
+
let platform = platformModule["Make"]()
|
|
97
|
+
|
|
98
|
+
let composition: compositionExports = await dynImport(NodeUrl.pathToFileURL(modulePath)["href"])
|
|
99
|
+
let built = composition["Make"](platform)
|
|
100
|
+
|
|
101
|
+
let manifestPath = NodePath.join([srcDir, "capabilities.json"])
|
|
102
|
+
NodeFs.writeFileSync(
|
|
103
|
+
manifestPath,
|
|
104
|
+
Reventless.CapabilityManifest.renderForStructure(built["pluginStructure"]),
|
|
105
|
+
)
|
|
106
|
+
Console.log("Generated: " ++ manifestPath)
|
|
107
|
+
|
|
108
|
+
// Applying the platform functor wires in-process infrastructure; exit
|
|
109
|
+
// explicitly so no lingering handle keeps the build step alive.
|
|
110
|
+
NodeProcess.exit(0)
|
|
111
|
+
})
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import * as Nodefs from "node:fs";
|
|
4
4
|
import * as Nodeurl from "node:url";
|
|
5
5
|
import * as Nodepath from "node:path";
|
|
6
|
+
import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
7
|
+
import * as CliArgs$Reventless from "@reventlessdev/reventless-spec/src/CliArgs.res.mjs";
|
|
6
8
|
import * as CapabilityManifest$Reventless from "@reventlessdev/reventless-spec/src/components/CapabilityManifest.res.mjs";
|
|
7
9
|
|
|
8
10
|
function compositionModulePath(srcDir, moduleArg) {
|
|
@@ -29,22 +31,51 @@ function fail(message) {
|
|
|
29
31
|
process.exit(1);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
let usage = `Usage: emit-capabilities <srcDir> [<compositionModule>]
|
|
35
|
+
|
|
36
|
+
<srcDir> the plugin's sources; capabilities.json is written there
|
|
37
|
+
<compositionModule> the composition root, when it is not the generated
|
|
38
|
+
Plugin.res: a module name inside <srcDir>, or a path
|
|
39
|
+
|
|
40
|
+
Run from the plugin package, after rescript build.`;
|
|
41
|
+
|
|
42
|
+
function parseArgs(argv) {
|
|
43
|
+
return Stdlib_Result.flatMap(Stdlib_Result.flatMap(CliArgs$Reventless.parse(undefined, undefined, undefined, undefined, argv), a => CliArgs$Reventless.atMost(a, 2)), a => {
|
|
44
|
+
let positionals = CliArgs$Reventless.positionals(a);
|
|
45
|
+
let srcDir = positionals[0];
|
|
46
|
+
if (srcDir !== undefined && srcDir !== "") {
|
|
47
|
+
return {
|
|
48
|
+
TAG: "Ok",
|
|
49
|
+
_0: {
|
|
50
|
+
srcDir: srcDir,
|
|
51
|
+
compositionModule: positionals[1]
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
} else {
|
|
55
|
+
return {
|
|
56
|
+
TAG: "Error",
|
|
57
|
+
_0: "<srcDir> is required."
|
|
58
|
+
};
|
|
45
59
|
}
|
|
46
|
-
|
|
47
|
-
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let cli = {
|
|
64
|
+
bin: "emit-capabilities",
|
|
65
|
+
usage: usage,
|
|
66
|
+
parse: parseArgs
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function main() {
|
|
70
|
+
return CliArgs$Reventless.run(cli, undefined, undefined, async param => {
|
|
71
|
+
let match = process.env["LOG_LEVEL"];
|
|
72
|
+
if (match !== undefined) {
|
|
73
|
+
|
|
74
|
+
} else {
|
|
75
|
+
process.env["LOG_LEVEL"] = "warn";
|
|
76
|
+
}
|
|
77
|
+
let srcDir = Nodepath.resolve(param.srcDir);
|
|
78
|
+
let modulePath = compositionModulePath(srcDir, param.compositionModule);
|
|
48
79
|
if (!Nodefs.existsSync(modulePath)) {
|
|
49
80
|
fail(modulePath + ` not found — run \`rescript build\` first.`);
|
|
50
81
|
}
|
|
@@ -56,15 +87,15 @@ async function main() {
|
|
|
56
87
|
Nodefs.writeFileSync(manifestPath, CapabilityManifest$Reventless.renderForStructure(built.pluginStructure), "utf8");
|
|
57
88
|
console.log("Generated: " + manifestPath);
|
|
58
89
|
process.exit(0);
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
console.error("Usage: emit-capabilities <srcDir> [<compositionModule>]");
|
|
62
|
-
process.exit(1);
|
|
90
|
+
});
|
|
63
91
|
}
|
|
64
92
|
|
|
65
93
|
export {
|
|
66
94
|
compositionModulePath,
|
|
67
95
|
fail,
|
|
96
|
+
usage,
|
|
97
|
+
parseArgs,
|
|
98
|
+
cli,
|
|
68
99
|
main,
|
|
69
100
|
}
|
|
70
101
|
/* node:fs Not a pure module */
|
|
@@ -171,11 +171,11 @@ let register = (
|
|
|
171
171
|
): unit => {
|
|
172
172
|
let path = write(~port, ~endpoint, ~loginEndpoint, ~store, ~tapPort?)
|
|
173
173
|
NodeProcess.onExit(_ => removeQuietly(path))
|
|
174
|
-
NodeProcess.onSignal(
|
|
174
|
+
NodeProcess.onSignal(SIGINT, () => {
|
|
175
175
|
removeQuietly(path)
|
|
176
176
|
NodeProcess.exit(130)
|
|
177
177
|
})
|
|
178
|
-
NodeProcess.onSignal(
|
|
178
|
+
NodeProcess.onSignal(SIGTERM, () => {
|
|
179
179
|
removeQuietly(path)
|
|
180
180
|
NodeProcess.exit(143)
|
|
181
181
|
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
open JestGlobals
|
|
2
|
+
|
|
3
|
+
// `emit-capabilities <srcDir> [<compositionModule>]` runs in the `postbuild` of
|
|
4
|
+
// every plugin package.
|
|
5
|
+
|
|
6
|
+
describe("EmitCapabilities.parseArgs", () => {
|
|
7
|
+
testSync("a plugin's postbuild: emit-capabilities src/", () =>
|
|
8
|
+
expect(EmitCapabilities.parseArgs(["src/"]))->toEqual(
|
|
9
|
+
Ok({EmitCapabilities.srcDir: "src/", compositionModule: None}),
|
|
10
|
+
)
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
testSync("a hand-written composition root is named after the source directory", () =>
|
|
14
|
+
expect(EmitCapabilities.parseArgs(["src", "Catalog"]))->toEqual(
|
|
15
|
+
Ok({EmitCapabilities.srcDir: "src", compositionModule: Some("Catalog")}),
|
|
16
|
+
)
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
testSync("no source directory is refused", () =>
|
|
20
|
+
expect(EmitCapabilities.parseArgs([])->Result.isError)->toBe(true)
|
|
21
|
+
)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe("emit-capabilities on the command line", () => {
|
|
25
|
+
module CliArgs = Reventless.CliArgs
|
|
26
|
+
let usage = EmitCapabilities.usage->String.trim
|
|
27
|
+
|
|
28
|
+
test("--help anywhere prints the usage to stdout and exits 0", async () =>
|
|
29
|
+
expect(await CliArgs.observe(EmitCapabilities.cli, ["src/", "--help"]))->toEqual({
|
|
30
|
+
CliArgs.stdout: [usage],
|
|
31
|
+
stderr: [],
|
|
32
|
+
exitCode: Some(0),
|
|
33
|
+
reachedMain: false,
|
|
34
|
+
})
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
test("a usage mistake names itself, shows the usage on stderr and exits 2", async () =>
|
|
38
|
+
expect(await CliArgs.observe(EmitCapabilities.cli, ["src", "Catalog", "extra"]))->toEqual({
|
|
39
|
+
CliArgs.stdout: [],
|
|
40
|
+
stderr: [`emit-capabilities: unknown argument "extra"\n\n${usage}`],
|
|
41
|
+
exitCode: Some(2),
|
|
42
|
+
reachedMain: false,
|
|
43
|
+
})
|
|
44
|
+
)
|
|
45
|
+
})
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
4
|
+
import * as CliArgs$Reventless from "@reventlessdev/reventless-spec/src/CliArgs.res.mjs";
|
|
5
|
+
import * as EmitCapabilities$ReventlessLocal from "../src/EmitCapabilities.res.mjs";
|
|
6
|
+
|
|
7
|
+
globalThis.describe("EmitCapabilities.parseArgs", () => {
|
|
8
|
+
globalThis.test("a plugin's postbuild: emit-capabilities src/", () => {
|
|
9
|
+
globalThis.expect(EmitCapabilities$ReventlessLocal.parseArgs(["src/"])).toEqual({
|
|
10
|
+
TAG: "Ok",
|
|
11
|
+
_0: {
|
|
12
|
+
srcDir: "src/",
|
|
13
|
+
compositionModule: undefined
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
globalThis.test("a hand-written composition root is named after the source directory", () => {
|
|
18
|
+
globalThis.expect(EmitCapabilities$ReventlessLocal.parseArgs([
|
|
19
|
+
"src",
|
|
20
|
+
"Catalog"
|
|
21
|
+
])).toEqual({
|
|
22
|
+
TAG: "Ok",
|
|
23
|
+
_0: {
|
|
24
|
+
srcDir: "src",
|
|
25
|
+
compositionModule: "Catalog"
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
globalThis.test("no source directory is refused", () => {
|
|
30
|
+
globalThis.expect(Stdlib_Result.isError(EmitCapabilities$ReventlessLocal.parseArgs([]))).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
globalThis.describe("emit-capabilities on the command line", () => {
|
|
35
|
+
let usage = EmitCapabilities$ReventlessLocal.usage.trim();
|
|
36
|
+
globalThis.test("--help anywhere prints the usage to stdout and exits 0", async () => {
|
|
37
|
+
globalThis.expect(await CliArgs$Reventless.observe(EmitCapabilities$ReventlessLocal.cli, [
|
|
38
|
+
"src/",
|
|
39
|
+
"--help"
|
|
40
|
+
])).toEqual({
|
|
41
|
+
stdout: [usage],
|
|
42
|
+
stderr: [],
|
|
43
|
+
exitCode: 0,
|
|
44
|
+
reachedMain: false
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
globalThis.test("a usage mistake names itself, shows the usage on stderr and exits 2", async () => {
|
|
48
|
+
globalThis.expect(await CliArgs$Reventless.observe(EmitCapabilities$ReventlessLocal.cli, [
|
|
49
|
+
"src",
|
|
50
|
+
"Catalog",
|
|
51
|
+
"extra"
|
|
52
|
+
])).toEqual({
|
|
53
|
+
stdout: [],
|
|
54
|
+
stderr: [`emit-capabilities: unknown argument "extra"\n\n` + usage],
|
|
55
|
+
exitCode: 2,
|
|
56
|
+
reachedMain: false
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/* Not a pure module */
|