Skip to content

Commit

Permalink
feat(timodule): support detecting native modules installed via npm
Browse files Browse the repository at this point in the history
  • Loading branch information
sgtcoolguy committed Apr 19, 2019
1 parent 92e926f commit d0a0d7f
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 40 deletions.
9 changes: 3 additions & 6 deletions .eslintrc
@@ -1,19 +1,16 @@
{
"extends": [ "axway/env-node", "axway/+mocha"],
"parserOptions": {
"ecmaVersion": 2015,
"ecmaVersion": 2017,
"sourceType": "script"
},
"rules": {
"strict": ["error", "global"]
},
"overrides": [
{
"files": [ "dangerfile.js" ],
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "module"
},
},
}
}
]
}
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -10,3 +10,4 @@ junit_report.xml
coverage/
retirejs.output.json
.nyc_output/
!test/resources/node_modules/
122 changes: 95 additions & 27 deletions lib/timodule.js
Expand Up @@ -4,35 +4,35 @@
* @module timodule
*
* @copyright
* Copyright (c) 2009-2016 by Appcelerator, Inc. All Rights Reserved.
* Copyright (c) 2009-2019 by Appcelerator, Inc. All Rights Reserved.
*
* @license
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
'use strict';

const
__ = require('./i18n')(__dirname).__,
afs = require('./fs'),
async = require('async'),
fs = require('fs'),
path = require('path'),
util = require('./util'),
version = require('./version'),
zip = require('./zip'),

platformAliases = {
// add additional aliases here for new platforms
ipad: 'iphone',
ios: 'iphone'
};
const __ = require('./i18n')(__dirname).__;
const afs = require('./fs');
const asyncLib = require('async');
const fs = require('fs-extra');
const path = require('path');
const util = require('./util');
const version = require('./version');
const zip = require('./zip');

const platformAliases = {
// add additional aliases here for new platforms
ipad: 'iphone',
ios: 'iphone'
};

let moduleCache = {};

exports.scopedDetect = scopedDetect;
exports.detect = detect;
exports.find = find;
exports.detectNodeModules = detectNodeModules;

/**
* Scans search paths for Titanium modules. This function will not scan any paths
Expand Down Expand Up @@ -72,7 +72,7 @@ function scopedDetect(searchPaths, config, logger, callback, bypassCache) {
});
});

async.parallel(tasks, function () {
asyncLib.parallel(tasks, function () {
callback(results);
});
}
Expand Down Expand Up @@ -131,7 +131,7 @@ function detect(paramsOrSearchPaths, logger, callback, bypassCache) {
});
}

async.parallel({
asyncLib.parallel({
project: function (next) {
// resolve all search paths, but also remove a search path if it's already in the sdk paths
const searchPaths = {},
Expand All @@ -148,7 +148,7 @@ function detect(paramsOrSearchPaths, logger, callback, bypassCache) {
searchPaths[p] = 1;
});

async.each(Object.keys(searchPaths), function (modulePath, cb) {
asyncLib.each(Object.keys(searchPaths), function (modulePath, cb) {
detectModules({
bypassCache: params.bypassCache,
modulesDir: path.join(modulePath, 'modules'),
Expand All @@ -164,7 +164,7 @@ function detect(paramsOrSearchPaths, logger, callback, bypassCache) {
},
global: function (next) {
const results = {};
async.each(sdkPaths, function (modulePath, cb) {
asyncLib.each(sdkPaths, function (modulePath, cb) {
detectModules({
bypassCache: params.bypassCache,
modulesDir: path.join(modulePath, 'modules'),
Expand Down Expand Up @@ -241,13 +241,9 @@ function find(modulesOrParams, platforms, deployType, tiManifest, searchPaths, l

// clean up platforms
if (typeof params.platforms === 'string') {
params.platforms = params.platforms.split(',').filter(function (p) {
return p;
});
params.platforms = params.platforms.split(',').filter(p => p);
} else if (Array.isArray(params.platforms)) {
params.platforms = params.platforms.filter(function (p) {
return p;
});
params.platforms = params.platforms.filter(p => p);
} else {
params.platforms = [];
}
Expand Down Expand Up @@ -485,7 +481,7 @@ function detectModules(params) {
}

// auto-install zipped modules
async.each(fs.readdirSync(moduleRoot), function (name, next) {
asyncLib.each(fs.readdirSync(moduleRoot), function (name, next) {
const file = path.join(moduleRoot, name);
if (!zipRegExp.test(name) || !fs.existsSync(file) || !fs.statSync(file).isFile()) {
return next();
Expand Down Expand Up @@ -559,3 +555,75 @@ function detectModules(params) {
callback(null, moduleCache[modulesDir] = results);
});
}

function flattenDeep(arr1) {
return arr1.reduce((acc, val) => {
return Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val);
}, []);
}

/**
*
* @param {string[]} searchPaths the list of directories to consider
* @return {object[]}
*/
async function detectNodeModules(searchPaths) {
const promises = searchPaths.map(dir => detectNativeModulesViaNodeModulesDir(dir));
const results = await Promise.all(promises);
const flattened = flattenDeep(results);
return flattened.filter(item => item !== null);
}

/**
*
* @param {string} nodeModuleDir path to a single node_modules directory to search
* @returns {object[]} the representations of the modules found
*/
async function detectNativeModulesViaNodeModulesDir(nodeModuleDir) {
// List top-level directories under node_modules (or scoped packages dir)
const subDirs = await fs.readdir(nodeModuleDir);
// for each dir, try and collect module data (or null)
const promises = subDirs.map(dir => {
if (dir.startsWith('@')) { // scoped package, recurse!
return detectNativeModulesViaNodeModulesDir(path.join(nodeModuleDir, dir));
} else {
return detectNativeModuleViaNPMPackage(path.join(nodeModuleDir, dir));
}
});
return Promise.all(promises);
}

/**
* @param {string} singlePackageDir the npm package directory to look at
* @returns {object|null} null if no native module found; otherwise an object with metadata about the module.
*/
async function detectNativeModuleViaNPMPackage(singlePackageDir) {
// is this given package a native module?
try {
const json = await fs.readJSON(path.join(singlePackageDir, 'package.json'));
if (json && json.titanium && json.titanium.moduleid && json.titanium.type === 'native-module') {
// Hey! it's a native module for us!
let platform = json.titanium.platform;
if (platformAliases[platform]) {
platform = platformAliases[platform];
}
return {
id: json.titanium.moduleid,
modulePath: singlePackageDir,
platform: [ platform ],
version: json.version,
manifest: {
name: json.titanium.name,
minsdk: json.titanium.minsdk,
apiversion: json.titanium.apiversion,
guid: json.titanium.guid,
moduleid: json.titanium.moduleid,
architectures: json.titanium.architectures
}
};
}
} catch (e) {
// ignore if we failed to find/read a package.json file!
}
return null;
}
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -64,7 +64,7 @@
"license": "Apache-2.0",
"main": "./index",
"engines": {
"node": ">=4.0"
"node": ">=8.0"
},
"scripts": {
"lint": "eslint .",
Expand Down
8 changes: 8 additions & 0 deletions test/resources/node_modules/fake-module/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions test/resources/node_modules/native-module/package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Empty file.
34 changes: 28 additions & 6 deletions test/test-timodule.js
@@ -1,17 +1,18 @@
/**
* node-appc - Appcelerator Common Library for Node.js
* Copyright (c) 2009-2014 by Appcelerator, Inc. All Rights Reserved.
* Copyright (c) 2009-2019 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
/* eslint no-unused-expressions: "off" */
'use strict';

const appc = require('../index'),
assert = require('assert'),
fs = require('fs-extra'),
path = require('path'),
colors = require('colors');
const appc = require('../index');
const assert = require('assert');
const fs = require('fs-extra');
const path = require('path');
require('should');
require('colors');

function MockConfig() {
this.get = function (s) {
Expand Down Expand Up @@ -801,4 +802,25 @@ describe('timodule', function () {
});
});
});

describe('#detectNodeModules()', () => {
it('detects native module in node_modules folder with specific package.json properties', async () => {
const modules = await appc.timodule.detectNodeModules([ path.join(__dirname, 'resources/node_modules') ]);
modules.should.be.an.Array;
modules.should.have.length(1);
const nativeModule = modules[0];
nativeModule.id.should.eql('native-module');
nativeModule.modulePath.should.eql(path.join(__dirname, 'resources/node_modules/native-module'));
nativeModule.platform.should.eql([ 'iphone' ]);
nativeModule.version.should.eql('2.0.1');
nativeModule.manifest.should.be.an.Object;
nativeModule.manifest.should.have.a.property('minsdk').which.is.eql('5.0.0');
nativeModule.manifest.should.have.a.property('apiversion').which.is.eql(2);
nativeModule.manifest.should.have.a.property('guid').which.is.eql('bba89061-0fdb-4ff1-95a8-02876f5601f9');
nativeModule.manifest.should.have.a.property('moduleid').which.is.eql('native-module');
nativeModule.manifest.should.have.a.property('architectures').which.is.eql([ 'armv7', 'arm64', 'i386', 'x86_64' ]);
});

// TODO Detect multiple with differing platforms?
});
});

0 comments on commit d0a0d7f

Please sign in to comment.