codechronicle-cli 1.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.
- package/commands/add.js +376 -0
- package/commands/clone.js +75 -0
- package/commands/commit.js +178 -0
- package/commands/init.js +106 -0
- package/commands/login.js +56 -0
- package/commands/logout.js +12 -0
- package/commands/pull.js +94 -0
- package/commands/push.js +70 -0
- package/commands/revert.js +121 -0
- package/commands/whoami.js +11 -0
- package/config/config.js +3 -0
- package/config/supabase.js +24 -0
- package/index.js +112 -0
- package/package.json +26 -0
- package/services/api.js +11 -0
- package/services/authService.js +18 -0
- package/services/repositoryApi.js +86 -0
- package/utils/auth.js +23 -0
- package/utils/chronConfig.js +30 -0
- package/utils/unzipDirectory.js +17 -0
- package/utils/zipDirectory.js +32 -0
package/commands/add.js
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
const fs = require("fs").promises;
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { requireAuth } = require("../utils/auth");
|
|
4
|
+
|
|
5
|
+
async function addRepo(filePath) {
|
|
6
|
+
requireAuth();
|
|
7
|
+
const projectRoot = process.cwd();
|
|
8
|
+
const repoPath = path.join(projectRoot, ".chron");
|
|
9
|
+
// path.join → combines segments into a clean, normalized path
|
|
10
|
+
const stagingPath = path.join(repoPath, "staging");
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
// Ensure the user has initialized a Code Chronicle repository first (.chron exists or not).
|
|
14
|
+
await fs.access(repoPath);
|
|
15
|
+
|
|
16
|
+
// "add ." stages the complete project.
|
|
17
|
+
if (filePath === ".") {
|
|
18
|
+
await copyDirectory(projectRoot, stagingPath, projectRoot);
|
|
19
|
+
console.log("All project files added to the staging area.");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Convert the provided file path into an absolute path.
|
|
24
|
+
const sourcePath = path.resolve(projectRoot, filePath);
|
|
25
|
+
|
|
26
|
+
// Check whether the requested file actually exists.
|
|
27
|
+
const fileStat = await fs.stat(sourcePath);
|
|
28
|
+
|
|
29
|
+
// Preserve the file's relative directory structure inside staging.
|
|
30
|
+
const relativePath = path.relative(projectRoot, sourcePath);
|
|
31
|
+
// path.relative(from, to) → gives the relative path from 'from' to 'to'
|
|
32
|
+
|
|
33
|
+
// Prevent files outside the current project from being staged.
|
|
34
|
+
if (relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
|
|
35
|
+
console.log("Cannot add files outside the repository.");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const destinationPath = path.join(stagingPath, relativePath);
|
|
40
|
+
|
|
41
|
+
if (fileStat.isFile()) {
|
|
42
|
+
// If only a file is added
|
|
43
|
+
// Create parent folders when adding files such as src/controllers/user.js.
|
|
44
|
+
await fs.mkdir(path.dirname(destinationPath), { recursive: true });
|
|
45
|
+
await fs.copyFile(sourcePath, destinationPath);
|
|
46
|
+
} else if (fileStat.isDirectory()) {
|
|
47
|
+
// If the whole directory is added
|
|
48
|
+
await copyDirectory(sourcePath, destinationPath, projectRoot);
|
|
49
|
+
} else {
|
|
50
|
+
console.log("Unsupported file type.");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (fileStat.isFile()) {
|
|
55
|
+
console.log(`File '${relativePath}' added to the staging area.`);
|
|
56
|
+
} else {
|
|
57
|
+
console.log(`Directory '${relativePath}' added to the staging area.`);
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (err.code === "ENOENT") {
|
|
61
|
+
console.log(
|
|
62
|
+
"Repository or file not found. Initialize the repository first and check the file path."
|
|
63
|
+
);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
console.error("Error adding file:", err.message);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function copyDirectory(source, destination) {
|
|
71
|
+
await fs.mkdir(destination, { recursive: true });
|
|
72
|
+
|
|
73
|
+
const entries = await fs.readdir(source, {
|
|
74
|
+
withFileTypes: true,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
for (const entry of entries) {
|
|
78
|
+
if (entry.name === ".chron" || entry.name === "node_modules") {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const sourcePath = path.join(source, entry.name);
|
|
83
|
+
const destinationPath = path.join(destination, entry.name);
|
|
84
|
+
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
await copyDirectory(sourcePath, destinationPath);
|
|
87
|
+
} else {
|
|
88
|
+
await fs.copyFile(sourcePath, destinationPath);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { addRepo };
|
|
94
|
+
|
|
95
|
+
/*
|
|
96
|
+
============================================================
|
|
97
|
+
WORKING OF ADD COMMAND
|
|
98
|
+
============================================================
|
|
99
|
+
|
|
100
|
+
The addRepo() function adds a selected file from the working directory
|
|
101
|
+
to the local staging area of the Code Chronicle version-control system.
|
|
102
|
+
|
|
103
|
+
When the user runs:
|
|
104
|
+
node index.js add <filePath>
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
node index.js add src/app.js
|
|
108
|
+
|
|
109
|
+
1. index.js uses Yargs to detect the "add" command and passes the
|
|
110
|
+
provided file path to addRepo(filePath).
|
|
111
|
+
|
|
112
|
+
2. process.cwd() identifies the directory where the command was
|
|
113
|
+
executed. This directory is treated as the repository/project root.
|
|
114
|
+
|
|
115
|
+
3. The function locates the ".vcsGit" directory created earlier by
|
|
116
|
+
the "init" command.
|
|
117
|
+
|
|
118
|
+
4. fs.access() checks whether ".vcsGit" exists. This ensures that
|
|
119
|
+
the repository has been initialized before files can be staged.
|
|
120
|
+
|
|
121
|
+
5. path.resolve() converts the provided file path into an absolute
|
|
122
|
+
path so that filesystem operations can reliably locate the file.
|
|
123
|
+
|
|
124
|
+
6. fs.stat() checks the provided path and verifies that it represents
|
|
125
|
+
a file. Currently, this implementation supports adding individual
|
|
126
|
+
files rather than entire directories.
|
|
127
|
+
|
|
128
|
+
7. path.relative() calculates the file's location relative to the
|
|
129
|
+
project root. This allows the original directory structure to be
|
|
130
|
+
preserved inside the staging area.
|
|
131
|
+
|
|
132
|
+
For example:
|
|
133
|
+
|
|
134
|
+
Project file:
|
|
135
|
+
src/app.js
|
|
136
|
+
|
|
137
|
+
Staged file:
|
|
138
|
+
.vcsGit/staging/src/app.js
|
|
139
|
+
|
|
140
|
+
8. Files located outside the current repository are rejected to prevent
|
|
141
|
+
unrelated files from being added to the staging area.
|
|
142
|
+
|
|
143
|
+
9. Before copying the file, fs.mkdir() with { recursive: true } creates
|
|
144
|
+
any required parent directories inside the staging area.
|
|
145
|
+
|
|
146
|
+
10. fs.copyFile() copies the selected file from the working directory
|
|
147
|
+
into ".vcsGit/staging".
|
|
148
|
+
|
|
149
|
+
The original file remains unchanged in the working directory. The
|
|
150
|
+
staging area contains a copy representing the version of the file that
|
|
151
|
+
will be included in the next commit.
|
|
152
|
+
|
|
153
|
+
Overall flow:
|
|
154
|
+
|
|
155
|
+
CLI Command
|
|
156
|
+
↓
|
|
157
|
+
node index.js add src/app.js
|
|
158
|
+
↓
|
|
159
|
+
Yargs detects "add"
|
|
160
|
+
↓
|
|
161
|
+
addRepo("src/app.js")
|
|
162
|
+
↓
|
|
163
|
+
Check .vcsGit exists
|
|
164
|
+
↓
|
|
165
|
+
Locate and validate the source file
|
|
166
|
+
↓
|
|
167
|
+
Calculate its relative project path
|
|
168
|
+
↓
|
|
169
|
+
Preserve directory structure
|
|
170
|
+
↓
|
|
171
|
+
Copy file to staging
|
|
172
|
+
↓
|
|
173
|
+
.vcsGit/staging/src/app.js
|
|
174
|
+
|
|
175
|
+
Current VCS Flow:
|
|
176
|
+
|
|
177
|
+
Working Directory
|
|
178
|
+
│
|
|
179
|
+
│ add
|
|
180
|
+
▼
|
|
181
|
+
.vcsGit/staging/
|
|
182
|
+
│
|
|
183
|
+
│ commit
|
|
184
|
+
▼
|
|
185
|
+
.vcsGit/commits/
|
|
186
|
+
│
|
|
187
|
+
│ push
|
|
188
|
+
▼
|
|
189
|
+
Remote Storage (Supabase)
|
|
190
|
+
|
|
191
|
+
Important:
|
|
192
|
+
The "add" command is completely local. It does not create a commit
|
|
193
|
+
and does not communicate with Supabase. Its responsibility is only
|
|
194
|
+
to prepare selected files in the staging area for the next commit.
|
|
195
|
+
|
|
196
|
+
Unlike real Git, which uses an index and a content-addressable object
|
|
197
|
+
database internally, Code Chronicle currently uses physical file copies
|
|
198
|
+
inside the staging directory. This provides a simpler Git-inspired
|
|
199
|
+
version-control implementation.
|
|
200
|
+
============================================================
|
|
201
|
+
*/
|
|
202
|
+
|
|
203
|
+
/*
|
|
204
|
+
==========================================
|
|
205
|
+
WORKING OF copyDirectory()
|
|
206
|
+
==========================================
|
|
207
|
+
|
|
208
|
+
Purpose:
|
|
209
|
+
--------
|
|
210
|
+
This helper function recursively copies an entire directory from the source
|
|
211
|
+
location to the destination while preserving the original folder structure.
|
|
212
|
+
|
|
213
|
+
It is mainly used by the "add" command whenever the user stages a complete
|
|
214
|
+
folder instead of a single file.
|
|
215
|
+
|
|
216
|
+
Example:
|
|
217
|
+
--------
|
|
218
|
+
Project Structure
|
|
219
|
+
|
|
220
|
+
controllers/
|
|
221
|
+
├── add.js
|
|
222
|
+
├── commit.js
|
|
223
|
+
└── auth/
|
|
224
|
+
├── login.js
|
|
225
|
+
└── signup.js
|
|
226
|
+
|
|
227
|
+
Command:
|
|
228
|
+
node index.js add controllers
|
|
229
|
+
|
|
230
|
+
Result:
|
|
231
|
+
|
|
232
|
+
.vcsGit/
|
|
233
|
+
└── staging/
|
|
234
|
+
└── controllers/
|
|
235
|
+
├── add.js
|
|
236
|
+
├── commit.js
|
|
237
|
+
└── auth/
|
|
238
|
+
├── login.js
|
|
239
|
+
└── signup.js
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
Working Flow:
|
|
243
|
+
-------------
|
|
244
|
+
|
|
245
|
+
1. Create the destination directory.
|
|
246
|
+
---------------------------------
|
|
247
|
+
fs.mkdir(destination, { recursive: true })
|
|
248
|
+
|
|
249
|
+
Before copying anything, we ensure that the destination folder exists.
|
|
250
|
+
The recursive option automatically creates any missing parent directories.
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
2. Read all entries inside the source directory.
|
|
254
|
+
---------------------------------------------
|
|
255
|
+
fs.readdir(source, { withFileTypes: true })
|
|
256
|
+
|
|
257
|
+
Instead of returning only file names, Node returns Dirent objects.
|
|
258
|
+
|
|
259
|
+
This allows us to distinguish between:
|
|
260
|
+
• Files
|
|
261
|
+
• Directories
|
|
262
|
+
|
|
263
|
+
using methods such as:
|
|
264
|
+
entry.isFile()
|
|
265
|
+
entry.isDirectory()
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
3. Iterate over every entry.
|
|
269
|
+
--------------------------
|
|
270
|
+
Each file and folder inside the current directory is processed one by one.
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
4. Ignore unnecessary directories.
|
|
274
|
+
-------------------------------
|
|
275
|
+
Certain directories should never be copied into the staging area.
|
|
276
|
+
|
|
277
|
+
Examples:
|
|
278
|
+
.vcsGit
|
|
279
|
+
node_modules
|
|
280
|
+
|
|
281
|
+
The "continue" statement skips these entries and moves to the next one.
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
5. Build source and destination paths.
|
|
285
|
+
-----------------------------------
|
|
286
|
+
sourcePath:
|
|
287
|
+
Absolute path of the current file/folder.
|
|
288
|
+
|
|
289
|
+
destinationPath:
|
|
290
|
+
Location where the same item should be copied inside staging.
|
|
291
|
+
|
|
292
|
+
Since only the current entry name is appended, the original project
|
|
293
|
+
hierarchy is naturally preserved.
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
6. Check whether the current entry is a directory.
|
|
297
|
+
-----------------------------------------------
|
|
298
|
+
If it is a directory, the function calls itself:
|
|
299
|
+
|
|
300
|
+
copyDirectory(sourcePath, destinationPath)
|
|
301
|
+
|
|
302
|
+
This is called recursion.
|
|
303
|
+
|
|
304
|
+
Every recursive call handles one subdirectory until all nested folders
|
|
305
|
+
have been processed.
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
7. Copy files.
|
|
309
|
+
-----------
|
|
310
|
+
If the current entry is a file, it is copied directly using:
|
|
311
|
+
|
|
312
|
+
fs.copyFile(sourcePath, destinationPath)
|
|
313
|
+
|
|
314
|
+
Files represent the base case of recursion because they cannot contain
|
|
315
|
+
any further children.
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
Recursion Example:
|
|
319
|
+
------------------
|
|
320
|
+
|
|
321
|
+
copyDirectory(controllers)
|
|
322
|
+
|
|
323
|
+
│
|
|
324
|
+
├── add.js
|
|
325
|
+
│ │
|
|
326
|
+
│ ▼
|
|
327
|
+
│ copyFile()
|
|
328
|
+
│
|
|
329
|
+
├── commit.js
|
|
330
|
+
│ │
|
|
331
|
+
│ ▼
|
|
332
|
+
│ copyFile()
|
|
333
|
+
│
|
|
334
|
+
└── auth/
|
|
335
|
+
│
|
|
336
|
+
▼
|
|
337
|
+
copyDirectory(auth)
|
|
338
|
+
│
|
|
339
|
+
├── login.js
|
|
340
|
+
│ │
|
|
341
|
+
│ ▼
|
|
342
|
+
│ copyFile()
|
|
343
|
+
│
|
|
344
|
+
└── signup.js
|
|
345
|
+
│
|
|
346
|
+
▼
|
|
347
|
+
copyFile()
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
Time Complexity:
|
|
351
|
+
----------------
|
|
352
|
+
O(N)
|
|
353
|
+
|
|
354
|
+
where N is the total number of files and directories being copied.
|
|
355
|
+
|
|
356
|
+
Every file and directory is visited exactly once.
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
Space Complexity:
|
|
360
|
+
-----------------
|
|
361
|
+
O(H)
|
|
362
|
+
|
|
363
|
+
where H is the maximum depth of the directory tree due to recursive
|
|
364
|
+
function calls.
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
Interview Explanation:
|
|
368
|
+
----------------------
|
|
369
|
+
This function recursively copies a complete directory while preserving
|
|
370
|
+
its folder structure. It first creates the destination directory,
|
|
371
|
+
reads all files and subdirectories using fs.readdir() with
|
|
372
|
+
withFileTypes: true, skips ignored directories such as .vcsGit and
|
|
373
|
+
node_modules, copies files directly using fs.copyFile(), and recursively
|
|
374
|
+
calls itself whenever it encounters another directory. This continues
|
|
375
|
+
until every nested folder and file has been copied into the staging area.
|
|
376
|
+
*/
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const fsp = require("fs").promises; //file system: we can use asynchronous Promise-based operations like await fs.mkdir(...), await fs.writeFile(...)
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { getToken } = require("../utils/auth");
|
|
5
|
+
const { requireAuth } = require("../utils/auth");
|
|
6
|
+
const repositoryApi = require("../services/repositoryApi");
|
|
7
|
+
const { unzipDirectory } = require("../utils/unzipDirectory");
|
|
8
|
+
|
|
9
|
+
async function cloneRepo(repoId) {
|
|
10
|
+
let tempPath;
|
|
11
|
+
try {
|
|
12
|
+
requireAuth(); //check authentication
|
|
13
|
+
|
|
14
|
+
if (!repoId) {
|
|
15
|
+
console.log("Repository ID is required.");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//validate repoId format
|
|
20
|
+
const objectIdRegex = /^[0-9a-fA-F]{24}$/;
|
|
21
|
+
if (!objectIdRegex.test(repoId)) {
|
|
22
|
+
console.log("Invalid repository ID.");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//get repository details
|
|
27
|
+
const token = getToken();
|
|
28
|
+
const repo = await repositoryApi.getRepoDetail(repoId, token);
|
|
29
|
+
const repository = repo.data;
|
|
30
|
+
|
|
31
|
+
//check for same repo name folder exists
|
|
32
|
+
const targetPath = path.resolve(process.cwd(), repository.name);
|
|
33
|
+
if (fs.existsSync(targetPath)) {
|
|
34
|
+
console.log(`Directory '${repository.name}' already exists.`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fs.mkdirSync(targetPath, { recursive: true }); //create repo folder
|
|
39
|
+
|
|
40
|
+
//creating temp folder
|
|
41
|
+
tempPath = path.resolve(targetPath, ".clone-temp");
|
|
42
|
+
fs.mkdirSync(tempPath, { recursive: true });
|
|
43
|
+
|
|
44
|
+
//getting the commits to the local folder
|
|
45
|
+
const response = await repositoryApi.cloneRepository(repoId, token);
|
|
46
|
+
const zipPath = path.join(tempPath, "pull.zip");
|
|
47
|
+
|
|
48
|
+
const writer = fs.createWriteStream(zipPath);
|
|
49
|
+
response.data.pipe(writer);
|
|
50
|
+
|
|
51
|
+
await new Promise((resolve, reject) => {
|
|
52
|
+
writer.on("finish", resolve);
|
|
53
|
+
writer.on("error", reject);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await unzipDirectory(zipPath, targetPath);
|
|
57
|
+
console.log("done");
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error("Error cloning repository:", err.message);
|
|
60
|
+
} finally {
|
|
61
|
+
//deleting the temp folder
|
|
62
|
+
try {
|
|
63
|
+
if (tempPath && fs.existsSync(tempPath)) {
|
|
64
|
+
fs.rmSync(tempPath, {
|
|
65
|
+
recursive: true,
|
|
66
|
+
force: true,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.log(err);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { cloneRepo };
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
const fs = require("fs").promises;
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { v4: uuidv4 } = require("uuid");
|
|
4
|
+
|
|
5
|
+
async function commitRepo(message) {
|
|
6
|
+
const repoPath = path.resolve(process.cwd(), ".chron");
|
|
7
|
+
const stagingPath = path.join(repoPath, "staging");
|
|
8
|
+
const commitsPath = path.join(repoPath, "commits");
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
// Ensure that the repository was initialized before committing.
|
|
12
|
+
await fs.access(repoPath);
|
|
13
|
+
|
|
14
|
+
const stagedFiles = await fs.readdir(stagingPath);
|
|
15
|
+
|
|
16
|
+
// Prevent creating commits when nothing has been staged.
|
|
17
|
+
if (stagedFiles.length === 0) {
|
|
18
|
+
console.log("Nothing to commit. Add files to the staging area first.");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// UUID provides a unique identifier for each commit.
|
|
23
|
+
const commitID = uuidv4();
|
|
24
|
+
const commitDir = path.join(commitsPath, commitID);
|
|
25
|
+
await fs.mkdir(commitDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
const configPath = path.join(repoPath, "config.json");
|
|
28
|
+
const config = await getConfig(configPath);
|
|
29
|
+
const lastCommit = config.lastCommit;
|
|
30
|
+
if (lastCommit) {
|
|
31
|
+
const previousCommitPath = path.join(commitsPath, lastCommit);
|
|
32
|
+
await copyPreviousCommit(previousCommitPath, commitDir);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await copyStagingToCommit(stagingPath, commitDir);
|
|
36
|
+
|
|
37
|
+
// Store metadata describing this specific commit.
|
|
38
|
+
const commitMetadata = {
|
|
39
|
+
id: commitID,
|
|
40
|
+
message,
|
|
41
|
+
date: new Date().toISOString(),
|
|
42
|
+
parentCommit: lastCommit || null,
|
|
43
|
+
branch: "main",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
await fs.writeFile(
|
|
47
|
+
path.join(commitDir, "commit.json"),
|
|
48
|
+
JSON.stringify(commitMetadata, null, 2),
|
|
49
|
+
"utf-8"
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
config.lastCommit = commitID;
|
|
53
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2), "utf8");
|
|
54
|
+
|
|
55
|
+
/*
|
|
56
|
+
* Clear staging after a successful commit.
|
|
57
|
+
* rm() removes the old staging directory and mkdir() recreates it empty.
|
|
58
|
+
*/
|
|
59
|
+
await fs.rm(stagingPath, {
|
|
60
|
+
recursive: true,
|
|
61
|
+
force: true,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await fs.mkdir(stagingPath, {
|
|
65
|
+
recursive: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
console.log(`Commit ${commitID} created with message: ${message}\n`);
|
|
69
|
+
console.log("Current version supports one commit per push.");
|
|
70
|
+
console.log("Please push your latest commit before creating a new one.");
|
|
71
|
+
console.log("");
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (err.code === "ENOENT") {
|
|
74
|
+
console.log("Repository not initialized. Run the init command first.");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.error("Error during commit:", err.message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function copyStagingToCommit(stagingPath, commitDir) {
|
|
83
|
+
await fs.mkdir(commitDir, { recursive: true });
|
|
84
|
+
|
|
85
|
+
const entries = await fs.readdir(stagingPath, {
|
|
86
|
+
withFileTypes: true,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
const sourcePath = path.join(stagingPath, entry.name);
|
|
91
|
+
const destinationPath = path.join(commitDir, entry.name);
|
|
92
|
+
|
|
93
|
+
if (entry.isDirectory()) {
|
|
94
|
+
await copyStagingToCommit(sourcePath, destinationPath);
|
|
95
|
+
} else {
|
|
96
|
+
await fs.copyFile(sourcePath, destinationPath);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function getConfig(configPath) {
|
|
102
|
+
return JSON.parse(await fs.readFile(configPath, "utf8"));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function copyPreviousCommit(source, destination) {
|
|
106
|
+
await fs.mkdir(destination, { recursive: true });
|
|
107
|
+
|
|
108
|
+
const entries = await fs.readdir(source, {
|
|
109
|
+
withFileTypes: true,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
if (entry.name === "commit.json") {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const sourcePath = path.join(source, entry.name);
|
|
118
|
+
const destinationPath = path.join(destination, entry.name);
|
|
119
|
+
|
|
120
|
+
if (entry.isDirectory()) {
|
|
121
|
+
await copyPreviousCommit(sourcePath, destinationPath);
|
|
122
|
+
} else {
|
|
123
|
+
await fs.copyFile(sourcePath, destinationPath);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = { commitRepo };
|
|
129
|
+
|
|
130
|
+
/*
|
|
131
|
+
|--------------------------------------------------------------------------
|
|
132
|
+
| WORKING OF commitRepo()
|
|
133
|
+
|--------------------------------------------------------------------------
|
|
134
|
+
|
|
|
135
|
+
| The commit command creates a snapshot of the current project state.
|
|
136
|
+
| Unlike incremental commits, each commit stores the complete project
|
|
137
|
+
| snapshot, making it easy to restore any previous version later.
|
|
138
|
+
|
|
|
139
|
+
| Workflow:
|
|
140
|
+
|
|
|
141
|
+
| 1. Verify that the current directory contains an initialized
|
|
142
|
+
| .vcsGit repository.
|
|
143
|
+
|
|
|
144
|
+
| 2. Check whether the staging area contains any files.
|
|
145
|
+
| If nothing has been staged, the commit is aborted.
|
|
146
|
+
|
|
|
147
|
+
| 3. Generate a unique commit ID using UUID and create a new
|
|
148
|
+
| directory inside .vcsGit/commits/.
|
|
149
|
+
|
|
|
150
|
+
| 4. Read config.json to obtain the ID of the previous commit.
|
|
151
|
+
| If a previous commit exists:
|
|
152
|
+
| - Copy its entire snapshot into the new commit directory.
|
|
153
|
+
| - Skip copying commit.json since each commit has its own
|
|
154
|
+
| metadata.
|
|
155
|
+
|
|
|
156
|
+
| 5. Copy every staged file into the new commit directory.
|
|
157
|
+
| New files are added, while modified files overwrite the older
|
|
158
|
+
| versions copied from the previous snapshot.
|
|
159
|
+
|
|
|
160
|
+
| 6. Create commit.json containing metadata such as:
|
|
161
|
+
| - Commit ID
|
|
162
|
+
| - Commit message
|
|
163
|
+
| - Timestamp
|
|
164
|
+
|
|
|
165
|
+
| 7. Update config.json by setting lastCommit to the newly
|
|
166
|
+
| created commit ID so future commits know which snapshot
|
|
167
|
+
| to use as their base.
|
|
168
|
+
|
|
|
169
|
+
| 8. Clear the staging area by deleting the staging directory
|
|
170
|
+
| and recreating it as an empty folder.
|
|
171
|
+
|
|
|
172
|
+
| Result:
|
|
173
|
+
| Every commit represents a complete snapshot of the repository,
|
|
174
|
+
| allowing future commands such as revert, checkout, push, and
|
|
175
|
+
| pull to restore project states easily.
|
|
176
|
+
|
|
|
177
|
+
|--------------------------------------------------------------------------
|
|
178
|
+
*/
|