Skip to content

Commit

Permalink
feat: ship type declarations
Browse files Browse the repository at this point in the history
This PR adds type declaration generation. It does not compile via `tsc` (that can be done later).

Also needed to fix some docstrings, so I did that.

`index.js` moved to `lib/index.js` and the `main` file changed accordingly.
  • Loading branch information
boneskull committed Apr 3, 2023
1 parent cca1b07 commit 94548ae
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 53 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ build
coverage
.nyc_output/
package-lock.json*
tsconfig.tsbuildinfo
4 changes: 2 additions & 2 deletions lib/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const XCRUN_TIMEOUT = 15000;
*
* @param {string[]} args xcrun arguments
* @param {number} timeout [15000] The maximum number of milliseconds to wait until xcrun exists
* @returns {Promise<import("teen_process").ExecResult>} The result of xcrun execution
* @returns {Promise<import("teen_process").TeenProcessExecResult>} The result of xcrun execution
* @throws {Error} If xcrun returned non-zero exit code or timed out
*/
export async function runXcrunCommand (args, timeout = XCRUN_TIMEOUT) {
Expand Down Expand Up @@ -55,7 +55,7 @@ export async function findAppPaths (bundleId) {
return p;
}
})());
return (await B.all(results)).filter(Boolean);
return /** @type {string[]} */(await B.all(results)).filter(Boolean);
}

/**
Expand Down
6 changes: 5 additions & 1 deletion index.js → lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
getMaxIOSSDK,
getMaxTVOSSDK,
getClangVersion,
} from './lib/xcode';
} from './xcode';

const xcode = {
getPath,
Expand All @@ -23,3 +23,7 @@ export {
getClangVersion
};
export default xcode;

/**
* @typedef {import('./xcode').XcodeVersion} XcodeVersion
*/
80 changes: 49 additions & 31 deletions lib/xcode.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const log = logger.getLogger('Xcode');
* @throws {Error} If it is not possible to retrieve a proper path
*/
async function getPathFromXcodeSelect (timeout = XCRUN_TIMEOUT) {
/**
* @param {string} prefix
* @returns {Promise<string>}
*/
const generateErrorMessage = async (prefix) => {
const xcodePaths = await findAppPaths(XCODE_BUNDLE_ID);
if (_.isEmpty(xcodePaths)) {
Expand All @@ -36,51 +40,62 @@ async function getPathFromXcodeSelect (timeout = XCRUN_TIMEOUT) {
try {
({stdout} = await exec('xcode-select', ['--print-path'], {timeout}));
} catch (e) {
log.errorAndThrow(`Cannot determine the path to Xcode by running 'xcode-select -p' command. ` +
`Original error: ${e.stderr || e.message}`);
const msg = `Cannot determine the path to Xcode by running 'xcode-select -p' command. ` +
`Original error: ${e.stderr || e.message}`;
log.error(msg);
throw new Error(msg);
}
// trim and remove trailing slash
const developerRoot = stdout.replace(/\/$/, '').trim();
const developerRoot = String(stdout).replace(/\/$/, '').trim();
if (!developerRoot) {
log.errorAndThrow(await generateErrorMessage(`'xcode-select -p' returned an empty string`));
const msg = await generateErrorMessage(`'xcode-select -p' returned an empty string`);
log.error(msg);
throw new Error(msg);
}
// xcode-select might also return a path to command line tools
const {CFBundleIdentifier} = await readXcodePlist(developerRoot);
if (CFBundleIdentifier === XCODE_BUNDLE_ID) {
return developerRoot;
}

log.errorAndThrow(await generateErrorMessage(`'${developerRoot}' is not a valid Xcode path`));
const msg = await generateErrorMessage(`'${developerRoot}' is not a valid Xcode path`);
log.error(msg);
throw msg;
}

/**
* Retrieves the full path to Xcode Developer subfolder via DEVELOPER_DIR environment variable
* Retrieves the full path to Xcode Developer subfolder via `DEVELOPER_DIR` environment variable
*
* @returns {Promise<string>} Full path to Xcode Developer subfolder
* @throws {Error} If it is not possible to retrieve a proper path
* @privateRemarks This method assumes `DEVELOPER_DIR` is defined.
*/
async function getPathFromDeveloperDir () {
const developerRoot = process.env.DEVELOPER_DIR;
const developerRoot = /** @type {string} */(process.env.DEVELOPER_DIR);
const {CFBundleIdentifier} = await readXcodePlist(developerRoot);
if (CFBundleIdentifier === XCODE_BUNDLE_ID) {
return developerRoot;
}

log.errorAndThrow(`The path to Xcode Developer dir '${developerRoot}' provided in DEVELOPER_DIR ` +
`environment variable is not a valid path`);
const msg = `The path to Xcode Developer dir '${developerRoot}' provided in DEVELOPER_DIR ` +
`environment variable is not a valid path`;
log.error(msg);
throw new Error(msg);
}

/**
* Retrieves the full path to Xcode Developer subfolder.
* If DEVELOPER_DIR environment variable is provided then its value has a priority.
*
* @property {number} timeout [15000] The maximum timeout for xcode-select execution
* @returns {string} Full path to Xcode Developer subfolder
* If `DEVELOPER_DIR` environment variable is provided then its value has a priority.
* @param {number} timeout The maximum timeout for xcode-select execution
* @returns {Promise<string>} Full path to Xcode Developer subfolder timeout
* @throws {Error} If there was an error while retrieving the path.
*/
const getPath = _.memoize(function getPath (timeout = XCRUN_TIMEOUT) {
return process.env.DEVELOPER_DIR ? getPathFromDeveloperDir() : getPathFromXcodeSelect(timeout);
});
const getPath = _.memoize(
/**
* @param {number} timeout
* @returns {Promise<string>}
*/
(timeout = XCRUN_TIMEOUT) => process.env.DEVELOPER_DIR ? getPathFromDeveloperDir() : getPathFromXcodeSelect(timeout));

/**
* Retrieves Xcode version
Expand All @@ -99,8 +114,8 @@ async function getVersionWithoutRetry (timeout = XCRUN_TIMEOUT) {
/**
* Retrieves Xcode version or the cached one if called more than once
*
* @param {number} retries [2] How many retries to apply for version retrieval
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands
* @param {number} retries How many retries to apply for version retrieval
* @param {number} timeout Timeout of milliseconds to wait for terminal commands
* @returns {Promise<import("semver").SemVer | null>} Xcode version
* @throws {Error} If there was a failure while retrieving the version
*/
Expand All @@ -116,7 +131,8 @@ const getVersionMemoized = _.memoize(
* @property {number} versionFloat Xcode version as a float number
* @property {number} major Major number of Xcode version
* @property {number} minor Minor number of Xcode version
* @property {number?} patch Patch number of Xcode version (if exists)
* @property {number} [patch] Patch number of Xcode version (if exists)
* @property {() => string} toString Returns Xcode version as a string
*/

/**
Expand All @@ -129,7 +145,7 @@ const getVersionMemoized = _.memoize(
* @throws {Error} If there was a failure while retrieving the version
*/
async function getVersion (parse = false, retries = DEFAULT_NUMBER_OF_RETRIES, timeout = XCRUN_TIMEOUT) {
const version = await getVersionMemoized(retries, timeout);
const version = /** @type {import('semver').SemVer} */(await getVersionMemoized(retries, timeout));
// xcode version strings are not exactly semver string: patch versions of 0
// are removed (e.g., '10.0.0' => '10.0')
const versionString = version.patch > 0 ? version.version : `${version.major}.${version.minor}`;
Expand All @@ -153,7 +169,7 @@ async function getVersion (parse = false, retries = DEFAULT_NUMBER_OF_RETRIES, t
* Check https://trac.macports.org/wiki/XcodeVersionInfo
* to see the actual mapping between clang and other components.
*
* @returns {Promise<string?>} The actual Clang version in x.x.x.x or x.x.x format,
* @returns {Promise<string|null>} The actual Clang version in x.x.x.x or x.x.x format,
* which is supplied with Command Line Tools. `null` is returned
* if CLT are not installed.
*/
Expand All @@ -178,7 +194,7 @@ async function getClangVersion () {
* Retrieves the maximum version of iOS SDK supported by the installed Xcode
*
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands
* @returns {string} The SDK version
* @returns {Promise<string>} The SDK version
* @throws {Error} If the SDK version number cannot be determined
*/
async function getMaxIOSSDKWithoutRetry (timeout = XCRUN_TIMEOUT) {
Expand All @@ -195,8 +211,8 @@ async function getMaxIOSSDKWithoutRetry (timeout = XCRUN_TIMEOUT) {
/**
* Retrieves the maximum version of iOS SDK supported by the installed Xcode
*
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands
* @param {number} retries [2] The maximum number of retries
* @param {number} timeout Timeout of milliseconds to wait for terminal commands
* @param {number} retries The maximum number of retries
* @returns {string} The SDK version
* @throws {Error} If the SDK version number cannot be determined
*/
Expand All @@ -209,8 +225,8 @@ const getMaxIOSSDK = _.memoize(
/**
* Retrieves the maximum version of tvOS SDK supported by the installed Xcode
*
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands
* @returns {string} The SDK version
* @param {number} timeout Timeout of milliseconds to wait for terminal commands
* @returns {Promise<string>} The SDK version
* @throws {Error} If the SDK version number cannot be determined
*/
async function getMaxTVOSSDKWithoutRetry (timeout = XCRUN_TIMEOUT) {
Expand All @@ -226,14 +242,16 @@ async function getMaxTVOSSDKWithoutRetry (timeout = XCRUN_TIMEOUT) {
/**
* Retrieves the maximum version of tvOS SDK supported by the installed Xcode
*
* @param {number} timeout [15000] Timeout of milliseconds to wait for terminal commands
* @param {number} retries [2] The maximum number of retries
* @returns {string} The SDK version
* @throws {Error} If the SDK version number cannot be determined
*/
const getMaxTVOSSDK = _.memoize(
function getMaxTVOSSDK (retries = DEFAULT_NUMBER_OF_RETRIES, timeout = XCRUN_TIMEOUT) {
return retry(retries, getMaxTVOSSDKWithoutRetry, timeout);
/**
* @param {number} timeout Timeout of milliseconds to wait for terminal commands
* @param {number} retries The maximum number of retries
* @returns {Promise<string>} The SDK version
*/
async function getMaxTVOSSDK (retries = DEFAULT_NUMBER_OF_RETRIES, timeout = XCRUN_TIMEOUT) {
return /** @type {string} */(await retry(retries, getMaxTVOSSDKWithoutRetry, timeout));
}
);

Expand Down
19 changes: 13 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@
"node": ">=14",
"npm": ">=8"
},
"main": "./build/index.js",
"bin": {},
"main": "./build/lib/index.js",
"directories": {
"lib": "lib"
},
"files": [
"index.js",
"lib",
"build/index.js",
"build/lib",
"CHANGELOG.md"
],
"dependencies": {
"@appium/support": "^3.0.0",
"@babel/runtime": "^7.0.0",
"@types/lodash": "^4.14.192",
"@types/teen_process": "^2.0.0",
"asyncbox": "^2.3.0",
"lodash": "^4.17.4",
"plist": "^3.0.1",
Expand All @@ -43,7 +43,7 @@
"teen_process": "^2.0.0"
},
"scripts": {
"build": "rm -rf build && babel --out-dir=build/lib lib && babel --out-dir=build index.js",
"build": "rm -rf build && babel --out-dir=build/lib lib && tsc -b",
"dev": "npm run build -- --watch",
"lint": "eslint .",
"lint:fix": "npm run lint -- --fix",
Expand All @@ -69,6 +69,7 @@
},
"devDependencies": {
"@appium/eslint-config-appium": "^6.0.0",
"@appium/tsconfig": "^0.2.4",
"@babel/cli": "^7.18.10",
"@babel/core": "^7.18.10",
"@babel/eslint-parser": "^7.18.9",
Expand All @@ -77,6 +78,10 @@
"@babel/register": "^7.18.9",
"@semantic-release/changelog": "^6.0.1",
"@semantic-release/git": "^10.0.1",
"@types/chai": "^4.3.4",
"@types/chai-as-promised": "^7.1.5",
"@types/mocha": "^10.0.1",
"@types/semver": "^7.3.13",
"babel-plugin-source-map-support": "^2.2.0",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
Expand All @@ -90,6 +95,8 @@
"mocha": "^10.0.0",
"pre-commit": "^1.1.3",
"prettier": "^2.7.1",
"semantic-release": "^20.0.2"
}
"semantic-release": "^20.0.2",
"typescript": "^5.0.2"
},
"types": "./build/lib/index.d.ts"
}
24 changes: 12 additions & 12 deletions test/e2e/xcode-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('xcode @skip-linux', function () {
let versionRE = /\d\.\d\.*\d*/;

it('should get the version of xcode', async function () {
let version = await xcode.getVersion();
let version = /** @type {string} */(await xcode.getVersion());
should.exist(version);
_.isString(version).should.be.true;
versionRE.test(version).should.be.true;
Expand All @@ -59,17 +59,17 @@ describe('xcode @skip-linux', function () {
await xcode.getPath();
await xcode.getVersion();

let before = new Date();
let before = Number(new Date());
let path = await xcode.getPath();
let after = new Date();
let after = Number(new Date());

should.exist(path);
await fs.exists(path);
(after - before).should.be.at.most(2);

before = new Date();
let version = await xcode.getVersion();
after = new Date();
before = Number(new Date());
let version = /** @type {string} */(await xcode.getVersion());
after = Number(new Date());

should.exist(version);
_.isString(version).should.be.true;
Expand All @@ -79,28 +79,28 @@ describe('xcode @skip-linux', function () {

it('should get the parsed version', async function () {
let nonParsedVersion = await xcode.getVersion();
let version = await xcode.getVersion(true);
let version = /** @type {import('../../lib/xcode').XcodeVersion} */(await xcode.getVersion(true));
should.exist(version);
_.isString(version.versionString).should.be.true;
version.versionString.should.eql(nonParsedVersion);

parseFloat(version.versionFloat).should.equal(version.versionFloat);
parseInt(version.major, 10).should.equal(version.major);
parseInt(version.minor, 10).should.equal(version.minor);
parseFloat(String(version.versionFloat)).should.equal(version.versionFloat);
parseInt(String(version.major), 10).should.equal(version.major);
parseInt(String(version.minor), 10).should.equal(version.minor);
});
});

it('should get clang version', async function () {
const cliVersion = await xcode.getClangVersion();
_.isString(util.coerceVersion(cliVersion, true)).should.be.true;
_.isString(util.coerceVersion(/** @type {string} */(cliVersion), true)).should.be.true;
});

it('should get max iOS SDK version', async function () {
let version = await xcode.getMaxIOSSDK();

should.exist(version);
(typeof version).should.equal('string');
(parseFloat(version) - 6.1).should.be.at.least(0);
(parseFloat(String(version)) - 6.1).should.be.at.least(0);
});

it('should get max tvOS SDK version', async function () {
Expand Down
2 changes: 1 addition & 1 deletion test/unit/index-specs.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import xcode from '../../index.js';
import xcode from '../../lib/index';
import chai from 'chai';

chai.should();
Expand Down
13 changes: 13 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@appium/tsconfig/tsconfig.json",
"compilerOptions": {
"checkJs": true,
"declarationMap": true,
"declaration": true,
"outDir": "build",
"emitDeclarationOnly": true,
"types": ["node", "mocha", "chai", "chai-as-promised"]
},
"include": ["lib", "test"]
}

0 comments on commit 94548ae

Please sign in to comment.