@tailor-platform/sdk 1.56.1 → 1.58.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 +32 -0
- package/README.md +0 -23
- package/dist/{application-DuT_ae02.mjs → application-B59TaTk_.mjs} +10 -6
- package/dist/application-B59TaTk_.mjs.map +1 -0
- package/dist/application-gO_pa5BO.mjs +4 -0
- package/dist/cli/erd-viewer-assets/app.js +1181 -0
- package/dist/cli/erd-viewer-assets/index.html +73 -0
- package/dist/cli/erd-viewer-assets/serve.json +13 -0
- package/dist/cli/erd-viewer-assets/styles.css +789 -0
- package/dist/cli/index.mjs +847 -355
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +587 -3
- package/dist/cli/lib.mjs +3 -3
- package/dist/{client-DLPEPJ_s.mjs → client-62B-r3MN.mjs} +39 -10
- package/dist/client-62B-r3MN.mjs.map +1 -0
- package/dist/{client-DrzwCD1W.mjs → client-BWl3f1XS.mjs} +1 -1
- package/dist/configure/index.d.mts +2 -2
- package/dist/{crashreport-Bm2mN5tg.mjs → crashreport-CCGpLUlP.mjs} +2 -2
- package/dist/{crashreport-Bm2mN5tg.mjs.map → crashreport-CCGpLUlP.mjs.map} +1 -1
- package/dist/{crashreport-C5oHvHUC.mjs → crashreport-CXD_Kjk-.mjs} +1 -1
- package/dist/{index-B61gFI9a.d.mts → index-BWoHfE-i.d.mts} +3 -2
- package/dist/{runtime-745lvg7i.mjs → runtime-BC-FbQkg.mjs} +179 -190
- package/dist/runtime-BC-FbQkg.mjs.map +1 -0
- package/dist/utils/test/index.d.mts +2 -2
- package/dist/{workflow.generated-Kz-nQrTf.d.mts → workflow.generated-CV77NlFp.d.mts} +3 -2
- package/docs/cli/application.md +5 -5
- package/docs/cli/auth.md +55 -6
- package/docs/cli/function.md +2 -2
- package/docs/cli/staticwebsite.md +137 -0
- package/docs/cli/tailordb.md +31 -26
- package/docs/cli-reference.md +19 -16
- package/docs/generator/builtin.md +1 -1
- package/docs/services/auth.md +0 -11
- package/docs/services/staticwebsite.md +13 -0
- package/package.json +4 -6
- package/dist/application-CC3oaSay.mjs +0 -4
- package/dist/application-DuT_ae02.mjs.map +0 -1
- package/dist/client-DLPEPJ_s.mjs.map +0 -1
- package/dist/runtime-745lvg7i.mjs.map +0 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { E as CustomDomainStatus, K as PATScope, M as FunctionExecution_Type, c as fetchUserInfo, d as initOperatorClient, h as userAgent, i as fetchAll, n as closeConnectionPool, o as fetchPaged, s as fetchPlatformMachineUserToken, u as initOAuth2Client, z as AuthInvokerSchema } from "../client-62B-r3MN.mjs";
|
|
4
4
|
import { n as logger, r as styles } from "../logger-DpJyJvNz.mjs";
|
|
5
|
-
import { $ as listCommand$10, An as
|
|
6
|
-
import { A as
|
|
5
|
+
import { $ as listCommand$10, An as toPageDirection, At as startCommand, B as logBetaWarning, C as listCommand$13, Cn as commonArgs, Dn as isVerbose, Dt as jobsCommand, E as resumeCommand, En as deploymentArgs, F as writeDbTypesFile, Gt as parseMigrationLabelNumber, H as removeCommand$1, Ht as executeScript, I as getConfiguredEditorCommand, K as treeCommand, L as openInConfiguredEditor, Lt as functionExecutionStatusToString, Mt as getCommand$6, N as generateCommand$1, O as listCommand$12, On as pagedLogArgs, P as generateMigrationScript, Pt as executionsCommand, Rt as formatKeyValueTable, Sn as defineAppCommand, St as triggerCommand, T as healthCommand, Tn as confirmationArgs, U as updateCommand$3, Vt as deploy, Y as getCommand$5, Yt as INITIAL_SCHEMA_NUMBER, Z as updateCommand$2, _n as generateUserTypes, at as createCommand$3, b as createCommand$4, c as listCommand$14, cn as reconstructSnapshotFromMigrations, f as restoreCommand, ft as tokenCommand, g as getCommand$7, gn as PluginManager, gt as listCommand$7, hn as sdkNameLabelKey, ht as generate, i as updateCommand$4, in as getMigrationFiles, j as truncateCommand, jn as workspaceArgs, kn as paginationArgs, ln as formatMigrationNumber, lt as getCommand$3, m as listCommand$15, mn as resourceTrn, o as removeCommand, on as isValidMigrationNumber, pn as getNamespacesWithMigrations, pt as listCommand$8, q as listCommand$11, r as queryCommand, rn as getMigrationFilePath, rt as deleteCommand$3, sn as loadDiff, st as listCommand$9, t as isNativeTypeScriptRuntime, tt as getCommand$4, u as inviteCommand, v as deleteCommand$4, vn as prompt, vt as getCommand$2, wn as configArg, wt as listCommand$6, xn as assertWritable, xt as webhookCommand, yn as apiCommand, z as showCommand, zt as getCommand$1 } from "../runtime-BC-FbQkg.mjs";
|
|
6
|
+
import { A as resolveTokens, C as loadConfig, E as loadAccessToken, M as writePlatformConfig, O as loadWorkspaceId, T as fetchLatestToken, _ as createLogLevelTreeshakeOptions, a as WorkflowJobSchema, b as getDistDir, g as composeFunctionTreeshakeOptions, i as resolveInlineSourcemap, j as saveUserTokens, k as readPlatformConfig, l as ExecutorSchema, o as ResolverSchema, t as defineApplication, u as INVOKER_EXPR, v as resolveBundleLogLevel, w as deleteUserTokens, x as hashContent } from "../application-B59TaTk_.mjs";
|
|
7
7
|
import { t as multiline } from "../multiline-Cf9ODpr1.mjs";
|
|
8
|
+
import { r as isPluginGeneratedType } from "../seed-C0fE2sJB.mjs";
|
|
8
9
|
import { t as readPackageJson } from "../package-json-DcQApfPQ.mjs";
|
|
9
10
|
import { n as isCLIError } from "../errors-EsY4XO6O.mjs";
|
|
10
|
-
import { a as JSON_FOOTER_MARKER, i as CRASH_LOG_EXTENSION, o as parseCrashReportConfig, r as sendCrashReport, t as initCrashReporting } from "../crashreport-
|
|
11
|
-
import { createRequire } from "node:module";
|
|
11
|
+
import { a as JSON_FOOTER_MARKER, i as CRASH_LOG_EXTENSION, o as parseCrashReportConfig, r as sendCrashReport, t as initCrashReporting } from "../crashreport-CCGpLUlP.mjs";
|
|
12
12
|
import { arg, defineCommand, runCommand, runMain } from "politty";
|
|
13
13
|
import { withCompletionCommand } from "politty/completion";
|
|
14
14
|
import { z } from "zod";
|
|
@@ -17,7 +17,7 @@ import * as fs$1 from "node:fs";
|
|
|
17
17
|
import { timestampDate } from "@bufbuild/protobuf/wkt";
|
|
18
18
|
import * as path from "pathe";
|
|
19
19
|
import { dirname, resolve } from "pathe";
|
|
20
|
-
import { pathToFileURL } from "node:url";
|
|
20
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
21
21
|
import { generateCodeVerifier } from "@badgateway/oauth2-client";
|
|
22
22
|
import { Code, ConnectError } from "@connectrpc/connect";
|
|
23
23
|
import { resolvePackageJSON, resolveTSConfig } from "pkg-types";
|
|
@@ -26,9 +26,11 @@ import * as http from "node:http";
|
|
|
26
26
|
import open from "open";
|
|
27
27
|
import * as rolldown from "rolldown";
|
|
28
28
|
import * as fsPromises from "node:fs/promises";
|
|
29
|
+
import { glob } from "node:fs/promises";
|
|
29
30
|
import pLimit from "p-limit";
|
|
30
31
|
import { TraceMap, generatedPositionFor, originalPositionFor } from "@jridgewell/trace-mapping";
|
|
31
32
|
import { spawn, spawnSync } from "node:child_process";
|
|
33
|
+
import { watch } from "chokidar";
|
|
32
34
|
import * as fs from "fs";
|
|
33
35
|
import { lookup } from "mime-types";
|
|
34
36
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
@@ -159,6 +161,45 @@ const authorizeAuthConnectionCommand = defineAppCommand({
|
|
|
159
161
|
}
|
|
160
162
|
});
|
|
161
163
|
|
|
164
|
+
//#endregion
|
|
165
|
+
//#region src/cli/commands/authconnection/delete.ts
|
|
166
|
+
const deleteAuthConnectionCommand = defineAppCommand({
|
|
167
|
+
name: "delete",
|
|
168
|
+
description: "Delete an auth connection entirely.",
|
|
169
|
+
args: z.object({
|
|
170
|
+
...workspaceArgs,
|
|
171
|
+
...connectionNameArgs,
|
|
172
|
+
...confirmationArgs
|
|
173
|
+
}).strict(),
|
|
174
|
+
run: async (args) => {
|
|
175
|
+
await assertWritable({ profile: args.profile });
|
|
176
|
+
const client = await initOperatorClient(await loadAccessToken({
|
|
177
|
+
useProfile: true,
|
|
178
|
+
profile: args.profile
|
|
179
|
+
}));
|
|
180
|
+
const workspaceId = await loadWorkspaceId({
|
|
181
|
+
workspaceId: args["workspace-id"],
|
|
182
|
+
profile: args.profile
|
|
183
|
+
});
|
|
184
|
+
if (!args.yes) {
|
|
185
|
+
if (await prompt.text({ message: `Enter the connection name to confirm deletion ("${args.name}"):` }) !== args.name) {
|
|
186
|
+
logger.info("Auth connection deletion cancelled.");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
await client.deleteAuthConnection({
|
|
192
|
+
workspaceId,
|
|
193
|
+
connectionName: args.name
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Auth connection "${args.name}" not found.`, { cause: error });
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
199
|
+
logger.success(`Auth connection "${args.name}" deleted.`);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
162
203
|
//#endregion
|
|
163
204
|
//#region src/cli/commands/authconnection/list.ts
|
|
164
205
|
function connectionInfo(connection) {
|
|
@@ -216,7 +257,8 @@ const listAuthConnectionCommand = defineAppCommand({
|
|
|
216
257
|
//#region src/cli/commands/authconnection/revoke.ts
|
|
217
258
|
const revokeAuthConnectionCommand = defineAppCommand({
|
|
218
259
|
name: "revoke",
|
|
219
|
-
description: "Revoke an auth connection.",
|
|
260
|
+
description: "Revoke an auth connection's tokens (keeps the connection; use 'delete' to remove it).",
|
|
261
|
+
notes: "Revoke invalidates the connection's active session and tokens but keeps the connection and its stored credentials, so it can be re-authorized later. Use `delete` to remove the connection entirely.",
|
|
220
262
|
args: z.object({
|
|
221
263
|
...workspaceArgs,
|
|
222
264
|
...connectionNameArgs,
|
|
@@ -259,7 +301,8 @@ const authconnectionCommand = defineCommand({
|
|
|
259
301
|
subCommands: {
|
|
260
302
|
authorize: authorizeAuthConnectionCommand,
|
|
261
303
|
list: listAuthConnectionCommand,
|
|
262
|
-
revoke: revokeAuthConnectionCommand
|
|
304
|
+
revoke: revokeAuthConnectionCommand,
|
|
305
|
+
delete: deleteAuthConnectionCommand
|
|
263
306
|
},
|
|
264
307
|
async run() {
|
|
265
308
|
await runCommand(listAuthConnectionCommand, []);
|
|
@@ -945,9 +988,9 @@ async function downloadScriptForMapping(options) {
|
|
|
945
988
|
const logsCommand = defineAppCommand({
|
|
946
989
|
name: "logs",
|
|
947
990
|
description: "List or get function execution logs.",
|
|
948
|
-
notes: `When viewing a specific execution that failed, the command displays error details with the stack trace mapped back to original source files
|
|
991
|
+
notes: `When viewing a specific execution that failed, the command displays error details with the stack trace mapped back to your original source files (clickable file links and code snippets, matching \`function test-run\` output).
|
|
949
992
|
|
|
950
|
-
|
|
993
|
+
Stack traces stay accurate even after later redeploys, because the trace is resolved against the exact build that produced the execution. If that build is no longer available, the command falls back to a plain-text error display.`,
|
|
951
994
|
examples: [
|
|
952
995
|
{
|
|
953
996
|
cmd: "",
|
|
@@ -2082,7 +2125,7 @@ const secretValueArgs = {
|
|
|
2082
2125
|
*/
|
|
2083
2126
|
async function checkVaultManaged(params) {
|
|
2084
2127
|
const { client, workspaceId, vaultName } = params;
|
|
2085
|
-
const trn =
|
|
2128
|
+
const trn = resourceTrn(workspaceId, "vault", vaultName);
|
|
2086
2129
|
const notManaged = {
|
|
2087
2130
|
isManaged: false,
|
|
2088
2131
|
trn,
|
|
@@ -2982,6 +3025,115 @@ const deployCommand = defineAppCommand({
|
|
|
2982
3025
|
}
|
|
2983
3026
|
});
|
|
2984
3027
|
|
|
3028
|
+
//#endregion
|
|
3029
|
+
//#region src/cli/commands/staticwebsite/domain/status.ts
|
|
3030
|
+
const statusLabels = {
|
|
3031
|
+
[CustomDomainStatus.UNSPECIFIED]: "unspecified",
|
|
3032
|
+
[CustomDomainStatus.PENDING]: "pending",
|
|
3033
|
+
[CustomDomainStatus.VERIFYING]: "verifying",
|
|
3034
|
+
[CustomDomainStatus.CERT_ISSUED]: "cert_issued",
|
|
3035
|
+
[CustomDomainStatus.ACTIVE]: "active",
|
|
3036
|
+
[CustomDomainStatus.FAILED]: "failed"
|
|
3037
|
+
};
|
|
3038
|
+
|
|
3039
|
+
//#endregion
|
|
3040
|
+
//#region src/cli/commands/staticwebsite/domain/get.ts
|
|
3041
|
+
const domainGetCommand = defineAppCommand({
|
|
3042
|
+
name: "get",
|
|
3043
|
+
description: "Get details of a custom domain.",
|
|
3044
|
+
args: z.object({
|
|
3045
|
+
...workspaceArgs,
|
|
3046
|
+
domain: arg(z.string(), {
|
|
3047
|
+
positional: true,
|
|
3048
|
+
description: "Custom domain name"
|
|
3049
|
+
})
|
|
3050
|
+
}).strict(),
|
|
3051
|
+
run: async (args) => {
|
|
3052
|
+
const client = await initOperatorClient(await loadAccessToken({
|
|
3053
|
+
useProfile: true,
|
|
3054
|
+
profile: args.profile
|
|
3055
|
+
}));
|
|
3056
|
+
const workspaceId = await loadWorkspaceId({
|
|
3057
|
+
workspaceId: args["workspace-id"],
|
|
3058
|
+
profile: args.profile
|
|
3059
|
+
});
|
|
3060
|
+
const notFoundErrorMessage = `Custom domain "${args.domain}" not found.`;
|
|
3061
|
+
try {
|
|
3062
|
+
const { customDomain } = await client.getCustomDomain({
|
|
3063
|
+
workspaceId,
|
|
3064
|
+
domain: args.domain
|
|
3065
|
+
});
|
|
3066
|
+
if (!customDomain) throw new Error(notFoundErrorMessage);
|
|
3067
|
+
const info = {
|
|
3068
|
+
domain: customDomain.domain,
|
|
3069
|
+
status: statusLabels[customDomain.status] ?? "unknown",
|
|
3070
|
+
trafficCnameTarget: customDomain.trafficCnameTarget,
|
|
3071
|
+
certificateCnameTarget: customDomain.certificateCnameTarget,
|
|
3072
|
+
errorMessage: customDomain.errorMessage || void 0
|
|
3073
|
+
};
|
|
3074
|
+
logger.out(info);
|
|
3075
|
+
} catch (error) {
|
|
3076
|
+
if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(notFoundErrorMessage, { cause: error });
|
|
3077
|
+
throw error;
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
});
|
|
3081
|
+
|
|
3082
|
+
//#endregion
|
|
3083
|
+
//#region src/cli/commands/staticwebsite/domain/list.ts
|
|
3084
|
+
const domainListCommand = defineAppCommand({
|
|
3085
|
+
name: "list",
|
|
3086
|
+
description: "List custom domains for a static website.",
|
|
3087
|
+
args: z.object({
|
|
3088
|
+
...workspaceArgs,
|
|
3089
|
+
name: arg(z.string(), {
|
|
3090
|
+
positional: true,
|
|
3091
|
+
description: "Static website name"
|
|
3092
|
+
})
|
|
3093
|
+
}).strict(),
|
|
3094
|
+
run: async (args) => {
|
|
3095
|
+
const client = await initOperatorClient(await loadAccessToken({
|
|
3096
|
+
useProfile: true,
|
|
3097
|
+
profile: args.profile
|
|
3098
|
+
}));
|
|
3099
|
+
const workspaceId = await loadWorkspaceId({
|
|
3100
|
+
workspaceId: args["workspace-id"],
|
|
3101
|
+
profile: args.profile
|
|
3102
|
+
});
|
|
3103
|
+
try {
|
|
3104
|
+
const { customDomains } = await client.listCustomDomains({
|
|
3105
|
+
workspaceId,
|
|
3106
|
+
staticWebsiteName: args.name
|
|
3107
|
+
});
|
|
3108
|
+
if (customDomains.length === 0) {
|
|
3109
|
+
logger.info("No custom domains found.");
|
|
3110
|
+
return;
|
|
3111
|
+
}
|
|
3112
|
+
const formatted = customDomains.map((d) => ({
|
|
3113
|
+
domain: d.domain,
|
|
3114
|
+
status: statusLabels[d.status] ?? "unknown",
|
|
3115
|
+
trafficCnameTarget: d.trafficCnameTarget,
|
|
3116
|
+
certificateCnameTarget: d.certificateCnameTarget
|
|
3117
|
+
}));
|
|
3118
|
+
logger.out(formatted);
|
|
3119
|
+
} catch (error) {
|
|
3120
|
+
if (error instanceof ConnectError && error.code === Code.NotFound) throw new Error(`Static website "${args.name}" not found.`, { cause: error });
|
|
3121
|
+
throw error;
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
});
|
|
3125
|
+
|
|
3126
|
+
//#endregion
|
|
3127
|
+
//#region src/cli/commands/staticwebsite/domain/index.ts
|
|
3128
|
+
const domainCommand = defineCommand({
|
|
3129
|
+
name: "domain",
|
|
3130
|
+
description: "Manage custom domains for static websites.",
|
|
3131
|
+
subCommands: {
|
|
3132
|
+
list: domainListCommand,
|
|
3133
|
+
get: domainGetCommand
|
|
3134
|
+
}
|
|
3135
|
+
});
|
|
3136
|
+
|
|
2985
3137
|
//#endregion
|
|
2986
3138
|
//#region src/cli/commands/staticwebsite/get.ts
|
|
2987
3139
|
const getCommand = defineAppCommand({
|
|
@@ -3096,6 +3248,7 @@ const staticwebsiteCommand = defineCommand({
|
|
|
3096
3248
|
description: "Manage static websites in your workspace.",
|
|
3097
3249
|
subCommands: {
|
|
3098
3250
|
deploy: deployCommand,
|
|
3251
|
+
domain: domainCommand,
|
|
3099
3252
|
list: listCommand$2,
|
|
3100
3253
|
get: getCommand
|
|
3101
3254
|
},
|
|
@@ -3105,378 +3258,430 @@ const staticwebsiteCommand = defineCommand({
|
|
|
3105
3258
|
});
|
|
3106
3259
|
|
|
3107
3260
|
//#endregion
|
|
3108
|
-
//#region src/cli/
|
|
3261
|
+
//#region src/cli/commands/tailordb/erd/local-schema.ts
|
|
3109
3262
|
/**
|
|
3110
|
-
* Resolve
|
|
3111
|
-
* @param
|
|
3112
|
-
* @
|
|
3263
|
+
* Resolve TailorDB namespaces that need local type loading for ERD generation.
|
|
3264
|
+
* @param config - Loaded Tailor config.
|
|
3265
|
+
* @param options - Namespace selection options.
|
|
3266
|
+
* @returns Namespace names to load, or undefined to load all owned namespaces.
|
|
3113
3267
|
*/
|
|
3114
|
-
function
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3268
|
+
function resolveLocalErdSchemaNamespaces(config, options) {
|
|
3269
|
+
if (options.namespaces) return options.namespaces;
|
|
3270
|
+
if (!options.requireErdSite) return;
|
|
3271
|
+
return Object.entries(config.db ?? {}).flatMap(([namespace, dbConfig]) => "external" in dbConfig || !dbConfig.erdSite ? [] : [namespace]);
|
|
3272
|
+
}
|
|
3273
|
+
/**
|
|
3274
|
+
* Load local TailorDB namespaces exactly as SDK generation/deploy sees them.
|
|
3275
|
+
* @param options - Local schema loading options.
|
|
3276
|
+
* @returns Loaded TailorDB namespace data.
|
|
3277
|
+
*/
|
|
3278
|
+
async function loadLocalErdSchema(options) {
|
|
3279
|
+
const { config, plugins } = await loadConfig(options.configPath);
|
|
3280
|
+
await generateUserTypes({
|
|
3281
|
+
config,
|
|
3282
|
+
configPath: config.path
|
|
3283
|
+
});
|
|
3284
|
+
const application = defineApplication({
|
|
3285
|
+
config,
|
|
3286
|
+
pluginManager: plugins.length > 0 ? new PluginManager(plugins) : void 0
|
|
3287
|
+
});
|
|
3288
|
+
const namespaceNames = resolveLocalErdSchemaNamespaces(config, {
|
|
3289
|
+
namespaces: options.namespaces,
|
|
3290
|
+
requireErdSite: options.requireErdSite
|
|
3291
|
+
});
|
|
3292
|
+
const namespaceFilter = namespaceNames ? new Set(namespaceNames) : void 0;
|
|
3293
|
+
const services = namespaceFilter ? application.tailorDBServices.filter((db) => namespaceFilter.has(db.namespace)) : application.tailorDBServices;
|
|
3294
|
+
if (namespaceFilter && services.length !== namespaceFilter.size) {
|
|
3295
|
+
const available = application.tailorDBServices.map((db) => db.namespace).join(", ");
|
|
3296
|
+
const requested = [...namespaceFilter].join(", ");
|
|
3297
|
+
throw new Error(`TailorDB namespace "${requested}" not found in local config.db.` + (available ? ` Available owned namespaces: ${available}` : ""));
|
|
3122
3298
|
}
|
|
3123
|
-
const
|
|
3124
|
-
|
|
3125
|
-
|
|
3299
|
+
const namespaces = [];
|
|
3300
|
+
for (const db of services) {
|
|
3301
|
+
await db.loadTypes();
|
|
3302
|
+
await db.processNamespacePlugins();
|
|
3303
|
+
namespaces.push({
|
|
3304
|
+
namespace: db.namespace,
|
|
3305
|
+
types: { ...db.types },
|
|
3306
|
+
sourceInfo: new Map(Object.entries(db.typeSourceInfo)),
|
|
3307
|
+
pluginAttachments: db.pluginAttachments
|
|
3308
|
+
});
|
|
3309
|
+
}
|
|
3310
|
+
return {
|
|
3311
|
+
config,
|
|
3312
|
+
namespaces
|
|
3313
|
+
};
|
|
3126
3314
|
}
|
|
3127
3315
|
|
|
3128
3316
|
//#endregion
|
|
3129
3317
|
//#region src/cli/commands/tailordb/erd/schema.ts
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3318
|
+
const CLEAN_ROOM_NOTES = [
|
|
3319
|
+
"Generated by a TailorDB-specific viewer implementation.",
|
|
3320
|
+
"The implementation is based on TailorDB schema contracts, public Liam documentation, Liam CLI help, and black-box generated-output observation.",
|
|
3321
|
+
"It does not copy Liam source code, generated JavaScript/CSS, parser internals, or layout internals."
|
|
3322
|
+
];
|
|
3323
|
+
function buildRevision(schema) {
|
|
3324
|
+
return hashContent(JSON.stringify(schema)).slice(0, 16);
|
|
3325
|
+
}
|
|
3326
|
+
function toTypeSource(source) {
|
|
3327
|
+
if (!source) return void 0;
|
|
3328
|
+
if (isPluginGeneratedType(source)) return {
|
|
3329
|
+
kind: "plugin",
|
|
3330
|
+
exportName: source.exportName,
|
|
3331
|
+
pluginId: source.pluginId,
|
|
3332
|
+
pluginImportPath: source.pluginImportPath,
|
|
3333
|
+
originalExportName: source.originalExportName,
|
|
3334
|
+
generatedTypeKind: source.generatedTypeKind,
|
|
3335
|
+
namespace: source.namespace
|
|
3336
|
+
};
|
|
3337
|
+
return {
|
|
3338
|
+
kind: "user",
|
|
3339
|
+
exportName: source.exportName
|
|
3340
|
+
};
|
|
3341
|
+
}
|
|
3342
|
+
function toRelationships(relationships) {
|
|
3343
|
+
return Object.entries(relationships).sort(([a], [b]) => a.localeCompare(b)).map(([name, relationship]) => ({
|
|
3344
|
+
name,
|
|
3345
|
+
targetType: relationship.targetType,
|
|
3346
|
+
targetField: relationship.targetField,
|
|
3347
|
+
sourceField: relationship.sourceField,
|
|
3348
|
+
isArray: relationship.isArray,
|
|
3349
|
+
...relationship.description && { description: relationship.description }
|
|
3350
|
+
}));
|
|
3351
|
+
}
|
|
3352
|
+
function toIndexes(type) {
|
|
3353
|
+
return Object.entries(type.indexes ?? {}).sort(([a], [b]) => a.localeCompare(b)).map(([name, index]) => ({
|
|
3354
|
+
name,
|
|
3355
|
+
fields: [...index.fields],
|
|
3356
|
+
unique: index.unique === true
|
|
3357
|
+
}));
|
|
3358
|
+
}
|
|
3359
|
+
function toColumnRelation(options) {
|
|
3360
|
+
const { fieldConfig, parsedField } = options;
|
|
3361
|
+
if (!fieldConfig.foreignKey || !fieldConfig.foreignKeyType) return;
|
|
3362
|
+
const required = fieldConfig.required !== false;
|
|
3363
|
+
const kind = parsedField?.relation || fieldConfig.rawRelation ? "relation" : "foreignKey";
|
|
3364
|
+
return {
|
|
3365
|
+
targetTable: fieldConfig.foreignKeyType,
|
|
3366
|
+
targetColumn: fieldConfig.foreignKeyField || "id",
|
|
3367
|
+
kind,
|
|
3368
|
+
required,
|
|
3369
|
+
...fieldConfig.rawRelation?.type && { relationType: fieldConfig.rawRelation.type },
|
|
3370
|
+
...parsedField?.relation?.forwardName && { forwardName: parsedField.relation.forwardName },
|
|
3371
|
+
...parsedField?.relation?.backwardName && { backwardName: parsedField.relation.backwardName }
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
function toColumn(options) {
|
|
3375
|
+
const { fieldName, fieldConfig } = options;
|
|
3376
|
+
const indexNames = options.indexEntries.filter(([, index]) => index.fields.includes(fieldName)).map(([name]) => name);
|
|
3377
|
+
const uniqueIndexNames = options.indexEntries.filter(([, index]) => index.unique && index.fields.includes(fieldName)).map(([name]) => name);
|
|
3378
|
+
const enumValues = fieldConfig.allowedValues?.map((value) => value.value) ?? [];
|
|
3379
|
+
const enumValueDescriptions = Object.fromEntries((fieldConfig.allowedValues ?? []).flatMap((value) => value.description ? [[value.value, value.description]] : []));
|
|
3380
|
+
const nestedFields = Object.entries(fieldConfig.fields ?? {}).map(([nestedName, nestedConfig]) => toColumn({
|
|
3381
|
+
fieldName: nestedName,
|
|
3382
|
+
fieldConfig: nestedConfig,
|
|
3383
|
+
indexEntries: []
|
|
3384
|
+
}));
|
|
3385
|
+
const relation = toColumnRelation(options);
|
|
3138
3386
|
return {
|
|
3139
3387
|
name: fieldName,
|
|
3140
|
-
type: fieldConfig.
|
|
3141
|
-
|
|
3142
|
-
|
|
3388
|
+
type: fieldConfig.type || "string",
|
|
3389
|
+
required: fieldConfig.required !== false,
|
|
3390
|
+
array: fieldConfig.array === true,
|
|
3391
|
+
...fieldConfig.description && { description: fieldConfig.description },
|
|
3392
|
+
...fieldConfig.unique && { unique: true },
|
|
3393
|
+
...(fieldConfig.index || indexNames.length > 0) && { index: true },
|
|
3394
|
+
...indexNames.length > 0 && { indexNames },
|
|
3395
|
+
...uniqueIndexNames.length > 0 && { uniqueIndexNames },
|
|
3396
|
+
...enumValues.length > 0 && { enumValues },
|
|
3397
|
+
...Object.keys(enumValueDescriptions).length > 0 && { enumValueDescriptions },
|
|
3398
|
+
...fieldConfig.vector && { vector: true },
|
|
3399
|
+
...fieldConfig.serial && { serial: { ...fieldConfig.serial } },
|
|
3400
|
+
...fieldConfig.scale !== void 0 && { scale: fieldConfig.scale },
|
|
3401
|
+
...fieldConfig.validate?.length && { validations: fieldConfig.validate.length },
|
|
3402
|
+
...fieldConfig.hooks && { hooks: {
|
|
3403
|
+
...fieldConfig.hooks.create && { create: true },
|
|
3404
|
+
...fieldConfig.hooks.update && { update: true }
|
|
3405
|
+
} },
|
|
3406
|
+
...nestedFields.length > 0 && { fields: nestedFields },
|
|
3407
|
+
...relation && { relation }
|
|
3143
3408
|
};
|
|
3144
3409
|
}
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
const
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
const tableConstraints = [];
|
|
3162
|
-
columns.push({
|
|
3410
|
+
function toTable(type, source) {
|
|
3411
|
+
const indexes = toIndexes(type);
|
|
3412
|
+
const indexEntries = indexes.map((index) => [index.name, index]);
|
|
3413
|
+
const fieldColumns = Object.entries(type.fields).filter(([fieldName]) => fieldName !== "id").map(([fieldName, field]) => toColumn({
|
|
3414
|
+
fieldName,
|
|
3415
|
+
fieldConfig: field.config,
|
|
3416
|
+
parsedField: field,
|
|
3417
|
+
indexEntries
|
|
3418
|
+
}));
|
|
3419
|
+
const typeSource = toTypeSource(source);
|
|
3420
|
+
return {
|
|
3421
|
+
name: type.name,
|
|
3422
|
+
pluralForm: type.pluralForm,
|
|
3423
|
+
...type.description && { description: type.description },
|
|
3424
|
+
...typeSource && { source: typeSource },
|
|
3425
|
+
columns: [{
|
|
3163
3426
|
name: "id",
|
|
3164
3427
|
type: "uuid",
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
values = /* @__PURE__ */ new Set();
|
|
3182
|
-
enumsMap.set(enumName, values);
|
|
3183
|
-
}
|
|
3184
|
-
for (const value of fieldConfig.allowedValues) values.add(value.value);
|
|
3185
|
-
}
|
|
3186
|
-
if (fieldConfig.foreignKey && fieldConfig.foreignKeyType) {
|
|
3187
|
-
const foreignTable = fieldConfig.foreignKeyType;
|
|
3188
|
-
const foreignColumn = fieldConfig.foreignKeyField || "id";
|
|
3189
|
-
const childCardinality = fieldConfig.required ? "exactly_one" : "zero_or_one";
|
|
3190
|
-
relations.push({
|
|
3191
|
-
table: tableName,
|
|
3192
|
-
columns: [fieldName],
|
|
3193
|
-
parent_table: foreignTable,
|
|
3194
|
-
parent_columns: [foreignColumn],
|
|
3195
|
-
cardinality: childCardinality,
|
|
3196
|
-
parent_cardinality: "zero_or_more",
|
|
3197
|
-
def: ""
|
|
3198
|
-
});
|
|
3199
|
-
tableConstraints.push({
|
|
3200
|
-
name: `fk_${tableName}_${fieldName}`,
|
|
3201
|
-
type: "FOREIGN KEY",
|
|
3202
|
-
def: "",
|
|
3203
|
-
table: tableName,
|
|
3204
|
-
columns: [fieldName],
|
|
3205
|
-
referenced_table: foreignTable,
|
|
3206
|
-
referenced_columns: [foreignColumn]
|
|
3207
|
-
});
|
|
3208
|
-
if (!referencedByTable[tableName]) referencedByTable[tableName] = /* @__PURE__ */ new Set();
|
|
3209
|
-
referencedByTable[tableName].add(foreignTable);
|
|
3210
|
-
}
|
|
3211
|
-
}
|
|
3212
|
-
constraintsByTable[tableName] = tableConstraints;
|
|
3213
|
-
tables.push({
|
|
3214
|
-
name: tableName,
|
|
3215
|
-
type: "table",
|
|
3216
|
-
comment: schema?.description ?? "",
|
|
3217
|
-
columns,
|
|
3218
|
-
indexes: [],
|
|
3219
|
-
constraints: constraintsByTable[tableName] ?? [],
|
|
3220
|
-
triggers: [],
|
|
3221
|
-
def: "",
|
|
3222
|
-
referenced_tables: []
|
|
3223
|
-
});
|
|
3224
|
-
}
|
|
3225
|
-
for (const table of tables) {
|
|
3226
|
-
const referenced = referencedByTable[table.name];
|
|
3227
|
-
table.referenced_tables = referenced ? Array.from(referenced) : [];
|
|
3228
|
-
}
|
|
3229
|
-
const enums = [];
|
|
3230
|
-
for (const [name, values] of enumsMap.entries()) enums.push({
|
|
3231
|
-
name,
|
|
3232
|
-
values: Array.from(values)
|
|
3428
|
+
required: true,
|
|
3429
|
+
array: false,
|
|
3430
|
+
primaryKey: true,
|
|
3431
|
+
unique: true
|
|
3432
|
+
}, ...fieldColumns],
|
|
3433
|
+
indexes,
|
|
3434
|
+
forwardRelationships: toRelationships(type.forwardRelationships),
|
|
3435
|
+
backwardRelationships: toRelationships(type.backwardRelationships)
|
|
3436
|
+
};
|
|
3437
|
+
}
|
|
3438
|
+
function toRelation(sourceTable, field) {
|
|
3439
|
+
const relation = toColumnRelation({
|
|
3440
|
+
fieldName: field.name,
|
|
3441
|
+
fieldConfig: field.config,
|
|
3442
|
+
parsedField: field,
|
|
3443
|
+
indexEntries: []
|
|
3233
3444
|
});
|
|
3445
|
+
if (!relation) return void 0;
|
|
3234
3446
|
return {
|
|
3235
|
-
name:
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3447
|
+
name: `${sourceTable}.${field.name}->${relation.targetTable}.${relation.targetColumn}`,
|
|
3448
|
+
sourceTable,
|
|
3449
|
+
sourceColumns: [field.name],
|
|
3450
|
+
targetTable: relation.targetTable,
|
|
3451
|
+
targetColumns: [relation.targetColumn],
|
|
3452
|
+
required: relation.required,
|
|
3453
|
+
unique: field.config.unique === true || field.relation?.unique === true,
|
|
3454
|
+
kind: relation.kind,
|
|
3455
|
+
...relation.relationType && { relationType: relation.relationType },
|
|
3456
|
+
...relation.forwardName && { forwardName: relation.forwardName },
|
|
3457
|
+
...relation.backwardName && { backwardName: relation.backwardName }
|
|
3239
3458
|
};
|
|
3240
3459
|
}
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
const types = await fetchAll(async (pageToken, maxPageSize) => {
|
|
3249
|
-
try {
|
|
3250
|
-
const { tailordbTypes, nextPageToken } = await client.listTailorDBTypes({
|
|
3251
|
-
workspaceId,
|
|
3252
|
-
namespaceName: namespace,
|
|
3253
|
-
pageToken,
|
|
3254
|
-
pageSize: maxPageSize
|
|
3255
|
-
});
|
|
3256
|
-
return [tailordbTypes, nextPageToken];
|
|
3257
|
-
} catch (error) {
|
|
3258
|
-
if (error instanceof ConnectError && error.code === Code.NotFound) return [[], ""];
|
|
3259
|
-
throw error;
|
|
3260
|
-
}
|
|
3261
|
-
});
|
|
3262
|
-
if (types.length === 0) logger.warn(`No TailorDB types found in namespace "${namespace}". Returning empty schema.`);
|
|
3263
|
-
return buildTblsSchema(types, namespace);
|
|
3460
|
+
function buildRelations(types) {
|
|
3461
|
+
const relations = [];
|
|
3462
|
+
for (const type of Object.values(types)) for (const field of Object.values(type.fields)) {
|
|
3463
|
+
const relation = toRelation(type.name, field);
|
|
3464
|
+
if (relation) relations.push(relation);
|
|
3465
|
+
}
|
|
3466
|
+
return relations.sort((a, b) => a.name.localeCompare(b.name));
|
|
3264
3467
|
}
|
|
3265
3468
|
/**
|
|
3266
|
-
*
|
|
3267
|
-
* @param options -
|
|
3469
|
+
* Build the TailorDB ERD viewer schema for one namespace.
|
|
3470
|
+
* @param options - Schema build options.
|
|
3471
|
+
* @returns TailorDB ERD viewer schema.
|
|
3268
3472
|
*/
|
|
3269
|
-
|
|
3270
|
-
const
|
|
3271
|
-
const
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3473
|
+
function buildTailorDbErdSchema(options) {
|
|
3474
|
+
const { namespaceData } = options;
|
|
3475
|
+
const tables = Object.values(namespaceData.types).sort((a, b) => a.name.localeCompare(b.name)).map((type) => toTable(type, namespaceData.sourceInfo.get(type.name)));
|
|
3476
|
+
const schemaWithoutRevision = {
|
|
3477
|
+
version: 1,
|
|
3478
|
+
namespace: namespaceData.namespace,
|
|
3479
|
+
source: options.source ?? "local",
|
|
3480
|
+
cleanRoom: {
|
|
3481
|
+
implementation: "tailor-sdk",
|
|
3482
|
+
notes: CLEAN_ROOM_NOTES
|
|
3483
|
+
},
|
|
3484
|
+
tables,
|
|
3485
|
+
relations: buildRelations(namespaceData.types)
|
|
3486
|
+
};
|
|
3487
|
+
return {
|
|
3488
|
+
...schemaWithoutRevision,
|
|
3489
|
+
generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3490
|
+
revision: buildRevision(schemaWithoutRevision)
|
|
3491
|
+
};
|
|
3276
3492
|
}
|
|
3277
3493
|
|
|
3278
3494
|
//#endregion
|
|
3279
3495
|
//#region src/cli/commands/tailordb/erd/utils.ts
|
|
3280
3496
|
/**
|
|
3281
|
-
* Initialize shared ERD command
|
|
3282
|
-
* @param args - CLI arguments.
|
|
3283
|
-
* @returns Initialized context.
|
|
3497
|
+
* Initialize shared ERD command behavior.
|
|
3284
3498
|
*/
|
|
3285
|
-
|
|
3499
|
+
function initErdCommand() {
|
|
3286
3500
|
logBetaWarning("tailordb erd");
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
const { config } = await loadConfig(args.config);
|
|
3501
|
+
}
|
|
3502
|
+
/**
|
|
3503
|
+
* Initialize platform context for ERD deployment.
|
|
3504
|
+
* @param args - CLI arguments.
|
|
3505
|
+
* @returns Initialized deploy context.
|
|
3506
|
+
*/
|
|
3507
|
+
async function initErdDeployContext(args) {
|
|
3508
|
+
initErdCommand();
|
|
3296
3509
|
return {
|
|
3297
|
-
client
|
|
3298
|
-
|
|
3299
|
-
|
|
3510
|
+
client: await initOperatorClient(await loadAccessToken({
|
|
3511
|
+
useProfile: true,
|
|
3512
|
+
profile: args.profile
|
|
3513
|
+
})),
|
|
3514
|
+
workspaceId: await loadWorkspaceId({
|
|
3515
|
+
workspaceId: args.workspaceId,
|
|
3516
|
+
profile: args.profile
|
|
3517
|
+
})
|
|
3300
3518
|
};
|
|
3301
3519
|
}
|
|
3302
3520
|
|
|
3303
3521
|
//#endregion
|
|
3304
|
-
//#region src/cli/commands/tailordb/erd/
|
|
3305
|
-
const
|
|
3522
|
+
//#region src/cli/commands/tailordb/erd/viewer.ts
|
|
3523
|
+
const VIEWER_ASSETS_DIR = "erd-viewer-assets";
|
|
3524
|
+
const STYLES_LINK = "<link rel=\"stylesheet\" href=\"./styles.css\" />";
|
|
3525
|
+
const APP_SCRIPT = "<script src=\"./app.js\" type=\"module\"><\/script>";
|
|
3526
|
+
function assetDirCandidates() {
|
|
3527
|
+
const currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
3528
|
+
return [
|
|
3529
|
+
path.join(currentDir, "viewer-assets"),
|
|
3530
|
+
path.join(currentDir, VIEWER_ASSETS_DIR),
|
|
3531
|
+
path.join(currentDir, "commands", "tailordb", "erd", VIEWER_ASSETS_DIR),
|
|
3532
|
+
path.resolve(process.cwd(), "packages/sdk/src/cli/commands/tailordb/erd/viewer-assets")
|
|
3533
|
+
];
|
|
3534
|
+
}
|
|
3306
3535
|
/**
|
|
3307
|
-
* Resolve
|
|
3308
|
-
* @
|
|
3309
|
-
* @param explicitNamespace - Namespace override.
|
|
3310
|
-
* @returns Resolved namespace and erdSite.
|
|
3536
|
+
* Resolve the packaged ERD viewer asset directory.
|
|
3537
|
+
* @returns Absolute path to the viewer asset directory.
|
|
3311
3538
|
*/
|
|
3312
|
-
function
|
|
3313
|
-
const
|
|
3314
|
-
|
|
3315
|
-
const dbConfig = config.db?.[namespace];
|
|
3316
|
-
if (!dbConfig || typeof dbConfig !== "object" || "external" in dbConfig) throw new Error(`TailorDB namespace "${namespace}" not found in config.db.`);
|
|
3317
|
-
return {
|
|
3318
|
-
namespace,
|
|
3319
|
-
erdSite: dbConfig.erdSite
|
|
3320
|
-
};
|
|
3539
|
+
function resolveViewerAssetsDir() {
|
|
3540
|
+
for (const candidate of assetDirCandidates()) if (fs$1.existsSync(path.join(candidate, "index.html"))) return candidate;
|
|
3541
|
+
throw new Error(`ERD viewer assets not found. Checked: ${assetDirCandidates().join(", ")}`);
|
|
3321
3542
|
}
|
|
3322
3543
|
/**
|
|
3323
|
-
*
|
|
3324
|
-
*
|
|
3325
|
-
*
|
|
3326
|
-
*
|
|
3544
|
+
* Build the self-contained ERD viewer HTML document. CSS, JS, and the schema
|
|
3545
|
+
* are inlined as separately extractable blocks: a `<style>` element, a
|
|
3546
|
+
* `<script type="module">`, and a `<script type="application/json"
|
|
3547
|
+
* id="erd-schema">` data block. This renders without any sibling asset files
|
|
3548
|
+
* and lets external tooling (e.g. a future ERD diff) pull out the schema via
|
|
3549
|
+
* `JSON.parse`.
|
|
3550
|
+
* @param options - Viewer build options.
|
|
3551
|
+
* @returns The self-contained HTML document.
|
|
3327
3552
|
*/
|
|
3328
|
-
function
|
|
3329
|
-
const
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3553
|
+
function buildViewerHtml(options) {
|
|
3554
|
+
const assetsDir = resolveViewerAssetsDir();
|
|
3555
|
+
const html = fs$1.readFileSync(path.join(assetsDir, "index.html"), "utf8");
|
|
3556
|
+
const css = fs$1.readFileSync(path.join(assetsDir, "styles.css"), "utf8");
|
|
3557
|
+
const appJs = fs$1.readFileSync(path.join(assetsDir, "app.js"), "utf8");
|
|
3558
|
+
if (!html.includes(STYLES_LINK) || !html.includes(APP_SCRIPT)) throw new Error("ERD viewer index.html is missing expected asset references for inlining.");
|
|
3559
|
+
const embedScript = `<script type="application/json" id="erd-schema">${JSON.stringify(options.schema).replaceAll("<", "\\u003c")}<\/script>`;
|
|
3560
|
+
const inlineScript = `<script type="module">\n${appJs.replace(/<\/script/gi, "<\\/script")}\n<\/script>`;
|
|
3561
|
+
return html.replace(STYLES_LINK, `<style>\n${css}\n</style>`).replace(APP_SCRIPT, `${embedScript}\n ${inlineScript}`);
|
|
3335
3562
|
}
|
|
3336
3563
|
/**
|
|
3337
|
-
*
|
|
3338
|
-
* @param
|
|
3339
|
-
* @param cwd - Working directory where liam will run (dist is created here)
|
|
3340
|
-
* @returns Resolves when the build completes successfully
|
|
3564
|
+
* Write the self-contained TailorDB ERD viewer to `<distDir>/index.html`.
|
|
3565
|
+
* @param options - Viewer dist write options.
|
|
3341
3566
|
*/
|
|
3342
|
-
|
|
3343
|
-
fs$1.
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
try {
|
|
3347
|
-
liamBinPath = resolveCliBinPath({
|
|
3348
|
-
packageName: "@liam-hq/cli",
|
|
3349
|
-
binName: "liam"
|
|
3350
|
-
});
|
|
3351
|
-
} catch (error) {
|
|
3352
|
-
logger.error(String(error));
|
|
3353
|
-
reject(error);
|
|
3354
|
-
return;
|
|
3355
|
-
}
|
|
3356
|
-
const child = spawn(process.execPath, [
|
|
3357
|
-
liamBinPath,
|
|
3358
|
-
"erd",
|
|
3359
|
-
"build",
|
|
3360
|
-
"--format",
|
|
3361
|
-
"tbls",
|
|
3362
|
-
"--input",
|
|
3363
|
-
schemaPath
|
|
3364
|
-
], {
|
|
3365
|
-
stdio: [
|
|
3366
|
-
"pipe",
|
|
3367
|
-
"ignore",
|
|
3368
|
-
"pipe"
|
|
3369
|
-
],
|
|
3370
|
-
cwd
|
|
3371
|
-
});
|
|
3372
|
-
let stderrOutput = "";
|
|
3373
|
-
child.stderr?.on("data", (data) => {
|
|
3374
|
-
stderrOutput += data.toString();
|
|
3375
|
-
});
|
|
3376
|
-
child.on("error", (error) => {
|
|
3377
|
-
logger.error("Failed to run `@liam-hq/cli`.");
|
|
3378
|
-
reject(error);
|
|
3379
|
-
});
|
|
3380
|
-
child.on("close", (code) => {
|
|
3381
|
-
if (code === 0) resolve();
|
|
3382
|
-
else {
|
|
3383
|
-
if (stderrOutput) logger.error(stderrOutput);
|
|
3384
|
-
logger.error("liam CLI exited with a non-zero code. Ensure `@liam-hq/cli erd build --format tbls --input schema.json` works in your project.");
|
|
3385
|
-
reject(/* @__PURE__ */ new Error(`liam CLI exited with code ${code ?? 1}`));
|
|
3386
|
-
}
|
|
3387
|
-
});
|
|
3567
|
+
function writeViewerDist(options) {
|
|
3568
|
+
fs$1.rmSync(options.distDir, {
|
|
3569
|
+
recursive: true,
|
|
3570
|
+
force: true
|
|
3388
3571
|
});
|
|
3572
|
+
fs$1.mkdirSync(options.distDir, { recursive: true });
|
|
3573
|
+
fs$1.writeFileSync(path.join(options.distDir, "index.html"), buildViewerHtml({ schema: options.schema }), "utf8");
|
|
3574
|
+
}
|
|
3575
|
+
|
|
3576
|
+
//#endregion
|
|
3577
|
+
//#region src/cli/commands/tailordb/erd/export.ts
|
|
3578
|
+
const DEFAULT_ERD_BASE_DIR$1 = ".tailor-sdk/erd";
|
|
3579
|
+
function getErdSite(context, namespace) {
|
|
3580
|
+
const dbConfig = context.config.db?.[namespace];
|
|
3581
|
+
if (!dbConfig || "external" in dbConfig) return;
|
|
3582
|
+
return dbConfig.erdSite;
|
|
3583
|
+
}
|
|
3584
|
+
function resolveExplicitTarget(options) {
|
|
3585
|
+
const namespaceData = options.context.namespaces.find((candidate) => candidate.namespace === options.namespace);
|
|
3586
|
+
if (!namespaceData) {
|
|
3587
|
+
const available = options.context.namespaces.map((candidate) => candidate.namespace).join(", ");
|
|
3588
|
+
throw new Error(`TailorDB namespace "${options.namespace}" not found in local config.db.` + (available ? ` Available owned namespaces: ${available}` : ""));
|
|
3589
|
+
}
|
|
3590
|
+
const erdSite = getErdSite(options.context, namespaceData.namespace);
|
|
3591
|
+
if (options.requireErdSite && !erdSite) throw new Error(`No erdSite configured for namespace "${namespaceData.namespace}". Add erdSite: "<static-website-name>" to db.${namespaceData.namespace} in tailor.config.ts.`);
|
|
3592
|
+
return toTarget(options.outputDir, namespaceData, erdSite);
|
|
3593
|
+
}
|
|
3594
|
+
function resolveAllTargets(options) {
|
|
3595
|
+
const namespaces = options.context.namespaces.filter((namespaceData) => !options.requireErdSite || getErdSite(options.context, namespaceData.namespace));
|
|
3596
|
+
if (namespaces.length === 0) throw new Error(options.requireErdSite ? "No namespaces with erdSite configured found. Add erdSite: \"<static-website-name>\" to db.<namespace> in tailor.config.ts." : "No TailorDB namespaces found in config. Please define db services in tailor.config.ts.");
|
|
3597
|
+
logger.info(`Found ${namespaces.length} namespace(s)${options.requireErdSite ? " with erdSite configured" : ""}.`);
|
|
3598
|
+
return namespaces.map((namespaceData) => toTarget(options.outputDir, namespaceData, getErdSite(options.context, namespaceData.namespace)));
|
|
3599
|
+
}
|
|
3600
|
+
function toTarget(outputDir, namespaceData, erdSite) {
|
|
3601
|
+
return {
|
|
3602
|
+
namespaceData,
|
|
3603
|
+
erdSite,
|
|
3604
|
+
distDir: path.join(outputDir, namespaceData.namespace, "dist")
|
|
3605
|
+
};
|
|
3606
|
+
}
|
|
3607
|
+
function resolveTargets(options) {
|
|
3608
|
+
if (options.namespace) return [resolveExplicitTarget(options)];
|
|
3609
|
+
return resolveAllTargets(options);
|
|
3610
|
+
}
|
|
3611
|
+
function prepareErdBuild(target) {
|
|
3612
|
+
writeViewerDist({
|
|
3613
|
+
schema: buildTailorDbErdSchema({ namespaceData: target.namespaceData }),
|
|
3614
|
+
distDir: target.distDir
|
|
3615
|
+
});
|
|
3616
|
+
const relativePath = path.relative(process.cwd(), target.distDir);
|
|
3617
|
+
logger.success(`Built ERD to ${relativePath}`);
|
|
3618
|
+
return {
|
|
3619
|
+
namespace: target.namespaceData.namespace,
|
|
3620
|
+
erdSite: target.erdSite,
|
|
3621
|
+
distDir: target.distDir
|
|
3622
|
+
};
|
|
3389
3623
|
}
|
|
3390
3624
|
/**
|
|
3391
|
-
*
|
|
3625
|
+
* Prepare TailorDB ERD static viewer builds for one or more namespaces.
|
|
3392
3626
|
* @param options - Build options.
|
|
3627
|
+
* @returns Build results by namespace.
|
|
3393
3628
|
*/
|
|
3394
|
-
async function
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3629
|
+
async function prepareErdBuilds(options) {
|
|
3630
|
+
return prepareErdBuildsFromContext({
|
|
3631
|
+
context: await loadLocalErdSchema({
|
|
3632
|
+
configPath: options.configPath,
|
|
3633
|
+
namespaces: options.namespace ? [options.namespace] : void 0,
|
|
3634
|
+
requireErdSite: options.requireErdSite
|
|
3635
|
+
}),
|
|
3636
|
+
namespace: options.namespace,
|
|
3637
|
+
outputDir: options.outputDir,
|
|
3638
|
+
requireErdSite: options.requireErdSite
|
|
3639
|
+
});
|
|
3400
3640
|
}
|
|
3401
3641
|
/**
|
|
3402
|
-
* Prepare ERD builds
|
|
3642
|
+
* Prepare TailorDB ERD static viewer builds from an already loaded schema context.
|
|
3403
3643
|
* @param options - Build options.
|
|
3404
3644
|
* @returns Build results by namespace.
|
|
3405
3645
|
*/
|
|
3406
|
-
|
|
3407
|
-
const
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
targets = [{
|
|
3415
|
-
namespace,
|
|
3416
|
-
erdSite,
|
|
3417
|
-
schemaOutputPath: path.join(erdDir, "schema.json"),
|
|
3418
|
-
distDir: path.join(erdDir, "dist"),
|
|
3419
|
-
erdDir
|
|
3420
|
-
}];
|
|
3421
|
-
} else {
|
|
3422
|
-
const namespaces = resolveAllNamespaces(config, { requireErdSite: options.requireErdSite });
|
|
3423
|
-
if (namespaces.length === 0) throw new Error(options.requireErdSite ? "No namespaces with erdSite configured found. Add erdSite: \"<static-website-name>\" to db.<namespace> in tailor.config.ts." : "No TailorDB namespaces found in config. Please define db services in tailor.config.ts.");
|
|
3424
|
-
logger.info(`Found ${namespaces.length} namespace(s)${options.requireErdSite ? " with erdSite configured" : ""}.`);
|
|
3425
|
-
targets = namespaces.map(({ namespace, erdSite }) => {
|
|
3426
|
-
const erdDir = path.join(baseDir, namespace);
|
|
3427
|
-
return {
|
|
3428
|
-
namespace,
|
|
3429
|
-
erdSite,
|
|
3430
|
-
schemaOutputPath: path.join(erdDir, "schema.json"),
|
|
3431
|
-
distDir: path.join(erdDir, "dist"),
|
|
3432
|
-
erdDir
|
|
3433
|
-
};
|
|
3434
|
-
});
|
|
3435
|
-
}
|
|
3436
|
-
await Promise.all(targets.map((target) => prepareErdBuild({
|
|
3437
|
-
namespace: target.namespace,
|
|
3438
|
-
client,
|
|
3439
|
-
workspaceId,
|
|
3440
|
-
outputPath: target.schemaOutputPath,
|
|
3441
|
-
erdDir: target.erdDir
|
|
3442
|
-
})));
|
|
3443
|
-
return targets;
|
|
3646
|
+
function prepareErdBuildsFromContext(options) {
|
|
3647
|
+
const outputDir = path.resolve(process.cwd(), options.outputDir ?? DEFAULT_ERD_BASE_DIR$1);
|
|
3648
|
+
return resolveTargets({
|
|
3649
|
+
context: options.context,
|
|
3650
|
+
namespace: options.namespace,
|
|
3651
|
+
outputDir,
|
|
3652
|
+
requireErdSite: options.requireErdSite
|
|
3653
|
+
}).map((target) => prepareErdBuild(target));
|
|
3444
3654
|
}
|
|
3445
3655
|
const erdExportCommand = defineAppCommand({
|
|
3446
3656
|
name: "export",
|
|
3447
|
-
description: "Export
|
|
3657
|
+
description: "Export TailorDB ERD static viewer from local TailorDB schema.",
|
|
3448
3658
|
args: z.object({
|
|
3449
|
-
...
|
|
3659
|
+
...configArg,
|
|
3450
3660
|
namespace: arg(z.string().optional(), {
|
|
3451
3661
|
alias: "n",
|
|
3452
3662
|
description: "TailorDB namespace name (optional if only one namespace is defined in config)"
|
|
3453
3663
|
}),
|
|
3454
|
-
output: arg(z.string().default(DEFAULT_ERD_BASE_DIR), {
|
|
3664
|
+
output: arg(z.string().default(DEFAULT_ERD_BASE_DIR$1), {
|
|
3455
3665
|
alias: "o",
|
|
3456
|
-
description: "Output directory path for
|
|
3666
|
+
description: "Output directory path for TailorDB ERD viewer files (writes to `<outputDir>/<namespace>/dist`)",
|
|
3457
3667
|
completion: { type: "directory" }
|
|
3458
3668
|
})
|
|
3459
3669
|
}).strict(),
|
|
3460
3670
|
run: async (args) => {
|
|
3461
|
-
|
|
3462
|
-
const outputDir = path.resolve(process.cwd(), String(args.output));
|
|
3671
|
+
initErdCommand();
|
|
3463
3672
|
const results = await prepareErdBuilds({
|
|
3464
|
-
|
|
3465
|
-
workspaceId,
|
|
3466
|
-
config,
|
|
3673
|
+
configPath: args.config,
|
|
3467
3674
|
namespace: args.namespace,
|
|
3468
|
-
outputDir
|
|
3675
|
+
outputDir: args.output
|
|
3469
3676
|
});
|
|
3470
3677
|
logger.newline();
|
|
3471
3678
|
if (args.json) logger.out(results.map((result) => ({
|
|
3472
3679
|
namespace: result.namespace,
|
|
3473
|
-
distDir: result.distDir
|
|
3474
|
-
schemaOutputPath: result.schemaOutputPath
|
|
3680
|
+
distDir: result.distDir
|
|
3475
3681
|
})));
|
|
3476
3682
|
else for (const result of results) {
|
|
3477
3683
|
logger.out(`Exported ERD for namespace "${result.namespace}"`);
|
|
3478
|
-
logger.out(` -
|
|
3479
|
-
logger.out(` - tbls schema.json: ${result.schemaOutputPath}`);
|
|
3684
|
+
logger.out(` - ERD viewer: ${path.join(result.distDir, "index.html")}`);
|
|
3480
3685
|
}
|
|
3481
3686
|
}
|
|
3482
3687
|
});
|
|
@@ -3495,11 +3700,9 @@ const erdDeployCommand = defineAppCommand({
|
|
|
3495
3700
|
}).strict(),
|
|
3496
3701
|
run: async (args) => {
|
|
3497
3702
|
await assertWritable({ profile: args.profile });
|
|
3498
|
-
const { client, workspaceId
|
|
3703
|
+
const { client, workspaceId } = await initErdDeployContext(args);
|
|
3499
3704
|
const buildResults = await prepareErdBuilds({
|
|
3500
|
-
|
|
3501
|
-
workspaceId,
|
|
3502
|
-
config,
|
|
3705
|
+
configPath: args.config,
|
|
3503
3706
|
namespace: args.namespace,
|
|
3504
3707
|
requireErdSite: true
|
|
3505
3708
|
});
|
|
@@ -3527,65 +3730,354 @@ const erdDeployCommand = defineAppCommand({
|
|
|
3527
3730
|
|
|
3528
3731
|
//#endregion
|
|
3529
3732
|
//#region src/cli/commands/tailordb/erd/serve.ts
|
|
3733
|
+
const DEFAULT_ERD_BASE_DIR = ".tailor-sdk/erd";
|
|
3734
|
+
const LOCAL_HOST = "127.0.0.1";
|
|
3735
|
+
const GLOB_CHARS = /[*?[\]{}()!+@]/;
|
|
3530
3736
|
function formatServeCommand(namespace) {
|
|
3531
3737
|
return `tailor-sdk tailordb erd serve --namespace ${namespace}`;
|
|
3532
3738
|
}
|
|
3533
|
-
|
|
3534
|
-
|
|
3739
|
+
function getCacheControl(filePath) {
|
|
3740
|
+
return filePath.endsWith(".html") || filePath.endsWith(".json") ? "no-cache" : "public, max-age=3600";
|
|
3741
|
+
}
|
|
3742
|
+
function resolveRequestPath(distDir, requestUrl) {
|
|
3743
|
+
const url = new URL(requestUrl ?? "/", "http://localhost");
|
|
3744
|
+
let pathname;
|
|
3745
|
+
try {
|
|
3746
|
+
pathname = decodeURIComponent(url.pathname);
|
|
3747
|
+
} catch {
|
|
3748
|
+
return;
|
|
3749
|
+
}
|
|
3750
|
+
if (pathname === "/" || pathname.endsWith("/")) pathname = path.join(pathname, "index.html");
|
|
3751
|
+
const root = path.resolve(distDir);
|
|
3752
|
+
const filePath = path.resolve(root, `.${pathname}`);
|
|
3753
|
+
if (filePath !== root && !filePath.startsWith(`${root}${path.sep}`)) return;
|
|
3754
|
+
return filePath;
|
|
3755
|
+
}
|
|
3756
|
+
function openStaticFile(filePath) {
|
|
3757
|
+
let fd;
|
|
3758
|
+
try {
|
|
3759
|
+
fd = fs$1.openSync(filePath, "r");
|
|
3760
|
+
if (!fs$1.fstatSync(fd).isFile()) {
|
|
3761
|
+
fs$1.closeSync(fd);
|
|
3762
|
+
return;
|
|
3763
|
+
}
|
|
3764
|
+
return {
|
|
3765
|
+
filePath,
|
|
3766
|
+
fd
|
|
3767
|
+
};
|
|
3768
|
+
} catch {
|
|
3769
|
+
if (fd !== void 0) fs$1.closeSync(fd);
|
|
3770
|
+
return;
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
function serveFile(distDir, req, res) {
|
|
3774
|
+
const filePath = resolveRequestPath(distDir, req.url);
|
|
3775
|
+
if (!filePath) {
|
|
3776
|
+
res.writeHead(403);
|
|
3777
|
+
res.end("Forbidden");
|
|
3778
|
+
return;
|
|
3779
|
+
}
|
|
3780
|
+
const fallbackPath = path.join(distDir, "index.html");
|
|
3781
|
+
const target = openStaticFile(filePath) ?? openStaticFile(fallbackPath);
|
|
3782
|
+
if (!target) {
|
|
3783
|
+
res.writeHead(503, {
|
|
3784
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
3785
|
+
"Cache-Control": "no-cache",
|
|
3786
|
+
"Retry-After": "1"
|
|
3787
|
+
});
|
|
3788
|
+
res.end("ERD build is refreshing. Please retry.");
|
|
3789
|
+
return;
|
|
3790
|
+
}
|
|
3791
|
+
const mimeType = lookup(target.filePath) || "application/octet-stream";
|
|
3792
|
+
const stream = fs$1.createReadStream(target.filePath, {
|
|
3793
|
+
fd: target.fd,
|
|
3794
|
+
autoClose: true
|
|
3795
|
+
});
|
|
3796
|
+
stream.on("error", () => {
|
|
3797
|
+
if (!res.headersSent) {
|
|
3798
|
+
res.writeHead(503, {
|
|
3799
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
3800
|
+
"Cache-Control": "no-cache",
|
|
3801
|
+
"Retry-After": "1"
|
|
3802
|
+
});
|
|
3803
|
+
res.end("ERD build is refreshing. Please retry.");
|
|
3804
|
+
return;
|
|
3805
|
+
}
|
|
3806
|
+
res.destroy();
|
|
3807
|
+
});
|
|
3808
|
+
res.writeHead(200, {
|
|
3809
|
+
"Content-Type": mimeType,
|
|
3810
|
+
"Cache-Control": getCacheControl(target.filePath)
|
|
3811
|
+
});
|
|
3812
|
+
stream.pipe(res);
|
|
3813
|
+
}
|
|
3814
|
+
async function startStaticServer(options) {
|
|
3815
|
+
const server = http.createServer((req, res) => {
|
|
3816
|
+
serveFile(options.distDir, req, res);
|
|
3817
|
+
});
|
|
3818
|
+
return await new Promise((resolve, reject) => {
|
|
3819
|
+
server.once("error", reject);
|
|
3820
|
+
server.listen(options.port, LOCAL_HOST, () => {
|
|
3821
|
+
server.off("error", reject);
|
|
3822
|
+
const address = server.address();
|
|
3823
|
+
if (!address || typeof address === "string") {
|
|
3824
|
+
reject(/* @__PURE__ */ new Error("Failed to determine ERD server address."));
|
|
3825
|
+
return;
|
|
3826
|
+
}
|
|
3827
|
+
resolve({
|
|
3828
|
+
server,
|
|
3829
|
+
url: `http://${LOCAL_HOST}:${address.port}`
|
|
3830
|
+
});
|
|
3831
|
+
});
|
|
3832
|
+
});
|
|
3833
|
+
}
|
|
3834
|
+
function getWatchPatterns(config, results) {
|
|
3835
|
+
const namespaces = new Set(results.map((result) => result.namespace));
|
|
3836
|
+
const patterns = [config.path];
|
|
3837
|
+
for (const namespace of namespaces) {
|
|
3838
|
+
const dbConfig = config.db?.[namespace];
|
|
3839
|
+
if (dbConfig && !("external" in dbConfig)) patterns.push(...dbConfig.files);
|
|
3840
|
+
}
|
|
3841
|
+
return [...new Set(patterns)];
|
|
3842
|
+
}
|
|
3843
|
+
function hasGlobPattern(pattern) {
|
|
3844
|
+
return GLOB_CHARS.test(pattern);
|
|
3845
|
+
}
|
|
3846
|
+
function globBaseDir(pattern) {
|
|
3847
|
+
const absolutePattern = path.resolve(pattern);
|
|
3848
|
+
const parsed = path.parse(absolutePattern);
|
|
3849
|
+
const relativePattern = absolutePattern.slice(parsed.root.length);
|
|
3850
|
+
const literalParts = [];
|
|
3851
|
+
for (const part of relativePattern.split(path.sep)) {
|
|
3852
|
+
if (!part || GLOB_CHARS.test(part)) break;
|
|
3853
|
+
literalParts.push(part);
|
|
3854
|
+
}
|
|
3855
|
+
const literalPath = literalParts.length > 0 ? path.join(parsed.root, ...literalParts) : parsed.root;
|
|
3856
|
+
if (!literalPath || literalPath === parsed.root) return parsed.root || process.cwd();
|
|
3857
|
+
if (!fs$1.existsSync(literalPath)) return path.dirname(literalPath);
|
|
3858
|
+
return fs$1.statSync(literalPath).isDirectory() ? literalPath : path.dirname(literalPath);
|
|
3859
|
+
}
|
|
3860
|
+
async function expandWatchPattern(pattern) {
|
|
3861
|
+
if (!hasGlobPattern(pattern)) return [path.resolve(pattern)];
|
|
3862
|
+
const paths = /* @__PURE__ */ new Set();
|
|
3863
|
+
for await (const file of glob(pattern)) paths.add(path.resolve(file));
|
|
3864
|
+
const baseDir = globBaseDir(pattern);
|
|
3865
|
+
if (fs$1.existsSync(baseDir) && fs$1.statSync(baseDir).isDirectory()) paths.add(baseDir);
|
|
3866
|
+
return [...paths];
|
|
3867
|
+
}
|
|
3868
|
+
async function resolveWatchPathsFromConfig(config, results) {
|
|
3869
|
+
const paths = /* @__PURE__ */ new Set();
|
|
3870
|
+
for (const pattern of getWatchPatterns(config, results)) for (const watchPath of await expandWatchPattern(pattern)) paths.add(watchPath);
|
|
3871
|
+
return [...paths];
|
|
3872
|
+
}
|
|
3873
|
+
async function resolveWatchPaths(context, results) {
|
|
3874
|
+
return await resolveWatchPathsFromConfig(context.config, results);
|
|
3875
|
+
}
|
|
3876
|
+
function parseFreshErdExportResults(stdout) {
|
|
3877
|
+
const lines = stdout.trim().split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
3878
|
+
for (const line of lines.toReversed()) try {
|
|
3879
|
+
const parsed = JSON.parse(line);
|
|
3880
|
+
if (!Array.isArray(parsed)) continue;
|
|
3881
|
+
return parsed.map((entry) => {
|
|
3882
|
+
const result = entry;
|
|
3883
|
+
if (typeof result.namespace !== "string" || typeof result.distDir !== "string") throw new Error("Invalid ERD export JSON output.");
|
|
3884
|
+
return {
|
|
3885
|
+
namespace: result.namespace,
|
|
3886
|
+
distDir: result.distDir
|
|
3887
|
+
};
|
|
3888
|
+
});
|
|
3889
|
+
} catch {
|
|
3890
|
+
continue;
|
|
3891
|
+
}
|
|
3892
|
+
throw new Error("Failed to parse ERD export JSON output.");
|
|
3893
|
+
}
|
|
3894
|
+
function freshErdExportArgs(options) {
|
|
3895
|
+
const cliEntry = process.argv[1];
|
|
3896
|
+
if (!cliEntry) throw new Error("Cannot rebuild ERD schema in a fresh process: CLI entrypoint is unavailable.");
|
|
3897
|
+
const args = [
|
|
3898
|
+
cliEntry,
|
|
3899
|
+
"tailordb",
|
|
3900
|
+
"erd",
|
|
3901
|
+
"export",
|
|
3902
|
+
"--output",
|
|
3903
|
+
options.outputDir,
|
|
3904
|
+
"--json"
|
|
3905
|
+
];
|
|
3906
|
+
if (options.configPath) args.push("--config", options.configPath);
|
|
3907
|
+
if (options.namespace) args.push("--namespace", options.namespace);
|
|
3908
|
+
return args;
|
|
3909
|
+
}
|
|
3910
|
+
async function runFreshErdExport(options) {
|
|
3911
|
+
return await new Promise((resolve, reject) => {
|
|
3912
|
+
const child = spawn(process.execPath, freshErdExportArgs(options), {
|
|
3913
|
+
cwd: process.cwd(),
|
|
3914
|
+
env: process.env,
|
|
3915
|
+
stdio: [
|
|
3916
|
+
"ignore",
|
|
3917
|
+
"pipe",
|
|
3918
|
+
"pipe"
|
|
3919
|
+
]
|
|
3920
|
+
});
|
|
3921
|
+
let stdout = "";
|
|
3922
|
+
let stderr = "";
|
|
3923
|
+
child.stdout.setEncoding("utf8");
|
|
3924
|
+
child.stderr.setEncoding("utf8");
|
|
3925
|
+
child.stdout.on("data", (chunk) => {
|
|
3926
|
+
stdout += chunk;
|
|
3927
|
+
});
|
|
3928
|
+
child.stderr.on("data", (chunk) => {
|
|
3929
|
+
stderr += chunk;
|
|
3930
|
+
});
|
|
3931
|
+
child.on("error", reject);
|
|
3932
|
+
child.on("close", (code, signal) => {
|
|
3933
|
+
if (code !== 0) {
|
|
3934
|
+
const detail = stderr.trim() || signal || `exit code ${code}`;
|
|
3935
|
+
reject(/* @__PURE__ */ new Error(`Fresh ERD export failed: ${detail}`));
|
|
3936
|
+
return;
|
|
3937
|
+
}
|
|
3938
|
+
try {
|
|
3939
|
+
resolve(parseFreshErdExportResults(stdout));
|
|
3940
|
+
} catch (error) {
|
|
3941
|
+
reject(error);
|
|
3942
|
+
}
|
|
3943
|
+
});
|
|
3944
|
+
});
|
|
3945
|
+
}
|
|
3946
|
+
function selectPrimaryResult(results) {
|
|
3535
3947
|
const [primary, ...rest] = results;
|
|
3948
|
+
if (!primary) throw new Error("No ERD build results found.");
|
|
3536
3949
|
logger.info(`Serving ERD for namespace "${primary.namespace}".`);
|
|
3537
3950
|
if (rest.length > 0) {
|
|
3538
3951
|
const commands = rest.map((result) => ` - ${formatServeCommand(result.namespace)}`).join("\n");
|
|
3539
3952
|
logger.warn(`Multiple namespaces found. To serve another namespace, run:\n${commands}`);
|
|
3540
3953
|
}
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3954
|
+
return primary;
|
|
3955
|
+
}
|
|
3956
|
+
async function createErdWatcher(options) {
|
|
3957
|
+
let rebuilding = false;
|
|
3958
|
+
let pending = false;
|
|
3959
|
+
let watchPaths = await resolveWatchPaths(options.initialContext, options.initialResults);
|
|
3960
|
+
let importNonce = 0;
|
|
3961
|
+
const watcher = watch(watchPaths, {
|
|
3962
|
+
ignored: /node_modules/,
|
|
3963
|
+
ignoreInitial: true,
|
|
3964
|
+
awaitWriteFinish: {
|
|
3965
|
+
stabilityThreshold: 100,
|
|
3966
|
+
pollInterval: 100
|
|
3967
|
+
}
|
|
3968
|
+
});
|
|
3969
|
+
async function rebuild() {
|
|
3970
|
+
if (rebuilding) {
|
|
3971
|
+
pending = true;
|
|
3972
|
+
return;
|
|
3973
|
+
}
|
|
3974
|
+
rebuilding = true;
|
|
3544
3975
|
try {
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3976
|
+
const results = await runFreshErdExport({
|
|
3977
|
+
configPath: options.configPath,
|
|
3978
|
+
namespace: options.namespace,
|
|
3979
|
+
outputDir: options.outputDir
|
|
3548
3980
|
});
|
|
3981
|
+
const { config } = await loadConfig(options.configPath, { importNonce: String(importNonce += 1) });
|
|
3982
|
+
const nextWatchPaths = await resolveWatchPathsFromConfig(config, results);
|
|
3983
|
+
watcher.unwatch(watchPaths);
|
|
3984
|
+
watcher.add(nextWatchPaths);
|
|
3985
|
+
watchPaths = nextWatchPaths;
|
|
3986
|
+
logger.success(`Rebuilt ERD schema (${results.map((result) => result.namespace).join(", ")})`, { mode: "stream" });
|
|
3549
3987
|
} catch (error) {
|
|
3988
|
+
logger.error("Failed to rebuild ERD schema. Serving the last successful build.", { mode: "stream" });
|
|
3550
3989
|
logger.error(String(error));
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
cwd: primary.erdDir
|
|
3557
|
-
});
|
|
3558
|
-
child.on("error", (error) => {
|
|
3559
|
-
logger.error("Failed to run `serve dist`.");
|
|
3560
|
-
reject(error);
|
|
3561
|
-
});
|
|
3562
|
-
child.on("exit", (code) => {
|
|
3563
|
-
if (code === 0) resolve();
|
|
3564
|
-
else {
|
|
3565
|
-
logger.error("serve CLI exited with a non-zero code. Ensure `serve dist` works in your project.");
|
|
3566
|
-
reject(/* @__PURE__ */ new Error(`serve CLI exited with code ${code ?? 1}`));
|
|
3990
|
+
} finally {
|
|
3991
|
+
rebuilding = false;
|
|
3992
|
+
if (pending) {
|
|
3993
|
+
pending = false;
|
|
3994
|
+
await rebuild();
|
|
3567
3995
|
}
|
|
3568
|
-
}
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
let debounceTimer;
|
|
3999
|
+
const scheduleRebuild = (changedPath) => {
|
|
4000
|
+
logger.info(`Schema source changed: ${path.relative(process.cwd(), changedPath)}`, { mode: "stream" });
|
|
4001
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
4002
|
+
debounceTimer = setTimeout(() => {
|
|
4003
|
+
rebuild();
|
|
4004
|
+
}, 150);
|
|
4005
|
+
};
|
|
4006
|
+
watcher.on("add", scheduleRebuild);
|
|
4007
|
+
watcher.on("change", scheduleRebuild);
|
|
4008
|
+
watcher.on("unlink", scheduleRebuild);
|
|
4009
|
+
watcher.on("error", (error) => {
|
|
4010
|
+
logger.error(`ERD watcher error: ${String(error)}`, { mode: "stream" });
|
|
4011
|
+
});
|
|
4012
|
+
return watcher;
|
|
4013
|
+
}
|
|
4014
|
+
async function waitForShutdown(server, watcher) {
|
|
4015
|
+
return await new Promise((resolve) => {
|
|
4016
|
+
const shutdown = () => {
|
|
4017
|
+
watcher.close().finally(() => {
|
|
4018
|
+
server.close(() => {
|
|
4019
|
+
logger.info("ERD server stopped.");
|
|
4020
|
+
resolve();
|
|
4021
|
+
});
|
|
4022
|
+
});
|
|
4023
|
+
};
|
|
4024
|
+
process.once("SIGINT", shutdown);
|
|
4025
|
+
process.once("SIGTERM", shutdown);
|
|
3569
4026
|
});
|
|
3570
4027
|
}
|
|
3571
4028
|
const erdServeCommand = defineAppCommand({
|
|
3572
4029
|
name: "serve",
|
|
3573
|
-
description: "Generate and serve ERD locally
|
|
4030
|
+
description: "Generate and serve TailorDB ERD locally with watch reload. (beta)",
|
|
3574
4031
|
args: z.object({
|
|
3575
|
-
...
|
|
4032
|
+
...configArg,
|
|
3576
4033
|
namespace: arg(z.string().optional(), {
|
|
3577
4034
|
alias: "n",
|
|
3578
4035
|
description: "TailorDB namespace name (uses first namespace in config if not specified)"
|
|
3579
|
-
})
|
|
4036
|
+
}),
|
|
4037
|
+
port: arg(z.coerce.number().int().min(0).max(65535).default(0), { description: "Local server port (0 selects a free port)" }),
|
|
4038
|
+
open: arg(z.boolean().default(false), { description: "Open the ERD viewer in the default browser" })
|
|
3580
4039
|
}).strict(),
|
|
3581
4040
|
run: async (args) => {
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
4041
|
+
initErdCommand();
|
|
4042
|
+
const outputDir = path.resolve(process.cwd(), DEFAULT_ERD_BASE_DIR);
|
|
4043
|
+
const context = await loadLocalErdSchema({
|
|
4044
|
+
configPath: args.config,
|
|
4045
|
+
namespaces: args.namespace ? [args.namespace] : void 0
|
|
4046
|
+
});
|
|
4047
|
+
const results = prepareErdBuildsFromContext({
|
|
4048
|
+
context,
|
|
4049
|
+
namespace: args.namespace,
|
|
4050
|
+
outputDir
|
|
4051
|
+
});
|
|
4052
|
+
const primary = selectPrimaryResult(results);
|
|
4053
|
+
const { server, url } = await startStaticServer({
|
|
4054
|
+
distDir: primary.distDir,
|
|
4055
|
+
port: args.port
|
|
4056
|
+
});
|
|
4057
|
+
const watchUrl = `${url}/?watch=1`;
|
|
4058
|
+
const watcher = await createErdWatcher({
|
|
4059
|
+
configPath: args.config,
|
|
4060
|
+
namespace: args.namespace,
|
|
4061
|
+
outputDir,
|
|
4062
|
+
initialContext: context,
|
|
4063
|
+
initialResults: results
|
|
4064
|
+
});
|
|
4065
|
+
logger.newline();
|
|
4066
|
+
if (args.json) logger.out({
|
|
4067
|
+
namespace: primary.namespace,
|
|
4068
|
+
url: watchUrl,
|
|
4069
|
+
distDir: primary.distDir
|
|
4070
|
+
});
|
|
4071
|
+
else {
|
|
4072
|
+
logger.success("ERD server started.");
|
|
4073
|
+
logger.out(watchUrl);
|
|
4074
|
+
}
|
|
4075
|
+
if (args.open) try {
|
|
4076
|
+
await open(watchUrl);
|
|
4077
|
+
} catch {
|
|
4078
|
+
logger.warn("Failed to open browser automatically. Please open the URL above manually.");
|
|
4079
|
+
}
|
|
4080
|
+
await waitForShutdown(server, watcher);
|
|
3589
4081
|
}
|
|
3590
4082
|
});
|
|
3591
4083
|
|
|
@@ -3593,7 +4085,7 @@ const erdServeCommand = defineAppCommand({
|
|
|
3593
4085
|
//#region src/cli/commands/tailordb/erd/index.ts
|
|
3594
4086
|
const erdCommand = defineCommand({
|
|
3595
4087
|
name: "erd",
|
|
3596
|
-
description: "Generate ERD artifacts
|
|
4088
|
+
description: "Generate TailorDB ERD viewer artifacts from local TailorDB schema. (beta)",
|
|
3597
4089
|
subCommands: {
|
|
3598
4090
|
export: erdExportCommand,
|
|
3599
4091
|
serve: erdServeCommand,
|
|
@@ -3714,10 +4206,10 @@ async function set(options) {
|
|
|
3714
4206
|
useProfile: false,
|
|
3715
4207
|
profile: options.profile
|
|
3716
4208
|
}));
|
|
3717
|
-
const trn =
|
|
4209
|
+
const trn = resourceTrn(await loadWorkspaceId({
|
|
3718
4210
|
workspaceId: options.workspaceId,
|
|
3719
4211
|
profile: options.profile
|
|
3720
|
-
})
|
|
4212
|
+
}), "tailordb", targetNamespace);
|
|
3721
4213
|
let currentMigration;
|
|
3722
4214
|
try {
|
|
3723
4215
|
const { metadata } = await client.getMetadata({ trn });
|
|
@@ -3806,7 +4298,7 @@ async function collectMigrationStatuses(options) {
|
|
|
3806
4298
|
});
|
|
3807
4299
|
const statuses = [];
|
|
3808
4300
|
for (const { namespace, migrationsDir } of targetNamespaces) {
|
|
3809
|
-
const trn =
|
|
4301
|
+
const trn = resourceTrn(workspaceId, "tailordb", namespace);
|
|
3810
4302
|
let currentMigration;
|
|
3811
4303
|
try {
|
|
3812
4304
|
const { metadata } = await client.getMetadata({ trn });
|
|
@@ -4362,7 +4854,7 @@ runMain(mainCommand, {
|
|
|
4362
4854
|
if (isVerbose() && error.stack) logger.debug(`\nStack trace:\n${error.stack}`);
|
|
4363
4855
|
} else logger.error(`Unknown error: ${error}`);
|
|
4364
4856
|
if (!isCLIError(error) && (!(error instanceof Error) || error instanceof TypeError || error instanceof RangeError)) {
|
|
4365
|
-
const { reportCrash } = await import("../crashreport-
|
|
4857
|
+
const { reportCrash } = await import("../crashreport-CXD_Kjk-.mjs");
|
|
4366
4858
|
await reportCrash(error, "handledError");
|
|
4367
4859
|
}
|
|
4368
4860
|
}
|