@pikku/cli 0.12.91 → 0.12.93

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 (241) hide show
  1. package/LICENSE +106 -0
  2. package/console-app/assets/{index-D0HG8q0B.css → index-BYj7ZbUU.css} +1 -1
  3. package/console-app/assets/{index-CpkvmAGb.js → index-CmAplHjZ.js} +151 -149
  4. package/console-app/index.html +2 -2
  5. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  7. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  8. package/dist/.pikku/cli/pikku-cli-channel.js +16 -1
  9. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  10. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  11. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
  12. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
  13. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +15 -0
  14. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  15. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  16. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  17. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +48 -0
  18. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  19. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  20. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  21. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  22. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  23. package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
  24. package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
  25. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  26. package/dist/.pikku/function/pikku-functions-meta.gen.json +73 -40
  27. package/dist/.pikku/function/pikku-functions.gen.js +1 -3
  28. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  29. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  30. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  31. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  32. package/dist/.pikku/pikku-bootstrap-scenarios.gen.d.ts +2 -1
  33. package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +2 -1
  34. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  35. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  36. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  37. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  38. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  39. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  40. package/dist/.pikku/pikku-types.gen.js +1 -1
  41. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  42. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  43. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  44. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  45. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  46. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  47. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +3 -1
  48. package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +1 -1
  49. package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +1 -1
  50. package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +1 -1
  51. package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +1 -1
  52. package/dist/.pikku/scenarios/schemas/register.gen.d.ts +4 -0
  53. package/dist/.pikku/scenarios/schemas/register.gen.js +4 -0
  54. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  55. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  56. package/dist/.pikku/schemas/register.gen.js +13 -3
  57. package/dist/.pikku/schemas/schemas/FabricSecretsListOutput.schema.json +1 -1
  58. package/dist/.pikku/schemas/schemas/FabricSecretsRotateInput.schema.json +1 -0
  59. package/dist/.pikku/schemas/schemas/FabricSecretsRotateOutput.schema.json +1 -0
  60. package/dist/.pikku/schemas/schemas/FabricSecretsSetOutput.schema.json +1 -1
  61. package/dist/.pikku/schemas/schemas/KnowledgeIndexInput.schema.json +1 -0
  62. package/dist/.pikku/schemas/schemas/KnowledgeIndexOutput.schema.json +1 -0
  63. package/dist/.pikku/schemas/schemas/KnowledgeValidateInput.schema.json +1 -0
  64. package/dist/.pikku/schemas/schemas/KnowledgeValidateOutput.schema.json +1 -0
  65. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  66. package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +1 -1
  67. package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +1 -1
  68. package/dist/.pikku/scopes/pikku-scope-types.gen.js +1 -1
  69. package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
  70. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  71. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  72. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  73. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  74. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  75. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  76. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  77. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  78. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  79. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  80. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +2 -8
  81. package/dist/.pikku/workflow/pikku-scenario-actors.gen.d.ts +1 -1
  82. package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +1 -1
  83. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  84. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  85. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  86. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  87. package/dist/bin/pikku-bin.mjs +2 -2
  88. package/dist/src/cli.wiring.js +29 -0
  89. package/dist/src/deploy/analyzer/analyzer.js +22 -5
  90. package/dist/src/deploy/build-pipeline.js +5 -1
  91. package/dist/src/fabric/fabric-commands.d.ts +37 -9
  92. package/dist/src/fabric/fabric-commands.js +12 -0
  93. package/dist/src/fabric/functions/domains-add.function.d.ts +4 -4
  94. package/dist/src/fabric/functions/secrets-list.function.d.ts +16 -4
  95. package/dist/src/fabric/functions/secrets-list.function.js +12 -10
  96. package/dist/src/fabric/functions/secrets-rotate.function.d.ts +24 -0
  97. package/dist/src/fabric/functions/secrets-rotate.function.js +36 -0
  98. package/dist/src/fabric/functions/secrets-set.function.d.ts +8 -4
  99. package/dist/src/fabric/functions/secrets-set.function.js +18 -7
  100. package/dist/src/fabric/functions/validate.function.js +16 -7
  101. package/dist/src/fabric/lib/http.d.ts +7 -1
  102. package/dist/src/fabric/lib/sealed-box.d.ts +16 -0
  103. package/dist/src/fabric/lib/sealed-box.js +72 -0
  104. package/dist/src/functions/commands/dev.js +9 -0
  105. package/dist/src/functions/commands/knowledge-index.d.ts +30 -0
  106. package/dist/src/functions/commands/knowledge-index.js +11 -0
  107. package/dist/src/functions/commands/knowledge-validate.d.ts +33 -0
  108. package/dist/src/functions/commands/knowledge-validate.js +11 -0
  109. package/dist/src/functions/commands/new-addon.d.ts +18 -0
  110. package/dist/src/functions/commands/new-addon.js +40 -24
  111. package/dist/src/functions/commands/pikku-command-bootstrap.js +1 -0
  112. package/dist/src/functions/commands/scenario-browser.d.ts +17 -2
  113. package/dist/src/functions/commands/scenario-browser.js +16 -8
  114. package/dist/src/functions/commands/scenario-environment.d.ts +32 -0
  115. package/dist/src/functions/commands/scenario-environment.js +61 -0
  116. package/dist/src/functions/commands/scenario.d.ts +6 -0
  117. package/dist/src/functions/commands/scenario.js +14 -9
  118. package/dist/src/functions/commands/skills.js +27 -61
  119. package/dist/src/functions/db/db-codegen.d.ts +6 -0
  120. package/dist/src/functions/db/db-codegen.js +9 -0
  121. package/dist/src/functions/db/db-migrator.js +14 -0
  122. package/dist/src/functions/db/local-db.d.ts +8 -0
  123. package/dist/src/functions/db/local-db.js +24 -2
  124. package/dist/src/functions/db/migration-identifiers.d.ts +58 -0
  125. package/dist/src/functions/db/migration-identifiers.js +262 -0
  126. package/dist/src/functions/db/migration-provenance.d.ts +35 -0
  127. package/dist/src/functions/db/migration-provenance.js +80 -0
  128. package/dist/src/functions/db/schema-sql.d.ts +43 -0
  129. package/dist/src/functions/db/schema-sql.js +135 -0
  130. package/dist/src/functions/db/sqlite/sqlite-kysely.js +22 -2
  131. package/dist/src/functions/knowledge/render.d.ts +3 -0
  132. package/dist/src/functions/knowledge/render.js +66 -0
  133. package/dist/src/functions/knowledge/schemas.d.ts +51 -0
  134. package/dist/src/functions/knowledge/schemas.js +25 -0
  135. package/dist/src/functions/wirings/credentials/serialize-credentials-types.js +8 -1
  136. package/dist/src/functions/wirings/functions/schemas.js +13 -1
  137. package/dist/src/functions/wirings/rpc/pikku-command-rpc.js +6 -1
  138. package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.d.ts +10 -0
  139. package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.js +102 -0
  140. package/dist/src/functions/wirings/scenarios/scenario-partition.d.ts +13 -0
  141. package/dist/src/functions/wirings/scenarios/scenario-partition.js +15 -2
  142. package/dist/src/functions/wirings/scenarios/scenario-schema-partition.d.ts +33 -0
  143. package/dist/src/functions/wirings/scenarios/scenario-schema-partition.js +49 -0
  144. package/dist/src/functions/wirings/secrets/serialize-secrets-types.js +10 -3
  145. package/dist/src/functions/wirings/variables/serialize-variables-types.js +8 -1
  146. package/dist/src/functions/workflows/all.workflow.js +2 -7
  147. package/dist/src/utils/file-writer.js +14 -2
  148. package/dist/src/utils/pikku-cli-config.d.ts +14 -0
  149. package/dist/src/utils/pikku-cli-config.js +34 -0
  150. package/dist/src/utils/remove-legacy-scaffold-file.d.ts +14 -0
  151. package/dist/src/utils/remove-legacy-scaffold-file.js +23 -0
  152. package/dist/src/utils/serialize-schemas.d.ts +3 -1
  153. package/dist/src/utils/serialize-schemas.js +39 -4
  154. package/dist/tsconfig.tsbuildinfo +1 -1
  155. package/package.json +10 -9
  156. package/dist/.pikku/schemas/schemas/PikkuScenarioFunctionsOutput.schema.json +0 -1
  157. package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.d.ts +0 -1
  158. package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.js +0 -31
  159. package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.d.ts +0 -10
  160. package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.js +0 -104
  161. package/skills/pikku-addon/SKILL.md +0 -243
  162. package/skills/pikku-addon/references/addon-package-manifest.md +0 -63
  163. package/skills/pikku-ai-agent/SKILL.md +0 -231
  164. package/skills/pikku-ai-vercel/SKILL.md +0 -82
  165. package/skills/pikku-ai-voice/SKILL.md +0 -88
  166. package/skills/pikku-audit/SKILL.md +0 -175
  167. package/skills/pikku-aws/SKILL.md +0 -111
  168. package/skills/pikku-backblaze/SKILL.md +0 -71
  169. package/skills/pikku-better-auth/SKILL.md +0 -298
  170. package/skills/pikku-cli/SKILL.md +0 -198
  171. package/skills/pikku-cli/references/complete-example.md +0 -82
  172. package/skills/pikku-concepts/SKILL.md +0 -250
  173. package/skills/pikku-concepts/references/concept-mapping.md +0 -556
  174. package/skills/pikku-concepts/references/packages.md +0 -29
  175. package/skills/pikku-config/SKILL.md +0 -212
  176. package/skills/pikku-cron/SKILL.md +0 -214
  177. package/skills/pikku-deploy-azure/SKILL.md +0 -71
  178. package/skills/pikku-deploy-cloudflare/SKILL.md +0 -82
  179. package/skills/pikku-deploy-express/SKILL.md +0 -86
  180. package/skills/pikku-deploy-fastify/SKILL.md +0 -73
  181. package/skills/pikku-deploy-lambda/SKILL.md +0 -110
  182. package/skills/pikku-deploy-nextjs/SKILL.md +0 -78
  183. package/skills/pikku-deploy-uws/SKILL.md +0 -88
  184. package/skills/pikku-deps/SKILL.md +0 -98
  185. package/skills/pikku-emails/SKILL.md +0 -163
  186. package/skills/pikku-fabric/SKILL.md +0 -318
  187. package/skills/pikku-fabric-debug/SKILL.md +0 -112
  188. package/skills/pikku-feature/SKILL.md +0 -258
  189. package/skills/pikku-gateway-slack/SKILL.md +0 -115
  190. package/skills/pikku-http/SKILL.md +0 -220
  191. package/skills/pikku-http/references/http-options.md +0 -55
  192. package/skills/pikku-i18n/SKILL.md +0 -137
  193. package/skills/pikku-info/SKILL.md +0 -100
  194. package/skills/pikku-jose/SKILL.md +0 -105
  195. package/skills/pikku-kysely/SKILL.md +0 -219
  196. package/skills/pikku-machine-auth/SKILL.md +0 -183
  197. package/skills/pikku-mcp/SKILL.md +0 -241
  198. package/skills/pikku-middleware/SKILL.md +0 -231
  199. package/skills/pikku-middleware/references/middleware-patterns.md +0 -61
  200. package/skills/pikku-mongodb/SKILL.md +0 -105
  201. package/skills/pikku-n8n-import/SKILL.md +0 -109
  202. package/skills/pikku-n8n-import/SPEC.md +0 -84
  203. package/skills/pikku-n8n-import/references/addon-mapping.md +0 -121
  204. package/skills/pikku-n8n-import/references/code-translation.md +0 -121
  205. package/skills/pikku-n8n-import/references/loops-and-control.md +0 -87
  206. package/skills/pikku-paraglide/SKILL.md +0 -117
  207. package/skills/pikku-permissions/SKILL.md +0 -192
  208. package/skills/pikku-pino/SKILL.md +0 -79
  209. package/skills/pikku-product-second-opinion/README.md +0 -43
  210. package/skills/pikku-product-second-opinion/SKILL.md +0 -158
  211. package/skills/pikku-product-second-opinion/example/sample-report.md +0 -90
  212. package/skills/pikku-product-second-opinion/references/report-template.md +0 -73
  213. package/skills/pikku-queue/SKILL.md +0 -240
  214. package/skills/pikku-react/SKILL.md +0 -212
  215. package/skills/pikku-react-query/SKILL.md +0 -242
  216. package/skills/pikku-realtime/SKILL.md +0 -236
  217. package/skills/pikku-realtime/references/other-routes.md +0 -23
  218. package/skills/pikku-redis/SKILL.md +0 -90
  219. package/skills/pikku-rpc/SKILL.md +0 -171
  220. package/skills/pikku-rtl/SKILL.md +0 -219
  221. package/skills/pikku-scenario/SKILL.md +0 -382
  222. package/skills/pikku-schedule/SKILL.md +0 -57
  223. package/skills/pikku-schema-ajv/SKILL.md +0 -62
  224. package/skills/pikku-schema-cfworker/SKILL.md +0 -63
  225. package/skills/pikku-security/SKILL.md +0 -108
  226. package/skills/pikku-services/SKILL.md +0 -248
  227. package/skills/pikku-services/references/audit-wire-service.md +0 -34
  228. package/skills/pikku-software-archaeology/README.md +0 -70
  229. package/skills/pikku-software-archaeology/SKILL.md +0 -186
  230. package/skills/pikku-software-archaeology/references/blueprint.schema.json +0 -625
  231. package/skills/pikku-software-archaeology/references/pikku-mapping.md +0 -49
  232. package/skills/pikku-software-archaeology/scripts/validate.mjs +0 -173
  233. package/skills/pikku-tag-middleware/SKILL.md +0 -13
  234. package/skills/pikku-template-clone/SKILL.md +0 -40
  235. package/skills/pikku-trigger/SKILL.md +0 -181
  236. package/skills/pikku-versioning/SKILL.md +0 -173
  237. package/skills/pikku-websocket/SKILL.md +0 -243
  238. package/skills/pikku-workflow/SKILL.md +0 -172
  239. package/skills/pikku-workflow/references/workflow-reference.md +0 -63
  240. package/skills/pikku-workflows-client/SKILL.md +0 -150
  241. package/skills/pikku-ws/SKILL.md +0 -47
@@ -1,49 +1,25 @@
1
- import { readdir, readFile, mkdir, writeFile } from 'fs/promises';
1
+ import { readdir, mkdir, writeFile } from 'fs/promises';
2
2
  import { existsSync } from 'fs';
3
- import { join, dirname, sep } from 'path';
4
- import { fileURLToPath } from 'url';
3
+ import { dirname, join, sep } from 'path';
4
+ import { listSkillFiles, listSkillNames, readSkillFile } from '@pikku/skills';
5
5
  import { pikkuSessionlessFunc } from '#pikku';
6
6
  /**
7
- * Walk up from this compiled file to find the package root (the directory
8
- * containing the @pikku/cli package.json), then resolve `<root>/skills/`.
9
- * Works whether the file is loaded from `dist/...` (published package) or
10
- * `src/...` (local dev via tsx/vitest).
7
+ * Write one skill's files out to `dest`, reading through @pikku/skills rather
8
+ * than off a directory we resolved ourselves. That package reads from its own
9
+ * `skills/` folder when there is one and from its embedded manifest otherwise,
10
+ * which is what makes this work inside the `bun --compile` binaries they carry
11
+ * no filesystem copy, so the old "walk up to the @pikku/cli package root"
12
+ * lookup threw on every Homebrew install.
11
13
  */
12
- async function findSkillsDir() {
13
- let dir = dirname(fileURLToPath(import.meta.url));
14
- for (let i = 0; i < 8; i++) {
15
- const pkgPath = join(dir, 'package.json');
16
- if (existsSync(pkgPath)) {
17
- try {
18
- const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'));
19
- if (pkg.name === '@pikku/cli') {
20
- const skills = join(dir, 'skills');
21
- if (existsSync(skills))
22
- return skills;
23
- }
24
- }
25
- catch { }
26
- }
27
- const parent = dirname(dir);
28
- if (parent === dir)
29
- break;
30
- dir = parent;
31
- }
32
- throw new Error('Could not locate bundled skills directory. Was @pikku/cli installed correctly?');
33
- }
34
- async function copyDir(src, dest) {
35
- await mkdir(dest, { recursive: true });
36
- const entries = await readdir(src, { withFileTypes: true });
37
- for (const entry of entries) {
38
- const s = join(src, entry.name);
39
- const d = join(dest, entry.name);
40
- if (entry.isDirectory()) {
41
- await copyDir(s, d);
42
- }
43
- else if (entry.isFile()) {
44
- const content = await readFile(s);
45
- await writeFile(d, content);
46
- }
14
+ async function writeSkill(name, dest) {
15
+ for (const path of await listSkillFiles(name)) {
16
+ const contents = await readSkillFile(path);
17
+ if (contents === null)
18
+ continue;
19
+ // Paths are relative to the skills root, so drop the leading skill name.
20
+ const target = join(dest, ...path.split('/').slice(1));
21
+ await mkdir(dirname(target), { recursive: true });
22
+ await writeFile(target, contents, 'utf-8');
47
23
  }
48
24
  }
49
25
  /**
@@ -82,17 +58,16 @@ function parseInstallGroups(frontmatter) {
82
58
  }
83
59
  return [];
84
60
  }
85
- async function readSkillInstallGroups(skillsDir, skillName) {
86
- const skillPath = join(skillsDir, skillName, 'SKILL.md');
87
- if (!existsSync(skillPath))
61
+ async function readSkillInstallGroups(skillName) {
62
+ const content = await readSkillFile(`${skillName}/SKILL.md`);
63
+ if (content === null)
88
64
  return [];
89
- const content = await readFile(skillPath, 'utf-8');
90
65
  const match = content.match(/^---\n([\s\S]*?)\n---/);
91
66
  if (!match)
92
67
  return [];
93
68
  return parseInstallGroups(match[1]);
94
69
  }
95
- async function getWantedSkills(skillsDir, allEntries, { only, core = false, fabric = false, }) {
70
+ async function getWantedSkills(allEntries, { only, core = false, fabric = false, }) {
96
71
  const wanted = new Set();
97
72
  if (only) {
98
73
  for (const name of only
@@ -108,7 +83,7 @@ async function getWantedSkills(skillsDir, allEntries, { only, core = false, fabr
108
83
  ];
109
84
  if (requestedGroups.length > 0) {
110
85
  for (const name of allEntries) {
111
- const groups = await readSkillInstallGroups(skillsDir, name);
86
+ const groups = await readSkillInstallGroups(name);
112
87
  if (requestedGroups.some((group) => groups.includes(group))) {
113
88
  wanted.add(name);
114
89
  }
@@ -118,11 +93,7 @@ async function getWantedSkills(skillsDir, allEntries, { only, core = false, fabr
118
93
  }
119
94
  export const pikkuSkillsList = pikkuSessionlessFunc({
120
95
  func: async ({ logger }) => {
121
- const skillsDir = await findSkillsDir();
122
- const entries = (await readdir(skillsDir, { withFileTypes: true }))
123
- .filter((e) => e.isDirectory())
124
- .map((e) => e.name)
125
- .sort();
96
+ const entries = await listSkillNames();
126
97
  const installed = new Set();
127
98
  for (const relative of Object.values(AGENT_SKILL_DIRS)) {
128
99
  const dir = join(process.cwd(), relative);
@@ -154,12 +125,8 @@ export const pikkuSkillsInstall = pikkuSessionlessFunc({
154
125
  process.exitCode = 1;
155
126
  return;
156
127
  }
157
- const skillsDir = await findSkillsDir();
158
- const allEntries = (await readdir(skillsDir, { withFileTypes: true }))
159
- .filter((e) => e.isDirectory())
160
- .map((e) => e.name)
161
- .sort();
162
- const wanted = await getWantedSkills(skillsDir, allEntries, {
128
+ const allEntries = await listSkillNames();
129
+ const wanted = await getWantedSkills(allEntries, {
163
130
  only,
164
131
  core,
165
132
  fabric,
@@ -175,13 +142,12 @@ export const pikkuSkillsInstall = pikkuSessionlessFunc({
175
142
  let installed = 0;
176
143
  let skipped = 0;
177
144
  for (const name of wanted) {
178
- const src = join(skillsDir, name);
179
145
  const dest = join(installRoot, name);
180
146
  if (existsSync(dest) && !update) {
181
147
  skipped++;
182
148
  continue;
183
149
  }
184
- await copyDir(src, dest);
150
+ await writeSkill(name, dest);
185
151
  installed++;
186
152
  }
187
153
  const destLabel = `${AGENT_SKILL_DIRS[agent].split(sep).join('/')}/`;
@@ -19,6 +19,12 @@ export interface CodegenOptions {
19
19
  rootDir?: string;
20
20
  /** DB dialect — drives real-type-aware date typing. Defaults to 'sqlite'. */
21
21
  dialect?: Dialect;
22
+ /**
23
+ * Where the project's migrations live. Only used to attribute each table in
24
+ * `schemaJsonFile` to whoever declared it; omitting it makes every table read
25
+ * as app-owned, which is what a project with no generated migrations is.
26
+ */
27
+ migrationsDir?: string;
22
28
  }
23
29
  export interface CodegenResult {
24
30
  outFile: string;
@@ -2,6 +2,7 @@ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
2
  import { dirname } from 'node:path';
3
3
  import { loadAnnotations, nameSuggestsKind, } from './annotation-parser.js';
4
4
  import { ZOD_FORMATS } from './zod-codegen.js';
5
+ import { bareUnquotedTableName, readMigrationProvenance, } from './migration-provenance.js';
5
6
  import { ErrorCode } from '@pikku/inspector';
6
7
  /**
7
8
  * The column kind implied by the *real* DB type, dialect-aware. Postgres has
@@ -504,8 +505,16 @@ export async function generateSchemaTypes(introspector, options) {
504
505
  let schemaJsonBody = null;
505
506
  if (options.schemaJsonFile) {
506
507
  const fkMap = await introspector.getAllForeignKeys();
508
+ // Attribution comes from the migrations rather than from `schemaSources()`:
509
+ // the JSON is written by `db migrate`, which has no reason to load the
510
+ // project's Better Auth config, and the generated migrations already record
511
+ // both the source and its prose. See `readMigrationProvenance`.
512
+ const provenance = options.migrationsDir
513
+ ? readMigrationProvenance(options.migrationsDir)
514
+ : new Map();
507
515
  const jsonTables = tables.map((t) => ({
508
516
  name: t.name,
517
+ ...(provenance.get(bareUnquotedTableName(t.name)) ?? { source: 'app' }),
509
518
  columns: t.columns.map((c) => {
510
519
  const fk = fkMap.get(t.name)?.find((f) => f.column === c.name);
511
520
  return {
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
3
3
  import { join } from 'node:path';
4
+ import { assertSnakeCaseIdentifiers } from './migration-identifiers.js';
4
5
  /**
5
6
  * The migrator's own bookkeeping table, which belongs to no dialect and to no
6
7
  * project's schema.
@@ -75,7 +76,20 @@ function assertNoDrift(applied, migrationsDir) {
75
76
  }
76
77
  }
77
78
  }
79
+ /**
80
+ * Every migration on disk, read once.
81
+ *
82
+ * The identifier check reads all of them and not just the pending ones, so that
83
+ * whether a migration is rejected depends on the files alone. A camelCase
84
+ * column that had already been applied somewhere would otherwise pass on that
85
+ * machine and fail on a fresh checkout, which is the opposite of deterministic.
86
+ */
87
+ const readMigrations = (migrationsDir) => migrationFiles(migrationsDir).map((name) => ({
88
+ name,
89
+ sql: readFileSync(join(migrationsDir, name), 'utf8'),
90
+ }));
78
91
  export async function migrate(executor, migrationsDir) {
92
+ assertSnakeCaseIdentifiers(readMigrations(migrationsDir));
79
93
  await executor.ensureTrackingTable();
80
94
  const applied = await executor.getApplied();
81
95
  assertNoDrift(applied, migrationsDir);
@@ -307,6 +307,14 @@ export interface GenerateResult {
307
307
  * Partially covered writes the delta, because re-emitting the whole schema
308
308
  * would fail on the tables that already exist.
309
309
  *
310
+ * The delta is itself two different things, and only one of them is a diff. A
311
+ * table the migrations already have but a column short is a genuine alteration,
312
+ * so it becomes `ALTER TABLE … ADD COLUMN`. A table they do not have at all —
313
+ * what enabling a Better Auth plugin or upgrading an addon produces — has
314
+ * nothing to diff against, so it is lifted verbatim out of the source's own SQL
315
+ * for the same reason the first-time case is: the column map it would otherwise
316
+ * be rendered from knows nothing about keys, constraints or indexes.
317
+ *
310
318
  * Migrations are written one file per source, numbered in dependency order, so
311
319
  * a project can review and apply them independently.
312
320
  */
@@ -10,6 +10,7 @@ import { loadAuthOptions, getAuthMigrations } from './better-auth-schema.js';
10
10
  import { generateSchemaTypes } from './db-codegen.js';
11
11
  import { generateZodTypes } from './zod-codegen.js';
12
12
  import { createCoercionPlugin } from './coercion-plugin.js';
13
+ import { tableCreationSql } from './schema-sql.js';
13
14
  import { SqliteMigrationExecutor } from './sqlite/sqlite-migrator.js';
14
15
  import { SqliteIntrospector } from './sqlite/sqlite-introspector.js';
15
16
  import { createSqliteKysely } from './sqlite/sqlite-kysely.js';
@@ -190,6 +191,7 @@ export async function migrateAndCodegen(resolved, options = {}) {
190
191
  camelCase: resolved.camelCase,
191
192
  rootDir: resolved.rootDir,
192
193
  dialect: 'sqlite',
194
+ migrationsDir: resolved.migrationsDir,
193
195
  });
194
196
  }
195
197
  finally {
@@ -216,6 +218,7 @@ export async function migrateAndCodegen(resolved, options = {}) {
216
218
  camelCase: resolved.camelCase,
217
219
  rootDir: resolved.rootDir,
218
220
  dialect: 'postgres',
221
+ migrationsDir: resolved.migrationsDir,
219
222
  });
220
223
  }
221
224
  finally {
@@ -983,6 +986,14 @@ function addColumnStatements(table, columns) {
983
986
  * Partially covered writes the delta, because re-emitting the whole schema
984
987
  * would fail on the tables that already exist.
985
988
  *
989
+ * The delta is itself two different things, and only one of them is a diff. A
990
+ * table the migrations already have but a column short is a genuine alteration,
991
+ * so it becomes `ALTER TABLE … ADD COLUMN`. A table they do not have at all —
992
+ * what enabling a Better Auth plugin or upgrading an addon produces — has
993
+ * nothing to diff against, so it is lifted verbatim out of the source's own SQL
994
+ * for the same reason the first-time case is: the column map it would otherwise
995
+ * be rendered from knows nothing about keys, constraints or indexes.
996
+ *
986
997
  * Migrations are written one file per source, numbered in dependency order, so
987
998
  * a project can review and apply them independently.
988
999
  */
@@ -1009,9 +1020,20 @@ export async function generateMigrations(resolved, rootDir, srcDirectories, logg
1009
1020
  else {
1010
1021
  const statements = [];
1011
1022
  for (const table of missingTables) {
1023
+ // A wholly new table is the first-time case in miniature: nothing to
1024
+ // diff against, and the source's own SQL already says exactly how to
1025
+ // build it. Rendering the column map instead would drop the primary
1026
+ // key, the foreign keys and the indexes — a table that applies cleanly
1027
+ // and is permanently wrong.
1028
+ const own = tableCreationSql(source.desired.sql, table);
1029
+ if (own.length > 0) {
1030
+ statements.push(own.join('\n\n'));
1031
+ continue;
1032
+ }
1012
1033
  const columns = source.desired.tables.get(table);
1013
- statements.push(`-- REVIEW: ${table} is new. Copy its CREATE TABLE from the source's own SQL —\n` +
1014
- `-- the column list below carries no indexes, constraints or foreign keys.\n` +
1034
+ statements.push(`-- REVIEW: ${table} is new, and its CREATE TABLE could not be found in the\n` +
1035
+ `-- source's own SQL. The column list below carries no indexes, constraints\n` +
1036
+ `-- or foreign keys — copy the real statement over it before applying.\n` +
1015
1037
  `CREATE TABLE ${table} (\n` +
1016
1038
  [...(columns?.values() ?? [])]
1017
1039
  .map((c) => ` ${c.name} ${c.type}${c.notNull ? ' NOT NULL' : ''}`)
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Catching camelCase columns at migrate time, before they can half-work.
3
+ *
4
+ * Pikku's Kysely runs with `CamelCasePlugin`, so `priceCents` in TypeScript is
5
+ * `price_cents` in SQL and nothing else. A migration that declares the column
6
+ * *as* `priceCents` is not merely unconventional — it is broken in the one way
7
+ * that hides itself: `.selectAll()` compiles to `SELECT *` and never names an
8
+ * identifier, so the table reads back perfectly, while the first query that
9
+ * names the column (`.select(['animal.priceCents'])`) asks for `price_cents`
10
+ * and gets `no such column`. The usual conclusion is that the plugin is broken,
11
+ * and the usual response is a raw `sql` template or a retreat to `.selectAll()`
12
+ * — both of which keep the real cause alive.
13
+ *
14
+ * There is no exception to escape. Even the Better Auth schema `db generate`
15
+ * writes is snake_case (`email_verified`, `user_id`), because Better Auth is
16
+ * handed the app's own Kysely and its camelCase field names compile the same
17
+ * way everyone else's do.
18
+ */
19
+ /** A camelCase identifier a migration declares, and what it should have said. */
20
+ export interface CamelCaseIdentifier {
21
+ file: string;
22
+ table: string;
23
+ /** `null` when the table name itself is the offender. */
24
+ column: string | null;
25
+ suggestion: string;
26
+ }
27
+ export declare class CamelCaseIdentifierError extends Error {
28
+ readonly offenders: CamelCaseIdentifier[];
29
+ constructor(offenders: CamelCaseIdentifier[]);
30
+ }
31
+ /**
32
+ * Strip comments so a word in prose can never be read as an identifier.
33
+ *
34
+ * `splitStatements` already skips over comments when it looks for a boundary,
35
+ * but it hands back the statement with them still in it, and `-- the userId
36
+ * column` would otherwise be flagged. Quoting rules are honoured for the same
37
+ * reason they are there: `'a -- b'` is a string, not a comment.
38
+ */
39
+ export declare function stripSqlComments(sql: string): string;
40
+ /**
41
+ * Every camelCase identifier one migration file declares.
42
+ *
43
+ * Only declarations are read — a `CREATE TABLE` column list and an `ALTER TABLE
44
+ * … ADD COLUMN`. Everywhere else an identifier merely refers to something that
45
+ * was declared somewhere, and reporting those would name the same mistake once
46
+ * per reference while adding nothing to the fix.
47
+ */
48
+ export declare function findCamelCaseIdentifiers(file: string, sql: string): CamelCaseIdentifier[];
49
+ /**
50
+ * Bail unless every migration on disk is snake_case throughout.
51
+ *
52
+ * Reports all of them at once: a camelCase column is rarely alone, and a
53
+ * one-at-a-time failure turns a single edit into one migrate run per column.
54
+ */
55
+ export declare function assertSnakeCaseIdentifiers(migrations: Array<{
56
+ name: string;
57
+ sql: string;
58
+ }>): void;
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Catching camelCase columns at migrate time, before they can half-work.
3
+ *
4
+ * Pikku's Kysely runs with `CamelCasePlugin`, so `priceCents` in TypeScript is
5
+ * `price_cents` in SQL and nothing else. A migration that declares the column
6
+ * *as* `priceCents` is not merely unconventional — it is broken in the one way
7
+ * that hides itself: `.selectAll()` compiles to `SELECT *` and never names an
8
+ * identifier, so the table reads back perfectly, while the first query that
9
+ * names the column (`.select(['animal.priceCents'])`) asks for `price_cents`
10
+ * and gets `no such column`. The usual conclusion is that the plugin is broken,
11
+ * and the usual response is a raw `sql` template or a retreat to `.selectAll()`
12
+ * — both of which keep the real cause alive.
13
+ *
14
+ * There is no exception to escape. Even the Better Auth schema `db generate`
15
+ * writes is snake_case (`email_verified`, `user_id`), because Better Auth is
16
+ * handed the app's own Kysely and its camelCase field names compile the same
17
+ * way everyone else's do.
18
+ */
19
+ import { splitStatements } from './schema-sql.js';
20
+ export class CamelCaseIdentifierError extends Error {
21
+ offenders;
22
+ constructor(offenders) {
23
+ const lines = offenders.map((o) => ` ${o.file} ${o.column === null ? o.table : `${o.table}.${o.column}`} → ${o.suggestion}`);
24
+ super(`[PKU-DB-CAMEL] Migrations declare camelCase identifiers.\n\n` +
25
+ `Pikku's Kysely runs with CamelCasePlugin, which maps camelCase in TypeScript\n` +
26
+ `to snake_case in SQL. A camelCase column half-works: \`.selectAll()\` emits\n` +
27
+ `\`SELECT *\` so the table reads fine, but naming the column compiles it to\n` +
28
+ `snake_case and the database answers \`no such column\`.\n\n` +
29
+ `${lines.join('\n')}\n\n` +
30
+ `Fix the column definition in the migration file itself and replay your dev\n` +
31
+ `database from scratch. Do not write a RENAME COLUMN migration — that leaves\n` +
32
+ `the banned identifier in a SQL file forever.`);
33
+ this.offenders = offenders;
34
+ this.name = 'CamelCaseIdentifierError';
35
+ }
36
+ }
37
+ /**
38
+ * Strip comments so a word in prose can never be read as an identifier.
39
+ *
40
+ * `splitStatements` already skips over comments when it looks for a boundary,
41
+ * but it hands back the statement with them still in it, and `-- the userId
42
+ * column` would otherwise be flagged. Quoting rules are honoured for the same
43
+ * reason they are there: `'a -- b'` is a string, not a comment.
44
+ */
45
+ export function stripSqlComments(sql) {
46
+ let out = '';
47
+ let i = 0;
48
+ while (i < sql.length) {
49
+ const ch = sql[i];
50
+ if (ch === "'" || ch === '"' || ch === '`') {
51
+ const start = i;
52
+ i++;
53
+ while (i < sql.length) {
54
+ if (sql[i] === '\\' && ch === "'") {
55
+ i += 2;
56
+ continue;
57
+ }
58
+ if (sql[i] === ch) {
59
+ if (sql[i + 1] === ch) {
60
+ i += 2;
61
+ continue;
62
+ }
63
+ i++;
64
+ break;
65
+ }
66
+ i++;
67
+ }
68
+ out += sql.slice(start, i);
69
+ continue;
70
+ }
71
+ if (ch === '-' && sql[i + 1] === '-') {
72
+ const end = sql.indexOf('\n', i);
73
+ i = end === -1 ? sql.length : end;
74
+ continue;
75
+ }
76
+ if (ch === '/' && sql[i + 1] === '*') {
77
+ const end = sql.indexOf('*/', i + 2);
78
+ i = end === -1 ? sql.length : end + 2;
79
+ // A block comment can span lines, and removing it outright would join two
80
+ // statements onto one line. A space keeps the tokens either side apart.
81
+ out += ' ';
82
+ continue;
83
+ }
84
+ out += ch;
85
+ i++;
86
+ }
87
+ return out;
88
+ }
89
+ const IDENTIFIER = String.raw `"[^"]*"|\`[^\`]*\`|\[[^\]]*\]|[A-Za-z_][A-Za-z_0-9$]*`;
90
+ const CREATE_TABLE = new RegExp(String.raw `^CREATE\s+(?:TEMP(?:ORARY)?\s+|UNLOGGED\s+)*TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(\S+?)\s*\(`, 'i');
91
+ const ALTER_TABLE = new RegExp(String.raw `^ALTER\s+TABLE\s+(?:IF\s+EXISTS\s+)?(?:ONLY\s+)?(\S+)`, 'i');
92
+ const ADD_COLUMN = new RegExp(String.raw `^\s*ADD\s+(?:COLUMN\s+)?(?:IF\s+NOT\s+EXISTS\s+)?(${IDENTIFIER})`, 'i');
93
+ const COLUMN_NAME = new RegExp(String.raw `^\s*(${IDENTIFIER})`);
94
+ /**
95
+ * The words that open a table-level constraint rather than a column.
96
+ *
97
+ * Only consulted for an unquoted first token: `"check"` in quotes is a column
98
+ * named check, however unwise, and skipping it would let `"checkedAt"`'s
99
+ * neighbours hide behind it.
100
+ */
101
+ const CONSTRAINT_KEYWORDS = new Set([
102
+ 'constraint',
103
+ 'primary',
104
+ 'foreign',
105
+ 'unique',
106
+ 'check',
107
+ 'exclude',
108
+ 'index',
109
+ 'key',
110
+ 'like',
111
+ 'period',
112
+ 'fulltext',
113
+ 'spatial',
114
+ ]);
115
+ /** Drop the quoting a dialect happens to use, leaving the identifier itself. */
116
+ const unquote = (name) => /^["`[]/.test(name) ? name.slice(1, -1) : name;
117
+ const isCamelCase = (name) => /[a-z][A-Z]/.test(name);
118
+ const toSnakeCase = (name) => name.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
119
+ /**
120
+ * Walk `sql` from `from` and return the offsets just inside and just outside
121
+ * the parenthesised group that starts there, or `null` if it never closes.
122
+ *
123
+ * Quote-aware, because a `)` inside `DEFAULT ')'` closes nothing.
124
+ */
125
+ function parenSpan(sql, from) {
126
+ let depth = 0;
127
+ let i = from;
128
+ while (i < sql.length) {
129
+ const ch = sql[i];
130
+ if (ch === "'" || ch === '"' || ch === '`') {
131
+ i++;
132
+ while (i < sql.length && sql[i] !== ch)
133
+ i++;
134
+ i++;
135
+ continue;
136
+ }
137
+ if (ch === '(') {
138
+ depth++;
139
+ if (depth === 1)
140
+ from = i + 1;
141
+ }
142
+ else if (ch === ')') {
143
+ depth--;
144
+ if (depth === 0)
145
+ return { start: from, end: i };
146
+ }
147
+ i++;
148
+ }
149
+ return null;
150
+ }
151
+ /**
152
+ * Split a definition list on the commas that separate its items.
153
+ *
154
+ * The commas inside `NUMERIC(10,2)`, a `CHECK (x IN ('a','b'))` or a compound
155
+ * `PRIMARY KEY (a, b)` belong to those and not to the list, so only depth zero
156
+ * counts.
157
+ */
158
+ function splitTopLevel(list) {
159
+ const items = [];
160
+ let depth = 0;
161
+ let start = 0;
162
+ let i = 0;
163
+ while (i < list.length) {
164
+ const ch = list[i];
165
+ if (ch === "'" || ch === '"' || ch === '`') {
166
+ i++;
167
+ while (i < list.length && list[i] !== ch)
168
+ i++;
169
+ i++;
170
+ continue;
171
+ }
172
+ if (ch === '(')
173
+ depth++;
174
+ else if (ch === ')')
175
+ depth--;
176
+ else if (ch === ',' && depth === 0) {
177
+ items.push(list.slice(start, i));
178
+ start = i + 1;
179
+ }
180
+ i++;
181
+ }
182
+ items.push(list.slice(start));
183
+ return items.filter((item) => item.trim().length > 0);
184
+ }
185
+ /**
186
+ * Every camelCase identifier one migration file declares.
187
+ *
188
+ * Only declarations are read — a `CREATE TABLE` column list and an `ALTER TABLE
189
+ * … ADD COLUMN`. Everywhere else an identifier merely refers to something that
190
+ * was declared somewhere, and reporting those would name the same mistake once
191
+ * per reference while adding nothing to the fix.
192
+ */
193
+ export function findCamelCaseIdentifiers(file, sql) {
194
+ const offenders = [];
195
+ for (const statement of splitStatements(stripSqlComments(sql))) {
196
+ const create = CREATE_TABLE.exec(statement);
197
+ if (create) {
198
+ const table = unquote(create[1].split('.').pop());
199
+ if (isCamelCase(table)) {
200
+ offenders.push({
201
+ file,
202
+ table,
203
+ column: null,
204
+ suggestion: toSnakeCase(table),
205
+ });
206
+ }
207
+ const span = parenSpan(statement, create.index + create[0].length - 1);
208
+ if (!span)
209
+ continue;
210
+ for (const item of splitTopLevel(statement.slice(span.start, span.end))) {
211
+ const name = COLUMN_NAME.exec(item)?.[1];
212
+ if (!name)
213
+ continue;
214
+ if (CONSTRAINT_KEYWORDS.has(name.toLowerCase()))
215
+ continue;
216
+ const column = unquote(name);
217
+ if (isCamelCase(column)) {
218
+ offenders.push({
219
+ file,
220
+ table,
221
+ column,
222
+ suggestion: toSnakeCase(column),
223
+ });
224
+ }
225
+ }
226
+ continue;
227
+ }
228
+ const alter = ALTER_TABLE.exec(statement);
229
+ if (!alter)
230
+ continue;
231
+ const table = unquote(alter[1].split('.').pop());
232
+ // Postgres lets one ALTER carry several actions, and only the ADDs declare.
233
+ for (const action of splitTopLevel(statement.slice(alter[0].length).replace(/;\s*$/, ''))) {
234
+ const name = ADD_COLUMN.exec(action)?.[1];
235
+ if (!name)
236
+ continue;
237
+ if (CONSTRAINT_KEYWORDS.has(name.toLowerCase()))
238
+ continue;
239
+ const column = unquote(name);
240
+ if (isCamelCase(column)) {
241
+ offenders.push({
242
+ file,
243
+ table,
244
+ column,
245
+ suggestion: toSnakeCase(column),
246
+ });
247
+ }
248
+ }
249
+ }
250
+ return offenders;
251
+ }
252
+ /**
253
+ * Bail unless every migration on disk is snake_case throughout.
254
+ *
255
+ * Reports all of them at once: a camelCase column is rarely alone, and a
256
+ * one-at-a-time failure turns a single edit into one migrate run per column.
257
+ */
258
+ export function assertSnakeCaseIdentifiers(migrations) {
259
+ const offenders = migrations.flatMap(({ name, sql }) => findCamelCaseIdentifiers(name, sql));
260
+ if (offenders.length > 0)
261
+ throw new CamelCaseIdentifierError(offenders);
262
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Who declared a table, and where the declaration came from.
3
+ *
4
+ * `source` is the stable identifier a consumer filters on — the same label
5
+ * `db generate` gives the migration it writes (`better-auth`, `pikku-runtime`,
6
+ * an addon's flattened package name). `origin` is the prose from the migration
7
+ * header, written for a human reading a tooltip.
8
+ */
9
+ export interface TableProvenance {
10
+ source: string;
11
+ origin: string;
12
+ }
13
+ /**
14
+ * Drop the schema qualifier and any quoting, so `"app"."user"` and `user` are
15
+ * the same key.
16
+ *
17
+ * Introspection reports a bare name on SQLite and a qualified one on Postgres,
18
+ * while a migration may write either — comparing on the bare, unquoted name is
19
+ * the only form both sides agree on.
20
+ */
21
+ export declare function bareUnquotedTableName(name: string): string;
22
+ /**
23
+ * Read the table→source map out of a project's migration directory.
24
+ *
25
+ * Provenance is decided at `db generate` time, but the schema JSON is written
26
+ * at `db migrate` time, and the two commands do not have to run together. The
27
+ * migration files are the durable record that survives between them: each
28
+ * generated one carries its origin in a header and its source in its filename,
29
+ * so re-reading them costs a directory scan and needs neither a Better Auth
30
+ * config load nor a live database.
31
+ *
32
+ * Tables absent from the result are app-owned — including every table when the
33
+ * project has no generated migrations at all.
34
+ */
35
+ export declare function readMigrationProvenance(migrationsDir: string): Map<string, TableProvenance>;