From 6c7b73a9b1f7e2acba9e68c9b339c00c158285cb Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 5 Apr 2024 19:29:47 -0400 Subject: [PATCH] Add PGXN and GitHub release workflow Add `CHANGELOG.md` and fill it out for the forthcoming v0.1.0. Add `.ci/mknotes` to parse `CHANGELOG.md` and generate a change log only for the current version in `target/release_notes.md` (ported from a personal Go project). Add `META.json.in` and a `make` target to generate `META.json` to release on PGXN. Add `github/workflows/release.yml` to release to PGXN and GitHub. Add other `make` targets to build `target/release_notes.md` and a release Zip file. Although the `pgxn-bundle` script in the workflow does the same thing, this is useful for testing that the bundle is as it should be. --- .ci/mknotes | 150 ++++++++++++++++++++++++++++++++++ .gitattributes | 5 ++ .github/workflows/release.yml | 39 +++++++++ .gitignore | 2 + CHANGELOG.md | 34 ++++++++ META.json.in | 47 +++++++++++ Makefile | 23 +++++- README.md | 2 + 8 files changed, 299 insertions(+), 3 deletions(-) create mode 100755 .ci/mknotes create mode 100644 .gitattributes create mode 100644 .github/workflows/release.yml create mode 100644 CHANGELOG.md create mode 100644 META.json.in diff --git a/.ci/mknotes b/.ci/mknotes new file mode 100755 index 0000000..76061a9 --- /dev/null +++ b/.ci/mknotes @@ -0,0 +1,150 @@ +#!/usr/bin/env perl + +package mknotes; + +use strict; +use warnings; +use v5.30; +use Getopt::Long qw(GetOptions); +use File::Path qw(make_path); +use File::Basename qw(dirname); +use utf8; + +my $item_regex = qr/^[*-+]\s+/; +my $indent_regex = qr/^\s+/; + +sub new { + my $self = { + version => '', + file => '', + repo => '', + output => '', + }; + + GetOptions( + "f=s" => \$self->{file}, + "v=s" => \$self->{version}, + "r=s" => \$self->{repo}, + "o=s" => \$self->{output}, + ) or die("Error in command line arguments\n"); + + if (!$self->{file} || !$self->{version} || !$self->{repo}) { + die "Usage: mknotes -v [VERSION] -f [CHANGELOG] -r [REPO]\n"; + } + + # Trim a slash from the URI. + $self->{repo} =~ s{/$}{}; + + bless $self => __PACKAGE__; +} + +sub run { + my $self = shift; + + open my $fh, '<:encoding(UTF-8)', $self->{file} + or die "Cannot open $self->{file}: $!\n"; + + # Print to STDOUT by default. + my $out; + if ($self->{output}) { + my $dir = dirname $self->{output}; + make_path $dir unless -d $dir; + open $out, '>:encoding(UTF-8)', $self->{output} + or die "Cannot open $self->{output}: $!\n"; + } else { + $out = \*STDOUT; + binmode $out, ':encoding(UTF-8)'; + } + + my $header = "## [v$self->{version}]"; + my ($found, $in_item) = (0, 0); + + while (my $line = <$fh>) { + if ($line =~ /^\Q$header/) { + # Found the header for this version. Build a regex for its link + # reference. + $found = 1; + my $link_regex = qr/^\s*\Q[v$self->{version}]\E:\s+https:/; + + # Continue scanning until we reach the next `## ` header. + while (my $line = <$fh>) { + chomp $line; + return $self->finish_list($out, $line) if $line =~ /^## /; + + # Skip the line if it's the link reference for the header. + $in_item = $self->print_line($out, $line, $in_item) + unless $line =~ $link_regex; + } + } + } + + # All done! + die "Version $self->{version} not found in $self->{file}\n" unless $found; +} + +# Called when the next version header is found in line. +sub finish_list { + my ($self, $out, $line) = @_; + # Next header. Extract its version. + + my ($prev) = $line =~ /\[([^]]+)\]/; + die "No version found in $line\n" unless $prev; + + # Emit a footer line with a link to the diff for this version. + print {$out} sprintf( + "---\n\n๐Ÿ†š For more detail compare [changes since %s](%s/compare/%s...%s).\n", + $prev, $self->{repo}, $prev, $self->{version}, + ); + + # All done. + return 1; +} + +# Called for a line to print, keeping track of list item status. +sub print_line { + my ($self, $out, $line, $in_item) = @_; + # Convert wrapped list items to single lines. + if ($line =~ $item_regex) { + if ($in_item) { + # Previous item done + print {$out} "\n"; + } else { + # We're in an item now. + $in_item = 1 + } + # Print the line with no newline. + print {$out} $line; + + return $in_item; + } + + if ($in_item) { + # In an item, but not starting a new item. + if ($line =~ $indent_regex) { + # Continued item, convert indent to single space. + $line =~ s/$indent_regex/ /g; + # Print without newline. + print {$out} $line; + } else { + # No longer in an item. + $in_item = 0; + # Previous item done; print complete line. + print {$out} "\n", $line, "\n"; + } + + return $in_item; + } + + # Not in an item. + $in_item = 0; + # Print complete line + print {$out} $line, "\n"; + + return $in_item; +} + +package main; + +mknotes->new->run; + +__END__ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fde9442 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +.ci export-ignore +.gitignore export-ignore +.gitattributes export-ignore +.github export-ignore +META.json.in export-ignore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..bc9703b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,39 @@ +name: ๐Ÿš€ Bundle and Release +on: + push: + # Release on semantic version tag. + tags: ['v[0-9]+.[0-9]+.[0-9]+'] +jobs: + release: + name: ๐Ÿš€ Release on PGXN and GitHub + runs-on: ubuntu-latest + container: pgxn/pgxn-tools + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PGXN_USERNAME: ${{ secrets.PGXN_USERNAME }} + PGXN_PASSWORD: ${{ secrets.PGXN_PASSWORD }} + steps: + - name: Check out the repo + uses: actions/checkout@v4 + - name: Bundle the Release + id: bundle + env: { GIT_BUNDLE_OPTS: --add-file META.json } + run: make META.json && pgxn-bundle + - name: Release on PGXN + run: pgxn-release + - name: Generate Release Changes + run: make target/release-notes.md + - name: Create GitHub Release + id: release + uses: actions/create-release@v1 + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + body_path: target/release-notes.md + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + with: + upload_url: ${{ steps.release.outputs.upload_url }} + asset_path: ./${{ steps.bundle.outputs.bundle }} + asset_name: ${{ steps.bundle.outputs.bundle }} + asset_content_type: application/zip diff --git a/.gitignore b/.gitignore index be4e334..6e09beb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ **/*.rs.bk Cargo.lock .vscode/ +META.json +jsonschema-* diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d7c94f6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,34 @@ +# Changelog + +All notable changes to this project will be documented in this file. It uses the +[Keep a Changelog] format, and this project adheres to [Semantic Versioning]. + + [Keep a Changelog]: https://keepachangelog.com/en/1.1.0/ + [Semantic Versioning]: https://semver.org/spec/v2.0.0.html + "Semantic Versioning 2.0.0" + +## [v0.1.0] --- Unreleased + +The theme of this release is *learning Rust and pgrx.* + +### โšก Improvements + +* First release, everything is new! +* JSON Schema validation using [boon] +* Fully supports draft 2020-12, draft 2019-09, draft-7, draft-6, and draft-4 +* Multi-object schema specification + +### ๐Ÿ—๏ธ Build Setup + +* Built with Rust +* Use `make` for most actions + +### ๐Ÿ“š Documentation + +* Build and install docs in the [README] +* Full [reference documentation] + + [v0.1.0]: https://github.com/tembo-io/pg-jsonschema/compare/34d5d49...HEAD + [boon]: https://github.com/santhosh-tekuri/boon + [README]: https://github.com/tembo-io/pg-jsonschema/blob/v0.1.0/README.md + [reference documentation]: https://github.com/tembo-io/pg-jsonschema/blob/v0.1.0/doc/jsonschema.md diff --git a/META.json.in b/META.json.in new file mode 100644 index 0000000..7c32bfe --- /dev/null +++ b/META.json.in @@ -0,0 +1,47 @@ +{ + "name": "jsonschema", + "abstract": "JSON Schema validation functions for PostgreSQL", + "description": "The jsonschema extension provides JSON Schema validation functions supporting drafts 2020-12, 2019-09, 7, 6, and 4", + "version": "@CARGO_VERSION@", + "maintainer": [ + "David E. Wheeler ", + "Tembo " + ], + "license": "mit", + "provides": { + "jsonschema": { + "abstract": "JSON Schema validation functions for PostgreSQL", + "file": "jsonschema.control", + "docfile": "doc/jsonschema.md", + "version": "@CARGO_VERSION@" + } + }, + "prereqs": { + "runtime": { + "requires": { + "PostgreSQL": "11.0.0" + } + } + }, + "resources": { + "bugtracker": { + "web": "https://github.com/tembo-io/pg-jsonschema/issues/" + }, + "repository": { + "url": "git://github.com/tembo-io/pg-jsonschema.git", + "web": "https://github.com/tembo-io/pg-jsonschema/", + "type": "git" + } + }, + "generated_by": "David E. Wheeler", + "meta-spec": { + "version": "1.0.0", + "url": "https://pgxn.org/meta/spec.txt" + }, + "tags": [ + "jsonschema", + "validation", + "pgrx", + "rust" + ] +} diff --git a/Makefile b/Makefile index 9cdfac2..5655e97 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,8 @@ PG_CONFIG ?= $(shell which pg_config) -PGRXV="$(shell perl -nE '/^pgrx\s+=\s"=?([^"]+)/ && do { say $$1; exit }' Cargo.toml)" -PGV=$(shell perl -E 'shift =~ /(\d+)/ && say $$1' "$(shell $(PG_CONFIG) --version)") +DISTNAME = $(shell perl -nE '/^name\s*=\s*"([^"]+)/ && do { say $$1; exit }' Cargo.toml) +DISTVERSION = $(shell perl -nE '/^version\s*=\s*"([^"]+)/ && do { say $$1; exit }' Cargo.toml) +PGRXV = $(shell perl -nE '/^pgrx\s+=\s"=?([^"]+)/ && do { say $$1; exit }' Cargo.toml) +PGV = $(shell perl -E 'shift =~ /(\d+)/ && say $$1' "$(shell $(PG_CONFIG) --version)") .DEFAULT_GOAL: package # Build jsonshcmea for the PostgreSQL cluster identified by pg_config. package: @@ -29,6 +31,21 @@ pgrx-version: pg-version: Cargo.toml @echo $(PGV) -## cleaan: Remove build artifacts and intermediate files. +## clean: Remove build artifacts and intermediate files. clean: target @cargo clean + @rm -rf META.json $(DISTNAME)-$(DISTVERSION).zip + +# Create the PGXN META.json file. +META.json: META.json.in Cargo.toml + @sed "s/@CARGO_VERSION@/$(DISTVERSION)/g" $< > $@ + +# Create a PGXN-compatible zip file. +$(DISTNAME)-$(DISTVERSION).zip: META.json + git archive --format zip --prefix $(DISTNAME)-$(DISTVERSION)/ --add-file $< -o $(DISTNAME)-$(DISTVERSION).zip HEAD + +## pgxn-zip: Create a PGXN-compatible zip file. +pgxn-zip: $(DISTNAME)-$(DISTVERSION).zip + +target/release-notes.md: CHANGELOG.md .ci/mknotes + @./.ci/mknotes -v $(DISTVERSION) -f $< -r https://github.com/$(or $(GITHUB_REPOSITORY),tembo-io/pg-jsonschema) -o $@ diff --git a/README.md b/README.md index e537979..7a05de3 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ JSON Schema Postgres Extension [![Build Status](https://github.com/tembo-io/pg-jsonschema/actions/workflows/lint-and-test.yml/badge.svg)](https://github.com/tembo-io/pg-jsonschema/actions/workflows/lint-and-test.yml "๐Ÿงช Lint and Test") [![Code Coverage](https://codecov.io/gh/tembo-io/pg-jsonschema/graph/badge.svg?token=DIFED324ZY)](https://codecov.io/gh/tembo-io/pg-jsonschema "๐Ÿ“Š Code Coverage") +[Change Log](CHANGELOG.md) | [Documentation](doc/jsonschema.md) + This library provides the `jsonschema` extension for validating JSON and JSONB against a [JSON Schema] in Postgres. It supports the following [specification drafts] as validated by the [JSON-Schema-Test-Suite] excluding optional