@withone/cli 1.37.1 → 1.37.3

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 (2) hide show
  1. package/dist/index.js +107 -32
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4853,30 +4853,82 @@ function handleId(response, config2, records) {
4853
4853
  import fs9 from "fs";
4854
4854
  import path9 from "path";
4855
4855
  var SYNC_DIR = path9.join(".one", "sync");
4856
- var STATE_FILE = path9.join(SYNC_DIR, "sync_state.json");
4857
- function readSyncState() {
4856
+ var STATE_DIR = path9.join(SYNC_DIR, "state");
4857
+ var LEGACY_STATE_FILE = path9.join(SYNC_DIR, "sync_state.json");
4858
+ function modelFilePath(platform, model) {
4859
+ return path9.join(STATE_DIR, platform, `${model}.json`);
4860
+ }
4861
+ function migrateLegacyIfNeeded() {
4862
+ if (!fs9.existsSync(LEGACY_STATE_FILE)) return;
4858
4863
  try {
4859
- if (!fs9.existsSync(STATE_FILE)) return {};
4860
- const raw = fs9.readFileSync(STATE_FILE, "utf-8");
4861
- return JSON.parse(raw);
4864
+ const raw = fs9.readFileSync(LEGACY_STATE_FILE, "utf-8");
4865
+ const legacy = JSON.parse(raw);
4866
+ for (const [platform, models] of Object.entries(legacy)) {
4867
+ for (const [model, modelState] of Object.entries(models)) {
4868
+ if (fs9.existsSync(modelFilePath(platform, model))) continue;
4869
+ writeModelFile(platform, model, modelState);
4870
+ }
4871
+ }
4872
+ fs9.unlinkSync(LEGACY_STATE_FILE);
4862
4873
  } catch {
4863
- return {};
4874
+ try {
4875
+ fs9.unlinkSync(LEGACY_STATE_FILE);
4876
+ } catch {
4877
+ }
4864
4878
  }
4865
4879
  }
4866
- function writeSyncState(state) {
4867
- fs9.mkdirSync(SYNC_DIR, { recursive: true });
4868
- const tmp = STATE_FILE + ".tmp";
4880
+ function writeModelFile(platform, model, state) {
4881
+ const dir = path9.join(STATE_DIR, platform);
4882
+ fs9.mkdirSync(dir, { recursive: true });
4883
+ const file = path9.join(dir, `${model}.json`);
4884
+ const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2, 10)}.tmp`;
4869
4885
  fs9.writeFileSync(tmp, JSON.stringify(state, null, 2));
4870
- fs9.renameSync(tmp, STATE_FILE);
4886
+ fs9.renameSync(tmp, file);
4887
+ }
4888
+ function readModelFile(platform, model) {
4889
+ try {
4890
+ const raw = fs9.readFileSync(modelFilePath(platform, model), "utf-8");
4891
+ return JSON.parse(raw);
4892
+ } catch {
4893
+ return null;
4894
+ }
4895
+ }
4896
+ function readSyncState() {
4897
+ migrateLegacyIfNeeded();
4898
+ const result = {};
4899
+ let platforms;
4900
+ try {
4901
+ platforms = fs9.readdirSync(STATE_DIR);
4902
+ } catch {
4903
+ return result;
4904
+ }
4905
+ for (const platform of platforms) {
4906
+ const platformDir = path9.join(STATE_DIR, platform);
4907
+ let entries;
4908
+ try {
4909
+ entries = fs9.readdirSync(platformDir);
4910
+ } catch {
4911
+ continue;
4912
+ }
4913
+ for (const entry of entries) {
4914
+ if (!entry.endsWith(".json")) continue;
4915
+ const model = entry.slice(0, -".json".length);
4916
+ const modelState = readModelFile(platform, model);
4917
+ if (modelState) {
4918
+ if (!result[platform]) result[platform] = {};
4919
+ result[platform][model] = modelState;
4920
+ }
4921
+ }
4922
+ }
4923
+ return result;
4871
4924
  }
4872
4925
  function getModelState(platform, model) {
4873
- const state = readSyncState();
4874
- return state[platform]?.[model] ?? null;
4926
+ migrateLegacyIfNeeded();
4927
+ return readModelFile(platform, model);
4875
4928
  }
4876
4929
  function updateModelState(platform, model, partial) {
4877
- const state = readSyncState();
4878
- if (!state[platform]) state[platform] = {};
4879
- const existing = state[platform][model] ?? {
4930
+ migrateLegacyIfNeeded();
4931
+ const existing = readModelFile(platform, model) ?? {
4880
4932
  lastSync: null,
4881
4933
  lastCursor: null,
4882
4934
  totalRecords: 0,
@@ -4884,21 +4936,26 @@ function updateModelState(platform, model, partial) {
4884
4936
  since: null,
4885
4937
  status: "idle"
4886
4938
  };
4887
- state[platform][model] = { ...existing, ...partial };
4888
- writeSyncState(state);
4939
+ writeModelFile(platform, model, { ...existing, ...partial });
4889
4940
  }
4890
4941
  function removeModelState(platform, model) {
4891
- const state = readSyncState();
4892
- if (!state[platform]) return;
4942
+ migrateLegacyIfNeeded();
4943
+ const platformDir = path9.join(STATE_DIR, platform);
4893
4944
  if (model) {
4894
- delete state[platform][model];
4895
- if (Object.keys(state[platform]).length === 0) {
4896
- delete state[platform];
4945
+ try {
4946
+ fs9.unlinkSync(modelFilePath(platform, model));
4947
+ } catch {
4948
+ }
4949
+ try {
4950
+ if (fs9.readdirSync(platformDir).length === 0) fs9.rmdirSync(platformDir);
4951
+ } catch {
4897
4952
  }
4898
4953
  } else {
4899
- delete state[platform];
4954
+ try {
4955
+ fs9.rmSync(platformDir, { recursive: true, force: true });
4956
+ } catch {
4957
+ }
4900
4958
  }
4901
- writeSyncState(state);
4902
4959
  }
4903
4960
 
4904
4961
  // src/lib/sync/db.ts
@@ -5721,10 +5778,10 @@ async function syncModel(api, profile, options) {
5721
5778
  }
5722
5779
  const lock = options.dryRun ? null : acquireSyncLock(platform, model);
5723
5780
  const existingState = getModelState(platform, model);
5724
- if (existingState?.status === "syncing" && !options.force) {
5725
- if (lock) lock.release();
5726
- throw new Error(
5727
- `Sync state says ${platform}/${model} is already syncing. Use --force to override (this may happen if a previous sync crashed before cleanup).`
5781
+ if (existingState?.status === "syncing" && !options.dryRun && !isAgentMode()) {
5782
+ process.stderr.write(
5783
+ `Recovered from crashed previous sync of ${platform}/${model} (state was 'syncing', no live owner)
5784
+ `
5728
5785
  );
5729
5786
  }
5730
5787
  if (!profile.dateFilter && !options.dryRun && !options.fullRefresh && !isAgentMode()) {
@@ -5733,13 +5790,28 @@ async function syncModel(api, profile, options) {
5733
5790
  `
5734
5791
  );
5735
5792
  }
5736
- if (!options.dryRun) {
5737
- updateModelState(platform, model, { status: "syncing" });
5738
- }
5739
5793
  let db = null;
5740
5794
  let totalRecords = 0;
5741
5795
  let pagesProcessed = 0;
5742
5796
  let lastCursor = null;
5797
+ const signalCleanup = (signal) => {
5798
+ try {
5799
+ updateModelState(platform, model, { status: "failed", pagesProcessed, lastCursor });
5800
+ } catch {
5801
+ }
5802
+ try {
5803
+ if (lock) lock.release();
5804
+ } catch {
5805
+ }
5806
+ process.exit(signal === "SIGINT" ? 130 : 143);
5807
+ };
5808
+ const onSigint = () => signalCleanup("SIGINT");
5809
+ const onSigterm = () => signalCleanup("SIGTERM");
5810
+ if (!options.dryRun) {
5811
+ process.on("SIGINT", onSigint);
5812
+ process.on("SIGTERM", onSigterm);
5813
+ updateModelState(platform, model, { status: "syncing" });
5814
+ }
5743
5815
  const seenIds = /* @__PURE__ */ new Set();
5744
5816
  let hooksInserted = 0;
5745
5817
  let hooksUpdated = 0;
@@ -6102,6 +6174,9 @@ async function syncModel(api, profile, options) {
6102
6174
  wrapped._recordsSynced = totalRecords;
6103
6175
  wrapped._pagesProcessed = pagesProcessed;
6104
6176
  throw wrapped;
6177
+ } finally {
6178
+ process.off("SIGINT", onSigint);
6179
+ process.off("SIGTERM", onSigterm);
6105
6180
  }
6106
6181
  }
6107
6182
 
@@ -8676,7 +8751,7 @@ Fetches ALL records and deletes local rows whose IDs are no longer in the source
8676
8751
  .one/sync/
8677
8752
  profiles/{platform}_{model}.json # sync profiles
8678
8753
  data/{platform}.db # SQLite databases (WAL mode)
8679
- sync_state.json # checkpoint tracking
8754
+ state/{platform}/{model}.json # per-model checkpoint tracking
8680
8755
  events/{platform}_{model}.jsonl # change event logs (if onChange: "log")
8681
8756
  logs/{platform}.log # cron run logs
8682
8757
  locks/{platform}_{model}/ # cross-process sync locks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/cli",
3
- "version": "1.37.1",
3
+ "version": "1.37.3",
4
4
  "description": "CLI for managing One",
5
5
  "type": "module",
6
6
  "files": [