@shivaedev/quality 0.27.0 → 0.27.1
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 +11 -0
- package/README.md +6 -1
- package/dist/biome/run.d.ts.map +1 -1
- package/dist/biome/run.js +15 -2
- package/dist/biome/run.js.map +1 -1
- package/dist/cli/args.d.ts +3 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +3 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/messages.d.ts +1 -1
- package/dist/cli/messages.d.ts.map +1 -1
- package/dist/cli/messages.js +3 -1
- package/dist/cli/messages.js.map +1 -1
- package/dist/cli/program.d.ts.map +1 -1
- package/dist/cli/program.js +3 -0
- package/dist/cli/program.js.map +1 -1
- package/dist/hooks/install.d.ts.map +1 -1
- package/dist/hooks/install.js +9 -8
- package/dist/hooks/install.js.map +1 -1
- package/dist/hooks/shim.d.ts.map +1 -1
- package/dist/hooks/shim.js +11 -1
- package/dist/hooks/shim.js.map +1 -1
- package/dist/hooks/status.d.ts +6 -0
- package/dist/hooks/status.d.ts.map +1 -0
- package/dist/hooks/status.js +45 -0
- package/dist/hooks/status.js.map +1 -0
- package/dist/hooks/uninstall.d.ts.map +1 -1
- package/dist/hooks/uninstall.js +6 -0
- package/dist/hooks/uninstall.js.map +1 -1
- package/dist/inventory/collect.d.ts.map +1 -1
- package/dist/inventory/collect.js +3 -0
- package/dist/inventory/collect.js.map +1 -1
- package/dist/inventory/shadcnAlias.d.ts +2 -0
- package/dist/inventory/shadcnAlias.d.ts.map +1 -0
- package/dist/inventory/shadcnAlias.js +73 -0
- package/dist/inventory/shadcnAlias.js.map +1 -0
- package/dist/inventory/shadcnFolders.d.ts +4 -0
- package/dist/inventory/shadcnFolders.d.ts.map +1 -0
- package/dist/inventory/shadcnFolders.js +25 -0
- package/dist/inventory/shadcnFolders.js.map +1 -0
- package/dist/naming/migrationFile.d.ts +4 -0
- package/dist/naming/migrationFile.d.ts.map +1 -0
- package/dist/naming/migrationFile.js +8 -0
- package/dist/naming/migrationFile.js.map +1 -0
- package/dist/rules/files/folderNames.d.ts.map +1 -1
- package/dist/rules/files/folderNames.js +1 -2
- package/dist/rules/files/folderNames.js.map +1 -1
- package/dist/rules/files/namedAfterExport.d.ts.map +1 -1
- package/dist/rules/files/namedAfterExport.js +4 -1
- package/dist/rules/files/namedAfterExport.js.map +1 -1
- package/dist/rules/files/otherNames.js +1 -1
- package/dist/rules/files/otherNames.js.map +1 -1
- package/docs/debt-and-hooks.md +4 -2
- package/package.json +5 -2
- package/src/biome/run.ts +16 -2
- package/src/cli/args.ts +4 -0
- package/src/cli/messages.ts +3 -1
- package/src/cli/program.ts +3 -0
- package/src/hooks/install.ts +11 -12
- package/src/hooks/shim.ts +11 -1
- package/src/hooks/status.ts +50 -0
- package/src/hooks/uninstall.ts +6 -0
- package/src/inventory/collect.ts +3 -0
- package/src/inventory/shadcnAlias.ts +88 -0
- package/src/inventory/shadcnFolders.ts +30 -0
- package/src/naming/migrationFile.ts +9 -0
- package/src/rules/files/folderNames.ts +1 -3
- package/src/rules/files/namedAfterExport.ts +4 -1
- package/src/rules/files/otherNames.ts +1 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
|
|
4
|
+
const PROBE = "__quality_shadcn__.tsx";
|
|
5
|
+
|
|
6
|
+
function capture(pattern: string, alias: string): string | undefined {
|
|
7
|
+
const star = pattern.indexOf("*");
|
|
8
|
+
if (star === -1) {
|
|
9
|
+
return pattern === alias ? "" : undefined;
|
|
10
|
+
}
|
|
11
|
+
const prefix = pattern.slice(0, star);
|
|
12
|
+
const suffix = pattern.slice(star + 1);
|
|
13
|
+
return alias.startsWith(prefix) && alias.endsWith(suffix)
|
|
14
|
+
? alias.slice(prefix.length, suffix.length === 0 ? undefined : -suffix.length)
|
|
15
|
+
: undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function pathTarget(alias: string, options: ts.CompilerOptions, directory: string): string | undefined {
|
|
19
|
+
const pathsBase = Reflect.get(options, "pathsBasePath");
|
|
20
|
+
const base = options.baseUrl ?? (typeof pathsBase === "string" ? pathsBase : directory);
|
|
21
|
+
const patterns = Object.entries(options.paths ?? {}).toSorted(([left], [right]) => right.indexOf("*") - left.indexOf("*"));
|
|
22
|
+
const exact = options.paths?.[alias]?.[0];
|
|
23
|
+
if (exact !== undefined) {
|
|
24
|
+
return resolve(base, exact);
|
|
25
|
+
}
|
|
26
|
+
for (const [pattern, targets] of patterns) {
|
|
27
|
+
const captured = capture(pattern, alias);
|
|
28
|
+
const target = targets[0];
|
|
29
|
+
if (captured !== undefined && target !== undefined) {
|
|
30
|
+
return resolve(base, target.replace("*", captured));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function shadcnAlias(directory: string, alias: string): string {
|
|
37
|
+
if (alias.startsWith(".") || isAbsolute(alias)) {
|
|
38
|
+
return resolve(directory, alias);
|
|
39
|
+
}
|
|
40
|
+
const config = ts.findConfigFile(directory, ts.sys.fileExists);
|
|
41
|
+
const parsed =
|
|
42
|
+
config === undefined
|
|
43
|
+
? undefined
|
|
44
|
+
: ts.getParsedCommandLineOfConfigFile(
|
|
45
|
+
config,
|
|
46
|
+
{},
|
|
47
|
+
{
|
|
48
|
+
...ts.sys,
|
|
49
|
+
onUnRecoverableConfigFileDiagnostic: () => undefined,
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
if (parsed?.errors.length) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
ts.formatDiagnostics(parsed.errors, {
|
|
55
|
+
getCanonicalFileName: (file) => file,
|
|
56
|
+
getCurrentDirectory: () => directory,
|
|
57
|
+
getNewLine: () => "\n",
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const options = parsed?.options ?? {};
|
|
62
|
+
const target = pathTarget(alias, options, config === undefined ? directory : dirname(config));
|
|
63
|
+
if (target !== undefined) {
|
|
64
|
+
return target;
|
|
65
|
+
}
|
|
66
|
+
if (!alias.startsWith("#")) {
|
|
67
|
+
throw new Error(`Cannot resolve shadcn UI alias "${alias}" from ${directory}. Check tsconfig paths.`);
|
|
68
|
+
}
|
|
69
|
+
// A synthetic child lets TypeScript resolve directory aliases through package imports without requiring a stock component to exist.
|
|
70
|
+
const resolved = ts.resolveModuleName(
|
|
71
|
+
`${alias}/${PROBE}`,
|
|
72
|
+
join(directory, "components.tsx"),
|
|
73
|
+
{
|
|
74
|
+
...options,
|
|
75
|
+
customConditions: ["source", ...(options.customConditions ?? [])],
|
|
76
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
...ts.sys,
|
|
80
|
+
directoryExists: () => true,
|
|
81
|
+
fileExists: (file) => file.endsWith(`/${PROBE}`) || ts.sys.fileExists(file),
|
|
82
|
+
},
|
|
83
|
+
).resolvedModule;
|
|
84
|
+
if (resolved === undefined) {
|
|
85
|
+
throw new Error(`Cannot resolve shadcn UI alias "${alias}" from ${directory}. Check tsconfig paths or package imports.`);
|
|
86
|
+
}
|
|
87
|
+
return dirname(resolved.resolvedFileName);
|
|
88
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { basename, dirname, relative } from "node:path";
|
|
2
|
+
import { Effect, type FileSystem, Schema } from "effect";
|
|
3
|
+
import { FilesystemFailure, readRequiredText } from "./filesystem.ts";
|
|
4
|
+
import { posix } from "./ignoreScope.ts";
|
|
5
|
+
import { shadcnAlias } from "./shadcnAlias.ts";
|
|
6
|
+
import { walk } from "./walk.ts";
|
|
7
|
+
|
|
8
|
+
const Components = Schema.fromJsonString(Schema.Struct({ aliases: Schema.Struct({ ui: Schema.NonEmptyString }) }));
|
|
9
|
+
|
|
10
|
+
export const shadcnFolders = Effect.fn("Inventory.shadcnFolders")(function* (
|
|
11
|
+
root: string,
|
|
12
|
+
): Effect.fn.Return<readonly string[], FilesystemFailure, FileSystem.FileSystem> {
|
|
13
|
+
const paths = (yield* walk(root)).filter((path) => basename(path) === "components.json");
|
|
14
|
+
return yield* Effect.forEach(paths, (path) =>
|
|
15
|
+
Effect.gen(function* () {
|
|
16
|
+
const text = yield* readRequiredText(path);
|
|
17
|
+
return yield* Effect.try({
|
|
18
|
+
catch: (cause) => new FilesystemFailure({ message: `cannot resolve ${path}: ${String(cause)}`, path }),
|
|
19
|
+
try: () => {
|
|
20
|
+
const { aliases } = Schema.decodeUnknownSync(Components)(text);
|
|
21
|
+
const folder = posix(relative(root, shadcnAlias(dirname(path), aliases.ui)));
|
|
22
|
+
if (folder === "" || folder === ".." || folder.startsWith("../")) {
|
|
23
|
+
throw new Error("the shadcn UI folder must be inside the repository and cannot be its root");
|
|
24
|
+
}
|
|
25
|
+
return folder;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
|
|
3
|
+
export const MIGRATIONS_DIRECTORY = "src/server/migrations";
|
|
4
|
+
export const MIGRATION_FILE_PATTERN = /^[0-9]{4}_[a-z][a-z0-9]*(?:_[a-z0-9]+)*\.ts$/u;
|
|
5
|
+
|
|
6
|
+
export function isMigrationFile(path: string): boolean {
|
|
7
|
+
const directory = posix.dirname(path);
|
|
8
|
+
return (directory === MIGRATIONS_DIRECTORY || directory.endsWith(`/${MIGRATIONS_DIRECTORY}`)) && MIGRATION_FILE_PATTERN.test(posix.basename(path));
|
|
9
|
+
}
|
|
@@ -5,15 +5,13 @@ import { packageLayout } from "#naming/packageLayout.ts";
|
|
|
5
5
|
import { KEBAB, PASCAL } from "#naming/words.ts";
|
|
6
6
|
import { defineRule, type Finding } from "#rule.ts";
|
|
7
7
|
|
|
8
|
-
const DEFAULT_TOOL_OWNED: readonly string[] = ["migrations/"];
|
|
9
|
-
|
|
10
8
|
const SNAKE = /^[a-z0-9]+(?:_[a-z0-9]+)*$/u;
|
|
11
9
|
|
|
12
10
|
const CODE = /\.[cm]?[jt]sx?$/u;
|
|
13
11
|
|
|
14
12
|
const FolderNamesOptions = Schema.Struct({
|
|
15
13
|
content: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
|
|
16
|
-
toolOwned: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed(
|
|
14
|
+
toolOwned: Schema.Array(Schema.String).pipe(Schema.withDecodingDefaultKey(Effect.succeed([]))),
|
|
17
15
|
});
|
|
18
16
|
|
|
19
17
|
function foldersIn(files: readonly string[]): readonly string[] {
|
|
@@ -3,6 +3,7 @@ import { Effect, Schema } from "effect";
|
|
|
3
3
|
import ignore from "ignore";
|
|
4
4
|
import { exportFits } from "#naming/exportFits.ts";
|
|
5
5
|
import { mainExport } from "#naming/mainExport.ts";
|
|
6
|
+
import { isMigrationFile } from "#naming/migrationFile.ts";
|
|
6
7
|
import { ownExports } from "#naming/ownExports.ts";
|
|
7
8
|
import { type PackageLayout, packageLayout } from "#naming/packageLayout.ts";
|
|
8
9
|
import { CAMEL, KEBAB, sameWord, wordsOf } from "#naming/words.ts";
|
|
@@ -86,7 +87,9 @@ export const namedAfterExport = defineRule({
|
|
|
86
87
|
check: async ({ files, options, readText, sources }) => {
|
|
87
88
|
const layout = await packageLayout({ files, readText });
|
|
88
89
|
const toolOwned = ignore().add([...options.toolOwned]);
|
|
89
|
-
return sources
|
|
90
|
+
return sources
|
|
91
|
+
.filter((file) => !(TEST.test(file.path) || isMigrationFile(file.path) || toolOwned.ignores(file.path)))
|
|
92
|
+
.flatMap((file) => findingsOf(file, layout));
|
|
90
93
|
},
|
|
91
94
|
description:
|
|
92
95
|
"A file is named after its main export, with its folders as the prefix; a file of several exports is a camelCase topic file, and a script is kebab-case. Rename the file or the export; nothing renames them for you.",
|
|
@@ -5,7 +5,7 @@ import { CAMEL, KEBAB } from "#naming/words.ts";
|
|
|
5
5
|
import { defineRule, type Finding } from "#rule.ts";
|
|
6
6
|
import { stylesheetImporters } from "./stylesheetImporters.ts";
|
|
7
7
|
|
|
8
|
-
const DEFAULT_TOOL_OWNED: readonly string[] = [".*", "package.json", "tsconfig*.json", "biome.json", "biome.jsonc", "*.config.*"
|
|
8
|
+
const DEFAULT_TOOL_OWNED: readonly string[] = [".*", "package.json", "tsconfig*.json", "biome.json", "biome.jsonc", "*.config.*"];
|
|
9
9
|
|
|
10
10
|
const CONVENTIONAL: ReadonlySet<string> = new Set(["AGENTS", "CHANGELOG", "CLAUDE", "CONTRIBUTING", "LICENSE", "README", "SECURITY", "SKILL"]);
|
|
11
11
|
|