atris 3.15.12 → 3.15.14
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/pull.js +26 -2
- package/commands/push.js +71 -4
- package/package.json +1 -1
package/commands/pull.js
CHANGED
|
@@ -566,11 +566,35 @@ async function pullBusiness(slug) {
|
|
|
566
566
|
// Compute local file hashes
|
|
567
567
|
const localFiles = localFilesBeforePull;
|
|
568
568
|
|
|
569
|
+
function isPullPathInScope(filePath) {
|
|
570
|
+
if (!onlyPrefixes) return true;
|
|
571
|
+
const rel = filePath.replace(/^\//, '');
|
|
572
|
+
return onlyPrefixes.some((pref) => rel.startsWith(pref));
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function filterFilesToPullScope(filesMap = {}) {
|
|
576
|
+
if (!onlyPrefixes) return filesMap;
|
|
577
|
+
return Object.fromEntries(
|
|
578
|
+
Object.entries(filesMap).filter(([p]) => isPullPathInScope(p))
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function filterManifestToPullScope(existingManifest) {
|
|
583
|
+
if (!onlyPrefixes || !existingManifest || !existingManifest.files) return existingManifest;
|
|
584
|
+
return {
|
|
585
|
+
...existingManifest,
|
|
586
|
+
files: filterFilesToPullScope(existingManifest.files),
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
569
590
|
// If output dir is empty (fresh clone) or --force, treat as first sync — pull everything
|
|
570
|
-
const
|
|
591
|
+
const scopedLocalFiles = filterFilesToPullScope(localFiles);
|
|
592
|
+
const scopedRemoteFiles = filterFilesToPullScope(remoteFiles);
|
|
593
|
+
const scopedManifest = filterManifestToPullScope(manifest);
|
|
594
|
+
const effectiveManifest = (Object.keys(scopedLocalFiles).length === 0 || force) ? null : scopedManifest;
|
|
571
595
|
|
|
572
596
|
// Three-way compare
|
|
573
|
-
const diff = threeWayCompare(
|
|
597
|
+
const diff = threeWayCompare(scopedLocalFiles, scopedRemoteFiles, effectiveManifest);
|
|
574
598
|
|
|
575
599
|
if (dryRun) {
|
|
576
600
|
console.log('');
|
package/commands/push.js
CHANGED
|
@@ -95,11 +95,49 @@ function shouldRetrySyncIndividually(result, filesToPush) {
|
|
|
95
95
|
return result.status !== 403 && result.status !== 409;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
function isMassDeletePlan({ deletedPaths = [], filesToPush = [], unchangedCount = 0 } = {}) {
|
|
99
|
+
const deleteCount = deletedPaths.length;
|
|
100
|
+
if (deleteCount === 0) return false;
|
|
101
|
+
const survivingCount = filesToPush.length + unchangedCount;
|
|
102
|
+
if (deleteCount >= 10 && survivingCount === 0) return true;
|
|
103
|
+
if (deleteCount >= 25) return true;
|
|
104
|
+
return deleteCount >= 10 && deleteCount > survivingCount;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function parsePushTimeoutSec(argv = process.argv, defaultSec = 120) {
|
|
108
|
+
let raw = null;
|
|
109
|
+
const eqArg = argv.find(a => a.startsWith('--timeout='));
|
|
110
|
+
if (eqArg) raw = eqArg.slice('--timeout='.length);
|
|
111
|
+
else {
|
|
112
|
+
const idx = argv.indexOf('--timeout');
|
|
113
|
+
if (idx !== -1 && argv[idx + 1]) raw = argv[idx + 1];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const parsed = raw == null ? defaultSec : Number.parseInt(raw, 10);
|
|
117
|
+
if (!Number.isFinite(parsed)) return defaultSec;
|
|
118
|
+
return Math.max(5, Math.min(300, parsed));
|
|
119
|
+
}
|
|
120
|
+
|
|
98
121
|
async function pushAtris() {
|
|
99
122
|
const elapsedMs = startTimer();
|
|
100
123
|
let slug = process.argv[3];
|
|
101
124
|
let _coldWake = false;
|
|
102
125
|
|
|
126
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
127
|
+
console.log('Usage: atris push [business] [--from <path>] [--only <prefix>] [--force] [--delete] [--delete-all]');
|
|
128
|
+
console.log('');
|
|
129
|
+
console.log(' Push requires a fresh pull. If cloud has changed since your last pull,');
|
|
130
|
+
console.log(' the push will be blocked until you run `atris pull`. Use --force to override.');
|
|
131
|
+
console.log('');
|
|
132
|
+
console.log(' atris push Push from current folder (auto-detect business)');
|
|
133
|
+
console.log(' atris push pallet Push pallet/ or atris/pallet/');
|
|
134
|
+
console.log(' atris push pallet --only team/nate Only push files in team/nate/');
|
|
135
|
+
console.log(' atris push --force Bypass freshness check (force-push, may overwrite cloud changes)');
|
|
136
|
+
console.log(' atris push --delete Allow small cloud deletes shown by --dry-run');
|
|
137
|
+
console.log(' atris push --delete-all Extra confirmation for mass-delete recovery');
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}
|
|
140
|
+
|
|
103
141
|
// Auto-detect business from .atris/business.json in current dir
|
|
104
142
|
if (!slug || slug.startsWith('-')) {
|
|
105
143
|
const bizFile = path.join(process.cwd(), '.atris', 'business.json');
|
|
@@ -113,7 +151,7 @@ async function pushAtris() {
|
|
|
113
151
|
}
|
|
114
152
|
|
|
115
153
|
if (!slug || slug === '--help') {
|
|
116
|
-
console.log('Usage: atris push [business] [--from <path>] [--only <prefix>] [--force] [--delete]');
|
|
154
|
+
console.log('Usage: atris push [business] [--from <path>] [--only <prefix>] [--force] [--delete] [--delete-all]');
|
|
117
155
|
console.log('');
|
|
118
156
|
console.log(' Push requires a fresh pull. If cloud has changed since your last pull,');
|
|
119
157
|
console.log(' the push will be blocked until you run `atris pull`. Use --force to override.');
|
|
@@ -122,14 +160,17 @@ async function pushAtris() {
|
|
|
122
160
|
console.log(' atris push pallet Push pallet/ or atris/pallet/');
|
|
123
161
|
console.log(' atris push pallet --only team/nate Only push files in team/nate/');
|
|
124
162
|
console.log(' atris push --force Bypass freshness check (force-push, may overwrite cloud changes)');
|
|
125
|
-
console.log(' atris push --delete Allow cloud deletes shown by --dry-run');
|
|
163
|
+
console.log(' atris push --delete Allow small cloud deletes shown by --dry-run');
|
|
164
|
+
console.log(' atris push --delete-all Extra confirmation for mass-delete recovery');
|
|
126
165
|
process.exit(0);
|
|
127
166
|
}
|
|
128
167
|
|
|
129
168
|
const force = process.argv.includes('--force');
|
|
130
169
|
const dryRun = process.argv.includes('--dry-run');
|
|
131
170
|
const allowDelete = process.argv.includes('--delete');
|
|
171
|
+
const allowMassDelete = process.argv.includes('--delete-all');
|
|
132
172
|
const allowCrossRootManifest = process.argv.includes('--allow-cross-root-manifest');
|
|
173
|
+
const timeoutSec = parsePushTimeoutSec(process.argv);
|
|
133
174
|
|
|
134
175
|
// Parse --only
|
|
135
176
|
let onlyRaw = null;
|
|
@@ -390,6 +431,24 @@ async function pushAtris() {
|
|
|
390
431
|
process.exit(1);
|
|
391
432
|
}
|
|
392
433
|
|
|
434
|
+
if (deletedPaths.length > 0 && allowDelete && !allowMassDelete && isMassDeletePlan({ deletedPaths, filesToPush, unchangedCount })) {
|
|
435
|
+
console.log('');
|
|
436
|
+
console.log(` ✗ Refusing mass delete of ${deletedPaths.length} cloud files with --delete alone.`);
|
|
437
|
+
console.log('');
|
|
438
|
+
console.log(' This looks like a missing local folder or wrong source root.');
|
|
439
|
+
console.log(' No cloud files were deleted.');
|
|
440
|
+
console.log('');
|
|
441
|
+
console.log(' First inspect the plan:');
|
|
442
|
+
console.log(` atris push ${resolvedSlug || slug} --dry-run`);
|
|
443
|
+
console.log('');
|
|
444
|
+
console.log(' If this is truly an intentional full cleanup, rerun with both flags:');
|
|
445
|
+
console.log(` atris push ${resolvedSlug || slug} --delete --delete-all`);
|
|
446
|
+
console.log('');
|
|
447
|
+
console.log(' If this is surprising, pull cloud truth first:');
|
|
448
|
+
console.log(` atris pull ${resolvedSlug || slug} --keep-local --timeout 120`);
|
|
449
|
+
process.exit(1);
|
|
450
|
+
}
|
|
451
|
+
|
|
393
452
|
let pushed = 0;
|
|
394
453
|
let deleted = 0;
|
|
395
454
|
let skipped = [];
|
|
@@ -418,8 +477,14 @@ async function pushAtris() {
|
|
|
418
477
|
};
|
|
419
478
|
const wireFiles = (files) => files.map((f) => ({ path: toWirePath(f.path), content: f.content }));
|
|
420
479
|
const syncFiles = (files) => apiRequestJson(
|
|
421
|
-
`/business/${businessId}/workspaces/${workspaceId}/sync`,
|
|
422
|
-
{
|
|
480
|
+
`/business/${businessId}/workspaces/${workspaceId}/sync?timeout=${timeoutSec}`,
|
|
481
|
+
{
|
|
482
|
+
method: 'POST',
|
|
483
|
+
token: creds.token,
|
|
484
|
+
body: { files: wireFiles(files) },
|
|
485
|
+
headers: { 'X-Atris-Actor-Source': 'cli' },
|
|
486
|
+
timeoutMs: (timeoutSec + 15) * 1000,
|
|
487
|
+
}
|
|
423
488
|
);
|
|
424
489
|
|
|
425
490
|
// Inspect per-file results from a /sync response. Treat "written" and
|
|
@@ -682,4 +747,6 @@ module.exports = {
|
|
|
682
747
|
canonicalWorkspaceRoot,
|
|
683
748
|
basenameOfManifestPath,
|
|
684
749
|
isBusinessWorkspaceRoot,
|
|
750
|
+
isMassDeletePlan,
|
|
751
|
+
parsePushTimeoutSec,
|
|
685
752
|
};
|