@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.
- package/CHANGELOG.md +30 -0
- package/dist/application-BakHtldG.mjs +4 -0
- package/dist/{application-Cr-limKC.mjs → application-Df5_I83n.mjs} +318 -78
- package/dist/application-Df5_I83n.mjs.map +1 -0
- package/dist/cli/erd-viewer-assets/app.js +279 -36
- package/dist/cli/erd-viewer-assets/index.html +4 -0
- package/dist/cli/erd-viewer-assets/styles.css +252 -5
- package/dist/cli/index.mjs +641 -90
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +14 -8
- package/dist/cli/lib.mjs +2 -2
- package/dist/completion/zsh-worker.zsh +153 -2
- package/dist/configure/index.d.mts +5 -5
- package/dist/configure/index.mjs +8 -6
- package/dist/configure/index.mjs.map +1 -1
- package/dist/{index-B7VbJm0_.d.mts → index-BAEaAqmz.d.mts} +90 -40
- package/dist/{index-CklcVeMG.d.mts → index-C-vsbx27.d.mts} +2 -2
- package/dist/{index-hXoO-AOC.d.mts → index-CKI0eZP6.d.mts} +2 -2
- package/dist/{index-DYhnxXYR.d.mts → index-CrqOgUF2.d.mts} +2 -2
- package/dist/{index-DlDRSzFZ.d.mts → index-DESLU9kI.d.mts} +2 -2
- package/dist/plugin/builtin/enum-constants/index.d.mts +1 -1
- package/dist/plugin/builtin/file-utils/index.d.mts +1 -1
- package/dist/plugin/builtin/kysely-type/index.d.mts +1 -1
- package/dist/plugin/builtin/seed/index.d.mts +1 -1
- package/dist/plugin/index.d.mts +1 -1
- package/dist/{runtime-jowoN6qC.mjs → runtime-CSY0eD4_.mjs} +330 -190
- package/dist/runtime-CSY0eD4_.mjs.map +1 -0
- package/dist/{schema-1msIhXwA.mjs → schema-C4fkpWV_.mjs} +9 -15
- package/dist/schema-C4fkpWV_.mjs.map +1 -0
- package/dist/{types-2Be3wSMc.mjs → types-32lUMToj.mjs} +1 -1
- package/dist/{types-CmzfQP_m.mjs → types-D4QMmNWh.mjs} +1 -12
- package/dist/types-D4QMmNWh.mjs.map +1 -0
- package/dist/{types-Bzr0RQME.d.mts → types-Dynq4AJv.d.mts} +2 -2
- package/dist/{types-DZrtN6-H.d.mts → types-rj8YJcEe.d.mts} +5 -2
- package/dist/utils/test/index.d.mts +2 -2
- package/dist/{workflow.generated-Br9bmLdX.d.mts → workflow.generated-DJULCuRr.d.mts} +177 -172
- package/docs/cli/application.md +37 -2
- package/docs/cli/setup.md +1 -0
- package/docs/cli/tailordb.md +24 -0
- package/docs/cli/user.md +11 -1
- package/docs/cli/workspace.md +13 -7
- package/docs/cli-reference.md +6 -0
- package/docs/github-actions.md +27 -0
- package/docs/multi-environment.md +22 -0
- package/docs/services/aigateway.md +4 -2
- package/docs/services/http-adapter.md +16 -1
- package/package.json +1 -1
- package/dist/application-Br48NXBD.mjs +0 -4
- package/dist/application-Cr-limKC.mjs.map +0 -1
- package/dist/runtime-jowoN6qC.mjs.map +0 -1
- package/dist/schema-1msIhXwA.mjs.map +0 -1
- package/dist/types-CmzfQP_m.mjs.map +0 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -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 { $
|
|
7
|
-
import { A as
|
|
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
|
|
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-
|
|
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
|
-
|
|
1738
|
-
|
|
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({
|
|
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({
|
|
1841
|
-
|
|
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
|
|
1844
|
-
|
|
1845
|
-
|
|
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
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
3032
|
+
configExists,
|
|
2899
3033
|
defaultBranch,
|
|
2900
|
-
templateVersion:
|
|
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:
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
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 (
|
|
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-
|
|
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 (!
|
|
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
|
|
5188
|
-
Current user '${
|
|
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:
|
|
5715
|
+
logger.out({ user: currentUser });
|
|
5193
5716
|
return;
|
|
5194
5717
|
}
|
|
5195
|
-
logger.out(
|
|
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(
|
|
5765
|
+
logger.out([...new Set(userInfos.map((userInfo) => userInfo.user))]);
|
|
5219
5766
|
return;
|
|
5220
5767
|
}
|
|
5221
|
-
|
|
5222
|
-
if (
|
|
5223
|
-
else logger.log(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|