@trebired/code-discipline 0.1.0 → 1.0.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +14 -10
  2. package/README.md +141 -147
  3. package/dist/checks/fix-folderization.d.ts +17 -0
  4. package/dist/checks/fix-folderization.d.ts.map +1 -0
  5. package/dist/checks/fix-folderization.js +223 -0
  6. package/dist/checks/fix-folderization.js.map +1 -0
  7. package/dist/checks/index.d.ts +21 -2
  8. package/dist/checks/index.d.ts.map +1 -1
  9. package/dist/checks/index.js +104 -12
  10. package/dist/checks/index.js.map +1 -1
  11. package/dist/checks/rules/folderize-compound-files.d.ts.map +1 -1
  12. package/dist/checks/rules/folderize-compound-files.js +15 -33
  13. package/dist/checks/rules/folderize-compound-files.js.map +1 -1
  14. package/dist/checks/rules/folderize-plan.d.ts +16 -0
  15. package/dist/checks/rules/folderize-plan.d.ts.map +1 -0
  16. package/dist/checks/rules/folderize-plan.js +80 -0
  17. package/dist/checks/rules/folderize-plan.js.map +1 -0
  18. package/dist/checks/rules/max-file-lines.d.ts.map +1 -1
  19. package/dist/checks/rules/max-file-lines.js +2 -1
  20. package/dist/checks/rules/max-file-lines.js.map +1 -1
  21. package/dist/checks/types.d.ts +31 -20
  22. package/dist/checks/types.d.ts.map +1 -1
  23. package/dist/cli/run-cli.d.ts.map +1 -1
  24. package/dist/cli/run-cli.js +40 -16
  25. package/dist/cli/run-cli.js.map +1 -1
  26. package/dist/config/normalize-check-options.d.ts.map +1 -1
  27. package/dist/config/normalize-check-options.js +8 -1
  28. package/dist/config/normalize-check-options.js.map +1 -1
  29. package/dist/config/normalize-rule-options.d.ts +21 -7
  30. package/dist/config/normalize-rule-options.d.ts.map +1 -1
  31. package/dist/config/normalize-rule-options.js +43 -18
  32. package/dist/config/normalize-rule-options.js.map +1 -1
  33. package/dist/config/normalize-sync-imports-options.d.ts.map +1 -1
  34. package/dist/config/normalize-sync-imports-options.js +18 -6
  35. package/dist/config/normalize-sync-imports-options.js.map +1 -1
  36. package/dist/imports/aliases.d.ts +2 -1
  37. package/dist/imports/aliases.d.ts.map +1 -1
  38. package/dist/imports/aliases.js +18 -14
  39. package/dist/imports/aliases.js.map +1 -1
  40. package/dist/imports/check-sync-imports.d.ts +6 -0
  41. package/dist/imports/check-sync-imports.d.ts.map +1 -0
  42. package/dist/imports/check-sync-imports.js +55 -0
  43. package/dist/imports/check-sync-imports.js.map +1 -0
  44. package/dist/imports/module-specifiers.d.ts +20 -0
  45. package/dist/imports/module-specifiers.d.ts.map +1 -0
  46. package/dist/imports/module-specifiers.js +69 -0
  47. package/dist/imports/module-specifiers.js.map +1 -0
  48. package/dist/imports/rewrite.d.ts +3 -2
  49. package/dist/imports/rewrite.d.ts.map +1 -1
  50. package/dist/imports/rewrite.js +27 -90
  51. package/dist/imports/rewrite.js.map +1 -1
  52. package/dist/imports/sync-imports.d.ts.map +1 -1
  53. package/dist/imports/sync-imports.js +50 -5
  54. package/dist/imports/sync-imports.js.map +1 -1
  55. package/dist/imports/types.d.ts +18 -24
  56. package/dist/imports/types.d.ts.map +1 -1
  57. package/dist/index.d.ts +7 -7
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +4 -4
  60. package/dist/index.js.map +1 -1
  61. package/dist/shared/constants.d.ts +4 -6
  62. package/dist/shared/constants.d.ts.map +1 -1
  63. package/dist/shared/constants.js +4 -6
  64. package/dist/shared/constants.js.map +1 -1
  65. package/dist/shared/errors.d.ts +8 -2
  66. package/dist/shared/errors.d.ts.map +1 -1
  67. package/dist/shared/errors.js +13 -1
  68. package/dist/shared/errors.js.map +1 -1
  69. package/dist/shared/logging-types.d.ts +7 -1
  70. package/dist/shared/logging-types.d.ts.map +1 -1
  71. package/dist/shared/logging.d.ts +2 -3
  72. package/dist/shared/logging.d.ts.map +1 -1
  73. package/dist/shared/logging.js.map +1 -1
  74. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -2,19 +2,23 @@
2
2
 
3
3
  All notable changes to `@trebired/code-discipline` will be documented here.
4
4
 
5
- This project follows semantic versioning once published.
5
+ ## 1.0.0
6
6
 
7
- ## Unreleased
8
-
9
- - established `@trebired/code-discipline` as the package identity
10
- - added `checkCodeDiscipline()` with `maxFileLines` and `folderizeCompoundFiles` rules
11
- - added the `code-discipline check` and `code-discipline sync` CLI commands
12
- - kept `syncImports()` as the package's mutating import-sync feature
13
- - changed the project license to `AGPL-3.0-only`
14
- - reorganized the source and test layout into smaller responsibility-focused modules
7
+ - introduced the breaking `enabled` / `stop` / `fix` rule model across the package
8
+ - removed `severity` from discipline and sync-import config
9
+ - removed suffix-based folderization and replaced it with structural folder grouping
10
+ - removed `suffixes` from `folderizeCompoundFiles`
11
+ - removed nested `syncImports.imports`
12
+ - removed `rewrite` as a sync config option in favor of `syncImports.fix`
13
+ - replaced `keepRelative` with `allowRelative`
14
+ - made `check` fully read-only
15
+ - made `sync` mutate imports and `tsconfig.json` only when `syncImports.fix` is `true`
16
+ - added explicit `fix` support for folderization moves and affected import rewrites
17
+ - added `fixCodeDiscipline()` as the structural mutation API
18
+ - added blocking-or-warning behavior through `stop` instead of severity levels
19
+ - bumped the package to `1.0.0` for the config and API cleanup
15
20
 
16
21
  ## 0.1.0
17
22
 
18
23
  - initial public release
19
24
  - added source scanning, alias generation, tsconfig path syncing, and in-place import rewriting
20
- - added configurable alias strategies, keep-relative policies, and restrained logging adapters
package/README.md CHANGED
@@ -1,10 +1,8 @@
1
1
  # @trebired/code-discipline
2
2
 
3
- Configurable codebase discipline checks and import syncing for Bun and Node.js projects.
3
+ Configurable repository discipline checks, structural fixes, and import syncing for Bun and Node.js projects.
4
4
 
5
- `@trebired/code-discipline` scans a configured source tree, runs project-level discipline rules, and can also keep TypeScript path aliases plus source imports aligned in one package.
6
-
7
- The package is intentionally focused. It helps with repository structure rules, file-shape rules, and import hygiene. It does not try to be a full linter, formatter, or build system.
5
+ `@trebired/code-discipline` is intentionally focused. It helps you keep a source tree disciplined without turning into a full linter, formatter, or build system.
8
6
 
9
7
  ## Install
10
8
 
@@ -14,59 +12,105 @@ Runtime support: Bun 1+ and Node.js 18+.
14
12
  npm install @trebired/code-discipline
15
13
  ```
16
14
 
17
- ## Quick Start
18
-
19
- ```ts
20
- import { checkCodeDiscipline } from "@trebired/code-discipline";
21
-
22
- const result = await checkCodeDiscipline({
23
- projectRoot: "/repo",
24
- sourceRoot: "src",
25
- rules: {
26
- maxFileLines: {
27
- enabled: true,
28
- max: 500,
29
- },
30
- folderizeCompoundFiles: {
31
- enabled: true,
32
- suffixes: ["start", "service"],
33
- separators: ["_", "-"],
34
- },
35
- },
36
- });
15
+ ## Why This Package
37
16
 
38
- if (!result.ok) {
39
- console.error(result.violations);
40
- process.exit(1);
41
- }
42
- ```
17
+ Some repository rules are not really single-file lint rules. They are about the shape of the tree:
43
18
 
44
- The first public slice is intentionally small:
19
+ - files growing too large
20
+ - compound filenames that want to become folders
21
+ - alias drift between `tsconfig.json` and source imports
22
+ - blocking or warning on repository policy in CI or startup flows
45
23
 
46
- - `checkCodeDiscipline()`
47
- - `syncImports()`
48
- - `defineCodeDisciplineConfig()`
49
- - `loadCodeDisciplineConfig()`
24
+ That is the lane of this package.
50
25
 
51
- If you want repo-driven usage instead of embedding the API directly, use the CLI with a top-level config file:
26
+ ## Commands
52
27
 
53
28
  ```sh
54
29
  code-discipline check
55
30
  code-discipline sync
31
+ code-discipline fix
32
+ ```
33
+
34
+ Command responsibilities stay clean:
35
+
36
+ - `check`: read-only validation and logging
37
+ - `sync`: import and `tsconfig.json` synchronization only
38
+ - `fix`: explicit folderization moves only
39
+
40
+ Both `sync` and `fix` are gated by rule config. They do not mutate anything unless their rule has `fix: true`.
41
+
42
+ ## Config
43
+
44
+ Every rule uses the same control model:
45
+
46
+ ```txt
47
+ enabled
48
+ = whether the rule runs
49
+
50
+ stop
51
+ = whether violations fail the result / exit non-zero
52
+
53
+ fix
54
+ = whether explicit mutation commands may change files
56
55
  ```
57
56
 
58
- ## What This Package Checks
57
+ Example `code-discipline.config.json`:
59
58
 
60
- The first discipline layer is intentionally narrow:
59
+ ```json
60
+ {
61
+ "sourceRoot": "src",
62
+ "sourceExtensions": [
63
+ ".ts",
64
+ ".tsx",
65
+ ".js",
66
+ ".jsx"
67
+ ],
68
+ "excludeDirs": [
69
+ "node_modules",
70
+ "dist",
71
+ ".vite"
72
+ ],
73
+ "rules": {
74
+ "maxFileLines": {
75
+ "enabled": true,
76
+ "stop": true,
77
+ "max": 500
78
+ },
79
+ "folderizeCompoundFiles": {
80
+ "enabled": true,
81
+ "stop": true,
82
+ "fix": false,
83
+ "separators": [
84
+ "_",
85
+ "-"
86
+ ]
87
+ },
88
+ "syncImports": {
89
+ "enabled": true,
90
+ "stop": true,
91
+ "fix": true,
92
+ "alias": {
93
+ "strategy": "relative-path-slug"
94
+ },
95
+ "allowRelative": [
96
+ "./"
97
+ ]
98
+ }
99
+ }
100
+ }
101
+ ```
61
102
 
62
- - `maxFileLines`
63
- - `folderizeCompoundFiles`
103
+ Breaking cleanup in `1.0.0`:
64
104
 
65
- `maxFileLines` reports files whose physical line count exceeds a configured threshold.
105
+ - `severity` was removed
106
+ - `suffixes` was removed
107
+ - `keepRelative` was replaced by `allowRelative`
108
+ - nested `syncImports.imports` was removed
109
+ - `rewrite` was removed as a config flag because `fix` controls mutation
66
110
 
67
- `folderizeCompoundFiles` reports names such as `user_start.ts` or `user-start.ts` that could be grouped into a more structured path such as `user/start.ts`.
111
+ ## Checks
68
112
 
69
- Example:
113
+ `checkCodeDiscipline()` is read-only. It never moves files, rewrites imports, or updates `tsconfig.json`.
70
114
 
71
115
  ```ts
72
116
  import { checkCodeDiscipline } from "@trebired/code-discipline";
@@ -76,29 +120,38 @@ const result = await checkCodeDiscipline({
76
120
  rules: {
77
121
  maxFileLines: {
78
122
  enabled: true,
123
+ stop: true,
79
124
  max: 500,
80
- severity: "error",
81
125
  },
82
126
  folderizeCompoundFiles: {
83
127
  enabled: true,
84
- suffixes: ["start"],
128
+ stop: false,
85
129
  separators: ["_", "-"],
86
- severity: "warn",
130
+ },
131
+ syncImports: {
132
+ enabled: true,
133
+ stop: true,
134
+ fix: false,
135
+ alias: {
136
+ strategy: "relative-path-slug",
137
+ },
138
+ allowRelative: ["./"],
87
139
  },
88
140
  },
89
141
  });
90
142
  ```
91
143
 
92
- The result shape stays simple:
144
+ Result shape:
93
145
 
94
146
  ```ts
95
147
  {
96
148
  ok: boolean;
97
149
  warnings: number;
98
- errors: number;
150
+ failures: number;
99
151
  violations: Array<{
100
- rule: "max-file-lines" | "folderize-compound-files";
101
- severity: "warn" | "error";
152
+ rule: "max-file-lines" | "folderize-compound-files" | "sync-imports";
153
+ stop: boolean;
154
+ fix: boolean;
102
155
  filePath: string;
103
156
  message: string;
104
157
  details: Record<string, unknown>;
@@ -107,135 +160,76 @@ The result shape stays simple:
107
160
  }
108
161
  ```
109
162
 
110
- Checks are read-only. They report problems and let the caller decide whether to fail startup, fail CI, or only warn.
163
+ ## Folderization
111
164
 
112
- ## CLI And Config
165
+ `folderizeCompoundFiles` is structural. It no longer depends on configured suffix lists.
113
166
 
114
- The CLI is meant for repository-owned discipline rules:
167
+ Same-directory groups:
115
168
 
116
- ```sh
117
- code-discipline check
118
- code-discipline sync
169
+ ```txt
170
+ src/api/user_route.ts
171
+ src/api/user_schema.ts
172
+ src/api/user_controller.ts
119
173
  ```
120
174
 
121
- Both commands accept `--config <path>`. When that flag is omitted, the CLI looks for `code-discipline.config.json` in the current working directory.
175
+ suggest:
122
176
 
123
- Example config:
177
+ ```txt
178
+ src/api/user/route.ts
179
+ src/api/user/schema.ts
180
+ src/api/user/controller.ts
181
+ ```
124
182
 
125
- ```json
126
- {
127
- "sourceRoot": "src",
128
- "rules": {
129
- "maxFileLines": {
130
- "enabled": true,
131
- "max": 500
132
- },
133
- "folderizeCompoundFiles": {
134
- "enabled": true,
135
- "suffixes": ["start", "service"],
136
- "separators": ["_", "-"]
137
- },
138
- "syncImports": {
139
- "alias": {
140
- "strategy": "relative-path-slug"
141
- }
142
- }
143
- }
144
- }
183
+ Repeated folder-prefix files are also detected:
184
+
185
+ ```txt
186
+ src/api/user/user_route.ts
145
187
  ```
146
188
 
147
- `check` is read-only. It reports violations and exits non-zero when any error-severity rule fails.
189
+ suggests:
148
190
 
149
- `sync` is the mutating import-alignment command. It updates `compilerOptions.paths` and rewrites eligible source imports.
191
+ ```txt
192
+ src/api/user/route.ts
193
+ ```
150
194
 
151
- In practice:
195
+ `check` only reports these candidates.
152
196
 
153
- - use JSON config plus the CLI when the rules belong to the repo
154
- - use the API when another tool wants to control configuration or reporting dynamically
197
+ `fix` may apply them only when `folderizeCompoundFiles.fix` is `true`, and it rewrites affected relative imports after the move.
155
198
 
156
199
  ## Import Sync
157
200
 
158
- `syncImports()` remains a first-class part of the package. It scans source files, generates aliases, preserves still-valid alias ids, writes stable `compilerOptions.paths` output, and rewrites eligible relative imports to those aliases.
201
+ `syncImports()` and `code-discipline sync` use one flat `syncImports` config.
159
202
 
160
203
  ```ts
161
204
  import { syncImports } from "@trebired/code-discipline";
162
205
 
163
206
  const result = await syncImports({
164
207
  projectRoot: "/repo",
165
- sourceRoot: "src",
166
- tsconfigPath: "/repo/tsconfig.json",
167
- });
168
- ```
169
-
170
- Default behavior:
171
-
172
- - `sourceRoot: "src"`
173
- - `tsconfigPath: "<projectRoot>/tsconfig.json"`
174
- - `sourceExtensions: [".ts", ".tsx", ".js", ".jsx"]`
175
- - `excludeDirs: ["node_modules", "dist", ".vite"]`
176
- - `imports.rewrite: true`
177
- - `imports.keepRelative: ["./"]`
178
- - `alias.prefix: "#"`
179
- - `alias.strategy: "random"`
180
- - `alias.randomLength: 12`
181
-
182
- By default, same-directory imports such as `./local` stay relative. Imports that walk upward, such as `../shared/util`, are eligible for rewrite when they resolve to a file under the configured source root.
183
-
184
- Alias strategies:
185
-
186
- - `"random"`
187
- - `"relative-path-slug"`
188
- - `"relative-path-hash"`
189
- - custom function
190
-
191
- Example custom strategy:
192
-
193
- ```ts
194
- await syncImports({
195
- projectRoot: "/repo",
208
+ fix: true,
196
209
  alias: {
197
- strategy(input) {
198
- return `@${input.relativeFromSourceRoot.replace(/\//g, "__")}`;
199
- },
210
+ strategy: "relative-path-slug",
200
211
  },
212
+ allowRelative: ["./"],
201
213
  });
202
214
  ```
203
215
 
204
- If a custom strategy returns an invalid id or a duplicate id, the package fails clearly instead of guessing a fallback.
216
+ Behavior:
205
217
 
206
- ## Why This Package
207
-
208
- Many repository rules are awkward in a single-file linter pass because they are really about the shape of a source tree, not only one file at a time.
209
-
210
- This package is a better fit when you want checks such as:
218
+ - `check` reports import-policy drift in read-only mode
219
+ - `sync` rewrites imports and updates `tsconfig.json` only when `syncImports.fix` is `true`
220
+ - `allowRelative: ["./"]` keeps same-folder relative imports
221
+ - upward relative imports can be reported or rewritten through the configured alias policy
211
222
 
212
- - file-size limits across a configured source root
213
- - filename patterns that imply a cleaner folder layout
214
- - path alias synchronization across many files
215
- - startup, CI, or pre-build gates based on repository conventions
223
+ ## Logging
216
224
 
217
- It is intentionally repo-oriented. The package does one lane of work: codebase discipline plus import alignment.
225
+ When you provide a logger, discipline results are emitted through it. Trebired-style loggers are supported directly, and the package falls back safely when no logger is provided.
218
226
 
219
- ## Current API
227
+ ## Public API
220
228
 
221
229
  - `checkCodeDiscipline()`
230
+ - `fixCodeDiscipline()`
231
+ - `syncImports()`
222
232
  - `defineCodeDisciplineConfig()`
223
233
  - `loadCodeDisciplineConfig()`
224
- - `syncImports()`
225
- - `scanSourceFiles()`
226
- - `syncTsconfigAliases()`
227
- - `rewriteSourceImports()`
228
- - `resolveRelativeImport()`
229
- - `createRandomAlias()`
230
- - `createRelativePathHashAlias()`
231
- - `createRelativePathSlugAlias()`
232
-
233
- The package also exports the public TypeScript types for options, results, violations, alias strategies, keep-relative callbacks, log events, and source scan rows.
234
-
235
- ## What This Package Does Not Do
236
-
237
- - it does not build or compile TypeScript
238
- - it does not move files automatically for folderization violations
239
- - it does not rewrite emitted build output
240
- - it does not depend on ESLint
241
- - it does not assume a framework-specific runtime layout
234
+
235
+ The package also exports the public TypeScript types for rule config, results, violations, alias strategies, logging adapters, and source scan rows.
@@ -0,0 +1,17 @@
1
+ import type { CodeDisciplineViolation, FixCodeDisciplineResult, NormalizedCheckCodeDisciplineOptions } from "./types.js";
2
+ import type { ScannedSourceFile } from "../imports/types.js";
3
+ import type { NormalizedSyncImportsLogger } from "../shared/logging-types.js";
4
+ type PlannedMove = {
5
+ fromAbsolutePath: string;
6
+ fromRelativePath: string;
7
+ toAbsolutePath: string;
8
+ toRelativePath: string;
9
+ };
10
+ declare function createFolderizationViolation(filePath: string, suggestedPath: string, options: NormalizedCheckCodeDisciplineOptions, details: Record<string, unknown>): CodeDisciplineViolation;
11
+ declare function buildMovePlan(sourceFiles: ScannedSourceFile[], options: NormalizedCheckCodeDisciplineOptions): {
12
+ moves: PlannedMove[];
13
+ violations: CodeDisciplineViolation[];
14
+ };
15
+ declare function fixFolderization(sourceFiles: ScannedSourceFile[], options: NormalizedCheckCodeDisciplineOptions, logger: NormalizedSyncImportsLogger): Promise<FixCodeDisciplineResult>;
16
+ export { buildMovePlan, createFolderizationViolation, fixFolderization };
17
+ //# sourceMappingURL=fix-folderization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix-folderization.d.ts","sourceRoot":"","sources":["../../src/checks/fix-folderization.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,uBAAuB,EACvB,uBAAuB,EACvB,oCAAoC,EACrC,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AAO9E,KAAK,WAAW,GAAG;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,iBAAS,4BAA4B,CACnC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,oCAAoC,EAC7C,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,uBAAuB,CAUzB;AAED,iBAAS,aAAa,CACpB,WAAW,EAAE,iBAAiB,EAAE,EAChC,OAAO,EAAE,oCAAoC,GAC5C;IAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IAAC,UAAU,EAAE,uBAAuB,EAAE,CAAA;CAAE,CAqBjE;AAsKD,iBAAe,gBAAgB,CAC7B,WAAW,EAAE,iBAAiB,EAAE,EAChC,OAAO,EAAE,oCAAoC,EAC7C,MAAM,EAAE,2BAA2B,GAClC,OAAO,CAAC,uBAAuB,CAAC,CAyElC;AAED,OAAO,EAAE,aAAa,EAAE,4BAA4B,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,223 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { applyTextReplacements, collectModuleSpecifiers } from "../imports/module-specifiers.js";
4
+ import { isRelativeImportSpecifier } from "../imports/resolve.js";
5
+ import { FileConflictError, FixFailureError, RewriteFailureError } from "../shared/errors.js";
6
+ import { ensureDotExtension, pathExists, stripKnownExtension, toPosixPath } from "../shared/utils.js";
7
+ import { planFolderizeCompoundFiles } from "./rules/folderize-plan.js";
8
+ function createFolderizationViolation(filePath, suggestedPath, options, details) {
9
+ return {
10
+ rule: "folderize-compound-files",
11
+ stop: options.rules.folderizeCompoundFiles.stop,
12
+ fix: options.rules.folderizeCompoundFiles.fix,
13
+ filePath,
14
+ message: `file can be grouped under ${suggestedPath}`,
15
+ suggestedPath,
16
+ details,
17
+ };
18
+ }
19
+ function buildMovePlan(sourceFiles, options) {
20
+ const candidates = planFolderizeCompoundFiles(sourceFiles, options);
21
+ const moves = candidates.map((candidate) => ({
22
+ fromAbsolutePath: candidate.absolutePath,
23
+ fromRelativePath: candidate.relativeFromProjectRoot,
24
+ toAbsolutePath: candidate.suggestedAbsolutePath,
25
+ toRelativePath: candidate.suggestedPath,
26
+ }));
27
+ const violations = candidates.map((candidate) => createFolderizationViolation(candidate.relativeFromProjectRoot, candidate.suggestedPath, options, {
28
+ mode: candidate.mode,
29
+ prefix: candidate.prefix,
30
+ remainder: candidate.remainder,
31
+ separator: candidate.separator,
32
+ }));
33
+ return { moves, violations };
34
+ }
35
+ function resolveRelativeFromInventory(specifier, sourceFilePath, sourceExtensions, knownFiles) {
36
+ if (!isRelativeImportSpecifier(specifier))
37
+ return null;
38
+ const basePath = path.resolve(path.dirname(sourceFilePath), specifier);
39
+ const exactCandidates = [basePath];
40
+ const fileCandidates = sourceExtensions.map((extension) => `${basePath}${ensureDotExtension(extension)}`);
41
+ const indexCandidates = sourceExtensions.map((extension) => path.join(basePath, `index${ensureDotExtension(extension)}`));
42
+ for (const candidate of [...exactCandidates, ...fileCandidates, ...indexCandidates]) {
43
+ if (knownFiles.has(candidate)) {
44
+ return candidate;
45
+ }
46
+ }
47
+ return null;
48
+ }
49
+ function formatRelativeSpecifier(originalSpecifier, fromAbsolutePath, toAbsolutePath, sourceExtensions) {
50
+ const hadExplicitExtension = sourceExtensions.some((extension) => originalSpecifier.toLowerCase().endsWith(extension.toLowerCase()));
51
+ let relativePath = toPosixPath(path.relative(path.dirname(fromAbsolutePath), toAbsolutePath));
52
+ if (!relativePath.startsWith("."))
53
+ relativePath = `./${relativePath}`;
54
+ if (hadExplicitExtension) {
55
+ return relativePath;
56
+ }
57
+ const withoutExtension = stripKnownExtension(relativePath, sourceExtensions);
58
+ const targetBasename = path.basename(toAbsolutePath);
59
+ const originalWithoutExtension = stripKnownExtension(originalSpecifier, sourceExtensions);
60
+ const originalUsesIndex = /(^|\/)index$/.test(originalWithoutExtension);
61
+ if (targetBasename.startsWith("index.") && !originalUsesIndex) {
62
+ return withoutExtension.replace(/\/index$/, "") || ".";
63
+ }
64
+ return withoutExtension;
65
+ }
66
+ async function planImportRewritesForFolderizationMoves(sourceFiles, moves, options) {
67
+ if (moves.length === 0) {
68
+ return {
69
+ rewrittenByPath: new Map(),
70
+ rewrittenFiles: 0,
71
+ rewrittenImports: 0,
72
+ };
73
+ }
74
+ const movedPaths = new Map(moves.map((move) => [move.fromAbsolutePath, move.toAbsolutePath]));
75
+ const knownFiles = new Set(sourceFiles.map((file) => file.absolutePath));
76
+ const rewrittenByPath = new Map();
77
+ let rewrittenFiles = 0;
78
+ let rewrittenImports = 0;
79
+ for (const file of sourceFiles) {
80
+ const originalText = await fs.readFile(file.absolutePath, "utf8");
81
+ const futureFilePath = movedPaths.get(file.absolutePath) ?? file.absolutePath;
82
+ const replacements = [];
83
+ try {
84
+ for (const occurrence of collectModuleSpecifiers(originalText, file.absolutePath)) {
85
+ if (!isRelativeImportSpecifier(occurrence.specifier))
86
+ continue;
87
+ const resolvedTarget = resolveRelativeFromInventory(occurrence.specifier, file.absolutePath, options.sourceExtensions, knownFiles);
88
+ if (!resolvedTarget)
89
+ continue;
90
+ const futureTargetPath = movedPaths.get(resolvedTarget) ?? resolvedTarget;
91
+ const sourceMoved = futureFilePath !== file.absolutePath;
92
+ const targetMoved = futureTargetPath !== resolvedTarget;
93
+ if (!sourceMoved && !targetMoved)
94
+ continue;
95
+ replacements.push({
96
+ start: occurrence.start,
97
+ end: occurrence.end,
98
+ value: formatRelativeSpecifier(occurrence.specifier, futureFilePath, futureTargetPath, options.sourceExtensions),
99
+ });
100
+ }
101
+ }
102
+ catch (error) {
103
+ throw new RewriteFailureError(file.relativeFromProjectRoot, error);
104
+ }
105
+ const next = applyTextReplacements(originalText, replacements);
106
+ if (next.count === 0)
107
+ continue;
108
+ rewrittenByPath.set(futureFilePath, next);
109
+ rewrittenFiles += 1;
110
+ rewrittenImports += next.count;
111
+ }
112
+ return { rewrittenByPath, rewrittenFiles, rewrittenImports };
113
+ }
114
+ async function validateMovePlan(moves) {
115
+ const seenTargets = new Set();
116
+ const movingFrom = new Set(moves.map((move) => move.fromAbsolutePath));
117
+ for (const move of moves) {
118
+ if (move.fromAbsolutePath === move.toAbsolutePath)
119
+ continue;
120
+ if (seenTargets.has(move.toAbsolutePath)) {
121
+ throw new FileConflictError(move.toRelativePath, {
122
+ reason: "duplicate-target",
123
+ });
124
+ }
125
+ seenTargets.add(move.toAbsolutePath);
126
+ if (movingFrom.has(move.toAbsolutePath))
127
+ continue;
128
+ if (await pathExists(move.toAbsolutePath)) {
129
+ throw new FileConflictError(move.toRelativePath, {
130
+ reason: "existing-target",
131
+ });
132
+ }
133
+ }
134
+ }
135
+ async function moveFiles(moves) {
136
+ let movedFiles = 0;
137
+ for (const move of moves) {
138
+ if (move.fromAbsolutePath === move.toAbsolutePath)
139
+ continue;
140
+ await fs.mkdir(path.dirname(move.toAbsolutePath), { recursive: true });
141
+ await fs.rename(move.fromAbsolutePath, move.toAbsolutePath);
142
+ movedFiles += 1;
143
+ }
144
+ return movedFiles;
145
+ }
146
+ async function removeEmptyDirectories(directories) {
147
+ const sorted = [...new Set(directories)].sort((left, right) => right.length - left.length);
148
+ for (const directoryPath of sorted) {
149
+ try {
150
+ await fs.rmdir(directoryPath);
151
+ }
152
+ catch {
153
+ // The directory still contains files, which is expected in many cases.
154
+ }
155
+ }
156
+ }
157
+ async function fixFolderization(sourceFiles, options, logger) {
158
+ const { moves, violations } = buildMovePlan(sourceFiles, options);
159
+ const warnings = violations.filter((violation) => !violation.stop).length;
160
+ const failures = violations.length - warnings;
161
+ if (!options.rules.folderizeCompoundFiles.enabled || moves.length === 0) {
162
+ logger.info("fix-folderization-unchanged", "no folderization moves required", {
163
+ moves: 0,
164
+ });
165
+ return {
166
+ ok: true,
167
+ moved_files: 0,
168
+ rewritten_files: 0,
169
+ rewritten_imports: 0,
170
+ warnings,
171
+ failures,
172
+ violations,
173
+ };
174
+ }
175
+ if (!options.rules.folderizeCompoundFiles.fix) {
176
+ logger.warn("fix-folderization-disabled", "folderization fix is disabled", {
177
+ candidates: moves.length,
178
+ });
179
+ return {
180
+ ok: failures === 0,
181
+ moved_files: 0,
182
+ rewritten_files: 0,
183
+ rewritten_imports: 0,
184
+ warnings,
185
+ failures,
186
+ violations,
187
+ };
188
+ }
189
+ await validateMovePlan(moves);
190
+ const sourceDirectories = moves.map((move) => path.dirname(move.fromAbsolutePath));
191
+ try {
192
+ const rewriteState = await planImportRewritesForFolderizationMoves(sourceFiles, moves, options);
193
+ const movedFiles = await moveFiles(moves);
194
+ for (const [filePath, next] of rewriteState.rewrittenByPath) {
195
+ await fs.writeFile(filePath, next.text);
196
+ }
197
+ await removeEmptyDirectories(sourceDirectories);
198
+ logger.success("fix-folderization-finished", `folderized files=${movedFiles} rewrittenImports=${rewriteState.rewrittenImports}`, {
199
+ movedFiles,
200
+ rewrittenFiles: rewriteState.rewrittenFiles,
201
+ rewrittenImports: rewriteState.rewrittenImports,
202
+ });
203
+ return {
204
+ ok: true,
205
+ moved_files: movedFiles,
206
+ rewritten_files: rewriteState.rewrittenFiles,
207
+ rewritten_imports: rewriteState.rewrittenImports,
208
+ warnings,
209
+ failures: 0,
210
+ violations,
211
+ };
212
+ }
213
+ catch (error) {
214
+ if (error instanceof FileConflictError || error instanceof RewriteFailureError) {
215
+ throw error;
216
+ }
217
+ throw new FixFailureError("Folderization fix failed", {
218
+ cause: error instanceof Error ? error.message : String(error),
219
+ });
220
+ }
221
+ }
222
+ export { buildMovePlan, createFolderizationViolation, fixFolderization };
223
+ //# sourceMappingURL=fix-folderization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix-folderization.js","sourceRoot":"","sources":["../../src/checks/fix-folderization.ts"],"names":[],"mappings":"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"}