@source-repo/rpc-cli 3.0.0 → 3.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.
@@ -0,0 +1,94 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ /**
5
+ * The dependencies a script directory is allowed to import.
6
+ *
7
+ * A script is an ordinary Node program, so sooner or later one wants something off the registry -
8
+ * a date library, a CSV parser, a driver for the thing on the other end of the serial port. Without
9
+ * this the only answer is "leave the conversation and run npm yourself", which is the wrong shape
10
+ * for a directory a model is otherwise managing end to end.
11
+ *
12
+ * **This is not a new grant.** `--scripts` already permits arbitrary Node processes, and a script
13
+ * could `child_process.exec('npm i …')` on its own. Putting it behind a second flag would be theatre;
14
+ * what a tool buys is that the model *declares* what it wants, where it can be seen in the tool log
15
+ * and in a committed package.json, rather than doing it sideways.
16
+ *
17
+ * What is worth defending against is different: **installing a package runs code from the registry
18
+ * that nobody reviewed.** So installs pass `--ignore-scripts` by default, which is the setting that
19
+ * stops a postinstall hook from being the attack. A package that genuinely needs one - anything with
20
+ * a native build - has to ask, and the asking is visible.
21
+ */
22
+ /** How long an install may take before it is abandoned. A cold cache on a slow link is the case to allow for. */
23
+ const INSTALL_BUDGET_MS = 180_000;
24
+ /**
25
+ * The manifest a script directory needs anyway.
26
+ *
27
+ * `type: module` is the load-bearing field: a `.ts` script uses `import`, and Node decides whether
28
+ * that is legal from the nearest package.json. Inside a CommonJS project there is one, it does not
29
+ * say module, and every run then prints a reparse warning that lands in the script's own output.
30
+ * `private` because this is a directory of programs, not something anybody publishes.
31
+ */
32
+ export const ensureManifest = (directory) => {
33
+ const file = join(resolve(directory), 'package.json');
34
+ if (existsSync(file))
35
+ return file;
36
+ writeFileSync(file, `${JSON.stringify({ name: 'source-rpc-scripts', private: true, type: 'module' }, null, 2)}\n`, 'utf8');
37
+ return file;
38
+ };
39
+ const manifest = (directory) => {
40
+ const file = join(resolve(directory), 'package.json');
41
+ if (!existsSync(file))
42
+ return {};
43
+ try {
44
+ return JSON.parse(readFileSync(file, 'utf8'));
45
+ }
46
+ catch {
47
+ return {};
48
+ }
49
+ };
50
+ /** What is declared, and what is actually on disk - which differ when an install failed halfway. */
51
+ export const listPackages = (directory) => Object.entries(manifest(directory).dependencies ?? {})
52
+ .map(([name, range]) => {
53
+ const installed = join(resolve(directory), 'node_modules', name, 'package.json');
54
+ let version;
55
+ try {
56
+ version = JSON.parse(readFileSync(installed, 'utf8')).version;
57
+ }
58
+ catch {
59
+ version = undefined;
60
+ }
61
+ return { name, range, ...(version ? { installed: version } : { installed: null }) };
62
+ })
63
+ .sort((a, b) => a.name.localeCompare(b.name));
64
+ /**
65
+ * A package name a shell would not reinterpret.
66
+ *
67
+ * The spec reaches npm as one argv element rather than through a shell, so this is not quoting - it
68
+ * is refusing the shapes that are not package specs at all: a flag that would change what npm does,
69
+ * or a path that would install something off disk.
70
+ */
71
+ const SAFE_SPEC = /^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*(@[\w.^~*><=|\s-]+)?$/i;
72
+ const npm = (directory, args) => new Promise((done) => {
73
+ execFile(process.platform === 'win32' ? 'npm.cmd' : 'npm', args, { cwd: resolve(directory), timeout: INSTALL_BUDGET_MS, maxBuffer: 8 * 1024 * 1024 }, (error, stdout, stderr) => {
74
+ const output = `${stdout}${stderr}`.trim();
75
+ if (!error)
76
+ return done({ ok: true, output });
77
+ const missing = error.code === 'ENOENT';
78
+ done({ ok: false, output: missing ? 'npm is not on PATH, so packages cannot be managed from here.' : output || error.message });
79
+ });
80
+ });
81
+ export const addPackage = async (directory, spec, allowInstallScripts = false) => {
82
+ if (!SAFE_SPEC.test(spec))
83
+ throw new Error(`'${spec}' is not a package name. Give it \`lodash\` or \`@scope/name@^2\`, not a flag or a path.`);
84
+ ensureManifest(directory);
85
+ // --ignore-scripts unless asked: an install hook is code from the registry running here, and it
86
+ // is the part of `npm install` that is not about files at all.
87
+ return await npm(directory, ['install', '--save', ...(allowInstallScripts ? [] : ['--ignore-scripts']), spec]);
88
+ };
89
+ export const removePackage = async (directory, name) => {
90
+ if (!SAFE_SPEC.test(name))
91
+ throw new Error(`'${name}' is not a package name.`);
92
+ return await npm(directory, ['uninstall', '--save', name]);
93
+ };
94
+ //# sourceMappingURL=packages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packages.js","sourceRoot":"","sources":["../src/packages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import type { NetworkOptions } from './network.js';
2
+ /** TypeScript first; `.mjs` for a Node too old to run it, or an author who would rather not. */
3
+ export declare const SCRIPT_EXTENSIONS: readonly ['.ts', '.mjs'];
4
+ export type ScriptLanguage = 'ts' | 'mjs';
5
+ /** Where a script of this language would be written. Proven to stay inside the directory. */
6
+ export declare const scriptPath: (directory: string, name: string, language?: ScriptLanguage) => string;
7
+ /** The file a saved script actually occupies, whichever language it was written in. */
8
+ export declare const scriptFile: (directory: string, name: string) => string;
9
+ export declare const saveScript: (directory: string, name: string, source: string, language?: ScriptLanguage) => string;
10
+ export declare const readScript: (directory: string, name: string) => string;
11
+ export declare const deleteScript: (directory: string, name: string) => void;
12
+ export declare const listScripts: (directory: string) => {
13
+ name: string;
14
+ language: ScriptLanguage;
15
+ }[];
16
+ /**
17
+ * What this Node needs in order to run that file.
18
+ *
19
+ * Type stripping landed behind `--experimental-strip-types` in 22.6 and became the default in 23.6,
20
+ * so the flag is passed only where it is both needed and understood - passing it to 24 is a warning,
21
+ * and passing it to 22.5 is an error about an unknown option rather than about TypeScript.
22
+ */
23
+ export declare const nodeArgvFor: (file: string, version?: string) => string[];
24
+ /**
25
+ * The network this server is on, handed to a script as environment.
26
+ *
27
+ * So a script does not hardcode a broker url that is right on one machine and wrong on the next -
28
+ * and so a model writing one has something to read rather than a value to invent. The names match
29
+ * the flags they came from, and `SOURCE_RPC_TOKEN` is the one the CLI already reads for credentials.
30
+ */
31
+ export declare const environmentFor: (options: NetworkOptions) => {
32
+ [key: string]: string;
33
+ };
34
+ export interface RunningScript {
35
+ name: string;
36
+ pid?: number;
37
+ startedAt: number;
38
+ /** Set once it has ended, so a stopped script reports why rather than merely being absent. */
39
+ ended?: {
40
+ code: number | null;
41
+ signal: string | null;
42
+ at: number;
43
+ };
44
+ output: string[];
45
+ }
46
+ /** Starts, stops and remembers the scripts this server is running. */
47
+ export declare class ScriptRunner {
48
+ private directory;
49
+ private environment;
50
+ private running;
51
+ private finished;
52
+ constructor(directory: string, environment?: {
53
+ [key: string]: string;
54
+ });
55
+ isRunning(name: string): boolean;
56
+ start(name: string): RunningScript;
57
+ stop(name: string): Promise<RunningScript>;
58
+ status(name: string): RunningScript | undefined;
59
+ all(): RunningScript[];
60
+ stopAll(): Promise<void>;
61
+ }
62
+ //# sourceMappingURL=scripts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scripts.d.ts","sourceRoot":"","sources":["../src/scripts.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AA8BlD,gGAAgG;AAChG,eAAO,MAAM,iBAAiB,YAAI,KAAK,EAAE,MAAM,CAAU,CAAA;AACzD,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,KAAK,CAAA;AASzC,6FAA6F;AAC7F,eAAO,MAAM,UAAU,cAAe,MAAM,QAAQ,MAAM,aAAY,cAAc,WAMnF,CAAA;AAED,uFAAuF;AACvF,eAAO,MAAM,UAAU,cAAe,MAAM,QAAQ,MAAM,WAOzD,CAAA;AAED,eAAO,MAAM,UAAU,cAAe,MAAM,QAAQ,MAAM,UAAU,MAAM,aAAY,cAAc,WAcnG,CAAA;AAED,eAAO,MAAM,UAAU,cAAe,MAAM,QAAQ,MAAM,WAAsD,CAAA;AAEhH,eAAO,MAAM,YAAY,cAAe,MAAM,QAAQ,MAAM,SAAwC,CAAA;AAEpG,eAAO,MAAM,WAAW,cAAe,MAAM;;cAIiE,cAAc;GAM3H,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,SAAU,MAAM,+BAQvC,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,YAAa,cAAc,KAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAO9E,CAAA;AAEF,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,8FAA8F;IAC9F,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;IAClE,MAAM,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,sEAAsE;AACtE,qBAAa,YAAY;IAKjB,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,WAAW;IALvB,OAAO,CAAC,OAAO,CAAoE;IACnF,OAAO,CAAC,QAAQ,CAAmC;IAEnD,YACY,SAAS,EAAE,MAAM,EACjB,WAAW,GAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;KAAO,EACnD;IAEJ,SAAS,CAAC,IAAI,EAAE,MAAM,WAErB;IAED,KAAK,CAAC,IAAI,EAAE,MAAM,iBAiCjB;IAEK,IAAI,CAAC,IAAI,EAAE,MAAM,0BAetB;IAED,MAAM,CAAC,IAAI,EAAE,MAAM,6BAElB;IAED,GAAG,IAAI,aAAa,EAAE,CAErB;IAEK,OAAO,kBAEZ;CACJ"}
@@ -0,0 +1,193 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { extname, join, resolve, sep } from 'node:path';
4
+ import { createInterface } from 'node:readline';
5
+ import { ensureManifest } from './packages.js';
6
+ /**
7
+ * A directory of peers that are kept rather than typed again.
8
+ *
9
+ * `start_fake` answers calls from a contract, which is the two-minute answer and is gone when the
10
+ * conversation ends. A script is the other thing: a real program using the client package, living in
11
+ * a file, that can drive a sequence, poll a device and log what it sees, bridge two networks, or
12
+ * stand up a simulator far past what a method body can hold. It is written once and started again
13
+ * next week.
14
+ *
15
+ * **Each script is its own process.** That is the whole design decision. It means a script can
16
+ * `import` whatever it likes and be run by hand with `node` as easily as through a tool; it means a
17
+ * script that throws, leaks or wedges cannot take the server down with it; and it means starting and
18
+ * stopping are a spawn and a kill rather than a module cache to reason about.
19
+ *
20
+ * It also means a script is **arbitrary code with the privileges of whoever started this server** -
21
+ * more than the sandboxed handlers in handlers.ts, not less. `--scripts <dir>` is what permits it,
22
+ * absent by default, and the directory says exactly where the writing is allowed to happen.
23
+ *
24
+ * TypeScript is the default, because this library's whole idea is that a class is the contract: a
25
+ * script that says `import type { Pump } from '../plant.js'` gets the same typed proxy the rest of
26
+ * the codebase does, which is most of the reason to write one here rather than curl a socket. Node
27
+ * runs it directly - see `nodeArgvFor` for which versions, and `.mjs` for the ones that cannot.
28
+ */
29
+ /** Scripts are named, not pathed. A name that could climb out of the directory is refused. */
30
+ const SAFE_SCRIPT_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
31
+ /** TypeScript first; `.mjs` for a Node too old to run it, or an author who would rather not. */
32
+ export const SCRIPT_EXTENSIONS = ['.ts', '.mjs'];
33
+ /** Recent output per script, so a model can read what one printed without a terminal. Bounded, since a chatty script must not grow without limit. */
34
+ const KEPT_LINES = 200;
35
+ const assertUsable = (name) => {
36
+ if (!SAFE_SCRIPT_NAME.test(name))
37
+ throw new Error(`'${name}' is not a usable script name`);
38
+ };
39
+ /** Where a script of this language would be written. Proven to stay inside the directory. */
40
+ export const scriptPath = (directory, name, language = 'ts') => {
41
+ assertUsable(name);
42
+ const file = resolve(join(directory, `${name}.${language}`));
43
+ // Proven to stay inside rather than assumed to, the same way contracts and console assets are.
44
+ if (!file.startsWith(resolve(directory) + sep))
45
+ throw new Error(`'${name}' would write outside the scripts directory`);
46
+ return file;
47
+ };
48
+ /** The file a saved script actually occupies, whichever language it was written in. */
49
+ export const scriptFile = (directory, name) => {
50
+ assertUsable(name);
51
+ for (const extension of SCRIPT_EXTENSIONS) {
52
+ const file = scriptPath(directory, name, extension.slice(1));
53
+ if (existsSync(file))
54
+ return file;
55
+ }
56
+ throw new Error(`no script called '${name}' in the scripts directory`);
57
+ };
58
+ export const saveScript = (directory, name, source, language = 'ts') => {
59
+ const file = scriptPath(directory, name, language);
60
+ mkdirSync(resolve(directory), { recursive: true });
61
+ // Written on the way past, because a `.ts` script using `import` inside a CommonJS project makes
62
+ // Node warn on every run, and that warning lands in the script's own output.
63
+ ensureManifest(directory);
64
+ // The other language's file goes, so a script rewritten as TypeScript does not leave a stale
65
+ // .mjs behind for scriptFile to find first.
66
+ for (const other of SCRIPT_EXTENSIONS) {
67
+ const candidate = scriptPath(directory, name, other.slice(1));
68
+ if (candidate !== file && existsSync(candidate))
69
+ rmSync(candidate);
70
+ }
71
+ writeFileSync(file, source.endsWith('\n') ? source : `${source}\n`, 'utf8');
72
+ return file;
73
+ };
74
+ export const readScript = (directory, name) => readFileSync(scriptFile(directory, name), 'utf8');
75
+ export const deleteScript = (directory, name) => rmSync(scriptFile(directory, name));
76
+ export const listScripts = (directory) => {
77
+ try {
78
+ return readdirSync(resolve(directory))
79
+ .filter((file) => SCRIPT_EXTENSIONS.includes(extname(file)))
80
+ .map((file) => ({ name: file.slice(0, -extname(file).length), language: extname(file).slice(1) }))
81
+ .sort((a, b) => a.name.localeCompare(b.name));
82
+ }
83
+ catch {
84
+ // Not yet created is not an error: it is an empty directory that costs nothing to report.
85
+ return [];
86
+ }
87
+ };
88
+ /**
89
+ * What this Node needs in order to run that file.
90
+ *
91
+ * Type stripping landed behind `--experimental-strip-types` in 22.6 and became the default in 23.6,
92
+ * so the flag is passed only where it is both needed and understood - passing it to 24 is a warning,
93
+ * and passing it to 22.5 is an error about an unknown option rather than about TypeScript.
94
+ */
95
+ export const nodeArgvFor = (file, version = process.versions.node) => {
96
+ if (extname(file) !== '.ts')
97
+ return [file];
98
+ const [major = 0, minor = 0] = version.split('.').map(Number);
99
+ if (major > 23 || (major === 23 && minor >= 6))
100
+ return [file];
101
+ if (major > 22 || (major === 22 && minor >= 6))
102
+ return ['--experimental-strip-types', file];
103
+ throw new Error(`this Node (v${version}) cannot run TypeScript directly - it arrived in 22.6. Save the script as .mjs, or run the server on a newer Node.`);
104
+ };
105
+ /**
106
+ * The network this server is on, handed to a script as environment.
107
+ *
108
+ * So a script does not hardcode a broker url that is right on one machine and wrong on the next -
109
+ * and so a model writing one has something to read rather than a value to invent. The names match
110
+ * the flags they came from, and `SOURCE_RPC_TOKEN` is the one the CLI already reads for credentials.
111
+ */
112
+ export const environmentFor = (options) => ({
113
+ ...(options.broker ? { SOURCE_RPC_BROKER: options.broker } : {}),
114
+ ...(options.hub ? { SOURCE_RPC_HUB: options.hub } : {}),
115
+ ...(options.prefix ? { SOURCE_RPC_PREFIX: options.prefix } : {}),
116
+ ...(options.hubCredentials?.token
117
+ ? { SOURCE_RPC_TOKEN: options.hubCredentials.token }
118
+ : {})
119
+ });
120
+ /** Starts, stops and remembers the scripts this server is running. */
121
+ export class ScriptRunner {
122
+ directory;
123
+ environment;
124
+ running = new Map();
125
+ finished = new Map();
126
+ constructor(directory, environment = {}) {
127
+ this.directory = directory;
128
+ this.environment = environment;
129
+ }
130
+ isRunning(name) {
131
+ return this.running.has(name);
132
+ }
133
+ start(name) {
134
+ if (this.running.has(name))
135
+ throw new Error(`'${name}' is already running. Stop it first.`);
136
+ const file = scriptFile(this.directory, name);
137
+ const argv = nodeArgvFor(file);
138
+ const record = { name, startedAt: Date.now(), output: [] };
139
+ const child = spawn(process.execPath, argv, {
140
+ // The script's own directory, so a relative import in it means what its author meant and
141
+ // `@source-repo/rpc` resolves from the project the directory sits in.
142
+ cwd: resolve(this.directory),
143
+ env: { ...process.env, ...this.environment },
144
+ stdio: ['ignore', 'pipe', 'pipe']
145
+ });
146
+ record.pid = child.pid ?? undefined;
147
+ const keep = (prefix) => (line) => {
148
+ record.output.push(`${prefix}${line}`);
149
+ if (record.output.length > KEPT_LINES)
150
+ record.output.splice(0, record.output.length - KEPT_LINES);
151
+ };
152
+ if (child.stdout)
153
+ createInterface({ input: child.stdout }).on('line', keep(''));
154
+ if (child.stderr)
155
+ createInterface({ input: child.stderr }).on('line', keep('! '));
156
+ child.on('exit', (code, signal) => {
157
+ record.ended = { code, signal, at: Date.now() };
158
+ this.running.delete(name);
159
+ // Kept rather than dropped: "it stopped, and here is the last thing it said" is the
160
+ // answer somebody wants, and it is gone if the record goes with the process.
161
+ this.finished.set(name, record);
162
+ });
163
+ child.on('error', (e) => keep('! ')(`could not start: ${e.message}`));
164
+ this.running.set(name, { child, record });
165
+ return record;
166
+ }
167
+ async stop(name) {
168
+ const entry = this.running.get(name);
169
+ if (!entry)
170
+ throw new Error(`'${name}' is not running here.`);
171
+ entry.child.kill();
172
+ // A script that ignores SIGTERM is not allowed to hold the server open indefinitely.
173
+ const ended = new Promise((done) => entry.child.once('exit', () => done()));
174
+ const forced = new Promise((done) => setTimeout(() => {
175
+ entry.child.kill('SIGKILL');
176
+ done();
177
+ }, 2000).unref());
178
+ await Promise.race([ended, forced]);
179
+ this.running.delete(name);
180
+ return entry.record;
181
+ }
182
+ status(name) {
183
+ return this.running.get(name)?.record ?? this.finished.get(name);
184
+ }
185
+ all() {
186
+ return [...[...this.running.values()].map((entry) => entry.record), ...this.finished.values()];
187
+ }
188
+ async stopAll() {
189
+ for (const name of [...this.running.keys()])
190
+ await this.stop(name).catch(() => undefined);
191
+ }
192
+ }
193
+ //# sourceMappingURL=scripts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scripts.js","sourceRoot":"","sources":["../src/scripts.ts"],"names":[],"mappings":"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"}