@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.
- package/AI.md +279 -57
- package/README.md +35 -10
- package/catalog.json +2520 -258
- package/describe.schema.json +748 -0
- package/dist/api-app.d.ts +2 -0
- package/dist/api-app.js +35 -0
- package/dist/app-urls.js +3 -2
- package/dist/bin.js +638 -37
- package/dist/capability-guide.d.ts +30 -0
- package/dist/capability-guide.js +122 -0
- package/dist/catalog.d.ts +16 -2
- package/dist/catalog.js +19 -7
- package/dist/check/asset-rules.d.ts +6 -0
- package/dist/check/asset-rules.js +163 -0
- package/dist/check/browser-sources.d.ts +20 -0
- package/dist/check/browser-sources.js +89 -0
- package/dist/check/console-rules.d.ts +6 -0
- package/dist/check/console-rules.js +40 -0
- package/dist/check/error-rules.d.ts +21 -0
- package/dist/check/error-rules.js +212 -0
- package/dist/check/form-rules.d.ts +12 -0
- package/dist/check/form-rules.js +69 -6
- package/dist/check/formatter-rules.d.ts +2 -0
- package/dist/check/formatter-rules.js +71 -0
- package/dist/check/i18n-rules.d.ts +27 -0
- package/dist/check/i18n-rules.js +353 -0
- package/dist/check/index.d.ts +15 -1
- package/dist/check/index.js +89 -11
- package/dist/check/mail-rules.d.ts +4 -0
- package/dist/check/mail-rules.js +72 -0
- package/dist/check/module-rules.d.ts +13 -0
- package/dist/check/module-rules.js +104 -0
- package/dist/check/packages.d.ts +8 -0
- package/dist/check/packages.js +16 -0
- package/dist/check/pool-rules.d.ts +8 -0
- package/dist/check/pool-rules.js +164 -0
- package/dist/check/problems.d.ts +6 -1
- package/dist/check/problems.js +100 -2
- package/dist/check/project-rules.d.ts +23 -3
- package/dist/check/project-rules.js +265 -23
- package/dist/check/seal-rules.d.ts +6 -0
- package/dist/check/seal-rules.js +68 -0
- package/dist/check/security-rules.d.ts +10 -0
- package/dist/check/security-rules.js +450 -0
- package/dist/check/shared-state-rules.d.ts +15 -0
- package/dist/check/shared-state-rules.js +117 -0
- package/dist/check/source-rules.d.ts +26 -3
- package/dist/check/source-rules.js +249 -28
- package/dist/check/structure-rules.d.ts +23 -0
- package/dist/check/structure-rules.js +502 -0
- package/dist/check/timer-rules.d.ts +7 -0
- package/dist/check/timer-rules.js +543 -0
- package/dist/check/typescript.d.ts +35 -5
- package/dist/check/typescript.js +163 -17
- package/dist/check/vite-rules.d.ts +2 -0
- package/dist/check/vite-rules.js +136 -0
- package/dist/check/wait-rules.d.ts +16 -0
- package/dist/check/wait-rules.js +337 -0
- package/dist/commands.d.ts +7 -0
- package/dist/commands.js +381 -36
- package/dist/config-diff.d.ts +42 -0
- package/dist/config-diff.js +289 -0
- package/dist/config-edit.d.ts +35 -2
- package/dist/config-edit.js +145 -13
- package/dist/config-init.d.ts +15 -0
- package/dist/config-init.js +61 -0
- package/dist/config-paths.d.ts +22 -0
- package/dist/config-paths.js +82 -0
- package/dist/config-secrets.d.ts +46 -0
- package/dist/config-secrets.js +187 -0
- package/dist/config-show.d.ts +82 -0
- package/dist/config-show.js +563 -0
- package/dist/config-values.d.ts +8 -0
- package/dist/config-values.js +68 -0
- package/dist/configuration-scaffold.d.ts +42 -0
- package/dist/configuration-scaffold.js +258 -4
- package/dist/configuration.d.ts +63 -3
- package/dist/configuration.js +178 -4
- package/dist/deploy-scripts.d.ts +4 -0
- package/dist/deploy-scripts.js +361 -0
- package/dist/deploy-templates.d.ts +46 -0
- package/dist/deploy-templates.js +379 -0
- package/dist/deploy.d.ts +46 -0
- package/dist/deploy.js +592 -0
- package/dist/deployment.d.ts +2 -0
- package/dist/deployment.js +111 -0
- package/dist/describe-db.d.ts +22 -0
- package/dist/describe-db.js +176 -0
- package/dist/describe.d.ts +92 -0
- package/dist/describe.js +417 -0
- package/dist/dev-compose.d.ts +20 -0
- package/dist/dev-compose.js +154 -0
- package/dist/dev-guard.js +44 -0
- package/dist/dev-host.d.ts +13 -0
- package/dist/dev-host.js +55 -0
- package/dist/dev-infra.d.ts +72 -4
- package/dist/dev-infra.js +237 -17
- package/dist/dev-watch.d.ts +34 -0
- package/dist/dev-watch.js +192 -0
- package/dist/diagnostics.d.ts +10 -7
- package/dist/diagnostics.js +22 -4
- package/dist/drift.d.ts +50 -0
- package/dist/drift.js +218 -0
- package/dist/env-file.d.ts +25 -1
- package/dist/env-file.js +64 -6
- package/dist/errors.d.ts +7 -1
- package/dist/example-config.d.ts +2 -0
- package/dist/example-config.js +3 -0
- package/dist/first-admin.d.ts +43 -0
- package/dist/first-admin.js +175 -0
- package/dist/formatter-ignore.d.ts +37 -0
- package/dist/formatter-ignore.js +344 -0
- package/dist/generator.d.ts +45 -3
- package/dist/generator.js +205 -15
- package/dist/guide.d.ts +1 -1
- package/dist/guide.js +42 -21
- package/dist/install-module.d.ts +20 -2
- package/dist/install-module.js +45 -8
- package/dist/install-plan.js +5 -3
- package/dist/layout.d.ts +12 -1
- package/dist/layout.js +19 -6
- package/dist/log-writer-control.d.ts +2 -0
- package/dist/log-writer-control.js +17 -2
- package/dist/log-writer.js +43 -10
- package/dist/logging-config.d.ts +8 -3
- package/dist/logging-config.js +19 -5
- package/dist/manifest.d.ts +29 -1
- package/dist/manifest.js +84 -6
- package/dist/merge-install-defaults.d.ts +0 -1
- package/dist/merge-install-defaults.js +13 -47
- package/dist/npm-client.d.ts +40 -4
- package/dist/npm-client.js +152 -15
- package/dist/npm-paths.js +18 -4
- package/dist/openapi.d.ts +27 -0
- package/dist/openapi.js +649 -0
- package/dist/option-fixes.d.ts +39 -0
- package/dist/option-fixes.js +130 -0
- package/dist/output.d.ts +1 -1
- package/dist/output.js +5 -5
- package/dist/plugin-lifecycle.d.ts +34 -2
- package/dist/plugin-lifecycle.js +195 -16
- package/dist/plugin-options.d.ts +19 -4
- package/dist/plugin-options.js +138 -6
- package/dist/plugins-command.js +44 -7
- package/dist/process-group.d.ts +39 -0
- package/dist/process-group.js +132 -0
- package/dist/production-doctor.d.ts +3 -0
- package/dist/production-doctor.js +176 -0
- package/dist/project-guide.d.ts +19 -1
- package/dist/project-guide.js +119 -20
- package/dist/project-inspector.d.ts +15 -0
- package/dist/project-inspector.js +497 -33
- package/dist/project-state.d.ts +5 -0
- package/dist/project-state.js +8 -0
- package/dist/project.d.ts +203 -19
- package/dist/project.js +3006 -333
- package/dist/recipe-facts.d.ts +127 -0
- package/dist/recipe-facts.js +563 -0
- package/dist/recipe-login.d.ts +63 -0
- package/dist/recipe-login.js +464 -0
- package/dist/recipe-mail.d.ts +21 -0
- package/dist/recipe-mail.js +214 -0
- package/dist/recipe-members.d.ts +35 -0
- package/dist/recipe-members.js +178 -0
- package/dist/recipe-record.d.ts +82 -7
- package/dist/recipe-record.js +226 -22
- package/dist/recipe-reset.d.ts +2 -0
- package/dist/recipe-reset.js +117 -0
- package/dist/recipe-site.d.ts +48 -0
- package/dist/recipe-site.js +396 -0
- package/dist/recipe-step.d.ts +42 -0
- package/dist/recipe-step.js +89 -0
- package/dist/recipe.d.ts +56 -20
- package/dist/recipe.js +703 -131
- package/dist/registry.d.ts +98 -2
- package/dist/registry.js +171 -5
- package/dist/reloadable.d.ts +35 -0
- package/dist/reloadable.js +257 -0
- package/dist/remote-verify.d.ts +31 -0
- package/dist/remote-verify.js +204 -0
- package/dist/server.d.ts +43 -4
- package/dist/server.js +601 -52
- package/dist/settings.d.ts +48 -0
- package/dist/settings.js +168 -0
- package/dist/site-auth.d.ts +39 -0
- package/dist/site-auth.js +195 -0
- package/dist/template-fixes.d.ts +26 -0
- package/dist/template-fixes.js +285 -0
- package/dist/test-reporter.d.ts +49 -0
- package/dist/test-reporter.js +190 -0
- package/dist/transaction.d.ts +47 -0
- package/dist/transaction.js +241 -7
- package/dist/verify-tests.d.ts +98 -0
- package/dist/verify-tests.js +449 -0
- package/dist/verify.d.ts +103 -3
- package/dist/verify.js +272 -21
- package/dist/view-choice.d.ts +58 -0
- package/dist/view-choice.js +131 -0
- package/dist/vite-watch.js +4 -2
- package/package.json +8 -6
package/dist/check/typescript.js
CHANGED
|
@@ -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
|
-
'
|
|
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,
|
|
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({
|
|
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
|
-
|
|
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 ${
|
|
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
|
-
|
|
124
|
-
|
|
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
|
|
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
|
|
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', {
|
|
143
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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,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;
|