@ankhorage/doctor 0.11.2 → 0.11.4
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 +17 -0
- package/README.md +31 -41
- package/dist/analysis.d.ts.map +1 -1
- package/dist/analysis.js +5 -7
- package/dist/analysis.js.map +1 -1
- package/dist/bunPolicyAnalysis.d.ts +3 -0
- package/dist/bunPolicyAnalysis.d.ts.map +1 -1
- package/dist/bunPolicyAnalysis.js +76 -82
- package/dist/bunPolicyAnalysis.js.map +1 -1
- package/package.json +5 -3
- package/src/analysis.ts +6 -8
- package/src/bunPolicyAnalysis.ts +83 -106
- package/dist/readme-usage.d.ts +0 -2
- package/dist/readme-usage.d.ts.map +0 -1
- package/dist/readme-usage.js +0 -51
- package/dist/readme-usage.js.map +0 -1
- package/src/readme-usage.ts +0 -51
package/src/bunPolicyAnalysis.ts
CHANGED
|
@@ -1,31 +1,21 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
2
|
import path from 'node:path';
|
|
4
|
-
import { pathToFileURL } from 'node:url';
|
|
5
3
|
|
|
6
|
-
import
|
|
7
|
-
import type { DoctorDiagnostic, DoctorRuleId } from './diagnostics.js';
|
|
4
|
+
import { REPOSITORY_POLICY } from '@ankhorage/policy/repository';
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
readonly typesRange: string;
|
|
12
|
-
readonly version: string;
|
|
13
|
-
}
|
|
6
|
+
import type { DoctorAnalysisResult } from './analysis.js';
|
|
7
|
+
import type { DoctorDiagnostic } from './diagnostics.js';
|
|
14
8
|
|
|
15
|
-
const
|
|
9
|
+
const BUN_POLICY = REPOSITORY_POLICY.runtime.bun;
|
|
16
10
|
const REPAIR_HINT = 'Run "ankh devtools sync" to repair the managed Bun state.';
|
|
17
11
|
const BUN_VERSION_PATTERN = /^\s*bun-version:\s*['"]?([^'"\s#]+)['"]?\s*(?:#.*)?$/mu;
|
|
18
|
-
const WORKFLOWS = [
|
|
19
|
-
{
|
|
20
|
-
relativePath: path.join('.github', 'workflows', 'ci.yml'),
|
|
21
|
-
ruleId: 'repo.workflows.ci.bun-policy',
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
relativePath: path.join('.github', 'workflows', 'release.yml'),
|
|
25
|
-
ruleId: 'repo.workflows.release.bun-policy',
|
|
26
|
-
},
|
|
27
|
-
] as const satisfies readonly { readonly relativePath: string; readonly ruleId: DoctorRuleId }[];
|
|
28
12
|
|
|
13
|
+
type RepositoryPolicyRule = (typeof REPOSITORY_POLICY.rules)[keyof typeof REPOSITORY_POLICY.rules];
|
|
14
|
+
type WorkflowTarget = (typeof BUN_POLICY.workflowTargets)[number];
|
|
15
|
+
|
|
16
|
+
/***
|
|
17
|
+
* Applies canonical repository Bun-policy diagnostics to a Doctor analysis result.
|
|
18
|
+
*/
|
|
29
19
|
export async function applyBunRuntimePolicy(
|
|
30
20
|
result: DoctorAnalysisResult,
|
|
31
21
|
): Promise<DoctorAnalysisResult> {
|
|
@@ -33,54 +23,37 @@ export async function applyBunRuntimePolicy(
|
|
|
33
23
|
return result;
|
|
34
24
|
}
|
|
35
25
|
|
|
36
|
-
const policy = await loadTargetBunRuntimePolicy(result.targetPath);
|
|
37
26
|
const packageJsonPath = path.join(result.targetPath, 'package.json');
|
|
38
27
|
const packageJson = await readPackageJson(packageJsonPath);
|
|
39
|
-
if (
|
|
40
|
-
return result;
|
|
41
|
-
}
|
|
28
|
+
if (packageJson === null) return result;
|
|
42
29
|
|
|
43
30
|
const diagnostics = [
|
|
44
31
|
...result.diagnostics,
|
|
45
|
-
...analyzePackageBunPolicy(packageJson, packageJsonPath, result
|
|
46
|
-
...(await analyzeWorkflowBunPolicy(result
|
|
32
|
+
...analyzePackageBunPolicy(packageJson, packageJsonPath, result),
|
|
33
|
+
...(await analyzeWorkflowBunPolicy(result)),
|
|
47
34
|
];
|
|
48
35
|
|
|
49
36
|
return withDiagnostics(result, diagnostics);
|
|
50
37
|
}
|
|
51
38
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const requireFromTarget = createRequire(path.join(targetPath, 'package.json'));
|
|
56
|
-
try {
|
|
57
|
-
const modulePath = requireFromTarget.resolve(DEVTOOLS_POLICY_SPECIFIER);
|
|
58
|
-
const importedModule = (await import(pathToFileURL(modulePath).href)) as unknown;
|
|
59
|
-
return parseBunRuntimePolicy(importedModule);
|
|
60
|
-
} catch (error) {
|
|
61
|
-
if (isUnavailablePolicyError(error)) {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
throw error;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
39
|
+
/***
|
|
40
|
+
* Reports package-manifest drift from the canonical Bun runtime contract.
|
|
41
|
+
*/
|
|
68
42
|
function analyzePackageBunPolicy(
|
|
69
43
|
packageJson: Record<string, unknown>,
|
|
70
44
|
packageJsonPath: string,
|
|
71
45
|
result: DoctorAnalysisResult,
|
|
72
|
-
policy: BunRuntimePolicySnapshot,
|
|
73
46
|
): DoctorDiagnostic[] {
|
|
74
47
|
const diagnostics: DoctorDiagnostic[] = [];
|
|
75
48
|
const { packageManager } = packageJson;
|
|
76
49
|
if (isNonEmptyString(packageManager) && packageManager.startsWith('bun@')) {
|
|
77
50
|
pushMismatch(diagnostics, {
|
|
78
51
|
actual: packageManager,
|
|
79
|
-
expected:
|
|
52
|
+
expected: BUN_POLICY.packageManager,
|
|
80
53
|
location: 'package.json#packageManager',
|
|
81
54
|
path: packageJsonPath,
|
|
82
55
|
profile: result.profile,
|
|
83
|
-
|
|
56
|
+
rule: REPOSITORY_POLICY.rules.packageManager,
|
|
84
57
|
});
|
|
85
58
|
}
|
|
86
59
|
|
|
@@ -91,77 +64,86 @@ function analyzePackageBunPolicy(
|
|
|
91
64
|
if (isNonEmptyString(bunTypes)) {
|
|
92
65
|
pushMismatch(diagnostics, {
|
|
93
66
|
actual: bunTypes,
|
|
94
|
-
expected:
|
|
67
|
+
expected: BUN_POLICY.typesRange,
|
|
95
68
|
location: 'package.json#devDependencies.@types/bun',
|
|
96
69
|
path: packageJsonPath,
|
|
97
70
|
profile: result.profile,
|
|
98
|
-
|
|
71
|
+
rule: REPOSITORY_POLICY.rules.bunTypes,
|
|
99
72
|
});
|
|
100
73
|
}
|
|
101
74
|
|
|
102
75
|
return diagnostics;
|
|
103
76
|
}
|
|
104
77
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
): Promise<DoctorDiagnostic[]> {
|
|
78
|
+
/***
|
|
79
|
+
* Reports managed workflow drift from the canonical Bun setup version.
|
|
80
|
+
*/
|
|
81
|
+
async function analyzeWorkflowBunPolicy(result: DoctorAnalysisResult): Promise<DoctorDiagnostic[]> {
|
|
109
82
|
const diagnostics = await Promise.all(
|
|
110
|
-
|
|
83
|
+
BUN_POLICY.workflowTargets.map(async (workflow) => await analyzeWorkflow(result, workflow)),
|
|
111
84
|
);
|
|
112
85
|
return diagnostics.flat();
|
|
113
86
|
}
|
|
114
87
|
|
|
88
|
+
/***
|
|
89
|
+
* Checks one managed workflow for the canonical Bun setup version.
|
|
90
|
+
*/
|
|
115
91
|
async function analyzeWorkflow(
|
|
116
92
|
result: DoctorAnalysisResult,
|
|
117
|
-
workflow:
|
|
118
|
-
policy: BunRuntimePolicySnapshot,
|
|
93
|
+
workflow: WorkflowTarget,
|
|
119
94
|
): Promise<DoctorDiagnostic[]> {
|
|
120
|
-
const workflowPath = path.join(result.targetPath, workflow.
|
|
95
|
+
const workflowPath = path.join(result.targetPath, workflow.path);
|
|
121
96
|
const contents = await readTextOrNull(workflowPath);
|
|
122
97
|
if (contents === null) {
|
|
123
|
-
return [createWorkflowDiagnostic(result, workflow,
|
|
98
|
+
return [createWorkflowDiagnostic(result, workflow, workflowPath, 'missing-path')];
|
|
124
99
|
}
|
|
125
100
|
|
|
126
101
|
const match = BUN_VERSION_PATTERN.exec(contents);
|
|
127
102
|
const actual = match?.[1];
|
|
128
103
|
if (!isNonEmptyString(actual)) {
|
|
129
|
-
return [createWorkflowDiagnostic(result, workflow,
|
|
104
|
+
return [createWorkflowDiagnostic(result, workflow, workflowPath, 'field-missing')];
|
|
130
105
|
}
|
|
131
106
|
|
|
132
107
|
const diagnostics: DoctorDiagnostic[] = [];
|
|
133
108
|
pushMismatch(diagnostics, {
|
|
134
109
|
actual,
|
|
135
|
-
expected:
|
|
136
|
-
location: `${workflow.
|
|
110
|
+
expected: BUN_POLICY.version,
|
|
111
|
+
location: `${workflow.path}#bun-version`,
|
|
137
112
|
path: workflowPath,
|
|
138
113
|
profile: result.profile,
|
|
139
|
-
|
|
114
|
+
rule: getRepositoryRule(workflow.ruleId),
|
|
140
115
|
});
|
|
141
116
|
return diagnostics;
|
|
142
117
|
}
|
|
143
118
|
|
|
119
|
+
/***
|
|
120
|
+
* Creates a missing or malformed managed-workflow diagnostic from central rule metadata.
|
|
121
|
+
*/
|
|
144
122
|
function createWorkflowDiagnostic(
|
|
145
123
|
result: DoctorAnalysisResult,
|
|
146
|
-
workflow:
|
|
147
|
-
policy: BunRuntimePolicySnapshot,
|
|
124
|
+
workflow: WorkflowTarget,
|
|
148
125
|
workflowPath: string,
|
|
149
126
|
code: DoctorDiagnostic['code'],
|
|
150
127
|
): DoctorDiagnostic {
|
|
151
128
|
const detail =
|
|
152
129
|
code === 'missing-path'
|
|
153
|
-
? `Managed workflow is missing: ${workflow.
|
|
154
|
-
: `${workflow.
|
|
130
|
+
? `Managed workflow is missing: ${workflow.path}.`
|
|
131
|
+
: `${workflow.path} does not define bun-version.`;
|
|
132
|
+
const rule = getRepositoryRule(workflow.ruleId);
|
|
133
|
+
|
|
155
134
|
return {
|
|
156
135
|
code,
|
|
157
|
-
message: `${detail} Expected Bun ${
|
|
136
|
+
message: `${detail} Expected Bun ${BUN_POLICY.version}. ${REPAIR_HINT}`,
|
|
158
137
|
path: workflowPath,
|
|
159
138
|
profile: result.profile,
|
|
160
|
-
ruleId:
|
|
161
|
-
severity:
|
|
139
|
+
ruleId: rule.id,
|
|
140
|
+
severity: rule.severity,
|
|
162
141
|
};
|
|
163
142
|
}
|
|
164
143
|
|
|
144
|
+
/***
|
|
145
|
+
* Adds one value-mismatch diagnostic using the owning Policy rule metadata.
|
|
146
|
+
*/
|
|
165
147
|
function pushMismatch(
|
|
166
148
|
diagnostics: DoctorDiagnostic[],
|
|
167
149
|
input: {
|
|
@@ -170,34 +152,35 @@ function pushMismatch(
|
|
|
170
152
|
readonly location: string;
|
|
171
153
|
readonly path: string;
|
|
172
154
|
readonly profile: DoctorAnalysisResult['profile'];
|
|
173
|
-
readonly
|
|
155
|
+
readonly rule: RepositoryPolicyRule;
|
|
174
156
|
},
|
|
175
157
|
): void {
|
|
176
|
-
if (input.actual === input.expected)
|
|
177
|
-
|
|
178
|
-
}
|
|
158
|
+
if (input.actual === input.expected) return;
|
|
159
|
+
|
|
179
160
|
diagnostics.push({
|
|
180
161
|
code: 'field-invalid',
|
|
181
162
|
message: `Bun policy drift at ${input.location}: expected "${input.expected}", found "${input.actual}". ${REPAIR_HINT}`,
|
|
182
163
|
path: input.path,
|
|
183
164
|
profile: input.profile,
|
|
184
|
-
ruleId: input.
|
|
185
|
-
severity:
|
|
165
|
+
ruleId: input.rule.id,
|
|
166
|
+
severity: input.rule.severity,
|
|
186
167
|
});
|
|
187
168
|
}
|
|
188
169
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
170
|
+
/***
|
|
171
|
+
* Resolves one stable repository rule by its Policy-owned id.
|
|
172
|
+
*/
|
|
173
|
+
function getRepositoryRule(ruleId: string): RepositoryPolicyRule {
|
|
174
|
+
const rule = Object.values(REPOSITORY_POLICY.rules).find((entry) => entry.id === ruleId);
|
|
175
|
+
if (rule === undefined) {
|
|
176
|
+
throw new Error(`Unknown repository policy rule: ${ruleId}`);
|
|
192
177
|
}
|
|
193
|
-
|
|
194
|
-
return isNonEmptyString(packageManager) &&
|
|
195
|
-
isNonEmptyString(typesRange) &&
|
|
196
|
-
isNonEmptyString(version)
|
|
197
|
-
? { packageManager, typesRange, version }
|
|
198
|
-
: null;
|
|
178
|
+
return rule;
|
|
199
179
|
}
|
|
200
180
|
|
|
181
|
+
/***
|
|
182
|
+
* Copies updated diagnostics into the analysis result and optional fix plan.
|
|
183
|
+
*/
|
|
201
184
|
function withDiagnostics(
|
|
202
185
|
result: DoctorAnalysisResult,
|
|
203
186
|
diagnostics: readonly DoctorDiagnostic[],
|
|
@@ -209,6 +192,9 @@ function withDiagnostics(
|
|
|
209
192
|
};
|
|
210
193
|
}
|
|
211
194
|
|
|
195
|
+
/***
|
|
196
|
+
* Reads a package manifest when it contains a JSON object.
|
|
197
|
+
*/
|
|
212
198
|
async function readPackageJson(packageJsonPath: string): Promise<Record<string, unknown> | null> {
|
|
213
199
|
try {
|
|
214
200
|
const parsed = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')) as unknown;
|
|
@@ -218,44 +204,35 @@ async function readPackageJson(packageJsonPath: string): Promise<Record<string,
|
|
|
218
204
|
}
|
|
219
205
|
}
|
|
220
206
|
|
|
207
|
+
/***
|
|
208
|
+
* Reads a UTF-8 file while treating a missing managed path as absent.
|
|
209
|
+
*/
|
|
221
210
|
async function readTextOrNull(filePath: string): Promise<string | null> {
|
|
222
211
|
try {
|
|
223
212
|
return await fs.readFile(filePath, 'utf8');
|
|
224
213
|
} catch (error) {
|
|
225
|
-
if (isNodeError(error) && error.code === 'ENOENT')
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
214
|
+
if (isNodeError(error) && error.code === 'ENOENT') return null;
|
|
228
215
|
throw error;
|
|
229
216
|
}
|
|
230
217
|
}
|
|
231
218
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
error.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED')
|
|
238
|
-
) {
|
|
239
|
-
return true;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
243
|
-
return (
|
|
244
|
-
message.includes(DEVTOOLS_POLICY_SPECIFIER) &&
|
|
245
|
-
(message.includes('Cannot find module') ||
|
|
246
|
-
message.includes('not exported') ||
|
|
247
|
-
message.includes('Package subpath'))
|
|
248
|
-
);
|
|
219
|
+
/***
|
|
220
|
+
* Narrows unknown JSON data to a non-array record.
|
|
221
|
+
*/
|
|
222
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
223
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
249
224
|
}
|
|
250
225
|
|
|
226
|
+
/***
|
|
227
|
+
* Narrows a value to a non-empty string.
|
|
228
|
+
*/
|
|
251
229
|
function isNonEmptyString(value: unknown): value is string {
|
|
252
230
|
return typeof value === 'string' && value.trim() !== '';
|
|
253
231
|
}
|
|
254
232
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
233
|
+
/***
|
|
234
|
+
* Narrows unknown failures to Node errors with stable error codes.
|
|
235
|
+
*/
|
|
259
236
|
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
260
237
|
return error instanceof Error && 'code' in error;
|
|
261
238
|
}
|
package/dist/readme-usage.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"readme-usage.d.ts","sourceRoot":"","sources":["../src/readme-usage.ts"],"names":[],"mappings":""}
|
package/dist/readme-usage.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { runCli } from './cli/standalone.js';
|
|
2
|
-
/***
|
|
3
|
-
* Provider and CLI surface
|
|
4
|
-
*
|
|
5
|
-
* `@ankhorage/doctor` is the repo/package compliance provider for Ankhorage.
|
|
6
|
-
*
|
|
7
|
-
* The same shared command implementation backs both:
|
|
8
|
-
*
|
|
9
|
-
* - `ankh doctor ...`
|
|
10
|
-
* - `bunx @ankhorage/doctor ...`
|
|
11
|
-
*
|
|
12
|
-
* Current command surface:
|
|
13
|
-
*
|
|
14
|
-
* - `validate`
|
|
15
|
-
* - `fix`
|
|
16
|
-
* - `repo`
|
|
17
|
-
* - `package`
|
|
18
|
-
*
|
|
19
|
-
* `doctor#2` adds a profile-based policy engine:
|
|
20
|
-
*
|
|
21
|
-
* - strict `public-package` validation for extracted public package repos
|
|
22
|
-
* - light recognized `integration-monorepo` validation for `ankhorage4`
|
|
23
|
-
* - canonical app-manifest authentication validation for JSON file targets
|
|
24
|
-
* - non-mutating `fix` plans for deterministic mechanical changes only
|
|
25
|
-
*
|
|
26
|
-
* Public-package script validation follows the canonical Devtools contract. In particular,
|
|
27
|
-
* packages must expose `knip:check`; the obsolete `knip` script does not satisfy that policy.
|
|
28
|
-
* Changesets dependency placement follows the same ownership boundary: `@ankhorage/devtools`
|
|
29
|
-
* publishes `@changesets/cli` from `dependencies`, while consumers use the Devtools-owned
|
|
30
|
-
* command without declaring the CLI directly.
|
|
31
|
-
*
|
|
32
|
-
* Manifest validation accepts `infra.auth.flow` as the only auth-flow location, rejects
|
|
33
|
-
* `settings.authFlow` with a manual-migration diagnostic, and permits authentication without
|
|
34
|
-
* an authorization block.
|
|
35
|
-
*
|
|
36
|
-
* Still deferred:
|
|
37
|
-
*
|
|
38
|
-
* - GitHub checks
|
|
39
|
-
* - CI checks
|
|
40
|
-
* - on-disk `fix --apply`
|
|
41
|
-
* - deeper cross-repo policy enforcement
|
|
42
|
-
*
|
|
43
|
-
* Path handling:
|
|
44
|
-
*
|
|
45
|
-
* - pass a repo/package directory or app-manifest JSON file as `[path]`, or
|
|
46
|
-
* - omit it to inspect the current working directory
|
|
47
|
-
*
|
|
48
|
-
* @usage
|
|
49
|
-
*/
|
|
50
|
-
await runCli(['--help']);
|
|
51
|
-
//# sourceMappingURL=readme-usage.js.map
|
package/dist/readme-usage.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"readme-usage.js","sourceRoot":"","sources":["../src/readme-usage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC","sourcesContent":["import { runCli } from './cli/standalone.js';\n\n/***\n * Provider and CLI surface\n *\n * `@ankhorage/doctor` is the repo/package compliance provider for Ankhorage.\n *\n * The same shared command implementation backs both:\n *\n * - `ankh doctor ...`\n * - `bunx @ankhorage/doctor ...`\n *\n * Current command surface:\n *\n * - `validate`\n * - `fix`\n * - `repo`\n * - `package`\n *\n * `doctor#2` adds a profile-based policy engine:\n *\n * - strict `public-package` validation for extracted public package repos\n * - light recognized `integration-monorepo` validation for `ankhorage4`\n * - canonical app-manifest authentication validation for JSON file targets\n * - non-mutating `fix` plans for deterministic mechanical changes only\n *\n * Public-package script validation follows the canonical Devtools contract. In particular,\n * packages must expose `knip:check`; the obsolete `knip` script does not satisfy that policy.\n * Changesets dependency placement follows the same ownership boundary: `@ankhorage/devtools`\n * publishes `@changesets/cli` from `dependencies`, while consumers use the Devtools-owned\n * command without declaring the CLI directly.\n *\n * Manifest validation accepts `infra.auth.flow` as the only auth-flow location, rejects\n * `settings.authFlow` with a manual-migration diagnostic, and permits authentication without\n * an authorization block.\n *\n * Still deferred:\n *\n * - GitHub checks\n * - CI checks\n * - on-disk `fix --apply`\n * - deeper cross-repo policy enforcement\n *\n * Path handling:\n *\n * - pass a repo/package directory or app-manifest JSON file as `[path]`, or\n * - omit it to inspect the current working directory\n *\n * @usage\n */\nawait runCli(['--help']);\n"]}
|
package/src/readme-usage.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { runCli } from './cli/standalone.js';
|
|
2
|
-
|
|
3
|
-
/***
|
|
4
|
-
* Provider and CLI surface
|
|
5
|
-
*
|
|
6
|
-
* `@ankhorage/doctor` is the repo/package compliance provider for Ankhorage.
|
|
7
|
-
*
|
|
8
|
-
* The same shared command implementation backs both:
|
|
9
|
-
*
|
|
10
|
-
* - `ankh doctor ...`
|
|
11
|
-
* - `bunx @ankhorage/doctor ...`
|
|
12
|
-
*
|
|
13
|
-
* Current command surface:
|
|
14
|
-
*
|
|
15
|
-
* - `validate`
|
|
16
|
-
* - `fix`
|
|
17
|
-
* - `repo`
|
|
18
|
-
* - `package`
|
|
19
|
-
*
|
|
20
|
-
* `doctor#2` adds a profile-based policy engine:
|
|
21
|
-
*
|
|
22
|
-
* - strict `public-package` validation for extracted public package repos
|
|
23
|
-
* - light recognized `integration-monorepo` validation for `ankhorage4`
|
|
24
|
-
* - canonical app-manifest authentication validation for JSON file targets
|
|
25
|
-
* - non-mutating `fix` plans for deterministic mechanical changes only
|
|
26
|
-
*
|
|
27
|
-
* Public-package script validation follows the canonical Devtools contract. In particular,
|
|
28
|
-
* packages must expose `knip:check`; the obsolete `knip` script does not satisfy that policy.
|
|
29
|
-
* Changesets dependency placement follows the same ownership boundary: `@ankhorage/devtools`
|
|
30
|
-
* publishes `@changesets/cli` from `dependencies`, while consumers use the Devtools-owned
|
|
31
|
-
* command without declaring the CLI directly.
|
|
32
|
-
*
|
|
33
|
-
* Manifest validation accepts `infra.auth.flow` as the only auth-flow location, rejects
|
|
34
|
-
* `settings.authFlow` with a manual-migration diagnostic, and permits authentication without
|
|
35
|
-
* an authorization block.
|
|
36
|
-
*
|
|
37
|
-
* Still deferred:
|
|
38
|
-
*
|
|
39
|
-
* - GitHub checks
|
|
40
|
-
* - CI checks
|
|
41
|
-
* - on-disk `fix --apply`
|
|
42
|
-
* - deeper cross-repo policy enforcement
|
|
43
|
-
*
|
|
44
|
-
* Path handling:
|
|
45
|
-
*
|
|
46
|
-
* - pass a repo/package directory or app-manifest JSON file as `[path]`, or
|
|
47
|
-
* - omit it to inspect the current working directory
|
|
48
|
-
*
|
|
49
|
-
* @usage
|
|
50
|
-
*/
|
|
51
|
-
await runCli(['--help']);
|