@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,31 @@
1
+ export interface RemoteCheck {
2
+ readonly name: string;
3
+ readonly ok: boolean;
4
+ /** A failed check that is only a warning (the result stays ok). */
5
+ readonly warning?: boolean;
6
+ readonly status?: number;
7
+ readonly detail: string;
8
+ readonly ms: number;
9
+ }
10
+ export interface RemoteVerifyResult {
11
+ readonly ok: boolean;
12
+ readonly url: string;
13
+ readonly checks: readonly RemoteCheck[];
14
+ readonly certificate?: {
15
+ readonly subject: string;
16
+ readonly issuer: string;
17
+ readonly validTo: string;
18
+ readonly daysLeft: number;
19
+ };
20
+ }
21
+ export interface RemoteVerifyOptions {
22
+ readonly root?: string;
23
+ readonly timeoutMs?: number;
24
+ /** Trusted certificates besides the system's (tests: a local CA). */
25
+ readonly ca?: string;
26
+ /** The port of the plain http check (default 80; tests use a free one). */
27
+ readonly httpPort?: number;
28
+ }
29
+ /** `dokebi verify --remote` (D-0184 §4). */
30
+ export declare function remoteVerify(input: string, options?: RemoteVerifyOptions): Promise<RemoteVerifyResult>;
31
+ export declare function remoteVerifyText(result: RemoteVerifyResult): string;
@@ -0,0 +1,204 @@
1
+ // `dokebi verify --remote <https-url>` (D-0184 §4): checks a deployed site from the outside —
2
+ // its TLS certificate, the http to https redirect, the health routes and the security headers.
3
+ // Nothing local runs; the result says which check failed and why.
4
+ import { request as httpRequest } from 'node:http';
5
+ import { request as httpsRequest } from 'node:https';
6
+ import { connect } from 'node:tls';
7
+ import { parseConfig } from '@dokebi/config';
8
+ import { readSettings } from './config-show.js';
9
+ import { debug, describeError } from './diagnostics.js';
10
+ import { CliError } from './errors.js';
11
+ /** Days left below which the certificate check warns (renewal should have happened). */
12
+ const RENEW_DAYS = 14;
13
+ /** HSTS max-age below this many seconds (180 days) is a warning. */
14
+ const HSTS_MIN = 15_552_000;
15
+ function fetchOnce(url, timeoutMs, ca) {
16
+ return new Promise((resolve, reject) => {
17
+ const send = url.protocol === 'https:' ? httpsRequest : httpRequest;
18
+ const request = send(url, {
19
+ method: 'GET',
20
+ headers: { 'user-agent': 'dokebi-verify-remote', accept: '*/*' },
21
+ timeout: timeoutMs,
22
+ ...(ca === undefined ? {} : { ca }),
23
+ }, (response) => {
24
+ // Only the status and headers matter; the body is read to the end and dropped.
25
+ response.resume();
26
+ response.once('end', () => resolve({ status: response.statusCode ?? 0, headers: response.headers }));
27
+ response.once('error', reject);
28
+ });
29
+ request.once('timeout', () => request.destroy(new Error(`no answer within ${timeoutMs} ms`)));
30
+ request.once('error', reject);
31
+ request.end();
32
+ });
33
+ }
34
+ function certificateOf(url, timeoutMs, ca) {
35
+ return new Promise((resolve, reject) => {
36
+ const socket = connect({
37
+ host: url.hostname,
38
+ port: Number(url.port || 443),
39
+ servername: url.hostname,
40
+ timeout: timeoutMs,
41
+ ...(ca === undefined ? {} : { ca }),
42
+ });
43
+ socket.once('secureConnect', () => {
44
+ const certificate = socket.getPeerCertificate();
45
+ socket.end();
46
+ if (!socket.authorized)
47
+ reject(new Error(String(socket.authorizationError)));
48
+ else
49
+ resolve(certificate);
50
+ });
51
+ socket.once('timeout', () => socket.destroy(new Error(`no TLS handshake within ${timeoutMs} ms`)));
52
+ socket.once('error', reject);
53
+ });
54
+ }
55
+ function header(answer, name) {
56
+ const value = answer.headers[name];
57
+ return Array.isArray(value) ? value.join(', ') : value;
58
+ }
59
+ /** The health routes of the project's settings when run in one, else the defaults. */
60
+ async function healthRoutes(root) {
61
+ const defaults = { live: '/health/live', ready: '/health/ready' };
62
+ if (root === undefined)
63
+ return defaults;
64
+ try {
65
+ const { source } = await readSettings(root);
66
+ const routes = parseConfig(source).options['dokebi-web']?.healthRoutes;
67
+ return {
68
+ live: typeof routes?.live === 'string' ? routes.live : defaults.live,
69
+ ready: typeof routes?.ready === 'string' ? routes.ready : defaults.ready,
70
+ };
71
+ }
72
+ catch (error) {
73
+ // Outside a project (or settings that do not parse) the default routes are checked.
74
+ debug('verify --remote uses the default health routes', { error: describeError(error) });
75
+ return defaults;
76
+ }
77
+ }
78
+ /** `dokebi verify --remote` (D-0184 §4). */
79
+ export async function remoteVerify(input, options = {}) {
80
+ let url;
81
+ try {
82
+ url = new URL(input);
83
+ }
84
+ catch (cause) {
85
+ throw new CliError('USAGE', `--remote ${input}: an https URL such as https://shop.example.com`, {
86
+ phase: 'arguments',
87
+ cause,
88
+ });
89
+ }
90
+ if (url.protocol !== 'https:' || url.username || url.password)
91
+ throw new CliError('USAGE', '--remote takes an https URL without credentials', {
92
+ phase: 'arguments',
93
+ details: { fix: 'npx dokebi verify --remote https://<domain> --json' },
94
+ });
95
+ const timeoutMs = options.timeoutMs ?? 10_000;
96
+ const routes = await healthRoutes(options.root);
97
+ const checks = [];
98
+ const timed = async (name, run) => {
99
+ const started = Date.now();
100
+ try {
101
+ checks.push({ name, ...(await run()), ms: Date.now() - started });
102
+ }
103
+ catch (error) {
104
+ checks.push({
105
+ name,
106
+ ok: false,
107
+ detail: describeError(error).split('\n')[0] ?? 'failed',
108
+ ms: Date.now() - started,
109
+ });
110
+ }
111
+ };
112
+ let certificate;
113
+ await timed('tls', async () => {
114
+ const peer = await certificateOf(url, timeoutMs, options.ca);
115
+ const daysLeft = Math.floor((Date.parse(peer.valid_to) - Date.now()) / 86_400_000);
116
+ certificate = {
117
+ subject: String(peer.subject?.CN ?? ''),
118
+ issuer: String(peer.issuer?.O ?? peer.issuer?.CN ?? ''),
119
+ validTo: new Date(peer.valid_to).toISOString(),
120
+ daysLeft,
121
+ };
122
+ return daysLeft < RENEW_DAYS
123
+ ? {
124
+ ok: false,
125
+ warning: true,
126
+ detail: `the certificate ends in ${daysLeft} days (renewal should have happened)`,
127
+ }
128
+ : { ok: true, detail: `valid for ${url.hostname}, ${daysLeft} days left` };
129
+ });
130
+ await timed('redirect', async () => {
131
+ const plain = new URL(url);
132
+ plain.protocol = 'http:';
133
+ plain.port = options.httpPort === undefined ? '' : String(options.httpPort);
134
+ const answer = await fetchOnce(plain, timeoutMs);
135
+ const location = header(answer, 'location') ?? '';
136
+ const ok = [301, 302, 307, 308].includes(answer.status) && location.startsWith('https://');
137
+ return ok
138
+ ? { ok, status: answer.status, detail: `http answers ${answer.status} to ${location}` }
139
+ : {
140
+ ok,
141
+ warning: true,
142
+ status: answer.status,
143
+ detail: `http answers ${answer.status} without a redirect to https`,
144
+ };
145
+ });
146
+ for (const [name, path] of [
147
+ ['live', routes.live],
148
+ ['ready', routes.ready],
149
+ ])
150
+ await timed(name, async () => {
151
+ const answer = await fetchOnce(new URL(path, url), timeoutMs, options.ca);
152
+ return {
153
+ ok: answer.status === 200,
154
+ status: answer.status,
155
+ detail: answer.status === 200 ? `${path} answers 200` : `${path} answers ${answer.status}`,
156
+ };
157
+ });
158
+ await timed('headers', async () => {
159
+ const answer = await fetchOnce(url, timeoutMs, options.ca);
160
+ const problems = [];
161
+ const notes = [];
162
+ const hsts = header(answer, 'strict-transport-security');
163
+ const age = Number(/max-age=(\d+)/i.exec(hsts ?? '')?.[1] ?? Number.NaN);
164
+ if (!hsts)
165
+ problems.push('no Strict-Transport-Security');
166
+ else if (!(age >= HSTS_MIN))
167
+ notes.push(`Strict-Transport-Security max-age ${age} is under 180 days`);
168
+ if (header(answer, 'x-content-type-options')?.toLowerCase() !== 'nosniff')
169
+ problems.push('no X-Content-Type-Options: nosniff');
170
+ // Framing and the referrer policy matter for pages (D-0149 sets them on HTML answers); an
171
+ // API answer at the URL needs HSTS and nosniff only.
172
+ const html = /text\/html/i.test(header(answer, 'content-type') ?? '');
173
+ const csp = header(answer, 'content-security-policy') ?? '';
174
+ if (html && !header(answer, 'x-frame-options') && !/frame-ancestors/i.test(csp))
175
+ problems.push('no X-Frame-Options or CSP frame-ancestors');
176
+ if (html && !header(answer, 'referrer-policy'))
177
+ notes.push('no Referrer-Policy');
178
+ if (header(answer, 'x-powered-by'))
179
+ problems.push('X-Powered-By names the server software');
180
+ return problems.length
181
+ ? { ok: false, status: answer.status, detail: [...problems, ...notes].join('; ') }
182
+ : notes.length
183
+ ? { ok: false, warning: true, status: answer.status, detail: notes.join('; ') }
184
+ : {
185
+ ok: true,
186
+ status: answer.status,
187
+ detail: html
188
+ ? 'HSTS, nosniff, framing and referrer policy set'
189
+ : `HSTS and nosniff set (a ${header(answer, 'content-type')?.split(';')[0] ?? 'non-HTML'} answer: framing and referrer policy not checked)`,
190
+ };
191
+ });
192
+ const ok = checks.every((check) => check.ok || check.warning === true);
193
+ debug('Remote site checked', {
194
+ host: url.hostname,
195
+ ok,
196
+ failed: checks.filter((check) => !check.ok).map((check) => check.name),
197
+ });
198
+ return { ok, url: url.href, checks, ...(certificate ? { certificate } : {}) };
199
+ }
200
+ export function remoteVerifyText(result) {
201
+ return `${result.checks
202
+ .map((check) => `${check.ok ? 'ok' : check.warning ? 'WARN' : 'FAIL'} ${check.name} (${check.ms} ms): ${check.detail}`)
203
+ .join('\n')}\n${result.ok ? `verify --remote ok: ${result.url}` : `verify --remote FAILED: ${result.url}`}\n`;
204
+ }
package/dist/server.d.ts CHANGED
@@ -103,6 +103,14 @@ export declare function proveInstance(root: string, record: Pick<PidRecord, 'tok
103
103
  tried: string[];
104
104
  reason?: string;
105
105
  }>;
106
+ export declare function checkProject(root: string): Promise<string>;
107
+ /**
108
+ * R1 T4 review: why a start failed, from the app log of this start only (after its last start
109
+ * mark): the first record with an error, as `<code>: <first line>` and, when the error has
110
+ * causes, `… <its last Caused by line>`. A START_FAILED said only "exited with code 1", and its
111
+ * tail began with an earlier start's lines. Undefined when no record says.
112
+ */
113
+ export declare function startFailureCause(lines: readonly string[]): string | undefined;
106
114
  export interface LogOptions {
107
115
  /** Rotate app.log when it would pass this size (default 50 MB). */
108
116
  readonly logMaxBytes?: number;
@@ -185,12 +193,43 @@ export declare function stopStarted(root: string, started: Pick<StartResult, 'pi
185
193
  export declare function logs(root: string, lines?: number, follow?: boolean, write?: (text: string) => void, signal?: AbortSignal): Promise<void>;
186
194
  /** Runs the app in the foreground and forwards SIGINT/SIGTERM; resolves with its exit code. */
187
195
  export declare function foreground(root: string): Promise<number>;
196
+ /** The installed version of a package, looked up like Node does (node_modules up the tree). */
197
+ export declare function installedVersion(root: string, name: string): [number, number, number] | undefined;
188
198
  /**
189
199
  * Development loop: runs the app, restarts it (SIGTERM, drain, start) 300 ms after a change in
190
- * src/, dokebi.yaml or .env. With a Vite config, Inertia views come from the Vite dev server
191
- * (HMR, D-0045) when template-inertia supports it, otherwise from watch builds that restart the
192
- * app. With template-inertia 0.4.0 that Vite is one host process beside the app, shared by every
193
- * cluster worker and kept across app restarts (D-0045 addendum 3); older ones start it inside the app.
200
+ * src/, dokebi.yaml or .env (a path whose state differs from the recorded one, see dev-watch.ts).
201
+ * D-0154: a change of dokebi.yaml alone is first offered to the running app: when every changed
202
+ * option is reloadable it applies them and keeps running; an invalid file keeps the app as it is;
203
+ * anything else (an option that needs a restart, an app without reloads) restarts it.
204
+ * With a Vite config, Inertia views come from the Vite dev server (HMR, D-0045) when
205
+ * template-inertia supports it, otherwise from watch builds that restart the app. With
206
+ * template-inertia 0.4.0 that Vite is one host process beside the app, shared by every cluster
207
+ * worker and kept across app restarts (D-0045 addendum 3); older ones start it inside the app.
194
208
  * The first Ctrl+C drains everything; a second one kills.
195
209
  */
196
210
  export declare function dev(root: string, say: (line: string) => void): Promise<number>;
211
+ /** What `dokebi reload` found and did (D-0154). */
212
+ export interface ReloadResult {
213
+ readonly running: boolean;
214
+ readonly pid?: number;
215
+ /** `applied`, `unchanged`, or `unknown` (no outcome line within the timeout). */
216
+ readonly outcome?: 'applied' | 'unchanged' | 'unknown';
217
+ readonly code?: string;
218
+ /** The configuration revision after the reload, when the app said it. */
219
+ readonly revision?: number;
220
+ /** Changed option paths the app named (never values). */
221
+ readonly paths?: string[];
222
+ readonly ms?: number;
223
+ readonly proof?: Proof;
224
+ /** Who sent SIGHUP: this command, or the app's log writer (a sandbox per command). */
225
+ readonly via?: 'signal' | 'log-writer';
226
+ readonly warnings?: string[];
227
+ }
228
+ /**
229
+ * `dokebi reload` (D-0154): asks the background app to read dokebi.yaml again (SIGHUP to the pid
230
+ * `start` recorded, proven like `stop`; through its log writer when this shell may not signal
231
+ * it), then reads its outcome from .dokebi/run/app.log. Options marked reloadable apply without
232
+ * a restart; an option that needs one (`RELOAD_RESTART_REQUIRED`), an invalid file
233
+ * (`RELOAD_INVALID`) or a plugin that could not prepare (`RELOAD_FAILED`) change nothing.
234
+ */
235
+ export declare function reload(root: string, timeoutMs?: number, read?: ProcessReader, kill?: ProcessSignaller): Promise<ReloadResult>;