@tailor-platform/sdk 1.69.0 → 1.70.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 (52) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/application-BakHtldG.mjs +4 -0
  3. package/dist/{application-Cr-limKC.mjs → application-Df5_I83n.mjs} +318 -78
  4. package/dist/application-Df5_I83n.mjs.map +1 -0
  5. package/dist/cli/erd-viewer-assets/app.js +279 -36
  6. package/dist/cli/erd-viewer-assets/index.html +4 -0
  7. package/dist/cli/erd-viewer-assets/styles.css +252 -5
  8. package/dist/cli/index.mjs +641 -90
  9. package/dist/cli/index.mjs.map +1 -1
  10. package/dist/cli/lib.d.mts +14 -8
  11. package/dist/cli/lib.mjs +2 -2
  12. package/dist/completion/zsh-worker.zsh +153 -2
  13. package/dist/configure/index.d.mts +5 -5
  14. package/dist/configure/index.mjs +8 -6
  15. package/dist/configure/index.mjs.map +1 -1
  16. package/dist/{index-B7VbJm0_.d.mts → index-BAEaAqmz.d.mts} +90 -40
  17. package/dist/{index-CklcVeMG.d.mts → index-C-vsbx27.d.mts} +2 -2
  18. package/dist/{index-hXoO-AOC.d.mts → index-CKI0eZP6.d.mts} +2 -2
  19. package/dist/{index-DYhnxXYR.d.mts → index-CrqOgUF2.d.mts} +2 -2
  20. package/dist/{index-DlDRSzFZ.d.mts → index-DESLU9kI.d.mts} +2 -2
  21. package/dist/plugin/builtin/enum-constants/index.d.mts +1 -1
  22. package/dist/plugin/builtin/file-utils/index.d.mts +1 -1
  23. package/dist/plugin/builtin/kysely-type/index.d.mts +1 -1
  24. package/dist/plugin/builtin/seed/index.d.mts +1 -1
  25. package/dist/plugin/index.d.mts +1 -1
  26. package/dist/{runtime-jowoN6qC.mjs → runtime-CSY0eD4_.mjs} +330 -190
  27. package/dist/runtime-CSY0eD4_.mjs.map +1 -0
  28. package/dist/{schema-1msIhXwA.mjs → schema-C4fkpWV_.mjs} +9 -15
  29. package/dist/schema-C4fkpWV_.mjs.map +1 -0
  30. package/dist/{types-2Be3wSMc.mjs → types-32lUMToj.mjs} +1 -1
  31. package/dist/{types-CmzfQP_m.mjs → types-D4QMmNWh.mjs} +1 -12
  32. package/dist/types-D4QMmNWh.mjs.map +1 -0
  33. package/dist/{types-Bzr0RQME.d.mts → types-Dynq4AJv.d.mts} +2 -2
  34. package/dist/{types-DZrtN6-H.d.mts → types-rj8YJcEe.d.mts} +5 -2
  35. package/dist/utils/test/index.d.mts +2 -2
  36. package/dist/{workflow.generated-Br9bmLdX.d.mts → workflow.generated-DJULCuRr.d.mts} +177 -172
  37. package/docs/cli/application.md +37 -2
  38. package/docs/cli/setup.md +1 -0
  39. package/docs/cli/tailordb.md +24 -0
  40. package/docs/cli/user.md +11 -1
  41. package/docs/cli/workspace.md +13 -7
  42. package/docs/cli-reference.md +6 -0
  43. package/docs/github-actions.md +27 -0
  44. package/docs/multi-environment.md +22 -0
  45. package/docs/services/aigateway.md +4 -2
  46. package/docs/services/http-adapter.md +16 -1
  47. package/package.json +1 -1
  48. package/dist/application-Br48NXBD.mjs +0 -4
  49. package/dist/application-Cr-limKC.mjs.map +0 -1
  50. package/dist/runtime-jowoN6qC.mjs.map +0 -1
  51. package/dist/schema-1msIhXwA.mjs.map +0 -1
  52. package/dist/types-CmzfQP_m.mjs.map +0 -1
@@ -3,8 +3,8 @@
3
3
  import { C as CustomDomainStatus, R as FunctionExecution_Type, ft as AuthInvokerSchema, yt as PATScope } from "../service_pb-DSNjrcbW.mjs";
4
4
  import { t as assertDefined } from "../assert-CKfwrmCV.mjs";
5
5
  import { n as logger, r as styles } from "../logger-DKF-JsAK.mjs";
6
- import { $ as updateCommand$2, $t as protoGqlPermission, A as listCommand$12, At as jobsCommand, Bn as toPageDirection, Bt as functionExecutionStatusToString, C as listCommand$13, Cn as ensureConfigId, Ct as webhookCommand, Dn as generateUserTypes, E as waitCommand, En as PluginManager, Et as listCommand$6, F as generateCommand$1, Fn as confirmationArgs, Ft as getCommand$6, G as updateCommand$3, Gt as executeScript, H as logBetaWarning, Ht as getCommand$1, I as generateMigrationScript, In as deploymentArgs, J as treeCommand, Jt as MIGRATION_LABEL_KEY, L as writeDbTypesFile, Ln as isVerbose, Lt as executionsCommand, Mn as defineAppCommand, N as truncateCommand, Nn as commonArgs, Nt as startCommand, O as resumeCommand, On as prompt, Pn as configArg, Qt as generateAllTypeManifestsFromSnapshot, R as getConfiguredEditorCommand, Rn as pagedLogArgs, Sn as getNamespacesWithMigrations, T as healthCommand, Tn as sdkNameLabelKey, V as showCommand, Vn as workspaceArgs, Vt as formatKeyValueTable, W as removeCommand$1, Wt as deploy, Xt as parseMigrationLabelNumber, Y as listCommand$11, Yt as handleOptionalToRequiredError, Z as getCommand$5, Zt as compareSnapshotWithRemote, _n as formatMigrationNumber, _t as generate, an as assertValidMigrationFiles, at as deleteCommand$3, b as createCommand$4, bn as formatMigrationDiff, bt as getCommand$2, c as listCommand$14, cn as createSnapshotFromLocalTypes, dn as getMigrationFilePath, dt as getCommand$3, f as restoreCommand, fn as getMigrationFiles, g as getCommand$7, gn as reconstructSnapshotFromMigrations, hn as loadDiff, ht as listCommand$8, i as updateCommand$4, jn as assertWritable, kn as apiCommand, ln as getLatestMigrationNumber, lt as listCommand$9, m as listCommand$15, mn as isValidMigrationNumber, mt as tokenCommand, nn as INITIAL_SCHEMA_NUMBER, o as removeCommand, on as compareLocalTypesWithSnapshot, r as queryCommand, rt as getCommand$4, st as createCommand$3, t as isNativeTypeScriptRuntime, tt as listCommand$10, u as inviteCommand, v as deleteCommand$4, vn as parseMigrationNumberArg, vt as listCommand$7, wn as resourceTrn, wt as triggerCommand, xn as hasChanges, z as openInConfiguredEditor, zn as paginationArgs } from "../runtime-jowoN6qC.mjs";
7
- import { A as loadMachineUserName, B as fetchPlatformMachineUserToken, C as hashContent$1, D as fetchLatestToken, E as deleteUserTokens, F as writePlatformConfig, H as initOAuth2Client, I as closeConnectionPool, L as fetchAll, M as readPlatformConfig, N as resolveTokens, O as loadAccessToken, P as saveUserTokens, S as getDistDir, T as loadConfig, U as initOperatorClient, V as fetchUserInfo, _ as createLogLevelTreeshakeOptions, a as WorkflowJobSchema, c as INVOKER_EXPR, g as composeFunctionTreeshakeOptions, h as platformBundleDefinePlugin, i as resolveInlineSourcemap, j as loadWorkspaceId, o as ResolverSchema, t as defineApplication, v as resolveBundleLogLevel, z as fetchPaged } from "../application-Cr-limKC.mjs";
6
+ import { $t as generateAllTypeManifestsFromSnapshot, A as listCommand$12, An as apiCommand, Bn as paginationArgs, C as listCommand$13, Cn as getNamespacesWithMigrations, Dn as PluginManager, Dt as listCommand$6, E as waitCommand, En as sdkNameLabelKey, F as generateCommand$1, Fn as configArg, G as removeCommand$1, Gt as deploy, H as extractOwnedNamespaces, Hn as workspaceArgs, Ht as formatKeyValueTable, I as generateMigrationScript, In as confirmationArgs, It as getCommand$6, K as updateCommand$3, Kt as executeScript, L as writeDbTypesFile, Ln as deploymentArgs, Mn as assertWritable, N as truncateCommand, Nn as defineAppCommand, O as resumeCommand, On as generateUserTypes, Pn as commonArgs, Pt as startCommand, Q as getCommand$5, Qt as compareSnapshotWithRemote, R as getConfiguredEditorCommand, Rn as isVerbose, Rt as executionsCommand, Sn as hasChanges, T as healthCommand, Tn as resourceTrn, Tt as triggerCommand, U as logBetaWarning, Ut as getCommand$1, V as showCommand, Vn as toPageDirection, Vt as functionExecutionStatusToString, X as listCommand$11, Xt as handleOptionalToRequiredError, Y as treeCommand, Yt as MIGRATION_LABEL_KEY, Zt as parseMigrationLabelNumber, _n as reconstructSnapshotFromMigrations, b as createCommand$4, c as listCommand$14, ct as createCommand$3, en as protoGqlPermission, et as updateCommand$2, f as restoreCommand, fn as getMigrationFilePath, ft as getCommand$3, g as getCommand$7, gn as loadDiff, gt as listCommand$8, hn as isValidMigrationNumber, ht as tokenCommand, i as updateCommand$4, it as getCommand$4, jt as jobsCommand, kn as prompt, ln as createSnapshotFromLocalTypes, m as listCommand$15, nt as listCommand$10, o as removeCommand, on as assertValidMigrationFiles, ot as deleteCommand$3, pn as getMigrationFiles, r as queryCommand, rn as INITIAL_SCHEMA_NUMBER, sn as compareLocalTypesWithSnapshot, t as isNativeTypeScriptRuntime, u as inviteCommand, un as getLatestMigrationNumber, ut as listCommand$9, v as deleteCommand$4, vn as formatMigrationNumber, vt as generate, wn as ensureConfigId, wt as webhookCommand, xn as formatMigrationDiff, xt as getCommand$2, yn as parseMigrationNumberArg, yt as listCommand$7, z as openInConfiguredEditor, zn as pagedLogArgs } from "../runtime-CSY0eD4_.mjs";
7
+ import { $ as initOperatorClient, A as loadAccessToken, B as saveUserTokens, C as hashContent$1, D as fetchLatestToken, E as deleteUserTokens, F as loadStoredUserTokens, H as closeConnectionPool, I as loadWorkspaceId, J as fetchUserInfo, K as fetchPaged, L as platformConfigFromProfile, M as loadConsoleBaseUrl, N as loadMachineUserName, O as hasAnyUserTokenEntry, Q as initOAuth2Client, R as readPlatformConfig, S as getDistDir, T as loadConfig, U as defaultPlatformBaseUrl, V as writePlatformConfig, W as fetchAll, _ as createLogLevelTreeshakeOptions, a as WorkflowJobSchema, c as INVOKER_EXPR, et as isDefaultPlatform, g as composeFunctionTreeshakeOptions, h as platformBundleDefinePlugin, i as resolveInlineSourcemap, k as hasUserTokenEntry, o as ResolverSchema, q as fetchPlatformMachineUserToken, t as defineApplication, v as resolveBundleLogLevel, z as resolveUserTokenKey } from "../application-Df5_I83n.mjs";
8
8
  import { n as ExecutorSchema } from "../service-B2Jd9CxS.mjs";
9
9
  import { t as multiline } from "../multiline-Cf9ODpr1.mjs";
10
10
  import { r as isPluginGeneratedType } from "../seed-YAbtMy65.mjs";
@@ -255,7 +255,6 @@ const listAuthConnectionCommand = defineAppCommand({
255
255
 
256
256
  //#endregion
257
257
  //#region src/cli/commands/authconnection/open.ts
258
- const consoleBaseUrl$1 = "https://console.tailor.tech";
259
258
  const openAuthConnectionCommand = defineAppCommand({
260
259
  name: "open",
261
260
  description: "Open the auth connections page in the Tailor Platform Console.",
@@ -266,7 +265,11 @@ const openAuthConnectionCommand = defineAppCommand({
266
265
  profile: args.profile
267
266
  });
268
267
  const consolePath = `/workspaces/${workspaceId}/settings/connections`;
269
- const consoleUrl = new URL(consolePath, consoleBaseUrl$1).toString();
268
+ const consoleBaseUrl = await loadConsoleBaseUrl({
269
+ profile: args.profile,
270
+ ...args["workspace-id"] !== void 0 ? { allowMissingProfile: true } : {}
271
+ });
272
+ const consoleUrl = new URL(consolePath, consoleBaseUrl).toString();
270
273
  const jsonOutput = logger.jsonMode;
271
274
  logger.info("Opening auth connections page in Tailor Platform Console...");
272
275
  let opened = true;
@@ -1285,7 +1288,7 @@ async function detectFunctionType(options) {
1285
1288
  const rawInput = module.default.input;
1286
1289
  let inputSchema;
1287
1290
  if (rawInput) {
1288
- const { t } = await import("../types-2Be3wSMc.mjs");
1291
+ const { t } = await import("../types-32lUMToj.mjs");
1289
1292
  inputSchema = t.object(rawInput);
1290
1293
  }
1291
1294
  return {
@@ -1734,8 +1737,15 @@ const redirectUri = `http://localhost:${redirectPort}/callback`;
1734
1737
  function randomState() {
1735
1738
  return crypto.randomBytes(32).toString("base64url");
1736
1739
  }
1737
- const startAuthServer = async () => {
1738
- const client = initOAuth2Client();
1740
+ function assertProfileLoginUser(args, authenticatedUser) {
1741
+ if (args.profile && args.profileUser && authenticatedUser !== args.profileUser) throw new Error(`Profile "${args.profile}" is configured for "${args.profileUser}", but login authenticated "${authenticatedUser}".`);
1742
+ }
1743
+ function shouldUpdateCurrentUser(profile, platformConfig) {
1744
+ if (!profile) return true;
1745
+ return isDefaultPlatform(platformConfig);
1746
+ }
1747
+ const startAuthServer = async (args = {}) => {
1748
+ const client = initOAuth2Client(args.platformConfig);
1739
1749
  const state = randomState();
1740
1750
  const codeVerifier = await generateCodeVerifier();
1741
1751
  return new Promise((resolve, reject) => {
@@ -1747,13 +1757,14 @@ const startAuthServer = async () => {
1747
1757
  state,
1748
1758
  codeVerifier
1749
1759
  });
1750
- const userInfo = await fetchUserInfo(tokens.accessToken);
1760
+ const userInfo = await fetchUserInfo(tokens.accessToken, args.platformConfig);
1761
+ assertProfileLoginUser(args, userInfo.email);
1751
1762
  const pfConfig = await readPlatformConfig();
1752
1763
  await saveUserTokens(pfConfig, userInfo.email, {
1753
1764
  accessToken: tokens.accessToken,
1754
1765
  refreshToken: tokens.refreshToken ?? void 0
1755
- }, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString());
1756
- pfConfig.current_user = userInfo.email;
1766
+ }, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString(), args.platformConfig);
1767
+ if (args.updateCurrentUser ?? true) pfConfig.current_user = userInfo.email;
1757
1768
  writePlatformConfig(pfConfig);
1758
1769
  res.writeHead(200, { "Content-Type": "application/json" });
1759
1770
  res.end(JSON.stringify({
@@ -1795,17 +1806,22 @@ const startAuthServer = async () => {
1795
1806
  });
1796
1807
  };
1797
1808
  async function loginAsMachineUser(args) {
1809
+ assertProfileLoginUser(args, args.clientId);
1798
1810
  const clientSecret = args.clientSecret ?? await prompt.password({ message: "Client secret" });
1799
- const tokens = await fetchPlatformMachineUserToken(args.clientId, clientSecret);
1811
+ const tokens = await fetchPlatformMachineUserToken(args.clientId, clientSecret, args.platformConfig);
1800
1812
  const pfConfig = await readPlatformConfig();
1801
- await saveUserTokens(pfConfig, args.clientId, { accessToken: tokens.accessToken }, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString());
1802
- pfConfig.current_user = args.clientId;
1813
+ await saveUserTokens(pfConfig, args.clientId, { accessToken: tokens.accessToken }, new Date(assertDefined(tokens.expiresAt, "token response missing expiresAt")).toISOString(), args.platformConfig);
1814
+ if (args.updateCurrentUser ?? true) pfConfig.current_user = args.clientId;
1803
1815
  writePlatformConfig(pfConfig);
1804
1816
  }
1805
1817
  const loginCommand = defineAppCommand({
1806
1818
  name: "login",
1807
1819
  description: "Login to Tailor Platform.",
1808
- args: z.xor([z.object({}).strict().describe("User Login"), z.object({
1820
+ args: z.xor([z.object({ profile: arg(z.string().optional(), {
1821
+ alias: "p",
1822
+ description: "Workspace profile whose platform settings should be used for login.",
1823
+ env: "TAILOR_PLATFORM_PROFILE"
1824
+ }) }).strict().describe("User Login"), z.object({
1809
1825
  "machine-user": arg(z.literal(true), {
1810
1826
  hiddenAlias: "machineuser",
1811
1827
  description: "Login as a platform machine user.",
@@ -1819,14 +1835,37 @@ const loginCommand = defineAppCommand({
1819
1835
  "client-secret": arg(z.string().optional(), {
1820
1836
  description: "Client secret",
1821
1837
  env: "TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET"
1838
+ }),
1839
+ profile: arg(z.string().optional(), {
1840
+ alias: "p",
1841
+ description: "Workspace profile whose platform settings should be used for login.",
1842
+ env: "TAILOR_PLATFORM_PROFILE"
1822
1843
  })
1823
1844
  }).strict().describe("Machine User Login")]),
1824
1845
  run: async (args) => {
1846
+ let platformConfig;
1847
+ let profileUser;
1848
+ if ("profile" in args && args.profile) {
1849
+ const profileEntry = (await readPlatformConfig()).profiles[args.profile];
1850
+ if (!profileEntry) throw new Error(`Profile "${args.profile}" not found`);
1851
+ platformConfig = platformConfigFromProfile(profileEntry);
1852
+ profileUser = profileEntry.user;
1853
+ }
1854
+ const updateCurrentUser = shouldUpdateCurrentUser(args.profile, platformConfig);
1825
1855
  if ("machine-user" in args) await loginAsMachineUser({
1826
1856
  clientId: args.clientId,
1827
- clientSecret: args.clientSecret
1857
+ clientSecret: args.clientSecret,
1858
+ profile: args.profile,
1859
+ profileUser,
1860
+ platformConfig,
1861
+ updateCurrentUser
1862
+ });
1863
+ else await startAuthServer({
1864
+ profile: args.profile,
1865
+ profileUser,
1866
+ platformConfig,
1867
+ updateCurrentUser
1828
1868
  });
1829
- else await startAuthServer();
1830
1869
  logger.success("Successfully logged in to Tailor Platform.");
1831
1870
  await closeConnectionPool();
1832
1871
  }
@@ -1837,30 +1876,53 @@ const loginCommand = defineAppCommand({
1837
1876
  const logoutCommand = defineAppCommand({
1838
1877
  name: "logout",
1839
1878
  description: "Logout from Tailor Platform.",
1840
- args: z.object({}).strict(),
1841
- run: async () => {
1879
+ args: z.object({ profile: arg(z.string().optional(), {
1880
+ alias: "p",
1881
+ description: "Workspace profile whose platform settings should be used for logout.",
1882
+ env: "TAILOR_PLATFORM_PROFILE"
1883
+ }) }).strict(),
1884
+ run: async (args) => {
1885
+ const profile = args.profile || process.env.TAILOR_PLATFORM_PROFILE;
1842
1886
  const pfConfig = await readPlatformConfig();
1843
- const currentUser = pfConfig.current_user;
1844
- const userEntry = currentUser ? pfConfig.users[currentUser] : void 0;
1845
- if (!userEntry || !currentUser) {
1887
+ const profileEntry = profile ? pfConfig.profiles[profile] : void 0;
1888
+ if (profile && !profileEntry) throw new Error(`Profile "${profile}" not found`);
1889
+ const platformConfig = profileEntry ? platformConfigFromProfile(profileEntry) : void 0;
1890
+ const currentUser = profileEntry ? profileEntry.user : pfConfig.current_user;
1891
+ const deletesDefaultToken = isDefaultPlatform(platformConfig);
1892
+ const lookupOptions = profile ? { allowLegacyUserKey: true } : void 0;
1893
+ if (!currentUser) {
1846
1894
  logger.info("You are not logged in.");
1847
1895
  return;
1848
1896
  }
1897
+ const hasDefaultUserToken = () => hasUserTokenEntry(pfConfig, currentUser, { platformUrl: defaultPlatformBaseUrl });
1898
+ const shouldClearCurrentUser = () => pfConfig.current_user === currentUser && (deletesDefaultToken ? !hasDefaultUserToken() : !hasAnyUserTokenEntry(pfConfig, currentUser) && !hasDefaultUserToken());
1899
+ let storedTokens;
1900
+ let tokenLoadFailed = false;
1849
1901
  try {
1850
- const { accessToken, refreshToken } = await resolveTokens(userEntry, currentUser);
1851
- const client = initOAuth2Client();
1852
- const tokenTypeHint = refreshToken ? "refresh_token" : "access_token";
1902
+ storedTokens = await loadStoredUserTokens(pfConfig, currentUser, platformConfig, lookupOptions);
1903
+ } catch (error) {
1904
+ tokenLoadFailed = true;
1905
+ logger.warn(`Failed to revoke token: ${error instanceof Error ? error.message : error}`);
1906
+ }
1907
+ if (!storedTokens && !tokenLoadFailed) {
1908
+ logger.info("You are not logged in.");
1909
+ if (shouldClearCurrentUser()) pfConfig.current_user = null;
1910
+ writePlatformConfig(pfConfig);
1911
+ return;
1912
+ }
1913
+ if (storedTokens) try {
1914
+ const client = initOAuth2Client(platformConfig);
1915
+ const tokenTypeHint = storedTokens.refreshToken ? "refresh_token" : "access_token";
1853
1916
  await client.revoke({
1854
- accessToken,
1855
- refreshToken: refreshToken ?? null,
1856
- expiresAt: Date.parse(userEntry.token_expires_at)
1917
+ accessToken: storedTokens.accessToken,
1918
+ refreshToken: storedTokens.refreshToken ?? null,
1919
+ expiresAt: Date.parse(storedTokens.userEntry.token_expires_at)
1857
1920
  }, tokenTypeHint);
1858
1921
  } catch (error) {
1859
1922
  logger.warn(`Failed to revoke token: ${error instanceof Error ? error.message : error}`);
1860
1923
  }
1861
- await deleteUserTokens(pfConfig, currentUser);
1862
- delete pfConfig.users[currentUser];
1863
- pfConfig.current_user = null;
1924
+ await deleteUserTokens(pfConfig, currentUser, platformConfig, lookupOptions);
1925
+ if (shouldClearCurrentUser()) pfConfig.current_user = null;
1864
1926
  writePlatformConfig(pfConfig);
1865
1927
  logger.success("Successfully logged out from Tailor Platform.");
1866
1928
  }
@@ -1896,7 +1958,6 @@ const oauth2clientCommand = defineCommand({
1896
1958
 
1897
1959
  //#endregion
1898
1960
  //#region src/cli/commands/open.ts
1899
- const consoleBaseUrl = "https://console.tailor.tech";
1900
1961
  const openCommand = defineAppCommand({
1901
1962
  name: "open",
1902
1963
  description: "Open Tailor Platform Console.",
@@ -1909,6 +1970,10 @@ const openCommand = defineAppCommand({
1909
1970
  const { config } = await loadConfig(args.config);
1910
1971
  const applicationName = config.name;
1911
1972
  const consolePath = `/workspaces/${workspaceId}/applications/${encodeURIComponent(applicationName)}/overview`;
1973
+ const consoleBaseUrl = await loadConsoleBaseUrl({
1974
+ profile: args.profile,
1975
+ ...args["workspace-id"] !== void 0 ? { allowMissingProfile: true } : {}
1976
+ });
1912
1977
  const consoleUrl = new URL(consolePath, consoleBaseUrl).toString();
1913
1978
  const jsonOutput = logger.jsonMode;
1914
1979
  logger.info("Opening Tailor Platform Console...");
@@ -1989,13 +2054,31 @@ const createCommand$2 = defineAppCommand({
1989
2054
  alias: "m",
1990
2055
  description: "Default machine user name for application-data commands (query, workflow start, function test-run, machineuser token)."
1991
2056
  }),
1992
- "machine-user-override": arg(z.enum(["allow", "deny"]).optional(), { description: "Whether the command line or TAILOR_PLATFORM_MACHINE_USER_NAME may override the profile's machine user. 'deny' requires --machine-user." })
2057
+ "machine-user-override": arg(z.enum(["allow", "deny"]).optional(), { description: "Whether the command line or TAILOR_PLATFORM_MACHINE_USER_NAME may override the profile's machine user. 'deny' requires --machine-user." }),
2058
+ "platform-url": arg(z.url().optional(), {
2059
+ description: "Platform API base URL for this profile.",
2060
+ env: "TAILOR_PLATFORM_URL"
2061
+ }),
2062
+ "oauth2-client-id": arg(z.string().optional(), {
2063
+ description: "OAuth2 client ID for logging in to this profile's platform.",
2064
+ env: "TAILOR_PLATFORM_OAUTH2_CLIENT_ID"
2065
+ }),
2066
+ "console-url": arg(z.url().optional(), {
2067
+ description: "Console base URL for this profile.",
2068
+ env: "TAILOR_PLATFORM_CONSOLE_URL"
2069
+ })
1993
2070
  }).strict(),
1994
2071
  run: async (args) => {
1995
2072
  if (args["machine-user-override"] === "deny" && !args["machine-user"]) throw new Error("--machine-user-override deny requires --machine-user.");
1996
2073
  const config = await readPlatformConfig();
1997
2074
  if (config.profiles[args.name]) throw new Error(`Profile "${args.name}" already exists.`);
1998
- const client = await initOperatorClient(await fetchLatestToken(config, args.user));
2075
+ const platformConfigInput = {
2076
+ ...args["platform-url"] ? { platformUrl: args["platform-url"] } : {},
2077
+ ...args["oauth2-client-id"] ? { oauth2ClientId: args["oauth2-client-id"] } : {},
2078
+ ...args["console-url"] ? { consoleUrl: args["console-url"] } : {}
2079
+ };
2080
+ const platformConfig = Object.keys(platformConfigInput).length > 0 ? platformConfigInput : void 0;
2081
+ const client = await initOperatorClient(await fetchLatestToken(config, args.user, platformConfig), platformConfig);
1999
2082
  if (!(await fetchAll(async (pageToken, maxPageSize) => {
2000
2083
  const { workspaces, nextPageToken } = await client.listWorkspaces({
2001
2084
  pageToken,
@@ -2008,7 +2091,10 @@ const createCommand$2 = defineAppCommand({
2008
2091
  workspace_id: args["workspace-id"],
2009
2092
  ...args.permission === "read" ? { readonly: true } : {},
2010
2093
  ...args["machine-user"] ? { machine_user: args["machine-user"] } : {},
2011
- ...args["machine-user-override"] === "deny" ? { machine_user_override: "deny" } : {}
2094
+ ...args["machine-user-override"] === "deny" ? { machine_user_override: "deny" } : {},
2095
+ ...args["platform-url"] ? { platform_url: args["platform-url"] } : {},
2096
+ ...args["oauth2-client-id"] ? { oauth2_client_id: args["oauth2-client-id"] } : {},
2097
+ ...args["console-url"] ? { console_url: args["console-url"] } : {}
2012
2098
  };
2013
2099
  writePlatformConfig(config);
2014
2100
  if (!args.json) logger.success(`Profile "${args.name}" created successfully.`);
@@ -2020,7 +2106,10 @@ const createCommand$2 = defineAppCommand({
2020
2106
  ...args["machine-user"] ? {
2021
2107
  machineUser: args["machine-user"],
2022
2108
  machineUserOverride: args["machine-user-override"] ?? "allow"
2023
- } : {}
2109
+ } : {},
2110
+ ...args["platform-url"] ? { platformUrl: args["platform-url"] } : {},
2111
+ ...args["oauth2-client-id"] ? { oauth2ClientId: args["oauth2-client-id"] } : {},
2112
+ ...args["console-url"] ? { consoleUrl: args["console-url"] } : {}
2024
2113
  };
2025
2114
  logger.out(profileInfo);
2026
2115
  }
@@ -2072,7 +2161,10 @@ const listCommand$4 = defineAppCommand({
2072
2161
  ...p.machine_user ? {
2073
2162
  machineUser: p.machine_user,
2074
2163
  machineUserOverride: p.machine_user_override ?? "allow"
2075
- } : {}
2164
+ } : {},
2165
+ ...p.platform_url ? { platformUrl: p.platform_url } : {},
2166
+ ...p.oauth2_client_id ? { oauth2ClientId: p.oauth2_client_id } : {},
2167
+ ...p.console_url ? { consoleUrl: p.console_url } : {}
2076
2168
  };
2077
2169
  });
2078
2170
  logger.out(profileInfos);
@@ -2102,25 +2194,38 @@ const updateCommand$1 = defineAppCommand({
2102
2194
  alias: "m",
2103
2195
  description: "Default machine user name for application-data commands (query, workflow start, function test-run, machineuser token). Pass an empty string to clear."
2104
2196
  }),
2105
- "machine-user-override": arg(z.enum(["allow", "deny"]).optional(), { description: "Whether the command line or TAILOR_PLATFORM_MACHINE_USER_NAME may override the profile's machine user. 'deny' requires --machine-user; 'allow' lifts the restriction." })
2197
+ "machine-user-override": arg(z.enum(["allow", "deny"]).optional(), { description: "Whether the command line or TAILOR_PLATFORM_MACHINE_USER_NAME may override the profile's machine user. 'deny' requires --machine-user; 'allow' lifts the restriction." }),
2198
+ "platform-url": arg(z.union([z.url(), z.literal("")]).optional(), { description: "Platform API base URL for this profile. Pass an empty string to clear." }),
2199
+ "oauth2-client-id": arg(z.string().optional(), { description: "OAuth2 client ID for logging in to this profile's platform. Pass an empty string to clear." }),
2200
+ "console-url": arg(z.union([z.url(), z.literal("")]).optional(), { description: "Console base URL for this profile. Pass an empty string to clear." })
2106
2201
  }).strict(),
2107
2202
  run: async (args) => {
2108
2203
  const config = await readPlatformConfig();
2109
2204
  const profile = config.profiles[args.name];
2110
2205
  if (!profile) throw new Error(`Profile "${args.name}" not found.`);
2111
- if (!args.user && !args["workspace-id"] && args.permission === void 0 && args["machine-user"] === void 0 && args["machine-user-override"] === void 0) throw new Error("Please provide at least one property to update.");
2206
+ if (!args.user && !args["workspace-id"] && args.permission === void 0 && args["machine-user"] === void 0 && args["machine-user-override"] === void 0 && args["platform-url"] === void 0 && args["oauth2-client-id"] === void 0 && args["console-url"] === void 0) throw new Error("Please provide at least one property to update.");
2112
2207
  const oldUser = profile.user;
2113
2208
  const newUser = args.user || oldUser;
2114
2209
  const oldWorkspaceId = profile.workspace_id;
2115
2210
  const newWorkspaceId = args["workspace-id"] || oldWorkspaceId;
2116
2211
  const finalMachineUser = args["machine-user"] === "" ? void 0 : args["machine-user"] ?? profile.machine_user;
2117
2212
  const finalOverride = args["machine-user-override"] === "allow" ? void 0 : args["machine-user-override"] ?? profile.machine_user_override;
2213
+ const finalPlatformUrl = args["platform-url"] === "" ? void 0 : args["platform-url"] ?? profile.platform_url;
2214
+ const finalOAuth2ClientId = args["oauth2-client-id"] === "" ? void 0 : args["oauth2-client-id"] ?? profile.oauth2_client_id;
2215
+ const finalConsoleUrl = args["console-url"] === "" ? void 0 : args["console-url"] ?? profile.console_url;
2216
+ const finalPlatformConfigInput = {
2217
+ ...finalPlatformUrl ? { platformUrl: finalPlatformUrl } : {},
2218
+ ...finalOAuth2ClientId ? { oauth2ClientId: finalOAuth2ClientId } : {},
2219
+ ...finalConsoleUrl ? { consoleUrl: finalConsoleUrl } : {}
2220
+ };
2221
+ const finalPlatformConfig = Object.keys(finalPlatformConfigInput).length > 0 ? finalPlatformConfigInput : void 0;
2222
+ const tokenLookupPlatformConfig = args["platform-url"] === "" ? platformConfigFromProfile(profile) : finalPlatformConfig;
2118
2223
  if ((args["machine-user"] !== void 0 || args["machine-user-override"] !== void 0) && finalOverride === "deny" && !finalMachineUser) {
2119
2224
  if (args["machine-user-override"] === "deny") throw new Error("--machine-user-override deny requires --machine-user.");
2120
2225
  throw new Error(`Cannot clear the machine user while machine-user-override is "deny". Also pass --machine-user-override allow.`);
2121
2226
  }
2122
- if (args.user !== void 0 || args["workspace-id"] !== void 0) {
2123
- const client = await initOperatorClient(await fetchLatestToken(config, newUser));
2227
+ if (args.user !== void 0 || args["workspace-id"] !== void 0 || args["platform-url"] !== void 0) {
2228
+ const client = await initOperatorClient(await fetchLatestToken(config, newUser, tokenLookupPlatformConfig), finalPlatformConfig);
2124
2229
  if (!(await fetchAll(async (pageToken, maxPageSize) => {
2125
2230
  const { workspaces, nextPageToken } = await client.listWorkspaces({
2126
2231
  pageToken,
@@ -2137,6 +2242,12 @@ const updateCommand$1 = defineAppCommand({
2137
2242
  else profile.machine_user = args["machine-user"];
2138
2243
  if (args["machine-user-override"] === "deny") profile.machine_user_override = "deny";
2139
2244
  else if (args["machine-user-override"] === "allow") delete profile.machine_user_override;
2245
+ if (args["platform-url"] !== void 0) if (args["platform-url"] === "") delete profile.platform_url;
2246
+ else profile.platform_url = args["platform-url"];
2247
+ if (args["oauth2-client-id"] !== void 0) if (args["oauth2-client-id"] === "") delete profile.oauth2_client_id;
2248
+ else profile.oauth2_client_id = args["oauth2-client-id"];
2249
+ if (args["console-url"] !== void 0) if (args["console-url"] === "") delete profile.console_url;
2250
+ else profile.console_url = args["console-url"];
2140
2251
  writePlatformConfig(config);
2141
2252
  if (!args.json) logger.success(`Profile "${args.name}" updated successfully`);
2142
2253
  const profileInfo = {
@@ -2147,7 +2258,10 @@ const updateCommand$1 = defineAppCommand({
2147
2258
  ...profile.machine_user ? {
2148
2259
  machineUser: profile.machine_user,
2149
2260
  machineUserOverride: profile.machine_user_override ?? "allow"
2150
- } : {}
2261
+ } : {},
2262
+ ...profile.platform_url ? { platformUrl: profile.platform_url } : {},
2263
+ ...profile.oauth2_client_id ? { oauth2ClientId: profile.oauth2_client_id } : {},
2264
+ ...profile.console_url ? { consoleUrl: profile.console_url } : {}
2151
2265
  };
2152
2266
  logger.out(profileInfo);
2153
2267
  }
@@ -2708,7 +2822,7 @@ function findTarget(lock, kind, workspaceName) {
2708
2822
 
2709
2823
  //#endregion
2710
2824
  //#region src/cli/commands/setup/branch.workflow.yml
2711
- var branch_workflow_default = "# __HEADER__\nname: Tailor (__WORKSPACE_NAME__)\n\non:\n # __PULL_REQUEST_START__\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PATHS__\n # __PULL_REQUEST_END__\n push:\n branches: [\"__BRANCH__\"]\n # __PATHS__\n workflow_dispatch:\n # __DISPATCH_INPUTS_START__\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n # __DISPATCH_INPUTS_END__\n\npermissions:\n contents: read\n\njobs:\n # __PLAN_JOB_START__\n tailor-plan:\n if: >-\n github.event_name == 'pull_request' ||\n (github.event_name == 'workflow_dispatch' && inputs['dry-run'])\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n concurrency:\n group: tailor-plan-__WORKSPACE_NAME__-${{ github.event.pull_request.number || github.run_id }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n - id: tailor-plan\n # Fork PRs cannot read secrets; the checks above still run for them.\n if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork\n uses: tailor-platform/actions/plan@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n # __PLAN_JOB_END__\n tailor-deploy:\n # __DEPLOY_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n permissions:\n contents: read\n environment: __ENVIRONMENT__\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n";
2825
+ var branch_workflow_default = "# __HEADER__\nname: Tailor (__WORKSPACE_NAME__)\n\non:\n # __PULL_REQUEST_START__\n pull_request:\n branches: [\"__BRANCH__\"]\n # __PATHS__\n # __PULL_REQUEST_END__\n push:\n branches: [\"__BRANCH__\"]\n # __PATHS__\n workflow_dispatch:\n # __DISPATCH_INPUTS_START__\n inputs:\n dry-run:\n description: Preview changes without deploying\n type: boolean\n default: false\n # __DISPATCH_INPUTS_END__\n\npermissions:\n contents: read\n\njobs:\n # __PLAN_JOB_START__\n tailor-plan:\n if: >-\n github.event_name == 'pull_request' ||\n (github.event_name == 'workflow_dispatch' && inputs['dry-run'])\n runs-on: ubuntu-latest\n timeout-minutes: 30\n environment: __ENVIRONMENT__\n permissions:\n contents: read\n pull-requests: write\n concurrency:\n group: tailor-plan-__WORKSPACE_NAME__-${{ github.event.pull_request.number || github.run_id }}\n cancel-in-progress: true\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-generate-check\n uses: tailor-platform/actions/generate-check@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n - id: tailor-plan\n # Fork PRs cannot read secrets; the checks above still run for them.\n if: github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork\n uses: tailor-platform/actions/plan@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n label: __WORKSPACE_NAME__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n github-token: ${{ secrets.GITHUB_TOKEN }}\n # __PLAN_JOB_END__\n # __ERD_PREVIEW_JOB_START__\n tailor-erd-preview-matrix:\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n contents: read\n outputs:\n namespaces: ${{ steps.tailor-erd-preview-matrix.outputs.namespaces }}\n base-app-dir: ${{ steps.tailor-erd-preview-matrix.outputs['base-app-dir'] }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n persist-credentials: false\n - id: tailor-checkout-base\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n repository: ${{ github.event.pull_request.base.repo.full_name }}\n ref: ${{ github.event.pull_request.base.sha }}\n path: .tailor-erd-base\n persist-credentials: false\n - id: tailor-erd-preview-matrix\n shell: bash\n env:\n APP_DIR: \"__APP_DIR__\"\n WORKSPACE_NAME: \"__WORKSPACE_NAME__\"\n run: |\n set -euo pipefail\n node <<'NODE'\n const fs = require(\"node:fs\");\n\n const appDir = process.env.APP_DIR || \".\";\n const workspaceName = process.env.WORKSPACE_NAME;\n const output = process.env.GITHUB_OUTPUT;\n const dirPattern = /^[A-Za-z0-9._/-]+$/;\n const namespacePattern = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;\n\n function lockTarget(file) {\n try {\n const lock = JSON.parse(fs.readFileSync(file, \"utf8\"));\n return lock.targets?.find(\n (candidate) =>\n candidate.kind === \"branch\" &&\n candidate.workspaceName === workspaceName,\n );\n } catch (error) {\n if (error && error.code === \"ENOENT\") return undefined;\n throw error;\n }\n }\n\n function namespacesFromTarget(target, label) {\n const namespaces =\n target?.inputs?.erdPreview === true && Array.isArray(target.inputs?.erdNamespaces)\n ? target.inputs.erdNamespaces\n : [];\n for (const namespace of namespaces) {\n if (typeof namespace !== \"string\" || !namespacePattern.test(namespace)) {\n throw new Error(`Invalid ERD namespace in ${label}: ${String(namespace)}`);\n }\n }\n return namespaces;\n }\n\n function normalizeDir(value, label) {\n const raw = typeof value === \"string\" && value.length > 0 ? value : appDir;\n const normalized =\n raw.replaceAll(\"\\\\\", \"/\").replace(/\\/{2,}/g, \"/\").replace(/^\\.\\//, \"\").replace(/\\/$/, \"\") ||\n \".\";\n if (\n !dirPattern.test(normalized) ||\n normalized.startsWith(\"/\") ||\n normalized.split(\"/\").includes(\"..\")\n ) {\n throw new Error(`Invalid ERD app dir in ${label}: ${raw}`);\n }\n return normalized;\n }\n\n const headTarget = lockTarget(\".github/tailor-sdk.lock\");\n const baseTarget = lockTarget(\".tailor-erd-base/.github/tailor-sdk.lock\");\n const baseAppDir = normalizeDir(baseTarget?.inputs?.dir, \"base setup lock\");\n const namespaces = [\n ...new Set([\n ...namespacesFromTarget(headTarget, \"head setup lock\"),\n ...namespacesFromTarget(baseTarget, \"base setup lock\"),\n ]),\n ].sort((a, b) => a.localeCompare(b));\n\n if (namespaces.length === 0) {\n console.error(\"No ERD preview namespaces found in the head or base setup lock.\");\n process.exit(1);\n }\n\n fs.appendFileSync(output, `namespaces=${JSON.stringify(namespaces)}\\n`);\n fs.appendFileSync(output, `base-app-dir=${baseAppDir}\\n`);\n NODE\n\n tailor-erd-preview:\n needs: tailor-erd-preview-matrix\n if: github.event_name == 'pull_request'\n runs-on: ubuntu-latest\n timeout-minutes: 15\n permissions:\n contents: read\n strategy:\n fail-fast: false\n matrix:\n namespace: ${{ fromJSON(needs.tailor-erd-preview-matrix.outputs.namespaces) }}\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n persist-credentials: false\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-checkout-base\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n with:\n repository: ${{ github.event.pull_request.base.repo.full_name }}\n ref: ${{ github.event.pull_request.base.sha }}\n path: .tailor-erd-base\n persist-credentials: false\n - id: tailor-detect-base-package-manager\n shell: bash\n run: |\n set -euo pipefail\n cd .tailor-erd-base\n base_package_manager=\"$(\n if [ -f pnpm-lock.yaml ]; then\n echo pnpm\n elif [ -f yarn.lock ]; then\n echo yarn\n elif [ -f bun.lockb ] || [ -f bun.lock ]; then\n echo bun\n elif [ -f package-lock.json ]; then\n echo npm\n else\n echo \"__PACKAGE_MANAGER__\"\n fi\n )\"\n echo \"package-manager=$base_package_manager\" >> \"$GITHUB_OUTPUT\"\n if [ \"$base_package_manager\" = \"pnpm\" ]; then\n base_pnpm_version=\"$(node <<'NODE'\n const fs = require(\"node:fs\");\n\n const fallback = \"10\";\n\n function readManifest(file) {\n try {\n return JSON.parse(fs.readFileSync(file, \"utf8\"));\n } catch {\n return {};\n }\n }\n\n function pnpmVersion(manifest) {\n const packageManager = manifest.packageManager;\n if (typeof packageManager === \"string\" && packageManager.startsWith(\"pnpm@\")) {\n return packageManager.slice(\"pnpm@\".length).split(\"+\")[0];\n }\n\n const devPackageManager = manifest.devEngines?.packageManager;\n if (devPackageManager?.name === \"pnpm\" && devPackageManager.version) {\n return devPackageManager.version;\n }\n\n return undefined;\n }\n\n process.stdout.write(\n pnpmVersion(readManifest(\"package.json\")) ?? fallback,\n );\n NODE\n )\"\n echo \"pnpm-version=$base_pnpm_version\" >> \"$GITHUB_OUTPUT\"\n fi\n - id: tailor-setup-base-pnpm\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'pnpm'\n uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9\n with:\n version: ${{ steps.tailor-detect-base-package-manager.outputs['pnpm-version'] }}\n package_json_file: .tailor-erd-base/package.json\n run_install: false\n - id: tailor-setup-base-node\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] != 'bun'\n uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0\n with:\n node-version-file: .tailor-erd-base/package.json\n - id: tailor-setup-base-yarn\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'yarn'\n shell: bash\n working-directory: .tailor-erd-base\n run: |\n set -euo pipefail\n corepack enable\n - id: tailor-setup-base-bun\n if: steps.tailor-detect-base-package-manager.outputs['package-manager'] == 'bun'\n uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0\n - id: tailor-install-base\n shell: bash\n working-directory: .tailor-erd-base\n env:\n BASE_PACKAGE_MANAGER: ${{ steps.tailor-detect-base-package-manager.outputs['package-manager'] }}\n run: |\n set -euo pipefail\n case \"$BASE_PACKAGE_MANAGER\" in\n pnpm) pnpm install --frozen-lockfile ;;\n npm) npm ci ;;\n yarn)\n if grep -q '^__metadata:' yarn.lock; then\n yarn install --immutable\n else\n yarn install --frozen-lockfile\n fi\n ;;\n bun) bun install --frozen-lockfile ;;\n *)\n echo \"::error::Unsupported base package-manager '$BASE_PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n - id: tailor-restore-head-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-build-erd-preview\n shell: bash\n env:\n APP_DIR: \"__APP_DIR__\"\n BASE_APP_DIR: ${{ needs.tailor-erd-preview-matrix.outputs['base-app-dir'] }}\n BASE_PACKAGE_MANAGER: ${{ steps.tailor-detect-base-package-manager.outputs['package-manager'] }}\n NAMESPACE: ${{ matrix.namespace }}\n run: |\n set -euo pipefail\n\n run_tailor_sdk() {\n case \"__PACKAGE_MANAGER__\" in\n pnpm) pnpm exec tailor-sdk \"$@\" ;;\n npm) npx tailor-sdk \"$@\" ;;\n yarn) yarn tailor-sdk \"$@\" ;;\n bun) bunx tailor-sdk \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n }\n\n run_head_node() {\n case \"__PACKAGE_MANAGER__\" in\n pnpm) pnpm exec node \"$@\" ;;\n npm) node \"$@\" ;;\n yarn) yarn node \"$@\" ;;\n bun) node \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n }\n\n tailor_sdk_bin=\"$(\n run_head_node - <<'NODE'\n const path = require(\"node:path\");\n const { createRequire } = require(\"node:module\");\n const appDir = process.env.APP_DIR || \".\";\n const githubWorkspace = process.env.GITHUB_WORKSPACE;\n if (!githubWorkspace) {\n throw new Error(\"GITHUB_WORKSPACE is required.\");\n }\n\n let lastError;\n for (const manifestPath of [\n path.join(githubWorkspace, appDir, \"package.json\"),\n path.join(githubWorkspace, \"package.json\"),\n ]) {\n try {\n const requireFromManifest = createRequire(manifestPath);\n process.stdout.write(\n path.join(\n path.dirname(requireFromManifest.resolve(\"@tailor-platform/sdk/cli\")),\n \"index.mjs\",\n ),\n );\n process.exit(0);\n } catch (error) {\n lastError = error;\n }\n }\n throw lastError || new Error(\"Failed to resolve @tailor-platform/sdk/cli.\");\n NODE\n )\"\n\n run_head_tailor_sdk_bin() {\n local command_cwd=\"$1\"\n shift\n\n run_head_cli_env() {\n env TAILOR_SDK_BIN=\"$tailor_sdk_bin\" TAILOR_SDK_CWD=\"$command_cwd\" \"$@\"\n }\n\n (\n cd \"$GITHUB_WORKSPACE\" || exit 1\n case \"__PACKAGE_MANAGER__\" in\n pnpm) run_head_cli_env pnpm exec node \"$head_cli_runner\" \"$@\" ;;\n npm) run_head_cli_env node \"$head_cli_runner\" \"$@\" ;;\n yarn) run_head_cli_env yarn node \"$head_cli_runner\" \"$@\" ;;\n bun) run_head_cli_env bun \"$head_cli_runner\" \"$@\" ;;\n *)\n echo \"::error::Unsupported package-manager '__PACKAGE_MANAGER__' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n )\n }\n\n run_base_tailor_sdk_bin() {\n local command_cwd=\"$1\"\n shift\n\n run_head_cli_env() {\n env TAILOR_SDK_BIN=\"$tailor_sdk_bin\" TAILOR_SDK_CWD=\"$command_cwd\" \"$@\"\n }\n\n (\n cd \"$command_cwd\" || exit 1\n case \"$BASE_PACKAGE_MANAGER\" in\n pnpm) run_head_cli_env pnpm exec node \"$head_cli_runner\" \"$@\" ;;\n npm) run_head_cli_env node \"$head_cli_runner\" \"$@\" ;;\n yarn) run_head_cli_env yarn node \"$head_cli_runner\" \"$@\" ;;\n bun) run_head_cli_env bun \"$head_cli_runner\" \"$@\" ;;\n *)\n echo \"::error::Unsupported base package-manager '$BASE_PACKAGE_MANAGER' (expected pnpm, npm, yarn, or bun)\"\n exit 1\n ;;\n esac\n )\n }\n\n head_output=\"$RUNNER_TEMP/tailor-erd/head\"\n base_output=\"$RUNNER_TEMP/tailor-erd/base\"\n preview_output=\"$RUNNER_TEMP/tailor-erd/preview\"\n dts_output=\"$RUNNER_TEMP/tailor-erd/dts\"\n head_cli_runner=\"$RUNNER_TEMP/tailor-erd/run-head-tailor-sdk.mjs\"\n head_log=\"$RUNNER_TEMP/tailor-erd/head-$NAMESPACE.log\"\n base_log=\"$RUNNER_TEMP/tailor-erd/base-$NAMESPACE.log\"\n rm -rf \"$head_output\" \"$base_output\" \"$preview_output\" \"$dts_output\"\n mkdir -p \"$head_output\" \"$base_output\" \"$preview_output\" \"$dts_output\"\n cat > \"$head_cli_runner\" <<'NODE'\n import { pathToFileURL } from \"node:url\";\n\n const cliBin = process.env.TAILOR_SDK_BIN;\n const commandCwd = process.env.TAILOR_SDK_CWD;\n if (!cliBin || !commandCwd) {\n throw new Error(\"TAILOR_SDK_BIN and TAILOR_SDK_CWD are required.\");\n }\n\n process.chdir(commandCwd);\n process.argv = [process.argv[0], cliBin, ...process.argv.slice(2)];\n await import(pathToFileURL(cliBin).href);\n NODE\n\n head_config=\"$GITHUB_WORKSPACE/$APP_DIR/tailor.config.ts\"\n base_config=\"$GITHUB_WORKSPACE/.tailor-erd-base/$BASE_APP_DIR/tailor.config.ts\"\n head_html=\"$head_output/$NAMESPACE/dist/index.html\"\n base_html=\"$base_output/$NAMESPACE/dist/index.html\"\n preview_html=\"$preview_output/$NAMESPACE-viewer.html\"\n\n head_missing=\"false\"\n if [ ! -f \"$head_config\" ]; then\n echo \"Head ERD config not found; rendering base objects as removed.\"\n head_missing=\"true\"\n elif ! (\n cd \"$GITHUB_WORKSPACE/$APP_DIR\" || exit 1\n TAILOR_PLATFORM_SDK_DTS_PATH=\"$dts_output/head-$NAMESPACE.d.ts\" \\\n run_tailor_sdk tailordb erd export --config \"$head_config\" --namespace \"$NAMESPACE\" --output \"$head_output\"\n ) >\"$head_log\" 2>&1; then\n if grep -q 'not found in local config.db' \"$head_log\"; then\n cat \"$head_log\"\n echo \"Head ERD namespace '$NAMESPACE' not found; rendering base objects as removed.\"\n head_missing=\"true\"\n else\n cat \"$head_log\"\n exit 1\n fi\n elif [ -s \"$head_log\" ]; then\n cat \"$head_log\"\n fi\n\n base_missing=\"false\"\n if [ ! -f \"$base_config\" ]; then\n echo \"Base ERD config not found; rendering current objects as added.\"\n base_missing=\"true\"\n elif ! (\n TAILOR_PLATFORM_SDK_DTS_PATH=\"$dts_output/base-$NAMESPACE.d.ts\" \\\n run_base_tailor_sdk_bin \"$GITHUB_WORKSPACE/.tailor-erd-base/$BASE_APP_DIR\" tailordb erd export --config \"$base_config\" --namespace \"$NAMESPACE\" --output \"$base_output\"\n ) >\"$base_log\" 2>&1; then\n if grep -q 'not found in local config.db' \"$base_log\"; then\n cat \"$base_log\"\n echo \"Base ERD namespace '$NAMESPACE' not found; rendering current objects as added.\"\n base_missing=\"true\"\n else\n cat \"$base_log\"\n exit 1\n fi\n elif [ -s \"$base_log\" ]; then\n cat \"$base_log\"\n fi\n\n if [ \"$head_missing\" = \"true\" ] && [ \"$base_missing\" = \"true\" ]; then\n echo \"::error::ERD namespace '$NAMESPACE' was not found in head or base.\"\n exit 1\n fi\n if [ \"$head_missing\" = \"false\" ] && [ ! -f \"$head_html\" ]; then\n echo \"::error::Head ERD viewer was not generated at $head_html\"\n exit 1\n fi\n if [ \"$base_missing\" = \"false\" ] && [ ! -f \"$base_html\" ]; then\n echo \"::error::Base ERD viewer was not generated at $base_html\"\n exit 1\n fi\n\n diff_args=(--namespace \"$NAMESPACE\" --output \"$preview_html\")\n if [ \"$head_missing\" = \"false\" ]; then\n diff_args+=(--head-html \"$head_html\")\n fi\n if [ \"$base_missing\" = \"false\" ]; then\n diff_args+=(--base-html \"$base_html\")\n fi\n run_head_tailor_sdk_bin \"$GITHUB_WORKSPACE\" tailordb erd diff \"${diff_args[@]}\"\n - id: tailor-upload-erd-viewer\n uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1\n with:\n name: ${{ matrix.namespace }}-viewer.html\n path: ${{ runner.temp }}/tailor-erd/preview/${{ matrix.namespace }}-viewer.html\n if-no-files-found: error\n archive: false\n retention-days: 7\n\n # __ERD_PREVIEW_JOB_END__\n # __ERD_PREVIEW_COMMENT_JOB_START__\n tailor-erd-preview-comment:\n needs: tailor-erd-preview\n if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository\n runs-on: ubuntu-latest\n timeout-minutes: 5\n permissions:\n actions: read\n pull-requests: write\n steps:\n - id: tailor-comment-erd-preview\n env:\n GH_TOKEN: ${{ github.token }}\n REPO: ${{ github.repository }}\n RUN_ID: ${{ github.run_id }}\n SERVER: ${{ github.server_url }}\n PR_NUMBER: ${{ github.event.pull_request.number }}\n BASE_REF: ${{ github.event.pull_request.base.ref }}\n HEAD_SHA: ${{ github.event.pull_request.head.sha }}\n MARKER: \"<!-- tailor-erd-preview: __WORKSPACE_NAME__ -->\"\n run: |\n set -euo pipefail\n\n current_head=$(gh api \"repos/$REPO/pulls/$PR_NUMBER\" --jq '.head.sha')\n if [ \"$current_head\" != \"$HEAD_SHA\" ]; then\n echo \"Skipping stale ERD preview comment for $HEAD_SHA; current head is $current_head.\"\n exit 0\n fi\n\n body=\"$MARKER\"$'\\n'\"### TailorDB ERD preview (__WORKSPACE_NAME__)\"$'\\n\\n'\n body+=\"Self-contained ERD viewer HTML artifacts for this pull request. Each viewer can switch between the current schema and a diff against base branch \\`$BASE_REF\\`.\"$'\\n'\n\n while IFS=$'\\t' read -r name id; do\n [ -z \"$name\" ] && continue\n if [[ \"$name\" != *-viewer.html ]]; then\n continue\n fi\n ns=${name%-viewer.html}\n label=\"$ns viewer\"\n body+=$'\\n'\"- **$label**: [$name]($SERVER/$REPO/actions/runs/$RUN_ID/artifacts/$id)\"\n done < <(gh api \"repos/$REPO/actions/runs/$RUN_ID/artifacts\" --paginate \\\n --jq '.artifacts[] | select(.name | endswith(\"-viewer.html\")) | [.name, (.id|tostring)] | @tsv' | sort)\n\n existing=$(gh api \"repos/$REPO/issues/$PR_NUMBER/comments\" --paginate \\\n --jq 'map(select(.body | contains(env.MARKER))) | .[0].id // empty')\n if [ -n \"$existing\" ]; then\n gh api --method PATCH \"repos/$REPO/issues/comments/$existing\" -f body=\"$body\" >/dev/null\n else\n gh api --method POST \"repos/$REPO/issues/$PR_NUMBER/comments\" -f body=\"$body\" >/dev/null\n fi\n\n # __ERD_PREVIEW_COMMENT_JOB_END__\n tailor-deploy:\n # __DEPLOY_IF__\n runs-on: ubuntu-latest\n timeout-minutes: 30\n permissions:\n contents: read\n environment: __ENVIRONMENT__\n concurrency:\n group: tailor-deploy-__WORKSPACE_NAME__\n cancel-in-progress: false\n steps:\n - id: tailor-checkout\n uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0\n - id: tailor-setup\n uses: tailor-platform/actions/setup@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n package-manager: __PACKAGE_MANAGER__\n - id: tailor-apply\n uses: tailor-platform/actions/deploy@7817f37adf2891fbb2ebbe0a3b222a5fa2ed0c1f # v1.3.0\n with:\n workspace-id: ${{ vars.TAILOR_PLATFORM_WORKSPACE_ID }}\n package-manager: __PACKAGE_MANAGER__\n # __WORKING_DIRECTORY__\n platform-client-id: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_ID }}\n platform-client-secret: ${{ secrets.TAILOR_PLATFORM_MACHINE_USER_CLIENT_SECRET }}\n";
2712
2826
 
2713
2827
  //#endregion
2714
2828
  //#region src/cli/commands/setup/tag.workflow.yml
@@ -2752,7 +2866,7 @@ function applyCommon(content, params) {
2752
2866
  const { workingDirectory, environment, packageManager } = params;
2753
2867
  let out = line(content, "HEADER", HEADER);
2754
2868
  out = line(out, "WORKING_DIRECTORY", workingDirectory ? `working-directory: ${workingDirectory}` : void 0);
2755
- return out.replaceAll("__WORKSPACE_NAME__", () => params.workspaceName).replaceAll("__ENVIRONMENT__", () => environment).replaceAll("__PACKAGE_MANAGER__", () => packageManager);
2869
+ return out.replaceAll("__WORKSPACE_NAME__", () => params.workspaceName).replaceAll("__ENVIRONMENT__", () => environment).replaceAll("__PACKAGE_MANAGER__", () => packageManager).replaceAll("__APP_DIR__", () => workingDirectory ?? ".");
2756
2870
  }
2757
2871
  /**
2758
2872
  * Render the branch-target deploy workflow.
@@ -2765,8 +2879,11 @@ function applyCommon(content, params) {
2765
2879
  */
2766
2880
  function renderBranchWorkflow(params) {
2767
2881
  const { branch, plan } = params;
2882
+ const erdPreview = plan ? params.erdPreview : null;
2768
2883
  let out = branch_workflow_default;
2769
2884
  out = block(out, "PLAN_JOB", plan);
2885
+ out = block(out, "ERD_PREVIEW_JOB", erdPreview !== null);
2886
+ out = block(out, "ERD_PREVIEW_COMMENT_JOB", erdPreview !== null);
2770
2887
  out = block(out, "PULL_REQUEST", plan);
2771
2888
  out = block(out, "DISPATCH_INPUTS", plan);
2772
2889
  out = line(out, "DEPLOY_IF", plan ? `if: >-\n github.event_name == 'push' ||\n (github.event_name == 'workflow_dispatch' && !inputs['dry-run'])` : void 0);
@@ -2774,6 +2891,7 @@ function renderBranchWorkflow(params) {
2774
2891
  out = applyCommon(out, params).replaceAll("__BRANCH__", () => branch);
2775
2892
  const generatedIds = [];
2776
2893
  if (plan) generatedIds.push("tailor-plan", "tailor-plan/tailor-checkout", "tailor-plan/tailor-setup", "tailor-plan/tailor-generate-check", "tailor-plan/tailor-plan");
2894
+ if (erdPreview) generatedIds.push("tailor-erd-preview-matrix", "tailor-erd-preview-matrix/tailor-checkout", "tailor-erd-preview-matrix/tailor-checkout-base", "tailor-erd-preview-matrix/tailor-erd-preview-matrix", "tailor-erd-preview", "tailor-erd-preview/tailor-checkout", "tailor-erd-preview/tailor-setup", "tailor-erd-preview/tailor-checkout-base", "tailor-erd-preview/tailor-detect-base-package-manager", "tailor-erd-preview/tailor-setup-base-pnpm", "tailor-erd-preview/tailor-setup-base-node", "tailor-erd-preview/tailor-setup-base-yarn", "tailor-erd-preview/tailor-setup-base-bun", "tailor-erd-preview/tailor-install-base", "tailor-erd-preview/tailor-restore-head-setup", "tailor-erd-preview/tailor-build-erd-preview", "tailor-erd-preview/tailor-upload-erd-viewer", "tailor-erd-preview-comment", "tailor-erd-preview-comment/tailor-comment-erd-preview");
2777
2895
  generatedIds.push("tailor-deploy", "tailor-deploy/tailor-checkout", "tailor-deploy/tailor-setup", "tailor-deploy/tailor-apply");
2778
2896
  return {
2779
2897
  content: out,
@@ -2843,6 +2961,15 @@ function findTargetDrift(target, state) {
2843
2961
  rule: "default-branch",
2844
2962
  message: `The workflow triggers on "${target.inputs.branch}" but the repository default branch is now "${state.defaultBranch}". If this is intentional, ignore this; otherwise re-run setup so the trigger matches the default branch.`
2845
2963
  });
2964
+ if (target.kind === "branch" && target.inputs.erdPreview && state.configExists) {
2965
+ const recorded = [...target.inputs.erdNamespaces ?? []].toSorted((a, b) => a.localeCompare(b));
2966
+ const current = state.erdNamespaces?.toSorted((a, b) => a.localeCompare(b)) ?? null;
2967
+ if (current === null || recorded.length !== current.length || recorded.some((namespace, index) => namespace !== current[index])) findings.push({
2968
+ target: id,
2969
+ rule: "erd-namespaces",
2970
+ message: "TailorDB namespaces for ERD preview changed. Re-run setup so the ERD preview matrix is regenerated."
2971
+ });
2972
+ }
2846
2973
  return findings;
2847
2974
  }
2848
2975
  function detectDefaultBranchSafe(cwd, run) {
@@ -2871,6 +2998,10 @@ function readHash(absFile) {
2871
2998
  return null;
2872
2999
  }
2873
3000
  }
3001
+ async function defaultLoadErdNamespaces$1(configPath) {
3002
+ const { config } = await loadConfig(configPath);
3003
+ return extractOwnedNamespaces(config);
3004
+ }
2874
3005
  /**
2875
3006
  * Audit the generated workflows for drift against the current config/repo
2876
3007
  * state. Read-only: never writes files, the lock, or the config.
@@ -2880,24 +3011,28 @@ function readHash(absFile) {
2880
3011
  * (per-rule ignore / continue-on-error); the CLI itself reports via exit code.
2881
3012
  * @param options - Check options
2882
3013
  */
2883
- function checkGitHub(options) {
3014
+ async function checkGitHub(options) {
2884
3015
  logBetaWarning("setup");
2885
3016
  const { outputDir } = options;
2886
3017
  const lock = readLock(outputDir);
2887
3018
  if (!lock || lock.targets.length === 0) throw new Error("No managed workflows found (.github/tailor-sdk.lock is missing or empty). Run `tailor-sdk setup` first.");
2888
3019
  const exists = options.configExistsAt ?? ((p) => fs$1.existsSync(p));
2889
3020
  const defaultBranch = detectDefaultBranchSafe(outputDir, options.gitRunner);
3021
+ const loadErdNamespaces = options.loadErdNamespaces ?? defaultLoadErdNamespaces$1;
2890
3022
  const findings = [];
2891
3023
  for (const target of lock.targets) {
2892
3024
  const absFile = resolveWithinRoot(outputDir, target.file);
2893
3025
  const currentHash = absFile === null ? null : readHash(absFile);
2894
3026
  const configAbs = resolveWithinRoot(outputDir, path.join(target.inputs.dir, "tailor.config.ts"));
3027
+ const configExists = configAbs !== null && exists(configAbs);
3028
+ const erdNamespaces = target.kind === "branch" && target.inputs.erdPreview && configAbs !== null && configExists ? await loadErdNamespaces(configAbs) : null;
2895
3029
  findings.push(...findTargetDrift(target, {
2896
3030
  fileExists: currentHash !== null,
2897
3031
  currentHash,
2898
- configExists: configAbs !== null && exists(configAbs),
3032
+ configExists,
2899
3033
  defaultBranch,
2900
- templateVersion: 2
3034
+ templateVersion: 5,
3035
+ erdNamespaces
2901
3036
  }));
2902
3037
  }
2903
3038
  const count = lock.targets.length;
@@ -2915,6 +3050,10 @@ async function defaultLoadConfigName(configPath) {
2915
3050
  const { config } = await loadConfig(configPath);
2916
3051
  return config.name;
2917
3052
  }
3053
+ async function defaultLoadErdNamespaces(configPath) {
3054
+ const { config } = await loadConfig(configPath);
3055
+ return extractOwnedNamespaces(config);
3056
+ }
2918
3057
  const WORKSPACE_NAME_RE = /^[a-z0-9-]+$/;
2919
3058
  function validateWorkspaceName(name) {
2920
3059
  if (name.length < 3 || name.length > 63 || !WORKSPACE_NAME_RE.test(name) || name.startsWith("-") || name.endsWith("-")) throw new Error(`Invalid workspace name "${name}". Names must be 3-63 characters of lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen. Pass a valid name with --workspace-name.`);
@@ -2935,6 +3074,10 @@ const DIR_RE = /^[A-Za-z0-9._/-]+$/;
2935
3074
  function validateDir(dir) {
2936
3075
  if (!DIR_RE.test(dir)) throw new Error(`Invalid --dir "${dir}". Only letters, numbers, ".", "_", "/", and "-" are supported.`);
2937
3076
  }
3077
+ const ERD_NAMESPACE_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
3078
+ function validateErdNamespaces(namespaces) {
3079
+ for (const namespace of namespaces) if (!ERD_NAMESPACE_RE.test(namespace)) throw new Error(`TailorDB namespace "${namespace}" cannot be used in --erd-preview. Only letters, numbers, '.', '_', and '-' are supported, and the name must start with a letter or number.`);
3080
+ }
2938
3081
  function escapesRoot(rel) {
2939
3082
  return rel === ".." || rel.startsWith(`..${path.sep}`) || rel.startsWith("../") || path.isAbsolute(rel);
2940
3083
  }
@@ -2968,6 +3111,7 @@ function resolveConfigPath(outputDir, dir) {
2968
3111
  */
2969
3112
  async function resolve$1(options) {
2970
3113
  if (options.tag && !options.plan) throw new Error("--no-plan cannot be combined with --tag (tag targets always run plan before deploy). Drop --no-plan or use a branch target.");
3114
+ if (options.erdPreview && (options.tag || !options.plan)) throw new Error("--erd-preview requires a branch target with plan enabled.");
2971
3115
  const dir = options.dir.replaceAll("\\", "/").replace(/\/{2,}/g, "/").replace(/^\.\//, "").replace(/\/$/, "") || ".";
2972
3116
  validateDir(dir);
2973
3117
  const workingDirectory = dir !== "." ? dir : void 0;
@@ -2984,6 +3128,12 @@ async function resolve$1(options) {
2984
3128
  let branch = null;
2985
3129
  let branchAutoDetected = false;
2986
3130
  let render;
3131
+ let erdNamespaces = [];
3132
+ if (options.erdPreview) {
3133
+ erdNamespaces = await (options.loadErdNamespaces ?? defaultLoadErdNamespaces)(configPath);
3134
+ if (erdNamespaces.length === 0) throw new Error("No TailorDB namespaces found for --erd-preview. Define owned db namespaces in tailor.config.ts.");
3135
+ validateErdNamespaces(erdNamespaces);
3136
+ }
2987
3137
  if (kind === "branch") {
2988
3138
  branchAutoDetected = options.branch === void 0;
2989
3139
  branch = options.branch ?? detectDefaultBranch(options.outputDir, options.gitRunner);
@@ -2994,7 +3144,8 @@ async function resolve$1(options) {
2994
3144
  workingDirectory,
2995
3145
  environment,
2996
3146
  packageManager,
2997
- plan: options.plan
3147
+ plan: options.plan,
3148
+ erdPreview: options.erdPreview ? { namespaces: erdNamespaces } : null
2998
3149
  });
2999
3150
  } else {
3000
3151
  branch = options.branch ?? null;
@@ -3016,7 +3167,9 @@ async function resolve$1(options) {
3016
3167
  environment,
3017
3168
  dir,
3018
3169
  packageManager,
3019
- plan: kind === "branch" ? options.plan : true
3170
+ plan: kind === "branch" ? options.plan : true,
3171
+ erdPreview: kind === "branch" ? options.erdPreview : false,
3172
+ erdNamespaces: kind === "branch" && options.erdPreview ? erdNamespaces : void 0
3020
3173
  };
3021
3174
  return {
3022
3175
  kind,
@@ -3024,6 +3177,7 @@ async function resolve$1(options) {
3024
3177
  branch,
3025
3178
  environment,
3026
3179
  packageManager,
3180
+ erdNamespaces,
3027
3181
  render,
3028
3182
  inputs,
3029
3183
  file,
@@ -3128,7 +3282,7 @@ async function setupGitHub(options) {
3128
3282
  kind: resolved.kind,
3129
3283
  workspaceName: resolved.workspaceName,
3130
3284
  file: resolved.file,
3131
- templateVersion: 2,
3285
+ templateVersion: 5,
3132
3286
  inputs: resolved.inputs,
3133
3287
  generatedIds: resolved.render.generatedIds,
3134
3288
  ejectedIds: existing?.ejectedIds ?? [],
@@ -3157,8 +3311,8 @@ const checkCommand = defineAppCommand({
3157
3311
  name: "check",
3158
3312
  description: "Audit generated workflows for drift against the current config/repo (read-only).",
3159
3313
  args: z.object({}).strict(),
3160
- run: () => {
3161
- checkGitHub({ outputDir: process.cwd() });
3314
+ run: async () => {
3315
+ await checkGitHub({ outputDir: process.cwd() });
3162
3316
  }
3163
3317
  });
3164
3318
  const setupCommand = defineAppCommand({
@@ -3178,6 +3332,7 @@ const setupCommand = defineAppCommand({
3178
3332
  "tag-pattern": arg(z.string().min(1).optional(), { description: "Tag glob to match (requires --tag; defaults to v*)" }),
3179
3333
  environment: arg(z.string().min(1).optional(), { description: "GitHub Environment for the plan/deploy jobs (defaults to the workspace name)" }),
3180
3334
  "no-plan": arg(z.boolean().default(false), { description: "Disable the plan job for a branch target (cannot be combined with --tag)" }),
3335
+ "erd-preview": arg(z.boolean().default(false), { description: "Add PR ERD viewer artifacts with current/diff previews for TailorDB namespaces" }),
3181
3336
  dir: arg(z.string().min(1).default("."), {
3182
3337
  alias: "d",
3183
3338
  description: "App directory (for monorepo setups)"
@@ -3195,6 +3350,7 @@ const setupCommand = defineAppCommand({
3195
3350
  tagPattern: args["tag-pattern"] ?? "v*",
3196
3351
  environment: args.environment,
3197
3352
  plan: !args["no-plan"],
3353
+ erdPreview: args["erd-preview"],
3198
3354
  dir: args.dir,
3199
3355
  force: args.force,
3200
3356
  outputDir: process.cwd()
@@ -3972,6 +4128,9 @@ async function initErdDeployContext(args) {
3972
4128
  const VIEWER_ASSETS_DIR = "erd-viewer-assets";
3973
4129
  const STYLES_LINK = "<link rel=\"stylesheet\" href=\"./styles.css\" />";
3974
4130
  const APP_SCRIPT = "<script src=\"./app.js\" type=\"module\"><\/script>";
4131
+ function escapeHtml(value) {
4132
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
4133
+ }
3975
4134
  function assetDirCandidates() {
3976
4135
  const currentDir = path.dirname(fileURLToPath(import.meta.url));
3977
4136
  return [
@@ -3994,8 +4153,8 @@ function resolveViewerAssetsDir() {
3994
4153
  * are inlined as separately extractable blocks: a `<style>` element, a
3995
4154
  * `<script type="module">`, and a `<script type="application/json"
3996
4155
  * id="erd-schema">` data block. This renders without any sibling asset files
3997
- * and lets external tooling (e.g. a future ERD diff) pull out the schema via
3998
- * `JSON.parse`.
4156
+ * and lets external tooling (including the ERD diff command) pull out the
4157
+ * schema via `JSON.parse`.
3999
4158
  * @param options - Viewer build options.
4000
4159
  * @returns The self-contained HTML document.
4001
4160
  */
@@ -4006,8 +4165,10 @@ function buildViewerHtml(options) {
4006
4165
  const appJs = fs$1.readFileSync(path.join(assetsDir, "app.js"), "utf8");
4007
4166
  if (!html.includes(STYLES_LINK) || !html.includes(APP_SCRIPT)) throw new Error("ERD viewer index.html is missing expected asset references for inlining.");
4008
4167
  const embedScript = `<script type="application/json" id="erd-schema">${JSON.stringify(options.schema).replaceAll("<", "\\u003c")}<\/script>`;
4168
+ const currentSchemaScript = options.currentSchema === void 0 ? "" : `\n <script type="application/json" id="erd-current-schema">${JSON.stringify(options.currentSchema).replaceAll("<", "\\u003c")}<\/script>`;
4169
+ const diffScript = options.diff === void 0 ? "" : `\n <script type="application/json" id="erd-diff">${JSON.stringify(options.diff).replaceAll("<", "\\u003c")}<\/script>`;
4009
4170
  const inlineScript = `<script type="module">\n${appJs.replace(/<\/script/gi, "<\\/script")}\n<\/script>`;
4010
- return html.replace(STYLES_LINK, `<style>\n${css}\n</style>`).replace(APP_SCRIPT, `${embedScript}\n ${inlineScript}`);
4171
+ return html.replace("<title>TailorDB ERD</title>", `<title>${escapeHtml(options.title ?? "TailorDB ERD")}</title>`).replace(STYLES_LINK, `<style>\n${css}\n</style>`).replace(APP_SCRIPT, `${embedScript}${currentSchemaScript}${diffScript}\n ${inlineScript}`);
4011
4172
  }
4012
4173
  /**
4013
4174
  * Write the self-contained TailorDB ERD viewer to `<distDir>/index.html`.
@@ -4177,6 +4338,362 @@ const erdDeployCommand = defineAppCommand({
4177
4338
  }
4178
4339
  });
4179
4340
 
4341
+ //#endregion
4342
+ //#region src/cli/commands/tailordb/erd/diff.ts
4343
+ const SCHEMA_BLOCK_PATTERN = /<script type="application\/json" id="erd-schema">([\s\S]*?)<\/script>/;
4344
+ function stableValue(value) {
4345
+ if (Array.isArray(value)) return value.map((item) => stableValue(item));
4346
+ if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, stableValue(item)]));
4347
+ return value;
4348
+ }
4349
+ function stableJson(value) {
4350
+ return JSON.stringify(stableValue(value));
4351
+ }
4352
+ function changedFields(base, head) {
4353
+ return [.../* @__PURE__ */ new Set([...Object.keys(base), ...Object.keys(head)])].toSorted((a, b) => a.localeCompare(b)).filter((key) => stableJson(base[key]) !== stableJson(head[key]));
4354
+ }
4355
+ function detailForChangedFields(fields) {
4356
+ return `Changed fields: ${fields.join(", ")}`;
4357
+ }
4358
+ function mapByName(items) {
4359
+ return new Map(items.map((item) => [item.name, item]));
4360
+ }
4361
+ function compareCommonNamed(baseItems, headItems, addChange) {
4362
+ const baseByName = mapByName(baseItems);
4363
+ const headByName = mapByName(headItems);
4364
+ const commonNames = [...baseByName.keys()].filter((name) => headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
4365
+ for (const name of commonNames) {
4366
+ const base = baseByName.get(name);
4367
+ const head = headByName.get(name);
4368
+ if (!base || !head) continue;
4369
+ const fields = changedFields(base, head);
4370
+ if (fields.length > 0) addChange(name, fields);
4371
+ }
4372
+ }
4373
+ function removedNames(baseItems, headItems) {
4374
+ const headByName = mapByName(headItems);
4375
+ return baseItems.map((item) => item.name).filter((name) => !headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
4376
+ }
4377
+ function addedNames(baseItems, headItems) {
4378
+ const baseByName = mapByName(baseItems);
4379
+ return headItems.map((item) => item.name).filter((name) => !baseByName.has(name)).toSorted((a, b) => a.localeCompare(b));
4380
+ }
4381
+ function tableMetadata(table) {
4382
+ return {
4383
+ backwardRelationships: table.backwardRelationships,
4384
+ description: table.description,
4385
+ forwardRelationships: table.forwardRelationships,
4386
+ pluralForm: table.pluralForm,
4387
+ source: table.source
4388
+ };
4389
+ }
4390
+ function pushChange(changes, change) {
4391
+ changes.push(change);
4392
+ }
4393
+ function diffColumns(changes, tableName, baseColumns, headColumns) {
4394
+ compareCommonNamed(baseColumns, headColumns, (name, fields) => {
4395
+ pushChange(changes, {
4396
+ action: "changed",
4397
+ entity: "column",
4398
+ path: `${tableName}.${name}`,
4399
+ detail: detailForChangedFields(fields)
4400
+ });
4401
+ });
4402
+ for (const name of removedNames(baseColumns, headColumns)) pushChange(changes, {
4403
+ action: "removed",
4404
+ entity: "column",
4405
+ path: `${tableName}.${name}`,
4406
+ detail: "Column removed"
4407
+ });
4408
+ for (const name of addedNames(baseColumns, headColumns)) pushChange(changes, {
4409
+ action: "added",
4410
+ entity: "column",
4411
+ path: `${tableName}.${name}`,
4412
+ detail: "Column added"
4413
+ });
4414
+ }
4415
+ function diffIndexes(changes, tableName, baseIndexes, headIndexes) {
4416
+ compareCommonNamed(baseIndexes, headIndexes, (name, fields) => {
4417
+ pushChange(changes, {
4418
+ action: "changed",
4419
+ entity: "index",
4420
+ path: `${tableName}.${name}`,
4421
+ detail: detailForChangedFields(fields)
4422
+ });
4423
+ });
4424
+ for (const name of removedNames(baseIndexes, headIndexes)) pushChange(changes, {
4425
+ action: "removed",
4426
+ entity: "index",
4427
+ path: `${tableName}.${name}`,
4428
+ detail: "Index removed"
4429
+ });
4430
+ for (const name of addedNames(baseIndexes, headIndexes)) pushChange(changes, {
4431
+ action: "added",
4432
+ entity: "index",
4433
+ path: `${tableName}.${name}`,
4434
+ detail: "Index added"
4435
+ });
4436
+ }
4437
+ function diffTables(changes, baseTables, headTables) {
4438
+ const baseByName = mapByName(baseTables);
4439
+ const headByName = mapByName(headTables);
4440
+ for (const name of removedNames(baseTables, headTables)) pushChange(changes, {
4441
+ action: "removed",
4442
+ entity: "table",
4443
+ path: name,
4444
+ detail: "Table removed"
4445
+ });
4446
+ for (const name of addedNames(baseTables, headTables)) pushChange(changes, {
4447
+ action: "added",
4448
+ entity: "table",
4449
+ path: name,
4450
+ detail: "Table added"
4451
+ });
4452
+ const commonNames = [...baseByName.keys()].filter((name) => headByName.has(name)).toSorted((a, b) => a.localeCompare(b));
4453
+ for (const name of commonNames) {
4454
+ const base = baseByName.get(name);
4455
+ const head = headByName.get(name);
4456
+ if (!base || !head) continue;
4457
+ const tableFields = changedFields(tableMetadata(base), tableMetadata(head));
4458
+ if (tableFields.length > 0) pushChange(changes, {
4459
+ action: "changed",
4460
+ entity: "table",
4461
+ path: name,
4462
+ detail: detailForChangedFields(tableFields)
4463
+ });
4464
+ diffColumns(changes, name, base.columns, head.columns);
4465
+ diffIndexes(changes, name, base.indexes, head.indexes);
4466
+ }
4467
+ }
4468
+ function diffRelations(changes, baseRelations, headRelations) {
4469
+ compareCommonNamed(baseRelations, headRelations, (name, fields) => {
4470
+ pushChange(changes, {
4471
+ action: "changed",
4472
+ entity: "relation",
4473
+ path: name,
4474
+ detail: detailForChangedFields(fields)
4475
+ });
4476
+ });
4477
+ for (const name of removedNames(baseRelations, headRelations)) pushChange(changes, {
4478
+ action: "removed",
4479
+ entity: "relation",
4480
+ path: name,
4481
+ detail: "Relation removed"
4482
+ });
4483
+ for (const name of addedNames(baseRelations, headRelations)) pushChange(changes, {
4484
+ action: "added",
4485
+ entity: "relation",
4486
+ path: name,
4487
+ detail: "Relation added"
4488
+ });
4489
+ }
4490
+ function summarize(changes) {
4491
+ return {
4492
+ added: changes.filter((change) => change.action === "added").length,
4493
+ changed: changes.filter((change) => change.action === "changed").length,
4494
+ removed: changes.filter((change) => change.action === "removed").length
4495
+ };
4496
+ }
4497
+ function extractEmbeddedErdSchema(html) {
4498
+ const match = SCHEMA_BLOCK_PATTERN.exec(html);
4499
+ if (!match?.[1]) throw new Error("ERD schema block not found.");
4500
+ try {
4501
+ return JSON.parse(match[1]);
4502
+ } catch (error) {
4503
+ throw new Error(`Failed to parse ERD schema block: ${String(error)}`, { cause: error });
4504
+ }
4505
+ }
4506
+ function createEmptyErdSchema(options) {
4507
+ return {
4508
+ version: 1,
4509
+ namespace: options.namespace,
4510
+ generatedAt: (/* @__PURE__ */ new Date(0)).toISOString(),
4511
+ revision: options.revision,
4512
+ source: "local",
4513
+ cleanRoom: {
4514
+ implementation: "tailor-sdk",
4515
+ notes: ["Synthetic empty schema used for ERD diff generation."]
4516
+ },
4517
+ tables: [],
4518
+ relations: []
4519
+ };
4520
+ }
4521
+ function buildErdSchemaDiff(options) {
4522
+ const { base, head } = options;
4523
+ if (base.namespace !== head.namespace) throw new Error(`Cannot diff ERD schemas from different namespaces: ${base.namespace}, ${head.namespace}`);
4524
+ const changes = [];
4525
+ diffTables(changes, base.tables, head.tables);
4526
+ diffRelations(changes, base.relations, head.relations);
4527
+ const summary = summarize(changes);
4528
+ return {
4529
+ namespace: head.namespace,
4530
+ baseRevision: base.revision,
4531
+ headRevision: head.revision,
4532
+ changed: changes.length > 0,
4533
+ summary,
4534
+ changes
4535
+ };
4536
+ }
4537
+ function mergeNamedByHead(baseItems, headItems) {
4538
+ const headByName = mapByName(headItems);
4539
+ const removedItems = baseItems.filter((item) => !headByName.has(item.name)).toSorted((a, b) => a.name.localeCompare(b.name));
4540
+ return [...headItems, ...removedItems];
4541
+ }
4542
+ function mergeDiffViewerTable(baseTable, headTable) {
4543
+ return {
4544
+ ...headTable,
4545
+ columns: mergeNamedByHead(baseTable.columns, headTable.columns),
4546
+ indexes: mergeNamedByHead(baseTable.indexes, headTable.indexes)
4547
+ };
4548
+ }
4549
+ function mergeDiffViewerTables(baseTables, headTables) {
4550
+ const baseByName = mapByName(baseTables);
4551
+ const headByName = mapByName(headTables);
4552
+ const merged = headTables.map((headTable) => {
4553
+ const baseTable = baseByName.get(headTable.name);
4554
+ return baseTable ? mergeDiffViewerTable(baseTable, headTable) : headTable;
4555
+ });
4556
+ const removedTables = baseTables.filter((table) => !headByName.has(table.name)).toSorted((a, b) => a.name.localeCompare(b.name));
4557
+ return [...merged, ...removedTables];
4558
+ }
4559
+ /**
4560
+ * Build the schema rendered by the diff viewer. The ordinary ERD viewer only
4561
+ * knows how to draw objects present in its schema, so the diff schema keeps
4562
+ * base-only tables, columns, indexes, and relations visible for removal
4563
+ * highlighting while preferring head-side metadata for unchanged objects.
4564
+ * @param options - Base and head schemas to merge for diff rendering.
4565
+ * @returns A TailorDB ERD schema suitable for the visual diff viewer.
4566
+ */
4567
+ function buildErdDiffViewerSchema(options) {
4568
+ const { base, head } = options;
4569
+ if (base.namespace !== head.namespace) throw new Error(`Cannot diff ERD schemas from different namespaces: ${base.namespace}, ${head.namespace}`);
4570
+ return {
4571
+ ...head,
4572
+ tables: mergeDiffViewerTables(base.tables, head.tables),
4573
+ relations: mergeNamedByHead(base.relations, head.relations)
4574
+ };
4575
+ }
4576
+ function renderErdDiffHtml(options) {
4577
+ return buildViewerHtml({
4578
+ schema: options.schema,
4579
+ currentSchema: options.currentSchema,
4580
+ diff: options.diff,
4581
+ title: `TailorDB ERD diff - ${options.diff.namespace}`
4582
+ });
4583
+ }
4584
+
4585
+ //#endregion
4586
+ //#region src/cli/commands/tailordb/erd/diff-command.ts
4587
+ function readSchema(filePath) {
4588
+ if (!filePath) return void 0;
4589
+ return extractEmbeddedErdSchema(fs$1.readFileSync(filePath, "utf8"));
4590
+ }
4591
+ function resolveNamespace(options) {
4592
+ const namespace = options.namespace ?? options.head?.namespace ?? options.base?.namespace;
4593
+ if (!namespace) throw new Error("Missing --namespace when one side of the ERD diff is omitted.");
4594
+ if (options.base && options.base.namespace !== namespace) throw new Error(`Base ERD namespace "${options.base.namespace}" does not match "${namespace}".`);
4595
+ if (options.head && options.head.namespace !== namespace) throw new Error(`Head ERD namespace "${options.head.namespace}" does not match "${namespace}".`);
4596
+ return namespace;
4597
+ }
4598
+ function writeFile(filePath, content) {
4599
+ fs$1.mkdirSync(path.dirname(filePath), { recursive: true });
4600
+ fs$1.writeFileSync(filePath, content, "utf8");
4601
+ }
4602
+ function writeErdDiff(options) {
4603
+ const base = readSchema(options.baseHtml);
4604
+ const head = readSchema(options.headHtml);
4605
+ if (!base && !head) throw new Error("At least one of --base-html or --head-html is required.");
4606
+ const namespace = resolveNamespace({
4607
+ namespace: options.namespace,
4608
+ base,
4609
+ head
4610
+ });
4611
+ const baseSchema = base ?? createEmptyErdSchema({
4612
+ namespace,
4613
+ revision: "missing-base"
4614
+ });
4615
+ const headSchema = head ?? createEmptyErdSchema({
4616
+ namespace,
4617
+ revision: "missing-head"
4618
+ });
4619
+ const diff = buildErdSchemaDiff({
4620
+ base: baseSchema,
4621
+ head: headSchema
4622
+ });
4623
+ const viewerSchema = buildErdDiffViewerSchema({
4624
+ base: baseSchema,
4625
+ head: headSchema
4626
+ });
4627
+ writeFile(options.outputHtml, renderErdDiffHtml({
4628
+ schema: viewerSchema,
4629
+ currentSchema: headSchema,
4630
+ diff
4631
+ }));
4632
+ if (options.outputJson) writeFile(options.outputJson, `${JSON.stringify(diff, null, 2)}\n`);
4633
+ return {
4634
+ namespace,
4635
+ outputHtml: options.outputHtml,
4636
+ outputJson: options.outputJson,
4637
+ diff
4638
+ };
4639
+ }
4640
+ const erdDiffCommand = defineAppCommand({
4641
+ name: "diff",
4642
+ description: "Render TailorDB ERD schema diff HTML from exported ERD viewers.",
4643
+ args: z.object({
4644
+ "base-html": arg(z.string().optional(), {
4645
+ description: "Base ERD viewer HTML file",
4646
+ completion: {
4647
+ type: "file",
4648
+ matcher: [".html"]
4649
+ }
4650
+ }),
4651
+ "head-html": arg(z.string().optional(), {
4652
+ description: "Head ERD viewer HTML file",
4653
+ completion: {
4654
+ type: "file",
4655
+ matcher: [".html"]
4656
+ }
4657
+ }),
4658
+ namespace: arg(z.string().optional(), {
4659
+ alias: "n",
4660
+ description: "TailorDB namespace name (defaults to the provided ERD schema namespace)"
4661
+ }),
4662
+ output: arg(z.string().min(1), {
4663
+ alias: "o",
4664
+ description: "Output ERD diff HTML file",
4665
+ completion: {
4666
+ type: "file",
4667
+ matcher: [".html"]
4668
+ }
4669
+ }),
4670
+ "output-json": arg(z.string().optional(), {
4671
+ description: "Optional output JSON file for the computed diff",
4672
+ completion: {
4673
+ type: "file",
4674
+ matcher: [".json"]
4675
+ }
4676
+ })
4677
+ }).strict(),
4678
+ run: (args) => {
4679
+ initErdCommand();
4680
+ const result = writeErdDiff({
4681
+ baseHtml: args["base-html"],
4682
+ headHtml: args["head-html"],
4683
+ namespace: args.namespace,
4684
+ outputHtml: args.output,
4685
+ outputJson: args["output-json"]
4686
+ });
4687
+ if (args.json) logger.out({
4688
+ namespace: result.namespace,
4689
+ outputHtml: result.outputHtml,
4690
+ outputJson: result.outputJson,
4691
+ summary: result.diff.summary
4692
+ });
4693
+ else logger.success(`Wrote ERD diff to ${path.relative(process.cwd(), result.outputHtml)}`);
4694
+ }
4695
+ });
4696
+
4180
4697
  //#endregion
4181
4698
  //#region src/cli/commands/tailordb/erd/serve.ts
4182
4699
  const DEFAULT_ERD_BASE_DIR = ".tailor-sdk/erd";
@@ -4537,6 +5054,7 @@ const erdCommand = defineCommand({
4537
5054
  description: "Generate TailorDB ERD viewer artifacts from local TailorDB schema. (beta)",
4538
5055
  subCommands: {
4539
5056
  export: erdExportCommand,
5057
+ diff: erdDiffCommand,
4540
5058
  serve: erdServeCommand,
4541
5059
  deploy: erdDeployCommand
4542
5060
  }
@@ -4873,7 +5391,7 @@ async function fetchRemoteTypes(client, workspaceId, namespace) {
4873
5391
  async function assertMigrationsReproduceLocalTypes(loaded, target) {
4874
5392
  const { config, plugins } = loaded;
4875
5393
  const pluginManager = plugins.length > 0 ? new PluginManager(plugins) : void 0;
4876
- const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-Br48NXBD.mjs");
5394
+ const { defineApplication, generatePluginFilesIfNeeded } = await import("../application-BakHtldG.mjs");
4877
5395
  const application = defineApplication({
4878
5396
  config,
4879
5397
  pluginManager
@@ -5179,25 +5697,52 @@ const currentCommand = defineAppCommand({
5179
5697
  args: z.object({}).strict(),
5180
5698
  run: async () => {
5181
5699
  const config = await readPlatformConfig();
5700
+ const profile = process.env.TAILOR_PLATFORM_PROFILE;
5701
+ const profileEntry = profile ? config.profiles[profile] : void 0;
5702
+ if (profile && !profileEntry) throw new Error(`Profile "${profile}" not found`);
5703
+ const platformConfig = profileEntry ? platformConfigFromProfile(profileEntry) : void 0;
5704
+ const currentUser = profile ? profileEntry?.user ?? null : config.current_user;
5182
5705
  const jsonOutput = logger.jsonMode;
5183
- if (!config.current_user) throw new Error(multiline`
5706
+ if (!currentUser) throw new Error(multiline`
5184
5707
  Current user not set.
5185
5708
  Please login first using 'tailor-sdk login' command to register a user.
5186
5709
  `);
5187
- if (!config.users[config.current_user]) throw new Error(multiline`
5188
- Current user '${config.current_user}' not found in registered users.
5710
+ if (!hasUserTokenEntry(config, currentUser, platformConfig)) throw new Error(multiline`
5711
+ Current user '${currentUser}' not found in registered users.
5189
5712
  Please login again using 'tailor-sdk login' command to register the user.
5190
5713
  `);
5191
5714
  if (jsonOutput) {
5192
- logger.out({ user: config.current_user });
5715
+ logger.out({ user: currentUser });
5193
5716
  return;
5194
5717
  }
5195
- logger.out(config.current_user);
5718
+ logger.out(currentUser);
5196
5719
  }
5197
5720
  });
5198
5721
 
5199
5722
  //#endregion
5200
5723
  //#region src/cli/commands/user/list.ts
5724
+ function activeCurrentUserKey(config) {
5725
+ const activeProfile = process.env.TAILOR_PLATFORM_PROFILE;
5726
+ if (!activeProfile) {
5727
+ if (!config.current_user) return null;
5728
+ return resolveUserTokenKey(config, config.current_user);
5729
+ }
5730
+ const profile = config.profiles[activeProfile];
5731
+ if (!profile) return null;
5732
+ return resolveUserTokenKey(config, profile.user, platformConfigFromProfile(profile));
5733
+ }
5734
+ function toUserListInfo(userKey, currentUserKey) {
5735
+ const separatorIndex = userKey.indexOf("|");
5736
+ const platformUrl = separatorIndex === -1 ? null : userKey.slice(0, separatorIndex);
5737
+ return {
5738
+ user: separatorIndex === -1 ? userKey : userKey.slice(separatorIndex + 1),
5739
+ platformUrl,
5740
+ current: currentUserKey === userKey
5741
+ };
5742
+ }
5743
+ function formatUserListInfo(info) {
5744
+ return `${info.user}${info.platformUrl ? ` [${info.platformUrl}]` : ""}${info.current ? " (current)" : ""}`;
5745
+ }
5201
5746
  const listCommand$1 = defineAppCommand({
5202
5747
  name: "list",
5203
5748
  description: "List all users.",
@@ -5214,13 +5759,15 @@ const listCommand$1 = defineAppCommand({
5214
5759
  if (jsonOutput) logger.out([]);
5215
5760
  return;
5216
5761
  }
5762
+ const currentUserKey = activeCurrentUserKey(config);
5763
+ const userInfos = users.map((user) => toUserListInfo(user, currentUserKey));
5217
5764
  if (jsonOutput) {
5218
- logger.out(users);
5765
+ logger.out([...new Set(userInfos.map((userInfo) => userInfo.user))]);
5219
5766
  return;
5220
5767
  }
5221
- users.forEach((user) => {
5222
- if (user === config.current_user) logger.success(`${user} (current)`, { mode: "plain" });
5223
- else logger.log(user);
5768
+ userInfos.forEach((userInfo) => {
5769
+ if (userInfo.current) logger.success(formatUserListInfo(userInfo), { mode: "plain" });
5770
+ else logger.log(formatUserListInfo(userInfo));
5224
5771
  });
5225
5772
  }
5226
5773
  });
@@ -5281,6 +5828,24 @@ function printCreatedToken(name, token, write, action) {
5281
5828
  `);
5282
5829
  }
5283
5830
 
5831
+ //#endregion
5832
+ //#region src/cli/commands/user/pat/user.ts
5833
+ function resolvePatUser(config) {
5834
+ const activeProfile = process.env.TAILOR_PLATFORM_PROFILE;
5835
+ if (activeProfile) return config.profiles[activeProfile]?.user ?? null;
5836
+ return config.current_user;
5837
+ }
5838
+ async function createPatOperatorClient() {
5839
+ const config = await readPlatformConfig();
5840
+ const activeProfile = process.env.TAILOR_PLATFORM_PROFILE;
5841
+ const profileEntry = activeProfile ? config.profiles[activeProfile] : void 0;
5842
+ if (activeProfile && !profileEntry) throw new Error(`Profile "${activeProfile}" not found`);
5843
+ const platformConfig = profileEntry ? platformConfigFromProfile(profileEntry) : void 0;
5844
+ const user = resolvePatUser(config);
5845
+ if (!user) throw new Error("No user logged in.\nPlease login first using 'tailor-sdk login' command.");
5846
+ return await initOperatorClient(await fetchLatestToken(config, user, platformConfig), platformConfig);
5847
+ }
5848
+
5284
5849
  //#endregion
5285
5850
  //#region src/cli/commands/user/pat/create.ts
5286
5851
  const createCommand = defineAppCommand({
@@ -5298,12 +5863,7 @@ const createCommand = defineAppCommand({
5298
5863
  }).strict(),
5299
5864
  run: async (args) => {
5300
5865
  await assertWritable();
5301
- const config = await readPlatformConfig();
5302
- if (!config.current_user) throw new Error(multiline`
5303
- No user logged in.
5304
- Please login first using 'tailor-sdk login' command.
5305
- `);
5306
- const client = await initOperatorClient(await fetchLatestToken(config, config.current_user));
5866
+ const client = await createPatOperatorClient();
5307
5867
  const scopes = getScopesFromWriteFlag(args.write);
5308
5868
  const result = await client.createPersonalAccessToken({
5309
5869
  name: args.name,
@@ -5325,12 +5885,7 @@ const deleteCommand = defineAppCommand({
5325
5885
  }) }).strict(),
5326
5886
  run: async (args) => {
5327
5887
  await assertWritable();
5328
- const config = await readPlatformConfig();
5329
- if (!config.current_user) throw new Error(multiline`
5330
- No user logged in.
5331
- Please login first using 'tailor-sdk login' command.
5332
- `);
5333
- await (await initOperatorClient(await fetchLatestToken(config, config.current_user))).deletePersonalAccessToken({ name: args.name });
5888
+ await (await createPatOperatorClient()).deletePersonalAccessToken({ name: args.name });
5334
5889
  logger.success(`Personal access token "${args.name}" deleted successfully.`);
5335
5890
  }
5336
5891
  });
@@ -5343,12 +5898,7 @@ const listCommand = defineAppCommand({
5343
5898
  args: z.object({ ...paginationArgs() }).strict(),
5344
5899
  run: async (args) => {
5345
5900
  const jsonOutput = logger.jsonMode;
5346
- const config = await readPlatformConfig();
5347
- if (!config.current_user) throw new Error(multiline`
5348
- No user logged in.
5349
- Please login first using 'tailor-sdk login' command.
5350
- `);
5351
- const client = await initOperatorClient(await fetchLatestToken(config, config.current_user));
5901
+ const client = await createPatOperatorClient();
5352
5902
  const pageDirection = toPageDirection(args.order);
5353
5903
  const pats = await fetchPaged(async (pageToken, pageSize) => {
5354
5904
  const { personalAccessTokens, nextPageToken } = await client.listPersonalAccessTokens({
@@ -5396,12 +5946,7 @@ const updateCommand = defineAppCommand({
5396
5946
  }).strict(),
5397
5947
  run: async (args) => {
5398
5948
  await assertWritable();
5399
- const config = await readPlatformConfig();
5400
- if (!config.current_user) throw new Error(multiline`
5401
- No user logged in.
5402
- Please login first using 'tailor-sdk login' command.
5403
- `);
5404
- const client = await initOperatorClient(await fetchLatestToken(config, config.current_user));
5949
+ const client = await createPatOperatorClient();
5405
5950
  await client.deletePersonalAccessToken({ name: args.name });
5406
5951
  const scopes = getScopesFromWriteFlag(args.write);
5407
5952
  const result = await client.createPersonalAccessToken({
@@ -5440,11 +5985,17 @@ const switchCommand = defineAppCommand({
5440
5985
  }) }).strict(),
5441
5986
  run: async (args) => {
5442
5987
  const config = await readPlatformConfig();
5443
- if (!config.users[args.user]) throw new Error(multiline`
5988
+ const activeProfileName = process.env.TAILOR_PLATFORM_PROFILE;
5989
+ const activeProfileEntry = activeProfileName ? config.profiles[activeProfileName] : void 0;
5990
+ if (activeProfileName && !activeProfileEntry) throw new Error(`Profile "${activeProfileName}" not found`);
5991
+ const platformConfig = activeProfileEntry ? platformConfigFromProfile(activeProfileEntry) : void 0;
5992
+ if (args.user.includes("|")) throw new Error(`User "${args.user}" looks like a platform-scoped token key. Pass the user name without the platform URL and select the platform with TAILOR_PLATFORM_URL or a profile.`);
5993
+ if (!hasUserTokenEntry(config, args.user, platformConfig)) throw new Error(multiline`
5444
5994
  User "${args.user}" not found.
5445
5995
  Please login first using 'tailor-sdk login' command to register this user.
5446
5996
  `);
5447
- config.current_user = args.user;
5997
+ if (activeProfileEntry) activeProfileEntry.user = args.user;
5998
+ else config.current_user = args.user;
5448
5999
  writePlatformConfig(config);
5449
6000
  logger.success(`Current user set to "${args.user}" successfully.`);
5450
6001
  }