From c20810e804e3960e5a2e5a6f8c052824291bb511 Mon Sep 17 00:00:00 2001 From: Dan Allen Date: Sat, 23 Nov 2024 23:19:40 -0700 Subject: [PATCH] resolves #57 add js build that publishes an npm package --- .github/workflows/ci.yml | 19 ++++++-- .github/workflows/release.yml | 25 +++++----- js/.gitignore | 3 ++ js/.npmrc | 5 ++ js/README.adoc | 63 +++++++++++++++++++++++++ js/lib/extensions.js | 5 ++ js/lib/index.js | 5 ++ js/npm/transpile.js | 27 +++++++++++ js/package.json | 49 +++++++++++++++++++ js/test/fixtures/smoke-include.adoc | 1 + js/test/fixtures/smoke.adoc | 7 +++ js/test/reducer-smoke-test.js | 42 +++++++++++++++++ lib/asciidoctor/reducer/extensions.rb | 10 ++-- lib/asciidoctor/reducer/preprocessor.rb | 6 ++- release.sh | 42 +++++++++++++++-- 15 files changed, 280 insertions(+), 29 deletions(-) create mode 100644 js/.gitignore create mode 100644 js/.npmrc create mode 100644 js/README.adoc create mode 100644 js/lib/extensions.js create mode 100644 js/lib/index.js create mode 100644 js/npm/transpile.js create mode 100644 js/package.json create mode 100644 js/test/fixtures/smoke-include.adoc create mode 100644 js/test/fixtures/smoke.adoc create mode 100644 js/test/reducer-smoke-test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 538297c..5b7848d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,10 +2,10 @@ name: CI on: push: branches: ['**'] - paths-ignore: ['*.adoc', 'docs/**'] + paths-ignore: ['*.adoc', 'docs/**', 'js/*.adoc'] pull_request: branches: [main] - paths-ignore: ['*.adoc', 'docs/**'] + paths-ignore: ['*.adoc', 'docs/**', 'js/*.adoc'] schedule: - cron: '30 2 * * MON' concurrency: @@ -13,9 +13,9 @@ concurrency: cancel-in-progress: true jobs: activate: - if: | + if: >- + github.event_name == 'push' || (github.event_name == 'schedule' && github.repository_owner == 'asciidoctor') || - (github.event_name == 'push') || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'docs/')) runs-on: ubuntu-latest steps: @@ -64,7 +64,7 @@ jobs: with: ruby-version: ${{ matrix.ruby }} bundler-cache: ${{ github.event_name != 'schedule' }} - - name: Install dependencies (scheduled build only) + - name: Install Ruby dependencies (scheduled build only) if: github.event_name == 'schedule' run: | bundle config --local path vendor/bundle @@ -74,3 +74,12 @@ jobs: run: bundle exec rake lint - name: Run tests run: bundle exec ruby -w $(bundle exec ruby -e 'print File.join Gem.bindir, %q(rake)') spec + - name: Install Node.js + if: matrix.primary + uses: actions/setup-node@v3 + with: + node-version: '22' + - name: Run smoke test for npm package + if: matrix.primary + working-directory: js + run: npm run ci diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7a1c3e1..31e453f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,13 +7,8 @@ on: description: Enter version to release (e.g., 1.0.1). required: false jobs: - activate: - runs-on: ubuntu-latest - if: github.repository_owner == 'asciidoctor' && github.event_name == 'workflow_dispatch' - steps: - - run: echo ok go perform: - needs: activate + if: github.repository_owner == 'asciidoctor' && github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest environment: releases steps: @@ -24,19 +19,23 @@ jobs: with: ruby-version: '3.3' bundler-cache: false - - name: Configure Bundler + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: '22' + - name: Install Ruby dependencies run: | bundle config --local path vendor/bundle bundle config --local without coverage docs - - name: Install dependencies - run: bundle --jobs 3 --retry 3 - - name: Run tests - run: bundle exec rake spec + bundle --jobs 3 --retry 3 - name: Run linter run: bundle exec rake lint - - name: Setup release environment + - name: Run tests + run: bundle exec rake spec + - name: Set up release environment run: | echo RELEASE_VERSION=${{ github.event.inputs.release-version }} >> $GITHUB_ENV + echo RELEASE_NPM_TOKEN=${{ secrets[format('NPM_TOKEN_{0}', github.actor)] }} >> $GITHUB_ENV echo RELEASE_RUBYGEMS_API_KEY=${{ secrets[format('RUBYGEMS_API_KEY_{0}', github.actor)] }} >> $GITHUB_ENV - - name: Build, tag, and publish gem + - name: Build, tag, and publish packages run: ./release.sh diff --git a/js/.gitignore b/js/.gitignore new file mode 100644 index 0000000..f27042f --- /dev/null +++ b/js/.gitignore @@ -0,0 +1,3 @@ +/.nvmrc +/dist/ +/node_modules/ diff --git a/js/.npmrc b/js/.npmrc new file mode 100644 index 0000000..df191f5 --- /dev/null +++ b/js/.npmrc @@ -0,0 +1,5 @@ +access=public +audit=false +fund=false +omit=optional +package-lock=false diff --git a/js/README.adoc b/js/README.adoc new file mode 100644 index 0000000..41e4671 --- /dev/null +++ b/js/README.adoc @@ -0,0 +1,63 @@ += Asciidoctor Reducer + +An Asciidoctor.js extension that reduces an AsciiDoc document containing include directives to a single AsciiDoc document by expanding the includes reachable from the parent document. +Additionally, the extension evaluates preprocessor conditionals (unless the option to preserve them is enabled), only keeping those lines from conditions which are true. +If the document does not contain any preprocessor directives, the extension provides access to the unmodified source. + +(The CLI and API are not currently available in the JavaScript version). + +== Install + +This package depends on the `asciidoctor` package (>= 2.2.0, < 3.0.0), but doesn't declare it as a dependency. +Therefore, you must install that package when installing this one. + + $ npm i asciidoctor @asciidoctor/reducer + +If you're using the extension with Antora, there's no need to install the `asciidoctor` package as Antora provides it. + +== Usage + +=== Extension + +You can use this extension in combination with the load API provided by Asciidoctor. +If you want to register the extension globally, require the library as follows: + +[,js] +---- +const Asciidoctor = require('asciidoctor')() + +require('@asciidoctor/reducer').register() +---- + +When you use the Asciidoctor load API, the document will automatically be reduced. +You can access the reduced source by calling either the `getSource()` or `getSourceLines()` on the loaded document. + +[,js] +---- +const doc = Asciidoctor.loadFile('main.adoc', { safe: 'safe' }) + +console.log(doc.getSource()) +---- + +You can pass a registry instance to the `register` method to register the extension with a scoped registry (scoped to the load API call). + +[,js] +---- +const Asciidoctor = require('asciidoctor')() + +const registry = Asciidoctor.Extensions.create() +require('@asciidoctor/reducer').register(registry) + +const doc = Asciidoctor.loadFile('main.adoc', { extension_registry: registry, safe: 'safe' }) +---- + +You can also require `@asciidoctor/reducer/extensions` to access the `Extensions` class. + +== Copyright and License + +Copyright (C) 2021-present Dan Allen and the individual contributors to this project. +Use of this software is granted under the terms of the MIT License. + +== Trademarks + +AsciiDoc(R) and AsciiDoc Language(TM) are trademarks of the Eclipse Foundation, Inc. diff --git a/js/lib/extensions.js b/js/lib/extensions.js new file mode 100644 index 0000000..14c9890 --- /dev/null +++ b/js/lib/extensions.js @@ -0,0 +1,5 @@ +'use strict' + +require('../dist') + +module.exports = Opal.Asciidoctor.Reducer.Extensions diff --git a/js/lib/index.js b/js/lib/index.js new file mode 100644 index 0000000..f447191 --- /dev/null +++ b/js/lib/index.js @@ -0,0 +1,5 @@ +'use strict' + +const Extensions = require('./extensions') + +module.exports.register = (registry) => Extensions.$register(registry) diff --git a/js/npm/transpile.js b/js/npm/transpile.js new file mode 100644 index 0000000..e1d21f0 --- /dev/null +++ b/js/npm/transpile.js @@ -0,0 +1,27 @@ +'use strict' + +const { env: ENV } = require('node:process') +const fs = require('node:fs') +const ospath = require('node:path') + +let opalCompilerPath = 'opal-compiler' +try { + require.resolve(opalCompilerPath) +} catch { + const npxInstallDir = ENV.PATH.split(':')[0] + if (npxInstallDir?.endsWith('/node_modules/.bin') && npxInstallDir.startsWith(ENV.npm_config_cache + '/')) { + opalCompilerPath = require.resolve('opal-compiler', { paths: [ospath.dirname(npxInstallDir)] }) + } +} + +const transpiled = require(opalCompilerPath).Builder + .create() + .build('../lib/asciidoctor/reducer/conditional_directive_tracker.rb') + .build('../lib/asciidoctor/reducer/include_directive_tracker.rb') + .build('../lib/asciidoctor/reducer/header_attribute_tracker.rb') + .build('../lib/asciidoctor/reducer/preprocessor.rb') + .build('../lib/asciidoctor/reducer/tree_processor.rb') + .build('../lib/asciidoctor/reducer/extensions.rb') + .toString() +fs.mkdirSync('dist', { recursive: true }) +fs.writeFileSync('dist/index.js', transpiled) diff --git a/js/package.json b/js/package.json new file mode 100644 index 0000000..cb22918 --- /dev/null +++ b/js/package.json @@ -0,0 +1,49 @@ +{ + "name": "@asciidoctor/reducer", + "version": "1.1.0", + "description": "An Asciidoctor.js extension to reduce an AsciiDoc document containing includes and conditionals to a single AsciiDoc document.", + "license": "MIT", + "author": "Dan Allen", + "contributors": [ + "Dan Allen " + ], + "repository": "github:asciidoctor/asciidoctor-reducer", + "bugs": { + "url": "https://github.com/asciidoctor/asciidoctor-reducer/issues" + }, + "scripts": { + "build": "npx -y --package opal-compiler@1.0.13 node npm/transpile.js", + "preci": "npm i", + "ci": "npm run build", + "postci": "npm test", + "clean": "npx rimraf dist node_modules", + "postpublish": "npx -y downdoc --postpublish", + "prepublishOnly": "npx -y downdoc --prepublish", + "test": "node --test test/*-test.js" + }, + "main": "lib/index.js", + "exports": { + ".": "./lib/index.js", + "./extensions": "./lib/extensions.js", + "./dist/*": "./dist/*", + "./package.json": "./package.json" + }, + "devDependencies": { + "@asciidoctor/core": "~2" + }, + "files": [ + "bin", + "dist", + "lib" + ], + "engines": { + "node": ">=16.0.0" + }, + "keywords": [ + "asciidoc", + "asciidoctor", + "extension", + "include", + "preprocessor" + ] +} diff --git a/js/test/fixtures/smoke-include.adoc b/js/test/fixtures/smoke-include.adoc new file mode 100644 index 0000000..0178b17 --- /dev/null +++ b/js/test/fixtures/smoke-include.adoc @@ -0,0 +1 @@ +Text in include. diff --git a/js/test/fixtures/smoke.adoc b/js/test/fixtures/smoke.adoc new file mode 100644 index 0000000..44c68cd --- /dev/null +++ b/js/test/fixtures/smoke.adoc @@ -0,0 +1,7 @@ += Smoke Test + +Text in main document. + +include::smoke-include.adoc[] + +Text in main document. diff --git a/js/test/reducer-smoke-test.js b/js/test/reducer-smoke-test.js new file mode 100644 index 0000000..dcb08a3 --- /dev/null +++ b/js/test/reducer-smoke-test.js @@ -0,0 +1,42 @@ +'use strict' + +const assert = require('node:assert/strict') +const { describe, before, after, it } = require('node:test') +const Asciidoctor = require('@asciidoctor/core')() +const ospath = require('node:path') + +const FIXTURES_DIR = ospath.join(__dirname, 'fixtures') + +describe('reducer smoke test', () => { + it('should reduce document with includes', async () => { + const registry = Asciidoctor.Extensions.create() + require('@asciidoctor/reducer').register(registry) + const input = ospath.join(FIXTURES_DIR, 'smoke.adoc') + const expected = [ + '= Smoke Test', + '', + 'Text in main document.', + '', + 'Text in include.', + '', + 'Text in main document.', + ] + const actual = Asciidoctor.loadFile(input, { extension_registry: registry, safe: 'safe' }).getSourceLines() + assert.deepEqual(actual, expected) + }) + + it('should provide access to header attributes defined in source', async () => { + const registry = Asciidoctor.Extensions.create() + require('@asciidoctor/reducer').register(registry) + const input = [ + '= Smoke Test', + ':icons: font', + ':toc:', + '', + 'body text', + ] + const expected = { icons: 'font', toc: '' } + const actual = Asciidoctor.load(input, { extension_registry: registry, safe: 'safe' }).source_header_attributes + assert.deepEqual(actual.$$smap, expected) + }) +}) diff --git a/lib/asciidoctor/reducer/extensions.rb b/lib/asciidoctor/reducer/extensions.rb index d88710f..fb03a30 100644 --- a/lib/asciidoctor/reducer/extensions.rb +++ b/lib/asciidoctor/reducer/extensions.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true -require 'asciidoctor' unless defined? Asciidoctor.load -require_relative 'header_attribute_tracker' -require_relative 'preprocessor' -require_relative 'tree_processor' +unless RUBY_ENGINE == 'opal' + require 'asciidoctor' unless defined? Asciidoctor.load + require_relative 'header_attribute_tracker' + require_relative 'preprocessor' + require_relative 'tree_processor' +end module Asciidoctor::Reducer module Extensions diff --git a/lib/asciidoctor/reducer/preprocessor.rb b/lib/asciidoctor/reducer/preprocessor.rb index f6c84e9..42fa479 100644 --- a/lib/asciidoctor/reducer/preprocessor.rb +++ b/lib/asciidoctor/reducer/preprocessor.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true -require_relative 'include_directive_tracker' -require_relative 'conditional_directive_tracker' +unless RUBY_ENGINE == 'opal' + require_relative 'include_directive_tracker' + require_relative 'conditional_directive_tracker' +end module Asciidoctor::Reducer class Preprocessor < ::Asciidoctor::Extensions::Preprocessor diff --git a/release.sh b/release.sh index 1a1a839..6bd10ff 100755 --- a/release.sh +++ b/release.sh @@ -1,11 +1,15 @@ #!/bin/bash -# required packages (for ubuntu:kinetic): curl git jq ruby +# required packages (for ubuntu:kinetic): curl git jq nodejs npm ruby if [ -z "$RELEASE_RUBYGEMS_API_KEY" ]; then echo No API key specified for publishing to rubygems.org. Stopping release. exit 1 fi +if [ -z "$RELEASE_NPM_TOKEN" ]; then + echo No npm token specified for publishing to npmjs.com. Stopping release. + exit 1 +fi export RELEASE_BRANCH=${GITHUB_REF_NAME:-main} if [ ! -v RELEASE_USER ]; then export RELEASE_USER=$GITHUB_ACTOR @@ -29,28 +33,56 @@ mkdir -p $HOME/.gem echo -e "---\n:rubygems_api_key: $RELEASE_RUBYGEMS_API_KEY" > $HOME/.gem/credentials chmod 600 $HOME/.gem/credentials +# configure npm client for publishing +if [ "$RELEASE_VERSION" != "${RELEASE_VERSION/-/}" ]; then + RELEASE_NPM_TAG=testing +else + RELEASE_NPM_TAG=latest +fi +if case $RELEASE_VERSION in 1.0.0-*) ;; *) false;; esac; then + RELEASE_NPM_TAG=latest +fi +echo -e "//registry.npmjs.org/:_authToken=$RELEASE_NPM_TOKEN" > $HOME/.npmrc + # release! ( set -e ruby tasks/version.rb + ( + cd js + npm version --no-git-tag-version $RELEASE_VERSION + ) git commit -a -m "release $RELEASE_VERSION [no ci]" + HEAD_COMMIT=$(git rev-parse HEAD) + ( + cd js + npm run ci + sed -i '/^\/dist\/$/d' .gitignore + git add dist + ) + git commit -a -m 'add dist files for npm package' git tag -m "version $RELEASE_VERSION" v$RELEASE_VERSION mkdir -p pkg gem build $GEMSPEC -o pkg/$RELEASE_GEM_NAME-$RELEASE_GEM_VERSION.gem git push origin $(git describe --tags --exact-match) gem push pkg/$RELEASE_GEM_NAME-$RELEASE_GEM_VERSION.gem + ( + cd js + npm publish --tag $RELEASE_NPM_TAG + ) + git reset --hard $HEAD_COMMIT git push origin $RELEASE_BRANCH - #sed -i 3d README.adoc - #sed -i "$(grep -m 1 -n '^== ' CHANGELOG.adoc | cut -f1 -d:)i == Unreleased\n\n_No changes since previous release._\n" CHANGELOG.adoc - #git commit -a -m 'begin development on next version [no ci]' - #git push origin $RELEASE_BRANCH ) exit_code=$? +# nuke npm settings +rm -f $HOME/.npmrc + # nuke gem credentials rm -rf $HOME/.gem +# check for any uncommitted files git status -s -b exit $exit_code