@reboot-dev/reboot 0.19.1 → 0.20.0

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 CHANGED
@@ -1,33 +1,35 @@
1
1
  {
2
2
  "dependencies": {
3
- "chalk": "^4.1.2",
4
- "esbuild": "^0.24.0",
5
- "node-addon-api": "^7.0.0",
6
- "protobufjs": "^7.2.5",
7
- "@types/node": "20.11.5",
8
- "@types/uuid": "^9.0.4",
9
- "uuid": "^9.0.1",
10
- "which-pm-runs": "^1.1.0"
11
- },
12
- "peerDependencies": {
13
3
  "@bufbuild/protobuf": "1.3.2",
14
4
  "@bufbuild/protoplugin": "1.3.2",
15
5
  "@bufbuild/protoc-gen-es": "1.3.2",
16
- "@reboot-dev/reboot-api": "0.19.1",
17
- "typescript": ">=4.9.5"
6
+ "@reboot-dev/reboot-api": "0.20.0",
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"
18
14
  },
19
15
  "type": "module",
20
16
  "name": "@reboot-dev/reboot",
21
- "version": "0.19.1",
17
+ "version": "0.20.0",
22
18
  "description": "npm package for Reboot",
23
19
  "scripts": {
24
20
  "postinstall": "rbt || exit 0",
25
21
  "test": "echo \"Error: no test specified\" && exit 1",
26
- "prepare": "tsc"
22
+ "prepack": "tsc"
23
+ },
24
+ "devDependencies": {
25
+ "typescript": ">=4.9.5",
26
+ "@types/node": "20.11.5",
27
+ "@types/uuid": "^9.0.4"
27
28
  },
28
29
  "bin": {
29
30
  "rbt": "./rbt.js",
30
- "rbt-esbuild": "./rbt-esbuild.js"
31
+ "rbt-esbuild": "./rbt-esbuild.js",
32
+ "protoc-gen-es": "./protoc-gen-es.js"
31
33
  },
32
34
  "engines": {
33
35
  "node": ">=18.0.0"
@@ -41,6 +43,8 @@
41
43
  "include",
42
44
  "internal",
43
45
  "package.json",
46
+ "protoc-gen-es.d.ts",
47
+ "protoc-gen-es.js",
44
48
  "rbt.d.ts",
45
49
  "rbt-esbuild.d.ts",
46
50
  "rbt-esbuild.js",
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "@bufbuild/protoc-gen-es/bin/protoc-gen-es";
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ // NOTE: Yarn doesn't add transitive binary dependencies to PATH!!!
3
+ // Thus this script which "proxies" to `protoc-gen-es`.
4
+ import "@bufbuild/protoc-gen-es/bin/protoc-gen-es";
package/rbt-esbuild.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import chalk from "chalk";
2
3
  import esbuild from "esbuild";
3
4
  import { writeFileSync } from "node:fs";
4
5
  import * as path from "path";
@@ -11,12 +12,14 @@ const args = process.argv.slice(2);
11
12
  const application = args[0];
12
13
  const name = args[1];
13
14
  export const BUNDLE_PATH = path.join(__dirname, ".bundles", name);
14
- const workspaces = await locateWorkspaces();
15
- if (!workspaces) {
16
- console.error(`Could not determine the package manager for ${application}.`);
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}`));
17
21
  process.exit(-1);
18
22
  }
19
- const workspaceModules = workspaces.map((workspace) => workspace.name);
20
23
  const plugin = {
21
24
  name: "rbt-esbuild",
22
25
  setup(build) {
@@ -27,9 +30,8 @@ const plugin = {
27
30
  return null;
28
31
  }
29
32
  // Workspace modules are also local, not external.
30
- for (const workspaceModule of workspaceModules) {
31
- if (args.path === workspaceModule ||
32
- args.path.startsWith(workspaceModule + "/")) {
33
+ for (const workspace of workspaces) {
34
+ if (args.path === workspace || args.path.startsWith(workspace + "/")) {
33
35
  return null;
34
36
  }
35
37
  }
@@ -81,6 +83,7 @@ esbuild
81
83
  })
82
84
  .then(async (result) => {
83
85
  writeFileSync(path.join(BUNDLE_PATH, "meta.json"), JSON.stringify(result.metafile));
86
+ // Output the path where we bundled for `rbt dev` to consume.
84
87
  console.log(BUNDLE_PATH);
85
88
  process.exit(0);
86
89
  })
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
@@ -88,10 +88,10 @@ export type ServicerFactory = {
88
88
  */
89
89
  export declare class Auth {
90
90
  userId?: string;
91
- properties: unknown;
91
+ properties: protobuf_es.JsonValue;
92
92
  constructor(options?: {
93
93
  userId?: string;
94
- properties?: unknown;
94
+ properties?: protobuf_es.JsonValue;
95
95
  });
96
96
  toProtoBytes(): Uint8Array;
97
97
  static fromProtoBytes(bytes: Uint8Array): Auth;
package/reboot.js CHANGED
@@ -95,7 +95,7 @@ export class ExternalContext {
95
95
  __classPrivateFieldSet(this, _ExternalContext_external, args.external, "f");
96
96
  }
97
97
  else {
98
- if (!args.url.startsWith("http")) {
98
+ if (!args.url || !args.url.startsWith("http")) {
99
99
  throw new Error("`ExternalContext` must be constructed with a `url` " +
100
100
  "including an explicit 'http' or 'https' protocol");
101
101
  }
@@ -277,7 +277,7 @@ export class Auth {
277
277
  toProtoBytes() {
278
278
  const auth = new auth_pb.Auth({
279
279
  userId: this.userId,
280
- properties: this.properties,
280
+ properties: protobuf_es.Struct.fromJson(this.properties),
281
281
  });
282
282
  return auth.toBinary();
283
283
  }
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
  );
@@ -19,12 +19,21 @@ const reboot_native = { exports: {} };
19
19
  // If we are in a Next.js context, check that serverExternalPackages is
20
20
  // correctly set. If not correctly set, Reboot backend TS code will not work
21
21
  // with React Server Components inside Next.
22
-
23
22
  if (__dirname.includes(".next")) {
24
23
  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"
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
+ `
28
37
  );
29
38
  }
30
39
 
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";
1
+ export declare const REBOOT_VERSION = "0.20.0";
package/version.js CHANGED
@@ -1 +1 @@
1
- export const REBOOT_VERSION = "0.19.1";
1
+ export const REBOOT_VERSION = "0.20.0";
package/workspaces.js CHANGED
@@ -1,5 +1,5 @@
1
- import { spawnSync } from "child_process";
2
1
  import whichPMRuns from "which-pm-runs";
2
+ import { parseVersion, spawnAndReturnStdout, supportedVersion, } from "./utils/index.js";
3
3
  /**
4
4
  * Returns package-manager Workspaces of which a project in the given directory
5
5
  * is a member.
@@ -9,16 +9,13 @@ export async function locateWorkspaces() {
9
9
  if (!packageManager) {
10
10
  return null;
11
11
  }
12
- // NOTE: These methods will error if they fail to extract workspaces, based on the
13
- // assumption that `preferredPM` will do a reasonable job of detecting a package
14
- // manager, and based on having validated that it falls back to `null` if it doesn't
15
- // have a reasonable guess.
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
16
  switch (packageManager.name) {
17
17
  case "yarn": {
18
- if (packageManager.version.startswith("4.")) {
19
- return extractYarnWorkspaces();
20
- }
21
- throw new Error(`'yarn' version '${packageManager.version}' is not supported. Please report this issue to the maintainers!`);
18
+ return extractYarnWorkspaces();
22
19
  }
23
20
  case "npm": {
24
21
  return extractNpmWorkspaces();
@@ -27,52 +24,73 @@ export async function locateWorkspaces() {
27
24
  return extractPnpmWorkspaces();
28
25
  }
29
26
  default: {
30
- throw new Error(`Unsupported package manager: '${packageManager}'. Please report this issue to the maintainers!`);
27
+ throw new Error(`Unsupported package manager '${packageManager}'`);
31
28
  }
32
29
  }
33
30
  }
34
31
  function extractYarnWorkspaces() {
35
- const workspacesJSON = spawnAndParseJSON("yarn", [
36
- "workspaces",
37
- "list",
38
- "--json",
39
- "--recursive",
40
- ]);
41
- return workspacesJSON.map((entry) => ({
42
- name: entry.name,
43
- }));
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
+ });
44
50
  }
45
51
  function extractNpmWorkspaces() {
46
- const workspacesJSON = spawnAndParseJSON("npm", ["query", ".workspace"]);
47
- const workspaces = workspacesJSON.map((entry) => ({
48
- name: entry.name,
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,
49
63
  }));
50
- // Also get the package name as it might be used. This is also
51
- // consistent with both `yarn` and `pnpm` which always include the
52
- // top-level package name even if they aren't explicitly in
53
- // `workspaces`.
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`.
54
67
  const name = spawnAndParseJSON("npm", ["pkg", "get", "name"]);
55
- if (typeof name !== "string") {
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) {
56
71
  return workspaces;
57
72
  }
58
- return [...workspaces, { name: name }];
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];
59
77
  }
60
78
  function extractPnpmWorkspaces() {
79
+ // TODO: is there a minimum version required?
61
80
  const command = "pnpm";
62
81
  const args = ["list", "--recursive", "--depth", "-1", "--only-projects"];
63
- const workspacesString = spawnAndParseJSON(command, args);
64
- // TODO: `pnpm list --recursive --depth -1 --only-projects --json` emits something
82
+ // `pnpm list --recursive --depth -1 --only-projects --json` emits something
65
83
  // which is not quite valid JSON: closer to JSON lines, but not easily splittable.
66
84
  //
67
85
  // Instead, we parse the human-readable output -- a series of lines like:
68
86
  // @reboot-pm/prosemirror@0.0.1 /Users/example/src/repo_root/project_dir (PRIVATE)
69
- return workspacesString
87
+ return spawnAndReturnStdout(command, args)
70
88
  .trim()
71
89
  .split("\n")
72
90
  .map((line) => {
73
91
  const parts = line.trim().split(" ");
74
92
  if (parts.length !== 3) {
75
- throw new Error(`Unexpected output from '${command} ${args}'. Please report the output of that command to the maintainers!`);
93
+ throw new Error(`Unexpected output from '${command} ${args.join(" ")}'. Please report the output of that command to the maintainers!`);
76
94
  }
77
95
  // Strip off the version from the name.
78
96
  const lastAtSign = parts[0].lastIndexOf("@");
@@ -83,10 +101,7 @@ function extractPnpmWorkspaces() {
83
101
  else {
84
102
  name = parts[0];
85
103
  }
86
- ({
87
- name: name,
88
- path: parts[1],
89
- });
104
+ return { name };
90
105
  });
91
106
  }
92
107
  /**
@@ -97,25 +112,9 @@ function extractPnpmWorkspaces() {
97
112
  function spawnAndParseJSON(command, args) {
98
113
  const stdout = spawnAndReturnStdout(command, args);
99
114
  try {
100
- const json = JSON.parse(stdout.toString());
101
- return json;
115
+ return JSON.parse(stdout);
102
116
  }
103
117
  catch (error) {
104
- throw new Error(`Failed to parse output of '${command} ${args}': ${error}`);
105
- }
106
- }
107
- /**
108
- * Spawn a process with the given arguments, and return its stdout.
109
- */
110
- function spawnAndReturnStdout(command, args) {
111
- const result = spawnSync(command, args, {
112
- stdio: ["pipe", "pipe", process.stderr],
113
- });
114
- if (result.status !== 0) {
115
- const errorStr = result.error
116
- ? result.error.toString()
117
- : result.stderr.toString();
118
- throw new Error(`Process '${command} ${args}' exited with code ${result.status} and error: ${errorStr}`);
118
+ throw new Error(`Failed to parse output of '${command} ${args.join(" ")}' as JSON: ${error}`);
119
119
  }
120
- return result.stdout;
121
120
  }