@gobing-ai/ts-runtime 0.2.9 → 0.3.1
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/dist/fs.d.ts +4 -0
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +23 -1
- package/dist/plugin/capability-registry.d.ts +35 -0
- package/dist/plugin/capability-registry.d.ts.map +1 -0
- package/dist/plugin/capability-registry.js +43 -0
- package/dist/plugin/extension-loader.d.ts +66 -0
- package/dist/plugin/extension-loader.d.ts.map +1 -0
- package/dist/plugin/extension-loader.js +47 -0
- package/dist/plugin/extension-path.d.ts +15 -0
- package/dist/plugin/extension-path.d.ts.map +1 -0
- package/dist/plugin/extension-path.js +20 -0
- package/dist/plugin/index.d.ts +4 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +3 -0
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +1 -0
- package/package.json +6 -2
- package/src/fs.ts +25 -1
- package/src/plugin/capability-registry.ts +58 -0
- package/src/plugin/extension-loader.ts +105 -0
- package/src/plugin/extension-path.ts +20 -0
- package/src/plugin/index.ts +3 -0
- package/src/plugin.ts +1 -0
package/dist/fs.d.ts
CHANGED
|
@@ -26,7 +26,9 @@ export interface SyncFileSystem {
|
|
|
26
26
|
readFile(path: string): string;
|
|
27
27
|
writeFile(path: string, content: string): void;
|
|
28
28
|
mkdir(path: string): void;
|
|
29
|
+
exists(path: string): boolean;
|
|
29
30
|
readDir(path: string): string[];
|
|
31
|
+
stat(path: string): FileStat | null;
|
|
30
32
|
unlink(path: string): void;
|
|
31
33
|
}
|
|
32
34
|
export declare class NodeFileSystem implements FileSystem {
|
|
@@ -47,7 +49,9 @@ export declare class NodeSyncFileSystem implements SyncFileSystem {
|
|
|
47
49
|
readFile(path: string): string;
|
|
48
50
|
writeFile(path: string, content: string): void;
|
|
49
51
|
mkdir(path: string): void;
|
|
52
|
+
exists(path: string): boolean;
|
|
50
53
|
readDir(path: string): string[];
|
|
54
|
+
stat(path: string): FileStat | null;
|
|
51
55
|
unlink(path: string): void;
|
|
52
56
|
}
|
|
53
57
|
export declare class CloudflareFileSystem implements FileSystem {
|
package/dist/fs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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"}
|
package/dist/fs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirnamePath, getProcessCwd, joinPath, resolvePath } from './path.js';
|
|
3
3
|
let fsPromisesModule = null;
|
|
4
4
|
let fsModule = null;
|
|
@@ -89,9 +89,31 @@ export class NodeSyncFileSystem {
|
|
|
89
89
|
mkdir(path) {
|
|
90
90
|
mkdirSync(path, { recursive: true });
|
|
91
91
|
}
|
|
92
|
+
exists(path) {
|
|
93
|
+
try {
|
|
94
|
+
return this.stat(path) !== null;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
92
100
|
readDir(path) {
|
|
93
101
|
return readdirSync(path);
|
|
94
102
|
}
|
|
103
|
+
stat(path) {
|
|
104
|
+
try {
|
|
105
|
+
const value = statSync(path);
|
|
106
|
+
return {
|
|
107
|
+
isFile: () => value.isFile(),
|
|
108
|
+
isDirectory: () => value.isDirectory(),
|
|
109
|
+
size: value.size,
|
|
110
|
+
mtimeMs: value.mtimeMs,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
95
117
|
unlink(path) {
|
|
96
118
|
rmSync(path, { recursive: true, force: true });
|
|
97
119
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Registry origin for a host capability. */
|
|
2
|
+
export type CapabilityOrigin = 'builtin' | 'extension';
|
|
3
|
+
/** Registry entry metadata. */
|
|
4
|
+
export interface CapabilityEntry<TCapability> {
|
|
5
|
+
/** Capability implementation. */
|
|
6
|
+
capability: TCapability;
|
|
7
|
+
/** Registration origin. */
|
|
8
|
+
origin: CapabilityOrigin;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Generic, domain-agnostic capability registry shared by engine hosts.
|
|
12
|
+
*
|
|
13
|
+
* Stores named capabilities of an opaque type `TCapability` with replace-by-name
|
|
14
|
+
* semantics and `origin` metadata. It knows nothing about what a capability *is* —
|
|
15
|
+
* each engine supplies its own type and owns its own error types and override
|
|
16
|
+
* semantics (the registry only reports what it stores).
|
|
17
|
+
*/
|
|
18
|
+
export declare class CapabilityRegistry<TCapability> {
|
|
19
|
+
private readonly kind;
|
|
20
|
+
private readonly capabilities;
|
|
21
|
+
constructor(kind: string);
|
|
22
|
+
/** Register or replace a capability. */
|
|
23
|
+
register(name: string, capability: TCapability, origin?: CapabilityOrigin): void;
|
|
24
|
+
/** Return true when a capability exists. */
|
|
25
|
+
has(name: string): boolean;
|
|
26
|
+
/** Get a registered capability or throw a clear error. */
|
|
27
|
+
get(name: string): TCapability;
|
|
28
|
+
/** Get a registered entry (capability plus origin) without throwing. */
|
|
29
|
+
getEntry(name: string): CapabilityEntry<TCapability> | undefined;
|
|
30
|
+
/** List registered capability names in insertion order. */
|
|
31
|
+
list(): string[];
|
|
32
|
+
/** List registered entries (name + capability + origin) in insertion order. */
|
|
33
|
+
entries(): Array<readonly [string, CapabilityEntry<TCapability>]>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=capability-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability-registry.d.ts","sourceRoot":"","sources":["../../src/plugin/capability-registry.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,WAAW,CAAC;AAEvD,+BAA+B;AAC/B,MAAM,WAAW,eAAe,CAAC,WAAW;IACxC,iCAAiC;IACjC,UAAU,EAAE,WAAW,CAAC;IACxB,2BAA2B;IAC3B,MAAM,EAAE,gBAAgB,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,qBAAa,kBAAkB,CAAC,WAAW;IAG3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAmD;gBAEnD,IAAI,EAAE,MAAM;IAEzC,wCAAwC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAE,gBAA8B,GAAG,IAAI;IAI7F,4CAA4C;IAC5C,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAI1B,0DAA0D;IAC1D,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW;IAQ9B,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAAC,WAAW,CAAC,GAAG,SAAS;IAIhE,2DAA2D;IAC3D,IAAI,IAAI,MAAM,EAAE;IAIhB,+EAA+E;IAC/E,OAAO,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,eAAe,CAAC,WAAW,CAAC,CAAC,CAAC;CAGpE"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic, domain-agnostic capability registry shared by engine hosts.
|
|
3
|
+
*
|
|
4
|
+
* Stores named capabilities of an opaque type `TCapability` with replace-by-name
|
|
5
|
+
* semantics and `origin` metadata. It knows nothing about what a capability *is* —
|
|
6
|
+
* each engine supplies its own type and owns its own error types and override
|
|
7
|
+
* semantics (the registry only reports what it stores).
|
|
8
|
+
*/
|
|
9
|
+
export class CapabilityRegistry {
|
|
10
|
+
kind;
|
|
11
|
+
capabilities = new Map();
|
|
12
|
+
constructor(kind) {
|
|
13
|
+
this.kind = kind;
|
|
14
|
+
}
|
|
15
|
+
/** Register or replace a capability. */
|
|
16
|
+
register(name, capability, origin = 'extension') {
|
|
17
|
+
this.capabilities.set(name, { capability, origin });
|
|
18
|
+
}
|
|
19
|
+
/** Return true when a capability exists. */
|
|
20
|
+
has(name) {
|
|
21
|
+
return this.capabilities.has(name);
|
|
22
|
+
}
|
|
23
|
+
/** Get a registered capability or throw a clear error. */
|
|
24
|
+
get(name) {
|
|
25
|
+
const entry = this.capabilities.get(name);
|
|
26
|
+
if (entry === undefined) {
|
|
27
|
+
throw new Error(`Unknown ${this.kind}: ${name}`);
|
|
28
|
+
}
|
|
29
|
+
return entry.capability;
|
|
30
|
+
}
|
|
31
|
+
/** Get a registered entry (capability plus origin) without throwing. */
|
|
32
|
+
getEntry(name) {
|
|
33
|
+
return this.capabilities.get(name);
|
|
34
|
+
}
|
|
35
|
+
/** List registered capability names in insertion order. */
|
|
36
|
+
list() {
|
|
37
|
+
return [...this.capabilities.keys()];
|
|
38
|
+
}
|
|
39
|
+
/** List registered entries (name + capability + origin) in insertion order. */
|
|
40
|
+
entries() {
|
|
41
|
+
return [...this.capabilities.entries()];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single extension module reference.
|
|
3
|
+
*
|
|
4
|
+
* `kind` is an engine-defined tag the registration callback uses to route the
|
|
5
|
+
* module to the right registry — the loader itself never interprets it. `path` is
|
|
6
|
+
* the relative path as authored; the loader **derives** the import target by
|
|
7
|
+
* resolving `path` against `baseDir` *after* validating it, so the trust guard
|
|
8
|
+
* always governs the actual module that gets imported. `sourceName` identifies the
|
|
9
|
+
* declaring config for diagnostics.
|
|
10
|
+
*/
|
|
11
|
+
export interface ExtensionRef<TExtensionKind extends string = string> {
|
|
12
|
+
/** Engine-defined capability tag, used only by the registration callback. */
|
|
13
|
+
readonly kind: TExtensionKind;
|
|
14
|
+
/** Relative path as authored, enforced by {@link assertRelativeExtensionPath}. */
|
|
15
|
+
readonly path: string;
|
|
16
|
+
/** Absolute directory the authored `path` is resolved against. */
|
|
17
|
+
readonly baseDir: string;
|
|
18
|
+
/** Name of the config (preset, workflow, …) that declared this ref. */
|
|
19
|
+
readonly sourceName: string;
|
|
20
|
+
}
|
|
21
|
+
/** Options controlling extension-module loading. */
|
|
22
|
+
export interface LoadExtensionsOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Whether to actually import extension modules. Defaults to `false`: loading
|
|
25
|
+
* arbitrary code is a trust decision the caller must make explicitly. When refs
|
|
26
|
+
* exist and this is not `true`, loading throws **before any import**.
|
|
27
|
+
*/
|
|
28
|
+
readonly allowExtensions?: boolean;
|
|
29
|
+
/** Optional sink for non-fatal warnings (e.g. capability overrides). */
|
|
30
|
+
readonly logger?: {
|
|
31
|
+
warn: (message: string) => void;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Required module loader. The generic core never performs a dynamic `import`
|
|
35
|
+
* itself — the embedder supplies the import policy (typically
|
|
36
|
+
* `(absPath) => import(absPath)`), and tests pass a stub. Keeping this explicit
|
|
37
|
+
* means the shared core has no ambient code-loading capability of its own.
|
|
38
|
+
*/
|
|
39
|
+
readonly moduleLoader: (absPath: string) => Promise<Record<string, unknown>>;
|
|
40
|
+
}
|
|
41
|
+
/** A validated extension module export: an object carrying at least a string `name`. */
|
|
42
|
+
export interface LoadedExtension {
|
|
43
|
+
/** Stable name of the contributed capability bundle. */
|
|
44
|
+
readonly name: string;
|
|
45
|
+
/** Engine-specific contribution payload (actions, evaluators, …). */
|
|
46
|
+
readonly [key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Import each extension module behind an explicit trust gate, validate its export,
|
|
50
|
+
* then hand it to an engine-provided registration callback.
|
|
51
|
+
*
|
|
52
|
+
* The loader is domain-agnostic: it does not know which registry a module belongs to.
|
|
53
|
+
* It enforces the trust boundary (gate + relative-path guard), validates the
|
|
54
|
+
* default/named-`extension` export shape, and delegates routing to `register`.
|
|
55
|
+
*
|
|
56
|
+
* Security invariants:
|
|
57
|
+
* - No refs → no-op.
|
|
58
|
+
* - Refs present but `allowExtensions !== true` → throws **before** any `import` or
|
|
59
|
+
* `moduleLoader` call, so a declared extension is never silently dropped.
|
|
60
|
+
* - Every ref's authored path is re-validated by {@link assertRelativeExtensionPath}.
|
|
61
|
+
*
|
|
62
|
+
* @throws When extensions are present but `allowExtensions` is not `true`, when a path
|
|
63
|
+
* fails the trust guard, or when a module lacks a valid `name`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function loadExtensionModules<TExtensionKind extends string>(refs: readonly ExtensionRef<TExtensionKind>[], options: LoadExtensionsOptions, register: (ref: ExtensionRef<TExtensionKind>, extension: LoadedExtension) => void | Promise<void>): Promise<void>;
|
|
66
|
+
//# sourceMappingURL=extension-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-loader.d.ts","sourceRoot":"","sources":["../../src/plugin/extension-loader.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY,CAAC,cAAc,SAAS,MAAM,GAAG,MAAM;IAChE,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,kFAAkF;IAClF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC/B;AAED,oDAAoD;AACpD,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,CAAC;IACtD;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAChF;AAED,wFAAwF;AACxF,MAAM,WAAW,eAAe;IAC5B,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,oBAAoB,CAAC,cAAc,SAAS,MAAM,EACpE,IAAI,EAAE,SAAS,YAAY,CAAC,cAAc,CAAC,EAAE,EAC7C,OAAO,EAAE,qBAAqB,EAC9B,QAAQ,EAAE,CAAC,GAAG,EAAE,YAAY,CAAC,cAAc,CAAC,EAAE,SAAS,EAAE,eAAe,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAClG,OAAO,CAAC,IAAI,CAAC,CAgCf"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
2
|
+
import { assertRelativeExtensionPath } from './extension-path.js';
|
|
3
|
+
/**
|
|
4
|
+
* Import each extension module behind an explicit trust gate, validate its export,
|
|
5
|
+
* then hand it to an engine-provided registration callback.
|
|
6
|
+
*
|
|
7
|
+
* The loader is domain-agnostic: it does not know which registry a module belongs to.
|
|
8
|
+
* It enforces the trust boundary (gate + relative-path guard), validates the
|
|
9
|
+
* default/named-`extension` export shape, and delegates routing to `register`.
|
|
10
|
+
*
|
|
11
|
+
* Security invariants:
|
|
12
|
+
* - No refs → no-op.
|
|
13
|
+
* - Refs present but `allowExtensions !== true` → throws **before** any `import` or
|
|
14
|
+
* `moduleLoader` call, so a declared extension is never silently dropped.
|
|
15
|
+
* - Every ref's authored path is re-validated by {@link assertRelativeExtensionPath}.
|
|
16
|
+
*
|
|
17
|
+
* @throws When extensions are present but `allowExtensions` is not `true`, when a path
|
|
18
|
+
* fails the trust guard, or when a module lacks a valid `name`.
|
|
19
|
+
*/
|
|
20
|
+
export async function loadExtensionModules(refs, options, register) {
|
|
21
|
+
if (refs.length === 0)
|
|
22
|
+
return;
|
|
23
|
+
// Fail closed before importing anything: a declared extension under a disabled gate
|
|
24
|
+
// is a hard error, never a silent drop.
|
|
25
|
+
if (options.allowExtensions !== true) {
|
|
26
|
+
const first = refs[0];
|
|
27
|
+
throw new Error(`"${first.sourceName}" declares ${first.kind} extension "${first.path}", but extensions are disabled — pass allowExtensions: true to load extension modules`);
|
|
28
|
+
}
|
|
29
|
+
for (const ref of refs) {
|
|
30
|
+
if (!isAbsolute(ref.baseDir)) {
|
|
31
|
+
throw new Error(`"${ref.sourceName}" extension baseDir "${ref.baseDir}" must be an absolute directory`);
|
|
32
|
+
}
|
|
33
|
+
// Validate the authored path, then derive the import target from it so the
|
|
34
|
+
// trust guard always governs the module actually imported — the loader never
|
|
35
|
+
// imports a caller-supplied absolute path it did not resolve itself.
|
|
36
|
+
assertRelativeExtensionPath(ref.path, { sourceName: ref.sourceName });
|
|
37
|
+
const absPath = resolve(ref.baseDir, ref.path);
|
|
38
|
+
const moduleExports = await options.moduleLoader(absPath);
|
|
39
|
+
const candidate = moduleExports.default ?? moduleExports.extension;
|
|
40
|
+
if (candidate === null ||
|
|
41
|
+
typeof candidate !== 'object' ||
|
|
42
|
+
typeof candidate.name !== 'string') {
|
|
43
|
+
throw new Error(`"${ref.sourceName}" extension "${ref.path}" must export an object with a string "name"`);
|
|
44
|
+
}
|
|
45
|
+
await register(ref, candidate);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assert that an extension module path is relative and does not escape its
|
|
3
|
+
* declaring directory.
|
|
4
|
+
*
|
|
5
|
+
* Extension declarations are data, and a path that is absolute or escapes via `..`
|
|
6
|
+
* is a trust-boundary violation even when extension loading is explicitly allowed.
|
|
7
|
+
* This is a standalone validator (not a schema refinement) so the loader can enforce
|
|
8
|
+
* it at load time, independent of any engine's config schema — defense in depth.
|
|
9
|
+
*
|
|
10
|
+
* @throws When the path is absolute or contains a `..` traversal segment.
|
|
11
|
+
*/
|
|
12
|
+
export declare function assertRelativeExtensionPath(path: string, options?: {
|
|
13
|
+
sourceName?: string;
|
|
14
|
+
}): void;
|
|
15
|
+
//# sourceMappingURL=extension-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extension-path.d.ts","sourceRoot":"","sources":["../../src/plugin/extension-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,IAAI,CAQrG"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assert that an extension module path is relative and does not escape its
|
|
3
|
+
* declaring directory.
|
|
4
|
+
*
|
|
5
|
+
* Extension declarations are data, and a path that is absolute or escapes via `..`
|
|
6
|
+
* is a trust-boundary violation even when extension loading is explicitly allowed.
|
|
7
|
+
* This is a standalone validator (not a schema refinement) so the loader can enforce
|
|
8
|
+
* it at load time, independent of any engine's config schema — defense in depth.
|
|
9
|
+
*
|
|
10
|
+
* @throws When the path is absolute or contains a `..` traversal segment.
|
|
11
|
+
*/
|
|
12
|
+
export function assertRelativeExtensionPath(path, options = {}) {
|
|
13
|
+
const where = options.sourceName !== undefined ? ` declared by "${options.sourceName}"` : '';
|
|
14
|
+
if (/^([/\\]|[A-Za-z]:[/\\])/.test(path)) {
|
|
15
|
+
throw new Error(`extension path "${path}"${where} must be relative (no absolute paths)`);
|
|
16
|
+
}
|
|
17
|
+
if (path.split(/[/\\]/).includes('..')) {
|
|
18
|
+
throw new Error(`extension path "${path}"${where} must not contain ".." traversal`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugin/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC"}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './plugin/index.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "@gobing-ai/ts-runtime — Runtime abstractions for Bun, Node, and Cloudflare Workers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -35,6 +35,10 @@
|
|
|
35
35
|
"./bun-sqlite": {
|
|
36
36
|
"types": "./dist/bun-sqlite.d.ts",
|
|
37
37
|
"import": "./dist/bun-sqlite.js"
|
|
38
|
+
},
|
|
39
|
+
"./plugin": {
|
|
40
|
+
"types": "./dist/plugin.d.ts",
|
|
41
|
+
"import": "./dist/plugin.js"
|
|
38
42
|
}
|
|
39
43
|
},
|
|
40
44
|
"files": [
|
|
@@ -54,7 +58,7 @@
|
|
|
54
58
|
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-runtime-v<version> && git push --tags' && exit 1"
|
|
55
59
|
},
|
|
56
60
|
"dependencies": {
|
|
57
|
-
"@gobing-ai/ts-utils": "^0.
|
|
61
|
+
"@gobing-ai/ts-utils": "^0.3.1",
|
|
58
62
|
"execa": "^9.5.0",
|
|
59
63
|
"yaml": "^2.7.0",
|
|
60
64
|
"zod": "^4.1.0"
|
package/src/fs.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirnamePath, getProcessCwd, joinPath, resolvePath } from './path';
|
|
3
3
|
|
|
4
4
|
export interface FileStat {
|
|
@@ -32,7 +32,9 @@ export interface SyncFileSystem {
|
|
|
32
32
|
readFile(path: string): string;
|
|
33
33
|
writeFile(path: string, content: string): void;
|
|
34
34
|
mkdir(path: string): void;
|
|
35
|
+
exists(path: string): boolean;
|
|
35
36
|
readDir(path: string): string[];
|
|
37
|
+
stat(path: string): FileStat | null;
|
|
36
38
|
unlink(path: string): void;
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -144,10 +146,32 @@ export class NodeSyncFileSystem implements SyncFileSystem {
|
|
|
144
146
|
mkdirSync(path, { recursive: true });
|
|
145
147
|
}
|
|
146
148
|
|
|
149
|
+
exists(path: string): boolean {
|
|
150
|
+
try {
|
|
151
|
+
return this.stat(path) !== null;
|
|
152
|
+
} catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
147
157
|
readDir(path: string): string[] {
|
|
148
158
|
return readdirSync(path);
|
|
149
159
|
}
|
|
150
160
|
|
|
161
|
+
stat(path: string): FileStat | null {
|
|
162
|
+
try {
|
|
163
|
+
const value = statSync(path);
|
|
164
|
+
return {
|
|
165
|
+
isFile: () => value.isFile(),
|
|
166
|
+
isDirectory: () => value.isDirectory(),
|
|
167
|
+
size: value.size,
|
|
168
|
+
mtimeMs: value.mtimeMs,
|
|
169
|
+
};
|
|
170
|
+
} catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
151
175
|
unlink(path: string): void {
|
|
152
176
|
rmSync(path, { recursive: true, force: true });
|
|
153
177
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** Registry origin for a host capability. */
|
|
2
|
+
export type CapabilityOrigin = 'builtin' | 'extension';
|
|
3
|
+
|
|
4
|
+
/** Registry entry metadata. */
|
|
5
|
+
export interface CapabilityEntry<TCapability> {
|
|
6
|
+
/** Capability implementation. */
|
|
7
|
+
capability: TCapability;
|
|
8
|
+
/** Registration origin. */
|
|
9
|
+
origin: CapabilityOrigin;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Generic, domain-agnostic capability registry shared by engine hosts.
|
|
14
|
+
*
|
|
15
|
+
* Stores named capabilities of an opaque type `TCapability` with replace-by-name
|
|
16
|
+
* semantics and `origin` metadata. It knows nothing about what a capability *is* —
|
|
17
|
+
* each engine supplies its own type and owns its own error types and override
|
|
18
|
+
* semantics (the registry only reports what it stores).
|
|
19
|
+
*/
|
|
20
|
+
export class CapabilityRegistry<TCapability> {
|
|
21
|
+
private readonly capabilities = new Map<string, CapabilityEntry<TCapability>>();
|
|
22
|
+
|
|
23
|
+
constructor(private readonly kind: string) {}
|
|
24
|
+
|
|
25
|
+
/** Register or replace a capability. */
|
|
26
|
+
register(name: string, capability: TCapability, origin: CapabilityOrigin = 'extension'): void {
|
|
27
|
+
this.capabilities.set(name, { capability, origin });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Return true when a capability exists. */
|
|
31
|
+
has(name: string): boolean {
|
|
32
|
+
return this.capabilities.has(name);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Get a registered capability or throw a clear error. */
|
|
36
|
+
get(name: string): TCapability {
|
|
37
|
+
const entry = this.capabilities.get(name);
|
|
38
|
+
if (entry === undefined) {
|
|
39
|
+
throw new Error(`Unknown ${this.kind}: ${name}`);
|
|
40
|
+
}
|
|
41
|
+
return entry.capability;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Get a registered entry (capability plus origin) without throwing. */
|
|
45
|
+
getEntry(name: string): CapabilityEntry<TCapability> | undefined {
|
|
46
|
+
return this.capabilities.get(name);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** List registered capability names in insertion order. */
|
|
50
|
+
list(): string[] {
|
|
51
|
+
return [...this.capabilities.keys()];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** List registered entries (name + capability + origin) in insertion order. */
|
|
55
|
+
entries(): Array<readonly [string, CapabilityEntry<TCapability>]> {
|
|
56
|
+
return [...this.capabilities.entries()];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
2
|
+
import { assertRelativeExtensionPath } from './extension-path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A single extension module reference.
|
|
6
|
+
*
|
|
7
|
+
* `kind` is an engine-defined tag the registration callback uses to route the
|
|
8
|
+
* module to the right registry — the loader itself never interprets it. `path` is
|
|
9
|
+
* the relative path as authored; the loader **derives** the import target by
|
|
10
|
+
* resolving `path` against `baseDir` *after* validating it, so the trust guard
|
|
11
|
+
* always governs the actual module that gets imported. `sourceName` identifies the
|
|
12
|
+
* declaring config for diagnostics.
|
|
13
|
+
*/
|
|
14
|
+
export interface ExtensionRef<TExtensionKind extends string = string> {
|
|
15
|
+
/** Engine-defined capability tag, used only by the registration callback. */
|
|
16
|
+
readonly kind: TExtensionKind;
|
|
17
|
+
/** Relative path as authored, enforced by {@link assertRelativeExtensionPath}. */
|
|
18
|
+
readonly path: string;
|
|
19
|
+
/** Absolute directory the authored `path` is resolved against. */
|
|
20
|
+
readonly baseDir: string;
|
|
21
|
+
/** Name of the config (preset, workflow, …) that declared this ref. */
|
|
22
|
+
readonly sourceName: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Options controlling extension-module loading. */
|
|
26
|
+
export interface LoadExtensionsOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Whether to actually import extension modules. Defaults to `false`: loading
|
|
29
|
+
* arbitrary code is a trust decision the caller must make explicitly. When refs
|
|
30
|
+
* exist and this is not `true`, loading throws **before any import**.
|
|
31
|
+
*/
|
|
32
|
+
readonly allowExtensions?: boolean;
|
|
33
|
+
/** Optional sink for non-fatal warnings (e.g. capability overrides). */
|
|
34
|
+
readonly logger?: { warn: (message: string) => void };
|
|
35
|
+
/**
|
|
36
|
+
* Required module loader. The generic core never performs a dynamic `import`
|
|
37
|
+
* itself — the embedder supplies the import policy (typically
|
|
38
|
+
* `(absPath) => import(absPath)`), and tests pass a stub. Keeping this explicit
|
|
39
|
+
* means the shared core has no ambient code-loading capability of its own.
|
|
40
|
+
*/
|
|
41
|
+
readonly moduleLoader: (absPath: string) => Promise<Record<string, unknown>>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A validated extension module export: an object carrying at least a string `name`. */
|
|
45
|
+
export interface LoadedExtension {
|
|
46
|
+
/** Stable name of the contributed capability bundle. */
|
|
47
|
+
readonly name: string;
|
|
48
|
+
/** Engine-specific contribution payload (actions, evaluators, …). */
|
|
49
|
+
readonly [key: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Import each extension module behind an explicit trust gate, validate its export,
|
|
54
|
+
* then hand it to an engine-provided registration callback.
|
|
55
|
+
*
|
|
56
|
+
* The loader is domain-agnostic: it does not know which registry a module belongs to.
|
|
57
|
+
* It enforces the trust boundary (gate + relative-path guard), validates the
|
|
58
|
+
* default/named-`extension` export shape, and delegates routing to `register`.
|
|
59
|
+
*
|
|
60
|
+
* Security invariants:
|
|
61
|
+
* - No refs → no-op.
|
|
62
|
+
* - Refs present but `allowExtensions !== true` → throws **before** any `import` or
|
|
63
|
+
* `moduleLoader` call, so a declared extension is never silently dropped.
|
|
64
|
+
* - Every ref's authored path is re-validated by {@link assertRelativeExtensionPath}.
|
|
65
|
+
*
|
|
66
|
+
* @throws When extensions are present but `allowExtensions` is not `true`, when a path
|
|
67
|
+
* fails the trust guard, or when a module lacks a valid `name`.
|
|
68
|
+
*/
|
|
69
|
+
export async function loadExtensionModules<TExtensionKind extends string>(
|
|
70
|
+
refs: readonly ExtensionRef<TExtensionKind>[],
|
|
71
|
+
options: LoadExtensionsOptions,
|
|
72
|
+
register: (ref: ExtensionRef<TExtensionKind>, extension: LoadedExtension) => void | Promise<void>,
|
|
73
|
+
): Promise<void> {
|
|
74
|
+
if (refs.length === 0) return;
|
|
75
|
+
|
|
76
|
+
// Fail closed before importing anything: a declared extension under a disabled gate
|
|
77
|
+
// is a hard error, never a silent drop.
|
|
78
|
+
if (options.allowExtensions !== true) {
|
|
79
|
+
const first = refs[0] as ExtensionRef<TExtensionKind>;
|
|
80
|
+
throw new Error(
|
|
81
|
+
`"${first.sourceName}" declares ${first.kind} extension "${first.path}", but extensions are disabled — pass allowExtensions: true to load extension modules`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const ref of refs) {
|
|
86
|
+
if (!isAbsolute(ref.baseDir)) {
|
|
87
|
+
throw new Error(`"${ref.sourceName}" extension baseDir "${ref.baseDir}" must be an absolute directory`);
|
|
88
|
+
}
|
|
89
|
+
// Validate the authored path, then derive the import target from it so the
|
|
90
|
+
// trust guard always governs the module actually imported — the loader never
|
|
91
|
+
// imports a caller-supplied absolute path it did not resolve itself.
|
|
92
|
+
assertRelativeExtensionPath(ref.path, { sourceName: ref.sourceName });
|
|
93
|
+
const absPath = resolve(ref.baseDir, ref.path);
|
|
94
|
+
const moduleExports = await options.moduleLoader(absPath);
|
|
95
|
+
const candidate = moduleExports.default ?? moduleExports.extension;
|
|
96
|
+
if (
|
|
97
|
+
candidate === null ||
|
|
98
|
+
typeof candidate !== 'object' ||
|
|
99
|
+
typeof (candidate as { name?: unknown }).name !== 'string'
|
|
100
|
+
) {
|
|
101
|
+
throw new Error(`"${ref.sourceName}" extension "${ref.path}" must export an object with a string "name"`);
|
|
102
|
+
}
|
|
103
|
+
await register(ref, candidate as LoadedExtension);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assert that an extension module path is relative and does not escape its
|
|
3
|
+
* declaring directory.
|
|
4
|
+
*
|
|
5
|
+
* Extension declarations are data, and a path that is absolute or escapes via `..`
|
|
6
|
+
* is a trust-boundary violation even when extension loading is explicitly allowed.
|
|
7
|
+
* This is a standalone validator (not a schema refinement) so the loader can enforce
|
|
8
|
+
* it at load time, independent of any engine's config schema — defense in depth.
|
|
9
|
+
*
|
|
10
|
+
* @throws When the path is absolute or contains a `..` traversal segment.
|
|
11
|
+
*/
|
|
12
|
+
export function assertRelativeExtensionPath(path: string, options: { sourceName?: string } = {}): void {
|
|
13
|
+
const where = options.sourceName !== undefined ? ` declared by "${options.sourceName}"` : '';
|
|
14
|
+
if (/^([/\\]|[A-Za-z]:[/\\])/.test(path)) {
|
|
15
|
+
throw new Error(`extension path "${path}"${where} must be relative (no absolute paths)`);
|
|
16
|
+
}
|
|
17
|
+
if (path.split(/[/\\]/).includes('..')) {
|
|
18
|
+
throw new Error(`extension path "${path}"${where} must not contain ".." traversal`);
|
|
19
|
+
}
|
|
20
|
+
}
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './plugin/index';
|