@reboot-dev/reboot 0.18.1 → 0.19.0-alpha
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 +5 -5
- package/reboot.js +34 -11
- package/reboot_native.cc +43 -32
- package/reboot_native.cjs +19 -20
- package/reboot_native.d.ts +1 -2
- 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.
|
|
15
|
-
"
|
|
6
|
+
"@reboot-dev/reboot-api": "0.19.0-alpha",
|
|
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.
|
|
17
|
+
"version": "0.19.0-alpha",
|
|
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
|
@@ -25,8 +25,7 @@ export declare class ExternalContext {
|
|
|
25
25
|
#private;
|
|
26
26
|
constructor(args: {
|
|
27
27
|
name: string;
|
|
28
|
-
|
|
29
|
-
secureChannel?: boolean;
|
|
28
|
+
url?: string;
|
|
30
29
|
bearerToken?: string;
|
|
31
30
|
idempotencySeed?: string;
|
|
32
31
|
idempotencyRequired?: boolean;
|
|
@@ -89,10 +88,10 @@ export type ServicerFactory = {
|
|
|
89
88
|
*/
|
|
90
89
|
export declare class Auth {
|
|
91
90
|
userId?: string;
|
|
92
|
-
properties:
|
|
91
|
+
properties: protobuf_es.JsonValue;
|
|
93
92
|
constructor(options?: {
|
|
94
93
|
userId?: string;
|
|
95
|
-
properties?:
|
|
94
|
+
properties?: protobuf_es.JsonValue;
|
|
96
95
|
});
|
|
97
96
|
toProtoBytes(): Uint8Array;
|
|
98
97
|
static fromProtoBytes(bytes: Uint8Array): Auth;
|
|
@@ -151,9 +150,10 @@ export declare class AllowAllIfAuthenticated extends Authorizer<protobuf_es.Mess
|
|
|
151
150
|
}
|
|
152
151
|
export declare class Application {
|
|
153
152
|
#private;
|
|
154
|
-
constructor({ servicers, initialize, tokenVerifier, }: {
|
|
153
|
+
constructor({ servicers, initialize, initializeBearerToken, tokenVerifier, }: {
|
|
155
154
|
servicers: ServicerFactory[];
|
|
156
155
|
initialize?: (context: ExternalContext) => Promise<void>;
|
|
156
|
+
initializeBearerToken?: string;
|
|
157
157
|
tokenVerifier?: TokenVerifier;
|
|
158
158
|
});
|
|
159
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() {
|
|
@@ -76,7 +95,11 @@ export class ExternalContext {
|
|
|
76
95
|
__classPrivateFieldSet(this, _ExternalContext_external, args.external, "f");
|
|
77
96
|
}
|
|
78
97
|
else {
|
|
79
|
-
|
|
98
|
+
if (!args.url || !args.url.startsWith("http")) {
|
|
99
|
+
throw new Error("`ExternalContext` must be constructed with a `url` " +
|
|
100
|
+
"including an explicit 'http' or 'https' protocol");
|
|
101
|
+
}
|
|
102
|
+
__classPrivateFieldSet(this, _ExternalContext_external, reboot_native.ExternalContext_constructor(args.name, args.url, args.bearerToken, args.idempotencySeed, args.idempotencyRequired, args.idempotencyRequiredReason), "f");
|
|
80
103
|
}
|
|
81
104
|
}
|
|
82
105
|
static fromNativeExternal(external) {
|
|
@@ -254,7 +277,7 @@ export class Auth {
|
|
|
254
277
|
toProtoBytes() {
|
|
255
278
|
const auth = new auth_pb.Auth({
|
|
256
279
|
userId: this.userId,
|
|
257
|
-
properties: this.properties,
|
|
280
|
+
properties: protobuf_es.Struct.fromJson(this.properties),
|
|
258
281
|
});
|
|
259
282
|
return auth.toBinary();
|
|
260
283
|
}
|
|
@@ -301,7 +324,7 @@ export class AllowAllIfAuthenticated extends Authorizer {
|
|
|
301
324
|
}
|
|
302
325
|
}
|
|
303
326
|
export class Application {
|
|
304
|
-
constructor({ servicers, initialize, tokenVerifier, }) {
|
|
327
|
+
constructor({ servicers, initialize, initializeBearerToken, tokenVerifier, }) {
|
|
305
328
|
_Application_external.set(this, void 0);
|
|
306
329
|
__classPrivateFieldSet(this, _Application_external, reboot_native.Application_constructor(ExternalContext.fromNativeExternal, servicers, async (context) => {
|
|
307
330
|
if (initialize !== undefined) {
|
|
@@ -317,7 +340,7 @@ export class Application {
|
|
|
317
340
|
}
|
|
318
341
|
}
|
|
319
342
|
}
|
|
320
|
-
}, tokenVerifier), "f");
|
|
343
|
+
}, initializeBearerToken, tokenVerifier), "f");
|
|
321
344
|
}
|
|
322
345
|
async run() {
|
|
323
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,
|
|
@@ -1738,14 +1736,9 @@ Napi::Value Future_await(const Napi::CallbackInfo& info) {
|
|
|
1738
1736
|
Napi::Value ExternalContext_constructor(const Napi::CallbackInfo& info) {
|
|
1739
1737
|
std::string name = info[0].As<Napi::String>().Utf8Value();
|
|
1740
1738
|
|
|
1741
|
-
std::optional<std::string>
|
|
1739
|
+
std::optional<std::string> url;
|
|
1742
1740
|
if (!info[1].IsUndefined()) {
|
|
1743
|
-
|
|
1744
|
-
}
|
|
1745
|
-
|
|
1746
|
-
std::optional<bool> secure_channel;
|
|
1747
|
-
if (!info[2].IsUndefined()) {
|
|
1748
|
-
secure_channel = info[2].As<Napi::Boolean>();
|
|
1741
|
+
url = info[1].As<Napi::String>().Utf8Value();
|
|
1749
1742
|
}
|
|
1750
1743
|
|
|
1751
1744
|
std::optional<std::string> bearer_token;
|
|
@@ -1776,8 +1769,7 @@ Napi::Value ExternalContext_constructor(const Napi::CallbackInfo& info) {
|
|
|
1776
1769
|
adaptor->ScheduleCallbackOnPythonEventLoop(
|
|
1777
1770
|
[&promise,
|
|
1778
1771
|
&name,
|
|
1779
|
-
&
|
|
1780
|
-
&secure_channel,
|
|
1772
|
+
&url,
|
|
1781
1773
|
&bearer_token,
|
|
1782
1774
|
&idempotency_seed,
|
|
1783
1775
|
&idempotency_required,
|
|
@@ -1806,8 +1798,7 @@ Napi::Value ExternalContext_constructor(const Napi::CallbackInfo& info) {
|
|
|
1806
1798
|
promise.set_value(
|
|
1807
1799
|
new py::object(py_external.attr("ExternalContext")(
|
|
1808
1800
|
"name"_a = py::str(name),
|
|
1809
|
-
"
|
|
1810
|
-
"secure_channel"_a = convert_bool(secure_channel),
|
|
1801
|
+
"url"_a = convert_str(url),
|
|
1811
1802
|
"bearer_token"_a = convert_str(bearer_token),
|
|
1812
1803
|
"idempotency_seed"_a = convert_str(idempotency_seed),
|
|
1813
1804
|
"idempotency_required"_a = py::bool_(idempotency_required),
|
|
@@ -1844,9 +1835,14 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
|
|
|
1844
1835
|
auto js_initialize = NapiSafeFunctionReference(
|
|
1845
1836
|
info[2].As<Napi::Function>());
|
|
1846
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
|
+
|
|
1847
1843
|
std::optional<NapiSafeObjectReference> js_token_verifier;
|
|
1848
|
-
if (info[
|
|
1849
|
-
js_token_verifier = NapiSafeReference(info[
|
|
1844
|
+
if (!info[4].IsUndefined()) {
|
|
1845
|
+
js_token_verifier = NapiSafeReference(info[4].As<Napi::Object>());
|
|
1850
1846
|
}
|
|
1851
1847
|
|
|
1852
1848
|
std::promise<py::object*> promise;
|
|
@@ -1854,6 +1850,7 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
|
|
|
1854
1850
|
adaptor->ScheduleCallbackOnPythonEventLoop(
|
|
1855
1851
|
[&promise,
|
|
1856
1852
|
servicer_details = std::move(servicer_details),
|
|
1853
|
+
initialize_bearer_token = std::move(initialize_bearer_token),
|
|
1857
1854
|
js_initialize = std::move(js_initialize),
|
|
1858
1855
|
js_token_verifier,
|
|
1859
1856
|
js_from_native_external = std::move(js_from_native_external)]() {
|
|
@@ -1950,8 +1947,10 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
|
|
|
1950
1947
|
return py_future;
|
|
1951
1948
|
});
|
|
1952
1949
|
|
|
1953
|
-
py::object
|
|
1954
|
-
|
|
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
|
+
}
|
|
1955
1954
|
|
|
1956
1955
|
py::object py_token_verifier = py::none();
|
|
1957
1956
|
if (js_token_verifier.has_value()) {
|
|
@@ -1959,10 +1958,14 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
|
|
|
1959
1958
|
}
|
|
1960
1959
|
|
|
1961
1960
|
promise.set_value(
|
|
1962
|
-
new py::object(
|
|
1963
|
-
"
|
|
1964
|
-
|
|
1965
|
-
|
|
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)));
|
|
1966
1969
|
}
|
|
1967
1970
|
// TODO(benh): improve error handling mechanism to force all
|
|
1968
1971
|
// raised exceptions to be handled.
|
|
@@ -2023,9 +2026,10 @@ Napi::Value Application_run(const Napi::CallbackInfo& info) {
|
|
|
2023
2026
|
py_application,
|
|
2024
2027
|
deferred = std::move(deferred)]() {
|
|
2025
2028
|
py::object py_task =
|
|
2026
|
-
py::module::import("
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
+
py::module::import("reboot.nodejs.python")
|
|
2030
|
+
.attr("create_task")(
|
|
2031
|
+
py_application->attr("run")(),
|
|
2032
|
+
"name"_a = "Application.run() in nodejs");
|
|
2029
2033
|
|
|
2030
2034
|
py_task.attr("add_done_callback")(py::cpp_function(
|
|
2031
2035
|
[deferred = std::move(deferred),
|
|
@@ -2048,9 +2052,16 @@ Napi::Value Application_run(const Napi::CallbackInfo& info) {
|
|
|
2048
2052
|
if (!exception.has_value()) {
|
|
2049
2053
|
deferred->Resolve(env.Null());
|
|
2050
2054
|
} else {
|
|
2055
|
+
env.Global()
|
|
2056
|
+
.Get("process")
|
|
2057
|
+
.As<Napi::Object>()
|
|
2058
|
+
.Set("exitCode", 1);
|
|
2051
2059
|
deferred->Reject(
|
|
2052
2060
|
Napi::Error::New(env, *exception).Value());
|
|
2053
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);
|
|
2054
2065
|
});
|
|
2055
2066
|
|
|
2056
2067
|
delete py_task;
|
|
@@ -2347,7 +2358,7 @@ Napi::Value retry_reactively_until(const Napi::CallbackInfo& info) {
|
|
|
2347
2358
|
|
|
2348
2359
|
py::object py_task =
|
|
2349
2360
|
py::module::import("reboot.nodejs.python")
|
|
2350
|
-
.attr("
|
|
2361
|
+
.attr("create_task_with_context")(
|
|
2351
2362
|
(py::module::import("reboot.aio.contexts")
|
|
2352
2363
|
.attr("retry_reactively_until"))(
|
|
2353
2364
|
py_context,
|
|
@@ -2483,7 +2494,7 @@ Napi::Value atLeastOrMostOnce(const Napi::CallbackInfo& info) {
|
|
|
2483
2494
|
|
|
2484
2495
|
py::object py_task =
|
|
2485
2496
|
py::module::import("reboot.nodejs.python")
|
|
2486
|
-
.attr("
|
|
2497
|
+
.attr("create_task_with_context")(
|
|
2487
2498
|
(py::module::import("reboot.aio.memoize").attr("memoize"))(
|
|
2488
2499
|
py::str(idempotency_alias),
|
|
2489
2500
|
py_context,
|
|
@@ -2560,7 +2571,7 @@ Napi::Value Servicer_read(const Napi::CallbackInfo& info) {
|
|
|
2560
2571
|
deferred = std::move(deferred)]() {
|
|
2561
2572
|
py::object py_task =
|
|
2562
2573
|
py::module::import("reboot.nodejs.python")
|
|
2563
|
-
.attr("
|
|
2574
|
+
.attr("create_task_with_context")(
|
|
2564
2575
|
py_servicer->attr("_read")(py_context),
|
|
2565
2576
|
py_context,
|
|
2566
2577
|
"name"_a = "servicer._read(...) in nodejs");
|
|
@@ -2709,7 +2720,7 @@ Napi::Value Servicer_write(const Napi::CallbackInfo& info) {
|
|
|
2709
2720
|
|
|
2710
2721
|
py::object py_task =
|
|
2711
2722
|
py::module::import("reboot.nodejs.python")
|
|
2712
|
-
.attr("
|
|
2723
|
+
.attr("create_task_with_context")(
|
|
2713
2724
|
py_servicer->attr("_write")(
|
|
2714
2725
|
py_context,
|
|
2715
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,25 @@ if (major < 18) {
|
|
|
16
16
|
|
|
17
17
|
const reboot_native = { exports: {} };
|
|
18
18
|
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// https://stackoverflow.com/questions/77589486/nextjs-cannot-find-dirname
|
|
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.
|
|
25
22
|
if (__dirname.includes(".next")) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
throw new Error(
|
|
24
|
+
`For Next.js to work correctly with Reboot native code, an external
|
|
25
|
+
package must be added to 'next.config.ts':
|
|
26
|
+
|
|
27
|
+
In Next.js 14:
|
|
28
|
+
experimental: {
|
|
29
|
+
serverComponentsExternalPackages: ["@reboot-dev/reboot"],
|
|
30
|
+
},
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
In Next.js 15
|
|
34
|
+
'serverExternalPackages: ['@reboot-dev/reboot']',
|
|
35
|
+
...
|
|
36
|
+
`
|
|
31
37
|
);
|
|
32
|
-
} else {
|
|
33
|
-
node_modules_reboot_directory = __dirname;
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
// NOTE: as of Python 3.8 we _must_ load the Python library via
|
|
@@ -41,12 +45,7 @@ process.dlopen(
|
|
|
41
45
|
reboot_native,
|
|
42
46
|
// TODO(benh): consider using `require(bindings)` to properly locate
|
|
43
47
|
// 'reboot_native.node'.
|
|
44
|
-
path.join(
|
|
45
|
-
node_modules_reboot_directory,
|
|
46
|
-
"build",
|
|
47
|
-
"Release",
|
|
48
|
-
"reboot_native.node"
|
|
49
|
-
),
|
|
48
|
+
path.join(__dirname, "build", "Release", "reboot_native.node"),
|
|
50
49
|
os.constants.dlopen.RTLD_NOW | os.constants.dlopen.RTLD_GLOBAL
|
|
51
50
|
);
|
|
52
51
|
|
package/reboot_native.d.ts
CHANGED
|
@@ -45,8 +45,7 @@ export namespace rbt_native {
|
|
|
45
45
|
function Future_await(props: Future_awaitProps): string;
|
|
46
46
|
function ExternalContext_constructor(
|
|
47
47
|
name: string,
|
|
48
|
-
|
|
49
|
-
secureChannel: boolean,
|
|
48
|
+
url: string,
|
|
50
49
|
bearerToken: string,
|
|
51
50
|
idempotencySeed: string,
|
|
52
51
|
idempotencyRequired: boolean,
|
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.
|
|
1
|
+
export declare const REBOOT_VERSION = "0.19.0-alpha";
|
package/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const REBOOT_VERSION = "0.
|
|
1
|
+
export const REBOOT_VERSION = "0.19.0-alpha";
|
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
|
+
}
|