@arki/dot 0.2.0 → 0.4.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/README.md +86 -86
- package/dist/cli/discover.js +2 -2
- package/dist/cli/discover.js.map +1 -1
- package/dist/cli/error-codes.d.ts +8 -0
- package/dist/cli/error-codes.d.ts.map +1 -1
- package/dist/cli/error-codes.js +12 -0
- package/dist/cli/error-codes.js.map +1 -1
- package/dist/cli/index.d.ts +11 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +66 -11
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/new.d.ts +1 -1
- package/dist/cli/new.js +1 -1
- package/dist/cli/render-doctor.d.ts +1 -1
- package/dist/cli/render-doctor.js +23 -23
- package/dist/cli/render-doctor.js.map +1 -1
- package/dist/cli/render-explain.d.ts.map +1 -1
- package/dist/cli/render-explain.js +33 -19
- package/dist/cli/render-explain.js.map +1 -1
- package/dist/cli/render-graph.d.ts +3 -3
- package/dist/cli/render-graph.js +12 -12
- package/dist/cli/render-graph.js.map +1 -1
- package/dist/cli/render-openapi.d.ts +38 -0
- package/dist/cli/render-openapi.d.ts.map +1 -0
- package/dist/cli/render-openapi.js +131 -0
- package/dist/cli/render-openapi.js.map +1 -0
- package/dist/cli/render-projection.d.ts +29 -0
- package/dist/cli/render-projection.d.ts.map +1 -0
- package/dist/cli/render-projection.js +202 -0
- package/dist/cli/render-projection.js.map +1 -0
- package/dist/define-app.d.ts +49 -17
- package/dist/define-app.d.ts.map +1 -1
- package/dist/define-app.js +16 -9
- package/dist/define-app.js.map +1 -1
- package/dist/diagnostics.d.ts +19 -15
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +2 -2
- package/dist/index.d.ts +15 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/init-pips.d.ts +45 -0
- package/dist/init-pips.d.ts.map +1 -0
- package/dist/init-pips.js +49 -0
- package/dist/init-pips.js.map +1 -0
- package/dist/init-plugins.d.ts +45 -0
- package/dist/init-plugins.d.ts.map +1 -0
- package/dist/init-plugins.js +49 -0
- package/dist/init-plugins.js.map +1 -0
- package/dist/kernel/app-instance.d.ts +5 -5
- package/dist/kernel/app-instance.d.ts.map +1 -1
- package/dist/kernel/app-instance.js +375 -244
- package/dist/kernel/app-instance.js.map +1 -1
- package/dist/kernel/otel.d.ts +19 -19
- package/dist/kernel/otel.d.ts.map +1 -1
- package/dist/kernel/otel.js +22 -22
- package/dist/kernel/otel.js.map +1 -1
- package/dist/lifecycle-observer.d.ts +10 -10
- package/dist/lifecycle-observer.d.ts.map +1 -1
- package/dist/lifecycle-observer.js +1 -1
- package/dist/lifecycle.d.ts +18 -14
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +13 -9
- package/dist/lifecycle.js.map +1 -1
- package/dist/manifest.d.ts +70 -29
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +74 -5
- package/dist/manifest.js.map +1 -1
- package/dist/pip-contract.d.ts +40 -10
- package/dist/pip-contract.d.ts.map +1 -1
- package/dist/pip-contract.js +1 -25
- package/dist/pip-contract.js.map +1 -1
- package/dist/pip.d.ts +7 -4
- package/dist/pip.d.ts.map +1 -1
- package/dist/pip.js +2 -0
- package/dist/pip.js.map +1 -1
- package/dist/plugin-contract.d.ts +335 -143
- package/dist/plugin-contract.d.ts.map +1 -1
- package/dist/plugin-contract.js +201 -36
- package/dist/plugin-contract.js.map +1 -1
- package/dist/plugin.d.ts +18 -15
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +11 -11
- package/dist/plugin.js.map +1 -1
- package/dist/test-harness.d.ts +31 -31
- package/dist/test-harness.d.ts.map +1 -1
- package/dist/test-harness.js +29 -28
- package/dist/test-harness.js.map +1 -1
- package/dist/timeline.d.ts +2 -2
- package/dist/timeline.js +5 -5
- package/dist/timeline.js.map +1 -1
- package/package.json +9 -9
- package/src/cli/discover.ts +2 -2
- package/src/cli/error-codes.ts +12 -0
- package/src/cli/index.ts +93 -15
- package/src/cli/new.ts +1 -1
- package/src/cli/render-doctor.ts +21 -21
- package/src/cli/render-explain.ts +37 -19
- package/src/cli/render-graph.ts +12 -12
- package/src/cli/render-projection.ts +239 -0
- package/src/define-app.ts +85 -22
- package/src/diagnostics.ts +19 -15
- package/src/index.ts +38 -20
- package/src/init-plugins.ts +142 -0
- package/src/kernel/app-instance.ts +428 -282
- package/src/kernel/otel.ts +27 -27
- package/src/lifecycle-observer.ts +10 -10
- package/src/lifecycle.ts +19 -15
- package/src/manifest.ts +128 -31
- package/src/{pip-contract.ts → plugin-contract.ts} +83 -44
- package/src/{pip.ts → plugin.ts} +38 -18
- package/src/test-harness.ts +52 -51
- package/src/timeline.ts +6 -6
- package/templates/app-minimal/AGENTS.md.tmpl +9 -9
- package/templates/app-minimal/README.md.tmpl +1 -1
- package/templates/app-minimal/tests/boot.test.ts.tmpl +1 -1
package/dist/test-harness.js
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Test harness for unit-testing DOT
|
|
2
|
+
* Test harness for unit-testing DOT plugins.
|
|
3
3
|
*
|
|
4
|
-
* Provides `testApp` — a convenience wrapper that lets
|
|
4
|
+
* Provides `testApp` — a convenience wrapper that lets plugin authors verify
|
|
5
5
|
* lifecycle behaviour, registration, and service publishing without dragging
|
|
6
6
|
* in concrete framework dependencies.
|
|
7
7
|
*
|
|
8
|
-
* NOTE: `testApp` takes an erased
|
|
8
|
+
* NOTE: `testApp` takes an erased plugin array, so the compile-time wiring
|
|
9
9
|
* guard does not apply here — the kernel's runtime validation (unsatisfied
|
|
10
10
|
* needs, collisions) still does. Use `defineApp(...).use(...)` chains in
|
|
11
11
|
* tests that should exercise the type-level guard.
|
|
12
12
|
*
|
|
13
13
|
* @example
|
|
14
|
-
* import { testApp,
|
|
14
|
+
* import { testApp, plugin } from '@arki/dot';
|
|
15
15
|
*
|
|
16
|
-
* const
|
|
16
|
+
* const myPlugin = plugin({
|
|
17
17
|
* name: 'counter',
|
|
18
18
|
* boot: () => ({ counter: { value: 0 } }),
|
|
19
19
|
* });
|
|
20
20
|
*
|
|
21
21
|
* it('publishes a counter service', async () => {
|
|
22
|
-
* const app = await bootTestApp<{ counter: { value: number } }>([
|
|
22
|
+
* const app = await bootTestApp<{ counter: { value: number } }>([myPlugin]);
|
|
23
23
|
* expect(app.services.counter.value).toBe(0);
|
|
24
24
|
* await app.dispose();
|
|
25
25
|
* });
|
|
26
26
|
*/
|
|
27
27
|
import { defineApp } from './define-app.js';
|
|
28
28
|
/**
|
|
29
|
-
* Build a DOT app builder pre-populated with the given
|
|
29
|
+
* Build a DOT app builder pre-populated with the given plugins, ready to
|
|
30
30
|
* `.configure()`, `.boot()` or `.start()` from a test.
|
|
31
31
|
*/
|
|
32
|
-
export function testApp(
|
|
32
|
+
export function testApp(plugins = [], options = {}) {
|
|
33
33
|
let builder = defineApp(options.name ?? 'test-app', { config: options.config });
|
|
34
|
-
for (const p of
|
|
34
|
+
for (const p of plugins) {
|
|
35
35
|
builder = builder.use(p);
|
|
36
36
|
}
|
|
37
37
|
return builder;
|
|
@@ -40,22 +40,22 @@ export function testApp(pips = [], options = {}) {
|
|
|
40
40
|
* Convenience: build, boot, return the running app. Caller is responsible for
|
|
41
41
|
* calling `app.dispose()` when finished.
|
|
42
42
|
*/
|
|
43
|
-
export async function bootTestApp(
|
|
44
|
-
return testApp(
|
|
43
|
+
export async function bootTestApp(plugins = [], options = {}) {
|
|
44
|
+
return testApp(plugins, options).boot();
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
* Unit-test a single
|
|
47
|
+
* Unit-test a single plugin with **typed overrides** — the compile-checked
|
|
48
48
|
* counterpart to {@link testApp}'s erased arrays.
|
|
49
49
|
*
|
|
50
|
-
* Each `.provide()` satisfies one of the
|
|
51
|
-
* provider
|
|
50
|
+
* Each `.provide()` satisfies one of the plugin's needs directly (no real
|
|
51
|
+
* provider plugin, no dependency chain) and removes it from the builder's
|
|
52
52
|
* remaining-needs type. `boot()` only compiles once every need is covered,
|
|
53
53
|
* and a fake of the wrong shape fails at the `.provide()` call site:
|
|
54
54
|
*
|
|
55
55
|
* ```ts
|
|
56
|
-
* import {
|
|
56
|
+
* import { testPlugin } from '@arki/dot/test-harness';
|
|
57
57
|
*
|
|
58
|
-
* const app = await
|
|
58
|
+
* const app = await testPlugin(catalog)
|
|
59
59
|
* .provide(Db, fakeDb) // token need
|
|
60
60
|
* .provide('cache', fakeKv) // anonymous need — wire key is the alias
|
|
61
61
|
* .boot();
|
|
@@ -67,41 +67,42 @@ export async function bootTestApp(pips = [], options = {}) {
|
|
|
67
67
|
* `service.lazy<T>()` needs accept either a plain `T` fake (the kernel
|
|
68
68
|
* lifts it) or a `Lazy<T>` handle (`lazyOf(value)` is handy here).
|
|
69
69
|
* Lifecycle semantics are the real kernel's — the fakes are published by a
|
|
70
|
-
* synthetic first
|
|
70
|
+
* synthetic first plugin, so reverse-order teardown and lazy auto-dispose
|
|
71
71
|
* behave exactly as in production.
|
|
72
72
|
*/
|
|
73
|
-
export function
|
|
73
|
+
export function testPlugin(plugin, options = {}) {
|
|
74
74
|
// Erasure seam — same boundary as `makeBuilder` in define-app.ts: the
|
|
75
75
|
// runtime impl is untyped, the type parameters do all the guarding.
|
|
76
|
-
return
|
|
76
|
+
return makeTestPluginBuilder({ plugin: plugin, fakes: {}, options });
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
|
-
* Erased implementation behind {@link
|
|
79
|
+
* Erased implementation behind {@link testPlugin}. Mirrors `makeBuilder` in
|
|
80
80
|
* `define-app.ts`: the runtime is untyped, the single cast at the return
|
|
81
81
|
* is the seam, and the type parameters do all the guarding.
|
|
82
82
|
*/
|
|
83
|
-
function
|
|
83
|
+
function makeTestPluginBuilder(state) {
|
|
84
84
|
const bootApp = async () => {
|
|
85
|
-
const
|
|
85
|
+
const plugins = [];
|
|
86
86
|
if (Object.keys(state.fakes).length > 0) {
|
|
87
87
|
const fakes = { ...state.fakes };
|
|
88
|
-
|
|
89
|
-
name: `test:fakes(${state.
|
|
88
|
+
plugins.push({
|
|
89
|
+
name: `test:fakes(${state.plugin.name})`,
|
|
90
90
|
needs: {},
|
|
91
|
+
actions: [],
|
|
91
92
|
renames: {},
|
|
92
93
|
hooks: { boot: () => fakes },
|
|
93
94
|
});
|
|
94
95
|
}
|
|
95
|
-
|
|
96
|
-
return testApp(
|
|
97
|
-
name: state.options.name ?? `test:${state.
|
|
96
|
+
plugins.push(state.plugin);
|
|
97
|
+
return testApp(plugins, {
|
|
98
|
+
name: state.options.name ?? `test:${state.plugin.name}`,
|
|
98
99
|
...(state.options.config === undefined ? {} : { config: state.options.config }),
|
|
99
100
|
}).boot();
|
|
100
101
|
};
|
|
101
102
|
const impl = {
|
|
102
103
|
provide(tokenOrKey, value) {
|
|
103
104
|
const key = typeof tokenOrKey === 'string' ? tokenOrKey : tokenOrKey.key;
|
|
104
|
-
return
|
|
105
|
+
return makeTestPluginBuilder({ ...state, fakes: { ...state.fakes, [key]: value } });
|
|
105
106
|
},
|
|
106
107
|
async boot(..._guard) {
|
|
107
108
|
return bootApp();
|
package/dist/test-harness.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-harness.js","sourceRoot":"","sources":["../src/test-harness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAIH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAkB5C;;;GAGG;AACH,MAAM,UAAU,OAAO,CACrB,
|
|
1
|
+
{"version":3,"file":"test-harness.js","sourceRoot":"","sources":["../src/test-harness.ts"],"names":[],"mappings":"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"}
|
package/dist/timeline.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ASCII waterfall renderer for DOT lifecycle diagnostics.
|
|
3
3
|
*
|
|
4
|
-
* Given a `DotDiagnosticsSnapshot`, builds a compact per-phase, per-
|
|
4
|
+
* Given a `DotDiagnosticsSnapshot`, builds a compact per-phase, per-plugin
|
|
5
5
|
* duration chart suitable for printing in a terminal or embedding in a
|
|
6
6
|
* `dot doctor` text report. No colour, no Unicode beyond `█` so the
|
|
7
7
|
* output stays clean in log aggregators and CI.
|
|
@@ -29,7 +29,7 @@ export type RenderTimelineOptions = {
|
|
|
29
29
|
/** Maximum bar width in characters (default `50`). */
|
|
30
30
|
readonly barWidth?: number;
|
|
31
31
|
/**
|
|
32
|
-
* When `true`, also emit any
|
|
32
|
+
* When `true`, also emit any plugin's diagnostic `issues[]` underneath
|
|
33
33
|
* the bar so failure reasons are visible in the timeline view.
|
|
34
34
|
* Default `true`.
|
|
35
35
|
*/
|
package/dist/timeline.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ASCII waterfall renderer for DOT lifecycle diagnostics.
|
|
3
3
|
*
|
|
4
|
-
* Given a `DotDiagnosticsSnapshot`, builds a compact per-phase, per-
|
|
4
|
+
* Given a `DotDiagnosticsSnapshot`, builds a compact per-phase, per-plugin
|
|
5
5
|
* duration chart suitable for printing in a terminal or embedding in a
|
|
6
6
|
* `dot doctor` text report. No colour, no Unicode beyond `█` so the
|
|
7
7
|
* output stays clean in log aggregators and CI.
|
|
@@ -60,8 +60,8 @@ export function renderTimeline(snapshot, opts = {}) {
|
|
|
60
60
|
const title = `Timeline: ${snapshot.app.name} (state=${snapshot.app.state})`;
|
|
61
61
|
lines.push(title);
|
|
62
62
|
lines.push('─'.repeat(title.length));
|
|
63
|
-
// Layout: 2-space indent, 10-char
|
|
64
|
-
const
|
|
63
|
+
// Layout: 2-space indent, 10-char plugin column, 8-char duration column, then bar.
|
|
64
|
+
const PLUGIN_COL = 12;
|
|
65
65
|
const DUR_COL = 8;
|
|
66
66
|
const indent = ' ';
|
|
67
67
|
let anyRendered = false;
|
|
@@ -77,9 +77,9 @@ export function renderTimeline(snapshot, opts = {}) {
|
|
|
77
77
|
const durationMs = entry.durationMs ?? 0;
|
|
78
78
|
const bar = makeBar(durationMs, maxDuration, barWidth);
|
|
79
79
|
const failedMark = entry.state === 'failed' ? ' ✗' : '';
|
|
80
|
-
const
|
|
80
|
+
const pluginCol = entry.plugin.padEnd(PLUGIN_COL);
|
|
81
81
|
const durCol = formatDuration(durationMs).padStart(DUR_COL);
|
|
82
|
-
lines.push(`${indent}${
|
|
82
|
+
lines.push(`${indent}${pluginCol}${durCol} ${bar}${failedMark}`);
|
|
83
83
|
if (showIssues && entry.issues.length > 0) {
|
|
84
84
|
for (const issue of entry.issues) {
|
|
85
85
|
lines.push(`${indent} └─ ${issue.code}: ${issue.message}`);
|
package/dist/timeline.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeline.js","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAKH,MAAM,WAAW,GAAgC,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;AAEnG,MAAM,QAAQ,GAAG,GAAG,CAAC;AACrB,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAa9B;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgC,EAAE,OAA8B,EAAE;IAC/F,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,iBAAiB,CAAC;IACpD,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC;IAE3C,sEAAsE;IACtE,sEAAsE;IACtE,qEAAqE;IACrE,yDAAyD;IACzD,MAAM,OAAO,GAAG,IAAI,GAAG,EAA2C,CAAC;IACnE,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;;YAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACxC,CAAC;IAED,0EAA0E;IAC1E,oEAAoE;IACpE,uDAAuD;IACvD,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QACvC,MAAM,CAAC,GAAG,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC,GAAG,WAAW;YAAE,WAAW,GAAG,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,aAAa,QAAQ,CAAC,GAAG,CAAC,IAAI,WAAW,QAAQ,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC;IAC7E,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAErC,
|
|
1
|
+
{"version":3,"file":"timeline.js","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arki/dot",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "TypeScript-first application composition framework for the ARKI package family — lifecycle-aware
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "TypeScript-first application composition framework for the ARKI package family — lifecycle-aware plugins, deterministic boot order, manifest/diagnostics envelopes, and an agent-native CLI. A DOT app is the sum of its plugins.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"arki",
|
|
16
16
|
"typescript",
|
|
17
17
|
"framework",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
18
|
+
"plugin",
|
|
19
|
+
"plugins",
|
|
20
20
|
"lifecycle",
|
|
21
21
|
"diagnostics",
|
|
22
22
|
"manifest",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"import": "./src/index.ts",
|
|
32
32
|
"default": "./dist/index.js"
|
|
33
33
|
},
|
|
34
|
-
"./
|
|
35
|
-
"types": "./dist/
|
|
36
|
-
"import": "./dist/
|
|
37
|
-
"default": "./dist/
|
|
34
|
+
"./plugin": {
|
|
35
|
+
"types": "./dist/plugin.d.ts",
|
|
36
|
+
"import": "./dist/plugin.js",
|
|
37
|
+
"default": "./dist/plugin.js"
|
|
38
38
|
},
|
|
39
39
|
"./package.json": "./package.json",
|
|
40
40
|
"./cli": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
],
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@arki/assert": "0.0.2",
|
|
66
|
-
"@arki/contracts": "0.0.
|
|
66
|
+
"@arki/contracts": "0.0.3",
|
|
67
67
|
"@arki/log": "0.0.2",
|
|
68
68
|
"@opentelemetry/api": "^1.9.0",
|
|
69
69
|
"zod": "4.3.5"
|
package/src/cli/discover.ts
CHANGED
|
@@ -186,7 +186,7 @@ export async function loadAppFromFile(filePath: string): Promise<DiscoveredApp>
|
|
|
186
186
|
code: DotCliErrorCode.AppInvalidExport,
|
|
187
187
|
message: `Default export from ${filePath} is not a DotApp or DotAppBuilder.`,
|
|
188
188
|
remediation:
|
|
189
|
-
'Export a DotApp or DotAppBuilder as the default. Example: `export default defineApp("my-app").use(
|
|
189
|
+
'Export a DotApp or DotAppBuilder as the default. Example: `export default defineApp("my-app").use(plugin);`',
|
|
190
190
|
metadata: { filePath },
|
|
191
191
|
});
|
|
192
192
|
}
|
|
@@ -204,7 +204,7 @@ export async function discoverApp(options: DiscoveryOptions = {}): Promise<Disco
|
|
|
204
204
|
code: DotCliErrorCode.AppNotFound,
|
|
205
205
|
message: `No DOT app file found in ${cwd}.`,
|
|
206
206
|
remediation:
|
|
207
|
-
'Create `dot.config.ts` exporting your app (e.g. `export default defineApp("my-app").use(
|
|
207
|
+
'Create `dot.config.ts` exporting your app (e.g. `export default defineApp("my-app").use(plugin)`), or pass `--app <path>` to point at an existing file.',
|
|
208
208
|
metadata: { cwd, searched: DEFAULT_APP_PATHS },
|
|
209
209
|
});
|
|
210
210
|
}
|
package/src/cli/error-codes.ts
CHANGED
|
@@ -21,6 +21,14 @@ export const DotCliErrorCode = {
|
|
|
21
21
|
AppLifecycleFailed: 'DOT_CLI_E006',
|
|
22
22
|
/** `dot doctor --observability` ran but no OTel SDK is registered. */
|
|
23
23
|
ObservabilityNoSdk: 'DOT_CLI_E007',
|
|
24
|
+
/** Requested projection format is not registered by the app. */
|
|
25
|
+
ProjectionNotFound: 'DOT_CLI_E008',
|
|
26
|
+
/** Projection module import failed or did not export `project`. */
|
|
27
|
+
ProjectionImportFailed: 'DOT_CLI_E009',
|
|
28
|
+
/** Projection execution failed or returned non-JSON output. */
|
|
29
|
+
ProjectionExecutionFailed: 'DOT_CLI_E010',
|
|
30
|
+
/** Multiple modules claim the same projection format. */
|
|
31
|
+
ProjectionConflict: 'DOT_CLI_E011',
|
|
24
32
|
} as const;
|
|
25
33
|
|
|
26
34
|
export type DotCliErrorCodeValue = (typeof DotCliErrorCode)[keyof typeof DotCliErrorCode];
|
|
@@ -39,6 +47,10 @@ export const DotCliDocsAnchor: Record<DotCliErrorCodeValue, string> = {
|
|
|
39
47
|
[DotCliErrorCode.InvalidArgs]: 'invalid-args',
|
|
40
48
|
[DotCliErrorCode.AppLifecycleFailed]: 'app-lifecycle-failed',
|
|
41
49
|
[DotCliErrorCode.ObservabilityNoSdk]: 'observability-no-sdk',
|
|
50
|
+
[DotCliErrorCode.ProjectionNotFound]: 'projection-not-found',
|
|
51
|
+
[DotCliErrorCode.ProjectionImportFailed]: 'projection-import-failed',
|
|
52
|
+
[DotCliErrorCode.ProjectionExecutionFailed]: 'projection-execution-failed',
|
|
53
|
+
[DotCliErrorCode.ProjectionConflict]: 'projection-conflict',
|
|
42
54
|
};
|
|
43
55
|
|
|
44
56
|
export function dotCliDocsUrl(code: DotCliErrorCodeValue): string {
|
package/src/cli/index.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { createDebugLogger } from '@arki/log/debug';
|
|
|
24
24
|
|
|
25
25
|
import type { DotApp, DotAppBuilder, DotAppConfigured } from '../define-app.js';
|
|
26
26
|
import type { DiagnosticIssue } from '../diagnostics.js';
|
|
27
|
-
import type { DiscoveredApp } from './discover.js';
|
|
27
|
+
import type { DiscoveredApp, DiscoveryResult } from './discover.js';
|
|
28
28
|
import type { DotCliEnvelope, DotCliEnvelopeStatus } from './render-explain.js';
|
|
29
29
|
import { DotLifecycleError } from '../lifecycle.js';
|
|
30
30
|
import { discoverApp, guards } from './discover.js';
|
|
@@ -34,6 +34,7 @@ import { probeObservability } from './observability-probe.js';
|
|
|
34
34
|
import { renderDoctor } from './render-doctor.js';
|
|
35
35
|
import { renderExplain } from './render-explain.js';
|
|
36
36
|
import { renderGraph } from './render-graph.js';
|
|
37
|
+
import { renderProjection } from './render-projection.js';
|
|
37
38
|
|
|
38
39
|
const debugCli = createDebugLogger('arki:dot:cli');
|
|
39
40
|
|
|
@@ -58,10 +59,16 @@ Common options:
|
|
|
58
59
|
--app <path> Path to the app entry file (default: discovers
|
|
59
60
|
./dot.config.ts, ./src/app.ts, or ./app.ts)
|
|
60
61
|
--cwd <dir> Working directory (default: current)
|
|
61
|
-
--graph Emit the
|
|
62
|
+
--graph Emit the plugin graph as Mermaid flowchart source
|
|
62
63
|
instead of the standard output. explain shows
|
|
63
64
|
declaration (= boot) order; doctor shows the
|
|
64
65
|
wiring observed during boot. Composes with --json.
|
|
66
|
+
--as <format> (explain only) Render a registered projection format.
|
|
67
|
+
Examples: openapi, asyncapi, help-text. Composes
|
|
68
|
+
with --json.
|
|
69
|
+
--module <specifier> (explain --as only) Override the registered
|
|
70
|
+
projection module for one invocation.
|
|
71
|
+
--openapi Deprecated alias for --as openapi.
|
|
65
72
|
|
|
66
73
|
\`doctor\` options:
|
|
67
74
|
--observability Also probe whether an OpenTelemetry SDK is
|
|
@@ -96,6 +103,12 @@ export type CliArgs = {
|
|
|
96
103
|
observability?: boolean;
|
|
97
104
|
/** `--graph` (honored by `explain` and `doctor`). */
|
|
98
105
|
graph?: boolean;
|
|
106
|
+
/** `--as` projection format (honored by `explain` only). */
|
|
107
|
+
as?: string;
|
|
108
|
+
/** `--module` projection override (honored by `explain --as` only). */
|
|
109
|
+
module?: string;
|
|
110
|
+
/** Deprecated `--openapi` alias for `--as openapi`. */
|
|
111
|
+
openapi?: boolean;
|
|
99
112
|
};
|
|
100
113
|
|
|
101
114
|
/**
|
|
@@ -160,6 +173,9 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
160
173
|
force: { type: 'boolean', default: false },
|
|
161
174
|
observability: { type: 'boolean', default: false },
|
|
162
175
|
graph: { type: 'boolean', default: false },
|
|
176
|
+
as: { type: 'string' },
|
|
177
|
+
module: { type: 'string' },
|
|
178
|
+
openapi: { type: 'boolean', default: false },
|
|
163
179
|
},
|
|
164
180
|
});
|
|
165
181
|
} catch (err) {
|
|
@@ -183,6 +199,9 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
183
199
|
force?: boolean;
|
|
184
200
|
observability?: boolean;
|
|
185
201
|
graph?: boolean;
|
|
202
|
+
as?: string;
|
|
203
|
+
module?: string;
|
|
204
|
+
openapi?: boolean;
|
|
186
205
|
};
|
|
187
206
|
|
|
188
207
|
if (values.help) command = 'help';
|
|
@@ -190,6 +209,30 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
190
209
|
|
|
191
210
|
if (!command) command = 'help';
|
|
192
211
|
|
|
212
|
+
const projectionFormat = values.as ?? (values.openapi === true ? 'openapi' : undefined);
|
|
213
|
+
|
|
214
|
+
if (projectionFormat !== undefined && values.graph === true) {
|
|
215
|
+
throw new DotCliError({
|
|
216
|
+
code: DotCliErrorCode.InvalidArgs,
|
|
217
|
+
message: '--as/--openapi and --graph are mutually exclusive.',
|
|
218
|
+
remediation: 'Pick one output format per invocation.',
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (projectionFormat !== undefined && command === 'doctor') {
|
|
222
|
+
throw new DotCliError({
|
|
223
|
+
code: DotCliErrorCode.InvalidArgs,
|
|
224
|
+
message: '--as is not supported by doctor.',
|
|
225
|
+
remediation: 'Use `dot explain --as <format>` — projections render from the static manifest, no boot needed.',
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (values.module !== undefined && projectionFormat === undefined) {
|
|
229
|
+
throw new DotCliError({
|
|
230
|
+
code: DotCliErrorCode.InvalidArgs,
|
|
231
|
+
message: '--module requires --as <format>.',
|
|
232
|
+
remediation: 'Pass a projection format with --as, or remove --module.',
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
193
236
|
let pm: CliArgs['pm'];
|
|
194
237
|
if (values.pm !== undefined) {
|
|
195
238
|
if (values.pm !== 'npm' && values.pm !== 'pnpm' && values.pm !== 'bun') {
|
|
@@ -215,13 +258,15 @@ export function parseArgs(argv: readonly string[]): CliArgs {
|
|
|
215
258
|
force: values.force ?? false,
|
|
216
259
|
observability: values.observability ?? false,
|
|
217
260
|
graph: values.graph ?? false,
|
|
261
|
+
as: projectionFormat,
|
|
262
|
+
module: values.module,
|
|
263
|
+
openapi: values.openapi ?? false,
|
|
218
264
|
};
|
|
219
265
|
}
|
|
220
266
|
|
|
221
267
|
/** Discovery wrapper — returns `null` for help/version commands. */
|
|
222
|
-
async function loadApp(args: CliArgs): Promise<
|
|
223
|
-
|
|
224
|
-
return app;
|
|
268
|
+
async function loadApp(args: CliArgs): Promise<DiscoveryResult> {
|
|
269
|
+
return discoverApp({ appPath: args.appPath, cwd: args.cwd });
|
|
225
270
|
}
|
|
226
271
|
|
|
227
272
|
/**
|
|
@@ -230,7 +275,16 @@ async function loadApp(args: CliArgs): Promise<DiscoveredApp> {
|
|
|
230
275
|
*/
|
|
231
276
|
export async function runExplain(
|
|
232
277
|
discovered: DiscoveredApp,
|
|
233
|
-
opts: {
|
|
278
|
+
opts: {
|
|
279
|
+
json: boolean;
|
|
280
|
+
graph?: boolean;
|
|
281
|
+
as?: string;
|
|
282
|
+
module?: string;
|
|
283
|
+
appFilePath?: string;
|
|
284
|
+
openapi?: boolean;
|
|
285
|
+
out?: (line: string) => void;
|
|
286
|
+
now?: () => Date;
|
|
287
|
+
},
|
|
234
288
|
): Promise<DotCliEnvelope<unknown>> {
|
|
235
289
|
let configured: DotApp<Record<string, unknown>> | DotAppConfigured<Record<string, unknown>>;
|
|
236
290
|
try {
|
|
@@ -246,6 +300,18 @@ export async function runExplain(
|
|
|
246
300
|
throw wrapLifecycleError(err, 'configure');
|
|
247
301
|
}
|
|
248
302
|
|
|
303
|
+
if (opts.as !== undefined) {
|
|
304
|
+
return renderProjection(
|
|
305
|
+
{
|
|
306
|
+
manifest: configured.manifest,
|
|
307
|
+
command: 'explain',
|
|
308
|
+
format: opts.as,
|
|
309
|
+
appFilePath: opts.appFilePath ?? process.cwd(),
|
|
310
|
+
...(opts.module === undefined ? {} : { module: opts.module }),
|
|
311
|
+
},
|
|
312
|
+
{ json: opts.json, out: opts.out, now: opts.now },
|
|
313
|
+
);
|
|
314
|
+
}
|
|
249
315
|
if (opts.graph === true) {
|
|
250
316
|
return renderGraph(
|
|
251
317
|
{ manifest: configured.manifest, command: 'explain' },
|
|
@@ -265,7 +331,7 @@ type DoctorRunOptions = {
|
|
|
265
331
|
* present. Default `false`.
|
|
266
332
|
*/
|
|
267
333
|
observability?: boolean;
|
|
268
|
-
/** When `true`, emit the
|
|
334
|
+
/** When `true`, emit the plugin graph (Mermaid) instead of diagnostics. */
|
|
269
335
|
graph?: boolean;
|
|
270
336
|
};
|
|
271
337
|
|
|
@@ -297,7 +363,7 @@ export async function runDoctor(
|
|
|
297
363
|
}
|
|
298
364
|
|
|
299
365
|
// Builder or configured seam: drive lifecycle ourselves. Boot failures fall
|
|
300
|
-
// back to the configured seam's diagnostics so per-
|
|
366
|
+
// back to the configured seam's diagnostics so per-plugin issues stay
|
|
301
367
|
// visible.
|
|
302
368
|
let configured: DotAppConfigured<Record<string, unknown>>;
|
|
303
369
|
try {
|
|
@@ -314,7 +380,7 @@ export async function runDoctor(
|
|
|
314
380
|
bootedApp = await configured.boot();
|
|
315
381
|
} catch (err) {
|
|
316
382
|
// Boot failed — surface diagnostics from the configured seam below.
|
|
317
|
-
// We deliberately swallow the throw so the user sees the per-
|
|
383
|
+
// We deliberately swallow the throw so the user sees the per-plugin
|
|
318
384
|
// issues instead of an opaque wrapper error.
|
|
319
385
|
bootThrew = true;
|
|
320
386
|
debugCli('boot threw, falling back to configured diagnostics: %O', err);
|
|
@@ -323,7 +389,7 @@ export async function runDoctor(
|
|
|
323
389
|
try {
|
|
324
390
|
if (opts.graph === true) {
|
|
325
391
|
// Post-boot manifest carries the observed wiring edges; after a boot
|
|
326
|
-
// failure it carries the edges recorded up to the failing
|
|
392
|
+
// failure it carries the edges recorded up to the failing plugin.
|
|
327
393
|
const manifest = bootedApp ? bootedApp.manifest : configured.manifest;
|
|
328
394
|
const graphEnvelope = renderGraph(
|
|
329
395
|
{ manifest, command: 'doctor' },
|
|
@@ -379,8 +445,8 @@ function wrapLifecycleError(err: unknown, phase: 'configure' | 'boot'): DotCliEr
|
|
|
379
445
|
message: `App ${phase} failed: ${message}`,
|
|
380
446
|
remediation:
|
|
381
447
|
phase === 'configure'
|
|
382
|
-
? 'Check the
|
|
383
|
-
: 'Run `dot doctor` to see per-
|
|
448
|
+
? 'Check the plugins registered in your app for a synchronous `configure` hook that throws or returns a Promise. Run `dot doctor` for per-plugin diagnostics.'
|
|
449
|
+
: 'Run `dot doctor` to see per-plugin diagnostics. The boot hook for one of your plugins failed.',
|
|
384
450
|
metadata: { phase, underlyingCode: code },
|
|
385
451
|
cause: err,
|
|
386
452
|
});
|
|
@@ -488,9 +554,18 @@ export async function main(options: MainOptions): Promise<number> {
|
|
|
488
554
|
let envelope: DotCliEnvelope<unknown>;
|
|
489
555
|
|
|
490
556
|
if (args.command === 'explain') {
|
|
491
|
-
|
|
557
|
+
if (args.openapi === true) {
|
|
558
|
+
stderr('dot: --openapi is deprecated; use --as openapi.\n');
|
|
559
|
+
}
|
|
560
|
+
envelope = await runExplain(discovered.app, {
|
|
561
|
+
...opts,
|
|
562
|
+
as: args.as,
|
|
563
|
+
module: args.module,
|
|
564
|
+
appFilePath: discovered.filePath,
|
|
565
|
+
openapi: args.openapi,
|
|
566
|
+
});
|
|
492
567
|
} else {
|
|
493
|
-
envelope = await runDoctor(discovered, { ...opts, observability: args.observability });
|
|
568
|
+
envelope = await runDoctor(discovered.app, { ...opts, observability: args.observability });
|
|
494
569
|
}
|
|
495
570
|
|
|
496
571
|
return envelopeExitCode(envelope.status);
|
|
@@ -556,11 +631,14 @@ const isMainModule = await (async () => {
|
|
|
556
631
|
/* ignore */
|
|
557
632
|
}
|
|
558
633
|
// Node fallback: compare argv[1]'s resolved file URL with import.meta.url.
|
|
634
|
+
// argv[1] is the bin symlink (node_modules/.bin/dot) while Node resolves
|
|
635
|
+
// import.meta.url to the real file — realpath argv[1] so they can match.
|
|
559
636
|
try {
|
|
560
637
|
const entry = process.argv[1];
|
|
561
638
|
if (typeof entry !== 'string') return false;
|
|
562
639
|
const { pathToFileURL } = await import('node:url');
|
|
563
|
-
|
|
640
|
+
const { realpath } = await import('node:fs/promises');
|
|
641
|
+
return pathToFileURL(await realpath(entry)).href === import.meta.url;
|
|
564
642
|
} catch {
|
|
565
643
|
return false;
|
|
566
644
|
}
|
package/src/cli/new.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* stripped during scaffolding so `package.json.tmpl` becomes
|
|
18
18
|
* `package.json` on disk.
|
|
19
19
|
*
|
|
20
|
-
* Commands deferred to v1.1 (`dot add <
|
|
20
|
+
* Commands deferred to v1.1 (`dot add <plugin>`, `dot dev`,
|
|
21
21
|
* `dot migrate`) are deliberately NOT surfaced here.
|
|
22
22
|
*/
|
|
23
23
|
|
package/src/cli/render-doctor.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Renderers for `dot doctor`.
|
|
3
3
|
*
|
|
4
4
|
* Reads a booted (or fully-failed) app's `diagnostics` snapshot and emits
|
|
5
|
-
* either a JSON envelope or a human-readable per-
|
|
5
|
+
* either a JSON envelope or a human-readable per-plugin status report.
|
|
6
6
|
*
|
|
7
7
|
* Envelope `status` reflects the worst severity present:
|
|
8
8
|
* - `failure` if any issue has severity `error`
|
|
@@ -27,8 +27,8 @@ function nowIso(opts: RenderOptions): string {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
|
-
* Walk every issue carried anywhere in the snapshot (top-level + per-
|
|
31
|
-
* per-
|
|
30
|
+
* Walk every issue carried anywhere in the snapshot (top-level + per-plugin +
|
|
31
|
+
* per-action + per-service + per-lifecycle entry) and pick the worst severity.
|
|
32
32
|
*/
|
|
33
33
|
function worstSeverity(snap: DotDiagnosticsSnapshot): DotCliEnvelopeStatus {
|
|
34
34
|
let hasWarning = false;
|
|
@@ -46,8 +46,8 @@ function worstSeverity(snap: DotDiagnosticsSnapshot): DotCliEnvelopeStatus {
|
|
|
46
46
|
|
|
47
47
|
let hasError = false;
|
|
48
48
|
collect(snap.issues);
|
|
49
|
-
for (const p of snap.
|
|
50
|
-
for (const
|
|
49
|
+
for (const p of snap.plugins) collect(p.issues);
|
|
50
|
+
for (const action of snap.actions) collect(action.issues);
|
|
51
51
|
for (const s of snap.services) collect(s.issues);
|
|
52
52
|
for (const l of snap.lifecycle) collect(l.issues);
|
|
53
53
|
|
|
@@ -72,8 +72,8 @@ export function buildDoctorEnvelope(
|
|
|
72
72
|
for (const issue of issues) errors.push(issue);
|
|
73
73
|
};
|
|
74
74
|
collect(source.diagnostics.issues);
|
|
75
|
-
for (const p of source.diagnostics.
|
|
76
|
-
for (const
|
|
75
|
+
for (const p of source.diagnostics.plugins) collect(p.issues);
|
|
76
|
+
for (const action of source.diagnostics.actions) collect(action.issues);
|
|
77
77
|
for (const s of source.diagnostics.services) collect(s.issues);
|
|
78
78
|
for (const l of source.diagnostics.lifecycle) collect(l.issues);
|
|
79
79
|
|
|
@@ -120,13 +120,13 @@ function renderTextDoctor(snap: DotDiagnosticsSnapshot, status: DotCliEnvelopeSt
|
|
|
120
120
|
lines.push(`generatedAt: ${snap.generatedAt}`);
|
|
121
121
|
lines.push('');
|
|
122
122
|
|
|
123
|
-
// Per-
|
|
124
|
-
lines.push(`
|
|
125
|
-
if (snap.
|
|
123
|
+
// Per-plugin status
|
|
124
|
+
lines.push(`Plugins (${snap.plugins.length})`);
|
|
125
|
+
if (snap.plugins.length === 0) {
|
|
126
126
|
lines.push(' (none)');
|
|
127
127
|
} else {
|
|
128
|
-
for (const p of snap.
|
|
129
|
-
lines.push(` ${statusIcon(p.status)} ${p.
|
|
128
|
+
for (const p of snap.plugins) {
|
|
129
|
+
lines.push(` ${statusIcon(p.status)} ${p.plugin}`);
|
|
130
130
|
if (p.issues.length > 0) {
|
|
131
131
|
lines.push(...renderIssueLines(p.issues, ' '));
|
|
132
132
|
}
|
|
@@ -138,7 +138,7 @@ function renderTextDoctor(snap: DotDiagnosticsSnapshot, status: DotCliEnvelopeSt
|
|
|
138
138
|
if (snap.services.length > 0) {
|
|
139
139
|
lines.push(`Services (${snap.services.length})`);
|
|
140
140
|
for (const s of snap.services) {
|
|
141
|
-
lines.push(` ${statusIcon(s.status)} ${s.service} (
|
|
141
|
+
lines.push(` ${statusIcon(s.status)} ${s.service} (plugin: ${s.plugin})`);
|
|
142
142
|
if (s.issues.length > 0) {
|
|
143
143
|
lines.push(...renderIssueLines(s.issues, ' '));
|
|
144
144
|
}
|
|
@@ -146,13 +146,13 @@ function renderTextDoctor(snap: DotDiagnosticsSnapshot, status: DotCliEnvelopeSt
|
|
|
146
146
|
lines.push('');
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
//
|
|
150
|
-
if (snap.
|
|
151
|
-
lines.push(`
|
|
152
|
-
for (const
|
|
153
|
-
lines.push(` ${statusIcon(
|
|
154
|
-
if (
|
|
155
|
-
lines.push(...renderIssueLines(
|
|
149
|
+
// Actions
|
|
150
|
+
if (snap.actions.length > 0) {
|
|
151
|
+
lines.push(`Actions (${snap.actions.length})`);
|
|
152
|
+
for (const action of snap.actions) {
|
|
153
|
+
lines.push(` ${statusIcon(action.status)} ${action.binding}:${action.id} (plugin: ${action.plugin})`);
|
|
154
|
+
if (action.issues.length > 0) {
|
|
155
|
+
lines.push(...renderIssueLines(action.issues, ' '));
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
lines.push('');
|
|
@@ -163,7 +163,7 @@ function renderTextDoctor(snap: DotDiagnosticsSnapshot, status: DotCliEnvelopeSt
|
|
|
163
163
|
if (lifeWithIssues.length > 0) {
|
|
164
164
|
lines.push(`Lifecycle issues (${lifeWithIssues.length})`);
|
|
165
165
|
for (const l of lifeWithIssues) {
|
|
166
|
-
lines.push(` ${l.
|
|
166
|
+
lines.push(` ${l.plugin}:${l.hook} (order=${l.order}, state=${l.state})`);
|
|
167
167
|
lines.push(...renderIssueLines(l.issues, ' '));
|
|
168
168
|
}
|
|
169
169
|
lines.push('');
|