Skip to content

Commit

Permalink
DRAFT: Add support for syncing file modes
Browse files Browse the repository at this point in the history
  • Loading branch information
timthelion committed Feb 2, 2022
1 parent f79bd10 commit ea36792
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 12 deletions.
1 change: 1 addition & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const argv = yargs.options({
"state-name": { type: "string", default: ".ftp-deploy-sync-state.json" },
"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" },
"sync-posix-modes": { type: "boolean", default: false, description: "Sync POSIX file modes to server for new files. (Note: Only supported on POSIX compatible FTP servers.)"},
"exclude": { type: "array", default: excludeDefaults, description: "An array of glob patterns, these files will not be included in the publish/delete process" },
"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: "" }
Expand Down
4 changes: 2 additions & 2 deletions src/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export async function deploy(args: IFtpDeployArgumentsWithDefaults, logger: ILog

timings.start("upload");
try {
const syncProvider = new FTPSyncProvider(client, logger, timings, args["local-dir"], args["server-dir"], args["state-name"], args["dry-run"]);
const syncProvider = new FTPSyncProvider(client, logger, timings, args["local-dir"], args["server-dir"], args["state-name"], args["dry-run"], args["sync-posix-modes"]);
await syncProvider.syncLocalToServer(diffs);
}
finally {
Expand Down Expand Up @@ -213,4 +213,4 @@ export async function deploy(args: IFtpDeployArgumentsWithDefaults, logger: ILog
logger.all(`----------------------------------------------------------------`);
logger.all(`Total time: ${timings.getTimeFormatted("total")}`);
logger.all(`----------------------------------------------------------------`);
}
}
13 changes: 7 additions & 6 deletions src/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ describe("FTP sync commands", () => {
ensureDir() { },
uploadFrom() { },
};
const syncProvider = new FTPSyncProvider(mockClient as any, mockedLogger, mockedTimings, "local-dir/", "server-dir/", "state-name", false);
const syncProvider = new FTPSyncProvider(mockClient as any, mockedLogger, mockedTimings, "local-dir/", "server-dir/", "state-name", false, false);
const spyRemoveFile = jest.spyOn(syncProvider, "uploadFile");
const mockClientUploadFrom = jest.spyOn(mockClient, "uploadFrom");
await syncProvider.syncLocalToServer(diffs);
Expand Down Expand Up @@ -322,7 +322,7 @@ describe("FTP sync commands", () => {
remove() { },
uploadFrom() { },
};
const syncProvider = new FTPSyncProvider(mockClient as any, mockedLogger, mockedTimings, "local-dir/", "server-dir/", "state-name", false);
const syncProvider = new FTPSyncProvider(mockClient as any, mockedLogger, mockedTimings, "local-dir/", "server-dir/", "state-name", false, false);
const spyUploadFile = jest.spyOn(syncProvider, "uploadFile");
const spyRemoveFile = jest.spyOn(syncProvider, "removeFile");
const mockClientUploadFrom = jest.spyOn(mockClient, "uploadFrom");
Expand Down Expand Up @@ -386,7 +386,7 @@ describe("FTP sync commands", () => {
remove() { },
uploadFrom() { },
};
const syncProvider = new FTPSyncProvider(mockClient as any, mockedLogger, mockedTimings, "local-dir/", "server-dir/", "state-name", false);
const syncProvider = new FTPSyncProvider(mockClient as any, mockedLogger, mockedTimings, "local-dir/", "server-dir/", "state-name", false, false);
const spyUploadFile = jest.spyOn(syncProvider, "uploadFile");
const mockClientUploadFrom = jest.spyOn(mockClient, "uploadFrom");
await syncProvider.syncLocalToServer(diffs);
Expand Down Expand Up @@ -435,7 +435,7 @@ describe("FTP sync commands", () => {
remove() { },
uploadFrom() { },
};
const syncProvider = new FTPSyncProvider(mockClient as any, mockedLogger, mockedTimings, "local-dir/", "server-dir/", "state-name", false);
const syncProvider = new FTPSyncProvider(mockClient as any, mockedLogger, mockedTimings, "local-dir/", "server-dir/", "state-name", false, false);
const spyRemoveFile = jest.spyOn(syncProvider, "removeFile");
const mockClientRemove = jest.spyOn(mockClient, "remove");
const mockClientUploadFrom = jest.spyOn(mockClient, "uploadFrom");
Expand Down Expand Up @@ -493,7 +493,7 @@ describe("FTP sync commands", () => {
uploadFrom() { },
cdup() { },
};
const syncProvider = new FTPSyncProvider(mockClient as any, mockedLogger, mockedTimings, "local-dir/", "server-dir/", "state-name", false);
const syncProvider = new FTPSyncProvider(mockClient as any, mockedLogger, mockedTimings, "local-dir/", "server-dir/", "state-name", false, false);
const spyRemoveFolder = jest.spyOn(syncProvider, "removeFolder");
const mockClientRemove = jest.spyOn(mockClient, "remove");
const mockClientUploadFrom = jest.spyOn(mockClient, "uploadFrom");
Expand Down Expand Up @@ -589,6 +589,7 @@ describe("getLocalFiles", () => {
exclude: [],
"log-level": "standard",
security: "loose",
"sync-posix-modes": true,
});

const mainYamlDiff = localDirDiffs.data.find(diff => diff.name === "workflows/main.yml")! as IFile;
Expand Down Expand Up @@ -760,4 +761,4 @@ describe("Deploy", () => {

ftpServer.close();
}, 30000);
});
});
25 changes: 23 additions & 2 deletions src/syncProvider.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { stat } from "fs";

import prettyBytes from "pretty-bytes";
import type * as ftp from "basic-ftp";
import { DiffResult, ErrorCode, IFilePath } from "./types";
import { DiffResult, ErrorCode, IFilePath, Record } from "./types";
import { ILogger, pluralize, retryRequest, ITimings } from "./utilities";

export async function ensureDir(client: ftp.Client, logger: ILogger, timings: ITimings, folder: string): Promise<void> {
Expand Down Expand Up @@ -29,14 +31,15 @@ interface ISyncProvider {
}

export class FTPSyncProvider implements ISyncProvider {
constructor(client: ftp.Client, logger: ILogger, timings: ITimings, localPath: string, serverPath: string, stateName: string, dryRun: boolean) {
constructor(client: ftp.Client, logger: ILogger, timings: ITimings, localPath: string, serverPath: string, stateName: string, dryRun: boolean, syncPosixModes: boolean) {
this.client = client;
this.logger = logger;
this.timings = timings;
this.localPath = localPath;
this.serverPath = serverPath;
this.stateName = stateName;
this.dryRun = dryRun;
this.syncPosixModes = syncPosixModes;
}

private client: ftp.Client;
Expand All @@ -45,6 +48,7 @@ export class FTPSyncProvider implements ISyncProvider {
private localPath: string;
private serverPath: string;
private dryRun: boolean;
private syncPosixModes: boolean;
private stateName: string;


Expand Down Expand Up @@ -146,6 +150,21 @@ export class FTPSyncProvider implements ISyncProvider {
this.logger.verbose(` file ${typePast}`);
}

async syncMode(file: Record) {
if (this.syncPosixModes) {
// https://www.martin-brennan.com/nodejs-file-permissions-fstat/
stat(this.localPath + file.name, (err, stats) => {
let mode: string = "0" + (stats.mode & parseInt('777', 8)).toString(8);
// https://github.com/patrickjuchli/basic-ftp/issues/9
let command = "SITE CHMOD " + mode + " " + file.name
if (this.dryRun == false) {
this.client.ftp.send(command);
}
this.logger.verbose("Setting file mode with command " + command);
});
}
}

async syncLocalToServer(diffs: DiffResult) {
const totalCount = diffs.delete.length + diffs.upload.length + diffs.replace.length;

Expand All @@ -157,11 +176,13 @@ export class FTPSyncProvider implements ISyncProvider {
// create new folders
for (const file of diffs.upload.filter(item => item.type === "folder")) {
await this.createFolder(file.name);
this.syncMode(file);
}

// upload new files
for (const file of diffs.upload.filter(item => item.type === "file").filter(item => item.name !== this.stateName)) {
await this.uploadFile(file.name, "upload");
this.syncMode(file);
}

// replace new files
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export interface IFtpDeployArgumentsWithDefaults {
"server-dir": string;
"state-name": string;
"dry-run": boolean;
"sync-posix-modes": boolean;
"dangerous-clean-slate": boolean;
exclude: string[];
"log-level": "minimal" | "standard" | "verbose";
Expand Down Expand Up @@ -203,4 +204,4 @@ export enum ErrorCode {
CannotConnectRefusedByServer = 10061,
DirectoryNotEmpty = 10066,
TooManyUsers = 10068,
};
};
3 changes: 2 additions & 1 deletion src/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export function getDefaultSettings(withoutDefaults: IFtpDeployArguments): IFtpDe
"exclude": withoutDefaults.exclude ?? excludeDefaults,
"log-level": withoutDefaults["log-level"] ?? "standard",
"security": withoutDefaults.security ?? "loose",
"sync-posix-modes": false,
};
}

Expand All @@ -209,4 +210,4 @@ export function applyExcludeFilter(stat: IStats, excludeFilters: Readonly<string
}

return true;
}
}

0 comments on commit ea36792

Please sign in to comment.