@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,50 @@
1
+ import { analyzeAuthProviderReadiness } from './authReadinessProvider.js';
2
+ import { resolveAuthReadinessTargets } from './authReadinessTargets.js';
3
+ import type { DoctorDiagnostic } from './diagnostics.js';
4
+ import type { DoctorReadiness } from './readiness.js';
5
+
6
+ export interface DoctorAuthReadinessAnalysis {
7
+ readonly diagnostics: readonly DoctorDiagnostic[];
8
+ readonly readiness: readonly DoctorReadiness[];
9
+ }
10
+
11
+ export function analyzeAuthReadiness(
12
+ manifest: unknown,
13
+ manifestPath = 'manifest.json',
14
+ ): DoctorAuthReadinessAnalysis {
15
+ if (!isRecord(manifest)) return emptyAnalysis();
16
+
17
+ const infra = isRecord(manifest.infra) ? manifest.infra : null;
18
+ const auth = infra !== null && isRecord(infra.auth) ? infra.auth : null;
19
+ const oauth = auth !== null && isRecord(auth.oauth) ? auth.oauth : null;
20
+ if (oauth?.enabled !== true || !Array.isArray(oauth.providers)) return emptyAnalysis();
21
+
22
+ const targetResult = resolveAuthReadinessTargets(manifest.deploy, manifestPath);
23
+ const diagnostics = [...targetResult.diagnostics];
24
+ if (targetResult.targets === null) return { diagnostics, readiness: [] };
25
+
26
+ const readiness: DoctorReadiness[] = [];
27
+ for (const provider of oauth.providers) {
28
+ if (!isRecord(provider) || provider.enabled === false) continue;
29
+ const result = analyzeAuthProviderReadiness({
30
+ authProvider: auth?.provider,
31
+ callbackRoute: oauth.callbackRoute,
32
+ enabledTargets: targetResult.enabledTargets,
33
+ manifestPath,
34
+ provider,
35
+ targets: targetResult.targets,
36
+ });
37
+ diagnostics.push(...result.diagnostics);
38
+ readiness.push(...result.readiness);
39
+ }
40
+
41
+ return { diagnostics, readiness };
42
+ }
43
+
44
+ function emptyAnalysis(): DoctorAuthReadinessAnalysis {
45
+ return { diagnostics: [], readiness: [] };
46
+ }
47
+
48
+ function isRecord(value: unknown): value is Record<string, unknown> {
49
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
50
+ }
@@ -0,0 +1,18 @@
1
+ import type { DoctorDiagnostic } from './diagnostics.js';
2
+
3
+ export function createAuthReadinessDiagnostic(input: {
4
+ readonly code: DoctorDiagnostic['code'];
5
+ readonly message: string;
6
+ readonly path: string;
7
+ readonly ruleId: DoctorDiagnostic['ruleId'];
8
+ readonly severity: DoctorDiagnostic['severity'];
9
+ }): DoctorDiagnostic {
10
+ return {
11
+ code: input.code,
12
+ message: input.message,
13
+ path: input.path,
14
+ profile: 'app-manifest',
15
+ ruleId: input.ruleId,
16
+ severity: input.severity,
17
+ };
18
+ }
@@ -0,0 +1,51 @@
1
+ import type { AuthOAuthSetupPlan } from '@ankhorage/contracts/auth';
2
+ import type {
3
+ AppDeployEnvironmentId,
4
+ AppDeployTargetId,
5
+ AppDeployTargets,
6
+ } from '@ankhorage/contracts/deploy';
7
+
8
+ import { collectMissingAuthReadinessRequirements } from './authReadinessRequirements.js';
9
+ import type { DoctorReadiness } from './readiness.js';
10
+
11
+ export function analyzeAuthReadinessEnvironment(input: {
12
+ readonly callbackRoute: unknown;
13
+ readonly credentialsRef: unknown;
14
+ readonly enabledTargets: readonly AppDeployTargetId[];
15
+ readonly environment: AppDeployEnvironmentId;
16
+ readonly plan: AuthOAuthSetupPlan;
17
+ readonly provider: string;
18
+ readonly targets: AppDeployTargets;
19
+ }): {
20
+ readonly missingNativeTargets: readonly AppDeployTargetId[];
21
+ readonly readiness: readonly DoctorReadiness[];
22
+ } {
23
+ const missingNativeTargets = new Set<AppDeployTargetId>();
24
+ const readiness = input.enabledTargets.map((target): DoctorReadiness => {
25
+ const missing = collectMissingAuthReadinessRequirements({
26
+ callbackRoute: input.callbackRoute,
27
+ credentialsRef: input.credentialsRef,
28
+ plan: input.plan,
29
+ target,
30
+ targets: input.targets,
31
+ });
32
+ if (target !== 'web' && missing.includes(`${target} deep-link scheme`)) {
33
+ missingNativeTargets.add(target);
34
+ }
35
+
36
+ return {
37
+ category: 'auth-oauth',
38
+ environment: input.environment,
39
+ message:
40
+ missing.length === 0
41
+ ? 'Manifest and adapter setup requirements are satisfiable; concrete callback URLs remain Infra/runtime-owned.'
42
+ : `Missing ${missing.join(' and ')}.`,
43
+ provider: input.provider,
44
+ status: missing.length === 0 ? 'ready' : 'missing',
45
+ target,
46
+ transport: 'brokeredRedirect',
47
+ };
48
+ });
49
+
50
+ return { missingNativeTargets: [...missingNativeTargets], readiness };
51
+ }
@@ -0,0 +1,14 @@
1
+ import { promises as fs } from 'node:fs';
2
+
3
+ import { analyzeAuthReadiness, type DoctorAuthReadinessAnalysis } from './authReadinessAnalysis.js';
4
+
5
+ export async function analyzeAuthReadinessFile(
6
+ filePath: string,
7
+ ): Promise<DoctorAuthReadinessAnalysis> {
8
+ try {
9
+ const manifest = JSON.parse(await fs.readFile(filePath, 'utf8')) as unknown;
10
+ return analyzeAuthReadiness(manifest, filePath);
11
+ } catch {
12
+ return { diagnostics: [], readiness: [] };
13
+ }
14
+ }
@@ -0,0 +1,17 @@
1
+ import type { AppDeployTargetId } from '@ankhorage/contracts/deploy';
2
+
3
+ import { createAuthReadinessDiagnostic } from './authReadinessDiagnostic.js';
4
+ import type { DoctorDiagnostic } from './diagnostics.js';
5
+
6
+ export function createNativeAuthCallbackDiagnostic(
7
+ target: AppDeployTargetId,
8
+ path: string,
9
+ ): DoctorDiagnostic {
10
+ return createAuthReadinessDiagnostic({
11
+ code: 'field-missing',
12
+ message: `Enabled ${target} brokered OAuth requires deploy.targets.${target}.scheme for the app callback.`,
13
+ path,
14
+ ruleId: 'manifest.auth.oauth.callback-target.configured',
15
+ severity: 'error',
16
+ });
17
+ }
@@ -0,0 +1,61 @@
1
+ import type { AppDeployTargetId, AppDeployTargets } from '@ankhorage/contracts/deploy';
2
+ import { APP_DEPLOY_ENVIRONMENT_IDS } from '@ankhorage/contracts/deploy';
3
+
4
+ import { createNativeAuthCallbackDiagnostic } from './authReadinessNativeDiagnostic.js';
5
+ import { analyzeAuthProviderEnvironment } from './authReadinessProviderEnvironment.js';
6
+ import { createUnsupportedAuthDiagnostic } from './authReadinessUnsupported.js';
7
+ import { analyzeUnsupportedAuthBackend } from './authReadinessUnsupportedBackend.js';
8
+ import type { DoctorDiagnostic } from './diagnostics.js';
9
+ import type { DoctorReadiness } from './readiness.js';
10
+
11
+ export function analyzeAuthProviderReadiness(input: {
12
+ readonly authProvider: unknown;
13
+ readonly callbackRoute: unknown;
14
+ readonly enabledTargets: readonly AppDeployTargetId[];
15
+ readonly manifestPath: string;
16
+ readonly provider: Record<string, unknown>;
17
+ readonly targets: AppDeployTargets;
18
+ }): { readonly diagnostics: DoctorDiagnostic[]; readonly readiness: DoctorReadiness[] } {
19
+ const providerId = typeof input.provider.id === 'string' ? input.provider.id : '';
20
+ if (providerId === '') return { diagnostics: [], readiness: [] };
21
+ if (input.authProvider !== 'supabase') {
22
+ const result = analyzeUnsupportedAuthBackend({
23
+ enabledTargets: input.enabledTargets,
24
+ manifestPath: input.manifestPath,
25
+ provider: providerId,
26
+ });
27
+ return { diagnostics: [...result.diagnostics], readiness: [...result.readiness] };
28
+ }
29
+
30
+ const diagnostics: DoctorDiagnostic[] = [];
31
+ const readiness: DoctorReadiness[] = [];
32
+ const missingNativeTargets = new Set<AppDeployTargetId>();
33
+ let unsupported = false;
34
+
35
+ for (const environment of APP_DEPLOY_ENVIRONMENT_IDS) {
36
+ const result = analyzeAuthProviderEnvironment({
37
+ callbackRoute: input.callbackRoute,
38
+ credentialsRef: input.provider.credentialsRef,
39
+ enabledTargets: input.enabledTargets,
40
+ environment,
41
+ provider: providerId,
42
+ targets: input.targets,
43
+ });
44
+ readiness.push(...result.readiness);
45
+ unsupported ||= result.unsupported;
46
+ result.missingNativeTargets.forEach((target) => missingNativeTargets.add(target));
47
+ }
48
+
49
+ if (unsupported) {
50
+ diagnostics.push(
51
+ createUnsupportedAuthDiagnostic({
52
+ message: `OAuth provider "${providerId}" is not supported by the Supabase brokeredRedirect setup resolver.`,
53
+ path: input.manifestPath,
54
+ }),
55
+ );
56
+ }
57
+ missingNativeTargets.forEach((target) =>
58
+ diagnostics.push(createNativeAuthCallbackDiagnostic(target, input.manifestPath)),
59
+ );
60
+ return { diagnostics, readiness };
61
+ }
@@ -0,0 +1,54 @@
1
+ import type {
2
+ AppDeployEnvironmentId,
3
+ AppDeployTargetId,
4
+ AppDeployTargets,
5
+ } from '@ankhorage/contracts/deploy';
6
+ import { resolveSupabaseOAuthSetupPlan } from '@ankhorage/supabase-auth';
7
+
8
+ import { analyzeAuthReadinessEnvironment } from './authReadinessEnvironment.js';
9
+ import { createUnsupportedAuthReadiness } from './authReadinessUnsupported.js';
10
+ import type { DoctorReadiness } from './readiness.js';
11
+
12
+ export function analyzeAuthProviderEnvironment(input: {
13
+ readonly callbackRoute: unknown;
14
+ readonly credentialsRef: unknown;
15
+ readonly enabledTargets: readonly AppDeployTargetId[];
16
+ readonly environment: AppDeployEnvironmentId;
17
+ readonly provider: string;
18
+ readonly targets: AppDeployTargets;
19
+ }): {
20
+ readonly missingNativeTargets: readonly AppDeployTargetId[];
21
+ readonly readiness: readonly DoctorReadiness[];
22
+ readonly unsupported: boolean;
23
+ } {
24
+ const plan = resolveSupabaseOAuthSetupPlan({
25
+ provider: input.provider,
26
+ transport: 'brokeredRedirect',
27
+ environment: input.environment,
28
+ targets: input.enabledTargets,
29
+ });
30
+ if (plan === null) {
31
+ return {
32
+ missingNativeTargets: [],
33
+ readiness: createUnsupportedAuthReadiness({
34
+ environment: input.environment,
35
+ provider: input.provider,
36
+ targets: input.enabledTargets,
37
+ }),
38
+ unsupported: true,
39
+ };
40
+ }
41
+
42
+ return {
43
+ ...analyzeAuthReadinessEnvironment({
44
+ callbackRoute: input.callbackRoute,
45
+ credentialsRef: input.credentialsRef,
46
+ enabledTargets: input.enabledTargets,
47
+ environment: input.environment,
48
+ plan,
49
+ provider: input.provider,
50
+ targets: input.targets,
51
+ }),
52
+ unsupported: false,
53
+ };
54
+ }
@@ -0,0 +1,50 @@
1
+ import type { AuthOAuthSetupPlan } from '@ankhorage/contracts/auth';
2
+ import type { AppDeployTargetId, AppDeployTargets } from '@ankhorage/contracts/deploy';
3
+ import { normalizeSecretRef } from '@ankhorage/contracts/secrets';
4
+
5
+ export function collectMissingAuthReadinessRequirements(input: {
6
+ readonly callbackRoute: unknown;
7
+ readonly credentialsRef: unknown;
8
+ readonly plan: AuthOAuthSetupPlan;
9
+ readonly target: AppDeployTargetId;
10
+ readonly targets: AppDeployTargets;
11
+ }): readonly string[] {
12
+ const missing: string[] = [];
13
+ if (requiresTrustedCredential(input.plan) && !hasCanonicalCredentialRef(input.credentialsRef)) {
14
+ missing.push('credential reference');
15
+ }
16
+
17
+ const callbackRequired = input.plan.requirements.some(
18
+ (requirement) =>
19
+ requirement.kind === 'callback' &&
20
+ requirement.role === 'app' &&
21
+ requirement.required &&
22
+ requirement.target === input.target,
23
+ );
24
+ if (!callbackRequired) return missing;
25
+
26
+ if (!isAbsoluteRoute(input.callbackRoute)) missing.push('callback route');
27
+ if (input.target !== 'web' && input.targets[input.target]?.scheme === undefined) {
28
+ missing.push(`${input.target} deep-link scheme`);
29
+ }
30
+ return missing;
31
+ }
32
+
33
+ function requiresTrustedCredential(plan: AuthOAuthSetupPlan): boolean {
34
+ return plan.requirements.some(
35
+ (requirement) =>
36
+ requirement.kind === 'field' &&
37
+ requirement.required &&
38
+ requirement.persistence === 'trustedCredential',
39
+ );
40
+ }
41
+
42
+ function hasCanonicalCredentialRef(value: unknown): boolean {
43
+ if (typeof value !== 'string') return false;
44
+ const result = normalizeSecretRef(value);
45
+ return result.ok && result.data === value;
46
+ }
47
+
48
+ function isAbsoluteRoute(value: unknown): boolean {
49
+ return typeof value === 'string' && value.trim().startsWith('/') && value.trim().length > 0;
50
+ }
@@ -0,0 +1,38 @@
1
+ import { createAuthReadinessDiagnostic } from './authReadinessDiagnostic.js';
2
+ import type { AuthReadinessTargetResolution } from './authReadinessTargetResolution.js';
3
+
4
+ export function resolveAuthReadinessTargetFallback(
5
+ deploy: unknown,
6
+ manifestPath: string,
7
+ ): AuthReadinessTargetResolution {
8
+ if (deploy === undefined) {
9
+ return {
10
+ diagnostics: [
11
+ createAuthReadinessDiagnostic({
12
+ code: 'field-missing',
13
+ message:
14
+ 'deploy.targets is missing; Doctor evaluates legacy Web-only readiness until canonical targets are persisted.',
15
+ path: manifestPath,
16
+ ruleId: 'manifest.deploy.targets.legacy-web',
17
+ severity: 'warning',
18
+ }),
19
+ ],
20
+ enabledTargets: ['web'],
21
+ targets: { web: { enabled: true } },
22
+ };
23
+ }
24
+
25
+ return {
26
+ diagnostics: [
27
+ createAuthReadinessDiagnostic({
28
+ code: 'field-invalid',
29
+ message: 'manifest.deploy must match the canonical Contracts deploy target model.',
30
+ path: manifestPath,
31
+ ruleId: 'manifest.deploy.valid-shape',
32
+ severity: 'error',
33
+ }),
34
+ ],
35
+ enabledTargets: [],
36
+ targets: null,
37
+ };
38
+ }
@@ -0,0 +1,9 @@
1
+ import type { AppDeployTargetId, AppDeployTargets } from '@ankhorage/contracts/deploy';
2
+
3
+ import type { DoctorDiagnostic } from './diagnostics.js';
4
+
5
+ export interface AuthReadinessTargetResolution {
6
+ readonly diagnostics: readonly DoctorDiagnostic[];
7
+ readonly enabledTargets: readonly AppDeployTargetId[];
8
+ readonly targets: AppDeployTargets | null;
9
+ }
@@ -0,0 +1,35 @@
1
+ import { APP_DEPLOY_TARGET_IDS, isAppDeployManifest } from '@ankhorage/contracts/deploy';
2
+
3
+ import { createAuthReadinessDiagnostic } from './authReadinessDiagnostic.js';
4
+ import { resolveAuthReadinessTargetFallback } from './authReadinessTargetFallback.js';
5
+ import type { AuthReadinessTargetResolution } from './authReadinessTargetResolution.js';
6
+
7
+ export function resolveAuthReadinessTargets(
8
+ deploy: unknown,
9
+ manifestPath: string,
10
+ ): AuthReadinessTargetResolution {
11
+ if (!isAppDeployManifest(deploy)) {
12
+ return resolveAuthReadinessTargetFallback(deploy, manifestPath);
13
+ }
14
+
15
+ const enabledTargets = APP_DEPLOY_TARGET_IDS.filter(
16
+ (target) => deploy.targets[target]?.enabled === true,
17
+ );
18
+ if (enabledTargets.length > 0) {
19
+ return { diagnostics: [], enabledTargets, targets: deploy.targets };
20
+ }
21
+
22
+ return {
23
+ diagnostics: [
24
+ createAuthReadinessDiagnostic({
25
+ code: 'field-missing',
26
+ message: 'manifest.deploy.targets must enable at least one application target.',
27
+ path: manifestPath,
28
+ ruleId: 'manifest.deploy.targets.enabled',
29
+ severity: 'error',
30
+ }),
31
+ ],
32
+ enabledTargets,
33
+ targets: deploy.targets,
34
+ };
35
+ }
@@ -0,0 +1,34 @@
1
+ import type { AppDeployEnvironmentId, AppDeployTargetId } from '@ankhorage/contracts/deploy';
2
+
3
+ import { createAuthReadinessDiagnostic } from './authReadinessDiagnostic.js';
4
+ import type { DoctorDiagnostic } from './diagnostics.js';
5
+ import type { DoctorReadiness } from './readiness.js';
6
+
7
+ export function createUnsupportedAuthReadiness(input: {
8
+ readonly environment: AppDeployEnvironmentId;
9
+ readonly provider: string;
10
+ readonly targets: readonly AppDeployTargetId[];
11
+ }): DoctorReadiness[] {
12
+ return input.targets.map((target) => ({
13
+ category: 'auth-oauth',
14
+ environment: input.environment,
15
+ message: 'The selected backend/provider/transport combination has no setup capability.',
16
+ provider: input.provider,
17
+ status: 'unsupported',
18
+ target,
19
+ transport: 'brokeredRedirect',
20
+ }));
21
+ }
22
+
23
+ export function createUnsupportedAuthDiagnostic(input: {
24
+ readonly message: string;
25
+ readonly path: string;
26
+ }): DoctorDiagnostic {
27
+ return createAuthReadinessDiagnostic({
28
+ code: 'field-invalid',
29
+ message: input.message,
30
+ path: input.path,
31
+ ruleId: 'manifest.auth.oauth.setup.supported',
32
+ severity: 'error',
33
+ });
34
+ }
@@ -0,0 +1,34 @@
1
+ import type { AppDeployTargetId } from '@ankhorage/contracts/deploy';
2
+ import { APP_DEPLOY_ENVIRONMENT_IDS } from '@ankhorage/contracts/deploy';
3
+
4
+ import {
5
+ createUnsupportedAuthDiagnostic,
6
+ createUnsupportedAuthReadiness,
7
+ } from './authReadinessUnsupported.js';
8
+ import type { DoctorDiagnostic } from './diagnostics.js';
9
+ import type { DoctorReadiness } from './readiness.js';
10
+
11
+ export function analyzeUnsupportedAuthBackend(input: {
12
+ readonly enabledTargets: readonly AppDeployTargetId[];
13
+ readonly manifestPath: string;
14
+ readonly provider: string;
15
+ }): {
16
+ readonly diagnostics: readonly DoctorDiagnostic[];
17
+ readonly readiness: readonly DoctorReadiness[];
18
+ } {
19
+ return {
20
+ diagnostics: [
21
+ createUnsupportedAuthDiagnostic({
22
+ message: 'Configured OAuth backend has no Doctor setup resolver.',
23
+ path: input.manifestPath,
24
+ }),
25
+ ],
26
+ readiness: APP_DEPLOY_ENVIRONMENT_IDS.flatMap((environment) =>
27
+ createUnsupportedAuthReadiness({
28
+ environment,
29
+ provider: input.provider,
30
+ targets: input.enabledTargets,
31
+ }),
32
+ ),
33
+ };
34
+ }