opentype.js icon indicating copy to clipboard operation
opentype.js copied to clipboard

GitHub Action to deploy a release to npm

Open Jolg42 opened this issue 2 years ago • 40 comments
trafficstars

@ILOVEPIE what do you think about a GitHub Actions deployment, I can add the NPM_TOKEN there later for the npm package.

I used this recently on another project, and it could look like this maybe, any other idea is welcome 🙌🏼

Publishing a new version on npm

  1. Bump the package version
    • npm --no-git-tag-version version minor (or patch for a patch release)
  2. Commit files to main or merge to main via a PR
    • git add package*
    • git commit -am "Release <version>"
    • git push
  3. Create a release in the UI -> https://github.com/prisma/opentype.js/releases/new
    • Add a tag, use the version, like 1.0.1
      • Click on "Create new tag: on publish"
    • Add a release title, use the version, like 1.0.1
    • Add in the description the changes and links to PRs
    • "Target" should be "main" (default)
    • "Set as the latest release" should be checked (default)
    • Click "Publish release"
    • Monitor https://github.com/prisma/opentype.js/actions/workflows/npm-publish.yml for the publish workflow

And the action

name: Publish package to npm
on:
  release:
    types: [created]
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      # Setup .npmrc file to publish to npm
      - uses: actions/setup-node@v3
        with:
          node-version: '14'
          registry-url: 'https://registry.npmjs.org'

      - run: npm ci

      - run: npm run build

      # Publish to https://www.npmjs.com/package/checkpoint-client
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Jolg42 avatar Feb 02 '23 21:02 Jolg42

shouldn't we also have an action that runs dist upon a PR getting merged, also, i activated discussions so that we can keep the issues section dedicated to issues, and discussions separate.

ILOVEPIE avatar Feb 02 '23 21:02 ILOVEPIE

I don't mean publish on a pr getting merged, just dist, so that the repository contains the lastest build at all times.

ILOVEPIE avatar Feb 02 '23 21:02 ILOVEPIE

Another option would be to get rid of dist in the Git repository, it would make things easier. Not sure if anyone is using the dist files, if someone does, we could point them to npm maybe? https://www.npmjs.com/package/opentype.js?activeTab=explore (it's a new tab where the code is available)

Jolg42 avatar Feb 03 '23 06:02 Jolg42

The dist files are useful for people using non-npm based systems.

ILOVEPIE avatar Feb 03 '23 08:02 ILOVEPIE

plus they allow people to test the latest build from the current source code.

ILOVEPIE avatar Feb 03 '23 20:02 ILOVEPIE

@Jolg42 How do we go about getting this setup?

ILOVEPIE avatar Feb 05 '23 19:02 ILOVEPIE

I'm voting for the /dist/ folder removal because :

  • It makes the merging process impossible since all PR conflict with each other as soon as one od them changed the .min.js version
  • People who don't use npm can still fetch from the github release section, or from a NPM CDN (unpkg, cloudflare, ...)
  • There is not proof that the PR don't contain a malware in the .min.js version
  • Git is not meant to store build artefacts

See example at https://github.com/yne/picon/blob/master/.github/workflows/page.yml

yne avatar Feb 05 '23 20:02 yne

@yne fair enough, i'll do a PR that adds it to the .gitignore. My question was more on the github action for releasing to NPM & github releases.

ILOVEPIE avatar Feb 05 '23 20:02 ILOVEPIE

  • Github Action shall report status on PR open/update to help reviewer (+ add built artefact to the PR?)
  • Github Action shall build and publish artefact to NPM on tag creation
  • /dist/ shall be purged (not added to .gitignore)

This allow the following use cases:

  • If a PR fix a bug: we merge it and add an tag with an incremented micro
  • If a PR add a feature: we merge it and an tag with an incremented minor

If this tagging process is correctly done, I don't see why an end-users would need an untagged version.

yne avatar Feb 05 '23 20:02 yne

@Connum said:

I think it's good to have the current state of the master available in the dist folder as a preview version for people who want to test it out but don't want to clone and build themselves. This might even help detecting bugs earlier.

But I also acknowledge that it's annoying to have the files cause trouble when switching branches during development. I guess the optimal way would be to have a github action build the dist files on every merge on the master branch.

Anyway, if we decide to ignore the dist files, we should also delete the ones that are already tracked before that, otherwise we'll have the outdated files in the repo forever.

I can see both sides on this one. I'm thinking maybe we have a github action that builds a new "release" (labeled something like major.minor.patch-git-commit_hash) every time a pr is merged but doesn't mark it as latest and then when we actually do tag a release it will create a release (labeled major.minor.patch) and mark it as latest. IDK, whatever you think is best.

ILOVEPIE avatar Feb 05 '23 20:02 ILOVEPIE

If this tagging process is correctly done, I don't see why an end-users would need an untagged version.

Due to breaking changes, we're currently on our way to a major version update. It'd be great to have advanced users test it before officially releasing it.

Connum avatar Feb 05 '23 21:02 Connum

Due to breaking changes, we're currently on our way to a major version update. It'd be great to have advanced users test it before officially releasing it.

That's what release candidates are for no?

ILOVEPIE avatar Feb 05 '23 21:02 ILOVEPIE

Sure, if those don't accidentally land on npm, that's fine. Though for me an RC is more like "almost ready, we'll only fix bugs and won't add any more feature", but we could also have -dev versions or similar.

Connum avatar Feb 05 '23 22:02 Connum

That's what I was suggesting the "releases" ending in -git-commit_hash would be.

ILOVEPIE avatar Feb 05 '23 22:02 ILOVEPIE

It'd be great to have advanced users test it before officially releasing it.

@Connum those advanced users, who are capable of debugging OpenTypeJS, are probably also capable of running an npm run build command.

If they are only supposed to do some testing, then the alpha / beta / RC lifecycle @ILOVEPIE proposed seems very reasonable.

yne avatar Feb 05 '23 22:02 yne

Yeah, fair enough! 😄

Connum avatar Feb 05 '23 22:02 Connum

I made a POC on my fork that

  • build + lint + test on PR or merge
  • build + lint + test + publish on git tag on npm

I also took this opportunity to rework the buildflow to use esbuild / eslint / mocha.

  • no more /dist folder, .travis.yml ...
  • eslint warning are displayed by github !
  • package.json now have only 3 devDepdendencies (and 0 vulnerabilities)
  • the docs folder hold the github page (much cleaner git structure) see: demo
  • the src/opentype.js work out of the box as ESModule (<script type=module>import ...), no need to bundle/build it.

Overall I'm really surprised how smooth this migration went (thanks to the clean opentypejs codebase)

@Connum what does the major upgrade brings ? could it be used to migrate this buildflow ?

yne avatar Feb 13 '23 00:02 yne

Thanks for the work you put in! We tagged the issues and PRs we want to ship with the next version with a milestone here: https://github.com/opentypejs/opentype.js/milestone/2

If I see it correctly, you're currently targeting only modern clients with latest ES support, but I guess with esbuild it shouldn't be hard to provide a bundle for older browsers and non-module as well?

Connum avatar Feb 13 '23 07:02 Connum

Exactly, esbuild can output both esm and legacy (aka global) formats.

But shall we move the default entrypoint to the ESM for the 2.0.0 ?

using legacy --global-name=opentype format:

<script src="/dist/opentype.global.js"></script>
<script>
fetch('test/fonts/FiraSansMedium.woff')
.then(res => res.arrayBuffer())
.then(buf => console.log(opentype.parse(buf)))
</script>

using --format=esm we can use the import syntax (also directly work on /src/opentype.js):

<script type=module>
import * as opentype from '/dist/opentype.esm.js'; // alt: '/src/opentype.js'
fetch('test/fonts/FiraSansMedium.woff')
.then(res => res.arrayBuffer())
.then(buf => console.log(opentype.parse(buf)))
</script>

My only concern is that, to share the same ESM build across browsers and node.js, I had to strip usage of require(fs) from load() and download() functions. So the loading look like this for both platform:

// node
const buf = require('fs').readFileSync('font.woff');
opentype.parse(buf)

// browser
const buf = await fetch('font.woff').then(res => res.arrayBuffer())
opentype.parse(buf)

I can try to make load/download work, but I'm not a big fan of having platform-specific API in the codebase, especially if it only take 1 line from the caller (see: res.arrayBuffer() / fs.readFile) to do it on it side.

Again, that's a breaking change :/

yne avatar Feb 13 '23 11:02 yne

I really wouldn't like seeing that functionality stripped. How do other libraries handle this?

Connum avatar Feb 13 '23 11:02 Connum

I can't find much popular hybrid file-processing oriented JS libraries. But here are some:

  • mermaid 53K :star: : platform-specific are separated in a cli repo
  • marked.js 29K :star: : require('fs') is limited to node-specific /bin wrapper script
  • GraphicsMagick 6.8K :star: : require('fs') is limited to examples and tests. Not in src
  • prettier 44.8K :star: separate node-related modules at build (I guess ?)

In the meantime I'll continue to try to make require('fs') work if ESM version is imported in a browser enviroment.

yne avatar Feb 13 '23 12:02 yne

@yne I think we should maintain having both an esm and a non-esm build, the current system is expected by a lot of projects, we should also maintain the same naming scheme for the files. namely opentype.js and opentype.min.js being the non-esm version and opentype.module.js being the esm version.

ILOVEPIE avatar Feb 13 '23 22:02 ILOVEPIE

@ILOVEPIE you are right. I'll keep the original naming.

yne avatar Feb 14 '23 19:02 yne

I'm currently working on SVG-related stuff and noticed today that when we remove the dist/ folder, we'll be no longer able to use unicode-org/text-rendering-tests with pre-release versions by adding them as a github link in the package.json of the tool, but will have to replace the opentype.js file manually inside the node_modules folder. Absolute edge-case for almost anybody, but I didn't want to let it go completely unnoticed.

Connum avatar Feb 18 '23 02:02 Connum

@Connum I dont understand. I see in they package.json that they pull opentype.js so they will still get the dist files ...

did i missed something from they repo ?

yne avatar Feb 18 '23 09:02 yne

It won't be a problem when it's pulled from npm, only when testing unreleased versions locally for generating reports, like I do for https://github.com/Connum/opentypejs-reports

As I said, it's not a big deal, I'll just build the files manually and copy them over.

Connum avatar Feb 18 '23 09:02 Connum

You could even automate this build using a github action that build opentype dist file and deploy the static website (with your built files)

yne avatar Feb 18 '23 10:02 yne

That's a good idea!

Connum avatar Feb 18 '23 10:02 Connum

https://github.com/opentypejs/opentype.js/pull/567 is ready for review.

  • The dist naming are kept, even if .module.js shall be .mjs to work in node (or use "type":"module" in package.json)
  • .parse() accept both fs.readFile() Buffer and fetch() ArrayBuffer See loading-a-font example
  • The load/loadFromUrl/loadFromFile are kept, even if they are even more useless/absurd now that it can be done in 1 line
  • mocha version are untouched, since newer version require "type":"module" in package.json

So, a lot of burden that shall be lifted once we switch to 2.0.0

Any feedback are welcome

yne avatar Feb 19 '23 02:02 yne

Great! I would keep the .load() example in the README as an addition, though, as not everyone uses modules yet and can use await in the main level of the file, which might lead to confusion.

Connum avatar Feb 19 '23 09:02 Connum