From 4f02f9c88cc063d22c69ce8e103381d44c23f205 Mon Sep 17 00:00:00 2001 From: Matt Simerson Date: Fri, 5 Apr 2024 11:51:51 -0700 Subject: [PATCH] Release v1.1.0 (#26) - import from bin/haraka: copyFile, copyDir, mkDir, createFile - tests: added for new fns - add SUNSET 2025 warnings to unused qp functions --- .github/workflows/ci.yml | 28 ++--------- .gitmodules | 3 ++ .release | 1 + Changes.md | 10 ++++ index.js | 105 ++++++++++++++++++++++++++++++++++++++- package.json | 13 +++-- test/utils.js | 51 ++++++++++++++++++- 7 files changed, 178 insertions(+), 33 deletions(-) create mode 100644 .gitmodules create mode 160000 .release diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 74e7a36..7dd1ef0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,7 @@ name: CI on: push: + pull_request: env: CI: true @@ -15,27 +16,8 @@ jobs: secrets: inherit test: - needs: lint - runs-on: ${{ matrix.os }} - # services: - # redis: - # image: redis - # ports: - # - 6379:6379 - strategy: - matrix: - os: [ ubuntu-latest, windows-latest ] - node-version: [ 14, 16, 18 ] - fail-fast: false + needs: [ lint ] + uses: haraka/.github/.github/workflows/ubuntu.yml@master - steps: - - uses: actions/checkout@v3 - - - uses: actions/setup-node@v3 - name: Node ${{ matrix.node-version }} on ${{ matrix.os }} - with: - node-version: ${{ matrix.node-version }} - - - run: npm install - - - run: npm test + windows: + uses: haraka/.github/.github/workflows/windows.yml@master \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a8e94cb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule ".release"] + path = .release + url = git@github.com:msimerson/.release.git diff --git a/.release b/.release new file mode 160000 index 0000000..954197d --- /dev/null +++ b/.release @@ -0,0 +1 @@ +Subproject commit 954197dae07b32c4476ff87ec9ae7371311ec97d diff --git a/Changes.md b/Changes.md index a1ed9f9..fc3b522 100644 --- a/Changes.md +++ b/Changes.md @@ -1,4 +1,11 @@ +### Unreleased + +### 1.1.0 - 2024-04-05 + +- import from bin/haraka: copyFile, copyDir, mkDir, createFile +- add SUNSET 2025 warnings to unused qp functions + ### 1.0.3 - 2022-05-27 @@ -28,3 +35,6 @@ - depend on haraka-eslint - lint fixes for compat with eslint 4 + + +[1.1.0]: https://github.com/haraka/haraka-utils/releases/tag/1.1.0 diff --git a/index.js b/index.js index f38860b..fa19703 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,8 @@ 'use strict'; +const fs = require('fs') +const path = require('path') + // copied from http://www.broofa.com/Tools/Math.uuid.js const CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' .split(''); @@ -103,6 +106,7 @@ ${d.toString().match(/\sGMT([+-]\d+)/)[1]}`; } exports.decode_qp = function (line) { + console.warn(`SUNSET: 2025`) line = line.replace(/\r\n/g,"\n").replace(/[ \t]+\r?\n/g,"\n"); if (! /=/.test(line)) return Buffer.from(line); // maybe a pointless optimisation @@ -125,10 +129,12 @@ exports.decode_qp = function (line) { } function _char_to_qp (ch) { + _is_printable return _buf_to_qp(Buffer.from(ch)); } function _is_printable (charCode) { + console.warn(`SUNSET: 2025`) switch (charCode) { case 61: // = (special in encoded words) return false; @@ -142,6 +148,7 @@ function _is_printable (charCode) { } function _buf_to_qp (b) { + console.warn(`SUNSET: 2025`) let r = ''; for (let i=0; i?@A-Z[\\\]^_`a-z{|}~])/g; function asQuotedPrintable (str) { + console.warn(`SUNSET: 2025`) if (Buffer.isBuffer(str)) return _buf_to_qp(str); return str @@ -167,6 +175,7 @@ function asQuotedPrintable (str) { // NOTE: deprecated. Haraka now uses 'libqp' instead. // See https://github.com/haraka/haraka-utils/issues/22 exports.encode_qp = (str) => { + console.warn(`SUNSET: 2025`) // https://tools.ietf.org/html/rfc2045#section-6.7 str = asQuotedPrintable(str); @@ -219,8 +228,8 @@ exports.node_min = function (min, cur) { for (let i=0; i<=3; i++) { // note use of unary + for fast type conversion to num - if (+has[i] > +wants[i]) { return true; } - if (+has[i] < +wants[i]) { return false; } + if (+has[i] > +wants[i]) return true; + if (+has[i] < +wants[i]) return false; } // they're identical @@ -319,3 +328,95 @@ exports.wildcard_to_regexp = function (str) { } exports.line_regexp = /^([^\n]*\n)/; + +exports.copyDir = function (srcPath, dstPath) { + + exports.mkDir(dstPath); + + for (const file of fs.readdirSync(srcPath)) { + + // Ignore ".*" + if (/^\./.test(file)) continue; + + const srcFile = path.join(srcPath, file); + const dstFile = path.join(dstPath, file); + + const srcStat = fs.statSync(srcFile); + + if (srcStat.isDirectory()) { // if directory + exports.copyDir(srcFile, dstFile); // recurse + } + else if (srcStat.isFile()) { // if file + exports.copyFile(srcFile, dstFile); // copy to dstPath (no overwrite) + } + } +} + +exports.copyFile = function (srcFile, dstFile) { + + try { + if (fs.statSync(dstFile).isFile()) { + warningMsg(`EEXIST, File exists '${dstFile}'`); + return; + } + throw `EEXIST but not a file: '${dstFile}'`; + } + catch (e) { + // File NOT exists + if (e.code == 'ENOENT') { + exports.mkDir(path.dirname(dstFile)); + fs.writeFileSync(dstFile, fs.readFileSync(srcFile)); + createMsg(dstFile) + } + else { + console.log(`copy ${srcFile} to ${dstFile}`); + throw e; + } + } +} + +exports.createFile = function (filePath, data, info = {}, force=false) { + try { + if (fs.existsSync(filePath) && !force) { + throw `${filePath} already exists`; + } + exports.mkDir(path.dirname(filePath)); + const fd = fs.openSync(filePath, 'w'); + const output = data.replace(/%(\w+)%/g, function (i, m1) { return info[m1] }); + fs.writeSync(fd, output, null); + } + catch (e) { + warningMsg(`Unable to create file: ${e}`); + } +} + +function createMsg (dirPath) { + console.log(`\x1b[32mcreate\x1b[0m: ${dirPath}`); +} + +function warningMsg (msg) { + console.error(`\x1b[31mwarning\x1b[0m: ${msg}`); +} + +exports.mkDir = function (dstPath) { + try { + if (fs.statSync(dstPath).isDirectory()) return; + } + catch (ignore) {} + + try { + fs.mkdirSync(dstPath, { recursive: true }); + createMsg(dstPath); + } + catch (e) { + // File exists + console.error(e); + if (e.errno == 17) { + warningMsg(e.message); + } + else { + throw e; + } + } +} + diff --git a/package.json b/package.json index 5bfcc04..ca07ab6 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,16 @@ { "name": "haraka-utils", - "version": "1.0.3", + "version": "1.1.0", "description": "Haraka general purpose functions", "main": "index.js", + "files": [], "directories": { "test": "test" }, "scripts": { - "lint": "npx eslint *.js test", - "lintfix": "npx eslint --fix *.js test", - "test": "npx mocha", + "lint": "npx eslint@^8 *.js test", + "lintfix": "npx eslint@^8 --fix *.js test", + "test": "npx mocha@^10", "versions": "npx dependency-version-checker check" }, "repository": { @@ -28,8 +29,6 @@ }, "homepage": "https://github.com/haraka/haraka-utils#readme", "devDependencies": { - "eslint": "^8", - "eslint-plugin-haraka": "*", - "mocha": "9" + "eslint-plugin-haraka": "*" } } diff --git a/test/utils.js b/test/utils.js index 4026490..99d31a5 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,8 +1,23 @@ const assert = require('assert') +const fs = require('fs') +const path = require('path') const utils = require('../index') +function cleanup () { + try { + fs.rmSync(path.join('test', 'temp1'), { recursive: true, force: true }) + fs.rmSync(path.join('test', 'temp2'), { recursive: true, force: true }) + } + catch (e) { + console.log(e.message) + } +} + +before(() => { cleanup(); }) +after(() => { cleanup(); }) + describe('uuid', function () { it('generates a UUID of 36 characters', function (done) { @@ -32,7 +47,7 @@ describe('uniq', function () { }) }) -describe('encode_qp', function () { +describe.skip('encode_qp', function () { it('plain ascii should not be encoded', function () { assert.equal(utils.encode_qp('quoted printable'), 'quoted printable'); }) @@ -389,4 +404,38 @@ describe('indexOfLF', function () { it('find a LF at the right spot', function () { assert.equal(utils.indexOfLF(Buffer.from(`in the\neighth`)), 6) }) +}) + +describe('mkDir', function () { + it('creates a directory', () => { + const tmpPath = path.join('test', 'temp1') + utils.mkDir(tmpPath) + assert.ok(fs.existsSync(tmpPath)) + }) +}) + +describe('createFile', function () { + it('creates a file', () => { + const tmpFile = path.join('test', 'temp1', 'file') + utils.createFile(tmpFile, 'contents') + assert.ok(fs.existsSync(tmpFile)) + }) +}) + +describe('copyFile', function () { + it('copies a file', () => { + const srcFile = path.join('test', 'temp1', 'file') + const dstFile = path.join('test', 'temp1', 'file2') + utils.copyFile(srcFile, dstFile) + assert.ok(fs.existsSync(dstFile)) + }) +}) + +describe('copyDir', function () { + it('copies a directory', () => { + const srcDir = path.join('test', 'temp1') + const dstDir = path.join('test', 'temp2') + utils.copyDir(srcDir, dstDir) + assert.ok(fs.existsSync(dstDir)) + }) }) \ No newline at end of file