Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
244f995490 | ||
|
|
75adb282e7 | ||
|
|
eacfa17e40 | ||
|
|
f4448835ab | ||
|
|
732cec8771 | ||
|
|
5cf71fcae1 |
@@ -0,0 +1,20 @@
|
|||||||
|
name: release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ["*"]
|
||||||
|
|
||||||
|
env:
|
||||||
|
POETRY_HTTP_BASIC_InfernalGames_USERNAME: ${{ vars.PUBLISH_USERNAME }}
|
||||||
|
POETRY_HTTP_BASIC_InfernalGames_PASSWORD: ${{ secrets.PUBLISH_TOKEN }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- uses: https://git.shmyga.ru/InfernalGames/project-manager@master
|
||||||
|
with:
|
||||||
|
setup-python: true
|
||||||
|
- run: pm build -vv
|
||||||
|
- run: pm publish -vv
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
name: test
|
||||||
|
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
- uses: https://git.shmyga.ru/InfernalGames/project-manager@master
|
||||||
|
with:
|
||||||
|
setup-python: true
|
||||||
|
- run: pm lint -vv
|
||||||
|
- run: pm test -vv
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
# Project Manager
|
# Project Manager
|
||||||
|
|
||||||
|
Automatically detects modules and executes commands
|
||||||
|
|
||||||
|
Supported types:
|
||||||
|
|
||||||
|
- python (poetry)
|
||||||
|
- nodejs (npm)
|
||||||
|
- docker
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pipx install project-manager -i https://git.shmyga.ru/InfernalGames/project-manager/packages
|
pipx install project-manager -i https://git.shmyga.ru/api/packages/InfernalGames/pypi/simple/ --pip-args="--extra-index-url https://pypi.org/simple"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -14,17 +22,16 @@ pipx install project-manager -i https://git.shmyga.ru/InfernalGames/project-mana
|
|||||||
pm [-v] [-vv]
|
pm [-v] [-vv]
|
||||||
```
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Run task
|
### Run task
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pm <task>[:<tag>] [-v] [-vv]
|
pm <task>[:<tag>][:<sub_tag>] [-v] [-vv]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Presets locations
|

|
||||||
|

|
||||||
- default presets [`project_manager/preset/default/*.toml`](./project_manager/preset/default)
|
|
||||||
- user preset `$HOME/.pm.toml`
|
|
||||||
- project preset `./.pm.toml`
|
|
||||||
|
|
||||||
## Preset format
|
## Preset format
|
||||||
|
|
||||||
@@ -39,3 +46,9 @@ commands = ["<command>"]
|
|||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
[Default presets](./project_manager/preset/default)
|
[Default presets](./project_manager/preset/default)
|
||||||
|
|
||||||
|
## Presets locations
|
||||||
|
|
||||||
|
- default presets [`project_manager/preset/default/*.toml`](./project_manager/preset/default)
|
||||||
|
- user preset `$HOME/.pm.toml`
|
||||||
|
- project preset `./.pm.toml`
|
||||||
|
|||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
name: "Project Manager"
|
||||||
|
inputs:
|
||||||
|
python-version:
|
||||||
|
default: "3.14"
|
||||||
|
setup-python:
|
||||||
|
default: false
|
||||||
|
venv-version:
|
||||||
|
default: "1"
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- id: setup-python
|
||||||
|
uses: actions/setup-python@v7
|
||||||
|
with:
|
||||||
|
python-version: ${{ inputs.python-version }}
|
||||||
|
cache: "pip"
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
pip install project-manager \
|
||||||
|
--index-url https://git.shmyga.ru/api/packages/InfernalGames/pypi/simple/ \
|
||||||
|
--extra-index-url https://pypi.org/simple
|
||||||
|
|
||||||
|
- run: pm setup:config -vv
|
||||||
|
|
||||||
|
- if: inputs.setup-python
|
||||||
|
run: pip install poetry
|
||||||
|
|
||||||
|
- id: cache-venv
|
||||||
|
if: inputs.setup-python
|
||||||
|
uses: actions/cache@v6
|
||||||
|
with:
|
||||||
|
path: .venv
|
||||||
|
key: venv-${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}-v${{ inputs.venv-version }}
|
||||||
|
|
||||||
|
- if: ${{ inputs.setup-python && steps.cache-venv.outputs.cache-hit != 'true' }}
|
||||||
|
run: pm setup:python -vv
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
@@ -3,6 +3,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import tomllib
|
import tomllib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import dotenv
|
import dotenv
|
||||||
from jinja2 import Environment
|
from jinja2 import Environment
|
||||||
@@ -11,13 +12,21 @@ from ..preset.model import Variables
|
|||||||
|
|
||||||
|
|
||||||
def do_bool(data: str | None) -> bool:
|
def do_bool(data: str | None) -> bool:
|
||||||
return not (data is None or data.lower() in ["false", "null", "none", "0", ""])
|
if data is None:
|
||||||
|
return False
|
||||||
|
if not isinstance(data, str):
|
||||||
|
data = str(data)
|
||||||
|
return data.lower() not in ["false", "null", "none", "0", ""]
|
||||||
|
|
||||||
|
|
||||||
def do_env(key: str, default: str | None = None) -> str | None:
|
def do_env(key: str, default: str | None = None) -> str | None:
|
||||||
return os.environ.get(key=key.upper(), default=default)
|
return os.environ.get(key=key.upper(), default=default)
|
||||||
|
|
||||||
|
|
||||||
|
def do_not(value: Any | None) -> bool:
|
||||||
|
return not do_bool(str(value))
|
||||||
|
|
||||||
|
|
||||||
def _build_env(basepath: Path) -> Environment:
|
def _build_env(basepath: Path) -> Environment:
|
||||||
env = Environment()
|
env = Environment()
|
||||||
|
|
||||||
@@ -71,6 +80,7 @@ def _build_env(basepath: Path) -> Environment:
|
|||||||
env.filters["key"] = do_key
|
env.filters["key"] = do_key
|
||||||
env.filters["bool"] = do_bool
|
env.filters["bool"] = do_bool
|
||||||
env.filters["env"] = do_env
|
env.filters["env"] = do_env
|
||||||
|
env.filters["not"] = do_not
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -85,20 +85,20 @@ class Entry:
|
|||||||
for command in task.commands:
|
for command in task.commands:
|
||||||
self._logger.debug(" %s", command)
|
self._logger.debug(" %s", command)
|
||||||
|
|
||||||
def run_task(self, key: PartialTaskKey) -> bool:
|
def run_task(self, key: PartialTaskKey) -> int:
|
||||||
if key.name == "show":
|
if key.name == "show":
|
||||||
self.show()
|
self.show()
|
||||||
return True
|
return True
|
||||||
runned = False
|
c = 0
|
||||||
for task in self._tasks:
|
for task in self._tasks:
|
||||||
if key == task.key:
|
if key == task.key:
|
||||||
self._logger.info("🔵 %s %s", task.key, self)
|
self._logger.info("🔵 %s %s", task.key, self)
|
||||||
runned = True
|
|
||||||
for command in task.commands:
|
for command in task.commands:
|
||||||
CommandExecutor(command).run(
|
CommandExecutor(command).run(
|
||||||
(task.cwd and Path(task.cwd)) or self._basepath
|
(task.cwd and Path(task.cwd)) or self._basepath
|
||||||
)
|
)
|
||||||
return runned
|
c += 1
|
||||||
|
return c
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
path = self._basepath.relative_to(self._roopath.parent)
|
path = self._basepath.relative_to(self._roopath.parent)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from .entry import Entry
|
|||||||
|
|
||||||
|
|
||||||
class EntryResolver:
|
class EntryResolver:
|
||||||
def __init__(self, detector: EntryDetector, ignore: PathSpec, max_depth: int = 4):
|
def __init__(self, detector: EntryDetector, ignore: PathSpec, max_depth: int = 2):
|
||||||
self._detector = detector
|
self._detector = detector
|
||||||
self._ignore = ignore
|
self._ignore = ignore
|
||||||
self._max_depth = max_depth
|
self._max_depth = max_depth
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from enum import StrEnum
|
from enum import StrEnum, auto
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
|
|
||||||
@@ -14,8 +14,8 @@ logger = logging.getLogger("project")
|
|||||||
|
|
||||||
|
|
||||||
class ResultType(StrEnum):
|
class ResultType(StrEnum):
|
||||||
SUCCESS = "success"
|
SUCCESS = auto()
|
||||||
FAILED = "failed"
|
FAILED = auto()
|
||||||
|
|
||||||
|
|
||||||
RESULT_ICONS = {
|
RESULT_ICONS = {
|
||||||
@@ -40,8 +40,8 @@ class ProjectManager:
|
|||||||
counter = Counter()
|
counter = Counter()
|
||||||
for entry in self._iter_entries():
|
for entry in self._iter_entries():
|
||||||
try:
|
try:
|
||||||
if entry.run_task(key):
|
if count := entry.run_task(key):
|
||||||
counter[ResultType.SUCCESS] += 1
|
counter[ResultType.SUCCESS] += count
|
||||||
except CommandError as error:
|
except CommandError as error:
|
||||||
self._logger.error("%s\n%s", entry, error.output)
|
self._logger.error("%s\n%s", entry, error.output)
|
||||||
counter[ResultType.FAILED] += 1
|
counter[ResultType.FAILED] += 1
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
[presets.config.tasks.setup.config]
|
||||||
|
priority = 1
|
||||||
|
conditions = ["{{ ('.env-base' | file) and ('.env' | file | not ) }}"]
|
||||||
|
commands = ["cp .env-base .env"]
|
||||||
@@ -34,3 +34,13 @@ commands = [
|
|||||||
"mkdir -p {{ docker_output }}",
|
"mkdir -p {{ docker_output }}",
|
||||||
"docker save --output {{ docker_output }}/{{ docker_name }}-{{ docker_version }}.tar {{ docker_image }}",
|
"docker save --output {{ docker_output }}/{{ docker_name }}-{{ docker_version }}.tar {{ docker_image }}",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# run
|
||||||
|
[presets.docker.tasks.run.compose]
|
||||||
|
conditions = ["{{ 'docker-compose.yaml' | file }}"]
|
||||||
|
commands = ["docker compose -f docker-compose.yaml up"]
|
||||||
|
|
||||||
|
# develop
|
||||||
|
[presets.docker.tasks.develop.compose]
|
||||||
|
conditions = ["{{ 'docker-compose-develop.yaml' | file }}"]
|
||||||
|
commands = ["docker compose -f docker-compose-develop.yaml up --build --watch"]
|
||||||
|
|||||||
@@ -1,30 +1,32 @@
|
|||||||
[presets.nodejs]
|
[presets.nodejs]
|
||||||
conditions = ["{{ 'package.json' | file }}"]
|
conditions = ["{{ 'package.json' | file }}"]
|
||||||
|
variables.use_nvm = "{{ (((('home' | env) + '/.nvm/nvm.sh') | file) and ('.nvmrc' | file)) | bool }}"
|
||||||
|
variables.nvm = "{{ '. ~/.nvm/nvm.sh && nvm use && ' if use_nvm | bool else '' }}"
|
||||||
|
|
||||||
# install
|
# install
|
||||||
[presets.nodejs.tasks.setup.npm]
|
[presets.nodejs.tasks.setup.npm]
|
||||||
conditions = ["{{ 'package-lock.json' | file }}"]
|
conditions = ["{{ 'package-lock.json' | file }}"]
|
||||||
commands = ["npm install-clean"]
|
commands = ["{{ nvm }}npm install-clean"]
|
||||||
|
|
||||||
# install legacy
|
# install legacy
|
||||||
[presets.nodejs.tasks.setup.npm-legacy]
|
[presets.nodejs.tasks.setup.npm-legacy]
|
||||||
conditions = ["{{ not ('package-lock.json' | file) }}"]
|
conditions = ["{{ not ('package-lock.json' | file) }}"]
|
||||||
commands = ["npm install"]
|
commands = ["{{ nvm }}npm install"]
|
||||||
|
|
||||||
# test
|
# test
|
||||||
[presets.nodejs.tasks.test.jest]
|
[presets.nodejs.tasks.test.jest]
|
||||||
conditions = ["{{ 'node_modules/jest' | file }}"]
|
conditions = ["{{ 'node_modules/jest' | file }}"]
|
||||||
commands = ["npx jest"]
|
commands = ["{{ nvm }}npx jest"]
|
||||||
|
|
||||||
# build
|
# build
|
||||||
[presets.nodejs.tasks.build.npm]
|
[presets.nodejs.tasks.build.npm]
|
||||||
conditions = ["{{ 'package.json' | read | get('scripts.build') }}"]
|
conditions = ["{{ 'package.json' | read | get('scripts.build') }}"]
|
||||||
commands = ["npm run build"]
|
commands = ["{{ nvm }}npm run build"]
|
||||||
|
|
||||||
# publish
|
# publish
|
||||||
[presets.nodejs.tasks.publish.npm]
|
[presets.nodejs.tasks.publish.npm]
|
||||||
conditions = ["{{ 'package.json' | read | get('scripts.publish') }}"]
|
conditions = ["{{ 'package.json' | read | get('scripts.publish') }}"]
|
||||||
commands = ["npm run publish"]
|
commands = ["{{ nvm }}npm run publish"]
|
||||||
|
|
||||||
# clean
|
# clean
|
||||||
[presets.nodejs.tasks.clean.node_modules]
|
[presets.nodejs.tasks.clean.node_modules]
|
||||||
@@ -34,4 +36,4 @@ commands = ["rm -rf node_modules"]
|
|||||||
# version
|
# version
|
||||||
[presets.nodejs.tasks.version.npm]
|
[presets.nodejs.tasks.version.npm]
|
||||||
conditions = ["{{ version }}"]
|
conditions = ["{{ version }}"]
|
||||||
commands = ["npm version {{ version }} --allow-same-version"]
|
commands = ["{{ nvm }}npm version {{ version }} --allow-same-version"]
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ variables.python_poetry = "{{ 'pyproject.toml' | read | get('build-system.build-
|
|||||||
[presets.python.tasks.setup.poetry]
|
[presets.python.tasks.setup.poetry]
|
||||||
conditions = ["{{ python_poetry }}"]
|
conditions = ["{{ python_poetry }}"]
|
||||||
variables.python_version = "{{ 'pyproject.toml' | read | get('project.requires-python') | search('(\\d+.\\d+)') }}"
|
variables.python_version = "{{ 'pyproject.toml' | read | get('project.requires-python') | search('(\\d+.\\d+)') }}"
|
||||||
commands = ["poetry env use {{ python_version }}", "poetry install"]
|
commands = ["poetry env use {{ python_version }}", "poetry install --all-extras"]
|
||||||
|
|
||||||
|
# playwright
|
||||||
|
[presets.python.tasks.setup.playwright]
|
||||||
|
conditions = ["{{ '.venv/bin/playwright' | file }}"]
|
||||||
|
commands = [".venv/bin/playwright install chromium --with-deps --only-shell"]
|
||||||
|
|
||||||
# build
|
# build
|
||||||
[presets.python.tasks.build.poetry]
|
[presets.python.tasks.build.poetry]
|
||||||
|
|||||||
@@ -14,9 +14,14 @@ class PresetEntryDetector(EntryDetector):
|
|||||||
def __init__(self, preset_loader_config: PresetLoaderConfig | None = None):
|
def __init__(self, preset_loader_config: PresetLoaderConfig | None = None):
|
||||||
self._preset_loader_config = preset_loader_config
|
self._preset_loader_config = preset_loader_config
|
||||||
|
|
||||||
|
# pylint:disable=too-many-locals
|
||||||
def detect_entry(self, basepath: Path, rootpath: Path) -> Entry:
|
def detect_entry(self, basepath: Path, rootpath: Path) -> Entry:
|
||||||
template = PathTemplate(basepath)
|
template = PathTemplate(basepath)
|
||||||
preset = PresetLoader(basepath, self._preset_loader_config).load()
|
preset = PresetLoader(basepath, self._preset_loader_config).load()
|
||||||
|
if preset.modules:
|
||||||
|
modules = [Path(item).resolve() for item in preset.modules]
|
||||||
|
if basepath not in modules:
|
||||||
|
return ENTRY_SKIP
|
||||||
if preset.skip:
|
if preset.skip:
|
||||||
return ENTRY_SKIP
|
return ENTRY_SKIP
|
||||||
tasks = []
|
tasks = []
|
||||||
@@ -25,7 +30,10 @@ class PresetEntryDetector(EntryDetector):
|
|||||||
preset.presets.items(), key=lambda item: item[1].priority, reverse=True
|
preset.presets.items(), key=lambda item: item[1].priority, reverse=True
|
||||||
)
|
)
|
||||||
for tag, preset in presets:
|
for tag, preset in presets:
|
||||||
preset_variables: Variables = {**global_variables, "entry_path": basepath}
|
preset_variables: Variables = {
|
||||||
|
**global_variables,
|
||||||
|
"entry_path": str(basepath),
|
||||||
|
}
|
||||||
if preset.variables:
|
if preset.variables:
|
||||||
if tag == "*":
|
if tag == "*":
|
||||||
template.update_variables(preset.variables, global_variables)
|
template.update_variables(preset.variables, global_variables)
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ class EntryPreset(BaseModel):
|
|||||||
|
|
||||||
class Preset(BaseModel):
|
class Preset(BaseModel):
|
||||||
skip: bool | None = None
|
skip: bool | None = None
|
||||||
|
modules: list[str] | None = None
|
||||||
presets: dict[str, EntryPreset]
|
presets: dict[str, EntryPreset]
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "project-manager"
|
name = "project-manager"
|
||||||
description = ""
|
description = ""
|
||||||
version = "0.2.0"
|
version = "0.3.1"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = [{ name = "shmyga", email = "shmyga.z@gmail.com" }]
|
authors = [{ name = "shmyga", email = "shmyga.z@gmail.com" }]
|
||||||
requires-python = ">=3.14,<4.0"
|
requires-python = ">=3.14,<4.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user