@pipedream/sharepoint 0.7.2 → 0.8.1

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.
Files changed (30) hide show
  1. package/actions/create-folder/create-folder.mjs +1 -1
  2. package/actions/create-item/create-item.mjs +1 -1
  3. package/actions/create-link/create-link.mjs +1 -1
  4. package/actions/create-list/create-list.mjs +1 -1
  5. package/actions/download-file/download-file.mjs +1 -1
  6. package/actions/download-files/download-files.mjs +88 -0
  7. package/actions/find-file-by-name/find-file-by-name.mjs +1 -1
  8. package/actions/find-files-with-metadata/find-files-with-metadata.mjs +1 -1
  9. package/actions/get-excel-table/get-excel-table.mjs +1 -1
  10. package/actions/get-file-by-id/get-file-by-id.mjs +1 -1
  11. package/actions/get-site/get-site.mjs +1 -1
  12. package/actions/list-files-in-folder/list-files-in-folder.mjs +1 -1
  13. package/actions/list-sites/list-sites.mjs +1 -1
  14. package/actions/retrieve-file-metadata/retrieve-file-metadata.mjs +55 -0
  15. package/actions/search-and-filter-files/search-and-filter-files.mjs +1 -1
  16. package/actions/search-files/search-files.mjs +1 -1
  17. package/actions/search-sites/search-sites.mjs +1 -1
  18. package/actions/update-item/update-item.mjs +1 -1
  19. package/actions/upload-file/upload-file.mjs +1 -1
  20. package/common/constants.mjs +23 -0
  21. package/common/file-picker-base.mjs +308 -0
  22. package/common/utils.mjs +78 -0
  23. package/package.json +5 -2
  24. package/sharepoint.app.mjs +400 -3
  25. package/sources/new-file-created/new-file-created.mjs +1 -1
  26. package/sources/new-folder-created/new-folder-created.mjs +1 -1
  27. package/sources/new-list-item/new-list-item.mjs +1 -1
  28. package/sources/updated-file-instant/updated-file-instant.mjs +407 -0
  29. package/sources/updated-list-item/updated-list-item.mjs +1 -1
  30. package/actions/select-files/select-files.mjs +0 -198
@@ -4,7 +4,7 @@ export default {
4
4
  key: "sharepoint-create-folder",
5
5
  name: "Create Folder",
6
6
  description: "Create a new folder in SharePoint. [See the documentation](https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_post_children?view=odsp-graph-online)",
7
- version: "0.0.4",
7
+ version: "0.0.5",
8
8
  type: "action",
9
9
  annotations: {
10
10
  destructiveHint: false,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "sharepoint-create-item",
5
5
  name: "Create Item",
6
6
  description: "Create a new item in Microsoft Sharepoint. [See the documentation](https://learn.microsoft.com/en-us/graph/api/listitem-create?view=graph-rest-1.0&tabs=http)",
7
- version: "0.0.10",
7
+ version: "0.0.11",
8
8
  annotations: {
9
9
  destructiveHint: false,
10
10
  openWorldHint: true,
@@ -5,7 +5,7 @@ export default {
5
5
  key: "sharepoint-create-link",
6
6
  name: "Create Link",
7
7
  description: "Create a sharing link for a DriveItem. [See the documentation](https://docs.microsoft.com/en-us/graph/api/driveitem-createlink?view=graph-rest-1.0&tabs=http)",
8
- version: "0.0.6",
8
+ version: "0.0.7",
9
9
  type: "action",
10
10
  annotations: {
11
11
  destructiveHint: false,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "sharepoint-create-list",
5
5
  name: "Create List",
6
6
  description: "Create a new list in Microsoft Sharepoint. [See the documentation](https://learn.microsoft.com/en-us/graph/api/list-create?view=graph-rest-1.0&tabs=http)",
7
- version: "0.0.10",
7
+ version: "0.0.11",
8
8
  annotations: {
9
9
  destructiveHint: false,
10
10
  openWorldHint: true,
@@ -8,7 +8,7 @@ export default {
8
8
  key: "sharepoint-download-file",
9
9
  name: "Download File",
10
10
  description: "Download a Microsoft Sharepoint file to the /tmp directory. [See the documentation](https://learn.microsoft.com/en-us/graph/api/driveitem-get-content?view=graph-rest-1.0&tabs=http)",
11
- version: "0.0.11",
11
+ version: "0.0.12",
12
12
  annotations: {
13
13
  destructiveHint: false,
14
14
  openWorldHint: true,
@@ -0,0 +1,88 @@
1
+ import sharepoint from "../../sharepoint.app.mjs";
2
+ import utils from "../../common/utils.mjs";
3
+ import { filePickerMethods } from "../../common/file-picker-base.mjs";
4
+
5
+ export default {
6
+ key: "sharepoint-download-files",
7
+ name: "Download Files",
8
+ description: "Browse and select files from SharePoint and get their metadata along with pre-authenticated download URLs (valid ~1 hour). [See the documentation](https://learn.microsoft.com/en-us/graph/api/driveitem-get)",
9
+ version: "0.0.1",
10
+ type: "action",
11
+ annotations: {
12
+ destructiveHint: false,
13
+ openWorldHint: true,
14
+ readOnlyHint: true,
15
+ },
16
+ props: {
17
+ sharepoint,
18
+ siteId: {
19
+ propDefinition: [
20
+ sharepoint,
21
+ "siteId",
22
+ ],
23
+ withLabel: true,
24
+ },
25
+ driveId: {
26
+ propDefinition: [
27
+ sharepoint,
28
+ "driveId",
29
+ (c) => ({
30
+ siteId: c.siteId,
31
+ }),
32
+ ],
33
+ withLabel: true,
34
+ },
35
+ folderId: {
36
+ propDefinition: [
37
+ sharepoint,
38
+ "folderId",
39
+ (c) => ({
40
+ siteId: c.siteId,
41
+ driveId: c.driveId,
42
+ }),
43
+ ],
44
+ description: "The folder to browse. Leave empty to browse the root of the drive.",
45
+ optional: true,
46
+ withLabel: true,
47
+ },
48
+ fileIds: {
49
+ propDefinition: [
50
+ sharepoint,
51
+ "fileIds",
52
+ (c) => ({
53
+ siteId: c.siteId,
54
+ driveId: c.driveId,
55
+ folderId: c.folderId,
56
+ }),
57
+ ],
58
+ label: "Files",
59
+ description: "Select one or more files to download. **Note:** Only files can be downloaded; folders are not supported.",
60
+ },
61
+ },
62
+ methods: filePickerMethods,
63
+ async run({ $ }) {
64
+ // Parse the fileIds (which are JSON strings from the file picker)
65
+ const selections = utils.parseFileOrFolderList(this.fileIds);
66
+
67
+ if (selections.length === 0) {
68
+ throw new Error("Please select at least one file");
69
+ }
70
+
71
+ const siteId = this.sharepoint.resolveWrappedValue(this.siteId);
72
+ const driveId = this.sharepoint.resolveWrappedValue(this.driveId);
73
+
74
+ // Fetch metadata for all selected files with download URLs
75
+ const {
76
+ fileResults,
77
+ errors,
78
+ } = await this.fetchFileMetadata($, selections, siteId, driveId, {
79
+ includeDownloadUrl: true,
80
+ });
81
+
82
+ // Process and return results (no folders to handle)
83
+ return this.processResults($, fileResults, errors, [], {
84
+ successVerb: "Retrieved",
85
+ successNoun: "download URL(s)",
86
+ });
87
+ },
88
+ };
@@ -6,7 +6,7 @@ export default {
6
6
  key: "sharepoint-find-file-by-name",
7
7
  name: "Find File by Name",
8
8
  description: "Search for a file or folder by name. [See the documentation](https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_search)",
9
- version: "0.1.3",
9
+ version: "0.1.4",
10
10
  type: "action",
11
11
  annotations: {
12
12
  destructiveHint: false,
@@ -5,7 +5,7 @@ export default {
5
5
  name: "Find Files in List with Metadata",
6
6
  description:
7
7
  "Search and filter items in a SharePoint list based on metadata and custom columns. [See docs here](https://learn.microsoft.com/en-us/graph/api/listitem-list)",
8
- version: "0.0.1",
8
+ version: "0.0.2",
9
9
  type: "action",
10
10
  annotations: {
11
11
  destructiveHint: false,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "sharepoint-get-excel-table",
5
5
  name: "Get Excel Table",
6
6
  description: "Retrieve a table from an Excel spreadsheet stored in Sharepoint [See the documentation](https://learn.microsoft.com/en-us/graph/api/table-range?view=graph-rest-1.0&tabs=http)",
7
- version: "0.0.5",
7
+ version: "0.0.6",
8
8
  type: "action",
9
9
  annotations: {
10
10
  destructiveHint: false,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "sharepoint-get-file-by-id",
5
5
  name: "Get File by ID",
6
6
  description: "Retrieves a file by ID. [See the documentation](https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_get)",
7
- version: "0.0.4",
7
+ version: "0.0.5",
8
8
  type: "action",
9
9
  annotations: {
10
10
  destructiveHint: false,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "sharepoint-get-site",
5
5
  name: "Get Site",
6
6
  description: "Get a site in Microsoft Sharepoint. [See the documentation](https://learn.microsoft.com/en-us/graph/api/site-get?view=graph-rest-1.0&tabs=http)",
7
- version: "0.0.2",
7
+ version: "0.0.3",
8
8
  type: "action",
9
9
  annotations: {
10
10
  destructiveHint: false,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "sharepoint-list-files-in-folder",
5
5
  name: "List Files in Folder",
6
6
  description: "Retrieves a list of the files and/or folders directly within a folder. [See the documentation](https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_list_children)",
7
- version: "0.0.4",
7
+ version: "0.0.5",
8
8
  type: "action",
9
9
  annotations: {
10
10
  destructiveHint: false,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "sharepoint-list-sites",
5
5
  name: "List Sites",
6
6
  description: "List all sites in Microsoft Sharepoint. [See the documentation](https://learn.microsoft.com/en-us/graph/api/site-list?view=graph-rest-1.0&tabs=http)",
7
- version: "0.0.2",
7
+ version: "0.0.3",
8
8
  type: "action",
9
9
  annotations: {
10
10
  destructiveHint: false,
@@ -0,0 +1,55 @@
1
+ import utils from "../../common/utils.mjs";
2
+ import {
3
+ filePickerProps,
4
+ filePickerMethods,
5
+ } from "../../common/file-picker-base.mjs";
6
+
7
+ export default {
8
+ key: "sharepoint-retrieve-file-metadata",
9
+ name: "Retrieve File Metadata",
10
+ description: "Browse and select files from SharePoint to retrieve their metadata (name, size, dates, etc.) without download URLs. [See the documentation](https://learn.microsoft.com/en-us/graph/api/driveitem-get)",
11
+ version: "0.0.1",
12
+ type: "action",
13
+ annotations: {
14
+ destructiveHint: false,
15
+ openWorldHint: true,
16
+ readOnlyHint: true,
17
+ },
18
+ props: filePickerProps,
19
+ methods: filePickerMethods,
20
+ async run({ $ }) {
21
+ const selections = utils.parseFileOrFolderList(this.fileOrFolderIds);
22
+
23
+ if (selections.length === 0) {
24
+ throw new Error("Please select at least one file or folder");
25
+ }
26
+
27
+ const siteId = this.sharepoint.resolveWrappedValue(this.siteId);
28
+ const driveId = this.sharepoint.resolveWrappedValue(this.driveId);
29
+
30
+ // Separate files and folders
31
+ const {
32
+ files,
33
+ folders,
34
+ } = this.categorizeSelections(selections);
35
+
36
+ // If only folders selected, return folder info
37
+ if (files.length === 0 && folders.length > 0) {
38
+ return this.handleFolderOnlySelection($, folders);
39
+ }
40
+
41
+ // Fetch metadata for all selected files WITHOUT download URLs
42
+ const {
43
+ fileResults,
44
+ errors,
45
+ } = await this.fetchFileMetadata($, files, siteId, driveId, {
46
+ includeDownloadUrl: false,
47
+ });
48
+
49
+ // Process and return results
50
+ return this.processResults($, fileResults, errors, folders, {
51
+ successVerb: "Retrieved",
52
+ successNoun: "metadata",
53
+ });
54
+ },
55
+ };
@@ -6,7 +6,7 @@ export default {
6
6
  name: "Search and Filter Files",
7
7
  description:
8
8
  "Search and filter SharePoint files based on metadata and custom columns. This action allows you to query files using SharePoint's custom properties, managed metadata, and other column values. [See the documentation](https://learn.microsoft.com/en-us/graph/api/listitem-list)",
9
- version: "0.0.1",
9
+ version: "0.0.2",
10
10
  type: "action",
11
11
  annotations: {
12
12
  destructiveHint: false,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "sharepoint-search-files",
5
5
  name: "Search Files",
6
6
  description: "Search for files in Microsoft Sharepoint. [See the documentation](https://learn.microsoft.com/en-us/graph/api/search-query?view=graph-rest-1.0&tabs=http)",
7
- version: "0.0.2",
7
+ version: "0.0.3",
8
8
  type: "action",
9
9
  annotations: {
10
10
  destructiveHint: false,
@@ -4,7 +4,7 @@ export default {
4
4
  key: "sharepoint-search-sites",
5
5
  name: "Search Sites",
6
6
  description: "Search for sites in Microsoft Sharepoint. [See the documentation](https://learn.microsoft.com/en-us/graph/api/site-search?view=graph-rest-1.0&tabs=http)",
7
- version: "0.0.2",
7
+ version: "0.0.3",
8
8
  type: "action",
9
9
  annotations: {
10
10
  destructiveHint: false,
@@ -5,7 +5,7 @@ export default {
5
5
  key: "sharepoint-update-item",
6
6
  name: "Update Item",
7
7
  description: "Updates an existing item in Microsoft Sharepoint. [See the documentation](https://learn.microsoft.com/en-us/graph/api/listitem-update?view=graph-rest-1.0&tabs=http)",
8
- version: "0.0.10",
8
+ version: "0.0.11",
9
9
  annotations: {
10
10
  destructiveHint: true,
11
11
  openWorldHint: true,
@@ -5,7 +5,7 @@ export default {
5
5
  key: "sharepoint-upload-file",
6
6
  name: "Upload File",
7
7
  description: "Upload a file to OneDrive. [See the documentation](https://learn.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_put_content?view=odsp-graph-online)",
8
- version: "0.0.4",
8
+ version: "0.0.5",
9
9
  type: "action",
10
10
  annotations: {
11
11
  destructiveHint: false,
@@ -1,3 +1,21 @@
1
+ /**
2
+ * Microsoft Graph subscription constants for SharePoint webhooks.
3
+ * https://learn.microsoft.com/en-us/graph/api/subscription-post-subscriptions
4
+ */
5
+
6
+ /**
7
+ * Maximum subscription lifetime for driveItem resources is 42,300 minutes (~29.4 days).
8
+ * We use 29 days in milliseconds to stay safely within the limit.
9
+ */
10
+ const WEBHOOK_SUBSCRIPTION_EXPIRATION_TIME_MILLISECONDS = 29 * 24 * 60 * 60 * 1000;
11
+
12
+ /**
13
+ * Renewal interval at 95% of max lifetime to ensure renewal before expiration.
14
+ * 29 days * 0.95 = 27.55 days = 2,380,320 seconds
15
+ */
16
+ const WEBHOOK_SUBSCRIPTION_RENEWAL_SECONDS =
17
+ (WEBHOOK_SUBSCRIPTION_EXPIRATION_TIME_MILLISECONDS * 0.95) / 1000;
18
+
1
19
  const SHARING_LINK_TYPE_OPTIONS = [
2
20
  {
3
21
  label: "Create a read-only link to the DriveItem",
@@ -84,6 +102,11 @@ const RETURN_CONTENT_TYPE_OPTIONS = [
84
102
  },
85
103
  ];
86
104
 
105
+ export {
106
+ WEBHOOK_SUBSCRIPTION_EXPIRATION_TIME_MILLISECONDS,
107
+ WEBHOOK_SUBSCRIPTION_RENEWAL_SECONDS,
108
+ };
109
+
87
110
  export default {
88
111
  SHARING_LINK_TYPE_OPTIONS,
89
112
  SHARING_LINK_SCOPE_OPTIONS,
@@ -0,0 +1,308 @@
1
+ import sharepoint from "../sharepoint.app.mjs";
2
+
3
+ /**
4
+ * Shared prop definitions for file picker actions.
5
+ * These props provide a consistent file/folder browsing experience across actions.
6
+ */
7
+ export const filePickerProps = {
8
+ sharepoint,
9
+ siteId: {
10
+ propDefinition: [
11
+ sharepoint,
12
+ "siteId",
13
+ ],
14
+ withLabel: true,
15
+ },
16
+ driveId: {
17
+ propDefinition: [
18
+ sharepoint,
19
+ "driveId",
20
+ (c) => ({
21
+ siteId: c.siteId,
22
+ }),
23
+ ],
24
+ withLabel: true,
25
+ },
26
+ folderId: {
27
+ propDefinition: [
28
+ sharepoint,
29
+ "folderId",
30
+ (c) => ({
31
+ siteId: c.siteId,
32
+ driveId: c.driveId,
33
+ }),
34
+ ],
35
+ label: "Folder",
36
+ description: "The folder to browse. Leave empty to browse the root of the drive.",
37
+ optional: true,
38
+ withLabel: true,
39
+ },
40
+ fileOrFolderIds: {
41
+ propDefinition: [
42
+ sharepoint,
43
+ "fileOrFolderId",
44
+ (c) => ({
45
+ siteId: c.siteId,
46
+ driveId: c.driveId,
47
+ folderId: c.folderId,
48
+ }),
49
+ ],
50
+ type: "string[]",
51
+ label: "Files or Folders",
52
+ description: "Select one or more files, or select a folder and click 'Refresh Fields' to browse into it",
53
+ withLabel: true,
54
+ },
55
+ };
56
+
57
+ /**
58
+ * Shared methods for file picker actions.
59
+ * Provides common functionality for parsing selections, fetching metadata,
60
+ * and handling folder-only selections.
61
+ */
62
+ export const filePickerMethods = {
63
+ /**
64
+ * Categorizes selections into files and folders based on isFolder property.
65
+ *
66
+ * @param {Array<{id: string, name: string, isFolder: boolean}>} selections
67
+ * Array of parsed file/folder objects
68
+ * @returns {{files: Array, folders: Array}}
69
+ * Object with separate files and folders arrays
70
+ * @example
71
+ * const { files, folders } = this.categorizeSelections([
72
+ * { id: "1", name: "report.pdf", isFolder: false },
73
+ * { id: "2", name: "Documents", isFolder: true }
74
+ * ]);
75
+ * // files: [{ id: "1", name: "report.pdf", isFolder: false }]
76
+ * // folders: [{ id: "2", name: "Documents", isFolder: true }]
77
+ */
78
+ categorizeSelections(selections) {
79
+ return {
80
+ folders: selections.filter((s) => s.isFolder),
81
+ files: selections.filter((s) => !s.isFolder),
82
+ };
83
+ },
84
+
85
+ /**
86
+ * Handles the case where only folders are selected
87
+ * @param {Object} $ - Pipedream step context
88
+ * @param {Array} folders - Array of folder objects
89
+ * @returns {Object} Response object with folder information
90
+ */
91
+ handleFolderOnlySelection($, folders) {
92
+ const folderNames = folders.map((f) => f.name).join(", ");
93
+ $.export("$summary", `Selected ${folders.length} folder(s): ${folderNames}. Set one as the Folder ID and refresh to browse its contents.`);
94
+ return {
95
+ type: "folders",
96
+ folders: folders.map((f) => ({
97
+ id: f.id,
98
+ name: f.name,
99
+ })),
100
+ message: "To browse a folder, set it as the folderId and reload props",
101
+ };
102
+ },
103
+
104
+ /**
105
+ * Constructs a SharePoint library view URL that opens the file in the
106
+ * document library context. This is useful for providing users with a
107
+ * link to view the file in SharePoint's web interface.
108
+ *
109
+ * @param {string} fileWebUrl - The file's webUrl from Microsoft Graph API
110
+ * @returns {string|null} SharePoint AllItems.aspx URL with file location,
111
+ * or null if construction fails
112
+ * @example
113
+ * // Input: "https://contoso.sharepoint.com/sites/Marketing/..."
114
+ * // Output: "https://contoso.sharepoint.com/sites/Marketing/...aspx?..."
115
+ */
116
+ constructSharePointViewUrl(fileWebUrl) {
117
+ if (!fileWebUrl) return null;
118
+
119
+ try {
120
+ // Parse webUrl to extract components
121
+ // Example: https://tenant.sharepoint.com/sites/sitename/LibraryName/folder/file.ext
122
+ const url = new URL(fileWebUrl);
123
+
124
+ // Extract library path from webUrl (e.g., "Shared%20Documents")
125
+ // Match pattern: /sites/{sitename}/{libraryname}/...
126
+ const libraryMatch = fileWebUrl.match(/\/sites\/[^/]+\/([^/]+)/);
127
+ if (!libraryMatch) return null;
128
+
129
+ const libraryUrlPart = libraryMatch[1]; // This keeps the original encoding from webUrl
130
+
131
+ // Construct site URL
132
+ const siteUrlMatch = fileWebUrl.match(/(https:\/\/[^/]+\/sites\/[^/]+)/);
133
+ if (!siteUrlMatch) return null;
134
+
135
+ const siteUrl = siteUrlMatch[1];
136
+
137
+ // Construct the full file path (decode the pathname to get raw path)
138
+ const filePath = decodeURIComponent(url.pathname);
139
+
140
+ // Construct parent path by removing the filename
141
+ const parentPath = filePath.substring(0, filePath.lastIndexOf("/"));
142
+
143
+ // Build the AllItems.aspx URL - don't re-encode the library name in path
144
+ return `${siteUrl}/${libraryUrlPart}/Forms/AllItems.aspx?id=${encodeURIComponent(filePath)}&parent=${encodeURIComponent(parentPath)}`;
145
+ } catch (error) {
146
+ console.error("Error constructing SharePoint view URL:", error);
147
+ return null;
148
+ }
149
+ },
150
+
151
+ /**
152
+ * Fetches metadata for multiple files in parallel with individual error
153
+ * handling. Uses Promise.allSettled to ensure partial failures don't
154
+ * block successful fetches.
155
+ *
156
+ * @param {Object} $ - Pipedream step context for API calls
157
+ * @param {Array<{id: string, name: string}>} files
158
+ * Array of file objects with id property
159
+ * @param {string} siteId - SharePoint site ID
160
+ * @param {string} driveId - Drive ID within the site
161
+ * @param {Object} [options={}] - Configuration options
162
+ * @param {boolean} [options.includeDownloadUrl=true]
163
+ * Whether to include temporary downloadUrl in response
164
+ * @returns {Promise<{fileResults: Array, errors: Array}>}
165
+ * Object with successful results and errors
166
+ * @example
167
+ * const { fileResults, errors } = await this.fetchFileMetadata(
168
+ * $, files, siteId, driveId, { includeDownloadUrl: true }
169
+ * );
170
+ */
171
+ async fetchFileMetadata($, files, siteId, driveId, options = {}) {
172
+ const { includeDownloadUrl = true } = options;
173
+
174
+ // Fetch metadata for all selected files in parallel, handling individual failures
175
+ const settledResults = await Promise.allSettled(
176
+ files.map(async (selected) => {
177
+ // When includeDownloadUrl is true, omit $select to get @microsoft.graph.downloadUrl
178
+ // (Graph API excludes downloadUrl when using $select)
179
+ const params = includeDownloadUrl
180
+ ? {}
181
+ : {
182
+ $select: "id,name,size,webUrl,createdDateTime,lastModifiedDateTime,createdBy,lastModifiedBy,parentReference,file,folder,image,video,audio,photo,shared,fileSystemInfo,cTag,eTag,sharepointIds",
183
+ };
184
+
185
+ const file = await this.sharepoint.getDriveItem({
186
+ $,
187
+ siteId,
188
+ driveId,
189
+ fileId: selected.id,
190
+ params,
191
+ });
192
+
193
+ // Construct SharePoint library view URL
194
+ const sharepointViewUrl = this.constructSharePointViewUrl(file.webUrl);
195
+
196
+ const result = {
197
+ ...file,
198
+ ...(sharepointViewUrl && {
199
+ sharepointViewUrl,
200
+ }),
201
+ _meta: {
202
+ siteId,
203
+ driveId,
204
+ fileId: selected.id,
205
+ },
206
+ };
207
+
208
+ // Remove the Graph API property name from spread
209
+ delete result["@microsoft.graph.downloadUrl"];
210
+
211
+ // Conditionally include downloadUrl based on options
212
+ if (includeDownloadUrl) {
213
+ result.downloadUrl = file["@microsoft.graph.downloadUrl"];
214
+ }
215
+
216
+ return result;
217
+ }),
218
+ );
219
+
220
+ // Separate successful and failed results
221
+ const fileResults = [];
222
+ const errors = [];
223
+
224
+ settledResults.forEach((result, index) => {
225
+ if (result.status === "fulfilled") {
226
+ fileResults.push(result.value);
227
+ } else {
228
+ const selected = files[index];
229
+ const errorMessage = result.reason?.message || String(result.reason);
230
+ console.error(`Failed to fetch file ${selected.id} (${selected.name}): ${errorMessage}`);
231
+ errors.push({
232
+ fileId: selected.id,
233
+ fileName: selected.name,
234
+ error: errorMessage,
235
+ });
236
+ }
237
+ });
238
+
239
+ return {
240
+ fileResults,
241
+ errors,
242
+ };
243
+ },
244
+
245
+ /**
246
+ * Processes and formats the final results with user-friendly summary
247
+ * export. Handles single file vs. multiple files formatting for UX.
248
+ *
249
+ * @param {Object} $ - Pipedream step context for exports
250
+ * @param {Array} fileResults - Successfully fetched file metadata
251
+ * @param {Array} errors - Failed file fetches with error details
252
+ * @param {Array} folders
253
+ * Selected folders (for informational purposes)
254
+ * @param {Object} [options={}] - Configuration options
255
+ * @param {string} [options.successVerb="Retrieved"]
256
+ * Verb for summary (e.g., "Retrieved", "Downloaded")
257
+ * @param {string} [options.successNoun="file(s)"]
258
+ * Noun for summary (e.g., "download URL(s)", "metadata")
259
+ * @returns {Object|Array} Single file object if one file, otherwise
260
+ * object with files/errors/folders arrays
261
+ * @throws {Error} If all file fetches failed
262
+ * @example
263
+ * return this.processResults($, fileResults, errors, folders, {
264
+ * successVerb: "Retrieved",
265
+ * successNoun: "download URL(s)"
266
+ * });
267
+ */
268
+ processResults($, fileResults, errors, folders, options = {}) {
269
+ const {
270
+ successVerb = "Retrieved",
271
+ successNoun = "file(s)",
272
+ } = options;
273
+
274
+ // If all files failed, throw an error
275
+ if (fileResults.length === 0 && errors.length > 0) {
276
+ throw new Error(`Failed to fetch all selected files: ${errors.map((e) => e.fileName).join(", ")}`);
277
+ }
278
+
279
+ // If single file, return it directly for backwards compatibility
280
+ if (fileResults.length === 1 && folders.length === 0 && errors.length === 0) {
281
+ $.export("$summary", `${successVerb} ${successNoun} for: ${fileResults[0].name}`);
282
+ return fileResults[0];
283
+ }
284
+
285
+ // Multiple files: return as object with metadata
286
+ const fileNames = fileResults.map((f) => f.name).join(", ");
287
+ const summaryParts = [
288
+ `${successVerb} ${successNoun} for ${fileResults.length} file(s): ${fileNames}`,
289
+ ];
290
+ if (errors.length > 0) {
291
+ summaryParts.push(`Failed to fetch ${errors.length} file(s): ${errors.map((e) => e.fileName).join(", ")}`);
292
+ }
293
+ $.export("$summary", summaryParts.join(". "));
294
+
295
+ return {
296
+ files: fileResults,
297
+ ...(errors.length > 0 && {
298
+ errors,
299
+ }),
300
+ ...(folders.length > 0 && {
301
+ folders: folders.map((f) => ({
302
+ id: f.id,
303
+ name: f.name,
304
+ })),
305
+ }),
306
+ };
307
+ },
308
+ };