Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 84964b62c1 | |||
| 65fc6ced30 | |||
| 6fd3672edc | |||
| 2a32b998f6 |
@@ -0,0 +1,3 @@
|
|||||||
|
# Скрипты и пайплайны запускаются на Linux-раннере: только LF.
|
||||||
|
*.sh text eol=lf
|
||||||
|
.gitea/**/*.yml text eol=lf
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
name: publish
|
||||||
|
|
||||||
|
# Публикация @tnsu/ui-kit в реестр организации Tansu — только по кнопке (Actions -> publish -> Run workflow).
|
||||||
|
# Сначала юнит-тесты и сборка, потом ci/publish.sh: версию он считает сам (major.minor — из package.json,
|
||||||
|
# патч — следующий после опубликованного), а если код не менялся с последней опубликованной версии,
|
||||||
|
# падает с «Публиковать нечего».
|
||||||
|
#
|
||||||
|
# Секрет: PACKAGES_TOKEN — токен Gitea со scope write:package (его владелец должен состоять в организации
|
||||||
|
# Tansu с правом записи). Логин не нужен: npm авторизуется одним токеном.
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
run: npm ci --no-audit --no-fund
|
||||||
|
|
||||||
|
- name: Unit tests
|
||||||
|
run: npm test
|
||||||
|
|
||||||
|
- name: Publish
|
||||||
|
env:
|
||||||
|
PACKAGES_TOKEN: ${{ secrets.PACKAGES_TOKEN }}
|
||||||
|
run: bash ci/publish.sh
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
name: test
|
||||||
|
|
||||||
|
# Юнит-тесты и сборка пакета: на каждый push в main, на pull request и по кнопке.
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '22'
|
||||||
|
|
||||||
|
- name: Install
|
||||||
|
run: npm ci --no-audit --no-fund
|
||||||
|
|
||||||
|
- name: Unit tests
|
||||||
|
run: npm test
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
@@ -1 +1 @@
|
|||||||
@tnsu:registry=http://git.tnsu.kz/api/packages/timur.gorodetskiy/npm/
|
@tnsu:registry=http://git.tnsu.kz/api/packages/Tansu/npm/
|
||||||
|
|||||||
@@ -4,40 +4,15 @@
|
|||||||
|
|
||||||
> Внутренняя память проекта (решения, план, прогресс) — в [PROJECT.md](./PROJECT.md).
|
> Внутренняя память проекта (решения, план, прогресс) — в [PROJECT.md](./PROJECT.md).
|
||||||
|
|
||||||
## Первичная настройка (один раз на каждого разработчика)
|
|
||||||
|
|
||||||
Пакет лежит в приватном registry (Gitea), поэтому перед первой установкой нужно один
|
|
||||||
раз настроить доступ. Дальше про это можно забыть.
|
|
||||||
|
|
||||||
**Шаг 1. Создать токен в Gitea:**
|
|
||||||
1. Открой http://git.tnsu.kz → кликни на свой аватар справа вверху → **Настройки**.
|
|
||||||
2. Слева: **Приложения**.
|
|
||||||
3. В блоке «Управление токенами» → **Создать новый токен**:
|
|
||||||
- Имя: например `npm-read`.
|
|
||||||
- Права (scopes): найди строку **`package`** и поставь **«Прочитанные»** (Read).
|
|
||||||
Остальное — «Нет доступа».
|
|
||||||
4. Нажми **Генерировать токен** и **сразу скопируй** его (показывается один раз).
|
|
||||||
|
|
||||||
**Шаг 2. Прописать токен у себя на компьютере.**
|
|
||||||
Создай (или дополни) файл `.npmrc` в своей домашней папке
|
|
||||||
(`C:\Users\<твоё-имя>\.npmrc`) одной строкой:
|
|
||||||
|
|
||||||
```
|
|
||||||
//git.tnsu.kz/api/packages/timur.gorodetskiy/npm/:_authToken=ВСТАВЬ_СВОЙ_ТОКЕН
|
|
||||||
```
|
|
||||||
|
|
||||||
> ⚠️ Токен — это как пароль. Он лежит в домашней папке, **вне проекта**, и в git не попадает.
|
|
||||||
> Никогда не коммить его и не вставляй в код.
|
|
||||||
|
|
||||||
Всё. Теперь `npm i` в любом проекте, где подключён `@tnsu/ui-kit`, будет работать.
|
|
||||||
|
|
||||||
## Установка в проект
|
## Установка в проект
|
||||||
|
|
||||||
Убедись, что в корне проекта есть файл `.npmrc` со строкой (для request-portal он уже
|
Пакет лежит в реестре организации **Tansu** на git.tnsu.kz. Реестр публичный, а сам git.tnsu.kz
|
||||||
закоммичен):
|
доступен только из внутренней сети, поэтому **токен для установки не нужен** (из сети или по VPN).
|
||||||
|
|
||||||
|
В корне проекта — файл `.npmrc` со строкой:
|
||||||
|
|
||||||
```
|
```
|
||||||
@tnsu:registry=http://git.tnsu.kz/api/packages/timur.gorodetskiy/npm/
|
@tnsu:registry=http://git.tnsu.kz/api/packages/Tansu/npm/
|
||||||
```
|
```
|
||||||
|
|
||||||
Затем:
|
Затем:
|
||||||
@@ -86,11 +61,21 @@ npm run storybook # витрина компонентов (localhost:6006)
|
|||||||
|
|
||||||
### Публикация новой версии
|
### Публикация новой версии
|
||||||
|
|
||||||
```powershell
|
Публикует пайплайн **publish** в Gitea — по кнопке: Actions → publish → Run workflow. Сначала юнит-тесты
|
||||||
# 1. поднять version в package.json (напр. 0.0.1 → 0.0.2)
|
и сборка, потом публикация в реестр Tansu.
|
||||||
npm run build
|
|
||||||
npm publish
|
|
||||||
```
|
|
||||||
|
|
||||||
> npm не даёт перезаписать уже опубликованную версию — перед публикацией всегда
|
- **Версию поднимает пайплайн.** В `package.json` задаются только major.minor (патч там не важен):
|
||||||
> бампай `version` в package.json.
|
новая функциональность — поднять minor руками (`0.14.x` → `0.15.0`). Патч — следующий после самого
|
||||||
|
большого опубликованного с тем же major.minor. В git ничего не коммитится.
|
||||||
|
- **Тот же код не публикуется.** Пайплайн считает хэш исходников (`src` без тестов и историй Storybook,
|
||||||
|
`package.json`, конфиги сборки, README), кладёт его в публикуемый `package.json` (поле `sourceHash`)
|
||||||
|
и сравнивает с последней опубликованной версией. Совпало — пайплайн падает с «Публиковать нечего».
|
||||||
|
- Секрет: `PACKAGES_TOKEN` — токен Gitea со scope `write:package` (владелец — участник организации Tansu
|
||||||
|
с правом записи). Логин не нужен.
|
||||||
|
|
||||||
|
Пробный прогон без публикации: `DRY_RUN=1 bash ci/publish.sh`.
|
||||||
|
|
||||||
|
Версии до 0.14.0 скопированы из прежнего реестра `timur.gorodetskiy` без хэша, поэтому первая публикация
|
||||||
|
пайплайном выпустит `0.14.1` даже без изменений кода; дальше проверка работает как обычно.
|
||||||
|
|
||||||
|
На каждый push и pull request пайплайн **test** гоняет юнит-тесты и сборку.
|
||||||
|
|||||||
Executable
+108
@@ -0,0 +1,108 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Публикация @tnsu/ui-kit в реестр организации Tansu с автоматической версией.
|
||||||
|
#
|
||||||
|
# bash ci/publish.sh
|
||||||
|
#
|
||||||
|
# Версия: в package.json задаются только major.minor (патч там не важен). Патч считается здесь:
|
||||||
|
# следующий после самого большого опубликованного с тем же major.minor, или 0. В git ничего не коммитится.
|
||||||
|
#
|
||||||
|
# Хэш кода: sha256 от путей и git-хэшей файлов пакета (тесты и истории Storybook не входят). Кладётся
|
||||||
|
# в публикуемый package.json (поле sourceHash). Перед публикацией скачивается последняя опубликованная
|
||||||
|
# версия и хэш сравнивается: код тот же — публикации нет. Хэш берётся из самого архива, а не из
|
||||||
|
# метаданных реестра: архив реестр хранит как есть.
|
||||||
|
#
|
||||||
|
# Переменные: PACKAGES_TOKEN (scope write:package), REGISTRY_BASE (по умолчанию
|
||||||
|
# http://git.tnsu.kz/api/packages/Tansu), DRY_RUN=1 — всё посчитать и собрать, но не публиковать.
|
||||||
|
#
|
||||||
|
# Коды выхода: 0 — опубликовано; 1 — ошибка; 3 — публиковать нечего (код не менялся).
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
REGISTRY_BASE="${REGISTRY_BASE:-http://git.tnsu.kz/api/packages/Tansu}"
|
||||||
|
DRY_RUN="${DRY_RUN:-}"
|
||||||
|
PACKAGES_TOKEN="${PACKAGES_TOKEN:-}"
|
||||||
|
|
||||||
|
WORK="$(mktemp -d)"
|
||||||
|
trap 'rm -rf "$WORK"' EXIT
|
||||||
|
|
||||||
|
source_hash() {
|
||||||
|
git ls-files -s -- "$@" | grep -vE '\.(test\.ts|stories\.tsx)$' | awk '{print $2" "$4}' | sha256sum | cut -c1-16
|
||||||
|
}
|
||||||
|
|
||||||
|
max_version() {
|
||||||
|
node -e '
|
||||||
|
const v = require("fs").readFileSync(0, "utf8").split(/\s+/).filter((s) => /^\d+\.\d+\.\d+$/.test(s));
|
||||||
|
const key = (s) => s.split(".").map(Number);
|
||||||
|
v.sort((a, b) => { const x = key(a), y = key(b); return x[0] - y[0] || x[1] - y[1] || x[2] - y[2]; });
|
||||||
|
process.stdout.write(v.length ? v[v.length - 1] : "");
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
next_version() {
|
||||||
|
local repo_version="$1" published="$2"
|
||||||
|
node -e '
|
||||||
|
const [repo, published] = [process.argv[1], process.argv[2]];
|
||||||
|
const [major, minor] = repo.split(".").map(Number);
|
||||||
|
const versions = published.split(/\s+/).filter((s) => /^\d+\.\d+\.\d+$/.test(s)).map((s) => s.split(".").map(Number));
|
||||||
|
const newer = versions.find(([a, b]) => a > major || (a === major && b > minor));
|
||||||
|
if (newer) { console.error(`В реестре уже есть ${newer.join(".")}, а в репозитории ${major}.${minor}: поднимите версию в репозитории.`); process.exit(1); }
|
||||||
|
const patches = versions.filter(([a, b]) => a === major && b === minor).map(([, , c]) => c);
|
||||||
|
process.stdout.write(`${major}.${minor}.${patches.length ? Math.max(...patches) + 1 : 0}`);
|
||||||
|
' "$repo_version" "$published"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Реестр публичный, читать можно без токена.
|
||||||
|
fetch() {
|
||||||
|
local url="$1" output="$2" status
|
||||||
|
status=$(curl -sS -o "$output" -w '%{http_code}' "$url") || status=000
|
||||||
|
echo "$status"
|
||||||
|
}
|
||||||
|
|
||||||
|
hash=$(source_hash src package.json vite.config.ts tsconfig.json global.d.ts README.md)
|
||||||
|
repo_version=$(node -p "require('./package.json').version")
|
||||||
|
latest="" published="" latest_hash=""
|
||||||
|
|
||||||
|
status=$(fetch "$REGISTRY_BASE/npm/@tnsu%2Fui-kit" "$WORK/packument.json")
|
||||||
|
case "$status" in
|
||||||
|
200)
|
||||||
|
published=$(node -e 'process.stdout.write(Object.keys(require(process.argv[1]).versions || {}).join(" "))' "$WORK/packument.json")
|
||||||
|
latest=$(echo "$published" | tr ' ' '\n' | max_version)
|
||||||
|
;;
|
||||||
|
404) ;;
|
||||||
|
*) echo "Реестр npm ответил $status" >&2; cat "$WORK/packument.json" >&2 || true; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ -n $latest ]]; then
|
||||||
|
tarball=$(node -e 'process.stdout.write(require(process.argv[1]).versions[process.argv[2]].dist.tarball)' "$WORK/packument.json" "$latest")
|
||||||
|
status=$(fetch "$tarball" "$WORK/latest.tgz")
|
||||||
|
[[ $status == 200 ]] || { echo "Не скачался @tnsu/ui-kit@$latest: $status" >&2; exit 1; }
|
||||||
|
latest_hash=$(tar -xzOf "$WORK/latest.tgz" package/package.json | node -e 'process.stdout.write(JSON.parse(require("fs").readFileSync(0, "utf8")).sourceHash || "")')
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "хэш кода $hash; последняя опубликованная ${latest:-нет} (хэш ${latest_hash:-нет})"
|
||||||
|
if [[ -n $latest_hash && $latest_hash == "$hash" ]]; then
|
||||||
|
echo "Публиковать нечего: код не менялся с $latest." >&2
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
version=$(next_version "$repo_version" "$published")
|
||||||
|
echo "публикую $version"
|
||||||
|
|
||||||
|
# Версия и хэш — только в рабочей копии на время публикации.
|
||||||
|
cp package.json "$WORK/package.json.orig"
|
||||||
|
trap 'cp "$WORK/package.json.orig" package.json 2> /dev/null; rm -rf "$WORK"' EXIT
|
||||||
|
node -e '
|
||||||
|
const fs = require("fs"); const p = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
||||||
|
p.version = process.argv[1]; p.sourceHash = process.argv[2];
|
||||||
|
fs.writeFileSync("package.json", JSON.stringify(p, null, 2) + "\n");
|
||||||
|
' "$version" "$hash"
|
||||||
|
|
||||||
|
npm run build > /dev/null
|
||||||
|
if [[ -n $DRY_RUN ]]; then
|
||||||
|
npm pack --dry-run 2>&1 | grep -E 'name:|version:|total files'
|
||||||
|
echo "DRY_RUN: не публикую"
|
||||||
|
else
|
||||||
|
printf '//%s:_authToken=%s\n' "${REGISTRY_BASE#*://}/npm/" "$PACKAGES_TOKEN" > "$WORK/npmrc"
|
||||||
|
NPM_CONFIG_USERCONFIG="$WORK/npmrc" npm publish
|
||||||
|
fi
|
||||||
|
echo "Готово: @tnsu/ui-kit@$version"
|
||||||
@@ -8,6 +8,9 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://git.tnsu.kz/timur.gorodetskiy/tnsu-ui-kit.git"
|
"url": "http://git.tnsu.kz/timur.gorodetskiy/tnsu-ui-kit.git"
|
||||||
},
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"registry": "http://git.tnsu.kz/api/packages/Tansu/npm/"
|
||||||
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user