diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index f00cb63..a7637e0 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: - node-version: [10.x, 12.x, 14.x] + node-version: [12.x, 14.x, 16.x] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml index 51edeb1..50dfaa4 100644 --- a/.github/workflows/npmpublish.yml +++ b/.github/workflows/npmpublish.yml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 16 - run: npm install - run: npm test @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 with: - node-version: 12 + node-version: 16 registry-url: https://registry.npmjs.org/ - run: npm install - run: npm publish diff --git a/.gitignore b/.gitignore index 25ae498..46c6f64 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ coverage node_modules package-lock.json .nyc_output + +test/types/*.js +test/types/*.map diff --git a/.travis.yml b/.travis.yml index b0ce4a8..d5e8a8b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: node_js node_js: - "node" - "lts/*" - - "10" after_success: - npm install coveralls diff --git a/index.js b/index.js index 7ca01ec..617a4c9 100644 --- a/index.js +++ b/index.js @@ -457,6 +457,10 @@ class SSHConfig extends Array { config.push(node) config = node.config = new SSHConfig() } + else if (node.type === DIRECTIVE && !node.param) { + // blank lines at file end + config[config.length - 1].after += node.before + } else { config.push(node) } diff --git a/package.json b/package.json index fd5c06c..3c13968 100644 --- a/package.json +++ b/package.json @@ -13,15 +13,19 @@ "types" ], "devDependencies": { + "@types/mocha": "^9.1.0", + "@types/node": "^17.0.23", "eslint": "^7.17.0", "heredoc": "^1.3.1", "mocha": "^8.2.1", - "nyc": "^15.1.0" + "nyc": "^15.1.0", + "typescript": "^4.6.3" }, "scripts": { "lint": "eslint .", - "test": "mocha --exit", - "test:coverage": "nyc mocha --exit && nyc report --reporter=lcov" + "pretest": "tsc", + "test": "NODE_OPTIONS=--enable-source-maps mocha --exit --recursive", + "test:coverage": "nyc mocha --exit --recursive && nyc report --reporter=lcov" }, "engine": { "node": ">= 10.0.0" diff --git a/test/types/ssh-config.test.ts b/test/types/ssh-config.test.ts new file mode 100644 index 0000000..1728228 --- /dev/null +++ b/test/types/ssh-config.test.ts @@ -0,0 +1,49 @@ +import SSHConfig from '../..' +import { strict as assert } from 'assert' + +describe('SSHConfig (TypeScript)', function() { + let config: SSHConfig + + beforeEach(function() { + config = SSHConfig.parse(` + IdentityFile ~/.ssh/id_rsa + + Host ness + HostName lochness.com + `) + }) + + it('.find(line => boolean)', function() { + const section = config.find(line => line.type === SSHConfig.DIRECTIVE && line.param === 'Host' && line.value === 'ness') + assert.equal(section.type, SSHConfig.DIRECTIVE) + assert.equal(section.param, 'Host') + assert.equal(section.value, 'ness') + }) + + it('.find({ Host })', function() { + const section = config.find({ Host: 'ness' }) + assert.equal(section.type, SSHConfig.DIRECTIVE) + if ('config' in section) { + assert.deepEqual(SSHConfig.stringify(section.config).trim(), 'HostName lochness.com') + } + }) + + it('.compute(host)', function() { + const result = config.compute('ness'); + console.log(result) + assert.deepEqual(result, { + Host: 'ness', + HostName: 'lochness.com', + IdentityFile: [ '~/.ssh/id_rsa' ], + }) + }) + + it('.stringify(config)', function() { + assert.deepEqual(config.toString(), ` + IdentityFile ~/.ssh/id_rsa + + Host ness + HostName lochness.com + `) + }) +}) diff --git a/test/test.glob.js b/test/unit/glob.test.js similarity index 97% rename from test/test.glob.js rename to test/unit/glob.test.js index d26ccc4..23fcc50 100644 --- a/test/test.glob.js +++ b/test/unit/glob.test.js @@ -2,7 +2,7 @@ const assert = require('assert').strict || require('assert') -const glob = require('../src/glob') +const glob = require('../../src/glob') describe('glob', function() { diff --git a/test/test.parse.js b/test/unit/parse.test.js similarity index 96% rename from test/test.parse.js rename to test/unit/parse.test.js index a0383b1..d9129f2 100644 --- a/test/test.parse.js +++ b/test/unit/parse.test.js @@ -5,17 +5,17 @@ const assert = require('assert').strict || require('assert') const fs = require('fs') const heredoc = require('heredoc').strip const path = require('path') -const SSHConfig = require('..') +const SSHConfig = require('../..') const { parse, COMMENT, DIRECTIVE } = SSHConfig -function readFile(fpath) { - return fs.readFileSync(path.join(__dirname, fpath), 'utf-8') - .replace(/\r\n/g, '\n') +function readFile(fname) { + const fpath = path.join(__dirname, '..', fname) + return fs.readFileSync(fpath, 'utf-8').replace(/\r\n/g, '\n') } describe('parse', function() { - it('.parse simple config', function() { + it('.parse simple config', async function() { const config = parse(readFile('fixture/config')) assert.equal(config[0].param, 'ControlMaster') diff --git a/test/test.ssh-config.js b/test/unit/ssh-config.test.js similarity index 96% rename from test/test.ssh-config.js rename to test/unit/ssh-config.test.js index b3e3082..83ccc61 100644 --- a/test/test.ssh-config.js +++ b/test/unit/ssh-config.test.js @@ -5,17 +5,17 @@ const fs = require('fs') const path = require('path') const heredoc = require('heredoc').strip -const SSHConfig = require('..') +const SSHConfig = require('../..') const { DIRECTIVE } = SSHConfig -function readFile(fpath) { - return fs.readFileSync(path.join(__dirname, fpath), 'utf-8') - .replace(/\r\n/g, '\n') +function readFile(fname) { + const fpath = path.join(__dirname, '..', fname) + return fs.readFileSync(fpath, 'utf-8').replace(/\r\n/g, '\n') } describe('SSHConfig', function() { - it('.compute by Host', function() { + it('.compute by Host', async function() { const config = SSHConfig.parse(readFile('fixture/config')) const opts = config.compute('tahoe2') @@ -91,18 +91,18 @@ describe('SSHConfig', function() { } }) - it('.find with nothing shall yield error', function() { + it('.find with nothing shall yield error', async function() { const config = SSHConfig.parse(readFile('fixture/config')) assert.throws(function() { config.find() }) assert.throws(function() { config.find({}) }) }) - it('.find shall return null if nothing were found', function() { + it('.find shall return null if nothing were found', async function() { const config = SSHConfig.parse(readFile('fixture/config')) assert(config.find({ Host: 'not.exist' }) == null) }) - it('.find by Host', function() { + it('.find by Host', async function() { const config = SSHConfig.parse(readFile('fixture/config')) assert.deepEqual(config.find({ Host: 'tahoe1' }), { @@ -147,7 +147,7 @@ describe('SSHConfig', function() { }) }) - it('.remove by Host', function() { + it('.remove by Host', async function() { const config = SSHConfig.parse(readFile('fixture/config')) const length = config.length diff --git a/test/test.stringify.js b/test/unit/stringify.test.js similarity index 94% rename from test/test.stringify.js rename to test/unit/stringify.test.js index dd557eb..bc5d8e1 100644 --- a/test/test.stringify.js +++ b/test/unit/stringify.test.js @@ -4,16 +4,16 @@ const assert = require('assert').strict || require('assert') const fs = require('fs') const heredoc = require('heredoc').strip const path = require('path') -const SSHConfig = require('..') +const SSHConfig = require('../..') const { parse, stringify } = SSHConfig -function readFile(fpath) { - return fs.readFileSync(path.join(__dirname, fpath), 'utf-8') - .replace(/\r\n/g, '\n') +function readFile(fname) { + const fpath = path.join(__dirname, '..', fname) + return fs.readFileSync(fpath, 'utf-8').replace(/\r\n/g, '\n') } describe('stringify', function() { - it('.stringify the parsed object back to string', function() { + it('.stringify the parsed object back to string', async function() { const fixture = readFile('fixture/config') const config = parse(fixture) assert.equal(fixture, stringify(config)) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..dfd42bb --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2018", + "moduleResolution": "Node", + "module": "CommonJS", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "sourceMap": true + } +} diff --git a/types/index.d.ts b/types/index.d.ts index 019a304..f941180 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -13,7 +13,7 @@ interface Directive { } interface Section extends Directive { - config: SSHConfig; + config: SSHConfig; } interface Comment { @@ -21,21 +21,26 @@ interface Comment { content: string; } -type Line = Directive | Comment; +type Line = Section | Directive | Comment; -export default class SSHConfig extends Array { - static parse(text: string): SSHConfig; - static stringify(config: SSHConfig): string; +declare class SSHConfig extends Array { + static parse(text: string): SSHConfig; + static stringify(config: SSHConfig): string; + + static DIRECTIVE: ELine.DIRECTIVE; + static COMMENT: ELine.COMMENT; toString(): string; compute(host: string): Record; - find(predicate: (value: any, index: number, obj: any[]) => any); + find(this: SSHConfig, predicate: (line: T, index: number, config: T[]) => boolean): T; find(options: Record): Line | Section; remove(options: Record): Line | Section; - append(options: Record): SSHConfig; - prepend(options: Record): SSHConfig; + append(options: Record): SSHConfig; + prepend(options: Record): SSHConfig; } + +export default class extends SSHConfig {} diff --git a/types/ssh-config-tests.ts b/types/ssh-config-tests.ts deleted file mode 100644 index 66128b3..0000000 --- a/types/ssh-config-tests.ts +++ /dev/null @@ -1,10 +0,0 @@ -import SSHConfig from './index'; - -const config = SSHConfig.parse(` -IdentityFile ~/.ssh/id_rsa - -Host ness - HostName lochness.com -`); - -console.log(config.toString());