@ankhorage/doctor 0.7.3 → 0.9.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 (102) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/analysis.d.ts +2 -0
  3. package/dist/analysis.d.ts.map +1 -1
  4. package/dist/analysis.js.map +1 -1
  5. package/dist/authReadinessAnalysis.d.ts +8 -0
  6. package/dist/authReadinessAnalysis.d.ts.map +1 -0
  7. package/dist/authReadinessAnalysis.js +38 -0
  8. package/dist/authReadinessAnalysis.js.map +1 -0
  9. package/dist/authReadinessDiagnostic.d.ts +9 -0
  10. package/dist/authReadinessDiagnostic.d.ts.map +1 -0
  11. package/dist/authReadinessDiagnostic.js +11 -0
  12. package/dist/authReadinessDiagnostic.js.map +1 -0
  13. package/dist/authReadinessEnvironment.d.ts +16 -0
  14. package/dist/authReadinessEnvironment.d.ts.map +1 -0
  15. package/dist/authReadinessEnvironment.js +29 -0
  16. package/dist/authReadinessEnvironment.js.map +1 -0
  17. package/dist/authReadinessFileAnalysis.d.ts +3 -0
  18. package/dist/authReadinessFileAnalysis.d.ts.map +1 -0
  19. package/dist/authReadinessFileAnalysis.js +12 -0
  20. package/dist/authReadinessFileAnalysis.js.map +1 -0
  21. package/dist/authReadinessNativeDiagnostic.d.ts +4 -0
  22. package/dist/authReadinessNativeDiagnostic.d.ts.map +1 -0
  23. package/dist/authReadinessNativeDiagnostic.js +11 -0
  24. package/dist/authReadinessNativeDiagnostic.js.map +1 -0
  25. package/dist/authReadinessProvider.d.ts +15 -0
  26. package/dist/authReadinessProvider.d.ts.map +1 -0
  27. package/dist/authReadinessProvider.js +44 -0
  28. package/dist/authReadinessProvider.js.map +1 -0
  29. package/dist/authReadinessProviderEnvironment.d.ts +15 -0
  30. package/dist/authReadinessProviderEnvironment.d.ts.map +1 -0
  31. package/dist/authReadinessProviderEnvironment.js +35 -0
  32. package/dist/authReadinessProviderEnvironment.js.map +1 -0
  33. package/dist/authReadinessRequirements.d.ts +10 -0
  34. package/dist/authReadinessRequirements.d.ts.map +1 -0
  35. package/dist/authReadinessRequirements.js +34 -0
  36. package/dist/authReadinessRequirements.js.map +1 -0
  37. package/dist/authReadinessTargetFallback.d.ts +3 -0
  38. package/dist/authReadinessTargetFallback.d.ts.map +1 -0
  39. package/dist/authReadinessTargetFallback.js +32 -0
  40. package/dist/authReadinessTargetFallback.js.map +1 -0
  41. package/dist/authReadinessTargetResolution.d.ts +8 -0
  42. package/dist/authReadinessTargetResolution.d.ts.map +1 -0
  43. package/dist/authReadinessTargetResolution.js +2 -0
  44. package/dist/authReadinessTargetResolution.js.map +1 -0
  45. package/dist/authReadinessTargets.d.ts +3 -0
  46. package/dist/authReadinessTargets.d.ts.map +1 -0
  47. package/dist/authReadinessTargets.js +26 -0
  48. package/dist/authReadinessTargets.js.map +1 -0
  49. package/dist/authReadinessUnsupported.d.ts +13 -0
  50. package/dist/authReadinessUnsupported.d.ts.map +1 -0
  51. package/dist/authReadinessUnsupported.js +22 -0
  52. package/dist/authReadinessUnsupported.js.map +1 -0
  53. package/dist/authReadinessUnsupportedBackend.d.ts +12 -0
  54. package/dist/authReadinessUnsupportedBackend.d.ts.map +1 -0
  55. package/dist/authReadinessUnsupportedBackend.js +18 -0
  56. package/dist/authReadinessUnsupportedBackend.js.map +1 -0
  57. package/dist/bunPolicyAnalysis.d.ts +3 -0
  58. package/dist/bunPolicyAnalysis.d.ts.map +1 -0
  59. package/dist/bunPolicyAnalysis.js +190 -0
  60. package/dist/bunPolicyAnalysis.js.map +1 -0
  61. package/dist/commands.d.ts.map +1 -1
  62. package/dist/commands.js +14 -0
  63. package/dist/commands.js.map +1 -1
  64. package/dist/dependencyPolicyAnalysis.d.ts.map +1 -1
  65. package/dist/dependencyPolicyAnalysis.js +6 -2
  66. package/dist/dependencyPolicyAnalysis.js.map +1 -1
  67. package/dist/diagnostics.d.ts +1 -1
  68. package/dist/diagnostics.d.ts.map +1 -1
  69. package/dist/diagnostics.js.map +1 -1
  70. package/dist/index.d.ts +3 -0
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +2 -0
  73. package/dist/index.js.map +1 -1
  74. package/dist/manifestAnalysis.d.ts.map +1 -1
  75. package/dist/manifestAnalysis.js +4 -1
  76. package/dist/manifestAnalysis.js.map +1 -1
  77. package/dist/readiness.d.ts +13 -0
  78. package/dist/readiness.d.ts.map +1 -0
  79. package/dist/readiness.js +2 -0
  80. package/dist/readiness.js.map +1 -0
  81. package/package.json +3 -2
  82. package/src/analysis.ts +2 -0
  83. package/src/authReadinessAnalysis.ts +50 -0
  84. package/src/authReadinessDiagnostic.ts +18 -0
  85. package/src/authReadinessEnvironment.ts +51 -0
  86. package/src/authReadinessFileAnalysis.ts +14 -0
  87. package/src/authReadinessNativeDiagnostic.ts +17 -0
  88. package/src/authReadinessProvider.ts +61 -0
  89. package/src/authReadinessProviderEnvironment.ts +54 -0
  90. package/src/authReadinessRequirements.ts +50 -0
  91. package/src/authReadinessTargetFallback.ts +38 -0
  92. package/src/authReadinessTargetResolution.ts +9 -0
  93. package/src/authReadinessTargets.ts +35 -0
  94. package/src/authReadinessUnsupported.ts +34 -0
  95. package/src/authReadinessUnsupportedBackend.ts +34 -0
  96. package/src/bunPolicyAnalysis.ts +261 -0
  97. package/src/commands.ts +16 -0
  98. package/src/dependencyPolicyAnalysis.ts +9 -4
  99. package/src/diagnostics.ts +9 -0
  100. package/src/index.ts +3 -0
  101. package/src/manifestAnalysis.ts +4 -1
  102. package/src/readiness.ts +14 -0
@@ -0,0 +1,261 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import path from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+
6
+ import type { DoctorAnalysisResult } from './analysis.js';
7
+ import type { DoctorDiagnostic, DoctorRuleId } from './diagnostics.js';
8
+
9
+ interface BunRuntimePolicySnapshot {
10
+ readonly packageManager: string;
11
+ readonly typesRange: string;
12
+ readonly version: string;
13
+ }
14
+
15
+ const DEVTOOLS_POLICY_SPECIFIER = '@ankhorage/devtools/policy';
16
+ const REPAIR_HINT = 'Run "ankh devtools sync" to repair the managed Bun state.';
17
+ const BUN_VERSION_PATTERN = /^\s*bun-version:\s*['"]?([^'"\s#]+)['"]?\s*(?:#.*)?$/mu;
18
+ const WORKFLOWS = [
19
+ {
20
+ relativePath: path.join('.github', 'workflows', 'ci.yml'),
21
+ ruleId: 'repo.workflows.ci.bun-policy',
22
+ },
23
+ {
24
+ relativePath: path.join('.github', 'workflows', 'release.yml'),
25
+ ruleId: 'repo.workflows.release.bun-policy',
26
+ },
27
+ ] as const satisfies readonly { readonly relativePath: string; readonly ruleId: DoctorRuleId }[];
28
+
29
+ export async function applyBunRuntimePolicy(
30
+ result: DoctorAnalysisResult,
31
+ ): Promise<DoctorAnalysisResult> {
32
+ if (result.profile !== 'public-package' || !result.hasPackageJson) {
33
+ return result;
34
+ }
35
+
36
+ const policy = await loadTargetBunRuntimePolicy(result.targetPath);
37
+ const packageJsonPath = path.join(result.targetPath, 'package.json');
38
+ const packageJson = await readPackageJson(packageJsonPath);
39
+ if (policy === null || packageJson === null) {
40
+ return result;
41
+ }
42
+
43
+ const diagnostics = [
44
+ ...result.diagnostics,
45
+ ...analyzePackageBunPolicy(packageJson, packageJsonPath, result, policy),
46
+ ...(await analyzeWorkflowBunPolicy(result, policy)),
47
+ ];
48
+
49
+ return withDiagnostics(result, diagnostics);
50
+ }
51
+
52
+ async function loadTargetBunRuntimePolicy(
53
+ targetPath: string,
54
+ ): Promise<BunRuntimePolicySnapshot | null> {
55
+ const requireFromTarget = createRequire(path.join(targetPath, 'package.json'));
56
+ try {
57
+ const modulePath = requireFromTarget.resolve(DEVTOOLS_POLICY_SPECIFIER);
58
+ const importedModule = (await import(pathToFileURL(modulePath).href)) as unknown;
59
+ return parseBunRuntimePolicy(importedModule);
60
+ } catch (error) {
61
+ if (isUnavailablePolicyError(error)) {
62
+ return null;
63
+ }
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ function analyzePackageBunPolicy(
69
+ packageJson: Record<string, unknown>,
70
+ packageJsonPath: string,
71
+ result: DoctorAnalysisResult,
72
+ policy: BunRuntimePolicySnapshot,
73
+ ): DoctorDiagnostic[] {
74
+ const diagnostics: DoctorDiagnostic[] = [];
75
+ const { packageManager } = packageJson;
76
+ if (isNonEmptyString(packageManager) && packageManager.startsWith('bun@')) {
77
+ pushMismatch(diagnostics, {
78
+ actual: packageManager,
79
+ expected: policy.packageManager,
80
+ location: 'package.json#packageManager',
81
+ path: packageJsonPath,
82
+ profile: result.profile,
83
+ ruleId: 'package.json.package-manager.policy',
84
+ });
85
+ }
86
+
87
+ const devDependencies = isRecord(packageJson.devDependencies)
88
+ ? packageJson.devDependencies
89
+ : null;
90
+ const bunTypes = devDependencies?.['@types/bun'];
91
+ if (isNonEmptyString(bunTypes)) {
92
+ pushMismatch(diagnostics, {
93
+ actual: bunTypes,
94
+ expected: policy.typesRange,
95
+ location: 'package.json#devDependencies.@types/bun',
96
+ path: packageJsonPath,
97
+ profile: result.profile,
98
+ ruleId: 'package.dependencies.types-bun.policy',
99
+ });
100
+ }
101
+
102
+ return diagnostics;
103
+ }
104
+
105
+ async function analyzeWorkflowBunPolicy(
106
+ result: DoctorAnalysisResult,
107
+ policy: BunRuntimePolicySnapshot,
108
+ ): Promise<DoctorDiagnostic[]> {
109
+ const diagnostics = await Promise.all(
110
+ WORKFLOWS.map(async (workflow) => await analyzeWorkflow(result, workflow, policy)),
111
+ );
112
+ return diagnostics.flat();
113
+ }
114
+
115
+ async function analyzeWorkflow(
116
+ result: DoctorAnalysisResult,
117
+ workflow: (typeof WORKFLOWS)[number],
118
+ policy: BunRuntimePolicySnapshot,
119
+ ): Promise<DoctorDiagnostic[]> {
120
+ const workflowPath = path.join(result.targetPath, workflow.relativePath);
121
+ const contents = await readTextOrNull(workflowPath);
122
+ if (contents === null) {
123
+ return [createWorkflowDiagnostic(result, workflow, policy, workflowPath, 'missing-path')];
124
+ }
125
+
126
+ const match = BUN_VERSION_PATTERN.exec(contents);
127
+ const actual = match?.[1];
128
+ if (!isNonEmptyString(actual)) {
129
+ return [createWorkflowDiagnostic(result, workflow, policy, workflowPath, 'field-missing')];
130
+ }
131
+
132
+ const diagnostics: DoctorDiagnostic[] = [];
133
+ pushMismatch(diagnostics, {
134
+ actual,
135
+ expected: policy.version,
136
+ location: `${workflow.relativePath}#bun-version`,
137
+ path: workflowPath,
138
+ profile: result.profile,
139
+ ruleId: workflow.ruleId,
140
+ });
141
+ return diagnostics;
142
+ }
143
+
144
+ function createWorkflowDiagnostic(
145
+ result: DoctorAnalysisResult,
146
+ workflow: (typeof WORKFLOWS)[number],
147
+ policy: BunRuntimePolicySnapshot,
148
+ workflowPath: string,
149
+ code: DoctorDiagnostic['code'],
150
+ ): DoctorDiagnostic {
151
+ const detail =
152
+ code === 'missing-path'
153
+ ? `Managed workflow is missing: ${workflow.relativePath}.`
154
+ : `${workflow.relativePath} does not define bun-version.`;
155
+ return {
156
+ code,
157
+ message: `${detail} Expected Bun ${policy.version}. ${REPAIR_HINT}`,
158
+ path: workflowPath,
159
+ profile: result.profile,
160
+ ruleId: workflow.ruleId,
161
+ severity: 'error',
162
+ };
163
+ }
164
+
165
+ function pushMismatch(
166
+ diagnostics: DoctorDiagnostic[],
167
+ input: {
168
+ readonly actual: string;
169
+ readonly expected: string;
170
+ readonly location: string;
171
+ readonly path: string;
172
+ readonly profile: DoctorAnalysisResult['profile'];
173
+ readonly ruleId: DoctorRuleId;
174
+ },
175
+ ): void {
176
+ if (input.actual === input.expected) {
177
+ return;
178
+ }
179
+ diagnostics.push({
180
+ code: 'field-invalid',
181
+ message: `Bun policy drift at ${input.location}: expected "${input.expected}", found "${input.actual}". ${REPAIR_HINT}`,
182
+ path: input.path,
183
+ profile: input.profile,
184
+ ruleId: input.ruleId,
185
+ severity: 'error',
186
+ });
187
+ }
188
+
189
+ function parseBunRuntimePolicy(importedModule: unknown): BunRuntimePolicySnapshot | null {
190
+ if (!isRecord(importedModule) || !isRecord(importedModule.bunRuntimePolicy)) {
191
+ return null;
192
+ }
193
+ const { packageManager, typesRange, version } = importedModule.bunRuntimePolicy;
194
+ return isNonEmptyString(packageManager) &&
195
+ isNonEmptyString(typesRange) &&
196
+ isNonEmptyString(version)
197
+ ? { packageManager, typesRange, version }
198
+ : null;
199
+ }
200
+
201
+ function withDiagnostics(
202
+ result: DoctorAnalysisResult,
203
+ diagnostics: readonly DoctorDiagnostic[],
204
+ ): DoctorAnalysisResult {
205
+ return {
206
+ ...result,
207
+ diagnostics,
208
+ fixPlan: result.fixPlan === null ? null : { ...result.fixPlan, diagnostics },
209
+ };
210
+ }
211
+
212
+ async function readPackageJson(packageJsonPath: string): Promise<Record<string, unknown> | null> {
213
+ try {
214
+ const parsed = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')) as unknown;
215
+ return isRecord(parsed) ? parsed : null;
216
+ } catch {
217
+ return null;
218
+ }
219
+ }
220
+
221
+ async function readTextOrNull(filePath: string): Promise<string | null> {
222
+ try {
223
+ return await fs.readFile(filePath, 'utf8');
224
+ } catch (error) {
225
+ if (isNodeError(error) && error.code === 'ENOENT') {
226
+ return null;
227
+ }
228
+ throw error;
229
+ }
230
+ }
231
+
232
+ function isUnavailablePolicyError(error: unknown): boolean {
233
+ if (
234
+ isNodeError(error) &&
235
+ (error.code === 'MODULE_NOT_FOUND' ||
236
+ error.code === 'ERR_MODULE_NOT_FOUND' ||
237
+ error.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED')
238
+ ) {
239
+ return true;
240
+ }
241
+
242
+ const message = error instanceof Error ? error.message : String(error);
243
+ return (
244
+ message.includes(DEVTOOLS_POLICY_SPECIFIER) &&
245
+ (message.includes('Cannot find module') ||
246
+ message.includes('not exported') ||
247
+ message.includes('Package subpath'))
248
+ );
249
+ }
250
+
251
+ function isNonEmptyString(value: unknown): value is string {
252
+ return typeof value === 'string' && value.trim() !== '';
253
+ }
254
+
255
+ function isRecord(value: unknown): value is Record<string, unknown> {
256
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
257
+ }
258
+
259
+ function isNodeError(error: unknown): error is NodeJS.ErrnoException {
260
+ return error instanceof Error && 'code' in error;
261
+ }
package/src/commands.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  type DoctorDiagnostic,
10
10
  } from './diagnostics.js';
11
11
  import { DOCTOR_CAPABILITIES, DOCTOR_COMMAND_CATEGORY } from './packageMetadata.js';
12
+ import type { DoctorReadiness } from './readiness.js';
12
13
 
13
14
  type DoctorCommandName = 'fix' | 'package' | 'repo' | 'validate';
14
15
 
@@ -289,6 +290,17 @@ function renderAnalysisReport(
289
290
  }
290
291
  }
291
292
 
293
+ const readiness = result.readiness ?? [];
294
+ lines.push('');
295
+ lines.push('Readiness:');
296
+ if (readiness.length === 0) {
297
+ lines.push(' none');
298
+ } else {
299
+ for (const item of readiness) {
300
+ lines.push(renderReadinessLine(item));
301
+ }
302
+ }
303
+
292
304
  if (commandName === 'fix') {
293
305
  lines.push('');
294
306
  lines.push('Planned changes:');
@@ -316,6 +328,10 @@ function renderDiagnosticLine(diagnostic: DoctorDiagnostic): string {
316
328
  return ` - ${diagnostic.severity.toUpperCase()} ${diagnostic.ruleId} (${diagnostic.code}): ${diagnostic.message}`;
317
329
  }
318
330
 
331
+ function renderReadinessLine(item: DoctorReadiness): string {
332
+ return ` - ${item.target} ${item.environment} ${item.provider} ${item.transport}: ${item.status} — ${item.message}`;
333
+ }
334
+
319
335
  function renderPlannedChangeLine(change: DoctorPlannedChange): string {
320
336
  return ` - ${change.ruleId} [${change.kind}]: ${change.description}`;
321
337
  }
@@ -6,6 +6,7 @@ import {
6
6
  type DoctorAnalysisRequest,
7
7
  type DoctorAnalysisResult,
8
8
  } from './analysis.js';
9
+ import { applyBunRuntimePolicy } from './bunPolicyAnalysis.js';
9
10
  import { analyzeDoctorTargetWithCliLayout as analyzeBaseDoctorTargetWithCliLayout } from './cliLayoutAnalysis.js';
10
11
  import type { DoctorDiagnostic } from './diagnostics.js';
11
12
 
@@ -16,15 +17,19 @@ const DEVTOOLS_REQUIRED_RULE_ID = 'package.dependencies.devtools.required';
16
17
  export async function analyzeDoctorTarget(
17
18
  request: DoctorAnalysisRequest,
18
19
  ): Promise<DoctorAnalysisResult> {
19
- return applyDevtoolsDependencyPlacementPolicy(await analyzeBaseDoctorTarget(request));
20
+ return applyRepositoryPolicies(await analyzeBaseDoctorTarget(request));
20
21
  }
21
22
 
22
23
  export async function analyzeDoctorTargetWithCliLayout(
23
24
  request: DoctorAnalysisRequest,
24
25
  ): Promise<DoctorAnalysisResult> {
25
- return applyDevtoolsDependencyPlacementPolicy(
26
- await analyzeBaseDoctorTargetWithCliLayout(request),
27
- );
26
+ return applyRepositoryPolicies(await analyzeBaseDoctorTargetWithCliLayout(request));
27
+ }
28
+
29
+ async function applyRepositoryPolicies(
30
+ result: DoctorAnalysisResult,
31
+ ): Promise<DoctorAnalysisResult> {
32
+ return applyBunRuntimePolicy(await applyDevtoolsDependencyPlacementPolicy(result));
28
33
  }
29
34
 
30
35
  async function applyDevtoolsDependencyPlacementPolicy(
@@ -30,13 +30,18 @@ export type DoctorRuleId =
30
30
  | 'manifest.auth.flow.unauthorized-route.valid'
31
31
  | 'manifest.auth.flow.valid-shape'
32
32
  | 'manifest.auth.oauth.callback-route.valid'
33
+ | 'manifest.auth.oauth.callback-target.configured'
33
34
  | 'manifest.auth.oauth.provider.credentials-ref.required'
34
35
  | 'manifest.auth.oauth.provider.credentials-ref.valid'
35
36
  | 'manifest.auth.oauth.provider.duplicate'
36
37
  | 'manifest.auth.oauth.provider.inline-secret.disallowed'
37
38
  | 'manifest.auth.oauth.providers.valid'
39
+ | 'manifest.auth.oauth.setup.supported'
38
40
  | 'manifest.auth.oauth.valid-shape'
39
41
  | 'manifest.auth.valid-shape'
42
+ | 'manifest.deploy.targets.enabled'
43
+ | 'manifest.deploy.targets.legacy-web'
44
+ | 'manifest.deploy.valid-shape'
40
45
  | 'manifest.infra.valid-shape'
41
46
  | 'manifest.json.readable'
42
47
  | 'manifest.json.valid'
@@ -57,6 +62,7 @@ export type DoctorRuleId =
57
62
  | 'package.dependencies.changesets.required'
58
63
  | 'package.dependencies.devtools.required'
59
64
  | 'package.dependencies.paradox.required'
65
+ | 'package.dependencies.types-bun.policy'
60
66
  | 'package.dependencies.types-bun.required'
61
67
  | 'package.dependencies.types-node.required'
62
68
  | 'package.dependencies.typescript.required'
@@ -71,6 +77,7 @@ export type DoctorRuleId =
71
77
  | 'package.json.license.required'
72
78
  | 'package.json.name.required'
73
79
  | 'package.json.package-manager.bun'
80
+ | 'package.json.package-manager.policy'
74
81
  | 'package.json.package-manager.required'
75
82
  | 'package.json.private.public-package-disallowed'
76
83
  | 'package.json.publish-config.public'
@@ -99,6 +106,8 @@ export type DoctorRuleId =
99
106
  | 'repo.changeset.required'
100
107
  | 'repo.license.required'
101
108
  | 'repo.readme.required'
109
+ | 'repo.workflows.ci.bun-policy'
110
+ | 'repo.workflows.release.bun-policy'
102
111
  | 'repo.workflows.required'
103
112
  | 'studio.dependencies.dnd.required'
104
113
  | 'studio.dependencies.runtime.required'
package/src/index.ts CHANGED
@@ -7,6 +7,8 @@ export type {
7
7
  DoctorTargetCheck,
8
8
  DoctorTargetMode,
9
9
  } from './analysis.js';
10
+ export { analyzeAuthReadiness, type DoctorAuthReadinessAnalysis } from './authReadinessAnalysis.js';
11
+ export { analyzeAuthReadinessFile } from './authReadinessFileAnalysis.js';
10
12
  export {
11
13
  createDoctorRuntimeProvider,
12
14
  type CreateDoctorRuntimeProviderOptions,
@@ -37,3 +39,4 @@ export {
37
39
  DOCTOR_PACKAGE_NAME,
38
40
  DOCTOR_PACKAGE_VERSION,
39
41
  } from './packageMetadata.js';
42
+ export type { DoctorReadiness, DoctorReadinessStatus } from './readiness.js';
@@ -10,6 +10,7 @@ import type {
10
10
  DoctorFixPlan,
11
11
  DoctorPlannedChange,
12
12
  } from './analysis.js';
13
+ import { analyzeAuthReadinessFile } from './authReadinessFileAnalysis.js';
13
14
  import type {
14
15
  DoctorDiagnostic,
15
16
  DoctorDiagnosticCode,
@@ -52,7 +53,8 @@ export async function analyzeAppManifestTarget(
52
53
  return null;
53
54
  }
54
55
 
55
- const diagnostics = await analyzeAppManifestFile(targetPath);
56
+ const authReadiness = await analyzeAuthReadinessFile(targetPath);
57
+ const diagnostics = [...(await analyzeAppManifestFile(targetPath)), ...authReadiness.diagnostics];
56
58
  const plannedChanges: DoctorPlannedChange[] = [];
57
59
  const fixPlan: DoctorFixPlan | null =
58
60
  request.mode === 'fix'
@@ -71,6 +73,7 @@ export async function analyzeAppManifestTarget(
71
73
  hasPackageJson: false,
72
74
  plannedChanges,
73
75
  profile: MANIFEST_PROFILE,
76
+ readiness: authReadiness.readiness,
74
77
  repoMarkers: [],
75
78
  targetPath,
76
79
  };
@@ -0,0 +1,14 @@
1
+ import type { AuthOAuthProviderId, AuthOAuthTransportId } from '@ankhorage/contracts/auth';
2
+ import type { AppDeployEnvironmentId, AppDeployTargetId } from '@ankhorage/contracts/deploy';
3
+
4
+ export type DoctorReadinessStatus = 'missing' | 'ready' | 'unsupported';
5
+
6
+ export interface DoctorReadiness {
7
+ readonly category: 'auth-oauth';
8
+ readonly environment: AppDeployEnvironmentId;
9
+ readonly message: string;
10
+ readonly provider: AuthOAuthProviderId;
11
+ readonly status: DoctorReadinessStatus;
12
+ readonly target: AppDeployTargetId;
13
+ readonly transport: AuthOAuthTransportId;
14
+ }