@esri/solution-deployer 6.4.0-next.20250806 → 6.4.0-next.20250808
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.
- package/dist/cjs/deploySolutionFromTemplate.js +21 -0
- package/dist/cjs/deploySolutionItems.d.ts +1 -1
- package/dist/cjs/deploySolutionItems.js +37 -2
- package/dist/cjs/deployer.d.ts +1 -0
- package/dist/cjs/deployer.js +25 -1
- package/dist/esm/deploySolutionFromTemplate.js +21 -0
- package/dist/esm/deploySolutionItems.d.ts +1 -1
- package/dist/esm/deploySolutionItems.js +37 -2
- package/dist/esm/deployer.d.ts +1 -0
- package/dist/esm/deployer.js +23 -0
- package/package.json +14 -14
|
@@ -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
|
}
|
package/dist/cjs/deployer.d.ts
CHANGED
|
@@ -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;
|
package/dist/cjs/deployer.js
CHANGED
|
@@ -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
|
}
|
package/dist/esm/deployer.d.ts
CHANGED
|
@@ -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;
|
package/dist/esm/deployer.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "6.4.0-next.20250808",
|
|
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.
|
|
28
|
-
"@esri/solution-feature-layer": "^6.4.0-next.
|
|
29
|
-
"@esri/solution-file": "^6.4.0-next.
|
|
30
|
-
"@esri/solution-form": "^6.4.0-next.
|
|
31
|
-
"@esri/solution-group": "^6.4.0-next.
|
|
32
|
-
"@esri/solution-hub-types": "^6.4.0-next.
|
|
33
|
-
"@esri/solution-simple-types": "^6.4.0-next.
|
|
34
|
-
"@esri/solution-storymap": "^6.4.0-next.
|
|
35
|
-
"@esri/solution-velocity": "^6.4.0-next.
|
|
36
|
-
"@esri/solution-web-experience": "^6.4.0-next.
|
|
37
|
-
"@esri/solution-web-tool": "^6.4.0-next.
|
|
38
|
-
"@esri/solution-workflow": "^6.4.0-next.
|
|
27
|
+
"@esri/solution-common": "^6.4.0-next.20250808",
|
|
28
|
+
"@esri/solution-feature-layer": "^6.4.0-next.20250808",
|
|
29
|
+
"@esri/solution-file": "^6.4.0-next.20250808",
|
|
30
|
+
"@esri/solution-form": "^6.4.0-next.20250808",
|
|
31
|
+
"@esri/solution-group": "^6.4.0-next.20250808",
|
|
32
|
+
"@esri/solution-hub-types": "^6.4.0-next.20250808",
|
|
33
|
+
"@esri/solution-simple-types": "^6.4.0-next.20250808",
|
|
34
|
+
"@esri/solution-storymap": "^6.4.0-next.20250808",
|
|
35
|
+
"@esri/solution-velocity": "^6.4.0-next.20250808",
|
|
36
|
+
"@esri/solution-web-experience": "^6.4.0-next.20250808",
|
|
37
|
+
"@esri/solution-web-tool": "^6.4.0-next.20250808",
|
|
38
|
+
"@esri/solution-workflow": "^6.4.0-next.20250808",
|
|
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": "
|
|
93
|
+
"gitHead": "fc677f0f21a1a090d65743f08073eeed47e22ef0"
|
|
94
94
|
}
|