Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 62 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -46,12 +51,54 @@ data:text/csv;charset=utf-8,%22Crosby%2C%20Stills%2C%20Nash%20%26%20Young%22%2C%

### Install

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.
```sh
npm install text2datauri --save-dev
```

### CLI Usage

Convert a file and print to stdout:

### Edit Gruntfile.js
```sh
npx text2datauri input.html
```

Convert a file and write to an output file:

```sh
npx text2datauri input.html output.uri
```

Options:

```text
--encoding <type> Encoding type: base64 (default) or uri
--mime-type <type> MIME type (default: text/html)
--protocol <proto> URI protocol prefix (default: data:)
--source-charset <cs> Source file charset (default: utf-8)
--target-charset <cs> Target charset in URI (default: utf-8)
--version Print version
--help Show help
```

### Node.js API

```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("<html></html>", "base64");
// => 'PGh0bWw+PC9odG1sPg=='

const dataURI = prefix + data;
// => 'data:text/html;charset=utf-8;base64,PGh0bWw+PC9odG1sPg=='
```

### Grunt Plugin

Add the following to the `grunt.initConfig` section of the project
`Gruntfile.js` file:
Expand Down Expand Up @@ -144,6 +191,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
Expand All @@ -152,7 +203,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
Expand All @@ -176,7 +228,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+

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
97 changes: 97 additions & 0 deletions bin/text2datauri.js
Original file line number Diff line number Diff line change
@@ -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] <input> [output]

Convert a text file to a data URI.

Arguments:
input Source file to encode
output (optional) Output file; defaults to stdout

Options:
--encoding <type> Encoding type: base64 (default) or uri
--mime-type <type> MIME type (default: text/html)
--protocol <proto> URI protocol prefix (default: data:)
--source-charset <cs> Source file charset (default: utf-8)
--target-charset <cs> 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`);
}
26 changes: 20 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
}
86 changes: 86 additions & 0 deletions test/text2datauri_CLI.js
Original file line number Diff line number Diff line change
@@ -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 = "<html></html>";
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 = "<html></html>";
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);
fs.rmSync(tmpOut, { force: true });
}
});
});