syncodemayo 1.0.8 → 2.0.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +99 -30
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +66 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/env.d.ts +1 -0
  7. package/dist/env.js +17 -0
  8. package/dist/env.js.map +1 -0
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.js +2 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/lib/Args.d.ts +21 -0
  13. package/dist/lib/Args.js +99 -0
  14. package/dist/lib/Args.js.map +1 -0
  15. package/{lib → dist/lib}/Cli.d.ts +5 -0
  16. package/dist/lib/Cli.js +104 -0
  17. package/dist/lib/Cli.js.map +1 -0
  18. package/{lib → dist/lib}/Client.d.ts +5 -3
  19. package/dist/lib/Client.js +73 -0
  20. package/dist/lib/Client.js.map +1 -0
  21. package/dist/lib/Config.d.ts +48 -0
  22. package/dist/lib/Config.js +92 -0
  23. package/dist/lib/Config.js.map +1 -0
  24. package/dist/lib/ConfigSchema.d.ts +3 -0
  25. package/dist/lib/ConfigSchema.js +132 -0
  26. package/dist/lib/ConfigSchema.js.map +1 -0
  27. package/dist/lib/Connection.d.ts +41 -0
  28. package/dist/lib/Connection.js +176 -0
  29. package/dist/lib/Connection.js.map +1 -0
  30. package/dist/lib/Filelist.d.ts +42 -0
  31. package/dist/lib/Filelist.js +156 -0
  32. package/dist/lib/Filelist.js.map +1 -0
  33. package/dist/lib/Log.d.ts +12 -0
  34. package/dist/lib/Log.js +46 -0
  35. package/dist/lib/Log.js.map +1 -0
  36. package/dist/lib/Prompt.d.ts +15 -0
  37. package/dist/lib/Prompt.js +31 -0
  38. package/dist/lib/Prompt.js.map +1 -0
  39. package/dist/lib/RemoteConnection.d.ts +37 -0
  40. package/dist/lib/RemoteConnection.js +2 -0
  41. package/dist/lib/RemoteConnection.js.map +1 -0
  42. package/dist/lib/Sync.d.ts +18 -0
  43. package/dist/lib/Sync.js +127 -0
  44. package/dist/lib/Sync.js.map +1 -0
  45. package/dist/lib/TargetServer.d.ts +31 -0
  46. package/dist/lib/TargetServer.js +76 -0
  47. package/dist/lib/TargetServer.js.map +1 -0
  48. package/dist/lib/index.d.ts +8 -0
  49. package/dist/lib/index.js +9 -0
  50. package/dist/lib/index.js.map +1 -0
  51. package/dist/vendor/strip-json-comments.d.ts +23 -0
  52. package/dist/vendor/strip-json-comments.js +126 -0
  53. package/dist/vendor/strip-json-comments.js.map +1 -0
  54. package/package.json +24 -26
  55. package/.npmignore +0 -9
  56. package/cli.d.ts +0 -0
  57. package/cli.js +0 -93
  58. package/index.d.ts +0 -1
  59. package/index.js +0 -7
  60. package/lib/Cli.js +0 -122
  61. package/lib/Client.js +0 -81
  62. package/lib/Config.d.ts +0 -32
  63. package/lib/Config.js +0 -115
  64. package/lib/ConfigSchema.d.ts +0 -7
  65. package/lib/ConfigSchema.js +0 -65
  66. package/lib/Connection.d.ts +0 -14
  67. package/lib/Connection.js +0 -111
  68. package/lib/Filelist.d.ts +0 -11
  69. package/lib/Filelist.js +0 -59
  70. package/lib/Sync.d.ts +0 -18
  71. package/lib/Sync.js +0 -141
  72. package/lib/TargetServer.d.ts +0 -22
  73. package/lib/TargetServer.js +0 -73
  74. package/lib/index.d.ts +0 -7
  75. package/lib/index.js +0 -13
  76. package/missing-types.d.ts +0 -14
  77. package/tsconfig.json +0 -48
  78. package/yarn.lock +0 -975
package/CHANGELOG.md ADDED
@@ -0,0 +1,81 @@
1
+ # Changelog
2
+
3
+ ## 2.0.0
4
+
5
+ A modernized SyncoDeMayo: ESM, Node.js built-ins in place of most dependencies, FTPS support,
6
+ and fixes for v1 bugs. Some behavior changes; read the migration notes below before upgrading.
7
+
8
+ ### Migration from 1.x
9
+
10
+ **Before your first 2.0 sync, run `syncodemayo diff [target]`** and read the list of changes.
11
+ 2.0 now syncs dotfiles and builds remote paths differently (see below), so the first sync may
12
+ upload files v1 never did. This matters most with `deleteRemoteFiles: true`: check that nothing
13
+ you want kept on the server is listed for deletion, and that nothing private (such as `.env`) is
14
+ listed for upload.
15
+
16
+ #### Requirements and packaging
17
+
18
+ - Node.js 22.17 or later is required (`engines.node` is `>=22.17`).
19
+ - The package is ESM. The CLI is `dist/cli.js`; the library entry is `dist/index.js`, with
20
+ types in `dist/index.d.ts`. CommonJS callers can still `require()` the library on Node.js
21
+ 22.12 and later.
22
+ - Runtime dependencies drop from ten to one (`basic-ftp`).
23
+ - The library no longer exports the joi schemas `localSchema`, `targetSchema`, and
24
+ `configSchema`.
25
+
26
+ #### CLI
27
+
28
+ - An unknown command, extra positional arguments, and `-f` on any command other than `sync`
29
+ now exit 1 with an error. v1 printed the help or ignored them.
30
+ - `-c` now requires a value (`-c <file>`).
31
+ - The help text is reworded but lists the same commands and options.
32
+ - `--version` / `-V` prints the real package version.
33
+
34
+ #### Config
35
+
36
+ - Config validation errors keep the `Config Schema Error:` prefix, but the detail after it is
37
+ new.
38
+ - `local`, `targets`, or `exclude` given as a JSON *string* is now rejected.
39
+ - `.syncodemayo.json` and `sync-config.json` are now found when `syncodemayo.json` is absent.
40
+ - With no config, `init` creates `syncodemayo.json`, and `verify` / `ls` report that the folder
41
+ is not configured instead of throwing "Local config not found."
42
+ - Configs created by `init` exclude `**/.env*` by default. Existing configs are not changed:
43
+ if you have a `.env` under `local.path`, add `"**/.env*"` to your `exclude` list yourself.
44
+
45
+ #### File list
46
+
47
+ - Dotfiles and files inside dot-directories (such as `.htaccess` or `.well-known/`) are now
48
+ synced. v1 skipped them.
49
+ - `**/.git*` excludes `.gitignore` but not the files inside a `.git/` directory. Add
50
+ `**/.git/**` to keep one off the server.
51
+ - On macOS and Windows, `files` and `exclude` patterns match case-insensitively.
52
+ - Exclude patterns that start with `!` are matched literally, not as negations.
53
+ - `*.md` and `**/*.md` no longer match a file named exactly `.md`.
54
+ - Symlinked directories are walked for every `files` pattern, the way v1's default `**/**`
55
+ walked them: in full, without entering a symlinked directory inside one. Dangling symlinks
56
+ and symlink loops are skipped.
57
+
58
+ #### Remote and sync
59
+
60
+ - `init` no longer creates a missing remote `path`. It fails and tells you to create the
61
+ folder. `sync` still creates subfolders under the target path.
62
+ - A missing or unreadable remote file list is now a normal error (still exit code 1) instead
63
+ of a `process.exit` from inside the library.
64
+ - Remote paths are correct when `local.path` has a trailing or repeated slash, or
65
+ `target.path` has a trailing slash. v1 glued names together (`www` + `a.html` →
66
+ `wwwa.html`) or doubled slashes.
67
+
68
+ ### Added
69
+
70
+ - FTPS support through two new target fields:
71
+ - `secure`: `false` (default, plain FTP), `true` (explicit FTPS, `AUTH TLS`), or
72
+ `"implicit"` (implicit FTPS, default port 990).
73
+ - `rejectUnauthorized`: `true` (default) requires a trusted server certificate; `false`
74
+ accepts a self-signed one and prints a warning naming the target on every connection.
75
+
76
+ See the FTPS section of the [README](README.md#ftps-ftp-over-tls).
77
+
78
+ ### Unchanged
79
+
80
+ - A remote `.synco-filelist` written by v1 still diffs correctly: 2.0 uses the same keys and
81
+ CRCs, so upgrading does not re-upload everything.
package/README.md CHANGED
@@ -7,6 +7,11 @@ It's pretty simple. SyncoDeMayo caches a list of known files, and their CRCs, on
7
7
 
8
8
  ---
9
9
 
10
+ Requirements: Node.js 22.17 or later.
11
+
12
+ Upgrading from 1.x? Version 2.0 changes some behavior. Read the migration notes in
13
+ [CHANGELOG.md](CHANGELOG.md) before your first 2.0 sync.
14
+
10
15
  Install globally:
11
16
  ```
12
17
  $ npm install -g syncodemayo
@@ -21,7 +26,7 @@ Suggested usage as NPM script:
21
26
  ```js
22
27
  {
23
28
  // ... OTHER NPM FIELDS ...
24
- "script": {
29
+ "scripts": {
25
30
  "sync": "syncodemayo sync staging",
26
31
  "sync:production": "syncodemayo sync production"
27
32
  }
@@ -31,38 +36,39 @@ Suggested usage as NPM script:
31
36
  ---
32
37
 
33
38
  ```
34
- $ syncodemayo
35
-
36
- Usage: syncodemayo [options] [command]
37
-
38
-
39
- Options:
40
-
41
- -V, --version output the version number
42
- -c, --config [file] Specify local config [file]
43
- -h, --help output usage information
44
-
45
-
46
- Commands:
47
-
48
- init [target] Configure local folder and/or server to sync
49
- changes|diff [target] Perform sync dry run and display the changes
50
- verify [target] Verify server is configured
51
- sync [options] [target] Perform sync to server
52
- ls List defined targets in config
39
+ $ syncodemayo --help
40
+ Usage: syncodemayo [options] [command]
41
+
42
+ Options:
43
+ -c, --config <file> Specify local config <file>
44
+ -v, --verbose Verbose logging
45
+ -q, --quiet Minimal logging
46
+ -V, --version Output the version number
47
+ -h, --help Output usage information
48
+
49
+ Commands:
50
+ init [target] Configure local folder and/or server to sync
51
+ changes|diff [target] Perform sync dry run and display the changes
52
+ verify [target] Verify server is configured
53
+ sync [options] [target]
54
+ Perform sync to server
55
+ -f, --force Don't prompt before uploading.
56
+ ls List defined targets in config
57
+
58
+ With no command, shows this help.
53
59
  ```
54
60
 
55
61
 
56
62
  Example `syncodemayo.json` config:
57
63
 
58
- ```json
64
+ ```js
59
65
  {
60
66
  "local": {
61
- "path": "public",
62
- "files": "**/**",
63
- "exclude": ["**/*.map", "**/.DS_Store", "**/.git*"],
64
- "defaultTarget": "staging",
65
- "deleteRemoteFiles": true // Defaults to false
67
+ "path": "public", // Required, local path to sync
68
+ "files": "**/**", // Optional, default='**/**', file glob pattern
69
+ "exclude": ["**/*.map", "**/.DS_Store", "**/.git*", "**/.env*"], // Optional, default=[] (init writes this list), glob patterns to ignore
70
+ "defaultTarget": "staging", // Optional, default='staging', target to use if not specified on cmd line
71
+ "deleteRemoteFiles": true // Optional, default=false - for now...
66
72
  },
67
73
 
68
74
  "targets": {
@@ -71,9 +77,12 @@ Example `syncodemayo.json` config:
71
77
  "path": "MyApp/www/stage", // Required
72
78
  "user": "USERNAME", // Required
73
79
  "pass": "PASSWORD", // Optional, NOT RECOMMENDED! See note below for better way...
74
- "port": 21, // Default
75
- "cache": ".synco-filelist", // Default
76
- "prompt": false // Default is true, false will upload w/o confirming
80
+ "port": 21, // Optional, default=21 (990 when secure is "implicit")
81
+ "secure": false, // Optional, default=false. FTPS: false, true (explicit) or "implicit". See below.
82
+ "rejectUnauthorized": true, // Optional, default=true. false accepts self-signed certificates. See below.
83
+ "cache": ".synco-filelist", // Optional, default='.synco-filelist'
84
+ "prompt": false, // Optional, default=true, false will upload w/o confirming
85
+ "enabled": false // Optional, default=true
77
86
  },
78
87
 
79
88
  "production": {
@@ -83,11 +92,32 @@ Example `syncodemayo.json` config:
83
92
  "prompt": "This is for PRODUCTION! Do you really mean it?"
84
93
  }
85
94
  }
95
+
96
+ /*
97
+ Note: Yes, comments are allowed in config files.
98
+ SyncoDeMayo will strip them before parsing the JSON.
99
+ */
86
100
  }
87
101
  ```
88
102
 
103
+ ### Dotfiles
104
+
105
+ Dotfiles and dot-directories sync like any other file: `.htaccess`, for example, is uploaded.
106
+ (1.x skipped them.) Exclude anything that must not reach the server, such as `**/.env*` for
107
+ environment files and `**/.git/**` for a git directory. Note that `**/.git*` excludes
108
+ `.gitignore` but not the files inside a `.git/` directory.
109
+
110
+ On macOS and Windows, `files` and `exclude` patterns match case-insensitively.
111
+
112
+ ### Passwords and `.env`
113
+
89
114
  You can put the FTP password in the config, but if you're adding it to source control I wouldn't. Instead, add the password to an `.env` file that you set to ignored in your SCM.
90
115
 
116
+ SyncoDeMayo reads `.env` from the current working directory (where you run `syncodemayo`).
117
+ Variables already set in the environment win over the ones in `.env`. When a target has no
118
+ `pass`, its password is looked up as `<TARGET NAME UPPERCASED>_PWD`: `STAGING_PWD` for the
119
+ `staging` target.
120
+
91
121
  Example `.env` for above config:
92
122
 
93
123
  ```bash
@@ -95,4 +125,43 @@ STAGING_PWD=ftpPasswordHere
95
125
  PRODUCTION_PWD="Other password here"
96
126
  ```
97
127
 
98
- SyncoDeMayo will automatically look in your `.env` for passwords if they aren't in your config.
128
+ **Warning:** a `.env` inside `local.path` is a dotfile like any other, so it would be uploaded
129
+ unless excluded. Configs created by `init` exclude `**/.env*`; if your config predates 2.0, add
130
+ `"**/.env*"` to its `exclude` list.
131
+
132
+ ### FTPS (FTP over TLS)
133
+
134
+ If your host requires FTPS, set `secure` on the target:
135
+
136
+ | `secure` | Connection | Default `port` |
137
+ | --- | --- | --- |
138
+ | `false` (default) | Plain FTP | 21 |
139
+ | `true` | Explicit FTPS: connects in plain text, then upgrades with `AUTH TLS` | 21 |
140
+ | `"implicit"` | Implicit FTPS: TLS from the start | 990 |
141
+
142
+ An explicit `port` always wins over these defaults. `secure` takes exactly `true`, `false` or
143
+ `"implicit"` (not the strings `"true"`/`"false"`).
144
+
145
+ `rejectUnauthorized` (`true` or `false`, default `true`) controls whether the server's TLS
146
+ certificate must be trusted. With the default, a self-signed, expired or mismatched certificate
147
+ stops the command with an error that names the host. If you trust the server anyway, set
148
+ `"rejectUnauthorized": false` on the target: the connection then accepts the certificate, and
149
+ SyncoDeMayo prints a warning naming the target every time it makes such a connection (even
150
+ with `-q`).
151
+
152
+ ```js
153
+ "targets": {
154
+ "production": {
155
+ "host": "ftp.myapp.com",
156
+ "path": "MyApp/www",
157
+ "user": "USERNAME",
158
+ "secure": true,
159
+ "rejectUnauthorized": false // only for a server you trust with a self-signed certificate
160
+ }
161
+ }
162
+ ```
163
+
164
+ ## Changelog
165
+
166
+ See [CHANGELOG.md](CHANGELOG.md) for what changed in each release, including the migration
167
+ notes for upgrading from 1.x.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import './env.js';
package/dist/cli.js ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ // Must be the first import: loads .env before any other module reads process.env.
3
+ import './env.js';
4
+ import { readFileSync } from 'node:fs';
5
+ import * as CLI from './lib/Cli.js';
6
+ import * as Log from './lib/Log.js';
7
+ import { CliArgsError, HELP, parseCliArgs } from './lib/Args.js';
8
+ let args;
9
+ try {
10
+ args = parseCliArgs(process.argv.slice(2));
11
+ }
12
+ catch (err) {
13
+ if (!(err instanceof CliArgsError))
14
+ throw err;
15
+ process.stderr.write(`error: ${err.message}\n\nRun 'syncodemayo --help' for usage.\n`);
16
+ process.exit(1);
17
+ }
18
+ CLI.setFlags({ logLevel: args.logLevel });
19
+ const { config, target } = args;
20
+ switch (args.command) {
21
+ case "changes":
22
+ Log.debug(`Performing '${args.command}' on target: ${target || '(default)'}...\n`);
23
+ CLI.changes(config, target).then(showDone).catch(showError);
24
+ break;
25
+ case "check":
26
+ Log.debug(`Performing '${args.command}' on target: ${target || '(default)'}...\n`);
27
+ CLI.check(config, target).then(showDone).catch(showError);
28
+ break;
29
+ case "init":
30
+ Log.debug(`Performing '${args.command}' on target: ${target || '(default)'}...\n`);
31
+ CLI.init(config, target).then(showDone).catch(showError);
32
+ break;
33
+ case "ls":
34
+ Log.debug(`Performing '${args.command}'...\n`);
35
+ CLI.ls(config).then(showDone).catch(showError);
36
+ break;
37
+ case "sync":
38
+ if (args.force) {
39
+ Log.debug(`Performing (forced) 'sync' on target: ${target || '(default)'}\n...`);
40
+ }
41
+ else {
42
+ Log.debug(`Performing '${args.command}' on target: ${target || '(default)'}\n...`);
43
+ }
44
+ CLI.sync(config, target, args.force).then(showDone).catch(showError);
45
+ break;
46
+ case "version":
47
+ process.stdout.write(readPackageVersion() + "\n");
48
+ break;
49
+ case "help":
50
+ default:
51
+ process.stdout.write(HELP);
52
+ }
53
+ // The version of this package, read from its package.json (one level above dist/).
54
+ function readPackageVersion() {
55
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
56
+ return String(pkg.version);
57
+ }
58
+ function showDone() {
59
+ Log.log("\nDone.");
60
+ process.exit(0);
61
+ }
62
+ function showError(err) {
63
+ console.error("\n(!)", err.message, "\n\n", err);
64
+ process.exit(1);
65
+ }
66
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"./","sources":["cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\n// Must be the first import: loads .env before any other module reads process.env.\nimport './env.js'\n\nimport { readFileSync } from 'node:fs'\nimport * as CLI from './lib/Cli.js'\nimport * as Log from './lib/Log.js'\nimport { CliArgs, CliArgsError, HELP, parseCliArgs } from './lib/Args.js'\n\nlet args: CliArgs\ntry {\n args = parseCliArgs(process.argv.slice(2))\n}\ncatch (err) {\n if (!(err instanceof CliArgsError)) throw err\n process.stderr.write(`error: ${err.message}\\n\\nRun 'syncodemayo --help' for usage.\\n`)\n process.exit(1)\n}\n\nCLI.setFlags({ logLevel: args.logLevel })\n\nconst { config, target } = args\n\nswitch (args.command) {\n\n case \"changes\":\n Log.debug(`Performing '${args.command}' on target: ${target || '(default)'}...\\n`)\n CLI.changes(config, target).then(showDone).catch(showError)\n break\n\n case \"check\":\n Log.debug(`Performing '${args.command}' on target: ${target || '(default)'}...\\n`)\n CLI.check(config, target).then(showDone).catch(showError)\n break\n\n case \"init\":\n Log.debug(`Performing '${args.command}' on target: ${target || '(default)'}...\\n`)\n CLI.init(config, target).then(showDone).catch(showError)\n break\n\n case \"ls\":\n Log.debug(`Performing '${args.command}'...\\n`)\n CLI.ls(config).then(showDone).catch(showError)\n break\n\n case \"sync\":\n if (args.force) {\n Log.debug(`Performing (forced) 'sync' on target: ${target || '(default)'}\\n...`)\n }\n else {\n Log.debug(`Performing '${args.command}' on target: ${target || '(default)'}\\n...`)\n }\n CLI.sync(config, target, args.force).then(showDone).catch(showError)\n break\n\n case \"version\":\n process.stdout.write(readPackageVersion() + \"\\n\")\n break\n\n case \"help\":\n default:\n process.stdout.write(HELP)\n}\n\n// The version of this package, read from its package.json (one level above dist/).\nfunction readPackageVersion(): string {\n const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))\n return String(pkg.version)\n}\n\nfunction showDone() {\n Log.log(\"\\nDone.\")\n process.exit(0)\n}\n\nfunction showError(err: any) {\n console.error(\"\\n(!)\", err.message, \"\\n\\n\", err)\n process.exit(1)\n}\n"]}
package/dist/env.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function loadDotEnv(path?: string): boolean;
package/dist/env.js ADDED
@@ -0,0 +1,17 @@
1
+ // Loads `.env` from the working directory into process.env, as v1 did with dotenv.
2
+ // cli.ts imports this module first, so the variables are set before any other module
3
+ // reads process.env. Variables already in the environment are not overridden.
4
+ // Only a missing `.env` is ignored; any other read or parse error surfaces.
5
+ export function loadDotEnv(path = '.env') {
6
+ try {
7
+ process.loadEnvFile(path);
8
+ return true;
9
+ }
10
+ catch (err) {
11
+ if (err?.code === 'ENOENT')
12
+ return false;
13
+ throw err;
14
+ }
15
+ }
16
+ loadDotEnv();
17
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.js","sourceRoot":"./","sources":["env.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,qFAAqF;AACrF,8EAA8E;AAC9E,4EAA4E;AAE5E,MAAM,UAAU,UAAU,CAAC,IAAI,GAAG,MAAM;IACtC,IAAI,CAAC;QACH,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;QACzB,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,GAAG,EAAE,CAAC;QACX,IAAK,GAA6B,EAAE,IAAI,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAA;QACnE,MAAM,GAAG,CAAA;IACX,CAAC;AACH,CAAC;AAED,UAAU,EAAE,CAAA","sourcesContent":["// Loads `.env` from the working directory into process.env, as v1 did with dotenv.\n// cli.ts imports this module first, so the variables are set before any other module\n// reads process.env. Variables already in the environment are not overridden.\n// Only a missing `.env` is ignored; any other read or parse error surfaces.\n\nexport function loadDotEnv(path = '.env'): boolean {\n try {\n process.loadEnvFile(path)\n return true\n }\n catch (err) {\n if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') return false\n throw err\n }\n}\n\nloadDotEnv()\n"]}
@@ -0,0 +1 @@
1
+ export * from './lib/index.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export * from './lib/index.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"./","sources":["index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA","sourcesContent":["export * from './lib/index.js'"]}
@@ -0,0 +1,21 @@
1
+ import { LogLevel } from './Log.js';
2
+ /** What the CLI was asked to do. `check` is the `verify` command, as in v1. */
3
+ export type CliCommand = 'init' | 'changes' | 'check' | 'sync' | 'ls' | 'help' | 'version';
4
+ export interface CliArgs {
5
+ command: CliCommand;
6
+ target?: string;
7
+ config?: string;
8
+ logLevel: LogLevel;
9
+ /** `sync -f/--force`: don't prompt before uploading. */
10
+ force: boolean;
11
+ }
12
+ /** Thrown for a command line the CLI can't run (unknown option or command, extra arguments). */
13
+ export declare class CliArgsError extends Error {
14
+ constructor(message: string);
15
+ }
16
+ export declare const HELP = "Usage: syncodemayo [options] [command]\n\nOptions:\n -c, --config <file> Specify local config <file>\n -v, --verbose Verbose logging\n -q, --quiet Minimal logging\n -V, --version Output the version number\n -h, --help Output usage information\n\nCommands:\n init [target] Configure local folder and/or server to sync\n changes|diff [target] Perform sync dry run and display the changes\n verify [target] Verify server is configured\n sync [options] [target]\n Perform sync to server\n -f, --force Don't prompt before uploading.\n ls List defined targets in config\n\nWith no command, shows this help.\n";
17
+ /**
18
+ * Parses the CLI arguments (without the node and script paths) into what to run.
19
+ * Pure: it reads nothing and prints nothing. Throws CliArgsError for a command line it can't run.
20
+ */
21
+ export declare function parseCliArgs(argv: string[]): CliArgs;
@@ -0,0 +1,99 @@
1
+ import { parseArgs } from 'node:util';
2
+ import { LogLevel } from './Log.js';
3
+ /** Thrown for a command line the CLI can't run (unknown option or command, extra arguments). */
4
+ export class CliArgsError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = 'CliArgsError';
8
+ }
9
+ }
10
+ // Command name (and alias) -> what it runs, and whether it takes a [target].
11
+ const COMMANDS = {
12
+ init: { command: 'init', takesTarget: true },
13
+ changes: { command: 'changes', takesTarget: true },
14
+ diff: { command: 'changes', takesTarget: true },
15
+ verify: { command: 'check', takesTarget: true },
16
+ sync: { command: 'sync', takesTarget: true },
17
+ ls: { command: 'ls', takesTarget: false },
18
+ };
19
+ export const HELP = `Usage: syncodemayo [options] [command]
20
+
21
+ Options:
22
+ -c, --config <file> Specify local config <file>
23
+ -v, --verbose Verbose logging
24
+ -q, --quiet Minimal logging
25
+ -V, --version Output the version number
26
+ -h, --help Output usage information
27
+
28
+ Commands:
29
+ init [target] Configure local folder and/or server to sync
30
+ changes|diff [target] Perform sync dry run and display the changes
31
+ verify [target] Verify server is configured
32
+ sync [options] [target]
33
+ Perform sync to server
34
+ -f, --force Don't prompt before uploading.
35
+ ls List defined targets in config
36
+
37
+ With no command, shows this help.
38
+ `;
39
+ /**
40
+ * Parses the CLI arguments (without the node and script paths) into what to run.
41
+ * Pure: it reads nothing and prints nothing. Throws CliArgsError for a command line it can't run.
42
+ */
43
+ export function parseCliArgs(argv) {
44
+ let parsed;
45
+ try {
46
+ parsed = parseArgs({
47
+ args: argv,
48
+ allowPositionals: true,
49
+ strict: true,
50
+ options: {
51
+ config: { type: 'string', short: 'c' },
52
+ verbose: { type: 'boolean', short: 'v' },
53
+ quiet: { type: 'boolean', short: 'q' },
54
+ version: { type: 'boolean', short: 'V' },
55
+ help: { type: 'boolean', short: 'h' },
56
+ force: { type: 'boolean', short: 'f' },
57
+ },
58
+ });
59
+ }
60
+ catch (err) {
61
+ throw new CliArgsError(err.message);
62
+ }
63
+ const { values, positionals } = parsed;
64
+ const force = values.force === true;
65
+ const logLevel = values.verbose
66
+ ? LogLevel.Debug
67
+ : values.quiet
68
+ ? LogLevel.Info
69
+ : LogLevel.Default;
70
+ // Only the keys that are set, so a result compares equal to a plain literal.
71
+ const result = (command, target) => {
72
+ const args = { command, logLevel, force };
73
+ if (values.config !== undefined)
74
+ args.config = values.config;
75
+ if (target)
76
+ args.target = target;
77
+ return args;
78
+ };
79
+ if (values.help)
80
+ return result('help');
81
+ if (values.version)
82
+ return result('version');
83
+ if (positionals.length === 0) {
84
+ if (force)
85
+ throw new CliArgsError("Unknown option '-f, --force': it applies only to the sync command");
86
+ return result('help');
87
+ }
88
+ const [name, ...rest] = positionals;
89
+ const spec = Object.hasOwn(COMMANDS, name) ? COMMANDS[name] : undefined;
90
+ if (!spec)
91
+ throw new CliArgsError(`Unknown command '${name}'`);
92
+ if (force && spec.command !== 'sync')
93
+ throw new CliArgsError(`Unknown option '-f, --force' for the ${name} command: it applies only to sync`);
94
+ const maxArgs = spec.takesTarget ? 1 : 0;
95
+ if (rest.length > maxArgs)
96
+ throw new CliArgsError(`Too many arguments for the ${name} command: ${rest.join(' ')}`);
97
+ return result(spec.command, rest[0]);
98
+ }
99
+ //# sourceMappingURL=Args.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Args.js","sourceRoot":"./","sources":["lib/Args.ts"],"names":[],"mappings":"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","sourcesContent":["import { parseArgs } from 'node:util'\nimport { LogLevel } from './Log.js'\n\n/** What the CLI was asked to do. `check` is the `verify` command, as in v1. */\nexport type CliCommand = 'init' | 'changes' | 'check' | 'sync' | 'ls' | 'help' | 'version'\n\nexport interface CliArgs {\n command: CliCommand\n target?: string\n config?: string\n logLevel: LogLevel\n /** `sync -f/--force`: don't prompt before uploading. */\n force: boolean\n}\n\n/** Thrown for a command line the CLI can't run (unknown option or command, extra arguments). */\nexport class CliArgsError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'CliArgsError'\n }\n}\n\n// Command name (and alias) -> what it runs, and whether it takes a [target].\nconst COMMANDS: { [name: string]: { command: CliCommand, takesTarget: boolean } } = {\n init: { command: 'init', takesTarget: true },\n changes: { command: 'changes', takesTarget: true },\n diff: { command: 'changes', takesTarget: true },\n verify: { command: 'check', takesTarget: true },\n sync: { command: 'sync', takesTarget: true },\n ls: { command: 'ls', takesTarget: false },\n}\n\nexport const HELP = `Usage: syncodemayo [options] [command]\n\nOptions:\n -c, --config <file> Specify local config <file>\n -v, --verbose Verbose logging\n -q, --quiet Minimal logging\n -V, --version Output the version number\n -h, --help Output usage information\n\nCommands:\n init [target] Configure local folder and/or server to sync\n changes|diff [target] Perform sync dry run and display the changes\n verify [target] Verify server is configured\n sync [options] [target]\n Perform sync to server\n -f, --force Don't prompt before uploading.\n ls List defined targets in config\n\nWith no command, shows this help.\n`\n\n/**\n * Parses the CLI arguments (without the node and script paths) into what to run.\n * Pure: it reads nothing and prints nothing. Throws CliArgsError for a command line it can't run.\n */\nexport function parseCliArgs(argv: string[]): CliArgs {\n let parsed\n try {\n parsed = parseArgs({\n args: argv,\n allowPositionals: true,\n strict: true,\n options: {\n config: { type: 'string', short: 'c' },\n verbose: { type: 'boolean', short: 'v' },\n quiet: { type: 'boolean', short: 'q' },\n version: { type: 'boolean', short: 'V' },\n help: { type: 'boolean', short: 'h' },\n force: { type: 'boolean', short: 'f' },\n },\n })\n }\n catch (err) {\n throw new CliArgsError((err as Error).message)\n }\n\n const { values, positionals } = parsed\n const force = values.force === true\n const logLevel = values.verbose\n ? LogLevel.Debug\n : values.quiet\n ? LogLevel.Info\n : LogLevel.Default\n // Only the keys that are set, so a result compares equal to a plain literal.\n const result = (command: CliCommand, target?: string): CliArgs => {\n const args: CliArgs = { command, logLevel, force }\n if (values.config !== undefined) args.config = values.config\n if (target) args.target = target\n return args\n }\n\n if (values.help) return result('help')\n if (values.version) return result('version')\n if (positionals.length === 0) {\n if (force) throw new CliArgsError(\"Unknown option '-f, --force': it applies only to the sync command\")\n return result('help')\n }\n\n const [name, ...rest] = positionals\n const spec = Object.hasOwn(COMMANDS, name) ? COMMANDS[name] : undefined\n if (!spec) throw new CliArgsError(`Unknown command '${name}'`)\n\n if (force && spec.command !== 'sync')\n throw new CliArgsError(`Unknown option '-f, --force' for the ${name} command: it applies only to sync`)\n\n const maxArgs = spec.takesTarget ? 1 : 0\n if (rest.length > maxArgs)\n throw new CliArgsError(`Too many arguments for the ${name} command: ${rest.join(' ')}`)\n\n return result(spec.command, rest[0])\n}\n"]}
@@ -1,3 +1,8 @@
1
+ import * as Log from './Log.js';
2
+ export interface CLIFlags {
3
+ logLevel: Log.LogLevel;
4
+ }
5
+ export declare function setFlags(flags: CLIFlags): void;
1
6
  export declare function changes(configPath?: string, target?: string): Promise<boolean>;
2
7
  export declare function check(configPath?: string, target?: string): Promise<boolean>;
3
8
  export declare function init(configPath?: string, target?: string): Promise<boolean>;
@@ -0,0 +1,104 @@
1
+ import * as Log from './Log.js';
2
+ import { Client } from './Client.js';
3
+ export function setFlags(flags) {
4
+ Log.setLogLevel(flags.logLevel);
5
+ }
6
+ export async function changes(configPath, target) {
7
+ const client = await Client.create(configPath);
8
+ const changeset = await client.getTargetServer(target).sync(true);
9
+ if (changeset == null)
10
+ throw new Error("Change detection failed.");
11
+ const added = changeset.added.length;
12
+ const changed = changeset.changed.length;
13
+ const removed = changeset.removed.length;
14
+ if (added + changed + removed === 0) {
15
+ Log.log("\nNo changes detected.");
16
+ return true;
17
+ }
18
+ if (added > 0) {
19
+ Log.info("\nNew Local Files: (%s)", added);
20
+ changeset.added.forEach(f => Log.log(" -", f));
21
+ }
22
+ if (changed > 0) {
23
+ Log.info("\nChanged Local Files: (%s)", changeset.changed.length);
24
+ changeset.changed.forEach(f => Log.log(" -", f));
25
+ }
26
+ if (removed > 0) {
27
+ Log.info(`\nRemoved Local Files${client.config.local.deleteRemoteFiles ? '' : ' (disabled)'}: (%s)`, changeset.removed.length);
28
+ changeset.removed.forEach(f => Log.log(" -", f));
29
+ }
30
+ return true;
31
+ }
32
+ export async function check(configPath, target) {
33
+ const client = await Client.create(configPath);
34
+ if (client.hasError) {
35
+ throw client.loadError || client.parseError;
36
+ }
37
+ if (!client.hasConfig && !client.hasError) {
38
+ Log.urgent("This directory isn't configured for SyncoDeMayo!");
39
+ return false;
40
+ }
41
+ const targetServer = client.getTargetServer(target);
42
+ const isServerConfigured = await targetServer.verify();
43
+ if (isServerConfigured) {
44
+ Log.info("Target", JSON.stringify(targetServer.name), "is ready for syncing.");
45
+ }
46
+ else {
47
+ Log.urgent("Target", JSON.stringify(targetServer.name), "isn't configured, run init.");
48
+ return false;
49
+ }
50
+ return true;
51
+ }
52
+ export async function init(configPath, target) {
53
+ const client = await Client.create(configPath);
54
+ if (!client.hasConfig && client.hasError) {
55
+ throw client.loadError || client.parseError;
56
+ }
57
+ if (!client.hasConfig) {
58
+ await client.createConfig(configPath);
59
+ Log.info("Created local config at", configPath);
60
+ return true;
61
+ }
62
+ const targetServer = client.getTargetServer(target);
63
+ const isServerConfigured = await targetServer.verify();
64
+ if (!isServerConfigured) {
65
+ await targetServer.prepare();
66
+ Log.info("Created", targetServer.config.cache, "on server target", targetServer.config._name);
67
+ return true;
68
+ }
69
+ Log.info("Everything seems to be configured correctly.");
70
+ return false;
71
+ }
72
+ export async function ls(configPath) {
73
+ const client = await Client.create(configPath);
74
+ const targets = await client.getTargetNames();
75
+ Log.urgent("Defined server targets:");
76
+ targets.forEach(name => {
77
+ const target = client.config.targets[name];
78
+ const bullet = client.config.local.defaultTarget === target._name
79
+ ? " *"
80
+ : " -";
81
+ Log.urgent(bullet, name, `\t(${target.user}@${target.host}:${target.path})`);
82
+ });
83
+ return true;
84
+ }
85
+ export async function sync(configPath, target, forceConfirmation = false) {
86
+ const client = await Client.create(configPath);
87
+ if (!client.hasConfig) {
88
+ if (client.hasError)
89
+ throw client.loadError || client.parseError;
90
+ else
91
+ throw new Error("This directory isn't configured for SyncoDeMayo!");
92
+ }
93
+ const changes = await client.getTargetServer(target).sync(false, forceConfirmation);
94
+ if (changes != null) {
95
+ Log.info("\nSummary:");
96
+ Log.info(" %s files uploaded.", changes.added.length + changes.changed.length);
97
+ changes.added.forEach(path => Log.log(" +", path));
98
+ changes.changed.forEach(path => Log.log(" -", path));
99
+ Log.info(` %s files removed. ${client.config.local.deleteRemoteFiles ? '' : '(disabled)'}`, changes.removed.length);
100
+ changes.removed.forEach(path => Log.log(" x", path));
101
+ }
102
+ return true;
103
+ }
104
+ //# sourceMappingURL=Cli.js.map