feat(esm): migrate this package to a pure ESM

BREAKING CHANGE: Please run node 12.20 or higher
BREAKING CHANGE: This package is now a pure esm, please [read this](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c)
This commit is contained in:
Nico Jansen 2022-12-05 23:06:22 +01:00 committed by Antonin Stefanutti
parent 866daced5e
commit 056002fcdc
20 changed files with 513 additions and 519 deletions

View File

@ -2,22 +2,22 @@
'use strict'; 'use strict';
const chalk = require('chalk'), import chalk from 'chalk';
crypto = require('crypto'), import chalkTemplate from 'chalk-template';
Font = require('fonteditor-core').Font, import crypto from 'crypto';
fs = require('fs'), import { Font } from 'fonteditor-core';
os = require('os'), import fs from 'fs';
parser = require('./libs/nomnom'), import os from 'os';
path = require('path'), import parser from './libs/nomnom.js';
puppeteer = require('puppeteer'), import path from 'path';
URI = require('urijs'), import puppeteer from 'puppeteer';
util = require('util'); import URI from 'urijs';
import util from 'util';
import { fileURLToPath } from 'url';
const { PDFDocument, PDFName, ParseSpeeds, decodePDFRawStream } = require('pdf-lib'); import { PDFDocument, PDFName, ParseSpeeds, decodePDFRawStream } from 'pdf-lib';
const { delay, pause } = require('./libs/util'); import { delay, pause } from './libs/util.js';
const plugins = loadAvailablePlugins(path.join(path.dirname(__filename), 'plugins'));
parser.script('decktape').options({ parser.script('decktape').options({
url : { url : {
@ -184,7 +184,8 @@ parser.command('version')
.root(true) .root(true)
.help('Display decktape package version') .help('Display decktape package version')
.callback(_ => { .callback(_ => {
console.log(require('./package.json').version); const pkg = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url)));
console.log(pkg.version);
process.exit(); process.exit();
}); });
parser.nocommand() parser.nocommand()
@ -198,19 +199,10 @@ parser.command('automatic')
`Iterates over the available plugins, picks the compatible one for presentation at the `Iterates over the available plugins, picks the compatible one for presentation at the
specified <url> and uses it to export and write the PDF into the specified <filename>.` specified <url> and uses it to export and write the PDF into the specified <filename>.`
); );
Object.entries(plugins).forEach(([id, plugin]) => {
const command = parser.command(id);
if (typeof plugin.options === 'object') {
command.options(plugin.options);
}
if (typeof plugin.help === 'string') {
command.help(plugin.help);
}
});
// TODO: should be deactivated as well when it does not execute in a TTY context // TODO: should be deactivated as well when it does not execute in a TTY context
if (os.name === 'windows') parser.nocolors(); if (os.name === 'windows') parser.nocolors();
const options = parser.parse(process.argv.slice(2));
const color = type => { const color = type => {
switch (type) { switch (type) {
@ -226,6 +218,18 @@ process.on('unhandledRejection', error => {
}); });
(async () => { (async () => {
const plugins = await loadAvailablePlugins(path.join(path.dirname(fileURLToPath(import.meta.url)), 'plugins'));
Object.entries(plugins).forEach(([id, plugin]) => {
const command = parser.command(id);
if (typeof plugin.options === 'object') {
command.options(plugin.options);
}
if (typeof plugin.help === 'string') {
command.help(plugin.help);
}
});
const options = parser.parse(process.argv.slice(2));
const browser = await puppeteer.launch({ const browser = await puppeteer.launch({
headless : true, headless : true,
@ -255,9 +259,9 @@ process.on('unhandledRejection', error => {
.on('requestfailed', request => { .on('requestfailed', request => {
// do not output warning for cancelled requests // do not output warning for cancelled requests
if (request.failure() && request.failure().errorText === 'net::ERR_ABORTED') return; if (request.failure() && request.failure().errorText === 'net::ERR_ABORTED') return;
console.log(chalk`\n{keyword('orange') Unable to load resource from URL: ${request.url()}}`); console.log(chalkTemplate`\n{keyword('orange') Unable to load resource from URL: ${request.url()}}`);
}) })
.on('pageerror', error => console.log(chalk`\n{red Page error: ${error.message}}`)); .on('pageerror', error => console.log(chalkTemplate`\n{red Page error: ${error.message}}`));
console.log('Loading page', options.url, '...'); console.log('Loading page', options.url, '...');
const load = page.waitForNavigation({ waitUntil: 'load', timeout: options.urlLoadTimeout }); const load = page.waitForNavigation({ waitUntil: 'load', timeout: options.urlLoadTimeout });
@ -275,17 +279,16 @@ process.on('unhandledRejection', error => {
.then(_ => exportSlides(plugin, page, pdf)) .then(_ => exportSlides(plugin, page, pdf))
.then(async context => { .then(async context => {
await writePdf(options.filename, pdf); await writePdf(options.filename, pdf);
console.log(chalk`{green \nPrinted {bold ${context.exportedSlides}} slides}`); console.log(chalkTemplate`{green \nPrinted {bold ${context.exportedSlides}} slides}`);
browser.close(); browser.close();
process.exit(); process.exit();
})) }))
.catch(error => { .catch(error => {
console.log(chalk`{red \n${error}}`); console.log(chalkTemplate`{red \n${error}}`);
browser.close(); browser.close();
process.exit(1); process.exit(1);
}); });
})();
async function writePdf(filename, pdf) { async function writePdf(filename, pdf) {
const pdfDir = path.dirname(filename); const pdfDir = path.dirname(filename);
@ -297,14 +300,15 @@ async function writePdf(filename, pdf) {
fs.writeFileSync(filename, await pdf.save({ addDefaultPage: false })); fs.writeFileSync(filename, await pdf.save({ addDefaultPage: false }));
} }
function loadAvailablePlugins(pluginsPath) { async function loadAvailablePlugins(pluginsPath) {
return fs.readdirSync(pluginsPath).reduce((plugins, pluginPath) => { const plugins = await fs.promises.readdir(pluginsPath);
const entries = await Promise.all(plugins.map(async pluginPath => {
const [, plugin] = pluginPath.match(/^(.*)\.js$/); const [, plugin] = pluginPath.match(/^(.*)\.js$/);
if (plugin && fs.statSync(path.join(pluginsPath, pluginPath)).isFile()) { if (plugin && (await fs.promises.stat(path.join(pluginsPath, pluginPath))).isFile()) {
plugins[plugin] = require('./plugins/' + plugin); return [plugin, await import (`./plugins/${pluginPath}`)];
} }
return plugins; }));
}, {}); return Object.fromEntries(entries.filter(Boolean));
} }
async function createPlugin(page) { async function createPlugin(page) {
@ -321,7 +325,7 @@ async function createPlugin(page) {
throw Error(`Unable to activate the ${plugin.getName()} DeckTape plugin for the address: ${options.url}`); throw Error(`Unable to activate the ${plugin.getName()} DeckTape plugin for the address: ${options.url}`);
} }
} }
console.log(chalk`{cyan {bold ${plugin.getName()}} plugin activated}`); console.log(chalkTemplate`{cyan {bold ${plugin.getName()}} plugin activated}`);
return plugin; return plugin;
} }
@ -546,3 +550,4 @@ async function progressBar(plugin, context, { skip } = { skip : false }) {
context.progressBarOverflow = Math.max(index.length + 1 - 8, 0); context.progressBarOverflow = Math.max(index.length + 1 - 8, 0);
return cols.join(''); return cols.join('');
} }
})();

View File

@ -1,13 +1,12 @@
var chalk = require('chalk'); import chalk from 'chalk';
function ArgParser() { class ArgParser {
constructor() {
this.commands = {}; // expected commands this.commands = {}; // expected commands
this.specs = {}; // option specifications this.specs = {}; // option specifications
} }
ArgParser.prototype = {
/* Add a command to the expected commands */ /* Add a command to the expected commands */
command : function(name) { command(name) {
var command; var command;
if (name) { if (name) {
command = this.commands[name] = { command = this.commands[name] = {
@ -53,77 +52,62 @@ ArgParser.prototype = {
} }
}; };
return chain; return chain;
}, }
nocommand() {
nocommand : function() {
return this.command(); return this.command();
}, }
options(specs) {
options : function(specs) {
this.specs = specs; this.specs = specs;
return this; return this;
}, }
opts(specs) {
opts : function(specs) {
// old API // old API
return this.options(specs); return this.options(specs);
}, }
globalOpts(specs) {
globalOpts : function(specs) {
// old API // old API
return this.options(specs); return this.options(specs);
}, }
option(name, spec) {
option : function(name, spec) {
this.specs[name] = spec; this.specs[name] = spec;
return this; return this;
}, }
usage(usage) {
usage : function(usage) {
this._usage = usage; this._usage = usage;
return this; return this;
}, }
printer(print) {
printer : function(print) {
this.print = print; this.print = print;
return this; return this;
}, }
script(script) {
script : function(script) {
this._script = script; this._script = script;
return this; return this;
}, }
scriptName(script) {
scriptName : function(script) {
// old API // old API
return this.script(script); return this.script(script);
}, }
help(help) {
help : function(help) {
this._help = help; this._help = help;
return this; return this;
}, }
colors() {
colors: function() {
// deprecated - colors are on by default now // deprecated - colors are on by default now
return this; return this;
}, }
nocolors() {
nocolors : function() {
this._nocolors = true; this._nocolors = true;
return this; return this;
}, }
parseArgs(argv) {
parseArgs : function(argv) {
// old API // old API
return this.parse(argv); return this.parse(argv);
}, }
nom(argv) {
nom : function(argv) {
return this.parse(argv); return this.parse(argv);
}, }
parse(argv) {
parse : function(argv) {
this.print = this.print || function (str, code) { this.print = this.print || function (str, code) {
console.log(str); console.log(str);
process.exit(code || 0); process.exit(code || 0);
@ -321,9 +305,8 @@ ArgParser.prototype = {
} }
return options; return options;
}, }
getUsage() {
getUsage : function() {
if (this.command && this.command._usage) { if (this.command && this.command._usage) {
return this.command._usage; return this.command._usage;
} }
@ -400,10 +383,10 @@ ArgParser.prototype = {
var posStr = pos.string || pos.name; var posStr = pos.string || pos.name;
str += posStr + spaces(longest - posStr.length) + " "; str += posStr + spaces(longest - posStr.length) + " ";
if (!this._nocolors) { if (!this._nocolors) {
str += chalk.grey(pos.help || "") str += chalk.grey(pos.help || "");
} }
else { else {
str += (pos.help || "") str += (pos.help || "");
} }
str += "\n"; str += "\n";
}, this); }, this);
@ -440,9 +423,7 @@ ArgParser.prototype = {
} }
return str; return str;
} }
}; opt(arg) {
ArgParser.prototype.opt = function(arg) {
// get the specified opt for this parsed arg // get the specified opt for this parsed arg
var match = Opt({}); var match = Opt({});
this.specs.forEach(function (opt) { this.specs.forEach(function (opt) {
@ -451,9 +432,8 @@ ArgParser.prototype.opt = function(arg) {
} }
}); });
return match; return match;
}; }
setOption(options, arg, value) {
ArgParser.prototype.setOption = function(options, arg, value) {
var option = this.opt(arg); var option = this.opt(arg);
if (option.callback) { if (option.callback) {
var message = option.callback(value); var message = option.callback(value);
@ -466,7 +446,7 @@ ArgParser.prototype.setOption = function(options, arg, value) {
if (option.type != "string") { if (option.type != "string") {
try { try {
// infer type by JSON parsing the string // infer type by JSON parsing the string
value = JSON.parse(value) value = JSON.parse(value);
} }
catch (e) { } catch (e) { }
} }
@ -491,7 +471,11 @@ ArgParser.prototype.setOption = function(options, arg, value) {
else { else {
options[name] = value; options[name] = value;
} }
}; }
}
/* an arg is an item that's actually parsed from the command line /* an arg is an item that's actually parsed from the command line
@ -585,4 +569,4 @@ var createParser = function() {
return new ArgParser(); return new ArgParser();
} }
module.exports = createParser(); export default createParser();

View File

@ -1,45 +1,7 @@
'use strict'; 'use strict';
module.exports.delay = delay => value => new Promise(resolve => setTimeout(resolve, delay, value)); export const delay = delay => value => new Promise(resolve => setTimeout(resolve, delay, value));
module.exports.pause = ms => module.exports.delay(ms)(); export const pause = ms => delay(ms)();
module.exports.wait = ms => () => module.exports.delay(ms); export const wait = ms => () => delay(ms);
// Can be removed when Node 8 becomes a requirement
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
if (!String.prototype.padStart) {
String.prototype.padStart = function padStart(targetLength, padString) {
targetLength = targetLength >> 0; //floor if number or convert non-number to 0;
padString = String(padString || ' ');
if (this.length > targetLength) {
return String(this);
} else {
targetLength = targetLength - this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length);
}
return padString.slice(0, targetLength) + String(this);
}
};
}
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
if (!String.prototype.padEnd) {
String.prototype.padEnd = function padEnd(targetLength, padString) {
targetLength = targetLength >> 0; //floor if number or convert non-number to 0;
padString = String(padString || ' ');
if (this.length > targetLength) {
return String(this);
} else {
targetLength = targetLength - this.length;
if (targetLength > padString.length) {
padString += padString.repeat(targetLength / padString.length);
}
return String(this) + padString.slice(0, targetLength);
}
};
}

43
npm-shrinkwrap.json generated
View File

@ -9,7 +9,8 @@
"version": "3.5.0", "version": "3.5.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chalk": "^4.1.2", "chalk": "~5.1.2",
"chalk-template": "^0.4.0",
"fonteditor-core": "2.1.10", "fonteditor-core": "2.1.10",
"pdf-lib": "1.17.1", "pdf-lib": "1.17.1",
"puppeteer": "18.2.1", "puppeteer": "18.2.1",
@ -162,6 +163,31 @@
} }
}, },
"node_modules/chalk": { "node_modules/chalk": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.1.2.tgz",
"integrity": "sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ==",
"engines": {
"node": "^12.17.0 || ^14.13 || >=16.0.0"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chalk-template": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
"integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
"dependencies": {
"chalk": "^4.1.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/chalk-template?sponsor=1"
}
},
"node_modules/chalk-template/node_modules/chalk": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
@ -774,6 +800,19 @@
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==" "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="
}, },
"chalk": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.1.2.tgz",
"integrity": "sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ=="
},
"chalk-template": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz",
"integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==",
"requires": {
"chalk": "^4.1.2"
},
"dependencies": {
"chalk": { "chalk": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@ -782,6 +821,8 @@
"ansi-styles": "^4.1.0", "ansi-styles": "^4.1.0",
"supports-color": "^7.1.0" "supports-color": "^7.1.0"
} }
}
}
}, },
"chownr": { "chownr": {
"version": "1.1.4", "version": "1.1.4",

View File

@ -6,6 +6,7 @@
"homepage": "https://github.com/astefanutti/decktape", "homepage": "https://github.com/astefanutti/decktape",
"license": "MIT", "license": "MIT",
"main": "decktape.js", "main": "decktape.js",
"type": "module",
"bin": { "bin": {
"decktape": "decktape.js" "decktape": "decktape.js"
}, },
@ -20,7 +21,8 @@
"url": "https://github.com/astefanutti/decktape/issues" "url": "https://github.com/astefanutti/decktape/issues"
}, },
"dependencies": { "dependencies": {
"chalk": "^4.1.2", "chalk": "~5.1.2",
"chalk-template": "^0.4.0",
"fonteditor-core": "2.1.10", "fonteditor-core": "2.1.10",
"pdf-lib": "1.17.1", "pdf-lib": "1.17.1",
"puppeteer": "18.2.1", "puppeteer": "18.2.1",
@ -28,6 +30,6 @@
"urijs": "1.19.11" "urijs": "1.19.11"
}, },
"engines": { "engines": {
"node": ">=12.0.0" "node": ">=12.20"
} }
} }

View File

@ -1,9 +1,9 @@
exports.help = export const help =
`Requires the bespoke-extern module to expose the Bespoke.js API to a global variable named `Requires the bespoke-extern module to expose the Bespoke.js API to a global variable named
'bespoke' and provides access to the collection of deck instances via 'bespoke.decks 'bespoke' and provides access to the collection of deck instances via 'bespoke.decks
and the most recent deck via 'bespoke.deck'.`; and the most recent deck via 'bespoke.deck'.`;
exports.create = page => new Bespoke(page); export const create = page => new Bespoke(page);
class Bespoke { class Bespoke {

View File

@ -1,4 +1,4 @@
exports.create = page => new Deck(page); export const create = page => new Deck(page);
class Deck { class Deck {

View File

@ -1,4 +1,4 @@
exports.create = page => new DZSlides(page); export const create = page => new DZSlides(page);
class DZSlides { class DZSlides {

View File

@ -1,4 +1,4 @@
exports.create = page => new Flowtime(page); export const create = page => new Flowtime(page);
class Flowtime { class Flowtime {

View File

@ -2,9 +2,9 @@
// and detects changes to the DOM. The deck is considered over when no change // and detects changes to the DOM. The deck is considered over when no change
// is detected afterward. // is detected afterward.
const { pause } = require('../libs/util'); import { pause } from '../libs/util.js';
exports.options = { export const options = {
key : { key : {
default : 'ArrowRight', default : 'ArrowRight',
metavar : '<key>', metavar : '<key>',
@ -23,15 +23,15 @@ exports.options = {
}, },
}; };
exports.help = export const help =
`Emulates the end-user interaction by pressing the key with the specified --key option `Emulates the end-user interaction by pressing the key with the specified --key option
and iterates over the presentation as long as: and iterates over the presentation as long as:
- Any change to the DOM is detected by observing mutation events targeting the body element - Any change to the DOM is detected by observing mutation events targeting the body element
and its subtree, and its subtree,
- Nor the number of slides exported has reached the specified --max-slides option. - Nor the number of slides exported has reached the specified --max-slides option.
The --key option must be one of the 'KeyboardEvent' keys and defaults to [${exports.options.key.default}].`; The --key option must be one of the 'KeyboardEvent' keys and defaults to [${options.key.default}].`;
exports.create = (page, options) => new Generic(page, options); export const create = (page, options) => new Generic(page, options);
class Generic { class Generic {
constructor(page, options) { constructor(page, options) {
@ -39,8 +39,8 @@ class Generic {
this.options = options; this.options = options;
this.currentSlide = 1; this.currentSlide = 1;
this.isNextSlideDetected = false; this.isNextSlideDetected = false;
this.key = this.options.key || exports.options.key.default; this.key = this.options.key || options.key.default;
this.media = this.options.media || exports.options.media.default; this.media = this.options.media || options.media.default;
} }
getName() { getName() {

View File

@ -1,4 +1,4 @@
exports.create = page => new Impress(page); export const create = page => new Impress(page);
class Impress { class Impress {

View File

@ -1,4 +1,4 @@
exports.create = page => new Inspire(page); export const create = page => new Inspire(page);
class Inspire { class Inspire {

View File

@ -1,4 +1,4 @@
exports.create = page => new NueDeck(page); export const create = page => new NueDeck(page);
class NueDeck { class NueDeck {

View File

@ -1,4 +1,4 @@
exports.create = page => new Remark(page); export const create = page => new Remark(page);
class Remark { class Remark {

View File

@ -1,6 +1,6 @@
const URI = require('urijs'); import URI from 'urijs';
exports.create = page => new Reveal(page); export const create = page => new Reveal(page);
class Reveal { class Reveal {

View File

@ -1,4 +1,4 @@
exports.create = page => new RISE(page); export const create = page => new RISE(page);
class RISE { class RISE {

View File

@ -1,4 +1,4 @@
exports.create = page => new Shower(page); export const create = page => new Shower(page);
class Shower { class Shower {

View File

@ -1,4 +1,4 @@
exports.create = page => new Shower(page); export const create = page => new Shower(page);
class Shower { class Shower {

View File

@ -1,4 +1,4 @@
exports.create = page => new Slidy(page); export const create = page => new Slidy(page);
class Slidy { class Slidy {

View File

@ -1,4 +1,4 @@
exports.create = page => new WebSlides(page); export const create = page => new WebSlides(page);
class WebSlides { class WebSlides {