Home Manual Reference Source Test

src/hooks/check-atserver.ts

import { VariableIds } from 'node-opcua/lib/opcua_node_ids';
import { readJson } from 'fs-extra';
import { coerce, satisfies, gtr, minVersion } from 'semver';
import Logger from 'gulplog';
import prompts from 'prompts';
import { red, yellow } from 'chalk';
import { readNode } from '../api';
import { engines } from '../../package.json';
import { updateJson } from '../lib/helpers/fs';
import NodeId from '../lib/model/opcua/NodeId';
import { HookContext } from './hooks';

const atserverVersionNodeId = new NodeId(
  `ns=0;i=${VariableIds.Server_ServerStatus_BuildInfo_SoftwareVersion}`
);

export async function loadProjectRequirement(): Promise<string> {
  const packageManifest = await readJson('./package.json');

  return packageManifest.engines && packageManifest.engines.atserver;
}

export async function loadRemoteVersion(): Promise<string> {
  const raw = (await readNode(atserverVersionNodeId)).value;

  return coerce(raw).version;
}

export async function askForConfirmation({
  onAsk,
  ...options
}: {
  message: string;
  onAsk?: () => void;
}): Promise<boolean> {
  if (!process.stdin.isTTY) return false;

  if (onAsk) onAsk();

  return (
    await prompts({
      type: 'confirm',
      name: 'confirmed',
      ...options,
    })
  ).confirmed;
}

export async function approveToContinue(
  { log, continueOnError }: HookContext,
  error: Error
): Promise<void> {
  if (continueOnError) {
    log.warn(red(error.message));
    log.warn(`Using --continue, skipping...`);
    return;
  }

  const shouldContinue = await askForConfirmation({
    onAsk: () => Logger.error(red(error.message)),
    message: 'Do you want to continue anyway?',
  });

  if (!shouldContinue) {
    throw error;
  }
}

export default async function checkAtserver(context: HookContext): Promise<{ version: string }> {
  const { log } = context;

  log.debug('Checking atserver version');

  const atscmRequirement = engines.atserver;

  const [projectRequirement, remoteVersion] = await Promise.all([
    loadProjectRequirement(),
    loadRemoteVersion(),
  ]);

  if (!satisfies(remoteVersion, atscmRequirement)) {
    log.debug(`Version ${remoteVersion} does not satisfy requirement ${atscmRequirement}`);
    log.warn(
      yellow(
        `Your atvise server version (${remoteVersion}) is not supported, it may or may not work.`
      )
    );

    if (gtr(remoteVersion, atscmRequirement)) {
      log.info(
        `You're running a newer version of atvise server. Please run 'atscm update' to check for updates.`
      );
    } else {
      log.info(`Please upgrade to atserver ${minVersion(atscmRequirement)} or above.`);
    }
  }

  let updatePackage = false;
  if (!projectRequirement) {
    log.info(`Your package.json file doesn't specify an atserver version, adding it...`);

    updatePackage = true;
  } else if (!satisfies(remoteVersion, projectRequirement)) {
    await approveToContinue(
      context,
      new Error(
        `Your project is setup with atserver ${projectRequirement} but you're using ${remoteVersion}`
      )
    );

    updatePackage = await askForConfirmation({
      message: `Use atvise server ${remoteVersion} as new default?`,
    });
  } else {
    log.debug(`Running against atserver ${remoteVersion}`);
  }

  if (updatePackage) {
    await updateJson<{ engines?: { atserver?: string } }>('./package.json', (current) => {
      /* eslint-disable no-param-reassign */
      if (!current.engines) current.engines = {};
      current.engines.atserver = remoteVersion;
      /* eslint-enable no-param-reassign */

      return current;
    });
  }

  return { version: remoteVersion };
}