phio 0.3.5 → 0.4.0

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.
@@ -0,0 +1,226 @@
1
+ export const currentSyncFileVersion = "1.0.0";
2
+ export const syncFileDescription = "DO NOT DELETE THIS FILE. This file is used to keep track of which files have been synced in the most recent deployment. If you delete this file a resync will need to be done (which can take a while) - read more: https://github.com/SamKirkland/FTP-Deploy-Action";
3
+
4
+ export interface IFtpDeployArguments {
5
+ server: string;
6
+ username: string;
7
+ /** Not used when protocol is "sftp" (use private-key-path instead). */
8
+ password?: string;
9
+
10
+ /**
11
+ * Server port
12
+ * @default 21 (ftp/ftps) or 2222 (sftp)
13
+ */
14
+ port?: number;
15
+
16
+ /**
17
+ * "ftp": provides no encryption
18
+ * "ftps": full encryption newest standard (aka "explicit" ftps)
19
+ * "ftps-legacy": full encryption legacy standard (aka "implicit" ftps)
20
+ * "sftp": SSH file transfer (Ed25519 key auth)
21
+ * @default "ftp"
22
+ */
23
+ protocol?: "ftp" | "ftps" | "ftps-legacy" | "sftp";
24
+
25
+ /** PEM or OpenSSH private key path. Required when protocol is "sftp". */
26
+ "private-key-path"?: string;
27
+
28
+ /** @default "./" */
29
+ "local-dir"?: string;
30
+
31
+ /** @default "./" */
32
+ "server-dir"?: string;
33
+
34
+ /** @default ".ftp-deploy-sync-state.json" */
35
+ "state-name"?: string;
36
+
37
+ /**
38
+ * Prints which modifications will be made with current config options, but doesn't actually make any changes
39
+ * @default false
40
+ */
41
+ "dry-run"?: boolean;
42
+
43
+ /**
44
+ * Deletes ALL contents of server-dir, even items in excluded with 'exclude' argument
45
+ * @default false
46
+ */
47
+ "dangerous-clean-slate"?: boolean;
48
+
49
+ /**
50
+ * An array of glob patterns, these files will ONLY be included in the publish/delete process
51
+ * @default [ "**\/*" ]
52
+ */
53
+ include?: string[];
54
+
55
+ /**
56
+ * An array of glob patterns, these files will not be included in the publish/delete process
57
+ * @default [ ".git*", ".git*\/**", "node_modules\/**", "node_modules\/**\/*" ]
58
+ */
59
+ exclude?: string[];
60
+
61
+ /**
62
+ * How much information should print. minimal=only important info, standard=important info and basic file changes, verbose=print everything the script is doing
63
+ * @default "info"
64
+ */
65
+ "log-level"?: "minimal" | "standard" | "verbose";
66
+
67
+ /**
68
+ * When using protocol "ftps" or "ftps-legacy" should the cert name need to match exactly?
69
+ * Set this to "strict" to ensure your data is being encrypted
70
+ *
71
+ * Defaults to loose because of the sheer volume of shared hosts that give ftp domains a cert without a matching common name
72
+ * @default "loose"
73
+ */
74
+ security?: "strict" | "loose";
75
+
76
+ /**
77
+ * Timeout in milliseconds for FTP operations as handled by underlying basic-ftp connection library.
78
+ * @default 30000 (30 seconds)
79
+ */
80
+ timeout?: number;
81
+ }
82
+
83
+ export interface IFtpDeployArgumentsWithDefaults {
84
+ server: string;
85
+ username: string;
86
+ password: string;
87
+ port: number;
88
+ protocol: "ftp" | "ftps" | "ftps-legacy" | "sftp";
89
+ "private-key-path": string | undefined;
90
+ "local-dir": string;
91
+ "server-dir": string;
92
+ "state-name": string;
93
+ "dry-run": boolean;
94
+ "dangerous-clean-slate": boolean;
95
+ include: string[];
96
+ exclude: string[];
97
+ "log-level": "minimal" | "standard" | "verbose";
98
+ security: "strict" | "loose";
99
+ timeout: number;
100
+ }
101
+
102
+ export interface IFile {
103
+ type: "file";
104
+ name: string;
105
+ size: number;
106
+ hash: string;
107
+ }
108
+
109
+ export interface IFolder {
110
+ type: "folder";
111
+ name: string;
112
+ size: undefined;
113
+ }
114
+
115
+ export type Record = IFolder | IFile;
116
+
117
+ export interface IFileList {
118
+ /** Give some info to people opening this file wondering what it is */
119
+ description: string;
120
+
121
+ /** version number of this diff file */
122
+ version: "1.0.0";
123
+
124
+ /** UTC time of the last publish start */
125
+ generatedTime: number;
126
+ data: Record[];
127
+ }
128
+
129
+ export type DiffResult = {
130
+ upload: Record[];
131
+ delete: Record[];
132
+ replace: Record[];
133
+ same: Record[];
134
+
135
+ /** number of bytes that will need to be uploaded */
136
+ sizeUpload: number;
137
+
138
+ /** number of bytes that will need to be removed */
139
+ sizeDelete: number;
140
+
141
+ /** number of bytes that will need to be replaced */
142
+ sizeReplace: number;
143
+ }
144
+
145
+ export interface IDiff {
146
+ getDiffs(localFiles: IFileList, serverFiles: IFileList): DiffResult;
147
+ }
148
+
149
+ export interface IFilePath {
150
+ /** will be null when no folders exist in path */
151
+ folders: string[] | null;
152
+
153
+ /** will be null when no file exists in path */
154
+ file: string | null;
155
+ }
156
+
157
+ export enum ErrorCode {
158
+ // The requested action is being initiated, expect another reply before proceeding with a new command.
159
+ RestartMarkerReplay = 110,
160
+ ServiceReadyInNNNMinutes = 120,
161
+ DataConnectionAlreadyOpenStartingTransfer = 125,
162
+ FileStatusOkayOpeningDataConnection = 150,
163
+
164
+ // The requested action has been successfully completed.
165
+ CommandNotImplemented = 202,
166
+ SystemStatus = 211,
167
+ DirectoryStatus = 212,
168
+ FileStatus = 213,
169
+ HelpMessage = 214,
170
+ IANAOfficialName = 215,
171
+ ReadyForNewUser = 220,
172
+ ClosingControlConnection = 221,
173
+ DataConnectionOpen = 225,
174
+ SuccessNowClosingDataConnection = 226,
175
+ EnteringPassiveMode = 227,
176
+ EnteringLongPassiveMode = 228,
177
+ EnteringExtendedPassiveMode = 229,
178
+ UserLoggedIn = 230,
179
+ UserLoggedOut = 231,
180
+ LogoutWillCompleteWhenTransferDone = 232,
181
+ ServerAcceptsAuthenticationMethod = 234,
182
+ ActionComplete = 250,
183
+ PathNameCreated = 257,
184
+
185
+ // The command has been accepted, but the requested action is on hold, pending receipt of further information.
186
+ UsernameOkayPasswordNeeded = 331,
187
+ NeedAccountForLogin = 332,
188
+ RequestedFileActionPendingFurtherInformation = 350,
189
+
190
+
191
+ // The command was not accepted and the requested action did not take place, but the error condition is temporary and the action may be requested again.
192
+ ServiceNotAvailable = 421,
193
+ CantOpenDataConnection = 425,
194
+ ConnectionClosed = 426,
195
+ InvalidUsernameOrPassword = 430,
196
+ HostUnavailable = 434,
197
+ FileActionNotTaken = 450,
198
+ LocalErrorProcessing = 451,
199
+ InsufficientStorageSpaceOrFileInUse = 452,
200
+
201
+ // Syntax error, command unrecognized and the requested action did not take place. This may include errors such as command line too long.
202
+ SyntaxErrorInParameters = 501,
203
+ CommandNotImpemented = 502,
204
+ BadSequenceOfCommands = 503,
205
+ CommandNotImplementedForThatParameter = 504,
206
+ NotLoggedIn = 530,
207
+ NeedAccountForStoringFiles = 532,
208
+ CouldNotConnectToServerRequiresSSL = 534,
209
+ FileNotFoundOrNoAccess = 550,
210
+ UnknownPageType = 551,
211
+ ExceededStorageAllocation = 552,
212
+ FileNameNotAllowed = 553,
213
+
214
+ // Replies regarding confidentiality and integrity
215
+ IntegrityProtectedReply = 631,
216
+ ConfidentialityAndIntegrityProtectedReply = 632,
217
+ ConfidentialityProtectedReply = 633,
218
+
219
+
220
+ // Common Winsock Error Codes[2] (These are not FTP return codes)
221
+ ConnectionClosedByServer = 10054,
222
+ CannotConnect = 10060,
223
+ CannotConnectRefusedByServer = 10061,
224
+ DirectoryNotEmpty = 10066,
225
+ TooManyUsers = 10068,
226
+ };
@@ -0,0 +1,217 @@
1
+ import prettyMilliseconds from "pretty-ms";
2
+ import { includeDefaults, excludeDefaults } from "./module";
3
+ import { ErrorCode, IFtpDeployArguments, IFtpDeployArgumentsWithDefaults } from "./types";
4
+ import multimatch from "multimatch";
5
+
6
+ export interface ILogger {
7
+ all(...data: any[]): void;
8
+ standard(...data: any[]): void;
9
+ verbose(...data: any[]): void;
10
+ }
11
+
12
+ export class Logger implements ILogger {
13
+ constructor(level: "minimal" | "standard" | "verbose") {
14
+ this.level = level;
15
+ }
16
+
17
+ private level: "minimal" | "standard" | "verbose";
18
+
19
+ public all(...data: any[]): void {
20
+ console.log(...data);
21
+ }
22
+
23
+ public standard(...data: any[]): void {
24
+ if (this.level === "minimal") { return; }
25
+
26
+ console.log(...data);
27
+ }
28
+
29
+ public verbose(...data: any[]): void {
30
+ if (this.level !== "verbose") { return; }
31
+
32
+ console.log(...data);
33
+ }
34
+ }
35
+
36
+ export function pluralize(count: number, singular: string, plural: string) {
37
+ if (count === 1) {
38
+ return singular;
39
+ }
40
+
41
+ return plural;
42
+ }
43
+
44
+ export function formatNumber(number: number): string {
45
+ return number.toLocaleString();
46
+ }
47
+
48
+ /**
49
+ * retry a request
50
+ *
51
+ * @example retryRequest(logger, async () => await item());
52
+ */
53
+ export async function retryRequest<T>(logger: ILogger, callback: () => Promise<T>): Promise<T> {
54
+ try {
55
+ return await callback();
56
+ }
57
+ catch (e: any) {
58
+ if (e.code >= 400 && e.code <= 499) {
59
+ logger.standard("400 level error from server when performing action - retrying...");
60
+ logger.standard(e);
61
+
62
+ if (e.code === ErrorCode.ConnectionClosed) {
63
+ logger.all("Connection closed. This library does not currently handle reconnects");
64
+ // await global.reconnect();
65
+ // todo reset current working dir
66
+ throw e;
67
+ }
68
+
69
+ return await callback();
70
+ }
71
+ else {
72
+ throw e;
73
+ }
74
+ }
75
+ }
76
+
77
+ interface ITimers {
78
+ [key: string]: Timer | undefined;
79
+ }
80
+
81
+ type AvailableTimers = "connecting" | "hash" | "upload" | "total" | "changingDir" | "logging";
82
+
83
+ export interface ITimings {
84
+ start(type: AvailableTimers): void;
85
+ stop(type: AvailableTimers): void;
86
+ getTime(type: AvailableTimers): number;
87
+ getTimeFormatted(type: AvailableTimers): string;
88
+ }
89
+
90
+ export class Timings implements ITimings {
91
+ private timers: ITimers = {};
92
+
93
+ public start(type: AvailableTimers): void {
94
+ if (this.timers[type] === undefined) {
95
+ this.timers[type] = new Timer();
96
+ }
97
+
98
+ this.timers[type]!.start();
99
+ }
100
+
101
+ public stop(type: AvailableTimers): void {
102
+ this.timers[type]!.stop();
103
+ }
104
+
105
+ public getTime(type: AvailableTimers): number {
106
+ const timer = this.timers[type];
107
+ if (timer === undefined || timer.time === null) {
108
+ return 0;
109
+ }
110
+
111
+ return timer.time;
112
+ }
113
+
114
+ public getTimeFormatted(type: AvailableTimers): string {
115
+ const timer = this.timers[type];
116
+ if (timer === undefined || timer.time === null) {
117
+ return "💣 Failed";
118
+ }
119
+
120
+ return prettyMilliseconds(timer.time, { verbose: true });
121
+ }
122
+ }
123
+
124
+ /**
125
+ * first number is seconds
126
+ * second number is nanoseconds
127
+ */
128
+ type HRTime = [number, number];
129
+
130
+ export class Timer {
131
+ private totalTime: HRTime | null = null;
132
+ private startTime: HRTime | null = null;
133
+ private endTime: HRTime | null = null;
134
+
135
+ start() {
136
+ this.startTime = process.hrtime();
137
+ }
138
+
139
+ stop() {
140
+ if (this.startTime === null) {
141
+ throw new Error("Called .stop() before calling .start()");
142
+ }
143
+
144
+ this.endTime = process.hrtime(this.startTime);
145
+
146
+ const currentSeconds = this.totalTime === null ? 0 : this.totalTime[0];
147
+ const currentNS = this.totalTime === null ? 0 : this.totalTime[1];
148
+
149
+ this.totalTime = [
150
+ currentSeconds + this.endTime[0],
151
+ currentNS + this.endTime[1]
152
+ ];
153
+ }
154
+
155
+ get time() {
156
+ if (this.totalTime === null) {
157
+ return null;
158
+ }
159
+
160
+ return (this.totalTime[0] * 1000) + (this.totalTime[1] / 1000000);
161
+ }
162
+ }
163
+
164
+ export function getDefaultSettings(withoutDefaults: IFtpDeployArguments): IFtpDeployArgumentsWithDefaults {
165
+ if (withoutDefaults["local-dir"] !== undefined) {
166
+ if (!withoutDefaults["local-dir"].endsWith("/")) {
167
+ throw new Error("local-dir should be a folder (must end with /)");
168
+ }
169
+ }
170
+
171
+ if (withoutDefaults["server-dir"] !== undefined) {
172
+ if (!withoutDefaults["server-dir"].endsWith("/")) {
173
+ throw new Error("server-dir should be a folder (must end with /)");
174
+ }
175
+ }
176
+
177
+ const protocol = withoutDefaults.protocol ?? "ftp";
178
+
179
+ return {
180
+ "server": withoutDefaults.server,
181
+ "username": withoutDefaults.username,
182
+ "password": withoutDefaults.password ?? "",
183
+ "port": withoutDefaults.port ?? (protocol === "sftp" ? 2222 : 21),
184
+ "protocol": protocol,
185
+ "private-key-path": withoutDefaults["private-key-path"],
186
+ "local-dir": withoutDefaults["local-dir"] ?? "./",
187
+ "server-dir": withoutDefaults["server-dir"] ?? "./",
188
+ "state-name": withoutDefaults["state-name"] ?? ".ftp-deploy-sync-state.json",
189
+ "dry-run": withoutDefaults["dry-run"] ?? false,
190
+ "dangerous-clean-slate": withoutDefaults["dangerous-clean-slate"] ?? false,
191
+ "include": withoutDefaults.include ?? includeDefaults,
192
+ "exclude": withoutDefaults.exclude ?? excludeDefaults,
193
+ "log-level": withoutDefaults["log-level"] ?? "standard",
194
+ "security": withoutDefaults.security ?? "loose",
195
+ "timeout": withoutDefaults.timeout ?? 30000,
196
+ };
197
+ }
198
+
199
+ interface IStats {
200
+ path: string;
201
+ isDirectory(): boolean;
202
+ }
203
+
204
+ export function applyExcludeFilter(stat: IStats, excludeFilters: Readonly<string[]>): boolean {
205
+ // match exclude, return immediatley
206
+ if (excludeFilters.length > 0) {
207
+ // todo this could be a performance problem...
208
+ const pathWithFolderSlash = stat.path + (stat.isDirectory() ? "/" : "");
209
+ const excludeMatch = multimatch(pathWithFolderSlash, excludeFilters, { matchBase: true, dot: true });
210
+
211
+ if (excludeMatch.length > 0) {
212
+ return false;
213
+ }
214
+ }
215
+
216
+ return true;
217
+ }
@@ -1,15 +0,0 @@
1
- import { Command } from 'commander'
2
- import { config } from '../lib/config'
3
-
4
- export const WhoAmICommand = () => {
5
- return new Command(`whoami`)
6
- .description(`Show the current user`)
7
- .action(() => {
8
- const email = config(`email`)
9
- if (email) {
10
- console.log(`Current user is: ${email}`)
11
- return
12
- }
13
- console.log(`No user is currently logged in`)
14
- })
15
- }
package/src/global.d.ts DELETED
@@ -1,3 +0,0 @@
1
- declare module '@sentool/fetch-event-source' {
2
- export function fetchEventSource(url: string, options: any): void
3
- }