@ibgib/vcs-gib 0.0.6

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 (202) hide show
  1. package/README.md +94 -0
  2. package/VCS_CLI_USER_GUIDE.md +198 -0
  3. package/dist/AUTO-GENERATED-version.d.mts +6 -0
  4. package/dist/AUTO-GENERATED-version.d.mts.map +1 -0
  5. package/dist/AUTO-GENERATED-version.mjs +6 -0
  6. package/dist/AUTO-GENERATED-version.mjs.map +1 -0
  7. package/dist/assumptions.respec.d.mts +2 -0
  8. package/dist/assumptions.respec.d.mts.map +1 -0
  9. package/dist/assumptions.respec.mjs +41 -0
  10. package/dist/assumptions.respec.mjs.map +1 -0
  11. package/dist/bootstrap/node-bootstrap.d.mts +35 -0
  12. package/dist/bootstrap/node-bootstrap.d.mts.map +1 -0
  13. package/dist/bootstrap/node-bootstrap.mjs +165 -0
  14. package/dist/bootstrap/node-bootstrap.mjs.map +1 -0
  15. package/dist/bootstrap/node-bootstrap.respec.node.d.mts +7 -0
  16. package/dist/bootstrap/node-bootstrap.respec.node.d.mts.map +1 -0
  17. package/dist/bootstrap/node-bootstrap.respec.node.mjs +28 -0
  18. package/dist/bootstrap/node-bootstrap.respec.node.mjs.map +1 -0
  19. package/dist/cli/cli-arg-parser.d.mts +27 -0
  20. package/dist/cli/cli-arg-parser.d.mts.map +1 -0
  21. package/dist/cli/cli-arg-parser.mjs +96 -0
  22. package/dist/cli/cli-arg-parser.mjs.map +1 -0
  23. package/dist/cli/cli-arg-parser.respec.node.d.mts +7 -0
  24. package/dist/cli/cli-arg-parser.respec.node.d.mts.map +1 -0
  25. package/dist/cli/cli-arg-parser.respec.node.mjs +35 -0
  26. package/dist/cli/cli-arg-parser.respec.node.mjs.map +1 -0
  27. package/dist/cli/cli-constants.d.mts +26 -0
  28. package/dist/cli/cli-constants.d.mts.map +1 -0
  29. package/dist/cli/cli-constants.mjs +67 -0
  30. package/dist/cli/cli-constants.mjs.map +1 -0
  31. package/dist/cli/cli-e2e.respec.node.d.mts +8 -0
  32. package/dist/cli/cli-e2e.respec.node.d.mts.map +1 -0
  33. package/dist/cli/cli-e2e.respec.node.mjs +263 -0
  34. package/dist/cli/cli-e2e.respec.node.mjs.map +1 -0
  35. package/dist/cli/commands/add-cmd.d.mts +20 -0
  36. package/dist/cli/commands/add-cmd.d.mts.map +1 -0
  37. package/dist/cli/commands/add-cmd.mjs +333 -0
  38. package/dist/cli/commands/add-cmd.mjs.map +1 -0
  39. package/dist/cli/commands/branch-checkout-cmd.respec.node.d.mts +7 -0
  40. package/dist/cli/commands/branch-checkout-cmd.respec.node.d.mts.map +1 -0
  41. package/dist/cli/commands/branch-checkout-cmd.respec.node.mjs +288 -0
  42. package/dist/cli/commands/branch-checkout-cmd.respec.node.mjs.map +1 -0
  43. package/dist/cli/commands/branch-cmd.d.mts +25 -0
  44. package/dist/cli/commands/branch-cmd.d.mts.map +1 -0
  45. package/dist/cli/commands/branch-cmd.mjs +187 -0
  46. package/dist/cli/commands/branch-cmd.mjs.map +1 -0
  47. package/dist/cli/commands/checkout-cmd.d.mts +21 -0
  48. package/dist/cli/commands/checkout-cmd.d.mts.map +1 -0
  49. package/dist/cli/commands/checkout-cmd.mjs +154 -0
  50. package/dist/cli/commands/checkout-cmd.mjs.map +1 -0
  51. package/dist/cli/commands/commit-cmd.d.mts +23 -0
  52. package/dist/cli/commands/commit-cmd.d.mts.map +1 -0
  53. package/dist/cli/commands/commit-cmd.mjs +326 -0
  54. package/dist/cli/commands/commit-cmd.mjs.map +1 -0
  55. package/dist/cli/commands/commit-log-cmd.respec.node.d.mts +7 -0
  56. package/dist/cli/commands/commit-log-cmd.respec.node.d.mts.map +1 -0
  57. package/dist/cli/commands/commit-log-cmd.respec.node.mjs +251 -0
  58. package/dist/cli/commands/commit-log-cmd.respec.node.mjs.map +1 -0
  59. package/dist/cli/commands/init-cmd.d.mts +20 -0
  60. package/dist/cli/commands/init-cmd.d.mts.map +1 -0
  61. package/dist/cli/commands/init-cmd.mjs +119 -0
  62. package/dist/cli/commands/init-cmd.mjs.map +1 -0
  63. package/dist/cli/commands/init-cmd.respec.node.d.mts +7 -0
  64. package/dist/cli/commands/init-cmd.respec.node.d.mts.map +1 -0
  65. package/dist/cli/commands/init-cmd.respec.node.mjs +40 -0
  66. package/dist/cli/commands/init-cmd.respec.node.mjs.map +1 -0
  67. package/dist/cli/commands/log-cmd.d.mts +27 -0
  68. package/dist/cli/commands/log-cmd.d.mts.map +1 -0
  69. package/dist/cli/commands/log-cmd.mjs +146 -0
  70. package/dist/cli/commands/log-cmd.mjs.map +1 -0
  71. package/dist/cli/commands/status-add-cmd.respec.node.d.mts +7 -0
  72. package/dist/cli/commands/status-add-cmd.respec.node.d.mts.map +1 -0
  73. package/dist/cli/commands/status-add-cmd.respec.node.mjs +215 -0
  74. package/dist/cli/commands/status-add-cmd.respec.node.mjs.map +1 -0
  75. package/dist/cli/commands/status-cmd.d.mts +25 -0
  76. package/dist/cli/commands/status-cmd.d.mts.map +1 -0
  77. package/dist/cli/commands/status-cmd.mjs +286 -0
  78. package/dist/cli/commands/status-cmd.mjs.map +1 -0
  79. package/dist/cli/vcs-cli-main.d.mts +14 -0
  80. package/dist/cli/vcs-cli-main.d.mts.map +1 -0
  81. package/dist/cli/vcs-cli-main.mjs +136 -0
  82. package/dist/cli/vcs-cli-main.mjs.map +1 -0
  83. package/dist/engine/branch/branch-graph-helper.d.mts +19 -0
  84. package/dist/engine/branch/branch-graph-helper.d.mts.map +1 -0
  85. package/dist/engine/branch/branch-graph-helper.mjs +49 -0
  86. package/dist/engine/branch/branch-graph-helper.mjs.map +1 -0
  87. package/dist/engine/branch/branch-helper.d.mts +26 -0
  88. package/dist/engine/branch/branch-helper.d.mts.map +1 -0
  89. package/dist/engine/branch/branch-helper.mjs +57 -0
  90. package/dist/engine/branch/branch-helper.mjs.map +1 -0
  91. package/dist/engine/branch/branch-space-helper.d.mts +22 -0
  92. package/dist/engine/branch/branch-space-helper.d.mts.map +1 -0
  93. package/dist/engine/branch/branch-space-helper.mjs +77 -0
  94. package/dist/engine/branch/branch-space-helper.mjs.map +1 -0
  95. package/dist/engine/branch/branch-space.respec.node.d.mts +8 -0
  96. package/dist/engine/branch/branch-space.respec.node.d.mts.map +1 -0
  97. package/dist/engine/branch/branch-space.respec.node.mjs +138 -0
  98. package/dist/engine/branch/branch-space.respec.node.mjs.map +1 -0
  99. package/dist/engine/branch/branch-types.d.mts +26 -0
  100. package/dist/engine/branch/branch-types.d.mts.map +1 -0
  101. package/dist/engine/branch/branch-types.mjs +7 -0
  102. package/dist/engine/branch/branch-types.mjs.map +1 -0
  103. package/dist/engine/diff/line-diff-helper.d.mts +76 -0
  104. package/dist/engine/diff/line-diff-helper.d.mts.map +1 -0
  105. package/dist/engine/diff/line-diff-helper.mjs +144 -0
  106. package/dist/engine/diff/line-diff-helper.mjs.map +1 -0
  107. package/dist/engine/diff/line-diff-helper.respec.node.d.mts +7 -0
  108. package/dist/engine/diff/line-diff-helper.respec.node.d.mts.map +1 -0
  109. package/dist/engine/diff/line-diff-helper.respec.node.mjs +66 -0
  110. package/dist/engine/diff/line-diff-helper.respec.node.mjs.map +1 -0
  111. package/dist/engine/filter/glob-helper.d.mts +51 -0
  112. package/dist/engine/filter/glob-helper.d.mts.map +1 -0
  113. package/dist/engine/filter/glob-helper.mjs +132 -0
  114. package/dist/engine/filter/glob-helper.mjs.map +1 -0
  115. package/dist/engine/filter/glob-helper.respec.node.d.mts +7 -0
  116. package/dist/engine/filter/glob-helper.respec.node.d.mts.map +1 -0
  117. package/dist/engine/filter/glob-helper.respec.node.mjs +78 -0
  118. package/dist/engine/filter/glob-helper.respec.node.mjs.map +1 -0
  119. package/dist/engine/index/index-helper.d.mts +78 -0
  120. package/dist/engine/index/index-helper.d.mts.map +1 -0
  121. package/dist/engine/index/index-helper.mjs +289 -0
  122. package/dist/engine/index/index-helper.mjs.map +1 -0
  123. package/dist/engine/index/index-types.d.mts +63 -0
  124. package/dist/engine/index/index-types.d.mts.map +1 -0
  125. package/dist/engine/index/index-types.mjs +7 -0
  126. package/dist/engine/index/index-types.mjs.map +1 -0
  127. package/dist/engine/item/item-atom-helper.d.mts +20 -0
  128. package/dist/engine/item/item-atom-helper.d.mts.map +1 -0
  129. package/dist/engine/item/item-atom-helper.mjs +30 -0
  130. package/dist/engine/item/item-atom-helper.mjs.map +1 -0
  131. package/dist/engine/item/item-helper.d.mts +37 -0
  132. package/dist/engine/item/item-helper.d.mts.map +1 -0
  133. package/dist/engine/item/item-helper.mjs +194 -0
  134. package/dist/engine/item/item-helper.mjs.map +1 -0
  135. package/dist/engine/item/item-types.d.mts +127 -0
  136. package/dist/engine/item/item-types.d.mts.map +1 -0
  137. package/dist/engine/item/item-types.mjs +16 -0
  138. package/dist/engine/item/item-types.mjs.map +1 -0
  139. package/dist/index.d.mts +7 -0
  140. package/dist/index.d.mts.map +1 -0
  141. package/dist/index.mjs +7 -0
  142. package/dist/index.mjs.map +1 -0
  143. package/dist/respec-gib.node.d.mts +7 -0
  144. package/dist/respec-gib.node.d.mts.map +1 -0
  145. package/dist/respec-gib.node.mjs +11 -0
  146. package/dist/respec-gib.node.mjs.map +1 -0
  147. package/dist/vcs-gib-constants.d.mts +7 -0
  148. package/dist/vcs-gib-constants.d.mts.map +1 -0
  149. package/dist/vcs-gib-constants.mjs +7 -0
  150. package/dist/vcs-gib-constants.mjs.map +1 -0
  151. package/dist/vcs-gib.respec.node.d.mts +8 -0
  152. package/dist/vcs-gib.respec.node.d.mts.map +1 -0
  153. package/dist/vcs-gib.respec.node.mjs +82 -0
  154. package/dist/vcs-gib.respec.node.mjs.map +1 -0
  155. package/generate-version-file.js +20 -0
  156. package/package.json +28 -0
  157. package/src/AUTO-GENERATED-version.mts +5 -0
  158. package/src/assumptions.respec.mts +50 -0
  159. package/src/bootstrap/node-bootstrap.mts +198 -0
  160. package/src/bootstrap/node-bootstrap.respec.node.mts +35 -0
  161. package/src/cli/cli-arg-parser.mts +119 -0
  162. package/src/cli/cli-arg-parser.respec.node.mts +45 -0
  163. package/src/cli/cli-constants.mts +77 -0
  164. package/src/cli/cli-e2e.respec.node.mts +318 -0
  165. package/src/cli/commands/add-cmd.mts +415 -0
  166. package/src/cli/commands/branch-checkout-cmd.respec.node.mts +322 -0
  167. package/src/cli/commands/branch-cmd.mts +243 -0
  168. package/src/cli/commands/checkout-cmd.mts +202 -0
  169. package/src/cli/commands/commit-cmd.mts +421 -0
  170. package/src/cli/commands/commit-log-cmd.respec.node.mts +276 -0
  171. package/src/cli/commands/init-cmd.mts +152 -0
  172. package/src/cli/commands/init-cmd.respec.node.mts +49 -0
  173. package/src/cli/commands/log-cmd.mts +197 -0
  174. package/src/cli/commands/status-add-cmd.respec.node.mts +246 -0
  175. package/src/cli/commands/status-cmd.mts +366 -0
  176. package/src/cli/vcs-cli-main.mts +153 -0
  177. package/src/engine/branch/branch-graph-helper.mts +69 -0
  178. package/src/engine/branch/branch-helper.mts +79 -0
  179. package/src/engine/branch/branch-space-helper.mts +104 -0
  180. package/src/engine/branch/branch-space.respec.node.mts +158 -0
  181. package/src/engine/branch/branch-types.mts +29 -0
  182. package/src/engine/diff/line-diff-helper.mts +210 -0
  183. package/src/engine/diff/line-diff-helper.respec.node.mts +84 -0
  184. package/src/engine/filter/glob-helper.mts +165 -0
  185. package/src/engine/filter/glob-helper.respec.node.mts +94 -0
  186. package/src/engine/index/index-helper.mts +378 -0
  187. package/src/engine/index/index-types.mts +66 -0
  188. package/src/engine/item/item-atom-helper.mts +30 -0
  189. package/src/engine/item/item-helper.mts +239 -0
  190. package/src/engine/item/item-types.mts +136 -0
  191. package/src/index.mts +7 -0
  192. package/src/respec-gib.node.mts +12 -0
  193. package/src/vcs-gib-constants.mts +7 -0
  194. package/src/vcs-gib.respec.node.mts +96 -0
  195. package/test_runs/001-hello-node/20260728_090100_0001-flow-core-main.md +39 -0
  196. package/test_runs/001-hello-node/20260728_090200_0003-flow-branching-worktree.md +54 -0
  197. package/test_runs/001-hello-node/20260728_090300_0002-flow-selective-add.md +45 -0
  198. package/test_runs/001-hello-node/20260728_142100_0007-flow-edge-cases.md +44 -0
  199. package/test_runs/001-hello-node/20260728_142400_0005-flow-hardcoded-ignores.md +36 -0
  200. package/tools/pack-for-test.mjs +73 -0
  201. package/tsconfig.json +18 -0
  202. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,246 @@
1
+ /**
2
+ * @module status-add-cmd.respec
3
+ *
4
+ * Unit tests for `vcs status` and `vcs add` command handlers.
5
+ */
6
+
7
+ import path from 'node:path';
8
+ import fs from 'node:fs/promises';
9
+
10
+ import {
11
+ respecfully, ifWe, iReckon
12
+ } from '@ibgib/helper-gib/dist/respec-gib/respec-gib.mjs';
13
+
14
+ import { executeInitCmd } from './init-cmd.mjs';
15
+ import { executeStatusCmd } from './status-cmd.mjs';
16
+ import { executeAddCmd } from './add-cmd.mjs';
17
+ import { executeCommitCmd } from './commit-cmd.mjs';
18
+ import { bootstrapVcsMetaspace } from '../../bootstrap/node-bootstrap.mjs';
19
+ import { getB2tFSIndexIbGib } from '../../engine/index/index-helper.mjs';
20
+
21
+ const lc = `[apps/vcs-gib/src/cli/commands/status-add-cmd.respec.node.mts]`;
22
+
23
+ await respecfully(lc, `vcs status and vcs add Command Handlers`, async () => {
24
+
25
+ await ifWe(lc, `detects untracked files in fresh repository`, async () => {
26
+ const testDir = path.join(process.cwd(), '.test_status_add_1');
27
+ const vcsFolderName = '.vcsgib_test_1';
28
+ try {
29
+ await executeInitCmd({
30
+ targetDir: testDir,
31
+ vcsFolderName,
32
+ quiet: true,
33
+ });
34
+
35
+ await fs.writeFile(path.join(testDir, 'hello.txt'), 'Hello ibgib!', 'utf8');
36
+
37
+ const statusRes = await executeStatusCmd({
38
+ targetDir: testDir,
39
+ vcsFolderName,
40
+ quiet: true,
41
+ });
42
+
43
+ iReckon(lc, statusRes.branchName).willEqual('main');
44
+ iReckon(lc, statusRes.untracked.map(x => x.path)).includes('hello.txt');
45
+ iReckon(lc, statusRes.staged.length).willEqual(0);
46
+ } finally {
47
+ await fs.rm(testDir, { recursive: true, force: true });
48
+ }
49
+ });
50
+
51
+ await ifWe(lc, `discovers .vcsgib via upward discovery when executed from a subfolder`, async () => {
52
+ const testDir = path.join(process.cwd(), '.test_status_add_subfolder');
53
+ const vcsFolderName = '.vcsgib_test_subfolder';
54
+ try {
55
+ await executeInitCmd({
56
+ targetDir: testDir,
57
+ vcsFolderName,
58
+ quiet: true,
59
+ });
60
+
61
+ const subDir = path.join(testDir, 'src', 'components');
62
+ await fs.mkdir(subDir, { recursive: true });
63
+ await fs.writeFile(path.join(subDir, 'Button.tsx'), 'export const Button = () => null;', 'utf8');
64
+
65
+ const statusRes = await executeStatusCmd({
66
+ targetDir: subDir,
67
+ vcsFolderName,
68
+ quiet: true,
69
+ });
70
+
71
+ iReckon(lc, statusRes.branchName).willEqual('main');
72
+ iReckon(lc, statusRes.untracked.map(x => x.path)).includes('src/components/Button.tsx');
73
+ } finally {
74
+ await fs.rm(testDir, { recursive: true, force: true });
75
+ }
76
+ });
77
+
78
+ await ifWe(lc, `skips re-staging unmodified committed files during executeAddCmd with all flag`, async () => {
79
+ const testDir = path.join(process.cwd(), '.test_status_add_unmodified');
80
+ const vcsFolderName = '.vcsgib_test_unmodified';
81
+ try {
82
+ await executeInitCmd({
83
+ targetDir: testDir,
84
+ vcsFolderName,
85
+ quiet: true,
86
+ });
87
+
88
+ await fs.writeFile(path.join(testDir, 'file.txt'), 'Initial Content', 'utf8');
89
+
90
+ await executeAddCmd({
91
+ paths: ['file.txt'],
92
+ targetDir: testDir,
93
+ vcsFolderName,
94
+ quiet: true,
95
+ });
96
+
97
+ await executeCommitCmd({
98
+ message: 'Initial commit',
99
+ targetDir: testDir,
100
+ vcsFolderName,
101
+ quiet: true,
102
+ });
103
+
104
+ // Run add -a again when file is unmodified
105
+ const addRes = await executeAddCmd({
106
+ all: true,
107
+ targetDir: testDir,
108
+ vcsFolderName,
109
+ quiet: true,
110
+ });
111
+
112
+ iReckon(lc, addRes.stagedCount).willEqual(0);
113
+ iReckon(lc, addRes.stagedPaths.length).willEqual(0);
114
+ } finally {
115
+ await fs.rm(testDir, { recursive: true, force: true });
116
+ }
117
+ });
118
+
119
+ await ifWe(lc, `stages untracked file via executeAddCmd`, async () => {
120
+ const testDir = path.join(process.cwd(), '.test_status_add_2');
121
+ const vcsFolderName = '.vcsgib_test_2';
122
+ try {
123
+ await executeInitCmd({
124
+ targetDir: testDir,
125
+ vcsFolderName,
126
+ quiet: true,
127
+ });
128
+
129
+ await fs.writeFile(path.join(testDir, 'file1.txt'), 'Content 1', 'utf8');
130
+ await fs.writeFile(path.join(testDir, 'file2.txt'), 'Content 2', 'utf8');
131
+
132
+ const addRes = await executeAddCmd({
133
+ paths: ['file1.txt'],
134
+ targetDir: testDir,
135
+ vcsFolderName,
136
+ quiet: true,
137
+ });
138
+
139
+ iReckon(lc, addRes.stagedPaths).includes('file1.txt');
140
+
141
+ const { localSpace, vcsPath } = await bootstrapVcsMetaspace({
142
+ repoPath: testDir,
143
+ vcsFolderName,
144
+ });
145
+ const indexIbGib = await getB2tFSIndexIbGib({ space: localSpace, vcsPath });
146
+ iReckon(lc, Boolean(indexIbGib?.data?.staged_item && indexIbGib.data.staged_item.length > 0)).isGonnaBeTrue();
147
+ iReckon(lc, indexIbGib?.rel8ns?.staged_item).isGonnaBeFalsy();
148
+
149
+ const statusRes = await executeStatusCmd({
150
+ targetDir: testDir,
151
+ vcsFolderName,
152
+ quiet: true,
153
+ });
154
+
155
+ iReckon(lc, statusRes.staged.map(x => x.path)).includes('file1.txt');
156
+ iReckon(lc, statusRes.untracked.map(x => x.path)).includes('file2.txt');
157
+ iReckon(lc, statusRes.untracked.map(x => x.path)).not.includes('file1.txt');
158
+ } finally {
159
+ await fs.rm(testDir, { recursive: true, force: true });
160
+ }
161
+ });
162
+
163
+ await ifWe(lc, `respects .ibgibignore patterns during status and add`, async () => {
164
+ const testDir = path.join(process.cwd(), '.test_status_add_3');
165
+ const vcsFolderName = '.vcsgib_test_3';
166
+ try {
167
+ await executeInitCmd({
168
+ targetDir: testDir,
169
+ vcsFolderName,
170
+ quiet: true,
171
+ });
172
+
173
+ await fs.writeFile(path.join(testDir, '.ibgibignore'), 'ignored.txt\ntemp/\n', 'utf8');
174
+ await fs.writeFile(path.join(testDir, 'ignored.txt'), 'Secrets', 'utf8');
175
+ await fs.writeFile(path.join(testDir, 'tracked.txt'), 'Public', 'utf8');
176
+ await fs.mkdir(path.join(testDir, 'temp'), { recursive: true });
177
+ await fs.writeFile(path.join(testDir, 'temp/cache.dat'), 'Cache', 'utf8');
178
+
179
+ const statusRes = await executeStatusCmd({
180
+ targetDir: testDir,
181
+ vcsFolderName,
182
+ quiet: true,
183
+ });
184
+
185
+ iReckon(lc, statusRes.untracked.map(x => x.path)).includes('tracked.txt');
186
+ iReckon(lc, statusRes.untracked.map(x => x.path)).not.includes('ignored.txt');
187
+ iReckon(lc, statusRes.untracked.map(x => x.path)).not.includes('temp/cache.dat');
188
+
189
+ const addRes = await executeAddCmd({
190
+ all: true,
191
+ targetDir: testDir,
192
+ vcsFolderName,
193
+ quiet: true,
194
+ });
195
+
196
+ iReckon(lc, addRes.stagedPaths).includes('tracked.txt');
197
+ iReckon(lc, addRes.stagedPaths).not.includes('ignored.txt');
198
+ iReckon(lc, addRes.stagedPaths).not.includes('temp/cache.dat');
199
+ } finally {
200
+ await fs.rm(testDir, { recursive: true, force: true });
201
+ }
202
+ });
203
+
204
+ await ifWe(lc, `stages files matching glob pattern with executeAddCmd`, async () => {
205
+ const testDir = path.join(process.cwd(), '.test_status_add_glob');
206
+ const vcsFolderName = '.vcsgib_test_glob';
207
+ try {
208
+ await executeInitCmd({
209
+ targetDir: testDir,
210
+ vcsFolderName,
211
+ quiet: true,
212
+ });
213
+
214
+ await fs.writeFile(path.join(testDir, 'app.js'), 'console.log("app");', 'utf8');
215
+ await fs.writeFile(path.join(testDir, 'utils.js'), 'console.log("utils");', 'utf8');
216
+ await fs.writeFile(path.join(testDir, 'index.html'), '<html></html>', 'utf8');
217
+
218
+ const addRes = await executeAddCmd({
219
+ paths: ['*.js'],
220
+ targetDir: testDir,
221
+ vcsFolderName,
222
+ quiet: true,
223
+ });
224
+
225
+ iReckon(lc, addRes.stagedPaths).includes('app.js');
226
+ iReckon(lc, addRes.stagedPaths).includes('utils.js');
227
+ iReckon(lc, addRes.stagedPaths).not.includes('index.html');
228
+
229
+ let errorThrown = false;
230
+ try {
231
+ await executeAddCmd({
232
+ paths: ['*.xyz'],
233
+ targetDir: testDir,
234
+ vcsFolderName,
235
+ quiet: true,
236
+ });
237
+ } catch (err: any) {
238
+ errorThrown = true;
239
+ iReckon(lc, err.message).includes('No files matched glob pattern: *.xyz');
240
+ }
241
+ iReckon(lc, errorThrown).isGonnaBeTrue();
242
+ } finally {
243
+ await fs.rm(testDir, { recursive: true, force: true });
244
+ }
245
+ });
246
+ });
@@ -0,0 +1,366 @@
1
+ /**
2
+ * @module status-cmd
3
+ *
4
+ * Command handler for `vcs status`.
5
+ * Compares physical workspace state against active branch snapshot tip and in-band index staging area.
6
+ */
7
+
8
+ import path from 'node:path';
9
+ import fs from 'node:fs/promises';
10
+ import { Dirent } from 'node:fs';
11
+ import crypto from 'node:crypto';
12
+
13
+ import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
14
+ import { getIbGibsFromCache_fallbackToSpaces } from '@ibgib/core-gib/dist/common/other/ibgib-helper.mjs';
15
+
16
+ import { bootstrapVcsMetaspace } from '../../bootstrap/node-bootstrap.mjs';
17
+ import { getOrCreateBranchSpace } from '../../engine/branch/branch-space-helper.mjs';
18
+ import { B2tFSItemIbGib_V1, B2TFS_CHILD_ITEM_REL8N_NAME } from '../../engine/item/item-types.mjs';
19
+ import { getIbGibWithMetaspaceFallback } from '../../engine/item/item-helper.mjs';
20
+ import { B2tFSBranchIbGib_V1 } from '../../engine/branch/branch-types.mjs';
21
+ import { isPathFiltered, parseFilterFileContent } from '../../engine/filter/glob-helper.mjs';
22
+ import { getB2tFSIndexIbGib } from '../../engine/index/index-helper.mjs';
23
+ import { B2tFSStagedItemRecord } from '../../engine/index/index-types.mjs';
24
+ import { GLOBAL_LOG_A_LOT } from '../../vcs-gib-constants.mjs';
25
+
26
+ const lcFile = `[status-cmd]`;
27
+ const logalot = GLOBAL_LOG_A_LOT;
28
+
29
+ export interface VcsStatusItem {
30
+ path: string;
31
+ fsType: 'file' | 'folder';
32
+ status: 'untracked' | 'modified' | 'deleted' | 'staged';
33
+ }
34
+
35
+ export interface StatusCmdResult {
36
+ branchName: string;
37
+ staged: VcsStatusItem[];
38
+ modified: VcsStatusItem[];
39
+ deleted: VcsStatusItem[];
40
+ untracked: VcsStatusItem[];
41
+ }
42
+
43
+ export interface StatusCmdOpts {
44
+ targetDir?: string;
45
+ vcsFolderName?: string;
46
+ quiet?: boolean;
47
+ }
48
+
49
+ export async function executeStatusCmd({
50
+ targetDir = process.cwd(),
51
+ vcsFolderName = '.vcsgib',
52
+ quiet = false,
53
+ }: StatusCmdOpts = {}): Promise<StatusCmdResult> {
54
+ const lc = `${lcFile}[${executeStatusCmd.name}]`;
55
+
56
+ try {
57
+ if (logalot) {
58
+ console.log(`${lc} starting executeStatusCmd (I: 7a8b9c10d11e12f13a14b15c16d17e18)`);
59
+ }
60
+
61
+ const absTargetDir = path.resolve(targetDir);
62
+
63
+ const { metaspace, localSpace, vcsPath, repoRoot } = await bootstrapVcsMetaspace({
64
+ repoPath: absTargetDir,
65
+ vcsFolderName,
66
+ });
67
+
68
+ // Verify repo exists
69
+ try {
70
+ await fs.access(vcsPath);
71
+ } catch {
72
+ throw new Error(`Not an ibgib repository (directory ${vcsPath} does not exist) (E: 4d5e6f7a8b9c10d11e12f13a14b15c6d)`);
73
+ }
74
+
75
+ // 1. Fetch authoritative B2tFSIndexIbGib_V1 directly from localSpace
76
+ const indexIbGib = await getB2tFSIndexIbGib({ space: localSpace, vcsPath });
77
+ const branchName = indexIbGib?.data?.activeBranch || 'main';
78
+
79
+ const activeWorktreeDir = branchName === 'main' ? repoRoot : path.join(repoRoot, 'b', branchName);
80
+
81
+ // Target active branch space b2s_<activeBranch>
82
+ const { space: activeBranchSpace } = await getOrCreateBranchSpace({
83
+ metaspace,
84
+ vcsPath,
85
+ branchName,
86
+ });
87
+
88
+ // 2. Extract staged items directly from in-band B2tFSIndexIbGib_V1 data
89
+ const stagedMap = new Map<string, B2tFSStagedItemRecord>();
90
+ const indexStagedPaths = indexIbGib?.data?.stagedPaths || {};
91
+ for (const [relPath, itemRec] of Object.entries(indexStagedPaths)) {
92
+ stagedMap.set(relPath, itemRec);
93
+ }
94
+
95
+ // 3. Find active branch root item address from in-band branch graph node
96
+ let rootItemAddr: string | undefined;
97
+ const branchAddrs = [
98
+ ...(indexIbGib?.rel8ns?.branch || []),
99
+ ...(indexIbGib?.rel8ns?.branch_tjp || []),
100
+ ];
101
+
102
+ if (branchAddrs.length > 0) {
103
+ let branchIbGibs: B2tFSBranchIbGib_V1[] = [];
104
+ for (const bAddr of branchAddrs) {
105
+ const bIbGib = await getIbGibWithMetaspaceFallback({
106
+ addr: bAddr,
107
+ space: activeBranchSpace,
108
+ metaspace,
109
+ });
110
+ if (bIbGib) {
111
+ branchIbGibs.push(bIbGib as B2tFSBranchIbGib_V1);
112
+ }
113
+ }
114
+
115
+ const activeBranchIbGib = branchIbGibs.find(
116
+ (b) => (b as B2tFSBranchIbGib_V1)?.data?.name === branchName,
117
+ ) as B2tFSBranchIbGib_V1 || (branchIbGibs.at(0) as B2tFSBranchIbGib_V1);
118
+
119
+ rootItemAddr = activeBranchIbGib?.rel8ns?.b2tfs_item?.at(0);
120
+ }
121
+
122
+ // Read .ibgibignore patterns
123
+ let filterPatterns: string[] = [];
124
+ const ignorePath = path.join(activeWorktreeDir, '.ibgibignore');
125
+ try {
126
+ const ignoreContent = await fs.readFile(ignorePath, 'utf8');
127
+ filterPatterns = parseFilterFileContent(ignoreContent);
128
+ } catch {
129
+ // Ignore file missing is fine
130
+ }
131
+
132
+ // 4. Walk active branch graph items
133
+ const branchItemsMap = new Map<string, { relPath: string; fileHash?: string; fsType: 'file' | 'folder' }>();
134
+
135
+ if (rootItemAddr) {
136
+ const rootItem = await getIbGibWithMetaspaceFallback({
137
+ addr: rootItemAddr,
138
+ space: activeBranchSpace,
139
+ metaspace,
140
+ }) as B2tFSItemIbGib_V1;
141
+
142
+ if (rootItem) {
143
+ await statusCmd_walkBranchGraph({
144
+ item: rootItem,
145
+ space: activeBranchSpace,
146
+ metaspace,
147
+ currentRelPath: '',
148
+ outMap: branchItemsMap,
149
+ });
150
+ }
151
+ }
152
+
153
+ // 5. Scan physical workspace
154
+ const physicalItemsMap = new Map<string, { relPath: string; fullPath: string; isDirectory: boolean; fileHash?: string }>();
155
+ await statusCmd_scanPhysicalWorkspace({
156
+ dirPath: activeWorktreeDir,
157
+ baseDir: activeWorktreeDir,
158
+ vcsFolderName,
159
+ filterPatterns,
160
+ outMap: physicalItemsMap,
161
+ });
162
+
163
+ // 6. Categorize items into staged, modified, deleted, untracked
164
+ const staged: VcsStatusItem[] = [];
165
+ const modified: VcsStatusItem[] = [];
166
+ const deleted: VcsStatusItem[] = [];
167
+ const untracked: VcsStatusItem[] = [];
168
+
169
+ // Add staged items
170
+ for (const [relPath, stagedRec] of stagedMap.entries()) {
171
+ staged.push({
172
+ path: relPath,
173
+ fsType: stagedRec.fsType || 'file',
174
+ status: 'staged',
175
+ });
176
+ }
177
+
178
+ // Compare physical items against branch items and staged
179
+ for (const [relPath, physItem] of physicalItemsMap.entries()) {
180
+ if (stagedMap.has(relPath)) {
181
+ continue; // Staged item already reported
182
+ }
183
+
184
+ const branchItem = branchItemsMap.get(relPath);
185
+ if (!branchItem) {
186
+ // Not in active branch snapshot and not staged => untracked
187
+ untracked.push({
188
+ path: relPath,
189
+ fsType: physItem.isDirectory ? 'folder' : 'file',
190
+ status: 'untracked',
191
+ });
192
+ } else {
193
+ // Tracked in branch -> check if modified
194
+ if (!physItem.isDirectory && physItem.fileHash && branchItem.fileHash && physItem.fileHash !== branchItem.fileHash) {
195
+ modified.push({
196
+ path: relPath,
197
+ fsType: 'file',
198
+ status: 'modified',
199
+ });
200
+ }
201
+ }
202
+ }
203
+
204
+ // Check for deleted items (in branch snapshot, but not in physical workspace and not staged)
205
+ for (const [relPath, branchItem] of branchItemsMap.entries()) {
206
+ if (!relPath || relPath === '.' || relPath === '') { continue; }
207
+ if (!physicalItemsMap.has(relPath) && !stagedMap.has(relPath)) {
208
+ deleted.push({
209
+ path: relPath,
210
+ fsType: branchItem.fsType,
211
+ status: 'deleted',
212
+ });
213
+ }
214
+ }
215
+
216
+ if (!quiet) {
217
+ console.log(`On branch ${branchName} (I: 9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b)`);
218
+ if (staged.length > 0) {
219
+ console.log('\nChanges to be committed:');
220
+ for (const item of staged) {
221
+ console.log(` staged: ${item.path}`);
222
+ }
223
+ }
224
+ if (modified.length > 0 || deleted.length > 0) {
225
+ console.log('\nChanges not staged for commit:');
226
+ for (const item of modified) {
227
+ console.log(` modified: ${item.path}`);
228
+ }
229
+ for (const item of deleted) {
230
+ console.log(` deleted: ${item.path}`);
231
+ }
232
+ }
233
+ if (untracked.length > 0) {
234
+ console.log('\nUntracked files:');
235
+ for (const item of untracked) {
236
+ console.log(` untracked:${item.path}`);
237
+ }
238
+ }
239
+ if (staged.length === 0 && modified.length === 0 && deleted.length === 0 && untracked.length === 0) {
240
+ console.log('nothing to commit, working tree clean');
241
+ }
242
+ }
243
+
244
+ return {
245
+ branchName,
246
+ staged,
247
+ modified,
248
+ deleted,
249
+ untracked,
250
+ };
251
+ } catch (error) {
252
+ console.error(`${lc} ${extractErrorMsg(error)} (E: 5e6f7a8b9c10d11e12f13a14b15c6d7e)`);
253
+ throw error;
254
+ } finally {
255
+ if (logalot) {
256
+ console.log(`${lc} complete. (I: 6f7a8b9c10d11e12f13a14b15c6d7e8f)`);
257
+ }
258
+ }
259
+ }
260
+
261
+ // #region statusCmd_walkBranchGraph
262
+ async function statusCmd_walkBranchGraph({
263
+ item,
264
+ space,
265
+ metaspace,
266
+ currentRelPath,
267
+ outMap,
268
+ }: {
269
+ item: B2tFSItemIbGib_V1;
270
+ space: any;
271
+ metaspace?: any;
272
+ currentRelPath: string;
273
+ outMap: Map<string, { relPath: string; fileHash?: string; fsType: 'file' | 'folder' }>;
274
+ }): Promise<void> {
275
+ if (!item || !item.data) { return; }
276
+
277
+ const { fsType, fileHash } = item.data;
278
+ if (currentRelPath) {
279
+ outMap.set(currentRelPath, { relPath: currentRelPath, fileHash, fsType });
280
+ }
281
+
282
+ if (fsType === 'folder') {
283
+ const childAddrs = item.rel8ns?.[B2TFS_CHILD_ITEM_REL8N_NAME] || [];
284
+ for (const childAddr of childAddrs) {
285
+ const childItem = await getIbGibWithMetaspaceFallback({
286
+ addr: childAddr,
287
+ space,
288
+ metaspace,
289
+ }) as B2tFSItemIbGib_V1;
290
+ if (childItem && childItem.data) {
291
+ const childRelPath = currentRelPath
292
+ ? `${currentRelPath}/${childItem.data.name}`
293
+ : childItem.data.name;
294
+ await statusCmd_walkBranchGraph({
295
+ item: childItem,
296
+ space,
297
+ metaspace,
298
+ currentRelPath: childRelPath,
299
+ outMap,
300
+ });
301
+ }
302
+ }
303
+ }
304
+ }
305
+ // #endregion statusCmd_walkBranchGraph
306
+
307
+ // #region statusCmd_scanPhysicalWorkspace
308
+ async function statusCmd_scanPhysicalWorkspace({
309
+ dirPath,
310
+ baseDir,
311
+ vcsFolderName,
312
+ filterPatterns,
313
+ outMap,
314
+ }: {
315
+ dirPath: string;
316
+ baseDir: string;
317
+ vcsFolderName: string;
318
+ filterPatterns: string[];
319
+ outMap: Map<string, { relPath: string; fullPath: string; isDirectory: boolean; fileHash?: string }>;
320
+ }): Promise<void> {
321
+ let entries: Dirent[] = [];
322
+ try {
323
+ entries = await fs.readdir(dirPath, { withFileTypes: true });
324
+ } catch {
325
+ return;
326
+ }
327
+
328
+ for (const entry of entries) {
329
+ const fullEntryPath = path.join(dirPath, entry.name);
330
+ const relPath = path.relative(baseDir, fullEntryPath).replace(/\\/g, '/');
331
+
332
+ // Ignore vcs storage folder, .git, and worktrees folder b
333
+ if (
334
+ relPath === vcsFolderName || relPath.startsWith(vcsFolderName + '/') ||
335
+ relPath === '.git' || relPath.startsWith('.git/') ||
336
+ relPath === 'b' || relPath.startsWith('b/')
337
+ ) {
338
+ continue;
339
+ }
340
+
341
+ if (isPathFiltered({ relativePath: relPath, isDirectory: entry.isDirectory(), filterPatterns })) {
342
+ continue;
343
+ }
344
+
345
+ if (entry.isDirectory()) {
346
+ outMap.set(relPath, { relPath, fullPath: fullEntryPath, isDirectory: true });
347
+ await statusCmd_scanPhysicalWorkspace({
348
+ dirPath: fullEntryPath,
349
+ baseDir,
350
+ vcsFolderName,
351
+ filterPatterns,
352
+ outMap,
353
+ });
354
+ } else if (entry.isFile()) {
355
+ let fileHash: string | undefined;
356
+ try {
357
+ const fileData = await fs.readFile(fullEntryPath);
358
+ fileHash = crypto.createHash('sha256').update(fileData).digest('hex');
359
+ } catch {
360
+ // File unreadable
361
+ }
362
+ outMap.set(relPath, { relPath, fullPath: fullEntryPath, isDirectory: false, fileHash });
363
+ }
364
+ }
365
+ }
366
+ // #endregion statusCmd_scanPhysicalWorkspace