argsbarg 6.1.2 → 6.1.3

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 (229) hide show
  1. package/CHANGELOG.md +65 -1
  2. package/README.md +17 -19
  3. package/bin/argsbarg +10 -0
  4. package/docs/README.md +4 -3
  5. package/docs/ai-skills.md +4 -2
  6. package/docs/bundled-docs.md +50 -25
  7. package/docs/cli-program.md +52 -10
  8. package/docs/config-schema.md +10 -11
  9. package/docs/configure.md +2 -0
  10. package/docs/decisions.md +40 -0
  11. package/docs/developing.md +43 -5
  12. package/docs/http-server.md +171 -0
  13. package/docs/json-schema-subset.md +51 -0
  14. package/docs/mcp.md +4 -2
  15. package/docs/output-schema.md +55 -62
  16. package/examples/formats.ts +6 -6
  17. package/examples/full-example/Formula/full-example.rb +35 -0
  18. package/examples/full-example/README.md +20 -21
  19. package/examples/full-example/docs/README.md +1 -1
  20. package/examples/full-example/docs/cli-schema.json +1790 -98
  21. package/examples/full-example/docs/cli.md +1990 -0
  22. package/examples/full-example/docs/http.md +28 -29
  23. package/examples/full-example/docs/mcp.md +8 -22
  24. package/examples/full-example/docs/openapi.json +783 -50
  25. package/examples/full-example/docs/skill.md +10 -10
  26. package/examples/full-example/justfile +11 -1
  27. package/examples/full-example/src/commands/render-json/__generated__/RenderJsonInputSchema.json +15 -0
  28. package/examples/full-example/src/commands/render-json/__generated__/index.ts +5 -0
  29. package/examples/full-example/src/commands/render-json/command.test.ts +46 -0
  30. package/examples/full-example/src/commands/render-json/command.ts +30 -0
  31. package/examples/full-example/src/commands/render-json/types.ts +9 -0
  32. package/examples/full-example/src/commands/status/__generated__/StatusJsonOutputSchema.json +15 -0
  33. package/examples/full-example/src/commands/status/__generated__/index.ts +2 -2
  34. package/examples/full-example/src/commands/status/command.ts +5 -13
  35. package/examples/full-example/src/commands/status/types.ts +1 -14
  36. package/examples/full-example/src/commands/workspaces/__generated__/WorkspaceNameInputSchema.json +15 -0
  37. package/examples/full-example/src/commands/workspaces/__generated__/index.ts +5 -0
  38. package/examples/full-example/src/commands/workspaces/command.test.ts +58 -0
  39. package/examples/full-example/src/commands/workspaces/command.ts +94 -0
  40. package/examples/full-example/src/commands/workspaces/types.ts +6 -0
  41. package/examples/full-example/src/db/index.test.ts +86 -0
  42. package/examples/full-example/src/db/index.ts +101 -0
  43. package/examples/full-example/src/db/migrate.test.ts +35 -0
  44. package/examples/full-example/src/db/migrate.ts +69 -0
  45. package/examples/full-example/src/db/migrations/001_workspaces.sql +6 -0
  46. package/examples/full-example/src/db/tables/workspaces.ts +66 -0
  47. package/examples/full-example/src/program.ts +11 -36
  48. package/examples/full-example/src/types/argsbarg.d.ts +11 -0
  49. package/examples/full-example/src/types/md.d.ts +4 -0
  50. package/examples/full-example/tsconfig.json +5 -2
  51. package/examples/mcp-test.ts +1 -2
  52. package/examples/minimal.ts +1 -7
  53. package/examples/nested.ts +1 -2
  54. package/examples/option-required.ts +1 -1
  55. package/examples/servers.ts +4 -5
  56. package/index.d.ts +431 -136
  57. package/package.json +19 -2
  58. package/src/builtins/builtins.test.ts +7 -7
  59. package/src/builtins/completion-bash.ts +1 -1
  60. package/src/builtins/completion-fish.ts +1 -1
  61. package/src/builtins/completion-group.ts +4 -4
  62. package/src/builtins/completion-simulate-shared.ts +9 -0
  63. package/src/builtins/completion-zsh.ts +1 -1
  64. package/src/builtins/config.test.ts +3 -3
  65. package/src/builtins/config.ts +9 -9
  66. package/src/builtins/configure-copy.ts +2 -2
  67. package/src/builtins/configure.ts +4 -4
  68. package/src/builtins/dispatch.ts +19 -18
  69. package/src/builtins/export.ts +7 -5
  70. package/src/builtins/http.ts +68 -0
  71. package/src/builtins/mcp.ts +28 -4
  72. package/src/builtins/presentation.ts +6 -6
  73. package/src/builtins/registry.ts +6 -6
  74. package/src/builtins/scopes.ts +2 -2
  75. package/src/builtins/version.ts +1 -1
  76. package/src/cli-tool/full-example-capabilities.test.ts +10 -15
  77. package/src/cli-tool/main.ts +1 -1
  78. package/src/cli-tool/program.ts +3 -2
  79. package/src/cli-tool/prompt.ts +1 -1
  80. package/src/cli-tool/run-schemagen.ts +1 -3
  81. package/src/cli-tool/schemagen/cleanup.ts +6 -7
  82. package/src/cli-tool/schemagen/discover-schema-roots.ts +66 -120
  83. package/src/cli-tool/schemagen/index.ts +2 -2
  84. package/src/cli-tool/schemagen/names.ts +8 -13
  85. package/src/cli-tool/schemagen/run.ts +21 -28
  86. package/src/cli-tool/schemagen/schemagen.test.ts +136 -46
  87. package/src/config/bindings.test.ts +1 -1
  88. package/src/config/bindings.ts +1 -1
  89. package/src/config/bootstrap.test.ts +1 -1
  90. package/src/config/bootstrap.ts +36 -4
  91. package/src/config/context.test.ts +1 -1
  92. package/src/config/context.ts +1 -1
  93. package/src/config/entry.ts +1 -1
  94. package/src/config/file.test.ts +1 -1
  95. package/src/config/file.ts +3 -3
  96. package/src/config/manifest.ts +1 -1
  97. package/src/config/resolve.test.ts +1 -1
  98. package/src/config/resolve.ts +1 -1
  99. package/src/config/schema.ts +1 -1
  100. package/src/config/validate.ts +1 -1
  101. package/src/{install → configure/artifacts}/binary-placement.test.ts +1 -1
  102. package/src/{install → configure/artifacts}/binary-placement.ts +1 -1
  103. package/src/{install → configure/artifacts}/gh-release-update.ts +1 -1
  104. package/src/{install → configure/artifacts}/install-validate.test.ts +3 -3
  105. package/src/{install → configure/artifacts}/mcp-config.ts +1 -1
  106. package/src/{install → configure/artifacts}/mcp-opencode.test.ts +1 -1
  107. package/src/{install → configure/artifacts}/mcp-opencode.ts +1 -1
  108. package/src/{install → configure/artifacts}/paths.ts +5 -5
  109. package/src/configure/artifacts/plan.ts +24 -0
  110. package/src/{install → configure/artifacts}/status.test.ts +1 -1
  111. package/src/{install → configure/artifacts}/status.ts +2 -2
  112. package/src/{install → configure/artifacts}/target-base.ts +1 -1
  113. package/src/{install → configure/artifacts}/target-detect.ts +1 -1
  114. package/src/{install → configure/artifacts}/target-effective.ts +3 -9
  115. package/src/{install → configure/artifacts}/target-mcp-cli.ts +1 -1
  116. package/src/{install → configure/artifacts}/target-mcp-json.ts +1 -1
  117. package/src/{install → configure/artifacts}/target-plan-build.ts +2 -2
  118. package/src/{install → configure/artifacts}/target-registry.ts +2 -2
  119. package/src/{install → configure/artifacts}/target-scope.ts +3 -3
  120. package/src/{install → configure/artifacts}/target-skill.ts +1 -1
  121. package/src/{install → configure/artifacts}/target-types.ts +2 -2
  122. package/src/{install → configure/artifacts}/targets/app.ts +5 -5
  123. package/src/{install → configure/artifacts}/targets/chatgpt-mcp.ts +2 -2
  124. package/src/{install → configure/artifacts}/targets/claude-code-mcp.ts +2 -2
  125. package/src/{install → configure/artifacts}/targets/claude-desktop-mcp.ts +2 -2
  126. package/src/{install → configure/artifacts}/targets/claude-skill.ts +2 -2
  127. package/src/{install → configure/artifacts}/targets/codex-mcp.ts +2 -2
  128. package/src/{install → configure/artifacts}/targets/codex-skill.ts +2 -2
  129. package/src/{install → configure/artifacts}/targets/configure.ts +5 -5
  130. package/src/{install → configure/artifacts}/targets/cursor-mcp.ts +2 -2
  131. package/src/{install → configure/artifacts}/targets/cursor-skill.ts +2 -2
  132. package/src/{install → configure/artifacts}/targets/index.ts +1 -1
  133. package/src/{install → configure/artifacts}/targets/openclaw-mcp.ts +2 -2
  134. package/src/{install → configure/artifacts}/targets/openclaw-skill.ts +3 -3
  135. package/src/{install → configure/artifacts}/targets/opencode-mcp.ts +5 -5
  136. package/src/{install → configure/artifacts}/targets/opencode-skill.ts +3 -3
  137. package/src/{install → configure/artifacts}/targets.test.ts +1 -1
  138. package/src/{install → configure/artifacts}/uninstall.ts +1 -1
  139. package/src/configure/configure.test.ts +11 -11
  140. package/src/configure/index.ts +14 -14
  141. package/src/configure/prompt.ts +2 -2
  142. package/src/{context.ts → core/context.ts} +26 -20
  143. package/src/{json-leaf.test.ts → core/json-leaf.test.ts} +4 -4
  144. package/src/{leaf-inputs.test.ts → core/leaf-inputs.test.ts} +7 -7
  145. package/src/{leaf-inputs.ts → core/leaf-inputs.ts} +16 -12
  146. package/src/{parse.test.ts → core/parse.test.ts} +97 -109
  147. package/src/{parse.ts → core/parse.ts} +129 -31
  148. package/src/{schema.ts → core/schema.ts} +25 -13
  149. package/src/{types.ts → core/types.ts} +225 -35
  150. package/src/{validate.ts → core/validate.ts} +39 -29
  151. package/src/docs/builtin.ts +8 -19
  152. package/src/docs/{api-guide.test.ts → cli-guide.test.ts} +21 -21
  153. package/src/docs/{api-guide.ts → cli-guide.ts} +45 -16
  154. package/src/docs/docs.test.ts +76 -41
  155. package/src/docs/http-guide.ts +37 -34
  156. package/src/docs/mcp-guide.ts +12 -14
  157. package/src/docs/mcp-resources.test.ts +2 -3
  158. package/src/docs/mcp-resources.ts +6 -11
  159. package/src/docs/resolve.ts +22 -30
  160. package/src/docs/save.ts +3 -3
  161. package/src/exports/cli.ts +47 -0
  162. package/src/exports/headless.ts +13 -0
  163. package/src/exports/http.ts +6 -0
  164. package/src/exports/mcp.ts +6 -0
  165. package/src/{headless.test.ts → headless/routing.test.ts} +3 -3
  166. package/src/{headless.ts → headless/routing.ts} +3 -3
  167. package/src/headless/tool-call.ts +114 -46
  168. package/src/help.test.ts +152 -0
  169. package/src/help.ts +3 -3
  170. package/src/hooks/builtin.ts +20 -0
  171. package/src/hooks/run.ts +142 -0
  172. package/src/http/openapi.ts +182 -0
  173. package/src/http/readiness.ts +78 -0
  174. package/src/{api → http}/result.ts +16 -5
  175. package/src/http/routes.ts +329 -0
  176. package/src/http/server.ts +225 -0
  177. package/src/index.ts +36 -25
  178. package/src/log/ecs.test.ts +43 -0
  179. package/src/log/ecs.ts +59 -0
  180. package/src/log/emitter.ts +166 -0
  181. package/src/mcp/bundle.ts +2 -2
  182. package/src/mcp/claude.test.ts +1 -1
  183. package/src/mcp/claude.ts +4 -4
  184. package/src/{hidden-mcpb.test.ts → mcp/hidden-mcpb.test.ts} +10 -9
  185. package/src/mcp/result.ts +2 -2
  186. package/src/mcp/server.ts +54 -6
  187. package/src/mcp/tools.ts +9 -20
  188. package/src/{capabilities.ts → runtime/capabilities.ts} +11 -11
  189. package/src/{cli-errors.ts → runtime/cli-errors.ts} +4 -4
  190. package/src/{cli.ts → runtime/cli.ts} +159 -49
  191. package/src/runtime/exposure.ts +102 -0
  192. package/src/{invoke.test.ts → runtime/invoke.test.ts} +31 -7
  193. package/src/server/context.ts +25 -0
  194. package/src/server/overrides.ts +112 -0
  195. package/src/skill/generate.ts +8 -8
  196. package/src/skill/hint.ts +1 -1
  197. package/src/skill/install.ts +2 -2
  198. package/src/skill/naming.ts +1 -1
  199. package/src/{test-fixtures.ts → test/fixtures.ts} +3 -2
  200. package/src/{config.integration.test.ts → test/integration/config.test.ts} +8 -8
  201. package/src/{api.integration.test.ts → test/integration/http.test.ts} +170 -67
  202. package/src/{mcp.integration.test.ts → test/integration/mcp.test.ts} +11 -57
  203. package/docs/api-server.md +0 -141
  204. package/examples/full-example/docs/api.md +0 -511
  205. package/examples/full-example/src/commands/status/__generated__/outputSchema.json +0 -28
  206. package/examples/full-example/src/config/__generated__/configSchema.json +0 -40
  207. package/examples/full-example/src/config/__generated__/index.ts +0 -5
  208. package/examples/full-example/src/config/types.ts +0 -24
  209. package/src/api/openapi.ts +0 -117
  210. package/src/api/server.ts +0 -120
  211. package/src/builtins/api.ts +0 -38
  212. package/src/hidden.ts +0 -30
  213. package/src/install/plan.ts +0 -53
  214. /package/src/{install → configure/artifacts}/detect-installed.ts +0 -0
  215. /package/src/{install → configure/artifacts}/gh-release-update.test.ts +0 -0
  216. /package/src/{install → configure/artifacts}/mcp-codex.test.ts +0 -0
  217. /package/src/{install → configure/artifacts}/mcp-codex.ts +0 -0
  218. /package/src/{install → configure/artifacts}/mcp-openclaw.test.ts +0 -0
  219. /package/src/{install → configure/artifacts}/mcp-openclaw.ts +0 -0
  220. /package/src/{install → configure/artifacts}/normalize-uninstall.ts +0 -0
  221. /package/src/{install → configure/artifacts}/normalize.ts +0 -0
  222. /package/src/{install → configure/artifacts}/opts.ts +0 -0
  223. /package/src/{install → configure/artifacts}/shell.ts +0 -0
  224. /package/src/{formats.test.ts → core/formats.test.ts} +0 -0
  225. /package/src/{formats.ts → core/formats.ts} +0 -0
  226. /package/src/{respond.ts → core/respond.ts} +0 -0
  227. /package/src/{types.test.ts → core/types.test.ts} +0 -0
  228. /package/src/{api → http}/schema-deref.test.ts +0 -0
  229. /package/src/{api → http}/schema-deref.ts +0 -0
@@ -3,9 +3,9 @@ Remove stale __generated__/ directories and files after schemagen runs.
3
3
  */
4
4
 
5
5
  import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
6
- import { dirname, join, relative } from "node:path";
6
+ import { join, relative } from "node:path";
7
7
  import type { SchemaRoot } from "./discover-schema-roots.ts";
8
- import { GENERATED_DIR, schemaJsonBasename, TYPES_FILE } from "./names.ts";
8
+ import { GENERATED_DIR, schemaJsonBasename } from "./names.ts";
9
9
 
10
10
  function listGeneratedDirs(srcDir: string, projectRoot: string, out: string[]): void {
11
11
  for (const ent of readdirSync(srcDir)) {
@@ -22,11 +22,11 @@ function listGeneratedDirs(srcDir: string, projectRoot: string, out: string[]):
22
22
  }
23
23
  }
24
24
 
25
- /** Drop orphan `__generated__/` trees and JSON files for removed schema kinds. */
25
+ /** Drop orphan `__generated__/` trees and JSON files for removed schema roots. */
26
26
  export function cleanStaleGenerated(
27
27
  projectRoot: string,
28
28
  srcDir: string,
29
- activeBySchemaFile: Map<string, SchemaRoot[]>,
29
+ activeByGeneratedDir: Map<string, SchemaRoot[]>,
30
30
  ): void {
31
31
  const srcPath = join(projectRoot, srcDir);
32
32
  if (!existsSync(srcPath)) {
@@ -38,8 +38,7 @@ export function cleanStaleGenerated(
38
38
 
39
39
  for (const relGeneratedDir of generatedDirs) {
40
40
  const generatedDir = join(projectRoot, relGeneratedDir);
41
- const relTypesPath = relative(projectRoot, join(dirname(generatedDir), TYPES_FILE));
42
- const roots = existsSync(join(projectRoot, relTypesPath)) ? (activeBySchemaFile.get(relTypesPath) ?? []) : [];
41
+ const roots = activeByGeneratedDir.get(generatedDir) ?? [];
43
42
 
44
43
  if (roots.length === 0) {
45
44
  rmSync(generatedDir, { recursive: true, force: true });
@@ -47,7 +46,7 @@ export function cleanStaleGenerated(
47
46
  continue;
48
47
  }
49
48
 
50
- const keep = new Set(["index.ts", ...roots.map((root) => schemaJsonBasename(root.kind))]);
49
+ const keep = new Set(["index.ts", ...roots.map((root) => schemaJsonBasename(root.typeName))]);
51
50
  for (const ent of readdirSync(generatedDir)) {
52
51
  if (keep.has(ent)) {
53
52
  continue;
@@ -1,160 +1,111 @@
1
1
  /*
2
- Discovers schema roots in types.ts files via configType / inputType / outputType exports.
2
+ Discovers schema roots via @sg JSDoc markers on export interface/type declarations.
3
3
  */
4
4
 
5
5
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
6
- import { dirname, join, relative } from "node:path";
7
- import { TYPES_FILE } from "./names.ts";
6
+ import { join, relative } from "node:path";
8
7
 
9
- export type SchemaRootKind = "config" | "input" | "output";
8
+ /** Directories to scan relative to project root. */
9
+ const SEARCH_DIRS = ["src"] as const;
10
10
 
11
- export type SchemaRole = "configType" | "inputType" | "outputType";
11
+ /** Directory names skipped during walk (any depth). */
12
+ const EXCLUDE_DIRS = ["node_modules", "__generated__"] as const;
13
+
14
+ /** File basename patterns skipped (basename match only). */
15
+ const EXCLUDE_FILE_PATTERNS = [/\.test\.ts$/];
16
+
17
+ const SG_JSDOC_RE = /\/\*\*[\s\S]*?@sg[\s\S]*?\*\//g;
18
+ const EXPORT_DECL_RE = /^export\s+(interface|type)\s+(\w+)/;
12
19
 
13
20
  export interface SchemaRoot {
14
- kind: SchemaRootKind;
15
21
  typeName: string;
16
- /** Path to types.ts relative to project root (anchors __generated__/ output). */
22
+ /** Path to the file containing `@sg`, relative to project root (anchors `__generated__/`). */
17
23
  path: string;
18
- /** Path to the file that defines `typeName` (defaults to `path`). */
24
+ /** Path to the file that defines `typeName` (same as `path` for `@sg`). */
19
25
  sourcePath: string;
20
26
  }
21
27
 
22
- const ROLE_EXPORT_RE = /export\s+type\s+(configType|inputType|outputType)\s*=\s*(\w+)/g;
23
- const HAS_ROLE_EXPORT_RE = /export\s+type\s+(configType|inputType|outputType)\s*=/;
24
- const IMPORT_RE = /import\s+(?:type\s+)?\{([^}]+)\}\s+from\s+["']([^"']+)["']/g;
25
-
26
- const ROLE_TO_KIND: Record<SchemaRole, SchemaRootKind> = {
27
- configType: "config",
28
- inputType: "input",
29
- outputType: "output",
30
- };
28
+ function shouldIncludeFile(basename: string): boolean {
29
+ if (!basename.endsWith(".ts") || basename.endsWith(".d.ts")) {
30
+ return false;
31
+ }
32
+ return !EXCLUDE_FILE_PATTERNS.some((pattern) => pattern.test(basename));
33
+ }
31
34
 
32
- function listTypesManifestFiles(srcDir: string, baseDir: string, out: string[]): void {
33
- for (const ent of readdirSync(srcDir)) {
34
- const full = join(srcDir, ent);
35
+ function walkDir(dir: string, baseDir: string, out: string[]): void {
36
+ for (const ent of readdirSync(dir)) {
37
+ if ((EXCLUDE_DIRS as readonly string[]).includes(ent)) {
38
+ continue;
39
+ }
40
+ const full = join(dir, ent);
35
41
  const st = statSync(full);
36
42
  if (st.isDirectory()) {
37
- listTypesManifestFiles(full, baseDir, out);
43
+ walkDir(full, baseDir, out);
38
44
  continue;
39
45
  }
40
- if (ent !== TYPES_FILE) {
46
+ if (!shouldIncludeFile(ent)) {
41
47
  continue;
42
48
  }
43
- const text = readFileSync(full, "utf8");
44
- if (HAS_ROLE_EXPORT_RE.test(text)) {
45
- out.push(relative(baseDir, full));
46
- }
47
- }
48
- }
49
-
50
- /** True when `typeName` is declared in this file (not a schemagen role alias). */
51
- function isTypeDefinedInFile(text: string, typeName: string): boolean {
52
- if (new RegExp(`export\\s+interface\\s+${typeName}\\b`).test(text)) {
53
- return true;
54
- }
55
- if (new RegExp(`export\\s+type\\s+${typeName}\\s*=`).test(text)) {
56
- return !["configType", "inputType", "outputType"].includes(typeName);
49
+ out.push(relative(baseDir, full));
57
50
  }
58
- return false;
59
51
  }
60
52
 
61
- function parseLocalTypeImports(text: string): Map<string, string> {
62
- const imports = new Map<string, string>();
63
- for (const match of text.matchAll(IMPORT_RE)) {
64
- const names = match[1];
65
- const from = match[2];
66
- if (!names || !from?.startsWith(".")) {
53
+ function listScannableFiles(projectRoot: string): string[] {
54
+ const files: string[] = [];
55
+ for (const searchDir of SEARCH_DIRS) {
56
+ const abs = join(projectRoot, searchDir);
57
+ if (!existsSync(abs)) {
67
58
  continue;
68
59
  }
69
- for (const part of names.split(",")) {
70
- const trimmed = part.trim();
71
- const nameMatch = trimmed.match(/^(?:type\s+)?(\w+)(?:\s+as\s+(\w+))?$/);
72
- if (!nameMatch?.[1]) {
73
- continue;
74
- }
75
- const localName = nameMatch[2] ?? nameMatch[1];
76
- imports.set(localName, from);
77
- }
60
+ walkDir(abs, projectRoot, files);
78
61
  }
79
- return imports;
62
+ return files.sort();
80
63
  }
81
64
 
82
- function resolveModuleFile(manifestFile: string, specifier: string): string | null {
83
- const base = join(dirname(manifestFile), specifier);
84
- const candidates = [base, `${base}.ts`, join(base, "index.ts")];
85
- for (const candidate of candidates) {
86
- if (existsSync(candidate)) {
87
- return candidate;
88
- }
65
+ function parseTypeNameAfterSgBlock(text: string, blockEndIndex: number, relPath: string): string {
66
+ const rest = text.slice(blockEndIndex);
67
+ const sameLine = rest.match(/^([^\n]*)/)?.[1] ?? "";
68
+ if (sameLine.trim().length > 0) {
69
+ throw new Error(
70
+ `${relPath}: @sg JSDoc must be immediately followed by export interface/type (same-line export not supported)`,
71
+ );
89
72
  }
90
- return null;
91
- }
92
73
 
93
- /** Resolve a role alias to the module that defines `typeName`, when imported from a relative path. */
94
- function resolveAliasedTypeSource(
95
- projectRoot: string,
96
- manifestRelPath: string,
97
- manifestText: string,
98
- typeName: string,
99
- ): string | null {
100
- const manifestFile = join(projectRoot, manifestRelPath);
101
- const specifier = parseLocalTypeImports(manifestText).get(typeName);
102
- if (!specifier) {
103
- return null;
104
- }
105
- const moduleFile = resolveModuleFile(manifestFile, specifier);
106
- if (!moduleFile) {
107
- return null;
108
- }
109
- const moduleText = readFileSync(moduleFile, "utf8");
110
- if (!isTypeDefinedInFile(moduleText, typeName)) {
111
- return null;
112
- }
113
- return relative(projectRoot, moduleFile);
114
- }
115
-
116
- function discoverFromFile(projectRoot: string, path: string, text: string): SchemaRoot[] {
117
- const rolesSeen = new Set<SchemaRole>();
118
- const roots: SchemaRoot[] = [];
119
-
120
- for (const match of text.matchAll(ROLE_EXPORT_RE)) {
121
- const role = match[1] as SchemaRole | undefined;
122
- const typeName = match[2];
123
- if (!role || !typeName) {
124
- continue;
74
+ const lines = rest.split("\n").slice(1);
75
+ for (const line of lines) {
76
+ if (line.trim() === "") {
77
+ throw new Error(`${relPath}: @sg JSDoc must be immediately followed by export interface/type`);
125
78
  }
126
- if (rolesSeen.has(role)) {
127
- throw new Error(`${path}: duplicate export type ${role}`);
79
+ const match = line.match(EXPORT_DECL_RE);
80
+ if (match?.[2]) {
81
+ return match[2];
128
82
  }
129
- rolesSeen.add(role);
83
+ throw new Error(`${relPath}: @sg JSDoc must be immediately followed by export interface/type`);
84
+ }
130
85
 
131
- let sourcePath = path;
132
- if (!isTypeDefinedInFile(text, typeName)) {
133
- const resolved = resolveAliasedTypeSource(projectRoot, path, text, typeName);
134
- if (!resolved) {
135
- continue;
136
- }
137
- sourcePath = resolved;
138
- }
86
+ throw new Error(`${relPath}: @sg JSDoc must be immediately followed by export interface/type`);
87
+ }
139
88
 
140
- roots.push({ kind: ROLE_TO_KIND[role], typeName, path, sourcePath });
89
+ function discoverFromFile(relPath: string, text: string): SchemaRoot[] {
90
+ const roots: SchemaRoot[] = [];
91
+ for (const match of text.matchAll(SG_JSDOC_RE)) {
92
+ const index = match.index ?? 0;
93
+ const blockEnd = index + match[0].length;
94
+ const typeName = parseTypeNameAfterSgBlock(text, blockEnd, relPath);
95
+ roots.push({ typeName, path: relPath, sourcePath: relPath });
141
96
  }
142
-
143
97
  return roots;
144
98
  }
145
99
 
146
- /** Find all schema roots under `srcDir` in `types.ts` files with role exports. */
147
- export function discoverSchemaRoots(projectRoot: string, srcDir = "src"): SchemaRoot[] {
148
- const srcPath = join(projectRoot, srcDir);
149
- const files: string[] = [];
150
- listTypesManifestFiles(srcPath, projectRoot, files);
151
-
100
+ /** Find all `@sg` schema roots under `SEARCH_DIRS`. */
101
+ export function discoverSchemaRoots(projectRoot: string, _srcDir = "src"): SchemaRoot[] {
102
+ const files = listScannableFiles(projectRoot);
152
103
  const roots: SchemaRoot[] = [];
153
104
  const typeOwners = new Map<string, string>();
154
105
 
155
- for (const relPath of files.sort()) {
106
+ for (const relPath of files) {
156
107
  const text = readFileSync(join(projectRoot, relPath), "utf8");
157
- for (const root of discoverFromFile(projectRoot, relPath, text)) {
108
+ for (const root of discoverFromFile(relPath, text)) {
158
109
  const prev = typeOwners.get(root.typeName);
159
110
  if (prev) {
160
111
  throw new Error(`${relPath}: duplicate schema root type ${root.typeName} (already declared in ${prev})`);
@@ -164,10 +115,5 @@ export function discoverSchemaRoots(projectRoot: string, srcDir = "src"): Schema
164
115
  }
165
116
  }
166
117
 
167
- const configRoots = roots.filter((r) => r.kind === "config");
168
- if (configRoots.length > 1) {
169
- throw new Error(`multiple config schema roots: ${configRoots.map((r) => `${r.typeName} (${r.path})`).join(", ")}`);
170
- }
171
-
172
118
  return roots;
173
119
  }
@@ -1,3 +1,3 @@
1
- export { discoverSchemaRoots, type SchemaRoot, type SchemaRootKind } from "./discover-schema-roots.ts";
2
- export { GENERATED_DIR, schemaExportName, schemaJsonBasename, TYPES_FILE } from "./names.ts";
1
+ export { discoverSchemaRoots, type SchemaRoot } from "./discover-schema-roots.ts";
2
+ export { GENERATED_DIR, schemaExportName, schemaJsonBasename } from "./names.ts";
3
3
  export { type RunSchemagenOptions, type RunSchemagenResult, runSchemagen } from "./run.ts";
@@ -1,22 +1,17 @@
1
- import type { SchemaRootKind } from "./discover-schema-roots.ts";
2
-
3
- /** Directory name for generated schema artifacts (next to `types.ts`). */
1
+ /** Directory name for generated schema artifacts (colocated with `@sg` source files). */
4
2
  export const GENERATED_DIR = "__generated__";
5
3
 
6
- /** TypeScript schemagen manifest filename under `src/`. */
7
- export const TYPES_FILE = "types.ts";
8
-
9
- /** JSON basename for a schema root kind (`outputSchema.json`, etc.). */
10
- export function schemaJsonBasename(kind: SchemaRootKind): string {
11
- return `${kind}Schema.json`;
4
+ /** JSON basename for a schema root (`RenderJsonInputSchema.json`, etc.). */
5
+ export function schemaJsonBasename(typeName: string): string {
6
+ return `${typeName}Schema.json`;
12
7
  }
13
8
 
14
9
  /** Export const name wired on leaves or `program.appConfig`. */
15
- export function schemaExportName(kind: SchemaRootKind): string {
16
- return `${kind}Schema`;
10
+ export function schemaExportName(typeName: string): string {
11
+ return `${typeName}Schema`;
17
12
  }
18
13
 
19
14
  /** Safe import binding for a schema JSON basename. */
20
- export function schemaJsonImportVar(kind: SchemaRootKind): string {
21
- return `${kind}SchemaJson`;
15
+ export function schemaJsonImportVar(typeName: string): string {
16
+ return `${typeName}SchemaJson`;
22
17
  }
@@ -6,24 +6,20 @@ import { mkdirSync, writeFileSync } from "node:fs";
6
6
  import { dirname, join, relative } from "node:path";
7
7
  import { createGenerator } from "ts-json-schema-generator";
8
8
  import { cleanStaleGenerated } from "./cleanup.ts";
9
- import { discoverSchemaRoots, type SchemaRoot, type SchemaRootKind } from "./discover-schema-roots.ts";
9
+ import { discoverSchemaRoots, type SchemaRoot } from "./discover-schema-roots.ts";
10
10
  import { GENERATED_DIR, schemaExportName, schemaJsonBasename, schemaJsonImportVar } from "./names.ts";
11
11
 
12
- const KIND_ORDER: SchemaRootKind[] = ["config", "input", "output"];
13
-
14
12
  export interface RunSchemagenOptions {
15
13
  /** Project root (default: `process.cwd()`). */
16
14
  projectRoot?: string;
17
- /** Source tree directory relative to project root (default: `src`). */
15
+ /** Source tree directory relative to project root (default: `src`). Unused; kept for API compat. */
18
16
  srcDir?: string;
19
17
  /** Path to tsconfig relative to project root (default: `tsconfig.json`). */
20
18
  tsconfig?: string;
21
19
  }
22
20
 
23
21
  export interface RunSchemagenResult {
24
- configRoots: number;
25
- inputRoots: number;
26
- outputRoots: number;
22
+ schemas: number;
27
23
  }
28
24
 
29
25
  function resolveTsconfig(projectRoot: string, tsconfig: string): string {
@@ -35,6 +31,10 @@ function resolveTsconfig(projectRoot: string, tsconfig: string): string {
35
31
  }
36
32
  }
37
33
 
34
+ function generatedDirForRoot(projectRoot: string, root: SchemaRoot): string {
35
+ return join(dirname(join(projectRoot, root.path)), GENERATED_DIR);
36
+ }
37
+
38
38
  function generateJson(projectRoot: string, tsconfigPath: string, root: SchemaRoot): Record<string, unknown> {
39
39
  const typeFile = join(projectRoot, root.sourcePath);
40
40
  const generator = createGenerator({
@@ -44,27 +44,25 @@ function generateJson(projectRoot: string, tsconfigPath: string, root: SchemaRoo
44
44
  topRef: false,
45
45
  skipTypeCheck: false,
46
46
  jsDoc: "extended",
47
- additionalProperties: root.kind === "config" ? false : undefined,
47
+ additionalProperties: false,
48
48
  });
49
49
  const schema = generator.createSchema(root.typeName) as Record<string, unknown>;
50
- if (root.kind === "config") {
51
- schema.additionalProperties = false;
52
- }
50
+ schema.additionalProperties = false;
53
51
  return schema;
54
52
  }
55
53
 
56
54
  function writeGeneratedIndex(generatedDir: string, roots: SchemaRoot[]): void {
57
- const sorted = [...roots].sort((left, right) => KIND_ORDER.indexOf(left.kind) - KIND_ORDER.indexOf(right.kind));
55
+ const sorted = [...roots].sort((left, right) => left.typeName.localeCompare(right.typeName));
58
56
  const lines = ["// Auto-generated by argsbarg schemagen — do not edit by hand.", ""];
59
57
  for (const root of sorted) {
60
- lines.push(`import ${schemaJsonImportVar(root.kind)} from "./${schemaJsonBasename(root.kind)}";`);
58
+ lines.push(`import ${schemaJsonImportVar(root.typeName)} from "./${schemaJsonBasename(root.typeName)}";`);
61
59
  }
62
60
  if (sorted.length > 0) {
63
61
  lines.push("");
64
62
  }
65
63
  for (const root of sorted) {
66
64
  lines.push(
67
- `export const ${schemaExportName(root.kind)} = ${schemaJsonImportVar(root.kind)} as Record<string, unknown>;`,
65
+ `export const ${schemaExportName(root.typeName)} = ${schemaJsonImportVar(root.typeName)} as Record<string, unknown>;`,
68
66
  );
69
67
  lines.push("");
70
68
  }
@@ -78,32 +76,27 @@ export function runSchemagen(options: RunSchemagenOptions = {}): RunSchemagenRes
78
76
  const tsconfigPath = resolveTsconfig(projectRoot, options.tsconfig ?? "tsconfig.json");
79
77
 
80
78
  const roots = discoverSchemaRoots(projectRoot, srcDir);
81
- const bySchemaFile = new Map<string, SchemaRoot[]>();
79
+ const byGeneratedDir = new Map<string, SchemaRoot[]>();
82
80
 
83
81
  for (const root of roots) {
84
82
  const schema = generateJson(projectRoot, tsconfigPath, root);
85
- const generatedDir = join(dirname(join(projectRoot, root.path)), GENERATED_DIR);
83
+ const generatedDir = generatedDirForRoot(projectRoot, root);
86
84
  mkdirSync(generatedDir, { recursive: true });
87
- const jsonPath = join(generatedDir, schemaJsonBasename(root.kind));
85
+ const jsonPath = join(generatedDir, schemaJsonBasename(root.typeName));
88
86
  writeFileSync(jsonPath, `${JSON.stringify(schema, null, 2)}\n`);
89
87
  console.log(`wrote ${relative(projectRoot, jsonPath)} (${root.typeName})`);
90
88
 
91
- const list = bySchemaFile.get(root.path) ?? [];
89
+ const list = byGeneratedDir.get(generatedDir) ?? [];
92
90
  list.push(root);
93
- bySchemaFile.set(root.path, list);
91
+ byGeneratedDir.set(generatedDir, list);
94
92
  }
95
93
 
96
- for (const [relPath, fileRoots] of bySchemaFile) {
97
- const generatedDir = join(dirname(join(projectRoot, relPath)), GENERATED_DIR);
98
- writeGeneratedIndex(generatedDir, fileRoots);
94
+ for (const [generatedDir, dirRoots] of byGeneratedDir) {
95
+ writeGeneratedIndex(generatedDir, dirRoots);
99
96
  console.log(`wrote ${relative(projectRoot, join(generatedDir, "index.ts"))}`);
100
97
  }
101
98
 
102
- cleanStaleGenerated(projectRoot, srcDir, bySchemaFile);
99
+ cleanStaleGenerated(projectRoot, srcDir, byGeneratedDir);
103
100
 
104
- return {
105
- configRoots: roots.filter((r) => r.kind === "config").length,
106
- inputRoots: roots.filter((r) => r.kind === "input").length,
107
- outputRoots: roots.filter((r) => r.kind === "output").length,
108
- };
101
+ return { schemas: roots.length };
109
102
  }