diff --git a/src/HashDiff.ts b/src/HashDiff.ts index bb43ddf..4cb012e 100644 --- a/src/HashDiff.ts +++ b/src/HashDiff.ts @@ -30,7 +30,7 @@ export async function fileHash(filename: string, algorithm: "md5" | "sha1" | "sh } export class HashDiff implements IDiff { - getDiffs(localFiles: IFileList, serverFiles: IFileList) { + getDiffs(localFiles: IFileList, serverFiles: IFileList, allowUpload = true, allowReplace = true, allowDelete = true) { const uploadList: Record[] = []; const deleteList: Record[] = []; const replaceList: Record[] = []; @@ -63,13 +63,17 @@ export class HashDiff implements IDiff { } if (fileNameCompare < 0) { - uploadList.push(localFile); - sizeUpload += localFile.size ?? 0; + if (allowUpload) { + uploadList.push(localFile); + sizeUpload += localFile.size ?? 0; + } localPosition += 1; } else if (fileNameCompare > 0) { - deleteList.push(serverFile); - sizeDelete += serverFile.size ?? 0; + if (allowDelete) { + deleteList.push(serverFile); + sizeDelete += serverFile.size ?? 0; + } serverPosition += 1; } else if (fileNameCompare === 0) { @@ -79,8 +83,10 @@ export class HashDiff implements IDiff { sameList.push(localFile); } else { - sizeReplace += localFile.size ?? 0; - replaceList.push(localFile); + if (allowReplace) { + sizeReplace += localFile.size ?? 0; + replaceList.push(localFile); + } } } diff --git a/src/cli.ts b/src/cli.ts index d5730d1..ae5b0f4 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -15,6 +15,9 @@ const argv = yargs.options({ "dry-run": { type: "boolean", default: false, description: "Prints which modifications will be made with current config options, but doesn't actually make any changes" }, "dangerous-clean-slate": { type: "boolean", default: false, description: "Deletes ALL contents of server-dir, even items in excluded with 'exclude' argument" }, "exclude": { type: "array", default: excludeDefaults, description: "An array of glob patterns, these files will not be included in the publish/delete process" }, + "allowUpload": { type: "boolean", default: true, description: "Set to false to skip uploading new files and folders" }, + "allowReplace": { type: "boolean", default: true, description: "Set to false to skip replacing existing files and folders" }, + "allowDelete": { type: "boolean", default: true, description: "Set to false to skip deleting obsoleted files and folders" }, "log-level": { choices: ["minimal", "standard", "verbose"], default: "standard", description: "How much information should print. minimal=only important info, standard=important info and basic file changes, verbose=print everything the script is doing" }, "security": { choices: ["strict", "loose"], default: "loose", description: "" } }) diff --git a/src/deploy.ts b/src/deploy.ts index 25421a8..c8aa8cf 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -150,7 +150,7 @@ export async function deploy(args: IFtpDeployArgumentsWithDefaults, logger: ILog logger.standard(`Calculating differences between client & server`); logger.standard(`----------------------------------------------------------------`); - const diffs = diffTool.getDiffs(localFiles, serverFiles); + const diffs = diffTool.getDiffs(localFiles, serverFiles, args.allowUpload, args.allowReplace, args.allowDelete); diffs.upload.filter((itemUpload) => itemUpload.type === "folder").map((itemUpload) => { logger.standard(`📁 Create: ${itemUpload.name}`); diff --git a/src/main.test.ts b/src/main.test.ts index a5ce4ed..982a5d2 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -225,6 +225,93 @@ describe("HashDiff", () => { expect(diffs.sizeDelete).toEqual(1000); expect(diffs.sizeReplace).toEqual(0); }); + + test("Disallow upload", () => { + const minimalFileList2: IFileList = { + ...minimalFileList, + data: [ + ...minimalFileList.data, + { + type: "file", + name: "zzzz", + size: 1000, + hash: "hash", + } + ] + }; + const diffs = thing.getDiffs(minimalFileList2, minimalFileList, false); + + expect(diffs.upload.length).toEqual(0); + expect(diffs.delete.length).toEqual(0); + expect(diffs.replace.length).toEqual(0); + + expect(diffs.sizeUpload).toEqual(0); + expect(diffs.sizeDelete).toEqual(0); + expect(diffs.sizeReplace).toEqual(0); + }); + + test("Disallow replace", () => { + const localFiles: IFileList = { + version: currentSyncFileVersion, + description: "", + generatedTime: new Date().getTime(), + data: [ + { + type: "file", + name: "path/file.txt", + size: 3000, + hash: "hash1", + } + ] + }; + const serverFiles: IFileList = { + version: currentSyncFileVersion, + description: "", + generatedTime: new Date().getTime(), + data: [ + { + type: "file", + name: "path/file.txt", + size: 1000, + hash: "hash2", + } + ] + }; + + const diffs = thing.getDiffs(localFiles, serverFiles, true, false); + + expect(diffs.upload.length).toEqual(0); + expect(diffs.delete.length).toEqual(0); + expect(diffs.replace.length).toEqual(0); + + expect(diffs.sizeUpload).toEqual(0); + expect(diffs.sizeDelete).toEqual(0); + expect(diffs.sizeReplace).toEqual(0); + }); + + test("Disallow delete", () => { + const minimalFileList2: IFileList = { + ...minimalFileList, + data: [ + ...minimalFileList.data, + { + type: "file", + name: "zzzz", + size: 1000, + hash: "hash", + } + ] + }; + const diffs = thing.getDiffs(minimalFileList, minimalFileList2, true, true, false); + + expect(diffs.upload.length).toEqual(0); + expect(diffs.delete.length).toEqual(0); + expect(diffs.replace.length).toEqual(0); + + expect(diffs.sizeUpload).toEqual(0); + expect(diffs.sizeDelete).toEqual(0); + expect(diffs.sizeReplace).toEqual(0); + }); }); describe("FTP sync commands", () => { @@ -587,6 +674,9 @@ describe("getLocalFiles", () => { "dry-run": true, "dangerous-clean-slate": false, exclude: [], + allowUpload: true, + allowReplace: true, + allowDelete: true, "log-level": "standard", security: "loose", }); diff --git a/src/types.ts b/src/types.ts index b825922..6f6f117 100644 --- a/src/types.ts +++ b/src/types.ts @@ -47,6 +47,24 @@ export interface IFtpDeployArguments { */ exclude?: string[]; + /** + * Set to false to skip uploading new files and folders + * @default true + */ + allowUpload?: boolean; + + /** + * Set to false to skip replacing existing files and folders + * @default true + */ + allowReplace?: boolean; + + /** + * Set to false to skip deleting obsoleted files and folders + * @default true + */ + allowDelete?: boolean; + /** * How much information should print. minimal=only important info, standard=important info and basic file changes, verbose=print everything the script is doing * @default "info" @@ -75,6 +93,9 @@ export interface IFtpDeployArgumentsWithDefaults { "dry-run": boolean; "dangerous-clean-slate": boolean; exclude: string[]; + allowUpload: boolean; + allowReplace: boolean; + allowDelete: boolean; "log-level": "minimal" | "standard" | "verbose"; security: "strict" | "loose"; } @@ -123,7 +144,7 @@ export type DiffResult = { } export interface IDiff { - getDiffs(localFiles: IFileList, serverFiles: IFileList): DiffResult; + getDiffs(localFiles: IFileList, serverFiles: IFileList, allowUpload: boolean, allowReplace: boolean, allowDelete: boolean): DiffResult; } export interface IFilePath { diff --git a/src/utilities.ts b/src/utilities.ts index 5aed2a5..23b1c5f 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -186,6 +186,9 @@ export function getDefaultSettings(withoutDefaults: IFtpDeployArguments): IFtpDe "dry-run": withoutDefaults["dry-run"] ?? false, "dangerous-clean-slate": withoutDefaults["dangerous-clean-slate"] ?? false, "exclude": withoutDefaults.exclude ?? excludeDefaults, + "allowUpload": withoutDefaults.allowUpload ?? true, + "allowReplace": withoutDefaults.allowReplace ?? true, + "allowDelete": withoutDefaults.allowDelete ?? true, "log-level": withoutDefaults["log-level"] ?? "standard", "security": withoutDefaults.security ?? "loose", };