@ankhorage/doctor 0.5.0 → 0.7.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/CHANGELOG.md +12 -0
- package/README.md +7 -2
- package/dist/analysis.d.ts +1 -1
- package/dist/analysis.d.ts.map +1 -1
- package/dist/analysis.js.map +1 -1
- package/dist/cliLayoutAnalysis.d.ts.map +1 -1
- package/dist/cliLayoutAnalysis.js +5 -0
- package/dist/cliLayoutAnalysis.js.map +1 -1
- package/dist/commands.d.ts +3 -3
- package/dist/commands.d.ts.map +1 -1
- package/dist/commands.js +5 -5
- package/dist/commands.js.map +1 -1
- package/dist/diagnostics.d.ts +3 -3
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/manifestAnalysis.d.ts +6 -0
- package/dist/manifestAnalysis.d.ts.map +1 -0
- package/dist/manifestAnalysis.js +324 -0
- package/dist/manifestAnalysis.js.map +1 -0
- package/dist/readme-usage.js +6 -1
- package/dist/readme-usage.js.map +1 -1
- package/dist/secretManifestAnalysis.d.ts +3 -0
- package/dist/secretManifestAnalysis.d.ts.map +1 -0
- package/dist/secretManifestAnalysis.js +96 -0
- package/dist/secretManifestAnalysis.js.map +1 -0
- package/package.json +5 -2
- package/src/analysis.ts +1 -1
- package/src/cliLayoutAnalysis.ts +6 -0
- package/src/commands.ts +7 -6
- package/src/diagnostics.ts +28 -1
- package/src/index.ts +5 -0
- package/src/manifestAnalysis.ts +461 -0
- package/src/readme-usage.ts +6 -1
- package/src/secretManifestAnalysis.ts +241 -0
package/dist/commands.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { analyzeDoctorTargetWithCliLayout } from './cliLayoutAnalysis.js';
|
|
2
2
|
import { countErrorDiagnostics, countWarningDiagnostics, } from './diagnostics.js';
|
|
3
3
|
import { DOCTOR_CAPABILITIES, DOCTOR_COMMAND_CATEGORY } from './packageMetadata.js';
|
|
4
4
|
const COMMAND_CAPABILITIES = {
|
|
@@ -13,7 +13,7 @@ export const DOCTOR_COMMANDS = [
|
|
|
13
13
|
path: ['validate'],
|
|
14
14
|
capability: COMMAND_CAPABILITIES.validate,
|
|
15
15
|
mode: 'validate',
|
|
16
|
-
summary: '
|
|
16
|
+
summary: 'Validate a repo, package, or app-manifest JSON path with the applicable Doctor profile.',
|
|
17
17
|
run: runValidateCommand,
|
|
18
18
|
},
|
|
19
19
|
{
|
|
@@ -82,7 +82,7 @@ export function renderRootHelp(version) {
|
|
|
82
82
|
commandLines,
|
|
83
83
|
'',
|
|
84
84
|
'Path resolution:',
|
|
85
|
-
' Pass
|
|
85
|
+
' Pass a repo/package directory or app-manifest JSON file, or omit [path] to inspect the current directory.',
|
|
86
86
|
'',
|
|
87
87
|
].join('\n');
|
|
88
88
|
}
|
|
@@ -98,13 +98,13 @@ function renderCommandHelp(command) {
|
|
|
98
98
|
` ankh ${DOCTOR_COMMAND_CATEGORY} ${command.path.join(' ')} [path]`,
|
|
99
99
|
'',
|
|
100
100
|
'Path resolution:',
|
|
101
|
-
' Pass
|
|
101
|
+
' Pass a repo/package directory or app-manifest JSON file, or omit [path] to inspect the current directory.',
|
|
102
102
|
'',
|
|
103
103
|
].join('\n');
|
|
104
104
|
}
|
|
105
105
|
function createDoctorCommandServices(overrides = {}) {
|
|
106
106
|
return {
|
|
107
|
-
analyzeTarget: overrides.analyzeTarget ??
|
|
107
|
+
analyzeTarget: overrides.analyzeTarget ?? analyzeDoctorTargetWithCliLayout,
|
|
108
108
|
};
|
|
109
109
|
}
|
|
110
110
|
function parseCommandArguments(command, argv) {
|
package/dist/commands.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AnkhCapabilityId, AnkhCommandDescriptor } from '@ankhorage/contracts/cli';\n\nimport type { DoctorAnalysisResult, DoctorPlannedChange, DoctorTargetMode } from './analysis.js';\nimport { analyzeDoctorTarget } from './analysis.js';\nimport type { DoctorCommandContext, DoctorCommandRunResult } from './commandContext.js';\nimport {\n countErrorDiagnostics,\n countWarningDiagnostics,\n type DoctorDiagnostic,\n} from './diagnostics.js';\nimport { DOCTOR_CAPABILITIES, DOCTOR_COMMAND_CATEGORY } from './packageMetadata.js';\n\ntype DoctorCommandName = 'fix' | 'package' | 'repo' | 'validate';\n\ninterface DoctorCommandRunRequest {\n readonly context: DoctorCommandContext;\n readonly inputPath?: string;\n}\n\ntype DoctorCommandImplementation = (\n request: DoctorCommandRunRequest,\n services: DoctorCommandServices,\n) => Promise<DoctorCommandRunResult>;\n\nexport interface DoctorCommandDefinition {\n readonly capability: AnkhCapabilityId;\n readonly mode: DoctorTargetMode;\n readonly path: readonly [DoctorCommandName];\n readonly standaloneName: DoctorCommandName;\n readonly summary: string;\n readonly run: DoctorCommandImplementation;\n}\n\nexport interface DoctorCommandInvocation {\n readonly argv: readonly string[];\n readonly command: DoctorCommandDefinition;\n readonly context: DoctorCommandContext;\n}\n\nexport interface DoctorCommandServices {\n readonly analyzeTarget: typeof analyzeDoctorTarget;\n}\n\nexport interface RunDoctorCommandOptions {\n readonly runCommandImpl?: RunDoctorCommandImpl;\n readonly services?: Partial<DoctorCommandServices>;\n}\n\nexport type RunDoctorCommandImpl = (\n request: DoctorCommandInvocation,\n options?: RunDoctorCommandOptions,\n) => Promise<DoctorCommandRunResult>;\n\nconst COMMAND_CAPABILITIES = {\n validate: DOCTOR_CAPABILITIES[0],\n fix: DOCTOR_CAPABILITIES[1],\n repo: DOCTOR_CAPABILITIES[2],\n package: DOCTOR_CAPABILITIES[3],\n} as const satisfies Record<DoctorCommandName, AnkhCapabilityId>;\n\nexport const DOCTOR_COMMANDS = [\n {\n standaloneName: 'validate',\n path: ['validate'],\n capability: COMMAND_CAPABILITIES.validate,\n mode: 'validate',\n summary: 'Auto-detect the applicable doctor policy profile and validate a local path.',\n run: runValidateCommand,\n },\n {\n standaloneName: 'fix',\n path: ['fix'],\n capability: COMMAND_CAPABILITIES.fix,\n mode: 'fix',\n summary: 'Build a non-mutating fix plan for deterministic doctor policy violations.',\n run: runFixCommand,\n },\n {\n standaloneName: 'repo',\n path: ['repo'],\n capability: COMMAND_CAPABILITIES.repo,\n mode: 'repo',\n summary: 'Force repo-light diagnostics for a repo/workspace-root candidate path.',\n run: runRepoCommand,\n },\n {\n standaloneName: 'package',\n path: ['package'],\n capability: COMMAND_CAPABILITIES.package,\n mode: 'package',\n summary: 'Force package-light diagnostics for a directory containing package.json.',\n run: runPackageCommand,\n },\n] as const satisfies readonly DoctorCommandDefinition[];\n\nexport async function runDoctorCommand(\n request: DoctorCommandInvocation,\n options: RunDoctorCommandOptions = {},\n): Promise<DoctorCommandRunResult> {\n const services = createDoctorCommandServices(options.services);\n\n try {\n const parsedArgs = parseCommandArguments(request.command, request.argv);\n if (parsedArgs.kind === 'help') {\n request.context.writeStdout(renderCommandHelp(request.command));\n return { exitCode: 0 };\n }\n\n return await request.command.run(\n {\n context: request.context,\n inputPath: parsedArgs.inputPath,\n },\n services,\n );\n } catch (error) {\n request.context.writeStderr(renderCommandFailure(request.command.standaloneName, error));\n return { exitCode: 1 };\n }\n}\n\nexport function createProviderCommandDescriptors(): readonly AnkhCommandDescriptor[] {\n return DOCTOR_COMMANDS.map((command) => ({\n capability: command.capability,\n path: command.path,\n summary: command.summary,\n }));\n}\n\nexport function findDoctorCommandByStandaloneName(value: string): DoctorCommandDefinition | null {\n return DOCTOR_COMMANDS.find((command) => command.standaloneName === value) ?? null;\n}\n\nexport function renderRootHelp(version: string): string {\n const commandLines = DOCTOR_COMMANDS.map(\n (command) => ` ${command.standaloneName.padEnd(8, ' ')} ${command.summary}`,\n ).join('\\n');\n\n return [\n `@ankhorage/doctor v${version}`,\n '',\n 'Usage:',\n ' ankhorage-doctor <command> [path]',\n ` ankh ${DOCTOR_COMMAND_CATEGORY} <command> [path]`,\n '',\n 'Commands:',\n commandLines,\n '',\n 'Path resolution:',\n ' Pass [path], or omit it to validate the current working directory.',\n '',\n ].join('\\n');\n}\n\nexport function renderUnknownCommand(value: string): string {\n return [`Unknown doctor command: ${value}`, '', 'Run ankhorage-doctor --help', ''].join('\\n');\n}\n\nfunction renderCommandHelp(command: DoctorCommandDefinition): string {\n return [\n command.summary,\n '',\n 'Usage:',\n ` ankhorage-doctor ${command.standaloneName} [path]`,\n ` ankh ${DOCTOR_COMMAND_CATEGORY} ${command.path.join(' ')} [path]`,\n '',\n 'Path resolution:',\n ' Pass [path], or omit it to validate the current working directory.',\n '',\n ].join('\\n');\n}\n\nfunction createDoctorCommandServices(\n overrides: Partial<DoctorCommandServices> = {},\n): DoctorCommandServices {\n return {\n analyzeTarget: overrides.analyzeTarget ?? analyzeDoctorTarget,\n };\n}\n\nfunction parseCommandArguments(\n command: DoctorCommandDefinition,\n argv: readonly string[],\n): { readonly kind: 'help' } | { readonly kind: 'run'; readonly inputPath?: string } {\n const [firstArg] = argv;\n\n if (argv.length === 1 && firstArg !== undefined && isHelpToken(firstArg)) {\n return { kind: 'help' };\n }\n\n if (argv.length > 1) {\n throw new Error(`Doctor ${command.standaloneName} accepts at most one path argument.`);\n }\n\n return {\n kind: 'run',\n inputPath: firstArg,\n };\n}\n\nasync function runValidateCommand(\n request: DoctorCommandRunRequest,\n services: DoctorCommandServices,\n): Promise<DoctorCommandRunResult> {\n const result = await services.analyzeTarget({\n cwd: request.context.cwd,\n inputPath: request.inputPath,\n mode: 'validate',\n });\n\n request.context.writeStdout(renderAnalysisReport('validate', result));\n\n return {\n exitCode: countErrorDiagnostics(result.diagnostics) > 0 ? 1 : 0,\n };\n}\n\nasync function runFixCommand(\n request: DoctorCommandRunRequest,\n services: DoctorCommandServices,\n): Promise<DoctorCommandRunResult> {\n const result = await services.analyzeTarget({\n cwd: request.context.cwd,\n inputPath: request.inputPath,\n mode: 'fix',\n });\n\n request.context.writeStdout(renderAnalysisReport('fix', result));\n\n return {\n exitCode: countErrorDiagnostics(result.diagnostics) > 0 ? 1 : 0,\n };\n}\n\nasync function runRepoCommand(\n request: DoctorCommandRunRequest,\n services: DoctorCommandServices,\n): Promise<DoctorCommandRunResult> {\n const result = await services.analyzeTarget({\n cwd: request.context.cwd,\n inputPath: request.inputPath,\n mode: 'repo',\n });\n\n request.context.writeStdout(renderAnalysisReport('repo', result));\n\n return {\n exitCode: countErrorDiagnostics(result.diagnostics) > 0 ? 1 : 0,\n };\n}\n\nasync function runPackageCommand(\n request: DoctorCommandRunRequest,\n services: DoctorCommandServices,\n): Promise<DoctorCommandRunResult> {\n const result = await services.analyzeTarget({\n cwd: request.context.cwd,\n inputPath: request.inputPath,\n mode: 'package',\n });\n\n request.context.writeStdout(renderAnalysisReport('package', result));\n\n return {\n exitCode: countErrorDiagnostics(result.diagnostics) > 0 ? 1 : 0,\n };\n}\n\nfunction renderAnalysisReport(\n commandName: DoctorCommandName,\n result: DoctorAnalysisResult,\n): string {\n const lines = [\n `doctor ${commandName}`,\n `target: ${result.targetPath}`,\n `profile: ${result.profile}`,\n `checks: ${result.appliedChecks.length > 0 ? result.appliedChecks.join(', ') : 'none'}`,\n `repoMarkers: ${result.repoMarkers.length > 0 ? result.repoMarkers.join(', ') : 'none'}`,\n '',\n 'Diagnostics:',\n ];\n\n if (result.diagnostics.length === 0) {\n lines.push(' none');\n } else {\n for (const diagnostic of result.diagnostics) {\n lines.push(renderDiagnosticLine(diagnostic));\n }\n }\n\n if (commandName === 'fix') {\n lines.push('');\n lines.push('Planned changes:');\n\n if (result.plannedChanges.length === 0) {\n lines.push(' none');\n } else {\n for (const change of result.plannedChanges) {\n lines.push(renderPlannedChangeLine(change));\n }\n }\n }\n\n const errorCount = countErrorDiagnostics(result.diagnostics);\n const warningCount = countWarningDiagnostics(result.diagnostics);\n\n lines.push('');\n lines.push(`Summary: ${errorCount} error(s), ${warningCount} warning(s)`);\n lines.push('');\n\n return lines.join('\\n');\n}\n\nfunction renderDiagnosticLine(diagnostic: DoctorDiagnostic): string {\n return ` - ${diagnostic.severity.toUpperCase()} ${diagnostic.ruleId} (${diagnostic.code}): ${diagnostic.message}`;\n}\n\nfunction renderPlannedChangeLine(change: DoctorPlannedChange): string {\n return ` - ${change.ruleId} [${change.kind}]: ${change.description}`;\n}\n\nfunction renderCommandFailure(commandName: DoctorCommandName, error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n return [`Failed to run doctor ${commandName}: ${message}`, ''].join('\\n');\n}\n\nfunction isHelpToken(value: string): boolean {\n return value === '--help' || value === '-h' || value === 'help';\n}\n"]}
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AnkhCapabilityId, AnkhCommandDescriptor } from '@ankhorage/contracts/cli';\n\nimport type { DoctorAnalysisResult, DoctorPlannedChange, DoctorTargetMode } from './analysis.js';\nimport { analyzeDoctorTargetWithCliLayout } from './cliLayoutAnalysis.js';\nimport type { DoctorCommandContext, DoctorCommandRunResult } from './commandContext.js';\nimport {\n countErrorDiagnostics,\n countWarningDiagnostics,\n type DoctorDiagnostic,\n} from './diagnostics.js';\nimport { DOCTOR_CAPABILITIES, DOCTOR_COMMAND_CATEGORY } from './packageMetadata.js';\n\ntype DoctorCommandName = 'fix' | 'package' | 'repo' | 'validate';\n\ninterface DoctorCommandRunRequest {\n readonly context: DoctorCommandContext;\n readonly inputPath?: string;\n}\n\ntype DoctorCommandImplementation = (\n request: DoctorCommandRunRequest,\n services: DoctorCommandServices,\n) => Promise<DoctorCommandRunResult>;\n\nexport interface DoctorCommandDefinition {\n readonly capability: AnkhCapabilityId;\n readonly mode: DoctorTargetMode;\n readonly path: readonly [DoctorCommandName];\n readonly standaloneName: DoctorCommandName;\n readonly summary: string;\n readonly run: DoctorCommandImplementation;\n}\n\nexport interface DoctorCommandInvocation {\n readonly argv: readonly string[];\n readonly command: DoctorCommandDefinition;\n readonly context: DoctorCommandContext;\n}\n\nexport interface DoctorCommandServices {\n readonly analyzeTarget: typeof analyzeDoctorTargetWithCliLayout;\n}\n\nexport interface RunDoctorCommandOptions {\n readonly runCommandImpl?: RunDoctorCommandImpl;\n readonly services?: Partial<DoctorCommandServices>;\n}\n\nexport type RunDoctorCommandImpl = (\n request: DoctorCommandInvocation,\n options?: RunDoctorCommandOptions,\n) => Promise<DoctorCommandRunResult>;\n\nconst COMMAND_CAPABILITIES = {\n validate: DOCTOR_CAPABILITIES[0],\n fix: DOCTOR_CAPABILITIES[1],\n repo: DOCTOR_CAPABILITIES[2],\n package: DOCTOR_CAPABILITIES[3],\n} as const satisfies Record<DoctorCommandName, AnkhCapabilityId>;\n\nexport const DOCTOR_COMMANDS = [\n {\n standaloneName: 'validate',\n path: ['validate'],\n capability: COMMAND_CAPABILITIES.validate,\n mode: 'validate',\n summary:\n 'Validate a repo, package, or app-manifest JSON path with the applicable Doctor profile.',\n run: runValidateCommand,\n },\n {\n standaloneName: 'fix',\n path: ['fix'],\n capability: COMMAND_CAPABILITIES.fix,\n mode: 'fix',\n summary: 'Build a non-mutating fix plan for deterministic doctor policy violations.',\n run: runFixCommand,\n },\n {\n standaloneName: 'repo',\n path: ['repo'],\n capability: COMMAND_CAPABILITIES.repo,\n mode: 'repo',\n summary: 'Force repo-light diagnostics for a repo/workspace-root candidate path.',\n run: runRepoCommand,\n },\n {\n standaloneName: 'package',\n path: ['package'],\n capability: COMMAND_CAPABILITIES.package,\n mode: 'package',\n summary: 'Force package-light diagnostics for a directory containing package.json.',\n run: runPackageCommand,\n },\n] as const satisfies readonly DoctorCommandDefinition[];\n\nexport async function runDoctorCommand(\n request: DoctorCommandInvocation,\n options: RunDoctorCommandOptions = {},\n): Promise<DoctorCommandRunResult> {\n const services = createDoctorCommandServices(options.services);\n\n try {\n const parsedArgs = parseCommandArguments(request.command, request.argv);\n if (parsedArgs.kind === 'help') {\n request.context.writeStdout(renderCommandHelp(request.command));\n return { exitCode: 0 };\n }\n\n return await request.command.run(\n {\n context: request.context,\n inputPath: parsedArgs.inputPath,\n },\n services,\n );\n } catch (error) {\n request.context.writeStderr(renderCommandFailure(request.command.standaloneName, error));\n return { exitCode: 1 };\n }\n}\n\nexport function createProviderCommandDescriptors(): readonly AnkhCommandDescriptor[] {\n return DOCTOR_COMMANDS.map((command) => ({\n capability: command.capability,\n path: command.path,\n summary: command.summary,\n }));\n}\n\nexport function findDoctorCommandByStandaloneName(value: string): DoctorCommandDefinition | null {\n return DOCTOR_COMMANDS.find((command) => command.standaloneName === value) ?? null;\n}\n\nexport function renderRootHelp(version: string): string {\n const commandLines = DOCTOR_COMMANDS.map(\n (command) => ` ${command.standaloneName.padEnd(8, ' ')} ${command.summary}`,\n ).join('\\n');\n\n return [\n `@ankhorage/doctor v${version}`,\n '',\n 'Usage:',\n ' ankhorage-doctor <command> [path]',\n ` ankh ${DOCTOR_COMMAND_CATEGORY} <command> [path]`,\n '',\n 'Commands:',\n commandLines,\n '',\n 'Path resolution:',\n ' Pass a repo/package directory or app-manifest JSON file, or omit [path] to inspect the current directory.',\n '',\n ].join('\\n');\n}\n\nexport function renderUnknownCommand(value: string): string {\n return [`Unknown doctor command: ${value}`, '', 'Run ankhorage-doctor --help', ''].join('\\n');\n}\n\nfunction renderCommandHelp(command: DoctorCommandDefinition): string {\n return [\n command.summary,\n '',\n 'Usage:',\n ` ankhorage-doctor ${command.standaloneName} [path]`,\n ` ankh ${DOCTOR_COMMAND_CATEGORY} ${command.path.join(' ')} [path]`,\n '',\n 'Path resolution:',\n ' Pass a repo/package directory or app-manifest JSON file, or omit [path] to inspect the current directory.',\n '',\n ].join('\\n');\n}\n\nfunction createDoctorCommandServices(\n overrides: Partial<DoctorCommandServices> = {},\n): DoctorCommandServices {\n return {\n analyzeTarget: overrides.analyzeTarget ?? analyzeDoctorTargetWithCliLayout,\n };\n}\n\nfunction parseCommandArguments(\n command: DoctorCommandDefinition,\n argv: readonly string[],\n): { readonly kind: 'help' } | { readonly kind: 'run'; readonly inputPath?: string } {\n const [firstArg] = argv;\n\n if (argv.length === 1 && firstArg !== undefined && isHelpToken(firstArg)) {\n return { kind: 'help' };\n }\n\n if (argv.length > 1) {\n throw new Error(`Doctor ${command.standaloneName} accepts at most one path argument.`);\n }\n\n return {\n kind: 'run',\n inputPath: firstArg,\n };\n}\n\nasync function runValidateCommand(\n request: DoctorCommandRunRequest,\n services: DoctorCommandServices,\n): Promise<DoctorCommandRunResult> {\n const result = await services.analyzeTarget({\n cwd: request.context.cwd,\n inputPath: request.inputPath,\n mode: 'validate',\n });\n\n request.context.writeStdout(renderAnalysisReport('validate', result));\n\n return {\n exitCode: countErrorDiagnostics(result.diagnostics) > 0 ? 1 : 0,\n };\n}\n\nasync function runFixCommand(\n request: DoctorCommandRunRequest,\n services: DoctorCommandServices,\n): Promise<DoctorCommandRunResult> {\n const result = await services.analyzeTarget({\n cwd: request.context.cwd,\n inputPath: request.inputPath,\n mode: 'fix',\n });\n\n request.context.writeStdout(renderAnalysisReport('fix', result));\n\n return {\n exitCode: countErrorDiagnostics(result.diagnostics) > 0 ? 1 : 0,\n };\n}\n\nasync function runRepoCommand(\n request: DoctorCommandRunRequest,\n services: DoctorCommandServices,\n): Promise<DoctorCommandRunResult> {\n const result = await services.analyzeTarget({\n cwd: request.context.cwd,\n inputPath: request.inputPath,\n mode: 'repo',\n });\n\n request.context.writeStdout(renderAnalysisReport('repo', result));\n\n return {\n exitCode: countErrorDiagnostics(result.diagnostics) > 0 ? 1 : 0,\n };\n}\n\nasync function runPackageCommand(\n request: DoctorCommandRunRequest,\n services: DoctorCommandServices,\n): Promise<DoctorCommandRunResult> {\n const result = await services.analyzeTarget({\n cwd: request.context.cwd,\n inputPath: request.inputPath,\n mode: 'package',\n });\n\n request.context.writeStdout(renderAnalysisReport('package', result));\n\n return {\n exitCode: countErrorDiagnostics(result.diagnostics) > 0 ? 1 : 0,\n };\n}\n\nfunction renderAnalysisReport(\n commandName: DoctorCommandName,\n result: DoctorAnalysisResult,\n): string {\n const lines = [\n `doctor ${commandName}`,\n `target: ${result.targetPath}`,\n `profile: ${result.profile}`,\n `checks: ${result.appliedChecks.length > 0 ? result.appliedChecks.join(', ') : 'none'}`,\n `repoMarkers: ${result.repoMarkers.length > 0 ? result.repoMarkers.join(', ') : 'none'}`,\n '',\n 'Diagnostics:',\n ];\n\n if (result.diagnostics.length === 0) {\n lines.push(' none');\n } else {\n for (const diagnostic of result.diagnostics) {\n lines.push(renderDiagnosticLine(diagnostic));\n }\n }\n\n if (commandName === 'fix') {\n lines.push('');\n lines.push('Planned changes:');\n\n if (result.plannedChanges.length === 0) {\n lines.push(' none');\n } else {\n for (const change of result.plannedChanges) {\n lines.push(renderPlannedChangeLine(change));\n }\n }\n }\n\n const errorCount = countErrorDiagnostics(result.diagnostics);\n const warningCount = countWarningDiagnostics(result.diagnostics);\n\n lines.push('');\n lines.push(`Summary: ${errorCount} error(s), ${warningCount} warning(s)`);\n lines.push('');\n\n return lines.join('\\n');\n}\n\nfunction renderDiagnosticLine(diagnostic: DoctorDiagnostic): string {\n return ` - ${diagnostic.severity.toUpperCase()} ${diagnostic.ruleId} (${diagnostic.code}): ${diagnostic.message}`;\n}\n\nfunction renderPlannedChangeLine(change: DoctorPlannedChange): string {\n return ` - ${change.ruleId} [${change.kind}]: ${change.description}`;\n}\n\nfunction renderCommandFailure(commandName: DoctorCommandName, error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n return [`Failed to run doctor ${commandName}: ${message}`, ''].join('\\n');\n}\n\nfunction isHelpToken(value: string): boolean {\n return value === '--help' || value === '-h' || value === 'help';\n}\n"]}
|
package/dist/diagnostics.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export type DoctorDiagnosticCode = 'field-invalid' | 'field-missing' | 'invalid-ankh-metadata' | 'invalid-package-json' | 'missing-dependency' | 'missing-path' | 'missing-script' | 'provider-source-import-failed' | 'provider-source-missing' | 'target-not-directory' | 'target-not-found' | 'unsupported-target';
|
|
1
|
+
export type DoctorDiagnosticCode = 'field-invalid' | 'field-missing' | 'invalid-ankh-metadata' | 'invalid-app-manifest-json' | 'invalid-package-json' | 'missing-dependency' | 'missing-path' | 'missing-script' | 'provider-source-import-failed' | 'provider-source-missing' | 'target-not-directory' | 'target-not-found' | 'unsupported-target';
|
|
2
2
|
export type DoctorDiagnosticSeverity = 'error' | 'warning';
|
|
3
|
-
export type DoctorPolicyProfile = 'integration-monorepo' | 'public-package' | 'unknown';
|
|
4
|
-
export type DoctorRuleId = 'integration.package.ankh.disallowed' | 'integration.package.private.required' | 'integration.package.workspaces.required' | 'package.ankh.capabilities.match-provider' | 'package.ankh.present.valid-shape' | 'package.ankh.provider-path.required' | 'package.ankh.required-for-provider' | 'package.cli.export.required' | 'package.cli.index.required' | 'package.cli.root-file.disallowed' | 'package.dependencies.ankh-workspace-alias.disallowed' | 'package.dependencies.ankhorage4-source.disallowed' | 'package.dependencies.changesets.required' | 'package.dependencies.devtools.required' | 'package.dependencies.paradox.required' | 'package.dependencies.types-bun.required' | 'package.dependencies.types-node.required' | 'package.dependencies.typescript.required' | 'package.imports.ankh-workspace-alias.disallowed' | 'package.imports.ankhorage4-source.disallowed' | 'package.json.bugs.required' | 'package.json.description.required' | 'package.json.exports.required' | 'package.json.files.required' | 'package.json.homepage.required' | 'package.json.keywords.required' | 'package.json.license.required' | 'package.json.name.required' | 'package.json.package-manager.bun' | 'package.json.package-manager.required' | 'package.json.private.public-package-disallowed' | 'package.json.publish-config.public' | 'package.json.publish-config.required' | 'package.json.repository.required' | 'package.json.type.module' | 'package.json.type.required' | 'package.json.version.required' | 'package.scripts.build.required' | 'package.scripts.changeset-status.required' | 'package.scripts.changeset.required' | 'package.scripts.docs.required' | 'package.scripts.format-check.required' | 'package.scripts.format.required' | 'package.scripts.knip.required' | 'package.scripts.lint-fix.required' | 'package.scripts.lint.required' | 'package.scripts.test.required' | 'package.scripts.typecheck.required' | 'package.scripts.version-packages.required' | 'provider.commands.match-capabilities' | 'provider.commands.match-handlers' | 'provider.source.importable' | 'provider.source.required' | 'repo.changelog.required' | 'repo.changeset.required' | 'repo.license.required' | 'repo.readme.required' | 'repo.workflows.required' | 'studio.dependencies.dnd.required' | 'studio.dependencies.runtime.required' | 'studio.imports.dnd.required' | 'studio.imports.runtime.required' | 'target.package-json.required' | 'target.path.directory' | 'target.path.exists' | 'target.profile.supported' | 'target.repo-markers.required';
|
|
3
|
+
export type DoctorPolicyProfile = 'app-manifest' | 'integration-monorepo' | 'public-package' | 'unknown';
|
|
4
|
+
export type DoctorRuleId = 'integration.package.ankh.disallowed' | 'integration.package.private.required' | 'integration.package.workspaces.required' | 'manifest.auth.authorization.engine.valid' | 'manifest.auth.authorization.kind.valid' | 'manifest.auth.authorization.valid-shape' | 'manifest.auth.flow.post-sign-in.distinct' | 'manifest.auth.flow.route.required' | 'manifest.auth.flow.route.unique' | 'manifest.auth.flow.route.valid' | 'manifest.auth.flow.unauthorized-route.valid' | 'manifest.auth.flow.valid-shape' | 'manifest.auth.oauth.callback-route.valid' | 'manifest.auth.oauth.provider.credentials-ref.required' | 'manifest.auth.oauth.provider.credentials-ref.valid' | 'manifest.auth.oauth.provider.duplicate' | 'manifest.auth.oauth.provider.inline-secret.disallowed' | 'manifest.auth.oauth.providers.valid' | 'manifest.auth.oauth.valid-shape' | 'manifest.auth.valid-shape' | 'manifest.infra.valid-shape' | 'manifest.json.readable' | 'manifest.json.valid' | 'manifest.root.valid-shape' | 'manifest.secret-store.provider.required' | 'manifest.secret-store.provider.valid' | 'manifest.secret-store.valid-shape' | 'manifest.settings.auth-flow.removed' | 'package.ankh.capabilities.match-provider' | 'package.ankh.present.valid-shape' | 'package.ankh.provider-path.required' | 'package.ankh.required-for-provider' | 'package.cli.export.required' | 'package.cli.index.required' | 'package.cli.root-file.disallowed' | 'package.dependencies.ankh-workspace-alias.disallowed' | 'package.dependencies.ankhorage4-source.disallowed' | 'package.dependencies.changesets.required' | 'package.dependencies.devtools.required' | 'package.dependencies.paradox.required' | 'package.dependencies.types-bun.required' | 'package.dependencies.types-node.required' | 'package.dependencies.typescript.required' | 'package.imports.ankh-workspace-alias.disallowed' | 'package.imports.ankhorage4-source.disallowed' | 'package.json.bugs.required' | 'package.json.description.required' | 'package.json.exports.required' | 'package.json.files.required' | 'package.json.homepage.required' | 'package.json.keywords.required' | 'package.json.license.required' | 'package.json.name.required' | 'package.json.package-manager.bun' | 'package.json.package-manager.required' | 'package.json.private.public-package-disallowed' | 'package.json.publish-config.public' | 'package.json.publish-config.required' | 'package.json.repository.required' | 'package.json.type.module' | 'package.json.type.required' | 'package.json.version.required' | 'package.scripts.build.required' | 'package.scripts.changeset-status.required' | 'package.scripts.changeset.required' | 'package.scripts.docs.required' | 'package.scripts.format-check.required' | 'package.scripts.format.required' | 'package.scripts.knip.required' | 'package.scripts.lint-fix.required' | 'package.scripts.lint.required' | 'package.scripts.test.required' | 'package.scripts.typecheck.required' | 'package.scripts.version-packages.required' | 'provider.commands.match-capabilities' | 'provider.commands.match-handlers' | 'provider.source.importable' | 'provider.source.required' | 'repo.changelog.required' | 'repo.changeset.required' | 'repo.license.required' | 'repo.readme.required' | 'repo.workflows.required' | 'studio.dependencies.dnd.required' | 'studio.dependencies.runtime.required' | 'studio.imports.dnd.required' | 'studio.imports.runtime.required' | 'target.package-json.required' | 'target.path.directory' | 'target.path.exists' | 'target.profile.supported' | 'target.repo-markers.required';
|
|
5
5
|
export interface DoctorDiagnostic {
|
|
6
6
|
readonly code: DoctorDiagnosticCode;
|
|
7
7
|
readonly message: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAC5B,eAAe,GACf,eAAe,GACf,uBAAuB,GACvB,sBAAsB,GACtB,oBAAoB,GACpB,cAAc,GACd,gBAAgB,GAChB,+BAA+B,GAC/B,yBAAyB,GACzB,sBAAsB,GACtB,kBAAkB,GAClB,oBAAoB,CAAC;AAEzB,MAAM,MAAM,wBAAwB,GAAG,OAAO,GAAG,SAAS,CAAC;AAC3D,MAAM,MAAM,mBAAmB,GAAG,sBAAsB,GAAG,gBAAgB,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAC5B,eAAe,GACf,eAAe,GACf,uBAAuB,GACvB,2BAA2B,GAC3B,sBAAsB,GACtB,oBAAoB,GACpB,cAAc,GACd,gBAAgB,GAChB,+BAA+B,GAC/B,yBAAyB,GACzB,sBAAsB,GACtB,kBAAkB,GAClB,oBAAoB,CAAC;AAEzB,MAAM,MAAM,wBAAwB,GAAG,OAAO,GAAG,SAAS,CAAC;AAC3D,MAAM,MAAM,mBAAmB,GAC7B,cAAc,GAAG,sBAAsB,GAAG,gBAAgB,GAAG,SAAS,CAAC;AACzE,MAAM,MAAM,YAAY,GACpB,qCAAqC,GACrC,sCAAsC,GACtC,yCAAyC,GACzC,0CAA0C,GAC1C,wCAAwC,GACxC,yCAAyC,GACzC,0CAA0C,GAC1C,mCAAmC,GACnC,iCAAiC,GACjC,gCAAgC,GAChC,6CAA6C,GAC7C,gCAAgC,GAChC,0CAA0C,GAC1C,uDAAuD,GACvD,oDAAoD,GACpD,wCAAwC,GACxC,uDAAuD,GACvD,qCAAqC,GACrC,iCAAiC,GACjC,2BAA2B,GAC3B,4BAA4B,GAC5B,wBAAwB,GACxB,qBAAqB,GACrB,2BAA2B,GAC3B,yCAAyC,GACzC,sCAAsC,GACtC,mCAAmC,GACnC,qCAAqC,GACrC,0CAA0C,GAC1C,kCAAkC,GAClC,qCAAqC,GACrC,oCAAoC,GACpC,6BAA6B,GAC7B,4BAA4B,GAC5B,kCAAkC,GAClC,sDAAsD,GACtD,mDAAmD,GACnD,0CAA0C,GAC1C,wCAAwC,GACxC,uCAAuC,GACvC,yCAAyC,GACzC,0CAA0C,GAC1C,0CAA0C,GAC1C,iDAAiD,GACjD,8CAA8C,GAC9C,4BAA4B,GAC5B,mCAAmC,GACnC,+BAA+B,GAC/B,6BAA6B,GAC7B,gCAAgC,GAChC,gCAAgC,GAChC,+BAA+B,GAC/B,4BAA4B,GAC5B,kCAAkC,GAClC,uCAAuC,GACvC,gDAAgD,GAChD,oCAAoC,GACpC,sCAAsC,GACtC,kCAAkC,GAClC,0BAA0B,GAC1B,4BAA4B,GAC5B,+BAA+B,GAC/B,gCAAgC,GAChC,2CAA2C,GAC3C,oCAAoC,GACpC,+BAA+B,GAC/B,uCAAuC,GACvC,iCAAiC,GACjC,+BAA+B,GAC/B,mCAAmC,GACnC,+BAA+B,GAC/B,+BAA+B,GAC/B,oCAAoC,GACpC,2CAA2C,GAC3C,sCAAsC,GACtC,kCAAkC,GAClC,4BAA4B,GAC5B,0BAA0B,GAC1B,yBAAyB,GACzB,yBAAyB,GACzB,uBAAuB,GACvB,sBAAsB,GACtB,yBAAyB,GACzB,kCAAkC,GAClC,sCAAsC,GACtC,6BAA6B,GAC7B,iCAAiC,GACjC,8BAA8B,GAC9B,uBAAuB,GACvB,oBAAoB,GACpB,0BAA0B,GAC1B,8BAA8B,CAAC;AAEnC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,wBAAwB,CAAC;CAC7C;AAED,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,CAEtF;AAED,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,CAExF"}
|
package/dist/diagnostics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAyHA,MAAM,UAAU,qBAAqB,CAAC,WAAwC;IAC5E,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC;AACpF,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,WAAwC;IAC9E,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;AACtF,CAAC","sourcesContent":["export type DoctorDiagnosticCode =\n | 'field-invalid'\n | 'field-missing'\n | 'invalid-ankh-metadata'\n | 'invalid-app-manifest-json'\n | 'invalid-package-json'\n | 'missing-dependency'\n | 'missing-path'\n | 'missing-script'\n | 'provider-source-import-failed'\n | 'provider-source-missing'\n | 'target-not-directory'\n | 'target-not-found'\n | 'unsupported-target';\n\nexport type DoctorDiagnosticSeverity = 'error' | 'warning';\nexport type DoctorPolicyProfile =\n 'app-manifest' | 'integration-monorepo' | 'public-package' | 'unknown';\nexport type DoctorRuleId =\n | 'integration.package.ankh.disallowed'\n | 'integration.package.private.required'\n | 'integration.package.workspaces.required'\n | 'manifest.auth.authorization.engine.valid'\n | 'manifest.auth.authorization.kind.valid'\n | 'manifest.auth.authorization.valid-shape'\n | 'manifest.auth.flow.post-sign-in.distinct'\n | 'manifest.auth.flow.route.required'\n | 'manifest.auth.flow.route.unique'\n | 'manifest.auth.flow.route.valid'\n | 'manifest.auth.flow.unauthorized-route.valid'\n | 'manifest.auth.flow.valid-shape'\n | 'manifest.auth.oauth.callback-route.valid'\n | 'manifest.auth.oauth.provider.credentials-ref.required'\n | 'manifest.auth.oauth.provider.credentials-ref.valid'\n | 'manifest.auth.oauth.provider.duplicate'\n | 'manifest.auth.oauth.provider.inline-secret.disallowed'\n | 'manifest.auth.oauth.providers.valid'\n | 'manifest.auth.oauth.valid-shape'\n | 'manifest.auth.valid-shape'\n | 'manifest.infra.valid-shape'\n | 'manifest.json.readable'\n | 'manifest.json.valid'\n | 'manifest.root.valid-shape'\n | 'manifest.secret-store.provider.required'\n | 'manifest.secret-store.provider.valid'\n | 'manifest.secret-store.valid-shape'\n | 'manifest.settings.auth-flow.removed'\n | 'package.ankh.capabilities.match-provider'\n | 'package.ankh.present.valid-shape'\n | 'package.ankh.provider-path.required'\n | 'package.ankh.required-for-provider'\n | 'package.cli.export.required'\n | 'package.cli.index.required'\n | 'package.cli.root-file.disallowed'\n | 'package.dependencies.ankh-workspace-alias.disallowed'\n | 'package.dependencies.ankhorage4-source.disallowed'\n | 'package.dependencies.changesets.required'\n | 'package.dependencies.devtools.required'\n | 'package.dependencies.paradox.required'\n | 'package.dependencies.types-bun.required'\n | 'package.dependencies.types-node.required'\n | 'package.dependencies.typescript.required'\n | 'package.imports.ankh-workspace-alias.disallowed'\n | 'package.imports.ankhorage4-source.disallowed'\n | 'package.json.bugs.required'\n | 'package.json.description.required'\n | 'package.json.exports.required'\n | 'package.json.files.required'\n | 'package.json.homepage.required'\n | 'package.json.keywords.required'\n | 'package.json.license.required'\n | 'package.json.name.required'\n | 'package.json.package-manager.bun'\n | 'package.json.package-manager.required'\n | 'package.json.private.public-package-disallowed'\n | 'package.json.publish-config.public'\n | 'package.json.publish-config.required'\n | 'package.json.repository.required'\n | 'package.json.type.module'\n | 'package.json.type.required'\n | 'package.json.version.required'\n | 'package.scripts.build.required'\n | 'package.scripts.changeset-status.required'\n | 'package.scripts.changeset.required'\n | 'package.scripts.docs.required'\n | 'package.scripts.format-check.required'\n | 'package.scripts.format.required'\n | 'package.scripts.knip.required'\n | 'package.scripts.lint-fix.required'\n | 'package.scripts.lint.required'\n | 'package.scripts.test.required'\n | 'package.scripts.typecheck.required'\n | 'package.scripts.version-packages.required'\n | 'provider.commands.match-capabilities'\n | 'provider.commands.match-handlers'\n | 'provider.source.importable'\n | 'provider.source.required'\n | 'repo.changelog.required'\n | 'repo.changeset.required'\n | 'repo.license.required'\n | 'repo.readme.required'\n | 'repo.workflows.required'\n | 'studio.dependencies.dnd.required'\n | 'studio.dependencies.runtime.required'\n | 'studio.imports.dnd.required'\n | 'studio.imports.runtime.required'\n | 'target.package-json.required'\n | 'target.path.directory'\n | 'target.path.exists'\n | 'target.profile.supported'\n | 'target.repo-markers.required';\n\nexport interface DoctorDiagnostic {\n readonly code: DoctorDiagnosticCode;\n readonly message: string;\n readonly path: string;\n readonly profile: DoctorPolicyProfile;\n readonly ruleId: DoctorRuleId;\n readonly severity: DoctorDiagnosticSeverity;\n}\n\nexport function countErrorDiagnostics(diagnostics: readonly DoctorDiagnostic[]): number {\n return diagnostics.filter((diagnostic) => diagnostic.severity === 'error').length;\n}\n\nexport function countWarningDiagnostics(diagnostics: readonly DoctorDiagnostic[]): number {\n return diagnostics.filter((diagnostic) => diagnostic.severity === 'warning').length;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,5 +6,6 @@ export { analyzeDoctorTargetWithCliLayout } from './cliLayoutAnalysis.js';
|
|
|
6
6
|
export type { DoctorCommandContext, DoctorCommandRunResult } from './commandContext.js';
|
|
7
7
|
export { createDefaultCommandContext } from './commandContext.js';
|
|
8
8
|
export type { DoctorDiagnostic, DoctorDiagnosticCode, DoctorDiagnosticSeverity, DoctorPolicyProfile, DoctorRuleId, } from './diagnostics.js';
|
|
9
|
+
export { analyzeAppManifest, analyzeAppManifestFile, analyzeAppManifestTarget, } from './manifestAnalysis.js';
|
|
9
10
|
export { DOCTOR_CAPABILITIES, DOCTOR_COMMAND_CATEGORY, DOCTOR_PACKAGE_METADATA, DOCTOR_PACKAGE_NAME, DOCTOR_PACKAGE_VERSION, } from './packageMetadata.js';
|
|
10
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EACL,2BAA2B,EAC3B,KAAK,kCAAkC,GACxC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,gCAAgC,EAAE,MAAM,wBAAwB,CAAC;AAC1E,YAAY,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACxF,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAClE,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,wBAAwB,EACxB,mBAAmB,EACnB,YAAY,GACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EACL,2BAA2B,EAC3B,KAAK,kCAAkC,GACxC,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,gCAAgC,EAAE,MAAM,wBAAwB,CAAC;AAC1E,YAAY,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACxF,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAClE,YAAY,EACV,gBAAgB,EAChB,oBAAoB,EACpB,wBAAwB,EACxB,mBAAmB,EACnB,YAAY,GACb,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3,5 +3,6 @@ export { createDoctorRuntimeProvider, } from './cli/index.js';
|
|
|
3
3
|
export { runCli } from './cli/standalone.js';
|
|
4
4
|
export { analyzeDoctorTargetWithCliLayout } from './cliLayoutAnalysis.js';
|
|
5
5
|
export { createDefaultCommandContext } from './commandContext.js';
|
|
6
|
+
export { analyzeAppManifest, analyzeAppManifestFile, analyzeAppManifestTarget, } from './manifestAnalysis.js';
|
|
6
7
|
export { DOCTOR_CAPABILITIES, DOCTOR_COMMAND_CATEGORY, DOCTOR_PACKAGE_METADATA, DOCTOR_PACKAGE_NAME, DOCTOR_PACKAGE_VERSION, } from './packageMetadata.js';
|
|
7
8
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EACL,2BAA2B,GAE5B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,gCAAgC,EAAE,MAAM,wBAAwB,CAAC;AAE1E,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAQlE,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC","sourcesContent":["export type {\n DoctorAnalysisRequest,\n DoctorAnalysisResult,\n DoctorFixPlan,\n DoctorPlannedChange,\n DoctorPlannedChangeKind,\n DoctorTargetCheck,\n DoctorTargetMode,\n} from './analysis.js';\nexport { analyzeDoctorTarget } from './analysis.js';\nexport {\n createDoctorRuntimeProvider,\n type CreateDoctorRuntimeProviderOptions,\n} from './cli/index.js';\nexport { runCli } from './cli/standalone.js';\nexport { analyzeDoctorTargetWithCliLayout } from './cliLayoutAnalysis.js';\nexport type { DoctorCommandContext, DoctorCommandRunResult } from './commandContext.js';\nexport { createDefaultCommandContext } from './commandContext.js';\nexport type {\n DoctorDiagnostic,\n DoctorDiagnosticCode,\n DoctorDiagnosticSeverity,\n DoctorPolicyProfile,\n DoctorRuleId,\n} from './diagnostics.js';\nexport {\n DOCTOR_CAPABILITIES,\n DOCTOR_COMMAND_CATEGORY,\n DOCTOR_PACKAGE_METADATA,\n DOCTOR_PACKAGE_NAME,\n DOCTOR_PACKAGE_VERSION,\n} from './packageMetadata.js';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EACL,2BAA2B,GAE5B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,OAAO,EAAE,gCAAgC,EAAE,MAAM,wBAAwB,CAAC;AAE1E,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAQlE,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC","sourcesContent":["export type {\n DoctorAnalysisRequest,\n DoctorAnalysisResult,\n DoctorFixPlan,\n DoctorPlannedChange,\n DoctorPlannedChangeKind,\n DoctorTargetCheck,\n DoctorTargetMode,\n} from './analysis.js';\nexport { analyzeDoctorTarget } from './analysis.js';\nexport {\n createDoctorRuntimeProvider,\n type CreateDoctorRuntimeProviderOptions,\n} from './cli/index.js';\nexport { runCli } from './cli/standalone.js';\nexport { analyzeDoctorTargetWithCliLayout } from './cliLayoutAnalysis.js';\nexport type { DoctorCommandContext, DoctorCommandRunResult } from './commandContext.js';\nexport { createDefaultCommandContext } from './commandContext.js';\nexport type {\n DoctorDiagnostic,\n DoctorDiagnosticCode,\n DoctorDiagnosticSeverity,\n DoctorPolicyProfile,\n DoctorRuleId,\n} from './diagnostics.js';\nexport {\n analyzeAppManifest,\n analyzeAppManifestFile,\n analyzeAppManifestTarget,\n} from './manifestAnalysis.js';\nexport {\n DOCTOR_CAPABILITIES,\n DOCTOR_COMMAND_CATEGORY,\n DOCTOR_PACKAGE_METADATA,\n DOCTOR_PACKAGE_NAME,\n DOCTOR_PACKAGE_VERSION,\n} from './packageMetadata.js';\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DoctorAnalysisRequest, DoctorAnalysisResult } from './analysis.js';
|
|
2
|
+
import type { DoctorDiagnostic } from './diagnostics.js';
|
|
3
|
+
export declare function analyzeAppManifestTarget(request: DoctorAnalysisRequest): Promise<DoctorAnalysisResult | null>;
|
|
4
|
+
export declare function analyzeAppManifestFile(filePath: string): Promise<DoctorDiagnostic[]>;
|
|
5
|
+
export declare function analyzeAppManifest(manifest: unknown, manifestPath?: string): DoctorDiagnostic[];
|
|
6
|
+
//# sourceMappingURL=manifestAnalysis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifestAnalysis.d.ts","sourceRoot":"","sources":["../src/manifestAnalysis.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EAGrB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,gBAAgB,EAIjB,MAAM,kBAAkB,CAAC;AAsB1B,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAmCtC;AAED,wBAAsB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAgC1F;AAED,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,OAAO,EACjB,YAAY,SAAkB,GAC7B,gBAAgB,EAAE,CAuEpB"}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { AUTHZ_ENGINES, AUTHZ_KINDS } from '@ankhorage/contracts';
|
|
4
|
+
import { resolveAuthFlow } from '@ankhorage/contracts/auth';
|
|
5
|
+
import { analyzeSecretStoreManifest } from './secretManifestAnalysis.js';
|
|
6
|
+
const MANIFEST_PROFILE = 'app-manifest';
|
|
7
|
+
const AUTH_ACTION_ROUTE_KEYS = [
|
|
8
|
+
'signInRoute',
|
|
9
|
+
'signUpRoute',
|
|
10
|
+
'signOutRoute',
|
|
11
|
+
'forgotPasswordRoute',
|
|
12
|
+
'otpRoute',
|
|
13
|
+
];
|
|
14
|
+
const OPTIONAL_ROUTE_KEYS = [
|
|
15
|
+
'signUpRoute',
|
|
16
|
+
'signOutRoute',
|
|
17
|
+
'forgotPasswordRoute',
|
|
18
|
+
'otpRoute',
|
|
19
|
+
'unauthorizedRoute',
|
|
20
|
+
];
|
|
21
|
+
export async function analyzeAppManifestTarget(request) {
|
|
22
|
+
const targetPath = path.resolve(request.cwd, request.inputPath ?? '.');
|
|
23
|
+
const stats = await statOrNull(targetPath);
|
|
24
|
+
if (stats === null ||
|
|
25
|
+
!stats.isFile() ||
|
|
26
|
+
path.extname(targetPath).toLowerCase() !== '.json' ||
|
|
27
|
+
(request.mode !== 'validate' && request.mode !== 'fix')) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const diagnostics = await analyzeAppManifestFile(targetPath);
|
|
31
|
+
const plannedChanges = [];
|
|
32
|
+
const fixPlan = request.mode === 'fix'
|
|
33
|
+
? {
|
|
34
|
+
changes: plannedChanges,
|
|
35
|
+
diagnostics,
|
|
36
|
+
profile: MANIFEST_PROFILE,
|
|
37
|
+
targetPath,
|
|
38
|
+
}
|
|
39
|
+
: null;
|
|
40
|
+
return {
|
|
41
|
+
appliedChecks: ['manifest'],
|
|
42
|
+
diagnostics,
|
|
43
|
+
fixPlan,
|
|
44
|
+
hasPackageJson: false,
|
|
45
|
+
plannedChanges,
|
|
46
|
+
profile: MANIFEST_PROFILE,
|
|
47
|
+
repoMarkers: [],
|
|
48
|
+
targetPath,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export async function analyzeAppManifestFile(filePath) {
|
|
52
|
+
let source;
|
|
53
|
+
try {
|
|
54
|
+
source = await fs.readFile(filePath, 'utf8');
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
return [
|
|
58
|
+
createManifestDiagnostic({
|
|
59
|
+
code: 'invalid-app-manifest-json',
|
|
60
|
+
message: `Could not read app manifest JSON: ${toErrorMessage(error)}`,
|
|
61
|
+
path: filePath,
|
|
62
|
+
ruleId: 'manifest.json.readable',
|
|
63
|
+
}),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
let manifest;
|
|
67
|
+
try {
|
|
68
|
+
manifest = JSON.parse(source);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return [
|
|
72
|
+
createManifestDiagnostic({
|
|
73
|
+
code: 'invalid-app-manifest-json',
|
|
74
|
+
message: `App manifest must contain valid JSON: ${toErrorMessage(error)}`,
|
|
75
|
+
path: filePath,
|
|
76
|
+
ruleId: 'manifest.json.valid',
|
|
77
|
+
}),
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
return analyzeAppManifest(manifest, filePath);
|
|
81
|
+
}
|
|
82
|
+
export function analyzeAppManifest(manifest, manifestPath = 'manifest.json') {
|
|
83
|
+
if (!isRecord(manifest)) {
|
|
84
|
+
return [
|
|
85
|
+
createManifestDiagnostic({
|
|
86
|
+
code: 'field-invalid',
|
|
87
|
+
message: 'App manifest root must be a JSON object.',
|
|
88
|
+
path: manifestPath,
|
|
89
|
+
ruleId: 'manifest.root.valid-shape',
|
|
90
|
+
}),
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
const diagnostics = [];
|
|
94
|
+
const { settings } = manifest;
|
|
95
|
+
if (isRecord(settings) && hasOwn(settings, 'authFlow')) {
|
|
96
|
+
diagnostics.push(createManifestDiagnostic({
|
|
97
|
+
code: 'field-invalid',
|
|
98
|
+
message: 'settings.authFlow was removed. Move this configuration manually to infra.auth.flow; Doctor will not migrate it automatically.',
|
|
99
|
+
path: manifestPath,
|
|
100
|
+
ruleId: 'manifest.settings.auth-flow.removed',
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
const { infra } = manifest;
|
|
104
|
+
if (infra === undefined) {
|
|
105
|
+
return diagnostics;
|
|
106
|
+
}
|
|
107
|
+
if (!isRecord(infra)) {
|
|
108
|
+
diagnostics.push(createManifestDiagnostic({
|
|
109
|
+
code: 'field-invalid',
|
|
110
|
+
message: 'manifest.infra must be a JSON object when present.',
|
|
111
|
+
path: manifestPath,
|
|
112
|
+
ruleId: 'manifest.infra.valid-shape',
|
|
113
|
+
}));
|
|
114
|
+
return diagnostics;
|
|
115
|
+
}
|
|
116
|
+
diagnostics.push(...analyzeSecretStoreManifest(infra, manifestPath));
|
|
117
|
+
const { auth } = infra;
|
|
118
|
+
if (auth === undefined) {
|
|
119
|
+
return diagnostics;
|
|
120
|
+
}
|
|
121
|
+
if (!isRecord(auth)) {
|
|
122
|
+
diagnostics.push(createManifestDiagnostic({
|
|
123
|
+
code: 'field-invalid',
|
|
124
|
+
message: 'manifest.infra.auth must be a JSON object when present.',
|
|
125
|
+
path: manifestPath,
|
|
126
|
+
ruleId: 'manifest.auth.valid-shape',
|
|
127
|
+
}));
|
|
128
|
+
return diagnostics;
|
|
129
|
+
}
|
|
130
|
+
const flow = parseAuthFlow(auth.flow, manifestPath, diagnostics);
|
|
131
|
+
if (flow !== null) {
|
|
132
|
+
validateAuthFlowRelationships(flow, manifestPath, diagnostics);
|
|
133
|
+
}
|
|
134
|
+
validateAuthorization(auth.authorization, manifestPath, diagnostics);
|
|
135
|
+
return diagnostics;
|
|
136
|
+
}
|
|
137
|
+
function parseAuthFlow(value, manifestPath, diagnostics) {
|
|
138
|
+
if (value === undefined) {
|
|
139
|
+
return resolveAuthFlow();
|
|
140
|
+
}
|
|
141
|
+
if (!isRecord(value)) {
|
|
142
|
+
diagnostics.push(createManifestDiagnostic({
|
|
143
|
+
code: 'field-invalid',
|
|
144
|
+
message: 'manifest.infra.auth.flow must be a JSON object when present.',
|
|
145
|
+
path: manifestPath,
|
|
146
|
+
ruleId: 'manifest.auth.flow.valid-shape',
|
|
147
|
+
}));
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const signInRoute = readRequiredRoute(value, 'signInRoute', manifestPath, diagnostics);
|
|
151
|
+
const postSignInRoute = readRequiredRoute(value, 'postSignInRoute', manifestPath, diagnostics);
|
|
152
|
+
const optionalRoutes = readOptionalRoutes(value, manifestPath, diagnostics);
|
|
153
|
+
if (signInRoute === null || postSignInRoute === null || optionalRoutes === null) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
return resolveAuthFlow({
|
|
157
|
+
signInRoute,
|
|
158
|
+
postSignInRoute,
|
|
159
|
+
...optionalRoutes,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function readRequiredRoute(flow, key, manifestPath, diagnostics) {
|
|
163
|
+
const value = flow[key];
|
|
164
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
165
|
+
diagnostics.push(createManifestDiagnostic({
|
|
166
|
+
code: value === undefined ? 'field-missing' : 'field-invalid',
|
|
167
|
+
message: `manifest.infra.auth.flow.${key} must be a non-empty string.`,
|
|
168
|
+
path: manifestPath,
|
|
169
|
+
ruleId: 'manifest.auth.flow.route.required',
|
|
170
|
+
}));
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
function readOptionalRoutes(flow, manifestPath, diagnostics) {
|
|
176
|
+
const routes = {};
|
|
177
|
+
let valid = true;
|
|
178
|
+
for (const key of OPTIONAL_ROUTE_KEYS) {
|
|
179
|
+
const value = flow[key];
|
|
180
|
+
if (value === undefined) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
184
|
+
valid = false;
|
|
185
|
+
diagnostics.push(createManifestDiagnostic({
|
|
186
|
+
code: 'field-invalid',
|
|
187
|
+
message: `manifest.infra.auth.flow.${key} must be a non-empty string when present.`,
|
|
188
|
+
path: manifestPath,
|
|
189
|
+
ruleId: 'manifest.auth.flow.route.valid',
|
|
190
|
+
}));
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
routes[key] = value;
|
|
194
|
+
}
|
|
195
|
+
return valid ? routes : null;
|
|
196
|
+
}
|
|
197
|
+
function validateAuthFlowRelationships(flow, manifestPath, diagnostics) {
|
|
198
|
+
const actionRoutes = AUTH_ACTION_ROUTE_KEYS.flatMap((key) => {
|
|
199
|
+
const value = flow[key];
|
|
200
|
+
return typeof value === 'string' ? [{ key, route: normalizeRoute(value) }] : [];
|
|
201
|
+
});
|
|
202
|
+
const routesByName = new Map();
|
|
203
|
+
for (const entry of actionRoutes) {
|
|
204
|
+
const keys = routesByName.get(entry.route) ?? [];
|
|
205
|
+
keys.push(entry.key);
|
|
206
|
+
routesByName.set(entry.route, keys);
|
|
207
|
+
}
|
|
208
|
+
for (const [route, keys] of routesByName) {
|
|
209
|
+
if (keys.length < 2) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
diagnostics.push(createManifestDiagnostic({
|
|
213
|
+
code: 'field-invalid',
|
|
214
|
+
message: `Auth action routes must be unique; ${keys.join(', ')} all resolve to "${route}".`,
|
|
215
|
+
path: manifestPath,
|
|
216
|
+
ruleId: 'manifest.auth.flow.route.unique',
|
|
217
|
+
}));
|
|
218
|
+
}
|
|
219
|
+
const postSignInRoute = normalizeRoute(flow.postSignInRoute);
|
|
220
|
+
const collidingAction = actionRoutes.find((entry) => entry.route === postSignInRoute);
|
|
221
|
+
if (collidingAction !== undefined) {
|
|
222
|
+
diagnostics.push(createManifestDiagnostic({
|
|
223
|
+
code: 'field-invalid',
|
|
224
|
+
message: `postSignInRoute must resolve outside the auth action routes; it collides with ${collidingAction.key}.`,
|
|
225
|
+
path: manifestPath,
|
|
226
|
+
ruleId: 'manifest.auth.flow.post-sign-in.distinct',
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
if (flow.unauthorizedRoute === undefined) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const unauthorizedRoute = normalizeRoute(flow.unauthorizedRoute);
|
|
233
|
+
const signInRoute = normalizeRoute(flow.signInRoute);
|
|
234
|
+
if (unauthorizedRoute === postSignInRoute) {
|
|
235
|
+
diagnostics.push(createManifestDiagnostic({
|
|
236
|
+
code: 'field-invalid',
|
|
237
|
+
message: 'unauthorizedRoute must not resolve to postSignInRoute.',
|
|
238
|
+
path: manifestPath,
|
|
239
|
+
ruleId: 'manifest.auth.flow.unauthorized-route.valid',
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
if (unauthorizedRoute === signInRoute) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const collidingActionRoute = actionRoutes.find((entry) => entry.key !== 'signInRoute' && entry.route === unauthorizedRoute);
|
|
246
|
+
if (collidingActionRoute !== undefined) {
|
|
247
|
+
diagnostics.push(createManifestDiagnostic({
|
|
248
|
+
code: 'field-invalid',
|
|
249
|
+
message: `unauthorizedRoute may alias signInRoute, but it must not collide with ${collidingActionRoute.key}.`,
|
|
250
|
+
path: manifestPath,
|
|
251
|
+
ruleId: 'manifest.auth.flow.unauthorized-route.valid',
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function validateAuthorization(value, manifestPath, diagnostics) {
|
|
256
|
+
if (value === undefined) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (!isRecord(value)) {
|
|
260
|
+
diagnostics.push(createManifestDiagnostic({
|
|
261
|
+
code: 'field-invalid',
|
|
262
|
+
message: 'manifest.infra.auth.authorization must be a JSON object when explicitly configured.',
|
|
263
|
+
path: manifestPath,
|
|
264
|
+
ruleId: 'manifest.auth.authorization.valid-shape',
|
|
265
|
+
}));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
validateAuthorizationField({
|
|
269
|
+
allowed: AUTHZ_KINDS,
|
|
270
|
+
diagnostics,
|
|
271
|
+
fieldName: 'kind',
|
|
272
|
+
manifestPath,
|
|
273
|
+
value: value.kind,
|
|
274
|
+
ruleId: 'manifest.auth.authorization.kind.valid',
|
|
275
|
+
});
|
|
276
|
+
validateAuthorizationField({
|
|
277
|
+
allowed: AUTHZ_ENGINES,
|
|
278
|
+
diagnostics,
|
|
279
|
+
fieldName: 'engine',
|
|
280
|
+
manifestPath,
|
|
281
|
+
value: value.engine,
|
|
282
|
+
ruleId: 'manifest.auth.authorization.engine.valid',
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function validateAuthorizationField(request) {
|
|
286
|
+
if (typeof request.value === 'string' && request.allowed.includes(request.value)) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
request.diagnostics.push(createManifestDiagnostic({
|
|
290
|
+
code: request.value === undefined ? 'field-missing' : 'field-invalid',
|
|
291
|
+
message: `Explicit authorization.${request.fieldName} must be one of: ${request.allowed.join(', ')}.`,
|
|
292
|
+
path: request.manifestPath,
|
|
293
|
+
ruleId: request.ruleId,
|
|
294
|
+
}));
|
|
295
|
+
}
|
|
296
|
+
function createManifestDiagnostic(request) {
|
|
297
|
+
return {
|
|
298
|
+
...request,
|
|
299
|
+
profile: MANIFEST_PROFILE,
|
|
300
|
+
severity: 'error',
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function normalizeRoute(route) {
|
|
304
|
+
const normalized = route.trim().replace(/^\/+/, '').replace(/\/+$/, '');
|
|
305
|
+
return normalized === '' ? 'index' : normalized;
|
|
306
|
+
}
|
|
307
|
+
function hasOwn(record, key) {
|
|
308
|
+
return Object.prototype.hasOwnProperty.call(record, key);
|
|
309
|
+
}
|
|
310
|
+
function isRecord(value) {
|
|
311
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
312
|
+
}
|
|
313
|
+
async function statOrNull(filePath) {
|
|
314
|
+
try {
|
|
315
|
+
return await fs.stat(filePath);
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function toErrorMessage(error) {
|
|
322
|
+
return error instanceof Error ? error.message : String(error);
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=manifestAnalysis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifestAnalysis.js","sourceRoot":"","sources":["../src/manifestAnalysis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAuB,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAcjF,OAAO,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AAEzE,MAAM,gBAAgB,GAAG,cAA4C,CAAC;AACtE,MAAM,sBAAsB,GAAG;IAC7B,aAAa;IACb,aAAa;IACb,cAAc;IACd,qBAAqB;IACrB,UAAU;CAC0C,CAAC;AACvD,MAAM,mBAAmB,GAAG;IAC1B,aAAa;IACb,cAAc;IACd,qBAAqB;IACrB,UAAU;IACV,mBAAmB;CACiC,CAAC;AAKvD,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAA8B;IAE9B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,SAAS,IAAI,GAAG,CAAC,CAAC;IACvE,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,CAAC;IAE3C,IACE,KAAK,KAAK,IAAI;QACd,CAAC,KAAK,CAAC,MAAM,EAAE;QACf,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,WAAW,EAAE,KAAK,OAAO;QAClD,CAAC,OAAO,CAAC,IAAI,KAAK,UAAU,IAAI,OAAO,CAAC,IAAI,KAAK,KAAK,CAAC,EACvD,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,sBAAsB,CAAC,UAAU,CAAC,CAAC;IAC7D,MAAM,cAAc,GAA0B,EAAE,CAAC;IACjD,MAAM,OAAO,GACX,OAAO,CAAC,IAAI,KAAK,KAAK;QACpB,CAAC,CAAC;YACE,OAAO,EAAE,cAAc;YACvB,WAAW;YACX,OAAO,EAAE,gBAAgB;YACzB,UAAU;SACX;QACH,CAAC,CAAC,IAAI,CAAC;IAEX,OAAO;QACL,aAAa,EAAE,CAAC,UAAU,CAAC;QAC3B,WAAW;QACX,OAAO;QACP,cAAc,EAAE,KAAK;QACrB,cAAc;QACd,OAAO,EAAE,gBAAgB;QACzB,WAAW,EAAE,EAAE;QACf,UAAU;KACX,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,QAAgB;IAC3D,IAAI,MAAc,CAAC;IAEnB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,wBAAwB,CAAC;gBACvB,IAAI,EAAE,2BAA2B;gBACjC,OAAO,EAAE,qCAAqC,cAAc,CAAC,KAAK,CAAC,EAAE;gBACrE,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,wBAAwB;aACjC,CAAC;SACH,CAAC;IACJ,CAAC;IAED,IAAI,QAAiB,CAAC;IAEtB,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAY,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,wBAAwB,CAAC;gBACvB,IAAI,EAAE,2BAA2B;gBACjC,OAAO,EAAE,yCAAyC,cAAc,CAAC,KAAK,CAAC,EAAE;gBACzE,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,qBAAqB;aAC9B,CAAC;SACH,CAAC;IACJ,CAAC;IAED,OAAO,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,QAAiB,EACjB,YAAY,GAAG,eAAe;IAE9B,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxB,OAAO;YACL,wBAAwB,CAAC;gBACvB,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE,0CAA0C;gBACnD,IAAI,EAAE,YAAY;gBAClB,MAAM,EAAE,2BAA2B;aACpC,CAAC;SACH,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAuB,EAAE,CAAC;IAC3C,MAAM,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC;IAE9B,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,EAAE,CAAC;QACvD,WAAW,CAAC,IAAI,CACd,wBAAwB,CAAC;YACvB,IAAI,EAAE,eAAe;YACrB,OAAO,EACL,+HAA+H;YACjI,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,qCAAqC;SAC9C,CAAC,CACH,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC;IAC3B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,WAAW,CAAC,IAAI,CACd,wBAAwB,CAAC;YACvB,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,oDAAoD;YAC7D,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,4BAA4B;SACrC,CAAC,CACH,CAAC;QACF,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,WAAW,CAAC,IAAI,CAAC,GAAG,0BAA0B,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC,CAAC;IAErE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IACvB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,WAAW,CAAC,IAAI,CACd,wBAAwB,CAAC;YACvB,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,yDAAyD;YAClE,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,2BAA2B;SACpC,CAAC,CACH,CAAC;QACF,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IACjE,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,6BAA6B,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IACjE,CAAC;IAED,qBAAqB,CAAC,IAAI,CAAC,aAAa,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IAErE,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,aAAa,CACpB,KAAc,EACd,YAAoB,EACpB,WAA+B;IAE/B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,eAAe,EAAE,CAAC;IAC3B,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,WAAW,CAAC,IAAI,CACd,wBAAwB,CAAC;YACvB,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,8DAA8D;YACvE,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,gCAAgC;SACzC,CAAC,CACH,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,WAAW,GAAG,iBAAiB,CAAC,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IACvF,MAAM,eAAe,GAAG,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IAC/F,MAAM,cAAc,GAAG,kBAAkB,CAAC,KAAK,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;IAE5E,IAAI,WAAW,KAAK,IAAI,IAAI,eAAe,KAAK,IAAI,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;QAChF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,eAAe,CAAC;QACrB,WAAW;QACX,eAAe;QACf,GAAG,cAAc;KAClB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,iBAAiB,CACxB,IAA6B,EAC7B,GAAsC,EACtC,YAAoB,EACpB,WAA+B;IAE/B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IAExB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,WAAW,CAAC,IAAI,CACd,wBAAwB,CAAC;YACvB,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,eAAe;YAC7D,OAAO,EAAE,4BAA4B,GAAG,8BAA8B;YACtE,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,mCAAmC;SAC5C,CAAC,CACH,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,kBAAkB,CACzB,IAA6B,EAC7B,YAAoB,EACpB,WAA+B;IAE/B,MAAM,MAAM,GAAoD,EAAE,CAAC;IACnE,IAAI,KAAK,GAAG,IAAI,CAAC;IAEjB,KAAK,MAAM,GAAG,IAAI,mBAAmB,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,SAAS;QACX,CAAC;QAED,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACrD,KAAK,GAAG,KAAK,CAAC;YACd,WAAW,CAAC,IAAI,CACd,wBAAwB,CAAC;gBACvB,IAAI,EAAE,eAAe;gBACrB,OAAO,EAAE,4BAA4B,GAAG,2CAA2C;gBACnF,IAAI,EAAE,YAAY;gBAClB,MAAM,EAAE,gCAAgC;aACzC,CAAC,CACH,CAAC;YACF,SAAS;QACX,CAAC;QAED,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACtB,CAAC;IAED,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/B,CAAC;AAED,SAAS,6BAA6B,CACpC,IAAoB,EACpB,YAAoB,EACpB,WAA+B;IAE/B,MAAM,YAAY,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QAC1D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAClF,CAAC,CAAC,CAAC;IACH,MAAM,YAAY,GAAG,IAAI,GAAG,EAAgC,CAAC;IAE7D,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QACjD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACrB,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,YAAY,EAAE,CAAC;QACzC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,SAAS;QACX,CAAC;QAED,WAAW,CAAC,IAAI,CACd,wBAAwB,CAAC;YACvB,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,sCAAsC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,KAAK,IAAI;YAC3F,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,iCAAiC;SAC1C,CAAC,CACH,CAAC;IACJ,CAAC;IAED,MAAM,eAAe,GAAG,cAAc,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAC7D,MAAM,eAAe,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,eAAe,CAAC,CAAC;IACtF,IAAI,eAAe,KAAK,SAAS,EAAE,CAAC;QAClC,WAAW,CAAC,IAAI,CACd,wBAAwB,CAAC;YACvB,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,iFAAiF,eAAe,CAAC,GAAG,GAAG;YAChH,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,0CAA0C;SACnD,CAAC,CACH,CAAC;IACJ,CAAC;IAED,IAAI,IAAI,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;QACzC,OAAO;IACT,CAAC;IAED,MAAM,iBAAiB,GAAG,cAAc,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACjE,MAAM,WAAW,GAAG,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAErD,IAAI,iBAAiB,KAAK,eAAe,EAAE,CAAC;QAC1C,WAAW,CAAC,IAAI,CACd,wBAAwB,CAAC;YACvB,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,wDAAwD;YACjE,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,6CAA6C;SACtD,CAAC,CACH,CAAC;IACJ,CAAC;IAED,IAAI,iBAAiB,KAAK,WAAW,EAAE,CAAC;QACtC,OAAO;IACT,CAAC;IAED,MAAM,oBAAoB,GAAG,YAAY,CAAC,IAAI,CAC5C,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,KAAK,aAAa,IAAI,KAAK,CAAC,KAAK,KAAK,iBAAiB,CAC5E,CAAC;IACF,IAAI,oBAAoB,KAAK,SAAS,EAAE,CAAC;QACvC,WAAW,CAAC,IAAI,CACd,wBAAwB,CAAC;YACvB,IAAI,EAAE,eAAe;YACrB,OAAO,EAAE,yEAAyE,oBAAoB,CAAC,GAAG,GAAG;YAC7G,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,6CAA6C;SACtD,CAAC,CACH,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAC5B,KAAc,EACd,YAAoB,EACpB,WAA+B;IAE/B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO;IACT,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,WAAW,CAAC,IAAI,CACd,wBAAwB,CAAC;YACvB,IAAI,EAAE,eAAe;YACrB,OAAO,EACL,qFAAqF;YACvF,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,yCAAyC;SAClD,CAAC,CACH,CAAC;QACF,OAAO;IACT,CAAC;IAED,0BAA0B,CAAC;QACzB,OAAO,EAAE,WAAW;QACpB,WAAW;QACX,SAAS,EAAE,MAAM;QACjB,YAAY;QACZ,KAAK,EAAE,KAAK,CAAC,IAAI;QACjB,MAAM,EAAE,wCAAwC;KACjD,CAAC,CAAC;IACH,0BAA0B,CAAC;QACzB,OAAO,EAAE,aAAa;QACtB,WAAW;QACX,SAAS,EAAE,QAAQ;QACnB,YAAY;QACZ,KAAK,EAAE,KAAK,CAAC,MAAM;QACnB,MAAM,EAAE,0CAA0C;KACnD,CAAC,CAAC;AACL,CAAC;AAED,SAAS,0BAA0B,CAAC,OAOnC;IACC,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjF,OAAO;IACT,CAAC;IAED,OAAO,CAAC,WAAW,CAAC,IAAI,CACtB,wBAAwB,CAAC;QACvB,IAAI,EAAE,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,eAAe;QACrE,OAAO,EAAE,0BAA0B,OAAO,CAAC,SAAS,oBAAoB,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;QACrG,IAAI,EAAE,OAAO,CAAC,YAAY;QAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CACH,CAAC;AACJ,CAAC;AAED,SAAS,wBAAwB,CAAC,OAKjC;IACC,OAAO;QACL,GAAG,OAAO;QACV,OAAO,EAAE,gBAAgB;QACzB,QAAQ,EAAE,OAAO;KAClB,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAa;IACnC,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACxE,OAAO,UAAU,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC;AAClD,CAAC;AAED,SAAS,MAAM,CAAC,MAA+B,EAAE,GAAW;IAC1D,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,QAAgB;IACxC,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport { AUTHZ_ENGINES, AUTHZ_KINDS } from '@ankhorage/contracts';\nimport { type AuthFlowConfig, resolveAuthFlow } from '@ankhorage/contracts/auth';\n\nimport type {\n DoctorAnalysisRequest,\n DoctorAnalysisResult,\n DoctorFixPlan,\n DoctorPlannedChange,\n} from './analysis.js';\nimport type {\n DoctorDiagnostic,\n DoctorDiagnosticCode,\n DoctorPolicyProfile,\n DoctorRuleId,\n} from './diagnostics.js';\nimport { analyzeSecretStoreManifest } from './secretManifestAnalysis.js';\n\nconst MANIFEST_PROFILE = 'app-manifest' satisfies DoctorPolicyProfile;\nconst AUTH_ACTION_ROUTE_KEYS = [\n 'signInRoute',\n 'signUpRoute',\n 'signOutRoute',\n 'forgotPasswordRoute',\n 'otpRoute',\n] as const satisfies readonly (keyof AuthFlowConfig)[];\nconst OPTIONAL_ROUTE_KEYS = [\n 'signUpRoute',\n 'signOutRoute',\n 'forgotPasswordRoute',\n 'otpRoute',\n 'unauthorizedRoute',\n] as const satisfies readonly (keyof AuthFlowConfig)[];\n\ntype AuthActionRouteKey = (typeof AUTH_ACTION_ROUTE_KEYS)[number];\ntype OptionalRouteKey = (typeof OPTIONAL_ROUTE_KEYS)[number];\n\nexport async function analyzeAppManifestTarget(\n request: DoctorAnalysisRequest,\n): Promise<DoctorAnalysisResult | null> {\n const targetPath = path.resolve(request.cwd, request.inputPath ?? '.');\n const stats = await statOrNull(targetPath);\n\n if (\n stats === null ||\n !stats.isFile() ||\n path.extname(targetPath).toLowerCase() !== '.json' ||\n (request.mode !== 'validate' && request.mode !== 'fix')\n ) {\n return null;\n }\n\n const diagnostics = await analyzeAppManifestFile(targetPath);\n const plannedChanges: DoctorPlannedChange[] = [];\n const fixPlan: DoctorFixPlan | null =\n request.mode === 'fix'\n ? {\n changes: plannedChanges,\n diagnostics,\n profile: MANIFEST_PROFILE,\n targetPath,\n }\n : null;\n\n return {\n appliedChecks: ['manifest'],\n diagnostics,\n fixPlan,\n hasPackageJson: false,\n plannedChanges,\n profile: MANIFEST_PROFILE,\n repoMarkers: [],\n targetPath,\n };\n}\n\nexport async function analyzeAppManifestFile(filePath: string): Promise<DoctorDiagnostic[]> {\n let source: string;\n\n try {\n source = await fs.readFile(filePath, 'utf8');\n } catch (error) {\n return [\n createManifestDiagnostic({\n code: 'invalid-app-manifest-json',\n message: `Could not read app manifest JSON: ${toErrorMessage(error)}`,\n path: filePath,\n ruleId: 'manifest.json.readable',\n }),\n ];\n }\n\n let manifest: unknown;\n\n try {\n manifest = JSON.parse(source) as unknown;\n } catch (error) {\n return [\n createManifestDiagnostic({\n code: 'invalid-app-manifest-json',\n message: `App manifest must contain valid JSON: ${toErrorMessage(error)}`,\n path: filePath,\n ruleId: 'manifest.json.valid',\n }),\n ];\n }\n\n return analyzeAppManifest(manifest, filePath);\n}\n\nexport function analyzeAppManifest(\n manifest: unknown,\n manifestPath = 'manifest.json',\n): DoctorDiagnostic[] {\n if (!isRecord(manifest)) {\n return [\n createManifestDiagnostic({\n code: 'field-invalid',\n message: 'App manifest root must be a JSON object.',\n path: manifestPath,\n ruleId: 'manifest.root.valid-shape',\n }),\n ];\n }\n\n const diagnostics: DoctorDiagnostic[] = [];\n const { settings } = manifest;\n\n if (isRecord(settings) && hasOwn(settings, 'authFlow')) {\n diagnostics.push(\n createManifestDiagnostic({\n code: 'field-invalid',\n message:\n 'settings.authFlow was removed. Move this configuration manually to infra.auth.flow; Doctor will not migrate it automatically.',\n path: manifestPath,\n ruleId: 'manifest.settings.auth-flow.removed',\n }),\n );\n }\n\n const { infra } = manifest;\n if (infra === undefined) {\n return diagnostics;\n }\n\n if (!isRecord(infra)) {\n diagnostics.push(\n createManifestDiagnostic({\n code: 'field-invalid',\n message: 'manifest.infra must be a JSON object when present.',\n path: manifestPath,\n ruleId: 'manifest.infra.valid-shape',\n }),\n );\n return diagnostics;\n }\n\n diagnostics.push(...analyzeSecretStoreManifest(infra, manifestPath));\n\n const { auth } = infra;\n if (auth === undefined) {\n return diagnostics;\n }\n\n if (!isRecord(auth)) {\n diagnostics.push(\n createManifestDiagnostic({\n code: 'field-invalid',\n message: 'manifest.infra.auth must be a JSON object when present.',\n path: manifestPath,\n ruleId: 'manifest.auth.valid-shape',\n }),\n );\n return diagnostics;\n }\n\n const flow = parseAuthFlow(auth.flow, manifestPath, diagnostics);\n if (flow !== null) {\n validateAuthFlowRelationships(flow, manifestPath, diagnostics);\n }\n\n validateAuthorization(auth.authorization, manifestPath, diagnostics);\n\n return diagnostics;\n}\n\nfunction parseAuthFlow(\n value: unknown,\n manifestPath: string,\n diagnostics: DoctorDiagnostic[],\n): AuthFlowConfig | null {\n if (value === undefined) {\n return resolveAuthFlow();\n }\n\n if (!isRecord(value)) {\n diagnostics.push(\n createManifestDiagnostic({\n code: 'field-invalid',\n message: 'manifest.infra.auth.flow must be a JSON object when present.',\n path: manifestPath,\n ruleId: 'manifest.auth.flow.valid-shape',\n }),\n );\n return null;\n }\n\n const signInRoute = readRequiredRoute(value, 'signInRoute', manifestPath, diagnostics);\n const postSignInRoute = readRequiredRoute(value, 'postSignInRoute', manifestPath, diagnostics);\n const optionalRoutes = readOptionalRoutes(value, manifestPath, diagnostics);\n\n if (signInRoute === null || postSignInRoute === null || optionalRoutes === null) {\n return null;\n }\n\n return resolveAuthFlow({\n signInRoute,\n postSignInRoute,\n ...optionalRoutes,\n });\n}\n\nfunction readRequiredRoute(\n flow: Record<string, unknown>,\n key: 'postSignInRoute' | 'signInRoute',\n manifestPath: string,\n diagnostics: DoctorDiagnostic[],\n): string | null {\n const value = flow[key];\n\n if (typeof value !== 'string' || value.trim() === '') {\n diagnostics.push(\n createManifestDiagnostic({\n code: value === undefined ? 'field-missing' : 'field-invalid',\n message: `manifest.infra.auth.flow.${key} must be a non-empty string.`,\n path: manifestPath,\n ruleId: 'manifest.auth.flow.route.required',\n }),\n );\n return null;\n }\n\n return value;\n}\n\nfunction readOptionalRoutes(\n flow: Record<string, unknown>,\n manifestPath: string,\n diagnostics: DoctorDiagnostic[],\n): Pick<AuthFlowConfig, OptionalRouteKey> | null {\n const routes: Partial<Pick<AuthFlowConfig, OptionalRouteKey>> = {};\n let valid = true;\n\n for (const key of OPTIONAL_ROUTE_KEYS) {\n const value = flow[key];\n if (value === undefined) {\n continue;\n }\n\n if (typeof value !== 'string' || value.trim() === '') {\n valid = false;\n diagnostics.push(\n createManifestDiagnostic({\n code: 'field-invalid',\n message: `manifest.infra.auth.flow.${key} must be a non-empty string when present.`,\n path: manifestPath,\n ruleId: 'manifest.auth.flow.route.valid',\n }),\n );\n continue;\n }\n\n routes[key] = value;\n }\n\n return valid ? routes : null;\n}\n\nfunction validateAuthFlowRelationships(\n flow: AuthFlowConfig,\n manifestPath: string,\n diagnostics: DoctorDiagnostic[],\n): void {\n const actionRoutes = AUTH_ACTION_ROUTE_KEYS.flatMap((key) => {\n const value = flow[key];\n return typeof value === 'string' ? [{ key, route: normalizeRoute(value) }] : [];\n });\n const routesByName = new Map<string, AuthActionRouteKey[]>();\n\n for (const entry of actionRoutes) {\n const keys = routesByName.get(entry.route) ?? [];\n keys.push(entry.key);\n routesByName.set(entry.route, keys);\n }\n\n for (const [route, keys] of routesByName) {\n if (keys.length < 2) {\n continue;\n }\n\n diagnostics.push(\n createManifestDiagnostic({\n code: 'field-invalid',\n message: `Auth action routes must be unique; ${keys.join(', ')} all resolve to \"${route}\".`,\n path: manifestPath,\n ruleId: 'manifest.auth.flow.route.unique',\n }),\n );\n }\n\n const postSignInRoute = normalizeRoute(flow.postSignInRoute);\n const collidingAction = actionRoutes.find((entry) => entry.route === postSignInRoute);\n if (collidingAction !== undefined) {\n diagnostics.push(\n createManifestDiagnostic({\n code: 'field-invalid',\n message: `postSignInRoute must resolve outside the auth action routes; it collides with ${collidingAction.key}.`,\n path: manifestPath,\n ruleId: 'manifest.auth.flow.post-sign-in.distinct',\n }),\n );\n }\n\n if (flow.unauthorizedRoute === undefined) {\n return;\n }\n\n const unauthorizedRoute = normalizeRoute(flow.unauthorizedRoute);\n const signInRoute = normalizeRoute(flow.signInRoute);\n\n if (unauthorizedRoute === postSignInRoute) {\n diagnostics.push(\n createManifestDiagnostic({\n code: 'field-invalid',\n message: 'unauthorizedRoute must not resolve to postSignInRoute.',\n path: manifestPath,\n ruleId: 'manifest.auth.flow.unauthorized-route.valid',\n }),\n );\n }\n\n if (unauthorizedRoute === signInRoute) {\n return;\n }\n\n const collidingActionRoute = actionRoutes.find(\n (entry) => entry.key !== 'signInRoute' && entry.route === unauthorizedRoute,\n );\n if (collidingActionRoute !== undefined) {\n diagnostics.push(\n createManifestDiagnostic({\n code: 'field-invalid',\n message: `unauthorizedRoute may alias signInRoute, but it must not collide with ${collidingActionRoute.key}.`,\n path: manifestPath,\n ruleId: 'manifest.auth.flow.unauthorized-route.valid',\n }),\n );\n }\n}\n\nfunction validateAuthorization(\n value: unknown,\n manifestPath: string,\n diagnostics: DoctorDiagnostic[],\n): void {\n if (value === undefined) {\n return;\n }\n\n if (!isRecord(value)) {\n diagnostics.push(\n createManifestDiagnostic({\n code: 'field-invalid',\n message:\n 'manifest.infra.auth.authorization must be a JSON object when explicitly configured.',\n path: manifestPath,\n ruleId: 'manifest.auth.authorization.valid-shape',\n }),\n );\n return;\n }\n\n validateAuthorizationField({\n allowed: AUTHZ_KINDS,\n diagnostics,\n fieldName: 'kind',\n manifestPath,\n value: value.kind,\n ruleId: 'manifest.auth.authorization.kind.valid',\n });\n validateAuthorizationField({\n allowed: AUTHZ_ENGINES,\n diagnostics,\n fieldName: 'engine',\n manifestPath,\n value: value.engine,\n ruleId: 'manifest.auth.authorization.engine.valid',\n });\n}\n\nfunction validateAuthorizationField(request: {\n readonly allowed: readonly string[];\n readonly diagnostics: DoctorDiagnostic[];\n readonly fieldName: 'engine' | 'kind';\n readonly manifestPath: string;\n readonly ruleId: DoctorRuleId;\n readonly value: unknown;\n}): void {\n if (typeof request.value === 'string' && request.allowed.includes(request.value)) {\n return;\n }\n\n request.diagnostics.push(\n createManifestDiagnostic({\n code: request.value === undefined ? 'field-missing' : 'field-invalid',\n message: `Explicit authorization.${request.fieldName} must be one of: ${request.allowed.join(', ')}.`,\n path: request.manifestPath,\n ruleId: request.ruleId,\n }),\n );\n}\n\nfunction createManifestDiagnostic(request: {\n readonly code: DoctorDiagnosticCode;\n readonly message: string;\n readonly path: string;\n readonly ruleId: DoctorRuleId;\n}): DoctorDiagnostic {\n return {\n ...request,\n profile: MANIFEST_PROFILE,\n severity: 'error',\n };\n}\n\nfunction normalizeRoute(route: string): string {\n const normalized = route.trim().replace(/^\\/+/, '').replace(/\\/+$/, '');\n return normalized === '' ? 'index' : normalized;\n}\n\nfunction hasOwn(record: Record<string, unknown>, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(record, key);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nasync function statOrNull(filePath: string) {\n try {\n return await fs.stat(filePath);\n } catch {\n return null;\n }\n}\n\nfunction toErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"]}
|