tokrepo 3.0.0 → 3.1.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/bin/tokrepo.js +91 -36
- package/package.json +1 -1
package/bin/tokrepo.js
CHANGED
|
@@ -22,9 +22,10 @@ const C = {
|
|
|
22
22
|
|
|
23
23
|
const CONFIG_DIR = path.join(require('os').homedir(), '.tokrepo');
|
|
24
24
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
25
|
+
const SYNC_STATE_FILE = path.join(CONFIG_DIR, 'sync-state.json');
|
|
25
26
|
const PROJECT_CONFIG = '.tokrepo.json';
|
|
26
27
|
const DEFAULT_API = 'https://api.tokrepo.com';
|
|
27
|
-
const CLI_VERSION = '3.
|
|
28
|
+
const CLI_VERSION = '3.1.0';
|
|
28
29
|
const VERSION_CHECK_FILE = path.join(require('os').homedir(), '.tokrepo', '.version-check');
|
|
29
30
|
|
|
30
31
|
// ─── Helpers ───
|
|
@@ -105,6 +106,15 @@ function compareVersions(a, b) {
|
|
|
105
106
|
return 0;
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
// Sync state: maps "dirPath:title" → { uuid, hash, lastSync }
|
|
110
|
+
function readSyncState() {
|
|
111
|
+
try { return JSON.parse(fs.readFileSync(SYNC_STATE_FILE, 'utf8')); } catch { return {}; }
|
|
112
|
+
}
|
|
113
|
+
function writeSyncState(state) {
|
|
114
|
+
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
115
|
+
fs.writeFileSync(SYNC_STATE_FILE, JSON.stringify(state, null, 2), { mode: 0o600 });
|
|
116
|
+
}
|
|
117
|
+
|
|
108
118
|
function readProjectConfig(baseDir = process.cwd()) {
|
|
109
119
|
const configPath = path.join(baseDir, PROJECT_CONFIG);
|
|
110
120
|
try {
|
|
@@ -1063,35 +1073,54 @@ async function cmdSync() {
|
|
|
1063
1073
|
|
|
1064
1074
|
log(` Found ${C.bold}${assets.length}${C.reset} assets\n`);
|
|
1065
1075
|
|
|
1066
|
-
//
|
|
1067
|
-
|
|
1068
|
-
const diffPayload = assets.map(a => ({ title: a.title, content_hash: a.hash }));
|
|
1076
|
+
// Load local sync state for fast local-only diff
|
|
1077
|
+
const syncState = readSyncState();
|
|
1069
1078
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1079
|
+
// Classify each asset: check local state first, then remote diff for unknowns
|
|
1080
|
+
const needsRemoteCheck = [];
|
|
1081
|
+
const localStatus = {};
|
|
1082
|
+
|
|
1083
|
+
for (const asset of assets) {
|
|
1084
|
+
const key = asset.sourcePath;
|
|
1085
|
+
const cached = syncState[key];
|
|
1086
|
+
if (cached && cached.hash === asset.hash) {
|
|
1087
|
+
// Local hash matches last sync — unchanged
|
|
1088
|
+
localStatus[asset.title] = { status: 'unchanged', uuid: cached.uuid };
|
|
1089
|
+
} else if (cached && cached.hash !== asset.hash) {
|
|
1090
|
+
// Local hash differs from last sync — updated
|
|
1091
|
+
localStatus[asset.title] = { status: 'updated', uuid: cached.uuid };
|
|
1092
|
+
} else {
|
|
1093
|
+
// Not in local state — check remote
|
|
1094
|
+
needsRemoteCheck.push(asset);
|
|
1095
|
+
}
|
|
1078
1096
|
}
|
|
1079
1097
|
|
|
1080
|
-
//
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1098
|
+
// Remote diff for assets not in local state
|
|
1099
|
+
if (needsRemoteCheck.length > 0) {
|
|
1100
|
+
info('Comparing with remote...');
|
|
1101
|
+
const diffPayload = needsRemoteCheck.map(a => ({ title: a.title, content_hash: a.hash }));
|
|
1102
|
+
try {
|
|
1103
|
+
const data = await apiRequest('POST', '/api/v1/tokenboard/push/diff', { assets: diffPayload }, config.token, config.api);
|
|
1104
|
+
for (const r of data.results) {
|
|
1105
|
+
localStatus[r.title] = { status: r.status, uuid: r.remote_uuid || '' };
|
|
1106
|
+
}
|
|
1107
|
+
} catch (e) {
|
|
1108
|
+
warn(`Diff API: ${e.message} — treating unknowns as new`);
|
|
1109
|
+
for (const a of needsRemoteCheck) {
|
|
1110
|
+
localStatus[a.title] = { status: 'new', uuid: '' };
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1084
1113
|
}
|
|
1085
1114
|
|
|
1086
1115
|
// Show status
|
|
1087
1116
|
let newCount = 0, updatedCount = 0, unchangedCount = 0;
|
|
1088
1117
|
|
|
1089
1118
|
for (const asset of assets) {
|
|
1090
|
-
const
|
|
1091
|
-
if (status === 'new') {
|
|
1119
|
+
const st = localStatus[asset.title] || { status: 'new' };
|
|
1120
|
+
if (st.status === 'new') {
|
|
1092
1121
|
log(` ${C.green}+ new${C.reset} ${asset.title} ${C.dim}(${asset.files.length} files)${C.reset}`);
|
|
1093
1122
|
newCount++;
|
|
1094
|
-
} else if (status === 'updated') {
|
|
1123
|
+
} else if (st.status === 'updated') {
|
|
1095
1124
|
log(` ${C.yellow}~ updated${C.reset} ${asset.title}`);
|
|
1096
1125
|
updatedCount++;
|
|
1097
1126
|
} else {
|
|
@@ -1117,13 +1146,13 @@ async function cmdSync() {
|
|
|
1117
1146
|
if (confirm.toLowerCase() !== 'y') { log('Aborted.'); return; }
|
|
1118
1147
|
}
|
|
1119
1148
|
|
|
1120
|
-
//
|
|
1149
|
+
// Upsert each changed asset + save state
|
|
1121
1150
|
let successCount = 0;
|
|
1122
1151
|
let failCount = 0;
|
|
1123
1152
|
|
|
1124
1153
|
for (const asset of assets) {
|
|
1125
|
-
const
|
|
1126
|
-
if (status === 'unchanged') continue;
|
|
1154
|
+
const st = localStatus[asset.title] || { status: 'new' };
|
|
1155
|
+
if (st.status === 'unchanged') continue;
|
|
1127
1156
|
|
|
1128
1157
|
const totalChars = asset.files.reduce((sum, f) => sum + f.content.length, 0);
|
|
1129
1158
|
|
|
@@ -1138,6 +1167,15 @@ async function cmdSync() {
|
|
|
1138
1167
|
|
|
1139
1168
|
const action = data.action === 'created' ? C.green + '+ created' : C.yellow + '~ updated';
|
|
1140
1169
|
log(` ${action}${C.reset} ${asset.title} ${C.dim}${data.url}${C.reset}`);
|
|
1170
|
+
|
|
1171
|
+
// Save to local sync state
|
|
1172
|
+
syncState[asset.sourcePath] = {
|
|
1173
|
+
uuid: data.uuid,
|
|
1174
|
+
hash: asset.hash,
|
|
1175
|
+
title: asset.title,
|
|
1176
|
+
url: data.url,
|
|
1177
|
+
lastSync: new Date().toISOString(),
|
|
1178
|
+
};
|
|
1141
1179
|
successCount++;
|
|
1142
1180
|
} catch (e) {
|
|
1143
1181
|
log(` ${C.red}✗ failed${C.reset} ${asset.title}: ${e.message}`);
|
|
@@ -1145,6 +1183,9 @@ async function cmdSync() {
|
|
|
1145
1183
|
}
|
|
1146
1184
|
}
|
|
1147
1185
|
|
|
1186
|
+
// Persist sync state
|
|
1187
|
+
writeSyncState(syncState);
|
|
1188
|
+
|
|
1148
1189
|
log('');
|
|
1149
1190
|
if (failCount === 0) {
|
|
1150
1191
|
success(`Synced ${successCount} assets!`);
|
|
@@ -1176,28 +1217,42 @@ async function cmdStatus() {
|
|
|
1176
1217
|
return;
|
|
1177
1218
|
}
|
|
1178
1219
|
|
|
1179
|
-
//
|
|
1180
|
-
|
|
1181
|
-
const
|
|
1220
|
+
// Check local sync state first, remote for unknowns
|
|
1221
|
+
const syncState = readSyncState();
|
|
1222
|
+
const localStatus = {};
|
|
1223
|
+
const needsRemoteCheck = [];
|
|
1182
1224
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1225
|
+
for (const asset of assets) {
|
|
1226
|
+
const cached = syncState[asset.sourcePath];
|
|
1227
|
+
if (cached && cached.hash === asset.hash) {
|
|
1228
|
+
localStatus[asset.title] = { status: 'unchanged', uuid: cached.uuid };
|
|
1229
|
+
} else if (cached && cached.hash !== asset.hash) {
|
|
1230
|
+
localStatus[asset.title] = { status: 'updated', uuid: cached.uuid };
|
|
1231
|
+
} else {
|
|
1232
|
+
needsRemoteCheck.push(asset);
|
|
1233
|
+
}
|
|
1189
1234
|
}
|
|
1190
1235
|
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1236
|
+
if (needsRemoteCheck.length > 0) {
|
|
1237
|
+
info('Comparing with remote...');
|
|
1238
|
+
const diffPayload = needsRemoteCheck.map(a => ({ title: a.title, content_hash: a.hash }));
|
|
1239
|
+
try {
|
|
1240
|
+
const data = await apiRequest('POST', '/api/v1/tokenboard/push/diff', { assets: diffPayload }, config.token, config.api);
|
|
1241
|
+
for (const r of data.results) {
|
|
1242
|
+
localStatus[r.title] = { status: r.status, uuid: r.remote_uuid || '' };
|
|
1243
|
+
}
|
|
1244
|
+
} catch (e) {
|
|
1245
|
+
error(`Diff API error: ${e.message}`);
|
|
1246
|
+
}
|
|
1194
1247
|
}
|
|
1195
1248
|
|
|
1249
|
+
log('');
|
|
1196
1250
|
let newCount = 0, updatedCount = 0, unchangedCount = 0;
|
|
1197
1251
|
|
|
1198
1252
|
for (const asset of assets) {
|
|
1199
|
-
const
|
|
1200
|
-
const
|
|
1253
|
+
const st = localStatus[asset.title] || { status: 'new', uuid: '' };
|
|
1254
|
+
const status = st.status;
|
|
1255
|
+
const uuid = st.uuid || '';
|
|
1201
1256
|
const uuidShort = uuid ? ` ${C.dim}(${uuid.substring(0, 8)})${C.reset}` : '';
|
|
1202
1257
|
|
|
1203
1258
|
if (status === 'new') {
|