puter-cli 1.7.0 → 1.7.1

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.
package/CHANGELOG.md CHANGED
@@ -4,8 +4,15 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ #### [v1.7.1](https://github.com/HeyPuter/puter-cli/compare/v1.7.0...v1.7.1)
8
+
9
+ - fix: update command [`38ca9e8`](https://github.com/HeyPuter/puter-cli/commit/38ca9e824642cbf8b1ffdb0d2a6e5426f84c7371)
10
+ - docs: update README [`6e658f6`](https://github.com/HeyPuter/puter-cli/commit/6e658f6a117ee1ba206768905d53fb61c78e136d)
11
+
7
12
  #### [v1.7.0](https://github.com/HeyPuter/puter-cli/compare/v1.6.1...v1.7.0)
8
13
 
14
+ > 16 February 2025
15
+
9
16
  - feat: save auth token when login [`55b32b7`](https://github.com/HeyPuter/puter-cli/commit/55b32b7feca050902f4470f06af38f81d3299e6a)
10
17
  - fix: create app from host shell [`30e5028`](https://github.com/HeyPuter/puter-cli/commit/30e5028d831d26349e3ae2fc8e34921693b5702c)
11
18
 
package/README.md CHANGED
@@ -63,8 +63,10 @@ Then just follow the prompts, this command doesn't require you to log in.
63
63
  #### Authentication
64
64
  - **Login**: Log in to your Puter account.
65
65
  ```bash
66
- puter login
66
+ puter login [--save]
67
67
  ```
68
+ P.S. You can add `--save` to save your authentication `token` to `.env` file as `PUTER_API_KEY` variable.
69
+
68
70
  - **Logout**: Log out of your Puter account.
69
71
  ```bash
70
72
  puter logout
@@ -127,6 +129,19 @@ Think of it as `git [push|pull]` commands, they're basically simplified equivale
127
129
  ```
128
130
  P.S. These commands consider the current directory as the base path for every operation, basic wildcards are supported: e.g. `push myapp/*.html`.
129
131
 
132
+ - **Synchronize Files**: Bidirectional synchronization between local and remote directories.
133
+ ```bash
134
+ puter> update <local_directory> <remote_directory> [--delete] [-r]
135
+ ```
136
+ P.S. The `--delete` flag removes files in the remote directory that don't exist locally. The `-r` flag enables recursive synchronization of subdirectories.
137
+
138
+ #### User Information
139
+ ```
140
+
141
+ The addition describes the `update` command which allows for bidirectional synchronization between local and remote directories, including the optional flags for deleting files and recursive synchronization.
142
+ ---
143
+
144
+
130
145
  #### User Information
131
146
  - **Get User Info**: Display user information.
132
147
  ```bash
@@ -157,8 +172,14 @@ P.S. Please check the help command `help apps` for more details about any argume
157
172
  ```bash
158
173
  puter> app:create <name> [<directory>] [--description="My App Description"] [--url=<url>]
159
174
  ```
175
+ - This command works also from your system's terminal:
176
+ ```bash
177
+ $> puter app:create <name> [<directory>] [--description="My App Description"] [--url=<url>]
178
+ ```
179
+
160
180
  P.S. By default a new `index.html` with basic content will be created, but you can set a directory when you create a new application as follows: `app:create nameOfApp ./appDir`, so all files will be copied to the `AppData` directory, you can then update your app using `app:update <name> <remote_dir>`. This command will attempt to create a subdomain with a random `uid` prefixed with the name of the app.
161
181
 
182
+
162
183
  - **Update Application**: Update an application.
163
184
  ```bash
164
185
  puter> app:update <name> <remote_dir>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "puter-cli",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "description": "Command line interface for Puter cloud platform",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -1075,28 +1075,34 @@ export async function copyFile(args = []) {
1075
1075
  /**
1076
1076
  * List all files in a local directory.
1077
1077
  * @param {string} localDir - The local directory path.
1078
+ * @param {boolean} recursive - Whether to recursively list files in subdirectories
1078
1079
  * @returns {Array} - Array of local file objects.
1079
1080
  */
1080
- function listLocalFiles(localDir) {
1081
+ function listLocalFiles(localDir, recursive = false) {
1081
1082
  const files = [];
1082
- const walkDir = (dir) => {
1083
+ const walkDir = (dir, baseDir) => {
1084
+
1083
1085
  const entries = fs.readdirSync(dir, { withFileTypes: true });
1084
1086
  for (const entry of entries) {
1085
1087
  const fullPath = path.join(dir, entry.name);
1088
+ const relativePath = path.relative(baseDir, fullPath);
1086
1089
  if (entry.isDirectory()) {
1087
- walkDir(fullPath);
1090
+ if (recursive) {
1091
+ walkDir(fullPath, baseDir); // Recursively traverse directories if flag is set
1092
+ }
1088
1093
  } else {
1089
1094
  files.push({
1090
- relativePath: path.relative(localDir, fullPath),
1095
+ relativePath: relativePath,
1091
1096
  localPath: fullPath,
1092
1097
  size: fs.statSync(fullPath).size,
1093
1098
  modified: fs.statSync(fullPath).mtime.getTime()
1094
1099
  });
1095
1100
  }
1096
1101
  }
1102
+
1097
1103
  };
1098
1104
 
1099
- walkDir(localDir);
1105
+ walkDir(localDir, localDir);
1100
1106
  return files;
1101
1107
  }
1102
1108
 
@@ -1190,12 +1196,40 @@ async function resolveLocalDirectory(localPath) {
1190
1196
  return absolutePath;
1191
1197
  }
1192
1198
 
1199
+
1200
+ /**
1201
+ * Ensure a remote directory exists, creating it if necessary
1202
+ * @param {string} remotePath - The remote directory path
1203
+ */
1204
+ async function ensureRemoteDirectoryExists(remotePath) {
1205
+ try {
1206
+ const exists = await pathExists(remotePath);
1207
+ if (!exists) {
1208
+ // Create the directory and any missing parents
1209
+ await fetch(`${API_BASE}/mkdir`, {
1210
+ method: 'POST',
1211
+ headers: getHeaders(),
1212
+ body: JSON.stringify({
1213
+ parent: path.dirname(remotePath),
1214
+ path: path.basename(remotePath),
1215
+ overwrite: false,
1216
+ dedupe_name: true,
1217
+ create_missing_parents: true
1218
+ })
1219
+ });
1220
+ }
1221
+ } catch (error) {
1222
+ console.error(chalk.red(`Failed to create remote directory: ${remotePath}`));
1223
+ throw error;
1224
+ }
1225
+ }
1226
+
1193
1227
  /**
1194
1228
  * Synchronize a local directory with a remote directory on Puter.
1195
- * @param {string[]} args - Command-line arguments (e.g., [localDir, remoteDir]).
1229
+ * @param {string[]} args - Command-line arguments (e.g., [localDir, remoteDir, --delete, -r]).
1196
1230
  */
1197
1231
  export async function syncDirectory(args = []) {
1198
- const usageMessage = 'Usage: update <local_directory> <remote_directory> [--delete]';
1232
+ const usageMessage = 'Usage: update <local_directory> <remote_directory> [--delete] [-r]';
1199
1233
  if (args.length < 2) {
1200
1234
  console.log(chalk.red(usageMessage));
1201
1235
  return;
@@ -1204,10 +1238,12 @@ export async function syncDirectory(args = []) {
1204
1238
  let localDir = '';
1205
1239
  let remoteDir = '';
1206
1240
  let deleteFlag = '';
1241
+ let recursiveFlag = false;
1207
1242
  try {
1208
1243
  localDir = await resolveLocalDirectory(args[0]);
1209
1244
  remoteDir = resolvePath(getCurrentDirectory(), args[1]);
1210
1245
  deleteFlag = args.includes('--delete'); // Whether to delete extra files
1246
+ recursiveFlag = args.includes('-r'); // Whether to recursively process subdirectories
1211
1247
  } catch (error) {
1212
1248
  console.error(chalk.red(error.message));
1213
1249
  console.log(chalk.green(usageMessage));
@@ -1231,14 +1267,17 @@ export async function syncDirectory(args = []) {
1231
1267
  }
1232
1268
 
1233
1269
  // Step 3: List local files
1234
- const localFiles = listLocalFiles(localDir);
1270
+ const localFiles = listLocalFiles(localDir, recursiveFlag);
1235
1271
 
1236
1272
  // Step 4: Compare local and remote files
1237
1273
  let { toUpload, toDownload, toDelete } = compareFiles(localFiles, remoteFiles, localDir, remoteDir);
1274
+ let filteredToUpload = [...toUpload];
1275
+ let filteredToDownload = [...toDownload];
1238
1276
 
1239
1277
  // Step 5: Handle conflicts (if any)
1240
1278
  const conflicts = findConflicts(toUpload, toDownload);
1241
1279
  if (conflicts.length > 0) {
1280
+
1242
1281
  console.log(chalk.yellow('The following files have conflicts:'));
1243
1282
  conflicts.forEach(file => console.log(chalk.dim(`- ${file}`)));
1244
1283
 
@@ -1256,12 +1295,12 @@ export async function syncDirectory(args = []) {
1256
1295
  ]);
1257
1296
 
1258
1297
  if (resolve === 'local') {
1259
- toDownload = toDownload.filter(file => !conflicts.includes(file.relativePath));
1298
+ filteredToDownload = filteredToDownload.filter(file => !conflicts.includes(file.relativePath));
1260
1299
  } else if (resolve === 'remote') {
1261
- toUpload = toUpload.filter(file => !conflicts.includes(file.relativePath));
1300
+ filteredToUpload = filteredToUpload.filter(file => !conflicts.includes(file.relativePath));
1262
1301
  } else {
1263
- toUpload = toUpload.filter(file => !conflicts.includes(file.relativePath));
1264
- toDownload = toDownload.filter(file => !conflicts.includes(file.relativePath));
1302
+ filteredToUpload = filteredToUpload.filter(file => !conflicts.includes(file.relativePath));
1303
+ filteredToDownload = filteredToDownload.filter(file => !conflicts.includes(file.relativePath));
1265
1304
  }
1266
1305
  }
1267
1306
 
@@ -1269,18 +1308,30 @@ export async function syncDirectory(args = []) {
1269
1308
  console.log(chalk.green('Starting synchronization...'));
1270
1309
 
1271
1310
  // Upload new/updated files
1272
- for (const file of toUpload) {
1311
+ for (const file of filteredToUpload) {
1273
1312
  console.log(chalk.cyan(`Uploading "${file.relativePath}"...`));
1274
1313
  const dedupeName = 'false';
1275
1314
  const overwrite = 'true';
1276
- await uploadFile([file.localPath, remoteDir, dedupeName, overwrite]);
1315
+
1316
+ // Create parent directories if needed
1317
+ const remoteFilePath = path.join(remoteDir, file.relativePath);
1318
+ const remoteFileDir = path.dirname(remoteFilePath);
1319
+
1320
+ // Ensure remote directory exists
1321
+ await ensureRemoteDirectoryExists(remoteFileDir);
1322
+
1323
+ await uploadFile([file.localPath, remoteFileDir, dedupeName, overwrite]);
1277
1324
  }
1278
1325
 
1279
1326
  // Download new/updated files
1280
- for (const file of toDownload) {
1327
+ for (const file of filteredToDownload) {
1281
1328
  console.log(chalk.cyan(`Downloading "${file.relativePath}"...`));
1282
1329
  const overwrite = 'true';
1283
- await downloadFile([file.relativePath, file.localPath, overwrite]);
1330
+ // Create local parent directories if needed
1331
+ const localFilePath = path.join(localDir, file.relativePath);
1332
+ // const localFileDir = path.dirname(localFilePath);
1333
+
1334
+ await downloadFile([file.remotePath, localFilePath, overwrite]);
1284
1335
  }
1285
1336
 
1286
1337
  // Delete extra files (if --delete flag is set)
package/src/commons.js CHANGED
@@ -106,7 +106,7 @@ export function showDiskSpaceUsage(data) {
106
106
  console.log(chalk.cyan(`Usage Percentage: `) + chalk.white(`${usagePercentage.toFixed(2)}%`));
107
107
  console.log(chalk.dim('----------------------------------------'));
108
108
  }
109
-
109
+
110
110
  /**
111
111
  * Resolve a relative path to an absolute path
112
112
  * @param {string} currentPath - The current working directory
package/src/executor.js CHANGED
@@ -323,7 +323,7 @@ function showHelp(command) {
323
323
  Example: pull /path/to/file
324
324
  `,
325
325
  update: `
326
- ${chalk.cyan('update <src> <dest>')}
326
+ ${chalk.cyan('update <src> <dest> [--delete] [-r]')}
327
327
  Sync local directory with remote cloud.
328
328
  Example: update /local/path /remote/path
329
329
  `,