diff --git a/.github/publish.yaml b/.github/publish.yaml index 94724be..71d0f22 100644 --- a/.github/publish.yaml +++ b/.github/publish.yaml @@ -10,15 +10,11 @@ docker: auto_login: true images: - name: camptocamp/tag-publish - repository: - github: - server: ghcr.io - versions: - - version_tag - - version_branch - - rebuild helm: - folders: - - tests + packages: + - folder: tests +node: + packages: + - folder: tests dispatch: - {} diff --git a/config.md b/config.md index 2175369..485b797 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,9 +52,21 @@ _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)_ -- **`helm`** _(object)_: Configuration to publish Helm charts on GitHub release. - - **`folders`** _(array)_: The folders that will be published. +- **`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. + - **`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 pypi 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)_ - **`version_transform`** _(array)_: A version transformer definition. diff --git a/tag_publish/cli.py b/tag_publish/cli.py index 60c6ff6..d6ddafa 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, published_payload + ) success &= _handle_docker_publish( args.group, args.dry_run, @@ -195,7 +198,9 @@ def main() -> None: published_payload, local, ) - success &= _handle_helm_publish(args.dry_run, config, version, version_type, github, published_payload) + success &= _handle_helm_publish( + args.group, args.dry_run, config, version, version_type, github, published_payload + ) _trigger_dispatch_events(config, version, version_type, published_payload, github) if not success: @@ -232,6 +237,38 @@ 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, + 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, @@ -451,6 +488,7 @@ def _handle_docker_publish( def _handle_helm_publish( + group: str, dry_run: bool, config: tag_publish.configuration.Configuration, version: str, @@ -496,13 +534,19 @@ def _handle_helm_publish( versions[-1] = str(int(versions[-1]) + 1) version = ".".join(versions) - for folder in helm_config["folders"]: - if dry_run: - print(f"Publishing '{folder}' to helm, skipping (dry run)") - else: - token = os.environ["GITHUB_TOKEN"] - success &= tag_publish.publish.helm(folder, version, owner, repo, commit_sha, token) - published_payload.append({"type": "helm", "folder": folder}) + for package in helm_config["packages"]: + if package.get("group", tag_publish.configuration.PIP_PACKAGE_GROUP_DEFAULT) == group: + publish = version_type in helm_config.get( + "versions", tag_publish.configuration.PYPI_VERSIONS_DEFAULT + ) + if publish: + folder = package.get("folder", tag_publish.configuration.HELM_PACKAGE_FOLDER_DEFAULT) + if dry_run: + print(f"Publishing '{folder}' to helm, skipping (dry run)") + else: + token = os.environ["GITHUB_TOKEN"] + success &= tag_publish.publish.helm(folder, version, owner, repo, commit_sha, token) + published_payload.append({"type": "helm", "folder": folder}) return success diff --git a/tag_publish/configuration.py b/tag_publish/configuration.py index f7318a1..fa88ead 100644 --- a/tag_publish/configuration.py +++ b/tag_publish/configuration.py @@ -33,6 +33,13 @@ class Configuration(TypedDict, total=False): Configuration to publish on pypi """ + node: "Node" + """ + node. + + Configuration to publish on node + """ + helm: "Helm" """ helm. @@ -82,8 +89,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 +158,6 @@ class Docker(TypedDict, total=False): The repository where we should publish the images default: - dockerhub: {} github: server: ghcr.io versions: @@ -220,6 +225,14 @@ class DockerRepository(TypedDict, total=False): """ +HELM_PACKAGE_FOLDER_DEFAULT = "." +""" Default value of the field path 'helm package folder' """ + + +HELM_PACKAGE_GROUP_DEFAULT = "default" +""" Default value of the field path 'helm package group' """ + + HELM_VERSIONS_DEFAULT = ["version_tag"] """ Default value of the field path 'helm versions' """ @@ -231,8 +244,8 @@ class Helm(TypedDict, total=False): Configuration to publish Helm charts on GitHub release """ - folders: List[str] - """ The folders that will be published """ + packages: List["HelmPackage"] + """ The configuration of packages that will be published """ versions: List[str] """ @@ -245,6 +258,113 @@ class Helm(TypedDict, total=False): """ +class HelmPackage(TypedDict, total=False): + """ + helm package. + + The configuration of package that will be published + """ + + group: str + """ + helm package group. + + The image is in the group, should be used with the --group option of tag-publish script + + default: default + """ + + folder: str + """ + helm package folder. + + The folder of the pypi package + + default: . + """ + + +NODE_PACKAGE_FOLDER_DEFAULT = "." +""" Default value of the field path 'node package folder' """ + + +NODE_PACKAGE_GROUP_DEFAULT = "default" +""" Default value of the field path 'node package group' """ + + +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, "NodeRepository"] + """ + 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 + """ + node 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 NodeRepository(TypedDict, total=False): + """Node repository.""" + + server: str + """ The server URL """ + + 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..dffea05 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.NodePackage, + 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.read()) + package_json["version"] = version + with open(os.path.join(folder, "package.json"), "w", encoding="utf-8") as open_file: + open_file.write(json.dumps(package_json, indent=2) + "\n") + + 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..101ec36 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,16 +164,91 @@ } } }, + "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": "node 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 repository", + "type": "object", + "properties": { + "server": { + "description": "The server URL", + "type": "string" + } + } + } + } + } + }, "helm": { "title": "helm", "description": "Configuration to publish Helm charts on GitHub release", "type": "object", "properties": { - "folders": { - "description": "The folders that will be published", + "packages": { + "description": "The configuration of packages that will be published", "type": "array", "items": { - "type": "string" + "title": "helm 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": "helm package group", + "default": "default", + "type": "string" + }, + "folder": { + "title": "helm package folder", + "description": "The folder of the pypi package", + "type": "string", + "default": "." + } + } } }, "versions": { @@ -219,6 +293,7 @@ }, "docker": { "$ref": "#/definitions/docker" }, "pypi": { "$ref": "#/definitions/pypi" }, + "node": { "$ref": "#/definitions/node" }, "helm": { "$ref": "#/definitions/helm" }, "dispatch": { "title": "Dispatch", diff --git a/tests/package.json b/tests/package.json new file mode 100644 index 0000000..029e9b8 --- /dev/null +++ b/tests/package.json @@ -0,0 +1,3 @@ +{ + "name": "@camptocamp/tag-publish" +}