@rome-os/node-core 0.1.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/LICENSE +21 -0
- package/README.md +145 -0
- package/bin/daemon.js +2 -0
- package/dist/actions.d.ts +20 -0
- package/dist/actions.js +18 -0
- package/dist/auth.d.ts +3 -0
- package/dist/auth.js +76 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.js +130 -0
- package/dist/cloud.d.ts +7 -0
- package/dist/cloud.js +56 -0
- package/dist/connect.d.ts +27 -0
- package/dist/connect.js +115 -0
- package/dist/connector.d.ts +26 -0
- package/dist/connector.js +155 -0
- package/dist/daemon-client.d.ts +37 -0
- package/dist/daemon-client.js +244 -0
- package/dist/daemon-control.d.ts +13 -0
- package/dist/daemon-control.js +151 -0
- package/dist/daemon-entry.d.ts +1 -0
- package/dist/daemon-entry.js +16 -0
- package/dist/daemon-protocol.d.ts +16 -0
- package/dist/daemon-protocol.js +15 -0
- package/dist/daemon-rpc.d.ts +34 -0
- package/dist/daemon-rpc.js +171 -0
- package/dist/daemon.d.ts +2 -0
- package/dist/daemon.js +235 -0
- package/dist/devices-status.d.ts +23 -0
- package/dist/devices-status.js +68 -0
- package/dist/executor.d.ts +5 -0
- package/dist/executor.js +105 -0
- package/dist/gateway-url.d.ts +1 -0
- package/dist/gateway-url.js +11 -0
- package/dist/host.d.ts +3 -0
- package/dist/host.js +3 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/local.d.ts +21 -0
- package/dist/local.js +47 -0
- package/dist/login.d.ts +6 -0
- package/dist/login.js +107 -0
- package/dist/protocol.d.ts +29 -0
- package/dist/protocol.js +39 -0
- package/dist/socket.d.ts +2 -0
- package/dist/socket.js +25 -0
- package/dist/storage.d.ts +8 -0
- package/dist/storage.js +55 -0
- package/package.json +64 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { connectGateway, } from "./client.js";
|
|
2
|
+
export { MAX_MESSAGE_BYTES, byteLength, validId, } from "./protocol.js";
|
|
3
|
+
export { CloudError, cloudOrigin, cloudRequest, gatewayConfig } from "./cloud.js";
|
|
4
|
+
export { actionError, isRecord, parseResponse, } from "./actions.js";
|
package/dist/local.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface NodeConfig {
|
|
2
|
+
directory: string;
|
|
3
|
+
port: number;
|
|
4
|
+
}
|
|
5
|
+
export declare function createNodeConfig(options: {
|
|
6
|
+
directory: string;
|
|
7
|
+
port?: number;
|
|
8
|
+
}): NodeConfig;
|
|
9
|
+
export declare function nodeConfigFromEnvironment(env: Record<string, string | undefined>): NodeConfig;
|
|
10
|
+
export declare const callerCredentialPath: (config: NodeConfig) => string;
|
|
11
|
+
export declare const daemonStatePath: (config: NodeConfig) => string;
|
|
12
|
+
export interface CallerCredential {
|
|
13
|
+
cloudUrl: string;
|
|
14
|
+
token: string;
|
|
15
|
+
}
|
|
16
|
+
export declare class CallerConfigurationError extends Error {
|
|
17
|
+
readonly code: "not_configured" | "daemon_running" | "instance_token_required";
|
|
18
|
+
constructor(code: "not_configured" | "daemon_running" | "instance_token_required", message: string);
|
|
19
|
+
}
|
|
20
|
+
export declare function readOptionalCallerCredential(config: NodeConfig): Promise<CallerCredential | null>;
|
|
21
|
+
export declare function readCallerCredential(config: NodeConfig): Promise<CallerCredential>;
|
package/dist/local.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { isRecord } from "./actions.js";
|
|
5
|
+
import { cloudOrigin } from "./cloud.js";
|
|
6
|
+
import { readPrivateJson } from "./storage.js";
|
|
7
|
+
export function createNodeConfig(options) {
|
|
8
|
+
const directory = resolve(options.directory);
|
|
9
|
+
const port = options.port ??
|
|
10
|
+
20000 + (createHash("sha256").update(directory).digest().readUInt32BE(0) % 40000);
|
|
11
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535)
|
|
12
|
+
throw new Error("The daemon port must be between 1024 and 65535.");
|
|
13
|
+
return { directory, port };
|
|
14
|
+
}
|
|
15
|
+
export function nodeConfigFromEnvironment(env) {
|
|
16
|
+
return createNodeConfig({
|
|
17
|
+
directory: env.ROME_NODE_CONFIG_DIR ||
|
|
18
|
+
(process.platform === "win32"
|
|
19
|
+
? join(env.LOCALAPPDATA || homedir(), "RomeNode")
|
|
20
|
+
: join(env.XDG_CONFIG_HOME || join(homedir(), ".config"), "rome-node")),
|
|
21
|
+
port: env.ROME_NODE_DAEMON_PORT === undefined ? undefined : Number(env.ROME_NODE_DAEMON_PORT),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
export const callerCredentialPath = (config) => join(config.directory, "caller.json");
|
|
25
|
+
export const daemonStatePath = (config) => join(config.directory, "daemon.json");
|
|
26
|
+
export class CallerConfigurationError extends Error {
|
|
27
|
+
code;
|
|
28
|
+
constructor(code, message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.code = code;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export async function readOptionalCallerCredential(config) {
|
|
34
|
+
const value = await readPrivateJson(callerCredentialPath(config));
|
|
35
|
+
if (!isRecord(value) ||
|
|
36
|
+
typeof value.cloudUrl !== "string" ||
|
|
37
|
+
typeof value.token !== "string" ||
|
|
38
|
+
!/^romedev_[A-Za-z0-9_-]{43}$/.test(value.token))
|
|
39
|
+
return null;
|
|
40
|
+
return { cloudUrl: cloudOrigin(value.cloudUrl), token: value.token };
|
|
41
|
+
}
|
|
42
|
+
export async function readCallerCredential(config) {
|
|
43
|
+
const credential = await readOptionalCallerCredential(config);
|
|
44
|
+
if (!credential)
|
|
45
|
+
throw new CallerConfigurationError("not_configured", "A caller communication credential is required.");
|
|
46
|
+
return credential;
|
|
47
|
+
}
|
package/dist/login.d.ts
ADDED
package/dist/login.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
+
import { platform } from "node:os";
|
|
4
|
+
import { cloudOrigin } from "./cloud.js";
|
|
5
|
+
import { validId } from "./protocol.js";
|
|
6
|
+
export async function loginDevice(cloudUrl, name, signal, onAuthorizationRequired) {
|
|
7
|
+
const cloud = new URL(cloudOrigin(cloudUrl));
|
|
8
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
9
|
+
const state = randomBytes(32).toString("base64url");
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
if (signal.aborted) {
|
|
12
|
+
reject(new Error("Authorization canceled."));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
let redeeming = false;
|
|
16
|
+
const server = createServer(async (request, response) => {
|
|
17
|
+
const incoming = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
18
|
+
if (request.method !== "GET" ||
|
|
19
|
+
incoming.pathname !== "/callback" ||
|
|
20
|
+
incoming.searchParams.get("state") !== state ||
|
|
21
|
+
incoming.searchParams.get("iss") !== cloud.origin ||
|
|
22
|
+
!incoming.searchParams.get("code") ||
|
|
23
|
+
redeeming) {
|
|
24
|
+
response.writeHead(400).end("Invalid authorization callback");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
redeeming = true;
|
|
28
|
+
try {
|
|
29
|
+
const result = await fetch(`${cloud.origin}/oauth2/token`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(5000)]),
|
|
32
|
+
redirect: "error",
|
|
33
|
+
body: new URLSearchParams({
|
|
34
|
+
grant_type: "authorization_code",
|
|
35
|
+
client_id: "rome-computer",
|
|
36
|
+
redirect_uri: redirectUri,
|
|
37
|
+
code: incoming.searchParams.get("code"),
|
|
38
|
+
code_verifier: verifier,
|
|
39
|
+
platform: platform() === "darwin" ? "macos" : platform() === "win32" ? "windows" : "linux",
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
if (!result.ok)
|
|
43
|
+
throw new Error("Token exchange rejected");
|
|
44
|
+
const body = (await result.json());
|
|
45
|
+
if (typeof body.access_token !== "string" ||
|
|
46
|
+
!/^romedev_[A-Za-z0-9_-]{43}$/.test(body.access_token) ||
|
|
47
|
+
!validId(body.device_id))
|
|
48
|
+
throw new Error("Invalid token response");
|
|
49
|
+
response
|
|
50
|
+
.writeHead(200, { "content-type": "text/plain", "cache-control": "no-store" })
|
|
51
|
+
.end("Device authorized. Return to Rome Node.");
|
|
52
|
+
resolve({ cloudUrl: cloud.origin, token: body.access_token, deviceId: body.device_id });
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
response
|
|
56
|
+
.writeHead(400)
|
|
57
|
+
.end("Device authorization failed. Return to the application to retry.");
|
|
58
|
+
reject(new Error("Device authorization failed"));
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
signal.removeEventListener("abort", abort);
|
|
63
|
+
server.close();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
let redirectUri;
|
|
67
|
+
const abort = () => {
|
|
68
|
+
clearTimeout(timer);
|
|
69
|
+
server.closeAllConnections();
|
|
70
|
+
server.close();
|
|
71
|
+
reject(new Error("Authorization canceled."));
|
|
72
|
+
};
|
|
73
|
+
const timer = setTimeout(() => {
|
|
74
|
+
server.close();
|
|
75
|
+
signal.removeEventListener("abort", abort);
|
|
76
|
+
reject(new Error("Login timed out"));
|
|
77
|
+
}, 5 * 60_000);
|
|
78
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
79
|
+
server.on("error", () => {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
signal.removeEventListener("abort", abort);
|
|
82
|
+
reject(new Error("Loopback listener failed"));
|
|
83
|
+
});
|
|
84
|
+
server.listen(0, "127.0.0.1", () => {
|
|
85
|
+
const address = server.address();
|
|
86
|
+
if (!address || typeof address === "string")
|
|
87
|
+
return;
|
|
88
|
+
redirectUri = `http://127.0.0.1:${address.port}/callback`;
|
|
89
|
+
const url = new URL("/oauth2/authorize", cloud);
|
|
90
|
+
url.search = new URLSearchParams({
|
|
91
|
+
response_type: "code",
|
|
92
|
+
client_id: "rome-computer",
|
|
93
|
+
redirect_uri: redirectUri,
|
|
94
|
+
code_challenge: createHash("sha256").update(verifier).digest("base64url"),
|
|
95
|
+
code_challenge_method: "S256",
|
|
96
|
+
state,
|
|
97
|
+
display_name: name,
|
|
98
|
+
}).toString();
|
|
99
|
+
try {
|
|
100
|
+
onAuthorizationRequired(url.toString());
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
abort();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const MAX_MESSAGE_BYTES: number;
|
|
2
|
+
export declare const CLOSE: {
|
|
3
|
+
readonly revoked: 4001;
|
|
4
|
+
readonly superseded: 4002;
|
|
5
|
+
readonly outputLimit: 4003;
|
|
6
|
+
readonly invalid: 1008;
|
|
7
|
+
readonly tooLarge: 1009;
|
|
8
|
+
};
|
|
9
|
+
export interface OutboundEnvelope {
|
|
10
|
+
id: string;
|
|
11
|
+
to: string;
|
|
12
|
+
payload: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface InboundEnvelope {
|
|
15
|
+
id: string;
|
|
16
|
+
from: string;
|
|
17
|
+
payload: unknown;
|
|
18
|
+
}
|
|
19
|
+
interface RouteError {
|
|
20
|
+
id: string;
|
|
21
|
+
type: "error";
|
|
22
|
+
code: "target_unavailable" | "invalid_message" | "message_too_large";
|
|
23
|
+
}
|
|
24
|
+
export type GatewayMessage = InboundEnvelope | RouteError;
|
|
25
|
+
export declare function validId(value: unknown): value is string;
|
|
26
|
+
export declare function outboundEnvelope(value: unknown): OutboundEnvelope | null;
|
|
27
|
+
export declare function gatewayMessage(value: unknown): GatewayMessage | null;
|
|
28
|
+
export declare function byteLength(value: string): number;
|
|
29
|
+
export {};
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export const MAX_MESSAGE_BYTES = 32 * 1024 * 1024;
|
|
2
|
+
export const CLOSE = {
|
|
3
|
+
revoked: 4001,
|
|
4
|
+
superseded: 4002,
|
|
5
|
+
outputLimit: 4003,
|
|
6
|
+
invalid: 1008,
|
|
7
|
+
tooLarge: 1009,
|
|
8
|
+
};
|
|
9
|
+
export function validId(value) {
|
|
10
|
+
return (typeof value === "string" &&
|
|
11
|
+
value.length > 0 &&
|
|
12
|
+
value.length <= 128 &&
|
|
13
|
+
!/[\u0000-\u0020\u007f]/.test(value));
|
|
14
|
+
}
|
|
15
|
+
export function outboundEnvelope(value) {
|
|
16
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
17
|
+
return null;
|
|
18
|
+
const item = value;
|
|
19
|
+
if (!validId(item.id) || !validId(item.to) || !Object.hasOwn(item, "payload"))
|
|
20
|
+
return null;
|
|
21
|
+
return { id: item.id, to: item.to, payload: item.payload };
|
|
22
|
+
}
|
|
23
|
+
export function gatewayMessage(value) {
|
|
24
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
25
|
+
return null;
|
|
26
|
+
const item = value;
|
|
27
|
+
if (!validId(item.id))
|
|
28
|
+
return null;
|
|
29
|
+
if (item.type === "error" &&
|
|
30
|
+
["target_unavailable", "invalid_message", "message_too_large"].includes(String(item.code))) {
|
|
31
|
+
return item;
|
|
32
|
+
}
|
|
33
|
+
if (validId(item.from) && Object.hasOwn(item, "payload"))
|
|
34
|
+
return { id: item.id, from: item.from, payload: item.payload };
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
export function byteLength(value) {
|
|
38
|
+
return new TextEncoder().encode(value).byteLength;
|
|
39
|
+
}
|
package/dist/socket.d.ts
ADDED
package/dist/socket.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
export function createNodeSocket(url, authorization) {
|
|
3
|
+
const socket = new WebSocket(url, {
|
|
4
|
+
headers: { Authorization: authorization },
|
|
5
|
+
handshakeTimeout: 10_000,
|
|
6
|
+
followRedirects: false,
|
|
7
|
+
});
|
|
8
|
+
let alive = true;
|
|
9
|
+
const heartbeat = setInterval(() => {
|
|
10
|
+
if (socket.readyState !== WebSocket.OPEN)
|
|
11
|
+
return;
|
|
12
|
+
if (!alive) {
|
|
13
|
+
socket.terminate();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
alive = false;
|
|
17
|
+
socket.ping();
|
|
18
|
+
}, 30_000);
|
|
19
|
+
heartbeat.unref();
|
|
20
|
+
socket.on("pong", () => {
|
|
21
|
+
alive = true;
|
|
22
|
+
});
|
|
23
|
+
socket.on("close", () => clearInterval(heartbeat));
|
|
24
|
+
return socket;
|
|
25
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function readPrivateJson(path: string): Promise<unknown>;
|
|
2
|
+
export declare function writePrivateJson(path: string, value: unknown): Promise<void>;
|
|
3
|
+
export interface CredentialStore<T> {
|
|
4
|
+
load(): Promise<unknown>;
|
|
5
|
+
save(value: T): Promise<void>;
|
|
6
|
+
clear(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare function createFileCredentialStore<T>(path: string): CredentialStore<T>;
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
export async function readPrivateJson(path) {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
if (error instanceof SyntaxError || error.code === "ENOENT")
|
|
12
|
+
return null;
|
|
13
|
+
throw new Error("Could not read the local credential file.");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function writePrivateJson(path, value) {
|
|
17
|
+
const dir = dirname(path);
|
|
18
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
19
|
+
if (process.platform === "win32") {
|
|
20
|
+
const run = promisify(execFile);
|
|
21
|
+
const systemDirectory = join(process.env.SystemRoot ?? "C:\\Windows", "System32");
|
|
22
|
+
const { stdout } = await run(join(systemDirectory, "whoami.exe"), [
|
|
23
|
+
"/user",
|
|
24
|
+
"/fo",
|
|
25
|
+
"csv",
|
|
26
|
+
"/nh",
|
|
27
|
+
]);
|
|
28
|
+
const sid = stdout.match(/S-1-\d+(?:-\d+)+/)?.[0];
|
|
29
|
+
if (!sid)
|
|
30
|
+
throw new Error("Could not determine the Windows account identity.");
|
|
31
|
+
await run(join(systemDirectory, "icacls.exe"), [
|
|
32
|
+
dir,
|
|
33
|
+
"/inheritance:r",
|
|
34
|
+
"/grant:r",
|
|
35
|
+
`*${sid}:(OI)(CI)F`,
|
|
36
|
+
]);
|
|
37
|
+
}
|
|
38
|
+
else
|
|
39
|
+
await chmod(dir, 0o700);
|
|
40
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
41
|
+
try {
|
|
42
|
+
await writeFile(temporary, JSON.stringify(value), { mode: 0o600, flag: "wx" });
|
|
43
|
+
await rename(temporary, path);
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
await rm(temporary, { force: true });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function createFileCredentialStore(path) {
|
|
50
|
+
return {
|
|
51
|
+
load: () => readPrivateJson(path),
|
|
52
|
+
save: (value) => writePrivateJson(path, value),
|
|
53
|
+
clear: () => rm(path, { force: true }),
|
|
54
|
+
};
|
|
55
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rome-os/node-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "JavaScript APIs for Rome device hosts and shared caller daemons",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=24"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./client": {
|
|
21
|
+
"types": "./dist/daemon-client.d.ts",
|
|
22
|
+
"default": "./dist/daemon-client.js"
|
|
23
|
+
},
|
|
24
|
+
"./host": {
|
|
25
|
+
"types": "./dist/host.d.ts",
|
|
26
|
+
"default": "./dist/host.js"
|
|
27
|
+
},
|
|
28
|
+
"./daemon": {
|
|
29
|
+
"types": "./dist/daemon.d.ts",
|
|
30
|
+
"default": "./dist/daemon.js"
|
|
31
|
+
},
|
|
32
|
+
"./storage": {
|
|
33
|
+
"types": "./dist/storage.d.ts",
|
|
34
|
+
"default": "./dist/storage.js"
|
|
35
|
+
},
|
|
36
|
+
"./auth": {
|
|
37
|
+
"types": "./dist/auth.d.ts",
|
|
38
|
+
"default": "./dist/auth.js"
|
|
39
|
+
},
|
|
40
|
+
"./socket": {
|
|
41
|
+
"types": "./dist/socket.d.ts",
|
|
42
|
+
"default": "./dist/socket.js"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"ws": "^8.18.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@rstest/core": "^0.11.11",
|
|
50
|
+
"@types/node": "25.2.3",
|
|
51
|
+
"typescript": "^5.9.3",
|
|
52
|
+
"@types/ws": "^8.18.0"
|
|
53
|
+
},
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "git+https://github.com/rome-os/rome.git",
|
|
57
|
+
"directory": "packages/rome-node-core"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsc -p tsconfig.build.json",
|
|
61
|
+
"typecheck": "tsc --noEmit",
|
|
62
|
+
"test": "pnpm build && ../../scripts/test-env.sh rstest"
|
|
63
|
+
}
|
|
64
|
+
}
|