chore(release): standardize publish and update workflow
Some checks are pending
CI / build (push) Waiting to run
Some checks are pending
CI / build (push) Waiting to run
This commit is contained in:
parent
365906a58d
commit
d1725d4af9
3 changed files with 228 additions and 0 deletions
23
AGENTS.md
Normal file
23
AGENTS.md
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# AGENTS
|
||||
|
||||
## Publicacao e atualizacao do pacote
|
||||
|
||||
- Sempre que a tarefa envolver publicar ou atualizar este community node, use `python scripts/publish_and_update.py`.
|
||||
- Nunca rode `npm publish` diretamente, exceto se o usuario pedir explicitamente para ignorar esse fluxo.
|
||||
- Para atualizar uma instalacao local do n8n baseada em diretorio, use `--target`.
|
||||
- Para atualizar uma instalacao em Docker Compose, use `--docker-service` e, quando necessario, `--docker-compose-file`.
|
||||
- Se houver bloqueio externo de politica, credencial, npm, Docker ou ambiente, explique isso claramente ao usuario e mantenha o script como fluxo padrao.
|
||||
|
||||
## Exemplos
|
||||
|
||||
Publicar e atualizar um n8n em diretorio local:
|
||||
|
||||
```bash
|
||||
python scripts/publish_and_update.py --token "<NPM_TOKEN>" --target "C:\\caminho\\do\\n8n"
|
||||
```
|
||||
|
||||
Publicar e atualizar um n8n em Docker Compose:
|
||||
|
||||
```bash
|
||||
python scripts/publish_and_update.py --token "<NPM_TOKEN>" --docker-service n8n --docker-compose-file "C:\\caminho\\docker-compose.yml"
|
||||
```
|
||||
26
README.md
26
README.md
|
|
@ -57,6 +57,32 @@ Para instalar o pacote publicado no n8n:
|
|||
npm install @jessefreitas/n8n-nodes-mega
|
||||
```
|
||||
|
||||
## Publicacao e update
|
||||
|
||||
O fluxo padrao para publicar uma nova versao e atualizar uma instalacao do n8n deve usar:
|
||||
|
||||
```bash
|
||||
python scripts/publish_and_update.py
|
||||
```
|
||||
|
||||
Exemplo para publicar e atualizar um n8n local por diretorio:
|
||||
|
||||
```bash
|
||||
python scripts/publish_and_update.py --token "<NPM_TOKEN>" --target "C:\\caminho\\do\\n8n"
|
||||
```
|
||||
|
||||
Exemplo para publicar e atualizar um n8n em Docker Compose:
|
||||
|
||||
```bash
|
||||
python scripts/publish_and_update.py --token "<NPM_TOKEN>" --docker-service n8n --docker-compose-file "C:\\caminho\\docker-compose.yml"
|
||||
```
|
||||
|
||||
Se voce ja publicou a versao e quer apenas atualizar o ambiente:
|
||||
|
||||
```bash
|
||||
python scripts/publish_and_update.py --skip-publish --version 0.4.12 --target "C:\\caminho\\do\\n8n"
|
||||
```
|
||||
|
||||
## Credenciais
|
||||
|
||||
Crie uma credencial `Mega API` no n8n com:
|
||||
|
|
|
|||
179
scripts/publish_and_update.py
Normal file
179
scripts/publish_and_update.py
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
import argparse
|
||||
import getpass
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PACKAGE_NAME = "@jessefreitas/n8n-nodes-mega"
|
||||
|
||||
|
||||
def resolve_npm() -> str:
|
||||
if sys.platform == "win32":
|
||||
return shutil.which("npm.cmd") or shutil.which("npm") or "npm"
|
||||
return shutil.which("npm") or "npm"
|
||||
|
||||
|
||||
def run(command: list[str], cwd: Path, env: dict[str, str] | None = None) -> None:
|
||||
result = subprocess.run(command, cwd=cwd, env=env, check=False)
|
||||
if result.returncode != 0:
|
||||
raise SystemExit(result.returncode)
|
||||
|
||||
|
||||
def build_temp_npmrc(token: str) -> tuple[Path, Path]:
|
||||
temp_dir = Path(tempfile.mkdtemp(prefix="npm-publish-"))
|
||||
npmrc = temp_dir / ".npmrc"
|
||||
npmrc.write_text(
|
||||
f"//registry.npmjs.org/:_authToken={token}\nregistry=https://registry.npmjs.org/\n",
|
||||
encoding="ascii",
|
||||
)
|
||||
return temp_dir, npmrc
|
||||
|
||||
|
||||
def read_package_version(repo_root: Path) -> str:
|
||||
package_json = repo_root / "package.json"
|
||||
content = package_json.read_text(encoding="utf-8")
|
||||
marker = '"version": "'
|
||||
start = content.find(marker)
|
||||
if start == -1:
|
||||
raise RuntimeError(f"Could not find version in {package_json}")
|
||||
start += len(marker)
|
||||
end = content.find('"', start)
|
||||
if end == -1:
|
||||
raise RuntimeError(f"Could not parse version in {package_json}")
|
||||
return content[start:end]
|
||||
|
||||
|
||||
def publish_package(repo_root: Path, token: str, skip_whoami: bool) -> str:
|
||||
npm = resolve_npm()
|
||||
version = read_package_version(repo_root)
|
||||
temp_dir, npmrc = build_temp_npmrc(token)
|
||||
env = os.environ.copy()
|
||||
env["NPM_CONFIG_USERCONFIG"] = str(npmrc)
|
||||
|
||||
try:
|
||||
if not skip_whoami:
|
||||
run([npm, "whoami"], cwd=repo_root, env=env)
|
||||
|
||||
run(
|
||||
[npm, "publish", "--access", "public", "--ignore-scripts"],
|
||||
cwd=repo_root,
|
||||
env=env,
|
||||
)
|
||||
|
||||
run([npm, "view", PACKAGE_NAME, "version"], cwd=repo_root, env=env)
|
||||
finally:
|
||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||
|
||||
return version
|
||||
|
||||
|
||||
def update_directory(target: Path, version: str) -> None:
|
||||
if not target.exists():
|
||||
raise RuntimeError(f"Target directory does not exist: {target}")
|
||||
|
||||
npm = resolve_npm()
|
||||
run([npm, "install", f"{PACKAGE_NAME}@{version}"], cwd=target)
|
||||
|
||||
|
||||
def update_docker(service: str, version: str, compose_file: Path | None, project_dir: Path) -> None:
|
||||
docker = shutil.which("docker")
|
||||
if docker is None:
|
||||
raise RuntimeError("Docker is not available in PATH")
|
||||
|
||||
command = [docker, "compose"]
|
||||
if compose_file is not None:
|
||||
command.extend(["-f", str(compose_file)])
|
||||
command.extend(["exec", "-T", service, "npm", "install", f"{PACKAGE_NAME}@{version}"])
|
||||
run(command, cwd=project_dir)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Publish the Mega n8n package and update a target n8n installation.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--token",
|
||||
default=os.environ.get("NPM_TOKEN", "").strip(),
|
||||
help="Granular npm token with publish permission. Defaults to NPM_TOKEN.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-publish",
|
||||
action="store_true",
|
||||
help="Skip npm publish and only run the update step.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
default="",
|
||||
help="Version to install on the target. Defaults to package.json version.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-whoami",
|
||||
action="store_true",
|
||||
help="Skip npm whoami before publish.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--target",
|
||||
default="",
|
||||
help="Directory where npm install should run for the n8n update step.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--docker-service",
|
||||
default="",
|
||||
help="Docker Compose service name to update with npm install inside the container.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--docker-compose-file",
|
||||
default="",
|
||||
help="Optional docker-compose file to use with --docker-service.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--docker-project-dir",
|
||||
default="",
|
||||
help="Working directory for docker compose. Defaults to the compose file directory or current directory.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
repo_root = Path(__file__).resolve().parent.parent
|
||||
version = args.version.strip() or read_package_version(repo_root)
|
||||
|
||||
if not args.skip_publish:
|
||||
token = args.token
|
||||
if not token:
|
||||
token = getpass.getpass("NPM token: ").strip()
|
||||
|
||||
if not token:
|
||||
print("Missing npm token. Pass --token, set NPM_TOKEN, or enter it when prompted.", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
version = publish_package(repo_root, token, args.skip_whoami)
|
||||
|
||||
if args.target.strip():
|
||||
update_directory(Path(args.target).resolve(), version)
|
||||
|
||||
if args.docker_service.strip():
|
||||
compose_file = Path(args.docker_compose_file).resolve() if args.docker_compose_file.strip() else None
|
||||
if args.docker_project_dir.strip():
|
||||
project_dir = Path(args.docker_project_dir).resolve()
|
||||
elif compose_file is not None:
|
||||
project_dir = compose_file.parent
|
||||
else:
|
||||
project_dir = Path.cwd()
|
||||
update_docker(args.docker_service.strip(), version, compose_file, project_dir)
|
||||
|
||||
if not args.target.strip() and not args.docker_service.strip():
|
||||
print("Publish completed. No update target was provided.")
|
||||
else:
|
||||
print(f"Completed publish/update flow for {PACKAGE_NAME}@{version}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Loading…
Reference in a new issue