diff --git a/package-lock.json b/package-lock.json index ced13f4..c031691 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1276,6 +1276,12 @@ "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", "dev": true }, + "@types/mime": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz", + "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==", + "dev": true + }, "@types/minimist": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", diff --git a/package.json b/package.json index e4eba45..0c38e15 100644 --- a/package.json +++ b/package.json @@ -39,11 +39,13 @@ "license": "ISC", "devDependencies": { "@types/jest": "^26.0.23", + "@types/mime": "^2.0.3", "@types/tough-cookie": "^4.0.0", "cp": "^0.2.0", "dotenv": "^10.0.0", "jest": "^27.0.4", "jest-extended": "^0.11.5", + "mime": "^2.5.2", "path": "^0.12.7", "process": "^0.11.10", "rimraf": "^3.0.2", diff --git a/src/SASViyaApiClient.ts b/src/SASViyaApiClient.ts index a708332..bb629bf 100644 --- a/src/SASViyaApiClient.ts +++ b/src/SASViyaApiClient.ts @@ -12,6 +12,7 @@ import { Context, ContextAllAttributes, Folder, + File, EditContextInput, JobDefinition, PollOptions @@ -30,6 +31,7 @@ import { isAuthorizeFormRequired } from './auth/isAuthorizeFormRequired' import { RequestClient } from './request/RequestClient' import { SasAuthResponse, MacroVar } from '@sasjs/utils/types' import { prefixMessage } from '@sasjs/utils/error' +import * as mime from 'mime' /** * A client for interfacing with the SAS Viya REST API. @@ -536,6 +538,53 @@ export class SASViyaApiClient { .then((res) => res.result) } + /** + * Creates a file. Path to or URI of the parent folder is required. + * @param fileName - the name of the new file. + * @param contentBuffer - the content of the new file in Buffer. + * @param parentFolderPath - the full path to the parent folder. If not + * provided, the parentFolderUri must be provided. + * @param parentFolderUri - the URI (eg /folders/folders/UUID) of the parent + * folder. If not provided, the parentFolderPath must be provided. + * @param accessToken - an access token for authorizing the request. + */ + public async createFile( + fileName: string, + contentBuffer: Buffer, + parentFolderPath?: string, + parentFolderUri?: string, + accessToken?: string + ): Promise { + if (!parentFolderPath && !parentFolderUri) { + throw new Error('Path or URI of the parent folder is required.') + } + + if (!parentFolderUri && parentFolderPath) { + parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken) + } + + const headers = { + Accept: 'application/vnd.sas.file+json', + 'Content-Disposition': `filename="${fileName}";` + } + + const formData = new NodeFormData() + formData.append('file', contentBuffer, fileName) + + const mimeType = + mime.getType(fileName.match(/\.[0-9a-z]+$/i)?.[0] || '') ?? 'text/plain' + + return ( + await this.requestClient.post( + `/files/files?parentFolderUri=${parentFolderUri}&typeDefName=file#rawUpload`, + formData, + accessToken, + 'multipart/form-data; boundary=' + (formData as any)._boundary, + headers + ) + ).result + } + /** * Creates a folder. Path to or URI of the parent folder is required. * @param folderName - the name of the new folder. diff --git a/src/SASjs.ts b/src/SASjs.ts index 0af290e..bc232fe 100644 --- a/src/SASjs.ts +++ b/src/SASjs.ts @@ -268,7 +268,7 @@ export default class SASjs { } /** - * Creates a folder at SAS file system. + * Creates a folder in the logical SAS folder tree * @param folderName - name of the folder to be created. * @param parentFolderPath - the full path (eg `/Public/example/myFolder`) of the parent folder. * @param parentFolderUri - the URI of the parent folder. @@ -300,6 +300,40 @@ export default class SASjs { ) } + /** + * Creates a file in the logical SAS folder tree + * @param fileName - name of the file to be created. + * @param content - content of the file to be created. + * @param parentFolderPath - the full path (eg `/Public/example/myFolder`) of the parent folder. + * @param parentFolderUri - the URI of the parent folder. + * @param accessToken - the access token to authorizing the request. + * @param sasApiClient - a client for interfacing with SAS API. + */ + public async createFile( + fileName: string, + content: Buffer, + parentFolderPath: string, + parentFolderUri?: string, + accessToken?: string, + sasApiClient?: SASViyaApiClient + ) { + if (sasApiClient) + return await sasApiClient.createFile( + fileName, + content, + parentFolderPath, + parentFolderUri, + accessToken + ) + return await this.sasViyaApiClient!.createFile( + fileName, + content, + parentFolderPath, + parentFolderUri, + accessToken + ) + } + /** * Fetches a folder from the SAS file system. * @param folderPath - path of the folder to be fetched. @@ -880,6 +914,16 @@ export default class SASjs { isForced ) break + case 'file': + await this.createFile( + member.name, + member.code, + parentFolder, + undefined, + accessToken, + sasApiClient + ) + break case 'service': await this.createJobDefinition( member.name, diff --git a/src/types/File.ts b/src/types/File.ts new file mode 100644 index 0000000..a1afe32 --- /dev/null +++ b/src/types/File.ts @@ -0,0 +1,8 @@ +import { Link } from './Link' + +export interface File { + id: string + name: string + parentUri: string + links: Link[] +} diff --git a/src/types/index.ts b/src/types/index.ts index 36ee543..313aef2 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,6 +1,7 @@ export * from './Context' export * from './CsrfToken' export * from './Folder' +export * from './File' export * from './Job' export * from './JobDefinition' export * from './JobResult'