@ankhorage/doctor 0.14.14 → 0.14.16
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
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.16
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3ddaa1d: Update dependencies: `@ankhorage/rules-architecture`.
|
|
8
|
+
|
|
9
|
+
## 0.14.15
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- b72a1b3: Allow the canonical Rules owner to publish a new Bun runtime contract while retaining strict checks for consumer repositories.
|
|
14
|
+
|
|
3
15
|
## 0.14.14
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# @ankhorage/doctor
|
|
5
5
|
|
|
6
|
-
         
|
|
7
7
|
|
|
8
8
|
Executable Ankh provider and standalone CLI for lightweight repo and package compliance diagnostics.
|
|
9
9
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bunPolicyAnalysis.d.ts","sourceRoot":"","sources":["../src/bunPolicyAnalysis.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAW1D;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,oBAAoB,CAAC,
|
|
1
|
+
{"version":3,"file":"bunPolicyAnalysis.d.ts","sourceRoot":"","sources":["../src/bunPolicyAnalysis.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAW1D;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,oBAAoB,CAAC,CAoB/B"}
|
|
@@ -16,6 +16,10 @@ export async function applyBunRuntimePolicy(result) {
|
|
|
16
16
|
const packageJson = await readPackageJson(packageJsonPath);
|
|
17
17
|
if (packageJson === null)
|
|
18
18
|
return result;
|
|
19
|
+
// The canonical owner validates its candidate metadata locally before publication.
|
|
20
|
+
// Its installed Rules package necessarily contains the previous released version.
|
|
21
|
+
if (packageJson.name === '@ankhorage/rules-repository')
|
|
22
|
+
return result;
|
|
19
23
|
const diagnostics = [
|
|
20
24
|
...result.diagnostics,
|
|
21
25
|
...analyzePackageBunPolicy(packageJson, packageJsonPath, result),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bunPolicyAnalysis.js","sourceRoot":"","sources":["../src/bunPolicyAnalysis.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport { REPOSITORY_RULE_IDS, REPOSITORY_RULE_METADATA } from '@ankhorage/rules-repository';\n\nimport type { DoctorAnalysisResult } from './analysis.js';\nimport type { DoctorDiagnostic } from './diagnostics.js';\nimport { getRepositoryRule } from './utils/getRepositoryRule.js';\n\nconst BUN_POLICY = REPOSITORY_RULE_METADATA.runtime.bun;\nconst REPAIR_HINT = 'Run \"ankh devtools sync\" to repair the managed Bun state.';\nconst BUN_VERSION_PATTERN = /^\\s*bun-version:\\s*['\"]?([^'\"\\s#]+)['\"]?\\s*(?:#.*)?$/mu;\n\ntype RepositoryRule = ReturnType<typeof getRepositoryRule>;\ntype WorkflowTarget = (typeof BUN_POLICY.workflowTargets)[number];\n\n/***\n * Applies canonical repository Bun-policy diagnostics to a Doctor analysis result.\n */\nexport async function applyBunRuntimePolicy(\n result: DoctorAnalysisResult,\n): Promise<DoctorAnalysisResult> {\n if (result.profile !== 'public-package' || !result.hasPackageJson) {\n return result;\n }\n\n const packageJsonPath = path.join(result.targetPath, 'package.json');\n const packageJson = await readPackageJson(packageJsonPath);\n if (packageJson === null) return result;\n\n const diagnostics = [\n ...result.diagnostics,\n ...analyzePackageBunPolicy(packageJson, packageJsonPath, result),\n ...(await analyzeWorkflowBunPolicy(result)),\n ];\n\n return withDiagnostics(result, diagnostics);\n}\n\n/***\n * Reports package-manifest drift from the canonical Bun runtime contract.\n */\nfunction analyzePackageBunPolicy(\n packageJson: Record<string, unknown>,\n packageJsonPath: string,\n result: DoctorAnalysisResult,\n): DoctorDiagnostic[] {\n const diagnostics: DoctorDiagnostic[] = [];\n const { packageManager } = packageJson;\n if (isNonEmptyString(packageManager) && packageManager.startsWith('bun@')) {\n pushMismatch(diagnostics, {\n actual: packageManager,\n expected: BUN_POLICY.packageManager,\n location: 'package.json#packageManager',\n path: packageJsonPath,\n profile: result.profile,\n rule: getRepositoryRule(REPOSITORY_RULE_IDS.bunPackageManager),\n });\n }\n\n const devDependencies = isRecord(packageJson.devDependencies)\n ? packageJson.devDependencies\n : null;\n const bunTypes = devDependencies?.['@types/bun'];\n if (isNonEmptyString(bunTypes)) {\n pushMismatch(diagnostics, {\n actual: bunTypes,\n expected: BUN_POLICY.typesRange,\n location: 'package.json#devDependencies.@types/bun',\n path: packageJsonPath,\n profile: result.profile,\n rule: getRepositoryRule(REPOSITORY_RULE_IDS.bunTypes),\n });\n }\n\n return diagnostics;\n}\n\n/***\n * Reports managed workflow drift from the canonical Bun setup version.\n */\nasync function analyzeWorkflowBunPolicy(result: DoctorAnalysisResult): Promise<DoctorDiagnostic[]> {\n const diagnostics = await Promise.all(\n BUN_POLICY.workflowTargets.map(async (workflow) => await analyzeWorkflow(result, workflow)),\n );\n return diagnostics.flat();\n}\n\n/***\n * Checks one managed workflow for the canonical Bun setup version.\n */\nasync function analyzeWorkflow(\n result: DoctorAnalysisResult,\n workflow: WorkflowTarget,\n): Promise<DoctorDiagnostic[]> {\n const workflowPath = path.join(result.targetPath, workflow.path);\n const contents = await readTextOrNull(workflowPath);\n if (contents === null) {\n return [createWorkflowDiagnostic(result, workflow, workflowPath, 'missing-path')];\n }\n\n const match = BUN_VERSION_PATTERN.exec(contents);\n const actual = match?.[1];\n if (!isNonEmptyString(actual)) {\n return [createWorkflowDiagnostic(result, workflow, workflowPath, 'field-missing')];\n }\n\n const diagnostics: DoctorDiagnostic[] = [];\n pushMismatch(diagnostics, {\n actual,\n expected: BUN_POLICY.version,\n location: `${workflow.path}#bun-version`,\n path: workflowPath,\n profile: result.profile,\n rule: getRepositoryRule(workflow.ruleId),\n });\n return diagnostics;\n}\n\n/***\n * Creates a missing or malformed managed-workflow diagnostic from central rule metadata.\n */\nfunction createWorkflowDiagnostic(\n result: DoctorAnalysisResult,\n workflow: WorkflowTarget,\n workflowPath: string,\n code: DoctorDiagnostic['code'],\n): DoctorDiagnostic {\n const detail =\n code === 'missing-path'\n ? `Managed workflow is missing: ${workflow.path}.`\n : `${workflow.path} does not define bun-version.`;\n const rule = getRepositoryRule(workflow.ruleId);\n\n return {\n code,\n message: `${detail} Expected Bun ${BUN_POLICY.version}. ${REPAIR_HINT}`,\n path: workflowPath,\n profile: result.profile,\n ruleId: rule.id,\n severity: rule.severity,\n };\n}\n\n/***\n * Adds one value-mismatch diagnostic using the owning Policy rule metadata.\n */\nfunction pushMismatch(\n diagnostics: DoctorDiagnostic[],\n input: {\n readonly actual: string;\n readonly expected: string;\n readonly location: string;\n readonly path: string;\n readonly profile: DoctorAnalysisResult['profile'];\n readonly rule: RepositoryRule;\n },\n): void {\n if (input.actual === input.expected) return;\n\n diagnostics.push({\n code: 'field-invalid',\n message: `Bun policy drift at ${input.location}: expected \"${input.expected}\", found \"${input.actual}\". ${REPAIR_HINT}`,\n path: input.path,\n profile: input.profile,\n ruleId: input.rule.id,\n severity: input.rule.severity,\n });\n}\n\n/***\n * Copies updated diagnostics into the analysis result and optional fix plan.\n */\nfunction withDiagnostics(\n result: DoctorAnalysisResult,\n diagnostics: readonly DoctorDiagnostic[],\n): DoctorAnalysisResult {\n return {\n ...result,\n diagnostics,\n fixPlan: result.fixPlan === null ? null : { ...result.fixPlan, diagnostics },\n };\n}\n\n/***\n * Reads a package manifest when it contains a JSON object.\n */\nasync function readPackageJson(packageJsonPath: string): Promise<Record<string, unknown> | null> {\n try {\n const parsed = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')) as unknown;\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\n/***\n * Reads a UTF-8 file while treating a missing managed path as absent.\n */\nasync function readTextOrNull(filePath: string): Promise<string | null> {\n try {\n return await fs.readFile(filePath, 'utf8');\n } catch (error) {\n if (isNodeError(error) && error.code === 'ENOENT') return null;\n throw error;\n }\n}\n\n/***\n * Narrows unknown JSON data to a non-array record.\n */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/***\n * Narrows a value to a non-empty string.\n */\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\n/***\n * Narrows unknown failures to Node errors with stable error codes.\n */\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && 'code' in error;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"bunPolicyAnalysis.js","sourceRoot":"","sources":["../src/bunPolicyAnalysis.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport { REPOSITORY_RULE_IDS, REPOSITORY_RULE_METADATA } from '@ankhorage/rules-repository';\n\nimport type { DoctorAnalysisResult } from './analysis.js';\nimport type { DoctorDiagnostic } from './diagnostics.js';\nimport { getRepositoryRule } from './utils/getRepositoryRule.js';\n\nconst BUN_POLICY = REPOSITORY_RULE_METADATA.runtime.bun;\nconst REPAIR_HINT = 'Run \"ankh devtools sync\" to repair the managed Bun state.';\nconst BUN_VERSION_PATTERN = /^\\s*bun-version:\\s*['\"]?([^'\"\\s#]+)['\"]?\\s*(?:#.*)?$/mu;\n\ntype RepositoryRule = ReturnType<typeof getRepositoryRule>;\ntype WorkflowTarget = (typeof BUN_POLICY.workflowTargets)[number];\n\n/***\n * Applies canonical repository Bun-policy diagnostics to a Doctor analysis result.\n */\nexport async function applyBunRuntimePolicy(\n result: DoctorAnalysisResult,\n): Promise<DoctorAnalysisResult> {\n if (result.profile !== 'public-package' || !result.hasPackageJson) {\n return result;\n }\n\n const packageJsonPath = path.join(result.targetPath, 'package.json');\n const packageJson = await readPackageJson(packageJsonPath);\n if (packageJson === null) return result;\n\n // The canonical owner validates its candidate metadata locally before publication.\n // Its installed Rules package necessarily contains the previous released version.\n if (packageJson.name === '@ankhorage/rules-repository') return result;\n\n const diagnostics = [\n ...result.diagnostics,\n ...analyzePackageBunPolicy(packageJson, packageJsonPath, result),\n ...(await analyzeWorkflowBunPolicy(result)),\n ];\n\n return withDiagnostics(result, diagnostics);\n}\n\n/***\n * Reports package-manifest drift from the canonical Bun runtime contract.\n */\nfunction analyzePackageBunPolicy(\n packageJson: Record<string, unknown>,\n packageJsonPath: string,\n result: DoctorAnalysisResult,\n): DoctorDiagnostic[] {\n const diagnostics: DoctorDiagnostic[] = [];\n const { packageManager } = packageJson;\n if (isNonEmptyString(packageManager) && packageManager.startsWith('bun@')) {\n pushMismatch(diagnostics, {\n actual: packageManager,\n expected: BUN_POLICY.packageManager,\n location: 'package.json#packageManager',\n path: packageJsonPath,\n profile: result.profile,\n rule: getRepositoryRule(REPOSITORY_RULE_IDS.bunPackageManager),\n });\n }\n\n const devDependencies = isRecord(packageJson.devDependencies)\n ? packageJson.devDependencies\n : null;\n const bunTypes = devDependencies?.['@types/bun'];\n if (isNonEmptyString(bunTypes)) {\n pushMismatch(diagnostics, {\n actual: bunTypes,\n expected: BUN_POLICY.typesRange,\n location: 'package.json#devDependencies.@types/bun',\n path: packageJsonPath,\n profile: result.profile,\n rule: getRepositoryRule(REPOSITORY_RULE_IDS.bunTypes),\n });\n }\n\n return diagnostics;\n}\n\n/***\n * Reports managed workflow drift from the canonical Bun setup version.\n */\nasync function analyzeWorkflowBunPolicy(result: DoctorAnalysisResult): Promise<DoctorDiagnostic[]> {\n const diagnostics = await Promise.all(\n BUN_POLICY.workflowTargets.map(async (workflow) => await analyzeWorkflow(result, workflow)),\n );\n return diagnostics.flat();\n}\n\n/***\n * Checks one managed workflow for the canonical Bun setup version.\n */\nasync function analyzeWorkflow(\n result: DoctorAnalysisResult,\n workflow: WorkflowTarget,\n): Promise<DoctorDiagnostic[]> {\n const workflowPath = path.join(result.targetPath, workflow.path);\n const contents = await readTextOrNull(workflowPath);\n if (contents === null) {\n return [createWorkflowDiagnostic(result, workflow, workflowPath, 'missing-path')];\n }\n\n const match = BUN_VERSION_PATTERN.exec(contents);\n const actual = match?.[1];\n if (!isNonEmptyString(actual)) {\n return [createWorkflowDiagnostic(result, workflow, workflowPath, 'field-missing')];\n }\n\n const diagnostics: DoctorDiagnostic[] = [];\n pushMismatch(diagnostics, {\n actual,\n expected: BUN_POLICY.version,\n location: `${workflow.path}#bun-version`,\n path: workflowPath,\n profile: result.profile,\n rule: getRepositoryRule(workflow.ruleId),\n });\n return diagnostics;\n}\n\n/***\n * Creates a missing or malformed managed-workflow diagnostic from central rule metadata.\n */\nfunction createWorkflowDiagnostic(\n result: DoctorAnalysisResult,\n workflow: WorkflowTarget,\n workflowPath: string,\n code: DoctorDiagnostic['code'],\n): DoctorDiagnostic {\n const detail =\n code === 'missing-path'\n ? `Managed workflow is missing: ${workflow.path}.`\n : `${workflow.path} does not define bun-version.`;\n const rule = getRepositoryRule(workflow.ruleId);\n\n return {\n code,\n message: `${detail} Expected Bun ${BUN_POLICY.version}. ${REPAIR_HINT}`,\n path: workflowPath,\n profile: result.profile,\n ruleId: rule.id,\n severity: rule.severity,\n };\n}\n\n/***\n * Adds one value-mismatch diagnostic using the owning Policy rule metadata.\n */\nfunction pushMismatch(\n diagnostics: DoctorDiagnostic[],\n input: {\n readonly actual: string;\n readonly expected: string;\n readonly location: string;\n readonly path: string;\n readonly profile: DoctorAnalysisResult['profile'];\n readonly rule: RepositoryRule;\n },\n): void {\n if (input.actual === input.expected) return;\n\n diagnostics.push({\n code: 'field-invalid',\n message: `Bun policy drift at ${input.location}: expected \"${input.expected}\", found \"${input.actual}\". ${REPAIR_HINT}`,\n path: input.path,\n profile: input.profile,\n ruleId: input.rule.id,\n severity: input.rule.severity,\n });\n}\n\n/***\n * Copies updated diagnostics into the analysis result and optional fix plan.\n */\nfunction withDiagnostics(\n result: DoctorAnalysisResult,\n diagnostics: readonly DoctorDiagnostic[],\n): DoctorAnalysisResult {\n return {\n ...result,\n diagnostics,\n fixPlan: result.fixPlan === null ? null : { ...result.fixPlan, diagnostics },\n };\n}\n\n/***\n * Reads a package manifest when it contains a JSON object.\n */\nasync function readPackageJson(packageJsonPath: string): Promise<Record<string, unknown> | null> {\n try {\n const parsed = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')) as unknown;\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\n/***\n * Reads a UTF-8 file while treating a missing managed path as absent.\n */\nasync function readTextOrNull(filePath: string): Promise<string | null> {\n try {\n return await fs.readFile(filePath, 'utf8');\n } catch (error) {\n if (isNodeError(error) && error.code === 'ENOENT') return null;\n throw error;\n }\n}\n\n/***\n * Narrows unknown JSON data to a non-array record.\n */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/***\n * Narrows a value to a non-empty string.\n */\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\n/***\n * Narrows unknown failures to Node errors with stable error codes.\n */\nfunction isNodeError(error: unknown): error is NodeJS.ErrnoException {\n return error instanceof Error && 'code' in error;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/doctor",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.16",
|
|
4
4
|
"description": "Executable Ankh provider and standalone CLI for lightweight repo and package compliance diagnostics.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -134,14 +134,14 @@
|
|
|
134
134
|
"@ankhorage/capability": "^0.2.1",
|
|
135
135
|
"@ankhorage/contracts": "^26.0.0",
|
|
136
136
|
"@ankhorage/dependency-graph": "^1.3.0",
|
|
137
|
-
"@ankhorage/rules-architecture": "^0.6.
|
|
137
|
+
"@ankhorage/rules-architecture": "^0.6.1",
|
|
138
138
|
"@ankhorage/rules-repository": "^0.2.0",
|
|
139
139
|
"@ankhorage/supabase-auth": "^1.2.44",
|
|
140
140
|
"@ankhorage/utility": "^1.9.85"
|
|
141
141
|
},
|
|
142
142
|
"devDependencies": {
|
|
143
143
|
"@ankhorage/ankh": "^0.13.1",
|
|
144
|
-
"@ankhorage/devtools": "^2.3.
|
|
144
|
+
"@ankhorage/devtools": "^2.3.18",
|
|
145
145
|
"@ankhorage/paradox": "^0.3.4",
|
|
146
146
|
"@types/bun": "^1.4.2",
|
|
147
147
|
"@types/node": "^26.6.5",
|
package/src/bunPolicyAnalysis.ts
CHANGED
|
@@ -28,6 +28,10 @@ export async function applyBunRuntimePolicy(
|
|
|
28
28
|
const packageJson = await readPackageJson(packageJsonPath);
|
|
29
29
|
if (packageJson === null) return result;
|
|
30
30
|
|
|
31
|
+
// The canonical owner validates its candidate metadata locally before publication.
|
|
32
|
+
// Its installed Rules package necessarily contains the previous released version.
|
|
33
|
+
if (packageJson.name === '@ankhorage/rules-repository') return result;
|
|
34
|
+
|
|
31
35
|
const diagnostics = [
|
|
32
36
|
...result.diagnostics,
|
|
33
37
|
...analyzePackageBunPolicy(packageJson, packageJsonPath, result),
|