From 8f47fcbb4987a853e3bcbce5dc86adbdaea3afb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Brunner?= Date: Tue, 19 Nov 2024 16:25:29 +0100 Subject: [PATCH] Add support of node packages --- config.md | 13 ++++- tag_publish/cli.py | 36 +++++++++++++ tag_publish/configuration.py | 98 +++++++++++++++++++++++++++++++++--- tag_publish/publish.py | 68 +++++++++++++++++++++++++ tag_publish/schema.json | 63 ++++++++++++++++++++++- 5 files changed, 268 insertions(+), 10 deletions(-) diff --git a/config.md b/config.md index 2175369..fffd936 100644 --- a/config.md +++ b/config.md @@ -9,6 +9,7 @@ _Tag Publish configuration file_ - **`tag_to_version_re`**: Refer to _[#/definitions/version_transform](#definitions/version_transform)_. - **`docker`**: Refer to _[#/definitions/docker](#definitions/docker)_. - **`pypi`**: Refer to _[#/definitions/pypi](#definitions/pypi)_. +- **`node`**: Refer to _[#/definitions/node](#definitions/node)_. - **`helm`**: Refer to _[#/definitions/helm](#definitions/helm)_. - **`dispatch`** _(array)_: Default: `[]`. - **Items** _(object)_: Send a dispatch event to an other repository. Default: `{}`. @@ -25,7 +26,7 @@ _Tag Publish configuration file_ - **`name`** _(string)_: The image name. - **`tags`** _(array)_: The tag name, will be formatted with the version=, the image with version=latest should be present when we call the tag-publish script. Default: `["{version}"]`. - **Items** _(string)_ - - **`repository`** _(object)_: The repository where we should publish the images. Can contain additional properties. Default: `{"github": {"server": "ghcr.io", "versions": ["version_tag", "version_branch", "rebuild"]}, "dockerhub": {}}`. + - **`repository`** _(object)_: The repository where we should publish the images. Can contain additional properties. Default: `{"github": {"server": "ghcr.io", "versions": ["version_tag", "version_branch", "rebuild"]}}`. - **Additional properties** _(object)_ - **`server`** _(string)_: The server URL. - **`versions`** _(array)_: The kind or version that should be published, tag, branch or value of the --version argument of the tag-publish script. Default: `["version_tag", "version_branch", "rebuild", "feature_branch"]`. @@ -51,6 +52,16 @@ _Tag Publish configuration file_ - **Items** _(string)_ - **`versions`** _(array)_: The kind or version that should be published, tag, branch or value of the --version argument of the tag-publish script. Default: `["version_tag"]`. - **Items** _(string)_ +- **`node`** _(object)_: Configuration to publish on node. + - **`packages`** _(array)_: The configuration of packages that will be published. + - **Items** _(object)_: The configuration of package that will be published. + - **`group`** _(string)_: The image is in the group, should be used with the --group option of tag-publish script. Default: `"default"`. + - **`folder`** _(string)_: The folder of the node package. Default: `"."`. + - **`versions`** _(array)_: The kind or version that should be published, tag, branch or value of the --version argument of the tag-publish script. Default: `["version_tag"]`. + - **Items** _(string)_ + - **`repository`** _(object)_: The packages repository where we should publish the packages. Can contain additional properties. Default: `{"github": {"server": "npm.pkg.github.com"}}`. + - **Additional properties** _(object)_ + - **`server`** _(string)_: The server URL. - **`helm`** _(object)_: Configuration to publish Helm charts on GitHub release. - **`folders`** _(array)_: The folders that will be published. - **Items** _(string)_ diff --git a/tag_publish/cli.py b/tag_publish/cli.py index 60c6ff6..9ab9bc6 100644 --- a/tag_publish/cli.py +++ b/tag_publish/cli.py @@ -183,6 +183,9 @@ def main() -> None: success &= _handle_pypi_publish( args.group, args.dry_run, config, version, version_type, github, published_payload ) + success &= _handle_node_publish( + args.group, args.dry_run, config, version, version_type, github, published_payload + ) success &= _handle_docker_publish( args.group, args.dry_run, @@ -232,6 +235,39 @@ def _handle_pypi_publish( return success +def _handle_node_publish( + group: str, + dry_run: bool, + config: tag_publish.configuration.Configuration, + version: str, + version_type: str, + github: tag_publish.GH, + published_payload: list[tag_publish.PublishedPayload], +) -> bool: + success = True + node_config = config.get("node", {}) + if node_config: + for package in node_config.get("packages", []): + if package.get("group", tag_publish.configuration.PIP_PACKAGE_GROUP_DEFAULT) == group: + publish = version_type in node_config.get( + "versions", tag_publish.configuration.PYPI_VERSIONS_DEFAULT + ) + folder = package.get("folder", tag_publish.configuration.PYPI_PACKAGE_FOLDER_DEFAULT) + for repo_name, repo_config in node_config.get("repository", {}).items(): + if dry_run: + print( + f"{'Publishing' if publish else 'Checking'} '{folder}' to {repo_name}, " + "skipping (dry run)" + ) + else: + success &= tag_publish.publish.node( + package, version, version_type, repo_config, publish + ) + if publish: + published_payload.append({"type": "node", "folder": folder}) + return success + + def _handle_docker_publish( group: str, dry_run: bool, diff --git a/tag_publish/configuration.py b/tag_publish/configuration.py index f7318a1..354dc4e 100644 --- a/tag_publish/configuration.py +++ b/tag_publish/configuration.py @@ -1,13 +1,11 @@ -""" -Automatically generated file from a JSON schema. +"""Automatically generated file from a JSON schema. """ from typing import Any, Dict, List, Literal, TypedDict, Union class Configuration(TypedDict, total=False): - """ - configuration. + """configuration. Tag Publish configuration file """ @@ -33,6 +31,13 @@ class Configuration(TypedDict, total=False): Configuration to publish on pypi """ + node: "Node" + """ + node. + + Configuration to publish on node + """ + helm: "Helm" """ helm. @@ -82,8 +87,7 @@ class Configuration(TypedDict, total=False): DOCKER_REPOSITORY_DEFAULT = { - "github": {"server": "ghcr.io", "versions": ["version_tag", "version_branch", "rebuild"]}, - "dockerhub": {}, + "github": {"server": "ghcr.io", "versions": ["version_tag", "version_branch", "rebuild"]} } """ Default value of the field path 'Docker repository' """ @@ -152,7 +156,6 @@ class Docker(TypedDict, total=False): The repository where we should publish the images default: - dockerhub: {} github: server: ghcr.io versions: @@ -245,6 +248,87 @@ class Helm(TypedDict, total=False): """ +NODE_PACKAGE_FOLDER_DEFAULT = "." +""" Default value of the field path 'node package folder' """ + + +NODE_REPOSITORY_DEFAULT = {"github": {"server": "npm.pkg.github.com"}} +""" Default value of the field path 'node repository' """ + + +NODE_VERSIONS_DEFAULT = ["version_tag"] +""" Default value of the field path 'node versions' """ + + +class Node(TypedDict, total=False): + """ + node. + + Configuration to publish on node + """ + + packages: List["NodePackage"] + """ The configuration of packages that will be published """ + + versions: List[str] + """ + node versions. + + The kind or version that should be published, tag, branch or value of the --version argument of the tag-publish script + + default: + - version_tag + """ + + repository: Dict[str, "NodePackagesRepository"] + """ + Node repository. + + The packages repository where we should publish the packages + + default: + github: + server: npm.pkg.github.com + """ + + +class NodePackage(TypedDict, total=False): + """ + node package. + + The configuration of package that will be published + """ + + group: str + """ + pip package group. + + The image is in the group, should be used with the --group option of tag-publish script + + default: default + """ + + folder: str + """ + node package folder. + + The folder of the node package + + default: . + """ + + +class NodePackagesRepository(TypedDict, total=False): + """Node packages repository.""" + + server: str + """ The server URL """ + + +PIP_PACKAGE_GROUP2953_DEFAULT = "default" +""" Default value of the field path 'node package group' """ + + PIP_PACKAGE_GROUP_DEFAULT = "default" """ Default value of the field path 'pypi package group' """ diff --git a/tag_publish/publish.py b/tag_publish/publish.py index 35d1dd1..6516ed7 100644 --- a/tag_publish/publish.py +++ b/tag_publish/publish.py @@ -4,6 +4,7 @@ import datetime import glob +import json import os import re import subprocess # nosec @@ -103,6 +104,73 @@ def pip( return True +def node( + package: tag_publish.configuration.PypiPackage, + version: str, + version_type: str, + repo_config: tag_publish.configuration.NodeRepository, + publish: bool, +) -> bool: + """ + Publish to npm. + + Arguments: + version: The version that will be published + version_type: Describe the kind of release we do: rebuild (specified using --type), version_tag, + version_branch, feature_branch, feature_tag (for pull request) + repo_config: The repository configuration + publish: If False only check the package + package: The package configuration + github: The GitHub helper + + """ + del version_type + + folder = package.get("folder", tag_publish.configuration.PYPI_PACKAGE_FOLDER_DEFAULT) + print(f"::group::{'Publishing' if publish else 'Checking'} '{folder}' to npm") + sys.stdout.flush() + sys.stderr.flush() + + try: + with open(os.path.join(folder, "package.json"), encoding="utf-8") as open_file: + package_json = json.loads(open_file) + package_json["version"] = version + with open(os.path.join(folder, "package.json"), "w", encoding="utf-8") as open_file: + json.dumps(package_json, open_file) + + cwd = os.path.abspath(folder) + + is_github = repo_config["server"] == "npm.pkg.github.com" + old_npmrc = None + npmrc_filename = os.path.expanduser("~/.npmrc") + env = {**os.environ} + if is_github: + old_npmrc = None + if os.path.exists(npmrc_filename): + with open(npmrc_filename, encoding="utf-8") as open_file: + old_npmrc = open_file.read() + with open(npmrc_filename, "w", encoding="utf-8") as open_file: + open_file.write(f"registry=https://{repo_config['server']}\n") + open_file.write("always-auth=true\n") + env["NODE_AUTH_TOKEN"] = os.environ["GITHUB_TOKEN"] + + subprocess.run(["npm", "publish", *([] if publish else ["--dry-run"])], cwd=cwd, check=True, env=env) + + if is_github: + if old_npmrc is None: + os.remove(npmrc_filename) + else: + with open(npmrc_filename, "w", encoding="utf-8") as open_file: + open_file.write(old_npmrc) + print("::endgroup::") + except subprocess.CalledProcessError as exception: + print(f"Error: {exception}") + print("::endgroup::") + print("::error::With error") + return False + return True + + def docker( config: tag_publish.configuration.DockerRepository, name: str, diff --git a/tag_publish/schema.json b/tag_publish/schema.json index 2f22059..a3ed4ec 100644 --- a/tag_publish/schema.json +++ b/tag_publish/schema.json @@ -53,8 +53,7 @@ "github": { "server": "ghcr.io", "versions": ["version_tag", "version_branch", "rebuild"] - }, - "dockerhub": {} + } }, "type": "object", "additionalProperties": { @@ -165,6 +164,65 @@ } } }, + "node": { + "title": "node", + "description": "Configuration to publish on node", + "type": "object", + "properties": { + "packages": { + "description": "The configuration of packages that will be published", + "type": "array", + "items": { + "title": "node package", + "description": "The configuration of package that will be published", + "type": "object", + "properties": { + "group": { + "description": "The image is in the group, should be used with the --group option of tag-publish script", + "title": "pip package group", + "default": "default", + "type": "string" + }, + "folder": { + "title": "node package folder", + "description": "The folder of the node package", + "type": "string", + "default": "." + } + } + } + }, + "versions": { + "title": "node versions", + "description": "The kind or version that should be published, tag, branch or value of the --version argument of the tag-publish script", + "type": "array", + "default": ["version_tag"], + "items": { + "type": "string" + } + }, + "repository": { + "title": "Node repository", + "description": "The packages repository where we should publish the packages", + "default": { + "github": { + "server": "npm.pkg.github.com" + } + }, + "type": "object", + "additionalProperties": { + "title": "Node packages repository", + "type": "object", + "properties": { + "server": { + "description": "The server URL", + "type": "string" + } + } + } + } + } + }, "helm": { "title": "helm", "description": "Configuration to publish Helm charts on GitHub release", @@ -219,6 +277,7 @@ }, "docker": { "$ref": "#/definitions/docker" }, "pypi": { "$ref": "#/definitions/pypi" }, + "node": { "$ref": "#/definitions/node" }, "helm": { "$ref": "#/definitions/helm" }, "dispatch": { "title": "Dispatch",