Home Manual Reference Source Test

src/tasks/push.js

import Logger from 'gulplog';
import { StatusCodes } from 'node-opcua/lib/datamodel/opcua_status_code';
import { NodeClass } from 'node-opcua/lib/datamodel/nodeclass';
import src from '../lib/gulp/src';
import { writeNode, createNode, addReferences } from '../api';
import Transformer, { TransformDirection } from '../lib/transform/Transformer.js';
import NodeId from '../lib/model/opcua/NodeId.js';
import { ReferenceTypeIds, ReferenceTypeNames } from '../lib/model/Node';
import { reportProgress } from '../lib/helpers/log.js';
import ProjectConfig from '../config/ProjectConfig.js';
import { finishTask, handleTaskError } from '../lib/helpers/tasks.js';
import Session from '../lib/server/Session.js';
import checkServerscripts from '../hooks/check-serverscripts';
import checkAtserver from '../hooks/check-atserver';
import { setupContext } from '../hooks/hooks';

/**
 * Status codes indicating a node is opened in atvise builder and therefore not writable right now.
 * @type {Set<node-opcua~StatusCodes>}
 */
const openInBuilderStatus = new Set([StatusCodes.BadUserAccessDenied, StatusCodes.BadNotWritable]);

/**
 * The reference types ignored when adding references. The corresponding references are created
 * alongside the node itself using the 'CreateNode' server script.
 * @type {Set<node-opcua~ReferenceTypeId>}
 */
const ignoredReferences = new Set([
  ReferenceTypeIds.toParent,
  ReferenceTypeIds.HasTypeDefinition,
  // ReferenceTypeIds.HasModellingRule,
]);

/**
 * Pushes the given path to the server.
 * @param {string} path The local path to push.
 * @param {Object} options Options passed to {@link src}.
 */
export function performPush(path, options) {
  const applyTransforms = Transformer.combinedTransformer(
    ProjectConfig.useTransformers,
    TransformDirection.FromFilesystem
  );

  const ensureReferences = (node) => {
    const references = [...node.references].reduce((result, [key, value]) => {
      if (ignoredReferences.has(key)) {
        return result;
      }

      return Object.assign(result, {
        [key]: [...value].map((s) => (typeof s === 'string' ? `ns=1;s=${s}` : s)),
      });
    }, {});

    if (Object.keys(references).length > 0) {
      return addReferences(node.nodeId, references)
        .then(({ outputArguments }) => {
          const [{ value: failures }] = outputArguments[3].value;

          if (failures) {
            throw new Error(
              `Failed to create reference(s) from ${node.nodeId} to ${failures.join(', ')}`
            );
          } else {
            Logger.debug(`Added ${Object.keys(references).length} reference(s) to ${node.nodeId}`);
          }
        })
        .catch((err) => {
          throw Object.assign(err, { node });
        });
    }

    return Promise.resolve();
  };

  const create = (node) => {
    const nodeId = new NodeId(node.nodeId);
    let parentNodeId = node.parent && node.parent.nodeId;

    if (!node.parent) {
      parentNodeId = nodeId.parent;
      Logger.debug(`Assuming ${parentNodeId} as parent of ${node.nodeId}`);
    }

    return createNode(nodeId, {
      name: node.name,
      parentNodeId,
      nodeClass: node.nodeClass,
      typeDefinition: node.typeDefinition,
      modellingRule: node.modellingRule,
      reference: ReferenceTypeNames[node.references.getSingle(ReferenceTypeIds.toParent)],
      value:
        node.nodeClass && node.nodeClass.value === NodeClass.Variable.value && node.variantValue,
    })
      .then(({ outputArguments }) => {
        const [{ value: createdNode }, { value: createFailed }] = outputArguments[3].value;

        if (createFailed) {
          Logger.warn('Failed to create node', node.nodeId);
          return Promise.resolve();
        } else if (createdNode) {
          Logger.debug('Created node', node.nodeId);
        } else {
          // Node already existed
        }

        return ensureReferences(node);
      })
      .catch((err) => {
        throw Object.assign(err, { node });
      });
  };

  return src(path, {
    ...options,
    readNodeFile(node) {
      const r = ProjectConfig.useTransformers
        .reverse()
        .reduce((result, t) => (result === undefined ? t.readNodeFile(node) : result), undefined);
      return r === undefined ? true : r;
    },
    async handleNode(node) {
      // NOTE: context = this
      await applyTransforms(node, this);

      if (node.push === false) {
        // Skip write
        return false;
      }

      // Create / write node
      if (node.nodeClass.value !== NodeClass.Variable.value) {
        return create(node);
      }

      // console.error('write', node.nodeId, node.value);
      return writeNode(`ns=1;s=${node.nodeId}`, node.variantValue).then(
        () => ensureReferences(node),
        (err) => {
          if (openInBuilderStatus.has(err.statusCode)) {
            Logger.warn(`Error writing node ${node.nodeId}
    - Make sure it is not opened in atvise builder
    - Make sure the corresponding datasource is connected`);
            return StatusCodes.Good;
          }

          if (err.statusCode === StatusCodes.BadNodeIdUnknown) {
            Logger.debug(`Node ${node.nodeId} does not exist: Attempting to create it...`);

            return create(node);
          }

          throw Object.assign(err, { node });
        }
      );
    },
  });
}

/**
 * Pushes {@link AtviseFile}s to atvise server.
 */
export default async function push() {
  Session.pool();

  const context = setupContext();

  const { version: atserverVersion } = await checkAtserver(context);
  await checkServerscripts(context);

  const promise = performPush('./src', { atserverVersion });

  return reportProgress(promise, {
    getter: () => promise.browser._pushedPath.size,
    formatter: (count) => `Processed ${count} files`,
  }).then(finishTask, handleTaskError);
}

push.description = 'Push all stored nodes to atvise server';