@nitra/cursor 3.18.2 → 3.20.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.
- package/CHANGELOG.md +24 -0
- package/bin/n-cursor.js +12 -0
- package/package.json +1 -1
- package/rules/docker/docker.mdc +3 -3
- package/rules/docker/js/lint.mjs +1 -1
- package/rules/docker/lib/docker-hadolint.mjs +27 -55
- package/rules/ga/lint/lint.mjs +18 -54
- package/rules/image-compress/meta.json +1 -1
- package/rules/k8s/lint/lint.mjs +3 -10
- package/rules/nginx-default-tpl/js/template.mjs +39 -1
- package/rules/nginx-default-tpl/nginx-default-tpl.mdc +3 -1
- package/rules/npm-module/js/package_structure.mjs +40 -9
- package/rules/npm-module/npm-module.mdc +1 -1
- package/rules/npm-module/policy/npm_publish_yml/target.json +1 -0
- package/rules/rego/lint/lint.mjs +10 -55
- package/rules/text/lint/lint.mjs +11 -40
- package/rules/worktree/policy/vscode_settings/target.json +5 -0
- package/rules/worktree/policy/vscode_settings/template/settings.json.snippet.json +8 -0
- package/rules/worktree/policy/zed_settings/target.json +5 -0
- package/rules/worktree/policy/zed_settings/template/settings.json.snippet.json +12 -0
- package/rules/worktree/worktree.mdc +52 -0
- package/schemas/target.json +5 -0
- package/scripts/lib/assert-project-root.mjs +74 -0
- package/scripts/lib/ensure-tool.mjs +352 -0
- package/scripts/lib/run-conftest-batch.mjs +6 -28
- package/scripts/lib/run-rule.mjs +61 -5
- package/scripts/lib/template.mjs +29 -3
- package/scripts/lib/worktree-notice.mjs +52 -1
- package/skills/fix/SKILL.md +4 -4
- package/types/bin/n-cursor.d.ts +1 -1
- package/rules/npm-module/policy/npm_publish_yml/npm_publish_yml.rego +0 -87
package/rules/rego/lint/lint.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Лінт Rego-полісі (`conftest.mdc` + `rego.mdc`):
|
|
3
|
-
* далі послідовно `opa check --strict`,
|
|
4
|
-
* `conftest verify` (для `*_test.rego`-файлів) якщо conftest у PATH.
|
|
2
|
+
* Лінт Rego-полісі (`conftest.mdc` + `rego.mdc`): `ensureTool` на `opa` і `regal`
|
|
3
|
+
* (авто-install per-platform або hard-fail), далі послідовно `opa check --strict`,
|
|
4
|
+
* `regal lint` і опційний `conftest verify` (для `*_test.rego`-файлів) якщо conftest у PATH.
|
|
5
5
|
*
|
|
6
6
|
* Чому два-три інструменти:
|
|
7
7
|
* - `opa check --strict` — компіляція з типами і строгим режимом (мертвий код, неоднозначні
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
* Якщо conftest відсутній у PATH — пропускаємо без помилки (тести опційні в локальному середовищі;
|
|
14
14
|
* у CI потрібно встановити conftest).
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* потрібен VS Code-розширенню `tsandall.opa` (LSP, format-on-save через
|
|
19
|
-
* `mdc/rego.mdc`.
|
|
16
|
+
* `opa`/`regal` резолвляться через `ensureTool` (PATH → кеш → авто-install brew/scoop/GitHub
|
|
17
|
+
* Release → hard-fail) — без них лінт мовчки злетів би з невиразним повідомленням від shell.
|
|
18
|
+
* `opa` додатково потрібен VS Code-розширенню `tsandall.opa` (LSP, format-on-save через
|
|
19
|
+
* `opa fmt`) — деталі в `mdc/rego.mdc`.
|
|
20
20
|
*
|
|
21
21
|
* Цілі лінту: `npm/rules/` (де живуть Rego-полісі пакета `@nitra/cursor` — у
|
|
22
22
|
* `npm/rules/<id>/policy/<concern>/`). Усі три інструменти приймають один шлях
|
|
@@ -31,47 +31,13 @@ import { existsSync } from 'node:fs'
|
|
|
31
31
|
import { resolve } from 'node:path'
|
|
32
32
|
|
|
33
33
|
import { isRunAsCli } from '../../../scripts/cli-entry.mjs'
|
|
34
|
+
import { ensureTool } from '../../../scripts/lib/ensure-tool.mjs'
|
|
34
35
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
35
36
|
import { runStandardLint } from '../../../scripts/lib/run-standard-lint.mjs'
|
|
36
37
|
|
|
37
38
|
/** Шляхи з Rego-полісі (відносно cwd). Існують не всі на ранніх стадіях — фільтруємо нижче. */
|
|
38
39
|
const LINT_TARGETS = ['npm/rules']
|
|
39
40
|
|
|
40
|
-
/**
|
|
41
|
-
* Друкує підказку зі встановлення `opa` (потрібен для `opa check --strict` і VS Code LSP).
|
|
42
|
-
* @returns {void}
|
|
43
|
-
*/
|
|
44
|
-
function printOpaInstallHints() {
|
|
45
|
-
process.stderr.write(
|
|
46
|
-
[
|
|
47
|
-
'❌ opa не знайдено в PATH.',
|
|
48
|
-
' Без нього не запускається `opa check --strict` (типи + мертвий код у *.rego),',
|
|
49
|
-
' і не працює VS Code-розширення `tsandall.opa` (LSP, format-on-save через opa fmt).',
|
|
50
|
-
' Встанови:',
|
|
51
|
-
' macOS: brew install opa',
|
|
52
|
-
' Universal: https://www.openpolicyagent.org/docs/latest/#1-download-opa',
|
|
53
|
-
''
|
|
54
|
-
].join('\n')
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Друкує підказку зі встановлення `regal`.
|
|
60
|
-
* @returns {void}
|
|
61
|
-
*/
|
|
62
|
-
function printRegalInstallHints() {
|
|
63
|
-
process.stderr.write(
|
|
64
|
-
[
|
|
65
|
-
'❌ regal не знайдено в PATH.',
|
|
66
|
-
' Без нього не перевіряється rego.v1 синтаксис у *.rego (правило `conftest`).',
|
|
67
|
-
' Встанови:',
|
|
68
|
-
' macOS: brew install regal',
|
|
69
|
-
' Universal: https://docs.styra.com/regal#installation',
|
|
70
|
-
''
|
|
71
|
-
].join('\n')
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
41
|
/**
|
|
76
42
|
* Запускає крок з відображенням команди користувачу. Stdout/stderr передаємо як є
|
|
77
43
|
* (`stdio: 'inherit'`), щоб виглядало як прямий виклик у shell.
|
|
@@ -101,19 +67,8 @@ function runStep(bin, args, cwd) {
|
|
|
101
67
|
*/
|
|
102
68
|
export function runLintRegoSteps(cwd = process.cwd()) {
|
|
103
69
|
const root = resolve(cwd)
|
|
104
|
-
const opa =
|
|
105
|
-
const regal =
|
|
106
|
-
|
|
107
|
-
let preflightOk = true
|
|
108
|
-
if (!opa) {
|
|
109
|
-
printOpaInstallHints()
|
|
110
|
-
preflightOk = false
|
|
111
|
-
}
|
|
112
|
-
if (!regal) {
|
|
113
|
-
printRegalInstallHints()
|
|
114
|
-
preflightOk = false
|
|
115
|
-
}
|
|
116
|
-
if (!preflightOk) return 1
|
|
70
|
+
const opa = ensureTool('opa')
|
|
71
|
+
const regal = ensureTool('regal')
|
|
117
72
|
|
|
118
73
|
const targets = LINT_TARGETS.filter(rel => existsSync(resolve(root, rel)))
|
|
119
74
|
if (targets.length === 0) {
|
package/rules/text/lint/lint.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI-обгортка над канонічним `lint-text` (text.mdc):
|
|
3
|
-
* (
|
|
2
|
+
* CLI-обгортка над канонічним `lint-text` (text.mdc): авто-встановлює `shellcheck` і `dotenv-linter`
|
|
3
|
+
* через `ensureTool` (brew/scoop/GitHub Release per-platform), перевіряє наявність `patch`
|
|
4
|
+
* (для авто-фіксу shellcheck); далі послідовно
|
|
4
5
|
* 1) `cspell .` — перевірка правопису з `@nitra/cspell-dict`;
|
|
5
6
|
* 2) `runShellcheckText()` — авто-фікс і фінальна перевірка `*.sh` через `shellcheck`;
|
|
6
7
|
* 3) `runDotenvLinter()` — авто-фікс і фінальна перевірка `.env*` через `dotenv-linter`;
|
|
@@ -9,7 +10,7 @@
|
|
|
9
10
|
*
|
|
10
11
|
* Без preflight локальний прогін може пройти cspell/markdownlint, а CI на ubuntu-latest
|
|
11
12
|
* (де shellcheck передвстановлений, але dotenv-linter — ні) падає на кроці dotenv-linter
|
|
12
|
-
* з неінформативним повідомленням.
|
|
13
|
+
* з неінформативним повідомленням. ensureTool збирає всі відсутні бінарники до першого кроку.
|
|
13
14
|
*
|
|
14
15
|
* Перший ненульовий код з ланцюжка повертається як код виходу; наступні кроки не запускаються.
|
|
15
16
|
* Експортовано як `runLintTextCli` — використовується з `bin/n-cursor.js` як підкоманда `lint-text`.
|
|
@@ -22,6 +23,7 @@ import { platform } from 'node:process'
|
|
|
22
23
|
import { runLintStep } from '../../../scripts/lib/run-lint-step.mjs'
|
|
23
24
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
24
25
|
import { runStandardLint } from '../../../scripts/lib/run-standard-lint.mjs'
|
|
26
|
+
import { ensureTool } from '../../../scripts/lib/ensure-tool.mjs'
|
|
25
27
|
import { runDotenvLinter } from './run-dotenv-linter.mjs'
|
|
26
28
|
import { runShellcheckText } from './run-shellcheck.mjs'
|
|
27
29
|
import { runV8rWithGlobs } from './run-v8r.mjs'
|
|
@@ -36,22 +38,6 @@ import { runV8rWithGlobs } from './run-v8r.mjs'
|
|
|
36
38
|
* @property {string} successMsg повідомлення на pass-шлях
|
|
37
39
|
*/
|
|
38
40
|
|
|
39
|
-
/** @type {PreflightDep} */
|
|
40
|
-
const SHELLCHECK_PREFLIGHT = {
|
|
41
|
-
bin: 'shellcheck',
|
|
42
|
-
winBins: ['shellcheck.exe'],
|
|
43
|
-
explanation: [
|
|
44
|
-
'Без нього `runShellcheckText()` пропускає перевірку tracked `*.sh` — локально lint-text',
|
|
45
|
-
'може бути зеленим, а CI (shellcheck + patch) падає на тих самих скриптах.'
|
|
46
|
-
].join('\n '),
|
|
47
|
-
install: [
|
|
48
|
-
'macOS: brew install shellcheck',
|
|
49
|
-
'Debian/Ubuntu: sudo apt-get install -y shellcheck',
|
|
50
|
-
'Arch: sudo pacman -S shellcheck'
|
|
51
|
-
],
|
|
52
|
-
successMsg: '✅ shellcheck знайдено в PATH — lint-text перевірить *.sh'
|
|
53
|
-
}
|
|
54
|
-
|
|
55
41
|
/** @type {PreflightDep} */
|
|
56
42
|
const PATCH_PREFLIGHT = {
|
|
57
43
|
bin: 'patch',
|
|
@@ -62,22 +48,6 @@ const PATCH_PREFLIGHT = {
|
|
|
62
48
|
successMsg: '✅ patch знайдено в PATH — shellcheck auto-fix працюватиме'
|
|
63
49
|
}
|
|
64
50
|
|
|
65
|
-
/** @type {PreflightDep} */
|
|
66
|
-
const DOTENV_LINTER_PREFLIGHT = {
|
|
67
|
-
bin: 'dotenv-linter',
|
|
68
|
-
winBins: ['dotenv-linter.exe'],
|
|
69
|
-
explanation: [
|
|
70
|
-
'Без нього не виконається крок `.env*` у lint-text — локально cspell/markdownlint',
|
|
71
|
-
'пройдуть, а CI без Install dotenv-linter впаде неінформативно.'
|
|
72
|
-
].join('\n '),
|
|
73
|
-
install: [
|
|
74
|
-
'macOS: brew install dotenv-linter',
|
|
75
|
-
'Linux: curl -sSfL https://git.io/JLbXn | sh -s -- -b /usr/local/bin',
|
|
76
|
-
'cargo: cargo install dotenv-linter'
|
|
77
|
-
],
|
|
78
|
-
successMsg: '✅ dotenv-linter знайдено в PATH — lint-text перевірить .env*'
|
|
79
|
-
}
|
|
80
|
-
|
|
81
51
|
/**
|
|
82
52
|
* Шукає шлях до бінарника `dep.bin` у `PATH`; на Windows додатково перебирає `dep.winBins`.
|
|
83
53
|
* @param {PreflightDep} dep опис залежності з canon-списку preflight-перевірок
|
|
@@ -128,11 +98,12 @@ function preflight(dep) {
|
|
|
128
98
|
* @returns {number} 0 — все OK, інакше — код першого кроку, що впав
|
|
129
99
|
*/
|
|
130
100
|
function runLintTextSteps() {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
101
|
+
// Auto-install: throws on failure → propagates as exit 1 from runStandardLint
|
|
102
|
+
ensureTool('shellcheck')
|
|
103
|
+
ensureTool('dotenv-linter')
|
|
104
|
+
|
|
105
|
+
// patch is hint-only (system tool)
|
|
106
|
+
if (!preflight(PATCH_PREFLIGHT)) return 1
|
|
136
107
|
|
|
137
108
|
const cspellCode = runLintStep('cspell', 'npx', ['cspell', '.'])
|
|
138
109
|
if (cspellCode !== 0) return cspellCode
|
|
@@ -20,6 +20,58 @@ alwaysApply: true
|
|
|
20
20
|
Слеш у гілці перетворюється на дефіс: `feat/skill-meta` → `.worktrees/feat-skill-meta/`.
|
|
21
21
|
Git-гілка лишається з оригінальним імʼям (`feat/skill-meta`).
|
|
22
22
|
|
|
23
|
+
## Налаштування редакторів
|
|
24
|
+
|
|
25
|
+
Worktree-каталоги — це вкладені копії всього дерева. Якщо редактор індексує їх,
|
|
26
|
+
пошук дає дублі, а file-watcher вантажить диск. Тому редактор має **виключити**
|
|
27
|
+
`.worktrees/` (а для Zed ще й приватний `.claude/worktrees/`) з пошуку та сканування.
|
|
28
|
+
|
|
29
|
+
Канон перевіряється **лише** якщо файл налаштувань уже є — проєктам без VS Code / Zed
|
|
30
|
+
нічого не нав'язується.
|
|
31
|
+
|
|
32
|
+
### VS Code
|
|
33
|
+
|
|
34
|
+
У `.vscode/settings.json` обовʼязково виключи `**/.worktrees/**` з пошуку і з дерева
|
|
35
|
+
файлів. `node_modules` додавати не треба — VS Code виключає його з пошуку дефолтно.
|
|
36
|
+
|
|
37
|
+
```json title=".vscode/settings.json"
|
|
38
|
+
{
|
|
39
|
+
"search.exclude": {
|
|
40
|
+
"**/.worktrees/**": true
|
|
41
|
+
},
|
|
42
|
+
"files.exclude": {
|
|
43
|
+
"**/.worktrees/**": true
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Канон (subset — інші ключі дозволені): [settings.json.snippet.json](./policy/vscode_settings/template/settings.json.snippet.json)
|
|
49
|
+
|
|
50
|
+
### Zed
|
|
51
|
+
|
|
52
|
+
Zed **замінює** масив `file_scan_exclusions` цілком (не зливає з дефолтами), тому в
|
|
53
|
+
`.zed/settings.json` перелічуй і дефолти Zed, і `**/.worktrees`, і `**/.claude/worktrees`.
|
|
54
|
+
Перевірка вимагає **всі** записи канону (бо інакше дефолти губляться).
|
|
55
|
+
|
|
56
|
+
```json title=".zed/settings.json"
|
|
57
|
+
{
|
|
58
|
+
"file_scan_exclusions": [
|
|
59
|
+
"**/.git",
|
|
60
|
+
"**/.svn",
|
|
61
|
+
"**/.hg",
|
|
62
|
+
"**/.DS_Store",
|
|
63
|
+
"**/Thumbs.db",
|
|
64
|
+
"**/node_modules",
|
|
65
|
+
"**/.worktrees",
|
|
66
|
+
"**/.claude/worktrees"
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Канон (усі елементи обовʼязкові, зайві дозволені): [settings.json.snippet.json](./policy/zed_settings/template/settings.json.snippet.json)
|
|
72
|
+
|
|
73
|
+
`.zed/settings.json` без стабільної схеми в Schema Store — додай його до `.v8rignore` (як `.vscode/*`, див. **text.mdc**).
|
|
74
|
+
|
|
23
75
|
## Команди
|
|
24
76
|
|
|
25
77
|
- **Створити** (опис обовʼязковий): `npx @nitra/cursor worktree add <branch> "<навіщо>"`
|
package/schemas/target.json
CHANGED
|
@@ -12,6 +12,11 @@
|
|
|
12
12
|
"format": "uri",
|
|
13
13
|
"description": "Опційне посилання на JSON Schema для IDE-валідації; очікувано https://unpkg.com/@nitra/cursor/schemas/target.json"
|
|
14
14
|
},
|
|
15
|
+
"check": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"enum": ["template"],
|
|
18
|
+
"description": "Режим перевірки концерну. 'template' → без власного <name>.rego: канон зі template/<target>.snippet|deny|contains.<ext> звіряється generic deep-subset-ом (checkSnippet/checkDeny/checkContains/checkTextSubset). Сніпет — єдине джерело істини: його зміна одразу змінює enforce, без правок rego й міграторів. Без поля → класичний rego-режим (пара з <name>.rego)."
|
|
19
|
+
},
|
|
15
20
|
"files": {
|
|
16
21
|
"oneOf": [
|
|
17
22
|
{
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guard для дефолтної синхронізації `npx @nitra/cursor` (гілка без підкоманди).
|
|
3
|
+
*
|
|
4
|
+
* Дефолтний sync (`runSync` у `bin/n-cursor.js`) скаффолдить у `cwd()` керовані
|
|
5
|
+
* артефакти — `.cursor/rules/`, `.cursor/skills/`, `.claude/`, `AGENTS.md`,
|
|
6
|
+
* `CLAUDE.md`, `.n-cursor.json`, `.gitignore` — і запускає `bun install`. Усе це
|
|
7
|
+
* розраховане на **корінь** проєкту-споживача. Якщо бінар викликати напряму
|
|
8
|
+
* (`bun npm/bin/n-cursor.js`, `node …/n-cursor.js`) з піддиректорії git-репо,
|
|
9
|
+
* `cwd()` — та піддиректорія, і конфіг розкидається не туди.
|
|
10
|
+
*
|
|
11
|
+
* `bun run start`/`npm start` цей кейс не створює (менеджер скидає cwd на корінь
|
|
12
|
+
* пакета), але прямий виклик бінаря — створює. Тому guard прив'язаний до
|
|
13
|
+
* **git-кореня**, а не до конкретного монорепо: CLI публічний і легітимно
|
|
14
|
+
* запускається в корені будь-якого репо-споживача.
|
|
15
|
+
*/
|
|
16
|
+
import { spawnSync } from 'node:child_process'
|
|
17
|
+
import { realpathSync } from 'node:fs'
|
|
18
|
+
import { cwd } from 'node:process'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Корінь git-репозиторію для `dir` через `git rev-parse --show-toplevel`.
|
|
22
|
+
* Повертає realpath-шлях кореня або `null`, якщо `dir` поза git-репо чи `git`
|
|
23
|
+
* недоступний — у такому разі визначити корінь неможливо, тож не блокуємо.
|
|
24
|
+
* @param {string} [dir] каталог, з якого питаємо git
|
|
25
|
+
* @returns {string | null} абсолютний (realpath) корінь репо або null
|
|
26
|
+
*/
|
|
27
|
+
export function gitToplevel(dir = cwd()) {
|
|
28
|
+
const res = spawnSync('git', ['rev-parse', '--show-toplevel'], { cwd: dir, encoding: 'utf8' })
|
|
29
|
+
if (res.status !== 0 || typeof res.stdout !== 'string') return null
|
|
30
|
+
const top = res.stdout.trim()
|
|
31
|
+
return top.length > 0 ? top : null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Безпечний realpath: повертає реальний шлях `dir`, а якщо його не існує —
|
|
36
|
+
* сам `dir` без змін (порівняння нижче все одно дасть коректний результат).
|
|
37
|
+
* Потрібен бо `git rev-parse --show-toplevel` віддає realpath (symlink'и
|
|
38
|
+
* розгорнуті), а `cwd()` — ні; без нормалізації корінь під symlink-шляхом
|
|
39
|
+
* (типово `/var` → `/private/var` на macOS) хибно вважався б піддиректорією.
|
|
40
|
+
* @param {string} dir шлях для нормалізації
|
|
41
|
+
* @returns {string} realpath або вихідний шлях
|
|
42
|
+
*/
|
|
43
|
+
function safeRealpath(dir) {
|
|
44
|
+
try {
|
|
45
|
+
return realpathSync(dir)
|
|
46
|
+
} catch {
|
|
47
|
+
return dir
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Кидає помилку, якщо `dir` — піддиректорія git-репозиторію (тобто не його
|
|
53
|
+
* корінь). Поза git-репо (немає toplevel) — пропускає без помилки.
|
|
54
|
+
*
|
|
55
|
+
* Викликати лише перед дефолтним sync (до перших мутацій файлів), не для
|
|
56
|
+
* підкоманд із власним `--root` чи read-only-логікою.
|
|
57
|
+
* @param {string} [dir] каталог, що перевіряємо (типово `cwd()`)
|
|
58
|
+
* @throws {Error} коли `dir` всередині git-репо, але не його корінь
|
|
59
|
+
* @returns {void}
|
|
60
|
+
*/
|
|
61
|
+
export function assertCwdIsProjectRoot(dir = cwd()) {
|
|
62
|
+
const top = gitToplevel(dir)
|
|
63
|
+
if (top === null) return
|
|
64
|
+
const here = safeRealpath(dir)
|
|
65
|
+
if (here === top) return
|
|
66
|
+
throw new Error(
|
|
67
|
+
`❌ @nitra/cursor запущено не в корені проєкту.\n` +
|
|
68
|
+
` Поточний каталог: ${here}\n` +
|
|
69
|
+
` Корінь git-репо: ${top}\n` +
|
|
70
|
+
` Дефолтна синхронізація скаффолдить .cursor/, .claude/, CLAUDE.md, .n-cursor.json\n` +
|
|
71
|
+
` і робить bun install у поточному каталозі — із піддиректорії це розкидало б\n` +
|
|
72
|
+
` конфіг не туди. Перейдіть у корінь репозиторію: cd ${top}`
|
|
73
|
+
)
|
|
74
|
+
}
|