@nitra/cursor 1.17.0 → 1.17.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 +6 -0
- package/bin/n-cursor.js +16 -16
- package/package.json +2 -1
- package/rules/abie/fix.mjs +3 -3
- package/rules/adr/fix.mjs +3 -3
- package/rules/adr/js/hooks.mjs +6 -6
- package/rules/bun/fix.mjs +3 -3
- package/rules/capacitor/fix.mjs +3 -3
- package/rules/changelog/fix.mjs +3 -3
- package/rules/changelog/js/consistency.mjs +15 -15
- package/rules/ci4/fix.mjs +3 -3
- package/rules/docker/fix.mjs +3 -3
- package/rules/efes/fix.mjs +3 -3
- package/rules/feedback/fix.mjs +3 -3
- package/rules/ga/fix.mjs +3 -3
- package/rules/graphql/fix.mjs +3 -3
- package/rules/hasura/fix.mjs +3 -3
- package/rules/image-avif/fix.mjs +3 -3
- package/rules/image-compress/fix.mjs +3 -3
- package/rules/js-bun-db/fix.mjs +3 -3
- package/rules/js-bun-redis/fix.mjs +3 -3
- package/rules/js-lint/coverage/coverage.mjs +22 -20
- package/rules/js-lint/fix.mjs +3 -3
- package/rules/js-mssql/fix.mjs +3 -3
- package/rules/js-run/fix.mjs +3 -3
- package/rules/k8s/fix.mjs +3 -3
- package/rules/nginx-default-tpl/fix.mjs +3 -3
- package/rules/npm-module/fix.mjs +3 -3
- package/rules/php/fix.mjs +3 -3
- package/rules/rego/fix.mjs +3 -3
- package/rules/rust/coverage/coverage.mjs +19 -20
- package/rules/rust/fix.mjs +3 -3
- package/rules/rust/lib/has-cargo-toml.mjs +1 -3
- package/rules/security/fix.mjs +3 -3
- package/rules/style-lint/fix.mjs +3 -3
- package/rules/style-lint/js/tooling.mjs +1 -1
- package/rules/tauri/fix.mjs +3 -3
- package/rules/test/coverage/coverage.mjs +27 -25
- package/rules/test/fix.mjs +3 -3
- package/rules/test/js/location.mjs +1 -1
- package/rules/text/fix.mjs +3 -3
- package/rules/vue/fix.mjs +3 -3
- package/scripts/lib/run-rule-cli.mjs +11 -0
- package/scripts/lib/run-standard-rule.mjs +1 -1
- package/scripts/utils/with-lock.mjs +27 -16
- package/scripts/utils/worktree-fingerprint.mjs +10 -7
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Library-mode виклик з CLI orchestration — інше: див. `runStandardRule` + `fix.mjs::run(ctx)`.
|
|
8
8
|
*/
|
|
9
9
|
import { basename } from 'node:path'
|
|
10
|
+
import { pathToFileURL } from 'node:url'
|
|
10
11
|
|
|
11
12
|
import { isRuleEnabled, readNCursorConfigLite } from './read-n-cursor-config-lite.mjs'
|
|
12
13
|
import { runStandardRule } from './run-standard-rule.mjs'
|
|
@@ -14,6 +15,16 @@ import { getOrCreateWalkCache } from '../utils/walk-cache.mjs'
|
|
|
14
15
|
|
|
15
16
|
const PACKAGE_NAME = '@nitra/cursor'
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Чи поточний модуль запущено як CLI entry-point (`bun rules/<id>/fix.mjs`).
|
|
20
|
+
* @returns {boolean} true, коли `import.meta.url` збігається з `process.argv[1]`
|
|
21
|
+
*/
|
|
22
|
+
export function isRunAsCli() {
|
|
23
|
+
const entry = process.argv[1]
|
|
24
|
+
if (!entry) return false
|
|
25
|
+
return import.meta.url === pathToFileURL(entry).href
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
/**
|
|
18
29
|
* @param {string} ruleDir абсолютний шлях до `rules/<id>/`
|
|
19
30
|
* @returns {Promise<number>} 0 — OK або правило не enabled; 1 — порушення
|
|
@@ -32,7 +32,7 @@ import { withLock } from '../utils/with-lock.mjs'
|
|
|
32
32
|
* @param {RuleContext} [ctx] контекст прогону (walkCache тощо)
|
|
33
33
|
* @returns {Promise<number>} 0 OK, 1 violations
|
|
34
34
|
*/
|
|
35
|
-
export
|
|
35
|
+
export function runStandardRule(ruleDir, ctx = {}) {
|
|
36
36
|
const ruleId = basename(ruleDir)
|
|
37
37
|
const bundledRulesDir = dirname(ruleDir)
|
|
38
38
|
return withLock(`fix-${ruleId}`, async () => {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Алгоритм: mkdirSync-based lock, перевірка живості PID, sha256-dedup з TTL.
|
|
4
4
|
*/
|
|
5
5
|
import * as fs from 'node:fs'
|
|
6
|
-
import
|
|
6
|
+
import { join } from 'node:path'
|
|
7
7
|
import * as os from 'node:os'
|
|
8
8
|
import { setTimeout as sleep } from 'node:timers/promises'
|
|
9
9
|
import { worktreeFingerprint } from './worktree-fingerprint.mjs'
|
|
@@ -12,9 +12,14 @@ const DEFAULTS = {
|
|
|
12
12
|
ttl: 600_000,
|
|
13
13
|
staleThreshold: 1_800_000,
|
|
14
14
|
waitTimeout: 1_200_000,
|
|
15
|
-
pollInterval:
|
|
15
|
+
pollInterval: 1500
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Чи процес із заданим PID ще живий на поточному host.
|
|
20
|
+
* @param {number} pid ідентифікатор процесу з owner.json
|
|
21
|
+
* @returns {boolean} true, якщо process.kill(pid, 0) не кинув помилку
|
|
22
|
+
*/
|
|
18
23
|
function isAlive(pid) {
|
|
19
24
|
try {
|
|
20
25
|
process.kill(pid, 0)
|
|
@@ -24,14 +29,21 @@ function isAlive(pid) {
|
|
|
24
29
|
}
|
|
25
30
|
}
|
|
26
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Повертає функцію, що знімає lock-директорію.
|
|
34
|
+
* @param {string} lockDir абсолютний шлях до lock-директорії
|
|
35
|
+
* @returns {() => void} release-колбек для finally/signal handler
|
|
36
|
+
*/
|
|
27
37
|
function makeRelease(lockDir) {
|
|
28
38
|
return () => fs.rmSync(lockDir, { recursive: true, force: true })
|
|
29
39
|
}
|
|
30
40
|
|
|
31
41
|
/**
|
|
32
|
-
*
|
|
33
|
-
* @param {string|null}
|
|
34
|
-
* @param {
|
|
42
|
+
* Чи можна пропустити повторний прогін за кешованим result.json.
|
|
43
|
+
* @param {{exitCode:number, fingerprint:string|null, finishedAt:number}} result попередній результат з result.json
|
|
44
|
+
* @param {string|null} fingerprint поточний fingerprint робочого дерева
|
|
45
|
+
* @param {number} ttl TTL дедуплікації в мілісекундах
|
|
46
|
+
* @returns {boolean} true, якщо попередній успішний прогін можна повторно використати
|
|
35
47
|
*/
|
|
36
48
|
export function shouldDedup(result, fingerprint, ttl) {
|
|
37
49
|
if (result.exitCode !== 0) return false
|
|
@@ -41,27 +53,26 @@ export function shouldDedup(result, fingerprint, ttl) {
|
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
/**
|
|
44
|
-
*
|
|
45
|
-
* @param {
|
|
46
|
-
* @param {
|
|
47
|
-
* @
|
|
56
|
+
* Серіалізує важку команду через атомарний lock і dedup за fingerprint.
|
|
57
|
+
* @param {string} key ключ локу (наприклад `lint-ga`, `fix-bun`)
|
|
58
|
+
* @param {() => number | Promise<number>} runFn основна робота; повертає exit code
|
|
59
|
+
* @param {{ttl?:number, staleThreshold?:number, waitTimeout?:number, pollInterval?:number, cacheDir?:string, getFingerprint?:() => string | null}} [opts] TTL, шлях кешу та override fingerprint
|
|
60
|
+
* @returns {Promise<number>} exit code виконаної або дедуплікованої команди
|
|
48
61
|
*/
|
|
49
62
|
export async function withLock(key, runFn, opts = {}) {
|
|
50
63
|
const { ttl, staleThreshold, waitTimeout, pollInterval } = { ...DEFAULTS, ...opts }
|
|
51
64
|
const getFingerprint = opts.getFingerprint ?? worktreeFingerprint
|
|
52
|
-
const cacheDir = opts.cacheDir ??
|
|
53
|
-
const lockDir =
|
|
54
|
-
const ownerFile =
|
|
55
|
-
const resultFile =
|
|
65
|
+
const cacheDir = opts.cacheDir ?? join(process.cwd(), 'node_modules/.cache/n-cursor', key)
|
|
66
|
+
const lockDir = join(cacheDir, 'lock')
|
|
67
|
+
const ownerFile = join(lockDir, 'owner.json')
|
|
68
|
+
const resultFile = join(cacheDir, 'result.json')
|
|
56
69
|
const release = makeRelease(lockDir)
|
|
57
70
|
|
|
58
71
|
const fingerprint = getFingerprint()
|
|
59
72
|
fs.mkdirSync(cacheDir, { recursive: true })
|
|
60
73
|
|
|
61
74
|
const loopStart = Date.now()
|
|
62
|
-
let locked = false
|
|
63
75
|
|
|
64
|
-
// eslint-disable-next-line no-constant-condition
|
|
65
76
|
while (true) {
|
|
66
77
|
if (Date.now() - loopStart >= waitTimeout) {
|
|
67
78
|
console.error(`⚠️ ${key}: чекав ${waitTimeout / 60_000} хв — запускаю без локу`)
|
|
@@ -73,7 +84,6 @@ export async function withLock(key, runFn, opts = {}) {
|
|
|
73
84
|
ownerFile,
|
|
74
85
|
JSON.stringify({ pid: process.pid, host: os.hostname(), startedAt: Date.now(), fingerprint })
|
|
75
86
|
)
|
|
76
|
-
locked = true
|
|
77
87
|
break
|
|
78
88
|
} catch (error) {
|
|
79
89
|
if (error.code !== 'EEXIST') throw error
|
|
@@ -113,6 +123,7 @@ export async function withLock(key, runFn, opts = {}) {
|
|
|
113
123
|
|
|
114
124
|
const onSignal = () => {
|
|
115
125
|
release()
|
|
126
|
+
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit -- SIGINT/SIGTERM мають завершити процес із кодом 130
|
|
116
127
|
process.exit(130)
|
|
117
128
|
}
|
|
118
129
|
process.once('SIGINT', onSignal)
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fingerprint поточного стану git-робочого дерева.
|
|
3
|
-
* Повертає sha256-hex (64 символи) або null, якщо не в git-репо.
|
|
4
|
-
* @param {typeof import('child_process').spawnSync} spawn
|
|
5
|
-
*/
|
|
6
1
|
import { spawnSync } from 'node:child_process'
|
|
7
2
|
import { createHash } from 'node:crypto'
|
|
8
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Fingerprint поточного стану git-робочого дерева.
|
|
6
|
+
* @param {typeof import('child_process').spawnSync} [spawn] sync-виклик git (ін'єкція для тестів)
|
|
7
|
+
* @returns {string|null} sha256-hex (64 символи) або null, якщо не в git-репо
|
|
8
|
+
*/
|
|
9
9
|
export function worktreeFingerprint(spawn = spawnSync) {
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* @param {string[]} args аргументи підкоманди git
|
|
12
|
+
* @returns {string} stdout git-команди
|
|
13
|
+
*/
|
|
11
14
|
function git(args) {
|
|
12
15
|
const r = spawn('git', args, { encoding: 'utf8' })
|
|
13
16
|
if (r.status !== 0 || r.error) throw new Error(`git ${args[0]} failed`)
|
|
@@ -21,7 +24,7 @@ export function worktreeFingerprint(spawn = spawnSync) {
|
|
|
21
24
|
// повертаються у `"..."` формі, і `git hash-object` не знаходить файл → throw → fingerprint=null.
|
|
22
25
|
const untrackedRaw = git(['ls-files', '-z', '--others', '--exclude-standard'])
|
|
23
26
|
const untrackedFiles = untrackedRaw.split('\0').filter(Boolean)
|
|
24
|
-
const pairs = untrackedFiles.map(f => `${f}:${git(['hash-object', f]).trim()}`).
|
|
27
|
+
const pairs = untrackedFiles.map(f => `${f}:${git(['hash-object', f]).trim()}`).toSorted()
|
|
25
28
|
const raw = [commitHash, diffText, ...pairs].join('\n')
|
|
26
29
|
return createHash('sha256').update(raw).digest('hex')
|
|
27
30
|
} catch {
|