@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.
Files changed (116) hide show
  1. package/README.md +86 -86
  2. package/dist/cli/discover.js +2 -2
  3. package/dist/cli/discover.js.map +1 -1
  4. package/dist/cli/error-codes.d.ts +8 -0
  5. package/dist/cli/error-codes.d.ts.map +1 -1
  6. package/dist/cli/error-codes.js +12 -0
  7. package/dist/cli/error-codes.js.map +1 -1
  8. package/dist/cli/index.d.ts +11 -1
  9. package/dist/cli/index.d.ts.map +1 -1
  10. package/dist/cli/index.js +66 -11
  11. package/dist/cli/index.js.map +1 -1
  12. package/dist/cli/new.d.ts +1 -1
  13. package/dist/cli/new.js +1 -1
  14. package/dist/cli/render-doctor.d.ts +1 -1
  15. package/dist/cli/render-doctor.js +23 -23
  16. package/dist/cli/render-doctor.js.map +1 -1
  17. package/dist/cli/render-explain.d.ts.map +1 -1
  18. package/dist/cli/render-explain.js +33 -19
  19. package/dist/cli/render-explain.js.map +1 -1
  20. package/dist/cli/render-graph.d.ts +3 -3
  21. package/dist/cli/render-graph.js +12 -12
  22. package/dist/cli/render-graph.js.map +1 -1
  23. package/dist/cli/render-openapi.d.ts +38 -0
  24. package/dist/cli/render-openapi.d.ts.map +1 -0
  25. package/dist/cli/render-openapi.js +131 -0
  26. package/dist/cli/render-openapi.js.map +1 -0
  27. package/dist/cli/render-projection.d.ts +29 -0
  28. package/dist/cli/render-projection.d.ts.map +1 -0
  29. package/dist/cli/render-projection.js +202 -0
  30. package/dist/cli/render-projection.js.map +1 -0
  31. package/dist/define-app.d.ts +49 -17
  32. package/dist/define-app.d.ts.map +1 -1
  33. package/dist/define-app.js +16 -9
  34. package/dist/define-app.js.map +1 -1
  35. package/dist/diagnostics.d.ts +19 -15
  36. package/dist/diagnostics.d.ts.map +1 -1
  37. package/dist/diagnostics.js +2 -2
  38. package/dist/index.d.ts +15 -12
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +7 -5
  41. package/dist/index.js.map +1 -1
  42. package/dist/init-pips.d.ts +45 -0
  43. package/dist/init-pips.d.ts.map +1 -0
  44. package/dist/init-pips.js +49 -0
  45. package/dist/init-pips.js.map +1 -0
  46. package/dist/init-plugins.d.ts +45 -0
  47. package/dist/init-plugins.d.ts.map +1 -0
  48. package/dist/init-plugins.js +49 -0
  49. package/dist/init-plugins.js.map +1 -0
  50. package/dist/kernel/app-instance.d.ts +5 -5
  51. package/dist/kernel/app-instance.d.ts.map +1 -1
  52. package/dist/kernel/app-instance.js +375 -244
  53. package/dist/kernel/app-instance.js.map +1 -1
  54. package/dist/kernel/otel.d.ts +19 -19
  55. package/dist/kernel/otel.d.ts.map +1 -1
  56. package/dist/kernel/otel.js +22 -22
  57. package/dist/kernel/otel.js.map +1 -1
  58. package/dist/lifecycle-observer.d.ts +10 -10
  59. package/dist/lifecycle-observer.d.ts.map +1 -1
  60. package/dist/lifecycle-observer.js +1 -1
  61. package/dist/lifecycle.d.ts +18 -14
  62. package/dist/lifecycle.d.ts.map +1 -1
  63. package/dist/lifecycle.js +13 -9
  64. package/dist/lifecycle.js.map +1 -1
  65. package/dist/manifest.d.ts +70 -29
  66. package/dist/manifest.d.ts.map +1 -1
  67. package/dist/manifest.js +74 -5
  68. package/dist/manifest.js.map +1 -1
  69. package/dist/pip-contract.d.ts +40 -10
  70. package/dist/pip-contract.d.ts.map +1 -1
  71. package/dist/pip-contract.js +1 -25
  72. package/dist/pip-contract.js.map +1 -1
  73. package/dist/pip.d.ts +7 -4
  74. package/dist/pip.d.ts.map +1 -1
  75. package/dist/pip.js +2 -0
  76. package/dist/pip.js.map +1 -1
  77. package/dist/plugin-contract.d.ts +335 -143
  78. package/dist/plugin-contract.d.ts.map +1 -1
  79. package/dist/plugin-contract.js +201 -36
  80. package/dist/plugin-contract.js.map +1 -1
  81. package/dist/plugin.d.ts +18 -15
  82. package/dist/plugin.d.ts.map +1 -1
  83. package/dist/plugin.js +11 -11
  84. package/dist/plugin.js.map +1 -1
  85. package/dist/test-harness.d.ts +31 -31
  86. package/dist/test-harness.d.ts.map +1 -1
  87. package/dist/test-harness.js +29 -28
  88. package/dist/test-harness.js.map +1 -1
  89. package/dist/timeline.d.ts +2 -2
  90. package/dist/timeline.js +5 -5
  91. package/dist/timeline.js.map +1 -1
  92. package/package.json +9 -9
  93. package/src/cli/discover.ts +2 -2
  94. package/src/cli/error-codes.ts +12 -0
  95. package/src/cli/index.ts +93 -15
  96. package/src/cli/new.ts +1 -1
  97. package/src/cli/render-doctor.ts +21 -21
  98. package/src/cli/render-explain.ts +37 -19
  99. package/src/cli/render-graph.ts +12 -12
  100. package/src/cli/render-projection.ts +239 -0
  101. package/src/define-app.ts +85 -22
  102. package/src/diagnostics.ts +19 -15
  103. package/src/index.ts +38 -20
  104. package/src/init-plugins.ts +142 -0
  105. package/src/kernel/app-instance.ts +428 -282
  106. package/src/kernel/otel.ts +27 -27
  107. package/src/lifecycle-observer.ts +10 -10
  108. package/src/lifecycle.ts +19 -15
  109. package/src/manifest.ts +128 -31
  110. package/src/{pip-contract.ts → plugin-contract.ts} +83 -44
  111. package/src/{pip.ts → plugin.ts} +38 -18
  112. package/src/test-harness.ts +52 -51
  113. package/src/timeline.ts +6 -6
  114. package/templates/app-minimal/AGENTS.md.tmpl +9 -9
  115. package/templates/app-minimal/README.md.tmpl +1 -1
  116. package/templates/app-minimal/tests/boot.test.ts.tmpl +1 -1
@@ -1,37 +1,37 @@
1
1
  /**
2
- * Test harness for unit-testing DOT pips.
2
+ * Test harness for unit-testing DOT plugins.
3
3
  *
4
- * Provides `testApp` — a convenience wrapper that lets pip authors verify
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 pip array, so the compile-time wiring
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, pip } from '@arki/dot';
14
+ * import { testApp, plugin } from '@arki/dot';
15
15
  *
16
- * const myPip = pip({
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 } }>([myPip]);
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 pips, ready to
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(pips = [], options = {}) {
32
+ export function testApp(plugins = [], options = {}) {
33
33
  let builder = defineApp(options.name ?? 'test-app', { config: options.config });
34
- for (const p of pips) {
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(pips = [], options = {}) {
44
- return testApp(pips, options).boot();
43
+ export async function bootTestApp(plugins = [], options = {}) {
44
+ return testApp(plugins, options).boot();
45
45
  }
46
46
  /**
47
- * Unit-test a single pip with **typed overrides** — the compile-checked
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 pip's needs directly (no real
51
- * provider pip, no dependency chain) and removes it from the builder's
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 { testPip } from '@arki/dot/test-harness';
56
+ * import { testPlugin } from '@arki/dot/test-harness';
57
57
  *
58
- * const app = await testPip(catalog)
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 pip, so reverse-order teardown and lazy auto-dispose
70
+ * synthetic first plugin, so reverse-order teardown and lazy auto-dispose
71
71
  * behave exactly as in production.
72
72
  */
73
- export function testPip(pip, options = {}) {
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 makeTestPipBuilder({ pip: pip, fakes: {}, options });
76
+ return makeTestPluginBuilder({ plugin: plugin, fakes: {}, options });
77
77
  }
78
78
  /**
79
- * Erased implementation behind {@link testPip}. Mirrors `makeBuilder` in
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 makeTestPipBuilder(state) {
83
+ function makeTestPluginBuilder(state) {
84
84
  const bootApp = async () => {
85
- const pips = [];
85
+ const plugins = [];
86
86
  if (Object.keys(state.fakes).length > 0) {
87
87
  const fakes = { ...state.fakes };
88
- pips.push({
89
- name: `test:fakes(${state.pip.name})`,
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
- pips.push(state.pip);
96
- return testApp(pips, {
97
- name: state.options.name ?? `test:${state.pip.name}`,
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 makeTestPipBuilder({ ...state, fakes: { ...state.fakes, [key]: value } });
105
+ return makeTestPluginBuilder({ ...state, fakes: { ...state.fakes, [key]: value } });
105
106
  },
106
107
  async boot(..._guard) {
107
108
  return bootApp();
@@ -1 +1 @@
1
- {"version":3,"file":"test-harness.js","sourceRoot":"","sources":["../src/test-harness.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"test-harness.js","sourceRoot":"","sources":["../src/test-harness.ts"],"names":[],"mappings":"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"}
@@ -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-pip
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 pip's diagnostic `issues[]` underneath
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-pip
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 pip column, 8-char duration column, then bar.
64
- const PIP_COL = 12;
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 pipCol = entry.pip.padEnd(PIP_COL);
80
+ const pluginCol = entry.plugin.padEnd(PLUGIN_COL);
81
81
  const durCol = formatDuration(durationMs).padStart(DUR_COL);
82
- lines.push(`${indent}${pipCol}${durCol} ${bar}${failedMark}`);
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}`);
@@ -1 +1 @@
1
- {"version":3,"file":"timeline.js","sourceRoot":"","sources":["../src/timeline.ts"],"names":[],"mappings":"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"}
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.2.0",
4
- "description": "TypeScript-first application composition framework for the ARKI package family — lifecycle-aware pips, deterministic boot order, manifest/diagnostics envelopes, and an agent-native CLI. A DOT app is the sum of its pips.",
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
- "pip",
19
- "pips",
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
- "./pip": {
35
- "types": "./dist/pip.d.ts",
36
- "import": "./dist/pip.js",
37
- "default": "./dist/pip.js"
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.2",
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"
@@ -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(pip);`',
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(pip)`), or pass `--app <path>` to point at an existing file.',
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
  }
@@ -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 pip graph as Mermaid flowchart source
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<DiscoveredApp> {
223
- const { app } = await discoverApp({ appPath: args.appPath, cwd: args.cwd });
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: { json: boolean; graph?: boolean; out?: (line: string) => void; now?: () => Date },
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 pip graph (Mermaid) instead of diagnostics. */
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-pip issues stay
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-pip
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 pip.
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 pips registered in your app for a synchronous `configure` hook that throws or returns a Promise. Run `dot doctor` for per-pip diagnostics.'
383
- : 'Run `dot doctor` to see per-pip diagnostics. The boot hook for one of your pips failed.',
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
- envelope = await runExplain(discovered, opts);
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
- return pathToFileURL(entry).href === import.meta.url;
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 <pip>`, `dot dev`,
20
+ * Commands deferred to v1.1 (`dot add <plugin>`, `dot dev`,
21
21
  * `dot migrate`) are deliberately NOT surfaced here.
22
22
  */
23
23
 
@@ -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-pip status report.
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-pip +
31
- * per-route + per-service + per-lifecycle entry) and pick the worst severity.
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.pips) collect(p.issues);
50
- for (const r of snap.routes) collect(r.issues);
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.pips) collect(p.issues);
76
- for (const r of source.diagnostics.routes) collect(r.issues);
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-pip status
124
- lines.push(`Pips (${snap.pips.length})`);
125
- if (snap.pips.length === 0) {
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.pips) {
129
- lines.push(` ${statusIcon(p.status)} ${p.pip}`);
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} (pip: ${s.pip})`);
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
- // Routes
150
- if (snap.routes.length > 0) {
151
- lines.push(`Routes (${snap.routes.length})`);
152
- for (const r of snap.routes) {
153
- lines.push(` ${statusIcon(r.status)} ${r.id} (pip: ${r.pip})`);
154
- if (r.issues.length > 0) {
155
- lines.push(...renderIssueLines(r.issues, ' '));
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.pip}:${l.hook} (order=${l.order}, state=${l.state})`);
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('');