|  | 
|  | 1 | +'use strict'; | 
|  | 2 | + | 
|  | 3 | +import path from 'path'; | 
|  | 4 | +import moment from 'moment'; | 
|  | 5 | +import semver from 'semver'; | 
|  | 6 | +import username from 'username'; | 
|  | 7 | +import readPkgUp from 'read-pkg-up'; | 
|  | 8 | +import repo from './repo'; | 
|  | 9 | + | 
|  | 10 | +const REGEX_NOT_APLHA_NUMERIC = /\W/g; | 
|  | 11 | +const REGEX_ISODATE_MILLISECONDS = /\.[0]{3}/g; | 
|  | 12 | + | 
|  | 13 | +// Replace demarcators in a string, so that it is compatible as a semver fragment | 
|  | 14 | +const semverString = (str) => { | 
|  | 15 | +    return str.replace(REGEX_NOT_APLHA_NUMERIC, ''); | 
|  | 16 | +}; | 
|  | 17 | + | 
|  | 18 | +// Generate an ISO 8601 UTC timestamp that is safe for use in a semver string | 
|  | 19 | +// NOTE: The generated date should be ISO 8601 compatible. | 
|  | 20 | +// e.g. 2017-Jan-01, 15:00:01.100 => "20170130T1500Z" | 
|  | 21 | +const semverMoment = () => { | 
|  | 22 | +    // Get ISO date after ignoring the millisecond precision | 
|  | 23 | +    const isoDate = moment().milliseconds(0).toISOString(); | 
|  | 24 | + | 
|  | 25 | +    // Replace millisecond information as the 'dot' notation for it is incompatible with semver | 
|  | 26 | +    return semverString(isoDate.replace(REGEX_ISODATE_MILLISECONDS, '')); | 
|  | 27 | +}; | 
|  | 28 | + | 
|  | 29 | +// Read version key from the nearest package | 
|  | 30 | +const pkgVersion = async(o) => { | 
|  | 31 | +    const data = await readPkgUp({cwd: o.cwd}); | 
|  | 32 | +    if (data && data.pkg && data.pkg.version) { | 
|  | 33 | +        return semver.valid(data.pkg.version); | 
|  | 34 | +    } | 
|  | 35 | +    throw new TypeError('Could not read a valid version from: ' + data.path | 
|  | 36 | +        ? path.relative('', data.path) | 
|  | 37 | +        : 'package.json'); | 
|  | 38 | +}; | 
|  | 39 | + | 
|  | 40 | +// Help identify dirty builds, those that differ from the latest commit. | 
|  | 41 | +// If the working directory is dirty, append the username and date to | 
|  | 42 | +// the version string, to make it stand out. | 
|  | 43 | +const suffix = async(version, option) => { | 
|  | 44 | +    const startMarker = '+'; | 
|  | 45 | +    const fieldSeparator = '.'; | 
|  | 46 | +    const prefix = version.includes(startMarker) | 
|  | 47 | +        ? fieldSeparator | 
|  | 48 | +        : startMarker; | 
|  | 49 | +    const dirty = await repo.isDirty(option); | 
|  | 50 | +    const hash = await repo.hash(option); | 
|  | 51 | +    let buildmeta = [ | 
|  | 52 | +        option.prefix || 'SHA', | 
|  | 53 | +        hash | 
|  | 54 | +    ]; | 
|  | 55 | + | 
|  | 56 | +    if (dirty) { | 
|  | 57 | +        const name = await username(); | 
|  | 58 | +        buildmeta.push(semverString(name)); | 
|  | 59 | +        buildmeta.push(semverMoment()); | 
|  | 60 | +    } | 
|  | 61 | + | 
|  | 62 | +    // Attach or append to "build data", as defined by semver. | 
|  | 63 | +    return version + prefix + buildmeta.join(fieldSeparator); | 
|  | 64 | +}; | 
|  | 65 | + | 
|  | 66 | +const getRevision = async(option) => { | 
|  | 67 | +    const version = await pkgVersion(option); | 
|  | 68 | +    const revision = await suffix(version, option); | 
|  | 69 | + | 
|  | 70 | +    return revision; | 
|  | 71 | +}; | 
|  | 72 | + | 
|  | 73 | +const version = async(o) => { | 
|  | 74 | +    const option = Object.assign({}, o); | 
|  | 75 | +    const cwd = path.resolve(option.cwd || ''); | 
|  | 76 | +    const revision = await getRevision(option); | 
|  | 77 | + | 
|  | 78 | +    return revision; | 
|  | 79 | +}; | 
|  | 80 | + | 
|  | 81 | +export default version; | 
0 commit comments