@ankhorage/doctor 0.13.1 → 0.14.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 +1 -1
- package/dist/analysis.d.ts.map +1 -1
- package/dist/analysis.js +55 -3
- package/dist/analysis.js.map +1 -1
- package/dist/capabilityCatalog.d.ts +18 -0
- package/dist/capabilityCatalog.d.ts.map +1 -0
- package/dist/capabilityCatalog.js +51 -0
- package/dist/capabilityCatalog.js.map +1 -0
- package/dist/capabilityCatalogAst.d.ts +3 -0
- package/dist/capabilityCatalogAst.d.ts.map +1 -0
- package/dist/capabilityCatalogAst.js +255 -0
- package/dist/capabilityCatalogAst.js.map +1 -0
- package/dist/capabilityCatalogExports.d.ts +3 -0
- package/dist/capabilityCatalogExports.d.ts.map +1 -0
- package/dist/capabilityCatalogExports.js +41 -0
- package/dist/capabilityCatalogExports.js.map +1 -0
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js.map +1 -1
- package/package.json +3 -3
- package/src/analysis.ts +70 -5
- package/src/capabilityCatalog.ts +64 -0
- package/src/capabilityCatalogAst.ts +319 -0
- package/src/capabilityCatalogExports.ts +53 -0
- package/src/diagnostics.ts +4 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import * as ts from 'typescript';
|
|
4
|
+
/*** Evaluate one named catalog export from the supported static TypeScript subset. */
|
|
5
|
+
export async function capabilityCatalogAstAsync(packageRoot, modulePath, exportName) {
|
|
6
|
+
return new StaticCatalogResolver(packageRoot).resolveAsync(modulePath, exportName);
|
|
7
|
+
}
|
|
8
|
+
/*** Resolve static local imports and expressions without loading target modules. */
|
|
9
|
+
class StaticCatalogResolver {
|
|
10
|
+
packageRoot;
|
|
11
|
+
#symbols = new Map();
|
|
12
|
+
#activeSymbols = [];
|
|
13
|
+
constructor(packageRoot) {
|
|
14
|
+
this.packageRoot = packageRoot;
|
|
15
|
+
}
|
|
16
|
+
/*** Resolve a named local export without importing or running its module. */
|
|
17
|
+
async resolveAsync(modulePath, exportName) {
|
|
18
|
+
const key = `${modulePath}:${exportName}`;
|
|
19
|
+
return this.resolveSymbolAsync(key, () => this.resolveUncachedAsync(modulePath, exportName));
|
|
20
|
+
}
|
|
21
|
+
/*** Resolve one local symbol while detecting recursive static references. */
|
|
22
|
+
async resolveSymbolAsync(key, resolveAsync) {
|
|
23
|
+
const cached = this.#symbols.get(key);
|
|
24
|
+
if (cached !== undefined)
|
|
25
|
+
return cached;
|
|
26
|
+
const cycleStart = this.#activeSymbols.indexOf(key);
|
|
27
|
+
if (cycleStart >= 0)
|
|
28
|
+
throw new Error(`Cyclic static capability reference: ${[...this.#activeSymbols.slice(cycleStart), key].join(' -> ')}.`);
|
|
29
|
+
this.#activeSymbols.push(key);
|
|
30
|
+
try {
|
|
31
|
+
const value = await resolveAsync();
|
|
32
|
+
this.#symbols.set(key, value);
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
this.#activeSymbols.pop();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/*** Find and evaluate the declaration owning one exported static value. */
|
|
40
|
+
async resolveUncachedAsync(modulePath, exportName) {
|
|
41
|
+
const source = ts.createSourceFile(modulePath, await fs.readFile(modulePath, 'utf8'), ts.ScriptTarget.ESNext, true);
|
|
42
|
+
const declaration = source.statements
|
|
43
|
+
.filter((statement) => ts.isVariableStatement(statement))
|
|
44
|
+
.filter(hasExportModifier)
|
|
45
|
+
.filter((statement) => isConstDeclaration(statement.declarationList))
|
|
46
|
+
.flatMap((statement) => statement.declarationList.declarations)
|
|
47
|
+
.find((value) => ts.isIdentifier(value.name) && value.name.text === exportName);
|
|
48
|
+
if (declaration?.initializer === undefined)
|
|
49
|
+
throw new Error(`Unable to find static export ${exportName} in ${modulePath}.`);
|
|
50
|
+
return this.evaluateAsync(declaration.initializer, new StaticCatalogScope(this, source, modulePath, this.packageRoot));
|
|
51
|
+
}
|
|
52
|
+
/*** Evaluate an allowed static expression. */
|
|
53
|
+
async evaluateAsync(expression, environment) {
|
|
54
|
+
const node = unwrapExpression(expression);
|
|
55
|
+
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
|
|
56
|
+
return node.text;
|
|
57
|
+
if (ts.isNumericLiteral(node))
|
|
58
|
+
return Number(node.text);
|
|
59
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword)
|
|
60
|
+
return true;
|
|
61
|
+
if (node.kind === ts.SyntaxKind.FalseKeyword)
|
|
62
|
+
return false;
|
|
63
|
+
if (node.kind === ts.SyntaxKind.NullKeyword)
|
|
64
|
+
return null;
|
|
65
|
+
if (ts.isIdentifier(node))
|
|
66
|
+
return environment.resolveAsync(node.text);
|
|
67
|
+
if (ts.isArrayLiteralExpression(node))
|
|
68
|
+
return this.arrayAsync(node, environment);
|
|
69
|
+
if (ts.isObjectLiteralExpression(node))
|
|
70
|
+
return this.objectAsync(node, environment);
|
|
71
|
+
if (ts.isTemplateExpression(node))
|
|
72
|
+
return this.templateAsync(node, environment);
|
|
73
|
+
if (ts.isCallExpression(node))
|
|
74
|
+
return this.mapAsync(node, environment);
|
|
75
|
+
throw new Error(`Unsupported static capability expression: ${node.getText()}.`);
|
|
76
|
+
}
|
|
77
|
+
/*** Evaluate a static array literal. */
|
|
78
|
+
arrayAsync(node, environment) {
|
|
79
|
+
return this.evaluateElementsAsync(node.elements, environment);
|
|
80
|
+
}
|
|
81
|
+
/*** Evaluate static object properties and descriptor spreads. */
|
|
82
|
+
async objectAsync(node, environment) {
|
|
83
|
+
const result = {};
|
|
84
|
+
for (const property of node.properties) {
|
|
85
|
+
if (ts.isSpreadAssignment(property)) {
|
|
86
|
+
const value = await this.evaluateAsync(property.expression, environment);
|
|
87
|
+
if (!isStaticRecord(value))
|
|
88
|
+
throw new Error('Capability descriptor spreads must be static objects.');
|
|
89
|
+
Object.assign(result, value);
|
|
90
|
+
}
|
|
91
|
+
else if (ts.isPropertyAssignment(property) &&
|
|
92
|
+
(ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)))
|
|
93
|
+
result[property.name.text] = await this.evaluateAsync(property.initializer, environment);
|
|
94
|
+
else if (ts.isShorthandPropertyAssignment(property))
|
|
95
|
+
result[property.name.text] = await environment.resolveAsync(property.name.text);
|
|
96
|
+
else
|
|
97
|
+
throw new Error(`Unsupported static capability property: ${property.getText()}.`);
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
/*** Evaluate a template literal whose substitutions are scalar static values. */
|
|
102
|
+
async templateAsync(node, environment) {
|
|
103
|
+
let value = node.head.text;
|
|
104
|
+
for (const span of node.templateSpans) {
|
|
105
|
+
const interpolation = await this.evaluateAsync(span.expression, environment);
|
|
106
|
+
if (typeof interpolation === 'object' && interpolation !== null)
|
|
107
|
+
throw new Error('Capability templates may interpolate only static scalar values.');
|
|
108
|
+
value += `${interpolation}${span.literal.text}`;
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
/*** Evaluate Array.map with a single static arrow-function parameter. */
|
|
113
|
+
async mapAsync(node, environment) {
|
|
114
|
+
if (!ts.isPropertyAccessExpression(node.expression) || node.expression.name.text !== 'map')
|
|
115
|
+
throw new Error(`Unsupported static capability call: ${node.getText()}.`);
|
|
116
|
+
const values = await this.evaluateAsync(node.expression.expression, environment);
|
|
117
|
+
const callback = node.arguments.at(0);
|
|
118
|
+
if (!isStaticArray(values) || callback === undefined || !ts.isArrowFunction(callback))
|
|
119
|
+
throw new Error(`Unsupported static capability map: ${node.getText()}.`);
|
|
120
|
+
const parameter = callback.parameters.at(0);
|
|
121
|
+
const { body } = callback;
|
|
122
|
+
if (parameter === undefined || !ts.isIdentifier(parameter.name) || !ts.isExpression(body))
|
|
123
|
+
throw new Error(`Unsupported static capability map callback: ${callback.getText()}.`);
|
|
124
|
+
const parameterName = parameter.name.text;
|
|
125
|
+
const result = [];
|
|
126
|
+
for (const value of values)
|
|
127
|
+
result.push(await this.evaluateAsync(body, environment.withValue(parameterName, value)));
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
/*** Evaluate static array elements in source order. */
|
|
131
|
+
async evaluateElementsAsync(elements, environment) {
|
|
132
|
+
const result = [];
|
|
133
|
+
for (const element of elements) {
|
|
134
|
+
if (ts.isSpreadElement(element))
|
|
135
|
+
throw new Error('Catalog array spreads are not supported.');
|
|
136
|
+
result.push(await this.evaluateAsync(element, environment));
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/*** Resolve only static symbols reached from the requested catalog expression. */
|
|
142
|
+
class StaticCatalogScope {
|
|
143
|
+
resolver;
|
|
144
|
+
source;
|
|
145
|
+
modulePath;
|
|
146
|
+
packageRoot;
|
|
147
|
+
values;
|
|
148
|
+
constructor(resolver, source, modulePath, packageRoot, values = new Map()) {
|
|
149
|
+
this.resolver = resolver;
|
|
150
|
+
this.source = source;
|
|
151
|
+
this.modulePath = modulePath;
|
|
152
|
+
this.packageRoot = packageRoot;
|
|
153
|
+
this.values = values;
|
|
154
|
+
}
|
|
155
|
+
/*** Resolve a local constant or named local import when it is referenced. */
|
|
156
|
+
async resolveAsync(name) {
|
|
157
|
+
const value = this.values.get(name);
|
|
158
|
+
if (value !== undefined)
|
|
159
|
+
return value;
|
|
160
|
+
const declaration = findStaticDeclaration(this.source, name);
|
|
161
|
+
const initializer = declaration?.initializer;
|
|
162
|
+
if (initializer !== undefined)
|
|
163
|
+
return this.resolver.resolveSymbolAsync(`${this.modulePath}:${name}`, () => this.resolver.evaluateAsync(initializer, this));
|
|
164
|
+
const imported = findImportBinding(this.source, name);
|
|
165
|
+
if (imported === undefined)
|
|
166
|
+
throw new Error(`Unsupported dynamic capability value: ${name}.`);
|
|
167
|
+
if (!ts.isStringLiteral(imported.moduleSpecifier))
|
|
168
|
+
throw new Error('Capability catalogs support only named local value imports.');
|
|
169
|
+
const bindings = imported.importClause?.namedBindings;
|
|
170
|
+
if (imported.importClause?.isTypeOnly || bindings === undefined)
|
|
171
|
+
throw new Error(`Unsupported dynamic capability value: ${name}.`);
|
|
172
|
+
if (!ts.isNamedImports(bindings))
|
|
173
|
+
throw new Error('Capability catalogs support only named local value imports.');
|
|
174
|
+
const binding = bindings.elements.find((element) => !element.isTypeOnly && element.name.text === name);
|
|
175
|
+
if (binding === undefined)
|
|
176
|
+
throw new Error(`Unsupported dynamic capability value: ${name}.`);
|
|
177
|
+
const importPath = await resolveImportPathAsync(this.packageRoot, this.modulePath, imported.moduleSpecifier.text);
|
|
178
|
+
return this.resolver.resolveAsync(importPath, binding.propertyName?.text ?? binding.name.text);
|
|
179
|
+
}
|
|
180
|
+
/*** Add a static callback parameter without evaluating unrelated module symbols. */
|
|
181
|
+
withValue(name, value) {
|
|
182
|
+
return new StaticCatalogScope(this.resolver, this.source, this.modulePath, this.packageRoot, new Map([...this.values, [name, value]]));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/*** Find the static top-level declaration that owns a local identifier. */
|
|
186
|
+
function findStaticDeclaration(source, name) {
|
|
187
|
+
return source.statements
|
|
188
|
+
.filter((statement) => ts.isVariableStatement(statement))
|
|
189
|
+
.filter((statement) => isConstDeclaration(statement.declarationList))
|
|
190
|
+
.flatMap((statement) => statement.declarationList.declarations)
|
|
191
|
+
.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === name);
|
|
192
|
+
}
|
|
193
|
+
/*** Find the import declaration that introduces a referenced local identifier. */
|
|
194
|
+
function findImportBinding(source, name) {
|
|
195
|
+
return source.statements
|
|
196
|
+
.filter((statement) => ts.isImportDeclaration(statement))
|
|
197
|
+
.find((statement) => {
|
|
198
|
+
const clause = statement.importClause;
|
|
199
|
+
return (clause?.name?.text === name ||
|
|
200
|
+
(clause?.namedBindings !== undefined &&
|
|
201
|
+
ts.isNamedImports(clause.namedBindings) &&
|
|
202
|
+
clause.namedBindings.elements.some((element) => element.name.text === name)));
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/*** Determine whether a declaration list is immutable static catalog input. */
|
|
206
|
+
function isConstDeclaration(list) {
|
|
207
|
+
return (list.flags & ts.NodeFlags.Const) !== 0;
|
|
208
|
+
}
|
|
209
|
+
/*** Resolve a local import to one source file inside the inspected package. */
|
|
210
|
+
async function resolveImportPathAsync(packageRoot, fromPath, specifier) {
|
|
211
|
+
if (!specifier.startsWith('.'))
|
|
212
|
+
throw new Error('Capability catalogs may import only local static metadata.');
|
|
213
|
+
const candidate = path.resolve(path.dirname(fromPath), specifier);
|
|
214
|
+
if (!isInsidePackage(packageRoot, candidate) && candidate !== packageRoot)
|
|
215
|
+
throw new Error('Capability catalogs may not import outside their package.');
|
|
216
|
+
for (const extension of ['', '.ts', '.tsx', '.js', '.mjs', '/index.ts'])
|
|
217
|
+
if (await isFileAsync(`${candidate}${extension}`))
|
|
218
|
+
return `${candidate}${extension}`;
|
|
219
|
+
throw new Error(`Unable to resolve static capability import ${specifier}.`);
|
|
220
|
+
}
|
|
221
|
+
/*** Unwrap TypeScript syntax which does not alter a runtime value. */
|
|
222
|
+
function unwrapExpression(expression) {
|
|
223
|
+
return ts.isAsExpression(expression) ||
|
|
224
|
+
ts.isSatisfiesExpression(expression) ||
|
|
225
|
+
ts.isParenthesizedExpression(expression)
|
|
226
|
+
? unwrapExpression(expression.expression)
|
|
227
|
+
: expression;
|
|
228
|
+
}
|
|
229
|
+
/*** Narrow a static value to a descriptor-shaped record. */
|
|
230
|
+
function isStaticRecord(value) {
|
|
231
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
232
|
+
}
|
|
233
|
+
/*** Narrow a static value to an array. */
|
|
234
|
+
function isStaticArray(value) {
|
|
235
|
+
return Array.isArray(value);
|
|
236
|
+
}
|
|
237
|
+
/*** Detect an exported variable statement. */
|
|
238
|
+
function hasExportModifier(statement) {
|
|
239
|
+
return (statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false);
|
|
240
|
+
}
|
|
241
|
+
/*** Check whether a resolved path remains within the inspected package root. */
|
|
242
|
+
function isInsidePackage(packageRoot, candidate) {
|
|
243
|
+
const relativePath = path.relative(packageRoot, candidate);
|
|
244
|
+
return relativePath !== '' && !relativePath.startsWith(`..${path.sep}`) && relativePath !== '..';
|
|
245
|
+
}
|
|
246
|
+
/*** Check whether one candidate is a regular file. */
|
|
247
|
+
async function isFileAsync(candidate) {
|
|
248
|
+
try {
|
|
249
|
+
return (await fs.stat(candidate)).isFile();
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=capabilityCatalogAst.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilityCatalogAst.js","sourceRoot":"","sources":["../src/capabilityCatalogAst.ts"],"names":[],"mappings":"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","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport * as ts from 'typescript';\n\ntype StaticValue = string | number | boolean | null | readonly StaticValue[] | StaticRecord;\ninterface StaticRecord {\n readonly [key: string]: StaticValue;\n}\n\n/*** Evaluate one named catalog export from the supported static TypeScript subset. */\nexport async function capabilityCatalogAstAsync(\n packageRoot: string,\n modulePath: string,\n exportName: string,\n): Promise<unknown> {\n return new StaticCatalogResolver(packageRoot).resolveAsync(modulePath, exportName);\n}\n\n/*** Resolve static local imports and expressions without loading target modules. */\nclass StaticCatalogResolver {\n readonly #symbols = new Map<string, StaticValue>();\n readonly #activeSymbols: string[] = [];\n\n constructor(private readonly packageRoot: string) {}\n\n /*** Resolve a named local export without importing or running its module. */\n async resolveAsync(modulePath: string, exportName: string): Promise<StaticValue> {\n const key = `${modulePath}:${exportName}`;\n return this.resolveSymbolAsync(key, () => this.resolveUncachedAsync(modulePath, exportName));\n }\n\n /*** Resolve one local symbol while detecting recursive static references. */\n async resolveSymbolAsync(\n key: string,\n resolveAsync: () => Promise<StaticValue>,\n ): Promise<StaticValue> {\n const cached = this.#symbols.get(key);\n if (cached !== undefined) return cached;\n const cycleStart = this.#activeSymbols.indexOf(key);\n if (cycleStart >= 0)\n throw new Error(\n `Cyclic static capability reference: ${[...this.#activeSymbols.slice(cycleStart), key].join(' -> ')}.`,\n );\n this.#activeSymbols.push(key);\n try {\n const value = await resolveAsync();\n this.#symbols.set(key, value);\n return value;\n } finally {\n this.#activeSymbols.pop();\n }\n }\n\n /*** Find and evaluate the declaration owning one exported static value. */\n async resolveUncachedAsync(modulePath: string, exportName: string): Promise<StaticValue> {\n const source = ts.createSourceFile(\n modulePath,\n await fs.readFile(modulePath, 'utf8'),\n ts.ScriptTarget.ESNext,\n true,\n );\n const declaration = source.statements\n .filter((statement): statement is ts.VariableStatement => ts.isVariableStatement(statement))\n .filter(hasExportModifier)\n .filter((statement) => isConstDeclaration(statement.declarationList))\n .flatMap((statement) => statement.declarationList.declarations)\n .find((value) => ts.isIdentifier(value.name) && value.name.text === exportName);\n if (declaration?.initializer === undefined)\n throw new Error(`Unable to find static export ${exportName} in ${modulePath}.`);\n return this.evaluateAsync(\n declaration.initializer,\n new StaticCatalogScope(this, source, modulePath, this.packageRoot),\n );\n }\n\n /*** Evaluate an allowed static expression. */\n async evaluateAsync(\n expression: ts.Expression,\n environment: StaticCatalogScope,\n ): Promise<StaticValue> {\n const node = unwrapExpression(expression);\n if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;\n if (ts.isNumericLiteral(node)) return Number(node.text);\n if (node.kind === ts.SyntaxKind.TrueKeyword) return true;\n if (node.kind === ts.SyntaxKind.FalseKeyword) return false;\n if (node.kind === ts.SyntaxKind.NullKeyword) return null;\n if (ts.isIdentifier(node)) return environment.resolveAsync(node.text);\n if (ts.isArrayLiteralExpression(node)) return this.arrayAsync(node, environment);\n if (ts.isObjectLiteralExpression(node)) return this.objectAsync(node, environment);\n if (ts.isTemplateExpression(node)) return this.templateAsync(node, environment);\n if (ts.isCallExpression(node)) return this.mapAsync(node, environment);\n throw new Error(`Unsupported static capability expression: ${node.getText()}.`);\n }\n\n /*** Evaluate a static array literal. */\n arrayAsync(\n node: ts.ArrayLiteralExpression,\n environment: StaticCatalogScope,\n ): Promise<readonly StaticValue[]> {\n return this.evaluateElementsAsync(node.elements, environment);\n }\n\n /*** Evaluate static object properties and descriptor spreads. */\n async objectAsync(\n node: ts.ObjectLiteralExpression,\n environment: StaticCatalogScope,\n ): Promise<StaticRecord> {\n const result: Record<string, StaticValue> = {};\n for (const property of node.properties) {\n if (ts.isSpreadAssignment(property)) {\n const value = await this.evaluateAsync(property.expression, environment);\n if (!isStaticRecord(value))\n throw new Error('Capability descriptor spreads must be static objects.');\n Object.assign(result, value);\n } else if (\n ts.isPropertyAssignment(property) &&\n (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name))\n )\n result[property.name.text] = await this.evaluateAsync(property.initializer, environment);\n else if (ts.isShorthandPropertyAssignment(property))\n result[property.name.text] = await environment.resolveAsync(property.name.text);\n else throw new Error(`Unsupported static capability property: ${property.getText()}.`);\n }\n return result;\n }\n\n /*** Evaluate a template literal whose substitutions are scalar static values. */\n async templateAsync(\n node: ts.TemplateExpression,\n environment: StaticCatalogScope,\n ): Promise<string> {\n let value = node.head.text;\n for (const span of node.templateSpans) {\n const interpolation = await this.evaluateAsync(span.expression, environment);\n if (typeof interpolation === 'object' && interpolation !== null)\n throw new Error('Capability templates may interpolate only static scalar values.');\n value += `${interpolation}${span.literal.text}`;\n }\n return value;\n }\n\n /*** Evaluate Array.map with a single static arrow-function parameter. */\n async mapAsync(\n node: ts.CallExpression,\n environment: StaticCatalogScope,\n ): Promise<readonly StaticValue[]> {\n if (!ts.isPropertyAccessExpression(node.expression) || node.expression.name.text !== 'map')\n throw new Error(`Unsupported static capability call: ${node.getText()}.`);\n const values = await this.evaluateAsync(node.expression.expression, environment);\n const callback = node.arguments.at(0);\n if (!isStaticArray(values) || callback === undefined || !ts.isArrowFunction(callback))\n throw new Error(`Unsupported static capability map: ${node.getText()}.`);\n const parameter = callback.parameters.at(0);\n const { body } = callback;\n if (parameter === undefined || !ts.isIdentifier(parameter.name) || !ts.isExpression(body))\n throw new Error(`Unsupported static capability map callback: ${callback.getText()}.`);\n const parameterName = parameter.name.text;\n const result: StaticValue[] = [];\n for (const value of values)\n result.push(await this.evaluateAsync(body, environment.withValue(parameterName, value)));\n return result;\n }\n\n /*** Evaluate static array elements in source order. */\n async evaluateElementsAsync(\n elements: ts.NodeArray<ts.Expression | ts.SpreadElement>,\n environment: StaticCatalogScope,\n ): Promise<readonly StaticValue[]> {\n const result: StaticValue[] = [];\n for (const element of elements) {\n if (ts.isSpreadElement(element)) throw new Error('Catalog array spreads are not supported.');\n result.push(await this.evaluateAsync(element, environment));\n }\n return result;\n }\n}\n\n/*** Resolve only static symbols reached from the requested catalog expression. */\nclass StaticCatalogScope {\n constructor(\n private readonly resolver: StaticCatalogResolver,\n private readonly source: ts.SourceFile,\n private readonly modulePath: string,\n private readonly packageRoot: string,\n private readonly values: ReadonlyMap<string, StaticValue> = new Map(),\n ) {}\n\n /*** Resolve a local constant or named local import when it is referenced. */\n async resolveAsync(name: string): Promise<StaticValue> {\n const value = this.values.get(name);\n if (value !== undefined) return value;\n const declaration = findStaticDeclaration(this.source, name);\n const initializer = declaration?.initializer;\n if (initializer !== undefined)\n return this.resolver.resolveSymbolAsync(`${this.modulePath}:${name}`, () =>\n this.resolver.evaluateAsync(initializer, this),\n );\n const imported = findImportBinding(this.source, name);\n if (imported === undefined) throw new Error(`Unsupported dynamic capability value: ${name}.`);\n if (!ts.isStringLiteral(imported.moduleSpecifier))\n throw new Error('Capability catalogs support only named local value imports.');\n const bindings = imported.importClause?.namedBindings;\n if (imported.importClause?.isTypeOnly || bindings === undefined)\n throw new Error(`Unsupported dynamic capability value: ${name}.`);\n if (!ts.isNamedImports(bindings))\n throw new Error('Capability catalogs support only named local value imports.');\n const binding = bindings.elements.find(\n (element) => !element.isTypeOnly && element.name.text === name,\n );\n if (binding === undefined) throw new Error(`Unsupported dynamic capability value: ${name}.`);\n const importPath = await resolveImportPathAsync(\n this.packageRoot,\n this.modulePath,\n imported.moduleSpecifier.text,\n );\n return this.resolver.resolveAsync(importPath, binding.propertyName?.text ?? binding.name.text);\n }\n\n /*** Add a static callback parameter without evaluating unrelated module symbols. */\n withValue(name: string, value: StaticValue): StaticCatalogScope {\n return new StaticCatalogScope(\n this.resolver,\n this.source,\n this.modulePath,\n this.packageRoot,\n new Map([...this.values, [name, value]]),\n );\n }\n}\n\n/*** Find the static top-level declaration that owns a local identifier. */\nfunction findStaticDeclaration(\n source: ts.SourceFile,\n name: string,\n): ts.VariableDeclaration | undefined {\n return source.statements\n .filter((statement): statement is ts.VariableStatement => ts.isVariableStatement(statement))\n .filter((statement) => isConstDeclaration(statement.declarationList))\n .flatMap((statement) => statement.declarationList.declarations)\n .find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === name);\n}\n\n/*** Find the import declaration that introduces a referenced local identifier. */\nfunction findImportBinding(source: ts.SourceFile, name: string): ts.ImportDeclaration | undefined {\n return source.statements\n .filter((statement): statement is ts.ImportDeclaration => ts.isImportDeclaration(statement))\n .find((statement) => {\n const clause = statement.importClause;\n return (\n clause?.name?.text === name ||\n (clause?.namedBindings !== undefined &&\n ts.isNamedImports(clause.namedBindings) &&\n clause.namedBindings.elements.some((element) => element.name.text === name))\n );\n });\n}\n\n/*** Determine whether a declaration list is immutable static catalog input. */\nfunction isConstDeclaration(list: ts.VariableDeclarationList): boolean {\n return (list.flags & ts.NodeFlags.Const) !== 0;\n}\n\n/*** Resolve a local import to one source file inside the inspected package. */\nasync function resolveImportPathAsync(\n packageRoot: string,\n fromPath: string,\n specifier: string,\n): Promise<string> {\n if (!specifier.startsWith('.'))\n throw new Error('Capability catalogs may import only local static metadata.');\n const candidate = path.resolve(path.dirname(fromPath), specifier);\n if (!isInsidePackage(packageRoot, candidate) && candidate !== packageRoot)\n throw new Error('Capability catalogs may not import outside their package.');\n for (const extension of ['', '.ts', '.tsx', '.js', '.mjs', '/index.ts'])\n if (await isFileAsync(`${candidate}${extension}`)) return `${candidate}${extension}`;\n throw new Error(`Unable to resolve static capability import ${specifier}.`);\n}\n\n/*** Unwrap TypeScript syntax which does not alter a runtime value. */\nfunction unwrapExpression(expression: ts.Expression): ts.Expression {\n return ts.isAsExpression(expression) ||\n ts.isSatisfiesExpression(expression) ||\n ts.isParenthesizedExpression(expression)\n ? unwrapExpression(expression.expression)\n : expression;\n}\n\n/*** Narrow a static value to a descriptor-shaped record. */\nfunction isStaticRecord(value: StaticValue): value is StaticRecord {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/*** Narrow a static value to an array. */\nfunction isStaticArray(value: StaticValue): value is readonly StaticValue[] {\n return Array.isArray(value);\n}\n\n/*** Detect an exported variable statement. */\nfunction hasExportModifier(statement: ts.VariableStatement): boolean {\n return (\n statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false\n );\n}\n\n/*** Check whether a resolved path remains within the inspected package root. */\nfunction isInsidePackage(packageRoot: string, candidate: string): boolean {\n const relativePath = path.relative(packageRoot, candidate);\n return relativePath !== '' && !relativePath.startsWith(`..${path.sep}`) && relativePath !== '..';\n}\n\n/*** Check whether one candidate is a regular file. */\nasync function isFileAsync(candidate: string): Promise<boolean> {\n try {\n return (await fs.stat(candidate)).isFile();\n } catch {\n return false;\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilityCatalogExports.d.ts","sourceRoot":"","sources":["../src/capabilityCatalogExports.ts"],"names":[],"mappings":"AAEA,kFAAkF;AAClF,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,OAAO,EACvB,WAAW,EAAE,MAAM,GAClB,IAAI,CAgBN"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
/*** Resolve and validate the public export for an authored capability catalog. */
|
|
3
|
+
export function capabilityCatalogExports(packageRoot, packageExports, catalogPath) {
|
|
4
|
+
const target = readCapabilityExportTarget(isRecord(packageExports) ? packageExports['./capabilities'] : undefined);
|
|
5
|
+
if (target === null)
|
|
6
|
+
throw new Error('Package exports must define "./capabilities" for its canonical catalog.');
|
|
7
|
+
if (!target.startsWith('./') || !isInsidePackage(packageRoot, path.resolve(packageRoot, target)))
|
|
8
|
+
throw new Error('The public "./capabilities" export must resolve within its package.');
|
|
9
|
+
if (!capabilitySourceCandidatesForExport(target).some((candidate) => path.resolve(packageRoot, candidate) === catalogPath))
|
|
10
|
+
throw new Error('The public "./capabilities" export must resolve to src/capabilities/index.ts.');
|
|
11
|
+
}
|
|
12
|
+
/*** Select one concrete target from a Node package export condition object. */
|
|
13
|
+
function readCapabilityExportTarget(value) {
|
|
14
|
+
if (typeof value === 'string')
|
|
15
|
+
return value;
|
|
16
|
+
if (!isRecord(value))
|
|
17
|
+
return null;
|
|
18
|
+
for (const candidate of [value.import, value.default, value.types]) {
|
|
19
|
+
const target = readCapabilityExportTarget(candidate);
|
|
20
|
+
if (target !== null)
|
|
21
|
+
return target;
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
/*** Map a conventional dist export to authored source candidates. */
|
|
26
|
+
function capabilitySourceCandidatesForExport(target) {
|
|
27
|
+
if (!target.startsWith('./dist/') || !target.endsWith('.js'))
|
|
28
|
+
return [target];
|
|
29
|
+
const stem = target.slice('./dist/'.length, -'.js'.length);
|
|
30
|
+
return [`./src/${stem}.ts`, `./src/${stem}.tsx`, `./src/${stem}.js`, `./src/${stem}.mjs`];
|
|
31
|
+
}
|
|
32
|
+
/*** Check whether a resolved target remains inside the inspected package. */
|
|
33
|
+
function isInsidePackage(packageRoot, candidate) {
|
|
34
|
+
const relativePath = path.relative(packageRoot, candidate);
|
|
35
|
+
return relativePath !== '' && !relativePath.startsWith(`..${path.sep}`) && relativePath !== '..';
|
|
36
|
+
}
|
|
37
|
+
/*** Narrow an unknown value to a plain record. */
|
|
38
|
+
function isRecord(value) {
|
|
39
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=capabilityCatalogExports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilityCatalogExports.js","sourceRoot":"","sources":["../src/capabilityCatalogExports.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,kFAAkF;AAClF,MAAM,UAAU,wBAAwB,CACtC,WAAmB,EACnB,cAAuB,EACvB,WAAmB;IAEnB,MAAM,MAAM,GAAG,0BAA0B,CACvC,QAAQ,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS,CACxE,CAAC;IACF,IAAI,MAAM,KAAK,IAAI;QACjB,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAC;IAC7F,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAC9F,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;IACzF,IACE,CAAC,mCAAmC,CAAC,MAAM,CAAC,CAAC,IAAI,CAC/C,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,KAAK,WAAW,CACpE;QAED,MAAM,IAAI,KAAK,CACb,+EAA+E,CAChF,CAAC;AACN,CAAC;AAED,+EAA+E;AAC/E,SAAS,0BAA0B,CAAC,KAAc;IAChD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,KAAK,MAAM,SAAS,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACnE,MAAM,MAAM,GAAG,0BAA0B,CAAC,SAAS,CAAC,CAAC;QACrD,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,MAAM,CAAC;IACrC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,qEAAqE;AACrE,SAAS,mCAAmC,CAAC,MAAc;IACzD,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9E,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3D,OAAO,CAAC,SAAS,IAAI,KAAK,EAAE,SAAS,IAAI,MAAM,EAAE,SAAS,IAAI,KAAK,EAAE,SAAS,IAAI,MAAM,CAAC,CAAC;AAC5F,CAAC;AAED,6EAA6E;AAC7E,SAAS,eAAe,CAAC,WAAmB,EAAE,SAAiB;IAC7D,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IAC3D,OAAO,YAAY,KAAK,EAAE,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,YAAY,KAAK,IAAI,CAAC;AACnG,CAAC;AAED,kDAAkD;AAClD,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","sourcesContent":["import path from 'node:path';\n\n/*** Resolve and validate the public export for an authored capability catalog. */\nexport function capabilityCatalogExports(\n packageRoot: string,\n packageExports: unknown,\n catalogPath: string,\n): void {\n const target = readCapabilityExportTarget(\n isRecord(packageExports) ? packageExports['./capabilities'] : undefined,\n );\n if (target === null)\n throw new Error('Package exports must define \"./capabilities\" for its canonical catalog.');\n if (!target.startsWith('./') || !isInsidePackage(packageRoot, path.resolve(packageRoot, target)))\n throw new Error('The public \"./capabilities\" export must resolve within its package.');\n if (\n !capabilitySourceCandidatesForExport(target).some(\n (candidate) => path.resolve(packageRoot, candidate) === catalogPath,\n )\n )\n throw new Error(\n 'The public \"./capabilities\" export must resolve to src/capabilities/index.ts.',\n );\n}\n\n/*** Select one concrete target from a Node package export condition object. */\nfunction readCapabilityExportTarget(value: unknown): string | null {\n if (typeof value === 'string') return value;\n if (!isRecord(value)) return null;\n for (const candidate of [value.import, value.default, value.types]) {\n const target = readCapabilityExportTarget(candidate);\n if (target !== null) return target;\n }\n return null;\n}\n\n/*** Map a conventional dist export to authored source candidates. */\nfunction capabilitySourceCandidatesForExport(target: string): readonly string[] {\n if (!target.startsWith('./dist/') || !target.endsWith('.js')) return [target];\n const stem = target.slice('./dist/'.length, -'.js'.length);\n return [`./src/${stem}.ts`, `./src/${stem}.tsx`, `./src/${stem}.js`, `./src/${stem}.mjs`];\n}\n\n/*** Check whether a resolved target remains inside the inspected package. */\nfunction isInsidePackage(packageRoot: string, candidate: string): boolean {\n const relativePath = path.relative(packageRoot, candidate);\n return relativePath !== '' && !relativePath.startsWith(`..${path.sep}`) && relativePath !== '..';\n}\n\n/*** Narrow an unknown value to a plain record. */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n"]}
|
package/dist/diagnostics.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
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
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.callback-target.configured' | '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.setup.supported' | 'manifest.auth.oauth.valid-shape' | 'manifest.auth.valid-shape' | 'manifest.deploy.targets.enabled' | 'manifest.deploy.targets.legacy-web' | 'manifest.deploy.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.command-path.required' | 'package.cli.root-file.disallowed' | 'package.architecture.application-outward-import.disallowed' | 'package.architecture.catch-all-directory.disallowed' | 'package.architecture.feature-ownership.required' | 'package.architecture.domain-outward-import.disallowed' | 'package.architecture.delivery-concrete-adapter-import.disallowed' | 'package.architecture.port-outward-import.disallowed' | 'package.architecture.role-combination.invalid' | 'package.dependencies.ankh-workspace-alias.disallowed' | 'package.dependencies.ankhorage4-source.disallowed' | 'package.dependencies.local-protocol.disallowed' | 'package.dependencies.changesets.required' | 'package.dependencies.devtools.required' | 'package.dependencies.paradox.required' | 'package.dependencies.types-bun.policy' | '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.imports.outside-root.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.policy' | '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.ci.bun-policy' | 'repo.workflows.release.bun-policy' | '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';
|
|
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.callback-target.configured' | '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.setup.supported' | 'manifest.auth.oauth.valid-shape' | 'manifest.auth.valid-shape' | 'manifest.deploy.targets.enabled' | 'manifest.deploy.targets.legacy-web' | 'manifest.deploy.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.capabilities.catalog.valid' | 'package.ankh.capabilities.match-catalog' | 'package.ankh.capabilities.unique' | 'package.ankh.present.valid-shape' | 'package.ankh.provider-path.required' | 'package.ankh.required-for-provider' | 'package.ankh.required-for-capability-catalog' | 'package.cli.export.required' | 'package.cli.command-path.required' | 'package.cli.root-file.disallowed' | 'package.architecture.application-outward-import.disallowed' | 'package.architecture.catch-all-directory.disallowed' | 'package.architecture.feature-ownership.required' | 'package.architecture.domain-outward-import.disallowed' | 'package.architecture.delivery-concrete-adapter-import.disallowed' | 'package.architecture.port-outward-import.disallowed' | 'package.architecture.role-combination.invalid' | 'package.dependencies.ankh-workspace-alias.disallowed' | 'package.dependencies.ankhorage4-source.disallowed' | 'package.dependencies.local-protocol.disallowed' | 'package.dependencies.changesets.required' | 'package.dependencies.devtools.required' | 'package.dependencies.paradox.required' | 'package.dependencies.types-bun.policy' | '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.imports.outside-root.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.policy' | '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.ci.bun-policy' | 'repo.workflows.release.bun-policy' | '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":"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"}
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"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"}
|
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":"AA+IA,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.callback-target.configured'\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.setup.supported'\n | 'manifest.auth.oauth.valid-shape'\n | 'manifest.auth.valid-shape'\n | 'manifest.deploy.targets.enabled'\n | 'manifest.deploy.targets.legacy-web'\n | 'manifest.deploy.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.capabilities.catalog.valid'\n | 'package.ankh.capabilities.match-catalog'\n | 'package.ankh.capabilities.unique'\n | 'package.ankh.present.valid-shape'\n | 'package.ankh.provider-path.required'\n | 'package.ankh.required-for-provider'\n | 'package.ankh.required-for-capability-catalog'\n | 'package.cli.export.required'\n | 'package.cli.command-path.required'\n | 'package.cli.root-file.disallowed'\n | 'package.architecture.application-outward-import.disallowed'\n | 'package.architecture.catch-all-directory.disallowed'\n | 'package.architecture.feature-ownership.required'\n | 'package.architecture.domain-outward-import.disallowed'\n | 'package.architecture.delivery-concrete-adapter-import.disallowed'\n | 'package.architecture.port-outward-import.disallowed'\n | 'package.architecture.role-combination.invalid'\n | 'package.dependencies.ankh-workspace-alias.disallowed'\n | 'package.dependencies.ankhorage4-source.disallowed'\n | 'package.dependencies.local-protocol.disallowed'\n | 'package.dependencies.changesets.required'\n | 'package.dependencies.devtools.required'\n | 'package.dependencies.paradox.required'\n | 'package.dependencies.types-bun.policy'\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.imports.outside-root.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.policy'\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.ci.bun-policy'\n | 'repo.workflows.release.bun-policy'\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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/doctor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Executable Ankh provider and standalone CLI for lightweight repo and package compliance diagnostics.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -140,8 +140,8 @@
|
|
|
140
140
|
},
|
|
141
141
|
"devDependencies": {
|
|
142
142
|
"@ankhorage/ankh": "^0.12.0",
|
|
143
|
-
"@ankhorage/devtools": "^2.
|
|
144
|
-
"@ankhorage/paradox": "^0.
|
|
143
|
+
"@ankhorage/devtools": "^2.2.2",
|
|
144
|
+
"@ankhorage/paradox": "^0.3.1",
|
|
145
145
|
"@types/bun": "^1.4.2",
|
|
146
146
|
"@types/node": "^26.6.4",
|
|
147
147
|
"typescript": "^6.0.3"
|
package/src/analysis.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { REPOSITORY_RULE_METADATA } from '@ankhorage/rules-repository';
|
|
12
12
|
import { uniqueSortedStrings } from '@ankhorage/utility/array';
|
|
13
13
|
|
|
14
|
+
import { readCapabilityCatalogAsync } from './capabilityCatalog.js';
|
|
14
15
|
import type { DoctorDiagnostic, DoctorPolicyProfile, DoctorRuleId } from './diagnostics.js';
|
|
15
16
|
import type { DoctorReadiness } from './readiness.js';
|
|
16
17
|
import { getRepositoryRule } from './utils/getRepositoryRule.js';
|
|
@@ -566,6 +567,7 @@ async function validateAnkhMetadataAndProvider(request: {
|
|
|
566
567
|
}): Promise<DoctorDiagnostic[]> {
|
|
567
568
|
const diagnostics: DoctorDiagnostic[] = [];
|
|
568
569
|
const metadata = request.packageJson.ankh;
|
|
570
|
+
const catalog = await readCapabilityCatalogAsync(request.targetPath, request.packageJson.exports);
|
|
569
571
|
const providerPackage =
|
|
570
572
|
(isRecord(metadata) && isNonEmptyString(metadata.provider)) ||
|
|
571
573
|
(await isProviderPackage(request.targetPath));
|
|
@@ -594,6 +596,18 @@ async function validateAnkhMetadataAndProvider(request: {
|
|
|
594
596
|
}
|
|
595
597
|
|
|
596
598
|
if (metadata === undefined) {
|
|
599
|
+
if (catalog.path !== null) {
|
|
600
|
+
diagnostics.push(
|
|
601
|
+
createDiagnostic({
|
|
602
|
+
code: 'field-missing',
|
|
603
|
+
message: 'A public capability catalog requires package.json.ankh discovery metadata.',
|
|
604
|
+
path: request.packageJsonPath,
|
|
605
|
+
profile: request.profile,
|
|
606
|
+
ruleId: 'package.ankh.required-for-capability-catalog',
|
|
607
|
+
severity: 'error',
|
|
608
|
+
}),
|
|
609
|
+
);
|
|
610
|
+
}
|
|
597
611
|
return diagnostics;
|
|
598
612
|
}
|
|
599
613
|
|
|
@@ -612,6 +626,54 @@ async function validateAnkhMetadataAndProvider(request: {
|
|
|
612
626
|
return diagnostics;
|
|
613
627
|
}
|
|
614
628
|
|
|
629
|
+
if (!isRecord(metadata)) {
|
|
630
|
+
return diagnostics;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const metadataCapabilities = readCapabilities(metadata.capabilities);
|
|
634
|
+
const metadataIdsAreUnique = hasUniqueCapabilityIds(metadataCapabilities);
|
|
635
|
+
if (!metadataIdsAreUnique) {
|
|
636
|
+
diagnostics.push(
|
|
637
|
+
createDiagnostic({
|
|
638
|
+
code: 'field-invalid',
|
|
639
|
+
message: 'package.json.ankh capabilities must not contain duplicate capability ids.',
|
|
640
|
+
path: request.packageJsonPath,
|
|
641
|
+
profile: request.profile,
|
|
642
|
+
ruleId: 'package.ankh.capabilities.unique',
|
|
643
|
+
severity: 'error',
|
|
644
|
+
}),
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (catalog.path !== null && catalog.capabilities === null) {
|
|
649
|
+
diagnostics.push(
|
|
650
|
+
createDiagnostic({
|
|
651
|
+
code: 'field-invalid',
|
|
652
|
+
message: `The public capability catalog is invalid: ${catalog.reason}`,
|
|
653
|
+
path: catalog.path,
|
|
654
|
+
profile: request.profile,
|
|
655
|
+
ruleId: 'package.ankh.capabilities.catalog.valid',
|
|
656
|
+
severity: 'error',
|
|
657
|
+
}),
|
|
658
|
+
);
|
|
659
|
+
} else if (
|
|
660
|
+
catalog.capabilities !== null &&
|
|
661
|
+
metadataIdsAreUnique &&
|
|
662
|
+
!sameCapabilitySet(metadataCapabilities, catalog.capabilities)
|
|
663
|
+
) {
|
|
664
|
+
diagnostics.push(
|
|
665
|
+
createDiagnostic({
|
|
666
|
+
code: 'field-invalid',
|
|
667
|
+
message:
|
|
668
|
+
'package.json.ankh capabilities must match the canonical public CAPABILITIES catalog exactly.',
|
|
669
|
+
path: request.packageJsonPath,
|
|
670
|
+
profile: request.profile,
|
|
671
|
+
ruleId: 'package.ankh.capabilities.match-catalog',
|
|
672
|
+
severity: 'error',
|
|
673
|
+
}),
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
|
|
615
677
|
if (!providerPackage) {
|
|
616
678
|
return diagnostics;
|
|
617
679
|
}
|
|
@@ -645,7 +707,6 @@ async function validateAnkhMetadataAndProvider(request: {
|
|
|
645
707
|
return diagnostics;
|
|
646
708
|
}
|
|
647
709
|
|
|
648
|
-
const metadataCapabilities = readCapabilities(metadata.capabilities);
|
|
649
710
|
const providerCapabilities = inspection.result.capabilities;
|
|
650
711
|
const commandCapabilities = uniqueSortedStrings(inspection.result.commandCapabilities);
|
|
651
712
|
|
|
@@ -664,15 +725,14 @@ async function validateAnkhMetadataAndProvider(request: {
|
|
|
664
725
|
}
|
|
665
726
|
|
|
666
727
|
if (
|
|
667
|
-
!
|
|
668
|
-
providerCapabilities.
|
|
669
|
-
commandCapabilities,
|
|
728
|
+
!commandCapabilities.every((commandCapability) =>
|
|
729
|
+
providerCapabilities.some((capability) => capability.id === commandCapability),
|
|
670
730
|
)
|
|
671
731
|
) {
|
|
672
732
|
diagnostics.push(
|
|
673
733
|
createDiagnostic({
|
|
674
734
|
code: 'field-invalid',
|
|
675
|
-
message: '
|
|
735
|
+
message: 'Every provider command must reference a capability published by the provider.',
|
|
676
736
|
path: inspection.result.sourcePath,
|
|
677
737
|
profile: request.profile,
|
|
678
738
|
ruleId: 'provider.commands.match-capabilities',
|
|
@@ -1209,6 +1269,11 @@ function sameCapabilitySet(left: readonly Capability[], right: readonly Capabili
|
|
|
1209
1269
|
);
|
|
1210
1270
|
}
|
|
1211
1271
|
|
|
1272
|
+
/*** Determine whether every normalized catalog descriptor has a distinct stable identifier. */
|
|
1273
|
+
function hasUniqueCapabilityIds(capabilities: readonly Capability[]): boolean {
|
|
1274
|
+
return new Set(capabilities.map((capability) => capability.id)).size === capabilities.length;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1212
1277
|
function sameStringSet(left: readonly string[], right: readonly string[]): boolean {
|
|
1213
1278
|
const leftSet = uniqueSortedStrings(left);
|
|
1214
1279
|
const rightSet = uniqueSortedStrings(right);
|