@dokebi/cli 0.4.2 → 0.4.26

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 (188) hide show
  1. package/AI.md +262 -57
  2. package/README.md +35 -10
  3. package/catalog.json +2519 -257
  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 +518 -35
  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 +2 -0
  46. package/dist/check/shared-state-rules.js +114 -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 +307 -31
  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-show.d.ts +82 -0
  70. package/dist/config-show.js +563 -0
  71. package/dist/config-values.d.ts +8 -0
  72. package/dist/config-values.js +68 -0
  73. package/dist/configuration-scaffold.d.ts +42 -0
  74. package/dist/configuration-scaffold.js +258 -4
  75. package/dist/configuration.d.ts +63 -3
  76. package/dist/configuration.js +178 -4
  77. package/dist/deployment.d.ts +2 -0
  78. package/dist/deployment.js +111 -0
  79. package/dist/describe-db.d.ts +22 -0
  80. package/dist/describe-db.js +176 -0
  81. package/dist/describe.d.ts +92 -0
  82. package/dist/describe.js +417 -0
  83. package/dist/dev-compose.d.ts +20 -0
  84. package/dist/dev-compose.js +154 -0
  85. package/dist/dev-guard.js +44 -0
  86. package/dist/dev-host.d.ts +13 -0
  87. package/dist/dev-host.js +55 -0
  88. package/dist/dev-infra.d.ts +72 -4
  89. package/dist/dev-infra.js +237 -17
  90. package/dist/dev-watch.d.ts +34 -0
  91. package/dist/dev-watch.js +192 -0
  92. package/dist/diagnostics.d.ts +10 -7
  93. package/dist/diagnostics.js +22 -4
  94. package/dist/drift.d.ts +50 -0
  95. package/dist/drift.js +218 -0
  96. package/dist/env-file.d.ts +25 -1
  97. package/dist/env-file.js +64 -6
  98. package/dist/errors.d.ts +7 -1
  99. package/dist/example-config.d.ts +2 -0
  100. package/dist/example-config.js +3 -0
  101. package/dist/first-admin.d.ts +43 -0
  102. package/dist/first-admin.js +175 -0
  103. package/dist/formatter-ignore.d.ts +37 -0
  104. package/dist/formatter-ignore.js +344 -0
  105. package/dist/generator.d.ts +45 -3
  106. package/dist/generator.js +205 -15
  107. package/dist/guide.d.ts +1 -1
  108. package/dist/guide.js +42 -21
  109. package/dist/install-module.d.ts +20 -2
  110. package/dist/install-module.js +45 -8
  111. package/dist/install-plan.js +5 -3
  112. package/dist/layout.d.ts +12 -1
  113. package/dist/layout.js +19 -6
  114. package/dist/log-writer-control.d.ts +2 -0
  115. package/dist/log-writer-control.js +17 -2
  116. package/dist/log-writer.js +43 -10
  117. package/dist/logging-config.d.ts +8 -3
  118. package/dist/logging-config.js +19 -5
  119. package/dist/manifest.d.ts +29 -1
  120. package/dist/manifest.js +84 -6
  121. package/dist/merge-install-defaults.d.ts +0 -1
  122. package/dist/merge-install-defaults.js +13 -47
  123. package/dist/npm-client.d.ts +40 -4
  124. package/dist/npm-client.js +152 -15
  125. package/dist/npm-paths.js +18 -4
  126. package/dist/openapi.d.ts +27 -0
  127. package/dist/openapi.js +649 -0
  128. package/dist/option-fixes.d.ts +39 -0
  129. package/dist/option-fixes.js +130 -0
  130. package/dist/output.d.ts +1 -1
  131. package/dist/output.js +5 -5
  132. package/dist/plugin-lifecycle.d.ts +34 -2
  133. package/dist/plugin-lifecycle.js +195 -16
  134. package/dist/plugin-options.d.ts +19 -4
  135. package/dist/plugin-options.js +138 -6
  136. package/dist/plugins-command.js +44 -7
  137. package/dist/process-group.d.ts +39 -0
  138. package/dist/process-group.js +132 -0
  139. package/dist/project-guide.d.ts +19 -1
  140. package/dist/project-guide.js +119 -20
  141. package/dist/project-inspector.d.ts +15 -0
  142. package/dist/project-inspector.js +481 -33
  143. package/dist/project-state.d.ts +5 -0
  144. package/dist/project-state.js +8 -0
  145. package/dist/project.d.ts +203 -19
  146. package/dist/project.js +3006 -333
  147. package/dist/recipe-facts.d.ts +127 -0
  148. package/dist/recipe-facts.js +563 -0
  149. package/dist/recipe-login.d.ts +63 -0
  150. package/dist/recipe-login.js +464 -0
  151. package/dist/recipe-mail.d.ts +21 -0
  152. package/dist/recipe-mail.js +214 -0
  153. package/dist/recipe-members.d.ts +35 -0
  154. package/dist/recipe-members.js +178 -0
  155. package/dist/recipe-record.d.ts +82 -7
  156. package/dist/recipe-record.js +226 -22
  157. package/dist/recipe-reset.d.ts +2 -0
  158. package/dist/recipe-reset.js +117 -0
  159. package/dist/recipe-site.d.ts +48 -0
  160. package/dist/recipe-site.js +396 -0
  161. package/dist/recipe-step.d.ts +42 -0
  162. package/dist/recipe-step.js +89 -0
  163. package/dist/recipe.d.ts +56 -20
  164. package/dist/recipe.js +703 -131
  165. package/dist/registry.d.ts +98 -2
  166. package/dist/registry.js +171 -5
  167. package/dist/reloadable.d.ts +35 -0
  168. package/dist/reloadable.js +257 -0
  169. package/dist/server.d.ts +43 -4
  170. package/dist/server.js +601 -52
  171. package/dist/settings.d.ts +48 -0
  172. package/dist/settings.js +168 -0
  173. package/dist/site-auth.d.ts +39 -0
  174. package/dist/site-auth.js +195 -0
  175. package/dist/template-fixes.d.ts +26 -0
  176. package/dist/template-fixes.js +285 -0
  177. package/dist/test-reporter.d.ts +49 -0
  178. package/dist/test-reporter.js +190 -0
  179. package/dist/transaction.d.ts +47 -0
  180. package/dist/transaction.js +241 -7
  181. package/dist/verify-tests.d.ts +98 -0
  182. package/dist/verify-tests.js +449 -0
  183. package/dist/verify.d.ts +103 -3
  184. package/dist/verify.js +272 -21
  185. package/dist/view-choice.d.ts +58 -0
  186. package/dist/view-choice.js +131 -0
  187. package/dist/vite-watch.js +4 -2
  188. package/package.json +8 -6
@@ -4,6 +4,7 @@
4
4
  // TypeScript; it loads the copy the project installed, so the AST and the types are exactly what
5
5
  // `npm run typecheck` sees. The API is marked unstable, so only the few calls below are used and
6
6
  // a missing or changed API is a coded failure (TYPESCRIPT_UNAVAILABLE), never a silent pass.
7
+ import { existsSync } from 'node:fs';
7
8
  import { createRequire } from 'node:module';
8
9
  import { join, sep } from 'node:path';
9
10
  import { pathToFileURL } from 'node:url';
@@ -25,42 +26,122 @@ export function property(node, name) {
25
26
  }
26
27
  /** SyntaxKind names the rules use; each must exist in the loaded TypeScript. */
27
28
  const KIND_NAMES = [
29
+ 'ArrayLiteralExpression',
28
30
  'ArrowFunction',
31
+ 'AsExpression',
32
+ 'AwaitExpression',
33
+ 'BarBarEqualsToken',
34
+ 'BarBarToken',
35
+ 'BinaryExpression',
36
+ 'BindingElement',
29
37
  'Block',
38
+ 'BreakStatement',
30
39
  'CallExpression',
31
40
  'CatchClause',
41
+ 'ClassDeclaration',
42
+ 'ClassExpression',
43
+ 'ConditionalExpression',
32
44
  'Constructor',
33
- 'Decorator',
45
+ 'ContinueStatement',
34
46
  'DeclareKeyword',
47
+ 'Decorator',
48
+ 'DoStatement',
49
+ 'ElementAccessExpression',
35
50
  'EnumDeclaration',
51
+ 'EqualsEqualsEqualsToken',
52
+ 'EqualsEqualsToken',
53
+ 'EqualsToken',
54
+ 'ExclamationEqualsEqualsToken',
55
+ 'ExclamationEqualsToken',
36
56
  'ExportAssignment',
37
57
  'ExportDeclaration',
58
+ 'ExportKeyword',
38
59
  'ExpressionStatement',
39
60
  'ExternalModuleReference',
61
+ 'FalseKeyword',
62
+ 'ForInStatement',
63
+ 'ForOfStatement',
64
+ 'ForStatement',
65
+ 'FunctionDeclaration',
40
66
  'FunctionExpression',
67
+ 'GetAccessor',
68
+ 'GreaterThanEqualsToken',
69
+ 'GreaterThanToken',
41
70
  'Identifier',
71
+ 'IfStatement',
42
72
  'ImportDeclaration',
43
73
  'ImportEqualsDeclaration',
44
74
  'ImportKeyword',
45
75
  'ImportType',
46
76
  'InterfaceDeclaration',
77
+ 'LessThanEqualsToken',
78
+ 'LessThanToken',
47
79
  'LiteralType',
80
+ 'MethodDeclaration',
48
81
  'ModuleBlock',
49
82
  'ModuleDeclaration',
83
+ 'NamedImports',
84
+ 'NamespaceImport',
50
85
  'NewExpression',
51
86
  'NoSubstitutionTemplateLiteral',
87
+ 'NonNullExpression',
88
+ 'NullKeyword',
89
+ 'NumericLiteral',
90
+ 'ObjectBindingPattern',
91
+ 'ObjectLiteralExpression',
52
92
  'OverrideKeyword',
53
93
  'Parameter',
54
94
  'ParenthesizedExpression',
95
+ 'PrivateIdentifier',
55
96
  'PrivateKeyword',
56
97
  'PropertyAccessExpression',
98
+ 'PropertyAssignment',
99
+ 'PropertyDeclaration',
57
100
  'ProtectedKeyword',
58
101
  'PublicKeyword',
102
+ 'QuestionQuestionEqualsToken',
103
+ 'QuestionQuestionToken',
59
104
  'ReadonlyKeyword',
105
+ 'ReturnStatement',
106
+ 'SetAccessor',
107
+ 'ShorthandPropertyAssignment',
108
+ 'SpreadElement',
109
+ 'StaticKeyword',
60
110
  'StringLiteral',
111
+ 'TemplateExpression',
112
+ 'ThisKeyword',
113
+ 'ThrowStatement',
114
+ 'TrueKeyword',
61
115
  'TypeAliasDeclaration',
62
116
  'TypeAssertionExpression',
117
+ 'TypeOfExpression',
118
+ 'VariableDeclaration',
119
+ 'VariableStatement',
120
+ 'VoidExpression',
121
+ 'WhileStatement',
63
122
  ];
123
+ /** The virtual tsconfig of the browser program; never written to disk. */
124
+ export const BROWSER_TSCONFIG = 'tsconfig.dokebi-check.json';
125
+ /**
126
+ * How the browser program resolves and what it knows, over the project's tsconfig: browser code is
127
+ * bundled by Vite, so imports resolve as a bundler does (the project's own tsconfig.vue.json says
128
+ * the same), and the DOM exists. Under the server's NodeNext a package whose type files import
129
+ * `./app` without an extension (@inertiajs/vue3 3.x) resolves to nothing, and every call of it was
130
+ * untyped, so a floating `createInertiaApp(…)` passed (D-0120 §1.4).
131
+ */
132
+ const BROWSER_OPTIONS = {
133
+ module: 'esnext',
134
+ moduleResolution: 'bundler',
135
+ lib: ['esnext', 'dom', 'dom.iterable'],
136
+ };
137
+ /** Compiler options of a browser program without a project tsconfig (the framework's gate). */
138
+ const STANDALONE_OPTIONS = {
139
+ target: 'esnext',
140
+ strict: true,
141
+ skipLibCheck: true,
142
+ types: [],
143
+ };
144
+ const slashes = (path) => path.split(sep).join('/');
64
145
  function unavailable(root, message, cause) {
65
146
  return new CliError('TYPESCRIPT_UNAVAILABLE', message, {
66
147
  ...(cause === undefined ? {} : { cause }),
@@ -75,7 +156,14 @@ function unavailable(root, message, cause) {
75
156
  * closes it. Throws TYPESCRIPT_UNAVAILABLE (TypeScript missing, older than 7, or its API changed)
76
157
  * and TSCONFIG_INVALID (the program cannot be loaded).
77
158
  */
78
- export async function openTypeScript(root) {
159
+ export async function openTypeScript(root,
160
+ /**
161
+ * The tsconfig file in `root` (the framework's check:catch gate also reads its other
162
+ * programs); null: no project program, only the browser sources (with standalone options).
163
+ */
164
+ tsconfig = 'tsconfig.json',
165
+ /** D-0120: files outside the program read in a second program that extends `tsconfig`. */
166
+ browser = []) {
79
167
  const began = performance.now();
80
168
  const require = createRequire(join(root, 'package.json'));
81
169
  let sync;
@@ -99,48 +187,96 @@ export async function openTypeScript(root) {
99
187
  const anyFlags = (sync.TypeFlags?.Any ?? 0) | (sync.TypeFlags?.Unknown ?? 0);
100
188
  if (typeof sync.API !== 'function' || !anyFlags)
101
189
  throw unavailable(root, `TypeScript ${version} has no usable unstable/sync API`);
102
- const config = join(root, 'tsconfig.json');
190
+ const config = tsconfig === null ? undefined : join(root, tsconfig);
191
+ // The browser program (D-0120): a virtual tsconfig that extends the project's and lists only
192
+ // the virtual sources, so the project program stays exactly what npm run typecheck sees.
193
+ const browserConfig = browser.length ? slashes(join(root, BROWSER_TSCONFIG)) : undefined;
194
+ const virtual = new Map();
195
+ if (browserConfig) {
196
+ for (const source of browser)
197
+ virtual.set(slashes(source.fileName), source.text);
198
+ virtual.set(browserConfig, JSON.stringify({
199
+ ...(config && existsSync(config) ? { extends: `./${tsconfig}` } : {}),
200
+ compilerOptions: {
201
+ ...(config && existsSync(config) ? {} : STANDALONE_OPTIONS),
202
+ ...BROWSER_OPTIONS,
203
+ allowJs: true,
204
+ checkJs: false,
205
+ noEmit: true,
206
+ },
207
+ files: [...virtual.keys()],
208
+ include: [],
209
+ }));
210
+ }
103
211
  let api;
104
212
  try {
105
- api = new sync.API({ cwd: root });
213
+ api = new sync.API({
214
+ cwd: root,
215
+ // Only with browser sources: every file read then makes a round trip through this callback.
216
+ ...(virtual.size
217
+ ? {
218
+ fs: {
219
+ readFile: (name) => virtual.get(slashes(name)),
220
+ fileExists: (name) => (virtual.has(slashes(name)) ? true : undefined),
221
+ },
222
+ }
223
+ : {}),
224
+ });
106
225
  }
107
226
  catch (cause) {
108
227
  throw unavailable(root, 'Cannot start the TypeScript API (tsgo)', cause);
109
228
  }
229
+ const opened = [config, browserConfig].filter((item) => item !== undefined);
110
230
  let project;
231
+ let browserProject;
111
232
  try {
112
- project = api.updateSnapshot({ openProject: config }).getProject(config);
233
+ const snapshot = api.updateSnapshot({ openProjects: opened });
234
+ project = config ? snapshot.getProject(config) : undefined;
235
+ browserProject = browserConfig ? snapshot.getProject(browserConfig) : undefined;
113
236
  }
114
237
  catch (cause) {
115
238
  api.close();
116
- throw new CliError('TSCONFIG_INVALID', `TypeScript cannot load ${config}`, {
239
+ throw new CliError('TSCONFIG_INVALID', `TypeScript cannot load ${opened.join(', ')}`, {
117
240
  cause,
118
- details: { tsconfig: config },
241
+ details: { tsconfig: config ?? browserConfig },
119
242
  });
120
243
  }
121
- if (!project) {
244
+ if ((config && !project) || (browserConfig && !browserProject)) {
122
245
  api.close();
123
- throw new CliError('TSCONFIG_INVALID', `TypeScript has no project for ${config}`, {
124
- details: { tsconfig: config },
246
+ const missing = config && !project ? config : browserConfig;
247
+ throw new CliError('TSCONFIG_INVALID', `TypeScript has no project for ${missing}`, {
248
+ details: { tsconfig: missing },
125
249
  });
126
250
  }
127
251
  const prefix = root.endsWith(sep) ? root : root + sep;
128
252
  const modules = `${prefix}node_modules${sep}`;
129
253
  const files = [];
130
- for (const name of project.program.getSourceFileNames()) {
254
+ for (const name of project?.program.getSourceFileNames() ?? []) {
131
255
  const native = name.split('/').join(sep);
132
256
  if (!native.startsWith(prefix) || native.startsWith(modules))
133
257
  continue;
134
258
  if (!/\.(c|m)?tsx?$/.test(native) || /\.d\.(c|m)?ts$/.test(native))
135
259
  continue;
136
- const file = project.program.getSourceFile(name);
260
+ const file = project?.program.getSourceFile(name);
137
261
  if (file)
138
262
  files.push(file);
139
263
  }
264
+ const browserFiles = [];
265
+ for (const source of browser) {
266
+ const file = browserProject?.program.getSourceFile(slashes(source.fileName));
267
+ if (file)
268
+ browserFiles.push({ file, path: source.path });
269
+ else
270
+ debug('check: TypeScript did not load a browser source', { path: source.path });
271
+ }
140
272
  // The program loads lazily on the first file request, so the load ends here.
141
273
  const loadMs = Math.round(performance.now() - began);
142
- debug('check: TypeScript program loaded', { version, files: files.length, loadMs });
143
- const checker = project.checker;
274
+ debug('check: TypeScript program loaded', {
275
+ version,
276
+ files: files.length,
277
+ browser: browserFiles.length,
278
+ loadMs,
279
+ });
144
280
  // A Promise (or PromiseLike) — not any object with a `then`: Fastify's reply and query
145
281
  // builders are thenables whose methods return themselves, and leaving those alone is fine.
146
282
  const promiseType = (type, depth) => {
@@ -156,11 +292,21 @@ export async function openTypeScript(root) {
156
292
  kinds,
157
293
  files,
158
294
  loadMs,
159
- promises(expressions) {
160
- if (!expressions.length)
161
- return [];
295
+ browser: browserFiles,
296
+ promises(expressions, inBrowser = false) {
297
+ const checker = (inBrowser ? browserProject : project)?.checker;
298
+ if (!expressions.length || !checker)
299
+ return expressions.map(() => false);
162
300
  return checker.getTypeAtLocation(expressions).map((type) => promiseType(type, 0));
163
301
  },
302
+ untyped(nodes, inBrowser = false) {
303
+ const checker = (inBrowser ? browserProject : project)?.checker;
304
+ if (!nodes.length || !checker)
305
+ return nodes.map(() => true);
306
+ return checker
307
+ .getTypeAtLocation(nodes)
308
+ .map((type) => type === undefined || (type.flags & anyFlags) !== 0);
309
+ },
164
310
  close() {
165
311
  api.close();
166
312
  },
@@ -0,0 +1,2 @@
1
+ import type { Problem } from './problems.js';
2
+ export declare function viteConfigProblems(root: string, installed: ReadonlySet<string>): Promise<Problem[]>;
@@ -0,0 +1,136 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { debug, isMissing } from '../diagnostics.js';
4
+ import { CliError } from '../errors.js';
5
+ import { maskCode } from './form-rules.js';
6
+ import { readMarkers } from './source-rules.js';
7
+ const RULE = 'vite-dev-files';
8
+ const CONFIGS = ['vite.config.ts', 'vite.config.mts', 'vite.config.js', 'vite.config.mjs'];
9
+ /** Allow entries that widen the dev server to the root or to server code. */
10
+ const WIDE_ALLOW = /^\s*(?:(['"`])(?:\.|\.\/|\/|src|\.\/src|src\/apps|tests?|\.\/tests?|scripts|tools|\.\.|\.\.\/)\/?\1|process\.cwd\(\)|__dirname|import\.meta\.dirname|searchForWorkspaceRoot\([^)]*\))\s*$/;
11
+ /** Whether the project has an Inertia app (`src/apps/<app>/views/app.js`). */
12
+ async function hasInertiaApp(root) {
13
+ let entries;
14
+ try {
15
+ entries = await readdir(join(root, 'src/apps'), { withFileTypes: true });
16
+ }
17
+ catch (error) {
18
+ if (isMissing(error))
19
+ return false;
20
+ throw new CliError('READ_FAILED', 'Cannot read src/apps', { cause: error });
21
+ }
22
+ for (const entry of entries) {
23
+ if (!entry.isDirectory())
24
+ continue;
25
+ try {
26
+ await readFile(join(root, 'src/apps', entry.name, 'views/app.js'));
27
+ return true;
28
+ }
29
+ catch (error) {
30
+ if (!isMissing(error))
31
+ throw new CliError('READ_FAILED', `Cannot read src/apps/${entry.name}/views/app.js`, {
32
+ cause: error,
33
+ });
34
+ }
35
+ }
36
+ return false;
37
+ }
38
+ /** `[open, close]` of the `{ … }` that starts at `open` in masked code, or undefined. */
39
+ function block(code, open) {
40
+ let depth = 0;
41
+ for (let index = open; index < code.length; index++) {
42
+ const char = code[index];
43
+ if (char === '{' || char === '[' || char === '(')
44
+ depth++;
45
+ else if (char === '}' || char === ']' || char === ')') {
46
+ depth--;
47
+ if (depth === 0)
48
+ return [open, index];
49
+ }
50
+ }
51
+ return undefined;
52
+ }
53
+ /** The comma-separated items of the `[ … ]` at `open` (original text), with their offsets. */
54
+ function items(text, code, open) {
55
+ const range = block(code, open);
56
+ if (!range)
57
+ return [];
58
+ const found = [];
59
+ let depth = 0;
60
+ let start = open + 1;
61
+ for (let index = open; index <= range[1]; index++) {
62
+ const char = code[index];
63
+ if (char === '{' || char === '[' || char === '(')
64
+ depth++;
65
+ else if (char === '}' || char === ']' || char === ')')
66
+ depth--;
67
+ if ((char === ',' && depth === 1) || index === range[1]) {
68
+ const item = text.slice(start, index);
69
+ if (item.trim())
70
+ found.push([start, item]);
71
+ start = index + 1;
72
+ }
73
+ }
74
+ return found;
75
+ }
76
+ export async function viteConfigProblems(root, installed) {
77
+ if (!installed.has('dokebi-template-inertia') && !(await hasInertiaApp(root)))
78
+ return [];
79
+ let file;
80
+ let text = '';
81
+ for (const name of CONFIGS) {
82
+ try {
83
+ text = await readFile(join(root, name), 'utf8');
84
+ file = name;
85
+ break;
86
+ }
87
+ catch (error) {
88
+ if (!isMissing(error))
89
+ throw new CliError('READ_FAILED', `Cannot read ${name}`, { cause: error });
90
+ }
91
+ }
92
+ // No config: Vite runs with its defaults and the Vite host of dokebi dev adds the policy.
93
+ if (file === undefined)
94
+ return [];
95
+ const code = maskCode(text);
96
+ const lineAt = (offset) => text.slice(0, offset).split('\n').length;
97
+ const markers = readMarkers(text).filter((marker) => marker.rule === RULE);
98
+ const problems = [];
99
+ const report = (offset, message, fix) => {
100
+ const line = offset < 0 ? 0 : lineAt(offset);
101
+ const marker = markers.find((item) => item.line === line || item.line === line - 1);
102
+ if (line && marker?.reason)
103
+ return;
104
+ problems.push({
105
+ rule: RULE,
106
+ severity: 'warning',
107
+ file,
108
+ line,
109
+ message: `${message}${line && marker ? ' (the dokebi-check marker has no reason, so it does not count)' : ''}`,
110
+ fix,
111
+ });
112
+ };
113
+ if (!/\bdokebiInertia\s*\(/.test(code))
114
+ report(-1, `${file} does not use dokebiInertia() of @dokebi/template-inertia: a plain vite dev server then serves the whole project root (dokebi.yaml, .env, the server code), and pages bundle their own copy of the seal client. dokebi dev adds the file policy itself, other ways of running Vite do not`, "import { dokebiInertia } from '@dokebi/template-inertia/vite' and add it to plugins: [vue(), dokebiInertia()]");
115
+ for (const match of code.matchAll(/\bfs\s*:\s*\{/g)) {
116
+ const open = (match.index ?? 0) + match[0].length - 1;
117
+ const range = block(code, open);
118
+ if (!range)
119
+ continue;
120
+ const inner = code.slice(open, range[1]);
121
+ const strict = /\bstrict\s*:\s*false\b/.exec(inner);
122
+ if (strict)
123
+ report(open + strict.index, 'server.fs.strict: false asks the dev server to serve files outside its allow list (dokebiInertia() turns it on again and warns)', 'remove server.fs.strict; to serve another browser folder use dokebiInertia({ allow: [...] })');
124
+ const deny = /\bdeny\s*:/.exec(inner);
125
+ if (deny)
126
+ report(open + deny.index, "server.fs.deny of the config replaces Vite's default deny list (.env, keys, .git) wherever the framework's policy does not run; dokebiInertia() keeps dokebi.yaml, .env, .dokebi/, keys and compose files denied in any case", 'remove server.fs.deny and add your entries with dokebiInertia({ deny: [...] })');
127
+ }
128
+ for (const match of code.matchAll(/\ballow\s*:\s*\[/g)) {
129
+ const open = (match.index ?? 0) + match[0].length - 1;
130
+ for (const [offset, item] of items(text, code, open))
131
+ if (WIDE_ALLOW.test(item))
132
+ report(offset, `allow: [${item.trim()}] lets the dev server serve the project root or server code (src/ outside views/, tests/, scripts/); through a devHosts tunnel that is public`, 'allow only the folders the browser loads (the views folders are allowed already), for example dokebiInertia({ allow: ["src/shared/browser"] })');
133
+ }
134
+ debug('check: vite config read for the dev file rule', { file, problems: problems.length });
135
+ return problems;
136
+ }
@@ -0,0 +1,16 @@
1
+ import { type Kinds, type TsNode, type TsSourceFile } from './typescript.js';
2
+ export type Report = (node: TsNode, message: string, fix: string) => void;
3
+ export declare const WAIT_FIX = "bound the wait for a connection by its deadline instead (the pool option waitTimeoutMs: past it the caller gets 503 DB_UNAVAILABLE with Retry-After) and shed one tenant by the time its requests already hold, never by how many callers wait; a metric or log line that only reports the number takes // dokebi-check: allow wait-count-refusal <reason>";
4
+ export declare const QUEUE_FIX = "do not hold callers in a queue of your own: send the database work at once and let the pool bound the wait (waitTimeoutMs, then 503 DB_UNAVAILABLE with Retry-After), shedding one tenant by time it already holds; ordered background work goes to @dokebi/outbox. A deliberate one takes // dokebi-check: allow in-process-queue <reason>";
5
+ export type QueueReport = (node: TsNode, message: string, fix: string) => void;
6
+ /** Callers parked in a collection: the resolve of a `new Promise` executor stored to call later. */
7
+ export declare function queueRules(file: TsSourceFile, kinds: Kinds, report: QueueReport): void;
8
+ export declare function waitRules(file: TsSourceFile, kinds: Kinds, report: Report): void;
9
+ export declare const OWNER_CAP_FIX = "use the owner share of @dokebi/db instead of a count of your own: const release = context.getDependency(db).share(main).enter(owner) before the database work, release() after it; it refuses only while the pool is full (D-0156). By hand: refuse an owner only while the pool is full: admit every request while a connection is free (pg: max - pool.totalCount + pool.idleCount > 0) or the pool became full less than a connect time ago (connections being opened count as open), and when it is full refuse with 503 and Retry-After only an owner that already holds an equal share (max divided by the owners with requests in progress) (@dokebi/db AI.md 6.4, an owner's share). A budget that bounds something other than connections (the report CPU budget of @dokebi/db AI.md 6.9, a plan's quota) takes // dokebi-check: allow fixed-owner-cap <reason>";
10
+ /**
11
+ * A per-owner count of requests in progress compared with a fixed limit (AI-M37): a Map whose
12
+ * entries go up by one and down by one in the same file, read into a variable that an `if`
13
+ * compares with `>=` or `>` before a throw or a return, with no free or idle connection in the
14
+ * condition. It refuses work while the database is idle.
15
+ */
16
+ export declare function ownerCapRules(file: TsSourceFile, kinds: Kinds, report: Report): void;