@reboot-dev/reboot 0.19.0 → 0.19.2
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/package.json +31 -21
- package/protoc-gen-es.d.ts +2 -0
- package/protoc-gen-es.js +4 -0
- package/rbt-esbuild.d.ts +2 -0
- package/rbt-esbuild.js +93 -0
- package/rbt.js +37 -0
- package/reboot.d.ts +2 -1
- package/reboot.js +28 -9
- package/reboot_native.cc +39 -21
- package/reboot_native.cjs +10 -20
- package/utils/index.d.ts +11 -0
- package/utils/index.js +46 -0
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/workspaces.js +120 -0
package/package.json
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"@types/node": "20.11.5",
|
|
4
|
-
"@types/uuid": "^9.0.4",
|
|
5
|
-
"uuid": "^9.0.1",
|
|
6
|
-
"node-addon-api": "^7.0.0",
|
|
7
|
-
"protobufjs": "^7.2.5",
|
|
8
|
-
"chalk": "^4.1.2"
|
|
9
|
-
},
|
|
10
|
-
"peerDependencies": {
|
|
11
3
|
"@bufbuild/protobuf": "1.3.2",
|
|
12
4
|
"@bufbuild/protoplugin": "1.3.2",
|
|
13
5
|
"@bufbuild/protoc-gen-es": "1.3.2",
|
|
14
|
-
"@reboot-dev/reboot-api": "0.19.
|
|
15
|
-
"
|
|
6
|
+
"@reboot-dev/reboot-api": "0.19.2",
|
|
7
|
+
"chalk": "^4.1.2",
|
|
8
|
+
"node-addon-api": "^7.0.0",
|
|
9
|
+
"node-gyp": ">=10.2.0",
|
|
10
|
+
"uuid": "^9.0.1",
|
|
11
|
+
"which-pm-runs": "^1.1.0",
|
|
12
|
+
"extensionless": "^1.9.9",
|
|
13
|
+
"esbuild": "^0.24.0"
|
|
16
14
|
},
|
|
17
15
|
"type": "module",
|
|
18
16
|
"name": "@reboot-dev/reboot",
|
|
19
|
-
"version": "0.19.
|
|
17
|
+
"version": "0.19.2",
|
|
20
18
|
"description": "npm package for Reboot",
|
|
21
19
|
"scripts": {
|
|
22
20
|
"postinstall": "rbt || exit 0",
|
|
23
21
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
24
|
-
"
|
|
22
|
+
"prepack": "tsc"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"typescript": ">=4.9.5",
|
|
26
|
+
"@types/node": "20.11.5",
|
|
27
|
+
"@types/uuid": "^9.0.4"
|
|
25
28
|
},
|
|
26
29
|
"bin": {
|
|
27
|
-
"rbt": "./rbt.js"
|
|
30
|
+
"rbt": "./rbt.js",
|
|
31
|
+
"rbt-esbuild": "./rbt-esbuild.js",
|
|
32
|
+
"protoc-gen-es": "./protoc-gen-es.js"
|
|
28
33
|
},
|
|
29
34
|
"engines": {
|
|
30
35
|
"node": ">=18.0.0"
|
|
@@ -34,22 +39,27 @@
|
|
|
34
39
|
"author": "reboot-dev",
|
|
35
40
|
"license": "Apache-2.0",
|
|
36
41
|
"files": [
|
|
42
|
+
"binding.gyp",
|
|
37
43
|
"include",
|
|
38
44
|
"internal",
|
|
39
|
-
"utils",
|
|
40
|
-
"binding.gyp",
|
|
41
45
|
"package.json",
|
|
46
|
+
"protoc-gen-es.d.ts",
|
|
47
|
+
"protoc-gen-es.js",
|
|
48
|
+
"rbt.d.ts",
|
|
49
|
+
"rbt-esbuild.d.ts",
|
|
50
|
+
"rbt-esbuild.js",
|
|
51
|
+
"rbt.js",
|
|
52
|
+
"reboot.d.ts",
|
|
53
|
+
"reboot.js",
|
|
42
54
|
"reboot_native.cc",
|
|
43
55
|
"reboot_native.cjs",
|
|
44
56
|
"reboot_native.d.ts",
|
|
45
|
-
"
|
|
46
|
-
"reboot.d.ts",
|
|
47
|
-
"rbt.js",
|
|
48
|
-
"rbt.d.ts",
|
|
49
|
-
"venv.js",
|
|
57
|
+
"utils",
|
|
50
58
|
"venv.d.ts",
|
|
59
|
+
"venv.js",
|
|
60
|
+
"version.d.ts",
|
|
51
61
|
"version.js",
|
|
52
|
-
"
|
|
62
|
+
"workspaces.js"
|
|
53
63
|
],
|
|
54
64
|
"exports": {
|
|
55
65
|
"./package.json": "./package.json",
|
package/protoc-gen-es.js
ADDED
package/rbt-esbuild.d.ts
ADDED
package/rbt-esbuild.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import esbuild from "esbuild";
|
|
4
|
+
import { writeFileSync } from "node:fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { locateWorkspaces } from "./workspaces.js";
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
// Expected usage: rbt-esbuild path/to/application.ts name
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
const application = args[0];
|
|
13
|
+
const name = args[1];
|
|
14
|
+
export const BUNDLE_PATH = path.join(__dirname, ".bundles", name);
|
|
15
|
+
let workspaces = [];
|
|
16
|
+
try {
|
|
17
|
+
workspaces = (await locateWorkspaces()).map(({ name }) => name);
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
console.error(chalk.stderr.bold.red(`Failed to use your package manager to determine your workspaces (if any): ${e}`));
|
|
21
|
+
process.exit(-1);
|
|
22
|
+
}
|
|
23
|
+
const plugin = {
|
|
24
|
+
name: "rbt-esbuild",
|
|
25
|
+
setup(build) {
|
|
26
|
+
build.onResolve({ namespace: "file", filter: /.*/ }, (args) => {
|
|
27
|
+
// Do not mark as 'external' files starting with '.' or '/' because those
|
|
28
|
+
// are assumed to be local.
|
|
29
|
+
if (args.path.startsWith(".") || args.path.startsWith("/")) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
// Workspace modules are also local, not external.
|
|
33
|
+
for (const workspace of workspaces) {
|
|
34
|
+
if (args.path === workspace || args.path.startsWith(workspace + "/")) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// Mark everything else external.
|
|
39
|
+
return { path: args.path, external: true };
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
esbuild
|
|
44
|
+
.build({
|
|
45
|
+
entryPoints: [application],
|
|
46
|
+
bundle: true,
|
|
47
|
+
platform: "node",
|
|
48
|
+
format: "esm",
|
|
49
|
+
metafile: true,
|
|
50
|
+
sourcemap: "inline",
|
|
51
|
+
banner: {
|
|
52
|
+
js: "/* eslint-disable */",
|
|
53
|
+
},
|
|
54
|
+
outfile: path.join(BUNDLE_PATH, "bundle.js"),
|
|
55
|
+
plugins: [plugin],
|
|
56
|
+
// This is only called by `rbt dev` and thus should only do module
|
|
57
|
+
// resolution for "development".
|
|
58
|
+
conditions: ["development"],
|
|
59
|
+
// TODO: support taking either `esbuild.config.js` or a more
|
|
60
|
+
// general `rbt.config.js` which would export a default object
|
|
61
|
+
// with options to pass on for specific esbuild, for example:
|
|
62
|
+
//
|
|
63
|
+
// export default {
|
|
64
|
+
// esbuild: {
|
|
65
|
+
// conditions: ["something"]
|
|
66
|
+
// }
|
|
67
|
+
// };
|
|
68
|
+
//
|
|
69
|
+
// We'll likely need to have users pass us the path to this file
|
|
70
|
+
// via some flag like
|
|
71
|
+
// `--esbuild-config=path/to/esbuild.config.js` or
|
|
72
|
+
// `--nodejs-config=path/to/rbt.config.js`.
|
|
73
|
+
//
|
|
74
|
+
// We could try to find the file but it's not obvious where to
|
|
75
|
+
// look. We could look for it based on `.rbtrc` or
|
|
76
|
+
// `--state-directory` (which ever was specified) but it's not
|
|
77
|
+
// obvious it should always be there, e.g., in the case of a
|
|
78
|
+
// monorepo with a `.rbtrc` that has "config" based flags.
|
|
79
|
+
//
|
|
80
|
+
// Once we have the file we can support overriding, for example:
|
|
81
|
+
//
|
|
82
|
+
// ...("conditions" in config.esbuild && { conditions: config.esbuild.conditions } || {}),
|
|
83
|
+
})
|
|
84
|
+
.then(async (result) => {
|
|
85
|
+
writeFileSync(path.join(BUNDLE_PATH, "meta.json"), JSON.stringify(result.metafile));
|
|
86
|
+
// Output the path where we bundled for `rbt dev` to consume.
|
|
87
|
+
console.log(BUNDLE_PATH);
|
|
88
|
+
process.exit(0);
|
|
89
|
+
})
|
|
90
|
+
.catch((error) => {
|
|
91
|
+
console.error(error instanceof Error ? error.message : error);
|
|
92
|
+
process.exit(-1);
|
|
93
|
+
});
|
package/rbt.js
CHANGED
|
@@ -1,11 +1,48 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "child_process";
|
|
3
3
|
import * as path from "path";
|
|
4
|
+
import whichPMRuns from "which-pm-runs";
|
|
5
|
+
import { ensureYarnNodeLinker } from "./utils/index.js";
|
|
4
6
|
import { ensurePythonVenv, VENV_EXEC_PATH } from "./venv.js";
|
|
7
|
+
async function ensureSupportedYarn() {
|
|
8
|
+
const packageManager = await whichPMRuns();
|
|
9
|
+
if (!packageManager) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (packageManager.name === "yarn") {
|
|
13
|
+
ensureYarnNodeLinker();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function addExtensionlessToNodeOptions() {
|
|
17
|
+
// Add the `extensionless` loader to the `NODE_OPTIONS` env var so
|
|
18
|
+
// subsequent invocations of Node.js will use it. Depending on which
|
|
19
|
+
// version of Node.js we're using we have to add it differently.
|
|
20
|
+
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
21
|
+
// Make sure that we can append to the `NODE_OPTIONS` env var.
|
|
22
|
+
process.env.NODE_OPTIONS =
|
|
23
|
+
(process.env.NODE_OPTIONS && process.env.NODE_OPTIONS + " ") || "";
|
|
24
|
+
// The `module.register()` function was added to Node.js in 20.6.0
|
|
25
|
+
// for the main release line and 18.19.0 for the LTS release line.
|
|
26
|
+
//
|
|
27
|
+
// If we have one of those two versions then we can pre import
|
|
28
|
+
// `extensionless/register` to use the `module.register()` function,
|
|
29
|
+
// otherwise we need to fall back to `--experimental-loader`.
|
|
30
|
+
if (major > 20 ||
|
|
31
|
+
(major == 20 && minor >= 6) ||
|
|
32
|
+
(major == 18 && minor >= 19)) {
|
|
33
|
+
process.env.NODE_OPTIONS += "--import=extensionless/register";
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
process.env.NODE_OPTIONS += "--experimental-loader=extensionless";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
5
39
|
async function main() {
|
|
6
40
|
ensurePythonVenv();
|
|
41
|
+
await ensureSupportedYarn();
|
|
7
42
|
// Set env var to indicate that `rbt` is being invoked from Node.js.
|
|
8
43
|
process.env.RBT_FROM_NODEJS = "true";
|
|
44
|
+
// Add extensionless loader.
|
|
45
|
+
addExtensionlessToNodeOptions();
|
|
9
46
|
const rbt = spawnSync(`${path.join(VENV_EXEC_PATH, "rbt")} ${process.argv.slice(2).join(" ")}`, {
|
|
10
47
|
stdio: [process.stdin, process.stdout, process.stderr],
|
|
11
48
|
shell: true,
|
package/reboot.d.ts
CHANGED
|
@@ -150,9 +150,10 @@ export declare class AllowAllIfAuthenticated extends Authorizer<protobuf_es.Mess
|
|
|
150
150
|
}
|
|
151
151
|
export declare class Application {
|
|
152
152
|
#private;
|
|
153
|
-
constructor({ servicers, initialize, tokenVerifier, }: {
|
|
153
|
+
constructor({ servicers, initialize, initializeBearerToken, tokenVerifier, }: {
|
|
154
154
|
servicers: ServicerFactory[];
|
|
155
155
|
initialize?: (context: ExternalContext) => Promise<void>;
|
|
156
|
+
initializeBearerToken?: string;
|
|
156
157
|
tokenVerifier?: TokenVerifier;
|
|
157
158
|
});
|
|
158
159
|
run(): Promise<any>;
|
package/reboot.js
CHANGED
|
@@ -21,15 +21,34 @@ export { reboot_native };
|
|
|
21
21
|
export * from "./utils/index.js";
|
|
22
22
|
const startedInstances = [];
|
|
23
23
|
if (process != null) {
|
|
24
|
-
const
|
|
25
|
-
if (startedInstances.length) {
|
|
26
|
-
console.warn(`${startedInstances.length} Reboot instance(s) still running on exit. Did you forget to run \`rbt.stop()\`?`);
|
|
24
|
+
const checkForStartedInstances = () => {
|
|
25
|
+
if (startedInstances.length > 0) {
|
|
26
|
+
console.warn(`${startedInstances.length} Reboot instance(s) still running on exit which may keep your process from exiting. Did you forget to run \`rbt.stop()\`?`);
|
|
27
27
|
}
|
|
28
|
-
process.exit(0);
|
|
29
28
|
};
|
|
30
|
-
process.on("exit",
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
process.on("exit", (code) => {
|
|
30
|
+
checkForStartedInstances();
|
|
31
|
+
});
|
|
32
|
+
const checkIfNoOtherListenersAndIfSoExit = (signal, code) => {
|
|
33
|
+
// Check if there is only one listener for this signal which would
|
|
34
|
+
// imply just us and if so then we need to perform the _default_
|
|
35
|
+
// act of calling `process.exit()`.
|
|
36
|
+
if (process.listeners(signal).length === 1) {
|
|
37
|
+
process.exit(code);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
process.on("SIGTERM", () => {
|
|
41
|
+
checkForStartedInstances();
|
|
42
|
+
// NOTE: empirical testing shows that a Node.js process that
|
|
43
|
+
// exits due to a SIGTERM will exit with a code of 143.
|
|
44
|
+
checkIfNoOtherListenersAndIfSoExit("SIGTERM", 143);
|
|
45
|
+
});
|
|
46
|
+
process.on("SIGINT", () => {
|
|
47
|
+
checkForStartedInstances();
|
|
48
|
+
// NOTE: empirical testing shows that a Node.js process that
|
|
49
|
+
// exits due to a SIGINT will exit with a code of 130.
|
|
50
|
+
checkIfNoOtherListenersAndIfSoExit("SIGINT", 130);
|
|
51
|
+
});
|
|
33
52
|
}
|
|
34
53
|
export class Reboot {
|
|
35
54
|
constructor() {
|
|
@@ -305,7 +324,7 @@ export class AllowAllIfAuthenticated extends Authorizer {
|
|
|
305
324
|
}
|
|
306
325
|
}
|
|
307
326
|
export class Application {
|
|
308
|
-
constructor({ servicers, initialize, tokenVerifier, }) {
|
|
327
|
+
constructor({ servicers, initialize, initializeBearerToken, tokenVerifier, }) {
|
|
309
328
|
_Application_external.set(this, void 0);
|
|
310
329
|
__classPrivateFieldSet(this, _Application_external, reboot_native.Application_constructor(ExternalContext.fromNativeExternal, servicers, async (context) => {
|
|
311
330
|
if (initialize !== undefined) {
|
|
@@ -321,7 +340,7 @@ export class Application {
|
|
|
321
340
|
}
|
|
322
341
|
}
|
|
323
342
|
}
|
|
324
|
-
}, tokenVerifier), "f");
|
|
343
|
+
}, initializeBearerToken, tokenVerifier), "f");
|
|
325
344
|
}
|
|
326
345
|
async run() {
|
|
327
346
|
return await reboot_native.Application_run(__classPrivateFieldGet(this, _Application_external, "f"));
|
package/reboot_native.cc
CHANGED
|
@@ -1177,13 +1177,11 @@ py::object make_py_token_verifier(NapiSafeObjectReference js_token_verifier) {
|
|
|
1177
1177
|
{Napi::Function::New(
|
|
1178
1178
|
env,
|
|
1179
1179
|
[py_future](const Napi::CallbackInfo& info) {
|
|
1180
|
-
Napi::Object js_bytes_auth =
|
|
1181
|
-
info[0].As<Napi::Object>();
|
|
1182
1180
|
std::optional<std::string> bytes_auth;
|
|
1183
|
-
if (!
|
|
1181
|
+
if (!info[0].IsNull()) {
|
|
1184
1182
|
bytes_auth =
|
|
1185
1183
|
uint8array_to_str(
|
|
1186
|
-
|
|
1184
|
+
info[0].As<Napi::Uint8Array>());
|
|
1187
1185
|
}
|
|
1188
1186
|
|
|
1189
1187
|
adaptor->ScheduleCallbackOnPythonEventLoop(
|
|
@@ -1592,7 +1590,7 @@ Napi::Value Service_call(const Napi::CallbackInfo& info) {
|
|
|
1592
1590
|
|
|
1593
1591
|
py::object py_task =
|
|
1594
1592
|
py::module::import("reboot.nodejs.python")
|
|
1595
|
-
.attr("
|
|
1593
|
+
.attr("create_task_with_context")(
|
|
1596
1594
|
py_service->attr(("_" + kind).c_str())(
|
|
1597
1595
|
method,
|
|
1598
1596
|
py_context,
|
|
@@ -1687,7 +1685,7 @@ Napi::Value Future_await(const Napi::CallbackInfo& info) {
|
|
|
1687
1685
|
deferred = std::move(deferred)]() {
|
|
1688
1686
|
py::object py_task =
|
|
1689
1687
|
py::module::import("reboot.nodejs.python")
|
|
1690
|
-
.attr("
|
|
1688
|
+
.attr("create_task_with_context")(
|
|
1691
1689
|
py_service->attr("_future_await")(
|
|
1692
1690
|
method,
|
|
1693
1691
|
py_context,
|
|
@@ -1837,9 +1835,14 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
|
|
|
1837
1835
|
auto js_initialize = NapiSafeFunctionReference(
|
|
1838
1836
|
info[2].As<Napi::Function>());
|
|
1839
1837
|
|
|
1838
|
+
std::optional<std::string> initialize_bearer_token;
|
|
1839
|
+
if (!info[3].IsUndefined()) {
|
|
1840
|
+
initialize_bearer_token = info[3].As<Napi::String>().Utf8Value();
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1840
1843
|
std::optional<NapiSafeObjectReference> js_token_verifier;
|
|
1841
|
-
if (info[
|
|
1842
|
-
js_token_verifier = NapiSafeReference(info[
|
|
1844
|
+
if (!info[4].IsUndefined()) {
|
|
1845
|
+
js_token_verifier = NapiSafeReference(info[4].As<Napi::Object>());
|
|
1843
1846
|
}
|
|
1844
1847
|
|
|
1845
1848
|
std::promise<py::object*> promise;
|
|
@@ -1847,6 +1850,7 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
|
|
|
1847
1850
|
adaptor->ScheduleCallbackOnPythonEventLoop(
|
|
1848
1851
|
[&promise,
|
|
1849
1852
|
servicer_details = std::move(servicer_details),
|
|
1853
|
+
initialize_bearer_token = std::move(initialize_bearer_token),
|
|
1850
1854
|
js_initialize = std::move(js_initialize),
|
|
1851
1855
|
js_token_verifier,
|
|
1852
1856
|
js_from_native_external = std::move(js_from_native_external)]() {
|
|
@@ -1943,8 +1947,10 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
|
|
|
1943
1947
|
return py_future;
|
|
1944
1948
|
});
|
|
1945
1949
|
|
|
1946
|
-
py::object
|
|
1947
|
-
|
|
1950
|
+
py::object py_initialize_bearer_token = py::none();
|
|
1951
|
+
if (initialize_bearer_token.has_value()) {
|
|
1952
|
+
py_initialize_bearer_token = py::str(*initialize_bearer_token);
|
|
1953
|
+
}
|
|
1948
1954
|
|
|
1949
1955
|
py::object py_token_verifier = py::none();
|
|
1950
1956
|
if (js_token_verifier.has_value()) {
|
|
@@ -1952,10 +1958,14 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
|
|
|
1952
1958
|
}
|
|
1953
1959
|
|
|
1954
1960
|
promise.set_value(
|
|
1955
|
-
new py::object(
|
|
1956
|
-
"
|
|
1957
|
-
|
|
1958
|
-
|
|
1961
|
+
new py::object(
|
|
1962
|
+
py::module::import("reboot.aio.applications")
|
|
1963
|
+
.attr("Application")(
|
|
1964
|
+
"servicers"_a = py_servicers,
|
|
1965
|
+
"initialize"_a = py_initialize,
|
|
1966
|
+
"initialize_bearer_token"_a =
|
|
1967
|
+
py_initialize_bearer_token,
|
|
1968
|
+
"token_verifier"_a = py_token_verifier)));
|
|
1959
1969
|
}
|
|
1960
1970
|
// TODO(benh): improve error handling mechanism to force all
|
|
1961
1971
|
// raised exceptions to be handled.
|
|
@@ -2016,9 +2026,10 @@ Napi::Value Application_run(const Napi::CallbackInfo& info) {
|
|
|
2016
2026
|
py_application,
|
|
2017
2027
|
deferred = std::move(deferred)]() {
|
|
2018
2028
|
py::object py_task =
|
|
2019
|
-
py::module::import("
|
|
2020
|
-
|
|
2021
|
-
|
|
2029
|
+
py::module::import("reboot.nodejs.python")
|
|
2030
|
+
.attr("create_task")(
|
|
2031
|
+
py_application->attr("run")(),
|
|
2032
|
+
"name"_a = "Application.run() in nodejs");
|
|
2022
2033
|
|
|
2023
2034
|
py_task.attr("add_done_callback")(py::cpp_function(
|
|
2024
2035
|
[deferred = std::move(deferred),
|
|
@@ -2041,9 +2052,16 @@ Napi::Value Application_run(const Napi::CallbackInfo& info) {
|
|
|
2041
2052
|
if (!exception.has_value()) {
|
|
2042
2053
|
deferred->Resolve(env.Null());
|
|
2043
2054
|
} else {
|
|
2055
|
+
env.Global()
|
|
2056
|
+
.Get("process")
|
|
2057
|
+
.As<Napi::Object>()
|
|
2058
|
+
.Set("exitCode", 1);
|
|
2044
2059
|
deferred->Reject(
|
|
2045
2060
|
Napi::Error::New(env, *exception).Value());
|
|
2046
2061
|
}
|
|
2062
|
+
// When `Application.run` exits, we unref our
|
|
2063
|
+
// thread_safe_function to cause the runtime to exit.
|
|
2064
|
+
adaptor->thread_safe_function.Unref(env);
|
|
2047
2065
|
});
|
|
2048
2066
|
|
|
2049
2067
|
delete py_task;
|
|
@@ -2340,7 +2358,7 @@ Napi::Value retry_reactively_until(const Napi::CallbackInfo& info) {
|
|
|
2340
2358
|
|
|
2341
2359
|
py::object py_task =
|
|
2342
2360
|
py::module::import("reboot.nodejs.python")
|
|
2343
|
-
.attr("
|
|
2361
|
+
.attr("create_task_with_context")(
|
|
2344
2362
|
(py::module::import("reboot.aio.contexts")
|
|
2345
2363
|
.attr("retry_reactively_until"))(
|
|
2346
2364
|
py_context,
|
|
@@ -2476,7 +2494,7 @@ Napi::Value atLeastOrMostOnce(const Napi::CallbackInfo& info) {
|
|
|
2476
2494
|
|
|
2477
2495
|
py::object py_task =
|
|
2478
2496
|
py::module::import("reboot.nodejs.python")
|
|
2479
|
-
.attr("
|
|
2497
|
+
.attr("create_task_with_context")(
|
|
2480
2498
|
(py::module::import("reboot.aio.memoize").attr("memoize"))(
|
|
2481
2499
|
py::str(idempotency_alias),
|
|
2482
2500
|
py_context,
|
|
@@ -2553,7 +2571,7 @@ Napi::Value Servicer_read(const Napi::CallbackInfo& info) {
|
|
|
2553
2571
|
deferred = std::move(deferred)]() {
|
|
2554
2572
|
py::object py_task =
|
|
2555
2573
|
py::module::import("reboot.nodejs.python")
|
|
2556
|
-
.attr("
|
|
2574
|
+
.attr("create_task_with_context")(
|
|
2557
2575
|
py_servicer->attr("_read")(py_context),
|
|
2558
2576
|
py_context,
|
|
2559
2577
|
"name"_a = "servicer._read(...) in nodejs");
|
|
@@ -2702,7 +2720,7 @@ Napi::Value Servicer_write(const Napi::CallbackInfo& info) {
|
|
|
2702
2720
|
|
|
2703
2721
|
py::object py_task =
|
|
2704
2722
|
py::module::import("reboot.nodejs.python")
|
|
2705
|
-
.attr("
|
|
2723
|
+
.attr("create_task_with_context")(
|
|
2706
2724
|
py_servicer->attr("_write")(
|
|
2707
2725
|
py_context,
|
|
2708
2726
|
py_writer,
|
package/reboot_native.cjs
CHANGED
|
@@ -7,7 +7,7 @@ const [major, minor, patch] = process.versions.node.split(".").map(Number);
|
|
|
7
7
|
|
|
8
8
|
if (major < 18) {
|
|
9
9
|
console.error(
|
|
10
|
-
chalk.red(
|
|
10
|
+
chalk.stderr.bold.red(
|
|
11
11
|
`Reboot requires nodejs version >=18 (found ${major}.${minor}.${patch})`
|
|
12
12
|
)
|
|
13
13
|
);
|
|
@@ -16,21 +16,16 @@ if (major < 18) {
|
|
|
16
16
|
|
|
17
17
|
const reboot_native = { exports: {} };
|
|
18
18
|
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
// TODO: find a more permanent solution for this.
|
|
24
|
-
let node_modules_reboot_directory;
|
|
19
|
+
// If we are in a Next.js context, check that serverExternalPackages is
|
|
20
|
+
// correctly set. If not correctly set, Reboot backend TS code will not work
|
|
21
|
+
// with React Server Components inside Next.
|
|
22
|
+
|
|
25
23
|
if (__dirname.includes(".next")) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"reboot"
|
|
24
|
+
throw new Error(
|
|
25
|
+
"For Next.js to work correctly with Reboot native code, you " +
|
|
26
|
+
"must include option 'serverExternalPackages: ['@reboot-dev/reboot']' " +
|
|
27
|
+
"in your next.config.ts"
|
|
31
28
|
);
|
|
32
|
-
} else {
|
|
33
|
-
node_modules_reboot_directory = __dirname;
|
|
34
29
|
}
|
|
35
30
|
|
|
36
31
|
// NOTE: as of Python 3.8 we _must_ load the Python library via
|
|
@@ -41,12 +36,7 @@ process.dlopen(
|
|
|
41
36
|
reboot_native,
|
|
42
37
|
// TODO(benh): consider using `require(bindings)` to properly locate
|
|
43
38
|
// 'reboot_native.node'.
|
|
44
|
-
path.join(
|
|
45
|
-
node_modules_reboot_directory,
|
|
46
|
-
"build",
|
|
47
|
-
"Release",
|
|
48
|
-
"reboot_native.node"
|
|
49
|
-
),
|
|
39
|
+
path.join(__dirname, "build", "Release", "reboot_native.node"),
|
|
50
40
|
os.constants.dlopen.RTLD_NOW | os.constants.dlopen.RTLD_GLOBAL
|
|
51
41
|
);
|
|
52
42
|
|
package/utils/index.d.ts
CHANGED
|
@@ -1 +1,12 @@
|
|
|
1
1
|
export * from "./errors.js";
|
|
2
|
+
export declare function parseVersion(version: string): [number, number, number];
|
|
3
|
+
export type Version = [number, number, number];
|
|
4
|
+
export declare function supportedVersion({ required: [requiredMajor, requiredMinor, requiredPatch], found: [foundMajor, foundMinor, foundPatch], }: {
|
|
5
|
+
required: Version;
|
|
6
|
+
found: Version;
|
|
7
|
+
}): boolean;
|
|
8
|
+
export declare function ensureYarnNodeLinker(): void;
|
|
9
|
+
/**
|
|
10
|
+
* Spawn a process with the given arguments, and return its stdout as a string.
|
|
11
|
+
*/
|
|
12
|
+
export declare function spawnAndReturnStdout(command: string, args: string[]): string;
|
package/utils/index.js
CHANGED
|
@@ -1 +1,47 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { spawnSync } from "child_process";
|
|
1
3
|
export * from "./errors.js";
|
|
4
|
+
export function parseVersion(version) {
|
|
5
|
+
const match = version.trim().match(/(\d+\.\d+\.\d+)/);
|
|
6
|
+
if (!match) {
|
|
7
|
+
throw new Error(`Failed to parse '${version}' into a version`);
|
|
8
|
+
}
|
|
9
|
+
return version.split(".").map(Number);
|
|
10
|
+
}
|
|
11
|
+
export function supportedVersion({ required: [requiredMajor, requiredMinor, requiredPatch], found: [foundMajor, foundMinor, foundPatch], }) {
|
|
12
|
+
return (foundMajor > requiredMajor ||
|
|
13
|
+
(foundMajor === requiredMajor && foundMinor > requiredMinor) ||
|
|
14
|
+
(foundMajor === requiredMajor &&
|
|
15
|
+
foundMinor === requiredMinor &&
|
|
16
|
+
foundPatch >= requiredPatch));
|
|
17
|
+
}
|
|
18
|
+
export function ensureYarnNodeLinker() {
|
|
19
|
+
// Ensure that they aren't using Yarn Plug'n'Play which we don't yet
|
|
20
|
+
// support.
|
|
21
|
+
const nodeLinker = spawnAndReturnStdout("yarn", [
|
|
22
|
+
"config",
|
|
23
|
+
"get",
|
|
24
|
+
"nodeLinker",
|
|
25
|
+
]);
|
|
26
|
+
if (nodeLinker.trim() !== "node-modules") {
|
|
27
|
+
console.error(chalk.stderr.bold.red("Yarn Plug'n'Play is not yet supported, you must use 'node-modules' as your 'nodeLinker'"));
|
|
28
|
+
process.exit(-1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Spawn a process with the given arguments, and return its stdout as a string.
|
|
33
|
+
*/
|
|
34
|
+
export function spawnAndReturnStdout(command, args) {
|
|
35
|
+
const result = spawnSync(command, args, {
|
|
36
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
37
|
+
});
|
|
38
|
+
if (result.error) {
|
|
39
|
+
throw new Error(`Failed to run '${command} ${args.join(" ")}': ${result.error}\n` +
|
|
40
|
+
`\n` +
|
|
41
|
+
`Please report this bug to the maintainers!`);
|
|
42
|
+
}
|
|
43
|
+
else if (result.status !== 0) {
|
|
44
|
+
throw new Error(`Running '${command} ${args.join(" ")}' exited with status ${result.status}. Please report this bug including the output of that command (if any) to the maintainers!`);
|
|
45
|
+
}
|
|
46
|
+
return result.stdout.toString();
|
|
47
|
+
}
|
package/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const REBOOT_VERSION = "0.19.
|
|
1
|
+
export declare const REBOOT_VERSION = "0.19.2";
|
package/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const REBOOT_VERSION = "0.19.
|
|
1
|
+
export const REBOOT_VERSION = "0.19.2";
|
package/workspaces.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import whichPMRuns from "which-pm-runs";
|
|
2
|
+
import { parseVersion, spawnAndReturnStdout, supportedVersion, } from "./utils/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Returns package-manager Workspaces of which a project in the given directory
|
|
5
|
+
* is a member.
|
|
6
|
+
*/
|
|
7
|
+
export async function locateWorkspaces() {
|
|
8
|
+
const packageManager = await whichPMRuns();
|
|
9
|
+
if (!packageManager) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
// NOTE: These methods will error if they fail to extract
|
|
13
|
+
// workspaces, based on the assumption that `whichPMRuns` will do a
|
|
14
|
+
// reasonable job of detecting a package manager, otherwise throws
|
|
15
|
+
// an exception.
|
|
16
|
+
switch (packageManager.name) {
|
|
17
|
+
case "yarn": {
|
|
18
|
+
return extractYarnWorkspaces();
|
|
19
|
+
}
|
|
20
|
+
case "npm": {
|
|
21
|
+
return extractNpmWorkspaces();
|
|
22
|
+
}
|
|
23
|
+
case "pnpm": {
|
|
24
|
+
return extractPnpmWorkspaces();
|
|
25
|
+
}
|
|
26
|
+
default: {
|
|
27
|
+
throw new Error(`Unsupported package manager '${packageManager}'`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function extractYarnWorkspaces() {
|
|
32
|
+
// TODO: what is the actual minimum version we need?
|
|
33
|
+
const YARN_VERSION_REQUIRED = "4.0.0";
|
|
34
|
+
const required = parseVersion(YARN_VERSION_REQUIRED);
|
|
35
|
+
const found = parseVersion(spawnAndReturnStdout("yarn", ["--version"]));
|
|
36
|
+
if (!supportedVersion({ required, found })) {
|
|
37
|
+
throw new Error(`yarn version >=${YARN_VERSION_REQUIRED} is required, found ${found.join(".")}`);
|
|
38
|
+
}
|
|
39
|
+
const command = "yarn";
|
|
40
|
+
const args = ["workspaces", "list", "--json", "--recursive"];
|
|
41
|
+
// `yarn workspaces list --json` returns JSON lines, not complete
|
|
42
|
+
// JSON, so we have to do some extra parsing ourselves.
|
|
43
|
+
return spawnAndReturnStdout(command, args)
|
|
44
|
+
.trim()
|
|
45
|
+
.split("\n")
|
|
46
|
+
.map((line) => {
|
|
47
|
+
const { name } = JSON.parse(line.trim());
|
|
48
|
+
return { name };
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function extractNpmWorkspaces() {
|
|
52
|
+
// We need >=8.16.0 which is when `npm query` was introduced.
|
|
53
|
+
const NPM_VERSION_REQUIRED = "8.16.0";
|
|
54
|
+
const required = parseVersion(NPM_VERSION_REQUIRED);
|
|
55
|
+
const found = parseVersion(spawnAndReturnStdout("npm", ["--version"]));
|
|
56
|
+
if (!supportedVersion({ required, found })) {
|
|
57
|
+
throw new Error(`npm version >=${NPM_VERSION_REQUIRED} is required, found ${found.join(".")}`);
|
|
58
|
+
}
|
|
59
|
+
const command = "npm";
|
|
60
|
+
const args = ["query", ".workspace"];
|
|
61
|
+
const workspaces = spawnAndParseJSON(command, args).map(({ name }) => ({
|
|
62
|
+
name,
|
|
63
|
+
}));
|
|
64
|
+
// Include the package name as it might be used and it is consistent
|
|
65
|
+
// with both `yarn` and `pnpm` which always include the top-level
|
|
66
|
+
// package name even if they aren't explicitly in `workspaces`.
|
|
67
|
+
const name = spawnAndParseJSON("npm", ["pkg", "get", "name"]);
|
|
68
|
+
// `npm pkg get name` returns an empty object if there is no
|
|
69
|
+
// top-level package name.
|
|
70
|
+
if (Object.keys(name).length === 0) {
|
|
71
|
+
return workspaces;
|
|
72
|
+
}
|
|
73
|
+
else if (typeof name !== "string") {
|
|
74
|
+
throw new Error(`Failed to get name of your package. Please report this issue to the maintainers!`);
|
|
75
|
+
}
|
|
76
|
+
return [{ name }, ...workspaces];
|
|
77
|
+
}
|
|
78
|
+
function extractPnpmWorkspaces() {
|
|
79
|
+
// TODO: is there a minimum version required?
|
|
80
|
+
const command = "pnpm";
|
|
81
|
+
const args = ["list", "--recursive", "--depth", "-1", "--only-projects"];
|
|
82
|
+
// `pnpm list --recursive --depth -1 --only-projects --json` emits something
|
|
83
|
+
// which is not quite valid JSON: closer to JSON lines, but not easily splittable.
|
|
84
|
+
//
|
|
85
|
+
// Instead, we parse the human-readable output -- a series of lines like:
|
|
86
|
+
// @reboot-pm/prosemirror@0.0.1 /Users/example/src/repo_root/project_dir (PRIVATE)
|
|
87
|
+
return spawnAndReturnStdout(command, args)
|
|
88
|
+
.trim()
|
|
89
|
+
.split("\n")
|
|
90
|
+
.map((line) => {
|
|
91
|
+
const parts = line.trim().split(" ");
|
|
92
|
+
if (parts.length !== 3) {
|
|
93
|
+
throw new Error(`Unexpected output from '${command} ${args.join(" ")}'. Please report the output of that command to the maintainers!`);
|
|
94
|
+
}
|
|
95
|
+
// Strip off the version from the name.
|
|
96
|
+
const lastAtSign = parts[0].lastIndexOf("@");
|
|
97
|
+
let name;
|
|
98
|
+
if (lastAtSign !== -1 && lastAtSign !== 0) {
|
|
99
|
+
name = parts[0].substring(0, lastAtSign);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
name = parts[0];
|
|
103
|
+
}
|
|
104
|
+
return { name };
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Spawn a process with the given arguments, and parse its stdout as JSON.
|
|
109
|
+
*
|
|
110
|
+
* Raises an error if the process cannot be spawned, or if JSON cannot be parsed.
|
|
111
|
+
*/
|
|
112
|
+
function spawnAndParseJSON(command, args) {
|
|
113
|
+
const stdout = spawnAndReturnStdout(command, args);
|
|
114
|
+
try {
|
|
115
|
+
return JSON.parse(stdout);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
throw new Error(`Failed to parse output of '${command} ${args.join(" ")}' as JSON: ${error}`);
|
|
119
|
+
}
|
|
120
|
+
}
|