@itcase/config 1.6.84 → 1.6.86

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 CHANGED
@@ -25,6 +25,7 @@ npm install --save-dev @itcase/config
25
25
  - **Lint-staged** — запуск линтеров для измененных файлов
26
26
  - `lint-staged/index.js` — базовая конфигурация
27
27
  - `lint-staged-ts/index.js` — с проверкой типов TypeScript
28
+ - обе конфигурации блокируют коммит, если в `package.json` есть локальные зависимости `file:` / `link:`
28
29
 
29
30
  - **PostCSS** — обработка CSS
30
31
  - `postcss/index.js` — стандартная конфигурация
@@ -0,0 +1,130 @@
1
+ import { readdirSync, readFileSync } from 'node:fs'
2
+ import { join, relative } from 'node:path'
3
+
4
+ const FORBIDDEN_SPEC = /^(file:|link:)/i
5
+
6
+ const DEPENDENCY_KEYS = [
7
+ 'dependencies',
8
+ 'devDependencies',
9
+ 'optionalDependencies',
10
+ 'peerDependencies',
11
+ 'overrides',
12
+ 'resolutions',
13
+ ]
14
+
15
+ const SKIP_DIRS = new Set([
16
+ 'node_modules',
17
+ 'dist',
18
+ 'build',
19
+ 'coverage',
20
+ '.git',
21
+ '.next',
22
+ '.turbo',
23
+ 'storybook-static',
24
+ ])
25
+
26
+ function shouldSkipDir(name) {
27
+ return SKIP_DIRS.has(name) || name.startsWith('.')
28
+ }
29
+
30
+ function findPackageJsonFiles(directory, results = []) {
31
+ let entries
32
+ try {
33
+ entries = readdirSync(directory, { withFileTypes: true })
34
+ } catch {
35
+ return results
36
+ }
37
+
38
+ for (const entry of entries) {
39
+ const fullPath = join(directory, entry.name)
40
+ if (entry.isDirectory()) {
41
+ if (shouldSkipDir(entry.name)) continue
42
+ findPackageJsonFiles(fullPath, results)
43
+ continue
44
+ }
45
+ if (entry.isFile() && entry.name === 'package.json') {
46
+ results.push(fullPath)
47
+ }
48
+ }
49
+
50
+ return results
51
+ }
52
+
53
+ function readPackageJson(file) {
54
+ let raw
55
+ try {
56
+ raw = readFileSync(file, 'utf8')
57
+ } catch {
58
+ return null
59
+ }
60
+
61
+ try {
62
+ return JSON.parse(raw)
63
+ } catch {
64
+ return null
65
+ }
66
+ }
67
+
68
+ function formatLocator(parts) {
69
+ const [section, ...keys] = parts
70
+ return section + keys.map((key) => `[${JSON.stringify(key)}]`).join('')
71
+ }
72
+
73
+ function scanValue(value, parts, hits) {
74
+ if (typeof value === 'string') {
75
+ if (FORBIDDEN_SPEC.test(value.trim())) {
76
+ hits.push({ locator: formatLocator(parts), spec: value })
77
+ }
78
+ return
79
+ }
80
+
81
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return
82
+
83
+ for (const [key, nested] of Object.entries(value)) {
84
+ scanValue(nested, [...parts, key], hits)
85
+ }
86
+ }
87
+
88
+ function collectHits(pkg) {
89
+ const hits = []
90
+ for (const key of DEPENDENCY_KEYS) {
91
+ if (pkg[key] == null) continue
92
+ scanValue(pkg[key], [key], hits)
93
+ }
94
+ return hits
95
+ }
96
+
97
+ const cliFiles = process.argv.slice(2)
98
+ const files = cliFiles.length ? cliFiles : findPackageJsonFiles(process.cwd())
99
+
100
+ const hitsByFile = []
101
+
102
+ for (const file of files) {
103
+ const pkg = readPackageJson(file)
104
+ if (!pkg || typeof pkg !== 'object' || Array.isArray(pkg)) continue
105
+
106
+ const hits = collectHits(pkg)
107
+ if (hits.length) {
108
+ hitsByFile.push({ file, hits })
109
+ }
110
+ }
111
+
112
+ if (!hitsByFile.length) {
113
+ process.exit(0)
114
+ }
115
+
116
+ const lines = [
117
+ '❌ Локальные зависимости file: / link: нельзя коммитить.',
118
+ ' Замените их на версию из npm registry.',
119
+ '',
120
+ ]
121
+
122
+ for (const { file, hits } of hitsByFile) {
123
+ lines.push(` ${relative(process.cwd(), file)}`)
124
+ for (const { locator, spec } of hits) {
125
+ lines.push(` ${locator}: ${spec}`)
126
+ }
127
+ }
128
+
129
+ process.stderr.write(`${lines.join('\n')}\n`)
130
+ process.exit(1)
@@ -1,7 +1,16 @@
1
1
  import { existsSync } from 'node:fs'
2
- import { resolve } from 'node:path'
2
+ import { dirname, join, resolve } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
3
4
 
5
+ const checkNoFileDependencies = join(
6
+ dirname(fileURLToPath(import.meta.url)),
7
+ 'checkNoFileDependencies.js',
8
+ )
9
+
10
+ // eslint-disable-next-line import/no-default-export
4
11
  export default {
12
+ // На любой staged-файл: если в package.json есть file:/link: — exit 1, коммит не пройдёт
13
+ '*': () => `node ${JSON.stringify(checkNoFileDependencies)}`,
5
14
  '*.(js|jsx|ts|tsx)': (files) => {
6
15
  const existing = files.filter((file) => existsSync(file))
7
16
  if (!existing.length) return 'true'
@@ -22,9 +31,8 @@ export default {
22
31
  const rootPackageJson = resolve(process.cwd(), 'package.json')
23
32
  const isRoot = files.some((file) => resolve(file) === rootPackageJson)
24
33
  if (existsSync(rootPackageJson) && isRoot) {
25
- // lock пишется командой, но glob только package.json — без git add
26
- // он останется вне коммита (типичный leftover после bump deps)
27
- return ['npm install --package-lock-only', 'git add -- package-lock.json']
34
+ // lint-staged автоматически добавляет изменения задач в index
35
+ return 'npm install --package-lock-only'
28
36
  }
29
37
  return 'true' // Пропускаем, если не найден
30
38
  },
@@ -1,7 +1,16 @@
1
1
  import { existsSync } from 'node:fs'
2
- import { resolve } from 'node:path'
2
+ import { dirname, join, resolve } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
3
4
 
5
+ const checkNoFileDependencies = join(
6
+ dirname(fileURLToPath(import.meta.url)),
7
+ '../lint-staged/checkNoFileDependencies.js',
8
+ )
9
+
10
+ // eslint-disable-next-line import/no-default-export
4
11
  export default {
12
+ // На любой staged-файл: если в package.json есть file:/link: — exit 1, коммит не пройдёт
13
+ '*': () => `node ${JSON.stringify(checkNoFileDependencies)}`,
5
14
  '*.(ts|tsx)': (files) => {
6
15
  const existing = files.filter((file) => existsSync(file))
7
16
  const tsFiles = existing.filter((file) => /\.(ts|tsx)$/.test(file))
@@ -29,9 +38,8 @@ export default {
29
38
  const rootPackageJson = resolve(process.cwd(), 'package.json')
30
39
  const isRoot = files.some((file) => resolve(file) === rootPackageJson)
31
40
  if (existsSync(rootPackageJson) && isRoot) {
32
- // lock пишется командой, но glob только package.json — без git add
33
- // он останется вне коммита (типичный leftover после bump deps)
34
- return ['npm install --package-lock-only', 'git add -- package-lock.json']
41
+ // lint-staged автоматически добавляет изменения задач в index
42
+ return 'npm install --package-lock-only'
35
43
  }
36
44
  return 'true' // Пропускаем, если не найден
37
45
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itcase/config",
3
- "version": "1.6.84",
3
+ "version": "1.6.86",
4
4
  "author": "ITCase",
5
5
  "description": "ITCase Config",
6
6
  "engines": {