From 5de3650b1a779f47433cffd29976d345e135d2df Mon Sep 17 00:00:00 2001 From: Tom King Date: Sun, 15 Mar 2026 16:37:45 -0700 Subject: [PATCH 1/3] refactor- fix Dependabot alerts, make grunt optional, add tests --- README.md | 27 +++++++++-- SECURITY.md | 2 +- bin/text2datauri.js | 97 ++++++++++++++++++++++++++++++++++++++++ package.json | 26 ++++++++--- test/text2datauri_CLI.js | 86 +++++++++++++++++++++++++++++++++++ 5 files changed, 227 insertions(+), 11 deletions(-) create mode 100755 bin/text2datauri.js create mode 100644 test/text2datauri_CLI.js diff --git a/README.md b/README.md index 5fd9f27..d5672f3 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,16 @@ [![Codacy Code Quality Rating][Codacy-image]][Codacy-dash] A grunt plugin to convert a _text_ _file_ to a file with a data URI in base64 -or simple URI encoding. Suitable to convert an HTML file to a `data:text/html;charset=utf-8;base64,...` +or simple URI encoding. Works as a standalone CLI, a Node.js API, or a grunt +plugin. + +Suitable to convert an HTML file to a `data:text/html;charset=utf-8;base64,...` bookmark URI encoded in _base64_. Or use it to convert a CSV file to a `data:text/csv;charset=utf-8,...` URI using basic `encodeURIComponent()` encoding. It may also be useful for creating URLs to communicate between web -apps and iOS apps using URI protocol schemes. For converting a `.js` file to a +apps and iOS apps using URI protocol schemes. + +For converting a `.js` file to a `javascript:` URI, please see the [js2uri] grunt plugin. ## Examples @@ -42,6 +47,14 @@ might become data:text/csv;charset=utf-8,%22Crosby%2C%20Stills%2C%20Nash%20%26%20Young%22%2C%20%22D%C3%A9j%C3%A0%20Vu%22 ``` +## CLI Usage + +```sh +npx text2datauri input.html output.uri +text2datauri --encoding uri --mime-type text/csv input.csv +text2datauri --help +``` + ## Getting Started ### Install @@ -144,6 +157,10 @@ code using `eslint` (preferred) or `jshint`. ## Release History +1.13.0: add standalone CLI (`bin/text2datauri.js`), export helpers as `main`, +mark grunt as optional peerDependency, add npm overrides for minimatch 3.1.5 +to fix ReDoS vulnerabilities + 1.12.1: refined .npmrc & .npmignore, and reduced published package size 1.12.0: migrate to npm trusted publishing with OIDC authentication, require @@ -152,7 +169,8 @@ npm >= 11.5.1 for OIDC support; update documentation to reflect OIDC publishing 1.11.0: radically reduce dependencies, drop grunt-contrib-nodeunit, drop support for node < 22.12, streamline Gruntfile, update ci and build -1.10.4: no functional changes; update engine to node >=20.19.5 add node 25 to workflow tests, drop node 23 tests, bump version, update lockfile, republish +1.10.4: no functional changes; update engine to node >=20.19.5 add node 25 t +workflow tests, drop node 23 tests, bump version, update lockfile, republish 1.10.3: no functional changes; update cspell dictionary, strictly enforce node & npm requirements with .npmrc, bump version, update lockfile, republish @@ -176,7 +194,8 @@ update lockfile 1.6.0: drop support for node 12, as 14 becomes node LTS -1.4.0: drop snyk as it doubled dependencies & increased build time; rely on renovatebot +1.4.0: drop snyk as it doubled dependencies & increased build time; rely on +renovatebot 1.3.0: require node 10+ diff --git a/SECURITY.md b/SECURITY.md index 437f56c..4733744 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ The following versions of text2datauri are currently supported with security upd | Version | Supported | | ------- | ------------------ | -| 1.12.x | :white_check_mark: | +| 1.13.x | :white_check_mark: | ## Reporting a Vulnerability diff --git a/bin/text2datauri.js b/bin/text2datauri.js new file mode 100755 index 0000000..c09d89d --- /dev/null +++ b/bin/text2datauri.js @@ -0,0 +1,97 @@ +#!/usr/bin/env node +"use strict"; +const fs = require("fs"); +const path = require("path"); +const { text2data, text2dataPrefix } = require("../tasks/text2datauriHelpers.js"); + +const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8")); + +const USAGE = `Usage: text2datauri [options] [output] + +Convert a text file to a data URI. + +Arguments: + input Source file to encode + output (optional) Output file; defaults to stdout + +Options: + --encoding Encoding type: base64 (default) or uri + --mime-type MIME type (default: text/html) + --protocol URI protocol prefix (default: data:) + --source-charset Source file charset (default: utf-8) + --target-charset Target charset in URI (default: utf-8) + --version Print version and exit + --help Show this help message and exit +`; + +const args = process.argv.slice(2); + +if (args.includes("--help")) { + process.stdout.write(USAGE); + process.exit(0); +} + +if (args.includes("--version")) { + process.stdout.write(`${pkg.version}\n`); + process.exit(0); +} + +const opts = { + encoding: "base64", + mimeType: "text/html", + protocol: "data:", + sourceCharset: "utf-8", + targetCharset: "utf-8" +}; + +let inputFile = null; +let outputFile = null; + +for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg === "--encoding") { + opts.encoding = args[++i]; + } else if (arg === "--mime-type") { + opts.mimeType = args[++i]; + } else if (arg === "--protocol") { + opts.protocol = args[++i]; + } else if (arg === "--source-charset") { + opts.sourceCharset = args[++i]; + } else if (arg === "--target-charset") { + opts.targetCharset = args[++i]; + } else if (!arg.startsWith("--")) { + if (inputFile === null) { + inputFile = arg; + } else if (outputFile === null) { + outputFile = arg; + } + } +} + +if (!inputFile) { + process.stderr.write("Error: input file required\n\n"); + process.stderr.write(USAGE); + process.exit(1); +} + +let rawStr; +try { + rawStr = fs.readFileSync(inputFile, opts.sourceCharset); +} catch (e) { + process.stderr.write(`Error reading ${inputFile}: ${e.message}\n`); + process.exit(1); +} + +const result = text2dataPrefix(opts) + text2data(rawStr, opts.encoding); + +if (outputFile) { + try { + fs.writeFileSync(outputFile, result); + process.stderr.write(`${inputFile} -> ${outputFile} (${result.length} bytes)\n`); + } catch (e) { + process.stderr.write(`Error writing ${outputFile}: ${e.message}\n`); + process.exit(1); + } +} else { + process.stdout.write(`${result}\n`); +} diff --git a/package.json b/package.json index 88a0d8d..c90593f 100644 --- a/package.json +++ b/package.json @@ -4,32 +4,46 @@ "name": "\"mobilemind\" Tom King", "url": "https://mobilemind.net/" }, + "bin" : { + "text2datauri" : "bin/text2datauri.js" + }, "bugs": { "url": "https://github.com/mobilemind/text2datauri/issues" }, - "description": "A grunt plugin to convert a text file to a file with a data URI in base64 or simple URI encoding.", + "description": "Convert a text file to a data URI in base64 or URI encoding. Standalone CLI, Node.js API, and grunt plugin.", "devDependencies": {}, "engines": { "node": ">=22.21.1", "npm": ">=11.5.1" }, - "files": [ + "files": [ + "bin/", "tasks/", - "tests/", "Gruntfile.js", "LICENSE-MIT", "README.md" ], "homepage": "https://github.com/mobilemind/text2datauri", "keywords": [ - "gruntplugin" + "cli", + "data-uri", + "gruntplugin", + "uri-encode" ], "license": "MIT", - "main": "Gruntfile.js", + "main": "tasks/text2datauriHelpers.js", "name": "text2datauri", + "overrides" : { + "minimatch" : "3.1.5" + }, "peerDependencies": { "grunt": "1.6.1" }, + "peerDependenciesMeta" : { + "grunt" : { + "optional" : true + } + }, "publishConfig": { "access": "public", "provenance": true @@ -44,5 +58,5 @@ "audit": "npm audit --omit=dev --audit-level=high", "audit:fix": "npm audit fix --omit=dev" }, - "version": "1.12.2" + "version": "1.13.0" } diff --git a/test/text2datauri_CLI.js b/test/text2datauri_CLI.js new file mode 100644 index 0000000..cde6b39 --- /dev/null +++ b/test/text2datauri_CLI.js @@ -0,0 +1,86 @@ +"use strict"; +const { describe, test } = require("node:test"); +const assert = require("node:assert/strict"); +const { spawnSync } = require("child_process"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); + +const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8")); +const CLI = path.join(__dirname, "..", "bin", "text2datauri.js"); + +describe("text2datauri CLI", () => { + test("--help exits 0 and shows Usage:", () => { + const result = spawnSync(process.execPath, [CLI, "--help"], { encoding: "utf8" }); + assert.equal(result.status, 0); + assert.ok(result.stdout.includes("Usage:")); + }); + + test("--version exits 0 and prints version", () => { + const result = spawnSync(process.execPath, [CLI, "--version"], { encoding: "utf8" }); + assert.equal(result.status, 0); + assert.equal(result.stdout.trim(), pkg.version); + }); + + test("converts HTML file with defaults to base64 data URI", () => { + const tmpFile = path.join(os.tmpdir(), "t2d_test.html"); + const content = ""; + fs.writeFileSync(tmpFile, content, "utf8"); + try { + const result = spawnSync(process.execPath, [CLI, tmpFile], { encoding: "utf8" }); + assert.equal(result.status, 0); + const expected = "data:text/html;charset=utf-8;base64," + + Buffer.from(content).toString("base64"); + assert.equal(result.stdout.trim(), expected); + } finally { + fs.unlinkSync(tmpFile); + } + }); + + test("converts CSV file with --encoding uri --mime-type text/csv", () => { + const tmpFile = path.join(os.tmpdir(), "t2d_test.csv"); + const content = "a,b"; + fs.writeFileSync(tmpFile, content, "utf8"); + try { + const result = spawnSync( + process.execPath, + [CLI, "--encoding", "uri", "--mime-type", "text/csv", tmpFile], + { encoding: "utf8" } + ); + assert.equal(result.status, 0); + const expected = "data:text/csv;charset=utf-8," + encodeURIComponent(content); + assert.equal(result.stdout.trim(), expected); + } finally { + fs.unlinkSync(tmpFile); + } + }); + + test("missing input file exits 1 with Error on stderr", () => { + const result = spawnSync( + process.execPath, + [CLI, "/nonexistent/t2d_missing.html"], + { encoding: "utf8" } + ); + assert.equal(result.status, 1); + assert.ok(result.stderr.includes("Error")); + }); + + test("writes to output file and logs src -> dest to stderr", () => { + const tmpIn = path.join(os.tmpdir(), "t2d_in.html"); + const tmpOut = path.join(os.tmpdir(), "t2d_out.uri"); + const content = ""; + fs.writeFileSync(tmpIn, content, "utf8"); + try { + const result = spawnSync(process.execPath, [CLI, tmpIn, tmpOut], { encoding: "utf8" }); + assert.equal(result.status, 0); + const expected = "data:text/html;charset=utf-8;base64," + + Buffer.from(content).toString("base64"); + assert.equal(fs.readFileSync(tmpOut, "utf8"), expected); + assert.ok(result.stderr.includes("->")); + assert.ok(result.stderr.includes("bytes")); + } finally { + fs.unlinkSync(tmpIn); + try { fs.unlinkSync(tmpOut); } catch (_e) { /* ignore */ } + } + }); +}); From 32e3ed4c827d489d44ec0a0db317727f1959a8b6 Mon Sep 17 00:00:00 2001 From: Tom King Date: Sun, 15 Mar 2026 16:43:57 -0700 Subject: [PATCH 2/3] fix: ESLint issues- text2datauri_CLI.js --- test/text2datauri_CLI.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/text2datauri_CLI.js b/test/text2datauri_CLI.js index cde6b39..455e7c5 100644 --- a/test/text2datauri_CLI.js +++ b/test/text2datauri_CLI.js @@ -80,7 +80,7 @@ describe("text2datauri CLI", () => { assert.ok(result.stderr.includes("bytes")); } finally { fs.unlinkSync(tmpIn); - try { fs.unlinkSync(tmpOut); } catch (_e) { /* ignore */ } + fs.rmSync(tmpOut, { force: true }); } }); }); From aab3efbb33eb58957df66103be51d181f00f9397 Mon Sep 17 00:00:00 2001 From: Tom King Date: Sun, 15 Mar 2026 17:04:15 -0700 Subject: [PATCH 3/3] fix: add CLI info to Markdown lint --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d5672f3..e30cb96 100644 --- a/README.md +++ b/README.md @@ -47,24 +47,58 @@ might become data:text/csv;charset=utf-8,%22Crosby%2C%20Stills%2C%20Nash%20%26%20Young%22%2C%20%22D%C3%A9j%C3%A0%20Vu%22 ``` -## CLI Usage +## Getting Started + +### Install + +```sh +npm install text2datauri --save-dev +``` + +### CLI Usage + +Convert a file and print to stdout: + +```sh +npx text2datauri input.html +``` + +Convert a file and write to an output file: ```sh npx text2datauri input.html output.uri -text2datauri --encoding uri --mime-type text/csv input.csv -text2datauri --help ``` -## Getting Started +Options: -### Install +```text +--encoding Encoding type: base64 (default) or uri +--mime-type MIME type (default: text/html) +--protocol URI protocol prefix (default: data:) +--source-charset Source file charset (default: utf-8) +--target-charset Target charset in URI (default: utf-8) +--version Print version +--help Show help +``` + +### Node.js API -Install this grunt plugin into a project with: -`npm install text2datauri --save-dev`. The `--save-dev` option adds -`text2datauri` to the _devDependencies_ section of the project `package.json` -file. +```javascript +const { text2data, text2dataPrefix } = require("text2datauri"); + +const opts = { encoding: "base64", mimeType: "text/html", + protocol: "data:", targetCharset: "utf-8" }; +const prefix = text2dataPrefix(opts); +// => 'data:text/html;charset=utf-8;base64,' + +const data = text2data("", "base64"); +// => 'PGh0bWw+PC9odG1sPg==' + +const dataURI = prefix + data; +// => 'data:text/html;charset=utf-8;base64,PGh0bWw+PC9odG1sPg==' +``` -### Edit Gruntfile.js +### Grunt Plugin Add the following to the `grunt.initConfig` section of the project `Gruntfile.js` file: