@theglitchking/gimme-the-lint 2.4.0 → 2.5.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.
@@ -6,13 +6,13 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Official marketplace for gimme-the-lint - polyglot progressive linting with per-app baselines and drift detection",
9
- "version": "2.4.0"
9
+ "version": "2.5.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "gimme-the-lint",
14
14
  "description": "Polyglot progressive linting for monorepos — adapter-driven baselines, per-app drift detection, and idempotent skips across JavaScript/TypeScript, Python, Go, Rust, Terraform, and Ansible.",
15
- "version": "2.4.0",
15
+ "version": "2.5.0",
16
16
  "author": {
17
17
  "name": "TheGlitchKing"
18
18
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gimme-the-lint",
3
3
  "description": "Polyglot progressive linting for monorepos — adapter-driven baselines, per-app drift detection, and idempotent skips across JavaScript/TypeScript, Python, Go, Rust, Terraform, and Ansible.",
4
- "version": "2.4.0",
4
+ "version": "2.5.0",
5
5
  "author": {
6
6
  "name": "TheGlitchKing",
7
7
  "email": "theglitchking@users.noreply.github.com"
package/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.5.0] - 2026-05-17
9
+
10
+ ### Added
11
+ - **`.gtl/config.js` — consolidated config location.** gimme-the-lint's own
12
+ config file now lives canonically at `.gtl/config.js` (`.gtl/config.cjs` for
13
+ ESM projects), so it travels with the committed `.gtl/` baselines rather than
14
+ sitting loose at the repo root. `install` and `migrate` write new configs
15
+ there; `config-manager.findConfig()` resolves the location. A repo-root
16
+ `gimme-the-lint.config.js` is still read as a fallback — existing projects
17
+ need no change — and `.gtl/` wins when both exist. `uninstall` removes a
18
+ repo-root config but leaves a `.gtl/` one in place (it is part of the
19
+ preserved `.gtl/` directory). Linter configs (`eslint.config.js`,
20
+ `.tflint.hcl`, …) are unaffected: each linter resolves its own config from a
21
+ fixed location gimme-the-lint does not own.
22
+
8
23
  ## [2.4.0] - 2026-05-17
9
24
 
10
25
  ### Fixed
package/README.md CHANGED
@@ -173,7 +173,10 @@ git add -A && git commit -m "…" # retry
173
173
  ## Configuration
174
174
 
175
175
  Zero config is the default — apps and their linters are auto-detected. To
176
- override, add `gimme-the-lint.config.js` at the repo root:
176
+ override, add a config file. The canonical location is **`.gtl/config.js`**
177
+ (it travels with the committed `.gtl/` baselines); a repo-root
178
+ `gimme-the-lint.config.js` is also read, for back-compatibility. `install` and
179
+ `migrate` write new configs to `.gtl/`:
177
180
 
178
181
  ```js
179
182
  module.exports = {
@@ -86,6 +86,7 @@ program
86
86
  .action(async () => {
87
87
  const chalk = require('chalk');
88
88
  const gitHooksManager = require('../lib/git-hooks-manager');
89
+ const configManager = require('../lib/config-manager');
89
90
 
90
91
  console.log(chalk.blue('\ngimme-the-lint: Uninstalling...\n'));
91
92
 
@@ -94,10 +95,17 @@ program
94
95
  console.log(chalk.green(` ✓ Removed git hooks: ${removed.join(', ')}`));
95
96
  }
96
97
 
97
- const configPath = path.join(process.cwd(), 'gimme-the-lint.config.js');
98
- if (fs.existsSync(configPath)) {
99
- fs.unlinkSync(configPath);
100
- console.log(chalk.green(' ✓ Removed gimme-the-lint.config.js'));
98
+ // Remove a repo-root config. A config under .gtl/ is left in place — it is
99
+ // part of the .gtl/ directory, which uninstall preserves.
100
+ const cfgPath = configManager.findConfig(process.cwd());
101
+ if (cfgPath) {
102
+ const rel = path.relative(process.cwd(), cfgPath);
103
+ if (rel.startsWith(`.gtl${path.sep}`)) {
104
+ console.log(chalk.dim(` · Config kept (${rel} — part of .gtl/)`));
105
+ } else {
106
+ fs.unlinkSync(cfgPath);
107
+ console.log(chalk.green(` ✓ Removed ${path.basename(cfgPath)}`));
108
+ }
101
109
  }
102
110
 
103
111
  console.log(chalk.green('\n✓ Uninstall complete.\n'));
@@ -372,7 +380,7 @@ program
372
380
  const projectType = await configManager.detectProjectType(projectRoot);
373
381
  const venvStatus = venvManager.getStatus(projectRoot);
374
382
  const hookStatus = await gitHooksManager.getStatus(projectRoot);
375
- const configExists = fs.existsSync(path.join(projectRoot, 'gimme-the-lint.config.js'));
383
+ const configExists = Boolean(configManager.findConfig(projectRoot));
376
384
 
377
385
  console.log(chalk.blue('\ngimme-the-lint Status\n'));
378
386
  console.log(` Project type: ${projectType}`);
@@ -38,9 +38,49 @@ async function detectProjectType(projectRoot) {
38
38
  return 'unknown';
39
39
  }
40
40
 
41
+ // Config-file lookup order. The .gtl/ location is canonical — the config
42
+ // travels with the committed .gtl/ baselines and keeps gimme-the-lint's own
43
+ // files in one place. The repo-root location is retained as a fallback so
44
+ // projects created before this layout keep working with no change.
45
+ const CONFIG_CANDIDATES = [
46
+ ['.gtl', 'config.cjs'],
47
+ ['.gtl', 'config.js'],
48
+ ['gimme-the-lint.config.cjs'],
49
+ ['gimme-the-lint.config.js'],
50
+ ];
51
+
52
+ /** Absolute path of the project's gimme-the-lint config, or null if none. */
53
+ function findConfig(projectRoot) {
54
+ for (const parts of CONFIG_CANDIDATES) {
55
+ const candidate = path.join(projectRoot, ...parts);
56
+ if (fs.existsSync(candidate)) return candidate;
57
+ }
58
+ return null;
59
+ }
60
+
61
+ /** Is the target project ESM ("type": "module" in package.json)? */
62
+ async function isESMProject(projectRoot) {
63
+ const pkgPath = path.join(projectRoot, 'package.json');
64
+ if (await fs.pathExists(pkgPath)) {
65
+ try {
66
+ return JSON.parse(await fs.readFile(pkgPath, 'utf8')).type === 'module';
67
+ } catch {
68
+ /* unreadable package.json — treat as CommonJS */
69
+ }
70
+ }
71
+ return false;
72
+ }
73
+
74
+ /**
75
+ * Where a NEW config is written — the canonical .gtl/ location. ESM projects
76
+ * get a .cjs extension so `require()` of the CommonJS config still works.
77
+ */
78
+ async function newConfigPath(projectRoot) {
79
+ const esm = await isESMProject(projectRoot);
80
+ return path.join(projectRoot, '.gtl', esm ? 'config.cjs' : 'config.js');
81
+ }
82
+
41
83
  function getConfig(projectRoot) {
42
- const cjsPath = path.join(projectRoot, 'gimme-the-lint.config.cjs');
43
- const jsPath = path.join(projectRoot, 'gimme-the-lint.config.js');
44
84
  const defaults = {
45
85
  frontendDir: 'frontend',
46
86
  backendDir: 'backend',
@@ -52,7 +92,7 @@ function getConfig(projectRoot) {
52
92
  testExcludedBackend: ['tests', '*test*', '__pycache__'],
53
93
  };
54
94
 
55
- const configPath = fs.existsSync(cjsPath) ? cjsPath : fs.existsSync(jsPath) ? jsPath : null;
95
+ const configPath = findConfig(projectRoot);
56
96
  if (configPath) {
57
97
  try {
58
98
  const userConfig = require(configPath);
@@ -66,26 +106,19 @@ function getConfig(projectRoot) {
66
106
  }
67
107
 
68
108
  async function initConfig(projectRoot, options = {}) {
69
- const cjsPath = path.join(projectRoot, 'gimme-the-lint.config.cjs');
70
- const jsPath = path.join(projectRoot, 'gimme-the-lint.config.js');
109
+ const existing = findConfig(projectRoot);
71
110
 
72
- // Don't overwrite if either variant already exists
73
- if (!options.force && (await fs.pathExists(cjsPath) || await fs.pathExists(jsPath))) {
74
- const existing = await fs.pathExists(cjsPath) ? cjsPath : jsPath;
111
+ // Don't overwrite an existing config (wherever it lives).
112
+ if (!options.force && existing) {
75
113
  return { created: false, path: existing };
76
114
  }
77
115
 
78
- // Detect if the target project uses ESM ("type": "module" in package.json)
79
- let isESM = false;
80
- const pkgPath = path.join(projectRoot, 'package.json');
81
- if (await fs.pathExists(pkgPath)) {
82
- try {
83
- const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
84
- isESM = pkg.type === 'module';
85
- } catch {}
86
- }
116
+ // New config the canonical .gtl/ location. --force overwrites in place,
117
+ // so a legacy repo-root config is never silently shadowed by a .gtl/ one.
118
+ const configPath = existing && options.force
119
+ ? existing
120
+ : await newConfigPath(projectRoot);
87
121
 
88
- const configPath = isESM ? cjsPath : jsPath;
89
122
  const projectType = await detectProjectType(projectRoot);
90
123
  const config = {
91
124
  projectType,
@@ -100,36 +133,26 @@ async function initConfig(projectRoot, options = {}) {
100
133
  module.exports = ${JSON.stringify(config, null, 2)};
101
134
  `;
102
135
 
136
+ await fs.ensureDir(path.dirname(configPath));
103
137
  await fs.writeFile(configPath, content, 'utf8');
104
138
  return { created: true, path: configPath, projectType };
105
139
  }
106
140
 
107
141
  /**
108
- * Write an explicit `apps` map into gimme-the-lint.config.js so the discovered
109
- * app/linter layout is visible and editable instead of silently re-guessed on
110
- * every run. Never overwrites an existing config.
142
+ * Write an explicit `apps` map into the gimme-the-lint config so the
143
+ * discovered app/linter layout is visible and editable instead of silently
144
+ * re-guessed on every run. Never overwrites an existing config; a new one is
145
+ * written to the canonical .gtl/ location.
111
146
  * @param {string} projectRoot
112
147
  * @param {{appPath: string, linters: string[]}[]} apps
113
148
  */
114
149
  async function writeAppsConfig(projectRoot, apps) {
115
- const cjsPath = path.join(projectRoot, 'gimme-the-lint.config.cjs');
116
- const jsPath = path.join(projectRoot, 'gimme-the-lint.config.js');
117
- if ((await fs.pathExists(cjsPath)) || (await fs.pathExists(jsPath))) {
118
- return {
119
- created: false,
120
- path: (await fs.pathExists(cjsPath)) ? cjsPath : jsPath,
121
- };
122
- }
123
-
124
- let isESM = false;
125
- const pkgPath = path.join(projectRoot, 'package.json');
126
- if (await fs.pathExists(pkgPath)) {
127
- try {
128
- isESM = JSON.parse(await fs.readFile(pkgPath, 'utf8')).type === 'module';
129
- } catch {}
150
+ const existing = findConfig(projectRoot);
151
+ if (existing) {
152
+ return { created: false, path: existing };
130
153
  }
131
- const configPath = isESM ? cjsPath : jsPath;
132
154
 
155
+ const configPath = await newConfigPath(projectRoot);
133
156
  const appsObj = {};
134
157
  for (const app of apps || []) {
135
158
  appsObj[app.appPath] = { linters: app.linters };
@@ -139,6 +162,7 @@ async function writeAppsConfig(projectRoot, apps) {
139
162
  // Edit this map to correct any mis-discovered apps or linters.
140
163
  module.exports = ${JSON.stringify({ apps: appsObj }, null, 2)};
141
164
  `;
165
+ await fs.ensureDir(path.dirname(configPath));
142
166
  await fs.writeFile(configPath, content, 'utf8');
143
167
  return { created: true, path: configPath, apps: appsObj };
144
168
  }
@@ -146,6 +170,7 @@ module.exports = ${JSON.stringify({ apps: appsObj }, null, 2)};
146
170
  module.exports = {
147
171
  copyTemplate,
148
172
  detectProjectType,
173
+ findConfig,
149
174
  getConfig,
150
175
  initConfig,
151
176
  writeAppsConfig,
package/lib/installer.js CHANGED
@@ -33,7 +33,11 @@ async function init(projectRoot, options = {}) {
33
33
  frontendDir: options.frontendDir,
34
34
  backendDir: options.backendDir,
35
35
  });
36
- results.steps.push(configResult.created ? `Created ${path.basename(configResult.path)}` : 'Config already exists');
36
+ results.steps.push(
37
+ configResult.created
38
+ ? `Created ${path.relative(projectRoot, configResult.path)}`
39
+ : `Config already exists (${path.relative(projectRoot, configResult.path)})`
40
+ );
37
41
 
38
42
  // Step 3: Copy ESLint template (frontend)
39
43
  if (enableFrontend) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theglitchking/gimme-the-lint",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Polyglot progressive linting for monorepos — adapter-driven baselines, per-app drift detection, and idempotent skips across JavaScript/TypeScript, Python, Go, Rust, Terraform, and Ansible.",
5
5
  "keywords": [
6
6
  "linting",