Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75adb282e7 | ||
|
|
eacfa17e40 | ||
|
|
f4448835ab | ||
|
|
732cec8771 | ||
|
|
5cf71fcae1 |
@@ -1,9 +1,17 @@
|
||||
# Project Manager
|
||||
|
||||
Automatically detects modules and executes commands
|
||||
|
||||
Supported types:
|
||||
|
||||
- python (poetry)
|
||||
- nodejs (npm)
|
||||
- docker
|
||||
|
||||
## Install
|
||||
|
||||
```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
|
||||
@@ -14,17 +22,16 @@ pipx install project-manager -i https://git.shmyga.ru/InfernalGames/project-mana
|
||||
pm [-v] [-vv]
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Run task
|
||||
|
||||
```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
|
||||
|
||||
@@ -39,3 +46,9 @@ commands = ["<command>"]
|
||||
### Examples
|
||||
|
||||
[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`
|
||||
|
||||
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 tomllib
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import dotenv
|
||||
from jinja2 import Environment
|
||||
@@ -11,13 +12,21 @@ from ..preset.model import Variables
|
||||
|
||||
|
||||
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:
|
||||
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:
|
||||
env = Environment()
|
||||
|
||||
@@ -71,6 +80,7 @@ def _build_env(basepath: Path) -> Environment:
|
||||
env.filters["key"] = do_key
|
||||
env.filters["bool"] = do_bool
|
||||
env.filters["env"] = do_env
|
||||
env.filters["not"] = do_not
|
||||
return env
|
||||
|
||||
|
||||
|
||||
@@ -85,20 +85,20 @@ class Entry:
|
||||
for command in task.commands:
|
||||
self._logger.debug(" %s", command)
|
||||
|
||||
def run_task(self, key: PartialTaskKey) -> bool:
|
||||
def run_task(self, key: PartialTaskKey) -> int:
|
||||
if key.name == "show":
|
||||
self.show()
|
||||
return True
|
||||
runned = False
|
||||
c = 0
|
||||
for task in self._tasks:
|
||||
if key == task.key:
|
||||
self._logger.info("🔵 %s %s", task.key, self)
|
||||
runned = True
|
||||
for command in task.commands:
|
||||
CommandExecutor(command).run(
|
||||
(task.cwd and Path(task.cwd)) or self._basepath
|
||||
)
|
||||
return runned
|
||||
c += 1
|
||||
return c
|
||||
|
||||
def __repr__(self):
|
||||
path = self._basepath.relative_to(self._roopath.parent)
|
||||
|
||||
@@ -9,7 +9,7 @@ from .entry import Entry
|
||||
|
||||
|
||||
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._ignore = ignore
|
||||
self._max_depth = max_depth
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
from collections import Counter
|
||||
from enum import StrEnum
|
||||
from enum import StrEnum, auto
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
@@ -14,8 +14,8 @@ logger = logging.getLogger("project")
|
||||
|
||||
|
||||
class ResultType(StrEnum):
|
||||
SUCCESS = "success"
|
||||
FAILED = "failed"
|
||||
SUCCESS = auto()
|
||||
FAILED = auto()
|
||||
|
||||
|
||||
RESULT_ICONS = {
|
||||
@@ -40,8 +40,8 @@ class ProjectManager:
|
||||
counter = Counter()
|
||||
for entry in self._iter_entries():
|
||||
try:
|
||||
if entry.run_task(key):
|
||||
counter[ResultType.SUCCESS] += 1
|
||||
if count := entry.run_task(key):
|
||||
counter[ResultType.SUCCESS] += count
|
||||
except CommandError as error:
|
||||
self._logger.error("%s\n%s", entry, error.output)
|
||||
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 }}",
|
||||
"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]
|
||||
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
|
||||
[presets.nodejs.tasks.setup.npm]
|
||||
conditions = ["{{ 'package-lock.json' | file }}"]
|
||||
commands = ["npm install-clean"]
|
||||
commands = ["{{ nvm }}npm install-clean"]
|
||||
|
||||
# install legacy
|
||||
[presets.nodejs.tasks.setup.npm-legacy]
|
||||
conditions = ["{{ not ('package-lock.json' | file) }}"]
|
||||
commands = ["npm install"]
|
||||
commands = ["{{ nvm }}npm install"]
|
||||
|
||||
# test
|
||||
[presets.nodejs.tasks.test.jest]
|
||||
conditions = ["{{ 'node_modules/jest' | file }}"]
|
||||
commands = ["npx jest"]
|
||||
commands = ["{{ nvm }}npx jest"]
|
||||
|
||||
# build
|
||||
[presets.nodejs.tasks.build.npm]
|
||||
conditions = ["{{ 'package.json' | read | get('scripts.build') }}"]
|
||||
commands = ["npm run build"]
|
||||
commands = ["{{ nvm }}npm run build"]
|
||||
|
||||
# publish
|
||||
[presets.nodejs.tasks.publish.npm]
|
||||
conditions = ["{{ 'package.json' | read | get('scripts.publish') }}"]
|
||||
commands = ["npm run publish"]
|
||||
commands = ["{{ nvm }}npm run publish"]
|
||||
|
||||
# clean
|
||||
[presets.nodejs.tasks.clean.node_modules]
|
||||
@@ -34,4 +36,4 @@ commands = ["rm -rf node_modules"]
|
||||
# version
|
||||
[presets.nodejs.tasks.version.npm]
|
||||
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]
|
||||
conditions = ["{{ python_poetry }}"]
|
||||
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
|
||||
[presets.python.tasks.build.poetry]
|
||||
|
||||
@@ -14,9 +14,14 @@ class PresetEntryDetector(EntryDetector):
|
||||
def __init__(self, preset_loader_config: PresetLoaderConfig | None = None):
|
||||
self._preset_loader_config = preset_loader_config
|
||||
|
||||
# pylint:disable=too-many-locals
|
||||
def detect_entry(self, basepath: Path, rootpath: Path) -> Entry:
|
||||
template = PathTemplate(basepath)
|
||||
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:
|
||||
return ENTRY_SKIP
|
||||
tasks = []
|
||||
@@ -25,7 +30,10 @@ class PresetEntryDetector(EntryDetector):
|
||||
preset.presets.items(), key=lambda item: item[1].priority, reverse=True
|
||||
)
|
||||
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 tag == "*":
|
||||
template.update_variables(preset.variables, global_variables)
|
||||
|
||||
@@ -20,4 +20,5 @@ class EntryPreset(BaseModel):
|
||||
|
||||
class Preset(BaseModel):
|
||||
skip: bool | None = None
|
||||
modules: list[str] | None = None
|
||||
presets: dict[str, EntryPreset]
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "project-manager"
|
||||
description = ""
|
||||
version = "0.2.0"
|
||||
version = "0.3.1"
|
||||
readme = "README.md"
|
||||
authors = [{ name = "shmyga", email = "shmyga.z@gmail.com" }]
|
||||
requires-python = ">=3.14,<4.0"
|
||||
|
||||
Reference in New Issue
Block a user