@reventlessdev/reventless-local 3.0.0-alpha.261 → 3.0.0-alpha.263

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 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.263 (2026-09-24)
7
+
8
+ **Note:** Version bump only for package @reventlessdev/reventless-local
9
+
10
+
11
+
12
+
13
+
14
+ # 3.0.0-alpha.262 (2026-09-23)
15
+
16
+ * feat(node)!: every command-line tool reads its arguments through parseArgs ([2e41979](https://github.com/ReventlessDev/reventless-core/commit/2e41979b73646c46356e197e17eb4a3da0154c73))
17
+
18
+ ### BREAKING CHANGES
19
+
20
+ * NodeProcess.onSignal takes the new NodeProcess.signal
21
+ variant (SIGINT | SIGTERM | SIGHUP) instead of a polymorphic variant:
22
+ write onSignal(SIGINT, …) for onSignal(#SIGINT, …). Node receives the
23
+ same string.
24
+
25
+
26
+
6
27
  # 3.0.0-alpha.261 (2026-09-23)
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.261",
3
+ "version": "3.0.0-alpha.263",
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
37
  "@reventlessdev/rescript-jest": "1.0.0-alpha.11",
39
38
  "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.29",
40
- "@reventlessdev/rescript-node": "2.0.0-alpha.14",
41
39
  "@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.23",
40
+ "@reventlessdev/rescript-effect": "0.1.0-alpha.34",
41
+ "@reventlessdev/rescript-node": "2.0.0-alpha.15",
42
+ "@reventlessdev/reventless-core": "3.0.0-alpha.273",
42
43
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.22",
43
- "@reventlessdev/reventless-core": "3.0.0-alpha.271",
44
- "@reventlessdev/reventless-gwt": "1.0.0-alpha.218",
45
- "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.119",
46
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.135",
47
- "@reventlessdev/reventless-seed": "1.0.0-alpha.25",
48
- "@reventlessdev/reventless-spec": "3.0.0-alpha.143",
49
- "@reventlessdev/reventless-infra": "3.0.0-alpha.171"
44
+ "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.121",
45
+ "@reventlessdev/reventless-gwt": "1.0.0-alpha.220",
46
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.173",
47
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.137",
48
+ "@reventlessdev/reventless-seed": "1.0.0-alpha.26",
49
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.145"
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.89"
54
+ "@reventlessdev/reventless-ppx": "1.0.0-alpha.92"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "rescript": "^12.3.1"
@@ -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 main = async () => {
60
- // The local platform defaults to Debug-level logging; a build step should not.
61
- switch NodeProcess.env->Dict.get("LOG_LEVEL") {
62
- | Some(_) => ()
63
- | None => NodeProcess.env->Dict.set("LOG_LEVEL", "warn")
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
- switch NodeProcess.argv->Array.get(2) {
67
- | None | Some("") => {
68
- Console.error("Usage: emit-capabilities <srcDir> [<compositionModule>]")
69
- NodeProcess.exit(1)
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
- | Some(srcDirArg) => {
72
- let srcDir = NodePath.resolve([srcDirArg])
73
- let modulePath = compositionModulePath(~srcDir, ~moduleArg=NodeProcess.argv->Array.get(3))
74
- if !NodeFs.existsSync(modulePath) {
75
- fail(`${modulePath} not found — run \`rescript build\` first.`)
76
- }
77
-
78
- // Relative specifier: resolved against this module, so it finds the
79
- // sibling compiled platform whatever the working directory is.
80
- let platformModule: localPlatformExports = await dynImport("./Platform.res.mjs")
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
- async function main() {
33
- let match = process.env["LOG_LEVEL"];
34
- if (match !== undefined) {
35
-
36
- } else {
37
- process.env["LOG_LEVEL"] = "warn";
38
- }
39
- let srcDirArg = process.argv[2];
40
- if (srcDirArg !== undefined) {
41
- if (srcDirArg === "") {
42
- console.error("Usage: emit-capabilities <srcDir> [<compositionModule>]");
43
- process.exit(1);
44
- return;
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
- let srcDir = Nodepath.resolve(srcDirArg);
47
- let modulePath = compositionModulePath(srcDir, process.argv[3]);
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
- return;
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(#SIGINT, () => {
174
+ NodeProcess.onSignal(SIGINT, () => {
175
175
  removeQuietly(path)
176
176
  NodeProcess.exit(130)
177
177
  })
178
- NodeProcess.onSignal(#SIGTERM, () => {
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 */