@rudderjs/cli 4.4.0 → 4.6.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.
Files changed (63) hide show
  1. package/dist/commands/doctor.d.ts +12 -0
  2. package/dist/commands/doctor.d.ts.map +1 -0
  3. package/dist/commands/doctor.js +83 -0
  4. package/dist/commands/doctor.js.map +1 -0
  5. package/dist/commands/tinker.d.ts +48 -0
  6. package/dist/commands/tinker.d.ts.map +1 -0
  7. package/dist/commands/tinker.js +192 -0
  8. package/dist/commands/tinker.js.map +1 -0
  9. package/dist/doctor/boot-status.d.ts +10 -0
  10. package/dist/doctor/boot-status.d.ts.map +1 -0
  11. package/dist/doctor/boot-status.js +15 -0
  12. package/dist/doctor/boot-status.js.map +1 -0
  13. package/dist/doctor/built-in/_fs.d.ts +11 -0
  14. package/dist/doctor/built-in/_fs.d.ts.map +1 -0
  15. package/dist/doctor/built-in/_fs.js +53 -0
  16. package/dist/doctor/built-in/_fs.js.map +1 -0
  17. package/dist/doctor/built-in/deps.d.ts +2 -0
  18. package/dist/doctor/built-in/deps.d.ts.map +1 -0
  19. package/dist/doctor/built-in/deps.js +81 -0
  20. package/dist/doctor/built-in/deps.js.map +1 -0
  21. package/dist/doctor/built-in/env-vars.d.ts +2 -0
  22. package/dist/doctor/built-in/env-vars.d.ts.map +1 -0
  23. package/dist/doctor/built-in/env-vars.js +99 -0
  24. package/dist/doctor/built-in/env-vars.js.map +1 -0
  25. package/dist/doctor/built-in/index.d.ts +13 -0
  26. package/dist/doctor/built-in/index.d.ts.map +1 -0
  27. package/dist/doctor/built-in/index.js +18 -0
  28. package/dist/doctor/built-in/index.js.map +1 -0
  29. package/dist/doctor/built-in/node-version.d.ts +2 -0
  30. package/dist/doctor/built-in/node-version.d.ts.map +1 -0
  31. package/dist/doctor/built-in/node-version.js +72 -0
  32. package/dist/doctor/built-in/node-version.js.map +1 -0
  33. package/dist/doctor/built-in/package-manager.d.ts +2 -0
  34. package/dist/doctor/built-in/package-manager.d.ts.map +1 -0
  35. package/dist/doctor/built-in/package-manager.js +55 -0
  36. package/dist/doctor/built-in/package-manager.js.map +1 -0
  37. package/dist/doctor/built-in/runtime.d.ts +2 -0
  38. package/dist/doctor/built-in/runtime.d.ts.map +1 -0
  39. package/dist/doctor/built-in/runtime.js +82 -0
  40. package/dist/doctor/built-in/runtime.js.map +1 -0
  41. package/dist/doctor/built-in/structure.d.ts +2 -0
  42. package/dist/doctor/built-in/structure.d.ts.map +1 -0
  43. package/dist/doctor/built-in/structure.js +93 -0
  44. package/dist/doctor/built-in/structure.js.map +1 -0
  45. package/dist/doctor/fixer.d.ts +39 -0
  46. package/dist/doctor/fixer.d.ts.map +1 -0
  47. package/dist/doctor/fixer.js +80 -0
  48. package/dist/doctor/fixer.js.map +1 -0
  49. package/dist/doctor/load-package-checks.d.ts +2 -0
  50. package/dist/doctor/load-package-checks.d.ts.map +1 -0
  51. package/dist/doctor/load-package-checks.js +53 -0
  52. package/dist/doctor/load-package-checks.js.map +1 -0
  53. package/dist/doctor/orchestrator.d.ts +34 -0
  54. package/dist/doctor/orchestrator.d.ts.map +1 -0
  55. package/dist/doctor/orchestrator.js +65 -0
  56. package/dist/doctor/orchestrator.js.map +1 -0
  57. package/dist/doctor/reporter.d.ts +21 -0
  58. package/dist/doctor/reporter.d.ts.map +1 -0
  59. package/dist/doctor/reporter.js +151 -0
  60. package/dist/doctor/reporter.js.map +1 -0
  61. package/dist/index.js +52 -2
  62. package/dist/index.js.map +1 -1
  63. package/package.json +5 -5
@@ -0,0 +1,99 @@
1
+ import { registerDoctorCheck } from '@rudderjs/console';
2
+ import { readFileSafe, fileExists } from './_fs.js';
3
+ /**
4
+ * Minimal `.env` parser — covers `KEY=value`, `KEY="value"`, comments, blanks.
5
+ * Doesn't expand `${VAR}` references; we only need to check declared keys.
6
+ */
7
+ function parseEnvText(text) {
8
+ const out = new Map();
9
+ for (const rawLine of text.split('\n')) {
10
+ const line = rawLine.replace(/^\s+|\s+$/g, '');
11
+ if (!line || line.startsWith('#'))
12
+ continue;
13
+ const eq = line.indexOf('=');
14
+ if (eq === -1)
15
+ continue;
16
+ const key = line.slice(0, eq).trim();
17
+ let value = line.slice(eq + 1).trim();
18
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
19
+ value = value.slice(1, -1);
20
+ }
21
+ out.set(key, value);
22
+ }
23
+ return out;
24
+ }
25
+ registerDoctorCheck({
26
+ id: 'env:dotenv-loadable',
27
+ category: 'env',
28
+ title: '.env file',
29
+ run() {
30
+ if (!fileExists('.env')) {
31
+ const exampleHint = fileExists('.env.example')
32
+ ? 'Run `cp .env.example .env` and fill in the secrets'
33
+ : 'Create a .env file with your config (APP_KEY, AUTH_SECRET, etc.)';
34
+ return { status: 'error', message: 'missing', fix: exampleHint };
35
+ }
36
+ const text = readFileSafe('.env');
37
+ if (text === null) {
38
+ return { status: 'error', message: 'present but unreadable', fix: 'Check file permissions on .env' };
39
+ }
40
+ const parsed = parseEnvText(text);
41
+ return { status: 'ok', message: `parses (${parsed.size} keys)` };
42
+ },
43
+ });
44
+ registerDoctorCheck({
45
+ id: 'env:app-key',
46
+ category: 'env',
47
+ title: 'APP_KEY',
48
+ run() {
49
+ const v = process.env['APP_KEY'];
50
+ if (!v) {
51
+ return {
52
+ status: 'error',
53
+ message: 'unset',
54
+ fix: 'Generate a 32-byte base64 key (e.g. `node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'base64\'))"`) and put it in .env',
55
+ };
56
+ }
57
+ // APP_KEY can be raw or base64-encoded. Accept either form, validate decoded length.
58
+ let decodedLen;
59
+ try {
60
+ decodedLen = Buffer.from(v, 'base64').length;
61
+ }
62
+ catch {
63
+ decodedLen = Buffer.byteLength(v);
64
+ }
65
+ if (decodedLen < 32) {
66
+ return {
67
+ status: 'warn',
68
+ message: `present but only ${decodedLen} bytes — needs ≥ 32 for AES-256`,
69
+ fix: 'Generate a fresh 32-byte key with `node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'base64\'))"`',
70
+ };
71
+ }
72
+ return { status: 'ok', message: `set, ${decodedLen} bytes` };
73
+ },
74
+ });
75
+ registerDoctorCheck({
76
+ id: 'env:app-env',
77
+ category: 'env',
78
+ title: 'APP_ENV',
79
+ run() {
80
+ const v = process.env['APP_ENV'] ?? process.env['NODE_ENV'];
81
+ if (!v) {
82
+ return {
83
+ status: 'warn',
84
+ message: 'unset — defaults to "production" in many adapters',
85
+ fix: 'Add `APP_ENV=local` (or `dev`/`staging`/`production`) to .env',
86
+ };
87
+ }
88
+ const known = ['local', 'dev', 'development', 'test', 'staging', 'production'];
89
+ if (!known.includes(v)) {
90
+ return {
91
+ status: 'warn',
92
+ message: `non-standard value "${v}"`,
93
+ fix: `Use one of: ${known.join(', ')}`,
94
+ };
95
+ }
96
+ return { status: 'ok', message: v };
97
+ },
98
+ });
99
+ //# sourceMappingURL=env-vars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-vars.js","sourceRoot":"","sources":["../../../src/doctor/built-in/env-vars.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Built-in doctor checks owned by `@rudderjs/cli`. Each module's side-effect
3
+ * import calls `registerDoctorCheck()` so the checks are visible to the
4
+ * orchestrator after `loadBuiltInChecks()` runs.
5
+ */
6
+ import './node-version.js';
7
+ import './package-manager.js';
8
+ import './env-vars.js';
9
+ import './structure.js';
10
+ import './deps.js';
11
+ import './runtime.js';
12
+ export declare function loadBuiltInChecks(): void;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/doctor/built-in/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,mBAAmB,CAAA;AAC1B,OAAO,sBAAsB,CAAA;AAC7B,OAAO,eAAe,CAAA;AACtB,OAAO,gBAAgB,CAAA;AACvB,OAAO,WAAW,CAAA;AAClB,OAAO,cAAc,CAAA;AAErB,wBAAgB,iBAAiB,IAAI,IAAI,CAKxC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Built-in doctor checks owned by `@rudderjs/cli`. Each module's side-effect
3
+ * import calls `registerDoctorCheck()` so the checks are visible to the
4
+ * orchestrator after `loadBuiltInChecks()` runs.
5
+ */
6
+ import './node-version.js';
7
+ import './package-manager.js';
8
+ import './env-vars.js';
9
+ import './structure.js';
10
+ import './deps.js';
11
+ import './runtime.js';
12
+ export function loadBuiltInChecks() {
13
+ // Module loading is sufficient — side-effect imports above call
14
+ // registerDoctorCheck() at module-init time. This function exists so the
15
+ // doctor command can force the imports (otherwise tree-shaking would
16
+ // drop them).
17
+ }
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/doctor/built-in/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,mBAAmB,CAAA;AAC1B,OAAO,sBAAsB,CAAA;AAC7B,OAAO,eAAe,CAAA;AACtB,OAAO,gBAAgB,CAAA;AACvB,OAAO,WAAW,CAAA;AAClB,OAAO,cAAc,CAAA;AAErB,MAAM,UAAU,iBAAiB;IAC/B,gEAAgE;IAChE,yEAAyE;IACzE,qEAAqE;IACrE,cAAc;AAChB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=node-version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-version.d.ts","sourceRoot":"","sources":["../../../src/doctor/built-in/node-version.ts"],"names":[],"mappings":""}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=package-manager.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=runtime.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=structure.d.ts.map
@@ -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":"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"}