From d3c8bd7c1c98db6e24efe61361b24a4646492969 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 +++++----- README.adoc | 3 ++ 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 +++++++++++++++-- 16 files changed, 283 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/README.adoc b/README.adoc index 320f376..cbbbc26 100644 --- a/README.adoc +++ b/README.adoc @@ -20,6 +20,9 @@ endif::[] Additionally, the tool 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 tool returns the unmodified source. +TIP: This extension is also published as an npm package named `@asciidoctor/reducer` for use with Asciidoctor.js, and hence, with Antora. +See the xref:js/README.adoc[README] to find instructions on how to use this package. + == Prerequisites {project-name} is a Ruby application that you install using Ruby packaging. 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