@rudderjs/cli 4.3.0 → 4.5.0
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/dist/commands/add.d.ts +42 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +475 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/doctor.d.ts +12 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +83 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/remove.d.ts +24 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.js +170 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/doctor/boot-status.d.ts +10 -0
- package/dist/doctor/boot-status.d.ts.map +1 -0
- package/dist/doctor/boot-status.js +15 -0
- package/dist/doctor/boot-status.js.map +1 -0
- package/dist/doctor/built-in/_fs.d.ts +11 -0
- package/dist/doctor/built-in/_fs.d.ts.map +1 -0
- package/dist/doctor/built-in/_fs.js +53 -0
- package/dist/doctor/built-in/_fs.js.map +1 -0
- package/dist/doctor/built-in/deps.d.ts +2 -0
- package/dist/doctor/built-in/deps.d.ts.map +1 -0
- package/dist/doctor/built-in/deps.js +81 -0
- package/dist/doctor/built-in/deps.js.map +1 -0
- package/dist/doctor/built-in/env-vars.d.ts +2 -0
- package/dist/doctor/built-in/env-vars.d.ts.map +1 -0
- package/dist/doctor/built-in/env-vars.js +99 -0
- package/dist/doctor/built-in/env-vars.js.map +1 -0
- package/dist/doctor/built-in/index.d.ts +13 -0
- package/dist/doctor/built-in/index.d.ts.map +1 -0
- package/dist/doctor/built-in/index.js +18 -0
- package/dist/doctor/built-in/index.js.map +1 -0
- package/dist/doctor/built-in/node-version.d.ts +2 -0
- package/dist/doctor/built-in/node-version.d.ts.map +1 -0
- package/dist/doctor/built-in/node-version.js +72 -0
- package/dist/doctor/built-in/node-version.js.map +1 -0
- package/dist/doctor/built-in/package-manager.d.ts +2 -0
- package/dist/doctor/built-in/package-manager.d.ts.map +1 -0
- package/dist/doctor/built-in/package-manager.js +55 -0
- package/dist/doctor/built-in/package-manager.js.map +1 -0
- package/dist/doctor/built-in/runtime.d.ts +2 -0
- package/dist/doctor/built-in/runtime.d.ts.map +1 -0
- package/dist/doctor/built-in/runtime.js +82 -0
- package/dist/doctor/built-in/runtime.js.map +1 -0
- package/dist/doctor/built-in/structure.d.ts +2 -0
- package/dist/doctor/built-in/structure.d.ts.map +1 -0
- package/dist/doctor/built-in/structure.js +93 -0
- package/dist/doctor/built-in/structure.js.map +1 -0
- package/dist/doctor/fixer.d.ts +39 -0
- package/dist/doctor/fixer.d.ts.map +1 -0
- package/dist/doctor/fixer.js +80 -0
- package/dist/doctor/fixer.js.map +1 -0
- package/dist/doctor/load-package-checks.d.ts +2 -0
- package/dist/doctor/load-package-checks.d.ts.map +1 -0
- package/dist/doctor/load-package-checks.js +53 -0
- package/dist/doctor/load-package-checks.js.map +1 -0
- package/dist/doctor/orchestrator.d.ts +34 -0
- package/dist/doctor/orchestrator.d.ts.map +1 -0
- package/dist/doctor/orchestrator.js +65 -0
- package/dist/doctor/orchestrator.js.map +1 -0
- package/dist/doctor/reporter.d.ts +21 -0
- package/dist/doctor/reporter.d.ts.map +1 -0
- package/dist/doctor/reporter.js +151 -0
- package/dist/doctor/reporter.js.map +1 -0
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { registerDoctorCheck } from '@rudderjs/console';
|
|
2
|
+
import { readJsonSafe } from './_fs.js';
|
|
3
|
+
/**
|
|
4
|
+
* Loose semver `Major.Minor.Patch` parser — good enough to compare against a
|
|
5
|
+
* package.json `engines.node` range like `^20.19.0 || >=22.12.0`.
|
|
6
|
+
*
|
|
7
|
+
* We don't pull in `semver` because the CLI ships with the framework and
|
|
8
|
+
* pulling a 3rd-party parser for one comparison is wasteful. The range syntax
|
|
9
|
+
* we accept matches what create-rudder-app generates.
|
|
10
|
+
*/
|
|
11
|
+
function parseVersion(v) {
|
|
12
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(v);
|
|
13
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
14
|
+
}
|
|
15
|
+
function cmp(a, b) {
|
|
16
|
+
for (let i = 0; i < 3; i++) {
|
|
17
|
+
const ai = a[i], bi = b[i];
|
|
18
|
+
if (ai !== bi)
|
|
19
|
+
return ai - bi;
|
|
20
|
+
}
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
function matchesRange(version, range) {
|
|
24
|
+
// Handle `||` alternation
|
|
25
|
+
return range.split('||').some(part => matchesSingle(version, part.trim()));
|
|
26
|
+
}
|
|
27
|
+
function matchesSingle(version, range) {
|
|
28
|
+
// ^X.Y.Z — same major, >= X.Y.Z
|
|
29
|
+
let m = /^\^(\d+)\.(\d+)\.(\d+)$/.exec(range);
|
|
30
|
+
if (m) {
|
|
31
|
+
const target = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
32
|
+
return version[0] === target[0] && cmp(version, target) >= 0;
|
|
33
|
+
}
|
|
34
|
+
// >=X.Y.Z
|
|
35
|
+
m = /^>=\s*(\d+)\.(\d+)\.(\d+)$/.exec(range);
|
|
36
|
+
if (m) {
|
|
37
|
+
const target = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
38
|
+
return cmp(version, target) >= 0;
|
|
39
|
+
}
|
|
40
|
+
// Exact `X.Y.Z`
|
|
41
|
+
m = /^=?\s*(\d+)\.(\d+)\.(\d+)$/.exec(range);
|
|
42
|
+
if (m) {
|
|
43
|
+
const target = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
44
|
+
return cmp(version, target) === 0;
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
registerDoctorCheck({
|
|
49
|
+
id: 'env:node-version',
|
|
50
|
+
category: 'env',
|
|
51
|
+
title: 'Node version',
|
|
52
|
+
run() {
|
|
53
|
+
const pkg = readJsonSafe('package.json');
|
|
54
|
+
const range = pkg?.engines?.node;
|
|
55
|
+
const current = parseVersion(process.version);
|
|
56
|
+
if (!current) {
|
|
57
|
+
return { status: 'warn', message: `couldn't parse Node version "${process.version}"` };
|
|
58
|
+
}
|
|
59
|
+
if (!range) {
|
|
60
|
+
return { status: 'ok', message: `${process.version} (no engines.node constraint)` };
|
|
61
|
+
}
|
|
62
|
+
if (matchesRange(current, range)) {
|
|
63
|
+
return { status: 'ok', message: `${process.version} (matches ${range})` };
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
status: 'error',
|
|
67
|
+
message: `${process.version} does not satisfy ${range}`,
|
|
68
|
+
fix: `Install a Node version matching ${range} (use \`nvm install\`, fnm, or asdf)`,
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
//# sourceMappingURL=node-version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-version.js","sourceRoot":"","sources":["../../../src/doctor/built-in/node-version.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-manager.d.ts","sourceRoot":"","sources":["../../../src/doctor/built-in/package-manager.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { registerDoctorCheck } from '@rudderjs/console';
|
|
2
|
+
import { fileExists } from './_fs.js';
|
|
3
|
+
function detect() {
|
|
4
|
+
const lockMap = [
|
|
5
|
+
['pnpm-lock.yaml', 'pnpm'],
|
|
6
|
+
['package-lock.json', 'npm'],
|
|
7
|
+
['yarn.lock', 'yarn'],
|
|
8
|
+
['bun.lockb', 'bun'],
|
|
9
|
+
['bun.lock', 'bun'],
|
|
10
|
+
];
|
|
11
|
+
const lockfiles = lockMap.filter(([f]) => fileExists(f)).map(([f]) => f);
|
|
12
|
+
const fromLockfile = lockfiles.length === 1
|
|
13
|
+
? lockMap.find(([f]) => f === lockfiles[0])[1]
|
|
14
|
+
: null;
|
|
15
|
+
const ua = process.env['npm_config_user_agent'] ?? '';
|
|
16
|
+
const fromUserAgent = ua.startsWith('pnpm') ? 'pnpm' :
|
|
17
|
+
ua.startsWith('yarn') ? 'yarn' :
|
|
18
|
+
ua.startsWith('bun') ? 'bun' :
|
|
19
|
+
ua.startsWith('npm') ? 'npm' :
|
|
20
|
+
null;
|
|
21
|
+
return { fromLockfile, fromUserAgent, lockfiles };
|
|
22
|
+
}
|
|
23
|
+
registerDoctorCheck({
|
|
24
|
+
id: 'env:package-manager',
|
|
25
|
+
category: 'env',
|
|
26
|
+
title: 'Package manager',
|
|
27
|
+
run() {
|
|
28
|
+
const d = detect();
|
|
29
|
+
if (d.lockfiles.length === 0) {
|
|
30
|
+
return {
|
|
31
|
+
status: 'error',
|
|
32
|
+
message: 'no lockfile found',
|
|
33
|
+
fix: 'Run your package manager install (e.g. `pnpm install`, `npm install`, `yarn install`)',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (d.lockfiles.length > 1) {
|
|
37
|
+
return {
|
|
38
|
+
status: 'warn',
|
|
39
|
+
message: `multiple lockfiles present: ${d.lockfiles.join(', ')}`,
|
|
40
|
+
fix: `Pick one PM and delete the others — mixed lockfiles cause flaky installs`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const pm = d.fromLockfile;
|
|
44
|
+
const ua = d.fromUserAgent;
|
|
45
|
+
if (ua && ua !== pm) {
|
|
46
|
+
return {
|
|
47
|
+
status: 'warn',
|
|
48
|
+
message: `lockfile is ${pm} but ran with ${ua}`,
|
|
49
|
+
fix: `Run with ${pm} (\`${pm} install\`, \`${pm} <script>\`) to match the lockfile`,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return { status: 'ok', message: `${pm} — lockfile present` };
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
//# sourceMappingURL=package-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-manager.js","sourceRoot":"","sources":["../../../src/doctor/built-in/package-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAqB,MAAM,mBAAmB,CAAA;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAWrC,SAAS,MAAM;IACb,MAAM,OAAO,GAAqD;QAChE,CAAC,gBAAgB,EAAM,MAAM,CAAC;QAC9B,CAAC,mBAAmB,EAAG,KAAK,CAAC;QAC7B,CAAC,WAAW,EAAW,MAAM,CAAC;QAC9B,CAAC,WAAW,EAAW,KAAK,CAAC;QAC7B,CAAC,UAAU,EAAY,KAAK,CAAC;KAC9B,CAAA;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IACxE,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,KAAK,CAAC;QACzC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC;QAC/C,CAAC,CAAC,IAAI,CAAA;IAER,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,IAAI,EAAE,CAAA;IACrD,MAAM,aAAa,GACjB,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAChC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YAChC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,CAAE,CAAC,CAAC,KAAK,CAAC,CAAC;gBAC/B,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,CAAE,CAAC,CAAC,KAAK,CAAC,CAAC;oBAC/B,IAAI,CAAA;IAEN,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,CAAA;AACnD,CAAC;AAED,mBAAmB,CAAC;IAClB,EAAE,EAAQ,qBAAqB;IAC/B,QAAQ,EAAE,KAAK;IACf,KAAK,EAAK,iBAAiB;IAC3B,GAAG;QACD,MAAM,CAAC,GAAG,MAAM,EAAE,CAAA;QAClB,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO;gBACL,MAAM,EAAG,OAAO;gBAChB,OAAO,EAAE,mBAAmB;gBAC5B,GAAG,EAAM,uFAAuF;aACjG,CAAA;QACH,CAAC;QACD,IAAI,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO;gBACL,MAAM,EAAG,MAAM;gBACf,OAAO,EAAE,+BAA+B,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBAChE,GAAG,EAAM,0EAA0E;aACpF,CAAA;QACH,CAAC;QACD,MAAM,EAAE,GAAG,CAAC,CAAC,YAAa,CAAA;QAC1B,MAAM,EAAE,GAAG,CAAC,CAAC,aAAa,CAAA;QAC1B,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACpB,OAAO;gBACL,MAAM,EAAG,MAAM;gBACf,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,EAAE;gBAC/C,GAAG,EAAM,YAAY,EAAE,OAAO,EAAE,iBAAiB,EAAE,oCAAoC;aACxF,CAAA;QACH,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,qBAAqB,EAAE,CAAA;IAC9D,CAAC;CACF,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/doctor/built-in/runtime.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import net from 'node:net';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import { registerDoctorCheck } from '@rudderjs/console';
|
|
4
|
+
import { getBootStatus } from '../boot-status.js';
|
|
5
|
+
// ─── runtime:app-boot ─────────────────────────────────────
|
|
6
|
+
//
|
|
7
|
+
// The boot itself happens in the doctor command's --deep handler (BEFORE
|
|
8
|
+
// runChecks runs); the check's only job is to surface the captured status.
|
|
9
|
+
// This indirection means a boot crash doesn't blow up the orchestrator —
|
|
10
|
+
// every subsequent runtime check still gets a chance to render, and we can
|
|
11
|
+
// recognize "boot didn't happen" (status = null) as a "should never see"
|
|
12
|
+
// signal (it'd mean the doctor command was invoked without --deep but
|
|
13
|
+
// someone left needsBoot on a check by mistake).
|
|
14
|
+
registerDoctorCheck({
|
|
15
|
+
id: 'runtime:app-boot',
|
|
16
|
+
category: 'runtime',
|
|
17
|
+
title: 'Application bootstrap',
|
|
18
|
+
needsBoot: true,
|
|
19
|
+
run() {
|
|
20
|
+
const status = getBootStatus();
|
|
21
|
+
if (!status) {
|
|
22
|
+
return {
|
|
23
|
+
status: 'warn',
|
|
24
|
+
message: 'boot was not attempted (internal — should not see this)',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
if (status.ok) {
|
|
28
|
+
return { status: 'ok', message: `booted in ${status.durationMs.toFixed(0)}ms` };
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
status: 'error',
|
|
32
|
+
message: `boot threw: ${(status.error ?? '').split('\n')[0]}`,
|
|
33
|
+
detail: status.error ?? '',
|
|
34
|
+
fix: 'Read the stack trace above — usually a missing env var, an unreachable service (DB / Redis / SMTP), or a provider whose dependency hasn\'t been registered. Run without `--deep` to see env/deps checks first.',
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
// ─── runtime:port-free ────────────────────────────────────
|
|
39
|
+
function portInUseHolder(port) {
|
|
40
|
+
// `lsof` is preinstalled on macOS / most Linux. The Windows equivalent is
|
|
41
|
+
// `netstat -ano | findstr :3000` — we don't shell out there since this is
|
|
42
|
+
// a best-effort hint and Windows users have less to gain from a PID.
|
|
43
|
+
if (process.platform === 'win32')
|
|
44
|
+
return null;
|
|
45
|
+
try {
|
|
46
|
+
const out = execSync(`lsof -ti :${port}`, { stdio: ['ignore', 'pipe', 'ignore'], timeout: 1000 });
|
|
47
|
+
const pid = out.toString().trim().split('\n')[0];
|
|
48
|
+
return pid || null;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
registerDoctorCheck({
|
|
55
|
+
id: 'runtime:port-free',
|
|
56
|
+
category: 'runtime',
|
|
57
|
+
title: 'PORT free',
|
|
58
|
+
needsBoot: true,
|
|
59
|
+
async run() {
|
|
60
|
+
const port = Number(process.env['PORT'] ?? 3000);
|
|
61
|
+
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
62
|
+
return { status: 'warn', message: `PORT="${process.env['PORT']}" is not a valid number` };
|
|
63
|
+
}
|
|
64
|
+
const bound = await new Promise((resolve) => {
|
|
65
|
+
const server = net.createServer();
|
|
66
|
+
server.once('error', (err) => resolve({ ok: false, code: err.code ?? 'UNKNOWN' }));
|
|
67
|
+
server.once('listening', () => server.close(() => resolve({ ok: true })));
|
|
68
|
+
server.listen(port, '127.0.0.1');
|
|
69
|
+
});
|
|
70
|
+
if (bound.ok)
|
|
71
|
+
return { status: 'ok', message: `${port} available` };
|
|
72
|
+
if (bound.code === 'EADDRINUSE') {
|
|
73
|
+
const pid = portInUseHolder(port);
|
|
74
|
+
const fix = pid
|
|
75
|
+
? `Stop the process: \`kill ${pid}\` (or set PORT=<other> in .env)`
|
|
76
|
+
: `Set PORT=<other> in .env, or free port ${port}`;
|
|
77
|
+
return { status: 'error', message: `${port} in use${pid ? ` (PID ${pid})` : ''}`, fix };
|
|
78
|
+
}
|
|
79
|
+
return { status: 'warn', message: `bind failed: ${bound.code}` };
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/doctor/built-in/runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structure.d.ts","sourceRoot":"","sources":["../../../src/doctor/built-in/structure.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { registerDoctorCheck } from '@rudderjs/console';
|
|
2
|
+
import { fileExists, readFileSafe, anyExists } from './_fs.js';
|
|
3
|
+
registerDoctorCheck({
|
|
4
|
+
id: 'structure:bootstrap-app',
|
|
5
|
+
category: 'structure',
|
|
6
|
+
title: 'bootstrap/app.ts',
|
|
7
|
+
run() {
|
|
8
|
+
if (!fileExists('bootstrap/app.ts')) {
|
|
9
|
+
return {
|
|
10
|
+
status: 'error',
|
|
11
|
+
message: 'missing',
|
|
12
|
+
fix: 'Scaffold a fresh app via `pnpm create rudder@latest`, or write bootstrap/app.ts using Application.configure({...}).create()',
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
// Lexical-parse only — we deliberately don't import the module here
|
|
16
|
+
// (it boots providers and would defeat the skip-boot fast path).
|
|
17
|
+
const text = readFileSafe('bootstrap/app.ts') ?? '';
|
|
18
|
+
if (!/Application\.configure/.test(text)) {
|
|
19
|
+
return {
|
|
20
|
+
status: 'warn',
|
|
21
|
+
message: 'present but does not call Application.configure(…)',
|
|
22
|
+
fix: 'See docs/guide/bootstrap.md for the expected shape',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return { status: 'ok', message: 'parses' };
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
registerDoctorCheck({
|
|
29
|
+
id: 'structure:bootstrap-providers',
|
|
30
|
+
category: 'structure',
|
|
31
|
+
title: 'bootstrap/providers.ts',
|
|
32
|
+
run() {
|
|
33
|
+
if (!fileExists('bootstrap/providers.ts')) {
|
|
34
|
+
return {
|
|
35
|
+
status: 'error',
|
|
36
|
+
message: 'missing',
|
|
37
|
+
fix: 'Create bootstrap/providers.ts with `export default [...(await defaultProviders())]`',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const text = readFileSafe('bootstrap/providers.ts') ?? '';
|
|
41
|
+
if (!/export\s+default/.test(text)) {
|
|
42
|
+
return {
|
|
43
|
+
status: 'warn',
|
|
44
|
+
message: 'present but has no default export',
|
|
45
|
+
fix: 'bootstrap/providers.ts must `export default [...]`',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return { status: 'ok', message: 'has default export' };
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
registerDoctorCheck({
|
|
52
|
+
id: 'structure:routes',
|
|
53
|
+
category: 'structure',
|
|
54
|
+
title: 'routes/*',
|
|
55
|
+
run() {
|
|
56
|
+
const have = ['routes/web.ts', 'routes/api.ts', 'routes/console.ts'].filter(fileExists);
|
|
57
|
+
if (have.length === 0) {
|
|
58
|
+
return {
|
|
59
|
+
status: 'error',
|
|
60
|
+
message: 'no routes/* files found',
|
|
61
|
+
fix: 'Create at least one of routes/web.ts, routes/api.ts',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { status: 'ok', message: have.join(', ') };
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
registerDoctorCheck({
|
|
68
|
+
id: 'structure:welcome-view',
|
|
69
|
+
category: 'structure',
|
|
70
|
+
title: 'Welcome view / index page',
|
|
71
|
+
run() {
|
|
72
|
+
// The scaffolder ships one of these depending on the recipe:
|
|
73
|
+
// - Controller-view mode: app/Views/Welcome.* (with `export const route = '/'`)
|
|
74
|
+
// - Vike-direct mode: pages/index/+Page.*
|
|
75
|
+
const viewCandidates = [
|
|
76
|
+
'app/Views/Welcome.tsx', 'app/Views/Welcome.vue', 'app/Views/Welcome.jsx',
|
|
77
|
+
'app/Views/Welcome.ts', 'app/Views/Welcome.js', 'app/Views/Welcome.html',
|
|
78
|
+
];
|
|
79
|
+
const pageCandidates = [
|
|
80
|
+
'pages/index/+Page.tsx', 'pages/index/+Page.vue', 'pages/index/+Page.jsx',
|
|
81
|
+
'pages/index/+Page.ts',
|
|
82
|
+
];
|
|
83
|
+
if (anyExists(viewCandidates) || anyExists(pageCandidates)) {
|
|
84
|
+
return { status: 'ok', message: 'found' };
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
status: 'warn',
|
|
88
|
+
message: 'no landing page found',
|
|
89
|
+
fix: 'Add app/Views/Welcome.tsx (with `export const route = \'/\'`) or pages/index/+Page.tsx',
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=structure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structure.js","sourceRoot":"","sources":["../../../src/doctor/built-in/structure.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DoctorCheck, DoctorResult } from '@rudderjs/console';
|
|
2
|
+
import type { CheckOutcome } from './orchestrator.js';
|
|
3
|
+
export interface FixOutcome {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
/** Status of the check *before* the fixer ran. */
|
|
7
|
+
before: CheckOutcome['status'];
|
|
8
|
+
/** Status of the check *after* the fixer ran. */
|
|
9
|
+
after: DoctorResult['status'];
|
|
10
|
+
/** Did the user skip (declined the prompt)? */
|
|
11
|
+
skipped: boolean;
|
|
12
|
+
/** Fixer's own message ("ran prisma generate", "vendored 4 files", …). */
|
|
13
|
+
message: string;
|
|
14
|
+
/** If the fixer itself threw, the captured error. */
|
|
15
|
+
error?: string;
|
|
16
|
+
durationMs: number;
|
|
17
|
+
}
|
|
18
|
+
export interface FixOptions {
|
|
19
|
+
/** Skip prompts — assume yes. */
|
|
20
|
+
yes?: boolean;
|
|
21
|
+
/** Override the prompt for tests. Returns true → run, false → skip. */
|
|
22
|
+
prompt?: (check: DoctorCheck, outcome: CheckOutcome) => Promise<boolean> | boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface FixResult {
|
|
25
|
+
outcomes: FixOutcome[];
|
|
26
|
+
/** How many fixers were eligible (failing + has fixer). */
|
|
27
|
+
eligible: number;
|
|
28
|
+
/** How many fixers actually ran (not skipped). */
|
|
29
|
+
applied: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Iterate outcomes from a fast-path doctor run, and for every failing check
|
|
33
|
+
* that declares a `fixer()`, prompt the user (unless `yes` is set) and run it.
|
|
34
|
+
*
|
|
35
|
+
* Fixers must be idempotent regenerate-style operations — a fixer that throws
|
|
36
|
+
* is caught and reported as a red fix outcome; doctor itself never crashes.
|
|
37
|
+
*/
|
|
38
|
+
export declare function applyFixes(outcomes: CheckOutcome[], opts?: FixOptions): Promise<FixResult>;
|
|
39
|
+
//# sourceMappingURL=fixer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixer.d.ts","sourceRoot":"","sources":["../../src/doctor/fixer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAQ,MAAM,CAAA;IAChB,KAAK,EAAK,MAAM,CAAA;IAChB,kDAAkD;IAClD,MAAM,EAAI,YAAY,CAAC,QAAQ,CAAC,CAAA;IAChC,iDAAiD;IACjD,KAAK,EAAK,YAAY,CAAC,QAAQ,CAAC,CAAA;IAChC,+CAA+C;IAC/C,OAAO,EAAG,OAAO,CAAA;IACjB,0EAA0E;IAC1E,OAAO,EAAG,MAAM,CAAA;IAChB,qDAAqD;IACrD,KAAK,CAAC,EAAI,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,iCAAiC;IACjC,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,uEAAuE;IACvE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;CACnF;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAG,UAAU,EAAE,CAAA;IACvB,2DAA2D;IAC3D,QAAQ,EAAG,MAAM,CAAA;IACjB,kDAAkD;IAClD,OAAO,EAAI,MAAM,CAAA;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,YAAY,EAAE,EACxB,IAAI,GAAM,UAAe,GACxB,OAAO,CAAC,SAAS,CAAC,CA2DpB"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getRegisteredChecks } from '@rudderjs/console';
|
|
2
|
+
/**
|
|
3
|
+
* Iterate outcomes from a fast-path doctor run, and for every failing check
|
|
4
|
+
* that declares a `fixer()`, prompt the user (unless `yes` is set) and run it.
|
|
5
|
+
*
|
|
6
|
+
* Fixers must be idempotent regenerate-style operations — a fixer that throws
|
|
7
|
+
* is caught and reported as a red fix outcome; doctor itself never crashes.
|
|
8
|
+
*/
|
|
9
|
+
export async function applyFixes(outcomes, opts = {}) {
|
|
10
|
+
// Build an id → check map so we can find each fixer by outcome id.
|
|
11
|
+
const byId = new Map();
|
|
12
|
+
for (const c of getRegisteredChecks())
|
|
13
|
+
byId.set(c.id, c);
|
|
14
|
+
// Eligible = failed (warn|error) AND check declares a fixer.
|
|
15
|
+
const eligible = outcomes.filter(o => o.status !== 'ok' && byId.get(o.id)?.fixer);
|
|
16
|
+
if (eligible.length === 0) {
|
|
17
|
+
return { outcomes: [], eligible: 0, applied: 0 };
|
|
18
|
+
}
|
|
19
|
+
const promptFn = opts.prompt ?? (opts.yes ? () => true : defaultPrompt);
|
|
20
|
+
const fixOutcomes = [];
|
|
21
|
+
let applied = 0;
|
|
22
|
+
for (const outcome of eligible) {
|
|
23
|
+
const check = byId.get(outcome.id);
|
|
24
|
+
const fixer = check.fixer;
|
|
25
|
+
const accepted = await promptFn(check, outcome);
|
|
26
|
+
if (!accepted) {
|
|
27
|
+
fixOutcomes.push({
|
|
28
|
+
id: outcome.id,
|
|
29
|
+
title: outcome.title,
|
|
30
|
+
before: outcome.status,
|
|
31
|
+
after: outcome.status,
|
|
32
|
+
skipped: true,
|
|
33
|
+
message: 'skipped',
|
|
34
|
+
durationMs: 0,
|
|
35
|
+
});
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const t0 = performance.now();
|
|
39
|
+
let result;
|
|
40
|
+
let error;
|
|
41
|
+
try {
|
|
42
|
+
result = await fixer();
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
46
|
+
result = { status: 'error', message: `fixer threw: ${msg}` };
|
|
47
|
+
error = msg;
|
|
48
|
+
}
|
|
49
|
+
applied++;
|
|
50
|
+
const fo = {
|
|
51
|
+
id: outcome.id,
|
|
52
|
+
title: outcome.title,
|
|
53
|
+
before: outcome.status,
|
|
54
|
+
after: result.status,
|
|
55
|
+
skipped: false,
|
|
56
|
+
message: result.message,
|
|
57
|
+
durationMs: performance.now() - t0,
|
|
58
|
+
};
|
|
59
|
+
if (error)
|
|
60
|
+
fo.error = error;
|
|
61
|
+
fixOutcomes.push(fo);
|
|
62
|
+
}
|
|
63
|
+
return { outcomes: fixOutcomes, eligible: eligible.length, applied };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Default interactive prompt — uses @clack/prompts. Lazy-imported so the
|
|
67
|
+
* test path can pass `prompt: () => true/false` without pulling clack into
|
|
68
|
+
* the test process at all.
|
|
69
|
+
*/
|
|
70
|
+
async function defaultPrompt(check, outcome) {
|
|
71
|
+
const { confirm, isCancel } = await import('@clack/prompts');
|
|
72
|
+
const result = await confirm({
|
|
73
|
+
message: `Apply fix for ${check.title}? (${outcome.message})`,
|
|
74
|
+
initialValue: true,
|
|
75
|
+
});
|
|
76
|
+
if (isCancel(result))
|
|
77
|
+
return false;
|
|
78
|
+
return result === true;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=fixer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixer.js","sourceRoot":"","sources":["../../src/doctor/fixer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAkCvD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,QAAwB,EACxB,OAAuB,EAAE;IAEzB,mEAAmE;IACnE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuB,CAAA;IAC3C,KAAK,MAAM,CAAC,IAAI,mBAAmB,EAAE;QAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;IAExD,6DAA6D;IAC7D,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAA;IAEjF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAA;IAClD,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAA;IACvE,MAAM,WAAW,GAAiB,EAAE,CAAA;IACpC,IAAI,OAAO,GAAG,CAAC,CAAA;IAEf,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAE,CAAA;QACnC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAM,CAAA;QAE1B,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QAC/C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,WAAW,CAAC,IAAI,CAAC;gBACf,EAAE,EAAU,OAAO,CAAC,EAAE;gBACtB,KAAK,EAAO,OAAO,CAAC,KAAK;gBACzB,MAAM,EAAM,OAAO,CAAC,MAAM;gBAC1B,KAAK,EAAO,OAAO,CAAC,MAAM;gBAC1B,OAAO,EAAK,IAAI;gBAChB,OAAO,EAAK,SAAS;gBACrB,UAAU,EAAE,CAAC;aACd,CAAC,CAAA;YACF,SAAQ;QACV,CAAC;QAED,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,CAAA;QAC5B,IAAI,MAAoB,CAAA;QACxB,IAAI,KAAyB,CAAA;QAC7B,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,KAAK,EAAE,CAAA;QACxB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;YACtD,MAAM,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,GAAG,EAAE,EAAE,CAAA;YAC5D,KAAK,GAAI,GAAG,CAAA;QACd,CAAC;QACD,OAAO,EAAE,CAAA;QACT,MAAM,EAAE,GAAe;YACrB,EAAE,EAAU,OAAO,CAAC,EAAE;YACtB,KAAK,EAAO,OAAO,CAAC,KAAK;YACzB,MAAM,EAAM,OAAO,CAAC,MAAM;YAC1B,KAAK,EAAO,MAAM,CAAC,MAAM;YACzB,OAAO,EAAK,KAAK;YACjB,OAAO,EAAK,MAAM,CAAC,OAAO;YAC1B,UAAU,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE;SACnC,CAAA;QACD,IAAI,KAAK;YAAE,EAAE,CAAC,KAAK,GAAG,KAAK,CAAA;QAC3B,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACtB,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,CAAA;AACtE,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,aAAa,CAAC,KAAkB,EAAE,OAAqB;IACpE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAA;IAC5D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC;QAC3B,OAAO,EAAO,iBAAiB,KAAK,CAAC,KAAK,MAAM,OAAO,CAAC,OAAO,GAAG;QAClE,YAAY,EAAE,IAAI;KACnB,CAAC,CAAA;IACF,IAAI,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IAClC,OAAO,MAAM,KAAK,IAAI,CAAA;AACxB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-package-checks.d.ts","sourceRoot":"","sources":["../../src/doctor/load-package-checks.ts"],"names":[],"mappings":"AAkCA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAgBvD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy-load doctor checks from installed framework packages.
|
|
3
|
+
*
|
|
4
|
+
* Only invoked when `rudder doctor` runs — checks are useless outside the
|
|
5
|
+
* doctor command, so paying the import cost on every CLI invocation would
|
|
6
|
+
* be wasteful.
|
|
7
|
+
*
|
|
8
|
+
* Each contributing package exports a `<package>/doctor` subpath whose
|
|
9
|
+
* import has the side effect of calling `registerDoctorCheck()` for its
|
|
10
|
+
* rules. Adding a new contributing package: append to PACKAGES_WITH_CHECKS
|
|
11
|
+
* AND ensure the package's `package.json#exports` declares the subpath.
|
|
12
|
+
*
|
|
13
|
+
* Resolution: imports resolve from `process.cwd()`, NOT from this file's
|
|
14
|
+
* location. The cli doesn't declare these packages as dependencies — the
|
|
15
|
+
* USER's app does, and pnpm's strict mode means cli's node_modules doesn't
|
|
16
|
+
* see them. createRequire'ing from the user's package.json fixes that.
|
|
17
|
+
*/
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { pathToFileURL } from 'node:url';
|
|
21
|
+
const PACKAGES_WITH_CHECKS = [
|
|
22
|
+
// Phase 3 first wave — every package here ships a `<package>/doctor`
|
|
23
|
+
// subpath whose side-effect import calls `registerDoctorCheck()` for its
|
|
24
|
+
// rules. The dynamic import is `tryImport`-wrapped below, so packages not
|
|
25
|
+
// installed in the user's app are silently skipped.
|
|
26
|
+
'@rudderjs/auth', '@rudderjs/session', '@rudderjs/hash',
|
|
27
|
+
'@rudderjs/orm-prisma', '@rudderjs/orm-drizzle',
|
|
28
|
+
'@rudderjs/cashier-paddle',
|
|
29
|
+
'@rudderjs/queue-bullmq', '@rudderjs/queue-inngest',
|
|
30
|
+
'@rudderjs/mail',
|
|
31
|
+
'@rudderjs/ai', '@rudderjs/mcp',
|
|
32
|
+
'@rudderjs/telescope', '@rudderjs/pulse', '@rudderjs/horizon',
|
|
33
|
+
];
|
|
34
|
+
export async function loadPackageChecks() {
|
|
35
|
+
// Resolve via direct path through the user's `node_modules/<pkg>/dist/doctor.js`,
|
|
36
|
+
// not `import('<pkg>/doctor')` — the `./doctor` subpath only ships an `import`
|
|
37
|
+
// condition (no `require`/`default`), which `createRequire().resolve()`
|
|
38
|
+
// refuses to match. Walking the symlink/file path bypasses the conditional
|
|
39
|
+
// exports machinery and works the same on pnpm (symlinked) and npm/yarn
|
|
40
|
+
// (flat node_modules). Documented as the ESM-only-peer resolution workaround.
|
|
41
|
+
await Promise.all(PACKAGES_WITH_CHECKS.map(async (pkg) => {
|
|
42
|
+
try {
|
|
43
|
+
const target = path.join(process.cwd(), 'node_modules', pkg, 'dist', 'doctor.js');
|
|
44
|
+
if (!fs.existsSync(target))
|
|
45
|
+
return;
|
|
46
|
+
await import(/* @vite-ignore */ pathToFileURL(target).href);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
/* package not installed, or its doctor entry failed to load */
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=load-package-checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-package-checks.js","sourceRoot":"","sources":["../../src/doctor/load-package-checks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,MAAM,oBAAoB,GAAa;IACrC,qEAAqE;IACrE,yEAAyE;IACzE,0EAA0E;IAC1E,oDAAoD;IACpD,gBAAgB,EAAE,mBAAmB,EAAE,gBAAgB;IACvD,sBAAsB,EAAE,uBAAuB;IAC/C,0BAA0B;IAC1B,wBAAwB,EAAE,yBAAyB;IACnD,gBAAgB;IAChB,cAAc,EAAE,eAAe;IAC/B,qBAAqB,EAAE,iBAAiB,EAAE,mBAAmB;CAC9D,CAAA;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,kFAAkF;IAClF,+EAA+E;IAC/E,wEAAwE;IACxE,2EAA2E;IAC3E,wEAAwE;IACxE,8EAA8E;IAC9E,MAAM,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACvD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,EAAE,WAAW,CAAC,CAAA;YACjF,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC;gBAAE,OAAM;YAClC,MAAM,MAAM,CAAC,kBAAkB,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAA;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,+DAA+D;QACjE,CAAC;IACH,CAAC,CAAC,CAAC,CAAA;AACL,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DoctorStatus } from '@rudderjs/console';
|
|
2
|
+
export interface CheckOutcome {
|
|
3
|
+
id: string;
|
|
4
|
+
category: string;
|
|
5
|
+
title: string;
|
|
6
|
+
status: DoctorStatus;
|
|
7
|
+
message: string;
|
|
8
|
+
fix?: string;
|
|
9
|
+
detail?: string;
|
|
10
|
+
/** Wall-clock ms for the check's `run()`. */
|
|
11
|
+
durationMs: number;
|
|
12
|
+
}
|
|
13
|
+
export interface RunOptions {
|
|
14
|
+
/** If true, include checks marked `needsBoot: true`. */
|
|
15
|
+
deep?: boolean;
|
|
16
|
+
/** Filter checks by id substring (used by `--only` flag in future). */
|
|
17
|
+
filter?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface RunResult {
|
|
20
|
+
outcomes: CheckOutcome[];
|
|
21
|
+
totalMs: number;
|
|
22
|
+
counts: {
|
|
23
|
+
ok: number;
|
|
24
|
+
warn: number;
|
|
25
|
+
error: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Collect every registered check, filter by `deep` / `filter`, run them
|
|
30
|
+
* concurrently within each category (categories run sequentially so the
|
|
31
|
+
* report renders in declared order).
|
|
32
|
+
*/
|
|
33
|
+
export declare function runChecks(opts?: RunOptions): Promise<RunResult>;
|
|
34
|
+
//# sourceMappingURL=orchestrator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/doctor/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA6B,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhF,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAQ,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAK,MAAM,CAAA;IAChB,MAAM,EAAI,YAAY,CAAA;IACtB,OAAO,EAAG,MAAM,CAAA;IAChB,GAAG,CAAC,EAAM,MAAM,CAAA;IAChB,MAAM,CAAC,EAAG,MAAM,CAAA;IAChB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,wDAAwD;IACxD,IAAI,CAAC,EAAK,OAAO,CAAA;IACjB,uEAAuE;IACvE,MAAM,CAAC,EAAG,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,YAAY,EAAE,CAAA;IACxB,OAAO,EAAG,MAAM,CAAA;IAChB,MAAM,EAAI;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CACtD;AA6BD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,IAAI,GAAE,UAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CA8BzE"}
|