├── .github └── workflows │ └── ci.yml ├── README.md ├── contributing.md ├── library-guidelines.md ├── pursuit-preregistry.md └── updater ├── .gitignore ├── .tidyrc.json ├── README.md ├── UpdateChecklist.md ├── bin └── updater.js ├── docs ├── 01-Generate.md ├── 02-Sync-Labels.md └── README.md ├── index.js ├── package-lock.json ├── package.json ├── packages.dhall ├── shell.nix ├── spago.dhall ├── src ├── Main.purs ├── Node │ └── FS │ │ └── Aff │ │ ├── Extra.js │ │ └── Extra.purs └── Updater │ ├── Cli.purs │ ├── Command.purs │ ├── Generate │ ├── Changelog.purs │ └── Template.purs │ ├── SyncLabels │ ├── IssueLabel.purs │ ├── Repos.purs │ └── Request.purs │ └── Utils │ ├── Dhall.purs │ └── Options.purs ├── templates ├── base │ ├── .editorconfig │ ├── .github │ │ ├── ISSUE_TEMPLATE │ │ │ ├── bug-report.md │ │ │ ├── change-request.md │ │ │ └── config.yml │ │ ├── PULL_REQUEST_TEMPLATE.md │ │ └── workflows │ │ │ └── ci.yml │ ├── .gitignore │ ├── CHANGELOG.md │ ├── CONTRIBUTING.md │ ├── README.md │ └── docs │ │ └── README.md └── js │ ├── .eslintrc.json │ ├── .github │ └── workflows │ │ └── ci.yml │ ├── .gitignore │ └── package.json └── test └── Main.purs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: "*" 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up PureScript toolchain 17 | uses: purescript-contrib/setup-purescript@main 18 | with: 19 | purs-tidy: "latest" 20 | 21 | - name: Cache PureScript dependencies 22 | uses: actions/cache@v2 23 | with: 24 | key: ${{ runner.os }}-spago-${{ hashFiles('updater/**/*.dhall') }} 25 | path: | 26 | updater/.spago 27 | updater/output 28 | 29 | - name: Install dependencies 30 | run: spago install 31 | working-directory: updater 32 | 33 | - name: Build source 34 | run: spago build --no-install 35 | working-directory: updater 36 | 37 | - name: Lint source 38 | run: purs-tidy check src test 39 | working-directory: updater 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PureScript Contributors Governance 2 | 3 | The Contributors organization maintains useful libraries and tools on behalf of the PureScript community. This repository contains helpful resources for writing and maintainging libraries within the Contributors organization. 4 | 5 | If you are a PureScript organization or user we encourage you to use any of these resources as you see fit! There is no need to attribute anything to the Contributors organization. 6 | 7 | You may want to begin with these resources: 8 | 9 | - The [contributing](./contributing.md) guide helps new contributors get started with their first contributions to a member library. 10 | - The [library guidelines](./library-guidelines.md) guide outlines expectations for each library in the organization, including minimum requirements for maintenance, repository structure, documentation, and testing. 11 | - The [contrib-updater](./updater) CLI tool helps maintainers with tasks like standardizing repositories, setting issue labels, migrating new libraries into the organization, and more. It also includes templates for common files like READMEs, continuous integration, and more. 12 | - The [Pursuit publishing instructions](./pursuit-preregistry.md). 13 | 14 | ## What is the PureScript Contributors organization? 15 | 16 | The Contributors organization was started by PureScript core members, industrial Purescript users, and individuals to create and maintain commonly-used libraries for the PureScript ecosystem. It can be seen as an extension of the official PureScript organization which is less selective about membership and which is open to a broader set of libraries. 17 | 18 | The Contributors organization is also a great way for newcomers to PureScript to get their start contributing to open source in PureScript. One of our goals is to encourage new members of the community to feel welcome contributing to the ecosystem. 19 | 20 | ## How can I join? 21 | 22 | Members of the Contributors organization maintain at least one library within `purescript-contrib`. You certainly don't need to be the _only_ maintainer for a library! If you have displayed sustained interest in one or more libraries, feel free to reach out to an existing maintainer of the library or libraries in question to ask if you can become a member. 23 | 24 | For more information about joining the organization, please see the [contributing guide](./contributing.md). 25 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing to a PureScript Contributors library! This file is a short, sweet introduction to help you get started contributing to one of our projects. We ask that all new contributors read it before their first contribution to make sure we can get your work merged. 4 | 5 | ## Getting Started 6 | 7 | ### Do I belong here? 8 | 9 | Everyone is welcome! People of all experience levels can join, begin contributing, and feel comfortable and safe making mistakes. People of all backgrounds belong here so long as they treat others with dignity and respect and do not harass or belittle others. 10 | 11 | ### What is the correct way to ask a question? 12 | 13 | Feel free to ask questions by opening an issue on the relevant library. Maintainers are also active on: 14 | 15 | - The [PureScript Discourse](https://discourse.purescript.org) (the most popular option and best for detailed questions) 16 | - The [Purescript Discord](https://purescript.org/chat) where you can ask questions and have live discussions. 17 | 18 | ### I'd like to help, how do I pick something to work on? 19 | 20 | Any [open issue that is not yet assigned to someone](https://github.com/issues?q=is%3Aopen+is%3Aissue+archived%3Afalse+user%3Apurescript-contrib+no%3Aassignee) is good to work on! If it's your first time contributing it's probably best to pick an issue marked [`good first issue`](https://github.com/issues?q=is%3Aopen+is%3Aissue+archived%3Afalse+user%3Apurescript-contrib+no%3Aassignee+label%3A%22good+first+issue%22). In general, Contributors libraries follow these conventions: 21 | 22 | 1. Issues marked `good first issue` are good for beginners and/or new contributors to the library. 23 | 2. Issues marked `help wanted` signal that anyone can take the issue and it's a desired addition to the library. 24 | 3. Issues marked `document me` are requests for documentation and are often a great first issue to take on. 25 | 26 | The easiest way you can help is by contributing documentation, whether via looking for issues marked `document me` or by adding new documentation of your own. If you'd like to contribute documentation we suggest [reading about the four kinds of documentation](https://documentation.divio.com). 27 | 28 | ### How big should my contribution be? 29 | 30 | Your contribution can be as small as copypasting instructions from an issue into the project documentation! Everything is welcome, including very small changes and quality of life improvements. 31 | 32 | If you have larger contributions to make, those are also welcome. However, if you would like to contribute a particularly large or a breaking change, you may want to open an issue proposing the change before you implement it. That helps us ensure your time is not wasted. 33 | 34 | ## Contributing Code 35 | 36 | ### Tooling 37 | 38 | All `purescript-contrib` libraries use recent versions of [PureScript](https://github.com/purescript/purescript) and [Spago](https://github.com/purescript/spago). 39 | 40 | Any additional development dependencies can be installed via NPM and are listed in the `package.json` file for the repository. 41 | 42 | ### Proposing changes 43 | 44 | If you would like to contribute code, tests, or documentation, please feel free to open a pull request for small changes. For large changes we recommend you first open an issue to propose your change and ensure that the maintainers are on board before you spend time implementing the change. We want to respect your time and effort. We can also assign the issue to you if you would like to make sure you're the one to work on it. 45 | 46 | ### Merging changes 47 | 48 | All changes must happen through a pull request. Everyone with commit access can merge changes, though by convention we like to wait for two approvals for non-trivial changes. All pull requests must pass continuous integration; if the change adds new code we may also ask that you add a test. 49 | 50 | ### Publishing 51 | 52 | See 53 | - [Spago: Publish my library](https://github.com/purescript/spago?tab=readme-ov-file#publish-my-library) 54 | - [Registry: Publish a Package](https://github.com/purescript/registry#publish-a-package) 55 | 56 | ## How do I get the "commit bit"? 57 | 58 | If you'd like to take part in maintaining a package, just ask! We hand out the commit bit to folks who display sustained interest in the project. You can ask directly (for example: on Slack or via a DM on Discourse) or by opening an issue -- whichever you prefer! 59 | -------------------------------------------------------------------------------- /library-guidelines.md: -------------------------------------------------------------------------------- 1 | # PureScript Contributors Library Guidelines 2 | 3 | This short handbook outlines the mimimum expectations for libraries in the PureScript Contributors organization. Libraries must meet these requirements to be eligible to be included in the organization, and libraries that don't meet these requirements for an extended period may be transferred out of the organization or archived. This helps ensure we can maintain a high level of quality across the organization and remain accessible and welcoming to new contributors. 4 | 5 | ## Expectations for Libraries 6 | 7 | Libraries in the Contributors organization are expected to: 8 | 9 | 1. Have at least one (and preferably more) assigned maintainers, as indicated by badges in the project README. 10 | 1. Depend only on PureScript [core packages](https://github.com/orgs/purescript/repositories) or other **contrib** packages. 11 | 1. Have adequate documentation in the form of module documentation published to Pursuit, a README containing a library summary, installation instructions, and more, and a `docs` directory containing at least a short tutorial (see [the documentation section below](#documentation)). 12 | 1. Have a CHANGELOG.md in the root directory. 13 | 1. Have an adequate test suite which is exercised by continuous integration (see [the tests section below](#tests)) 14 | 1. Use the standard Contributors repository structure, which can be generated with the [contrib-updater](./updater) tool (see [the repository structure section below](#repository-structure)). 15 | 1. Use a default branch of `main` for the repository. 16 | 2. Package name registered with the [registry](https://github.com/purescript/registry). No `bower.json`. 17 | 18 | Libraries in this organization should be useful, tested, and well-documented. These are great things to shoot for in any library, but Contributors libraries are held to a particular standard to encourage new contributions and to serve as an example for other libraries in the community. 19 | 20 | ### Documentation 21 | 22 | Contributors libraries are expected to have adequate documentation. To accomplish that goal, we expect each repository to have: 23 | 24 | 1. A repository README containing badges for the build status, latest release, and maintainers, a short summary of the library's purpose, installation instructions, a quick start with a minimal usage example, and links to the documentation and contributing guide 25 | 1. Expanded documentation like how-tos, tutorials, and concept overviews in the docs directory (at minimum a short tutorial that expands on the quick start). 26 | 1. Documentation comments for the majority and preferably all publicly-exported types and functions in the library modules, which should should be uploaded to Pursuit. 27 | 28 | If you are migrating a library to the Contributors organization (or creating an new one), the [contrib-updater](./updater) tool can generate templates on your behalf to help you get started with documentation. 29 | 30 | ### Tests 31 | 32 | Contributors libraries are expected to have tests which exercise tricky parts of the code and serve as usage examples. These tests should help maintainers merge pull requests which pass CI with confidence. We expect each repository to have: 33 | 34 | 1. One or more tests in a `test` directory 35 | 1. Continuous integration via GitHub Actions which exercises this test on pull requests and the main branch 36 | 37 | ### Repository Structure 38 | 39 | Contributors libraries share a standard structure and set of configuration files which helps ensure that contributors know where to find things and how to get set up when they use a new library. Each library includes some standard files, such as: 40 | 41 | - A `.gitignore` file which ignores common dotfiles and compiler artifacts 42 | - An `.editorconfig` file which editors can use to set spacing, trailing newlines, and other configuration 43 | 44 | Some of these files can be customized per-repository, but most of them are standardized across the organization (for example, the `.editorconfig` file). In addition, libraries which use the FFI are expected to include additional files such as: 45 | 46 | - The standard `.eslintrc.json` configuration for `eslint`, which should be run on pull requests 47 | - A `package.json` file which installs `eslint` and can be used for scripts 48 | 49 | Each library also contains some standard directories, including: 50 | 51 | - A `docs` directory containing expanded documentation for the library such as tutorials and walkthroughs. 52 | - A `.github` directory containing issue templates, pull request templates, contributing guide, and workflows for tests in continuous integration and automatically labeling stale issues and pull requests. 53 | 54 | #### Standard Labels 55 | 56 | Labels are a big part of curating the issue tracker in a Contributors library. Labels are regularly and automatically synced via the [contrib-updater](./updater) CLI tool, which will set labels, colors, and descriptions across repositories. We use several standard labels for issues of various types. They can be found on [this repository's issue labels page](https://github.com/purescript-contrib/governance/issues/labels). For instructions on how to update labels across all Contributor libraries see the [Sync Labels documentation](./updater/docs/02-Sync-Labels.md). 57 | 58 | ## Expectations for Maintainers 59 | 60 | Maintainers are expected to ensure that libraries they maintain meet the minimum library requirements. The [contrib-updater CLI tool](./updater) can help you make sure the library you maintain has all the necessary scaffolding for these requirements. 61 | 62 | In addition, maintainers are expected to: 63 | 64 | 1. Interact with users of the library promptly and respectfully. Maintainers should respond to issues and pull requests quickly -- even if that response is a simple request for more information or to add a 'help wanted' label. Contributors should feel respected, heard, and motivated to contribute more. 65 | 1. Merge contributions of that are of sufficient quality. Pull requests should pass CI and should include adequate tests, documentation, and be reflected in the changelog. Pull requests that meet these criteria and are beneficial to the library should be merged. 66 | 1. Curate the issue tracker and repository contents. Maintainers should apply labels to issues, close old issues, and ensure that documentation adequately helps users get started with new contributions. If you notice a way to make the repository more accessible to newcomers, open and label an issue that describes how the project could be improved! 67 | 68 | Maintainers aren't necessarily expected to actively contribute code to the projects they maintain. They _are_ expected to keep the repository up to date, to respond quickly to issues and pull requests, and to curate issues in the issue tracker. 69 | 70 | ## Resources for Maintainers 71 | 72 | It can be daunting becoming a maintainer for the first time! Fortunately, many of the maintainers in the Contributors organization are (or were recently) in the same situation. Feel free to participate in the internal project management boards to get help with various maintenance tasks. 73 | 74 | You can also ping other maintainers on the PureScript Discourse or the functional programming Slack channel. 75 | 76 | ## FAQ 77 | 78 | ### Can my library become part of the Contributors organization? 79 | 80 | Yes! We regularly accept new libraries to become a part of `purescript-contrib`. Libraries in the organization benefit from shared maintenance and greater visibility to new contributors. However, so that we don't become overwhelmed with projects to maintain, we do have some ground rules for new libraries. 81 | 82 | 1. Your library must have at least one person committed to maintaining it. Others will help once it's in the organization, but we don't have the bandwidth to totally take over maintenance of new projects. 83 | 1. Your library should meet the minimum requirements as outlined for all Contributors libraries in this post. If you need help updating your library to meet these requirements, we can help you. 84 | -------------------------------------------------------------------------------- /pursuit-preregistry.md: -------------------------------------------------------------------------------- 1 | > [!WARNING] 2 | > 3 | > In late 2024 this document is obsolete. See 4 | > - [Spago: Publish my library](https://github.com/purescript/spago?tab=readme-ov-file#publish-my-library) 5 | > - [Registry: Publish a Package](https://github.com/purescript/registry#publish-a-package) 6 | 7 | # How to publish a package to Pursuit 8 | 9 | In 2022 Pursuit and the PureScript ecosystem are in the interstice between 10 | [Bower](https://bower.io/) and 11 | [Registry](https://github.com/purescript/registry). The PureScript package system was originally built on Bower, but 12 | [Bower has been retired](https://discourse.purescript.org/t/the-bower-registry-is-no-longer-accepting-package-submissions/1103). 13 | The PureScript core team is working on a replacement package system called Registry, but it is not yet ready. 14 | In this post-Bower, pre-Registry era, we still need to publish packages to Pursuit. 15 | 16 | These instructions provide a reasonable basic method for publishing to Pursuit in 2022. 17 | Most of this advice is only applicable in the year 2022 and will be obsolete after the whole Bower and Registry situation is sorted out. 18 | 19 | For a development shell which can run all of these commands, we recommend the deluxe `nix develop` shell from [__easy-purescript-nix__](https://github.com/justinwoo/easy-purescript-nix). Install the [Nix package manager](https://nixos.org/download.html). To enter the shell, run this command: 20 | 21 | ``` 22 | nix develop github:justinwoo/easy-purescript-nix#deluxe 23 | ``` 24 | 25 | Then, in the package repo directory, issue the following commands. 26 | 27 | 1. `git clean -xdff` 28 | 29 | Delete `.pulp-cache`, `.spago`, `bower_components`, `output`, `node_modules`, et cetera, for a totally clean build. 30 | 31 | 2. `spago bump-version --no-dry-run major` 32 | 33 | If any package dependencies might have changed then we need to generate a new `bower.json`. If we are sure that no package 34 | dependencies changed then we can skip this step. 35 | 36 | We don't really want to `spago bump-version` yet, what we want is for `spago` to generate a new `bower.json` for us, 37 | and this is the best way to get that. Commit the new `bower.json`. If it turns out that we didn't need a 38 | new `bower.json` then this command may actually succeed, in which case it will create a new git tag, which we should delete. 39 | 40 | 3. `bower install` 41 | 42 | `pulp` will need the bower dependencies installed. 43 | 44 | 4. `pulp build` `pulp docs` 45 | 46 | If these two commands succeed, then we know that the `pulp publish` command 47 | later will succeed. 48 | 49 | 5. `spago test` 50 | 51 | One last time to be sure. 52 | 53 | 6. `spago bump-version --no-dry-run major` 54 | 55 | For real this time. 56 | 57 | 7. `git push origin main` 58 | 59 | Push the *main* branch to Github. Make sure it passes CI. 60 | 61 | 8. `git push` the new tag. 62 | 63 | 9. Publish to the Registry 64 | 65 | https://github.com/purescript/registry#publish-a-package 66 | 67 | The [pacchettibotti](https://github.com/pacchettibotti) will publish our package to Pursuit. 68 | -------------------------------------------------------------------------------- /updater/.gitignore: -------------------------------------------------------------------------------- 1 | .psc* 2 | .purs* 3 | .spago 4 | 5 | bower_components 6 | node_modules 7 | output 8 | generated-docs 9 | 10 | bin/* 11 | !bin/updater.js 12 | -------------------------------------------------------------------------------- /updater/.tidyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "importSort": "source", 3 | "importWrap": "source", 4 | "indent": 2, 5 | "operatorsFile": null, 6 | "ribbon": 1, 7 | "typeArrowPlacement": "first", 8 | "unicode": "never", 9 | "width": null 10 | } 11 | -------------------------------------------------------------------------------- /updater/README.md: -------------------------------------------------------------------------------- 1 | # Updater 2 | 3 | This directory contains a command-line tool useful for updating existing contrib libraries, migrating new ones, and performing common administrative tasks. 4 | 5 | Please see the [contrib-updater documentation](./docs) to learn about the commands available in the CLI tool and how to manage related assets like templates. 6 | 7 | ## Installation 8 | 9 | The Nix shell provides all necessary dependencies and hooks for installing dependencies and building the tool. Start by entering the shell: 10 | 11 | ```sh 12 | nix-shell 13 | ``` 14 | 15 | You can now use `contrib-updater` to run the tool: 16 | 17 | ```sh 18 | contrib-updater --help 19 | ``` 20 | 21 | ## Usage 22 | 23 | You can see usage information by passing the `--help` flag to `contrib-updater` (or to one of its subcommands): 24 | 25 | ```sh 26 | contrib-updater --help 27 | ``` 28 | 29 | ## Documentation 30 | 31 | The [contrib-updater documentation](./docs) describes the commands available in the CLI tool and how to manage related assets like templates. 32 | -------------------------------------------------------------------------------- /updater/UpdateChecklist.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Library Update Checklist 3 | One goal of the Contributors working group is to make sure that the `purescript-contrib` libraries all meet the [library guidelines](https://github.com/purescript-contrib/governance/blob/main/library-guidelines.md). This includes adequate tests, documentation, and tooling (using Spago, using the same issue labels, and so on). The [contrib updater](https://github.com/purescript-contrib/governance/tree/main/updater) tool can help with this migration and it has documentation on correct use (see [the readme](https://github.com/purescript-contrib/governance/tree/main/updater#updater) for more). 4 | 5 | However, libraries require manual review and some manual steps as part of the process. In general, to update a library in the Contributors organization, you will need to: 6 | 7 | 1. Update the project to use Spago (if it doesn’t already use it) 8 | 2. Update some files that are lowercased so they are uppercased (contributing.md, for example), which should be done with git if you are on Mac or Windows 9 | 3. Use the contrib updater tool to update the project’s files 10 | 4. Reconcile any high quality content that would be removed by the updater tool by manually putting it back into the generated templates. 11 | 5. Open a pull request with your changes 12 | 6. Manually review the library’s tests and documentation to see if it meets the library guidelines. If not, open issues for each way in which it fails to meet the guidelines and add an appropriate label. 13 | 14 | You can use the [machines library](https://github.com/purescript-contrib/purescript-machines) or the [js timers library](https://github.com/purescript-contrib/purescript-js-timers) as an example of what the final result should look like (I used js timers in the walkthrough below). You can also take any of the open issues if you would like to tackle one! 15 | 16 | ## 1. Update the project to use Spago 17 | After forking / cloning the library in question and checking out a branch like `contrib-update`, the first thing to do is migrate it to Spago if it’s not already done. Specifically: 18 | 19 | 1. Run `spago init -C` to migrate the project, without comments. 20 | 2. If there are any problems, use the package registry to see what the Spago file needs to contain as dependencies. 21 | 3. (If you didn't run with `-C`, delete the comments in the packages.dhall and spago.dhall files — I consider them to be quite noisy in these standard files and make it harder to see what package set is in use.) 22 | 4. (If there are generated overrides and additions from the packages.dhall file, remove them -- now that Spago uses the `with` syntax they aren’t necessary. Or if I missed an update that no longer generates them, that’s fine too). 23 | 24 | You need to preserve the Bower file in place until the library can be updated for the package registry format. 25 | 26 | ![spago-migration](https://user-images.githubusercontent.com/10245104/92983105-6a89c500-f456-11ea-9a38-eee6c82ace62.gif) 27 | 28 | ## 2. Update some files that are lowercased 29 | Some files in these repositories (namely the pull request template and the contributing file) were added as lowercased files. The files have to be renamed because otherwise links won’t work — a link to ‘contributing.md’ is not the same as to ‘CONTRIBUTING.md’, so we need to standardize them. 30 | 31 | Simply renaming these files on Mac or Windows won’t have an effect because these file systems are case sensitive. Instead we can rename them via git with a command like: 32 | 33 | ```sh 34 | git mv .github/contributing.md ./CONTRIBUTING.md 35 | git mv .github/pull_request_template.md .github/PULL_REQUEST_TEMPLATE.md 36 | ``` 37 | 38 | ![update-lowercased](https://user-images.githubusercontent.com/10245104/92983114-78d7e100-f456-11ea-9bf9-883a4fa4604e.gif) 39 | 40 | You’ll also see an issue template in these repositories. You can ignore or delete this file. Either way, it will be removed in the next step (using the contrib-updater tool to update project files). 41 | 42 | ## 3. Use the contrib updater tool to update the project’s files 43 | In this step we’ll update the project files and repository structure. This involves a couple steps: 44 | 45 | 1. Delete any obvious unused files. Typically, you should delete the `issue_template.md` file, `.travis.yml` file, `package.json` and `package-lock.json` files (unless the project uses JS, in which case keep the `package.json` file), and `jshintrc` files. If in doubt about any other files and whether to keep them, just make a note in the PR about it. 46 | 2. Run the updater tool to generate the files the project should use. If the project uses JS (for example, via the FFI) then include the `--uses-js` flag. 47 | 48 | > Note: You should read the Travis file before deleting it. If all it does is download PureScript tooling, build the project, and upload docs to Pursuit (which is broken), then it’s safe to delete. 49 | 50 | Note: You will probably want to enter a Nix shell via the goverance/contrib-updater repository before using the updater tool, as it uses `dhall-to-json` to read the Spago file and determine the package name (you can also just install dhall-json): 51 | 52 | ![nix-shell](https://user-images.githubusercontent.com/10245104/92983124-82614900-f456-11ea-8d11-0295f71ffe42.gif) 53 | 54 | When you run the updater tool a new directory will be created named “backups”. This has a copy of any file that would have been overwritten by the updater tool, unless that file had the same contents as the updater tool has. We’ll use that in the next section to reconcile any content we want to keep. 55 | 56 | Typically you'll just run: 57 | 58 | ``` 59 | contrib-updater generate --maintainer MAINTAINER1 --maintainer MAINTAINER2 60 | ``` 61 | 62 | In action: 63 | ![delete-then-run-updater](https://user-images.githubusercontent.com/10245104/92983138-8b521a80-f456-11ea-9029-13a2c16f1763.gif) 64 | 65 | ## 4. Reconcile content 66 | If there is any good content that was in the repository before, then you can look in the backups directory and use it to update the relevant content. For example, the new README will have no library summary or quick start — most libraries do at least have a library summary already, so you can copy it over and improve it a little as you see fit. 67 | 68 | Some common things you might want to check: 69 | 70 | * If the project uses JS and has a package.json file, then you can check the backed-up file to see if it does anything other than provide a ‘clean’ command and build the project (and optionally run eslint). If this is all it provides, then you can delete it and just use the file generated by the updater tool. 71 | * Read through the backed-up README so you can excerpt any good content (like a library summary, quick start, or tutorial) into the new README or documentation folder. 72 | 73 | ## 5. Open a PR with your changes 74 | I am using this template for PRs and adding any other relevant information I notice to it — [I used this to open. PR to js-timers](https://github.com/purescript-contrib/purescript-js-timers/pull/15). Move the header to be the title of the PR and adjust the content as needed. 75 | 76 | ``` 77 | # Update according to Contributors library guidelines 78 | 79 | This pull request is part of an effort to update and standardize the Contributors libraries according to the [Library Guidelines](https://github.com/purescript-contrib/governance/blob/main/library-guidelines.md). Specifically, it: 80 | 81 | 1. Adjusts the files and repository structure according to the [repository structure](https://github.com/purescript-contrib/governance/blob/main/library-guidelines.md#repository-structure) section of the guidelines, which includes standard pr templates, issue templates, CI in GitHub Actions, automatic stale issue management, ensures the project uses Spago, and so on. 82 | 2. Updates the README and documentation according to the [documentation](https://github.com/purescript-contrib/governance/blob/main/library-guidelines.md#documentation) section of the guidelines. This is a first step towards ensuring Contributors libraries have adequate module documentation, READMEs, a docs directory, and tests (even if just usage examples) in a `test` directory. 83 | 3. Updates labels where relevant to help folks better sift through issues on this library and get started contributing. 84 | 85 | This PR is the groundwork for followup efforts to ensure contributor libraries are kept up-to-date, documented, tested, and accessible to users and new contributors. 86 | ``` 87 | 88 | ## 6. Manually review the project tests and documentation 89 | When reviewing tests and documentation I’m looking for four things, each of which is covered by an issue template. Each of these templates can be adjusted to fit the particular case you’re working with. 90 | 91 | First, does the package have any tests? (JS Timers does, so no problem here.) 92 | ``` 93 | # Repository doesn't have tests 94 | **Is your change request related to a problem? Please describe.** 95 | As described in the [tests section of the Library Guidelines](https://github.com/purescript-contrib/governance/blob/main/library-guidelines.md#tests), Contributors libraries are expected to have tests exercised in CI on pull requests and the main branch. 96 | 97 | This library currently doesn't have any real tests. 98 | 99 | **Describe the solution you'd like** 100 | There should be at least one real test in the [test](../blob/main/test) directory. It can be a minimal usage example similar to the quick start in the repository README. 101 | 102 | **Additional context** 103 | See the [Governance repository](https://github.com/purescript-contrib/governance) for more information about requirements in the Contributors organization. 104 | ``` 105 | 106 | Second, does the package README have quick start documentation to get a new user up to speed right away? (JS Timers [does not have this](https://github.com/purescript-contrib/purescript-js-timers/issues/16)). 107 | ``` 108 | # Repository doesn't have a quick start section in the README 109 | **Is your change request related to a problem? Please describe.** 110 | As described in the [documentation section of the Library Guidelines](https://github.com/purescript-contrib/governance/blob/main/library-guidelines.md#documentation), Contributors libraries are expected to have in their README a short summary of the library's purpose, installation instructions, a quick start with a minimal usage example, and links to the documentation and contributing guide. 111 | 112 | This library currently doesn't have a completed [quick start](../#quick-start) in the README. 113 | 114 | **Describe the solution you'd like** 115 | The library needs a quick start section after the installation instructions. [argonaut-codecs](https://github.com/purescript-contrib/purescript-argonaut-codecs#quick-start) is one example of a library with a quick start. 116 | 117 | **Additional context** 118 | See the [Governance repository](https://github.com/purescript-contrib/governance) for more information about requirements in the Contributors organization. 119 | ``` 120 | 121 | Third, does the package have module documentation? (JS Timers does have this.) 122 | ``` 123 | # Library doesn't have sufficient module documentation 124 | **Is your change request related to a problem? Please describe.** 125 | As described in the [documentation section of the Library Guidelines](https://github.com/purescript-contrib/governance/blob/main/library-guidelines.md#documentation), Contributors libraries are expected to have mostly complete module documentation, preferably uploaded to Pursuit. 126 | 127 | This library currently doesn't have sufficient module documentation -- many publicly-exported types and functions don't have any documentation comments. 128 | 129 | **Describe the solution you'd like** 130 | The library needs to have documentation comments on the majority (and preferably all) public types and functions. Once updated, we should tag a release and upload new module documentation to Pursuit so that folks can use it. 131 | 132 | **Additional context** 133 | See the [Governance repository](https://github.com/purescript-contrib/governance) for more information about requirements in the Contributors organization. 134 | ``` 135 | 136 | Fourth, does the package have good documentation in the docs directory. 137 | ``` 138 | # Library has inadequate documentation in the docs directory 139 | **Is your change request related to a problem? Please describe.** 140 | As described in the [documentation section of the Library Guidelines](https://github.com/purescript-contrib/governance/blob/main/library-guidelines.md#documentation), Contributors libraries are expected to have some documentation in the docs directory -- specifically, at least a short tutorial that expands on the quick start in the README. 141 | 142 | This library currently doesn't have comprehensive documentation in the [docs](../blob/main/docs) directory. 143 | 144 | **Describe the solution you'd like** 145 | At least a short tutorial needs to be added to the docs directory, or other documentation [as described in this Divio article](https://documentation.divio.com). 146 | 147 | The [argonaut-codecs docs directory](https://github.com/purescript-contrib/purescript-argonaut-codecs/tree/master/docs) has a good example of expanded documentation for a Contributor library. But it would even be useful to add something considerably smaller and shorter to this library. 148 | 149 | **Additional context** 150 | See the [Governance repository](https://github.com/purescript-contrib/governance) for more information about requirements in the Contributors organization. 151 | ``` 152 | -------------------------------------------------------------------------------- /updater/bin/updater.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require("./index.js"); 3 | -------------------------------------------------------------------------------- /updater/docs/01-Generate.md: -------------------------------------------------------------------------------- 1 | # Generate Template Files 2 | 3 | The `generate` command in the `contrib-updater` CLI generates a standard set of files that each project in the Contributors organization has. The templates for these files are stored in the `templates` directory. 4 | 5 | The `templates` directory contains templates for a standard set of files that each project in the Contributors organization has. Feel free to use these templates in non-Contributor libraries as well! 6 | 7 | ## Usage 8 | 9 | The `generate` command will create a standard set of files in the current repository based on the set of library templates in the `templates` directory. It will fill in a set of available variables with the contents you provide and will make backups of any existing files that conflict. 10 | 11 | This command is used to help manage migrating new libraries into the Contributors organization or to update existing libraries when our standard structure changes. 12 | 13 | Example CLI usage with defaults used (this is the typical case): 14 | 15 | ```sh 16 | contrib-updater generate --maintainer garyb --maintainer thomashoneyman 17 | ``` 18 | 19 | Example CLI usage with all options specified: 20 | 21 | ```sh 22 | contrib-updater generate \ 23 | # Indicates that JS-related templates should be 24 | # generated as well as the standard templates. 25 | --uses-js \ 26 | --owner purescript-contrib \ 27 | --repo purescript-argonaut-codecs \ 28 | --main-branch main \ 29 | --display-name '`argonaut-codecs`' \ 30 | --display-title 'Argonaut Codecs' \ 31 | --maintainer thomashoneyman 32 | ``` 33 | 34 | In typical usage you will: 35 | 36 | 1. Clone the target repository and change into it 37 | 2. Run `contrib-updater generate` and provide values for the relevant variables; if the library uses any JS files, include the `--uses-js` flag. 38 | 3. Reconcile the new content with any information that should be preserved from the existing repository (any conflicting files will have been added to the backups directory). For example, you may want to copy sections of the old README into the new one or into the documentation where appropriate. 39 | 4. Delete the backups directory and any other files which are no longer necessary in the target repository. 40 | 5. Open a PR with your changes! 41 | 42 | ## Variables 43 | 44 | Templates can use the following variables (in code, see the `Updater.Variable` module), provided via the `contrib-updater` tool: 45 | 46 | ```purs 47 | type Variables = 48 | { owner :: String 49 | , mainBranch :: String 50 | , packageName :: String 51 | , displayName :: String 52 | , displayTitle :: String 53 | , maintainer :: NonEmptyList String 54 | , repo :: String 55 | } 56 | ``` 57 | 58 | - `owner` refers to the owner of the repository being updated. Defaults in the CLI to `"purescript-contrib"`. 59 | - `mainBranch` refers to the primary branch used in the repository. Defaults in the CLI to `"main"`, but some libraries may need to use `"master"` instead. 60 | - `packageName` refers to the package name as represented in the PureScript registry and Spago installation instructions. This is pulled automatically from the `spago.dhall` file. 61 | - `repo` refers to the repository containing the package. Defaults in the CLI to `"purescript-{{packageName}}"`, re-using the package name as represented in the PureScript registry. 62 | - `displayName` refers to the way you'd like to render the package name in markdown files. Defaults in the CLI to the name of the package in backticks, ie. `argonaut-codecs`, but it's also common to provide a string (for example, Argonaut Codecs) instead. 63 | - `displayTitle` refers to the way you'd like to render the package name in markdown titles. Defaults in the CLI to the name of the package in title case. 64 | - `maintainer` refers to the assigned maintainer(s) for the library (ex: `"thomashoneyman"`). This is required in the CLI. 65 | 66 | Any of these variables can be used in template files via `{{variableName}}` syntax. When templates are generated for a particular repository these variables will be replaced with the values you provided. 67 | 68 | ## Provided Templates 69 | 70 | The templates include default content that each Contributor library is expected to have. Templates are provided in layers, where later layers override earlier ones: 71 | 72 | ### Layer 1: Base 73 | 74 | `base` includes content and configuration each Contributor library is expected to have. 75 | 76 | - An informative README with badges for the build status, latest release, latest Pursuit documentation, and current maintainer(s). 77 | - CHANGELOG and CONTRIBUTING files in the repository root. 78 | - A documentation directory containing extra library documentation (like tutorials). 79 | - A `.github` directory containing issue and pull request templates and continuous integration via GitHub Actions and `setup-purescript`. 80 | - Various dotfiles, including standard `.gitignore` and `.editorconfig` files. 81 | 82 | ### Layer 2: FFI 83 | 84 | Some libraries in the Contributors organization rely on NPM libraries and/or the foreign function interface (FFI). Those libraries have additional templates, including: 85 | 86 | - An `.eslintrc.json` configuration file. Any library using JavaScript is expected to pass `eslint` via this configuration. 87 | - A minimal `package.json` file listing any NPM dependencies (including `eslint`) and which includes a `build` script (which calls `eslint` before building the source). 88 | - An updated `ci.yml` file that installs and caches NPM dependencies in addition to the ordinary PureScript toolchain. 89 | 90 | If a library ought to use these files, provide the `--uses-js` flag to the `contrib-updater` CLI tool. 91 | -------------------------------------------------------------------------------- /updater/docs/02-Sync-Labels.md: -------------------------------------------------------------------------------- 1 | # Sync Labels 2 | 3 | The `sync-labels` command in the `contrib-updater` CLI sets the issue labels, descriptions, and colors according to the standard set used in Contributor libraries. It can optionally remove all labels which are not part of the standard set (this is not enabled by default). 4 | 5 | ## Usage 6 | 7 | The `sync-labels` command will use a personal GitHub access token you have created to update the labels in a target repository via the GitHub API. Accordingly, you'll need to provide your access token ([create one here](https://github.com/settings/tokens)) and the target repository to update. This command does not need to be run within a checkout of the target repository because all operations happen via the API. 8 | 9 | Example CLI usage: 10 | 11 | ```sh 12 | contrib-updater sync-labels \ 13 | --token abc123 \ 14 | --repo purescript-machines \ 15 | # Optional: Defaults to purescript-contrib if omitted. 16 | --owner purescript-contrib 17 | # Optional: Indicates that labels not in the standard Contributor set should 18 | # be removed from the repository. If omitted those labels will be preserved. 19 | --delete-unused 20 | ``` 21 | 22 | Example output: 23 | 24 | ```text 25 | Creating: "breaking change", "document me" 26 | Patching: "question", "help wanted", "enhancement" 27 | Deleting: "wontfix", "invalid", "duplicate" 28 | Successfully completed syncing labels. 29 | ``` 30 | 31 | ## Updating Labels 32 | 33 | The set of labels used in the Contributors libraries is maintained by the `SyncLabels` command and should be updated in the [source code](../src/Updater/SyncLabels/IssueLabel.purs) if you need to make a change. 34 | -------------------------------------------------------------------------------- /updater/docs/README.md: -------------------------------------------------------------------------------- 1 | # Updater Documentation 2 | 3 | The `contrib-updater` tool is a Swiss Army knife for maintaining PureScript Contributor libraries. It supports the following commands (click to read the relevant documentation): 4 | 5 | - [Generate](./01-Generate.md) - Generate a standard set of template files for the current repository. 6 | - [Sync Labels](./02-Sync-Labels.md) - Update issue labels and sync their colors and descriptions 7 | 8 | ## Usage 9 | 10 | Once installed, see all commands via: 11 | 12 | ```sh 13 | contrib-updater --help 14 | ``` 15 | 16 | and see documentation for a particular command via: 17 | 18 | ```sh 19 | # contrib-updater COMMAND --help 20 | contrib-updater generate --help 21 | contrib-updater sync-labels --help 22 | ``` 23 | -------------------------------------------------------------------------------- /updater/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Main = require("./output/index"); 4 | 5 | Main.main(); 6 | -------------------------------------------------------------------------------- /updater/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contrib-updater", 3 | "requires": true, 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "@vercel/ncc": { 7 | "version": "0.31.1", 8 | "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.31.1.tgz", 9 | "integrity": "sha512-g0FAxwdViI6UzsiVz5HssIHqjcPa1EHL6h+2dcJD893SoCJaGdqqgUF09xnMW6goWnnhbLvgiKlgJWrJa+7qYA==", 10 | "dev": true 11 | }, 12 | "xhr2": { 13 | "version": "0.2.1", 14 | "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.1.tgz", 15 | "integrity": "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /updater/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "contrib-updater", 4 | "bin": { 5 | "contrib-updater": "bin/updater.js" 6 | }, 7 | "scripts": { 8 | "build": "spago bundle-module --to output/index.js && ncc build --minify --out bin index.js" 9 | }, 10 | "devDependencies": { 11 | "@vercel/ncc": "^0.31.1" 12 | }, 13 | "dependencies": { 14 | "xhr2": "^0.2.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /updater/packages.dhall: -------------------------------------------------------------------------------- 1 | let upstream = 2 | https://github.com/purescript/package-sets/releases/download/psc-0.14.4-20211109/packages.dhall sha256:e8d8d5b339f6d46d950da90037c6c38e8809f7e34f727373089ab82c080fc709 3 | 4 | in upstream 5 | -------------------------------------------------------------------------------- /updater/shell.nix: -------------------------------------------------------------------------------- 1 | let 2 | pkgs = import (builtins.fetchTarball { 3 | url = "https://github.com/NixOS/nixpkgs/archive/21.05.tar.gz"; 4 | }) {}; 5 | 6 | # nix-prefetch-git https://github.com/justinwoo/easy-purescript-nix 7 | pursPkgs = import (pkgs.fetchFromGitHub { 8 | owner = "justinwoo"; 9 | repo = "easy-purescript-nix"; 10 | rev = "7802db65618c2ead3a55121355816b4c41d276d9"; 11 | sha256 = "0n99hxxcp9yc8yvx7bx4ac6askinfark7dnps3hzz5v9skrvq15q"; 12 | }) { inherit pkgs; }; 13 | 14 | in pkgs.stdenv.mkDerivation { 15 | name = "updater"; 16 | 17 | buildInputs = with pursPkgs; [ 18 | pursPkgs.purs 19 | pursPkgs.spago 20 | pursPkgs.purs-tidy 21 | pkgs.nodejs-14_x 22 | pkgs.dhall-json 23 | ]; 24 | 25 | shellHook ='' 26 | npm install 27 | npm run build 28 | alias contrib-updater="node $(readlink -f bin/index.js)" 29 | '' ; 30 | } 31 | -------------------------------------------------------------------------------- /updater/spago.dhall: -------------------------------------------------------------------------------- 1 | { name = "contrib-updater" 2 | , dependencies = 3 | [ "aff" 4 | , "affjax" 5 | , "argonaut-core" 6 | , "arrays" 7 | , "bifunctors" 8 | , "codec" 9 | , "codec-argonaut" 10 | , "console" 11 | , "control" 12 | , "effect" 13 | , "either" 14 | , "exceptions" 15 | , "foldable-traversable" 16 | , "functions" 17 | , "http-methods" 18 | , "interpolate" 19 | , "lists" 20 | , "maybe" 21 | , "node-buffer" 22 | , "node-child-process" 23 | , "node-fs" 24 | , "node-fs-aff" 25 | , "node-path" 26 | , "node-process" 27 | , "nullable" 28 | , "optparse" 29 | , "ordered-collections" 30 | , "parallel" 31 | , "prelude" 32 | , "psci-support" 33 | , "record" 34 | , "strings" 35 | , "strings-extra" 36 | , "stringutils" 37 | , "sunde" 38 | , "transformers" 39 | , "tuples" 40 | ] 41 | , packages = ./packages.dhall 42 | , sources = [ "src/**/*.purs", "test/**/*.purs" ] 43 | } 44 | -------------------------------------------------------------------------------- /updater/src/Main.purs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Updater.Cli as Cli 7 | import Updater.Command as Command 8 | 9 | main :: Effect Unit 10 | main = Cli.run >>= Command.run 11 | -------------------------------------------------------------------------------- /updater/src/Node/FS/Aff/Extra.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | exports.handleCallbackImpl = function (left, right, f) { 4 | return function (err, value) { 5 | if (err) { 6 | f(left(err))(); 7 | } else { 8 | f(right(value))(); 9 | } 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /updater/src/Node/FS/Aff/Extra.purs: -------------------------------------------------------------------------------- 1 | module Node.FS.Aff.Extra 2 | ( writeTextFile 3 | , mkdir 4 | ) where 5 | 6 | import Prelude 7 | 8 | import Data.Either (Either(..)) 9 | import Data.Function.Uncurried (Fn2, Fn3, runFn3) 10 | import Data.Nullable (Nullable) 11 | import Effect (Effect) 12 | import Effect.Aff (Aff, makeAff, nonCanceler) 13 | import Effect.Exception (Error) 14 | import Node.Encoding as Encoding 15 | import Node.FS.Aff as FS 16 | import Node.FS.Async (Callback) 17 | import Node.FS.Internal (mkEffect, unsafeRequireFS) 18 | import Node.Path (FilePath) 19 | import Node.Path as Path 20 | 21 | -- | A version of Node.FS.Aff.writeTextFile which recursively creates directories 22 | -- | along the way to the file's target path in the file system if they do not 23 | -- | already exist. 24 | writeTextFile :: FilePath -> String -> Aff Unit 25 | writeTextFile file contents = do 26 | let 27 | targetDirectory = Path.dirname file 28 | 29 | exists <- FS.exists targetDirectory 30 | 31 | unless exists do 32 | mkdir targetDirectory 33 | 34 | FS.writeTextFile Encoding.UTF8 file contents 35 | 36 | -- | A version of Node.FS.Aff.mkdir which recursively creates directories along 37 | -- | a file path. 38 | mkdir :: FilePath -> Aff Unit 39 | mkdir = toAff1 \file cb -> mkEffect \_ -> runFn3 fs.mkdir file { recursive: true } (handleCallback cb) 40 | 41 | toAff :: forall a. (Callback a -> Effect Unit) -> Aff a 42 | toAff p = makeAff \k -> p k $> nonCanceler 43 | 44 | toAff1 :: forall a x. (x -> Callback a -> Effect Unit) -> x -> Aff a 45 | toAff1 f a = toAff (f a) 46 | 47 | type JSCallback a = Fn2 (Nullable Error) Unit a 48 | 49 | foreign import handleCallbackImpl 50 | :: forall a. Fn3 (Error -> Either Error a) (a -> Either Error a) (Callback a) (JSCallback a) 51 | 52 | handleCallback :: forall a. (Callback a) -> JSCallback a 53 | handleCallback cb = runFn3 handleCallbackImpl Left Right cb 54 | 55 | fs :: { mkdir :: Fn3 FilePath { recursive :: Boolean } (JSCallback Unit) Unit } 56 | fs = unsafeRequireFS 57 | -------------------------------------------------------------------------------- /updater/src/Updater/Cli.purs: -------------------------------------------------------------------------------- 1 | module Updater.Cli 2 | ( run 3 | ) where 4 | 5 | import Prelude 6 | 7 | import Data.Foldable (fold) 8 | import Data.List.NonEmpty (fromFoldable) 9 | import Data.Maybe (optional) 10 | import Data.String.Pattern (Pattern(..)) 11 | import Effect (Effect) 12 | import Options.Applicative (Parser, (<**>)) 13 | import Options.Applicative as OA 14 | import Updater.Command (Command(..)) 15 | import Updater.Utils.Options (multiString) 16 | 17 | -- | Parse command line arguments into a valid `Command`, if possible, or 18 | -- | display the help menu or an error if the parser fails. 19 | run :: Effect Command 20 | run = OA.execParser $ OA.info (command <**> OA.helper) $ fold 21 | [ OA.fullDesc 22 | , OA.header 23 | """ 24 | contrib-updater - a utility for managing Contributor libraries 25 | """ 26 | ] 27 | 28 | -- | Parse a `Command` from a set of command line arguments. 29 | command :: Parser Command 30 | command = OA.hsubparser $ fold 31 | [ OA.command "generate" 32 | $ OA.info generate 33 | $ OA.progDesc "Generate template files that follow the Contributors best practices" 34 | , OA.command "sync-labels" 35 | $ OA.info syncLabels 36 | $ OA.progDesc "Sync issue label names, descriptions, and colors with the standard set" 37 | , OA.command "sync-all-labels" 38 | $ OA.info syncAllLabels 39 | $ OA.progDesc "Sync all issue label names, descriptions, and colors across the core, contrib, web, and node repos" 40 | , OA.command "list-labels" 41 | $ OA.info listLabels 42 | $ OA.progDesc "List all labels across all core, contrib, web, and node libraries." 43 | ] 44 | where 45 | generate :: Parser Command 46 | generate = map Generate ado 47 | usesJS <- OA.switch $ fold 48 | [ OA.long "uses-js" 49 | , OA.help "Whether to generate files for working with JavaScript (linting, etc.)" 50 | ] 51 | 52 | owner <- optional $ OA.strOption $ fold 53 | [ OA.long "owner" 54 | , OA.metavar "STRING" 55 | , OA.help "The owner of this repository. Default: purescript-contrib" 56 | ] 57 | 58 | repo <- optional $ OA.strOption $ fold 59 | [ OA.long "repo" 60 | , OA.metavar "STRING" 61 | , OA.help "The repository to use for updating the changelog. Ex: purescript-machines" 62 | ] 63 | 64 | mainBranch <- optional $ OA.strOption $ fold 65 | [ OA.long "main-branch" 66 | , OA.metavar "STRING" 67 | , OA.help "The main branch of this repository. Default: main" 68 | ] 69 | 70 | displayName <- optional $ OA.strOption $ fold 71 | [ OA.long "display-name" 72 | , OA.metavar "STRING" 73 | , OA.help "How to render this library's name in .md files. Default: '`package-name`'" 74 | ] 75 | 76 | displayTitle <- optional $ OA.strOption $ fold 77 | [ OA.long "display-title" 78 | , OA.metavar "STRING" 79 | , OA.help "How to render this library's name in .md file titles. Default: 'Package Name'" 80 | ] 81 | 82 | maintainers <- OA.some $ OA.strOption $ fold 83 | [ OA.long "maintainer" 84 | , OA.metavar "STRING" 85 | , OA.help "The assigned maintainer(s) for this repository (required). Ex: 'thomashoneyman'" 86 | ] 87 | 88 | files <- (fromFoldable =<< _) <$> 89 | ( optional $ OA.option (multiString $ Pattern ",") $ fold 90 | [ OA.long "files" 91 | , OA.metavar "file1,..,fileN" 92 | , OA.help "Generate only these files from the template. Uses the JS version when --uses-js is true. Ex: 'README.md,docs/README.md'" 93 | ] 94 | ) 95 | 96 | in { usesJS, owner, repo, mainBranch, displayName, displayTitle, maintainers, files } 97 | 98 | syncLabels :: Parser Command 99 | syncLabels = map SyncLabels ado 100 | token <- OA.strOption $ fold 101 | [ OA.long "token" 102 | , OA.metavar "STRING" 103 | , OA.help "A personal access token for GitHub with at least public_repo scope" 104 | ] 105 | 106 | repo <- OA.strOption $ fold 107 | [ OA.long "repo" 108 | , OA.metavar "STRING" 109 | , OA.help "The repository to update labels for. Ex: purescript-machines" 110 | ] 111 | 112 | owner <- optional $ OA.strOption $ fold 113 | [ OA.long "owner" 114 | , OA.metavar "STRING" 115 | , OA.help "The repository owner. Default: purescript-contrib" 116 | ] 117 | 118 | deleteUnused <- OA.switch $ fold 119 | [ OA.long "delete-unused" 120 | , OA.help "Whether to delete issue labels not in the standard Contributors set." 121 | ] 122 | 123 | in { token, repo, owner, deleteUnused } 124 | 125 | listLabels :: Parser Command 126 | listLabels = map ListLabels ado 127 | token <- OA.strOption $ fold 128 | [ OA.long "token" 129 | , OA.metavar "STRING" 130 | , OA.help "A personal access token for GitHub with at least public_repo scope" 131 | ] 132 | in { token } 133 | 134 | syncAllLabels :: Parser Command 135 | syncAllLabels = map SyncAllLabels ado 136 | token <- OA.strOption $ fold 137 | [ OA.long "token" 138 | , OA.metavar "STRING" 139 | , OA.help "A personal access token for GitHub with at least public_repo scope" 140 | ] 141 | in { token } 142 | -------------------------------------------------------------------------------- /updater/src/Updater/Command.purs: -------------------------------------------------------------------------------- 1 | module Updater.Command 2 | ( Command(..) 3 | , GenerateOptions(..) 4 | , SyncLabelsOptions(..) 5 | , ListLabelsOptions(..) 6 | , SyncAllLabelsOptions(..) 7 | , run 8 | ) where 9 | 10 | import Prelude 11 | 12 | import Control.Monad.Except (runExceptT, throwError) 13 | import Control.Parallel (parTraverse_) 14 | import Data.Array as Array 15 | import Data.Either (Either(..)) 16 | import Data.Foldable (traverse_) 17 | import Data.Interpolate (i) 18 | import Data.List.Types (NonEmptyList) 19 | import Data.Maybe (Maybe, fromMaybe, isJust, maybe) 20 | import Data.String (joinWith) 21 | import Data.String as String 22 | import Data.String.Extra as String.Extra 23 | import Effect (Effect) 24 | import Effect.Aff (Aff, launchAff_) 25 | import Effect.Aff as Aff 26 | import Effect.Class (liftEffect) 27 | import Effect.Class.Console (error, log) 28 | import Node.Path (FilePath) 29 | import Node.Process (exit) 30 | import Updater.Generate.Changelog (appendReleaseInfoToChangelog) 31 | import Updater.Generate.Template (allTemplates, docsChangelog, runTemplates, validateFiles) 32 | import Updater.SyncLabels.Repos (purescriptContribRepos, purescriptNodeRepos, purescriptRepos, purescriptWebRepos) 33 | import Updater.SyncLabels.Request (IssueLabelRequestOpts, LabelAction(..), calcLabelActions) 34 | import Updater.SyncLabels.Request as SyncLabels 35 | import Updater.Utils.Dhall as Utils.Dhall 36 | 37 | -- | The data type which describes what tasks this CLI tool can assist with. See 38 | -- | the library documentation for a full description of what these commands are 39 | -- | intended to accomplish. 40 | data Command 41 | = Generate GenerateOptions 42 | | SyncLabels SyncLabelsOptions 43 | | ListLabels ListLabelsOptions 44 | | SyncAllLabels SyncAllLabelsOptions 45 | 46 | -- | Run a command, performing any relevant updates. 47 | run :: Command -> Effect Unit 48 | run = launchAff_ <<< case _ of 49 | Generate opts -> 50 | runGenerate opts 51 | 52 | SyncLabels opts -> 53 | runSyncLabels opts 54 | 55 | ListLabels opts -> 56 | runListLabels opts 57 | 58 | SyncAllLabels opts -> 59 | runSyncAllLabels opts 60 | 61 | -- | Possible flags to control how library templates should be generated. These 62 | -- | are largely the same as the set of supported variables (see the documentation 63 | -- | for the `Generate` command for more details). 64 | type GenerateOptions = 65 | { usesJS :: Boolean 66 | , owner :: Maybe String 67 | , repo :: Maybe String 68 | , mainBranch :: Maybe String 69 | , displayName :: Maybe String 70 | , displayTitle :: Maybe String 71 | , maintainers :: NonEmptyList String 72 | , files :: Maybe (NonEmptyList FilePath) 73 | } 74 | 75 | -- | Generate templates in the repository, backing up any conflicting files 76 | -- | that would be overwritten. 77 | runGenerate :: GenerateOptions -> Aff Unit 78 | runGenerate opts = do 79 | spago <- Utils.Dhall.readSpagoFile 80 | 81 | let 82 | toTitleCase = 83 | String.joinWith " " 84 | <<< map String.Extra.upperCaseFirst 85 | <<< String.split (String.Pattern "-") 86 | 87 | maintainerTemplate maintainer = 88 | i "[![Maintainer: " maintainer "](https://img.shields.io/badge/maintainer-" maintainer "-teal.svg)](https://github.com/" maintainer ")" 89 | 90 | variables = 91 | { owner: fromMaybe "purescript-contrib" opts.owner 92 | , repo: fromMaybe ("purescript-" <> spago.name) opts.repo 93 | , mainBranch: fromMaybe "main" opts.mainBranch 94 | , packageName: spago.name 95 | , displayName: fromMaybe ("`" <> spago.name <> "`") opts.displayName 96 | , displayTitle: fromMaybe (toTitleCase spago.name) opts.displayTitle 97 | , maintainers: map maintainerTemplate opts.maintainers 98 | , usesJS: opts.usesJS 99 | } 100 | 101 | validatedTemplates = 102 | maybe 103 | (Right allTemplates) 104 | (validateFiles { usesJS: opts.usesJS, templates: allTemplates }) 105 | opts.files 106 | 107 | case validatedTemplates of 108 | Right templates -> do 109 | runTemplates variables templates 110 | 111 | when (isJust $ Array.find (eq docsChangelog) templates) $ 112 | appendReleaseInfoToChangelog { owner: variables.owner, repo: variables.repo } 113 | 114 | Left msg -> do 115 | error msg 116 | liftEffect $ exit 1 117 | 118 | log 119 | """ 120 | Finished generating files. You should verify any contents in the backups 121 | directory and remove that directory before committing your changes. 122 | 123 | !! NOT ALL CONTENT IS COMPLETE !! 124 | 125 | You should now fill in the library's Summary and Quick Start sections in 126 | the README.md file in the root of the repository. 127 | """ 128 | 129 | type SyncLabelsOptions = 130 | { token :: String 131 | , repo :: String 132 | , owner :: Maybe String 133 | , deleteUnused :: Boolean 134 | } 135 | 136 | -- | Update the issue labels used in the repository to match the colors, label 137 | -- | names, and descriptions used across the Contributors libraries. 138 | -- | that would be overwritten. 139 | runSyncLabels :: SyncLabelsOptions -> Aff Unit 140 | runSyncLabels opts = do 141 | let 142 | requestOpts :: IssueLabelRequestOpts 143 | requestOpts = 144 | { token: opts.token 145 | , repo: opts.repo 146 | , owner: fromMaybe "purescript-contrib" opts.owner 147 | } 148 | 149 | resp <- runExceptT do 150 | sifted <- SyncLabels.getSiftedLabels requestOpts 151 | 152 | let logPreview msg = log <<< append msg <<< joinWith ", " <<< map (show <<< _.name) 153 | 154 | when (not Array.null sifted.create) do 155 | logPreview "Creating: " sifted.create 156 | parTraverse_ (SyncLabels.createLabel requestOpts) sifted.create 157 | 158 | when (not Array.null sifted.update) do 159 | logPreview "Patching: " sifted.update 160 | parTraverse_ (SyncLabels.patchLabel requestOpts) sifted.update 161 | 162 | when (opts.deleteUnused && not Array.null sifted.delete) do 163 | logPreview "Deleting: " sifted.delete 164 | traverse_ (SyncLabels.deleteLabel requestOpts) sifted.delete 165 | 166 | case resp of 167 | Left e -> do 168 | log "Did not successfully create, update, and delete labels: " 169 | error (Aff.message e) 170 | log "You can retry this operation." 171 | throwError e 172 | 173 | Right _ -> 174 | log "Successfully completed syncing labels." 175 | 176 | type SyncAllLabelsOptions = 177 | { token :: String } 178 | 179 | -- | Update the issue labels used in the repositories across all 180 | -- | core, contrib, web, and node libraries to use the same 181 | -- | colors, label, names, and descriptions defined in IssueLabel. 182 | runSyncAllLabels :: SyncAllLabelsOptions -> Aff Unit 183 | runSyncAllLabels { token } = do 184 | pure unit 185 | let 186 | withOwner owner = map \repo -> { token, owner, repo } 187 | allRepos = join 188 | [ withOwner "purescript" purescriptRepos 189 | , withOwner "purescript-contrib" purescriptContribRepos 190 | , withOwner "purescript-web" purescriptWebRepos 191 | , withOwner "purescript-node" purescriptNodeRepos 192 | ] 193 | 194 | flip parTraverse_ allRepos \opts@{ owner, repo } -> do 195 | resp <- runExceptT do 196 | repoLabels <- SyncLabels.getLabels opts 197 | 198 | flip parTraverse_ (calcLabelActions repoLabels) case _ of 199 | Create label -> SyncLabels.createLabel opts label 200 | Update oldName newLabel -> SyncLabels.patchLabel' opts oldName newLabel 201 | Delete labelName -> SyncLabels.deleteLabel' opts labelName 202 | 203 | log $ i "For repo '" owner "/" repo "'..." 204 | case resp of 205 | Left e -> do 206 | log $ i "\tDid not successfully create, update, and delete labels: " 207 | error (Aff.message e) 208 | log "\tYou can retry this operation." 209 | throwError e 210 | 211 | Right _ -> 212 | log "\tSuccessfully completed syncing labels." 213 | 214 | type ListLabelsOptions = 215 | { token :: String 216 | } 217 | 218 | runListLabels :: ListLabelsOptions -> Aff Unit 219 | runListLabels opts = do 220 | void $ runExceptT $ SyncLabels.listAllLabels opts.token 221 | -------------------------------------------------------------------------------- /updater/src/Updater/Generate/Changelog.purs: -------------------------------------------------------------------------------- 1 | module Updater.Generate.Changelog where 2 | 3 | import Prelude 4 | 5 | import Affjax as AX 6 | import Affjax.ResponseFormat as RF 7 | import Affjax.StatusCode (StatusCode(..)) 8 | import Data.Array (filter, null) 9 | import Data.Codec (decode) 10 | import Data.Codec.Argonaut (JsonCodec, array, printJsonDecodeError) 11 | import Data.Codec.Argonaut as CA 12 | import Data.Codec.Argonaut.Record as CAR 13 | import Data.Either (Either(..)) 14 | import Data.Foldable (foldl) 15 | import Data.Maybe (Maybe(..)) 16 | import Data.Monoid (power) 17 | import Data.String (Pattern(..)) 18 | import Data.String.CodeUnits (drop, indexOf, length, takeWhile) 19 | import Data.String.Common (joinWith, trim) 20 | import Data.String.Utils (lines) 21 | import Effect.Aff (Aff, error, throwError) 22 | import Effect.Class.Console as Console 23 | import Node.Encoding (Encoding(..)) 24 | import Node.FS.Aff as FSA 25 | 26 | type ReleaseInfo = 27 | { tag_name :: String 28 | , html_url :: String 29 | , body :: String 30 | , published_at :: String 31 | , draft :: Boolean 32 | } 33 | 34 | releaseCodec :: JsonCodec ReleaseInfo 35 | releaseCodec = 36 | CAR.object "ReleaseInfo" $ 37 | { tag_name: CA.string 38 | , html_url: CA.string 39 | , body: CA.string 40 | , published_at: CA.string 41 | , draft: CA.boolean 42 | } 43 | 44 | appendReleaseInfoToChangelog :: forall r. { owner :: String, repo :: String | r } -> Aff Unit 45 | appendReleaseInfoToChangelog gh = do 46 | releases <- recursivelyFetchReleases [] 1 gh 47 | let 48 | realReleases = filter (\r -> r.draft == false) releases 49 | appendContent = foldl addReleaseInfo "" realReleases 50 | FSA.appendTextFile UTF8 "./CHANGELOG.md" $ joinWith "\n" 51 | [ "" 52 | , appendContent 53 | ] 54 | where 55 | addReleaseInfo :: String -> ReleaseInfo -> String 56 | addReleaseInfo acc rec = do 57 | let 58 | dateWithoutTimeZone = takeWhile (_ /= 'T') rec.published_at 59 | bodyWithFixedHeaders = fixHeaders $ trim rec.body 60 | 61 | acc <> joinWith "\n" 62 | [ "## [" <> rec.tag_name <> "](" <> rec.html_url <> ") - " <> dateWithoutTimeZone 63 | , "" 64 | , bodyWithFixedHeaders 65 | , "" 66 | , "" 67 | ] 68 | 69 | fixHeaders :: String -> String 70 | fixHeaders s = do 71 | let 72 | replaceAllHeaders = 73 | replaceHeaderWithBoldedText 5 74 | >>> replaceHeaderWithBoldedText 4 75 | >>> replaceHeaderWithBoldedText 3 76 | >>> replaceHeaderWithBoldedText 2 77 | >>> replaceHeaderWithBoldedText 1 78 | 79 | joinWith "\n" $ map replaceAllHeaders (lines s) 80 | 81 | replaceHeaderWithBoldedText :: Int -> String -> String 82 | replaceHeaderWithBoldedText level line = do 83 | let prefix = (power "#" level) <> " " 84 | case indexOf (Pattern prefix) line of 85 | Nothing -> line 86 | Just _ -> "**" <> drop (length prefix) line <> "**" 87 | 88 | recursivelyFetchReleases 89 | :: forall r 90 | . Array ReleaseInfo 91 | -> Int 92 | -> { owner :: String, repo :: String | r } 93 | -> Aff (Array ReleaseInfo) 94 | recursivelyFetchReleases accumulator page gh = do 95 | pageNResult <- fetchNextPageOfReleases page gh 96 | case pageNResult of 97 | Nothing -> pure accumulator 98 | Just arr -> recursivelyFetchReleases (accumulator <> arr) (page + 1) gh 99 | 100 | fetchNextPageOfReleases :: forall r. Int -> { owner :: String, repo :: String | r } -> Aff (Maybe (Array ReleaseInfo)) 101 | fetchNextPageOfReleases page gh = do 102 | -- For example 103 | -- https://api.github.com/repos/purescript-contrib/purescript-http-methods/releases 104 | let url = "https://api.github.com/repos/" <> gh.owner <> "/" <> gh.repo <> "/releases?per_page=100&page=" <> show page 105 | AX.get RF.json url >>= case _ of 106 | Left err -> do 107 | throwError (error $ AX.printError err) 108 | Right { body, status } | status == StatusCode 200 -> 109 | case decode (array releaseCodec) body of 110 | Left e -> do 111 | Console.error "Failed to decode releases response" 112 | throwError $ error $ printJsonDecodeError e 113 | Right releases | null releases -> 114 | pure Nothing 115 | Right releases -> do 116 | pure $ Just releases 117 | Right { status: StatusCode status } -> 118 | throwError $ error $ "Failed to fetch releases. Status " <> show status 119 | -------------------------------------------------------------------------------- /updater/src/Updater/Generate/Template.purs: -------------------------------------------------------------------------------- 1 | module Updater.Generate.Template 2 | ( Variables(..) 3 | , TemplateSource 4 | , TemplateSourceType 5 | , allTemplates 6 | , docsChangelog 7 | , runTemplates 8 | , validateFiles 9 | ) where 10 | 11 | import Prelude 12 | 13 | import Control.Alternative ((<|>)) 14 | import Data.Array (filter, fromFoldable) 15 | import Data.Either (Either(..)) 16 | import Data.Foldable (find, traverse_) 17 | import Data.Interpolate (i) 18 | import Data.List.NonEmpty as NEL 19 | import Data.List.Types (NonEmptyList) 20 | import Data.Maybe (Maybe(..)) 21 | import Data.String as String 22 | import Data.Traversable (traverse) 23 | import Effect.Aff (Aff) 24 | import Effect.Class (liftEffect) 25 | import Effect.Class.Console (log) 26 | import Node.Encoding (Encoding(..)) 27 | import Node.FS.Aff as FS 28 | import Node.FS.Aff.Extra as FS.Extra 29 | import Node.Globals (__dirname) 30 | import Node.Path (FilePath, resolve) 31 | import Node.Process (cwd) 32 | 33 | -- | The variables supported when generating templates. See the documentation 34 | -- | for more details on these variables and correct syntax. 35 | type Variables = 36 | { owner :: String 37 | , mainBranch :: String 38 | , packageName :: String 39 | , displayName :: String 40 | , displayTitle :: String 41 | , maintainers :: NonEmptyList String 42 | , repo :: String 43 | , usesJS :: Boolean 44 | } 45 | 46 | -- | Replace each variable in the provided file contents, returning the updated 47 | -- | file to be written. 48 | replaceVariables :: Variables -> String -> String 49 | replaceVariables vars contents = do 50 | contents 51 | # replaceOne "owner" vars.owner 52 | # replaceOne "mainBranch" vars.mainBranch 53 | # replaceOne "packageName" vars.packageName 54 | # replaceOne "displayName" vars.displayName 55 | # replaceOne "displayTitle" vars.displayTitle 56 | # replaceOne "repo" vars.repo 57 | # replaceMany "maintainers" vars.maintainers 58 | where 59 | format str = i "{{" str "}}" 60 | 61 | -- Variables which admit only one value should be replaced inline: 62 | -- 63 | -- "This package is {{packageName}} in the registry" 64 | -- where packageName = my-package becomes 65 | -- "This package is my-package in the registry" 66 | replaceOne k v = 67 | String.replaceAll 68 | (String.Pattern (format k)) 69 | (String.Replacement v) 70 | 71 | -- Variables which admit many values should be replaced multiple times with 72 | -- newlines in between: 73 | -- 74 | -- "{{maintainers}}" 75 | -- where maintainers = [ "a", "b" ] becomes 76 | -- "a\nb" 77 | replaceMany k vs = 78 | String.replaceAll 79 | (String.Pattern (format k)) 80 | (String.Replacement (String.joinWith "\n" (NEL.toUnfoldable vs))) 81 | 82 | allTemplates :: Array TemplateSource 83 | allTemplates = 84 | [ gitignore 85 | , repoReadme 86 | , docsReadme 87 | , docsChangelog 88 | , githubIssueBugReport 89 | , githubIssueChangeRequest 90 | , githubIssueConfig 91 | , githubContributing 92 | , githubPullRequest 93 | , githubWorkflowCI 94 | , editorconfig 95 | , tidyconfig 96 | , jsEslintrc 97 | , jsPackageJson 98 | ] 99 | 100 | -- | The directory name where conflicting files will be stored when writing new 101 | -- | templates. Any existing files which a template would overwrite will be 102 | -- | copied into this directory. 103 | backupsDirname :: String 104 | backupsDirname = "backups" 105 | 106 | -- | Generate the templates for a PureScript Contributor project into 107 | -- | the correct file locations, backing up any existing files that would 108 | -- | conflict (you will need to manually reconcile those files). 109 | runTemplates :: Variables -> Array TemplateSource -> Aff Unit 110 | runTemplates variables templateSources = do 111 | backupsDir <- getBackupsDirectory 112 | templatesDir <- liftEffect $ resolve [ __dirname, ".." ] "templates" 113 | let 114 | runTemplateOptions = { backupsDir, templatesDir, variables } 115 | templates = filterByType { usesJS: variables.usesJS, templates: templateSources } 116 | traverse_ (runTemplate runTemplateOptions) templates 117 | where 118 | getBackupsDirectory :: Aff FilePath 119 | getBackupsDirectory = do 120 | current <- liftEffect cwd 121 | log $ i "Creating directory '" backupsDirname "' for conflicting files." 122 | path <- liftEffect $ resolve [ current ] backupsDirname 123 | FS.mkdir path <|> pure unit 124 | pure path 125 | 126 | type RunTemplateOptions = 127 | { templatesDir :: FilePath 128 | , backupsDir :: FilePath 129 | , variables :: Variables 130 | } 131 | 132 | -- | Run an individual template. This consists of: 133 | -- | 134 | -- | 1. Backing up any existing files that would be overwritten 135 | -- | 2. Reading the contents of the template file 136 | -- | 3. Updating those contents by replacing any dynamic content (variables) 137 | -- | 4. Writing the new contents into the correct directory location 138 | runTemplate :: RunTemplateOptions -> TemplateSource -> Aff Unit 139 | runTemplate opts template = do 140 | let 141 | from = templateSourcePath { usesJS: opts.variables.usesJS } template 142 | to = template.destination 143 | templatePath <- liftEffect $ resolve [ opts.templatesDir ] from 144 | templateContents <- FS.readTextFile UTF8 templatePath 145 | 146 | exists <- FS.exists to 147 | when exists do 148 | existingContents <- FS.readTextFile UTF8 to 149 | -- Only back up a conflicting file if it differs from the existing file. 150 | unless (templateContents == existingContents) do 151 | backupsPath <- liftEffect $ resolve [ opts.backupsDir ] to 152 | FS.Extra.writeTextFile backupsPath existingContents 153 | FS.unlink to 154 | 155 | FS.Extra.writeTextFile to (replaceVariables opts.variables templateContents) 156 | 157 | -- | Keep JS only templates when using JS. 158 | filterByType :: { usesJS :: Boolean, templates :: Array TemplateSource } -> Array TemplateSource 159 | filterByType { usesJS: true, templates } = templates 160 | filterByType { usesJS: false, templates } = filter (not eq JS <<< _.sourceType) templates 161 | 162 | -- | Define the source of a given template by its type and destination path. 163 | -- | Common templates default to base unless using JS. 164 | templateSourcePath :: { usesJS :: Boolean } -> TemplateSource -> FilePath 165 | templateSourcePath { usesJS: true } { sourceType: Common, destination } = "js/" <> destination 166 | templateSourcePath _ { sourceType: JS, destination } = "js/" <> destination 167 | templateSourcePath _ { destination } = "base/" <> destination 168 | 169 | -- | Validate that the given paths of templates to run exist and for JS 170 | -- | templates that only are provided together with '--uses-js' 171 | validateFiles 172 | :: { usesJS :: Boolean, templates :: Array TemplateSource } 173 | -> NonEmptyList FilePath 174 | -> Either String (Array TemplateSource) 175 | validateFiles { usesJS, templates } files = fromFoldable <$> traverse validateFile files 176 | where 177 | validateFile :: FilePath -> Either String TemplateSource 178 | validateFile path = 179 | case find (eq path <<< _.destination) templates of 180 | Nothing -> Left $ "Path '" <> path <> "' is not a valid template" 181 | Just { sourceType: JS } | not usesJS -> 182 | Left $ "Path '" <> path <> "' is a JS-only template. Did you forget '--uses-js'?" 183 | Just template -> Right template 184 | 185 | -- | Template types: 186 | -- | 187 | -- | - Base: template which is the same in both PS-only and with-JS projects 188 | -- | - JS: template which is only used in JS projects 189 | -- | - Common: template used in both PS-only with-JS projects, but differs 190 | -- | which differs depending on the project type 191 | data TemplateSourceType = Base | JS | Common 192 | 193 | derive instance eqTemplateSourceType :: Eq TemplateSourceType 194 | 195 | -- | The source and type for a given template. 196 | type TemplateSource = { sourceType :: TemplateSourceType, destination :: FilePath } 197 | 198 | gitignore :: TemplateSource 199 | gitignore = { sourceType: Common, destination: ".gitignore" } 200 | 201 | tidyconfig :: TemplateSource 202 | tidyconfig = { sourceType: Base, destination: ".tidyrc.json" } 203 | 204 | editorconfig :: TemplateSource 205 | editorconfig = { sourceType: Base, destination: ".editorconfig" } 206 | 207 | repoReadme :: TemplateSource 208 | repoReadme = { sourceType: Base, destination: "README.md" } 209 | 210 | docsReadme :: TemplateSource 211 | docsReadme = { sourceType: Base, destination: "docs/README.md" } 212 | 213 | docsChangelog :: TemplateSource 214 | docsChangelog = { sourceType: Base, destination: "CHANGELOG.md" } 215 | 216 | githubWorkflowCI :: TemplateSource 217 | githubWorkflowCI = { sourceType: Common, destination: ".github/workflows/ci.yml" } 218 | 219 | githubIssueBugReport :: TemplateSource 220 | githubIssueBugReport = { sourceType: Base, destination: ".github/ISSUE_TEMPLATE/bug-report.md" } 221 | 222 | githubIssueChangeRequest :: TemplateSource 223 | githubIssueChangeRequest = { sourceType: Base, destination: ".github/ISSUE_TEMPLATE/change-request.md" } 224 | 225 | githubIssueConfig :: TemplateSource 226 | githubIssueConfig = { sourceType: Base, destination: ".github/ISSUE_TEMPLATE/config.yml" } 227 | 228 | githubContributing :: TemplateSource 229 | githubContributing = { sourceType: Base, destination: "CONTRIBUTING.md" } 230 | 231 | githubPullRequest :: TemplateSource 232 | githubPullRequest = { sourceType: Base, destination: ".github/PULL_REQUEST_TEMPLATE.md" } 233 | 234 | jsEslintrc :: TemplateSource 235 | jsEslintrc = { sourceType: JS, destination: ".eslintrc.json" } 236 | 237 | jsPackageJson :: TemplateSource 238 | jsPackageJson = { sourceType: JS, destination: "package.json" } 239 | -------------------------------------------------------------------------------- /updater/src/Updater/SyncLabels/IssueLabel.purs: -------------------------------------------------------------------------------- 1 | module Updater.SyncLabels.IssueLabel where 2 | 3 | import Data.Argonaut.Core (Json, fromString, isString) 4 | import Data.Codec ((<~<)) 5 | import Data.Codec.Argonaut as CA 6 | import Data.Codec.Argonaut.Migration as CAM 7 | import Data.Codec.Argonaut.Record as CAR 8 | import Data.Map (Map) 9 | import Data.Map as Map 10 | import Data.Maybe (Maybe(..)) 11 | import Data.Set (Set) 12 | import Data.Set as Set 13 | import Data.String (drop) 14 | import Data.Tuple.Nested ((/\)) 15 | 16 | -- | A GitHub issue label consisting of three parts: 17 | -- | 18 | -- | `name` is the name of the label, which can contain emojis and spaces 19 | -- | `description` is the description of the label, displayed in help contents 20 | -- | `color` is a hexadecimal value without the leading '#' representing a color 21 | type IssueLabel = 22 | { name :: String 23 | , description :: String 24 | , color :: String 25 | } 26 | 27 | issueLabelCodec :: CA.JsonCodec IssueLabel 28 | issueLabelCodec = 29 | CAR.object "IssueLabel" 30 | { name: CA.string 31 | , description: CA.string 32 | , color: CA.string 33 | } <~< CAM.addDefaultOrUpdateField "description" fixNull 34 | where 35 | fixNull :: Maybe Json -> Json 36 | fixNull = case _ of 37 | Just v | isString v -> v 38 | _ -> fromString "" 39 | 40 | -- | Maps a label from an old name to a new one 41 | renameLabelMapping :: Map String IssueLabel 42 | renameLabelMapping = 43 | Map.fromFoldable 44 | [ "breaking change" /\ breakingChangeLabel 45 | , "bug" /\ bugLabel 46 | , "document me" /\ documentationLabel 47 | , "documentation" /\ documentationLabel 48 | , "enhancement" /\ enhancementLabel 49 | , "fix before 0.14" /\ purs0_14Label 50 | , "good first issue" /\ goodFirstIssueLabel 51 | , "merge before 0.14" /\ purs0_14Label 52 | , "wontfix" /\ wontfixLabel 53 | ] 54 | 55 | -- | The set of labels to delete 56 | deleteLabels :: Set String 57 | deleteLabels = Set.fromFoldable 58 | [ "help wanted" 59 | , "invalid" 60 | , "reference" 61 | , "stale" 62 | , "question" 63 | ] 64 | 65 | -- | The full set of issue labels supported by Contributor libraries. 66 | labels :: Array IssueLabel 67 | labels = 68 | [ breakingChangeLabel 69 | , bugLabel 70 | , regressionLabel 71 | , enhancementLabel 72 | , documentationLabel 73 | , abandonedLabel 74 | , blockedLabel 75 | , needsMoreInfoLabel 76 | , acceptedLabel 77 | , needsReviewLabel 78 | , needsApprovalLabel 79 | , goodFirstIssueLabel 80 | , duplicateLabel 81 | , wontfixLabel 82 | , houseKeepingLabel 83 | , purs0_14Label 84 | , purs0_15Label 85 | ] 86 | 87 | dropColorHashSym :: IssueLabel -> IssueLabel 88 | dropColorHashSym r = r { color = drop 1 r.color } 89 | 90 | breakingChangeLabel :: IssueLabel 91 | breakingChangeLabel = dropColorHashSym 92 | { name: "type: breaking change" 93 | , color: "#e99695" 94 | , description: "A change that requires a major version bump." 95 | } 96 | 97 | bugLabel :: IssueLabel 98 | bugLabel = dropColorHashSym 99 | { name: "type: bug" 100 | , color: "#d73a4a" 101 | , description: "Something that should function correctly isn't." 102 | } 103 | 104 | regressionLabel :: IssueLabel 105 | regressionLabel = dropColorHashSym 106 | { name: "type: regression" 107 | , color: "#e4d0f0" 108 | , description: "Something that worked previously no longer works." 109 | } 110 | 111 | enhancementLabel :: IssueLabel 112 | enhancementLabel = dropColorHashSym 113 | { name: "type: enhancement" 114 | , color: "#a6e1ea" 115 | , description: "A new feature or addition." 116 | } 117 | 118 | documentationLabel :: IssueLabel 119 | documentationLabel = dropColorHashSym 120 | { name: "type: documentation" 121 | , color: "#0000ff" 122 | , description: "Improvements or additions to documentation." 123 | } 124 | 125 | abandonedLabel :: IssueLabel 126 | abandonedLabel = dropColorHashSym 127 | { name: "status: abandoned" 128 | , color: "#000000" 129 | , description: "This PR is no longer being worked on. Another can use it as a base for continuing the work." 130 | } 131 | 132 | blockedLabel :: IssueLabel 133 | blockedLabel = dropColorHashSym 134 | { name: "status: blocked" 135 | , color: "#c09000" 136 | , description: "This issue or PR is blocked by something and cannot make progress." 137 | } 138 | 139 | needsMoreInfoLabel :: IssueLabel 140 | needsMoreInfoLabel = dropColorHashSym 141 | { name: "status: needs more info" 142 | , color: "#e0a000" 143 | , description: "This issue needs more info before any action can be done." 144 | } 145 | 146 | acceptedLabel :: IssueLabel 147 | acceptedLabel = dropColorHashSym 148 | { name: "status: accepted" 149 | , color: "#005000" 150 | , description: "This issue is now ready to be implemented via a PR." 151 | } 152 | 153 | needsReviewLabel :: IssueLabel 154 | needsReviewLabel = dropColorHashSym 155 | { name: "status: needs review" 156 | , color: "#008000" 157 | , description: "This PR needs a review." 158 | } 159 | 160 | needsApprovalLabel :: IssueLabel 161 | needsApprovalLabel = dropColorHashSym 162 | { name: "status: needs approval" 163 | , color: "#00b000" 164 | , description: "This PR needs approval before it can be merged." 165 | } 166 | 167 | goodFirstIssueLabel :: IssueLabel 168 | goodFirstIssueLabel = dropColorHashSym 169 | { name: "good first issue" 170 | , color: "#7007ff" 171 | , description: "First-time contributors who are looking to help should work on these issues." 172 | } 173 | 174 | duplicateLabel :: IssueLabel 175 | duplicateLabel = dropColorHashSym 176 | { name: "duplicate" 177 | , color: "#cccccc" 178 | , description: "This issue or pull request already exists." 179 | } 180 | 181 | wontfixLabel :: IssueLabel 182 | wontfixLabel = dropColorHashSym 183 | { name: "status: wontfix" 184 | , color: "#ffffff" 185 | , description: "The maintainers of this library don't think the issue is actually a problem." 186 | } 187 | 188 | houseKeepingLabel :: IssueLabel 189 | houseKeepingLabel = dropColorHashSym 190 | { name: "type: housekeeping" 191 | , color: "#ebea04" 192 | , description: "Repo-related things (e.g. fixing CI) that need to be done." 193 | } 194 | 195 | purs0_14Label :: IssueLabel 196 | purs0_14Label = dropColorHashSym 197 | { name: "purs-0.14" 198 | , color: "#404040" 199 | , description: "A reminder to address this issue or merge this PR before we release PureScript v0.14.0" 200 | } 201 | 202 | purs0_15Label :: IssueLabel 203 | purs0_15Label = dropColorHashSym 204 | { name: "purs-0.15" 205 | , color: "#404040" 206 | , description: "A reminder to address this issue or merge this PR before we release PureScript v0.15.0" 207 | } 208 | -------------------------------------------------------------------------------- /updater/src/Updater/SyncLabels/Repos.purs: -------------------------------------------------------------------------------- 1 | module Updater.SyncLabels.Repos where 2 | 3 | -- spago ls packages | grep 'com/purescript/' | sed -r 's/([ \t])+/ /g' | 4 | -- \ cut -d ' ' -f 4 | sed 's/"//g' | sed 's;https://github.com/;;' | 5 | -- \ sed -r 's/.git$//g' | cut -d '/' -f 2 | tr '\n' ' ' 6 | {- 7 | spago ls packages | grep 'com/purescript/' | sed -r 's/([ \t])+/ /g' | 8 | \ cut -d ' ' -f 4 | sed 's/"//g' | sed 's;https://github.com/;;' | 9 | \ sed -r 's/.git$//g' | cut -d '/' -f 2 | tr '\n' ' ' | 10 | \ sed -r 's/([^ ]+)/"\1"/g' | sed -r 's/^(.+)$/\[ \1\n \]/g' | 11 | \ sed 's/" "/"\n , "/g' 12 | 13 | -} 14 | -- PureScript 15 | purescriptRepos :: Array String 16 | purescriptRepos = 17 | [ "purescript-arrays" 18 | , "purescript-assert" 19 | , "purescript-bifunctors" 20 | , "purescript-catenable-lists" 21 | , "purescript-console" 22 | , "purescript-const" 23 | , "purescript-contravariant" 24 | , "purescript-control" 25 | , "purescript-datetime" 26 | , "purescript-distributive" 27 | , "purescript-effect" 28 | , "purescript-either" 29 | , "purescript-enums" 30 | , "purescript-exceptions" 31 | , "purescript-exists" 32 | , "purescript-filterable" 33 | , "purescript-foldable-traversable" 34 | , "purescript-foreign" 35 | , "purescript-foreign-object" 36 | , "purescript-free" 37 | , "purescript-functions" 38 | , "purescript-functors" 39 | , "purescript-gen" 40 | , "purescript-graphs" 41 | , "purescript-identity" 42 | , "purescript-integers" 43 | , "purescript-invariant" 44 | , "purescript-lazy" 45 | , "purescript-lcg" 46 | , "purescript-lists" 47 | , "purescript-math" 48 | , "purescript-maybe" 49 | , "purescript-minibench" 50 | , "purescript-newtype" 51 | , "purescript-nonempty" 52 | , "purescript-numbers" 53 | , "purescript-ordered-collections" 54 | , "purescript-orders" 55 | , "purescript-parallel" 56 | , "purescript-partial" 57 | , "purescript-prelude" 58 | , "purescript-profunctor" 59 | , "purescript-psci-support" 60 | , "purescript-quickcheck" 61 | , "purescript-random" 62 | , "purescript-record" 63 | , "purescript-refs" 64 | , "purescript-safe-coerce" 65 | , "purescript-semirings" 66 | , "purescript-st" 67 | , "purescript-strings" 68 | , "purescript-tailrec" 69 | , "purescript-transformers" 70 | , "purescript-tuples" 71 | , "purescript-type-equality" 72 | , "purescript-typelevel-prelude" 73 | , "purescript-unfoldable" 74 | , "purescript-unsafe-coerce" 75 | , "purescript-validation" 76 | ] 77 | 78 | {- 79 | spago ls packages | grep 'com/purescript-contrib/' | sed -r 's/([ \t])+/ /g' | 80 | \ cut -d ' ' -f 4 | sed 's/"//g' | sed 's;https://github.com/;;' | 81 | \ sed -r 's/.git$//g' | cut -d '/' -f 2 | tr '\n' ' ' | 82 | \ sed -r 's/([^ ]+)/"\1"/g' | sed -r 's/^(.+)$/\[ \1\n \]/g' | 83 | \ sed 's/" "/"\n , "/g' 84 | 85 | -} 86 | purescriptContribRepos :: Array String 87 | purescriptContribRepos = 88 | [ "purescript-ace" 89 | , "purescript-aff" 90 | , "purescript-aff-bus" 91 | , "purescript-aff-coroutines" 92 | , "purescript-affjax" 93 | , "purescript-affjax-node" 94 | , "purescript-affjax-web" 95 | , "purescript-argonaut" 96 | , "purescript-argonaut-codecs" 97 | , "purescript-argonaut-core" 98 | , "purescript-argonaut-generic" 99 | , "purescript-argonaut-traversals" 100 | , "purescript-arraybuffer-types" 101 | , "purescript-avar" 102 | , "purescript-colors" 103 | , "purescript-concurrent-queues" 104 | , "purescript-coroutines" 105 | , "purescript-css" 106 | , "purescript-fixed-points" 107 | , "purescript-fork" 108 | , "purescript-form-urlencoded" 109 | , "purescript-formatters" 110 | , "purescript-freet" 111 | , "purescript-github-actions-toolkit" 112 | , "purescript-http-methods" 113 | , "purescript-js-date" 114 | , "purescript-js-timers" 115 | , "purescript-js-uri" 116 | , "purescript-machines" 117 | , "purescript-matryoshka" 118 | , "purescript-media-types" 119 | , "purescript-now" 120 | , "purescript-nullable" 121 | , "purescript-options" 122 | , "purescript-parsing" 123 | , "purescript-pathy" 124 | , "purescript-precise" 125 | , "purescript-profunctor-lenses" 126 | , "purescript-quickcheck-laws" 127 | , "purescript-react" 128 | , "purescript-react-dom" 129 | , "purescript-routing" 130 | , "purescript-string-parsers" 131 | , "purescript-strings-extra" 132 | , "purescript-these" 133 | , "purescript-unicode" 134 | , "purescript-unsafe-reference" 135 | , "purescript-uri" 136 | ] 137 | 138 | {- 139 | spago ls packages | grep 'com/purescript-web/' | sed -r 's/([ \t])+/ /g' | 140 | \ cut -d ' ' -f 4 | sed 's/"//g' | sed 's;https://github.com/;;' | 141 | \ sed -r 's/.git$//g' | cut -d '/' -f 2 | tr '\n' ' ' | 142 | \ sed -r 's/([^ ]+)/"\1"/g' | sed -r 's/^(.+)$/\[ \1\n \]/g' | 143 | \ sed 's/" "/"\n , "/g' 144 | 145 | -} 146 | 147 | purescriptNodeRepos :: Array String 148 | purescriptNodeRepos = 149 | [ "purescript-node-buffer" 150 | , "purescript-node-child-process" 151 | , "purescript-node-fs" 152 | , "purescript-node-fs-aff" 153 | , "purescript-node-http" 154 | , "purescript-node-net" 155 | , "purescript-node-path" 156 | , "purescript-node-process" 157 | , "purescript-node-readline" 158 | , "purescript-node-streams" 159 | , "purescript-node-url" 160 | , "purescript-posix-types" 161 | ] 162 | 163 | {- 164 | spago ls packages | grep 'com/purescript-node/' | sed -r 's/([ \t])+/ /g' | 165 | \ cut -d ' ' -f 4 | sed 's/"//g' | sed 's;https://github.com/;;' | 166 | \ sed -r 's/.git$//g' | cut -d '/' -f 2 | tr '\n' ' ' | 167 | \ sed -r 's/([^ ]+)/"\1"/g' | sed -r 's/^(.+)$/\[ \1\n \]/g' | 168 | \ sed 's/" "/"\n , "/g' 169 | 170 | -} 171 | 172 | purescriptWebRepos :: Array String 173 | purescriptWebRepos = 174 | [ "purescript-canvas" 175 | , "purescript-web-clipboard" 176 | , "purescript-web-cssom" 177 | , "purescript-web-dom" 178 | , "purescript-web-dom-parser" 179 | , "purescript-web-dom-xpath" 180 | , "purescript-web-encoding" 181 | , "purescript-web-events" 182 | , "purescript-web-fetch" 183 | , "purescript-web-file" 184 | , "purescript-web-html" 185 | , "purescript-web-promise" 186 | , "purescript-web-socket" 187 | , "purescript-web-storage" 188 | , "purescript-web-streams" 189 | , "purescript-web-touchevents" 190 | , "purescript-web-uievents" 191 | , "purescript-web-xhr" 192 | ] 193 | -------------------------------------------------------------------------------- /updater/src/Updater/SyncLabels/Request.purs: -------------------------------------------------------------------------------- 1 | module Updater.SyncLabels.Request 2 | ( IssueLabelRequestOpts(..) 3 | , listAllLabels 4 | , Sifted(..) 5 | , getLabels 6 | , getSiftedLabels 7 | , LabelAction(..) 8 | , calcLabelActions 9 | , createLabel 10 | , patchLabel 11 | , patchLabel' 12 | , deleteLabel 13 | , deleteLabel' 14 | ) where 15 | 16 | import Prelude 17 | 18 | import Affjax (URL) 19 | import Affjax as AX 20 | import Affjax.RequestBody (RequestBody(..)) 21 | import Affjax.RequestHeader (RequestHeader(..)) 22 | import Affjax.ResponseFormat as AXRF 23 | import Affjax.StatusCode (StatusCode(..)) 24 | import Control.Monad.Except (ExceptT(..), throwError) 25 | import Control.Parallel (parTraverse) 26 | import Data.Array (filter, sort) 27 | import Data.Array as Array 28 | import Data.Bifunctor (lmap) 29 | import Data.Codec (encode, (<~<)) 30 | import Data.Codec as Codec 31 | import Data.Codec.Argonaut as CA 32 | import Data.Codec.Argonaut.Migration as CAM 33 | import Data.Either (Either(..)) 34 | import Data.Foldable (foldl, traverse_) 35 | import Data.FoldableWithIndex (foldlWithIndex) 36 | import Data.HTTP.Method (Method(..)) 37 | import Data.Interpolate (i) 38 | import Data.Map (Map) 39 | import Data.Map as Map 40 | import Data.Maybe (Maybe(..), isNothing) 41 | import Data.Set as Set 42 | import Data.Symbol (SProxy(..)) 43 | import Data.Tuple (Tuple(..), fst, snd) 44 | import Effect.Aff (Aff, Error, error) 45 | import Effect.Class (liftEffect) 46 | import Effect.Class.Console (log) 47 | import Record as Record 48 | import Updater.SyncLabels.IssueLabel (IssueLabel, issueLabelCodec) 49 | import Updater.SyncLabels.IssueLabel as IssueLabel 50 | import Updater.SyncLabels.IssueLabel as Issuelabel 51 | import Updater.SyncLabels.Repos (purescriptContribRepos, purescriptNodeRepos, purescriptRepos, purescriptWebRepos) 52 | 53 | -- | The arguments necessary to construct a request to the GitHub API for issue 54 | -- | labels. 55 | type IssueLabelRequestOpts = 56 | { owner :: String 57 | , repo :: String 58 | , token :: String 59 | } 60 | 61 | type Sifted = 62 | { create :: Array IssueLabel 63 | , update :: Array IssueLabel 64 | , delete :: Array IssueLabel 65 | } 66 | 67 | listAllLabels :: String -> ExceptT Error Aff Unit 68 | listAllLabels token = do 69 | let 70 | allRepos = 71 | map { owner: "purescript", repo: _ } purescriptRepos 72 | <> map { owner: "purescript-contrib", repo: _ } purescriptContribRepos 73 | <> map { owner: "purescript-web", repo: _ } purescriptWebRepos 74 | <> map { owner: "purescript-node", repo: _ } purescriptNodeRepos 75 | 76 | getRepoLabel = listLabel <<< (\{ owner, repo } -> { token, owner, repo }) 77 | 78 | results <- parTraverse getRepoLabel allRepos 79 | 80 | let 81 | labelMap 82 | :: { labels :: Map String (Array String) 83 | , metadata :: Map String (Map { description :: String, color :: String } (Array String)) 84 | } 85 | labelMap = foldl insertRepoForEachLabel emptyMaps results 86 | where 87 | emptyMaps = { labels: Map.empty, metadata: Map.empty } 88 | 89 | insertRepoForEachLabel accMap { owner, repo, labels } = 90 | foldl (handleInsert owner repo) accMap labels 91 | 92 | handleInsert owner repo accMap label@{ description, color } = { labels, metadata } 93 | where 94 | labels = Map.insertWith (<>) label.name [ repo ] accMap.labels 95 | metadata = Map.insertWith (Map.unionWith (<>)) label.name labelMetadata accMap.metadata 96 | where 97 | labelMetadata = Map.singleton { description, color } [ i owner "/" repo ] 98 | 99 | liftEffect do 100 | let 101 | uniqueLabelsSize = show $ Map.size labelMap.labels 102 | allLabels = show $ sort $ map fst $ (Map.toUnfoldableUnordered labelMap.labels :: Array _) 103 | 104 | unfoldedMap = Map.toUnfoldableUnordered labelMap.metadata 105 | noDifferences = filter (eq 1 <<< Map.size <<< snd) unfoldedMap 106 | noDifferencesSortedShown = show $ sort $ map fst noDifferences 107 | haveDiff = filter (not <<< eq 1 <<< Map.size <<< snd) unfoldedMap 108 | haveDiffNumber = Array.length haveDiff 109 | metadataDiff = foldl foldFn [] haveDiff 110 | where 111 | foldFn acc (Tuple labelName metadata) = 112 | acc 113 | <> [ i labelName " has " (Map.size metadata) " differences" ] 114 | <> (foldl foldFn2 [] $ sort $ Map.toUnfoldable metadata) 115 | <> [ "" ] 116 | 117 | foldFn2 acc (Tuple r repos) = acc <> 118 | [ i "Color: #" r.color " | Description: " r.description 119 | , i " ↳ Repos (" (Array.length repos) "): " $ show repos 120 | ] 121 | 122 | labelAppearancesInRepos = foldlWithIndex foldFn [] labelMap.labels 123 | where 124 | foldFn labelName acc repos = acc <> 125 | [ i "Label '" labelName "' appears in " (Array.length repos) " repos:" 126 | , show repos 127 | , "" 128 | ] 129 | 130 | finalReport = 131 | [ i "# of Unique Labels: " uniqueLabelsSize 132 | , i "Label names: " allLabels 133 | , "----------------" 134 | ] 135 | <> labelAppearancesInRepos 136 | <> 137 | [ "----------------" 138 | , "Labels with no differences in metadata:" 139 | , noDifferencesSortedShown 140 | , "----------------" 141 | , i haveDiffNumber " labels have differences in metadata:" 142 | ] 143 | <> metadataDiff 144 | 145 | traverse_ log finalReport 146 | 147 | where 148 | listLabel opts@{ owner, repo } = do 149 | resp <- ExceptT $ map (lmap (error <<< AX.printError)) $ AX.request $ AX.defaultRequest 150 | { headers = mkHeaders opts 151 | , responseFormat = AXRF.json 152 | , url = mkApiUrl opts 153 | } 154 | 155 | unless (resp.status == StatusCode 200) do 156 | throwError $ error $ i "Did not receive StatusCode 200 when getting labels for: " opts.owner "/" opts.repo 157 | 158 | let decoded = Codec.decode (CA.array issueLabelCodec) resp.body 159 | 160 | labels <- ExceptT $ pure $ lmap (error <<< CA.printJsonDecodeError) decoded 161 | pure { owner, repo, labels } 162 | 163 | -- | Fetch all labels for the repository. 164 | getLabels :: IssueLabelRequestOpts -> ExceptT Error Aff (Array IssueLabel) 165 | getLabels opts = do 166 | resp <- ExceptT $ map (lmap (error <<< AX.printError)) $ AX.request $ AX.defaultRequest 167 | { headers = mkHeaders opts 168 | , responseFormat = AXRF.json 169 | , url = mkApiUrl opts 170 | } 171 | 172 | unless (resp.status == StatusCode 200) do 173 | throwError $ error "Did not receive StatusCode 200 when getting labels." 174 | 175 | let decoded = Codec.decode (CA.array issueLabelCodec) resp.body 176 | 177 | ExceptT $ pure $ lmap (error <<< CA.printJsonDecodeError) decoded 178 | 179 | -- | Fetch all labels for the repository. 180 | getSiftedLabels :: IssueLabelRequestOpts -> ExceptT Error Aff Sifted 181 | getSiftedLabels opts = do 182 | sift <$> getLabels opts 183 | 184 | -- | Reconcile the labels received from the API with the actions that should be 185 | -- | taken for each label (create missing labels, patch existing labels, or 186 | -- | delete extraneous labels). 187 | sift :: Array IssueLabel -> Sifted 188 | sift = 189 | Record.delete (SProxy :: _ "accept") 190 | <<< fillMissing 191 | <<< Record.insert (SProxy :: _ "create") [] 192 | <<< foldl checkApiLabel { update: [], delete: [], accept: [] } 193 | where 194 | checkApiLabel acc apiLabel = 195 | -- This label is already correct, so do nothing (add to the 'accept' array) 196 | case Array.find (eq apiLabel) Issuelabel.labels of 197 | Just v -> 198 | acc { accept = Array.cons v acc.accept } 199 | Nothing -> case Array.find (eq apiLabel.name <<< _.name) IssueLabel.labels of 200 | -- this label matches a contrib label, but not all information matches, 201 | -- so patch the label 202 | Just v -> 203 | acc { update = Array.cons v acc.update } 204 | -- this label does not exist in the contrib set, so it should be deleted 205 | -- (if that option was selected) 206 | Nothing -> 207 | acc { delete = Array.cons apiLabel acc.delete } 208 | 209 | -- Any API labels that don't already exist or need to be patched will need to 210 | -- be created. 211 | fillMissing sifted = sifted 212 | { create = 213 | Issuelabel.labels # Array.filter \{ name } -> 214 | isNothing (Array.find (eq name <<< _.name) sifted.update) 215 | && isNothing (Array.find (eq name <<< _.name) sifted.accept) 216 | } 217 | 218 | data LabelAction 219 | = Create IssueLabel 220 | | Update String IssueLabel 221 | | Delete String 222 | 223 | allLabelsMap :: Map String IssueLabel 224 | allLabelsMap = Map.fromFoldable $ map (\r -> Tuple r.name r) IssueLabel.labels 225 | 226 | -- | Using a repo's current array of labels, returns 227 | -- | an array of actions to take to fully sync that repo's labels 228 | -- | with the expected ones. 229 | -- | 230 | -- | Note: a label will be deleted if it is found in `IssueLabel.deleteLabels` 231 | calcLabelActions :: Array IssueLabel -> Array LabelAction 232 | calcLabelActions repoLabels = insertCreateActions calcUpdateDeleteAndCreate 233 | where 234 | -- | First, create an initial array of deletions and updates (i.e. `updateDelete`) 235 | -- | while also tracking which labels we no longer need to 236 | -- | create because they already exist (i.e. `create`). 237 | calcUpdateDeleteAndCreate = Array.foldl deleteUpdateIgnoreOrTrackCreateables init repoLabels 238 | where 239 | init :: { updateDelete :: Array LabelAction, create :: Map String IssueLabel } 240 | init = { updateDelete: [], create: allLabelsMap } 241 | 242 | deleteUpdateIgnoreOrTrackCreateables 243 | :: { updateDelete :: Array LabelAction, create :: Map String IssueLabel } 244 | -> IssueLabel 245 | -> { updateDelete :: Array LabelAction, create :: Map String IssueLabel } 246 | deleteUpdateIgnoreOrTrackCreateables acc next 247 | | Set.member next.name IssueLabel.deleteLabels = 248 | acc { updateDelete = acc.updateDelete `Array.snoc` (Delete next.name) } 249 | | Just newLabel <- Map.lookup next.name IssueLabel.renameLabelMapping = 250 | { updateDelete: acc.updateDelete `Array.snoc` (Update next.name newLabel) 251 | , create: Map.delete newLabel.name acc.create 252 | } 253 | | Just newLabel <- Map.lookup next.name allLabelsMap = 254 | { updateDelete: acc.updateDelete `Array.snoc` (Update next.name newLabel) 255 | , create: Map.delete next.name acc.create 256 | } 257 | -- label not found: ignore it 258 | | otherwise = acc 259 | 260 | -- | Second, insert the labels we need to create, as they weren't found in 261 | -- | the earlier fold. 262 | insertCreateActions 263 | :: { updateDelete :: Array LabelAction, create :: Map String IssueLabel } 264 | -> Array LabelAction 265 | insertCreateActions { updateDelete, create } = 266 | foldl addCreateActions updateDelete create 267 | where 268 | addCreateActions :: Array LabelAction -> IssueLabel -> Array LabelAction 269 | addCreateActions acc label = acc `Array.snoc` (Create label) 270 | 271 | -- | Create a new label. Note: if updating a label, use `patchLabel` instead. 272 | createLabel :: IssueLabelRequestOpts -> IssueLabel -> ExceptT Error Aff Unit 273 | createLabel opts label = do 274 | resp <- ExceptT $ map (lmap (error <<< AX.printError)) $ AX.request $ AX.defaultRequest 275 | { method = Left POST 276 | , headers = mkHeaders opts 277 | , url = mkApiUrl opts 278 | , content = Just $ Json $ encode issueLabelCodec label 279 | } 280 | 281 | unless (resp.status == StatusCode 201) do 282 | throwError $ error $ "Did not receive StatusCode 201 when creating label: " <> label.name 283 | 284 | -- | Update a label which already exists to a new value. 285 | -- | 286 | -- | The patch endpoint for labels uses `new_name` instead of `name` for the 287 | -- | label name, so we need to migrate our codec before making the request. 288 | -- | 289 | -- | See: https://developer.github.com/v3/issues/labels/#update-a-label 290 | patchLabel :: IssueLabelRequestOpts -> IssueLabel -> ExceptT Error Aff Unit 291 | patchLabel opts label = patchLabel' opts label.name label 292 | 293 | patchLabel' :: IssueLabelRequestOpts -> String -> IssueLabel -> ExceptT Error Aff Unit 294 | patchLabel' opts oldName label = do 295 | resp <- ExceptT $ map (lmap (error <<< AX.printError)) $ AX.request $ AX.defaultRequest 296 | { method = Left PATCH 297 | , headers = mkHeaders opts 298 | , url = i (mkApiUrl opts) "/" oldName 299 | , content = 300 | Just 301 | $ Json 302 | $ flip encode label 303 | $ issueLabelCodec <~< CAM.renameField "new_name" "name" 304 | } 305 | 306 | unless (resp.status == StatusCode 200) do 307 | throwError $ error $ "Did not receive StatusCode 200 when patching label: " <> label.name 308 | 309 | -- | Delete a particular label. 310 | deleteLabel :: IssueLabelRequestOpts -> IssueLabel -> ExceptT Error Aff Unit 311 | deleteLabel opts label = deleteLabel' opts label.name 312 | 313 | deleteLabel' :: IssueLabelRequestOpts -> String -> ExceptT Error Aff Unit 314 | deleteLabel' opts labelName = do 315 | resp <- ExceptT $ map (lmap (error <<< AX.printError)) $ AX.request $ AX.defaultRequest 316 | { method = Left DELETE 317 | , headers = mkHeaders opts 318 | , url = i (mkApiUrl opts) "/" labelName 319 | } 320 | 321 | unless (resp.status == StatusCode 204) do 322 | throwError $ error $ "Did not receive StatusCode 204 when deleting label: " <> labelName 323 | 324 | -- | Construct the GitHub API base endpoint for issue labels given a repository 325 | -- | owner and nmae. 326 | mkApiUrl :: forall r. { owner :: String, repo :: String | r } -> URL 327 | mkApiUrl { owner, repo } = 328 | i "https://api.github.com/repos/" owner "/" repo "/labels" 329 | 330 | -- | Construct the authentication header for the GitHub API given a personal 331 | -- | access token with repo scope. 332 | mkHeaders :: forall r. { token :: String | r } -> Array RequestHeader 333 | mkHeaders { token } = 334 | [ RequestHeader "authorization" (i "token " token) ] 335 | -------------------------------------------------------------------------------- /updater/src/Updater/Utils/Dhall.purs: -------------------------------------------------------------------------------- 1 | module Updater.Utils.Dhall 2 | ( readSpagoFile 3 | ) where 4 | 5 | import Prelude 6 | 7 | import Data.Argonaut.Core (Json) 8 | import Data.Argonaut.Parser (jsonParser) 9 | import Data.Codec.Argonaut (printJsonDecodeError) 10 | import Data.Codec.Argonaut as CA 11 | import Data.Codec.Argonaut.Record as CAR 12 | import Data.Either (Either(..)) 13 | import Data.Interpolate (i) 14 | import Data.Maybe (Maybe(..), isNothing) 15 | import Data.String as String 16 | import Effect.Aff (Aff) 17 | import Effect.Aff as Aff 18 | import Effect.Class.Console (error) 19 | import Node.ChildProcess as CP 20 | import Node.Encoding (Encoding(..)) 21 | import Node.FS.Aff as FS 22 | import Node.Path (FilePath) 23 | import Sunde as S 24 | 25 | -- | The contents of the `spago.dhall` file. Projects are required to use Spago. 26 | type SpagoContents = { name :: String } 27 | 28 | -- | Read the `spago.dhall` file in the root of the repository and parse its 29 | -- | contents. 30 | readSpagoFile :: Aff SpagoContents 31 | readSpagoFile = do 32 | expr <- readDhallFile "./spago.dhall" 33 | json <- runDhallToJson expr 34 | 35 | let 36 | -- Decode a Spago.dhall file, 37 | codec = CAR.object "Spago" { name: CA.string } 38 | 39 | case CA.decode codec json of 40 | Left e -> do 41 | let e' = printJsonDecodeError e 42 | error "A valid spago.dhall file is required." 43 | error e' 44 | Aff.throwError $ Aff.error e' 45 | 46 | Right v -> 47 | pure v 48 | 49 | -- | Read a file with a .dhall extension as a Dhall expression 50 | readDhallFile :: FilePath -> Aff DhallExpr 51 | readDhallFile filePath = do 52 | when (isNothing (String.stripSuffix (String.Pattern ".dhall") filePath)) do 53 | Aff.throwError $ Aff.error do 54 | i "readDhallFile requires a .dhall extension but got " filePath 55 | 56 | map DhallExpr $ FS.readTextFile UTF8 filePath 57 | 58 | -- | The type of a valid Dhall expression 59 | newtype DhallExpr = DhallExpr String 60 | 61 | -- | Run the `dhall-json` executable and parse the resulting JSON value. If this 62 | -- | fails, you probably aren't in the Nix shell, which provides that executable. 63 | runDhallToJson :: DhallExpr -> Aff Json 64 | runDhallToJson (DhallExpr expr) = do 65 | result <- S.spawn 66 | { cmd: "dhall-to-json", args: [], stdin: Just expr } 67 | CP.defaultSpawnOptions 68 | 69 | case result.exit of 70 | CP.Normally 0 -> do 71 | case jsonParser result.stdout of 72 | Left e -> do 73 | error e 74 | Aff.throwError $ Aff.error e 75 | 76 | Right v -> 77 | pure v 78 | 79 | _ -> do 80 | error $ i "Error running dhall-to-json: " (show result.exit) 81 | error result.stderr 82 | Aff.throwError $ Aff.error result.stderr 83 | -------------------------------------------------------------------------------- /updater/src/Updater/Utils/Options.purs: -------------------------------------------------------------------------------- 1 | module Updater.Utils.Options 2 | ( multiString 3 | ) where 4 | 5 | import Prelude 6 | 7 | import Data.Array (filter) 8 | import Data.Array (null) as Array 9 | import Data.Either (Either(..)) 10 | import Data.String (null) as String 11 | import Data.String.Common (split) 12 | import Data.String.Pattern (Pattern) 13 | import Options.Applicative.Types (ReadM) 14 | import Options.Applicative.Builder (eitherReader) 15 | 16 | multiString :: Pattern -> ReadM (Array String) 17 | multiString splitPattern = eitherReader \s -> do 18 | let strArray = filter (not <<< String.null) (split splitPattern s) 19 | if Array.null strArray then 20 | Left "got empty string as input" 21 | else 22 | Right strArray 23 | -------------------------------------------------------------------------------- /updater/templates/base/.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /updater/templates/base/.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report an issue 4 | title: "" 5 | labels: bug 6 | assignees: "" 7 | --- 8 | 9 | **Describe the bug** 10 | 11 | A clear and concise description of the bug. 12 | 13 | **To Reproduce** 14 | 15 | A minimal code example (preferably a runnable example on [Try PureScript](https://try.purescript.org)!) or steps to reproduce the issue. 16 | 17 | **Expected behavior** 18 | 19 | A clear and concise description of what you expected to happen. 20 | 21 | **Additional context** 22 | 23 | Add any other context about the problem here. 24 | -------------------------------------------------------------------------------- /updater/templates/base/.github/ISSUE_TEMPLATE/change-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Change request 3 | about: Propose an improvement to this library 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Is your change request related to a problem? Please describe.** 10 | 11 | A clear and concise description of the problem. 12 | 13 | Examples: 14 | 15 | - It's frustrating to have to [...] 16 | - I was looking for a function to [...] 17 | 18 | **Describe the solution you'd like** 19 | 20 | A clear and concise description of what a good solution to you looks like, including any solutions you've already considered. 21 | 22 | **Additional context** 23 | 24 | Add any other context about the change request here. 25 | -------------------------------------------------------------------------------- /updater/templates/base/.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: PureScript Discourse 4 | url: https://discourse.purescript.org/ 5 | about: Ask and answer questions on the PureScript discussion forum. 6 | - name: PureScript Discord 7 | url: https://purescript.org/chat 8 | about: Ask and answer questions on the PureScript chat. 9 | -------------------------------------------------------------------------------- /updater/templates/base/.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Description of the change** 2 | 3 | Clearly and concisely describe the purpose of the pull request. If this PR relates to an existing issue or change proposal, please link to it. Include any other background context that would help reviewers understand the motivation for this PR. 4 | 5 | --- 6 | 7 | **Checklist:** 8 | 9 | - [ ] Added the change to the changelog's "Unreleased" section with a link to this PR and your username 10 | - [ ] Linked any existing issues or proposals that this pull request should close 11 | - [ ] Updated or added relevant documentation in the README and/or documentation directory 12 | - [ ] Added a test for the contribution (if applicable) 13 | -------------------------------------------------------------------------------- /updater/templates/base/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [{{mainBranch}}] 6 | pull_request: 7 | branches: [{{mainBranch}}] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up a PureScript toolchain 17 | uses: purescript-contrib/setup-purescript@main 18 | 19 | - name: Cache PureScript dependencies 20 | uses: actions/cache@v2 21 | with: 22 | key: ${{ runner.os }}-spago-${{ hashFiles('**/*.dhall') }} 23 | path: | 24 | .spago 25 | output 26 | 27 | - name: Install dependencies 28 | run: spago install 29 | 30 | - name: Build source 31 | run: spago build --no-install --purs-args '--censor-lib --strict' 32 | 33 | - name: Run tests 34 | run: spago test --no-install 35 | -------------------------------------------------------------------------------- /updater/templates/base/.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | !.github 4 | !.editorconfig 5 | !.tidyrc.json 6 | 7 | output 8 | generated-docs 9 | bower_components 10 | -------------------------------------------------------------------------------- /updater/templates/base/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 4 | 5 | ## [Unreleased] 6 | 7 | Breaking changes: 8 | 9 | New features: 10 | 11 | Bugfixes: 12 | 13 | Other improvements: 14 | -------------------------------------------------------------------------------- /updater/templates/base/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to {{displayTitle}} 2 | 3 | Thanks for your interest in contributing to {{displayName}}! We welcome new contributions regardless of your level of experience or familiarity with PureScript. 4 | 5 | Every library in the Contributors organization shares a simple handbook that helps new contributors get started. With that in mind, please [read the short contributing guide on purescript-contrib/governance](https://github.com/purescript-contrib/governance/blob/main/contributing.md) before contributing to this library. 6 | -------------------------------------------------------------------------------- /updater/templates/base/README.md: -------------------------------------------------------------------------------- 1 | # {{displayTitle}} 2 | 3 | [![CI](https://github.com/{{owner}}/{{repo}}/workflows/CI/badge.svg?branch={{mainBranch}})](https://github.com/{{owner}}/{{repo}}/actions?query=workflow%3ACI+branch%3A{{mainBranch}}) 4 | [![Release](https://img.shields.io/github/release/{{owner}}/{{repo}}.svg)](https://github.com/{{owner}}/{{repo}}/releases) 5 | [![Pursuit](https://pursuit.purescript.org/packages/purescript-{{packageName}}/badge)](https://pursuit.purescript.org/packages/purescript-{{packageName}}) 6 | {{maintainers}} 7 | 8 | The library summary hasn't been written yet (contributions are welcome!). The library summary describes the library's purpose in one to three sentences. 9 | 10 | ## Installation 11 | 12 | Install {{displayName}} with [Spago](https://github.com/purescript/spago): 13 | 14 | ```sh 15 | spago install {{packageName}} 16 | ``` 17 | 18 | ## Quick start 19 | 20 | The quick start hasn't been written yet (contributions are welcome!). The quick start covers a common, minimal use case for the library, whereas longer examples and tutorials are kept in the [docs directory](./docs). 21 | 22 | ## Documentation 23 | 24 | {{displayName}} documentation is stored in a few places: 25 | 26 | 1. Module documentation is [published on Pursuit](https://pursuit.purescript.org/packages/purescript-{{packageName}}). 27 | 2. Written documentation is kept in the [docs directory](./docs). 28 | 3. Usage examples can be found in [the test suite](./test). 29 | 30 | If you get stuck, there are several ways to get help: 31 | 32 | - [Open an issue](https://github.com/{{owner}}/{{repo}}/issues) if you have encountered a bug or problem. 33 | - Ask general questions on the [PureScript Discourse](https://discourse.purescript.org) forum or the [PureScript Discord](https://discord.com/invite/sMqwYUbvz6) chat. 34 | 35 | ## Contributing 36 | 37 | You can contribute to {{displayName}} in several ways: 38 | 39 | 1. If you encounter a problem or have a question, please [open an issue](https://github.com/{{owner}}/{{repo}}/issues). We'll do our best to work with you to resolve or answer it. 40 | 41 | 2. If you would like to contribute code, tests, or documentation, please [read the contributor guide](./CONTRIBUTING.md). It's a short, helpful introduction to contributing to this library, including development instructions. 42 | 43 | 3. If you have written a library, tutorial, guide, or other resource based on this package, please share it on the [PureScript Discourse](https://discourse.purescript.org)! Writing libraries and learning resources are a great way to help this library succeed. 44 | -------------------------------------------------------------------------------- /updater/templates/base/docs/README.md: -------------------------------------------------------------------------------- 1 | # {{displayTitle}} Documentation 2 | 3 | This directory contains documentation for {{displayName}}. If you are interested in contributing new documentation, please read the [contributor guidelines](../CONTRIBUTING.md) and [What Nobody Tells You About Documentation](https://documentation.divio.com) for help getting started. 4 | -------------------------------------------------------------------------------- /updater/templates/js/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { "browser": true }, 3 | "extends": "eslint:recommended", 4 | "parserOptions": { "ecmaVersion": 6, "sourceType": "module" }, 5 | "rules": { 6 | "block-scoped-var": "error", 7 | "consistent-return": "error", 8 | "eqeqeq": "error", 9 | "guard-for-in": "error", 10 | "no-bitwise": "error", 11 | "no-caller": "error", 12 | "no-constant-condition": ["error", { "checkLoops": false }], 13 | "no-extra-parens": "off", 14 | "no-extend-native": "error", 15 | "no-loop-func": "error", 16 | "no-new": "error", 17 | "no-param-reassign": "error", 18 | "no-return-assign": "error", 19 | "no-sequences": "error", 20 | "no-unused-expressions": "error", 21 | "no-use-before-define": "error", 22 | "no-undef": "error", 23 | "no-eq-null": "error", 24 | "radix": ["error", "always"], 25 | "indent": ["error", 2, { "SwitchCase": 0 }], 26 | "quotes": ["error", "double"], 27 | "semi": ["error", "always"], 28 | "strict": ["error", "global"] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /updater/templates/js/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [{{mainBranch}}] 6 | pull_request: 7 | branches: [{{mainBranch}}] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up PureScript toolchain 17 | uses: purescript-contrib/setup-purescript@main 18 | 19 | - name: Cache PureScript dependencies 20 | uses: actions/cache@v2 21 | with: 22 | key: ${{ runner.os }}-spago-${{ hashFiles('**/*.dhall') }} 23 | path: | 24 | .spago 25 | output 26 | 27 | - name: Set up Node toolchain 28 | uses: actions/setup-node@v1 29 | with: 30 | node-version: "12.x" 31 | 32 | - name: Cache NPM dependencies 33 | uses: actions/cache@v2 34 | env: 35 | cache-name: cache-node-modules 36 | with: 37 | path: ~/.npm 38 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }} 39 | restore-keys: | 40 | ${{ runner.os }}-build-${{ env.cache-name }}- 41 | ${{ runner.os }}-build- 42 | ${{ runner.os }}- 43 | 44 | - name: Install NPM dependencies 45 | run: npm install 46 | 47 | - name: Build the project 48 | run: npm run build 49 | 50 | - name: Run tests 51 | run: npm run test 52 | -------------------------------------------------------------------------------- /updater/templates/js/.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | !.github 4 | !.editorconfig 5 | !.eslintrc.json 6 | 7 | output 8 | generated-docs 9 | bower_components 10 | 11 | node_modules 12 | package-lock.json 13 | *.lock 14 | -------------------------------------------------------------------------------- /updater/templates/js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "eslint src && spago build --purs-args '--censor-lib --strict'", 5 | "test": "spago test --no-install" 6 | }, 7 | "devDependencies": { 8 | "eslint": "^7.6.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /updater/test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Class.Console (log) 7 | 8 | main :: Effect Unit 9 | main = do 10 | log "🍝" 11 | log "You should add some tests." 12 | --------------------------------------------------------------------------------