semver#Range TypeScript Examples

The following examples show how to use semver#Range. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.ts    From xcodebuild with The Unlicense 4 votes vote down vote up
async function main() {
  const cwd = core.getInput('working-directory')
  if (cwd) {
    process.chdir(cwd)
  }

  const swiftPM = fs.existsSync('Package.swift')
  const platform = getPlatformInput('platform')
  const selected = await xcselect(
    getRangeInput('xcode'),
    getRangeInput('swift')
  )
  const action = getAction(selected, platform)
  const configuration = getConfiguration()
  const warningsAsErrors = core.getBooleanInput('warnings-as-errors')
  const destination = await getDestination(selected, platform)
  const identity = getIdentity(core.getInput('code-sign-identity'), platform)
  const xcpretty = verbosity() == 'xcpretty'

  core.info(`» Selected Xcode ${selected}`)

  const reason: string | false = shouldGenerateXcodeproj()
  if (reason) {
    generateXcodeproj(reason)
  }

  const apiKey = await getAppStoreConnectApiKey()

  await configureKeychain()
  await configureProvisioningProfiles()

  await build(await getScheme())

  if (core.getInput('upload-logs') == 'always') {
    await uploadLogs()
  }

  //// immediate funcs

  function getPlatformInput(input: string): Platform | undefined {
    const value = core.getInput(input)
    if (!value) return undefined
    return value as Platform
  }

  function getRangeInput(input: string): Range | undefined {
    const value = core.getInput(input)
    if (!value) return undefined
    try {
      return new Range(value)
    } catch (error) {
      throw new Error(
        `failed to parse semantic version range from '${value}': ${error}`
      )
    }
  }

  function shouldGenerateXcodeproj(): string | false {
    if (!swiftPM) return false
    if (platform == 'watchOS' && semver.lt(selected, '12.5.0')) {
      // watchOS prior to 12.4 will fail to `xcodebuild` a SwiftPM project
      // failing trying to build the test modules, so we generate a project
      return 'Xcode <12.5 fails to build Swift Packages for watchOS if tests exist'
    } else if (semver.lt(selected, '11.0.0')) {
      return 'Xcode <11 cannot build'
    } else if (warningsAsErrors) {
      // `build` with SwiftPM projects will build the tests too, and if there are warnings in the
      // tests we will then fail to build (it's common that the tests may have ok warnings)
      //TODO only do this if there are test targets
      return '`warningsAsErrors` is set'
    }
    return false
  }

  function generateXcodeproj(reason: string) {
    core.startGroup('Generating `.xcodeproj`')
    try {
      core.info(`Generating \`.xcodeproj\` ∵ ${reason}`)
      spawn('swift', ['package', 'generate-xcodeproj'])
    } finally {
      core.endGroup()
    }
  }

  async function getAppStoreConnectApiKey(): Promise<string[] | undefined> {
    const key = core.getInput('authentication-key-base64')
    if (!key) return

    if (semver.lt(selected, '13.0.0')) {
      core.notice(
        'Ignoring authentication-key-base64 because it requires Xcode 13 or later.'
      )
      return
    }

    const keyId = core.getInput('authentication-key-id')
    const keyIssuerId = core.getInput('authentication-key-issuer-id')
    if (!keyId || !keyIssuerId) {
      throw new Error(
        'authentication-key-base64 requires authentication-key-id and authentication-key-issuer-id.'
      )
    }

    // The user should have already stored these as encrypted secrets, but we'll
    // be paranoid on their behalf.
    core.setSecret(key)
    core.setSecret(keyId)
    core.setSecret(keyIssuerId)

    const keyPath = await createAppStoreConnectApiKeyFile(key)
    return [
      '-allowProvisioningDeviceRegistration',
      '-allowProvisioningUpdates',
      '-authenticationKeyPath',
      keyPath,
      '-authenticationKeyID',
      keyId,
      '-authenticationKeyIssuerID',
      keyIssuerId,
    ]
  }

  async function configureKeychain() {
    const certificate = core.getInput('code-sign-certificate')
    if (!certificate) return

    if (process.env.RUNNER_OS != 'macOS') {
      throw new Error('code-sign-certificate requires macOS.')
    }

    const passphrase = core.getInput('code-sign-certificate-passphrase')
    if (!passphrase) {
      throw new Error(
        'code-sign-certificate requires code-sign-certificate-passphrase.'
      )
    }

    await core.group('Configuring code signing', async () => {
      await createKeychain(certificate, passphrase)
    })
  }

  async function configureProvisioningProfiles() {
    const mobileProfiles = core.getMultilineInput(
      'mobile-provisioning-profiles-base64'
    )
    const profiles = core.getMultilineInput('provisioning-profiles-base64')
    if (!mobileProfiles || !profiles) return

    await createProvisioningProfiles(profiles, mobileProfiles)
  }

  async function build(scheme?: string) {
    if (warningsAsErrors && actionIsTestable(action)) {
      await xcodebuild('build', scheme)
    }
    await xcodebuild(action, scheme)
  }

  //// helper funcs

  async function xcodebuild(action?: string, scheme?: string) {
    if (action === 'none') return

    const title = ['xcodebuild', action].filter((x) => x).join(' ')
    await core.group(title, async () => {
      let args = destination
      if (scheme) args = args.concat(['-scheme', scheme])
      if (identity) args = args.concat(identity)
      if (verbosity() == 'quiet') args.push('-quiet')
      if (configuration) args = args.concat(['-configuration', configuration])
      if (apiKey) args = args.concat(apiKey)

      args = args.concat([
        '-resultBundlePath',
        `${action ?? 'xcodebuild'}.xcresult`,
      ])

      switch (action) {
        case 'build':
          if (warningsAsErrors) args.push(warningsAsErrorsFlags)
          break
        case 'test':
        case 'build-for-testing':
          if (core.getBooleanInput('code-coverage')) {
            args = args.concat(['-enableCodeCoverage', 'YES'])
          }
          break
      }

      if (action) args.push(action)

      await xcodebuildX(args, xcpretty)
    })
  }

  //NOTE this is not nearly clever enough I think
  async function getScheme(): Promise<string | undefined> {
    const scheme = core.getInput('scheme')
    if (scheme) {
      return scheme
    }

    if (swiftPM) {
      return getSchemeFromPackage()
    }
  }
}
Example #2
Source File: lib.ts    From xcodebuild with The Unlicense 4 votes vote down vote up
export async function xcselect(xcode?: Range, swift?: Range): Promise<SemVer> {
  if (swift) {
    return selectSwift(swift)
  } else if (xcode) {
    return selectXcode(xcode)
  }

  const gotDotSwiftVersion = dotSwiftVersion()
  if (gotDotSwiftVersion) {
    core.info(`» \`.swift-version\` » ~> ${gotDotSwiftVersion}`)
    return selectSwift(gotDotSwiftVersion)
  } else {
    // figure out the GHA image default Xcode’s version

    const devdir = await exec('xcode-select', ['--print-path'])
    const xcodePath = path.dirname(path.dirname(devdir))
    const version = await mdls(xcodePath)
    if (version) {
      return version
    } else {
      // shouldn’t happen, but this action needs to know the Xcode version
      // or we cannot function, this way we are #continuously-resilient
      return selectXcode()
    }
  }

  async function selectXcode(range?: Range): Promise<SemVer> {
    const rv = (await xcodes())
      .filter(([, v]) => (range ? semver.satisfies(v, range) : true))
      .sort((a, b) => semver.compare(a[1], b[1]))
      .pop()

    if (!rv) throw new Error(`No Xcode ~> ${range}`)

    spawn('sudo', ['xcode-select', '--switch', rv[0]])

    return rv[1]
  }

  async function selectSwift(range: Range): Promise<SemVer> {
    const rv1 = await xcodes()
    const rv2 = await Promise.all(rv1.map(swiftVersion))
    const rv3 = rv2
      .filter(([, , sv]) => semver.satisfies(sv, range))
      .sort((a, b) => semver.compare(a[1], b[1]))
      .pop()

    if (!rv3) throw new Error(`No Xcode with Swift ~> ${range}`)

    core.info(`» Selected Swift ${rv3[2]}`)

    spawn('sudo', ['xcode-select', '--switch', rv3[0]])

    return rv3[1]

    async function swiftVersion([DEVELOPER_DIR, xcodeVersion]: [
      string,
      SemVer
    ]): Promise<[string, SemVer, SemVer]> {
      // This command emits 'swift-driver version: ...' to stderr.
      const stdout = await exec(
        'swift',
        ['--version'],
        { DEVELOPER_DIR },
        false
      )
      const matches = stdout.match(/Swift version (.+?)\s/m)
      if (!matches || !matches[1])
        throw new Error(
          `failed to extract Swift version from Xcode ${xcodeVersion}`
        )
      const version = semver.coerce(matches[1])
      if (!version)
        throw new Error(
          `failed to parse Swift version from Xcode ${xcodeVersion}`
        )
      return [DEVELOPER_DIR, xcodeVersion, version]
    }
  }

  function dotSwiftVersion(): Range | undefined {
    if (!fs.existsSync('.swift-version')) return undefined
    const version = fs.readFileSync('.swift-version').toString().trim()
    try {
      // A .swift-version of '5.0' indicates a SemVer Range of '>=5.0.0 <5.1.0'
      return new Range('~' + version)
    } catch (error) {
      core.warning(
        `Failed to parse Swift version from .swift-version: ${error}`
      )
    }
  }
}