@tailor-platform/sdk 1.84.0 → 1.85.1

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 (31) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/application-C-zsrNjn.mjs +3 -0
  3. package/dist/{application-CnCljDwk.mjs → application-m5tYtz0-.mjs} +235 -62
  4. package/dist/application-m5tYtz0-.mjs.map +1 -0
  5. package/dist/cli/commands/deploy/tailordb/index.d.mts +1 -0
  6. package/dist/cli/commands/generate/seed/bundler.d.mts +2 -1
  7. package/dist/cli/commands/tailordb/migrate/schema-checks.d.mts +5 -0
  8. package/dist/cli/index.mjs +229 -22
  9. package/dist/cli/index.mjs.map +1 -1
  10. package/dist/cli/lib.mjs +92 -36
  11. package/dist/cli/lib.mjs.map +1 -1
  12. package/dist/completion/zsh-worker.zsh +52 -2
  13. package/dist/node-builtins-CmaL2Cbq.mjs +36 -0
  14. package/dist/node-builtins-CmaL2Cbq.mjs.map +1 -0
  15. package/dist/plugin/builtin/seed/index.mjs +1 -1
  16. package/dist/{register-typescript-runtime-BCvWR3HC.mjs → register-typescript-runtime-nG-n9lPw.mjs} +336 -268
  17. package/dist/register-typescript-runtime-nG-n9lPw.mjs.map +1 -0
  18. package/dist/rolldown-runtime-DXywRVcq.mjs +0 -1
  19. package/dist/{seed-DFFigL8V.mjs → seed-6eTFj-3Q.mjs} +158 -41
  20. package/dist/seed-6eTFj-3Q.mjs.map +1 -0
  21. package/dist/vitest/index.mjs +3 -35
  22. package/dist/vitest/index.mjs.map +1 -1
  23. package/docs/cli/tailordb.md +29 -7
  24. package/docs/cli-reference.md +16 -15
  25. package/docs/generator/builtin.md +7 -0
  26. package/docs/services/tailordb-migration.md +11 -2
  27. package/package.json +7 -8
  28. package/dist/application-5FOWIxFQ.mjs +0 -3
  29. package/dist/application-CnCljDwk.mjs.map +0 -1
  30. package/dist/register-typescript-runtime-BCvWR3HC.mjs.map +0 -1
  31. package/dist/seed-DFFigL8V.mjs.map +0 -1
@@ -6,4 +6,5 @@ import "../../../shared/client.mjs";
6
6
  import "../types.mjs";
7
7
  import "../../tailordb/migrate/config.mjs";
8
8
  import "../../tailordb/migrate/snapshot.mjs";
9
+ import "../../tailordb/migrate/schema-checks.mjs";
9
10
  import "@bufbuild/protobuf";
@@ -19,8 +19,9 @@ type SeedBundleResult = {
19
19
  * 4. Exports as main() for TestExecScript
20
20
  * @param namespace - TailorDB namespace
21
21
  * @param typeNames - List of type names to include in the seed
22
+ * @param baseDir - Directory whose dependencies and tsconfig the generated entry uses
22
23
  * @returns Bundled seed script result
23
24
  */
24
- declare function bundleSeedScript(namespace: string, typeNames: string[]): Promise<SeedBundleResult>;
25
+ declare function bundleSeedScript(namespace: string, typeNames: string[], baseDir?: string): Promise<SeedBundleResult>;
25
26
  //#endregion
26
27
  export { SeedBundleResult, bundleSeedScript };
@@ -0,0 +1,5 @@
1
+ import "../../../services/tailordb/service.mjs";
2
+ import "../../../shared/config-loader.mjs";
3
+ import "../../../shared/client.mjs";
4
+ import "./config.mjs";
5
+ import "./snapshot.mjs";
@@ -3,8 +3,8 @@ import { r as parseInputFields } from "../schema-CYrle8JZ.mjs";
3
3
  import { At as PATScope, Ct as AuthInvokerSchema, E as CustomDomainStatus, W as FunctionExecution_Type } from "../workspace_resource_pb-DXnO2AT-.mjs";
4
4
  import { t as assertDefined } from "../assert-DBxo8jPo.mjs";
5
5
  import { n as logger, r as styles } from "../logger-CXQq9YIp.mjs";
6
- import { $n as deploymentArgs, $t as handleOptionalToRequiredError, An as generateUserTypes, At as startCommand, Bn as loadConsoleBaseUrl, Ct as listCommand$7, Dn as resourceTrn, Dt as jobsCommand, E as listCommand$13, En as getNamespacesWithMigrations, F as openInConfiguredEditor, Fn as deleteUserTokens, G as treeCommand, Gn as readPlatformConfig, H as updateCommand$3, Hn as loadStoredUserTokens, Ht as listCommand$16, In as fetchLatestToken, J as getCommand$5, Jn as writePlatformConfig, Jt as ensureConfigId, K as listCommand$12, Kn as resolveUserTokenKey, Kt as formatKeyValueTable, L as showCommand, Ln as hasAnyUserTokenEntry, Lt as functionExecutionStatusToString, M as generateMigrationScript, Mn as apiCall, Mt as getCommand$6, N as writeDbTypesFile, Nn as assertWritable, On as sdkNameLabelKey, P as getConfiguredEditorCommand, Pn as loadConfig, Pt as executionsCommand, Q as listCommand$11, Qn as confirmationArgs, Qt as MIGRATION_LABEL_KEY, R as extractOwnedNamespaces, Rn as hasUserTokenEntry, Rt as getCommand$1, S as waitCommand, Sn as parseMigrationNumberArg, Tn as hasChanges, Un as loadWorkspaceId, V as removeCommand$1, Vn as loadMachineUserName, Vt as deployFromCLI, Wn as platformConfigFromProfile, Wt as createCommand$4, X as updateCommand$2, Xn as commonArgs, Yn as defineAppCommand, Yt as executeScript, Zn as configArg, _t as getCommand$2, ar as toPageDirection, bn as reconstructSnapshotFromMigrations, bt as webhookCommand, c as listCommand$15, ct as getCommand$3, dt as tokenCommand, en as parseMigrationLabelNumber, er as isVerbose, et as getCommand$4, f as restoreCommand, fn as createSnapshotFromLocalTypes, ft as listCommand$9, g as deleteCommand$5, gn as getMigrationFiles, hn as getMigrationFilePath, ht as listCommand$8, i as updateCommand$4, ir as resolveMachineUserInputSource, it as createCommand$3, j as generateCommand$1, jn as prompt, k as truncateCommand, kn as PluginManager, ln as assertValidMigrationFiles, m as getCommand$7, mt as generate, nn as generateAllTypeManifestsFromSnapshot, nr as pagedLogArgs, nt as deleteCommand$4, o as removeCommand, on as INITIAL_SCHEMA_NUMBER, or as workspaceArgs, ot as listCommand$10, pn as getLatestMigrationNumber, qn as saveUserTokens, qt as workspaceNameSchema, r as queryCommand, rn as protoGqlPermission, rr as paginationArgs, t as registerTypeScriptRuntime, tn as compareSnapshotWithRemote, tr as multiConfigArg, u as inviteCommand, un as compareLocalTypesWithSnapshot, vn as isValidMigrationNumber, w as resumeCommand, wn as formatMigrationDiff, x as healthCommand, xn as formatMigrationNumber, xt as triggerCommand, y as listCommand$14, yn as loadDiff, z as logBetaWarning, zn as loadAccessToken } from "../register-typescript-runtime-BCvWR3HC.mjs";
7
- import { A as defaultPlatformBaseUrl, D as hashContent$1, E as getDistDir, F as fetchPlatformMachineUserToken, H as initOperatorClient, I as fetchUserInfo, M as fetchAllTolerant, P as fetchPaged, S as resolveBundleLogLevel, U as isDefaultPlatform, V as initOAuth2Client, a as resolveInlineSourcemap, b as composeFunctionTreeshakeOptions, c as ResolverSchema, j as fetchAll, k as closeConnectionPool, p as buildResolverPermissionAndInputCheckExpr, s as WorkflowJobSchema, t as defineApplication, u as INVOKER_EXPR, v as resolveTSConfigWithFallback, x as createLogLevelTreeshakeOptions, y as platformBundleDefinePlugin, z as getOrNull } from "../application-CnCljDwk.mjs";
6
+ import { $n as writePlatformConfig, $t as logRemoteDriftGuidance, An as hasChanges, At as startCommand, Bn as deleteUserTokens, Cn as isValidMigrationNumber, Ct as listCommand$7, Dn as parseMigrationNumberArg, Dt as jobsCommand, E as listCommand$13, En as formatMigrationNumber, F as openInConfiguredEditor, Fn as generateUserTypes, G as treeCommand, Gn as loadConsoleBaseUrl, H as updateCommand$3, Hn as hasAnyUserTokenEntry, Ht as listCommand$16, In as prompt, J as getCommand$5, Jn as loadWorkspaceId, Jt as ensureConfigId, K as listCommand$12, Kn as loadMachineUserName, Kt as formatKeyValueTable, L as showCommand, Ln as apiCall, Lt as functionExecutionStatusToString, M as generateMigrationScript, Mn as resourceTrn, Mt as getCommand$6, N as writeDbTypesFile, Nn as sdkNameLabelKey, On as formatDiffSummary, P as getConfiguredEditorCommand, Pn as PluginManager, Pt as executionsCommand, Q as listCommand$11, Qn as saveUserTokens, Qt as checkMigrationDiffs, R as extractOwnedNamespaces, Rn as assertWritable, Rt as getCommand$1, S as waitCommand, Tn as reconstructSnapshotFromMigrations, Un as hasUserTokenEntry, V as removeCommand$1, Vn as fetchLatestToken, Vt as deployFromCLI, Wn as loadAccessToken, Wt as createCommand$4, X as updateCommand$2, Xn as readPlatformConfig, Yn as platformConfigFromProfile, Yt as executeScript, Zn as resolveUserTokenKey, _n as formatSchemaDrifts, _t as getCommand$2, an as compareSnapshotWithRemote, ar as isVerbose, bn as getMigrationFilePath, bt as webhookCommand, c as listCommand$15, cr as paginationArgs, ct as getCommand$3, dr as workspaceArgs, dt as tokenCommand, en as toTailorDBDeployInput, er as defineAppCommand, et as getCommand$4, f as restoreCommand, ft as listCommand$9, g as deleteCommand$5, gn as createSnapshotFromLocalTypes, ht as listCommand$8, i as updateCommand$4, in as parseMigrationLabelNumber, ir as deploymentArgs, it as createCommand$3, j as generateCommand$1, jn as getNamespacesWithMigrations, k as truncateCommand, kn as formatMigrationDiff, lr as resolveMachineUserInputSource, m as getCommand$7, mn as compareLocalTypesWithSnapshot, mt as generate, nn as MIGRATION_LABEL_KEY, nr as configArg, nt as deleteCommand$4, o as removeCommand, on as generateAllTypeManifestsFromSnapshot, or as multiConfigArg, ot as listCommand$10, pn as assertValidMigrationFiles, qn as loadStoredUserTokens, qt as workspaceNameSchema, r as queryCommand, rn as handleOptionalToRequiredError, rr as confirmationArgs, sn as protoGqlPermission, sr as pagedLogArgs, t as registerTypeScriptRuntime, tn as verifyRemoteSchema, tr as commonArgs, u as inviteCommand, un as INITIAL_SCHEMA_NUMBER, ur as toPageDirection, vn as getLatestMigrationNumber, w as resumeCommand, wn as loadDiff, x as healthCommand, xn as getMigrationFiles, xt as triggerCommand, y as listCommand$14, z as logBetaWarning, zn as loadConfig } from "../register-typescript-runtime-nG-n9lPw.mjs";
7
+ import { A as hashContent$1, C as createLogLevelTreeshakeOptions, F as fetchAllTolerant, G as initOperatorClient, H as getOrNull, K as isDefaultPlatform, L as fetchPaged, M as closeConnectionPool, N as defaultPlatformBaseUrl, P as fetchAll, R as fetchPlatformMachineUserToken, S as composeFunctionTreeshakeOptions, T as createBundleLog, W as initOAuth2Client, a as resolveInlineSourcemap, b as resolveTSConfigWithFallback, c as ResolverSchema, k as getDistDir, m as assertUniqueLocalTailorDBTypeNames, p as buildResolverPermissionAndInputCheckExpr, s as WorkflowJobSchema, t as defineApplication, u as INVOKER_EXPR, v as createGeneratedEntryResolverPlugin, w as resolveBundleLogLevel, x as platformBundleDefinePlugin, y as createTsconfigPathsPlugin, z as fetchUserInfo } from "../application-m5tYtz0-.mjs";
8
8
  import { n as ExecutorSchema } from "../service-CPJWhTLn.mjs";
9
9
  import { t as multiline } from "../multiline-sfHpTZZK.mjs";
10
10
  import { t as isPluginGeneratedType } from "../type-source-DH_LH20p.mjs";
@@ -1706,22 +1706,29 @@ async function bundleForTestRun(options) {
1706
1706
  const entryContent = generateEntry(detected, sourceFile, env, machineUser, workspaceId);
1707
1707
  fs$1.writeFileSync(entryPath, entryContent);
1708
1708
  const tsconfig = await resolveTSConfigWithFallback(baseDir);
1709
+ const bundleLog = createBundleLog({ tsconfig });
1710
+ const buildResult = await rolldown.build({
1711
+ plugins: [
1712
+ createGeneratedEntryResolverPlugin(entryPath, baseDir),
1713
+ createTsconfigPathsPlugin(),
1714
+ platformBundleDefinePlugin
1715
+ ],
1716
+ input: entryPath,
1717
+ write: false,
1718
+ output: {
1719
+ format: "esm",
1720
+ sourcemap: inlineSourcemap ? "inline" : true,
1721
+ minify: inlineSourcemap ? { mangle: { keepNames: true } } : true,
1722
+ codeSplitting: false,
1723
+ dir: process.cwd()
1724
+ },
1725
+ tsconfig,
1726
+ treeshake: composeFunctionTreeshakeOptions([createLogLevelTreeshakeOptions(bundleLogLevel)]),
1727
+ ...bundleLog.options
1728
+ });
1729
+ bundleLog.assertAllResolved();
1709
1730
  return {
1710
- bundledCode: (await rolldown.build({
1711
- plugins: [platformBundleDefinePlugin],
1712
- input: entryPath,
1713
- write: false,
1714
- output: {
1715
- format: "esm",
1716
- sourcemap: inlineSourcemap ? "inline" : true,
1717
- minify: inlineSourcemap ? { mangle: { keepNames: true } } : true,
1718
- codeSplitting: false,
1719
- dir: process.cwd()
1720
- },
1721
- tsconfig,
1722
- treeshake: composeFunctionTreeshakeOptions([createLogLevelTreeshakeOptions(bundleLogLevel)]),
1723
- logLevel: "silent"
1724
- })).output[0].code,
1731
+ bundledCode: buildResult.output[0].code,
1725
1732
  scriptName
1726
1733
  };
1727
1734
  }
@@ -6630,7 +6637,7 @@ async function set(options) {
6630
6637
  let currentMigration;
6631
6638
  try {
6632
6639
  const { metadata } = await client.getMetadata({ trn });
6633
- const label = metadata?.labels["sdk-migration"];
6640
+ const label = metadata?.labels[MIGRATION_LABEL_KEY];
6634
6641
  currentMigration = label ? parseMigrationLabelNumber(label) ?? 0 : 0;
6635
6642
  } catch {
6636
6643
  currentMigration = 0;
@@ -6664,7 +6671,7 @@ async function set(options) {
6664
6671
  trn,
6665
6672
  labels: {
6666
6673
  ...existingLabels,
6667
- "sdk-migration": `m${formatMigrationNumber(migrationNumber)}`
6674
+ [MIGRATION_LABEL_KEY]: `m${formatMigrationNumber(migrationNumber)}`
6668
6675
  }
6669
6676
  });
6670
6677
  logger.success(`Migration checkpoint set to ${styles.bold(formatMigrationNumber(migrationNumber))} for namespace ${styles.bold(targetNamespace)}`);
@@ -6716,7 +6723,7 @@ async function collectMigrationStatuses(options) {
6716
6723
  let currentMigration;
6717
6724
  try {
6718
6725
  const { metadata } = await client.getMetadata({ trn });
6719
- const label = metadata?.labels["sdk-migration"];
6726
+ const label = metadata?.labels[MIGRATION_LABEL_KEY];
6720
6727
  currentMigration = label ? parseMigrationLabelNumber(label) ?? 0 : 0;
6721
6728
  } catch {
6722
6729
  currentMigration = 0;
@@ -6839,7 +6846,7 @@ async function fetchRemoteTypes(client, workspaceId, namespace) {
6839
6846
  async function assertMigrationsReproduceLocalTypes(loaded, target) {
6840
6847
  const { config, plugins } = loaded;
6841
6848
  const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
6842
- const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-5FOWIxFQ.mjs");
6849
+ const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-C-zsrNjn.mjs");
6843
6850
  const application = defineApplication({
6844
6851
  config,
6845
6852
  pluginManager
@@ -7070,6 +7077,204 @@ const syncCommand = defineAppCommand({
7070
7077
  }
7071
7078
  });
7072
7079
 
7080
+ //#endregion
7081
+ //#region src/cli/commands/tailordb/migrate/validate.ts
7082
+ /**
7083
+ * Assert that every migration in the local history whose diff requires a data
7084
+ * migration script has a migrate.ts on disk or an explicit skip acknowledgment
7085
+ * @param {string} migrationsDir - Migrations directory path
7086
+ * @param {string} namespace - TailorDB namespace (for error messages)
7087
+ */
7088
+ function assertRequiredMigrationScripts(migrationsDir, namespace) {
7089
+ const missing = [];
7090
+ for (const file of getMigrationFiles(migrationsDir)) {
7091
+ if (file.type !== "diff") continue;
7092
+ const diff = loadDiff(file.path);
7093
+ if (!diff.requiresMigrationScript || diff.scriptSkipped) continue;
7094
+ if (!fs$1.existsSync(getMigrationFilePath(migrationsDir, file.number, "migrate"))) missing.push(file.number);
7095
+ }
7096
+ if (missing.length > 0) throw new Error(`Migration(s) ${missing.map(formatMigrationNumber).join(", ")} in namespace "${namespace}" require a migration script but have no migrate.ts. Add one with 'tailor-sdk tailordb migration script <number>', or record that no script is needed with 'tailor-sdk tailordb migration script <number> --no-script --reason "..."'.`);
7097
+ }
7098
+ /**
7099
+ * Build per-namespace reports from the checks that completed
7100
+ * @param {BuildValidationReportsOptions} options - Completed validation results
7101
+ * @returns {NamespaceValidationReport[]} Validation reports per namespace
7102
+ */
7103
+ function buildValidationReports(options) {
7104
+ const { targetNamespaces, migrationFileErrors, localResults, remoteResults } = options;
7105
+ return targetNamespaces.map((target) => {
7106
+ const fileError = migrationFileErrors.get(target.namespace);
7107
+ if (fileError !== void 0) return {
7108
+ namespace: target.namespace,
7109
+ valid: false,
7110
+ migrationFiles: {
7111
+ valid: false,
7112
+ error: fileError
7113
+ }
7114
+ };
7115
+ const local = assertDefined(localResults.find((result) => result.namespace === target.namespace), `local schema check result missing for namespace "${target.namespace}"`);
7116
+ const localSchema = {
7117
+ hasDiff: local.hasDiff,
7118
+ ...local.diff ? { diff: local.diff } : {}
7119
+ };
7120
+ if (remoteResults === void 0) return {
7121
+ namespace: target.namespace,
7122
+ valid: false,
7123
+ migrationFiles: { valid: true },
7124
+ localSchema,
7125
+ remoteSchema: { skipped: "check_failed" }
7126
+ };
7127
+ const remote = assertDefined(remoteResults.find((result) => result.namespace === target.namespace), `remote schema check result missing for namespace "${target.namespace}"`);
7128
+ const checkpointMissingLocal = remote.checkpointMissingLocal === true;
7129
+ const remoteSchema = {
7130
+ remoteMigrationNumber: remote.remoteMigrationNumber,
7131
+ hasDrift: remote.hasDrift,
7132
+ drifts: remote.drifts,
7133
+ ...remote.skipped ? { skipped: remote.skipped } : {},
7134
+ ...checkpointMissingLocal ? { checkpointMissingLocal: true } : {}
7135
+ };
7136
+ return {
7137
+ namespace: target.namespace,
7138
+ valid: !localSchema.hasDiff && !remoteSchema.hasDrift && !checkpointMissingLocal,
7139
+ migrationFiles: { valid: true },
7140
+ localSchema,
7141
+ remoteSchema
7142
+ };
7143
+ });
7144
+ }
7145
+ /**
7146
+ * Run all migration validation checks and collect per-namespace reports
7147
+ * @param {ValidateOptions} options - Command options
7148
+ * @returns {Promise<CollectedValidationReports>} Validation reports and any remote error
7149
+ */
7150
+ async function collectValidationReports(options) {
7151
+ const { config, plugins } = await loadConfig(options.configPath);
7152
+ const namespacesWithMigrations = getNamespacesWithMigrations(config, path.dirname(config.path));
7153
+ if (namespacesWithMigrations.length === 0) throw new Error("No TailorDB services with migrations configuration found");
7154
+ const targetNamespaces = options.namespace ? namespacesWithMigrations.filter((ns) => ns.namespace === options.namespace) : namespacesWithMigrations;
7155
+ if (targetNamespaces.length === 0) throw new Error(`Namespace "${options.namespace}" not found or does not have migrations configured`);
7156
+ const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
7157
+ const { defineApplication } = await import("../application-C-zsrNjn.mjs");
7158
+ const application = defineApplication({
7159
+ config,
7160
+ pluginManager
7161
+ });
7162
+ for (const service of application.tailorDBServices) {
7163
+ await service.loadTypes();
7164
+ await service.processNamespacePlugins();
7165
+ }
7166
+ assertUniqueLocalTailorDBTypeNames({ tailorDBServices: application.tailorDBServices });
7167
+ for (const { namespace } of targetNamespaces) if (!application.tailorDBServices.some((s) => s.namespace === namespace)) throw new Error(`No TailorDB service found for namespace "${namespace}"`);
7168
+ const tailorDBInputs = application.tailorDBServices.map(toTailorDBDeployInput);
7169
+ const typesByNamespace = new Map(tailorDBInputs.map((input) => [input.namespace, input.types]));
7170
+ const migrationFileErrors = /* @__PURE__ */ new Map();
7171
+ const checkableNamespaces = [];
7172
+ for (const target of targetNamespaces) try {
7173
+ assertValidMigrationFiles(target.migrationsDir, target.namespace);
7174
+ reconstructSnapshotFromMigrations(target.migrationsDir);
7175
+ assertRequiredMigrationScripts(target.migrationsDir, target.namespace);
7176
+ checkableNamespaces.push(target);
7177
+ } catch (error) {
7178
+ migrationFileErrors.set(target.namespace, error instanceof Error ? error.message : String(error));
7179
+ }
7180
+ const localReportOptions = {
7181
+ targetNamespaces,
7182
+ migrationFileErrors,
7183
+ localResults: await checkMigrationDiffs(typesByNamespace, checkableNamespaces)
7184
+ };
7185
+ if (checkableNamespaces.length === 0) return { reports: buildValidationReports(localReportOptions) };
7186
+ let remoteResults;
7187
+ try {
7188
+ remoteResults = await verifyRemoteSchema(await initOperatorClient(await loadAccessToken({ profile: options.profile })), await loadWorkspaceId({
7189
+ workspaceId: options.workspaceId,
7190
+ profile: options.profile
7191
+ }), checkableNamespaces, config, tailorDBInputs);
7192
+ } catch (remoteError) {
7193
+ return {
7194
+ reports: buildValidationReports(localReportOptions),
7195
+ remoteError
7196
+ };
7197
+ }
7198
+ return { reports: buildValidationReports({
7199
+ ...localReportOptions,
7200
+ remoteResults
7201
+ }) };
7202
+ }
7203
+ function printValidationReports(reports) {
7204
+ for (const report of reports) {
7205
+ logger.newline();
7206
+ logger.info(`Namespace: ${styles.bold(report.namespace)}`);
7207
+ if (!report.migrationFiles.valid) {
7208
+ logger.log(` Migration files: ${styles.error("invalid")}`);
7209
+ logger.log(` ${report.migrationFiles.error}`);
7210
+ continue;
7211
+ }
7212
+ logger.log(` Migration files: ${styles.success("OK")}`);
7213
+ const local = report.localSchema;
7214
+ if (local?.hasDiff) if (local.diff) {
7215
+ logger.log(` Local schema: ${styles.error("changes not in migration files")}`);
7216
+ logger.log(` ${formatDiffSummary(local.diff)}`);
7217
+ logger.log(formatMigrationDiff(local.diff));
7218
+ } else logger.log(` Local schema: ${styles.error("no migration snapshot found")}`);
7219
+ else logger.log(` Local schema: ${styles.success("OK")}`);
7220
+ const remote = report.remoteSchema;
7221
+ if (remote?.skipped === "check_failed") logger.log(` Remote schema: ${styles.error("not checked")}`);
7222
+ else if (remote?.checkpointMissingLocal) logger.log(` Remote schema: ${styles.error(`remote migration ${formatMigrationNumber(remote.remoteMigrationNumber)} is not in the local migration history`)}`);
7223
+ else if (remote?.hasDrift) {
7224
+ logger.log(` Remote schema: ${styles.error("drift detected")} (remote migration: ${formatMigrationNumber(remote.remoteMigrationNumber)})`);
7225
+ logger.log(formatSchemaDrifts(remote.drifts));
7226
+ } else if (remote?.skipped === "not_deployed") logger.log(` Remote schema: ${styles.dim("skipped (namespace not deployed)")}`);
7227
+ else if (remote?.skipped === "no_migration_label") logger.log(` Remote schema: ${styles.dim("skipped (deployed namespace has no migration state)")}`);
7228
+ else if (remote?.skipped === "no_snapshot") logger.log(` Remote schema: ${styles.dim(`skipped (no local snapshot for remote migration ${formatMigrationNumber(remote.remoteMigrationNumber)})`)}`);
7229
+ else if (remote) logger.log(` Remote schema: ${styles.success("OK")} (remote migration: ${formatMigrationNumber(remote.remoteMigrationNumber)})`);
7230
+ }
7231
+ logger.newline();
7232
+ }
7233
+ function printResolutionHints(reports) {
7234
+ if (reports.some((r) => r.localSchema?.hasDiff && !r.localSchema.diff)) logger.info("Run 'tailor-sdk tailordb migration generate' to create the initial snapshot.");
7235
+ if (reports.some((r) => r.localSchema?.diff)) logger.info("Run 'tailor-sdk tailordb migration generate' to create migration files.");
7236
+ if (reports.some((r) => r.remoteSchema?.checkpointMissingLocal)) logger.info("The remote migration checkpoint is ahead of the local history. Pull the latest migration files, or run 'tailor-sdk tailordb migration status' to compare.");
7237
+ if (reports.some((r) => r.remoteSchema?.hasDrift)) logRemoteDriftGuidance();
7238
+ }
7239
+ /**
7240
+ * Validate migration files and schema state without deploying
7241
+ * @param {ValidateOptions} options - Command options
7242
+ */
7243
+ async function validate(options) {
7244
+ logBetaWarning("tailordb migration");
7245
+ const collected = await collectValidationReports(options);
7246
+ const { reports } = collected;
7247
+ const invalidCount = reports.filter((r) => !r.valid).length;
7248
+ if (options.json) logger.out(reports);
7249
+ else {
7250
+ printValidationReports(reports);
7251
+ if (invalidCount === 0 && !("remoteError" in collected)) logger.success("All migration validation checks passed.");
7252
+ else printResolutionHints(reports);
7253
+ }
7254
+ if ("remoteError" in collected) throw collected.remoteError;
7255
+ if (invalidCount > 0) throw new Error(`Migration validation failed for ${invalidCount} namespace(s)`);
7256
+ }
7257
+ const validateCommand = defineAppCommand({
7258
+ name: "validate",
7259
+ description: "Validate the full migration history and detect schema drift (local types vs. migration snapshot, remote schema vs. migration checkpoint) without deploying. This includes the migration and schema-drift checks used by 'deploy' and exits with a non-zero code when issues are found.",
7260
+ args: z.object({
7261
+ ...deploymentArgs,
7262
+ namespace: arg(z.string().optional(), {
7263
+ alias: "n",
7264
+ description: "Target TailorDB namespace (validates all namespaces if not specified)"
7265
+ })
7266
+ }).strict(),
7267
+ run: async (args) => {
7268
+ await validate({
7269
+ configPath: args.config,
7270
+ namespace: args.namespace,
7271
+ workspaceId: args["workspace-id"],
7272
+ profile: args.profile,
7273
+ json: logger.jsonMode
7274
+ });
7275
+ }
7276
+ });
7277
+
7073
7278
  //#endregion
7074
7279
  //#region src/cli/commands/tailordb/migrate/index.ts
7075
7280
  /**
@@ -7081,6 +7286,7 @@ const syncCommand = defineAppCommand({
7081
7286
  * - set: Set migration checkpoint to a specific number
7082
7287
  * - status: Show migration status for TailorDB namespaces
7083
7288
  * - sync: Sync remote TailorDB schema to a specific migration snapshot
7289
+ * - validate: Validate migration files and detect schema drift without deploying
7084
7290
  */
7085
7291
  const migrationCommand = defineCommand({
7086
7292
  name: "migration",
@@ -7090,7 +7296,8 @@ const migrationCommand = defineCommand({
7090
7296
  script: scriptCommand,
7091
7297
  set: setCommand,
7092
7298
  status: statusCommand,
7093
- sync: syncCommand
7299
+ sync: syncCommand,
7300
+ validate: validateCommand
7094
7301
  }
7095
7302
  });
7096
7303