@usesocial/cli 0.3.4 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @usesocial/cli
2
2
 
3
+ ## 0.3.6
4
+
5
+ ### Patch Changes
6
+
7
+ - [#19](https://github.com/usesocial/monorepo/pull/19) [`297845c`](https://github.com/usesocial/monorepo/commit/297845cb3c8422c70942cb8504f9e4beb171743c) Thanks [@CyrusNuevoDia](https://github.com/CyrusNuevoDia)! - Capture sanitized diagnostic exception traces for unexpected CLI failures and publish CLI source maps for PostHog error tracking.
8
+
9
+ ## 0.3.5
10
+
11
+ ### Patch Changes
12
+
13
+ - [#18](https://github.com/usesocial/monorepo/pull/18) [`831fe99`](https://github.com/usesocial/monorepo/commit/831fe99ed1d8ebc045e053fd53a8c5372ec7f49a) Thanks [@CyrusNuevoDia](https://github.com/CyrusNuevoDia)! - Resume existing CLI login sessions at the billing step when a seat is missing, and show account-connect guidance only after login and seat setup complete.
14
+
3
15
  ## 0.3.4
4
16
 
5
17
  ### Patch Changes
package/dist/index.d.mts CHANGED
@@ -1334,7 +1334,8 @@ type Resolvable<T> = T | Promise<T> | (() => T) | (() => Promise<T>); //#endregi
1334
1334
  /** Every analytics event name the CLI emits. */
1335
1335
  type AnalyticsEvent = "command_executed" | "cli_first_run" | "login_started" | "login_phase_completed" | "login_completed" | "login_failed" | "logout";
1336
1336
  type Analytics = {
1337
- /** Queue an event. No-op when disabled. Never throws. */capture(event: AnalyticsEvent, properties?: Record<string, unknown>): void; /** True when telemetry is active (token present, not opted-out, not CI). */
1337
+ /** Queue an event. No-op when disabled. Never throws. */capture(event: AnalyticsEvent, properties?: Record<string, unknown>): void; /** Queue a sanitized exception. No-op when disabled. Never throws. */
1338
+ captureException(error: unknown, properties?: Record<string, unknown>): void; /** True when telemetry is active (token present, not opted-out, not CI). */
1338
1339
  readonly enabled: boolean;
1339
1340
  /**
1340
1341
  * On login: alias(anonymousId → userId) once, persist userId as the
@@ -1491,4 +1492,5 @@ declare const runSocial: ({
1491
1492
  }) => Promise<number>;
1492
1493
  declare const main: () => Promise<never>;
1493
1494
  //#endregion
1494
- export { createSocialCommand, main, resolveCommandPath, runSocial };
1495
+ export { createSocialCommand, main, resolveCommandPath, runSocial };
1496
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -10675,6 +10675,73 @@ const liftedTimestamp = (collection, item) => {
10675
10675
  const value = collection.lift(item)[collection.sinceField];
10676
10676
  return typeof value === "number" ? value : null;
10677
10677
  };
10678
+ const stepPage = (state, page, opts) => {
10679
+ const newestId = state.firstPage && page.items[0] ? String(page.items[0].id) : state.newestId;
10680
+ const rows = [];
10681
+ const ids = [];
10682
+ for (const item of page.items) {
10683
+ const id = String(item.id);
10684
+ if (state.checkpoint !== null && id === state.checkpoint) return {
10685
+ rows,
10686
+ ids,
10687
+ nextState: {
10688
+ ...state,
10689
+ newestId,
10690
+ firstPage: false
10691
+ },
10692
+ stoppedReason: "checkpoint",
10693
+ done: true
10694
+ };
10695
+ if (opts.since != null && opts.collection.sinceField) {
10696
+ const timestamp = liftedTimestamp(opts.collection, item);
10697
+ if (timestamp != null && timestamp < opts.since) return {
10698
+ rows,
10699
+ ids,
10700
+ nextState: {
10701
+ ...state,
10702
+ newestId,
10703
+ firstPage: false
10704
+ },
10705
+ stoppedReason: "since",
10706
+ done: true
10707
+ };
10708
+ }
10709
+ const row = opts.collection.lift(item);
10710
+ applyBakedQueryOverlay(row, opts.request.bakedQuery);
10711
+ applyParentOverlay(row, opts.parentIdToken, opts.parentId);
10712
+ rows.push(row);
10713
+ if (opts.collectIds) ids.push(id);
10714
+ }
10715
+ if (opts.collection.pagination.style === "offset") {
10716
+ const nextOffset = state.offset + opts.collection.pageSize;
10717
+ const done = page.items.length === 0 || page.totalCount != null && nextOffset >= page.totalCount || page.totalCount == null && page.items.length < opts.collection.pageSize;
10718
+ return {
10719
+ rows,
10720
+ ids,
10721
+ nextState: {
10722
+ ...state,
10723
+ offset: done ? state.offset : nextOffset,
10724
+ newestId,
10725
+ firstPage: false
10726
+ },
10727
+ stoppedReason: "complete",
10728
+ done
10729
+ };
10730
+ }
10731
+ const nextCursor = page.cursor;
10732
+ return {
10733
+ rows,
10734
+ ids,
10735
+ nextState: {
10736
+ ...state,
10737
+ cursor: nextCursor || null,
10738
+ newestId,
10739
+ firstPage: false
10740
+ },
10741
+ stoppedReason: "complete",
10742
+ done: !nextCursor
10743
+ };
10744
+ };
10678
10745
  const estimateFor = (collection, limitCredits, totalCount) => ({
10679
10746
  collectionKey: collection.key,
10680
10747
  ...estimateSync({
@@ -10698,16 +10765,19 @@ const refinedEstimateMessage = (collection, limitCredits, totalCount) => {
10698
10765
  const walkRequest = async (args) => {
10699
10766
  const checkpoint = args.useCheckpoint ? args.deps.cache.getCursor(args.collection.key)?.cursor ?? null : null;
10700
10767
  const ids = [];
10701
- let cursor = null;
10702
- let offset = 0;
10703
- let newestId = null;
10768
+ let state = {
10769
+ checkpoint,
10770
+ cursor: null,
10771
+ offset: 0,
10772
+ newestId: null,
10773
+ firstPage: true
10774
+ };
10704
10775
  let lastPageCredits = null;
10705
10776
  let pages = 0;
10706
10777
  let upserted = 0;
10707
- let firstPage = true;
10708
10778
  let stoppedReason = "complete";
10709
10779
  while (true) {
10710
- const predictedNext = firstPage ? estimatePageCredits(args.collection.estCostBPS, args.collection.estUnitsPerPage) : lastPageCredits ?? estimatePageCredits(args.collection.estCostBPS, args.collection.estUnitsPerPage);
10780
+ const predictedNext = state.firstPage ? estimatePageCredits(args.collection.estCostBPS, args.collection.estUnitsPerPage) : lastPageCredits ?? estimatePageCredits(args.collection.estCostBPS, args.collection.estUnitsPerPage);
10711
10781
  if (args.tracker.wouldExceed(predictedNext)) {
10712
10782
  stoppedReason = "billing-limit";
10713
10783
  break;
@@ -10717,61 +10787,34 @@ const walkRequest = async (args) => {
10717
10787
  ownIdToken: args.collection.ownIdToken,
10718
10788
  chatId: args.parentId,
10719
10789
  parentIdToken: args.parentIdToken
10720
- }) + buildQuery(args.collection.pagination, args.collection.pageSize, cursor, offset, args.request.bakedQuery);
10790
+ }) + buildQuery(args.collection.pagination, args.collection.pageSize, state.cursor, state.offset, args.request.bakedQuery);
10721
10791
  const response = await args.deps.api.get(path, { headers: args.deps.accountHeaders });
10722
10792
  const credits = creditsFromHeader(response.headers);
10723
10793
  args.tracker.add(credits);
10724
10794
  lastPageCredits = credits;
10725
10795
  pages++;
10726
10796
  const page = validateEnvelope(await response.json(), args.collection, args.onProgress);
10727
- if (firstPage && page.totalCount != null) args.onProgress?.(refinedEstimateMessage(args.collection, args.billingLimitCredits, page.totalCount));
10728
- const [firstItem] = page.items;
10729
- if (firstPage && firstItem) newestId = String(firstItem.id);
10730
- firstPage = false;
10731
- const rows = [];
10732
- let hitStop = false;
10733
- for (const item of page.items) {
10734
- const id = String(item.id);
10735
- if (checkpoint !== null && id === checkpoint) {
10736
- stoppedReason = "checkpoint";
10737
- hitStop = true;
10738
- break;
10739
- }
10740
- if (args.since != null && args.collection.sinceField) {
10741
- const timestamp = liftedTimestamp(args.collection, item);
10742
- if (timestamp != null && timestamp < args.since) {
10743
- stoppedReason = "since";
10744
- hitStop = true;
10745
- break;
10746
- }
10747
- }
10748
- const row = args.collection.lift(item);
10749
- applyBakedQueryOverlay(row, args.request.bakedQuery);
10750
- applyParentOverlay(row, args.parentIdToken, args.parentId);
10751
- rows.push(row);
10752
- if (args.collectIds) ids.push(id);
10753
- }
10754
- args.deps.cache.upsert(args.collection.key, rows);
10755
- upserted += rows.length;
10756
- if (hitStop) break;
10757
- if (args.collection.pagination.style === "offset") {
10758
- const nextOffset = offset + args.collection.pageSize;
10759
- if (page.items.length === 0 || page.totalCount != null && nextOffset >= page.totalCount || page.totalCount == null && page.items.length < args.collection.pageSize) {
10760
- stoppedReason = "complete";
10761
- break;
10762
- }
10763
- offset = nextOffset;
10764
- continue;
10765
- }
10766
- cursor = page.cursor;
10767
- if (!cursor) {
10768
- stoppedReason = "complete";
10797
+ if (state.firstPage && page.totalCount != null) args.onProgress?.(refinedEstimateMessage(args.collection, args.billingLimitCredits, page.totalCount));
10798
+ const step = stepPage(state, page, {
10799
+ collection: args.collection,
10800
+ request: args.request,
10801
+ since: args.since,
10802
+ collectIds: args.collectIds,
10803
+ parentId: args.parentId,
10804
+ parentIdToken: args.parentIdToken
10805
+ });
10806
+ args.deps.cache.upsert(args.collection.key, step.rows);
10807
+ upserted += step.rows.length;
10808
+ ids.push(...step.ids);
10809
+ state = step.nextState;
10810
+ if (step.done) {
10811
+ stoppedReason = step.stoppedReason;
10769
10812
  break;
10770
10813
  }
10771
10814
  }
10772
10815
  return {
10773
10816
  ids,
10774
- newestId,
10817
+ newestId: state.newestId,
10775
10818
  pages,
10776
10819
  upserted,
10777
10820
  checkpoint,
@@ -21059,7 +21102,7 @@ const createInstance = (defaults) => {
21059
21102
  const ky = createInstance();
21060
21103
  //#endregion
21061
21104
  //#region package.json
21062
- var version$1 = "0.3.4";
21105
+ var version$1 = "0.3.6";
21063
21106
  //#endregion
21064
21107
  //#region src/lib/env.ts
21065
21108
  const URLWithTrailingSlash = url().transform(ensureTrailingSlash);
@@ -21070,7 +21113,7 @@ const env = createEnv({
21070
21113
  SOCIAL_WEB_URL: process.env.SOCIAL_WEB_URL,
21071
21114
  WSL_DISTRO_NAME: process.env.WSL_DISTRO_NAME,
21072
21115
  WSL_INTEROP: process.env.WSL_INTEROP,
21073
- NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN: "phc_xxr5gJRiRMMSoyxQYCpT5zJQZmF88uMXf7mmYjKzLdki",
21116
+ NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN: "phc_A8HpmMUWsgUaHuyMoskLWtNDp69Rww9V3VEbCT4AxXdF",
21074
21117
  NEXT_PUBLIC_POSTHOG_HOST: "https://us.i.posthog.com",
21075
21118
  CI: process.env.CI,
21076
21119
  DO_NOT_TRACK: process.env.DO_NOT_TRACK,
@@ -26065,9 +26108,19 @@ const writeState = (path, state) => {
26065
26108
  };
26066
26109
  const FIRST_RUN_NOTICE = [
26067
26110
  "social collects anonymous usage analytics (command names, outcomes, timings,",
26068
- "and coarse environment — never your content, handles, IDs, or arguments).",
26111
+ "coarse environment, and diagnostic exception traces — never your content,",
26112
+ "handles, IDs, arguments, stdin, or response bodies).",
26069
26113
  "Opt out any time with DO_NOT_TRACK=1 or SOCIAL_DO_NOT_TRACK=1."
26070
26114
  ].join("\n");
26115
+ const sanitizeExceptionForPostHog = (error) => {
26116
+ const sanitized = /* @__PURE__ */ new Error("social command failed");
26117
+ sanitized.name = error instanceof Error && error.name ? error.name : "Error";
26118
+ if (error instanceof Error && error.stack) {
26119
+ const [, ...frames] = error.stack.split("\n");
26120
+ sanitized.stack = [`${sanitized.name}: ${sanitized.message}`, ...frames].join("\n");
26121
+ }
26122
+ return sanitized;
26123
+ };
26071
26124
  var PostHogAnalytics = class {
26072
26125
  enabled = true;
26073
26126
  #client;
@@ -26115,6 +26168,15 @@ var PostHogAnalytics = class {
26115
26168
  });
26116
26169
  } catch {}
26117
26170
  }
26171
+ captureException(error, properties = {}) {
26172
+ if (!this.#client) return;
26173
+ try {
26174
+ this.#client.captureException(sanitizeExceptionForPostHog(error), this.#distinctId, {
26175
+ ...this.#baseProps,
26176
+ ...properties
26177
+ });
26178
+ } catch {}
26179
+ }
26118
26180
  identifyUser(userId, properties = {}) {
26119
26181
  if (!(this.#client && userId)) return;
26120
26182
  const previousUserId = this.#state.userId;
@@ -26155,6 +26217,7 @@ var PostHogAnalytics = class {
26155
26217
  var DisabledAnalytics = class {
26156
26218
  enabled = false;
26157
26219
  capture() {}
26220
+ captureException() {}
26158
26221
  identifyUser() {}
26159
26222
  reset() {}
26160
26223
  shutdown() {
@@ -27444,7 +27507,6 @@ const DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
27444
27507
  const MAGIC_LINK_PATH = "/api/auth/sign-in/magic-link";
27445
27508
  const TERMS_URL = new URL(siteConfig.links.terms, siteConfig.publicWebURL).toString();
27446
27509
  const LOGIN_SUCCESS_MESSAGE = "Logged in! 🎉";
27447
- const NEXT_COMMANDS_MESSAGE = "Now run `social account connect linkedin` or `social account connect x`.";
27448
27510
  const formatUserCode = (code) => `${code.slice(0, 4)}-${code.slice(4)}`;
27449
27511
  const isRecord$2 = (value) => typeof value === "object" && value !== null;
27450
27512
  const isAuthErrorResponse = (data) => isRecord$2(data) && (typeof data.error === "string" || typeof data.error_description === "string" || typeof data.message === "string");
@@ -27563,7 +27625,6 @@ const waitForDeviceToken = async (ctx, deviceCode, phoneNumber) => {
27563
27625
  await updatePhoneNumber(ctx, phoneNumber);
27564
27626
  clearTick();
27565
27627
  spinner.stop(LOGIN_SUCCESS_MESSAGE);
27566
- ctx.ui.info(NEXT_COMMANDS_MESSAGE);
27567
27628
  return token;
27568
27629
  } catch (error) {
27569
27630
  clearTick();
@@ -27834,6 +27895,8 @@ const assertInteractiveTerminal = () => {
27834
27895
  if (!(process.stdin.isTTY && process.stdout.isTTY)) throw new UsageError("social account login requires an interactive terminal.");
27835
27896
  };
27836
27897
  const nextCommands = ["social account connect linkedin", "social account connect x"];
27898
+ const nextCommandsMessage = `Now run \`${nextCommands[0]}\` or \`${nextCommands[1]}\`.`;
27899
+ const billingReady = (billing) => billing.seats.available > 0;
27837
27900
  const alreadyLoggedInOutput = ({ credential, session }) => ({
27838
27901
  status: "already_logged_in",
27839
27902
  message: "Already logged in.",
@@ -27849,16 +27912,19 @@ const offlineOutput = () => ({
27849
27912
  code: "offline",
27850
27913
  message: "Couldn't reach the server. Check your network connection and try again."
27851
27914
  });
27852
- const runLogin = async (ctx) => {
27915
+ const runLogin = async (ctx, loginPhases = phases, deps = {}) => {
27916
+ const bearerIsValid = deps.isBearerValid ?? isBearerValid;
27917
+ const readLocalCredentials = deps.readCredentials ?? readCredentials;
27853
27918
  analytics.capture("login_started");
27854
- for (const phase of phases) {
27855
- if (phase.run !== accessPhase && phase.run !== signInPhase && !isBearerValid(await readCredentials())) throw new AuthError("Session expired before login finished. Run `social account login` again.");
27919
+ for (const phase of loginPhases) {
27920
+ if (phase.name !== "access" && phase.name !== "sign-in" && deps.authenticated !== true && !bearerIsValid(await readLocalCredentials())) throw new AuthError("Session expired before login finished. Run `social account login` again.");
27856
27921
  const result = await phase.run(ctx);
27857
27922
  if (result.status !== "done") return result;
27858
27923
  analytics.capture("login_phase_completed", { phase: phase.name });
27859
27924
  }
27860
27925
  const session = await ctx.client.cli.session.me();
27861
27926
  const billing = await ctx.client.billing.status();
27927
+ ctx.ui.info(nextCommandsMessage);
27862
27928
  return {
27863
27929
  status: "ok",
27864
27930
  user: session.user,
@@ -27906,7 +27972,18 @@ const loginCommand = defineCommand({
27906
27972
  try {
27907
27973
  const client = createORPCAPIClient();
27908
27974
  const existing = await readExistingAuthSession(client);
27909
- if (existing.kind === "existing") output = alreadyLoggedInOutput(existing.session);
27975
+ if (existing.kind === "existing") if (billingReady(await client.billing.status())) output = alreadyLoggedInOutput(existing.session);
27976
+ else {
27977
+ assertInteractiveTerminal();
27978
+ output = await runLogin({
27979
+ ui: createUI(process.stdout.isTTY === true),
27980
+ client,
27981
+ args: loginArgs
27982
+ }, [{
27983
+ name: "seat",
27984
+ run: seatPhase
27985
+ }], { authenticated: true });
27986
+ }
27910
27987
  else if (existing.kind === "offline") output = offlineOutput();
27911
27988
  else {
27912
27989
  assertInteractiveTerminal();
@@ -29099,6 +29176,16 @@ const captureCommandFinished = (exitCode, telemetry) => {
29099
29176
  duration_ms: Date.now() - commandContext.startedAt
29100
29177
  });
29101
29178
  };
29179
+ const captureCommandException = (error, exitCode, telemetry) => {
29180
+ if (!(commandContext && exitCode === EXIT.api)) return;
29181
+ telemetry.captureException(error, {
29182
+ command: commandContext.command,
29183
+ group: commandContext.group,
29184
+ exit_code: exitCode,
29185
+ outcome: outcomeForExitCode(exitCode),
29186
+ duration_ms: Date.now() - commandContext.startedAt
29187
+ });
29188
+ };
29102
29189
  const finish = async (code, telemetry) => {
29103
29190
  captureCommandFinished(code, telemetry);
29104
29191
  await telemetry.shutdown(ANALYTICS_FLUSH_MS);
@@ -29162,7 +29249,9 @@ const runSocial = async ({ command = createSocialCommand(), rawArgs = process.ar
29162
29249
  if (error instanceof SilentExit) return await finish(error.code, telemetry);
29163
29250
  if (isCittyUsageError(error) && process.stderr.isTTY) await printUsage(command, rawArgs);
29164
29251
  printError(error);
29165
- return await finish(exitCodeFor(error), telemetry);
29252
+ const exitCode = exitCodeFor(error);
29253
+ captureCommandException(error, exitCode, telemetry);
29254
+ return await finish(exitCode, telemetry);
29166
29255
  }
29167
29256
  return await finish(0, telemetry);
29168
29257
  };
@@ -29181,3 +29270,5 @@ const directEntryPath = () => {
29181
29270
  if (directEntryPath()) await main();
29182
29271
  //#endregion
29183
29272
  export { createSocialCommand, main, resolveCommandPath, runSocial };
29273
+
29274
+ //# sourceMappingURL=index.mjs.map