A few bug fixes
1) normalize file path 2) skip webpage control 3) skip non-custom type of pages 4) allow rule import failure as optional parameter 5) allow user choose if import control/group/form contributions 6) add jsonc support for config file
This commit is contained in:
Родитель
75c57bd739
Коммит
ee8c11710b
|
@ -5,11 +5,11 @@
|
|||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch process import/export ",
|
||||
"program": "${workspaceFolder}/build/main.js",
|
||||
"program": "${workspaceFolder}/build/nodejs/nodejs/main.js",
|
||||
"sourceMaps": true,
|
||||
"args": [
|
||||
"--mode=both",
|
||||
"--config=configuration.json",
|
||||
"--config=output\\repro.json",
|
||||
"--overwriteProcessOnTarget"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ NOTE: This only works with 'Inherited Process', for 'XML process' you can upload
|
|||
## Run
|
||||
|
||||
- Install npm if haven't - [link](https://www.npmjs.com/get-npm)
|
||||
- Install this package through `npm install vsts-process-import-export -g`
|
||||
- Install this package through `npm install process-import-export -g`
|
||||
- Create a configuration.json, see [doc section](#documentation) for explanation on details
|
||||
- Run `vstspie --mode=<import/export/both> [--config=<your-configuration-file-path>] [--overwriteProcessOnTarget]`
|
||||
- Run `pie --mode=<import/export/both> [--config=<your-configuration-file-path>] [--overwriteProcessOnTarget]`
|
||||
|
||||
## Contribute
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "process-import-export",
|
||||
"version": "0.9.0",
|
||||
"version": "0.9.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -68,6 +68,11 @@
|
|||
"resolved": "https://witiq.pkgs.visualstudio.com/_packaging/processimportexport/npm/registry/guid-typescript/-/guid-typescript-1.0.7.tgz",
|
||||
"integrity": "sha512-j1XPiaDUuNa0PO8EyGAikyKMpAnGbSDzr81UAzXz3H2xAoQW3c2hH8RS7Omo+DEgtD9emYdgJaxnfKZPYtDNRQ=="
|
||||
},
|
||||
"jsonc-parser": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.0.0.tgz",
|
||||
"integrity": "sha512-gYk8VcFDwky0AjrKeJSWgCm/lYGteP9hszGWtgg67Elz4owvhJF9qATjuIRAk5jgBMGM65MPAc+I4RTeoqoykA=="
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://witiq.pkgs.visualstudio.com/_packaging/processimportexport/npm/registry/minimist/-/minimist-1.2.0.tgz",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "process-import-export",
|
||||
"version": "0.9.0",
|
||||
"version": "0.9.2",
|
||||
"description": "Proces import/export Node.js application",
|
||||
"main": "",
|
||||
"bin": {
|
||||
|
@ -35,6 +35,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"guid-typescript": "^1.0.7",
|
||||
"jsonc-parser": "^2.0.0",
|
||||
"minimist": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"url": "^0.11.0",
|
||||
|
|
|
@ -1,24 +1,27 @@
|
|||
export const PICKLIST_NO_ACTION = "PICKLIST_NO_ACTION";
|
||||
export const defaultEncoding = "utf-8";
|
||||
export const defaultConfigurationFilename = "configuration.json";
|
||||
export const defaultLogFileName = "output\\process_import_export.log";
|
||||
export const defaultLogFileName = "output\\pie.log";
|
||||
export const defaultProcessFilename = "output\\process.json";
|
||||
export const paramMode = "mode";
|
||||
export const paramConfig = "config";
|
||||
export const paramOverwriteProcessOnTarget = "overwriteProcessOnTarget";
|
||||
export const defaultConfiguration =
|
||||
{
|
||||
`{
|
||||
"sourceAccountUrl": "<Source account url>",
|
||||
"sourceAccountToken": "<Source account PAT>",
|
||||
"sourceAccountToken": "<Source account personal access token>",
|
||||
"targetAccountUrl": "<Target account url>",
|
||||
"targetAccountToken": "<Target account PAT>",
|
||||
"sourceProcessName": "Process name for export, optional in import only mode, required in export/both mode",
|
||||
"targetProcessName<Optional>": "Set to override process name on import, remove <Optional> from param name",
|
||||
"targetAccountToken": "<Target account personal access token>",
|
||||
"sourceProcessName": "Process name for export, ignored in import only mode, required in export/both mode",
|
||||
// "targetProcessName": "Set to override process name on import",
|
||||
"options": {
|
||||
"processFilename<Optional>": "Set to override default export file name, remove <Optional> from param name",
|
||||
"logLevel<Optional>": "Set to override default log level (Information), remove <Optional> from param name",
|
||||
"logFilename<Optional>": "Set to override default log file name, remove <Optional> from param name",
|
||||
"overwritePicklist": false
|
||||
// "processFilename": "Default is 'output/process.json', set to override default export file name",
|
||||
// "logLevel": "Default is information, set to override log level, possible values are verbose/information/warning/error",
|
||||
// "logFilename": "Default is 'output/pie.log', set to override default log file name",
|
||||
// "continueOnRuleImportFailure": "Default is false, Set true to continue import on failure importing rules, warning will still be provided"
|
||||
// "skipImportControlContributions": "Default is false, Set true to skip import control contributions on work item form."
|
||||
// "skipImportGroupOrPageContributions": "Default is true, Set false to allow import group/page contributions on work item form. This should only be used when you want to hide contribution group/page."
|
||||
// "overwritePicklist": "Default is false, Set true to overwrite picklist if exist on target account."
|
||||
}
|
||||
};
|
||||
}`;
|
||||
export const regexRemoveHypen = new RegExp("-","g");
|
|
@ -43,6 +43,9 @@ export interface IConfigurationOptions {
|
|||
logFilename?: string;
|
||||
processFilename?: string;
|
||||
overwritePicklist?: boolean;
|
||||
continueOnRuleImportFailure?: boolean;
|
||||
skipImportControlContributions?: boolean;
|
||||
skipImportGroupOrPageContributions?: boolean;
|
||||
}
|
||||
|
||||
export interface IImportConfiguration extends IConfigurationFile {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { existsSync, writeFileSync } from "fs";
|
||||
import * as WITProcessDefinitionsInterfaces from "vso-node-api/interfaces/WorkItemTrackingProcessDefinitionsInterfaces";
|
||||
import * as WITProcessInterfaces from "vso-node-api/interfaces/WorkItemTrackingProcessInterfaces";
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import { IWorkItemTrackingProcessDefinitionsApi as WITProcessDefinitionApi_NOREQ
|
|||
import { IWorkItemTrackingProcessApi as WITProcessApi_NOREQUIRE } from "vso-node-api/WorkItemTrackingProcessApi";
|
||||
import { IWorkItemTrackingApi as WITApi_NOREQUIRE } from "vso-node-api/WorkItemTrackingApi";
|
||||
|
||||
import { PICKLIST_NO_ACTION, regexRemoveHypen } from "./Constants";
|
||||
import { PICKLIST_NO_ACTION } from "./Constants";
|
||||
import { Engine } from "./Engine";
|
||||
import { ImportError, ValidationError } from "./Errors";
|
||||
import { ICommandLineOptions, IConfigurationFile, IDictionaryStringTo, IProcessPayload, IWITLayout, IWITRules, IWITStates, IRestClients } from "./Interfaces";
|
||||
|
@ -188,7 +188,11 @@ export class ProcessImporter {
|
|||
|
||||
private async _importPage(targetLayout: WITProcessDefinitionsInterfaces.FormLayout, witLayout: IWITLayout, page: WITProcessDefinitionsInterfaces.Page, payload: IProcessPayload) {
|
||||
if (!page) {
|
||||
throw new ImportError(`Encourtered null page in work item type '${witLayout.workItemTypeRefName}'`);
|
||||
throw new ImportError(`Encountered null page in work item type '${witLayout.workItemTypeRefName}'`);
|
||||
}
|
||||
|
||||
if (page.isContribution && !this._config.options.skipImportGroupOrPageContributions !== false) {
|
||||
// skip import page contriubtion unless user explicitly asks so
|
||||
}
|
||||
|
||||
let newPage: WITProcessDefinitionsInterfaces.Page; //The newly created page, contains the pageId required to create groups.
|
||||
|
@ -214,6 +218,10 @@ export class ProcessImporter {
|
|||
for (const group of section.groups) {
|
||||
let newGroup: WITProcessDefinitionsInterfaces.Group;
|
||||
|
||||
if (group.isContribution === true && !this._config.options.skipImportGroupOrPageContributions !== false) {
|
||||
// skip import group contriubtion unless user explicitly asks so
|
||||
}
|
||||
|
||||
if (group.controls.length !== 0 && group.controls[0].controlType === "HtmlFieldControl") {
|
||||
//Handle groups with HTML Controls
|
||||
const createGroup: WITProcessDefinitionsInterfaces.Group = Utility.toCreateGroup(group);
|
||||
|
@ -272,20 +280,27 @@ export class ProcessImporter {
|
|||
try {
|
||||
let createControl: WITProcessDefinitionsInterfaces.Control = Utility.toCreateControl(control);
|
||||
|
||||
if (control.controlType === "WebpageControl" || (control.isContribution === true && this._config.options.skipImportControlContributions)) {
|
||||
// Skip web page control for now since not supported in inherited process.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (control.inherited) {
|
||||
if (control.overridden) {
|
||||
//edit
|
||||
await this._witProcessDefinitionApi.editControl(createControl, payload.process.typeId, witLayout.workItemTypeRefName, group.id, control.id);
|
||||
await Engine.Task(() => this._witProcessDefinitionApi.editControl(createControl, payload.process.typeId, witLayout.workItemTypeRefName, group.id, control.id),
|
||||
`Edit control '${control.id}' in group '${group.id}' in page '${page.id}' in work item type '${witLayout.workItemTypeRefName}'.`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
//create
|
||||
await this._witProcessDefinitionApi.addControlToGroup(createControl, payload.process.typeId, witLayout.workItemTypeRefName, group.id);
|
||||
await Engine.Task(() => this._witProcessDefinitionApi.addControlToGroup(createControl, payload.process.typeId, witLayout.workItemTypeRefName, group.id),
|
||||
`Create control '${control.id}' in group '${group.id}' in page '${page.id}' in work item type '${witLayout.workItemTypeRefName}'.`);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
Utility.handleKnownError(error);
|
||||
throw new ImportError(`Unable to add '${control}' control to page '${page}' in '${witLayout.workItemTypeRefName}'. ${error}`);
|
||||
throw new ImportError(`Unable to add '${control.id}' control to group '${group.id}' in page '${page.id}' in '${witLayout.workItemTypeRefName}'. ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -304,7 +319,9 @@ export class ProcessImporter {
|
|||
() => this._witProcessDefinitionApi.getFormLayout(payload.process.typeId, witLayoutEntry.workItemTypeRefName),
|
||||
`Get layout on target process for work item type '${witLayoutEntry.workItemTypeRefName}'`);
|
||||
for (const page of witLayoutEntry.layout.pages) {
|
||||
await this._importPage(targetLayout, witLayoutEntry, page, payload);
|
||||
if (page.pageType === WITProcessDefinitionsInterfaces.PageType.Custom) {
|
||||
await this._importPage(targetLayout, witLayoutEntry, page, payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -329,7 +346,7 @@ export class ProcessImporter {
|
|||
const existingStates: WITProcessDefinitionsInterfaces.WorkItemStateResultModel[] = targetWITStates.filter(targetState => sourceState.name === targetState.name);
|
||||
if (existingStates.length === 0) { // does not exist on target
|
||||
const createdState = await Engine.Task(
|
||||
() => this._witProcessDefinitionApi.createStateDefinition(sourceState, payload.process.typeId, witStateEntry.workItemTypeRefName),
|
||||
() => this._witProcessDefinitionApi.createStateDefinition(Utility.toCreateOrUpdateStateDefinition(sourceState), payload.process.typeId, witStateEntry.workItemTypeRefName),
|
||||
`Create state '${sourceState.name}' in '${witStateEntry.workItemTypeRefName}' work item type`);
|
||||
if (!createdState || !createdState.id) {
|
||||
throw new ImportError(`Unable to create state '${sourceState.name}' in '${witStateEntry.workItemTypeRefName}' work item type, server returned empty result or id.`);
|
||||
|
@ -349,7 +366,7 @@ export class ProcessImporter {
|
|||
if (sourceState.color !== existingState.color || sourceState.stateCategory !== existingState.stateCategory || sourceState.name !== existingState.name) {
|
||||
// Inherited state can be edited in custom work item types.
|
||||
const updatedState = await Engine.Task(
|
||||
() => this._witProcessDefinitionApi.updateStateDefinition(Utility.toUdpateStateDefinition(sourceState), payload.process.typeId, witStateEntry.workItemTypeRefName, existingState.id),
|
||||
() => this._witProcessDefinitionApi.updateStateDefinition(Utility.toCreateOrUpdateStateDefinition(sourceState), payload.process.typeId, witStateEntry.workItemTypeRefName, existingState.id),
|
||||
`Update state '${sourceState.name}' in '${witStateEntry.workItemTypeRefName}' work item type`);
|
||||
if (!updatedState || updatedState.name !== sourceState.name) {
|
||||
throw new ImportError(`Unable to update state '${sourceState.name}' in '${witStateEntry.workItemTypeRefName}' work item type, server returned empty result, id or state is not hidden.`);
|
||||
|
@ -377,7 +394,6 @@ export class ProcessImporter {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private async _importStates(payload: IProcessPayload): Promise<void> {
|
||||
for (const witStateEntry of payload.states) {
|
||||
await this._importWITStates(witStateEntry, payload);
|
||||
|
@ -395,8 +411,13 @@ export class ProcessImporter {
|
|||
}
|
||||
}
|
||||
catch (error) {
|
||||
Utility.handleKnownError(error);
|
||||
throw new ImportError(`Unable to create rule '${rule.id}' in work item type '${witRulesEntry.workItemTypeRefName}', see logs for details.`);
|
||||
if (this._config.options.continueOnRuleImportFailure === true) {
|
||||
logger.logWarning(`Failed to import rule below, continue importing rest of process.\r\n:Error:${error}\r\n${JSON.stringify(rule, null, 2)}`);
|
||||
}
|
||||
else {
|
||||
Utility.handleKnownError(error);
|
||||
throw new ImportError(`Unable to create rule '${rule.id}' in work item type '${witRulesEntry.workItemTypeRefName}', see logs for details.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -427,7 +448,7 @@ export class ProcessImporter {
|
|||
const createBehavior: WITProcessDefinitionsInterfaces.BehaviorCreateModel = Utility.toCreateBehavior(behavior);
|
||||
// Use a random name to avoid conflict on scenarios involving a name swap
|
||||
behaviorIdToRealNameBehavior[behavior.id] = Utility.toReplaceBehavior(behavior);
|
||||
createBehavior.name = Guid.create().toString().replace(regexRemoveHypen, "");
|
||||
createBehavior.name = Utility.createGuidWithoutHyphen();
|
||||
const createdBehavior = await Engine.Task(
|
||||
() => this._witProcessDefinitionApi.createBehavior(createBehavior, payload.process.typeId),
|
||||
`Create behavior '${behavior.id}' with fake name '${behavior.name}'`);
|
||||
|
@ -438,7 +459,7 @@ export class ProcessImporter {
|
|||
else {
|
||||
const replaceBehavior: WITProcessDefinitionsInterfaces.BehaviorReplaceModel = Utility.toReplaceBehavior(behavior);
|
||||
behaviorIdToRealNameBehavior[behavior.id] = Utility.toReplaceBehavior(behavior);
|
||||
replaceBehavior.name = Guid.create().toString().replace(regexRemoveHypen, "");
|
||||
replaceBehavior.name = Utility.createGuidWithoutHyphen();
|
||||
const replacedBehavior = await Engine.Task(
|
||||
() => this._witProcessDefinitionApi.replaceBehavior(replaceBehavior, payload.process.typeId, behavior.id),
|
||||
`Replace behavior '${behavior.id}' with fake name '${behavior.name}'`);
|
||||
|
|
|
@ -5,6 +5,8 @@ import { KnownError } from "./Errors";
|
|||
import { logger } from "./Logger";
|
||||
import { Modes, IConfigurationFile, LogLevel } from "./Interfaces";
|
||||
import * as url from "url";
|
||||
import { Guid } from "guid-typescript";
|
||||
import { regexRemoveHypen } from "./Constants";
|
||||
|
||||
export class Utility {
|
||||
/** Convert from WITProcess FieldModel to WITProcessDefinitions FieldModel
|
||||
|
@ -56,7 +58,7 @@ export class Utility {
|
|||
description: processModel.description,
|
||||
name: processModel.name,
|
||||
parentProcessTypeId: processModel.properties.parentProcessTypeId,
|
||||
referenceName: processModel.referenceName
|
||||
referenceName: Utility.createGuidWithoutHyphen() // Reference name does not really matter since we already have typeId
|
||||
};
|
||||
return createModel;
|
||||
}
|
||||
|
@ -64,7 +66,7 @@ export class Utility {
|
|||
/**Convert group from getLayout group interface to WITProcessDefinitionsInterfaces.Group
|
||||
* @param group
|
||||
*/
|
||||
public static toCreateGroup(group: any/*TODO: Change this type, not any*/): WITProcessDefinitionsInterfaces.Group {
|
||||
public static toCreateGroup(group: WITProcessDefinitionsInterfaces.Group): WITProcessDefinitionsInterfaces.Group {
|
||||
let createGroup: WITProcessDefinitionsInterfaces.Group = {
|
||||
id: group.id,
|
||||
inherited: group.inherited,
|
||||
|
@ -72,8 +74,8 @@ export class Utility {
|
|||
isContribution: group.isContribution,
|
||||
visible: group.visible,
|
||||
controls: null,
|
||||
contribution: null,
|
||||
height: null,
|
||||
contribution: group.contribution,
|
||||
height: group.height,
|
||||
order: null,
|
||||
overridden: null
|
||||
}
|
||||
|
@ -83,7 +85,7 @@ export class Utility {
|
|||
/**Convert control from getLayout control interface to WITProcessDefinitionsInterfaces.Control
|
||||
* @param control
|
||||
*/
|
||||
public static toCreateControl(control: any/*TODO: Change this type, not any*/): WITProcessDefinitionsInterfaces.Control {
|
||||
public static toCreateControl(control: WITProcessDefinitionsInterfaces.Control): WITProcessDefinitionsInterfaces.Control {
|
||||
let createControl: WITProcessDefinitionsInterfaces.Control = {
|
||||
id: control.id,
|
||||
inherited: control.inherited,
|
||||
|
@ -94,8 +96,8 @@ export class Utility {
|
|||
metadata: control.metadata,
|
||||
visible: control.visible,
|
||||
isContribution: control.isContribution,
|
||||
contribution: null,
|
||||
height: null,
|
||||
contribution: control.contribution,
|
||||
height: control.height,
|
||||
order: null,
|
||||
overridden: null
|
||||
}
|
||||
|
@ -105,17 +107,17 @@ export class Utility {
|
|||
/**Convert page from getLayout page interface to WITProcessDefinitionsInterfaces.Page
|
||||
* @param control
|
||||
*/
|
||||
public static toCreatePage(page: any/*TODO: Change this type, not any*/): WITProcessDefinitionsInterfaces.Page {
|
||||
public static toCreatePage(page: WITProcessDefinitionsInterfaces.Page): WITProcessDefinitionsInterfaces.Page {
|
||||
let createPage: WITProcessDefinitionsInterfaces.Page = {
|
||||
id: page.id,
|
||||
inherited: page.inherited,
|
||||
label: page.label,
|
||||
pageType: page.pageType,
|
||||
locked: page.loacked,
|
||||
locked: page.locked,
|
||||
visible: page.visible,
|
||||
isContribution: page.isContribution,
|
||||
sections: null,//yeah??
|
||||
contribution: null,
|
||||
sections: null,
|
||||
contribution: page.contribution,
|
||||
order: null,
|
||||
overridden: null
|
||||
}
|
||||
|
@ -125,7 +127,7 @@ export class Utility {
|
|||
/**Convert a state result to state input
|
||||
* @param group
|
||||
*/
|
||||
public static toUdpateStateDefinition(state: WITProcessInterfaces.WorkItemStateResultModel): WITProcessDefinitionsInterfaces.WorkItemStateInputModel {
|
||||
public static toCreateOrUpdateStateDefinition(state: WITProcessInterfaces.WorkItemStateResultModel): WITProcessDefinitionsInterfaces.WorkItemStateInputModel {
|
||||
const updateState: WITProcessDefinitionsInterfaces.WorkItemStateInputModel = {
|
||||
color: state.color,
|
||||
name: state.name,
|
||||
|
@ -198,6 +200,14 @@ export class Utility {
|
|||
logger.logError(`[Configuration validation] Option 'overwritePicklist' is not a valid boolean.`);
|
||||
return false;
|
||||
}
|
||||
if (configuration.options && configuration.options.continueOnRuleImportFailure && (configuration.options.continueOnRuleImportFailure !== true && configuration.options.continueOnRuleImportFailure !== false)) {
|
||||
logger.logError(`[Configuration validation] Option 'continueOnRuleImportFailure' is not a valid boolean.`);
|
||||
return false;
|
||||
}
|
||||
if (configuration.options && configuration.options.skipImportControlContributions && (configuration.options.skipImportControlContributions !== true && configuration.options.skipImportControlContributions !== false)) {
|
||||
logger.logError(`[Configuration validation] Option 'skipImportControlContributions' is not a valid boolean.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (configuration.options && configuration.options.logLevel && LogLevel[configuration.options.logLevel] === undefined) {
|
||||
|
@ -212,5 +222,9 @@ export class Utility {
|
|||
return Utility.isCancelled;
|
||||
}
|
||||
|
||||
public static createGuidWithoutHyphen(): string {
|
||||
return Guid.create().toString().replace(regexRemoveHypen, "");
|
||||
}
|
||||
|
||||
protected static isCancelled = false;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||
import { normalize } from "path";
|
||||
import * as minimist from "minimist";
|
||||
import * as url from "url";
|
||||
import { defaultConfiguration, defaultConfigurationFilename, defaultEncoding, paramConfig, paramMode, paramOverwriteProcessOnTarget } from "../common/Constants";
|
||||
import { IConfigurationFile, LogLevel, Modes, ICommandLineOptions } from "../common/Interfaces";
|
||||
import { logger } from "../common/Logger";
|
||||
import { Utility } from "../common/Utilities";
|
||||
import { parse as jsoncParse } from "jsonc-parser";
|
||||
|
||||
export function ProcesCommandLine(): ICommandLineOptions {
|
||||
const parseOptions: minimist.Opts = {
|
||||
|
@ -22,7 +24,7 @@ export function ProcesCommandLine(): ICommandLineOptions {
|
|||
process.exit(0);
|
||||
}
|
||||
|
||||
const configFileName = parsedArgs[paramConfig] || defaultConfigurationFilename;
|
||||
const configFileName = parsedArgs[paramConfig] || normalize(defaultConfigurationFilename);
|
||||
|
||||
const userSpecifiedMode = parsedArgs[paramMode] as string;
|
||||
let mode;
|
||||
|
@ -38,12 +40,12 @@ export function ProcesCommandLine(): ICommandLineOptions {
|
|||
mode = Modes.both;
|
||||
}
|
||||
|
||||
const ret = {};
|
||||
const ret = {};
|
||||
ret[paramMode] = mode;
|
||||
ret[paramConfig] = configFileName;
|
||||
ret[paramOverwriteProcessOnTarget] = !!parsedArgs[paramOverwriteProcessOnTarget];
|
||||
|
||||
return <ICommandLineOptions> ret;
|
||||
return <ICommandLineOptions>ret;
|
||||
}
|
||||
|
||||
export async function ProcessConfigurationFile(configFilename: string, mode: Modes): Promise<IConfigurationFile> {
|
||||
|
@ -56,8 +58,8 @@ export async function ProcessConfigurationFile(configFilename: string, mode: Mod
|
|||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const configuration = JSON.parse(await readFileSync(configFilename, defaultEncoding)) as IConfigurationFile;
|
||||
|
||||
const configuration = jsoncParse(readFileSync(configFilename, defaultEncoding)) as IConfigurationFile;
|
||||
if (!Utility.validateConfiguration(configuration, mode)) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env node
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { resolve, normalize } from "path";
|
||||
import { ProcesCommandLine, ProcessConfigurationFile } from "./ConfigurationProcessor";
|
||||
import { defaultEncoding, defaultProcessFilename } from "../common/Constants";
|
||||
import { ImportError, KnownError } from "../common/Errors";
|
||||
|
@ -40,7 +40,7 @@ async function main() {
|
|||
const exporter: ProcessExporter = new ProcessExporter(sourceRestClients, configuration);
|
||||
processPayload = await exporter.exportProcess();
|
||||
|
||||
const exportFilename = (configuration.options && configuration.options.processFilename) || defaultProcessFilename;
|
||||
const exportFilename = (configuration.options && configuration.options.processFilename) || normalize(defaultProcessFilename);
|
||||
await Engine.Task(() => NodeJsUtility.writeJsonToFile(exportFilename, processPayload), "Write process payload to file")
|
||||
logger.logInfo(`Export process completed successfully to '${resolve(exportFilename)}'.`);
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ async function main() {
|
|||
// Import
|
||||
if (mode === Modes.both || mode === Modes.import) {
|
||||
if (mode === Modes.import) { // Read payload from file instead
|
||||
const processFileName = (configuration.options && configuration.options.processFilename) || defaultProcessFilename;
|
||||
const processFileName = (configuration.options && configuration.options.processFilename) || normalize(defaultProcessFilename);
|
||||
if (!existsSync(processFileName)) {
|
||||
throw new ImportError(`Process payload file '${processFileName}' does not exist.`)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as vsts from "vso-node-api/WebApi";
|
||||
import { existsSync, writeFileSync } from "fs";
|
||||
import { dirname } from "path";
|
||||
import { dirname, normalize } from "path";
|
||||
import { sync as mkdirpSync } from "mkdirp";
|
||||
import * as readline from "readline";
|
||||
import { isFunction } from "util";
|
||||
|
@ -33,7 +33,7 @@ export class NodeJsUtility extends Utility {
|
|||
}
|
||||
|
||||
public static getLogFilePath(options: IConfigurationOptions): string {
|
||||
return options.logFilename ? options.logFilename : defaultLogFileName;
|
||||
return options.logFilename ? options.logFilename : normalize(defaultLogFileName);
|
||||
}
|
||||
|
||||
public static async getRestClients(accountUrl: string, PAT: string): Promise<IRestClients> {
|
||||
|
|
Загрузка…
Ссылка в новой задаче