@tailor-platform/sdk 1.60.3 → 1.62.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/{application-pusdxz35.mjs → application-BezXGbrU.mjs} +73 -509
  3. package/dist/application-BezXGbrU.mjs.map +1 -0
  4. package/dist/application-DSXntqnV.mjs +4 -0
  5. package/dist/assert-CKfwrmCV.mjs +10 -0
  6. package/dist/assert-CKfwrmCV.mjs.map +1 -0
  7. package/dist/cli/index.mjs +376 -136
  8. package/dist/cli/index.mjs.map +1 -1
  9. package/dist/cli/lib.d.mts +3 -1
  10. package/dist/cli/lib.mjs +13 -6
  11. package/dist/cli/lib.mjs.map +1 -1
  12. package/dist/{client-B-jRdlC_.mjs → client-C68VWo4g.mjs} +1 -1
  13. package/dist/{client-W5P4NYYX.mjs → client-CobIRHl-.mjs} +207 -2
  14. package/dist/{client-W5P4NYYX.mjs.map → client-CobIRHl-.mjs.map} +1 -1
  15. package/dist/configure/index.mjs +2 -2
  16. package/dist/{crashreport-D3DvAzdg.mjs → crashreport-BhD0y14F.mjs} +2 -2
  17. package/dist/{crashreport-D3DvAzdg.mjs.map → crashreport-BhD0y14F.mjs.map} +1 -1
  18. package/dist/{crashreport-lnVTnbB5.mjs → crashreport-D1wKBJ8N.mjs} +1 -1
  19. package/dist/{mock-Dpu__UeJ.mjs → mock-DMgIygjE.mjs} +3 -2
  20. package/dist/mock-DMgIygjE.mjs.map +1 -0
  21. package/dist/plugin/builtin/seed/index.mjs +1 -1
  22. package/dist/plugin/index.mjs +1 -1
  23. package/dist/plugin/index.mjs.map +1 -1
  24. package/dist/registry-D0uB0OrK.mjs.map +1 -1
  25. package/dist/{repl-editor-Y9QJDL0K.mjs → repl-editor-CJG3sz7A.mjs} +11 -9
  26. package/dist/repl-editor-CJG3sz7A.mjs.map +1 -0
  27. package/dist/{runtime-C0_FZWdE.mjs → runtime-C6o4hiYq.mjs} +903 -579
  28. package/dist/runtime-C6o4hiYq.mjs.map +1 -0
  29. package/dist/{schema-DYKNTu-n.mjs → schema-1msIhXwA.mjs} +12 -7
  30. package/dist/schema-1msIhXwA.mjs.map +1 -0
  31. package/dist/{seed-C0fE2sJB.mjs → seed-BH2FbrPV.mjs} +4 -3
  32. package/dist/seed-BH2FbrPV.mjs.map +1 -0
  33. package/dist/service-BHQIerYh.mjs +4 -0
  34. package/dist/{service-aPT0fx3y.mjs → service-DMohAx8a2.mjs} +3 -3
  35. package/dist/service-DMohAx8a2.mjs.map +1 -0
  36. package/dist/service-wI3Hvrgx.mjs +460 -0
  37. package/dist/service-wI3Hvrgx.mjs.map +1 -0
  38. package/dist/{types-Ccwchyj5.mjs → types-2Be3wSMc.mjs} +1 -1
  39. package/dist/{types-BwGth3a1.mjs → types-CmzfQP_m.mjs} +3 -3
  40. package/dist/types-CmzfQP_m.mjs.map +1 -0
  41. package/dist/utils/test/index.mjs +1 -1
  42. package/dist/utils/test/index.mjs.map +1 -1
  43. package/dist/vitest/index.mjs +7 -4
  44. package/dist/vitest/index.mjs.map +1 -1
  45. package/dist/vitest/setup.mjs +1 -1
  46. package/docs/cli/application.md +11 -10
  47. package/docs/cli/tailordb.md +54 -0
  48. package/docs/cli-reference.md +1 -0
  49. package/docs/services/tailordb-migration.md +17 -1
  50. package/package.json +2 -1
  51. package/dist/application-D4tRNn90.mjs +0 -4
  52. package/dist/application-pusdxz35.mjs.map +0 -1
  53. package/dist/mock-Dpu__UeJ.mjs.map +0 -1
  54. package/dist/repl-editor-Y9QJDL0K.mjs.map +0 -1
  55. package/dist/runtime-C0_FZWdE.mjs.map +0 -1
  56. package/dist/schema-DYKNTu-n.mjs.map +0 -1
  57. package/dist/seed-C0fE2sJB.mjs.map +0 -1
  58. package/dist/service-aPT0fx3y.mjs.map +0 -1
  59. package/dist/types-BwGth3a1.mjs.map +0 -1
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { J as PATScope, O as CustomDomainStatus, P as FunctionExecution_Type, V as AuthInvokerSchema, _ as userAgent, a as fetchAll, c as fetchPlatformMachineUserToken, d as initOAuth2Client, f as initOperatorClient, l as fetchUserInfo, r as closeConnectionPool, s as fetchPaged } from "../client-W5P4NYYX.mjs";
3
+ import { Et as AuthInvokerSchema, L as CustomDomainStatus, Nt as PATScope, Q as FunctionExecution_Type, _ as userAgent, a as fetchAll, c as fetchPlatformMachineUserToken, d as initOAuth2Client, f as initOperatorClient, l as fetchUserInfo, r as closeConnectionPool, s as fetchPaged } from "../client-CobIRHl-.mjs";
4
+ import { t as assertDefined } from "../assert-CKfwrmCV.mjs";
4
5
  import { n as logger, r as styles } from "../logger-DpJyJvNz.mjs";
5
- import { $ as listCommand$10, An as toPageDirection, At as startCommand, B as logBetaWarning, C as listCommand$13, Cn as commonArgs, Dn as isVerbose, Dt as jobsCommand, E as resumeCommand, En as deploymentArgs, F as writeDbTypesFile, Gt as parseMigrationLabelNumber, H as removeCommand$1, Ht as executeScript, I as getConfiguredEditorCommand, K as treeCommand, L as openInConfiguredEditor, Lt as functionExecutionStatusToString, Mt as getCommand$6, N as generateCommand$1, O as listCommand$12, On as pagedLogArgs, P as generateMigrationScript, Pt as executionsCommand, Rt as formatKeyValueTable, Sn as defineAppCommand, St as triggerCommand, T as healthCommand, Tn as confirmationArgs, U as updateCommand$3, Vt as deploy, Y as getCommand$5, Yt as INITIAL_SCHEMA_NUMBER, Z as updateCommand$2, _n as generateUserTypes, at as createCommand$3, b as createCommand$4, c as listCommand$14, cn as reconstructSnapshotFromMigrations, f as restoreCommand, ft as tokenCommand, g as getCommand$7, gn as PluginManager, gt as listCommand$7, hn as sdkNameLabelKey, ht as generate, i as updateCommand$4, in as getMigrationFiles, j as truncateCommand, jn as workspaceArgs, kn as paginationArgs, ln as formatMigrationNumber, lt as getCommand$3, m as listCommand$15, mn as resourceTrn, o as removeCommand, on as isValidMigrationNumber, pn as getNamespacesWithMigrations, pt as listCommand$8, q as listCommand$11, r as queryCommand, rn as getMigrationFilePath, rt as deleteCommand$3, sn as loadDiff, st as listCommand$9, t as isNativeTypeScriptRuntime, tt as getCommand$4, u as inviteCommand, v as deleteCommand$4, vn as prompt, vt as getCommand$2, wn as configArg, wt as listCommand$6, xn as assertWritable, xt as webhookCommand, yn as apiCommand, z as showCommand, zt as getCommand$1 } from "../runtime-C0_FZWdE.mjs";
6
- import { C as getDistDir, D as deleteUserTokens, E as loadConfig, F as writePlatformConfig, M as readPlatformConfig, N as resolveTokens, O as fetchLatestToken, P as saveUserTokens, a as WorkflowJobSchema, b as createLogLevelTreeshakeOptions, i as resolveInlineSourcemap, j as loadWorkspaceId, k as loadAccessToken, l as ExecutorSchema, o as ResolverSchema, t as defineApplication, u as INVOKER_EXPR, v as platformBundleDefinePlugin, w as hashContent, x as resolveBundleLogLevel, y as composeFunctionTreeshakeOptions } from "../application-pusdxz35.mjs";
6
+ import { $ as listCommand$10, $t as INITIAL_SCHEMA_NUMBER, An as configArg, At as startCommand, B as logBetaWarning, C as listCommand$13, Cn as generateUserTypes, Dn as assertWritable, Dt as jobsCommand, E as resumeCommand, F as writeDbTypesFile, Fn as paginationArgs, Gt as MIGRATION_LABEL_KEY, H as removeCommand$1, Ht as executeScript, I as getConfiguredEditorCommand, In as toPageDirection, Jt as compareSnapshotWithRemote, K as treeCommand, Kt as handleOptionalToRequiredError, L as openInConfiguredEditor, Ln as workspaceArgs, Lt as functionExecutionStatusToString, Mn as deploymentArgs, Mt as getCommand$6, N as generateCommand$1, Nn as isVerbose, O as listCommand$12, On as defineAppCommand, P as generateMigrationScript, Pn as pagedLogArgs, Pt as executionsCommand, Rt as formatKeyValueTable, Sn as PluginManager, St as triggerCommand, T as healthCommand, Tn as apiCommand, U as updateCommand$3, Vt as deploy, Xt as protoGqlPermission, Y as getCommand$5, Yt as generateAllTypeManifestsFromSnapshot, Z as updateCommand$2, _n as formatMigrationDiff, an as createSnapshotFromLocalTypes, at as createCommand$3, b as createCommand$4, bn as resourceTrn, c as listCommand$14, cn as getMigrationFilePath, dn as isValidMigrationNumber, f as restoreCommand, fn as loadDiff, ft as tokenCommand, g as getCommand$7, gt as listCommand$7, hn as parseMigrationNumberArg, ht as generate, i as updateCommand$4, j as truncateCommand, jn as confirmationArgs, kn as commonArgs, ln as getMigrationFiles, lt as getCommand$3, m as listCommand$15, mn as formatMigrationNumber, nn as assertValidMigrationFiles, o as removeCommand, on as getLatestMigrationNumber, pn as reconstructSnapshotFromMigrations, pt as listCommand$8, q as listCommand$11, qt as parseMigrationLabelNumber, r as queryCommand, rn as compareLocalTypesWithSnapshot, rt as deleteCommand$3, st as listCommand$9, t as isNativeTypeScriptRuntime, tt as getCommand$4, u as inviteCommand, v as deleteCommand$4, vn as hasChanges, vt as getCommand$2, wn as prompt, wt as listCommand$6, xn as sdkNameLabelKey, xt as webhookCommand, yn as getNamespacesWithMigrations, z as showCommand, zt as getCommand$1 } from "../runtime-C6o4hiYq.mjs";
7
+ import { A as resolveTokens, C as loadConfig, E as loadAccessToken, M as writePlatformConfig, O as loadWorkspaceId, T as fetchLatestToken, _ as createLogLevelTreeshakeOptions, a as WorkflowJobSchema, b as getDistDir, c as INVOKER_EXPR, g as composeFunctionTreeshakeOptions, h as platformBundleDefinePlugin, i as resolveInlineSourcemap, j as saveUserTokens, k as readPlatformConfig, o as ResolverSchema, t as defineApplication, v as resolveBundleLogLevel, w as deleteUserTokens, x as hashContent } from "../application-BezXGbrU.mjs";
8
+ import { n as ExecutorSchema } from "../service-wI3Hvrgx.mjs";
7
9
  import { t as multiline } from "../multiline-Cf9ODpr1.mjs";
8
- import { r as isPluginGeneratedType } from "../seed-C0fE2sJB.mjs";
10
+ import { r as isPluginGeneratedType } from "../seed-BH2FbrPV.mjs";
9
11
  import { t as readPackageJson } from "../package-json-DcQApfPQ.mjs";
10
12
  import { n as isCLIError } from "../errors-EsY4XO6O.mjs";
11
- import { a as JSON_FOOTER_MARKER, i as CRASH_LOG_EXTENSION, o as parseCrashReportConfig, r as sendCrashReport, t as initCrashReporting } from "../crashreport-D3DvAzdg.mjs";
13
+ import { a as JSON_FOOTER_MARKER, i as CRASH_LOG_EXTENSION, o as parseCrashReportConfig, r as sendCrashReport, t as initCrashReporting } from "../crashreport-BhD0y14F.mjs";
12
14
  import { arg, defineCommand, runCommand, runMain } from "politty";
13
15
  import { withCompletionCommand } from "politty/completion";
14
16
  import { z } from "zod";
@@ -74,10 +76,7 @@ const authorizeAuthConnectionCommand = defineAppCommand({
74
76
  }).strict(),
75
77
  run: async (args) => {
76
78
  await assertWritable({ profile: args.profile });
77
- const client = await initOperatorClient(await loadAccessToken({
78
- useProfile: true,
79
- profile: args.profile
80
- }));
79
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
81
80
  const workspaceId = await loadWorkspaceId({
82
81
  workspaceId: args["workspace-id"],
83
82
  profile: args.profile
@@ -178,10 +177,7 @@ const deleteAuthConnectionCommand = defineAppCommand({
178
177
  }).strict(),
179
178
  run: async (args) => {
180
179
  await assertWritable({ profile: args.profile });
181
- const client = await initOperatorClient(await loadAccessToken({
182
- useProfile: true,
183
- profile: args.profile
184
- }));
180
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
185
181
  const workspaceId = await loadWorkspaceId({
186
182
  workspaceId: args["workspace-id"],
187
183
  profile: args.profile
@@ -228,10 +224,7 @@ const listAuthConnectionCommand = defineAppCommand({
228
224
  ...paginationArgs()
229
225
  }).strict(),
230
226
  run: async (args) => {
231
- const client = await initOperatorClient(await loadAccessToken({
232
- useProfile: true,
233
- profile: args.profile
234
- }));
227
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
235
228
  const workspaceId = await loadWorkspaceId({
236
229
  workspaceId: args["workspace-id"],
237
230
  profile: args.profile
@@ -308,10 +301,7 @@ const revokeAuthConnectionCommand = defineAppCommand({
308
301
  }).strict(),
309
302
  run: async (args) => {
310
303
  await assertWritable({ profile: args.profile });
311
- const client = await initOperatorClient(await loadAccessToken({
312
- useProfile: true,
313
- profile: args.profile
314
- }));
304
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
315
305
  const workspaceId = await loadWorkspaceId({
316
306
  workspaceId: args["workspace-id"],
317
307
  profile: args.profile
@@ -487,6 +477,7 @@ const deployCommand$1 = defineAppCommand({
487
477
  description: "Run the command without making any changes"
488
478
  }),
489
479
  "no-schema-check": arg(z.boolean().optional(), { description: "Skip schema diff check against migration snapshots" }),
480
+ "no-validate": arg(z.boolean().optional(), { description: "Skip client-side validation against platform resource constraints" }),
490
481
  "no-cache": arg(z.boolean().optional(), { description: "Disable bundle caching for this run" }),
491
482
  "clean-cache": arg(z.boolean().optional(), { description: "Clean the bundle cache before building" })
492
483
  }).strict(),
@@ -501,6 +492,7 @@ const deployCommand$1 = defineAppCommand({
501
492
  dryRun: args["dry-run"],
502
493
  yes: args.yes,
503
494
  noSchemaCheck: args["no-schema-check"],
495
+ noValidate: args["no-validate"],
504
496
  noCache: args["no-cache"],
505
497
  cleanCache: args["clean-cache"]
506
498
  });
@@ -644,9 +636,9 @@ function parseStackTrace(error) {
644
636
  const match = STACK_FRAME_REGEX.exec(line);
645
637
  if (match) frames.push({
646
638
  functionName: match[1] || "<anonymous>",
647
- file: match[2],
648
- line: Number(match[3]),
649
- column: Number(match[4])
639
+ file: assertDefined(match[2], "stack frame file missing"),
640
+ line: Number(assertDefined(match[3], "stack frame line missing")),
641
+ column: Number(assertDefined(match[4], "stack frame column missing"))
650
642
  });
651
643
  }
652
644
  return {
@@ -664,7 +656,7 @@ function extractInlineSourcemap(bundledCode) {
664
656
  const match = INLINE_SOURCEMAP_REGEX.exec(bundledCode);
665
657
  if (!match) return null;
666
658
  try {
667
- const decoded = Buffer.from(match[1], "base64").toString("utf-8");
659
+ const decoded = Buffer.from(assertDefined(match[1], "sourcemap base64 data missing"), "base64").toString("utf-8");
668
660
  return new TraceMap(JSON.parse(decoded));
669
661
  } catch {
670
662
  return null;
@@ -918,7 +910,7 @@ function composeExecutionErrorString(error) {
918
910
  */
919
911
  function formatExecutionErrorFallback(error) {
920
912
  const [headerLine, ...frameLines] = composeExecutionErrorString(error).split("\n");
921
- return [` ${styles.error(headerLine ?? "")}`, ...frameLines.map((line) => ` ${styles.dim(line)}`)].join("\n");
913
+ return [` ${styles.error(headerLine)}`, ...frameLines.map((line) => ` ${styles.dim(line)}`)].join("\n");
922
914
  }
923
915
  /**
924
916
  * Format an execution error for display, applying sourcemap mapping
@@ -1061,10 +1053,7 @@ Stack traces stay accurate even after later redeploys, because the trace is reso
1061
1053
  })
1062
1054
  }).strict(),
1063
1055
  run: async (args) => {
1064
- const client = await initOperatorClient(await loadAccessToken({
1065
- useProfile: true,
1066
- profile: args.profile
1067
- }));
1056
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
1068
1057
  const workspaceId = await loadWorkspaceId({
1069
1058
  workspaceId: args["workspace-id"],
1070
1059
  profile: args.profile
@@ -1228,7 +1217,7 @@ function generateEntry(detected, sourceFile, env, machineUser, workspaceId) {
1228
1217
  `;
1229
1218
  }
1230
1219
  case "workflow-job": {
1231
- const exportName = detected.exportName;
1220
+ const exportName = assertDefined(detected.exportName, "workflow job export name missing");
1232
1221
  return multiline`
1233
1222
  import { ${exportName} } from "${absoluteSourcePath}";
1234
1223
 
@@ -1294,7 +1283,7 @@ async function detectFunctionType(options) {
1294
1283
  const rawInput = module.default.input;
1295
1284
  let inputSchema;
1296
1285
  if (rawInput) {
1297
- const { t } = await import("../types-Ccwchyj5.mjs");
1286
+ const { t } = await import("../types-2Be3wSMc.mjs");
1298
1287
  inputSchema = t.object(rawInput);
1299
1288
  }
1300
1289
  return {
@@ -1362,11 +1351,15 @@ function detectWorkflowJob(module, jobName) {
1362
1351
  exportName: match.exportName
1363
1352
  };
1364
1353
  }
1365
- if (jobs.length === 1) return {
1366
- type: "workflow-job",
1367
- name: jobs[0].name,
1368
- exportName: jobs[0].exportName
1369
- };
1354
+ if (jobs.length === 1) {
1355
+ const [jobEntry] = jobs;
1356
+ const job = assertDefined(jobEntry, "workflow job missing");
1357
+ return {
1358
+ type: "workflow-job",
1359
+ name: job.name,
1360
+ exportName: job.exportName
1361
+ };
1362
+ }
1370
1363
  const available = jobs.map((j) => ` - "${j.name}" (export: ${j.exportName})`).join("\n");
1371
1364
  throw new Error(`Multiple workflow jobs found. Specify one with --name:\n${available}`);
1372
1365
  }
@@ -1441,10 +1434,7 @@ When a \`.js\` file is provided, detection and bundling are skipped and the file
1441
1434
  const { config } = await loadConfig(args.config);
1442
1435
  const authNamespace = resolveAuthNamespace(config.auth);
1443
1436
  const machineUserName = resolveMachineUserName(args["machine-user"], config.auth);
1444
- const client = await initOperatorClient(await loadAccessToken({
1445
- useProfile: true,
1446
- profile: args.profile
1447
- }));
1437
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
1448
1438
  const workspaceId = await loadWorkspaceId({
1449
1439
  workspaceId: args["workspace-id"],
1450
1440
  profile: args.profile
@@ -1518,7 +1508,7 @@ When a \`.js\` file is provided, detection and bundling are skipped and the file
1518
1508
  else {
1519
1509
  if (result.success) logger.success("Execution succeeded");
1520
1510
  else logger.error("Execution failed");
1521
- if (result.logs?.trim()) {
1511
+ if (result.logs.trim()) {
1522
1512
  logger.log(styles.bold("\nLogs:"));
1523
1513
  for (const line of result.logs.split("\n")) logger.log(` ${line}`);
1524
1514
  }
@@ -1562,7 +1552,7 @@ function resolveMachineUserName(cliMachineUser, authConfig) {
1562
1552
  const machineUsers = authConfig.machineUsers;
1563
1553
  if (machineUsers) {
1564
1554
  const keys = Object.keys(machineUsers);
1565
- if (keys.length > 0) return keys[0];
1555
+ if (keys.length > 0) return assertDefined(keys[0], "machine user key missing");
1566
1556
  }
1567
1557
  }
1568
1558
  throw new Error("Machine user is required. Provide --machine-user or ensure tailor.config.ts has machine users configured.");
@@ -1617,7 +1607,7 @@ function resolveResolverArg(argStr, inputSchema, machineUser, workspaceId) {
1617
1607
  type: "machine_user",
1618
1608
  workspaceId,
1619
1609
  attributes: machineUser.attributes ?? null,
1620
- attributeList: machineUser.attributeList ?? []
1610
+ attributeList: machineUser.attributeList
1621
1611
  };
1622
1612
  if (!inputSchema.parse({
1623
1613
  value: parsed,
@@ -1751,7 +1741,7 @@ const startAuthServer = async () => {
1751
1741
  await saveUserTokens(pfConfig, userInfo.email, {
1752
1742
  accessToken: tokens.accessToken,
1753
1743
  refreshToken: tokens.refreshToken ?? void 0
1754
- }, new Date(tokens.expiresAt).toISOString());
1744
+ }, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString());
1755
1745
  pfConfig.current_user = userInfo.email;
1756
1746
  writePlatformConfig(pfConfig);
1757
1747
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -1797,7 +1787,7 @@ async function loginAsMachineUser(args) {
1797
1787
  const clientSecret = args.clientSecret ?? await prompt.password({ message: "Client secret" });
1798
1788
  const tokens = await fetchPlatformMachineUserToken(args.clientId, clientSecret);
1799
1789
  const pfConfig = await readPlatformConfig();
1800
- await saveUserTokens(pfConfig, args.clientId, { accessToken: tokens.accessToken }, new Date(tokens.expiresAt).toISOString());
1790
+ await saveUserTokens(pfConfig, args.clientId, { accessToken: tokens.accessToken }, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString());
1801
1791
  pfConfig.current_user = args.clientId;
1802
1792
  writePlatformConfig(pfConfig);
1803
1793
  }
@@ -1821,7 +1811,7 @@ const loginCommand = defineAppCommand({
1821
1811
  })
1822
1812
  }).strict().describe("Machine User Login")]),
1823
1813
  run: async (args) => {
1824
- if ("machine-user" in args && args["machine-user"]) await loginAsMachineUser({
1814
+ if ("machine-user" in args) await loginAsMachineUser({
1825
1815
  clientId: args.clientId,
1826
1816
  clientSecret: args.clientSecret
1827
1817
  });
@@ -1839,13 +1829,14 @@ const logoutCommand = defineAppCommand({
1839
1829
  args: z.object({}).strict(),
1840
1830
  run: async () => {
1841
1831
  const pfConfig = await readPlatformConfig();
1842
- const userEntry = pfConfig.current_user ? pfConfig.users[pfConfig.current_user] : void 0;
1843
- if (!userEntry) {
1832
+ const currentUser = pfConfig.current_user;
1833
+ const userEntry = currentUser ? pfConfig.users[currentUser] : void 0;
1834
+ if (!userEntry || !currentUser) {
1844
1835
  logger.info("You are not logged in.");
1845
1836
  return;
1846
1837
  }
1847
1838
  try {
1848
- const { accessToken, refreshToken } = await resolveTokens(userEntry, pfConfig.current_user);
1839
+ const { accessToken, refreshToken } = await resolveTokens(userEntry, currentUser);
1849
1840
  const client = initOAuth2Client();
1850
1841
  const tokenTypeHint = refreshToken ? "refresh_token" : "access_token";
1851
1842
  await client.revoke({
@@ -1856,8 +1847,8 @@ const logoutCommand = defineAppCommand({
1856
1847
  } catch (error) {
1857
1848
  logger.warn(`Failed to revoke token: ${error instanceof Error ? error.message : error}`);
1858
1849
  }
1859
- await deleteUserTokens(pfConfig, pfConfig.current_user);
1860
- delete pfConfig.users[pfConfig.current_user];
1850
+ await deleteUserTokens(pfConfig, currentUser);
1851
+ delete pfConfig.users[currentUser];
1861
1852
  pfConfig.current_user = null;
1862
1853
  writePlatformConfig(pfConfig);
1863
1854
  logger.success("Successfully logged out from Tailor Platform.");
@@ -2048,12 +2039,15 @@ const listCommand$4 = defineAppCommand({
2048
2039
  if (jsonOutput) logger.out([]);
2049
2040
  return;
2050
2041
  }
2051
- const profileInfos = profiles.map(([name, profile]) => ({
2052
- name,
2053
- user: profile.user,
2054
- workspaceId: profile.workspace_id,
2055
- permission: profile.readonly === true ? "read" : "write"
2056
- }));
2042
+ const profileInfos = profiles.map(([name, profile]) => {
2043
+ const p = assertDefined(profile, `profile entry "${name}" is undefined`);
2044
+ return {
2045
+ name,
2046
+ user: p.user,
2047
+ workspaceId: p.workspace_id,
2048
+ permission: p.readonly === true ? "read" : "write"
2049
+ };
2050
+ });
2057
2051
  logger.out(profileInfos);
2058
2052
  }
2059
2053
  });
@@ -2080,9 +2074,9 @@ const updateCommand$1 = defineAppCommand({
2080
2074
  }).strict(),
2081
2075
  run: async (args) => {
2082
2076
  const config = await readPlatformConfig();
2083
- if (!config.profiles[args.name]) throw new Error(`Profile "${args.name}" not found.`);
2084
- if (!args.user && !args["workspace-id"] && args.permission === void 0) throw new Error("Please provide at least one property to update.");
2085
2077
  const profile = config.profiles[args.name];
2078
+ if (!profile) throw new Error(`Profile "${args.name}" not found.`);
2079
+ if (!args.user && !args["workspace-id"] && args.permission === void 0) throw new Error("Please provide at least one property to update.");
2086
2080
  const oldUser = profile.user;
2087
2081
  const newUser = args.user || oldUser;
2088
2082
  const oldWorkspaceId = profile.workspace_id;
@@ -2222,10 +2216,7 @@ const createSecretCommand = defineAppCommand({
2222
2216
  }).strict(),
2223
2217
  run: async (args) => {
2224
2218
  await assertWritable({ profile: args.profile });
2225
- const client = await initOperatorClient(await loadAccessToken({
2226
- useProfile: true,
2227
- profile: args.profile
2228
- }));
2219
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
2229
2220
  const workspaceId = await loadWorkspaceId({
2230
2221
  workspaceId: args["workspace-id"],
2231
2222
  profile: args.profile
@@ -2275,10 +2266,7 @@ const deleteSecretCommand = defineAppCommand({
2275
2266
  }).strict(),
2276
2267
  run: async (args) => {
2277
2268
  await assertWritable({ profile: args.profile });
2278
- const client = await initOperatorClient(await loadAccessToken({
2279
- useProfile: true,
2280
- profile: args.profile
2281
- }));
2269
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
2282
2270
  const workspaceId = await loadWorkspaceId({
2283
2271
  workspaceId: args["workspace-id"],
2284
2272
  profile: args.profile
@@ -2327,10 +2315,7 @@ function secretInfo(secret) {
2327
2315
  * @returns List of secrets
2328
2316
  */
2329
2317
  async function secretList(options) {
2330
- const client = await initOperatorClient(await loadAccessToken({
2331
- useProfile: true,
2332
- profile: options.profile
2333
- }));
2318
+ const client = await initOperatorClient(await loadAccessToken({ profile: options.profile }));
2334
2319
  const workspaceId = await loadWorkspaceId({
2335
2320
  workspaceId: options.workspaceId,
2336
2321
  profile: options.profile
@@ -2384,10 +2369,7 @@ const updateSecretCommand = defineAppCommand({
2384
2369
  }).strict(),
2385
2370
  run: async (args) => {
2386
2371
  await assertWritable({ profile: args.profile });
2387
- const client = await initOperatorClient(await loadAccessToken({
2388
- useProfile: true,
2389
- profile: args.profile
2390
- }));
2372
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
2391
2373
  const workspaceId = await loadWorkspaceId({
2392
2374
  workspaceId: args["workspace-id"],
2393
2375
  profile: args.profile
@@ -2440,10 +2422,7 @@ const createCommand$1 = defineAppCommand({
2440
2422
  }).strict(),
2441
2423
  run: async (args) => {
2442
2424
  await assertWritable({ profile: args.profile });
2443
- const client = await initOperatorClient(await loadAccessToken({
2444
- useProfile: true,
2445
- profile: args.profile
2446
- }));
2425
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
2447
2426
  const workspaceId = await loadWorkspaceId({
2448
2427
  workspaceId: args["workspace-id"],
2449
2428
  profile: args.profile
@@ -2473,10 +2452,7 @@ const deleteCommand$1 = defineAppCommand({
2473
2452
  }).strict(),
2474
2453
  run: async (args) => {
2475
2454
  await assertWritable({ profile: args.profile });
2476
- const client = await initOperatorClient(await loadAccessToken({
2477
- useProfile: true,
2478
- profile: args.profile
2479
- }));
2455
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
2480
2456
  const workspaceId = await loadWorkspaceId({
2481
2457
  workspaceId: args["workspace-id"],
2482
2458
  profile: args.profile
@@ -2521,10 +2497,7 @@ function vaultInfo(vault) {
2521
2497
  * @returns List of vaults
2522
2498
  */
2523
2499
  async function vaultList(options) {
2524
- const client = await initOperatorClient(await loadAccessToken({
2525
- useProfile: true,
2526
- profile: options?.profile
2527
- }));
2500
+ const client = await initOperatorClient(await loadAccessToken({ profile: options?.profile }));
2528
2501
  const workspaceId = await loadWorkspaceId({
2529
2502
  workspaceId: options?.workspaceId,
2530
2503
  profile: options?.profile
@@ -3044,10 +3017,7 @@ const deployCommand = defineAppCommand({
3044
3017
  run: async (args) => {
3045
3018
  await assertWritable({ profile: args.profile });
3046
3019
  logger.info(`Deploying static website "${args.name}" from directory: ${args.dir}`);
3047
- const client = await initOperatorClient(await loadAccessToken({
3048
- useProfile: true,
3049
- profile: args.profile
3050
- }));
3020
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
3051
3021
  const name = args.name;
3052
3022
  const dir = path.resolve(process.cwd(), args.dir);
3053
3023
  const workspaceId = await loadWorkspaceId({
@@ -3093,10 +3063,7 @@ const domainGetCommand = defineAppCommand({
3093
3063
  })
3094
3064
  }).strict(),
3095
3065
  run: async (args) => {
3096
- const client = await initOperatorClient(await loadAccessToken({
3097
- useProfile: true,
3098
- profile: args.profile
3099
- }));
3066
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
3100
3067
  const workspaceId = await loadWorkspaceId({
3101
3068
  workspaceId: args["workspace-id"],
3102
3069
  profile: args.profile
@@ -3136,10 +3103,7 @@ const domainListCommand = defineAppCommand({
3136
3103
  })
3137
3104
  }).strict(),
3138
3105
  run: async (args) => {
3139
- const client = await initOperatorClient(await loadAccessToken({
3140
- useProfile: true,
3141
- profile: args.profile
3142
- }));
3106
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
3143
3107
  const workspaceId = await loadWorkspaceId({
3144
3108
  workspaceId: args["workspace-id"],
3145
3109
  profile: args.profile
@@ -3191,10 +3155,7 @@ const getCommand = defineAppCommand({
3191
3155
  })
3192
3156
  }).strict(),
3193
3157
  run: async (args) => {
3194
- const client = await initOperatorClient(await loadAccessToken({
3195
- useProfile: true,
3196
- profile: args.profile
3197
- }));
3158
+ const client = await initOperatorClient(await loadAccessToken({ profile: args.profile }));
3198
3159
  const workspaceId = await loadWorkspaceId({
3199
3160
  workspaceId: args["workspace-id"],
3200
3161
  profile: args.profile
@@ -3229,10 +3190,7 @@ const getCommand = defineAppCommand({
3229
3190
  * @returns List of static websites
3230
3191
  */
3231
3192
  async function listStaticWebsites(options) {
3232
- const client = await initOperatorClient(await loadAccessToken({
3233
- useProfile: true,
3234
- profile: options?.profile
3235
- }));
3193
+ const client = await initOperatorClient(await loadAccessToken({ profile: options?.profile }));
3236
3194
  const workspaceId = await loadWorkspaceId({
3237
3195
  workspaceId: options?.workspaceId,
3238
3196
  profile: options?.profile
@@ -3250,7 +3208,7 @@ async function listStaticWebsites(options) {
3250
3208
  workspaceId,
3251
3209
  name: site.name,
3252
3210
  description: site.description,
3253
- url: site.url ?? "",
3211
+ url: site.url,
3254
3212
  allowedIpAddresses: site.allowedIpAddresses
3255
3213
  }));
3256
3214
  }
@@ -3551,10 +3509,7 @@ function initErdCommand() {
3551
3509
  async function initErdDeployContext(args) {
3552
3510
  initErdCommand();
3553
3511
  return {
3554
- client: await initOperatorClient(await loadAccessToken({
3555
- useProfile: true,
3556
- profile: args.profile
3557
- })),
3512
+ client: await initOperatorClient(await loadAccessToken({ profile: args.profile })),
3558
3513
  workspaceId: await loadWorkspaceId({
3559
3514
  workspaceId: args.workspaceId,
3560
3515
  profile: args.profile
@@ -4153,18 +4108,13 @@ const erdCommand = defineCommand({
4153
4108
  */
4154
4109
  async function script(options) {
4155
4110
  logBetaWarning("tailordb migration");
4156
- let migrationNumber;
4157
- if (isValidMigrationNumber(options.number)) migrationNumber = parseInt(options.number, 10);
4158
- else if (/^[1-9]\d*$/.test(options.number)) {
4159
- migrationNumber = parseInt(options.number, 10);
4160
- if (migrationNumber > 9999) throw new Error(`Migration number ${options.number} is out of range. Expected 1-9999.`);
4161
- } else throw new Error(`Invalid migration number format: ${options.number}. Expected 4-digit format (e.g., 0001) or integer 1-9999 (e.g., 1).`);
4111
+ const migrationNumber = parseMigrationNumberArg(options.number);
4162
4112
  if (migrationNumber === 0) throw new Error(`Migration ${options.number} is the initial schema snapshot and cannot have a migration script.`);
4163
4113
  const { config } = await loadConfig(options.configPath);
4164
4114
  const namespacesWithMigrations = getNamespacesWithMigrations(config, path.dirname(config.path));
4165
4115
  if (namespacesWithMigrations.length === 0) throw new Error("No TailorDB services with migrations configuration found");
4166
4116
  const targetNamespace = resolveTargetNamespace(namespacesWithMigrations, options.namespace);
4167
- const { migrationsDir } = namespacesWithMigrations.find((ns) => ns.namespace === targetNamespace);
4117
+ const { migrationsDir } = assertDefined(namespacesWithMigrations.find((ns) => ns.namespace === targetNamespace), "namespace with migrations not found");
4168
4118
  const diffPath = getMigrationFilePath(migrationsDir, migrationNumber, "diff");
4169
4119
  if (!fs$1.existsSync(diffPath)) throw new Error(`Migration ${options.number} not found in ${migrationsDir}. Expected ${diffPath}.`);
4170
4120
  const migratePath = getMigrationFilePath(migrationsDir, migrationNumber, "migrate");
@@ -4196,7 +4146,10 @@ function resolveTargetNamespace(namespacesWithMigrations, requested) {
4196
4146
  if (!namespacesWithMigrations.some((ns) => ns.namespace === requested)) throw new Error(`Namespace "${requested}" not found or does not have migrations configured`);
4197
4147
  return requested;
4198
4148
  }
4199
- if (namespacesWithMigrations.length === 1) return namespacesWithMigrations[0].namespace;
4149
+ if (namespacesWithMigrations.length === 1) {
4150
+ const [ns] = namespacesWithMigrations;
4151
+ return assertDefined(ns, "namespace with migrations missing").namespace;
4152
+ }
4200
4153
  throw new Error(`Multiple TailorDB services found. Please specify namespace with --namespace flag: ${namespacesWithMigrations.map((ns) => ns.namespace).join(", ")}`);
4201
4154
  }
4202
4155
  const scriptCommand = defineAppCommand({
@@ -4244,12 +4197,11 @@ async function set(options) {
4244
4197
  if (options.namespace) {
4245
4198
  if (!namespacesWithMigrations.some((ns) => ns.namespace === options.namespace)) throw new Error(`Namespace "${options.namespace}" not found or does not have migrations configured`);
4246
4199
  targetNamespace = options.namespace;
4247
- } else if (namespacesWithMigrations.length === 1) targetNamespace = namespacesWithMigrations[0].namespace;
4248
- else throw new Error(`Multiple TailorDB services found. Please specify namespace with --namespace flag: ${namespacesWithMigrations.map((ns) => ns.namespace).join(", ")}`);
4249
- const client = await initOperatorClient(await loadAccessToken({
4250
- useProfile: false,
4251
- profile: options.profile
4252
- }));
4200
+ } else if (namespacesWithMigrations.length === 1) {
4201
+ const [ns] = namespacesWithMigrations;
4202
+ targetNamespace = assertDefined(ns, "namespace with migrations missing").namespace;
4203
+ } else throw new Error(`Multiple TailorDB services found. Please specify namespace with --namespace flag: ${namespacesWithMigrations.map((ns) => ns.namespace).join(", ")}`);
4204
+ const client = await initOperatorClient(await loadAccessToken({ profile: options.profile }));
4253
4205
  const trn = resourceTrn(await loadWorkspaceId({
4254
4206
  workspaceId: options.workspaceId,
4255
4207
  profile: options.profile
@@ -4257,7 +4209,7 @@ async function set(options) {
4257
4209
  let currentMigration;
4258
4210
  try {
4259
4211
  const { metadata } = await client.getMetadata({ trn });
4260
- const label = metadata?.labels?.["sdk-migration"];
4212
+ const label = metadata?.labels["sdk-migration"];
4261
4213
  currentMigration = label ? parseMigrationLabelNumber(label) ?? 0 : 0;
4262
4214
  } catch {
4263
4215
  currentMigration = 0;
@@ -4332,10 +4284,7 @@ async function collectMigrationStatuses(options) {
4332
4284
  if (namespacesWithMigrations.length === 0) throw new Error("No TailorDB services with migrations configuration found");
4333
4285
  const targetNamespaces = options.namespace ? namespacesWithMigrations.filter((ns) => ns.namespace === options.namespace) : namespacesWithMigrations;
4334
4286
  if (targetNamespaces.length === 0) throw new Error(`Namespace "${options.namespace}" not found or does not have migrations configured`);
4335
- const client = await initOperatorClient(await loadAccessToken({
4336
- useProfile: false,
4337
- profile: options.profile
4338
- }));
4287
+ const client = await initOperatorClient(await loadAccessToken({ profile: options.profile }));
4339
4288
  const workspaceId = await loadWorkspaceId({
4340
4289
  workspaceId: options.workspaceId,
4341
4290
  profile: options.profile
@@ -4346,7 +4295,7 @@ async function collectMigrationStatuses(options) {
4346
4295
  let currentMigration;
4347
4296
  try {
4348
4297
  const { metadata } = await client.getMetadata({ trn });
4349
- const label = metadata?.labels?.["sdk-migration"];
4298
+ const label = metadata?.labels["sdk-migration"];
4350
4299
  currentMigration = label ? parseMigrationLabelNumber(label) ?? 0 : 0;
4351
4300
  } catch {
4352
4301
  currentMigration = 0;
@@ -4419,6 +4368,295 @@ const statusCommand = defineAppCommand({
4419
4368
  }
4420
4369
  });
4421
4370
 
4371
+ //#endregion
4372
+ //#region src/cli/commands/tailordb/migrate/sync.ts
4373
+ async function fetchRemoteGqlPermissions(client, workspaceId, namespace) {
4374
+ return fetchAll(async (pageToken, maxPageSize) => {
4375
+ try {
4376
+ const { permissions, nextPageToken } = await client.listTailorDBGQLPermissions({
4377
+ workspaceId,
4378
+ namespaceName: namespace,
4379
+ pageToken,
4380
+ pageSize: maxPageSize
4381
+ });
4382
+ return [permissions, nextPageToken];
4383
+ } catch (error) {
4384
+ if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
4385
+ throw error;
4386
+ }
4387
+ });
4388
+ }
4389
+ async function fetchRemoteTypes(client, workspaceId, namespace) {
4390
+ return fetchAll(async (pageToken, maxPageSize) => {
4391
+ try {
4392
+ const { tailordbTypes, nextPageToken } = await client.listTailorDBTypes({
4393
+ workspaceId,
4394
+ namespaceName: namespace,
4395
+ pageToken,
4396
+ pageSize: maxPageSize
4397
+ });
4398
+ return [tailordbTypes, nextPageToken];
4399
+ } catch (error) {
4400
+ if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Cannot sync: TailorDB namespace "${namespace}" has not been deployed yet.`, { cause: error });
4401
+ throw error;
4402
+ }
4403
+ });
4404
+ }
4405
+ /**
4406
+ * Verify that replaying the full migration history reproduces the current
4407
+ * local type definitions, before anything is sent to the remote.
4408
+ *
4409
+ * Sync force-applies a snapshot reconstructed from the migration history, so
4410
+ * the history itself must be trustworthy. When the reconstruction at the
4411
+ * latest migration does not match the schema defined in the local type files,
4412
+ * either the migration files were edited incorrectly or a schema change has
4413
+ * not been recorded as a migration yet — and overwriting the remote with an
4414
+ * unverified snapshot could destroy data. Fails before any RPC is issued.
4415
+ *
4416
+ * Returns the manifest generation options deploy would use for this
4417
+ * namespace (executor-driven publishRecordEvents and namespace
4418
+ * gqlOperations), so the synced manifests match what deploy produces.
4419
+ * @param loaded - Result of `loadConfig` (config and plugins)
4420
+ * @param target - Namespace whose migration history is being synced
4421
+ * @returns Options for `generateAllTypeManifestsFromSnapshot`
4422
+ */
4423
+ async function assertMigrationsReproduceLocalTypes(loaded, target) {
4424
+ const { config, plugins } = loaded;
4425
+ const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
4426
+ const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-DSXntqnV.mjs");
4427
+ const application = defineApplication({
4428
+ config,
4429
+ pluginManager
4430
+ });
4431
+ const tailordbService = application.tailorDBServices.find((s) => s.namespace === target.namespace);
4432
+ if (!tailordbService) throw new Error(`No TailorDB service found for namespace "${target.namespace}"`);
4433
+ for (const service of application.tailorDBServices) {
4434
+ await service.loadTypes();
4435
+ await service.processNamespacePlugins();
4436
+ }
4437
+ const pluginExecutorFiles = generatePluginFilesIfNeeded(pluginManager, application.tailorDBServices, config.path);
4438
+ const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? (await import("../service-BHQIerYh.mjs")).createExecutorService({ config: { files: [] } }) : void 0);
4439
+ await executorService?.loadExecutors();
4440
+ if (pluginExecutorFiles.length > 0) await executorService?.loadPluginExecutorFiles([...pluginExecutorFiles]);
4441
+ const executorUsedTypes = /* @__PURE__ */ new Set();
4442
+ for (const executor of Object.values(executorService?.executors ?? {})) if (executor.trigger.kind === "tailordb") executorUsedTypes.add(executor.trigger.typeName);
4443
+ const manifestOptions = {
4444
+ executorUsedTypes,
4445
+ namespaceGqlOperations: tailordbService.config.gqlOperations
4446
+ };
4447
+ const latestSnapshot = reconstructSnapshotFromMigrations(target.migrationsDir);
4448
+ if (!latestSnapshot) return manifestOptions;
4449
+ const diff = compareLocalTypesWithSnapshot(latestSnapshot, createSnapshotFromLocalTypes(tailordbService.types, target.namespace).types, target.namespace);
4450
+ if (!hasChanges(diff)) return manifestOptions;
4451
+ logger.error(`Migration history does not reproduce the current local schema for namespace ${styles.bold(target.namespace)}:`);
4452
+ logger.log(formatMigrationDiff(diff));
4453
+ logger.newline();
4454
+ logger.info("This usually means one of the following:");
4455
+ logger.info(" - Migration files were edited and replaying them no longer matches the type definitions — fix the migration files.", { mode: "plain" });
4456
+ logger.info(" - Type definitions changed without a new migration — run 'tailor-sdk tailordb migration generate' first.", { mode: "plain" });
4457
+ logger.newline();
4458
+ throw new Error("Refusing to sync: the migration history must reproduce the current local schema before it can be applied to the remote.");
4459
+ }
4460
+ /**
4461
+ * Fetch the namespace's metadata labels and current migration number.
4462
+ *
4463
+ * Only GetMetadata NotFound is treated as "metadata does not exist yet".
4464
+ * Any other failure aborts the sync (which has not mutated anything at this
4465
+ * point): the fetched labels are written back verbatim at the end, so
4466
+ * proceeding with empty labels after a transient error would wipe the
4467
+ * namespace's existing metadata.
4468
+ * @param client - Operator client
4469
+ * @param trn - Namespace TRN
4470
+ * @returns Existing labels and the parsed current migration number
4471
+ */
4472
+ async function fetchRemoteMigrationState(client, trn) {
4473
+ try {
4474
+ const { metadata } = await client.getMetadata({ trn });
4475
+ const labels = metadata?.labels ?? {};
4476
+ const label = labels[MIGRATION_LABEL_KEY];
4477
+ return {
4478
+ labels,
4479
+ current: label ? parseMigrationLabelNumber(label) : null
4480
+ };
4481
+ } catch (error) {
4482
+ if (error instanceof ConnectError && error.code === Code.NotFound) return {
4483
+ labels: {},
4484
+ current: null
4485
+ };
4486
+ throw error;
4487
+ }
4488
+ }
4489
+ function selectTargetNamespace(namespacesWithMigrations, requested) {
4490
+ if (namespacesWithMigrations.length === 0) throw new Error("No TailorDB services with migrations configuration found");
4491
+ if (requested) {
4492
+ const found = namespacesWithMigrations.find((ns) => ns.namespace === requested);
4493
+ if (!found) throw new Error(`Namespace "${requested}" not found or does not have migrations configured`);
4494
+ return found;
4495
+ }
4496
+ if (namespacesWithMigrations.length > 1) throw new Error(`Multiple TailorDB services found. Please specify namespace with --namespace flag: ${namespacesWithMigrations.map((ns) => ns.namespace).join(", ")}`);
4497
+ return assertDefined(namespacesWithMigrations[0], "namespace with migrations missing");
4498
+ }
4499
+ /**
4500
+ * Sync remote TailorDB schema to a specific migration snapshot.
4501
+ *
4502
+ * Reconstructs the schema state at `<number>` from `0000/schema.json` + diffs,
4503
+ * then issues create/update/delete RPCs so the remote matches that snapshot.
4504
+ * Updates the migration label to `<number>` on success. Before any remote
4505
+ * mutation, verifies that the migration history reproduces the current local
4506
+ * type definitions (see {@link assertMigrationsReproduceLocalTypes}).
4507
+ *
4508
+ * Intended for recovering from drift introduced by `deploy --no-schema-check`
4509
+ * runs against an older revision: instead of having to `git checkout` that
4510
+ * revision and re-deploy, the operator can sync the remote back to a known
4511
+ * snapshot version directly.
4512
+ * @param options - Command options
4513
+ */
4514
+ async function sync(options) {
4515
+ logBetaWarning("tailordb migration");
4516
+ const targetVersion = parseMigrationNumberArg(options.number);
4517
+ const loaded = await loadConfig(options.configPath);
4518
+ const { config } = loaded;
4519
+ const target = selectTargetNamespace(getNamespacesWithMigrations(config, path.dirname(config.path)), options.namespace);
4520
+ assertValidMigrationFiles(target.migrationsDir, target.namespace);
4521
+ const latest = getLatestMigrationNumber(target.migrationsDir);
4522
+ if (targetVersion > latest) throw new Error(`Migration ${formatMigrationNumber(targetVersion)} does not exist in working tree (latest is ${formatMigrationNumber(latest)}).`);
4523
+ const snapshot = reconstructSnapshotFromMigrations(target.migrationsDir, targetVersion);
4524
+ if (!snapshot) throw new Error(`No initial schema snapshot found in ${target.migrationsDir}. Expected 0000/schema.json.`);
4525
+ const manifestOptions = await assertMigrationsReproduceLocalTypes(loaded, target);
4526
+ const client = await initOperatorClient(await loadAccessToken({ profile: options.profile }));
4527
+ const workspaceId = await loadWorkspaceId({
4528
+ workspaceId: options.workspaceId,
4529
+ profile: options.profile
4530
+ });
4531
+ const trn = resourceTrn(workspaceId, "tailordb", target.namespace);
4532
+ const remoteState = await fetchRemoteMigrationState(client, trn);
4533
+ const remoteTypes = await fetchRemoteTypes(client, workspaceId, target.namespace);
4534
+ const { creates, updates, deletes } = compareSnapshotWithRemote(snapshot, new Set(remoteTypes.map((t) => t.name)));
4535
+ const remoteGqlPermissions = await fetchRemoteGqlPermissions(client, workspaceId, target.namespace);
4536
+ const remoteGqlPermissionTypes = new Set(remoteGqlPermissions.map((p) => p.typeName));
4537
+ const desiredGqlPermissions = Object.entries(snapshot.types).flatMap(([typeName, snapshotType]) => snapshotType.permissions?.gql ? [{
4538
+ typeName,
4539
+ permission: protoGqlPermission(snapshotType.permissions.gql)
4540
+ }] : []);
4541
+ const desiredGqlPermissionTypes = new Set(desiredGqlPermissions.map((p) => p.typeName));
4542
+ const gqlPermissionDeletes = remoteGqlPermissions.filter((p) => !desiredGqlPermissionTypes.has(p.typeName));
4543
+ const current = remoteState.current;
4544
+ logger.newline();
4545
+ logger.info(`Namespace: ${styles.bold(target.namespace)}`);
4546
+ logger.log(` Current migration: ${current === null ? "<unset>" : styles.bold(formatMigrationNumber(current))}`);
4547
+ logger.log(` Target migration: ${styles.bold(formatMigrationNumber(targetVersion))}`);
4548
+ logger.log(` Types to create: ${styles.bold(String(creates.length))}`);
4549
+ logger.log(` Types to update: ${styles.bold(String(updates.length))}`);
4550
+ logger.log(` Types to delete: ${styles.bold(String(deletes.length))}`);
4551
+ logger.log(` GQL permissions to set: ${styles.bold(String(desiredGqlPermissions.length))}`);
4552
+ logger.log(` GQL permissions to delete: ${styles.bold(String(gqlPermissionDeletes.length))}`);
4553
+ logger.newline();
4554
+ if (creates.length + updates.length + deletes.length + desiredGqlPermissions.length + gqlPermissionDeletes.length === 0) logger.info("No types to apply; only the migration label will be updated.");
4555
+ else {
4556
+ logger.warn("This operation will overwrite remote TailorDB types to match the selected snapshot.");
4557
+ if (deletes.length > 0) logger.warn("Existing data in deleted types will be lost.");
4558
+ logger.newline();
4559
+ }
4560
+ logger.warn("Sync never runs migrate.ts scripts; it only applies the schema snapshot and moves the migration label.");
4561
+ logger.newline();
4562
+ if (current !== null && targetVersion < current) {
4563
+ logger.warn(`Migrations ${formatMigrationNumber(targetVersion + 1)}–${formatMigrationNumber(current)} will become pending again and re-execute on the next deploy, including their migrate.ts scripts. Make sure those scripts are idempotent (safe to re-run).`);
4564
+ logger.newline();
4565
+ } else if (current !== null && targetVersion > current) {
4566
+ logger.warn(`Moving the migration label forwards (${formatMigrationNumber(current)} → ${formatMigrationNumber(targetVersion)}): migrate.ts scripts for migrations ${formatMigrationNumber(current + 1)}–${formatMigrationNumber(targetVersion)} will not run on the next deploy.`);
4567
+ logger.newline();
4568
+ }
4569
+ if (!options.yes) {
4570
+ if (!await prompt.confirm({
4571
+ message: `Continue and set migration label to ${formatMigrationNumber(targetVersion)}?`,
4572
+ default: false
4573
+ })) {
4574
+ logger.info("Operation cancelled.");
4575
+ return;
4576
+ }
4577
+ logger.newline();
4578
+ }
4579
+ const manifests = generateAllTypeManifestsFromSnapshot(snapshot, manifestOptions);
4580
+ const manifestFor = (typeName) => {
4581
+ const manifest = manifests.get(typeName);
4582
+ if (!manifest) throw new Error(`Internal error: no manifest generated for type "${typeName}". No changes were applied.`);
4583
+ return manifest;
4584
+ };
4585
+ const createManifests = creates.map((typeName) => manifestFor(typeName));
4586
+ const updateManifests = updates.map((typeName) => manifestFor(typeName));
4587
+ try {
4588
+ await Promise.all([...createManifests.map((tailordbType) => client.createTailorDBType({
4589
+ workspaceId,
4590
+ namespaceName: target.namespace,
4591
+ tailordbType
4592
+ })), ...updateManifests.map((tailordbType) => client.updateTailorDBType({
4593
+ workspaceId,
4594
+ namespaceName: target.namespace,
4595
+ tailordbType
4596
+ }))]);
4597
+ } catch (error) {
4598
+ handleOptionalToRequiredError(error, ["The target snapshot marks a field as required, but existing remote records have no value for it.", "Populate those records first (e.g. with a migration script applied via 'tailor-sdk deploy'), then re-run the sync."]);
4599
+ }
4600
+ await Promise.all(desiredGqlPermissions.map(({ typeName, permission }) => {
4601
+ const request = {
4602
+ workspaceId,
4603
+ namespaceName: target.namespace,
4604
+ typeName,
4605
+ permission
4606
+ };
4607
+ return remoteGqlPermissionTypes.has(typeName) ? client.updateTailorDBGQLPermission(request) : client.createTailorDBGQLPermission(request);
4608
+ }));
4609
+ await Promise.all(gqlPermissionDeletes.map((p) => client.deleteTailorDBGQLPermission({
4610
+ workspaceId,
4611
+ namespaceName: target.namespace,
4612
+ typeName: p.typeName
4613
+ })));
4614
+ await Promise.all(deletes.map((typeName) => client.deleteTailorDBType({
4615
+ workspaceId,
4616
+ namespaceName: target.namespace,
4617
+ tailordbTypeName: typeName
4618
+ })));
4619
+ await client.setMetadata({
4620
+ trn,
4621
+ labels: {
4622
+ ...remoteState.labels,
4623
+ [MIGRATION_LABEL_KEY]: `${"m"}${formatMigrationNumber(targetVersion)}`
4624
+ }
4625
+ });
4626
+ logger.success(`Synced namespace ${styles.bold(target.namespace)} to migration ${styles.bold(formatMigrationNumber(targetVersion))}.`);
4627
+ if (targetVersion < latest) {
4628
+ logger.newline();
4629
+ logger.info(`Run 'tailor-sdk deploy' to apply migrations ${formatMigrationNumber(targetVersion + 1)}–${formatMigrationNumber(latest)} from the working tree.`);
4630
+ }
4631
+ }
4632
+ const syncCommand = defineAppCommand({
4633
+ name: "sync",
4634
+ description: "Sync remote TailorDB schema to a specific migration snapshot (recovery from --no-schema-check drift).",
4635
+ args: z.object({
4636
+ ...deploymentArgs,
4637
+ ...confirmationArgs,
4638
+ number: arg(z.string(), {
4639
+ positional: true,
4640
+ description: "Migration number to sync to (e.g., 0001 or 1; 0 targets the baseline snapshot)"
4641
+ }),
4642
+ namespace: arg(z.string().optional(), {
4643
+ alias: "n",
4644
+ description: "Target TailorDB namespace (required if multiple namespaces exist)"
4645
+ })
4646
+ }).strict(),
4647
+ run: async (args) => {
4648
+ await assertWritable({ profile: args.profile });
4649
+ await sync({
4650
+ configPath: args.config,
4651
+ number: args.number,
4652
+ namespace: args.namespace,
4653
+ yes: args.yes,
4654
+ workspaceId: args["workspace-id"],
4655
+ profile: args.profile
4656
+ });
4657
+ }
4658
+ });
4659
+
4422
4660
  //#endregion
4423
4661
  //#region src/cli/commands/tailordb/migrate/index.ts
4424
4662
  /**
@@ -4429,6 +4667,7 @@ const statusCommand = defineAppCommand({
4429
4667
  * - script: Add a migrate.ts template to an existing migration
4430
4668
  * - set: Set migration checkpoint to a specific number
4431
4669
  * - status: Show migration status for TailorDB namespaces
4670
+ * - sync: Sync remote TailorDB schema to a specific migration snapshot
4432
4671
  */
4433
4672
  const migrationCommand = defineCommand({
4434
4673
  name: "migration",
@@ -4437,7 +4676,8 @@ const migrationCommand = defineCommand({
4437
4676
  generate: generateCommand$1,
4438
4677
  script: scriptCommand,
4439
4678
  set: setCommand,
4440
- status: statusCommand
4679
+ status: statusCommand,
4680
+ sync: syncCommand
4441
4681
  }
4442
4682
  });
4443
4683
 
@@ -4472,7 +4712,7 @@ const upgradeCommand = defineAppCommand({
4472
4712
  run: async (args) => {
4473
4713
  const { initTelemetry } = await import("../telemetry-w92bvGdC.mjs");
4474
4714
  await initTelemetry();
4475
- const { upgrade } = await import("../service-aPT0fx3y.mjs");
4715
+ const { upgrade } = await import("../service-DMohAx8a2.mjs");
4476
4716
  await upgrade({
4477
4717
  from: args.from,
4478
4718
  dryRun: args["dry-run"],
@@ -4898,7 +5138,7 @@ runMain(mainCommand, {
4898
5138
  if (isVerbose() && error.stack) logger.debug(`\nStack trace:\n${error.stack}`);
4899
5139
  } else logger.error(`Unknown error: ${error}`);
4900
5140
  if (!isCLIError(error) && (!(error instanceof Error) || error instanceof TypeError || error instanceof RangeError)) {
4901
- const { reportCrash } = await import("../crashreport-lnVTnbB5.mjs");
5141
+ const { reportCrash } = await import("../crashreport-D1wKBJ8N.mjs");
4902
5142
  await reportCrash(error, "handledError");
4903
5143
  }
4904
5144
  }