@dokebi/cli 0.4.2 → 0.4.27

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 (200) hide show
  1. package/AI.md +279 -57
  2. package/README.md +35 -10
  3. package/catalog.json +2520 -258
  4. package/describe.schema.json +748 -0
  5. package/dist/api-app.d.ts +2 -0
  6. package/dist/api-app.js +35 -0
  7. package/dist/app-urls.js +3 -2
  8. package/dist/bin.js +638 -37
  9. package/dist/capability-guide.d.ts +30 -0
  10. package/dist/capability-guide.js +122 -0
  11. package/dist/catalog.d.ts +16 -2
  12. package/dist/catalog.js +19 -7
  13. package/dist/check/asset-rules.d.ts +6 -0
  14. package/dist/check/asset-rules.js +163 -0
  15. package/dist/check/browser-sources.d.ts +20 -0
  16. package/dist/check/browser-sources.js +89 -0
  17. package/dist/check/console-rules.d.ts +6 -0
  18. package/dist/check/console-rules.js +40 -0
  19. package/dist/check/error-rules.d.ts +21 -0
  20. package/dist/check/error-rules.js +212 -0
  21. package/dist/check/form-rules.d.ts +12 -0
  22. package/dist/check/form-rules.js +69 -6
  23. package/dist/check/formatter-rules.d.ts +2 -0
  24. package/dist/check/formatter-rules.js +71 -0
  25. package/dist/check/i18n-rules.d.ts +27 -0
  26. package/dist/check/i18n-rules.js +353 -0
  27. package/dist/check/index.d.ts +15 -1
  28. package/dist/check/index.js +89 -11
  29. package/dist/check/mail-rules.d.ts +4 -0
  30. package/dist/check/mail-rules.js +72 -0
  31. package/dist/check/module-rules.d.ts +13 -0
  32. package/dist/check/module-rules.js +104 -0
  33. package/dist/check/packages.d.ts +8 -0
  34. package/dist/check/packages.js +16 -0
  35. package/dist/check/pool-rules.d.ts +8 -0
  36. package/dist/check/pool-rules.js +164 -0
  37. package/dist/check/problems.d.ts +6 -1
  38. package/dist/check/problems.js +100 -2
  39. package/dist/check/project-rules.d.ts +23 -3
  40. package/dist/check/project-rules.js +265 -23
  41. package/dist/check/seal-rules.d.ts +6 -0
  42. package/dist/check/seal-rules.js +68 -0
  43. package/dist/check/security-rules.d.ts +10 -0
  44. package/dist/check/security-rules.js +450 -0
  45. package/dist/check/shared-state-rules.d.ts +15 -0
  46. package/dist/check/shared-state-rules.js +117 -0
  47. package/dist/check/source-rules.d.ts +26 -3
  48. package/dist/check/source-rules.js +249 -28
  49. package/dist/check/structure-rules.d.ts +23 -0
  50. package/dist/check/structure-rules.js +502 -0
  51. package/dist/check/timer-rules.d.ts +7 -0
  52. package/dist/check/timer-rules.js +543 -0
  53. package/dist/check/typescript.d.ts +35 -5
  54. package/dist/check/typescript.js +163 -17
  55. package/dist/check/vite-rules.d.ts +2 -0
  56. package/dist/check/vite-rules.js +136 -0
  57. package/dist/check/wait-rules.d.ts +16 -0
  58. package/dist/check/wait-rules.js +337 -0
  59. package/dist/commands.d.ts +7 -0
  60. package/dist/commands.js +381 -36
  61. package/dist/config-diff.d.ts +42 -0
  62. package/dist/config-diff.js +289 -0
  63. package/dist/config-edit.d.ts +35 -2
  64. package/dist/config-edit.js +145 -13
  65. package/dist/config-init.d.ts +15 -0
  66. package/dist/config-init.js +61 -0
  67. package/dist/config-paths.d.ts +22 -0
  68. package/dist/config-paths.js +82 -0
  69. package/dist/config-secrets.d.ts +46 -0
  70. package/dist/config-secrets.js +187 -0
  71. package/dist/config-show.d.ts +82 -0
  72. package/dist/config-show.js +563 -0
  73. package/dist/config-values.d.ts +8 -0
  74. package/dist/config-values.js +68 -0
  75. package/dist/configuration-scaffold.d.ts +42 -0
  76. package/dist/configuration-scaffold.js +258 -4
  77. package/dist/configuration.d.ts +63 -3
  78. package/dist/configuration.js +178 -4
  79. package/dist/deploy-scripts.d.ts +4 -0
  80. package/dist/deploy-scripts.js +361 -0
  81. package/dist/deploy-templates.d.ts +46 -0
  82. package/dist/deploy-templates.js +379 -0
  83. package/dist/deploy.d.ts +46 -0
  84. package/dist/deploy.js +592 -0
  85. package/dist/deployment.d.ts +2 -0
  86. package/dist/deployment.js +111 -0
  87. package/dist/describe-db.d.ts +22 -0
  88. package/dist/describe-db.js +176 -0
  89. package/dist/describe.d.ts +92 -0
  90. package/dist/describe.js +417 -0
  91. package/dist/dev-compose.d.ts +20 -0
  92. package/dist/dev-compose.js +154 -0
  93. package/dist/dev-guard.js +44 -0
  94. package/dist/dev-host.d.ts +13 -0
  95. package/dist/dev-host.js +55 -0
  96. package/dist/dev-infra.d.ts +72 -4
  97. package/dist/dev-infra.js +237 -17
  98. package/dist/dev-watch.d.ts +34 -0
  99. package/dist/dev-watch.js +192 -0
  100. package/dist/diagnostics.d.ts +10 -7
  101. package/dist/diagnostics.js +22 -4
  102. package/dist/drift.d.ts +50 -0
  103. package/dist/drift.js +218 -0
  104. package/dist/env-file.d.ts +25 -1
  105. package/dist/env-file.js +64 -6
  106. package/dist/errors.d.ts +7 -1
  107. package/dist/example-config.d.ts +2 -0
  108. package/dist/example-config.js +3 -0
  109. package/dist/first-admin.d.ts +43 -0
  110. package/dist/first-admin.js +175 -0
  111. package/dist/formatter-ignore.d.ts +37 -0
  112. package/dist/formatter-ignore.js +344 -0
  113. package/dist/generator.d.ts +45 -3
  114. package/dist/generator.js +205 -15
  115. package/dist/guide.d.ts +1 -1
  116. package/dist/guide.js +42 -21
  117. package/dist/install-module.d.ts +20 -2
  118. package/dist/install-module.js +45 -8
  119. package/dist/install-plan.js +5 -3
  120. package/dist/layout.d.ts +12 -1
  121. package/dist/layout.js +19 -6
  122. package/dist/log-writer-control.d.ts +2 -0
  123. package/dist/log-writer-control.js +17 -2
  124. package/dist/log-writer.js +43 -10
  125. package/dist/logging-config.d.ts +8 -3
  126. package/dist/logging-config.js +19 -5
  127. package/dist/manifest.d.ts +29 -1
  128. package/dist/manifest.js +84 -6
  129. package/dist/merge-install-defaults.d.ts +0 -1
  130. package/dist/merge-install-defaults.js +13 -47
  131. package/dist/npm-client.d.ts +40 -4
  132. package/dist/npm-client.js +152 -15
  133. package/dist/npm-paths.js +18 -4
  134. package/dist/openapi.d.ts +27 -0
  135. package/dist/openapi.js +649 -0
  136. package/dist/option-fixes.d.ts +39 -0
  137. package/dist/option-fixes.js +130 -0
  138. package/dist/output.d.ts +1 -1
  139. package/dist/output.js +5 -5
  140. package/dist/plugin-lifecycle.d.ts +34 -2
  141. package/dist/plugin-lifecycle.js +195 -16
  142. package/dist/plugin-options.d.ts +19 -4
  143. package/dist/plugin-options.js +138 -6
  144. package/dist/plugins-command.js +44 -7
  145. package/dist/process-group.d.ts +39 -0
  146. package/dist/process-group.js +132 -0
  147. package/dist/production-doctor.d.ts +3 -0
  148. package/dist/production-doctor.js +176 -0
  149. package/dist/project-guide.d.ts +19 -1
  150. package/dist/project-guide.js +119 -20
  151. package/dist/project-inspector.d.ts +15 -0
  152. package/dist/project-inspector.js +497 -33
  153. package/dist/project-state.d.ts +5 -0
  154. package/dist/project-state.js +8 -0
  155. package/dist/project.d.ts +203 -19
  156. package/dist/project.js +3006 -333
  157. package/dist/recipe-facts.d.ts +127 -0
  158. package/dist/recipe-facts.js +563 -0
  159. package/dist/recipe-login.d.ts +63 -0
  160. package/dist/recipe-login.js +464 -0
  161. package/dist/recipe-mail.d.ts +21 -0
  162. package/dist/recipe-mail.js +214 -0
  163. package/dist/recipe-members.d.ts +35 -0
  164. package/dist/recipe-members.js +178 -0
  165. package/dist/recipe-record.d.ts +82 -7
  166. package/dist/recipe-record.js +226 -22
  167. package/dist/recipe-reset.d.ts +2 -0
  168. package/dist/recipe-reset.js +117 -0
  169. package/dist/recipe-site.d.ts +48 -0
  170. package/dist/recipe-site.js +396 -0
  171. package/dist/recipe-step.d.ts +42 -0
  172. package/dist/recipe-step.js +89 -0
  173. package/dist/recipe.d.ts +56 -20
  174. package/dist/recipe.js +703 -131
  175. package/dist/registry.d.ts +98 -2
  176. package/dist/registry.js +171 -5
  177. package/dist/reloadable.d.ts +35 -0
  178. package/dist/reloadable.js +257 -0
  179. package/dist/remote-verify.d.ts +31 -0
  180. package/dist/remote-verify.js +204 -0
  181. package/dist/server.d.ts +43 -4
  182. package/dist/server.js +601 -52
  183. package/dist/settings.d.ts +48 -0
  184. package/dist/settings.js +168 -0
  185. package/dist/site-auth.d.ts +39 -0
  186. package/dist/site-auth.js +195 -0
  187. package/dist/template-fixes.d.ts +26 -0
  188. package/dist/template-fixes.js +285 -0
  189. package/dist/test-reporter.d.ts +49 -0
  190. package/dist/test-reporter.js +190 -0
  191. package/dist/transaction.d.ts +47 -0
  192. package/dist/transaction.js +241 -7
  193. package/dist/verify-tests.d.ts +98 -0
  194. package/dist/verify-tests.js +449 -0
  195. package/dist/verify.d.ts +103 -3
  196. package/dist/verify.js +272 -21
  197. package/dist/view-choice.d.ts +58 -0
  198. package/dist/view-choice.js +131 -0
  199. package/dist/vite-watch.js +4 -2
  200. package/package.json +8 -6
@@ -0,0 +1,111 @@
1
+ /** Optional, editable deployment files written atomically with a new project. */
2
+ export const DEPLOYMENT_FILES = {
3
+ Dockerfile: `# syntax=docker/dockerfile:1
4
+ FROM node:24-bookworm-slim AS build
5
+ WORKDIR /app
6
+ COPY package.json package-lock.json ./
7
+ # Optional private registry credentials: docker build --secret id=npmrc,src=/path/to/.npmrc .
8
+ RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
9
+ COPY . .
10
+ RUN npm run build --if-present && npm prune --omit=dev
11
+
12
+ FROM node:24-bookworm-slim AS runtime
13
+ ENV NODE_ENV=production
14
+ WORKDIR /app
15
+ COPY --from=build --chown=node:node /app /app
16
+ USER node
17
+ STOPSIGNAL SIGTERM
18
+ CMD ["node", "src/main.ts"]
19
+ `,
20
+ '.dockerignore': `# Explicit build inputs. Add other non-secret assets when the application needs them.
21
+ **
22
+ !package.json
23
+ !package-lock.json
24
+ !src/
25
+ !src/**
26
+ !public/
27
+ !public/**
28
+ !tsconfig*.json
29
+ !vite*.config.*
30
+ # Keep credentials and local artifacts out even under an allowed directory.
31
+ **/node_modules
32
+ **/.git
33
+ **/.env
34
+ **/.env.*
35
+ **/.npmrc
36
+ **/dokebi.yaml
37
+ **/*.backup
38
+ **/*.test.*
39
+ **/.DS_Store
40
+ `,
41
+ 'compose.deploy.yaml': `# Separate from local database/mail Compose files. Configure the app before starting.
42
+ # The container reads deploy/dokebi.yaml, never the project's dokebi.yaml, which npx dokebi
43
+ # start, dev and verify read on this machine (DEPLOY.md, step 3).
44
+ services:
45
+ app:
46
+ build:
47
+ context: .
48
+ init: true
49
+ user: "\${DOKEBI_UID:-1000}:\${DOKEBI_GID:-1000}"
50
+ environment:
51
+ NODE_ENV: production
52
+ ports:
53
+ - "127.0.0.1:\${DOKEBI_HTTP_PORT:-3000}:3000"
54
+ volumes:
55
+ - type: bind
56
+ source: ./deploy/dokebi.yaml
57
+ target: /app/dokebi.yaml
58
+ read_only: true
59
+ bind:
60
+ create_host_path: false
61
+ stop_grace_period: 60s
62
+ restart: unless-stopped
63
+ `,
64
+ 'DEPLOY.md': `# Deployment
65
+
66
+ These editable files are an application container skeleton. They do not provision a server,
67
+ TLS, databases, queues or backups. Finish your app or recipe and verify it before deploying.
68
+ A new core-only project has no long-running server and exits normally.
69
+
70
+ 1. Commit source and package-lock.json. Keep real dokebi.yaml, .env and credentials out of Git.
71
+ 2. Build with \`docker compose -f compose.deploy.yaml build\`. The build runs npm ci,
72
+ the optional build script (including Inertia assets/SSR), then removes devDependencies.
73
+ Put every package imported by the running app or SSR bundle in dependencies.
74
+ 3. Create deploy/dokebi.yaml, the container's settings, from dokebi.example.yaml. It is not
75
+ the project's dokebi.yaml: npx dokebi start, dev and verify read that one on the machine,
76
+ so container values (host 0.0.0.0, host.docker.internal, a database host of the container
77
+ network, paths inside the image) never go into it, and no release script writes it.
78
+ Both files stay out of Git and out of the image (.gitignore, .dockerignore). For a web app, set options.dokebi-web.host to 0.0.0.0 and port to 3000; configure
79
+ options.dokebi-app.domains for your public domain. Use production database/queue endpoints:
80
+ localhost inside a container is that container. Provision queues and run migrations as a
81
+ separate deployment step before starting traffic; never seed or migrate on every boot.
82
+ 4. Keep deploy/dokebi.yaml mode 0600 and owned by the container user (default UID/GID 1000:1000 on Linux).
83
+ To use another non-root UID/GID set DOKEBI_UID and DOKEBI_GID in the deployment environment.
84
+ The mount is read-only and refuses a missing file. Do not make credentials world-readable.
85
+ 5. Start with \`docker compose -f compose.deploy.yaml up -d\`. The host port defaults to
86
+ 127.0.0.1:3000 (DOKEBI_HTTP_PORT changes it). Terminate HTTPS in your reverse proxy and
87
+ configure only its trusted addresses in dokebi-web.trustProxy. For mail set siteUrl to
88
+ the public HTTPS origin. A worker-only app can remove the ports mapping.
89
+ 6. Inspect \`docker compose -f compose.deploy.yaml logs app\`; stop using
90
+ \`docker compose -f compose.deploy.yaml stop\`. Node runs in the foreground, receives
91
+ SIGTERM and drains. Set stop_grace_period above your configured total drain/cleanup budget.
92
+
93
+ The Docker build accepts only the inputs listed in .dockerignore. Add custom asset directories
94
+ there when needed, keeping credential exclusions last. The YAML is mounted at runtime and is
95
+ never an image layer. An authenticated npm registry can use BuildKit's secret mount:
96
+ \`docker build --secret id=npmrc,src=/private/path/.npmrc -t my-app .\`.
97
+ Do not COPY .npmrc or pass tokens as build arguments. Use registry-resolvable dependencies;
98
+ local tarball paths and workspace links need a separately prepared build context. npm lifecycle
99
+ and build scripts run without production credentials: they must not require dokebi.yaml.
100
+
101
+ Add readiness checks for the services you actually install and configure persistent storage for
102
+ uploads or other app-owned files. The skeleton does not invent an HTTP health endpoint or
103
+ silently install a web/health plugin. Pin the Node image digest in your deployment process.
104
+
105
+ An Inertia app run as several containers that are replaced one by one (a rolling update behind
106
+ a load balancer) serves two builds side by side, and each image holds only the chunks of its own
107
+ build: a page of the old build then misses its chunks on a new container. Carry the previous
108
+ build's dist/assets into the new image, or serve dist/assets from storage every container
109
+ shares (node_modules/@dokebi/template-inertia/AI.md, "Rolling deploys").
110
+ `,
111
+ };
@@ -0,0 +1,22 @@
1
+ /** Why `db` has no tables without --with-db. */
2
+ export declare const DB_NOT_READ = "not read: dokebi describe --with-db reads the tables (it connects to each database)";
3
+ export interface DbConnection {
4
+ readonly name: string;
5
+ readonly adapter: string;
6
+ /** D-0127: `optional` when dokebi.yaml says so (the default `required` is left out). */
7
+ readonly startup?: string;
8
+ readonly dialect?: string;
9
+ readonly schemas?: readonly string[];
10
+ readonly tables?: readonly unknown[];
11
+ readonly collections?: readonly unknown[];
12
+ readonly unavailable?: string;
13
+ }
14
+ /**
15
+ * The `db.connections` of `describe --with-db`: every dokebi-db connection with its dialect and
16
+ * tables (collections), or `unavailable` and why. The reads run side by side.
17
+ */
18
+ export declare function describeDatabases(root: string, connections: readonly {
19
+ name: string;
20
+ adapter: string;
21
+ startup?: string;
22
+ }[], timeoutMs: number): Promise<DbConnection[]>;
@@ -0,0 +1,176 @@
1
+ // `dokebi describe --with-db` (D-0077 F): the tables of each dokebi-db connection. Describe mode
2
+ // itself never connects (D-0077 B), so this is a separate step that does: for each connection it
3
+ // runs the project's own `dokebi-migrate schema --connection <name> --json` (the bin of the
4
+ // installed @dokebi/migrate, in the project folder, with the project's .env like describe). A
5
+ // connection that cannot be read gets `unavailable` with the reason; describe itself goes on.
6
+ import { spawn } from 'node:child_process';
7
+ import { readFileSync } from 'node:fs';
8
+ import { dirname, join } from 'node:path';
9
+ import { redaction } from '@dokebi/core/libraries';
10
+ import { debug, describeError } from './diagnostics.js';
11
+ import { appEnvironment } from './env-file.js';
12
+ /** Why `db` has no tables without --with-db. */
13
+ export const DB_NOT_READ = 'not read: dokebi describe --with-db reads the tables (it connects to each database)';
14
+ /** @dokebi/migrate 0.4.2 has the schema command. */
15
+ const MIGRATE_SCHEMA_VERSION = [0, 4, 2];
16
+ /** The largest answer read from one connection (a very large schema is cut, not buffered). */
17
+ const MAX_OUTPUT = 32 * 1024 * 1024;
18
+ /** One line, URL credentials removed, at most 500 characters. */
19
+ function reason(text) {
20
+ return (redaction
21
+ .maskCredentials(text.split('\n', 1)[0] ?? '')
22
+ .trim()
23
+ .slice(0, 500) || 'Unknown error');
24
+ }
25
+ /** The installed @dokebi/migrate (walking up like Node's resolution): its bin and version. */
26
+ function migratePackage(root) {
27
+ for (let directory = root;; directory = dirname(directory)) {
28
+ const folder = join(directory, 'node_modules', '@dokebi', 'migrate');
29
+ let manifest;
30
+ try {
31
+ manifest = JSON.parse(readFileSync(join(folder, 'package.json'), 'utf8'));
32
+ }
33
+ catch (error) {
34
+ if (error.code !== 'ENOENT')
35
+ debug('Unable to read the installed @dokebi/migrate package.json', {
36
+ error: describeError(error),
37
+ });
38
+ }
39
+ if (manifest) {
40
+ const bin = typeof manifest.bin === 'string'
41
+ ? manifest.bin
42
+ : manifest.bin?.['dokebi-migrate'];
43
+ if (typeof bin !== 'string')
44
+ return undefined;
45
+ const parts = /^(\d+)\.(\d+)\.(\d+)/.exec(String(manifest.version));
46
+ return {
47
+ bin: join(folder, bin),
48
+ version: parts ? [Number(parts[1]), Number(parts[2]), Number(parts[3])] : undefined,
49
+ };
50
+ }
51
+ if (dirname(directory) === directory)
52
+ return undefined;
53
+ }
54
+ }
55
+ const older = (a, b) => {
56
+ for (let index = 0; index < 3; index++)
57
+ if ((a[index] ?? 0) !== (b[index] ?? 0))
58
+ return (a[index] ?? 0) < (b[index] ?? 0);
59
+ return false;
60
+ };
61
+ /** Runs `dokebi-migrate schema` for one connection; resolves with the shape or a reason. */
62
+ function readOne(root, bin, name, timeoutMs) {
63
+ const started = Date.now();
64
+ const child = spawn(process.execPath, [bin, 'schema', '--connection', name, '--json'], {
65
+ cwd: root,
66
+ // The project's .env under the CLI's environment, like describe and dokebi start (D-0073).
67
+ env: appEnvironment(root, 'production'),
68
+ stdio: ['ignore', 'pipe', 'pipe'],
69
+ });
70
+ let stdout = '';
71
+ let cut = false;
72
+ // Its log lines (stderr) are kept only for the reason of a failure; never printed.
73
+ let stderr = '';
74
+ child.stdout?.on('data', (chunk) => {
75
+ if (stdout.length + chunk.length > MAX_OUTPUT)
76
+ cut = true;
77
+ else
78
+ stdout += chunk.toString('utf8');
79
+ });
80
+ child.stderr?.on('data', (chunk) => {
81
+ stderr = (stderr + chunk.toString('utf8')).slice(-16_384);
82
+ });
83
+ let timedOut = false;
84
+ return new Promise((resolve) => {
85
+ const timer = setTimeout(() => {
86
+ timedOut = true;
87
+ debug('Database schema read timed out', { connection: name, timeoutMs });
88
+ child.kill('SIGKILL');
89
+ }, timeoutMs);
90
+ let settled = false;
91
+ const finish = (value) => {
92
+ if (settled)
93
+ return;
94
+ settled = true;
95
+ clearTimeout(timer);
96
+ debug('Database schema read', {
97
+ connection: name,
98
+ ok: 'shape' in value,
99
+ ms: Date.now() - started,
100
+ });
101
+ resolve(value);
102
+ };
103
+ child.once('error', (error) => {
104
+ debug('Unable to run dokebi-migrate schema', {
105
+ connection: name,
106
+ error: describeError(error),
107
+ });
108
+ finish({ unavailable: `dokebi-migrate could not run: ${reason(error.message)}` });
109
+ });
110
+ child.once('close', (code, signal) => {
111
+ if (timedOut) {
112
+ finish({ unavailable: `DESCRIBE_TIMEOUT: no answer within ${timeoutMs} ms` });
113
+ return;
114
+ }
115
+ if (cut) {
116
+ finish({ unavailable: `SCHEMA_TOO_LARGE: more than ${MAX_OUTPUT} bytes` });
117
+ return;
118
+ }
119
+ let answer;
120
+ try {
121
+ answer = JSON.parse(stdout);
122
+ }
123
+ catch (error) {
124
+ debug('dokebi-migrate schema did not answer JSON', {
125
+ connection: name,
126
+ exit: code ?? signal,
127
+ error: describeError(error),
128
+ output: reason(stderr.split('\n').filter(Boolean).at(-1) ?? ''),
129
+ });
130
+ finish({
131
+ unavailable: `dokebi-migrate schema exited (${code ?? signal}) without a result`,
132
+ });
133
+ return;
134
+ }
135
+ const shape = answer.results?.[name];
136
+ if (answer.ok === true && shape !== null && typeof shape === 'object') {
137
+ finish({ shape: shape });
138
+ return;
139
+ }
140
+ const failure = answer.error ?? {};
141
+ finish({
142
+ unavailable: reason(`${typeof failure.code === 'string' ? failure.code : 'SCHEMA_FAILED'}: ${String(failure.message ?? 'no message')}`),
143
+ });
144
+ });
145
+ });
146
+ }
147
+ /**
148
+ * The `db.connections` of `describe --with-db`: every dokebi-db connection with its dialect and
149
+ * tables (collections), or `unavailable` and why. The reads run side by side.
150
+ */
151
+ export async function describeDatabases(root, connections, timeoutMs) {
152
+ if (!connections.length)
153
+ return [];
154
+ const installed = migratePackage(root);
155
+ const missing = !installed
156
+ ? '@dokebi/migrate is not installed: npx dokebi add @dokebi/migrate, then list the connection in options.dokebi-migrate.connections'
157
+ : installed.version && older(installed.version, MIGRATE_SCHEMA_VERSION)
158
+ ? `@dokebi/migrate ${installed.version.join('.')} has no schema command (0.4.2 or later)`
159
+ : undefined;
160
+ debug('Reading the database schemas', {
161
+ connections: connections.length,
162
+ migrate: installed?.version?.join('.') ?? null,
163
+ });
164
+ return Promise.all(connections.map(async ({ name, adapter, startup }) => {
165
+ // D-0127: the policy stays next to the name; `dokebi-migrate schema` opens every
166
+ // connection as required, so an optional one that is down is `unavailable` with why.
167
+ const base = { name, adapter, ...(startup === undefined ? {} : { startup }) };
168
+ if (missing || !installed)
169
+ return { ...base, unavailable: missing ?? 'unknown' };
170
+ const read = await readOne(root, installed.bin, name, timeoutMs);
171
+ if ('unavailable' in read)
172
+ return { ...base, unavailable: read.unavailable };
173
+ const { schemaVersion: _version, connection: _connection, ...shape } = read.shape;
174
+ return { ...base, ...shape };
175
+ }));
176
+ }
@@ -0,0 +1,92 @@
1
+ /** The JSON Schema of `result` (shipped next to dist/: packages/cli/describe.schema.json). */
2
+ export declare const DESCRIBE_SCHEMA_FILE = "describe.schema.json";
3
+ export declare const DESCRIBE_SCHEMA_VERSION = 1;
4
+ /** Default limit for loading and describing the app. */
5
+ export declare const DESCRIBE_TIMEOUT_MS = 30000;
6
+ /**
7
+ * Runs in the project's folder (`node --input-type=module --eval`): bare specifiers resolve from
8
+ * the project's node_modules and relative ones from its root, like src/main.ts.
9
+ */
10
+ export declare const DESCRIBE_RUNNER = "import { existsSync } from 'node:fs';\nimport { pathToFileURL } from 'node:url';\nconst base = pathToFileURL(process.cwd() + '/');\nconst reason = (error) => {\n let text;\n try { text = error instanceof Error ? error.message : String(error); } catch { text = typeof error; }\n return (String(text).split('\\n', 1)[0] || 'Unknown error').replace(/\\/\\/[^\\s/]*@/g, '//***@').slice(0, 500);\n};\nconst answer = (message) =>\n new Promise((resolve) => process.send(message, () => resolve(undefined)));\ntry {\n const { Application, loadConfig, definePoint } = await import('@dokebi/core');\n if (typeof Application.prototype.describe !== 'function') {\n await answer({ type: 'failure', code: 'DESCRIBE_UNSUPPORTED', message: 'The installed @dokebi/core cannot describe an app' });\n } else {\n // M50: the settings file the app reads, resolved by the CLI (DOKEBI_CONFIG, else dokebi.yaml).\n const settings = process.env.DOKEBI_CLI_SETTINGS_FILE;\n const document = await loadConfig(settings ? settings : new URL('dokebi.yaml', base));\n const app = new Application({ config: document.options, runtime: { describing: true } });\n const { registerPlugins } = await import(new URL('src/dokebi.generated.ts', base).href);\n await registerPlugins(app);\n const previewInput = process.env.DOKEBI_CLI_MAIL_PREVIEW;\n if (previewInput) {\n const input = JSON.parse(previewInput);\n const { dependency } = await import('@dokebi/mail');\n const point = definePoint('core:describe');\n app.register({\n metadata: { name: 'cli-mail-preview', version: '1.0.0', core: '*', dependencies: [dependency], contributes: ['core:describe'] },\n setup(context) {\n const mail = context.getDependency(dependency);\n context.logger.log('debug', 'Preparing mail template preview', { template: input.name });\n context.contribute(point, { section: 'mail-preview', describe: async () => {\n if (typeof mail.preview !== 'function') throw Object.assign(new Error('Upgrade @dokebi/mail for template preview'), { code: 'MAIL_PREVIEW_UNSUPPORTED' });\n const result = await mail.preview(input.name, input.locale);\n if (!result) throw Object.assign(new Error('Mail template does not exist'), { code: 'MAIL_TEMPLATE_NOT_FOUND' });\n return result;\n } });\n },\n });\n }\n const description = await app.describe();\n let errors = { file: false };\n const file = new URL('src/shared/errors.ts', base);\n if (existsSync(file)) {\n try {\n const module = await import(file.href);\n // errorCatalog of @dokebi/web 0.4.3 (D-0077 D) joins every defineErrors result exported.\n const web = await import('@dokebi/web');\n if (typeof web.errorCatalog !== 'function') throw Object.assign(new Error('The installed @dokebi/web has no errorCatalog (0.4.3 or later)'), { code: 'ERRORS_UNREADABLE' });\n const definitions = (web.errorCatalog(module) ?? []).map((item) => ({\n code: item.code,\n status: item.status,\n message: item.message,\n ...(typeof item.hint === 'string' ? { hint: item.hint } : {}),\n }));\n errors = { file: true, definitions };\n } catch (error) {\n errors = { file: true, failure: { code: typeof error?.code === 'string' ? error.code : 'ERRORS_UNREADABLE', reason: reason(error) } };\n }\n }\n await answer({ type: 'description', description, errors });\n }\n} catch (error) {\n await answer({\n type: 'failure',\n code: typeof error?.code === 'string' ? error.code : 'DESCRIBE_FAILED',\n message: reason(error),\n ...(typeof error?.plugin === 'string' ? { plugin: error.plugin } : {}),\n });\n process.exitCode = 1;\n}\nprocess.disconnect();\n";
11
+ interface CoreDescription {
12
+ readonly schemaVersion: number;
13
+ readonly core: string;
14
+ readonly plugins: readonly DescribedPlugin[];
15
+ readonly sections: readonly {
16
+ section: string;
17
+ plugin: string;
18
+ value: unknown;
19
+ }[];
20
+ readonly unavailable: readonly Unavailable[];
21
+ }
22
+ /** One plugin of core's description; the points only when it declares any (core 0.4.2, D-0130). */
23
+ interface DescribedPlugin {
24
+ readonly name: string;
25
+ readonly version: string;
26
+ readonly described: boolean;
27
+ readonly contributes?: readonly string[];
28
+ readonly collects?: readonly string[];
29
+ }
30
+ interface Unavailable {
31
+ readonly plugin: string;
32
+ readonly section?: string;
33
+ readonly code: string;
34
+ readonly reason: string;
35
+ }
36
+ interface ErrorsRead {
37
+ readonly file: boolean;
38
+ readonly definitions?: readonly ErrorDefinition[];
39
+ readonly failure?: {
40
+ readonly code: string;
41
+ readonly reason: string;
42
+ };
43
+ }
44
+ interface ErrorDefinition {
45
+ readonly code: string;
46
+ readonly status: number;
47
+ readonly message: string;
48
+ readonly hint?: string;
49
+ }
50
+ export interface DescribeResult {
51
+ readonly schemaVersion: 1;
52
+ readonly app: Record<string, unknown>;
53
+ readonly routes: Record<string, unknown>[];
54
+ readonly pages: unknown[];
55
+ readonly jobs: unknown[];
56
+ readonly schedules: unknown[];
57
+ readonly errors: ErrorDefinition[];
58
+ readonly config: {
59
+ readonly keys: string[];
60
+ readonly env: Record<string, unknown>[];
61
+ };
62
+ readonly db: Record<string, unknown>;
63
+ readonly unavailable: Unavailable[];
64
+ readonly warnings?: {
65
+ code: string;
66
+ message: string;
67
+ }[];
68
+ readonly ms: number;
69
+ }
70
+ export interface DescribeOptions {
71
+ readonly timeoutMs?: number;
72
+ /** D-0077 F: also read each connection's tables with dokebi-migrate schema (it connects). */
73
+ readonly withDb?: boolean;
74
+ /** Internal input for the mail preview command; only sample data is rendered. */
75
+ readonly mailPreview?: {
76
+ readonly name: string;
77
+ readonly locale?: string;
78
+ };
79
+ }
80
+ /** The CLI result from core's description and the error definitions. */
81
+ export declare function shapeDescription(name: string | null, description: CoreDescription, errors: ErrorsRead, ms: number, lingered?: boolean): DescribeResult;
82
+ /** `dokebi describe`: loads the project in describe mode and returns the description. */
83
+ export declare function describeProject(root: string, options?: DescribeOptions): Promise<DescribeResult>;
84
+ /**
85
+ * D-0130: what each module contributes to and who collects it, one line per module point, e.g.
86
+ * `module points: dokebi-module.notes -> dokebi-mcp:tools (collected by dokebi-mcp)`. A point no
87
+ * plugin collects says so: its values are dropped (warn POINT_NOT_COLLECTED at start).
88
+ */
89
+ export declare function modulePointsText(plugins: readonly DescribedPlugin[]): string[];
90
+ /** The short human summary. */
91
+ export declare function describeText(result: DescribeResult): string;
92
+ export {};