@esri/solution-deployer 6.4.0-next.20250806 → 6.4.0-next.20250807

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24,6 +24,19 @@ const post_process_1 = require("./helpers/post-process");
24
24
  const sortTemplates_1 = require("./helpers/sortTemplates");
25
25
  // NOTE: Moved to separate file to allow stubbing in main deploySolution tests
26
26
  async function deploySolutionFromTemplate(templateSolutionId, solutionTemplateBase, solutionTemplateData, authentication, options) {
27
+ /**
28
+ * function to abort the current process. Will delete solution and reject the promise
29
+ *
30
+ * @return a reject on the parent promise.
31
+ */
32
+ function checkCancelled() {
33
+ if (options && options.abortController) {
34
+ if (options.abortController.signal.aborted) {
35
+ throw new Error("Operation was cancelled");
36
+ }
37
+ }
38
+ }
39
+ checkCancelled();
27
40
  options.storageVersion = common.extractSolutionVersion(solutionTemplateData);
28
41
  // It is possible to provide a separate authentication for the source
29
42
  const storageAuthentication = options.storageAuthentication
@@ -75,6 +88,7 @@ async function deploySolutionFromTemplate(templateSolutionId, solutionTemplateBa
75
88
  templateDictionary.user = userResponse;
76
89
  templateDictionary.user.folders = foldersAndGroupsResponse.folders;
77
90
  templateDictionary.user.groups = foldersAndGroupsResponse.groups.filter((group) => group.owner === templateDictionary.user.username);
91
+ checkCancelled();
78
92
  // Add information needed for workflow manager
79
93
  const user = await common.getUser(authentication);
80
94
  templateDictionary.workflowBaseUrl = await common.getWorkflowBaseURL(authentication, portalResponse, user.orgId);
@@ -90,6 +104,7 @@ async function deploySolutionFromTemplate(templateSolutionId, solutionTemplateBa
90
104
  // Apply the portal extents to the solution
91
105
  const portalExtent = portalResponse.defaultExtent;
92
106
  const extentsPromise = common.convertExtentWithFallback(portalExtent, undefined, { wkid: 4326 }, portalResponse.helperServices.geometry.url, authentication);
107
+ checkCancelled();
93
108
  // Await completion of async actions: folder creation & extents conversion
94
109
  const folderExtentsResponses = await Promise.all([folderPromise, extentsPromise, trackingOwnerPromise]);
95
110
  const [folderResponse, wgs84Extent, trackingOwnerResponse] = folderExtentsResponses;
@@ -120,10 +135,12 @@ async function deploySolutionFromTemplate(templateSolutionId, solutionTemplateBa
120
135
  if (options.additionalTypeKeywords) {
121
136
  createSolutionItemBase.typeKeywords = ["Solution"].concat(options.additionalTypeKeywords);
122
137
  }
138
+ checkCancelled();
123
139
  // Create deployed solution item
124
140
  createSolutionItemBase.thumbnail = options.thumbnail;
125
141
  const createSolutionResponse = await common.createItemWithData(createSolutionItemBase, {}, authentication, deployedFolderId);
126
142
  deployedSolutionId = createSolutionResponse.id;
143
+ checkCancelled();
127
144
  // Protect the solution item
128
145
  const protectOptions = {
129
146
  id: deployedSolutionId,
@@ -135,9 +152,11 @@ async function deploySolutionFromTemplate(templateSolutionId, solutionTemplateBa
135
152
  solutionTemplateBase.tryitUrl = _checkedReplaceAll(solutionTemplateBase.tryitUrl, templateSolutionId, deployedSolutionId);
136
153
  solutionTemplateBase.url = _checkedReplaceAll(solutionTemplateBase.url, templateSolutionId, deployedSolutionId);
137
154
  }
155
+ checkCancelled();
138
156
  // Handle the contained item templates
139
157
  const clonedSolutionsResponse = await deployItems.deploySolutionItems(storageAuthentication.portal, templateSolutionId, solutionTemplateData.templates, storageAuthentication, templateDictionary, deployedSolutionId, authentication, options);
140
158
  solutionTemplateData.templates = solutionTemplateData.templates.map((itemTemplate) => {
159
+ checkCancelled();
141
160
  // Update ids present in template dictionary
142
161
  itemTemplate.itemId = common.getProp(templateDictionary, `${itemTemplate.itemId}.itemId`);
143
162
  // Update the dependencies hash to point to the new item ids
@@ -146,6 +165,7 @@ async function deploySolutionFromTemplate(templateSolutionId, solutionTemplateBa
146
165
  });
147
166
  // Sort the templates into build order, which is provided by clonedSolutionsResponse
148
167
  (0, sortTemplates_1.sortTemplates)(solutionTemplateData.templates, clonedSolutionsResponse.map((response) => response.id));
168
+ checkCancelled();
149
169
  // Wrap up with post-processing, in which we deal with groups and cycle remnants
150
170
  await (0, post_process_1.postProcess)(deployedSolutionId, solutionTemplateData.templates, clonedSolutionsResponse, authentication, templateDictionary);
151
171
  if (!options.dontCreateSolutionItem) {
@@ -168,6 +188,7 @@ async function deploySolutionFromTemplate(templateSolutionId, solutionTemplateBa
168
188
  if (templateDictionary.params) {
169
189
  solutionTemplateBase.data.params = templateDictionary.params;
170
190
  }
191
+ checkCancelled();
171
192
  await common.updateItem(solutionTemplateBase, authentication, deployedFolderId);
172
193
  }
173
194
  return solutionTemplateBase.id;
@@ -212,7 +212,7 @@ export declare function _findExistingItem(query: string, authentication: common.
212
212
  * already in the templates list
213
213
  * @private
214
214
  */
215
- export declare function _createItemFromTemplateWhenReady(template: common.IItemTemplate, resourceFilePaths: common.IDeployFileCopyPath[], storageAuthentication: common.UserSession, templateDictionary: any, destinationAuthentication: common.UserSession, itemProgressCallback: common.IItemProgressCallback): Promise<common.ICreateItemFromTemplateResponse>;
215
+ export declare function _createItemFromTemplateWhenReady(template: common.IItemTemplate, resourceFilePaths: common.IDeployFileCopyPath[], storageAuthentication: common.UserSession, templateDictionary: any, destinationAuthentication: common.UserSession, itemProgressCallback: common.IItemProgressCallback, abortController?: AbortController): Promise<common.ICreateItemFromTemplateResponse>;
216
216
  /**
217
217
  * Accumulates the estimated deployment cost of a set of templates.
218
218
  *
@@ -41,6 +41,18 @@ const UNSUPPORTED = null;
41
41
  */
42
42
  function deploySolutionItems(portalSharingUrl, storageItemId, templates, storageAuthentication, templateDictionary, deployedSolutionId, destinationAuthentication, options) {
43
43
  return new Promise((resolve, reject) => {
44
+ /**
45
+ * function to abort the current process. Will delete solution and reject the promise
46
+ *
47
+ * @return a reject on the parent promise.
48
+ */
49
+ function checkCancelled() {
50
+ if (options && options.abortController) {
51
+ if (options.abortController.signal.aborted) {
52
+ reject(new Error(`Operation was cancelled`));
53
+ }
54
+ }
55
+ }
44
56
  // Prepare feedback mechanism
45
57
  const totalEstimatedCost = _estimateDeploymentCost(templates) + 1; // solution items, plus avoid divide by 0
46
58
  let percentDone = 10; // allow for previous deployment work
@@ -48,6 +60,7 @@ function deploySolutionItems(portalSharingUrl, storageItemId, templates, storage
48
60
  const failedTemplateItemIds = [];
49
61
  const deployedItemIds = [];
50
62
  let statusOK = true;
63
+ checkCancelled();
51
64
  // TODO: move to separate fn
52
65
  const itemProgressCallback = (itemId, status, costUsed, createdItemId) => {
53
66
  percentDone += progressPercentStep * costUsed;
@@ -92,12 +105,15 @@ function deploySolutionItems(portalSharingUrl, storageItemId, templates, storage
92
105
  // * create item in destination group
93
106
  // * add created item's id into the template dictionary
94
107
  const awaitAllItems = [];
108
+ checkCancelled();
95
109
  const reuseItemsDef = _reuseDeployedItems(templates, options.enableItemReuse ?? false, templateDictionary, destinationAuthentication);
96
110
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
97
111
  reuseItemsDef.then(() => {
112
+ checkCancelled();
98
113
  const useExistingItemsDef = _useExistingItems(templates, common.getProp(templateDictionary, "params.useExisting"), templateDictionary, destinationAuthentication);
99
114
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
100
115
  useExistingItemsDef.then(() => {
116
+ checkCancelled();
101
117
  templates = common.setNamesAndTitles(templates);
102
118
  buildOrder.forEach((id) => {
103
119
  // Get the item's template out of the list of templates
@@ -110,11 +126,12 @@ function deploySolutionItems(portalSharingUrl, storageItemId, templates, storage
110
126
  // Remove qc.project.json files from the resources--we don't use them from solutions
111
127
  template.resources = template.resources.filter((filename) => !filename.endsWith("qc.project.json"));
112
128
  }
113
- awaitAllItems.push(_createItemFromTemplateWhenReady(template, common.generateStorageFilePaths(portalSharingUrl, storageItemId, template.resources, options.storageVersion), storageAuthentication, templateDictionary, destinationAuthentication, itemProgressCallback));
129
+ awaitAllItems.push(_createItemFromTemplateWhenReady(template, common.generateStorageFilePaths(portalSharingUrl, storageItemId, template.resources, options.storageVersion), storageAuthentication, templateDictionary, destinationAuthentication, itemProgressCallback, options.abortController));
114
130
  });
115
131
  // Wait until all items have been created
116
132
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
117
133
  Promise.all(awaitAllItems).then((clonedSolutionItems) => {
134
+ checkCancelled();
118
135
  if (failedTemplateItemIds.length === 0) {
119
136
  // Do we have any items to be patched (i.e., they refer to dependencies using the template id rather
120
137
  // than the cloned id because the item had to be created before the dependency)? Flag these items
@@ -736,7 +753,19 @@ exports._findExistingItem = _findExistingItem;
736
753
  * already in the templates list
737
754
  * @private
738
755
  */
739
- function _createItemFromTemplateWhenReady(template, resourceFilePaths, storageAuthentication, templateDictionary, destinationAuthentication, itemProgressCallback) {
756
+ function _createItemFromTemplateWhenReady(template, resourceFilePaths, storageAuthentication, templateDictionary, destinationAuthentication, itemProgressCallback, abortController) {
757
+ /**
758
+ * function to abort the current process. Will delete solution and reject the promise
759
+ *
760
+ * @return a reject on the parent promise.
761
+ */
762
+ function checkCancelled() {
763
+ if (abortController) {
764
+ if (abortController.signal.aborted) {
765
+ throw new Error(`Operation was cancelled`);
766
+ }
767
+ }
768
+ }
740
769
  const sourceItemId = template.itemId;
741
770
  // ensure this is present
742
771
  template.dependencies = template.dependencies || [];
@@ -744,6 +773,7 @@ function _createItemFromTemplateWhenReady(template, resourceFilePaths, storageAu
744
773
  // or if we have a basic entry without the deferred request for its creation, add it
745
774
  if (!templateDictionary.hasOwnProperty(template.itemId) ||
746
775
  !common.getProp(templateDictionary[template.itemId], "def")) {
776
+ checkCancelled();
747
777
  let createResponse;
748
778
  let statusCode = common.EItemProgressStatus.Unknown;
749
779
  let itemHandler;
@@ -773,6 +803,7 @@ function _createItemFromTemplateWhenReady(template, resourceFilePaths, storageAu
773
803
  : _awaitDependencies;
774
804
  Promise.all(awaitDependencies)
775
805
  .then(() => {
806
+ checkCancelled();
776
807
  // Find the conversion handler for this item type
777
808
  const templateType = template.type;
778
809
  itemHandler = module_map_1.moduleMap[templateType];
@@ -793,12 +824,14 @@ function _createItemFromTemplateWhenReady(template, resourceFilePaths, storageAu
793
824
  return common.getThumbnailFromStorageItem(storageAuthentication, resourceFilePaths);
794
825
  })
795
826
  .then((thumbnail) => {
827
+ checkCancelled();
796
828
  template.item.thumbnail = thumbnail;
797
829
  // Delegate the creation of the item to the handler
798
830
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
799
831
  return itemHandler.createItemFromTemplate(template, templateDictionary, destinationAuthentication, itemProgressCallback);
800
832
  })
801
833
  .then(async (response) => {
834
+ checkCancelled();
802
835
  if (response.id === "") {
803
836
  statusCode = common.EItemProgressStatus.Failed;
804
837
  throw new Error("handled"); // fails to create item
@@ -824,11 +857,13 @@ function _createItemFromTemplateWhenReady(template, resourceFilePaths, storageAu
824
857
  }
825
858
  });
826
859
  if (formZipFilePath) {
860
+ checkCancelled();
827
861
  // Fetch the form's zip file and send it to the item
828
862
  const zipObject = await common.fetchZipObject(formZipFilePath.url, storageAuthentication);
829
863
  await common.updateItemWithZipObject(zipObject, destinationItemId, destinationAuthentication);
830
864
  }
831
865
  }
866
+ checkCancelled();
832
867
  // Copy resources, metadata
833
868
  return common.copyFilesFromStorageItem(storageAuthentication, resourceFilePaths, sourceItemId, templateDictionary.folderId, destinationItemId, destinationAuthentication, createResponse.item, templateDictionary);
834
869
  }
@@ -32,3 +32,4 @@ import { IModel } from "@esri/hub-common";
32
32
  * @returns The id of the created deployed solution item
33
33
  */
34
34
  export declare function deploySolution(maybeModel: string | IModel, authentication: common.UserSession, options?: common.IDeploySolutionOptions): Promise<string>;
35
+ export declare function deployCatchHandler(ex: any, authentication: common.UserSession): void;
@@ -15,7 +15,7 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
- exports.deploySolution = void 0;
18
+ exports.deployCatchHandler = exports.deploySolution = void 0;
19
19
  const tslib_1 = require("tslib");
20
20
  /**
21
21
  * Manages the deployment of a Solution.
@@ -41,6 +41,18 @@ async function deploySolution(maybeModel, authentication, options) {
41
41
  if (!maybeModel) {
42
42
  return Promise.reject(common.fail("The Solution Template id is missing"));
43
43
  }
44
+ /**
45
+ * function to abort the current process. Will delete solution and reject the promise
46
+ *
47
+ * @return a reject on the parent promise.
48
+ */
49
+ function checkCancelled() {
50
+ if (options && options.abortController) {
51
+ if (options.abortController.signal.aborted) {
52
+ throw new Error("Operation was cancelled");
53
+ }
54
+ }
55
+ }
44
56
  let deployOptions = options || {};
45
57
  /* istanbul ignore else */
46
58
  if (deployOptions.progressCallback) {
@@ -50,9 +62,11 @@ async function deploySolution(maybeModel, authentication, options) {
50
62
  const storageAuthentication = deployOptions.storageAuthentication
51
63
  ? deployOptions.storageAuthentication
52
64
  : authentication;
65
+ void checkCancelled();
53
66
  // deal with maybe getting an item or an id
54
67
  return (0, deployerUtils_1.getSolutionTemplateItem)(maybeModel, storageAuthentication)
55
68
  .then((model) => {
69
+ void checkCancelled();
56
70
  if (!(0, deployerUtils_1.isSolutionTemplateItem)(model.item)) {
57
71
  return Promise.reject(common.fail(`${model.item.id} is not a Solution Template`));
58
72
  }
@@ -75,6 +89,7 @@ async function deploySolution(maybeModel, authentication, options) {
75
89
  deployOptions = (0, deployerUtils_1.updateDeployOptions)(deployOptions, item, storageAuthentication);
76
90
  // Clone before mutating? This was messing me up in some testing...
77
91
  common.deleteItemProps(item);
92
+ checkCancelled();
78
93
  return (0, deploySolutionFromTemplate_1.deploySolutionFromTemplate)(itemId, item, data, authentication, deployOptions);
79
94
  })
80
95
  .then((createdSolutionId) => {
@@ -92,7 +107,16 @@ async function deploySolution(maybeModel, authentication, options) {
92
107
  return Promise.reject(error);
93
108
  })
94
109
  .catch((ex) => {
110
+ deployCatchHandler(ex, authentication);
95
111
  throw ex;
96
112
  });
97
113
  }
98
114
  exports.deploySolution = deploySolution;
115
+ function deployCatchHandler(ex, authentication) {
116
+ const options = {
117
+ consoleProgress: true,
118
+ sendToRecycling: false,
119
+ };
120
+ void common.deleteSolution(ex.trim(), authentication, options);
121
+ }
122
+ exports.deployCatchHandler = deployCatchHandler;
@@ -20,6 +20,19 @@ import { postProcess } from "./helpers/post-process";
20
20
  import { sortTemplates } from "./helpers/sortTemplates";
21
21
  // NOTE: Moved to separate file to allow stubbing in main deploySolution tests
22
22
  export async function deploySolutionFromTemplate(templateSolutionId, solutionTemplateBase, solutionTemplateData, authentication, options) {
23
+ /**
24
+ * function to abort the current process. Will delete solution and reject the promise
25
+ *
26
+ * @return a reject on the parent promise.
27
+ */
28
+ function checkCancelled() {
29
+ if (options && options.abortController) {
30
+ if (options.abortController.signal.aborted) {
31
+ throw new Error("Operation was cancelled");
32
+ }
33
+ }
34
+ }
35
+ checkCancelled();
23
36
  options.storageVersion = common.extractSolutionVersion(solutionTemplateData);
24
37
  // It is possible to provide a separate authentication for the source
25
38
  const storageAuthentication = options.storageAuthentication
@@ -71,6 +84,7 @@ export async function deploySolutionFromTemplate(templateSolutionId, solutionTem
71
84
  templateDictionary.user = userResponse;
72
85
  templateDictionary.user.folders = foldersAndGroupsResponse.folders;
73
86
  templateDictionary.user.groups = foldersAndGroupsResponse.groups.filter((group) => group.owner === templateDictionary.user.username);
87
+ checkCancelled();
74
88
  // Add information needed for workflow manager
75
89
  const user = await common.getUser(authentication);
76
90
  templateDictionary.workflowBaseUrl = await common.getWorkflowBaseURL(authentication, portalResponse, user.orgId);
@@ -86,6 +100,7 @@ export async function deploySolutionFromTemplate(templateSolutionId, solutionTem
86
100
  // Apply the portal extents to the solution
87
101
  const portalExtent = portalResponse.defaultExtent;
88
102
  const extentsPromise = common.convertExtentWithFallback(portalExtent, undefined, { wkid: 4326 }, portalResponse.helperServices.geometry.url, authentication);
103
+ checkCancelled();
89
104
  // Await completion of async actions: folder creation & extents conversion
90
105
  const folderExtentsResponses = await Promise.all([folderPromise, extentsPromise, trackingOwnerPromise]);
91
106
  const [folderResponse, wgs84Extent, trackingOwnerResponse] = folderExtentsResponses;
@@ -116,10 +131,12 @@ export async function deploySolutionFromTemplate(templateSolutionId, solutionTem
116
131
  if (options.additionalTypeKeywords) {
117
132
  createSolutionItemBase.typeKeywords = ["Solution"].concat(options.additionalTypeKeywords);
118
133
  }
134
+ checkCancelled();
119
135
  // Create deployed solution item
120
136
  createSolutionItemBase.thumbnail = options.thumbnail;
121
137
  const createSolutionResponse = await common.createItemWithData(createSolutionItemBase, {}, authentication, deployedFolderId);
122
138
  deployedSolutionId = createSolutionResponse.id;
139
+ checkCancelled();
123
140
  // Protect the solution item
124
141
  const protectOptions = {
125
142
  id: deployedSolutionId,
@@ -131,9 +148,11 @@ export async function deploySolutionFromTemplate(templateSolutionId, solutionTem
131
148
  solutionTemplateBase.tryitUrl = _checkedReplaceAll(solutionTemplateBase.tryitUrl, templateSolutionId, deployedSolutionId);
132
149
  solutionTemplateBase.url = _checkedReplaceAll(solutionTemplateBase.url, templateSolutionId, deployedSolutionId);
133
150
  }
151
+ checkCancelled();
134
152
  // Handle the contained item templates
135
153
  const clonedSolutionsResponse = await deployItems.deploySolutionItems(storageAuthentication.portal, templateSolutionId, solutionTemplateData.templates, storageAuthentication, templateDictionary, deployedSolutionId, authentication, options);
136
154
  solutionTemplateData.templates = solutionTemplateData.templates.map((itemTemplate) => {
155
+ checkCancelled();
137
156
  // Update ids present in template dictionary
138
157
  itemTemplate.itemId = common.getProp(templateDictionary, `${itemTemplate.itemId}.itemId`);
139
158
  // Update the dependencies hash to point to the new item ids
@@ -142,6 +161,7 @@ export async function deploySolutionFromTemplate(templateSolutionId, solutionTem
142
161
  });
143
162
  // Sort the templates into build order, which is provided by clonedSolutionsResponse
144
163
  sortTemplates(solutionTemplateData.templates, clonedSolutionsResponse.map((response) => response.id));
164
+ checkCancelled();
145
165
  // Wrap up with post-processing, in which we deal with groups and cycle remnants
146
166
  await postProcess(deployedSolutionId, solutionTemplateData.templates, clonedSolutionsResponse, authentication, templateDictionary);
147
167
  if (!options.dontCreateSolutionItem) {
@@ -164,6 +184,7 @@ export async function deploySolutionFromTemplate(templateSolutionId, solutionTem
164
184
  if (templateDictionary.params) {
165
185
  solutionTemplateBase.data.params = templateDictionary.params;
166
186
  }
187
+ checkCancelled();
167
188
  await common.updateItem(solutionTemplateBase, authentication, deployedFolderId);
168
189
  }
169
190
  return solutionTemplateBase.id;
@@ -212,7 +212,7 @@ export declare function _findExistingItem(query: string, authentication: common.
212
212
  * already in the templates list
213
213
  * @private
214
214
  */
215
- export declare function _createItemFromTemplateWhenReady(template: common.IItemTemplate, resourceFilePaths: common.IDeployFileCopyPath[], storageAuthentication: common.UserSession, templateDictionary: any, destinationAuthentication: common.UserSession, itemProgressCallback: common.IItemProgressCallback): Promise<common.ICreateItemFromTemplateResponse>;
215
+ export declare function _createItemFromTemplateWhenReady(template: common.IItemTemplate, resourceFilePaths: common.IDeployFileCopyPath[], storageAuthentication: common.UserSession, templateDictionary: any, destinationAuthentication: common.UserSession, itemProgressCallback: common.IItemProgressCallback, abortController?: AbortController): Promise<common.ICreateItemFromTemplateResponse>;
216
216
  /**
217
217
  * Accumulates the estimated deployment cost of a set of templates.
218
218
  *
@@ -37,6 +37,18 @@ const UNSUPPORTED = null;
37
37
  */
38
38
  export function deploySolutionItems(portalSharingUrl, storageItemId, templates, storageAuthentication, templateDictionary, deployedSolutionId, destinationAuthentication, options) {
39
39
  return new Promise((resolve, reject) => {
40
+ /**
41
+ * function to abort the current process. Will delete solution and reject the promise
42
+ *
43
+ * @return a reject on the parent promise.
44
+ */
45
+ function checkCancelled() {
46
+ if (options && options.abortController) {
47
+ if (options.abortController.signal.aborted) {
48
+ reject(new Error(`Operation was cancelled`));
49
+ }
50
+ }
51
+ }
40
52
  // Prepare feedback mechanism
41
53
  const totalEstimatedCost = _estimateDeploymentCost(templates) + 1; // solution items, plus avoid divide by 0
42
54
  let percentDone = 10; // allow for previous deployment work
@@ -44,6 +56,7 @@ export function deploySolutionItems(portalSharingUrl, storageItemId, templates,
44
56
  const failedTemplateItemIds = [];
45
57
  const deployedItemIds = [];
46
58
  let statusOK = true;
59
+ checkCancelled();
47
60
  // TODO: move to separate fn
48
61
  const itemProgressCallback = (itemId, status, costUsed, createdItemId) => {
49
62
  percentDone += progressPercentStep * costUsed;
@@ -88,12 +101,15 @@ export function deploySolutionItems(portalSharingUrl, storageItemId, templates,
88
101
  // * create item in destination group
89
102
  // * add created item's id into the template dictionary
90
103
  const awaitAllItems = [];
104
+ checkCancelled();
91
105
  const reuseItemsDef = _reuseDeployedItems(templates, options.enableItemReuse ?? false, templateDictionary, destinationAuthentication);
92
106
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
93
107
  reuseItemsDef.then(() => {
108
+ checkCancelled();
94
109
  const useExistingItemsDef = _useExistingItems(templates, common.getProp(templateDictionary, "params.useExisting"), templateDictionary, destinationAuthentication);
95
110
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
96
111
  useExistingItemsDef.then(() => {
112
+ checkCancelled();
97
113
  templates = common.setNamesAndTitles(templates);
98
114
  buildOrder.forEach((id) => {
99
115
  // Get the item's template out of the list of templates
@@ -106,11 +122,12 @@ export function deploySolutionItems(portalSharingUrl, storageItemId, templates,
106
122
  // Remove qc.project.json files from the resources--we don't use them from solutions
107
123
  template.resources = template.resources.filter((filename) => !filename.endsWith("qc.project.json"));
108
124
  }
109
- awaitAllItems.push(_createItemFromTemplateWhenReady(template, common.generateStorageFilePaths(portalSharingUrl, storageItemId, template.resources, options.storageVersion), storageAuthentication, templateDictionary, destinationAuthentication, itemProgressCallback));
125
+ awaitAllItems.push(_createItemFromTemplateWhenReady(template, common.generateStorageFilePaths(portalSharingUrl, storageItemId, template.resources, options.storageVersion), storageAuthentication, templateDictionary, destinationAuthentication, itemProgressCallback, options.abortController));
110
126
  });
111
127
  // Wait until all items have been created
112
128
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
113
129
  Promise.all(awaitAllItems).then((clonedSolutionItems) => {
130
+ checkCancelled();
114
131
  if (failedTemplateItemIds.length === 0) {
115
132
  // Do we have any items to be patched (i.e., they refer to dependencies using the template id rather
116
133
  // than the cloned id because the item had to be created before the dependency)? Flag these items
@@ -716,7 +733,19 @@ export function _findExistingItem(query, authentication) {
716
733
  * already in the templates list
717
734
  * @private
718
735
  */
719
- export function _createItemFromTemplateWhenReady(template, resourceFilePaths, storageAuthentication, templateDictionary, destinationAuthentication, itemProgressCallback) {
736
+ export function _createItemFromTemplateWhenReady(template, resourceFilePaths, storageAuthentication, templateDictionary, destinationAuthentication, itemProgressCallback, abortController) {
737
+ /**
738
+ * function to abort the current process. Will delete solution and reject the promise
739
+ *
740
+ * @return a reject on the parent promise.
741
+ */
742
+ function checkCancelled() {
743
+ if (abortController) {
744
+ if (abortController.signal.aborted) {
745
+ throw new Error(`Operation was cancelled`);
746
+ }
747
+ }
748
+ }
720
749
  const sourceItemId = template.itemId;
721
750
  // ensure this is present
722
751
  template.dependencies = template.dependencies || [];
@@ -724,6 +753,7 @@ export function _createItemFromTemplateWhenReady(template, resourceFilePaths, st
724
753
  // or if we have a basic entry without the deferred request for its creation, add it
725
754
  if (!templateDictionary.hasOwnProperty(template.itemId) ||
726
755
  !common.getProp(templateDictionary[template.itemId], "def")) {
756
+ checkCancelled();
727
757
  let createResponse;
728
758
  let statusCode = common.EItemProgressStatus.Unknown;
729
759
  let itemHandler;
@@ -753,6 +783,7 @@ export function _createItemFromTemplateWhenReady(template, resourceFilePaths, st
753
783
  : _awaitDependencies;
754
784
  Promise.all(awaitDependencies)
755
785
  .then(() => {
786
+ checkCancelled();
756
787
  // Find the conversion handler for this item type
757
788
  const templateType = template.type;
758
789
  itemHandler = moduleMap[templateType];
@@ -773,12 +804,14 @@ export function _createItemFromTemplateWhenReady(template, resourceFilePaths, st
773
804
  return common.getThumbnailFromStorageItem(storageAuthentication, resourceFilePaths);
774
805
  })
775
806
  .then((thumbnail) => {
807
+ checkCancelled();
776
808
  template.item.thumbnail = thumbnail;
777
809
  // Delegate the creation of the item to the handler
778
810
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
779
811
  return itemHandler.createItemFromTemplate(template, templateDictionary, destinationAuthentication, itemProgressCallback);
780
812
  })
781
813
  .then(async (response) => {
814
+ checkCancelled();
782
815
  if (response.id === "") {
783
816
  statusCode = common.EItemProgressStatus.Failed;
784
817
  throw new Error("handled"); // fails to create item
@@ -804,11 +837,13 @@ export function _createItemFromTemplateWhenReady(template, resourceFilePaths, st
804
837
  }
805
838
  });
806
839
  if (formZipFilePath) {
840
+ checkCancelled();
807
841
  // Fetch the form's zip file and send it to the item
808
842
  const zipObject = await common.fetchZipObject(formZipFilePath.url, storageAuthentication);
809
843
  await common.updateItemWithZipObject(zipObject, destinationItemId, destinationAuthentication);
810
844
  }
811
845
  }
846
+ checkCancelled();
812
847
  // Copy resources, metadata
813
848
  return common.copyFilesFromStorageItem(storageAuthentication, resourceFilePaths, sourceItemId, templateDictionary.folderId, destinationItemId, destinationAuthentication, createResponse.item, templateDictionary);
814
849
  }
@@ -32,3 +32,4 @@ import { IModel } from "@esri/hub-common";
32
32
  * @returns The id of the created deployed solution item
33
33
  */
34
34
  export declare function deploySolution(maybeModel: string | IModel, authentication: common.UserSession, options?: common.IDeploySolutionOptions): Promise<string>;
35
+ export declare function deployCatchHandler(ex: any, authentication: common.UserSession): void;
@@ -37,6 +37,18 @@ export async function deploySolution(maybeModel, authentication, options) {
37
37
  if (!maybeModel) {
38
38
  return Promise.reject(common.fail("The Solution Template id is missing"));
39
39
  }
40
+ /**
41
+ * function to abort the current process. Will delete solution and reject the promise
42
+ *
43
+ * @return a reject on the parent promise.
44
+ */
45
+ function checkCancelled() {
46
+ if (options && options.abortController) {
47
+ if (options.abortController.signal.aborted) {
48
+ throw new Error("Operation was cancelled");
49
+ }
50
+ }
51
+ }
40
52
  let deployOptions = options || {};
41
53
  /* istanbul ignore else */
42
54
  if (deployOptions.progressCallback) {
@@ -46,9 +58,11 @@ export async function deploySolution(maybeModel, authentication, options) {
46
58
  const storageAuthentication = deployOptions.storageAuthentication
47
59
  ? deployOptions.storageAuthentication
48
60
  : authentication;
61
+ void checkCancelled();
49
62
  // deal with maybe getting an item or an id
50
63
  return getSolutionTemplateItem(maybeModel, storageAuthentication)
51
64
  .then((model) => {
65
+ void checkCancelled();
52
66
  if (!isSolutionTemplateItem(model.item)) {
53
67
  return Promise.reject(common.fail(`${model.item.id} is not a Solution Template`));
54
68
  }
@@ -71,6 +85,7 @@ export async function deploySolution(maybeModel, authentication, options) {
71
85
  deployOptions = updateDeployOptions(deployOptions, item, storageAuthentication);
72
86
  // Clone before mutating? This was messing me up in some testing...
73
87
  common.deleteItemProps(item);
88
+ checkCancelled();
74
89
  return deploySolutionFromTemplate(itemId, item, data, authentication, deployOptions);
75
90
  })
76
91
  .then((createdSolutionId) => {
@@ -88,6 +103,14 @@ export async function deploySolution(maybeModel, authentication, options) {
88
103
  return Promise.reject(error);
89
104
  })
90
105
  .catch((ex) => {
106
+ deployCatchHandler(ex, authentication);
91
107
  throw ex;
92
108
  });
93
109
  }
110
+ export function deployCatchHandler(ex, authentication) {
111
+ const options = {
112
+ consoleProgress: true,
113
+ sendToRecycling: false,
114
+ };
115
+ void common.deleteSolution(ex.trim(), authentication, options);
116
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@esri/solution-deployer",
3
- "version": "6.4.0-next.20250806",
3
+ "version": "6.4.0-next.20250807",
4
4
  "description": "Manages the deployment of a Solution for @esri/solution.js.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -24,18 +24,18 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@esri/hub-common": "^17.0.2",
27
- "@esri/solution-common": "^6.4.0-next.20250806",
28
- "@esri/solution-feature-layer": "^6.4.0-next.20250806",
29
- "@esri/solution-file": "^6.4.0-next.20250806",
30
- "@esri/solution-form": "^6.4.0-next.20250806",
31
- "@esri/solution-group": "^6.4.0-next.20250806",
32
- "@esri/solution-hub-types": "^6.4.0-next.20250806",
33
- "@esri/solution-simple-types": "^6.4.0-next.20250806",
34
- "@esri/solution-storymap": "^6.4.0-next.20250806",
35
- "@esri/solution-velocity": "^6.4.0-next.20250806",
36
- "@esri/solution-web-experience": "^6.4.0-next.20250806",
37
- "@esri/solution-web-tool": "^6.4.0-next.20250806",
38
- "@esri/solution-workflow": "^6.4.0-next.20250806",
27
+ "@esri/solution-common": "^6.4.0-next.20250807",
28
+ "@esri/solution-feature-layer": "^6.4.0-next.20250807",
29
+ "@esri/solution-file": "^6.4.0-next.20250807",
30
+ "@esri/solution-form": "^6.4.0-next.20250807",
31
+ "@esri/solution-group": "^6.4.0-next.20250807",
32
+ "@esri/solution-hub-types": "^6.4.0-next.20250807",
33
+ "@esri/solution-simple-types": "^6.4.0-next.20250807",
34
+ "@esri/solution-storymap": "^6.4.0-next.20250807",
35
+ "@esri/solution-velocity": "^6.4.0-next.20250807",
36
+ "@esri/solution-web-experience": "^6.4.0-next.20250807",
37
+ "@esri/solution-web-tool": "^6.4.0-next.20250807",
38
+ "@esri/solution-workflow": "^6.4.0-next.20250807",
39
39
  "tslib": "1.14.1"
40
40
  },
41
41
  "scripts": {
@@ -90,5 +90,5 @@
90
90
  "esri",
91
91
  "ES6"
92
92
  ],
93
- "gitHead": "231a47edad8afb71cb1d33e6a1b83679ec428193"
93
+ "gitHead": "fc677f0f21a1a090d65743f08073eeed47e22ef0"
94
94
  }