-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial code commit - testing action
- Loading branch information
1 parent
dd19324
commit a585e52
Showing
8,346 changed files
with
1,245,230 additions
and
2 deletions.
The diff you're trying to view is too large. We only load the first 3000 changed files.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
root = true | ||
|
||
[*] | ||
indent_style = space | ||
indent_size = 2 | ||
charset = utf-8 | ||
trim_trailing_whitespace = true | ||
insert_final_newline = true | ||
|
||
[*.md] | ||
trim_trailing_whitespace = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"endOfLine": "auto", | ||
"tabWidth": 2, | ||
"printWidth": 100, | ||
"semi": true, | ||
"singleQuote": false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
name: "sharepoint-download-action" | ||
description: "GitHub Action for downloading a single item from Microsoft SharePoint" | ||
author: "MedicalVR BV" | ||
branding: | ||
icon: "download" | ||
color: "green" | ||
inputs: | ||
azure-client-id: | ||
description: "Azure Client ID" | ||
required: true | ||
azure-client-secret: | ||
description: "Azure Client Secret" | ||
required: true | ||
azure-tenant-id: | ||
description: "Azure Tenant ID" | ||
required: true | ||
uri: | ||
description: "Target location of the file as URI, e.g. 'sharepoint://Team/Builds/Automation/alpha/v124/file.zip'" | ||
required: true | ||
target: | ||
description: "Target absolute path to download the file to" | ||
required: true | ||
runs: | ||
using: "node20" | ||
main: "dist/index.js" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import "isomorphic-fetch"; | ||
import { Client } from "@microsoft/microsoft-graph-client"; | ||
export type DriveItem = { | ||
id: string; | ||
path: string; | ||
}; | ||
export type Group = { | ||
id: string; | ||
name: string; | ||
}; | ||
export type GroupDrive = { | ||
id: string; | ||
groupId: string; | ||
}; | ||
export type SharePointClientConfig = { | ||
tenantId: string; | ||
clientId: string; | ||
clientSecret: string; | ||
}; | ||
export declare class SharePointClient { | ||
private readonly config; | ||
private clientSecretCredential; | ||
private graph; | ||
constructor(config: SharePointClientConfig); | ||
getGraph(): Client; | ||
getGroup(name: string): Promise<Group | undefined>; | ||
getDriveItem(group: Group, path: string): Promise<DriveItem | undefined>; | ||
downloadItem(group: Group, path: string, targetPath: string): Promise<void>; | ||
uploadItem(group: Group, path: string, targetPath: string, callback: (progress: number) => void): Promise<DriveItem>; | ||
} | ||
export declare const getClient: (config: SharePointClientConfig) => SharePointClient; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getClient = exports.SharePointClient = void 0; | ||
const tslib_1 = require("tslib"); | ||
require("isomorphic-fetch"); | ||
const identity_1 = require("@azure/identity"); | ||
const microsoft_graph_client_1 = require("@microsoft/microsoft-graph-client"); | ||
const node_fs_1 = require("node:fs"); | ||
const Path = tslib_1.__importStar(require("node:path")); | ||
const node_util_1 = require("node:util"); | ||
const promises_1 = require("node:fs/promises"); | ||
const Stream = tslib_1.__importStar(require("node:stream")); | ||
const azureTokenCredentials_1 = require("@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials"); | ||
const Finished = (0, node_util_1.promisify)(Stream.finished); | ||
const TEMP_FILE_CONTENTS = "Temporary contents..."; | ||
class SharePointClient { | ||
constructor(config) { | ||
this.config = config; | ||
this.clientSecretCredential = new identity_1.ClientSecretCredential(this.config.tenantId, this.config.clientId, this.config.clientSecret); | ||
} | ||
getGraph() { | ||
if (this.graph) { | ||
return this.graph; | ||
} | ||
const authProvider = new azureTokenCredentials_1.TokenCredentialAuthenticationProvider(this.clientSecretCredential, { | ||
scopes: ["https://graph.microsoft.com/.default"], | ||
}); | ||
this.graph = microsoft_graph_client_1.Client.initWithMiddleware({ authProvider }); | ||
return this.graph; | ||
} | ||
async getGroup(name) { | ||
const groups = await this.getGraph().api("/groups").select(["displayName", "id"]).get(); | ||
const nameLC = name.toLowerCase(); | ||
for (const group of groups.value) { | ||
if (group.displayName.toLowerCase() === nameLC) { | ||
return { | ||
id: group.id, | ||
name: group.displayName, | ||
}; | ||
} | ||
} | ||
return undefined; | ||
} | ||
async getDriveItem(group, path) { | ||
const res = await this.getGraph() | ||
.api(`/groups/${group.id}/drive/root:/${path}`) | ||
.select(["id"]) | ||
.get(); | ||
if (res) { | ||
return { | ||
id: res.id, | ||
path, | ||
}; | ||
} | ||
return undefined; | ||
} | ||
async downloadItem(group, path, targetPath) { | ||
const stream = await this.getGraph() | ||
.api(`/groups/${group.id}/drive/root:/${path}:/content`) | ||
.responseType(microsoft_graph_client_1.ResponseType.BLOB) | ||
.get(); | ||
if (stream) { | ||
const writer = (0, node_fs_1.createWriteStream)(targetPath); | ||
Stream.Readable.fromWeb(stream instanceof Blob ? stream.stream() : stream).pipe(writer); | ||
await Finished(writer); | ||
} | ||
} | ||
async uploadItem(group, path, targetPath, callback) { | ||
const pathDetails = Path.parse(targetPath); | ||
let item; | ||
try { | ||
item = await this.getDriveItem(group, targetPath); | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
} | ||
catch (ex) { | ||
// Do nothing. | ||
} | ||
const graph = this.getGraph(); | ||
if (!item) { | ||
// First we need to create a new (placeholder) item that's located at the same path. | ||
const parent = await this.getDriveItem(group, pathDetails.dir); | ||
if (!parent) { | ||
throw new Error(`Directory not found on SharePoint of group ${group.name}: ${pathDetails.dir}`); | ||
} | ||
const res = await graph | ||
.api(`/groups/${group.id}/drive/items/${parent.id}:/${pathDetails.base}:/content`) | ||
.put(TEMP_FILE_CONTENTS); | ||
item = { | ||
id: res.id, | ||
path: targetPath, | ||
}; | ||
} | ||
if (!item) { | ||
throw new Error(`Drive item at path ${targetPath} could not be created.`); | ||
} | ||
const stats = await (0, promises_1.stat)(path); | ||
const totalSize = stats.size; | ||
const progress = (range) => { | ||
if (!range) { | ||
callback(100); | ||
return; | ||
} | ||
const current = range.maxValue; | ||
const newer = Math.min(totalSize, current); | ||
const old = Math.max(totalSize, current); | ||
const frac = Math.abs(newer - old) / old; | ||
callback(100 - Math.floor(frac * 100)); | ||
}; | ||
const uploadEventHandlers = { | ||
progress, | ||
}; | ||
const options = { | ||
rangeSize: 1024 * 1024, | ||
uploadEventHandlers, | ||
}; | ||
// Create upload session for SharePoint upload. | ||
const payload = { | ||
item: { | ||
"@microsoft.graph.conflictBehavior": "replace", | ||
}, | ||
}; | ||
const reader = (0, node_fs_1.createReadStream)(path); | ||
const uploadSession = await microsoft_graph_client_1.LargeFileUploadTask.createUploadSession(graph, `https://graph.microsoft.com/v1.0/groups/${group.id}/drive/items/${item.id}/createuploadsession`, payload); | ||
const fileObject = new microsoft_graph_client_1.StreamUpload(reader, pathDetails.base, totalSize); | ||
const task = new microsoft_graph_client_1.LargeFileUploadTask(graph, fileObject, uploadSession, options); | ||
await task.upload(); | ||
return item; | ||
} | ||
} | ||
exports.SharePointClient = SharePointClient; | ||
let gClient = undefined; | ||
const getClient = (config) => { | ||
if (gClient) { | ||
return gClient; | ||
} | ||
gClient = new SharePointClient(config); | ||
return gClient; | ||
}; | ||
exports.getClient = getClient; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import type { BigIntStats, Stats } from "node:fs"; | ||
import { SharePointClientConfig } from "./client"; | ||
export type ExtStats = (Stats | BigIntStats) & { | ||
exists: boolean; | ||
}; | ||
export type StatOptions = { | ||
bigint: boolean; | ||
}; | ||
export declare const statExt: (path: string, options?: StatOptions) => Promise<ExtStats>; | ||
export declare const downloadFile: (uri: string, to: string, config: SharePointClientConfig) => Promise<void>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.downloadFile = exports.statExt = void 0; | ||
const tslib_1 = require("tslib"); | ||
const client_1 = require("./client"); | ||
const core_1 = require("@actions/core"); | ||
const promises_1 = require("node:fs/promises"); | ||
const Path = tslib_1.__importStar(require("node:path")); | ||
const groupsMap = new Map(); | ||
const decomposeSharePointURI = async (uri, client) => { | ||
const [groupName, ...pathParts] = uri.replace("sharepoint://", "").split("/"); | ||
// sharepoint://Team/Builds/Automation/alpha/v2.1.4/docs/library.pdf | ||
// [___________][___][______________________________________________] | ||
// | | | | ||
// Pseudo- Group Path to file on Team's Shared Documents | ||
// protocol name (relative to root) | ||
if (!groupName) { | ||
throw new Error(`No group name could be found in URI '${uri}'`); | ||
} | ||
const groupNameLC = groupName.toLowerCase(); | ||
if (!groupsMap.has(groupNameLC)) { | ||
const res = await client.getGroup(groupNameLC); | ||
if (res) { | ||
groupsMap.set(groupNameLC, res); | ||
} | ||
} | ||
const group = groupsMap.get(groupNameLC); | ||
if (!group) { | ||
throw new Error(`Could not find SharePoint group ${groupName}`); | ||
} | ||
return { group, pathParts }; | ||
}; | ||
const statExt = async (path, options) => { | ||
let s; | ||
try { | ||
s = await (0, promises_1.stat)(path, options); | ||
return Object.assign(s, { exists: true }); | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
} | ||
catch (ex) { | ||
// no-op. | ||
} | ||
return { ...{}, exists: false }; | ||
}; | ||
exports.statExt = statExt; | ||
const downloadFile = async (uri, to, config) => { | ||
if (!uri.startsWith("sharepoint:")) { | ||
throw new Error(`Invalid URI: ${uri}`); | ||
} | ||
const client = (0, client_1.getClient)(config); | ||
const { group, pathParts } = await decomposeSharePointURI(uri, client); | ||
const fileName = pathParts[pathParts.length - 1]; | ||
if (!fileName) { | ||
throw new Error(`Invalid URI: ${uri}`); | ||
} | ||
(0, core_1.info)(`Downloading '${uri}' to '${to}'...`); | ||
const itemPath = pathParts.join("/"); | ||
// Try once to create the directory if it doesn't exist yet. No mkdirp. | ||
const stat = await (0, exports.statExt)(to); | ||
if (!stat.exists) { | ||
await (0, promises_1.mkdir)(to); | ||
} | ||
try { | ||
// This fetch may yield a 404 error and is cheaper than a full download. | ||
await client.getDriveItem(group, itemPath); | ||
await client.downloadItem(group, itemPath, Path.join(to, fileName)); | ||
} | ||
catch (ex) { | ||
throw new Error(`Could not download '${itemPath}' with error ${ex.message}`); | ||
} | ||
(0, core_1.info)("done!"); | ||
}; | ||
exports.downloadFile = downloadFile; | ||
(async function () { | ||
const config = { | ||
clientId: (0, core_1.getInput)("azure-client-id"), | ||
clientSecret: (0, core_1.getInput)("azure-client-secret"), | ||
tenantId: (0, core_1.getInput)("azure-tenant-id"), | ||
}; | ||
await (0, exports.downloadFile)((0, core_1.getInput)("uri"), (0, core_1.getInput)("target"), config); | ||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import typescriptEslint from "@typescript-eslint/eslint-plugin"; | ||
import globals from "globals"; | ||
import tsParser from "@typescript-eslint/parser"; | ||
import path from "node:path"; | ||
import { fileURLToPath } from "node:url"; | ||
import js from "@eslint/js"; | ||
import { FlatCompat } from "@eslint/eslintrc"; | ||
|
||
const __filename = fileURLToPath(import.meta.url); | ||
const __dirname = path.dirname(__filename); | ||
const compat = new FlatCompat({ | ||
baseDirectory: __dirname, | ||
recommendedConfig: js.configs.recommended, | ||
allConfig: js.configs.all | ||
}); | ||
|
||
export default [ | ||
...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"), | ||
{ | ||
plugins: { | ||
"@typescript-eslint": typescriptEslint, | ||
}, | ||
|
||
languageOptions: { | ||
globals: { | ||
...globals.browser, | ||
...globals.jest, | ||
}, | ||
|
||
parser: tsParser, | ||
ecmaVersion: 12, | ||
sourceType: "module", | ||
}, | ||
|
||
rules: { | ||
"no-unused-vars": "off", | ||
indent: "off", | ||
"@typescript-eslint/ban-ts-comment": "off", | ||
"@typescript-eslint/no-explicit-any": "off", | ||
"@typescript-eslint/no-empty-object-type": "off", | ||
}, | ||
}, | ||
]; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.