@oamm/textor 1.0.8 → 1.0.9
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/README.md +14 -0
- package/dist/bin/textor.js +144 -50
- package/dist/bin/textor.js.map +1 -1
- package/dist/index.cjs +67 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +67 -48
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -279,6 +279,20 @@ pnpm textor normalize-state
|
|
|
279
279
|
**Options:**
|
|
280
280
|
- `--dry-run`: Print the normalized state without writing it.
|
|
281
281
|
|
|
282
|
+
### prune-missing
|
|
283
|
+
Remove missing references from Textor state. This command identifies files that are tracked in the state but are no longer present on disk and removes them from `.textor/state.json`.
|
|
284
|
+
|
|
285
|
+
**Safe-by-default**: This command never deletes any files from your disk; it only updates the state tracker.
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
pnpm textor prune-missing
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Options:**
|
|
292
|
+
- `--dry-run`: Preview what would be removed from the state without making changes.
|
|
293
|
+
- `--yes`: Skip confirmation prompt.
|
|
294
|
+
- `--no-interactive`: Disable interactive prompts (useful for CI).
|
|
295
|
+
|
|
282
296
|
## 🏗️ Technical Architecture
|
|
283
297
|
|
|
284
298
|
Textor is designed with enterprise-grade robustness, moving beyond simple scaffolding to provide a reliable refactoring engine.
|
package/dist/bin/textor.js
CHANGED
|
@@ -6,6 +6,7 @@ import path from 'path';
|
|
|
6
6
|
import { createHash } from 'crypto';
|
|
7
7
|
import { exec } from 'child_process';
|
|
8
8
|
import { promisify } from 'util';
|
|
9
|
+
import readline from 'readline';
|
|
9
10
|
|
|
10
11
|
const CONFIG_DIR$1 = '.textor';
|
|
11
12
|
const CONFIG_FILE = 'config.json';
|
|
@@ -3844,67 +3845,87 @@ async function validateStateCommand(options) {
|
|
|
3844
3845
|
}
|
|
3845
3846
|
}
|
|
3846
3847
|
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3848
|
+
/**
|
|
3849
|
+
* Computes the drift between the state and the actual files on disk.
|
|
3850
|
+
*
|
|
3851
|
+
* @param {import('./config.js').TextorConfig} config
|
|
3852
|
+
* @param {Object} state
|
|
3853
|
+
* @returns {Promise<{
|
|
3854
|
+
* missing: string[],
|
|
3855
|
+
* modified: string[],
|
|
3856
|
+
* untracked: string[],
|
|
3857
|
+
* orphaned: string[],
|
|
3858
|
+
* synced: number
|
|
3859
|
+
* }>}
|
|
3860
|
+
*/
|
|
3861
|
+
async function getProjectStatus(config, state) {
|
|
3862
|
+
const results = {
|
|
3863
|
+
missing: [],
|
|
3864
|
+
modified: [],
|
|
3865
|
+
untracked: [], // Has signature, not in state
|
|
3866
|
+
orphaned: [], // No signature, not in state
|
|
3867
|
+
synced: 0
|
|
3868
|
+
};
|
|
3859
3869
|
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3870
|
+
const roots = [
|
|
3871
|
+
resolvePath(config, 'pages'),
|
|
3872
|
+
resolvePath(config, 'features'),
|
|
3873
|
+
resolvePath(config, 'components')
|
|
3874
|
+
].map(p => path.resolve(p));
|
|
3865
3875
|
|
|
3866
|
-
|
|
3867
|
-
|
|
3876
|
+
const diskFiles = new Set();
|
|
3877
|
+
const configSignatures = Object.values(config.signatures || {});
|
|
3868
3878
|
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
}
|
|
3879
|
+
for (const root of roots) {
|
|
3880
|
+
if (existsSync(root)) {
|
|
3881
|
+
await scanDirectory(root, diskFiles);
|
|
3873
3882
|
}
|
|
3883
|
+
}
|
|
3874
3884
|
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3885
|
+
// 1. Check state files against disk
|
|
3886
|
+
for (const relativePath in state.files) {
|
|
3887
|
+
const fullPath = path.join(process.cwd(), relativePath);
|
|
3888
|
+
|
|
3889
|
+
if (!existsSync(fullPath)) {
|
|
3890
|
+
results.missing.push(relativePath);
|
|
3891
|
+
continue;
|
|
3892
|
+
}
|
|
3883
3893
|
|
|
3884
|
-
|
|
3894
|
+
// It exists on disk, so it's not untracked/orphaned
|
|
3895
|
+
diskFiles.delete(relativePath);
|
|
3885
3896
|
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3897
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
3898
|
+
const currentHash = calculateHash(content, config.hashing?.normalization);
|
|
3899
|
+
const fileData = state.files[relativePath];
|
|
3889
3900
|
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
}
|
|
3901
|
+
if (currentHash !== fileData.hash) {
|
|
3902
|
+
results.modified.push(relativePath);
|
|
3903
|
+
} else {
|
|
3904
|
+
results.synced++;
|
|
3895
3905
|
}
|
|
3906
|
+
}
|
|
3896
3907
|
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
}
|
|
3908
|
+
// 2. Check remaining disk files
|
|
3909
|
+
for (const relativePath of diskFiles) {
|
|
3910
|
+
const fullPath = path.join(process.cwd(), relativePath);
|
|
3911
|
+
const isGenerated = await isTextorGenerated(fullPath, configSignatures);
|
|
3912
|
+
|
|
3913
|
+
if (isGenerated) {
|
|
3914
|
+
results.untracked.push(relativePath);
|
|
3915
|
+
} else {
|
|
3916
|
+
results.orphaned.push(relativePath);
|
|
3907
3917
|
}
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3920
|
+
return results;
|
|
3921
|
+
}
|
|
3922
|
+
|
|
3923
|
+
async function statusCommand() {
|
|
3924
|
+
try {
|
|
3925
|
+
const config = await loadConfig();
|
|
3926
|
+
const state = await loadState();
|
|
3927
|
+
|
|
3928
|
+
const results = await getProjectStatus(config, state);
|
|
3908
3929
|
|
|
3909
3930
|
// Reporting
|
|
3910
3931
|
console.log('Textor Status Report:');
|
|
@@ -4329,6 +4350,71 @@ async function normalizeStateCommand(options) {
|
|
|
4329
4350
|
}
|
|
4330
4351
|
}
|
|
4331
4352
|
|
|
4353
|
+
/**
|
|
4354
|
+
* Removes missing references from Textor state.
|
|
4355
|
+
* @param {Object} options
|
|
4356
|
+
* @param {boolean} options.dryRun
|
|
4357
|
+
* @param {boolean} options.yes
|
|
4358
|
+
*/
|
|
4359
|
+
async function pruneMissingCommand(options = {}) {
|
|
4360
|
+
try {
|
|
4361
|
+
const config = await loadConfig();
|
|
4362
|
+
const state = await loadState();
|
|
4363
|
+
|
|
4364
|
+
const results = await getProjectStatus(config, state);
|
|
4365
|
+
|
|
4366
|
+
if (results.missing.length === 0) {
|
|
4367
|
+
console.log('No missing references found.');
|
|
4368
|
+
return;
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4371
|
+
console.log(`Found ${results.missing.length} missing references:`);
|
|
4372
|
+
results.missing.forEach(f => console.log(` - ${f}`));
|
|
4373
|
+
|
|
4374
|
+
if (options.dryRun) {
|
|
4375
|
+
console.log('\nDry run: no changes applied to state.');
|
|
4376
|
+
return;
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
if (!options.yes && options.interactive !== false && process.stdin.isTTY && process.env.NODE_ENV !== 'test') {
|
|
4380
|
+
const rl = readline.createInterface({
|
|
4381
|
+
input: process.stdin,
|
|
4382
|
+
output: process.stdout
|
|
4383
|
+
});
|
|
4384
|
+
|
|
4385
|
+
const confirmed = await new Promise(resolve => {
|
|
4386
|
+
rl.question('\nDo you want to proceed with pruning? (y/N) ', (answer) => {
|
|
4387
|
+
rl.close();
|
|
4388
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
4389
|
+
});
|
|
4390
|
+
});
|
|
4391
|
+
|
|
4392
|
+
if (!confirmed) {
|
|
4393
|
+
console.log('Aborted.');
|
|
4394
|
+
return;
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4397
|
+
|
|
4398
|
+
for (const relPath of results.missing) {
|
|
4399
|
+
delete state.files[relPath];
|
|
4400
|
+
}
|
|
4401
|
+
|
|
4402
|
+
// Reconstruct metadata
|
|
4403
|
+
state.components = reconstructComponents(state.files, config);
|
|
4404
|
+
state.sections = reconstructSections(state, config);
|
|
4405
|
+
|
|
4406
|
+
await saveState(state);
|
|
4407
|
+
console.log(`\n✓ Successfully removed ${results.missing.length} missing references from state.`);
|
|
4408
|
+
|
|
4409
|
+
} catch (error) {
|
|
4410
|
+
console.error('Error:', error.message);
|
|
4411
|
+
if (typeof process.exit === 'function' && process.env.NODE_ENV !== 'test') {
|
|
4412
|
+
process.exit(1);
|
|
4413
|
+
}
|
|
4414
|
+
throw error;
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
|
|
4332
4418
|
const program = new Command();
|
|
4333
4419
|
|
|
4334
4420
|
program
|
|
@@ -4466,5 +4552,13 @@ program
|
|
|
4466
4552
|
.option('--dry-run', 'Show the normalized state without writing to disk')
|
|
4467
4553
|
.action(normalizeStateCommand);
|
|
4468
4554
|
|
|
4555
|
+
program
|
|
4556
|
+
.command('prune-missing')
|
|
4557
|
+
.description('Remove missing files from state (files that are in state but not on disk)')
|
|
4558
|
+
.option('--dry-run', 'Show what would be removed without applying')
|
|
4559
|
+
.option('--yes', 'Skip confirmation')
|
|
4560
|
+
.option('--no-interactive', 'Disable interactive prompts')
|
|
4561
|
+
.action(pruneMissingCommand);
|
|
4562
|
+
|
|
4469
4563
|
program.parse();
|
|
4470
4564
|
//# sourceMappingURL=textor.js.map
|
package/dist/bin/textor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textor.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"textor.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.cjs
CHANGED
|
@@ -3208,58 +3208,77 @@ async function validateStateCommand(options) {
|
|
|
3208
3208
|
}
|
|
3209
3209
|
}
|
|
3210
3210
|
|
|
3211
|
+
/**
|
|
3212
|
+
* Computes the drift between the state and the actual files on disk.
|
|
3213
|
+
*
|
|
3214
|
+
* @param {import('./config.js').TextorConfig} config
|
|
3215
|
+
* @param {Object} state
|
|
3216
|
+
* @returns {Promise<{
|
|
3217
|
+
* missing: string[],
|
|
3218
|
+
* modified: string[],
|
|
3219
|
+
* untracked: string[],
|
|
3220
|
+
* orphaned: string[],
|
|
3221
|
+
* synced: number
|
|
3222
|
+
* }>}
|
|
3223
|
+
*/
|
|
3224
|
+
async function getProjectStatus(config, state) {
|
|
3225
|
+
const results = {
|
|
3226
|
+
missing: [],
|
|
3227
|
+
modified: [],
|
|
3228
|
+
untracked: [], // Has signature, not in state
|
|
3229
|
+
orphaned: [], // No signature, not in state
|
|
3230
|
+
synced: 0
|
|
3231
|
+
};
|
|
3232
|
+
const roots = [
|
|
3233
|
+
resolvePath(config, 'pages'),
|
|
3234
|
+
resolvePath(config, 'features'),
|
|
3235
|
+
resolvePath(config, 'components')
|
|
3236
|
+
].map(p => path.resolve(p));
|
|
3237
|
+
const diskFiles = new Set();
|
|
3238
|
+
const configSignatures = Object.values(config.signatures || {});
|
|
3239
|
+
for (const root of roots) {
|
|
3240
|
+
if (fs.existsSync(root)) {
|
|
3241
|
+
await scanDirectory(root, diskFiles);
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
// 1. Check state files against disk
|
|
3245
|
+
for (const relativePath in state.files) {
|
|
3246
|
+
const fullPath = path.join(process.cwd(), relativePath);
|
|
3247
|
+
if (!fs.existsSync(fullPath)) {
|
|
3248
|
+
results.missing.push(relativePath);
|
|
3249
|
+
continue;
|
|
3250
|
+
}
|
|
3251
|
+
// It exists on disk, so it's not untracked/orphaned
|
|
3252
|
+
diskFiles.delete(relativePath);
|
|
3253
|
+
const content = await promises.readFile(fullPath, 'utf-8');
|
|
3254
|
+
const currentHash = calculateHash(content, config.hashing?.normalization);
|
|
3255
|
+
const fileData = state.files[relativePath];
|
|
3256
|
+
if (currentHash !== fileData.hash) {
|
|
3257
|
+
results.modified.push(relativePath);
|
|
3258
|
+
}
|
|
3259
|
+
else {
|
|
3260
|
+
results.synced++;
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
// 2. Check remaining disk files
|
|
3264
|
+
for (const relativePath of diskFiles) {
|
|
3265
|
+
const fullPath = path.join(process.cwd(), relativePath);
|
|
3266
|
+
const isGenerated = await isTextorGenerated(fullPath, configSignatures);
|
|
3267
|
+
if (isGenerated) {
|
|
3268
|
+
results.untracked.push(relativePath);
|
|
3269
|
+
}
|
|
3270
|
+
else {
|
|
3271
|
+
results.orphaned.push(relativePath);
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
return results;
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3211
3277
|
async function statusCommand() {
|
|
3212
3278
|
try {
|
|
3213
3279
|
const config = await loadConfig();
|
|
3214
3280
|
const state = await loadState();
|
|
3215
|
-
const results =
|
|
3216
|
-
missing: [],
|
|
3217
|
-
modified: [],
|
|
3218
|
-
untracked: [], // Has signature, not in state
|
|
3219
|
-
orphaned: [], // No signature, not in state
|
|
3220
|
-
synced: 0
|
|
3221
|
-
};
|
|
3222
|
-
const roots = [
|
|
3223
|
-
resolvePath(config, 'pages'),
|
|
3224
|
-
resolvePath(config, 'features'),
|
|
3225
|
-
resolvePath(config, 'components')
|
|
3226
|
-
].map(p => path.resolve(p));
|
|
3227
|
-
const diskFiles = new Set();
|
|
3228
|
-
const configSignatures = Object.values(config.signatures || {});
|
|
3229
|
-
for (const root of roots) {
|
|
3230
|
-
if (fs.existsSync(root)) {
|
|
3231
|
-
await scanDirectory(root, diskFiles);
|
|
3232
|
-
}
|
|
3233
|
-
}
|
|
3234
|
-
// 1. Check state files against disk
|
|
3235
|
-
for (const relativePath in state.files) {
|
|
3236
|
-
const fullPath = path.join(process.cwd(), relativePath);
|
|
3237
|
-
if (!fs.existsSync(fullPath)) {
|
|
3238
|
-
results.missing.push(relativePath);
|
|
3239
|
-
continue;
|
|
3240
|
-
}
|
|
3241
|
-
diskFiles.delete(relativePath);
|
|
3242
|
-
const content = await promises.readFile(fullPath, 'utf-8');
|
|
3243
|
-
const currentHash = calculateHash(content, config.hashing?.normalization);
|
|
3244
|
-
const fileData = state.files[relativePath];
|
|
3245
|
-
if (currentHash !== fileData.hash) {
|
|
3246
|
-
results.modified.push(relativePath);
|
|
3247
|
-
}
|
|
3248
|
-
else {
|
|
3249
|
-
results.synced++;
|
|
3250
|
-
}
|
|
3251
|
-
}
|
|
3252
|
-
// 2. Check remaining disk files
|
|
3253
|
-
for (const relativePath of diskFiles) {
|
|
3254
|
-
const fullPath = path.join(process.cwd(), relativePath);
|
|
3255
|
-
const isGenerated = await isTextorGenerated(fullPath, configSignatures);
|
|
3256
|
-
if (isGenerated) {
|
|
3257
|
-
results.untracked.push(relativePath);
|
|
3258
|
-
}
|
|
3259
|
-
else {
|
|
3260
|
-
results.orphaned.push(relativePath);
|
|
3261
|
-
}
|
|
3262
|
-
}
|
|
3281
|
+
const results = await getProjectStatus(config, state);
|
|
3263
3282
|
// Reporting
|
|
3264
3283
|
console.log('Textor Status Report:');
|
|
3265
3284
|
console.log(` Synced files: ${results.synced}`);
|