diff --git a/.travis.yml b/.travis.yml index 5aa800e..4572593 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,6 @@ env: - img=existdb/existdb:latest - img=existdb/existdb:release - img=existdb/existdb:4.7.1 - - img=evolvedbinary/exist-db:eXist-3.6.1-minimal notifications: email: false diff --git a/README.md b/README.md index e1b4591..0cf7ac1 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,30 @@ > A gulp plugin to deploy to and query an eXist-db using eXist's XML-RPC API. +## Prerequisites + +In order to make use of `gulp-exist` you will need to have +[gulp](https://gulpjs.com/docs/en/getting-started/quick-start) installed (in version 4 or later). + +And a running [existdb](https://exist-db.org) instance, of course (version 4.7.1 or higher recommended). + +## Installation + +In your project folder run + +```sh +npm install --save-dev gulp @existdb/gulp-exist +``` + ## Usage -`gulp deploy` will store all files in the ```build``` directory to -**/db/apps/myapp** collection in eXist. +Then create a file with the name `gulpfile.js` in the root of your project with the following contents ```js const gulp = require('gulp'), exist = require('@existdb/gulp-exist') -// authenticate against eXist +// authenticate against local eXist instance for development const connectionOptions = { basic_auth: { user: "admin", @@ -33,7 +47,16 @@ function deploy () { exports.default = deploy ``` -NOTE: Non-existing collections and sub-folders will be created automatically. +Now, `gulp deploy` will store all files in the `build` directory to +**/db/apps/myapp** collection in eXist. + +Also note, that non-existing collections and sub-folders will be created +automatically for you. + +Have a look at the [example gulpfile](https://github.com/eXist-db/gulp-exist/tree/master/spec/examples/gulpfile.js) +for a more complete gulpfile offering more advanced tasks. + +## API ### exist.createClient(options) @@ -162,6 +185,40 @@ function deployWithPermissions () { exports.default = deployWithPermissions ``` +### existClient.install(options) + +#### Options + +##### packageUri + +The unique package descriptor of the application to be installed. + +##### customPackageRepoUrl + +The application repository that will be used to resolve dependencies. +Only needs to be set if the default repository cannot be used. + +#### Example + +```js +const { src } = require('gulp') +const { createClient } = require('@existdb/gulp-exist') + +// override defaults +const exist = createClient({ + basic_auth: { + user: 'admin', + pass: '' + } +}) +// this MUST be the unique package identifier of the XAR you want to install +const packageUri = 'http://exist-db.org/apps/test-app' + +function install () { + return src('spec/files/test-app.xar') + .pipe(exist.install({ packageUri })) +} +``` ### existClient.newer(options) @@ -206,7 +263,13 @@ exports.default = deployNewer Execute input files as XQuery on the server. -The input files will not be stored in eXist but read locally and executed directly. The query results will be logged in the console (can be disabled by setting ```printXqlResults``` to ```false```). For each input file, the result of the query will also be emitted as an output file that can optionally be copied into a local directory for logging. Timestamps will be appended to the filename. The filename extension of the output files can be controlled with ```xqlOutputExt``` (default is ```xml```). +The input files will not be stored in eXist but read locally and executed +directly. The query results will be logged in the console (can be disabled by +setting `printXqlResults` to `false`). For each input file, the result +of the query will also be emitted as an output file that can optionally be +copied into a local directory for logging. Timestamps will be appended to the +filename. The filename extension of the output files can be controlled with +`xqlOutputExt` (default is `xml`). #### Query options @@ -219,8 +282,8 @@ Default: `true` ##### xqlOutputExt -The filename extension that will be used for XQuery result files emitted by ```exist.query()```. -Possible values: 'xml' or 'json'. +The filename extension that will be used for XQuery result files emitted by +`exist.query()`. Possible values are 'xml' or 'json'. Type: `string` Default: `'xml'` @@ -229,7 +292,7 @@ Default: `'xml'` Upload a collection index configuration file and re-index the collection -*```scripts/reindex.xq```* +*scripts/reindex.xq* ```xquery xquery version "3.1"; @@ -238,7 +301,7 @@ declare option exist:serialize "method=json media-type=text/javascript"; map { "success": xmldb:reindex('/db/apps/myapp/data') } ``` -*```gulpfile.js```* +*gulpfile.js* ```js const { src, dest } = require('gulp') @@ -297,7 +360,7 @@ exist.defineMimeTypes({ 'text/foo': ['bar'] }) ## More examples -Have a look at the [example gulpfile](https://github.com/eXist-db/gulp-exist/tree/master/spec/examples/gulpfile.js) +Have a look at the [example gulpfile](https://github.com/eXist-db/gulp-exist/tree/master/spec/examples/gulpfile.js). ### Watch File Changes @@ -307,6 +370,7 @@ last execution. ```js const { watch, src, dest, lastRun } = require('gulp') +const { createClient } = require('@existdb/gulp-exist') // override defaults const connectionOptions = { @@ -316,14 +380,14 @@ const connectionOptions = { } } -const exClient = exist.createClient(connectionOptions) +const exist = createClient(connectionOptions) function deployBuild () { return src('build/**/*', { base: 'build', since: lastRun(deployBuild) }) - .pipe(exClient.dest({target})) + .pipe(exist.dest({target})) } exports.deploy = deployBuild @@ -336,12 +400,21 @@ exports.watch = watchBuild exports.default = series(deployBuild, watchDeploy) ``` -### Make XAR Archive +### Create and Install XAR Archive ```js const { src, dest } = require('gulp'), zip = require('gulp-zip'), - pkg = require('./package.json') + pkg = require('./package.json'), + { createClient } = require('@existdb/gulp-exist') + +// override some default connection options +const exist = createClient({ + basic_auth: { + user: "admin", + pass: "" + } +}) function build { // compile everything into the 'build' directory @@ -353,7 +426,12 @@ function xar () { .pipe(dest(".")); } -exports.default = series(build, xar) +function install () { + return src(`${pkg.abbrev}-${pkg.version}.xar`) + .pipe(exist.install({packageUri: "http://myapp"})) +} + +exports.default = series(build, xar, install) ``` ## Test diff --git a/appveyor.yml b/appveyor.yml index 967a86a..3d1fa5f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,13 +1,11 @@ version: 1.2.{build} environment: - NODE_VERSION: 8 + NODE_VERSION: 12 matrix: - EXIST_VERSION: 5.0.0-RC4 - EXIST_VERSION: 4.7.1 - - EXIST_VERSION: 3.6.1 - - EXIST_VERSION: 2.2 install: - ps: Install-Product node $env:NODE_VERSION diff --git a/index.js b/index.js index 37d9601..ecbf1b5 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,11 @@ +/** + * @typedef { import("xmlrpc").Client } XMLRPCClient + */ + +/** + * @typedef { import("through2").TransformFunction } TransformFunction + */ + // dependencies const os = require('os') const through = require('through2') @@ -8,6 +16,15 @@ const File = require('vinyl') const Path = require('path') const exist = require('@existdb/node-exist') +/** + * @typedef {Object} GulpExistConnectionOptions + * @prop {string} [host] database host, default: "localhost" + * @prop {string} [port] database port, default: "8080" + * @prop {boolean} [secure] use HTTPS? default: false + * @prop {string} [path] path to XMLRPC, default: "/exist/xmlrpc" + * @prop {user:string, pass:string} [basic_auth] database user credentials, default: "guest/guest" + */ + /** * NOTE: gulp-exist will still default to HTTP! * @@ -15,6 +32,7 @@ const exist = require('@existdb/node-exist') * you can now switch to HTTPS. * Set "secure" to true and the "port" to 8443 (the port * configured to serve HTTPS may differ in your installation) + * @type {GulpExistConnectionOptions} */ const defaultRPCoptions = { host: 'localhost', @@ -27,12 +45,36 @@ const defaultRPCoptions = { } } +/** + * @typedef {string} UnixPermission unix style permission string + * + */ + +/** + * @typedef {Object} GulpExistUploadOptions + * @prop {boolean} [html5AsBinary] override mimetype for invalid HTML, default: false + * @prop {string} target collection to write to, default: "" + * @prop {Object} [permissions] mapping of filename to unix style permission string + */ + +/** + * @type {GulpExistUploadOptions} + */ const defaultUploadOptions = { html5AsBinary: false, target: '', permissions: null } +/** + * @typedef {Object} GulpExistQueryOptions + * @prop {boolean} [printXqlResults] default: true + * @prop {"xml"|"json"|string} xqlOutputExt the file extension the results are written to + */ + +/** + * @type {GulpExistQueryOptions} + */ const defaultQueryOptions = { printXqlResults: true, xqlOutputExt: 'xml' @@ -54,185 +96,281 @@ function createCollection (client, collection) { return client.collections.create(normalizedCollectionPath) } -module.exports.createClient = function createClient (options) { - // TODO sanity checks - const _options = assign({}, defaultRPCoptions, options) - const client = exist.connect(_options) - return { - dest: sendFilesWith(client), - query: queryWith(client), - newer: checkForNewerWith(client) - } -} +/** + * upload a file to the connected database + * missing collections will be created + * + * @param {XMLRPCClient} client + * @param {GulpExistUploadOptions} options + * @return {TransformFunction} store files from stream + */ +function dest (client, options) { + const conf = assign({}, defaultUploadOptions, options) -module.exports.defineMimeTypes = function (mimeTypes) { - exist.defineMimeTypes(mimeTypes) -} + function storeFile (vf, enc, callback) { + if (vf.isStream()) { + return this.emit('error', new PluginError('gulp-exist', 'Streaming not supported')) + } -module.exports.getMimeType = function (path) { - return exist.getMimeType(path) -} + if (vf.isDirectory()) { + return createCollection(client, normalizePath(conf.target + '/' + vf.relative)) + .then(_ => callback()) + .catch(e => callback(e)) + } -function sendFilesWith (client) { - return function send (options) { - const conf = assign({}, defaultUploadOptions, options) - - const storeFile = function (vf, enc, callback) { - if (vf.isStream()) { - return this.emit('error', new PluginError('gulp-exist', 'Streaming not supported')) - } - - if (vf.isDirectory()) { - return createCollection(client, normalizePath(conf.target + '/' + vf.relative)) - .then(_ => callback()) - .catch(e => callback(e)) - } - - if (vf.isNull()) { - return callback() - } - - // rewrap to newer version of vinyl file object - const file = new File({ - base: vf.base, - path: vf.path, - contents: vf.contents - }) + if (vf.isNull()) { + return callback() + } - const remotePath = normalizePath(conf.target + '/' + file.relative) + // rewrap to newer version of vinyl file object + const file = new File({ + base: vf.base, + path: vf.path, + contents: vf.contents + }) + + const remotePath = normalizePath(conf.target + '/' + file.relative) + + const folder = file.relative.substring(0, file.relative.length - file.basename.length) + const collection = Path.normalize(conf.target) + '/' + folder + + // create target collection if neccessary + return client.collections.describe(collection) + .then(null, function (e) { + if (e.faultString) { + log(`collection ${collection} not found`) + return createCollection(client, collection) + } + // server may be down, unreachable or misconfigured + return Promise.reject(e) + }) - const folder = file.relative.substring(0, file.relative.length - file.basename.length) - const collection = Path.normalize(conf.target) + '/' + folder + // then upload file + .then(function (result) { + log('Storing "' + file.base + file.relative + '" as (' + exist.getMimeType(file.path) + ')...') + return client.documents.upload(file.contents) + }) - // create target collection if neccessary - return client.collections.describe(collection) - .then(null, function (e) { - if (e.faultString) { - log(`collection ${collection} not found`) - return createCollection(client, collection) - } - // server may be down, unreachable or misconfigured - return Promise.reject(e) - }) + // parse file on server + .then(function (result) { + return client.documents.parseLocal(result, remotePath, { mimetype: exist.getMimeType(file.path) }) + }) - // then upload file - .then(function (result) { - log('Storing "' + file.base + file.relative + '" as (' + exist.getMimeType(file.path) + ')...') + // handle re-upload as octet stream if parsing failed and html5AsBinary is set + .then(null, function (error) { + if (isSaxParserError(error) && conf.html5AsBinary && file.extname === '.html') { + log(file.relative + ' is not well-formed XML, storing as binary...') return client.documents.upload(file.contents) - }) + .then(function (result) { + return client.documents.parseLocal(result, remotePath, { mimetype: 'application/octet-stream' }) + }) + } else { + throw error + } + }) - // parse file on server - .then(function (result) { - return client.documents.parseLocal(result, remotePath, { mimetype: exist.getMimeType(file.path) }) - }) + // Then override permissions if specified in options + .then(function (result) { + if (conf.permissions && file.relative in conf.permissions) { + log('Setting permissions for "' + normalizePath(file.relative) + '" (' + conf.permissions[file.relative] + ')...') + return client.resources.setPermissions(remotePath, conf.permissions[file.relative]) + } + }) - // handle re-upload as octet stream if parsing failed and html5AsBinary is set - .then(null, function (error) { - if (isSaxParserError(error) && conf.html5AsBinary && file.extname === '.html') { - log(file.relative + ' is not well-formed XML, storing as binary...') - return client.documents.upload(file.contents) - .then(function (result) { - return client.documents.parseLocal(result, remotePath, { mimetype: 'application/octet-stream' }) - }) - } else { - throw error - } - }) + // Print result and proceed to next file + .then(function (result) { + log(' ✔ ︎' + remotePath + ' stored') + return callback(null, file) + }) + .catch(function (error) { + let errorMessage + if (isSaxParserError(error)) { + // Failed to invoke method parseLocal in class org.exist.xmlrpc.RpcConnection: org.xml.sax.SAXException: + errorMessage = error.faultString.split('\n')[0].substring(102) + } else { + errorMessage = error.message + } + log.error(' ✖ ' + remotePath + ' was not stored. Reason:', errorMessage) + return callback(error) + }) + } - // Then override permissions if specified in options - .then(function (result) { - if (conf.permissions && file.relative in conf.permissions) { - log('Setting permissions for "' + normalizePath(file.relative) + '" (' + conf.permissions[file.relative] + ')...') - return client.resources.setPermissions(remotePath, conf.permissions[file.relative]) - } - }) + return through.obj(storeFile) +} - // Print result and proceed to next file - .then(function (result) { - log(' ✔ ︎' + remotePath + ' stored') - return callback(null, file) - }) - .catch(function (error) { - let errorMessage - if (isSaxParserError(error)) { - // Failed to invoke method parseLocal in class org.exist.xmlrpc.RpcConnection: org.xml.sax.SAXException: - errorMessage = error.faultString.split('\n')[0].substring(102) - } else { - errorMessage = error.message - } - log.error(' ✖ ' + remotePath + ' was not stored. Reason:', errorMessage) - return callback(error) - }) - } +/** + * upload and execute an xquery script + * save the results to a file + * appends the date of execution + * and the expected file extension + * + * @param {XMLRPCClient} client + * @param {GulpExistQueryOptions} options + * @return {TransformFunction} upload and execute files from stream + */ +function query (client, options) { + const conf = assign({}, defaultQueryOptions, options) - return through.obj(storeFile) - } -} + function executeQuery (file, enc, callback) { + if (file.isStream()) { + return callback(new PluginError('gulp-exist', 'Streaming not supported')) + } -function queryWith (client) { - return function query (options) { - const conf = assign({}, defaultQueryOptions, options) + if (file.isDirectory() || file.isNull()) { + callback() + return + } - function executeQuery (file, enc, callback) { - if (file.isStream()) { - return callback(new PluginError('gulp-exist', 'Streaming not supported')) - } + log('Running XQuery on server: ' + file.relative) - if (file.isDirectory() || file.isNull()) { - callback() - return - } + client.queries.readAll(file.contents, {}) + .then(function (result) { + const resultBuffer = Buffer.concat(result.pages) + if (conf.printXqlResults) { + log(resultBuffer.toString()) + } - log('Running XQuery on server: ' + file.relative) + file.extname = `.${new Date().toJSON()}.${conf.xqlOutputExt}` + file.contents = resultBuffer - client.queries.readAll(file.contents, {}) - .then(function (result) { - const resultBuffer = Buffer.concat(result.pages) - if (conf.printXqlResults) { - log(resultBuffer.toString()) - } + return callback(null, file) + }) + .catch(function (error) { + return callback(new PluginError('gulp-exist', 'Error running XQuery ' + file.relative + ':\n' + error)) + }) + } - file.extname = `.${new Date().toJSON()}.${conf.xqlOutputExt}` - file.contents = resultBuffer + return through.obj(executeQuery) +} - return callback(null, file) - }) - .catch(function (error) { - return callback(new PluginError('gulp-exist', 'Error running XQuery ' + file.relative + ':\n' + error)) +/** + * check if a file exists in the database and if the local file is newer + * + * @param {XMLRPCClient} client + * @param {GulpExistUploadOptions} options + * @return {TransformFunction} filter files from stream that are older + */ +function newer (client, options) { + const conf = assign({}, defaultUploadOptions, options) + + function checkFile (file, enc, callback) { + if (file.isDirectory()) { + const collection = normalizePath(conf.target + '/' + file.relative) + client.collections.describe(collection) + .then(function () { + callback(null) + }, function () { + callback(null, file) }) + + return } - return through.obj(executeQuery) + client.resources.describe(normalizePath(conf.target + '/' + file.relative)) + .then(function (resourceInfo) { + const newer = !Object.prototype.hasOwnProperty.call(resourceInfo, 'modified') || (Date.parse(file.stat.mtime) > Date.parse(resourceInfo.modified)) + callback(null, newer ? file : null) + }) + .catch(function (e) { + callback(e) + }) } + + return through.obj(checkFile) } -function checkForNewerWith (client) { - return function newer (options) { - const conf = assign({}, defaultUploadOptions, options) - - function checkFile (file, enc, callback) { - if (file.isDirectory()) { - const collection = normalizePath(conf.target + '/' + file.relative) - client.collections.describe(collection) - .then(function () { - callback(null) - }, function () { - callback(null, file) - }) - - return - } - - client.resources.describe(normalizePath(conf.target + '/' + file.relative)) - .then(function (resourceInfo) { - const newer = !Object.prototype.hasOwnProperty.call(resourceInfo, 'modified') || (Date.parse(file.stat.mtime) > Date.parse(resourceInfo.modified)) - callback(null, newer ? file : null) - }) - .catch(function (e) { - callback(e) - }) - } +/** + * @typedef {Object} GulpExistInstallationOptions + * @prop {string} packageUri + * @prop {string} [customPackageRepoUrl] + */ + +/** + * Install a XAR package in the database + * + * @param {XMLRPCClient} client database client + * @param {GulpExistInstallationOptions} options installation options + * @return {TransformFunction} install XAR from vinyl file stream + */ +function install (client, options) { + if (!options || !options.packageUri) { return new PluginError('gulp-exist', 'packageUri must be declared') } + const packageUri = options.packageUri + const customPackageRepoUrl = options.customPackageRepoUrl || null + + function installPackage (file, enc, callback) { + const xarName = file.basename + + if (file.isStream()) { return callback(new PluginError('gulp-exist', 'Streaming not supported')) } + if (file.isDirectory()) { return callback(new PluginError('gulp-exist', `Source "${xarName}" is a directory`)) } + if (file.isNull()) { return callback(new PluginError('gulp-exist', `Source "${xarName}" is null`)) } + if (file.extname !== '.xar') { return callback(new PluginError('gulp-exist', `Source "${xarName}" is not a XAR package`)) } + + log(`Uploading ${xarName} (${file.contents.length} bytes)`) + + client.app.upload(file.contents, xarName) + .then(response => { + if (!response.success) { return callback(new PluginError('gulp-exist', 'XAR was not uploaded')) } + log(`Install ${packageUri} from ${xarName}`) + return client.app.install(xarName, packageUri, customPackageRepoUrl) + }) + .then(response => { + if (!response.success) { return callback(new PluginError('gulp-exist', 'XAR Installation failed')) } + if (response.result.update) { + log('Application was updated') + return callback(null, response) + } + log('Application was installed') + callback(null, response) + }) + .catch(error => callback(new PluginError('gulp-exist', `XAR Installation failed: ${error}`))) + } + return through.obj(installPackage) +} + +/** + * @typedef {Object} GulpExist + * @prop {(options:GulpExistUploadOptions) => TransformFunction} dest + * @prop {(options:GulpExistQueryOptions) => TransformFunction} query + * @prop {(options:GulpExistUploadOptions) => TransformFunction} newer + * @prop {(options:GulpExistInstallationOptions) => TransformFunction} install + */ - return through.obj(checkFile) +/** + * create database client and bind methods to it + * + * @param {GulpExistConnectionOptions} options + * @return {GulpExist} bound methods + */ +function createClient (options) { + // TODO sanity checks + const _options = assign({}, defaultRPCoptions, options) + const client = exist.connect(_options) + return { + dest: dest.bind(null, client), + query: query.bind(null, client), + newer: newer.bind(null, client), + install: install.bind(null, client) } } + +/** + * define additional mapping of file extension to mimetype + * will throw if already defined + * + * @param {Object} mimeTypes + */ +function defineMimeTypes (mimeTypes) { + exist.defineMimeTypes(mimeTypes) +} + +/** + * returns the mimetype of a file + * + * @param {string} path + * @returns {string} + */ +function getMimeType (path) { + return exist.getMimeType(path) +} + +module.exports = { createClient, defineMimeTypes, getMimeType } diff --git a/package-lock.json b/package-lock.json index 9676feb..16b8080 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,9 +103,9 @@ } }, "@existdb/node-exist": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@existdb/node-exist/-/node-exist-2.1.0.tgz", - "integrity": "sha512-qLYUOrNQb8WmzvOp/1axO6g9ZbGfsh8U26N0lgjLeVe23l0Oog1UY7kdtgegAWVjM9yjAOEXj6IxEGpRTYYbKA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@existdb/node-exist/-/node-exist-3.0.1.tgz", + "integrity": "sha512-otZiT+SfpZBWJUOrKo9ETr47AcKebJZxDmFFdMHCaC/TfpuqD6HmvllZwsEx7xW++MCt/6MHcqALblimMlID0A==", "requires": { "lodash.assign": "^4.0.2", "mime": "^2.3.1", diff --git a/package.json b/package.json index a906a7c..c4ff02e 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "fancy-log": "^1.3.2", "gulp": "^4.0.2", "lodash.assign": "^4.2.0", - "@existdb/node-exist": "^2.1.0", + "@existdb/node-exist": "^3.0.1", "plugin-error": "^1.0.1", "through2": "^4.0.2", "vinyl": "^2.2.1" diff --git a/spec/examples/gulpfile.js b/spec/examples/gulpfile.js index d919bb4..40046db 100644 --- a/spec/examples/gulpfile.js +++ b/spec/examples/gulpfile.js @@ -1,22 +1,23 @@ /** * an example gulpfile to make ant-less existdb package builds a reality */ -const fs = require('fs') const { src, dest, watch, series, parallel, lastRun } = require('gulp') const { createClient } = require('@existdb/gulp-exist') -const { connect } = require('@existdb/node-exist') + +const del = require('delete') const zip = require('gulp-zip') -const sass = require('gulp-sass') -const uglify = require('gulp-uglify-es').default const replace = require('gulp-replace') const rename = require('gulp-rename') -const del = require('delete') + +const sass = require('gulp-sass') +const uglify = require('gulp-uglify-es').default const pkg = require('./package.json') // read metadata from .existdb.json const existJSON = require('./.existdb.json') const serverInfo = existJSON.servers.localhost +const packageUri = existJSON.package.ns const target = serverInfo.root const connectionOptions = { @@ -28,7 +29,6 @@ const connectionOptions = { port: 8080 } const existClient = createClient(connectionOptions) -const db = connect(connectionOptions) /** * Use the `delete` module directly, instead of using gulp-rimraf @@ -198,17 +198,8 @@ function xar () { * upload and install the latest built XAR */ async function installXar () { - const file = packageName() - const remotePath = `/db/system/repo/${file}` - const buff = fs.readFileSync(file) - console.log('uploading...', file) - const uploadResult = await db.app.upload(buff, remotePath) - if (uploadResult !== true) { - return console.error('uploading failed with: ', uploadResult) - } - console.log('installing...', file) - const installationResult = await db.app.install(remotePath) - console.log(installationResult) + return src(packageName()) + .pipe(existClient.install({ packageUri })) } // composed tasks diff --git a/spec/files/test-app.xar b/spec/files/test-app.xar new file mode 100644 index 0000000..ca540b3 Binary files /dev/null and b/spec/files/test-app.xar differ diff --git a/spec/install.js b/spec/install.js new file mode 100644 index 0000000..ee932a9 --- /dev/null +++ b/spec/install.js @@ -0,0 +1,31 @@ +const test = require('tape') + +const gulp = require('gulp') +const { connect } = require('@existdb/node-exist') +const { createClient } = require('../index') + +const connectionOptions = require('./dbconnection') + +test('install XAR package', function (t) { + const testClient = createClient(connectionOptions) + const packageUri = 'http://exist-db.org/apps/test-app' + const packageTarget = '/db/apps/test-app' + const db = connect(connectionOptions) + function tearDown (e) { + const end = _ => e ? t.fail(e) : t.end() + db.app.remove(packageUri) + .then(end) + .catch(end) + } + + return gulp.src('test-app.xar', { cwd: 'spec/files' }) + .pipe(testClient.install({ packageUri })) + .on('data', function (d) { + t.plan(3) + t.true(d.success, 'succeeded') + t.false(d.result.update, 'first install') + t.equal(d.result.target, packageTarget, 'correct target') + }) + .on('error', e => tearDown(e)) + .on('finish', _ => tearDown()) +})