@adhd/apigen-cli 0.1.0 → 0.1.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/CHANGELOG.md +890 -73
- package/README.md +371 -83
- package/index.js +96 -31
- package/index.mjs +12140 -4448
- package/lib/commands/generate-registry.d.ts +1 -1
- package/lib/commands/generate.d.ts +42 -1
- package/lib/commands/list-types.d.ts +11 -0
- package/lib/commands/run-registry.d.ts +1 -1
- package/lib/commands/run.d.ts +46 -1
- package/lib/commands/serve.d.ts +158 -0
- package/lib/import-source.d.ts +15 -4
- package/lib/logging.d.ts +1 -1
- package/lib/orchestrator.d.ts +76 -11
- package/lib/plugin-registry.d.ts +33 -0
- package/package.json +23 -9
- package/default-tsconfig.json +0 -10
- package/lib/pipeline.d.ts +0 -22
|
@@ -1,6 +1,47 @@
|
|
|
1
|
-
import { ExportMode, OutputPlugin } from '@adhd/apigen-core';
|
|
1
|
+
import { ExportMode, OutputPlugin, ComposedSchemas } from '@adhd/apigen-core-client';
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Recursively walk a JSON-Schema node (any depth) and collect every
|
|
6
|
+
* unique `format` string value found anywhere in the tree.
|
|
7
|
+
*
|
|
8
|
+
* Pure function: no I/O, no mutation of the input.
|
|
9
|
+
*
|
|
10
|
+
* @param node Any JSON value (Schema node, array, primitive).
|
|
11
|
+
* @param seen Cycle guard — the set of objects already visited (prevents
|
|
12
|
+
* infinite loops on schemas with `definitions` back-refs).
|
|
13
|
+
* @returns A `Set<string>` of all format strings found.
|
|
14
|
+
*/
|
|
15
|
+
export declare function collectFormats(node: unknown, seen?: WeakSet<object>): Set<string>;
|
|
16
|
+
/**
|
|
17
|
+
* Given a surface's `ComposedSchemas`, return the npm dependency entries
|
|
18
|
+
* required to support the logical types actually used by its operations.
|
|
19
|
+
*
|
|
20
|
+
* Walks every input + output schema, unions their `format` annotations,
|
|
21
|
+
* looks each up in the authoritative {@link tsDepMap} from `@adhd/apigen-base-logical`,
|
|
22
|
+
* and returns a `Record<name, version>` suitable for merging into `package.json`
|
|
23
|
+
* `dependencies`.
|
|
24
|
+
*
|
|
25
|
+
* A surface with NO rich types returns an empty record (no `decimal.js`,
|
|
26
|
+
* etc.). A surface using `Decimal` returns `{ 'decimal.js': '^10' }`.
|
|
27
|
+
*
|
|
28
|
+
* @param schemas The surface's composed schema map (fn-name → {input, output}).
|
|
29
|
+
*/
|
|
30
|
+
export declare function collectLogicalTypeDeps(schemas: ComposedSchemas | Record<string, {
|
|
31
|
+
input: Record<string, unknown>;
|
|
32
|
+
output: Record<string, unknown>;
|
|
33
|
+
}>): Record<string, string>;
|
|
34
|
+
/**
|
|
35
|
+
* Patch the generated `package.json` in `outputDir` by merging in
|
|
36
|
+
* `logicalTypeDeps`. When the `package.json` has no deps or the dep
|
|
37
|
+
* map is empty this is a no-op (safe to call unconditionally).
|
|
38
|
+
*
|
|
39
|
+
* Called AFTER {@link emitResolutionScaffolding} so the base deps
|
|
40
|
+
* (apigen-runtime, sdk) are already present.
|
|
41
|
+
*
|
|
42
|
+
* Exported for testing; the generate command is the normal caller.
|
|
43
|
+
*/
|
|
44
|
+
export declare function patchPackageJsonDeps(outputDir: string, logicalTypeDeps: Record<string, string>): void;
|
|
4
45
|
/** Resolve the --export flag value to an ExportMode. */
|
|
5
46
|
export declare function resolveExportMode(exportFlag: string | undefined): ExportMode;
|
|
6
47
|
export declare function registerGenerateCommand(program: Command, plugins: Record<string, OutputPlugin>): void;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { OutputPlugin } from '@adhd/apigen-core-client';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `apigen list-types` — the only discovery mechanism for valid `--type`
|
|
6
|
+
* values before FEAT-APIGEN-019 was reading source, hitting an error, or
|
|
7
|
+
* already knowing them. Output is derived live from the same `plugins` map
|
|
8
|
+
* every other command receives, so it can never drift from what's actually
|
|
9
|
+
* registered (see `../plugin-registry`).
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerListTypesCommand(program: Command, plugins: Record<string, OutputPlugin>, write?: (text: string) => void): void;
|
package/lib/commands/run.d.ts
CHANGED
|
@@ -1,4 +1,49 @@
|
|
|
1
|
-
import { OutputPlugin } from '@adhd/apigen-core';
|
|
2
1
|
import { Command } from 'commander';
|
|
2
|
+
import { ComposedSchemas, OutputPlugin, Plugin } from '@adhd/apigen-core-client';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Assert that the function table built from the source module is non-empty.
|
|
6
|
+
*
|
|
7
|
+
* A source that yields 0 functions is almost certainly generated output, a
|
|
8
|
+
* type-only file, or the wrong path — not a callable apigen surface. Failing
|
|
9
|
+
* here with an actionable message avoids the cryptic `ERR_MODULE_NOT_FOUND`
|
|
10
|
+
* crash that occurs later when the server tries to dispatch to a non-existent
|
|
11
|
+
* route.
|
|
12
|
+
*
|
|
13
|
+
* @param fns - Function table produced by `buildFnTable`.
|
|
14
|
+
* @param sourceFile - Absolute path to the source, for the error message.
|
|
15
|
+
* @throws if `fns` contains no entries.
|
|
16
|
+
*/
|
|
17
|
+
export declare function assertFnsNonEmpty(fns: Record<string, (...args: unknown[]) => unknown>, sourceFile: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* A resolver function that mimics `require.resolve` for a package name.
|
|
20
|
+
* Injected in tests to simulate absence without actually removing packages.
|
|
21
|
+
*/
|
|
22
|
+
export type LibResolver = (pkg: string) => string;
|
|
23
|
+
/**
|
|
24
|
+
* Assert that `decimal.js` is resolvable when any function in `schemas`
|
|
25
|
+
* uses a `format:'decimal'` parameter or return value.
|
|
26
|
+
*
|
|
27
|
+
* @param schemas - The composed schemas for the surface.
|
|
28
|
+
* @param resolver - Optional resolver; defaults to `require.resolve`.
|
|
29
|
+
* Injected in tests to simulate the lib being absent.
|
|
30
|
+
* @throws if decimal-using functions are found but `decimal.js` cannot resolve.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assertDecimalLibPresent(schemas: ComposedSchemas, resolver?: LibResolver): void;
|
|
33
|
+
/**
|
|
34
|
+
* Resolve `--use` specifiers into loaded {@link Plugin} objects.
|
|
35
|
+
*
|
|
36
|
+
* Resolution order per specifier:
|
|
37
|
+
* 1. Built-in slug (`health`, `logger`) → the statically-imported plugin.
|
|
38
|
+
* 2. Otherwise treat the specifier as a package name or local path and
|
|
39
|
+
* dynamically `import()` it (default or named `plugin`/`<id>Plugin` export).
|
|
40
|
+
*
|
|
41
|
+
* The loaded plugins are threaded to the run plugin via `options.usePlugins`
|
|
42
|
+
* so the transport adapter can compose their `layer`/`mount` capabilities
|
|
43
|
+
* (RunInput carries no dedicated field).
|
|
44
|
+
*
|
|
45
|
+
* @param specifiers - The raw `--use` values (slugs, package names, or paths).
|
|
46
|
+
* @returns The loaded plugin objects, in declaration order.
|
|
47
|
+
*/
|
|
48
|
+
export declare function loadUsePlugins(specifiers: string[]): Promise<Plugin[]>;
|
|
4
49
|
export declare function registerRunCommand(program: Command, plugins: Record<string, OutputPlugin>): void;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { PluginLanguage } from '@adhd/apigen-core-client';
|
|
2
|
+
import { ChildProcess } from 'node:child_process';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
|
|
5
|
+
import * as http from 'node:http';
|
|
6
|
+
/** A single mounted source: its namespace, language, plugin, file, and runtime. */
|
|
7
|
+
export interface Host {
|
|
8
|
+
/** Namespace prefix this host serves under (`/<ns>/*`). */
|
|
9
|
+
namespace: string;
|
|
10
|
+
/** The host language (`ts` | `py` | …). */
|
|
11
|
+
language: PluginLanguage;
|
|
12
|
+
/** The plugin id driving the child (`api-fastify`, `py-flask`, `py-grpc`, …). */
|
|
13
|
+
plugin: string;
|
|
14
|
+
/** Absolute path to the source file. */
|
|
15
|
+
source: string;
|
|
16
|
+
/** Internal loopback port the child listens on. */
|
|
17
|
+
port: number;
|
|
18
|
+
/**
|
|
19
|
+
* Transport protocol for this host.
|
|
20
|
+
* - `'http'` — child is an HTTP/1.1 server (api-fastify, py-flask).
|
|
21
|
+
* - `'grpc'` — child is a gRPC (HTTP/2) server (py-grpc).
|
|
22
|
+
*/
|
|
23
|
+
transport: 'http' | 'grpc';
|
|
24
|
+
/** The child process (set once spawned). */
|
|
25
|
+
child?: ChildProcess;
|
|
26
|
+
/** Liveness flag flipped to `false` when the child exits. */
|
|
27
|
+
alive: boolean;
|
|
28
|
+
/** Readiness flag flipped to `true` once the child is accepting connections. */
|
|
29
|
+
ready: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** Per-host status in the aggregate `_meta/health` (§13.1). */
|
|
32
|
+
export type HostHealthStatus = 'ready' | 'down';
|
|
33
|
+
/**
|
|
34
|
+
* Parse `--mount <ns>=<plugin>` pairs into a `{ ns → plugin }` record.
|
|
35
|
+
*
|
|
36
|
+
* @throws if a pair is missing the `=` separator or has an empty side.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseMounts(pairs: string[]): Record<string, string>;
|
|
39
|
+
/** Derive a source's default namespace: its filename stem. */
|
|
40
|
+
export declare function namespaceOfSource(file: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the {@link Host} descriptors (sans runtime fields) for the given
|
|
43
|
+
* `--source` list, applying any `--mount` overrides.
|
|
44
|
+
*
|
|
45
|
+
* Resolution per source:
|
|
46
|
+
* 1. `language = languageOfSource(file)` — unknown extension → error.
|
|
47
|
+
* 2. `namespace = stem(file)`.
|
|
48
|
+
* 3. `plugin = mounts[namespace] ?? DEFAULT_PLUGIN_FOR_LANGUAGE[language]`.
|
|
49
|
+
* 4. `transport = GRPC_PLUGINS.has(plugin) ? 'grpc' : 'http'`.
|
|
50
|
+
*
|
|
51
|
+
* @throws on an unknown extension, an unmappable language, or a duplicate
|
|
52
|
+
* namespace (two sources would collide on the same `/<ns>/*` prefix).
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveHosts(sources: string[], mounts: Record<string, string>): Host[];
|
|
55
|
+
/**
|
|
56
|
+
* Extract the leading path segment of a request URL (the namespace).
|
|
57
|
+
*
|
|
58
|
+
* `/users/getUser?x=1` → `users`; `/_meta/health` → `_meta`; `/` → `''`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function namespaceFromUrl(url: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Ask the OS for a free loopback TCP port by binding port 0 and reading back
|
|
63
|
+
* the assigned port, then releasing it. There is an unavoidable TOCTOU window
|
|
64
|
+
* between release and the child binding it; in practice the child binds within
|
|
65
|
+
* milliseconds and the kernel does not immediately re-hand the same ephemeral
|
|
66
|
+
* port, so collisions are vanishingly rare. Each host gets its own port.
|
|
67
|
+
*/
|
|
68
|
+
export declare function findFreePort(): Promise<number>;
|
|
69
|
+
/**
|
|
70
|
+
* Poll an HTTP host's `_meta/health` until it answers 2xx or the deadline passes.
|
|
71
|
+
*
|
|
72
|
+
* Event-driven: it re-probes on a short interval, but each probe is a real HTTP
|
|
73
|
+
* round-trip to the child — readiness is the child *actually answering*, never
|
|
74
|
+
* a wall-clock sleep. Aborts early (rejects) if the child exits first.
|
|
75
|
+
*
|
|
76
|
+
* @param host - The host to probe.
|
|
77
|
+
* @param timeoutMs - Overall budget (default 15 s — Python cold-start + import).
|
|
78
|
+
* @param intervalMs - Delay between probes (default 100 ms).
|
|
79
|
+
*/
|
|
80
|
+
export declare function waitForReady(host: Host, timeoutMs?: number, intervalMs?: number): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* The path used to re-invoke this CLI for a child `run`. When the CLI is the
|
|
83
|
+
* bundled standalone (`dist/.../index.js`), `process.argv[1]` is that bundle;
|
|
84
|
+
* spawning `node <bundle> run …` reuses the same inlined plugin graph. Tests
|
|
85
|
+
* override this to point at a stub.
|
|
86
|
+
*/
|
|
87
|
+
export declare function selfCliPath(): string;
|
|
88
|
+
/**
|
|
89
|
+
* Spawn one child `apigen run` for a HTTP host and wire its lifecycle.
|
|
90
|
+
*
|
|
91
|
+
* The child is started detached-free (same process group) so the front's
|
|
92
|
+
* teardown can signal it directly. `alive` flips to `false` on exit; if a
|
|
93
|
+
* child dies, its `/<ns>/*` routes start returning 503 (partial availability).
|
|
94
|
+
*
|
|
95
|
+
* @param host - The host to start (mutated: `child`, `alive`).
|
|
96
|
+
* @param cliPath - Path to the apigen CLI entry to spawn (see {@link selfCliPath}).
|
|
97
|
+
* @param onExit - Callback invoked when the child exits (for logging).
|
|
98
|
+
* @param extraArgs - Extra args appended to the child `run` invocation (tests).
|
|
99
|
+
*/
|
|
100
|
+
export declare function spawnHost(host: Host, cliPath: string, onExit?: (host: Host, code: number | null, signal: NodeJS.Signals | null) => void, extraArgs?: string[]): ChildProcess;
|
|
101
|
+
/**
|
|
102
|
+
* Kill every child process and resolve once all have exited (or the deadline
|
|
103
|
+
* passes, after which a hard `SIGKILL` is sent). Idempotent.
|
|
104
|
+
*
|
|
105
|
+
* @param hosts - The hosts whose children to terminate.
|
|
106
|
+
* @param graceMs - Grace period before escalating SIGTERM → SIGKILL.
|
|
107
|
+
*/
|
|
108
|
+
export declare function killAll(hosts: Host[], graceMs?: number): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Build the aggregate `_meta/health` payload (§13.1).
|
|
111
|
+
*
|
|
112
|
+
* ```json
|
|
113
|
+
* { "status": "ok"|"degraded", "hosts": { "<ns>": "ready"|"down", … } }
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* `status` is `ok` only when every host is ready; any down host → `degraded`
|
|
117
|
+
* (the front itself is still up — partial availability, never whole-surface
|
|
118
|
+
* down).
|
|
119
|
+
*/
|
|
120
|
+
export declare function aggregateHealth(hosts: Host[]): {
|
|
121
|
+
status: 'ok' | 'degraded';
|
|
122
|
+
hosts: Record<string, HostHealthStatus>;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Create the combined front server: HTTP/1.1 proxy + gRPC (HTTP/2) proxy,
|
|
126
|
+
* both on the same TCP port, disambiguated by inspecting the first bytes of
|
|
127
|
+
* each connection.
|
|
128
|
+
*
|
|
129
|
+
* @param hosts - The resolved, spawned hosts (with live ports).
|
|
130
|
+
* @param frontPort - The TCP port the front should listen on.
|
|
131
|
+
*/
|
|
132
|
+
export declare function createFrontServer(hosts: Host[]): http.Server;
|
|
133
|
+
/**
|
|
134
|
+
* Start the full serve stack: spawn every host, await readiness, then start the
|
|
135
|
+
* front. Resolves to a teardown function and the listening front server.
|
|
136
|
+
*
|
|
137
|
+
* Exported (not just inlined in the action) so the behavioural test can drive
|
|
138
|
+
* the real stack in-process with bounded waits.
|
|
139
|
+
*
|
|
140
|
+
* @param opts.sources - The `--source` files.
|
|
141
|
+
* @param opts.port - The front port.
|
|
142
|
+
* @param opts.mounts - `{ ns → plugin }` overrides.
|
|
143
|
+
* @param opts.cliPath - The CLI entry to spawn children with (defaults to self).
|
|
144
|
+
* @param opts.log - Optional logger sink (defaults to console.error).
|
|
145
|
+
*/
|
|
146
|
+
export declare function startServe(opts: {
|
|
147
|
+
sources: string[];
|
|
148
|
+
port: number;
|
|
149
|
+
mounts?: Record<string, string>;
|
|
150
|
+
cliPath?: string;
|
|
151
|
+
log?: (msg: string) => void;
|
|
152
|
+
}): Promise<{
|
|
153
|
+
hosts: Host[];
|
|
154
|
+
front: http.Server;
|
|
155
|
+
shutdown: () => Promise<void>;
|
|
156
|
+
}>;
|
|
157
|
+
/** Register the `serve` command on the program. */
|
|
158
|
+
export declare function registerServeCommand(program: Command): void;
|
package/lib/import-source.d.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Dynamically import a (possibly TypeScript) source module, registering the
|
|
3
|
-
* ESM loader for the duration of the import so
|
|
4
|
-
* a plain `node` process.
|
|
5
|
-
* leaks into the rest of the run.
|
|
2
|
+
* Dynamically import a (possibly TypeScript) source module, registering both the
|
|
3
|
+
* tsx ESM loader and the tsx CJS `require` patch for the duration of the import so
|
|
4
|
+
* that `.ts` entry files resolve in a plain `node` process. Both loaders are
|
|
5
|
+
* unregistered afterward so no global hook leaks into the rest of the run.
|
|
6
|
+
*
|
|
7
|
+
* Both hooks are required, not just the ESM one: when the target (or a package it
|
|
8
|
+
* imports) has no `"type": "module"` in its nearest package.json — as internal
|
|
9
|
+
* workspace libs commonly don't — Node's ESM loader detects the `.ts` file as
|
|
10
|
+
* CommonJS and routes it through the CJS translator, which drives real
|
|
11
|
+
* `require()` calls for every relative import. Those requires use Node's classic
|
|
12
|
+
* CJS resolver, not the ESM resolve hook, so a source written with the
|
|
13
|
+
* NodeNext-style `./db.js` specifier (resolving to `./db.ts` on disk) fails with
|
|
14
|
+
* `MODULE_NOT_FOUND` unless `tsx/cjs/api` has also patched CJS resolution.
|
|
15
|
+
* Registering only `tsx/esm/api` reproduces exactly this failure; the full `tsx`
|
|
16
|
+
* CLI works because it patches both loaders — this mirrors that.
|
|
6
17
|
*
|
|
7
18
|
* Under a transpiling test runner (vitest) tsx registration is a harmless no-op,
|
|
8
19
|
* so this same path works in-repo and standalone.
|
package/lib/logging.d.ts
CHANGED
package/lib/orchestrator.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ProjectionConfig } from '@adhd/apigen-naming';
|
|
2
|
-
import { Operation, Descriptor, ComposedSchemas, ExportMode, Logger, OutputPlugin } from '@adhd/apigen-core';
|
|
2
|
+
import { Operation, Descriptor, ComposedSchemas, ExportMode, Logger, OutputPlugin } from '@adhd/apigen-core-client';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A single source entry passed to the orchestrator.
|
|
@@ -12,9 +12,20 @@ export interface SourceEntry {
|
|
|
12
12
|
/** Absolute path to the source file. */
|
|
13
13
|
file: string;
|
|
14
14
|
/**
|
|
15
|
-
* Export mode for this source
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Export mode for this source — scopes which of the fully-extracted
|
|
16
|
+
* operations are actually composed into `packageSchemas` / served
|
|
17
|
+
* (BUG-APIGEN-034 fix). v2's `extract()` walks the FULL export-shape
|
|
18
|
+
* matrix (named, default, named-object, CJS — see extract.ts's header
|
|
19
|
+
* comment) unconditionally in a single pass, unlike v1's three
|
|
20
|
+
* mutually-exclusive extractors (`extractNamed`/`extractDefault`/
|
|
21
|
+
* `extractNamedObject`); `buildDescriptor()`'s Step 5 restores the same
|
|
22
|
+
* effective scoping by reclassifying each `Operation` by its `path` shape
|
|
23
|
+
* (see `opMatchesExportMode()`) rather than by re-running a separate
|
|
24
|
+
* extraction pass. Does NOT affect `descriptor.operations` (collision
|
|
25
|
+
* check, `--use` mount plugins) — only the served surface — matching v1's
|
|
26
|
+
* own behaviour where the collision-check step never respected
|
|
27
|
+
* `exportMode` either. Omitted → no scoping (all extracted operations are
|
|
28
|
+
* served); this is what `generate-registry`/`run-registry` rely on today.
|
|
18
29
|
*/
|
|
19
30
|
exportMode?: ExportMode;
|
|
20
31
|
/**
|
|
@@ -24,15 +35,25 @@ export interface SourceEntry {
|
|
|
24
35
|
namespace?: string;
|
|
25
36
|
/** Explicit tsconfig.json path. Resolved per source file when omitted. */
|
|
26
37
|
tsconfig?: string;
|
|
38
|
+
/**
|
|
39
|
+
* Import specifier written into generated code for this source, e.g. an
|
|
40
|
+
* npm package name (`'@adhd/foo'`) instead of the absolute extraction
|
|
41
|
+
* path. Defaults to `file` when omitted — true for the single-source
|
|
42
|
+
* `generate`/`run` CLI commands, where the resolved source path IS the
|
|
43
|
+
* import path. `generate-registry`/`run-registry` set this explicitly:
|
|
44
|
+
* their package discovery resolves a physical entry file for extraction
|
|
45
|
+
* (`file`) that differs from the package's published import specifier.
|
|
46
|
+
*/
|
|
47
|
+
importPath?: string;
|
|
27
48
|
}
|
|
28
49
|
/**
|
|
29
50
|
* Projection-override config (Tenet 1).
|
|
30
51
|
*
|
|
31
52
|
* Accepted from `--opt http.verb.<id>=GET` pairs or an `apigen.config` file.
|
|
32
53
|
* Overrides are NEVER written to source.
|
|
54
|
+
* Extend here as other projection dimensions are added (route, name, …).
|
|
33
55
|
*/
|
|
34
|
-
export
|
|
35
|
-
}
|
|
56
|
+
export type OverrideConfig = ProjectionConfig;
|
|
36
57
|
/** Options passed to the v2 orchestrator. */
|
|
37
58
|
export interface OrchestratorOptions {
|
|
38
59
|
/** Source files to extract and merge. Must be non-empty. */
|
|
@@ -106,6 +127,42 @@ export declare function loadOverrideConfig(configPath: string | undefined, cliOv
|
|
|
106
127
|
* @param perSourceOps - Arrays produced by {@link extractSource} per file.
|
|
107
128
|
*/
|
|
108
129
|
export declare function mergeOperations(perSourceOps: Operation[][]): Operation[];
|
|
130
|
+
/**
|
|
131
|
+
* BUG-APIGEN-034: restores v1's `--export <mode>` scoping of the SERVED
|
|
132
|
+
* surface. v1 ran exactly one of three mutually-exclusive extractors
|
|
133
|
+
* (`extractNamed`/`extractDefault`/`extractNamedObject`) per `exportMode`;
|
|
134
|
+
* v2's `extract()` walks the full export-shape matrix unconditionally (see
|
|
135
|
+
* extract.ts's header comment), so this reclassifies each already-extracted
|
|
136
|
+
* `Operation` by `path` shape and filters Step 5's schema composition only —
|
|
137
|
+
* `descriptor.operations` (collision-check, `--use` mount plugins) stays
|
|
138
|
+
* unscoped, matching pre-v1-retirement behaviour (the old collision-check
|
|
139
|
+
* step never respected `exportMode` either — only what actually got SERVED
|
|
140
|
+
* did).
|
|
141
|
+
*
|
|
142
|
+
* Path-shape correspondence (extract.ts's six-shape matrix):
|
|
143
|
+
* - `'named'` → `path.length === 2` (shapes 1/2: a plain named
|
|
144
|
+
* function/const export). This shape is also produced by shape 6 (a CJS
|
|
145
|
+
* `module.exports` property) and shape 4's bare
|
|
146
|
+
* `export default function foo(){}` form — neither is distinguishable
|
|
147
|
+
* from a plain named export via `path` alone, since `Operation` carries
|
|
148
|
+
* no export-shape discriminator (that would require extending
|
|
149
|
+
* `extract()`/`Operation` itself — out of scope here, filed as a
|
|
150
|
+
* follow-up). v1 never supported CJS sources or a bare default-fn-decl
|
|
151
|
+
* under ANY `--export` mode (see extract.ts's shape 4/6 comments), so
|
|
152
|
+
* folding them into `'named'` is a strict superset of v1's true
|
|
153
|
+
* `extractNamed` coverage, never a subset: nothing v1 used to serve
|
|
154
|
+
* under `--export` (omitted) goes missing here.
|
|
155
|
+
* - `'default'` → `path.length === 3 && path[1].raw === 'default'`
|
|
156
|
+
* (shape 4's default-OBJECT branch only, e.g. `export default { a, b }`).
|
|
157
|
+
* v1's `extractDefault` covered EXCLUSIVELY this object-literal
|
|
158
|
+
* default-export form — never a bare `export default function foo(){}`
|
|
159
|
+
* (shape 4's fn-decl branch) or an anonymous default fn (shape 5), both
|
|
160
|
+
* new in v2 with no v1 predecessor — so excluding them here matches v1's
|
|
161
|
+
* actual historical coverage exactly.
|
|
162
|
+
* - `'named-object'` → `path.length === 3 && path[1].raw === mode.name`
|
|
163
|
+
* (shape 3: `export const <mode.name> = { ... }`).
|
|
164
|
+
*/
|
|
165
|
+
export declare function opMatchesExportMode(op: Operation, mode: ExportMode): boolean;
|
|
109
166
|
/**
|
|
110
167
|
* Build the unified `OrchestratorDescriptor` from a set of source entries.
|
|
111
168
|
*
|
|
@@ -114,7 +171,11 @@ export declare function mergeOperations(perSourceOps: Operation[][]): Operation[
|
|
|
114
171
|
* 2. Extract canonical `Operation[]` per source (v2 extract).
|
|
115
172
|
* 3. Merge into one list.
|
|
116
173
|
* 4. Run the collision check (SPEC §5 uniqueness invariant).
|
|
117
|
-
* 5.
|
|
174
|
+
* 5. Derive per-source `ComposedSchemas` (the plugin-facing surface every
|
|
175
|
+
* `OutputPlugin.generate()`/`run()` actually dispatches against) FROM
|
|
176
|
+
* the operations already extracted in step 2 — see the note on Step 5
|
|
177
|
+
* below for why this must not re-extract. Scoped per-source by
|
|
178
|
+
* `SourceEntry.exportMode` (BUG-APIGEN-034) via `opMatchesExportMode()`.
|
|
118
179
|
*
|
|
119
180
|
* @param opts - Orchestrator options.
|
|
120
181
|
*/
|
|
@@ -140,13 +201,17 @@ export declare function orchestrateGenerate(opts: OrchestratorOptions, plugin: O
|
|
|
140
201
|
* @param opts - Orchestrator options.
|
|
141
202
|
* @param plugin - The selected output plugin (`--type`).
|
|
142
203
|
* @param buildFnTables - Async function that imports each source and returns
|
|
143
|
-
* `(fns, createClient)` for that source.
|
|
144
|
-
*
|
|
145
|
-
*
|
|
204
|
+
* `(fns, createClient)` for that source. Receives the
|
|
205
|
+
* source's composed `ComposedSchemas` too, so the
|
|
206
|
+
* command layer can run schema-driven precondition
|
|
207
|
+
* guards (e.g. the `decimal.js` optional-peer-dep
|
|
208
|
+
* check) before importing the live module. Injected
|
|
209
|
+
* by the command layer so the orchestrator stays
|
|
210
|
+
* testable without live imports.
|
|
146
211
|
* @param signal - Abort signal forwarded from the process SIGINT handler.
|
|
147
212
|
* @param pluginOpts - Plugin-level options.
|
|
148
213
|
*/
|
|
149
|
-
export declare function orchestrateRun(opts: OrchestratorOptions, plugin: OutputPlugin, buildFnTables: (entry: SourceEntry) => Promise<{
|
|
214
|
+
export declare function orchestrateRun(opts: OrchestratorOptions, plugin: OutputPlugin, buildFnTables: (entry: SourceEntry, schemas: ComposedSchemas) => Promise<{
|
|
150
215
|
fns: Record<string, (...args: unknown[]) => unknown>;
|
|
151
216
|
createClient: (envelope: Record<string, unknown>) => Promise<object>;
|
|
152
217
|
}>, signal: AbortSignal, pluginOpts?: Record<string, unknown>): Promise<void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { OutputPlugin } from '@adhd/apigen-core-client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Single source of truth for everything the CLI derives from the `--type`
|
|
5
|
+
* plugin registry: help text, `--list-types` output, and the "unknown
|
|
6
|
+
* --type"/"doesn't support run" errors. Every one of those was previously a
|
|
7
|
+
* hand-maintained string that drifted from the real `plugins` map passed
|
|
8
|
+
* into each command's `registerXCommand()` (FEAT-APIGEN-019) — this module
|
|
9
|
+
* is the only place that reads `Object.keys(plugins)` / `plugin.run` so
|
|
10
|
+
* they can never drift again.
|
|
11
|
+
*/
|
|
12
|
+
/** All registered `--type` ids, in registration order (includes aliases like `cli`/`cli-output`). */
|
|
13
|
+
export declare function pluginTypeIds(plugins: Record<string, OutputPlugin>): string[];
|
|
14
|
+
/** `--type` ids whose plugin implements `run()` — usable with `run` / `run-registry`. */
|
|
15
|
+
export declare function runCapableTypeIds(plugins: Record<string, OutputPlugin>): string[];
|
|
16
|
+
/** `--type` ids whose plugin has no `run()` — generate-only targets. */
|
|
17
|
+
export declare function generateOnlyTypeIds(plugins: Record<string, OutputPlugin>): string[];
|
|
18
|
+
/** Commander help text for `generate` / `generate-registry`'s `--type <plugin-id>` option. */
|
|
19
|
+
export declare function describeTypeOption(plugins: Record<string, OutputPlugin>): string;
|
|
20
|
+
/** Commander help text for `run` / `run-registry`'s `--type <plugin-id>` option (run-capable subset only). */
|
|
21
|
+
export declare function describeRunTypeOption(plugins: Record<string, OutputPlugin>): string;
|
|
22
|
+
/** The error thrown when `--type` isn't a registered plugin id at all. */
|
|
23
|
+
export declare function unknownTypeError(type: string, plugins: Record<string, OutputPlugin>): Error;
|
|
24
|
+
/**
|
|
25
|
+
* The error thrown when `--type` names a real, registered plugin that simply
|
|
26
|
+
* has no `run()` (e.g. `jsonschema`, `cli`) — distinct from {@link unknownTypeError}
|
|
27
|
+
* so a typo'd/unrecognized `--type` (e.g. `express` instead of `api-express`)
|
|
28
|
+
* doesn't get misreported as "exists but doesn't support run" (BACKLOG
|
|
29
|
+
* FEAT-APIGEN-019 live field confirmation).
|
|
30
|
+
*/
|
|
31
|
+
export declare function unsupportedRunError(type: string, plugins: Record<string, OutputPlugin>): Error;
|
|
32
|
+
/** Human-readable multi-line listing for the `list-types` command. */
|
|
33
|
+
export declare function formatTypesList(plugins: Record<string, OutputPlugin>): string;
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adhd/apigen-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"bin": {
|
|
5
|
-
"apigen
|
|
5
|
+
"apigen": "./index.js"
|
|
6
6
|
},
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"commander": "^14.0.3",
|
|
@@ -15,13 +15,27 @@
|
|
|
15
15
|
"pino": "10.3.1",
|
|
16
16
|
"pino-pretty": "13.1.3",
|
|
17
17
|
"pino-http": "11.0.0",
|
|
18
|
-
"@adhd/apigen-
|
|
19
|
-
"@adhd/apigen-
|
|
20
|
-
"@adhd/apigen-plugin-
|
|
21
|
-
"@adhd/apigen-plugin-
|
|
22
|
-
"@adhd/apigen-plugin-
|
|
23
|
-
"@adhd/apigen-plugin-
|
|
24
|
-
"@adhd/apigen-plugin-
|
|
18
|
+
"@adhd/apigen-plugin-mcp": "^0.1.1",
|
|
19
|
+
"@adhd/apigen-plugin-jsonschema": "^0.1.1",
|
|
20
|
+
"@adhd/apigen-plugin-api-fastify": "^0.1.1",
|
|
21
|
+
"@adhd/apigen-plugin-api-express": "^0.1.1",
|
|
22
|
+
"@adhd/apigen-plugin-cli-output": "^0.1.1",
|
|
23
|
+
"@adhd/apigen-plugin-py-flask": "^0.1.1",
|
|
24
|
+
"@adhd/apigen-plugin-py-grpc": "^0.1.1",
|
|
25
|
+
"@adhd/apigen-plugin-health": "^0.1.2",
|
|
26
|
+
"@adhd/apigen-plugin-logger": "^0.1.2",
|
|
27
|
+
"@adhd/apigen-python-env": "^0.1.0",
|
|
28
|
+
"@adhd/apigen-base-logical": "^0.0.1",
|
|
29
|
+
"@adhd/apigen-engine-runtime": "^0.1.0",
|
|
30
|
+
"@adhd/apigen-core-client": "^0.1.0",
|
|
31
|
+
"@adhd/apigen-engine-naming": "^0.1.1",
|
|
32
|
+
"@adhd/apigen-plugin-openapi": "^0.1.2"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {},
|
|
35
|
+
"peerDependenciesMeta": {
|
|
36
|
+
"decimal.js": {
|
|
37
|
+
"optional": true
|
|
38
|
+
}
|
|
25
39
|
},
|
|
26
40
|
"main": "./index.js",
|
|
27
41
|
"module": "./index.mjs",
|
package/default-tsconfig.json
DELETED
package/lib/pipeline.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { ComposedSchemas, ExportMode, Logger } from '@adhd/apigen-core';
|
|
2
|
-
|
|
3
|
-
export interface PipelineOptions {
|
|
4
|
-
sourceFile: string;
|
|
5
|
-
exportMode?: ExportMode;
|
|
6
|
-
middlewares?: Array<{
|
|
7
|
-
id: string;
|
|
8
|
-
envelope?: Record<string, unknown>;
|
|
9
|
-
}>;
|
|
10
|
-
overrides?: Record<string, Record<string, boolean>>;
|
|
11
|
-
namespace?: string;
|
|
12
|
-
/** Explicit --tsconfig flag; when omitted, the nearest/builtin config is resolved from sourceFile. */
|
|
13
|
-
tsconfig?: string;
|
|
14
|
-
/** Optional shared logger; when present the pipeline logs schema extraction. */
|
|
15
|
-
logger?: Logger;
|
|
16
|
-
}
|
|
17
|
-
export interface PipelineResult {
|
|
18
|
-
schemas: ComposedSchemas;
|
|
19
|
-
createClient: (envelope: Record<string, unknown>) => Promise<object>;
|
|
20
|
-
}
|
|
21
|
-
/** Run the generate + compose pipeline for a single source file. */
|
|
22
|
-
export declare function runPipeline(opts: PipelineOptions): Promise<PipelineResult>;
|