@intentius/chant 0.0.4 → 0.0.8
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/README.md +10 -351
- package/bin/chant +20 -0
- package/package.json +18 -17
- package/src/bench.test.ts +3 -54
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/plugin.ts +8 -23
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/validate.ts +22 -18
- package/src/cli/commands/__snapshots__/init-lexicon.test.ts.snap +8 -23
- package/src/cli/commands/build.ts +1 -2
- package/src/cli/commands/import.test.ts +1 -1
- package/src/cli/commands/import.ts +2 -2
- package/src/cli/commands/init-lexicon.test.ts +0 -3
- package/src/cli/commands/init-lexicon.ts +31 -95
- package/src/cli/commands/init.test.ts +10 -14
- package/src/cli/commands/init.ts +16 -10
- package/src/cli/commands/lint.ts +9 -33
- package/src/cli/commands/list.ts +2 -2
- package/src/cli/commands/update.ts +5 -3
- package/src/cli/conflict-check.test.ts +0 -1
- package/src/cli/handlers/dev.ts +1 -9
- package/src/cli/main.ts +14 -4
- package/src/cli/mcp/server.test.ts +207 -4
- package/src/cli/mcp/server.ts +6 -0
- package/src/cli/mcp/tools/explain.ts +134 -0
- package/src/cli/mcp/tools/scaffold.ts +107 -0
- package/src/cli/mcp/tools/search.ts +98 -0
- package/src/codegen/docs-interpolation.test.ts +2 -2
- package/src/codegen/docs.ts +5 -4
- package/src/codegen/generate-registry.test.ts +2 -2
- package/src/codegen/generate-registry.ts +5 -6
- package/src/codegen/generate-typescript.test.ts +6 -6
- package/src/codegen/generate-typescript.ts +2 -6
- package/src/codegen/generate.ts +1 -12
- package/src/codegen/package.ts +28 -1
- package/src/codegen/typecheck.ts +6 -11
- package/src/codegen/validate.ts +16 -0
- package/src/config.ts +4 -0
- package/src/discovery/files.ts +6 -6
- package/src/discovery/import.ts +1 -1
- package/src/index.ts +1 -2
- package/src/lexicon-integrity.ts +5 -4
- package/src/lexicon.ts +2 -6
- package/src/lint/config.ts +8 -6
- package/src/lint/engine.ts +1 -5
- package/src/lint/rule.ts +0 -18
- package/src/lint/rules/evl009-composite-no-constant.test.ts +24 -8
- package/src/lint/rules/evl009-composite-no-constant.ts +50 -29
- package/src/lint/rules/index.ts +1 -22
- package/src/runtime-adapter.ts +158 -0
- package/src/serializer-walker.test.ts +0 -9
- package/src/serializer-walker.ts +1 -3
- package/src/stack-output.ts +3 -3
- package/src/barrel.test.ts +0 -157
- package/src/barrel.ts +0 -101
- package/src/cli/commands/__fixtures__/init-lexicon-output/src/codegen/rollback.ts +0 -45
- package/src/codegen/case.test.ts +0 -30
- package/src/codegen/case.ts +0 -11
- package/src/codegen/rollback.test.ts +0 -92
- package/src/codegen/rollback.ts +0 -115
- package/src/lint/rules/barrel-import-style.test.ts +0 -80
- package/src/lint/rules/barrel-import-style.ts +0 -59
- package/src/lint/rules/enforce-barrel-import.test.ts +0 -169
- package/src/lint/rules/enforce-barrel-import.ts +0 -81
- package/src/lint/rules/enforce-barrel-ref.test.ts +0 -114
- package/src/lint/rules/enforce-barrel-ref.ts +0 -75
- package/src/lint/rules/evl006-barrel-usage.test.ts +0 -63
- package/src/lint/rules/evl006-barrel-usage.ts +0 -95
- package/src/lint/rules/evl008-unresolvable-barrel-ref.test.ts +0 -118
- package/src/lint/rules/evl008-unresolvable-barrel-ref.ts +0 -140
- package/src/lint/rules/prefer-namespace-import.test.ts +0 -102
- package/src/lint/rules/prefer-namespace-import.ts +0 -63
- package/src/lint/rules/stale-barrel-types.ts +0 -60
- package/src/project/scan.test.ts +0 -178
- package/src/project/scan.ts +0 -182
- package/src/project/sync.test.ts +0 -87
- package/src/project/sync.ts +0 -46
|
@@ -8,18 +8,17 @@
|
|
|
8
8
|
|
|
9
9
|
import type { NamingStrategy } from "./naming";
|
|
10
10
|
import { propertyTypeName, extractDefName } from "./naming";
|
|
11
|
-
import { toCamelCase } from "./case";
|
|
12
11
|
import { constraintsIsEmpty, type PropertyConstraints } from "./json-schema";
|
|
13
12
|
|
|
14
13
|
export interface RegistryResource {
|
|
15
14
|
typeName: string;
|
|
16
15
|
attributes: { name: string }[];
|
|
17
16
|
properties: { name: string; constraints: PropertyConstraints }[];
|
|
18
|
-
propertyTypes: { name: string;
|
|
17
|
+
propertyTypes: { name: string; specType: string }[];
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
export interface RegistryConfig<E> {
|
|
22
|
-
/** Short name extractor (e.g.
|
|
21
|
+
/** Short name extractor (e.g. shortName from the spec type). */
|
|
23
22
|
shortName: (typeName: string) => string;
|
|
24
23
|
/** Build a resource entry from parsed data. */
|
|
25
24
|
buildEntry: (
|
|
@@ -47,12 +46,12 @@ export function buildRegistry<E>(
|
|
|
47
46
|
const tsName = naming.resolve(typeName);
|
|
48
47
|
if (!tsName) continue;
|
|
49
48
|
|
|
50
|
-
// Build attrs map:
|
|
49
|
+
// Build attrs map: name → raw name (identity mapping)
|
|
51
50
|
let attrs: Record<string, string> | undefined;
|
|
52
51
|
if (r.attributes.length > 0) {
|
|
53
52
|
attrs = {};
|
|
54
53
|
for (const a of r.attributes) {
|
|
55
|
-
attrs[
|
|
54
|
+
attrs[a.name] = a.name;
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
|
|
@@ -80,7 +79,7 @@ export function buildRegistry<E>(
|
|
|
80
79
|
for (const pt of r.propertyTypes) {
|
|
81
80
|
const defName = extractDefName(pt.name, shortName);
|
|
82
81
|
const ptName = propertyTypeName(tsName, defName);
|
|
83
|
-
const ptEntry = config.buildPropertyEntry(typeName, pt.
|
|
82
|
+
const ptEntry = config.buildPropertyEntry(typeName, pt.specType);
|
|
84
83
|
entries[ptName] = ptEntry;
|
|
85
84
|
|
|
86
85
|
if (ptAliases) {
|
|
@@ -18,8 +18,8 @@ describe("writeResourceClass", () => {
|
|
|
18
18
|
);
|
|
19
19
|
const output = lines.join("\n");
|
|
20
20
|
expect(output).toContain("export declare class Bucket {");
|
|
21
|
-
expect(output).toContain("
|
|
22
|
-
expect(output).toContain("readonly
|
|
21
|
+
expect(output).toContain("BucketName: string;");
|
|
22
|
+
expect(output).toContain("readonly Arn: string;");
|
|
23
23
|
expect(output).toContain("}");
|
|
24
24
|
});
|
|
25
25
|
|
|
@@ -34,7 +34,7 @@ describe("writeResourceClass", () => {
|
|
|
34
34
|
remap,
|
|
35
35
|
);
|
|
36
36
|
const output = lines.join("\n");
|
|
37
|
-
expect(output).toContain("readonly
|
|
37
|
+
expect(output).toContain("readonly Config: BucketConfig;");
|
|
38
38
|
});
|
|
39
39
|
});
|
|
40
40
|
|
|
@@ -48,7 +48,7 @@ describe("writePropertyClass", () => {
|
|
|
48
48
|
);
|
|
49
49
|
const output = lines.join("\n");
|
|
50
50
|
expect(output).toContain("export declare class BucketConfig {");
|
|
51
|
-
expect(output).toContain("
|
|
51
|
+
expect(output).toContain("Enabled?: boolean;");
|
|
52
52
|
expect(output).toContain("}");
|
|
53
53
|
});
|
|
54
54
|
});
|
|
@@ -71,8 +71,8 @@ describe("writeConstructor", () => {
|
|
|
71
71
|
undefined,
|
|
72
72
|
);
|
|
73
73
|
const output = lines.join("\n");
|
|
74
|
-
const reqIdx = output.indexOf("
|
|
75
|
-
const optIdx = output.indexOf("
|
|
74
|
+
const reqIdx = output.indexOf("Required:");
|
|
75
|
+
const optIdx = output.indexOf("Optional?:");
|
|
76
76
|
expect(reqIdx).toBeLessThan(optIdx);
|
|
77
77
|
});
|
|
78
78
|
|
|
@@ -51,7 +51,7 @@ export function writeResourceClass(
|
|
|
51
51
|
const attrs = [...attributes].sort((a, b) => a.name.localeCompare(b.name));
|
|
52
52
|
for (const a of attrs) {
|
|
53
53
|
const attrType = resolveConstructorType(a.type, remap);
|
|
54
|
-
lines.push(` readonly ${
|
|
54
|
+
lines.push(` readonly ${a.name}: ${attrType};`);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
lines.push("}");
|
|
@@ -98,7 +98,7 @@ export function writeConstructor(
|
|
|
98
98
|
if (p.description) {
|
|
99
99
|
lines.push(` /** ${p.description} */`);
|
|
100
100
|
}
|
|
101
|
-
lines.push(` ${
|
|
101
|
+
lines.push(` ${p.name}${optional}: ${tsType};`);
|
|
102
102
|
}
|
|
103
103
|
lines.push(" });");
|
|
104
104
|
}
|
|
@@ -155,7 +155,3 @@ export function resolveConstructorType(tsType: string, remap: Map<string, string
|
|
|
155
155
|
|
|
156
156
|
return tsType;
|
|
157
157
|
}
|
|
158
|
-
|
|
159
|
-
function toCamelCase(name: string): string {
|
|
160
|
-
return name.charAt(0).toLowerCase() + name.slice(1);
|
|
161
|
-
}
|
package/src/codegen/generate.ts
CHANGED
|
@@ -180,26 +180,15 @@ export interface WriteConfig {
|
|
|
180
180
|
generatedSubdir?: string;
|
|
181
181
|
/** Map of filename → content to write. */
|
|
182
182
|
files: Record<string, string>;
|
|
183
|
-
/** Optional snapshot function called before overwriting. */
|
|
184
|
-
snapshot?: (generatedDir: string) => void;
|
|
185
183
|
}
|
|
186
184
|
|
|
187
185
|
/**
|
|
188
|
-
* Write generated artifacts to disk
|
|
186
|
+
* Write generated artifacts to disk.
|
|
189
187
|
*/
|
|
190
188
|
export function writeGeneratedArtifacts(config: WriteConfig): void {
|
|
191
189
|
const generatedDir = join(config.baseDir, config.generatedSubdir ?? "src/generated");
|
|
192
190
|
mkdirSync(generatedDir, { recursive: true });
|
|
193
191
|
|
|
194
|
-
// Auto-snapshot before overwriting
|
|
195
|
-
if (config.snapshot) {
|
|
196
|
-
try {
|
|
197
|
-
config.snapshot(generatedDir);
|
|
198
|
-
} catch {
|
|
199
|
-
// Best effort — don't fail generation if snapshot fails
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
192
|
for (const [filename, content] of Object.entries(config.files)) {
|
|
204
193
|
writeFileSync(join(generatedDir, filename), content);
|
|
205
194
|
}
|
package/src/codegen/package.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* assemble BundleSpec → compute integrity → attach metadata.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { readFileSync, readdirSync } from "fs";
|
|
8
|
+
import { readFileSync, readdirSync, writeFileSync, mkdirSync } from "fs";
|
|
9
9
|
import { join } from "path";
|
|
10
10
|
import type { BundleSpec, LexiconManifest } from "../lexicon";
|
|
11
11
|
import { computeIntegrity } from "../lexicon-integrity";
|
|
@@ -147,6 +147,33 @@ export function collectRules(
|
|
|
147
147
|
return rules;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Write a BundleSpec to the given dist directory.
|
|
152
|
+
*
|
|
153
|
+
* Creates the directory structure and writes all artifacts:
|
|
154
|
+
* manifest.json, meta.json, types/index.d.ts, rules/*, skills/*, integrity.json.
|
|
155
|
+
*/
|
|
156
|
+
export function writeBundleSpec(spec: BundleSpec, distDir: string): void {
|
|
157
|
+
mkdirSync(join(distDir, "types"), { recursive: true });
|
|
158
|
+
mkdirSync(join(distDir, "rules"), { recursive: true });
|
|
159
|
+
mkdirSync(join(distDir, "skills"), { recursive: true });
|
|
160
|
+
|
|
161
|
+
writeFileSync(join(distDir, "manifest.json"), JSON.stringify(spec.manifest, null, 2));
|
|
162
|
+
writeFileSync(join(distDir, "meta.json"), spec.registry);
|
|
163
|
+
writeFileSync(join(distDir, "types", "index.d.ts"), spec.typesDTS);
|
|
164
|
+
|
|
165
|
+
for (const [name, content] of spec.rules) {
|
|
166
|
+
writeFileSync(join(distDir, "rules", name), content);
|
|
167
|
+
}
|
|
168
|
+
for (const [name, content] of spec.skills) {
|
|
169
|
+
writeFileSync(join(distDir, "skills", name), content);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (spec.integrity) {
|
|
173
|
+
writeFileSync(join(distDir, "integrity.json"), JSON.stringify(spec.integrity, null, 2));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
150
177
|
/**
|
|
151
178
|
* Collect skills from a plugin's skill definitions.
|
|
152
179
|
*/
|
package/src/codegen/typecheck.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { writeFileSync, mkdirSync, rmSync } from "fs";
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { tmpdir } from "os";
|
|
7
|
+
import { getRuntime } from "../runtime-adapter";
|
|
7
8
|
|
|
8
9
|
export interface TypeCheckResult {
|
|
9
10
|
ok: boolean;
|
|
@@ -39,17 +40,11 @@ export async function typecheckDTS(content: string): Promise<TypeCheckResult> {
|
|
|
39
40
|
writeFileSync(join(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
|
|
40
41
|
|
|
41
42
|
// Run tsc
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const [stdout, stderr] = await Promise.all([
|
|
49
|
-
new Response(proc.stdout).text(),
|
|
50
|
-
new Response(proc.stderr).text(),
|
|
51
|
-
]);
|
|
52
|
-
const exitCode = await proc.exited;
|
|
43
|
+
const rt = getRuntime();
|
|
44
|
+
const { stdout, stderr, exitCode } = await rt.spawn(
|
|
45
|
+
[rt.commands.exec, "tsc", "--noEmit", "--project", "tsconfig.json"],
|
|
46
|
+
{ cwd: dir },
|
|
47
|
+
);
|
|
53
48
|
|
|
54
49
|
// Parse diagnostics from stdout (tsc writes errors to stdout)
|
|
55
50
|
const output = stdout + stderr;
|
package/src/codegen/validate.ts
CHANGED
|
@@ -141,3 +141,19 @@ export async function validateLexiconArtifacts(config: LexiconValidationConfig):
|
|
|
141
141
|
checks,
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Print validation results to stderr and throw on failure.
|
|
147
|
+
*/
|
|
148
|
+
export function printValidationResult(result: ValidateResult): void {
|
|
149
|
+
for (const check of result.checks) {
|
|
150
|
+
const status = check.ok ? "OK" : "FAIL";
|
|
151
|
+
const msg = check.error ? ` — ${check.error}` : "";
|
|
152
|
+
console.error(` [${status}] ${check.name}${msg}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!result.success) {
|
|
156
|
+
throw new Error("Validation failed");
|
|
157
|
+
}
|
|
158
|
+
console.error("All validation checks passed.");
|
|
159
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { LintConfig } from "./lint/config";
|
|
|
7
7
|
* Zod schema for ChantConfig validation.
|
|
8
8
|
*/
|
|
9
9
|
export const ChantConfigSchema = z.object({
|
|
10
|
+
runtime: z.enum(["bun", "node"]).optional(),
|
|
10
11
|
lexicons: z.array(z.string().min(1)).optional(),
|
|
11
12
|
lint: z.record(z.string(), z.unknown()).optional(),
|
|
12
13
|
}).passthrough();
|
|
@@ -17,6 +18,9 @@ export const ChantConfigSchema = z.object({
|
|
|
17
18
|
* Loaded from `chant.config.ts` (preferred) or `chant.config.json`.
|
|
18
19
|
*/
|
|
19
20
|
export interface ChantConfig {
|
|
21
|
+
/** JS runtime to use for spawned commands: "bun" (default) or "node" */
|
|
22
|
+
runtime?: "bun" | "node";
|
|
23
|
+
|
|
20
24
|
/** Lexicon package names to load (e.g. ["aws"]) */
|
|
21
25
|
lexicons?: string[];
|
|
22
26
|
|
package/src/discovery/files.ts
CHANGED
|
@@ -30,12 +30,12 @@ export async function findInfraFiles(path: string): Promise<string[]> {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
if (entry.isDirectory()) {
|
|
33
|
-
// Child project boundary — a directory with its own
|
|
34
|
-
// separate scope, but only if we've already found the project's
|
|
35
|
-
// source root
|
|
36
|
-
//
|
|
37
|
-
const
|
|
38
|
-
if (existsSync(
|
|
33
|
+
// Child project boundary — a directory with its own chant.config.ts
|
|
34
|
+
// is a separate scope, but only if we've already found the project's
|
|
35
|
+
// own source root. The first config directory is the source root, not
|
|
36
|
+
// a child project.
|
|
37
|
+
const configPath = join(fullPath, "chant.config.ts");
|
|
38
|
+
if (existsSync(configPath)) {
|
|
39
39
|
if (sourceRoot === null) {
|
|
40
40
|
sourceRoot = fullPath;
|
|
41
41
|
} else {
|
package/src/discovery/import.ts
CHANGED
|
@@ -11,7 +11,7 @@ export async function importModule(
|
|
|
11
11
|
path: string
|
|
12
12
|
): Promise<Record<string, unknown>> {
|
|
13
13
|
try {
|
|
14
|
-
return
|
|
14
|
+
return await import(path);
|
|
15
15
|
} catch (error) {
|
|
16
16
|
const message =
|
|
17
17
|
error instanceof Error ? error.message : "Unknown import error";
|
package/src/index.ts
CHANGED
|
@@ -42,17 +42,16 @@ export * from "./lexicon-schema";
|
|
|
42
42
|
export * from "./config";
|
|
43
43
|
export * from "./validation";
|
|
44
44
|
export * from "./project-validation";
|
|
45
|
-
export { barrel } from "./barrel";
|
|
46
45
|
export * from "./codegen/naming";
|
|
47
46
|
export * from "./codegen/fetch";
|
|
48
47
|
export * from "./codegen/generate";
|
|
49
48
|
export * from "./codegen/package";
|
|
50
49
|
export * from "./codegen/typecheck";
|
|
51
|
-
export * from "./codegen/rollback";
|
|
52
50
|
export * from "./codegen/coverage";
|
|
53
51
|
export * from "./codegen/validate";
|
|
54
52
|
export * from "./codegen/docs";
|
|
55
53
|
export * from "./runtime";
|
|
54
|
+
export * from "./runtime-adapter";
|
|
56
55
|
export * from "./stack-output";
|
|
57
56
|
export * from "./child-project";
|
|
58
57
|
export * from "./lsp/types";
|
package/src/lexicon-integrity.ts
CHANGED
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
* Content hashing and integrity verification for lexicon artifacts.
|
|
3
3
|
*/
|
|
4
4
|
import type { BundleSpec } from "./lexicon";
|
|
5
|
+
import { getRuntime } from "./runtime-adapter";
|
|
5
6
|
|
|
6
7
|
export interface ArtifactIntegrity {
|
|
7
|
-
algorithm:
|
|
8
|
+
algorithm: string;
|
|
8
9
|
artifacts: Record<string, string>;
|
|
9
10
|
composite: string;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
|
-
* Hash a single artifact's content using
|
|
14
|
+
* Hash a single artifact's content using the runtime's hash algorithm.
|
|
14
15
|
*/
|
|
15
16
|
export function hashArtifact(content: string): string {
|
|
16
|
-
return
|
|
17
|
+
return getRuntime().hash(content);
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -39,7 +40,7 @@ export function computeIntegrity(spec: BundleSpec): ArtifactIntegrity {
|
|
|
39
40
|
const compositeInput = sorted.map(([k, v]) => `${k}:${v}`).join("\n");
|
|
40
41
|
const composite = hashArtifact(compositeInput);
|
|
41
42
|
|
|
42
|
-
return { algorithm:
|
|
43
|
+
return { algorithm: getRuntime().hashAlgorithm, artifacts, composite };
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/**
|
package/src/lexicon.ts
CHANGED
|
@@ -101,7 +101,7 @@ export interface IntrinsicDef {
|
|
|
101
101
|
* Plugin interface for lexicon packages.
|
|
102
102
|
*
|
|
103
103
|
* Required lifecycle methods enforce consistency: every lexicon must support
|
|
104
|
-
* generate, validate, coverage,
|
|
104
|
+
* generate, validate, coverage, and package operations.
|
|
105
105
|
*/
|
|
106
106
|
export interface LexiconPlugin {
|
|
107
107
|
// ── Required ──────────────────────────────────────────────
|
|
@@ -123,9 +123,6 @@ export interface LexiconPlugin {
|
|
|
123
123
|
/** Package lexicon into distributable tarball */
|
|
124
124
|
package(options?: { verbose?: boolean; force?: boolean }): Promise<void>;
|
|
125
125
|
|
|
126
|
-
/** List or restore generation snapshots */
|
|
127
|
-
rollback(options?: { restore?: string; verbose?: boolean }): Promise<void>;
|
|
128
|
-
|
|
129
126
|
// ── Optional extensions ───────────────────────────────────
|
|
130
127
|
/** Return lint rules provided by this lexicon */
|
|
131
128
|
lintRules?(): LintRule[];
|
|
@@ -206,7 +203,6 @@ export function isLexiconPlugin(value: unknown): value is LexiconPlugin {
|
|
|
206
203
|
typeof obj.generate === "function" &&
|
|
207
204
|
typeof obj.validate === "function" &&
|
|
208
205
|
typeof obj.coverage === "function" &&
|
|
209
|
-
typeof obj.package === "function"
|
|
210
|
-
typeof obj.rollback === "function"
|
|
206
|
+
typeof obj.package === "function"
|
|
211
207
|
);
|
|
212
208
|
}
|
package/src/lint/config.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { readFileSync, existsSync } from "fs";
|
|
2
2
|
import { join, dirname, resolve } from "path";
|
|
3
|
+
import { createRequire } from "module";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import type { Severity, RuleConfig } from "./rule";
|
|
6
|
+
import { moduleDir, getRuntime } from "../runtime-adapter";
|
|
5
7
|
import strictPreset from "./presets/strict.json";
|
|
6
8
|
|
|
7
9
|
/** Mapping of built-in preset names to their file paths */
|
|
8
10
|
const BUILTIN_PRESETS: Record<string, string> = {
|
|
9
|
-
"@intentius/chant/lint/presets/strict": resolve(import.meta.
|
|
10
|
-
"@intentius/chant/lint/presets/relaxed": resolve(import.meta.
|
|
11
|
+
"@intentius/chant/lint/presets/strict": resolve(moduleDir(import.meta.url), "presets/strict.json"),
|
|
12
|
+
"@intentius/chant/lint/presets/relaxed": resolve(moduleDir(import.meta.url), "presets/relaxed.json"),
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
// ── Zod schemas for lint config validation ─────────────────────────
|
|
@@ -307,11 +309,12 @@ function loadConfigFile(configPath: string, visited: Set<string> = new Set()): L
|
|
|
307
309
|
* @returns Loaded and merged configuration, or default config if not found
|
|
308
310
|
*/
|
|
309
311
|
export function loadConfig(dir: string): LintConfig {
|
|
310
|
-
// Try chant.config.ts first — Bun
|
|
312
|
+
// Try chant.config.ts first — Bun has native require() for .ts, Node uses tsx's loader
|
|
311
313
|
const tsConfigPath = join(dir, "chant.config.ts");
|
|
312
314
|
if (existsSync(tsConfigPath)) {
|
|
313
315
|
try {
|
|
314
|
-
const
|
|
316
|
+
const _require = createRequire(join(dir, "package.json"));
|
|
317
|
+
const mod = _require(tsConfigPath);
|
|
315
318
|
const config = mod.default ?? mod.config ?? mod;
|
|
316
319
|
if (typeof config === "object" && config !== null) {
|
|
317
320
|
// ChantConfig format: extract lint property
|
|
@@ -362,8 +365,7 @@ export function resolveRulesForFile(config: LintConfig, filePath: string): Recor
|
|
|
362
365
|
|
|
363
366
|
for (const override of config.overrides) {
|
|
364
367
|
const matches = override.files.some((pattern) => {
|
|
365
|
-
|
|
366
|
-
return glob.match(filePath);
|
|
368
|
+
return getRuntime().globMatch(pattern, filePath);
|
|
367
369
|
});
|
|
368
370
|
|
|
369
371
|
if (matches) {
|
package/src/lint/engine.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LintRule, LintDiagnostic, LintContext
|
|
1
|
+
import type { LintRule, LintDiagnostic, LintContext } from "./rule";
|
|
2
2
|
import { parseFile } from "./parser";
|
|
3
3
|
import { readFileSync } from "fs";
|
|
4
4
|
|
|
@@ -120,7 +120,6 @@ export async function runLint(
|
|
|
120
120
|
files: string[],
|
|
121
121
|
rules: LintRule[],
|
|
122
122
|
ruleOptions?: Map<string, Record<string, unknown>>,
|
|
123
|
-
runOptions?: LintRunOptions,
|
|
124
123
|
): Promise<LintDiagnostic[]> {
|
|
125
124
|
const allDiagnostics: LintDiagnostic[] = [];
|
|
126
125
|
const allRuleIds = new Set(rules.map((r) => r.id));
|
|
@@ -140,9 +139,6 @@ export async function runLint(
|
|
|
140
139
|
entities: [],
|
|
141
140
|
filePath,
|
|
142
141
|
lexicon: undefined,
|
|
143
|
-
barrelExports: runOptions?.barrelExports,
|
|
144
|
-
projectExports: runOptions?.projectExports,
|
|
145
|
-
projectScan: runOptions?.projectScan,
|
|
146
142
|
};
|
|
147
143
|
|
|
148
144
|
// Execute each rule
|
package/src/lint/rule.ts
CHANGED
|
@@ -56,24 +56,6 @@ export interface LintContext {
|
|
|
56
56
|
filePath: string;
|
|
57
57
|
/** Optional lexicon context (undefined for core rules) */
|
|
58
58
|
lexicon?: string;
|
|
59
|
-
/** Export names from the barrel file (for EVL008) */
|
|
60
|
-
barrelExports?: Set<string>;
|
|
61
|
-
/** All project exports keyed by name (for EVL008) */
|
|
62
|
-
projectExports?: Map<string, { file: string; className: string }>;
|
|
63
|
-
/** Project scan result (for COR016) */
|
|
64
|
-
projectScan?: import("../project/scan").ProjectScan;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Options for extending the lint context with project-level information
|
|
69
|
-
*/
|
|
70
|
-
export interface LintRunOptions {
|
|
71
|
-
/** Export names from the barrel file */
|
|
72
|
-
barrelExports?: Set<string>;
|
|
73
|
-
/** All project exports keyed by name */
|
|
74
|
-
projectExports?: Map<string, { file: string; className: string }>;
|
|
75
|
-
/** Project scan result (for COR016) */
|
|
76
|
-
projectScan?: import("../project/scan").ProjectScan;
|
|
77
59
|
}
|
|
78
60
|
|
|
79
61
|
/**
|
|
@@ -31,7 +31,7 @@ describe("EVL009: composite-no-constant", () => {
|
|
|
31
31
|
expect(diags).toHaveLength(1);
|
|
32
32
|
expect(diags[0].ruleId).toBe("EVL009");
|
|
33
33
|
expect(diags[0].message).toContain("assumeRolePolicyDocument");
|
|
34
|
-
expect(diags[0].message).toContain("
|
|
34
|
+
expect(diags[0].message).toContain("import directly");
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
test("flags inline array with objects that doesn't reference props", () => {
|
|
@@ -61,11 +61,25 @@ describe("EVL009: composite-no-constant", () => {
|
|
|
61
61
|
expect(diags[0].message).toContain("policies");
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
test("allows
|
|
64
|
+
test("allows imported refs (direct import)", () => {
|
|
65
65
|
const ctx = createContext(`
|
|
66
|
-
|
|
66
|
+
import { lambdaTrustPolicy } from "./defaults";
|
|
67
|
+
const MyComp = Composite((props) => {
|
|
68
|
+
const role = new Role({
|
|
69
|
+
assumeRolePolicyDocument: lambdaTrustPolicy,
|
|
70
|
+
});
|
|
71
|
+
return { role };
|
|
72
|
+
}, "MyComp");
|
|
73
|
+
`);
|
|
74
|
+
expect(evl009CompositeNoConstantRule.check(ctx)).toHaveLength(0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("allows namespace import refs", () => {
|
|
78
|
+
const ctx = createContext(`
|
|
79
|
+
import * as defaults from "./defaults";
|
|
80
|
+
const MyComp = Composite((props) => {
|
|
67
81
|
const role = new Role({
|
|
68
|
-
assumeRolePolicyDocument:
|
|
82
|
+
assumeRolePolicyDocument: defaults.lambdaTrustPolicy,
|
|
69
83
|
});
|
|
70
84
|
return { role };
|
|
71
85
|
}, "MyComp");
|
|
@@ -99,8 +113,9 @@ describe("EVL009: composite-no-constant", () => {
|
|
|
99
113
|
|
|
100
114
|
test("allows sibling member reference", () => {
|
|
101
115
|
const ctx = createContext(`
|
|
116
|
+
import { trustPolicy } from "./defaults";
|
|
102
117
|
const MyComp = Composite((props) => {
|
|
103
|
-
const role = new Role({ assumeRolePolicyDocument:
|
|
118
|
+
const role = new Role({ assumeRolePolicyDocument: trustPolicy });
|
|
104
119
|
const func = new Function({
|
|
105
120
|
config: { roleArn: role.arn },
|
|
106
121
|
});
|
|
@@ -146,16 +161,17 @@ describe("EVL009: composite-no-constant", () => {
|
|
|
146
161
|
expect(diags).toHaveLength(2);
|
|
147
162
|
});
|
|
148
163
|
|
|
149
|
-
test("allows array wrapping
|
|
164
|
+
test("allows array wrapping imported ref", () => {
|
|
150
165
|
const ctx = createContext(`
|
|
166
|
+
import { lambdaBasicExecutionArn } from "./defaults";
|
|
151
167
|
const MyComp = Composite((props) => {
|
|
152
168
|
const role = new Role({
|
|
153
|
-
managedPolicyArns: [
|
|
169
|
+
managedPolicyArns: [lambdaBasicExecutionArn],
|
|
154
170
|
});
|
|
155
171
|
return { role };
|
|
156
172
|
}, "MyComp");
|
|
157
173
|
`);
|
|
158
|
-
// Array with
|
|
174
|
+
// Array with imported ref inside — not flagged
|
|
159
175
|
// Also it's an array of identifiers, no objects inside
|
|
160
176
|
expect(evl009CompositeNoConstantRule.check(ctx)).toHaveLength(0);
|
|
161
177
|
});
|