@reliverse/dler 1.6.2 → 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -1
- package/bin/app/agg/cmd.d.ts +0 -6
- package/bin/app/agg/cmd.js +6 -12
- package/bin/app/check/cmd.d.ts +16 -2
- package/bin/app/check/cmd.js +484 -7
- package/bin/app/cmds.d.ts +34 -35
- package/bin/app/deps/cmd.d.ts +21 -4
- package/bin/app/deps/cmd.js +31 -9
- package/bin/app/deps/impl/deps-types.d.ts +5 -0
- package/bin/app/deps/impl/wrapper.d.ts +2 -0
- package/bin/app/deps/impl/wrapper.js +57 -0
- package/bin/app/inject/cmd.d.ts +0 -1
- package/bin/app/inject/cmd.js +1 -2
- package/bin/app/libs/cmd.d.ts +0 -1
- package/bin/app/libs/cmd.js +0 -1
- package/bin/app/migrate/cmd.d.ts +0 -1
- package/bin/app/migrate/cmd.js +1 -2
- package/bin/app/relifso/cmd.d.ts +0 -1
- package/bin/app/relifso/cmd.js +1 -2
- package/bin/app/relifso/init/cmd.d.ts +0 -7
- package/bin/app/relifso/init/cmd.js +4 -11
- package/bin/app/relifso/rename/cmd.d.ts +0 -7
- package/bin/app/relifso/rename/cmd.js +5 -12
- package/bin/app/rempts/cmd/cmd.d.ts +0 -2
- package/bin/app/rempts/cmd/cmd.js +0 -2
- package/bin/app/rempts/cmdsTs/cmd.d.ts +0 -1
- package/bin/app/rempts/cmdsTs/cmd.js +0 -1
- package/bin/app/spell/cmd.d.ts +0 -2
- package/bin/app/spell/cmd.js +2 -4
- package/bin/init/info.js +1 -1
- package/bin/libs/sdk/sdk-impl/build/build-library.js +5 -0
- package/bin/libs/sdk/sdk-impl/build/build-regular.js +5 -0
- package/bin/libs/sdk/sdk-impl/constants.d.ts +1 -0
- package/bin/libs/sdk/sdk-impl/constants.js +13 -0
- package/bin/libs/sdk/sdk-types.d.ts +31 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -150,6 +150,52 @@ bun tools:agg # shortcut for:
|
|
|
150
150
|
bun src/cli.ts tools --dev --tool agg --input src/libs/sdk/sdk-impl --out src/libs/sdk/sdk-mod.ts --recursive --named --strip src/libs/sdk
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
+
### 1.5. `check`
|
|
154
|
+
|
|
155
|
+
checks your project for common issues and potential improvements. This command performs several types of checks:
|
|
156
|
+
|
|
157
|
+
- **File Extensions**: Validates that files have the correct extensions based on their location and module resolution strategy
|
|
158
|
+
- Enforces `.ts` files in source and JSR distributions
|
|
159
|
+
- Enforces `.js` files in NPM distributions
|
|
160
|
+
- Supports `.css` and `.json` files in all environments
|
|
161
|
+
- Adapts to your module resolution strategy (bundler/nodenext)
|
|
162
|
+
|
|
163
|
+
- **Path Extensions**: Ensures import statements use the correct file extensions
|
|
164
|
+
- Validates import paths match your module resolution strategy
|
|
165
|
+
- Checks for proper extension usage in import statements
|
|
166
|
+
- Supports both relative and absolute imports
|
|
167
|
+
|
|
168
|
+
- **Dependencies**: Identifies missing dependencies in your project
|
|
169
|
+
- Scans all source files for imports
|
|
170
|
+
- Compares against package.json
|
|
171
|
+
- Reports missing dependencies
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Fully interactive mode (when no args provided)
|
|
175
|
+
dler check
|
|
176
|
+
|
|
177
|
+
# Mixed mode (some args provided, prompts for the rest)
|
|
178
|
+
dler check --directory src
|
|
179
|
+
dler check --checks file-extensions,path-extensions
|
|
180
|
+
dler check --strict
|
|
181
|
+
|
|
182
|
+
# Fully automated mode (all args provided)
|
|
183
|
+
dler check --directory src --checks file-extensions,path-extensions --strict
|
|
184
|
+
|
|
185
|
+
# Output in JSON format
|
|
186
|
+
dler check --json
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**arguments:**
|
|
190
|
+
|
|
191
|
+
- `--directory`: directory to check (src, dist-npm, dist-jsr, dist-libs/npm, dist-libs/jsr, or all)
|
|
192
|
+
- `--checks`: comma-separated list of checks to run (missing-dependencies, file-extensions, path-extensions)
|
|
193
|
+
- `--strict`: enable strict mode (requires explicit extensions)
|
|
194
|
+
- `--json`: output results in JSON format
|
|
195
|
+
|
|
196
|
+
**pro tip:**
|
|
197
|
+
the command will prompt you only for the arguments you haven't provided. for example, if you specify `--directory` but not `--checks`, it will only prompt you to select which checks to run.
|
|
198
|
+
|
|
153
199
|
### 2. `build`
|
|
154
200
|
|
|
155
201
|
since dler is fully modular, build command is separated for its own build-in plugin as well.
|
|
@@ -164,7 +210,7 @@ not yet documented.
|
|
|
164
210
|
|
|
165
211
|
### 4. `deps`
|
|
166
212
|
|
|
167
|
-
finds missing dependencies in your project by scanning your code for imports and comparing them to your `package.json`.
|
|
213
|
+
finds missing dependencies in your project by scanning your code for imports and comparing them to your `package.json`. This command is particularly useful for maintaining clean dependency lists and preventing runtime errors.
|
|
168
214
|
|
|
169
215
|
**what it does:**
|
|
170
216
|
|
|
@@ -179,6 +225,7 @@ finds missing dependencies in your project by scanning your code for imports and
|
|
|
179
225
|
- can also show all used dependencies (listed and missing)
|
|
180
226
|
- optionally includes node.js built-in modules in the report
|
|
181
227
|
- outputs results in a readable format or as json
|
|
228
|
+
- exits with error code 1 if missing dependencies are found
|
|
182
229
|
|
|
183
230
|
**usage examples:**
|
|
184
231
|
|
package/bin/app/agg/cmd.d.ts
CHANGED
|
@@ -34,7 +34,6 @@ declare const _default: import("@reliverse/rempts").Command<{
|
|
|
34
34
|
sort: {
|
|
35
35
|
description: string;
|
|
36
36
|
type: "boolean";
|
|
37
|
-
default: false;
|
|
38
37
|
};
|
|
39
38
|
header: {
|
|
40
39
|
description: string;
|
|
@@ -43,12 +42,10 @@ declare const _default: import("@reliverse/rempts").Command<{
|
|
|
43
42
|
verbose: {
|
|
44
43
|
description: string;
|
|
45
44
|
type: "boolean";
|
|
46
|
-
default: false;
|
|
47
45
|
};
|
|
48
46
|
includeInternal: {
|
|
49
47
|
description: string;
|
|
50
48
|
type: "boolean";
|
|
51
|
-
default: false;
|
|
52
49
|
};
|
|
53
50
|
internalMarker: {
|
|
54
51
|
description: string;
|
|
@@ -58,7 +55,6 @@ declare const _default: import("@reliverse/rempts").Command<{
|
|
|
58
55
|
override: {
|
|
59
56
|
description: string;
|
|
60
57
|
type: "boolean";
|
|
61
|
-
default: false;
|
|
62
58
|
};
|
|
63
59
|
extensions: {
|
|
64
60
|
description: string;
|
|
@@ -68,12 +64,10 @@ declare const _default: import("@reliverse/rempts").Command<{
|
|
|
68
64
|
separateTypesFile: {
|
|
69
65
|
description: string;
|
|
70
66
|
type: "boolean";
|
|
71
|
-
default: false;
|
|
72
67
|
};
|
|
73
68
|
typesOut: {
|
|
74
69
|
description: string;
|
|
75
70
|
type: "string";
|
|
76
|
-
required: false;
|
|
77
71
|
};
|
|
78
72
|
}>;
|
|
79
73
|
export default _default;
|
package/bin/app/agg/cmd.js
CHANGED
|
@@ -37,8 +37,7 @@ export default defineCommand({
|
|
|
37
37
|
},
|
|
38
38
|
sort: {
|
|
39
39
|
description: "Sort aggregated lines alphabetically",
|
|
40
|
-
type: "boolean"
|
|
41
|
-
default: false
|
|
40
|
+
type: "boolean"
|
|
42
41
|
},
|
|
43
42
|
header: {
|
|
44
43
|
description: "Add a header comment to the aggregator output",
|
|
@@ -46,13 +45,11 @@ export default defineCommand({
|
|
|
46
45
|
},
|
|
47
46
|
verbose: {
|
|
48
47
|
description: "Enable verbose logging",
|
|
49
|
-
type: "boolean"
|
|
50
|
-
default: false
|
|
48
|
+
type: "boolean"
|
|
51
49
|
},
|
|
52
50
|
includeInternal: {
|
|
53
51
|
description: "Include files marked as internal (starting with #)",
|
|
54
|
-
type: "boolean"
|
|
55
|
-
default: false
|
|
52
|
+
type: "boolean"
|
|
56
53
|
},
|
|
57
54
|
internalMarker: {
|
|
58
55
|
description: "Marker for internal files (default: #)",
|
|
@@ -61,8 +58,7 @@ export default defineCommand({
|
|
|
61
58
|
},
|
|
62
59
|
override: {
|
|
63
60
|
description: "Override entire file instead of updating only the aggregator block",
|
|
64
|
-
type: "boolean"
|
|
65
|
-
default: false
|
|
61
|
+
type: "boolean"
|
|
66
62
|
},
|
|
67
63
|
extensions: {
|
|
68
64
|
description: "Comma-separated list of file extensions to process (default: .ts,.js,.mts,.cts,.mjs,.cjs)",
|
|
@@ -71,13 +67,11 @@ export default defineCommand({
|
|
|
71
67
|
},
|
|
72
68
|
separateTypesFile: {
|
|
73
69
|
description: "Create a separate file for type exports",
|
|
74
|
-
type: "boolean"
|
|
75
|
-
default: false
|
|
70
|
+
type: "boolean"
|
|
76
71
|
},
|
|
77
72
|
typesOut: {
|
|
78
73
|
description: "Output file path for types (used when separateTypesFile is true)",
|
|
79
|
-
type: "string"
|
|
80
|
-
required: false
|
|
74
|
+
type: "string"
|
|
81
75
|
}
|
|
82
76
|
}),
|
|
83
77
|
async run({ args }) {
|
package/bin/app/check/cmd.d.ts
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
|
+
import type { CheckResult, FileCheckOptions } from "../../libs/sdk/sdk-types.js";
|
|
2
|
+
export declare function checkFileExtensions(options: FileCheckOptions): Promise<CheckResult>;
|
|
3
|
+
export declare function checkPathExtensions(options: FileCheckOptions): Promise<CheckResult>;
|
|
1
4
|
declare const _default: import("@reliverse/rempts").Command<{
|
|
2
|
-
|
|
5
|
+
directory: {
|
|
3
6
|
type: "string";
|
|
4
|
-
|
|
7
|
+
description: string;
|
|
8
|
+
};
|
|
9
|
+
checks: {
|
|
10
|
+
type: "string";
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
strict: {
|
|
14
|
+
type: "boolean";
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
json: {
|
|
18
|
+
type: "boolean";
|
|
5
19
|
description: string;
|
|
6
20
|
};
|
|
7
21
|
}>;
|
package/bin/app/check/cmd.js
CHANGED
|
@@ -1,19 +1,496 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getFileImportsExports, extname, join } from "@reliverse/pathkit";
|
|
2
|
+
import fs from "@reliverse/relifso";
|
|
3
|
+
import { relinka } from "@reliverse/relinka";
|
|
4
|
+
import {
|
|
5
|
+
defineCommand,
|
|
6
|
+
selectPrompt,
|
|
7
|
+
multiselectPrompt,
|
|
8
|
+
confirmPrompt,
|
|
9
|
+
defineArgs
|
|
10
|
+
} from "@reliverse/rempts";
|
|
11
|
+
import { readTSConfig } from "pkg-types";
|
|
12
|
+
import { checkMissingDependencies } from "../deps/impl/wrapper.js";
|
|
13
|
+
import { IGNORE_PATTERNS } from "../../libs/sdk/sdk-impl/constants.js";
|
|
14
|
+
const ALLOWED_FILE_EXTENSIONS = {
|
|
15
|
+
src: ["", ".ts", ".css", ".json"],
|
|
16
|
+
// ✅ .ts files allowed in src
|
|
17
|
+
"dist-npm": ["", ".js", ".css", ".json"],
|
|
18
|
+
// ❌ no .ts files in npm dist
|
|
19
|
+
"dist-jsr": ["", ".ts", ".css", ".json"],
|
|
20
|
+
// ✅ .ts files allowed in jsr dist
|
|
21
|
+
"dist-libs/npm": ["", ".js", ".css", ".json"],
|
|
22
|
+
// ❌ no .ts files in npm libs
|
|
23
|
+
"dist-libs/jsr": ["", ".ts", ".css", ".json"]
|
|
24
|
+
// ✅ .ts files allowed in jsr libs
|
|
25
|
+
};
|
|
26
|
+
const STRICT_FILE_EXTENSIONS = {
|
|
27
|
+
src: [".ts", ".css", ".json"],
|
|
28
|
+
// ✅ .ts files required in src
|
|
29
|
+
"dist-npm": [".js", ".css", ".json"],
|
|
30
|
+
// ❌ no .ts files in npm dist
|
|
31
|
+
"dist-jsr": [".ts", ".css", ".json"],
|
|
32
|
+
// ✅ .ts files required in jsr dist
|
|
33
|
+
"dist-libs/npm": [".js", ".css", ".json"],
|
|
34
|
+
// ❌ no .ts files in npm libs
|
|
35
|
+
"dist-libs/jsr": [".ts", ".css", ".json"]
|
|
36
|
+
// ✅ .ts files required in jsr libs
|
|
37
|
+
};
|
|
38
|
+
const ALLOWED_IMPORT_EXTENSIONS = {
|
|
39
|
+
src: ["", ".js", ".css", ".json"],
|
|
40
|
+
// ❌ no .ts imports (use .js)
|
|
41
|
+
"dist-npm": ["", ".js", ".css", ".json"],
|
|
42
|
+
// ❌ no .ts imports
|
|
43
|
+
"dist-jsr": ["", ".ts", ".css", ".json"],
|
|
44
|
+
// ✅ .ts imports allowed
|
|
45
|
+
"dist-libs/npm": ["", ".js", ".css", ".json"],
|
|
46
|
+
// ❌ no .ts imports
|
|
47
|
+
"dist-libs/jsr": ["", ".ts", ".css", ".json"]
|
|
48
|
+
// ✅ .ts imports allowed
|
|
49
|
+
};
|
|
50
|
+
const STRICT_IMPORT_EXTENSIONS = {
|
|
51
|
+
src: [".js", ".css", ".json"],
|
|
52
|
+
// ❌ no .ts imports, no empty
|
|
53
|
+
"dist-npm": [".js", ".css", ".json"],
|
|
54
|
+
// ❌ no .ts imports
|
|
55
|
+
"dist-jsr": [".ts", ".css", ".json"],
|
|
56
|
+
// ✅ .ts imports required
|
|
57
|
+
"dist-libs/npm": [".js", ".css", ".json"],
|
|
58
|
+
// ❌ no .ts imports
|
|
59
|
+
"dist-libs/jsr": [".ts", ".css", ".json"]
|
|
60
|
+
// ✅ .ts imports required
|
|
61
|
+
};
|
|
62
|
+
async function validateDirectory(dir) {
|
|
63
|
+
try {
|
|
64
|
+
const stat = await fs.stat(dir);
|
|
65
|
+
return stat.isDirectory();
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function shouldIgnoreFile(filePath) {
|
|
71
|
+
const pathSegments = filePath.split("/");
|
|
72
|
+
return IGNORE_PATTERNS.some(
|
|
73
|
+
(pattern) => pathSegments.some((segment) => segment.includes(pattern))
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
async function getAllFiles(dir, onProgress) {
|
|
77
|
+
const results = [];
|
|
78
|
+
let fileCount = 0;
|
|
79
|
+
async function searchDirectory(directory) {
|
|
80
|
+
try {
|
|
81
|
+
const files = await fs.readdir(directory);
|
|
82
|
+
for (const file of files) {
|
|
83
|
+
const fullPath = join(directory, file);
|
|
84
|
+
if (shouldIgnoreFile(fullPath)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const stat = await fs.stat(fullPath);
|
|
89
|
+
if (stat.isDirectory()) {
|
|
90
|
+
if (file === "templates") continue;
|
|
91
|
+
await searchDirectory(fullPath);
|
|
92
|
+
} else {
|
|
93
|
+
results.push(fullPath);
|
|
94
|
+
fileCount++;
|
|
95
|
+
onProgress?.(fileCount, fileCount, fullPath);
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
relinka("warn", `skipping inaccessible file: ${fullPath}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
relinka("warn", `skipping inaccessible directory: ${directory}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!await validateDirectory(dir)) {
|
|
106
|
+
throw new Error(`directory "${dir}" does not exist or is not accessible`);
|
|
107
|
+
}
|
|
108
|
+
await searchDirectory(dir);
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
111
|
+
async function validateModuleResolution() {
|
|
112
|
+
try {
|
|
113
|
+
const tsconfig = await readTSConfig();
|
|
114
|
+
const moduleResolution = tsconfig?.compilerOptions?.moduleResolution;
|
|
115
|
+
if (!moduleResolution) {
|
|
116
|
+
throw new Error("moduleResolution is not specified in tsconfig.json");
|
|
117
|
+
}
|
|
118
|
+
if (moduleResolution !== "bundler" && moduleResolution !== "nodenext") {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`unsupported moduleResolution: ${moduleResolution}. Only "bundler" and "nodenext" are supported`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
return moduleResolution;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`failed to read tsconfig.json: ${error instanceof Error ? error.message : "unknown error"}`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function getAllowedFileExtensions(directory, strict, moduleResolution) {
|
|
131
|
+
if (!strict) {
|
|
132
|
+
return ALLOWED_FILE_EXTENSIONS[directory];
|
|
133
|
+
}
|
|
134
|
+
if (moduleResolution === "bundler") {
|
|
135
|
+
return STRICT_FILE_EXTENSIONS[directory];
|
|
136
|
+
}
|
|
137
|
+
return STRICT_FILE_EXTENSIONS[directory];
|
|
138
|
+
}
|
|
139
|
+
function getAllowedImportExtensions(directory, strict) {
|
|
140
|
+
if (strict) {
|
|
141
|
+
return STRICT_IMPORT_EXTENSIONS[directory];
|
|
142
|
+
}
|
|
143
|
+
return ALLOWED_IMPORT_EXTENSIONS[directory];
|
|
144
|
+
}
|
|
145
|
+
export async function checkFileExtensions(options) {
|
|
146
|
+
const startTime = Date.now();
|
|
147
|
+
const issues = [];
|
|
148
|
+
const { directory, strict, moduleResolution, onProgress } = options;
|
|
149
|
+
const allowedExts = getAllowedFileExtensions(
|
|
150
|
+
directory,
|
|
151
|
+
strict,
|
|
152
|
+
moduleResolution
|
|
153
|
+
);
|
|
154
|
+
try {
|
|
155
|
+
const files = await getAllFiles(directory, onProgress);
|
|
156
|
+
const batchSize = 50;
|
|
157
|
+
const batches = [];
|
|
158
|
+
for (let i = 0; i < files.length; i += batchSize) {
|
|
159
|
+
batches.push(files.slice(i, i + batchSize));
|
|
160
|
+
}
|
|
161
|
+
for (const [batchIndex, batch] of batches.entries()) {
|
|
162
|
+
const batchPromises = batch.map(async (file, fileIndex) => {
|
|
163
|
+
const globalIndex = batchIndex * batchSize + fileIndex;
|
|
164
|
+
onProgress?.(globalIndex + 1, files.length, file);
|
|
165
|
+
const ext = extname(file);
|
|
166
|
+
if (!allowedExts.includes(ext)) {
|
|
167
|
+
let message = `file has disallowed extension "${ext}" (allowed: ${allowedExts.join(", ")})`;
|
|
168
|
+
if (ext === ".ts" && (directory === "dist-npm" || directory === "dist-libs/npm")) {
|
|
169
|
+
message = `typescript file found in javascript environment: ${file} (should be compiled to .js)`;
|
|
170
|
+
} else if (ext === ".js" && (directory === "src" || directory === "dist-jsr" || directory === "dist-libs/jsr")) {
|
|
171
|
+
message = `javascript file found in typescript environment: ${file} (should be .ts)`;
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
file,
|
|
175
|
+
message,
|
|
176
|
+
type: "file-extension"
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
});
|
|
181
|
+
const batchResults = await Promise.all(batchPromises);
|
|
182
|
+
issues.push(...batchResults.filter(Boolean));
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
success: issues.length === 0,
|
|
186
|
+
issues,
|
|
187
|
+
stats: {
|
|
188
|
+
filesChecked: files.length,
|
|
189
|
+
importsChecked: 0,
|
|
190
|
+
timeElapsed: Date.now() - startTime
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
} catch (error) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`failed to check file extensions: ${error instanceof Error ? error.message : "unknown error"}`
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
export async function checkPathExtensions(options) {
|
|
200
|
+
const startTime = Date.now();
|
|
201
|
+
const issues = [];
|
|
202
|
+
const { directory, strict, onProgress } = options;
|
|
203
|
+
const allowedExts = getAllowedImportExtensions(directory, strict);
|
|
204
|
+
try {
|
|
205
|
+
const files = await getAllFiles(directory);
|
|
206
|
+
let totalImports = 0;
|
|
207
|
+
const importableFiles = files.filter((file) => {
|
|
208
|
+
const ext = extname(file);
|
|
209
|
+
return [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext);
|
|
210
|
+
});
|
|
211
|
+
const batchSize = 20;
|
|
212
|
+
const batches = [];
|
|
213
|
+
for (let i = 0; i < importableFiles.length; i += batchSize) {
|
|
214
|
+
batches.push(importableFiles.slice(i, i + batchSize));
|
|
215
|
+
}
|
|
216
|
+
for (const [batchIndex, batch] of batches.entries()) {
|
|
217
|
+
const batchPromises = batch.map(async (file, fileIndex) => {
|
|
218
|
+
const globalIndex = batchIndex * batchSize + fileIndex;
|
|
219
|
+
onProgress?.(globalIndex + 1, importableFiles.length, file);
|
|
220
|
+
try {
|
|
221
|
+
const content = await fs.readFile(file, "utf-8");
|
|
222
|
+
const imports = getFileImportsExports(content, {
|
|
223
|
+
kind: "import",
|
|
224
|
+
pathTypes: ["relative", "alias"]
|
|
225
|
+
});
|
|
226
|
+
totalImports += imports.length;
|
|
227
|
+
const fileIssues = [];
|
|
228
|
+
for (const imp of imports) {
|
|
229
|
+
if (!imp.source) continue;
|
|
230
|
+
const ext = extname(imp.source);
|
|
231
|
+
if (!allowedExts.includes(ext)) {
|
|
232
|
+
const isTypeScriptImport = ext === ".ts";
|
|
233
|
+
const isJsEnvironment = directory === "src" || directory === "dist-npm" || directory === "dist-libs/npm";
|
|
234
|
+
let message;
|
|
235
|
+
if (isTypeScriptImport && isJsEnvironment) {
|
|
236
|
+
message = `import uses .ts extension in javascript environment: ${imp.source} (use .js extension instead)`;
|
|
237
|
+
} else {
|
|
238
|
+
message = `import has disallowed extension "${ext}": ${imp.source} (allowed: ${allowedExts.join(", ")})`;
|
|
239
|
+
}
|
|
240
|
+
fileIssues.push({
|
|
241
|
+
file,
|
|
242
|
+
message,
|
|
243
|
+
type: "path-extension",
|
|
244
|
+
line: getLineNumber(content, imp.start),
|
|
245
|
+
source: imp.source
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return { issues: fileIssues, importCount: imports.length };
|
|
250
|
+
} catch {
|
|
251
|
+
relinka("warn", `skipping unreadable file: ${file}`);
|
|
252
|
+
return { issues: [], importCount: 0 };
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
const batchResults = await Promise.all(batchPromises);
|
|
256
|
+
for (const result of batchResults) {
|
|
257
|
+
issues.push(...result.issues);
|
|
258
|
+
totalImports += result.importCount;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
success: issues.length === 0,
|
|
263
|
+
issues,
|
|
264
|
+
stats: {
|
|
265
|
+
filesChecked: importableFiles.length,
|
|
266
|
+
importsChecked: totalImports,
|
|
267
|
+
timeElapsed: Date.now() - startTime
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
} catch (error) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
`failed to check path extensions: ${error instanceof Error ? error.message : "unknown error"}`
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function getLineNumber(content, position) {
|
|
277
|
+
return content.slice(0, position).split("\n").length;
|
|
278
|
+
}
|
|
279
|
+
function displayResults(checkType, directory, result) {
|
|
280
|
+
const { success, issues, stats } = result;
|
|
281
|
+
if (success) {
|
|
282
|
+
relinka("success", `\u2713 ${checkType} check passed for ${directory}`);
|
|
283
|
+
relinka(
|
|
284
|
+
"info",
|
|
285
|
+
` files checked: ${stats.filesChecked}, imports: ${stats.importsChecked}, time: ${stats.timeElapsed}ms`
|
|
286
|
+
);
|
|
287
|
+
} else {
|
|
288
|
+
relinka(
|
|
289
|
+
"error",
|
|
290
|
+
`\u2717 ${checkType} check failed for ${directory} (${issues.length} issues)`
|
|
291
|
+
);
|
|
292
|
+
const fileIssues = issues.filter((i) => i.type === "file-extension");
|
|
293
|
+
const pathIssues = issues.filter((i) => i.type === "path-extension");
|
|
294
|
+
const missingDepIssues = issues.filter(
|
|
295
|
+
(i) => i.type === "missing-dependency"
|
|
296
|
+
);
|
|
297
|
+
const builtinIssues = issues.filter((i) => i.type === "builtin-module");
|
|
298
|
+
if (fileIssues.length > 0) {
|
|
299
|
+
relinka("error", ` file extension issues (${fileIssues.length}):`);
|
|
300
|
+
for (const issue of fileIssues.slice(0, 10)) {
|
|
301
|
+
relinka("error", ` ${issue.file}: ${issue.message}`);
|
|
302
|
+
}
|
|
303
|
+
if (fileIssues.length > 10) {
|
|
304
|
+
relinka("error", ` ... and ${fileIssues.length - 10} more`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (pathIssues.length > 0) {
|
|
308
|
+
relinka("error", ` import extension issues (${pathIssues.length}):`);
|
|
309
|
+
for (const issue of pathIssues.slice(0, 10)) {
|
|
310
|
+
relinka("error", ` ${issue.file}:${issue.line}: ${issue.message}`);
|
|
311
|
+
}
|
|
312
|
+
if (pathIssues.length > 10) {
|
|
313
|
+
relinka("error", ` ... and ${pathIssues.length - 10} more`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (missingDepIssues.length > 0) {
|
|
317
|
+
relinka("error", ` missing dependencies (${missingDepIssues.length}):`);
|
|
318
|
+
for (const issue of missingDepIssues.slice(0, 10)) {
|
|
319
|
+
relinka("error", ` ${issue.message}`);
|
|
320
|
+
}
|
|
321
|
+
if (missingDepIssues.length > 10) {
|
|
322
|
+
relinka("error", ` ... and ${missingDepIssues.length - 10} more`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (builtinIssues.length > 0) {
|
|
326
|
+
relinka("warn", ` builtin modules used (${builtinIssues.length}):`);
|
|
327
|
+
for (const issue of builtinIssues.slice(0, 10)) {
|
|
328
|
+
relinka("warn", ` ${issue.message}`);
|
|
329
|
+
}
|
|
330
|
+
if (builtinIssues.length > 10) {
|
|
331
|
+
relinka("warn", ` ... and ${builtinIssues.length - 10} more`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
relinka(
|
|
335
|
+
"info",
|
|
336
|
+
` stats: ${stats.filesChecked} files, ${stats.importsChecked} imports, ${stats.timeElapsed}ms`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
2
340
|
export default defineCommand({
|
|
3
341
|
meta: {
|
|
4
342
|
name: "check",
|
|
5
343
|
version: "1.0.0",
|
|
6
|
-
description: "
|
|
344
|
+
description: "check your codebase source and dists for any issues."
|
|
7
345
|
},
|
|
8
346
|
args: defineArgs({
|
|
9
|
-
|
|
347
|
+
directory: {
|
|
10
348
|
type: "string",
|
|
11
|
-
|
|
12
|
-
|
|
349
|
+
description: "directory to check (src, dist-npm, dist-jsr, dist-libs/npm, dist-libs/jsr, or all)"
|
|
350
|
+
},
|
|
351
|
+
checks: {
|
|
352
|
+
type: "string",
|
|
353
|
+
description: "comma-separated list of checks to run (missing-dependencies,file-extensions,path-extensions)"
|
|
354
|
+
},
|
|
355
|
+
strict: {
|
|
356
|
+
type: "boolean",
|
|
357
|
+
description: "enable strict mode (requires explicit extensions)"
|
|
358
|
+
},
|
|
359
|
+
json: {
|
|
360
|
+
type: "boolean",
|
|
361
|
+
description: "output results in JSON format"
|
|
13
362
|
}
|
|
14
363
|
}),
|
|
15
364
|
async run({ args }) {
|
|
16
|
-
|
|
17
|
-
|
|
365
|
+
relinka(
|
|
366
|
+
"info",
|
|
367
|
+
"this command checks your codebase for extension and dependency issues."
|
|
368
|
+
);
|
|
369
|
+
relinka(
|
|
370
|
+
"info",
|
|
371
|
+
"\u{1F4C1} file rules: .ts files allowed in src/jsr dirs, .js files in npm dirs"
|
|
372
|
+
);
|
|
373
|
+
relinka(
|
|
374
|
+
"info",
|
|
375
|
+
"\u{1F4E6} import rules: use .js imports in src/npm dirs, .ts imports in jsr dirs"
|
|
376
|
+
);
|
|
377
|
+
let moduleResolution;
|
|
378
|
+
try {
|
|
379
|
+
moduleResolution = await validateModuleResolution();
|
|
380
|
+
relinka("info", `using moduleResolution: ${moduleResolution}`);
|
|
381
|
+
} catch (error) {
|
|
382
|
+
relinka(
|
|
383
|
+
"error",
|
|
384
|
+
error instanceof Error ? error.message : "unknown error"
|
|
385
|
+
);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
let dir;
|
|
389
|
+
let checks;
|
|
390
|
+
let strict;
|
|
391
|
+
if (args.directory) {
|
|
392
|
+
dir = args.directory;
|
|
393
|
+
} else {
|
|
394
|
+
dir = await selectPrompt({
|
|
395
|
+
title: "select a directory to check",
|
|
396
|
+
options: [
|
|
397
|
+
{ label: "all directories", value: "all" },
|
|
398
|
+
{ label: "src (typescript source)", value: "src" },
|
|
399
|
+
{ label: "dist-npm (compiled js)", value: "dist-npm" },
|
|
400
|
+
{ label: "dist-jsr (typescript)", value: "dist-jsr" },
|
|
401
|
+
{ label: "dist-libs/npm (compiled js)", value: "dist-libs/npm" },
|
|
402
|
+
{ label: "dist-libs/jsr (typescript)", value: "dist-libs/jsr" }
|
|
403
|
+
]
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
if (args.checks) {
|
|
407
|
+
checks = args.checks.split(",");
|
|
408
|
+
} else {
|
|
409
|
+
checks = await multiselectPrompt({
|
|
410
|
+
title: "select checks to run",
|
|
411
|
+
options: [
|
|
412
|
+
{ label: "missing dependencies", value: "missing-dependencies" },
|
|
413
|
+
{
|
|
414
|
+
label: "file extensions (.ts/.js files)",
|
|
415
|
+
value: "file-extensions"
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
label: "import path extensions (.ts/.js imports)",
|
|
419
|
+
value: "path-extensions"
|
|
420
|
+
}
|
|
421
|
+
]
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
if (args.strict !== void 0) {
|
|
425
|
+
strict = args.strict;
|
|
426
|
+
} else {
|
|
427
|
+
strict = await confirmPrompt({
|
|
428
|
+
title: "activate strict mode?",
|
|
429
|
+
content: "strict mode requires explicit extensions (no empty extensions). files: .ts in src/jsr dirs, .js in npm dirs. imports: .js in src/npm dirs, .ts in jsr dirs. templates folder is always exempt."
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
if (checks.length === 0) {
|
|
433
|
+
relinka("warn", "no checks selected, exiting...");
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const directories = dir === "all" ? [
|
|
437
|
+
"src",
|
|
438
|
+
"dist-npm",
|
|
439
|
+
"dist-jsr",
|
|
440
|
+
"dist-libs/npm",
|
|
441
|
+
"dist-libs/jsr"
|
|
442
|
+
] : [dir];
|
|
443
|
+
for (const directory of directories) {
|
|
444
|
+
relinka("info", `
|
|
445
|
+
checking directory: ${directory}`);
|
|
446
|
+
const onProgress = (current, total) => {
|
|
447
|
+
if (current % 10 === 0 || current === total) {
|
|
448
|
+
process.stdout.write(`\r progress: ${current}/${total} files...`);
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
try {
|
|
452
|
+
if (checks.includes("missing-dependencies")) {
|
|
453
|
+
process.stdout.write(" checking missing dependencies...\n");
|
|
454
|
+
const result = await checkMissingDependencies({
|
|
455
|
+
directory,
|
|
456
|
+
strict: false,
|
|
457
|
+
// not used for deps check
|
|
458
|
+
moduleResolution,
|
|
459
|
+
// not used for deps check
|
|
460
|
+
onProgress
|
|
461
|
+
});
|
|
462
|
+
process.stdout.write("\r");
|
|
463
|
+
displayResults("missing dependencies", directory, result);
|
|
464
|
+
}
|
|
465
|
+
if (checks.includes("file-extensions")) {
|
|
466
|
+
process.stdout.write(" checking file extensions...\n");
|
|
467
|
+
const result = await checkFileExtensions({
|
|
468
|
+
directory,
|
|
469
|
+
strict,
|
|
470
|
+
moduleResolution,
|
|
471
|
+
onProgress
|
|
472
|
+
});
|
|
473
|
+
process.stdout.write("\r");
|
|
474
|
+
displayResults("file extensions", directory, result);
|
|
475
|
+
}
|
|
476
|
+
if (checks.includes("path-extensions")) {
|
|
477
|
+
process.stdout.write(" checking import path extensions...\n");
|
|
478
|
+
const result = await checkPathExtensions({
|
|
479
|
+
directory,
|
|
480
|
+
strict,
|
|
481
|
+
moduleResolution,
|
|
482
|
+
onProgress
|
|
483
|
+
});
|
|
484
|
+
process.stdout.write("\r");
|
|
485
|
+
displayResults("path extensions", directory, result);
|
|
486
|
+
}
|
|
487
|
+
} catch (error) {
|
|
488
|
+
relinka(
|
|
489
|
+
"error",
|
|
490
|
+
`failed to check ${directory}: ${error instanceof Error ? error.message : "unknown error"}`
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
relinka("success", "all checks completed!");
|
|
18
495
|
}
|
|
19
496
|
});
|