@tailor-platform/sdk 1.11.0 → 1.12.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 +34 -0
- package/dist/application-DM4zTgXU.mjs +4 -0
- package/dist/{application-BKBo5tGD.mjs → application-DnWZVbDO.mjs} +164 -26
- package/dist/application-DnWZVbDO.mjs.map +1 -0
- package/dist/cli/index.mjs +13 -19
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +149 -17
- package/dist/cli/lib.mjs +132 -4
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/configure/index.d.mts +4 -3
- package/dist/configure/index.mjs +16 -757
- package/dist/configure/index.mjs.map +1 -1
- package/dist/env-4RO7szrH.d.mts +66 -0
- package/dist/{index-D8zIUsWi.d.mts → index-BBr_q3vB.d.mts} +20 -11
- package/dist/{index-DcOTucF6.d.mts → index-Bid18Opo.d.mts} +473 -84
- package/dist/{jiti-ygK9KoRA.mjs → jiti-DuCiUfMj.mjs} +2 -2
- package/dist/{jiti-ygK9KoRA.mjs.map → jiti-DuCiUfMj.mjs.map} +1 -1
- package/dist/{job-l-pIR9IY.mjs → job-zGAXCidt.mjs} +1 -1
- package/dist/{job-l-pIR9IY.mjs.map → job-zGAXCidt.mjs.map} +1 -1
- package/dist/kysely/index.d.mts +25 -0
- package/dist/kysely/index.mjs +27 -0
- package/dist/kysely/index.mjs.map +1 -0
- package/dist/plugin/index.d.mts +105 -0
- package/dist/plugin/index.mjs +45 -0
- package/dist/plugin/index.mjs.map +1 -0
- package/dist/schema-BmKdDzr1.mjs +784 -0
- package/dist/schema-BmKdDzr1.mjs.map +1 -0
- package/dist/{src-CG8kJBI9.mjs → src-QNTCsO6J.mjs} +2 -2
- package/dist/{src-CG8kJBI9.mjs.map → src-QNTCsO6J.mjs.map} +1 -1
- package/dist/types-r-ZratAg.mjs +13 -0
- package/dist/types-r-ZratAg.mjs.map +1 -0
- package/dist/{update-D0muqqOP.mjs → update-B_W-UQnS.mjs} +1626 -390
- package/dist/update-B_W-UQnS.mjs.map +1 -0
- package/dist/utils/test/index.d.mts +2 -2
- package/dist/utils/test/index.mjs +1 -1
- package/docs/cli/tailordb.md +0 -12
- package/docs/generator/builtin.md +2 -2
- package/docs/plugin/custom.md +389 -0
- package/docs/plugin/index.md +112 -0
- package/docs/services/workflow.md +28 -0
- package/package.json +16 -23
- package/dist/application-BKBo5tGD.mjs.map +0 -1
- package/dist/application-a12-7TT3.mjs +0 -4
- package/dist/update-D0muqqOP.mjs.map +0 -1
- /package/dist/{chunk-CIV_ash9.mjs → chunk-C3Kl5s5P.mjs} +0 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as isPluginGeneratedType } from "./types-r-ZratAg.mjs";
|
|
2
|
+
import { t as db } from "./schema-BmKdDzr1.mjs";
|
|
3
|
+
import { a as ExecutorSchema, c as stringifyFunction, d as logger, f as styles, i as createExecutorService, l as tailorUserMap, n as WorkflowJobSchema, o as OAuth2ClientSchema, p as symbols, r as WorkflowSchema, s as ResolverSchema, t as defineApplication, u as loadFilesWithIgnores } from "./application-DnWZVbDO.mjs";
|
|
2
4
|
import { createRequire } from "node:module";
|
|
5
|
+
import { cloneDeep } from "es-toolkit";
|
|
3
6
|
import { arg, defineCommand, runCommand } from "politty";
|
|
4
7
|
import { z } from "zod";
|
|
5
8
|
import * as fs$2 from "node:fs";
|
|
@@ -2126,12 +2129,12 @@ async function loadExecutor(executorFilePath) {
|
|
|
2126
2129
|
* This function:
|
|
2127
2130
|
* 1. Creates entry file that extracts operation.body
|
|
2128
2131
|
* 2. Bundles in a single step with tree-shaking
|
|
2129
|
-
* @param
|
|
2130
|
-
* @param triggerContext - Trigger context for workflow/job transformations
|
|
2132
|
+
* @param options - Bundle executor options
|
|
2131
2133
|
* @returns Promise that resolves when bundling completes
|
|
2132
2134
|
*/
|
|
2133
|
-
async function bundleExecutors(
|
|
2134
|
-
const
|
|
2135
|
+
async function bundleExecutors(options) {
|
|
2136
|
+
const { config, triggerContext, additionalFiles = [] } = options;
|
|
2137
|
+
const files = [...loadFilesWithIgnores(config), ...additionalFiles];
|
|
2135
2138
|
if (files.length === 0) {
|
|
2136
2139
|
logger.warn(`No executor files found for patterns: ${config.files?.join(", ") ?? "(none)"}`);
|
|
2137
2140
|
return;
|
|
@@ -2680,6 +2683,163 @@ function createGeneratorConfigSchema(builtinGenerators$1) {
|
|
|
2680
2683
|
}).brand("CodeGenerator");
|
|
2681
2684
|
}
|
|
2682
2685
|
|
|
2686
|
+
//#endregion
|
|
2687
|
+
//#region src/parser/plugin-config/schema.ts
|
|
2688
|
+
const unauthenticatedTailorUser$1 = {
|
|
2689
|
+
id: "00000000-0000-0000-0000-000000000000",
|
|
2690
|
+
type: "",
|
|
2691
|
+
workspaceId: "00000000-0000-0000-0000-000000000000",
|
|
2692
|
+
attributes: null,
|
|
2693
|
+
attributeList: []
|
|
2694
|
+
};
|
|
2695
|
+
const PluginGeneratedTypeSchema = z.object({
|
|
2696
|
+
name: z.string(),
|
|
2697
|
+
fields: z.record(z.string(), z.unknown())
|
|
2698
|
+
});
|
|
2699
|
+
const PluginGeneratedResolverSchema = z.object({
|
|
2700
|
+
name: z.string(),
|
|
2701
|
+
operation: z.enum(["query", "mutation"]),
|
|
2702
|
+
inputFields: z.record(z.string(), z.unknown()).optional(),
|
|
2703
|
+
outputFields: z.record(z.string(), z.unknown()),
|
|
2704
|
+
body: z.string()
|
|
2705
|
+
});
|
|
2706
|
+
const PluginTriggerConfigSchema = z.object({
|
|
2707
|
+
kind: z.enum([
|
|
2708
|
+
"recordCreated",
|
|
2709
|
+
"recordUpdated",
|
|
2710
|
+
"recordDeleted",
|
|
2711
|
+
"schedule",
|
|
2712
|
+
"incomingWebhook"
|
|
2713
|
+
]),
|
|
2714
|
+
type: z.string().optional(),
|
|
2715
|
+
schedule: z.string().optional()
|
|
2716
|
+
});
|
|
2717
|
+
const PluginOperationConfigSchema = z.object({
|
|
2718
|
+
kind: z.enum([
|
|
2719
|
+
"function",
|
|
2720
|
+
"webhook",
|
|
2721
|
+
"graphql",
|
|
2722
|
+
"workflow"
|
|
2723
|
+
]),
|
|
2724
|
+
body: z.string().optional(),
|
|
2725
|
+
url: z.string().optional(),
|
|
2726
|
+
query: z.string().optional()
|
|
2727
|
+
});
|
|
2728
|
+
const PluginGeneratedExecutorSchema = z.object({
|
|
2729
|
+
name: z.string(),
|
|
2730
|
+
description: z.string().optional(),
|
|
2731
|
+
trigger: PluginTriggerConfigSchema,
|
|
2732
|
+
operation: PluginOperationConfigSchema
|
|
2733
|
+
});
|
|
2734
|
+
z.object({
|
|
2735
|
+
types: z.array(PluginGeneratedTypeSchema).optional(),
|
|
2736
|
+
resolvers: z.array(PluginGeneratedResolverSchema).optional(),
|
|
2737
|
+
executors: z.array(PluginGeneratedExecutorSchema).optional()
|
|
2738
|
+
});
|
|
2739
|
+
const CustomPluginSchema = z.object({
|
|
2740
|
+
id: z.string(),
|
|
2741
|
+
description: z.string(),
|
|
2742
|
+
importPath: z.string(),
|
|
2743
|
+
configSchema: z.any().optional(),
|
|
2744
|
+
pluginConfigSchema: z.any().optional(),
|
|
2745
|
+
pluginConfig: z.any().optional(),
|
|
2746
|
+
process: z.any().optional(),
|
|
2747
|
+
processNamespace: z.any().optional()
|
|
2748
|
+
}).superRefine((plugin, ctx) => {
|
|
2749
|
+
if (plugin.process && !plugin.configSchema) ctx.addIssue({
|
|
2750
|
+
code: z.ZodIssueCode.custom,
|
|
2751
|
+
message: "process requires configSchema to be defined.",
|
|
2752
|
+
path: ["configSchema"]
|
|
2753
|
+
});
|
|
2754
|
+
}).passthrough();
|
|
2755
|
+
const CustomPluginTupleSchema = z.tuple([CustomPluginSchema, z.unknown()]);
|
|
2756
|
+
/**
|
|
2757
|
+
* Type guard to check if a value is a PluginBase object
|
|
2758
|
+
* @param value - Value to check
|
|
2759
|
+
* @returns True if value is a PluginBase object
|
|
2760
|
+
*/
|
|
2761
|
+
function isPluginBase(value) {
|
|
2762
|
+
return CustomPluginSchema.safeParse(value).success;
|
|
2763
|
+
}
|
|
2764
|
+
function normalizePluginConfigSchema(schema) {
|
|
2765
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2766
|
+
const stack = [schema];
|
|
2767
|
+
while (stack.length > 0) {
|
|
2768
|
+
const field = stack.pop();
|
|
2769
|
+
if (!field || seen.has(field)) continue;
|
|
2770
|
+
seen.add(field);
|
|
2771
|
+
const requiredExplicit = field._metadata.requiredExplicit === true;
|
|
2772
|
+
field._metadata.required = requiredExplicit;
|
|
2773
|
+
for (const nestedField of Object.values(field.fields)) stack.push(nestedField);
|
|
2774
|
+
}
|
|
2775
|
+
return schema;
|
|
2776
|
+
}
|
|
2777
|
+
function clonePluginConfigSchema(schema) {
|
|
2778
|
+
return cloneDeep(schema);
|
|
2779
|
+
}
|
|
2780
|
+
function normalizePluginBase(plugin) {
|
|
2781
|
+
let normalized = plugin;
|
|
2782
|
+
if (normalized.configSchema) {
|
|
2783
|
+
const clonedConfigSchema = clonePluginConfigSchema(normalized.configSchema);
|
|
2784
|
+
normalizePluginConfigSchema(clonedConfigSchema);
|
|
2785
|
+
normalized = {
|
|
2786
|
+
...normalized,
|
|
2787
|
+
configSchema: clonedConfigSchema
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
if (normalized.pluginConfigSchema) {
|
|
2791
|
+
const pluginConfigSchema = clonePluginConfigSchema(normalized.pluginConfigSchema);
|
|
2792
|
+
normalizePluginConfigSchema(pluginConfigSchema);
|
|
2793
|
+
normalized = {
|
|
2794
|
+
...normalized,
|
|
2795
|
+
pluginConfigSchema
|
|
2796
|
+
};
|
|
2797
|
+
if (normalized.pluginConfig !== void 0) {
|
|
2798
|
+
const validationErrors = validatePluginConfig$1(normalized.pluginConfig, pluginConfigSchema);
|
|
2799
|
+
if (validationErrors.length > 0) {
|
|
2800
|
+
const errorDetails = validationErrors.map((e) => e.field ? `${e.field}: ${e.message}` : e.message).join("; ");
|
|
2801
|
+
throw new Error(`Invalid pluginConfig for plugin "${normalized.id}": ${errorDetails}`);
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
return normalized;
|
|
2806
|
+
}
|
|
2807
|
+
/**
|
|
2808
|
+
* Validate plugin config against its schema
|
|
2809
|
+
* @param config - The config object to validate
|
|
2810
|
+
* @param schema - The schema defining expected fields
|
|
2811
|
+
* @returns Array of validation errors (empty if valid)
|
|
2812
|
+
*/
|
|
2813
|
+
function validatePluginConfig$1(config, schema) {
|
|
2814
|
+
const result = schema.parse({
|
|
2815
|
+
value: config,
|
|
2816
|
+
data: config,
|
|
2817
|
+
user: unauthenticatedTailorUser$1
|
|
2818
|
+
});
|
|
2819
|
+
if ("issues" in result && result.issues) return result.issues.map((issue) => ({
|
|
2820
|
+
field: Array.isArray(issue.path) ? issue.path.join(".") : "",
|
|
2821
|
+
message: issue.message
|
|
2822
|
+
}));
|
|
2823
|
+
return [];
|
|
2824
|
+
}
|
|
2825
|
+
/**
|
|
2826
|
+
* Creates a PluginConfigSchema for custom plugins
|
|
2827
|
+
* @returns Plugin config schema that validates and transforms PluginBase instances
|
|
2828
|
+
*/
|
|
2829
|
+
function createPluginConfigSchema() {
|
|
2830
|
+
return z.union([CustomPluginSchema, CustomPluginTupleSchema]).transform((plugin) => {
|
|
2831
|
+
if (Array.isArray(plugin)) {
|
|
2832
|
+
const [first, options] = plugin;
|
|
2833
|
+
if (isPluginBase(first)) return normalizePluginBase({
|
|
2834
|
+
...first,
|
|
2835
|
+
pluginConfig: options
|
|
2836
|
+
});
|
|
2837
|
+
throw new Error(`Invalid plugin configuration: expected PluginBase object`);
|
|
2838
|
+
}
|
|
2839
|
+
return normalizePluginBase(plugin);
|
|
2840
|
+
}).brand("Plugin");
|
|
2841
|
+
}
|
|
2842
|
+
|
|
2683
2843
|
//#endregion
|
|
2684
2844
|
//#region src/cli/generator/types.ts
|
|
2685
2845
|
/**
|
|
@@ -3118,28 +3278,22 @@ function generateUnifiedKyselyTypes(namespaceData) {
|
|
|
3118
3278
|
Timestamp: false,
|
|
3119
3279
|
Serial: false
|
|
3120
3280
|
});
|
|
3121
|
-
const
|
|
3122
|
-
if (globalUsedUtilityTypes.Timestamp)
|
|
3123
|
-
|
|
3124
|
-
type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
|
|
3125
|
-
? ColumnType<S, I | undefined, U>
|
|
3126
|
-
: ColumnType<T, T | undefined, T>;
|
|
3127
|
-
`);
|
|
3128
|
-
if (globalUsedUtilityTypes.Serial) utilityTypeDeclarations.push(`type Serial<T = string | number> = ColumnType<T, never, never>;`);
|
|
3281
|
+
const utilityTypeImports = ["type Generated"];
|
|
3282
|
+
if (globalUsedUtilityTypes.Timestamp) utilityTypeImports.push("type Timestamp");
|
|
3283
|
+
if (globalUsedUtilityTypes.Serial) utilityTypeImports.push("type Serial");
|
|
3129
3284
|
return [
|
|
3130
3285
|
ml`
|
|
3131
3286
|
import {
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
type
|
|
3135
|
-
type
|
|
3136
|
-
type
|
|
3137
|
-
type
|
|
3138
|
-
type
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
${utilityTypeDeclarations.join("\n")}
|
|
3287
|
+
createGetDB,
|
|
3288
|
+
${utilityTypeImports.join(",\n")},
|
|
3289
|
+
type NamespaceDB,
|
|
3290
|
+
type NamespaceInsertable,
|
|
3291
|
+
type NamespaceSelectable,
|
|
3292
|
+
type NamespaceTable,
|
|
3293
|
+
type NamespaceTableName,
|
|
3294
|
+
type NamespaceTransaction,
|
|
3295
|
+
type NamespaceUpdateable,
|
|
3296
|
+
} from "@tailor-platform/sdk/kysely";
|
|
3143
3297
|
`,
|
|
3144
3298
|
`export interface Namespace {\n${namespaceData.map(({ namespace, types }) => {
|
|
3145
3299
|
return ` "${namespace}": {\n${types.map((type) => {
|
|
@@ -3147,44 +3301,20 @@ function generateUnifiedKyselyTypes(namespaceData) {
|
|
|
3147
3301
|
}).join("\n\n")}\n }`;
|
|
3148
3302
|
}).join(",\n")}\n}`,
|
|
3149
3303
|
ml`
|
|
3150
|
-
export
|
|
3151
|
-
namespace: N,
|
|
3152
|
-
kyselyConfig?: Omit<KyselyConfig, "dialect">,
|
|
3153
|
-
): Kysely<Namespace[N]> {
|
|
3154
|
-
const client = new tailordb.Client({ namespace });
|
|
3155
|
-
return new Kysely<Namespace[N]>({
|
|
3156
|
-
dialect: new TailordbDialect(client),
|
|
3157
|
-
...kyselyConfig,
|
|
3158
|
-
});
|
|
3159
|
-
}
|
|
3304
|
+
export const getDB = createGetDB<Namespace>();
|
|
3160
3305
|
|
|
3161
|
-
export type DB<N extends keyof Namespace = keyof Namespace> =
|
|
3306
|
+
export type DB<N extends keyof Namespace = keyof Namespace> = NamespaceDB<Namespace, N>;
|
|
3162
3307
|
`,
|
|
3163
3308
|
ml`
|
|
3164
3309
|
export type Transaction<K extends keyof Namespace | DB = keyof Namespace> =
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
type TableName =
|
|
3172
|
-
|
|
3173
|
-
}[keyof Namespace];
|
|
3174
|
-
export type Table<T extends TableName> = {
|
|
3175
|
-
[N in keyof Namespace]: T extends keyof Namespace[N] ? Namespace[N][T]
|
|
3176
|
-
: never;
|
|
3177
|
-
}[keyof Namespace];
|
|
3178
|
-
|
|
3179
|
-
export type Insertable<T extends keyof Namespace[keyof Namespace]> = KyselyInsertable<
|
|
3180
|
-
Table<T>
|
|
3181
|
-
>;
|
|
3182
|
-
export type Selectable<T extends keyof Namespace[keyof Namespace]> = KyselySelectable<
|
|
3183
|
-
Table<T>
|
|
3184
|
-
>;
|
|
3185
|
-
export type Updateable<T extends keyof Namespace[keyof Namespace]> = KyselyUpdateable<
|
|
3186
|
-
Table<T>
|
|
3187
|
-
>;
|
|
3310
|
+
NamespaceTransaction<Namespace, K>;
|
|
3311
|
+
|
|
3312
|
+
type TableName = NamespaceTableName<Namespace>;
|
|
3313
|
+
export type Table<T extends TableName> = NamespaceTable<Namespace, T>;
|
|
3314
|
+
|
|
3315
|
+
export type Insertable<T extends TableName> = NamespaceInsertable<Namespace, T>;
|
|
3316
|
+
export type Selectable<T extends TableName> = NamespaceSelectable<Namespace, T>;
|
|
3317
|
+
export type Updateable<T extends TableName> = NamespaceUpdateable<Namespace, T>;
|
|
3188
3318
|
`
|
|
3189
3319
|
].join("\n\n") + "\n";
|
|
3190
3320
|
}
|
|
@@ -3237,6 +3367,32 @@ function createKyselyGenerator(options) {
|
|
|
3237
3367
|
};
|
|
3238
3368
|
}
|
|
3239
3369
|
|
|
3370
|
+
//#endregion
|
|
3371
|
+
//#region src/cli/utils/plugin-import.ts
|
|
3372
|
+
/**
|
|
3373
|
+
* Collect base directories for resolving plugin import paths.
|
|
3374
|
+
* @param configPath - Path to tailor.config.ts
|
|
3375
|
+
* @returns Ordered list of base directories
|
|
3376
|
+
*/
|
|
3377
|
+
function getPluginImportBaseDirs(configPath) {
|
|
3378
|
+
if (configPath) return [path.dirname(configPath)];
|
|
3379
|
+
return [process.cwd()];
|
|
3380
|
+
}
|
|
3381
|
+
/**
|
|
3382
|
+
* Resolve a relative plugin import path against candidate base directories.
|
|
3383
|
+
* @param pluginImportPath - Relative plugin import path
|
|
3384
|
+
* @param baseDirs - Candidate base directories
|
|
3385
|
+
* @returns Absolute path if found, otherwise null
|
|
3386
|
+
*/
|
|
3387
|
+
function resolveRelativePluginImportPath(pluginImportPath, baseDirs) {
|
|
3388
|
+
if (!pluginImportPath.startsWith(".")) return null;
|
|
3389
|
+
for (const baseDir of baseDirs) {
|
|
3390
|
+
const absolutePath = path.resolve(baseDir, pluginImportPath);
|
|
3391
|
+
if (fs$2.existsSync(absolutePath)) return absolutePath;
|
|
3392
|
+
}
|
|
3393
|
+
return null;
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3240
3396
|
//#endregion
|
|
3241
3397
|
//#region src/cli/generator/builtin/seed/idp-user-processor.ts
|
|
3242
3398
|
/**
|
|
@@ -3314,7 +3470,45 @@ function generateIdpUserSchemaFile(usernameField, userTypeName) {
|
|
|
3314
3470
|
* @returns Generated lines-db metadata
|
|
3315
3471
|
*/
|
|
3316
3472
|
function processLinesDb(type, source) {
|
|
3317
|
-
if (
|
|
3473
|
+
if (isPluginGeneratedType(source)) return processLinesDbForPluginType(type, source);
|
|
3474
|
+
if (!source.filePath) throw new Error(`Missing source info for type ${type.name}`);
|
|
3475
|
+
if (!source.exportName) throw new Error(`Missing export name for type ${type.name}`);
|
|
3476
|
+
const { optionalFields, omitFields, indexes, foreignKeys } = extractFieldMetadata(type);
|
|
3477
|
+
return {
|
|
3478
|
+
typeName: type.name,
|
|
3479
|
+
exportName: source.exportName,
|
|
3480
|
+
importPath: source.filePath,
|
|
3481
|
+
optionalFields,
|
|
3482
|
+
omitFields,
|
|
3483
|
+
foreignKeys,
|
|
3484
|
+
indexes
|
|
3485
|
+
};
|
|
3486
|
+
}
|
|
3487
|
+
/**
|
|
3488
|
+
* Process lines-db metadata for plugin-generated types
|
|
3489
|
+
* @param type - Parsed TailorDB type
|
|
3490
|
+
* @param source - Plugin-generated type source info
|
|
3491
|
+
* @returns Generated lines-db metadata with plugin source
|
|
3492
|
+
*/
|
|
3493
|
+
function processLinesDbForPluginType(type, source) {
|
|
3494
|
+
const { optionalFields, omitFields, indexes, foreignKeys } = extractFieldMetadata(type);
|
|
3495
|
+
return {
|
|
3496
|
+
typeName: type.name,
|
|
3497
|
+
exportName: source.exportName,
|
|
3498
|
+
importPath: "",
|
|
3499
|
+
optionalFields,
|
|
3500
|
+
omitFields,
|
|
3501
|
+
foreignKeys,
|
|
3502
|
+
indexes,
|
|
3503
|
+
pluginSource: source
|
|
3504
|
+
};
|
|
3505
|
+
}
|
|
3506
|
+
/**
|
|
3507
|
+
* Extract field metadata from TailorDB type
|
|
3508
|
+
* @param type - Parsed TailorDB type
|
|
3509
|
+
* @returns Field metadata including optional fields, omit fields, indexes, and foreign keys
|
|
3510
|
+
*/
|
|
3511
|
+
function extractFieldMetadata(type) {
|
|
3318
3512
|
const optionalFields = ["id"];
|
|
3319
3513
|
const omitFields = [];
|
|
3320
3514
|
const indexes = [];
|
|
@@ -3341,29 +3535,19 @@ function processLinesDb(type, source) {
|
|
|
3341
3535
|
}
|
|
3342
3536
|
});
|
|
3343
3537
|
return {
|
|
3344
|
-
typeName: type.name,
|
|
3345
|
-
exportName: source.exportName,
|
|
3346
|
-
importPath: source.filePath,
|
|
3347
3538
|
optionalFields,
|
|
3348
3539
|
omitFields,
|
|
3349
|
-
|
|
3350
|
-
|
|
3540
|
+
indexes,
|
|
3541
|
+
foreignKeys
|
|
3351
3542
|
};
|
|
3352
3543
|
}
|
|
3353
3544
|
/**
|
|
3354
|
-
*
|
|
3355
|
-
* @param
|
|
3356
|
-
* @param
|
|
3357
|
-
* @returns Schema
|
|
3545
|
+
* Generate schema options code for lines-db
|
|
3546
|
+
* @param foreignKeys - Foreign key definitions
|
|
3547
|
+
* @param indexes - Index definitions
|
|
3548
|
+
* @returns Schema options code string
|
|
3358
3549
|
*/
|
|
3359
|
-
function
|
|
3360
|
-
const { exportName, optionalFields, omitFields, foreignKeys, indexes } = metadata;
|
|
3361
|
-
const schemaTypeCode = ml`
|
|
3362
|
-
const schemaType = t.object({
|
|
3363
|
-
...${exportName}.pickFields(${JSON.stringify(optionalFields)}, { optional: true }),
|
|
3364
|
-
...${exportName}.omitFields(${JSON.stringify([...optionalFields, ...omitFields])}),
|
|
3365
|
-
});
|
|
3366
|
-
`;
|
|
3550
|
+
function generateSchemaOptions(foreignKeys, indexes) {
|
|
3367
3551
|
const schemaOptions = [];
|
|
3368
3552
|
if (foreignKeys.length > 0) {
|
|
3369
3553
|
schemaOptions.push(`foreignKeys: [`);
|
|
@@ -3379,22 +3563,92 @@ function generateLinesDbSchemaFile(metadata, importPath) {
|
|
|
3379
3563
|
});
|
|
3380
3564
|
schemaOptions.push("],");
|
|
3381
3565
|
}
|
|
3566
|
+
return schemaOptions.length > 0 ? [
|
|
3567
|
+
"\n {",
|
|
3568
|
+
...schemaOptions.map((option) => ` ${option}`),
|
|
3569
|
+
" }"
|
|
3570
|
+
].join("\n") : "";
|
|
3571
|
+
}
|
|
3572
|
+
/**
|
|
3573
|
+
* Generates the schema file content for lines-db (for user-defined types with import)
|
|
3574
|
+
* @param metadata - lines-db metadata
|
|
3575
|
+
* @param importPath - Import path for the TailorDB type
|
|
3576
|
+
* @returns Schema file contents
|
|
3577
|
+
*/
|
|
3578
|
+
function generateLinesDbSchemaFile(metadata, importPath) {
|
|
3579
|
+
const { exportName, optionalFields, omitFields, foreignKeys, indexes } = metadata;
|
|
3382
3580
|
return ml`
|
|
3383
3581
|
import { t } from "@tailor-platform/sdk";
|
|
3384
3582
|
import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test";
|
|
3385
3583
|
import { defineSchema } from "@toiroakr/lines-db";
|
|
3386
3584
|
import { ${exportName} } from "${importPath}";
|
|
3387
3585
|
|
|
3586
|
+
${ml`
|
|
3587
|
+
const schemaType = t.object({
|
|
3588
|
+
...${exportName}.pickFields(${JSON.stringify(optionalFields)}, { optional: true }),
|
|
3589
|
+
...${exportName}.omitFields(${JSON.stringify([...optionalFields, ...omitFields])}),
|
|
3590
|
+
});
|
|
3591
|
+
`}
|
|
3592
|
+
|
|
3593
|
+
const hook = createTailorDBHook(${exportName});
|
|
3594
|
+
|
|
3595
|
+
export const schema = defineSchema(
|
|
3596
|
+
createStandardSchema(schemaType, hook),${generateSchemaOptions(foreignKeys, indexes)}
|
|
3597
|
+
);
|
|
3598
|
+
|
|
3599
|
+
`;
|
|
3600
|
+
}
|
|
3601
|
+
/**
|
|
3602
|
+
* Generates the schema file content using getGeneratedType API
|
|
3603
|
+
* (for plugin-generated types)
|
|
3604
|
+
* @param metadata - lines-db metadata (must have pluginSource)
|
|
3605
|
+
* @param params - Plugin import paths
|
|
3606
|
+
* @returns Schema file contents
|
|
3607
|
+
*/
|
|
3608
|
+
function generateLinesDbSchemaFileWithPluginAPI(metadata, params) {
|
|
3609
|
+
const { typeName, exportName, optionalFields, omitFields, foreignKeys, indexes, pluginSource } = metadata;
|
|
3610
|
+
if (!pluginSource) throw new Error(`pluginSource is required for plugin-generated type "${typeName}"`);
|
|
3611
|
+
const { pluginImportPath, originalImportPath } = params;
|
|
3612
|
+
const schemaTypeCode = ml`
|
|
3613
|
+
const schemaType = t.object({
|
|
3614
|
+
...${exportName}.pickFields(${JSON.stringify(optionalFields)}, { optional: true }),
|
|
3615
|
+
...${exportName}.omitFields(${JSON.stringify([...optionalFields, ...omitFields])}),
|
|
3616
|
+
});
|
|
3617
|
+
`;
|
|
3618
|
+
const schemaOptionsCode = generateSchemaOptions(foreignKeys, indexes);
|
|
3619
|
+
if (pluginSource.originalExportName && originalImportPath && pluginSource.generatedTypeKind) return ml`
|
|
3620
|
+
import { t } from "@tailor-platform/sdk";
|
|
3621
|
+
import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test";
|
|
3622
|
+
import { defineSchema } from "@toiroakr/lines-db";
|
|
3623
|
+
import { getGeneratedType } from "${pluginImportPath}";
|
|
3624
|
+
import { ${pluginSource.originalExportName} } from "${originalImportPath}";
|
|
3625
|
+
|
|
3626
|
+
const ${exportName} = getGeneratedType(${pluginSource.originalExportName}, "${pluginSource.generatedTypeKind}");
|
|
3627
|
+
|
|
3388
3628
|
${schemaTypeCode}
|
|
3389
3629
|
|
|
3390
3630
|
const hook = createTailorDBHook(${exportName});
|
|
3391
3631
|
|
|
3392
3632
|
export const schema = defineSchema(
|
|
3393
|
-
createStandardSchema(schemaType, hook),${
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3633
|
+
createStandardSchema(schemaType, hook),${schemaOptionsCode}
|
|
3634
|
+
);
|
|
3635
|
+
|
|
3636
|
+
`;
|
|
3637
|
+
if (!pluginSource.generatedTypeKind) throw new Error(`Namespace plugin "${pluginSource.pluginId}" must provide generatedTypeKind for type "${typeName}"`);
|
|
3638
|
+
return ml`
|
|
3639
|
+
import { t } from "@tailor-platform/sdk";
|
|
3640
|
+
import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test";
|
|
3641
|
+
import { defineSchema } from "@toiroakr/lines-db";
|
|
3642
|
+
import { getGeneratedType } from "${pluginImportPath}";
|
|
3643
|
+
|
|
3644
|
+
const ${exportName} = getGeneratedType(null, "${pluginSource.generatedTypeKind}");
|
|
3645
|
+
|
|
3646
|
+
${schemaTypeCode}
|
|
3647
|
+
|
|
3648
|
+
const hook = createTailorDBHook(${exportName});
|
|
3649
|
+
|
|
3650
|
+
export const schema = defineSchema(
|
|
3651
|
+
createStandardSchema(schemaType, hook),${schemaOptionsCode}
|
|
3398
3652
|
);
|
|
3399
3653
|
|
|
3400
3654
|
`;
|
|
@@ -3512,6 +3766,7 @@ function generateExecScript(machineUserName, relativeConfigPath, namespaceConfig
|
|
|
3512
3766
|
getMachineUserToken,
|
|
3513
3767
|
truncate,
|
|
3514
3768
|
bundleSeedScript,
|
|
3769
|
+
chunkSeedData,
|
|
3515
3770
|
executeScript,
|
|
3516
3771
|
initOperatorClient,
|
|
3517
3772
|
loadAccessToken,
|
|
@@ -3782,55 +4037,89 @@ ${namespaceConfigs.map(({ namespace, dependencies }) => {
|
|
|
3782
4037
|
// Bundle seed script
|
|
3783
4038
|
const bundled = await bundleSeedScript(namespace, typesWithData);
|
|
3784
4039
|
|
|
3785
|
-
//
|
|
3786
|
-
const
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
code: bundled.bundledCode,
|
|
3791
|
-
arg: JSON.stringify({ data, order: sortedTypes }),
|
|
3792
|
-
invoker: {
|
|
3793
|
-
namespace: authNamespace,
|
|
3794
|
-
machineUserName: "${machineUserName}",
|
|
3795
|
-
},
|
|
4040
|
+
// Chunk seed data to fit within gRPC message size limits
|
|
4041
|
+
const chunks = chunkSeedData({
|
|
4042
|
+
data,
|
|
4043
|
+
order: sortedTypes,
|
|
4044
|
+
codeByteSize: new TextEncoder().encode(bundled.bundledCode).length,
|
|
3796
4045
|
});
|
|
3797
4046
|
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
console.log(styleText("dim", \` \${line}\`));
|
|
3802
|
-
}
|
|
4047
|
+
if (chunks.length === 0) {
|
|
4048
|
+
console.log(styleText("dim", \` [\${namespace}] No data to seed\`));
|
|
4049
|
+
return { success: true, processed: {} };
|
|
3803
4050
|
}
|
|
3804
4051
|
|
|
3805
|
-
if (
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
4052
|
+
if (chunks.length > 1) {
|
|
4053
|
+
console.log(styleText("dim", \` Split into \${chunks.length} chunks\`));
|
|
4054
|
+
}
|
|
4055
|
+
|
|
4056
|
+
const allProcessed = {};
|
|
4057
|
+
let hasError = false;
|
|
4058
|
+
const allErrors = [];
|
|
4059
|
+
|
|
4060
|
+
for (const chunk of chunks) {
|
|
4061
|
+
if (chunks.length > 1) {
|
|
4062
|
+
console.log(styleText("dim", \` Chunk \${chunk.index + 1}/\${chunk.total}: \${chunk.order.join(", ")}\`));
|
|
3814
4063
|
}
|
|
3815
4064
|
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
4065
|
+
// Execute seed script for this chunk
|
|
4066
|
+
const result = await executeScript({
|
|
4067
|
+
client: operatorClient,
|
|
4068
|
+
workspaceId,
|
|
4069
|
+
name: \`seed-\${namespace}.ts\`,
|
|
4070
|
+
code: bundled.bundledCode,
|
|
4071
|
+
arg: JSON.stringify({ data: chunk.data, order: chunk.order }),
|
|
4072
|
+
invoker: {
|
|
4073
|
+
namespace: authNamespace,
|
|
4074
|
+
machineUserName: "${machineUserName}",
|
|
4075
|
+
},
|
|
4076
|
+
});
|
|
4077
|
+
|
|
4078
|
+
// Parse result and display logs
|
|
4079
|
+
if (result.logs) {
|
|
4080
|
+
for (const line of result.logs.split("\\n").filter(Boolean)) {
|
|
4081
|
+
console.log(styleText("dim", \` \${line}\`));
|
|
4082
|
+
}
|
|
3819
4083
|
}
|
|
3820
4084
|
|
|
3821
|
-
if (
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
4085
|
+
if (result.success) {
|
|
4086
|
+
let parsed;
|
|
4087
|
+
try {
|
|
4088
|
+
const parsedResult = JSON.parse(result.result || "{}");
|
|
4089
|
+
parsed = parsedResult && typeof parsedResult === "object" ? parsedResult : {};
|
|
4090
|
+
} catch (error) {
|
|
4091
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4092
|
+
console.error(styleText("red", \` ✗ Failed to parse seed result: \${message}\`));
|
|
4093
|
+
hasError = true;
|
|
4094
|
+
allErrors.push(message);
|
|
4095
|
+
continue;
|
|
4096
|
+
}
|
|
4097
|
+
|
|
4098
|
+
const processed = parsed.processed || {};
|
|
4099
|
+
for (const [type, count] of Object.entries(processed)) {
|
|
4100
|
+
allProcessed[type] = (allProcessed[type] || 0) + count;
|
|
4101
|
+
console.log(styleText("green", \` ✓ \${type}: \${count} rows inserted\`));
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4104
|
+
if (!parsed.success) {
|
|
4105
|
+
const errors = Array.isArray(parsed.errors) ? parsed.errors : [];
|
|
4106
|
+
const errorMessage =
|
|
4107
|
+
errors.length > 0 ? errors.join("\\n") : "Seed script reported failure";
|
|
4108
|
+
console.error(styleText("red", \` ✗ Seed failed: \${errorMessage}\`));
|
|
4109
|
+
hasError = true;
|
|
4110
|
+
allErrors.push(errorMessage);
|
|
4111
|
+
}
|
|
4112
|
+
} else {
|
|
4113
|
+
console.error(styleText("red", \` ✗ Seed failed: \${result.error}\`));
|
|
4114
|
+
hasError = true;
|
|
4115
|
+
allErrors.push(result.error);
|
|
3827
4116
|
}
|
|
4117
|
+
}
|
|
3828
4118
|
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
console.error(styleText("red", \` ✗ Seed failed: \${result.error}\`));
|
|
3832
|
-
return { success: false, error: result.error };
|
|
4119
|
+
if (hasError) {
|
|
4120
|
+
return { success: false, error: allErrors.join("\\n") };
|
|
3833
4121
|
}
|
|
4122
|
+
return { success: true, processed: allProcessed };
|
|
3834
4123
|
};
|
|
3835
4124
|
|
|
3836
4125
|
${generateIdpUserSeedFunction(hasIdpUser)}
|
|
@@ -3896,6 +4185,7 @@ function createSeedGenerator(options) {
|
|
|
3896
4185
|
processTailorDBNamespace: ({ types }) => types,
|
|
3897
4186
|
aggregate: ({ input, configPath }) => {
|
|
3898
4187
|
const files = [];
|
|
4188
|
+
const pluginImportBaseDirs = getPluginImportBaseDirs(configPath);
|
|
3899
4189
|
const namespaceConfigs = [];
|
|
3900
4190
|
for (const nsResult of input.tailordb) {
|
|
3901
4191
|
if (!nsResult.types) continue;
|
|
@@ -3912,12 +4202,34 @@ function createSeedGenerator(options) {
|
|
|
3912
4202
|
skipIfExists: true
|
|
3913
4203
|
});
|
|
3914
4204
|
const schemaOutputPath = path.join(outputBaseDir, "data", `${linesDb.typeName}.schema.ts`);
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
4205
|
+
if (linesDb.pluginSource && linesDb.pluginSource.pluginImportPath) {
|
|
4206
|
+
let originalImportPath;
|
|
4207
|
+
if (linesDb.pluginSource.originalFilePath && linesDb.pluginSource.originalExportName) {
|
|
4208
|
+
const relativePath = path.relative(path.dirname(schemaOutputPath), linesDb.pluginSource.originalFilePath);
|
|
4209
|
+
originalImportPath = relativePath.replace(/\.ts$/, "").startsWith(".") ? relativePath.replace(/\.ts$/, "") : `./${relativePath.replace(/\.ts$/, "")}`;
|
|
4210
|
+
}
|
|
4211
|
+
let pluginImportPath = linesDb.pluginSource.pluginImportPath;
|
|
4212
|
+
if (pluginImportPath.startsWith("./") || pluginImportPath.startsWith("../")) {
|
|
4213
|
+
const resolvedPluginPath = resolveRelativePluginImportPath(pluginImportPath, pluginImportBaseDirs) ?? path.resolve(pluginImportBaseDirs[0] ?? process.cwd(), pluginImportPath);
|
|
4214
|
+
const relativePluginPath = path.relative(path.dirname(schemaOutputPath), resolvedPluginPath);
|
|
4215
|
+
pluginImportPath = relativePluginPath.startsWith(".") ? relativePluginPath : `./${relativePluginPath}`;
|
|
4216
|
+
}
|
|
4217
|
+
const schemaContent = generateLinesDbSchemaFileWithPluginAPI(linesDb, {
|
|
4218
|
+
pluginImportPath,
|
|
4219
|
+
originalImportPath
|
|
4220
|
+
});
|
|
4221
|
+
files.push({
|
|
4222
|
+
path: schemaOutputPath,
|
|
4223
|
+
content: schemaContent
|
|
4224
|
+
});
|
|
4225
|
+
} else {
|
|
4226
|
+
const relativePath = path.relative(path.dirname(schemaOutputPath), linesDb.importPath);
|
|
4227
|
+
const schemaContent = generateLinesDbSchemaFile(linesDb, relativePath.replace(/\.ts$/, "").startsWith(".") ? relativePath.replace(/\.ts$/, "") : `./${relativePath.replace(/\.ts$/, "")}`);
|
|
4228
|
+
files.push({
|
|
4229
|
+
path: schemaOutputPath,
|
|
4230
|
+
content: schemaContent
|
|
4231
|
+
});
|
|
4232
|
+
}
|
|
3921
4233
|
}
|
|
3922
4234
|
namespaceConfigs.push({
|
|
3923
4235
|
namespace: nsResult.namespace,
|
|
@@ -3971,10 +4283,11 @@ const builtinGenerators = new Map([
|
|
|
3971
4283
|
[FileUtilsGeneratorID, (options) => createFileUtilsGenerator(options)]
|
|
3972
4284
|
]);
|
|
3973
4285
|
const GeneratorConfigSchema = createGeneratorConfigSchema(builtinGenerators);
|
|
4286
|
+
const PluginConfigSchema = createPluginConfigSchema();
|
|
3974
4287
|
/**
|
|
3975
|
-
* Load Tailor configuration file and associated generators.
|
|
4288
|
+
* Load Tailor configuration file and associated generators and plugins.
|
|
3976
4289
|
* @param configPath - Optional explicit config path
|
|
3977
|
-
* @returns Loaded config and
|
|
4290
|
+
* @returns Loaded config, generators, plugins, and config path
|
|
3978
4291
|
*/
|
|
3979
4292
|
async function loadConfig(configPath) {
|
|
3980
4293
|
const foundPath = loadConfigPath(configPath);
|
|
@@ -3984,8 +4297,9 @@ async function loadConfig(configPath) {
|
|
|
3984
4297
|
const configModule = await import(pathToFileURL(resolvedPath).href);
|
|
3985
4298
|
if (!configModule || !configModule.default) throw new Error("Invalid Tailor config module: default export not found");
|
|
3986
4299
|
const allGenerators = [];
|
|
4300
|
+
const allPlugins = [];
|
|
3987
4301
|
for (const value of Object.values(configModule)) if (Array.isArray(value)) {
|
|
3988
|
-
const
|
|
4302
|
+
const generatorParsed = value.reduce((acc, item) => {
|
|
3989
4303
|
if (!acc.success) return acc;
|
|
3990
4304
|
const result = GeneratorConfigSchema.safeParse(item);
|
|
3991
4305
|
if (result.success) acc.items.push(result.data);
|
|
@@ -3995,16 +4309,532 @@ async function loadConfig(configPath) {
|
|
|
3995
4309
|
success: true,
|
|
3996
4310
|
items: []
|
|
3997
4311
|
});
|
|
3998
|
-
|
|
4312
|
+
if (generatorParsed.success && generatorParsed.items.length > 0) {
|
|
4313
|
+
allGenerators.push(...generatorParsed.items);
|
|
4314
|
+
continue;
|
|
4315
|
+
}
|
|
4316
|
+
const pluginParsed = value.reduce((acc, item) => {
|
|
4317
|
+
if (!acc.success) return acc;
|
|
4318
|
+
const result = PluginConfigSchema.safeParse(item);
|
|
4319
|
+
if (result.success) acc.items.push(result.data);
|
|
4320
|
+
else acc.success = false;
|
|
4321
|
+
return acc;
|
|
4322
|
+
}, {
|
|
4323
|
+
success: true,
|
|
4324
|
+
items: []
|
|
4325
|
+
});
|
|
4326
|
+
if (pluginParsed.success && pluginParsed.items.length > 0) allPlugins.push(...pluginParsed.items);
|
|
3999
4327
|
}
|
|
4000
4328
|
return {
|
|
4001
4329
|
config: {
|
|
4002
4330
|
...configModule.default,
|
|
4003
4331
|
path: resolvedPath
|
|
4004
4332
|
},
|
|
4005
|
-
generators: allGenerators
|
|
4333
|
+
generators: allGenerators,
|
|
4334
|
+
plugins: allPlugins
|
|
4335
|
+
};
|
|
4336
|
+
}
|
|
4337
|
+
|
|
4338
|
+
//#endregion
|
|
4339
|
+
//#region src/parser/plugin-config/types.ts
|
|
4340
|
+
/**
|
|
4341
|
+
* Type guard to check if executor uses the new dynamic import format.
|
|
4342
|
+
* @param executor - Executor definition to check
|
|
4343
|
+
* @returns True if executor uses dynamic import format
|
|
4344
|
+
*/
|
|
4345
|
+
function isPluginExecutorWithFile(executor) {
|
|
4346
|
+
return "resolve" in executor && "context" in executor;
|
|
4347
|
+
}
|
|
4348
|
+
|
|
4349
|
+
//#endregion
|
|
4350
|
+
//#region src/cli/generator/plugin-executor-generator.ts
|
|
4351
|
+
/**
|
|
4352
|
+
* Plugin Executor Generator
|
|
4353
|
+
*
|
|
4354
|
+
* Generates TypeScript files for plugin-generated executors.
|
|
4355
|
+
* Supports both legacy format (inline trigger/operation) and new format (executorFile/context).
|
|
4356
|
+
*/
|
|
4357
|
+
/**
|
|
4358
|
+
* Generate TypeScript files for plugin-generated executors.
|
|
4359
|
+
* These files will be processed by the standard executor bundler.
|
|
4360
|
+
* @param executors - Array of plugin executor information
|
|
4361
|
+
* @param outputDir - Base output directory (e.g., .tailor-sdk)
|
|
4362
|
+
* @param typeGenerationResult - Result from plugin type generation (for import resolution)
|
|
4363
|
+
* @param sourceTypeInfoMap - Map of source type names to their source info
|
|
4364
|
+
* @param configPath - Path to tailor.config.ts (used for resolving plugin import paths)
|
|
4365
|
+
* @returns Array of generated file paths
|
|
4366
|
+
*/
|
|
4367
|
+
function generatePluginExecutorFiles(executors, outputDir, typeGenerationResult, sourceTypeInfoMap, configPath) {
|
|
4368
|
+
if (executors.length === 0) return [];
|
|
4369
|
+
const generatedFiles = [];
|
|
4370
|
+
const baseDirs = getPluginImportBaseDirs(configPath);
|
|
4371
|
+
for (const info of executors) {
|
|
4372
|
+
const filePath = generateSingleExecutorFile(info, outputDir, typeGenerationResult, sourceTypeInfoMap, baseDirs);
|
|
4373
|
+
generatedFiles.push(filePath);
|
|
4374
|
+
const relativePath = path.relative(process.cwd(), filePath);
|
|
4375
|
+
logger.log(` Plugin Executor File: ${styles.success(relativePath)} from plugin ${styles.info(info.pluginId)}`);
|
|
4376
|
+
}
|
|
4377
|
+
return generatedFiles;
|
|
4378
|
+
}
|
|
4379
|
+
/**
|
|
4380
|
+
* Generate a single executor file.
|
|
4381
|
+
* @param info - Plugin executor metadata and definition
|
|
4382
|
+
* @param outputDir - Base output directory (e.g., .tailor-sdk)
|
|
4383
|
+
* @param typeGenerationResult - Result from plugin type generation
|
|
4384
|
+
* @param sourceTypeInfoMap - Map of source type names to their source info
|
|
4385
|
+
* @param baseDirs - Base directories for resolving plugin import paths
|
|
4386
|
+
* @returns Absolute path to the generated file
|
|
4387
|
+
*/
|
|
4388
|
+
function generateSingleExecutorFile(info, outputDir, typeGenerationResult, sourceTypeInfoMap, baseDirs = []) {
|
|
4389
|
+
const pluginDir = sanitizePluginId$1(info.pluginId);
|
|
4390
|
+
const executorOutputDir = path.join(outputDir, pluginDir, "executors");
|
|
4391
|
+
fs$2.mkdirSync(executorOutputDir, { recursive: true });
|
|
4392
|
+
const fileName = sanitizeExecutorFileName(info.executor.name);
|
|
4393
|
+
const filePath = path.join(executorOutputDir, `${fileName}.ts`);
|
|
4394
|
+
let content;
|
|
4395
|
+
if (isPluginExecutorWithFile(info.executor)) content = generateExecutorFileContentNew(info, info.executor, outputDir, typeGenerationResult, sourceTypeInfoMap, baseDirs);
|
|
4396
|
+
else content = generateExecutorFileContentLegacy(info.executor);
|
|
4397
|
+
fs$2.writeFileSync(filePath, content);
|
|
4398
|
+
return filePath;
|
|
4399
|
+
}
|
|
4400
|
+
/**
|
|
4401
|
+
* Generate TypeScript file content for new format executor (dynamic import).
|
|
4402
|
+
* Uses the executor's resolve function to dynamically import the module.
|
|
4403
|
+
* @param info - Plugin executor information
|
|
4404
|
+
* @param executor - Executor definition with resolve
|
|
4405
|
+
* @param outputDir - Base output directory
|
|
4406
|
+
* @param typeGenerationResult - Result from plugin type generation
|
|
4407
|
+
* @param sourceTypeInfoMap - Map of source type names to their source info
|
|
4408
|
+
* @param baseDirs - Base directories for resolving plugin import paths
|
|
4409
|
+
* @returns TypeScript source code for executor file
|
|
4410
|
+
*/
|
|
4411
|
+
function generateExecutorFileContentNew(info, executor, outputDir, typeGenerationResult, sourceTypeInfoMap, baseDirs = []) {
|
|
4412
|
+
const { resolve, context } = executor;
|
|
4413
|
+
const pluginDir = sanitizePluginId$1(info.pluginId);
|
|
4414
|
+
const executorOutputDir = path.join(outputDir, pluginDir, "executors");
|
|
4415
|
+
const executorImportPath = resolveExecutorImportPath(resolve, info.pluginImportPath, executorOutputDir, baseDirs);
|
|
4416
|
+
const typeImports = collectTypeImports(context, outputDir, info.pluginId, typeGenerationResult, sourceTypeInfoMap);
|
|
4417
|
+
const imports = [];
|
|
4418
|
+
for (const [, importInfo] of typeImports) imports.push(`import { ${importInfo.variableName} } from "${importInfo.importPath}";`);
|
|
4419
|
+
const contextCode = generateContextCode(context, typeImports);
|
|
4420
|
+
return ml`
|
|
4421
|
+
/**
|
|
4422
|
+
* Auto-generated executor by plugin: ${info.pluginId}
|
|
4423
|
+
* DO NOT EDIT - This file is generated by @tailor-platform/sdk
|
|
4424
|
+
*/
|
|
4425
|
+
${imports.join("\n")}
|
|
4426
|
+
|
|
4427
|
+
const { default: executorFactory } = await import(${JSON.stringify(executorImportPath)});
|
|
4428
|
+
if (typeof executorFactory !== "function") {
|
|
4429
|
+
throw new Error(
|
|
4430
|
+
"Plugin executor module must export a default function created by withPluginContext().",
|
|
4431
|
+
);
|
|
4432
|
+
}
|
|
4433
|
+
export default executorFactory(${contextCode});
|
|
4434
|
+
`;
|
|
4435
|
+
}
|
|
4436
|
+
/**
|
|
4437
|
+
* Collect type imports needed for context.
|
|
4438
|
+
* @param context - Executor context values from plugin
|
|
4439
|
+
* @param outputDir - Base output directory for generated files
|
|
4440
|
+
* @param pluginId - Plugin identifier used for output paths
|
|
4441
|
+
* @param typeGenerationResult - Result from plugin type generation
|
|
4442
|
+
* @param sourceTypeInfoMap - Map of source type names to their source info
|
|
4443
|
+
* @returns Map of context keys to their import information
|
|
4444
|
+
*/
|
|
4445
|
+
function collectTypeImports(context, outputDir, pluginId, typeGenerationResult, sourceTypeInfoMap) {
|
|
4446
|
+
const typeImports = /* @__PURE__ */ new Map();
|
|
4447
|
+
const pluginDir = sanitizePluginId$1(pluginId);
|
|
4448
|
+
const executorDir = path.join(outputDir, pluginDir, "executors");
|
|
4449
|
+
for (const [key, value] of Object.entries(context)) if (isTypeObject(value)) {
|
|
4450
|
+
const typeName = value.name;
|
|
4451
|
+
const sourceInfo = sourceTypeInfoMap?.get(typeName);
|
|
4452
|
+
const variableName = sourceInfo?.exportName ?? toCamelCase$1(typeName);
|
|
4453
|
+
let importPath;
|
|
4454
|
+
let isGeneratedType = false;
|
|
4455
|
+
if (typeGenerationResult?.typeFilePaths.has(typeName)) {
|
|
4456
|
+
const typeFilePath = typeGenerationResult.typeFilePaths.get(typeName);
|
|
4457
|
+
const absoluteTypePath = path.join(outputDir, typeFilePath);
|
|
4458
|
+
importPath = path.relative(executorDir, absoluteTypePath).replace(/\.ts$/, "");
|
|
4459
|
+
if (!importPath.startsWith(".")) importPath = `./${importPath}`;
|
|
4460
|
+
isGeneratedType = true;
|
|
4461
|
+
} else if (sourceInfo) {
|
|
4462
|
+
const sourceFilePath = sourceInfo.filePath;
|
|
4463
|
+
importPath = path.relative(executorDir, sourceFilePath).replace(/\.ts$/, "");
|
|
4464
|
+
if (!importPath.startsWith(".")) importPath = `./${importPath}`;
|
|
4465
|
+
} else importPath = `../../../../tailordb/${toKebabCase$1(typeName)}`;
|
|
4466
|
+
typeImports.set(key, {
|
|
4467
|
+
variableName,
|
|
4468
|
+
importPath,
|
|
4469
|
+
isGeneratedType
|
|
4470
|
+
});
|
|
4471
|
+
}
|
|
4472
|
+
return typeImports;
|
|
4473
|
+
}
|
|
4474
|
+
/**
|
|
4475
|
+
* Generate TypeScript code for context object.
|
|
4476
|
+
* @param context - Executor context values from plugin
|
|
4477
|
+
* @param typeImports - Resolved type import information for context keys
|
|
4478
|
+
* @returns TypeScript object literal code
|
|
4479
|
+
*/
|
|
4480
|
+
function generateContextCode(context, typeImports) {
|
|
4481
|
+
const entries = [];
|
|
4482
|
+
for (const [key, value] of Object.entries(context)) if (isTypeObject(value)) {
|
|
4483
|
+
const importInfo = typeImports.get(key);
|
|
4484
|
+
if (importInfo) entries.push(` ${key}: ${importInfo.variableName}`);
|
|
4485
|
+
} else if (value !== void 0) entries.push(` ${key}: ${JSON.stringify(value)}`);
|
|
4486
|
+
return `{\n${entries.join(",\n")},\n}`;
|
|
4487
|
+
}
|
|
4488
|
+
/**
|
|
4489
|
+
* Check if a value is a TailorDB type object.
|
|
4490
|
+
* @param value - Value to inspect
|
|
4491
|
+
* @returns True if value is a type object with name and fields
|
|
4492
|
+
*/
|
|
4493
|
+
function isTypeObject(value) {
|
|
4494
|
+
return typeof value === "object" && value !== null && "name" in value && "fields" in value && typeof value.name === "string";
|
|
4495
|
+
}
|
|
4496
|
+
/**
|
|
4497
|
+
* Generate TypeScript file content for legacy format executor (trigger/operation).
|
|
4498
|
+
* @param executor - Legacy executor definition
|
|
4499
|
+
* @returns TypeScript source code for executor file
|
|
4500
|
+
*/
|
|
4501
|
+
function generateExecutorFileContentLegacy(executor) {
|
|
4502
|
+
const triggerCode = generateTriggerCode(executor.trigger);
|
|
4503
|
+
const operationCode = generateOperationCode(executor.operation);
|
|
4504
|
+
const injectDeclarations = generateInjectDeclarations(executor.operation.kind === "function" ? executor.operation.inject : void 0);
|
|
4505
|
+
const descriptionLine = executor.description ? `\n description: ${JSON.stringify(executor.description)},` : "";
|
|
4506
|
+
return ml`
|
|
4507
|
+
/**
|
|
4508
|
+
* Auto-generated executor by plugin
|
|
4509
|
+
* DO NOT EDIT - This file is generated by @tailor-platform/sdk
|
|
4510
|
+
*/
|
|
4511
|
+
import { createExecutor } from "@tailor-platform/sdk";
|
|
4512
|
+
${injectDeclarations}
|
|
4513
|
+
export default createExecutor({
|
|
4514
|
+
name: ${JSON.stringify(executor.name)},${descriptionLine}
|
|
4515
|
+
trigger: ${triggerCode},
|
|
4516
|
+
operation: ${operationCode},
|
|
4517
|
+
});
|
|
4518
|
+
`;
|
|
4519
|
+
}
|
|
4520
|
+
/**
|
|
4521
|
+
* Generate const declarations for injected variables.
|
|
4522
|
+
* @param inject - Map of injected values keyed by variable name
|
|
4523
|
+
* @returns TypeScript const declarations or empty string
|
|
4524
|
+
*/
|
|
4525
|
+
function generateInjectDeclarations(inject) {
|
|
4526
|
+
if (!inject || Object.keys(inject).length === 0) return "";
|
|
4527
|
+
return `\n// Injected variables from plugin\n${Object.entries(inject).map(([name, value]) => `const ${name} = ${JSON.stringify(value)};`).join("\n")}\n`;
|
|
4528
|
+
}
|
|
4529
|
+
/**
|
|
4530
|
+
* Generate TypeScript code for trigger configuration.
|
|
4531
|
+
* @param trigger - Trigger configuration for executor
|
|
4532
|
+
* @returns TypeScript code for trigger object
|
|
4533
|
+
*/
|
|
4534
|
+
function generateTriggerCode(trigger) {
|
|
4535
|
+
switch (trigger.kind) {
|
|
4536
|
+
case "recordCreated":
|
|
4537
|
+
case "recordUpdated":
|
|
4538
|
+
case "recordDeleted": return `{
|
|
4539
|
+
kind: ${JSON.stringify(trigger.kind)},
|
|
4540
|
+
typeName: ${JSON.stringify(trigger.typeName)},
|
|
4541
|
+
}`;
|
|
4542
|
+
case "schedule": return `{
|
|
4543
|
+
kind: "schedule",
|
|
4544
|
+
cron: ${JSON.stringify(trigger.cron)},
|
|
4545
|
+
timezone: ${JSON.stringify(trigger.timezone ?? "UTC")},
|
|
4546
|
+
}`;
|
|
4547
|
+
case "incomingWebhook": return `{
|
|
4548
|
+
kind: "incomingWebhook",
|
|
4549
|
+
}`;
|
|
4550
|
+
default: throw new Error(`Unknown trigger kind: ${trigger.kind}`);
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
/**
|
|
4554
|
+
* Generate TypeScript code for operation configuration.
|
|
4555
|
+
* @param operation - Operation configuration for executor
|
|
4556
|
+
* @returns TypeScript code for operation object
|
|
4557
|
+
*/
|
|
4558
|
+
function generateOperationCode(operation) {
|
|
4559
|
+
switch (operation.kind) {
|
|
4560
|
+
case "graphql": {
|
|
4561
|
+
const appNameLine = operation.appName ? `\n appName: ${JSON.stringify(operation.appName)},` : "";
|
|
4562
|
+
const variablesLine = operation.variables ? `\n variables: ${operation.variables},` : "";
|
|
4563
|
+
return `{
|
|
4564
|
+
kind: "graphql",
|
|
4565
|
+
query: \`${escapeTemplateLiteral(operation.query)}\`,${appNameLine}${variablesLine}
|
|
4566
|
+
}`;
|
|
4567
|
+
}
|
|
4568
|
+
case "function": return `{
|
|
4569
|
+
kind: "function",
|
|
4570
|
+
body: ${operation.body},
|
|
4571
|
+
}`;
|
|
4572
|
+
case "webhook": return `{
|
|
4573
|
+
kind: "webhook",
|
|
4574
|
+
url: () => ${JSON.stringify(operation.url)},
|
|
4575
|
+
}`;
|
|
4576
|
+
case "workflow": return `{
|
|
4577
|
+
kind: "workflow",
|
|
4578
|
+
workflowName: ${JSON.stringify(operation.workflowName)},
|
|
4579
|
+
}`;
|
|
4580
|
+
default: throw new Error(`Unknown operation kind: ${operation.kind}`);
|
|
4581
|
+
}
|
|
4582
|
+
}
|
|
4583
|
+
/**
|
|
4584
|
+
* Escape special characters in template literal content.
|
|
4585
|
+
* @param str - Raw template literal content
|
|
4586
|
+
* @returns Escaped string safe for template literals
|
|
4587
|
+
*/
|
|
4588
|
+
function escapeTemplateLiteral(str) {
|
|
4589
|
+
return str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
|
|
4590
|
+
}
|
|
4591
|
+
const require$1 = createRequire(import.meta.url);
|
|
4592
|
+
/**
|
|
4593
|
+
* Resolve the import path for a plugin executor module.
|
|
4594
|
+
* @param resolve - Executor resolve function
|
|
4595
|
+
* @param pluginImportPath - Plugin's import path
|
|
4596
|
+
* @param executorOutputDir - Directory where the generated executor will be written
|
|
4597
|
+
* @param baseDirs - Base directories for resolving plugin import paths
|
|
4598
|
+
* @returns Import path string for the executor module
|
|
4599
|
+
*/
|
|
4600
|
+
function resolveExecutorImportPath(resolve, pluginImportPath, executorOutputDir, baseDirs) {
|
|
4601
|
+
const specifier = extractDynamicImportSpecifier(resolve);
|
|
4602
|
+
if (!specifier.startsWith(".")) return specifier;
|
|
4603
|
+
const pluginBaseDir = resolvePluginBaseDir(pluginImportPath, baseDirs);
|
|
4604
|
+
if (!pluginBaseDir) throw new Error(`Unable to resolve plugin import base for "${pluginImportPath}". Tried base dirs: ${baseDirs.join(", ") || "(none)"}. Use an absolute import specifier in resolve(), or ensure the plugin path is resolvable.`);
|
|
4605
|
+
const absolutePath = path.resolve(pluginBaseDir, specifier);
|
|
4606
|
+
let relativePath = path.relative(executorOutputDir, absolutePath).replace(/\\/g, "/");
|
|
4607
|
+
relativePath = stripSourceExtension(relativePath);
|
|
4608
|
+
if (!relativePath.startsWith(".")) relativePath = `./${relativePath}`;
|
|
4609
|
+
return relativePath;
|
|
4610
|
+
}
|
|
4611
|
+
/**
|
|
4612
|
+
* Extract the dynamic import specifier from a resolve function.
|
|
4613
|
+
* @param resolve - Executor resolve function
|
|
4614
|
+
* @returns The module specifier string
|
|
4615
|
+
*/
|
|
4616
|
+
function extractDynamicImportSpecifier(resolve) {
|
|
4617
|
+
const match = resolve.toString().match(/import\s*\(\s*["']([^"']+)["']\s*\)/);
|
|
4618
|
+
if (!match) throw new Error(`resolve() must return a dynamic import, e.g. \`async () => await import("./executors/on-create")\`.`);
|
|
4619
|
+
return match[1];
|
|
4620
|
+
}
|
|
4621
|
+
/**
|
|
4622
|
+
* Resolve plugin base directory for relative imports.
|
|
4623
|
+
* @param pluginImportPath - Plugin import path
|
|
4624
|
+
* @param baseDirs - Base directories for resolving plugin import paths
|
|
4625
|
+
* @returns Directory path or null if not resolvable
|
|
4626
|
+
*/
|
|
4627
|
+
function resolvePluginBaseDir(pluginImportPath, baseDirs) {
|
|
4628
|
+
if (pluginImportPath.startsWith(".")) {
|
|
4629
|
+
const resolvedPath = resolveRelativePluginImportPath(pluginImportPath, baseDirs) ?? path.resolve(baseDirs[0] ?? process.cwd(), pluginImportPath);
|
|
4630
|
+
if (fs$2.existsSync(resolvedPath)) return fs$2.statSync(resolvedPath).isDirectory() ? resolvedPath : path.dirname(resolvedPath);
|
|
4631
|
+
return path.extname(resolvedPath) ? path.dirname(resolvedPath) : resolvedPath;
|
|
4632
|
+
}
|
|
4633
|
+
for (const baseDir of baseDirs) try {
|
|
4634
|
+
const resolved = require$1.resolve(pluginImportPath, { paths: [baseDir] });
|
|
4635
|
+
return path.dirname(resolved);
|
|
4636
|
+
} catch {
|
|
4637
|
+
continue;
|
|
4638
|
+
}
|
|
4639
|
+
return null;
|
|
4640
|
+
}
|
|
4641
|
+
/**
|
|
4642
|
+
* Strip TypeScript source extensions from import paths.
|
|
4643
|
+
* @param importPath - Path to normalize
|
|
4644
|
+
* @returns Path without .ts/.tsx extension
|
|
4645
|
+
*/
|
|
4646
|
+
function stripSourceExtension(importPath) {
|
|
4647
|
+
return importPath.replace(/\.(ts|tsx)$/, "");
|
|
4648
|
+
}
|
|
4649
|
+
/**
|
|
4650
|
+
* Convert plugin ID to safe directory name.
|
|
4651
|
+
* @param pluginId - Plugin identifier (e.g., "@scope/name")
|
|
4652
|
+
* @returns Safe directory name
|
|
4653
|
+
*/
|
|
4654
|
+
function sanitizePluginId$1(pluginId) {
|
|
4655
|
+
return pluginId.replace(/^@/, "").replace(/\//g, "-");
|
|
4656
|
+
}
|
|
4657
|
+
/**
|
|
4658
|
+
* Convert executor name to safe filename.
|
|
4659
|
+
* @param executorName - Executor name
|
|
4660
|
+
* @returns Safe filename without extension
|
|
4661
|
+
*/
|
|
4662
|
+
function sanitizeExecutorFileName(executorName) {
|
|
4663
|
+
const sanitized = path.basename(executorName).replace(/\.[^/.]+$/, "").replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
4664
|
+
if (!sanitized) throw new Error(`Invalid executor name: "${executorName}"`);
|
|
4665
|
+
return sanitized;
|
|
4666
|
+
}
|
|
4667
|
+
/**
|
|
4668
|
+
* Convert string to camelCase.
|
|
4669
|
+
* @param str - Input string to convert
|
|
4670
|
+
* @returns camelCase string
|
|
4671
|
+
*/
|
|
4672
|
+
function toCamelCase$1(str) {
|
|
4673
|
+
const result = str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "");
|
|
4674
|
+
return result.charAt(0).toLowerCase() + result.slice(1);
|
|
4675
|
+
}
|
|
4676
|
+
/**
|
|
4677
|
+
* Convert string to kebab-case.
|
|
4678
|
+
* @param str - Input string to convert
|
|
4679
|
+
* @returns kebab-case string
|
|
4680
|
+
*/
|
|
4681
|
+
function toKebabCase$1(str) {
|
|
4682
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
4683
|
+
}
|
|
4684
|
+
|
|
4685
|
+
//#endregion
|
|
4686
|
+
//#region src/cli/generator/plugin-type-generator.ts
|
|
4687
|
+
/**
|
|
4688
|
+
* Plugin Type Generator
|
|
4689
|
+
*
|
|
4690
|
+
* Generates TypeScript files for plugin-generated types (TailorDB types).
|
|
4691
|
+
* These files can be imported by plugin executors to reference generated types.
|
|
4692
|
+
*/
|
|
4693
|
+
/**
|
|
4694
|
+
* Generate TypeScript files for plugin-generated types.
|
|
4695
|
+
* These files export the type definition and can be imported by executor files.
|
|
4696
|
+
* @param types - Array of plugin type information
|
|
4697
|
+
* @param outputDir - Base output directory (e.g., .tailor-sdk)
|
|
4698
|
+
* @returns Generation result with file paths
|
|
4699
|
+
*/
|
|
4700
|
+
function generatePluginTypeFiles(types, outputDir) {
|
|
4701
|
+
const typeFilePaths = /* @__PURE__ */ new Map();
|
|
4702
|
+
const generatedFiles = [];
|
|
4703
|
+
if (types.length === 0) return {
|
|
4704
|
+
typeFilePaths,
|
|
4705
|
+
generatedFiles
|
|
4706
|
+
};
|
|
4707
|
+
const seenTypeNames = /* @__PURE__ */ new Map();
|
|
4708
|
+
for (const info of types) {
|
|
4709
|
+
const existing = seenTypeNames.get(info.type.name);
|
|
4710
|
+
if (existing) throw new Error(`Duplicate plugin-generated type name "${info.type.name}" detected. First: plugin "${existing.pluginId}" (kind: "${existing.kind}", source type: "${existing.sourceTypeName}"), Second: plugin "${info.pluginId}" (kind: "${info.kind}", source type: "${info.sourceTypeName}"). Plugin-generated type names must be unique.`);
|
|
4711
|
+
seenTypeNames.set(info.type.name, info);
|
|
4712
|
+
const pluginDir = sanitizePluginId(info.pluginId);
|
|
4713
|
+
const typeOutputDir = path.join(outputDir, pluginDir, "types");
|
|
4714
|
+
fs$2.mkdirSync(typeOutputDir, { recursive: true });
|
|
4715
|
+
const fileName = `${toKebabCase(info.type.name)}.ts`;
|
|
4716
|
+
const filePath = path.join(typeOutputDir, fileName);
|
|
4717
|
+
const content = generateTypeFileContent(info);
|
|
4718
|
+
fs$2.writeFileSync(filePath, content);
|
|
4719
|
+
generatedFiles.push(filePath);
|
|
4720
|
+
const relativePath = path.relative(outputDir, filePath);
|
|
4721
|
+
typeFilePaths.set(info.type.name, relativePath);
|
|
4722
|
+
const displayPath = path.relative(process.cwd(), filePath);
|
|
4723
|
+
logger.log(` Plugin Type File: ${styles.success(displayPath)} (${styles.dim(info.kind)}) from plugin ${styles.info(info.pluginId)}`);
|
|
4724
|
+
}
|
|
4725
|
+
return {
|
|
4726
|
+
typeFilePaths,
|
|
4727
|
+
generatedFiles
|
|
4006
4728
|
};
|
|
4007
4729
|
}
|
|
4730
|
+
/**
|
|
4731
|
+
* Generate TypeScript file content for a single type.
|
|
4732
|
+
* @param info - Plugin type information
|
|
4733
|
+
* @returns TypeScript source code
|
|
4734
|
+
*/
|
|
4735
|
+
function generateTypeFileContent(info) {
|
|
4736
|
+
const { type, pluginId, sourceTypeName, kind } = info;
|
|
4737
|
+
const variableName = toCamelCase(type.name);
|
|
4738
|
+
const fieldsCode = generateFieldsCode(type);
|
|
4739
|
+
return ml`
|
|
4740
|
+
/**
|
|
4741
|
+
* Auto-generated type by plugin: ${pluginId}
|
|
4742
|
+
* Source type: ${sourceTypeName}
|
|
4743
|
+
* Kind: ${kind}
|
|
4744
|
+
*
|
|
4745
|
+
* DO NOT EDIT - This file is generated by @tailor-platform/sdk
|
|
4746
|
+
*/
|
|
4747
|
+
import { db } from "@tailor-platform/sdk";
|
|
4748
|
+
|
|
4749
|
+
export const ${variableName} = db.type(${JSON.stringify(type.name)}, ${fieldsCode});
|
|
4750
|
+
|
|
4751
|
+
export type ${type.name} = typeof ${variableName};
|
|
4752
|
+
`;
|
|
4753
|
+
}
|
|
4754
|
+
/**
|
|
4755
|
+
* Generate TypeScript code for field definitions.
|
|
4756
|
+
* This creates a simplified version of the type's fields.
|
|
4757
|
+
* @param type - TailorDB type
|
|
4758
|
+
* @returns TypeScript code for fields object
|
|
4759
|
+
*/
|
|
4760
|
+
function generateFieldsCode(type) {
|
|
4761
|
+
const fieldEntries = [];
|
|
4762
|
+
for (const [fieldName, field] of Object.entries(type.fields)) {
|
|
4763
|
+
const fieldCode = generateSingleFieldCode(field);
|
|
4764
|
+
if (fieldCode) fieldEntries.push(` ${fieldName}: ${fieldCode}`);
|
|
4765
|
+
}
|
|
4766
|
+
return `{\n${fieldEntries.join(",\n")},\n}`;
|
|
4767
|
+
}
|
|
4768
|
+
/**
|
|
4769
|
+
* Map from TailorDB type to SDK method name.
|
|
4770
|
+
*/
|
|
4771
|
+
const typeToMethodMap = {
|
|
4772
|
+
string: "string",
|
|
4773
|
+
integer: "int",
|
|
4774
|
+
float: "float",
|
|
4775
|
+
boolean: "bool",
|
|
4776
|
+
uuid: "uuid",
|
|
4777
|
+
datetime: "datetime",
|
|
4778
|
+
date: "date",
|
|
4779
|
+
time: "time",
|
|
4780
|
+
enum: "enum",
|
|
4781
|
+
nested: "nested"
|
|
4782
|
+
};
|
|
4783
|
+
/**
|
|
4784
|
+
* Generate TypeScript code for a single field definition.
|
|
4785
|
+
* @param field - Field definition object
|
|
4786
|
+
* @returns TypeScript code for the field
|
|
4787
|
+
*/
|
|
4788
|
+
function generateSingleFieldCode(field) {
|
|
4789
|
+
if (!field || typeof field !== "object") return null;
|
|
4790
|
+
const fieldType = field.type;
|
|
4791
|
+
if (!fieldType) return null;
|
|
4792
|
+
const method = typeToMethodMap[fieldType] || fieldType;
|
|
4793
|
+
const metadata = field._metadata || field.metadata || {};
|
|
4794
|
+
const optionParts = [];
|
|
4795
|
+
if (metadata.required === false) optionParts.push("optional: true");
|
|
4796
|
+
const optionsArg = optionParts.length > 0 ? `{ ${optionParts.join(", ")} }` : "";
|
|
4797
|
+
if (fieldType === "enum") {
|
|
4798
|
+
const allowedValues = metadata.allowedValues;
|
|
4799
|
+
let enumValues = [];
|
|
4800
|
+
if (Array.isArray(allowedValues)) enumValues = allowedValues.map((v) => v.value);
|
|
4801
|
+
let code$1 = `db.enum(${JSON.stringify(enumValues)}${optionsArg ? `, ${optionsArg}` : ""})`;
|
|
4802
|
+
if (metadata.index) code$1 += ".index()";
|
|
4803
|
+
if (metadata.unique) code$1 += ".unique()";
|
|
4804
|
+
if (metadata.description) code$1 += `.description(${JSON.stringify(metadata.description)})`;
|
|
4805
|
+
return code$1;
|
|
4806
|
+
}
|
|
4807
|
+
let code = `db.${method}(${optionsArg})`;
|
|
4808
|
+
if (metadata.index) code += ".index()";
|
|
4809
|
+
if (metadata.unique) code += ".unique()";
|
|
4810
|
+
if (metadata.description) code += `.description(${JSON.stringify(metadata.description)})`;
|
|
4811
|
+
return code;
|
|
4812
|
+
}
|
|
4813
|
+
/**
|
|
4814
|
+
* Convert plugin ID to safe directory name.
|
|
4815
|
+
* @param pluginId - Plugin identifier (e.g., "@tailor-platform/change-history")
|
|
4816
|
+
* @returns Safe directory name (e.g., "tailor-platform-change-history")
|
|
4817
|
+
*/
|
|
4818
|
+
function sanitizePluginId(pluginId) {
|
|
4819
|
+
return pluginId.replace(/^@/, "").replace(/\//g, "-");
|
|
4820
|
+
}
|
|
4821
|
+
/**
|
|
4822
|
+
* Convert string to kebab-case.
|
|
4823
|
+
* @param str - Input string
|
|
4824
|
+
* @returns kebab-case string
|
|
4825
|
+
*/
|
|
4826
|
+
function toKebabCase(str) {
|
|
4827
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
4828
|
+
}
|
|
4829
|
+
/**
|
|
4830
|
+
* Convert string to camelCase.
|
|
4831
|
+
* @param str - Input string
|
|
4832
|
+
* @returns camelCase string
|
|
4833
|
+
*/
|
|
4834
|
+
function toCamelCase(str) {
|
|
4835
|
+
const result = str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "");
|
|
4836
|
+
return result.charAt(0).toLowerCase() + result.slice(1);
|
|
4837
|
+
}
|
|
4008
4838
|
|
|
4009
4839
|
//#endregion
|
|
4010
4840
|
//#region src/cli/type-generator.ts
|
|
@@ -4022,9 +4852,10 @@ function extractAttributesFromConfig(config) {
|
|
|
4022
4852
|
* @param attributeMap - Attribute map configuration
|
|
4023
4853
|
* @param attributeList - Attribute list configuration
|
|
4024
4854
|
* @param env - Environment configuration
|
|
4855
|
+
* @param pluginConfigs - Plugin configuration entries for PluginConfigs interface
|
|
4025
4856
|
* @returns Generated type definition source
|
|
4026
4857
|
*/
|
|
4027
|
-
function generateTypeDefinition(attributeMap, attributeList, env) {
|
|
4858
|
+
function generateTypeDefinition(attributeMap, attributeList, env, pluginConfigs) {
|
|
4028
4859
|
const mapFields = attributeMap ? Object.entries(attributeMap).map(([key, value]) => ` ${key}: ${value};`).join("\n") : "";
|
|
4029
4860
|
const mapBody = !attributeMap || Object.keys(attributeMap).length === 0 ? "{}" : `{
|
|
4030
4861
|
${mapFields}
|
|
@@ -4045,7 +4876,7 @@ declare module "@tailor-platform/sdk" {
|
|
|
4045
4876
|
interface AttributeList ${listBody}
|
|
4046
4877
|
interface Env ${!env || Object.keys(env).length === 0 ? "{}" : `{
|
|
4047
4878
|
${envFields}
|
|
4048
|
-
}`}
|
|
4879
|
+
}`}${pluginConfigs && pluginConfigs.length > 0 ? `\n${generatePluginConfigsDefinition(pluginConfigs)}` : ""}${pluginConfigs && pluginConfigs.length > 0 ? `\n${generatePluginMethodOverload(pluginConfigs)}` : ""}
|
|
4049
4880
|
}
|
|
4050
4881
|
|
|
4051
4882
|
export {};
|
|
@@ -4104,11 +4935,11 @@ function resolveTypeDefinitionPath(configPath) {
|
|
|
4104
4935
|
}
|
|
4105
4936
|
/**
|
|
4106
4937
|
* Generate user type definitions from the app config and write them to disk.
|
|
4107
|
-
* @param
|
|
4108
|
-
* @param configPath - Path to Tailor config file
|
|
4938
|
+
* @param options - Generation options
|
|
4109
4939
|
* @returns Promise that resolves when types are generated
|
|
4110
4940
|
*/
|
|
4111
|
-
async function generateUserTypes(
|
|
4941
|
+
async function generateUserTypes(options) {
|
|
4942
|
+
const { config, configPath, plugins } = options;
|
|
4112
4943
|
try {
|
|
4113
4944
|
const { attributeMap, attributeList } = extractAttributesFromConfig(config);
|
|
4114
4945
|
if (!attributeMap && !attributeList) logger.info("No attributes found in configuration", { mode: "plain" });
|
|
@@ -4116,7 +4947,14 @@ async function generateUserTypes(config, configPath) {
|
|
|
4116
4947
|
if (attributeList) logger.debug(`Extracted AttributeList: ${JSON.stringify(attributeList)}`);
|
|
4117
4948
|
const env = config.env;
|
|
4118
4949
|
if (env) logger.debug(`Extracted Env: ${JSON.stringify(env)}`);
|
|
4119
|
-
const
|
|
4950
|
+
const pluginConfigs = plugins?.filter((p) => p.configSchema !== void 0).map((p) => ({
|
|
4951
|
+
id: p.id,
|
|
4952
|
+
configSchema: p.configSchema,
|
|
4953
|
+
configTypeTemplate: p.configTypeTemplate,
|
|
4954
|
+
typeConfigRequired: isTypeConfigRequired(p)
|
|
4955
|
+
}));
|
|
4956
|
+
if (pluginConfigs && pluginConfigs.length > 0) logger.debug(`Extracted PluginConfigs: ${pluginConfigs.map((p) => p.id).join(", ")}`);
|
|
4957
|
+
const typeDefContent = generateTypeDefinition(attributeMap, attributeList, env, pluginConfigs);
|
|
4120
4958
|
const outputPath = resolveTypeDefinitionPath(configPath);
|
|
4121
4959
|
fs$2.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
4122
4960
|
fs$2.writeFileSync(outputPath, typeDefContent);
|
|
@@ -4145,6 +4983,386 @@ function resolvePackageDirectory(startDir) {
|
|
|
4145
4983
|
return null;
|
|
4146
4984
|
}
|
|
4147
4985
|
}
|
|
4986
|
+
/**
|
|
4987
|
+
* Convert a ConfigSchemaField to its TypeScript type string representation.
|
|
4988
|
+
* @param field - The field to convert
|
|
4989
|
+
* @param indent - Current indentation level
|
|
4990
|
+
* @returns TypeScript type string
|
|
4991
|
+
*/
|
|
4992
|
+
function fieldToTypeString(field, indent = 0) {
|
|
4993
|
+
const indentStr = " ".repeat(indent);
|
|
4994
|
+
const metadata = field.metadata;
|
|
4995
|
+
let baseType;
|
|
4996
|
+
switch (field.type) {
|
|
4997
|
+
case "string":
|
|
4998
|
+
case "uuid":
|
|
4999
|
+
case "date":
|
|
5000
|
+
case "datetime":
|
|
5001
|
+
case "time":
|
|
5002
|
+
baseType = "string";
|
|
5003
|
+
break;
|
|
5004
|
+
case "integer":
|
|
5005
|
+
case "float":
|
|
5006
|
+
baseType = "number";
|
|
5007
|
+
break;
|
|
5008
|
+
case "boolean":
|
|
5009
|
+
baseType = "boolean";
|
|
5010
|
+
break;
|
|
5011
|
+
case "enum":
|
|
5012
|
+
if (metadata.allowedValues && metadata.allowedValues.length > 0) baseType = metadata.allowedValues.map((v) => `"${v.value}"`).join(" | ");
|
|
5013
|
+
else baseType = "string";
|
|
5014
|
+
break;
|
|
5015
|
+
case "nested": {
|
|
5016
|
+
const fieldEntries = Object.entries(field.fields);
|
|
5017
|
+
if (fieldEntries.length === 0) baseType = "Record<string, unknown>";
|
|
5018
|
+
else baseType = `{\n${fieldEntries.map(([name, nestedField]) => {
|
|
5019
|
+
const nestedType = fieldToTypeString(nestedField, indent + 1);
|
|
5020
|
+
return `${indentStr} ${name}${!nestedField.metadata.required ? "?" : ""}: ${nestedType};`;
|
|
5021
|
+
}).join("\n")}\n${indentStr}}`;
|
|
5022
|
+
break;
|
|
5023
|
+
}
|
|
5024
|
+
default: baseType = "unknown";
|
|
5025
|
+
}
|
|
5026
|
+
if (metadata.array) baseType = `(${baseType})[]`;
|
|
5027
|
+
return baseType;
|
|
5028
|
+
}
|
|
5029
|
+
function isTypeConfigRequired(plugin) {
|
|
5030
|
+
const required = plugin.typeConfigRequired;
|
|
5031
|
+
if (typeof required === "function") return required(plugin.pluginConfig);
|
|
5032
|
+
return required === true;
|
|
5033
|
+
}
|
|
5034
|
+
/**
|
|
5035
|
+
* Generate PluginConfigs interface extension for user-defined.d.ts
|
|
5036
|
+
* @param plugins - Array of plugin configurations
|
|
5037
|
+
* @returns TypeScript interface extension string, or empty string if no plugins
|
|
5038
|
+
*/
|
|
5039
|
+
function generatePluginConfigsDefinition(plugins) {
|
|
5040
|
+
if (plugins.length === 0) return "";
|
|
5041
|
+
return ` interface PluginConfigs<Fields extends string> {
|
|
5042
|
+
${plugins.map((plugin) => {
|
|
5043
|
+
const typeString = plugin.configTypeTemplate ?? fieldToTypeString(plugin.configSchema, 2);
|
|
5044
|
+
const optionalMarker = plugin.typeConfigRequired ? "" : "?";
|
|
5045
|
+
return ` "${plugin.id}"${optionalMarker}: ${typeString};`;
|
|
5046
|
+
}).join("\n")}
|
|
5047
|
+
}`;
|
|
5048
|
+
}
|
|
5049
|
+
/**
|
|
5050
|
+
* Generate TailorDBType.plugin() method overload for IDE completion.
|
|
5051
|
+
* This adds an overload that uses `keyof Fields` directly for field-aware types,
|
|
5052
|
+
* enabling IDE autocompletion for field names in plugin configs.
|
|
5053
|
+
* @param plugins - Array of plugin configurations
|
|
5054
|
+
* @returns TypeScript interface extension string
|
|
5055
|
+
*/
|
|
5056
|
+
function generatePluginMethodOverload(plugins) {
|
|
5057
|
+
return ` // Overload for .plugin() method to enable IDE completion for field names
|
|
5058
|
+
interface TailorDBType<Fields, User> {
|
|
5059
|
+
plugin(config: {
|
|
5060
|
+
${plugins.map((plugin) => {
|
|
5061
|
+
const typeString = (plugin.configTypeTemplate ?? fieldToTypeString(plugin.configSchema, 3)).replace(/\bFields\b/g, "keyof Fields & string");
|
|
5062
|
+
const optionalMarker = plugin.typeConfigRequired ? "" : "?";
|
|
5063
|
+
return ` "${plugin.id}"${optionalMarker}: ${typeString};`;
|
|
5064
|
+
}).join("\n")}
|
|
5065
|
+
}): this;
|
|
5066
|
+
}`;
|
|
5067
|
+
}
|
|
5068
|
+
|
|
5069
|
+
//#endregion
|
|
5070
|
+
//#region src/plugin/manager.ts
|
|
5071
|
+
const unauthenticatedTailorUser = {
|
|
5072
|
+
id: "00000000-0000-0000-0000-000000000000",
|
|
5073
|
+
type: "",
|
|
5074
|
+
workspaceId: "00000000-0000-0000-0000-000000000000",
|
|
5075
|
+
attributes: null,
|
|
5076
|
+
attributeList: []
|
|
5077
|
+
};
|
|
5078
|
+
/**
|
|
5079
|
+
* Validate plugin config against its schema
|
|
5080
|
+
* @param config - The config object to validate
|
|
5081
|
+
* @param schema - The schema defining expected fields
|
|
5082
|
+
* @returns Array of validation errors (empty if valid)
|
|
5083
|
+
*/
|
|
5084
|
+
function validatePluginConfig(config, schema) {
|
|
5085
|
+
const result = schema.parse({
|
|
5086
|
+
value: config,
|
|
5087
|
+
data: config,
|
|
5088
|
+
user: unauthenticatedTailorUser
|
|
5089
|
+
});
|
|
5090
|
+
if ("issues" in result && result.issues) return result.issues.map((issue) => ({
|
|
5091
|
+
field: Array.isArray(issue.path) ? issue.path.join(".") : "",
|
|
5092
|
+
message: issue.message
|
|
5093
|
+
}));
|
|
5094
|
+
return [];
|
|
5095
|
+
}
|
|
5096
|
+
/**
|
|
5097
|
+
* Manages plugin registration and processing
|
|
5098
|
+
*/
|
|
5099
|
+
var PluginManager = class {
|
|
5100
|
+
plugins = /* @__PURE__ */ new Map();
|
|
5101
|
+
generatedExecutors = [];
|
|
5102
|
+
generatedTypes = [];
|
|
5103
|
+
namespaceGeneratedTypeKeys = /* @__PURE__ */ new Set();
|
|
5104
|
+
namespaceGeneratedExecutorKeys = /* @__PURE__ */ new Set();
|
|
5105
|
+
constructor(plugins = []) {
|
|
5106
|
+
for (const plugin of plugins) {
|
|
5107
|
+
if (this.plugins.has(plugin.id)) throw new Error(`Duplicate plugin ID "${plugin.id}" detected. Each plugin must have a unique ID.`);
|
|
5108
|
+
this.plugins.set(plugin.id, plugin);
|
|
5109
|
+
}
|
|
5110
|
+
}
|
|
5111
|
+
/**
|
|
5112
|
+
* Process a single plugin attachment on a raw TailorDBType.
|
|
5113
|
+
* This method is called during type loading before parsing.
|
|
5114
|
+
* @param context - Context containing the raw type, config, namespace, and plugin ID
|
|
5115
|
+
* @returns Result with plugin output on success, or error message on failure
|
|
5116
|
+
*/
|
|
5117
|
+
async processAttachment(context) {
|
|
5118
|
+
const plugin = this.plugins.get(context.pluginId);
|
|
5119
|
+
if (!plugin) return {
|
|
5120
|
+
success: false,
|
|
5121
|
+
error: `Plugin "${context.pluginId}" not found`
|
|
5122
|
+
};
|
|
5123
|
+
const typeConfigRequired = plugin.typeConfigRequired;
|
|
5124
|
+
if ((typeof typeConfigRequired === "function" ? typeConfigRequired(plugin.pluginConfig) : typeConfigRequired === true) && (context.config === void 0 || context.config === null)) return {
|
|
5125
|
+
success: false,
|
|
5126
|
+
error: `Plugin "${plugin.id}" requires config, but none was provided for type "${context.type.name}".`
|
|
5127
|
+
};
|
|
5128
|
+
if (plugin.configSchema) {
|
|
5129
|
+
const validationErrors = validatePluginConfig(context.config, plugin.configSchema);
|
|
5130
|
+
if (validationErrors.length > 0) {
|
|
5131
|
+
const errorDetails = validationErrors.map((e) => e.field ? `${e.field}: ${e.message}` : e.message).join("; ");
|
|
5132
|
+
return {
|
|
5133
|
+
success: false,
|
|
5134
|
+
error: `Invalid config for plugin "${plugin.id}" on type "${context.type.name}": ${errorDetails}`
|
|
5135
|
+
};
|
|
5136
|
+
}
|
|
5137
|
+
}
|
|
5138
|
+
if (!plugin.process) return {
|
|
5139
|
+
success: false,
|
|
5140
|
+
error: `Plugin "${plugin.id}" does not support type-attached processing (missing process method). Use processNamespace via definePlugins() instead.`
|
|
5141
|
+
};
|
|
5142
|
+
let output;
|
|
5143
|
+
try {
|
|
5144
|
+
output = await plugin.process({
|
|
5145
|
+
type: context.type,
|
|
5146
|
+
config: context.config,
|
|
5147
|
+
pluginConfig: plugin.pluginConfig,
|
|
5148
|
+
namespace: context.namespace
|
|
5149
|
+
});
|
|
5150
|
+
} catch (error) {
|
|
5151
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5152
|
+
return {
|
|
5153
|
+
success: false,
|
|
5154
|
+
error: `Plugin "${plugin.id}" threw an error while processing type "${context.type.name}": ${message}`
|
|
5155
|
+
};
|
|
5156
|
+
}
|
|
5157
|
+
if (output.types && Object.keys(output.types).length > 0) {
|
|
5158
|
+
const importPath = plugin.importPath;
|
|
5159
|
+
for (const [kind, type] of Object.entries(output.types)) this.generatedTypes.push({
|
|
5160
|
+
pluginId: context.pluginId,
|
|
5161
|
+
pluginImportPath: importPath,
|
|
5162
|
+
sourceTypeName: context.type.name,
|
|
5163
|
+
kind,
|
|
5164
|
+
type
|
|
5165
|
+
});
|
|
5166
|
+
}
|
|
5167
|
+
if (output.executors && output.executors.length > 0) for (const executor of output.executors) this.generatedExecutors.push({
|
|
5168
|
+
executor,
|
|
5169
|
+
pluginId: context.pluginId,
|
|
5170
|
+
namespace: context.namespace,
|
|
5171
|
+
sourceTypeName: context.type.name
|
|
5172
|
+
});
|
|
5173
|
+
return {
|
|
5174
|
+
success: true,
|
|
5175
|
+
output
|
|
5176
|
+
};
|
|
5177
|
+
}
|
|
5178
|
+
/**
|
|
5179
|
+
* Process namespace plugins that don't require a source type.
|
|
5180
|
+
* This method is called once per namespace for plugins with processNamespace method.
|
|
5181
|
+
* @param namespace - The target namespace for generated types
|
|
5182
|
+
* @param types - TailorDB types in the namespace (after type-attached processing)
|
|
5183
|
+
* @param generatedTypes - Plugin-generated types in the namespace
|
|
5184
|
+
* @returns Array of results with plugin outputs and configs
|
|
5185
|
+
*/
|
|
5186
|
+
async processNamespacePlugins(namespace, types, generatedTypes) {
|
|
5187
|
+
const results = [];
|
|
5188
|
+
for (const [pluginId, plugin] of this.plugins) {
|
|
5189
|
+
if (!plugin.processNamespace) continue;
|
|
5190
|
+
const config = plugin.pluginConfig;
|
|
5191
|
+
if (plugin.pluginConfigSchema && config !== void 0) {
|
|
5192
|
+
const validationErrors = validatePluginConfig(config, plugin.pluginConfigSchema);
|
|
5193
|
+
if (validationErrors.length > 0) {
|
|
5194
|
+
const errorDetails = validationErrors.map((e) => e.field ? `${e.field}: ${e.message}` : e.message).join("; ");
|
|
5195
|
+
results.push({
|
|
5196
|
+
pluginId,
|
|
5197
|
+
config,
|
|
5198
|
+
result: {
|
|
5199
|
+
success: false,
|
|
5200
|
+
error: `Invalid pluginConfig for plugin "${plugin.id}": ${errorDetails}`
|
|
5201
|
+
}
|
|
5202
|
+
});
|
|
5203
|
+
continue;
|
|
5204
|
+
}
|
|
5205
|
+
}
|
|
5206
|
+
const context = {
|
|
5207
|
+
pluginConfig: config,
|
|
5208
|
+
namespace,
|
|
5209
|
+
types,
|
|
5210
|
+
generatedTypes
|
|
5211
|
+
};
|
|
5212
|
+
let output;
|
|
5213
|
+
try {
|
|
5214
|
+
output = await plugin.processNamespace(context);
|
|
5215
|
+
} catch (error) {
|
|
5216
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5217
|
+
results.push({
|
|
5218
|
+
pluginId,
|
|
5219
|
+
config,
|
|
5220
|
+
result: {
|
|
5221
|
+
success: false,
|
|
5222
|
+
error: `Plugin "${plugin.id}" threw an error during namespace processing for "${namespace}": ${message}`
|
|
5223
|
+
}
|
|
5224
|
+
});
|
|
5225
|
+
continue;
|
|
5226
|
+
}
|
|
5227
|
+
if (output.executors && output.executors.length > 0) for (const executor of output.executors) {
|
|
5228
|
+
const executorKey = `${pluginId}:${executor.name}`;
|
|
5229
|
+
if (this.namespaceGeneratedExecutorKeys.has(executorKey)) continue;
|
|
5230
|
+
this.namespaceGeneratedExecutorKeys.add(executorKey);
|
|
5231
|
+
this.generatedExecutors.push({
|
|
5232
|
+
executor,
|
|
5233
|
+
pluginId,
|
|
5234
|
+
namespace
|
|
5235
|
+
});
|
|
5236
|
+
}
|
|
5237
|
+
if (output.types && Object.keys(output.types).length > 0) {
|
|
5238
|
+
const importPath = plugin.importPath;
|
|
5239
|
+
for (const [kind, type] of Object.entries(output.types)) {
|
|
5240
|
+
const typeKey = `${pluginId}:${kind}:${type.name}`;
|
|
5241
|
+
if (this.namespaceGeneratedTypeKeys.has(typeKey)) continue;
|
|
5242
|
+
this.namespaceGeneratedTypeKeys.add(typeKey);
|
|
5243
|
+
this.generatedTypes.push({
|
|
5244
|
+
pluginId,
|
|
5245
|
+
pluginImportPath: importPath,
|
|
5246
|
+
sourceTypeName: "(namespace)",
|
|
5247
|
+
kind,
|
|
5248
|
+
type
|
|
5249
|
+
});
|
|
5250
|
+
}
|
|
5251
|
+
}
|
|
5252
|
+
results.push({
|
|
5253
|
+
pluginId,
|
|
5254
|
+
config,
|
|
5255
|
+
result: {
|
|
5256
|
+
success: true,
|
|
5257
|
+
output
|
|
5258
|
+
}
|
|
5259
|
+
});
|
|
5260
|
+
}
|
|
5261
|
+
return results;
|
|
5262
|
+
}
|
|
5263
|
+
/**
|
|
5264
|
+
* Get plugins that have processNamespace method
|
|
5265
|
+
* @returns Array of plugin IDs that support namespace processing
|
|
5266
|
+
*/
|
|
5267
|
+
getNamespacePluginIds() {
|
|
5268
|
+
return Array.from(this.plugins.entries()).filter(([, plugin]) => plugin.processNamespace !== void 0).map(([id]) => id);
|
|
5269
|
+
}
|
|
5270
|
+
/**
|
|
5271
|
+
* Get the count of registered plugins
|
|
5272
|
+
* @returns Number of registered plugins
|
|
5273
|
+
*/
|
|
5274
|
+
get pluginCount() {
|
|
5275
|
+
return this.plugins.size;
|
|
5276
|
+
}
|
|
5277
|
+
/**
|
|
5278
|
+
* Get the import path for a plugin
|
|
5279
|
+
* @param pluginId - The plugin ID to look up
|
|
5280
|
+
* @returns The plugin's import path, or undefined if not found
|
|
5281
|
+
*/
|
|
5282
|
+
getPluginImportPath(pluginId) {
|
|
5283
|
+
return this.plugins.get(pluginId)?.importPath;
|
|
5284
|
+
}
|
|
5285
|
+
/**
|
|
5286
|
+
* Get all plugin-generated executors
|
|
5287
|
+
* @returns Array of plugin-generated executor info
|
|
5288
|
+
*/
|
|
5289
|
+
getPluginGeneratedExecutors() {
|
|
5290
|
+
return this.generatedExecutors;
|
|
5291
|
+
}
|
|
5292
|
+
/**
|
|
5293
|
+
* Get all plugin-generated executors with import paths
|
|
5294
|
+
* @returns Array of plugin-generated executor info with import paths
|
|
5295
|
+
*/
|
|
5296
|
+
getPluginGeneratedExecutorsWithImportPath() {
|
|
5297
|
+
return this.generatedExecutors.map((info) => ({
|
|
5298
|
+
...info,
|
|
5299
|
+
pluginImportPath: this.getPluginImportPath(info.pluginId) ?? ""
|
|
5300
|
+
}));
|
|
5301
|
+
}
|
|
5302
|
+
/**
|
|
5303
|
+
* Get all plugin-generated types
|
|
5304
|
+
* @returns Array of plugin-generated type info
|
|
5305
|
+
*/
|
|
5306
|
+
getPluginGeneratedTypes() {
|
|
5307
|
+
return this.generatedTypes;
|
|
5308
|
+
}
|
|
5309
|
+
/**
|
|
5310
|
+
* Get plugin-generated executors for a specific namespace
|
|
5311
|
+
* @param namespace - The namespace to filter by
|
|
5312
|
+
* @returns Array of plugin-generated executor info for the namespace
|
|
5313
|
+
*/
|
|
5314
|
+
getPluginGeneratedExecutorsForNamespace(namespace) {
|
|
5315
|
+
return this.generatedExecutors.filter((info) => info.namespace === namespace);
|
|
5316
|
+
}
|
|
5317
|
+
/**
|
|
5318
|
+
* Extend a TailorDB type with new fields.
|
|
5319
|
+
* This method handles the `db.type()` call and metadata copying internally.
|
|
5320
|
+
* @param params - Parameters for type extension
|
|
5321
|
+
* @returns The extended TailorDB type
|
|
5322
|
+
*/
|
|
5323
|
+
extendType(params) {
|
|
5324
|
+
const { originalType, extendFields, pluginId } = params;
|
|
5325
|
+
const existingFieldNames = Object.keys(originalType.fields);
|
|
5326
|
+
const duplicateFields = Object.keys(extendFields).filter((name) => existingFieldNames.includes(name));
|
|
5327
|
+
if (duplicateFields.length > 0) throw new Error(`Plugin "${pluginId}" attempted to add fields that already exist in type "${originalType.name}": ${duplicateFields.join(", ")}. extendFields cannot overwrite existing fields.`);
|
|
5328
|
+
const { id: _id, ...fieldsWithoutId } = {
|
|
5329
|
+
...originalType.fields,
|
|
5330
|
+
...extendFields
|
|
5331
|
+
};
|
|
5332
|
+
const pluralForm = originalType.metadata.settings?.pluralForm;
|
|
5333
|
+
const typeName = pluralForm ? [originalType.name, pluralForm] : originalType.name;
|
|
5334
|
+
return copyMetadataToExtendedType(originalType, db.type(typeName, fieldsWithoutId));
|
|
5335
|
+
}
|
|
5336
|
+
};
|
|
5337
|
+
/**
|
|
5338
|
+
* Copy metadata from original type to extended type.
|
|
5339
|
+
* Preserves files, settings, permissions, indexes, and plugins.
|
|
5340
|
+
* @param original - The original TailorDB type with metadata
|
|
5341
|
+
* @param extended - The newly created extended type
|
|
5342
|
+
* @returns The extended type with copied metadata
|
|
5343
|
+
*/
|
|
5344
|
+
function copyMetadataToExtendedType(original, extended) {
|
|
5345
|
+
let result = extended;
|
|
5346
|
+
if (original._description) result = result.description(original._description);
|
|
5347
|
+
const metadata = original.metadata;
|
|
5348
|
+
if (metadata.files && Object.keys(metadata.files).length > 0) result = result.files(metadata.files);
|
|
5349
|
+
if (metadata.settings) {
|
|
5350
|
+
const { pluralForm: _pluralForm, ...features } = metadata.settings;
|
|
5351
|
+
if (Object.keys(features).length > 0) result = result.features(features);
|
|
5352
|
+
}
|
|
5353
|
+
if (metadata.permissions?.record) result = result.permission(metadata.permissions.record);
|
|
5354
|
+
if (metadata.permissions?.gql) result = result.gqlPermission(metadata.permissions.gql);
|
|
5355
|
+
if (metadata.indexes && Object.keys(metadata.indexes).length > 0) {
|
|
5356
|
+
const indexDefs = Object.entries(metadata.indexes).map(([name, def]) => ({
|
|
5357
|
+
name,
|
|
5358
|
+
fields: def.fields,
|
|
5359
|
+
unique: def.unique
|
|
5360
|
+
}));
|
|
5361
|
+
result = result.indexes(...indexDefs);
|
|
5362
|
+
}
|
|
5363
|
+
if (original.plugins && original.plugins.length > 0) for (const plugin of original.plugins) result = result.plugin({ [plugin.pluginId]: plugin.config });
|
|
5364
|
+
return result;
|
|
5365
|
+
}
|
|
4148
5366
|
|
|
4149
5367
|
//#endregion
|
|
4150
5368
|
//#region src/cli/apply/services/label.ts
|
|
@@ -4188,12 +5406,14 @@ function createChangeSet(title) {
|
|
|
4188
5406
|
const creates = [];
|
|
4189
5407
|
const updates = [];
|
|
4190
5408
|
const deletes = [];
|
|
4191
|
-
const
|
|
5409
|
+
const replaces = [];
|
|
5410
|
+
const isEmpty = () => creates.length === 0 && updates.length === 0 && deletes.length === 0 && replaces.length === 0;
|
|
4192
5411
|
return {
|
|
4193
5412
|
title,
|
|
4194
5413
|
creates,
|
|
4195
5414
|
updates,
|
|
4196
5415
|
deletes,
|
|
5416
|
+
replaces,
|
|
4197
5417
|
isEmpty,
|
|
4198
5418
|
print: () => {
|
|
4199
5419
|
if (isEmpty()) return;
|
|
@@ -4201,6 +5421,7 @@ function createChangeSet(title) {
|
|
|
4201
5421
|
creates.forEach((item) => logger.log(` ${symbols.create} ${item.name}`));
|
|
4202
5422
|
deletes.forEach((item) => logger.log(` ${symbols.delete} ${item.name}`));
|
|
4203
5423
|
updates.forEach((item) => logger.log(` ${symbols.update} ${item.name}`));
|
|
5424
|
+
replaces.forEach((item) => logger.log(` ${symbols.replace} ${item.name}`));
|
|
4204
5425
|
}
|
|
4205
5426
|
};
|
|
4206
5427
|
}
|
|
@@ -4654,6 +5875,11 @@ async function applyAuth(client, result, phase = "create-update") {
|
|
|
4654
5875
|
update.request.oauth2Client.redirectUris = await resolveStaticWebsiteUrls(client, update.request.workspaceId, update.request.oauth2Client.redirectUris, "OAuth2 redirect URIs");
|
|
4655
5876
|
return client.updateAuthOAuth2Client(update.request);
|
|
4656
5877
|
})]);
|
|
5878
|
+
for (const replace of changeSet.oauth2Client.replaces) {
|
|
5879
|
+
await client.deleteAuthOAuth2Client(replace.deleteRequest);
|
|
5880
|
+
replace.createRequest.oauth2Client.redirectUris = await resolveStaticWebsiteUrls(client, replace.createRequest.workspaceId, replace.createRequest.oauth2Client.redirectUris, "OAuth2 redirect URIs");
|
|
5881
|
+
await client.createAuthOAuth2Client(replace.createRequest);
|
|
5882
|
+
}
|
|
4657
5883
|
await Promise.all([...changeSet.scim.creates.map((create$1) => client.createAuthSCIMConfig(create$1.request)), ...changeSet.scim.updates.map((update) => client.updateAuthSCIMConfig(update.request))]);
|
|
4658
5884
|
await Promise.all([...changeSet.scimResource.creates.map((create$1) => client.createAuthSCIMResource(create$1.request)), ...changeSet.scimResource.updates.map((update) => client.updateAuthSCIMResource(update.request))]);
|
|
4659
5885
|
} else if (phase === "delete-resources") {
|
|
@@ -5193,33 +6419,47 @@ async function planOAuth2Clients(client, workspaceId, auths, deletedServices) {
|
|
|
5193
6419
|
for (const auth of auths) {
|
|
5194
6420
|
const { parsedConfig: config } = auth;
|
|
5195
6421
|
const existingOAuth2Clients = await fetchOAuth2Clients(config.name);
|
|
5196
|
-
const
|
|
6422
|
+
const existingClientsMap = /* @__PURE__ */ new Map();
|
|
5197
6423
|
existingOAuth2Clients.forEach((oauth2Client) => {
|
|
5198
|
-
|
|
6424
|
+
existingClientsMap.set(oauth2Client.name, oauth2Client.clientType);
|
|
5199
6425
|
});
|
|
5200
6426
|
for (const oauth2ClientName of Object.keys(config.oauth2Clients ?? {})) {
|
|
5201
6427
|
const oauth2Client = config.oauth2Clients?.[oauth2ClientName];
|
|
5202
6428
|
if (!oauth2Client) continue;
|
|
5203
|
-
|
|
5204
|
-
|
|
6429
|
+
const newOAuth2Client = protoOAuth2Client(oauth2ClientName, oauth2Client);
|
|
6430
|
+
if (existingClientsMap.has(oauth2ClientName)) {
|
|
6431
|
+
if (existingClientsMap.get(oauth2ClientName) !== newOAuth2Client.clientType) changeSet.replaces.push({
|
|
6432
|
+
name: oauth2ClientName,
|
|
6433
|
+
deleteRequest: {
|
|
6434
|
+
workspaceId,
|
|
6435
|
+
namespaceName: config.name,
|
|
6436
|
+
name: oauth2ClientName
|
|
6437
|
+
},
|
|
6438
|
+
createRequest: {
|
|
6439
|
+
workspaceId,
|
|
6440
|
+
namespaceName: config.name,
|
|
6441
|
+
oauth2Client: newOAuth2Client
|
|
6442
|
+
}
|
|
6443
|
+
});
|
|
6444
|
+
else changeSet.updates.push({
|
|
5205
6445
|
name: oauth2ClientName,
|
|
5206
6446
|
request: {
|
|
5207
6447
|
workspaceId,
|
|
5208
6448
|
namespaceName: config.name,
|
|
5209
|
-
oauth2Client:
|
|
6449
|
+
oauth2Client: newOAuth2Client
|
|
5210
6450
|
}
|
|
5211
6451
|
});
|
|
5212
|
-
|
|
6452
|
+
existingClientsMap.delete(oauth2ClientName);
|
|
5213
6453
|
} else changeSet.creates.push({
|
|
5214
6454
|
name: oauth2ClientName,
|
|
5215
6455
|
request: {
|
|
5216
6456
|
workspaceId,
|
|
5217
6457
|
namespaceName: config.name,
|
|
5218
|
-
oauth2Client:
|
|
6458
|
+
oauth2Client: newOAuth2Client
|
|
5219
6459
|
}
|
|
5220
6460
|
});
|
|
5221
6461
|
}
|
|
5222
|
-
|
|
6462
|
+
existingClientsMap.forEach((_, name) => {
|
|
5223
6463
|
changeSet.deletes.push({
|
|
5224
6464
|
name,
|
|
5225
6465
|
request: {
|
|
@@ -7034,46 +8274,7 @@ function assertValidMigrationFiles(migrationsDir, namespace) {
|
|
|
7034
8274
|
}
|
|
7035
8275
|
}
|
|
7036
8276
|
/**
|
|
7037
|
-
*
|
|
7038
|
-
* This is used when TAILOR_INTERNAL_APPLY_MIGRATION_VERSION is specified to ensure
|
|
7039
|
-
* the deployed schema matches the specified migration version
|
|
7040
|
-
* @param {TailorDBType} type - Local parsed type (latest state)
|
|
7041
|
-
* @param {SnapshotType} snapshotType - Target snapshot state
|
|
7042
|
-
* @returns {TailorDBType} Filtered type matching the snapshot
|
|
7043
|
-
*/
|
|
7044
|
-
function filterTypeToSnapshot(type, snapshotType) {
|
|
7045
|
-
const filteredFields = {};
|
|
7046
|
-
if (type.fields.id) filteredFields.id = type.fields.id;
|
|
7047
|
-
for (const fieldName of Object.keys(snapshotType.fields)) if (type.fields[fieldName]) {
|
|
7048
|
-
const snapshotFieldConfig = snapshotType.fields[fieldName];
|
|
7049
|
-
const localField = type.fields[fieldName];
|
|
7050
|
-
filteredFields[fieldName] = {
|
|
7051
|
-
...localField,
|
|
7052
|
-
config: {
|
|
7053
|
-
...localField.config,
|
|
7054
|
-
required: snapshotFieldConfig.required,
|
|
7055
|
-
index: snapshotFieldConfig.index || false,
|
|
7056
|
-
unique: snapshotFieldConfig.unique || false,
|
|
7057
|
-
...snapshotFieldConfig.type === "enum" && snapshotFieldConfig.allowedValues && { allowedValues: snapshotFieldConfig.allowedValues.map((v) => typeof v === "string" ? { value: v } : v) }
|
|
7058
|
-
}
|
|
7059
|
-
};
|
|
7060
|
-
}
|
|
7061
|
-
const filteredIndexes = {};
|
|
7062
|
-
if (snapshotType.indexes) for (const [indexName, indexConfig] of Object.entries(snapshotType.indexes)) filteredIndexes[indexName] = {
|
|
7063
|
-
fields: indexConfig.fields,
|
|
7064
|
-
unique: indexConfig.unique
|
|
7065
|
-
};
|
|
7066
|
-
const filteredFiles = {};
|
|
7067
|
-
if (snapshotType.files) for (const [fileName, description] of Object.entries(snapshotType.files)) filteredFiles[fileName] = description;
|
|
7068
|
-
return {
|
|
7069
|
-
...type,
|
|
7070
|
-
fields: filteredFields,
|
|
7071
|
-
indexes: Object.keys(filteredIndexes).length > 0 ? filteredIndexes : void 0,
|
|
7072
|
-
files: Object.keys(filteredFiles).length > 0 ? filteredFiles : void 0
|
|
7073
|
-
};
|
|
7074
|
-
}
|
|
7075
|
-
/**
|
|
7076
|
-
* Convert remote TailorDBType to SnapshotFieldConfig for comparison
|
|
8277
|
+
* Convert remote ParsedTailorDBType to SnapshotFieldConfig for comparison
|
|
7077
8278
|
* @param {ProtoTailorDBType} remoteType - Remote TailorDB type from API
|
|
7078
8279
|
* @returns {Record<string, SnapshotFieldConfig>} Converted field configs
|
|
7079
8280
|
*/
|
|
@@ -7147,7 +8348,7 @@ function compareFields(typeName, fieldName, remoteField, snapshotField) {
|
|
|
7147
8348
|
const SYSTEM_FIELDS = new Set(["id"]);
|
|
7148
8349
|
/**
|
|
7149
8350
|
* Compare remote TailorDB types with a local snapshot
|
|
7150
|
-
* @param {ProtoTailorDBType[]} remoteTypes - Remote types from
|
|
8351
|
+
* @param {ProtoTailorDBType[]} remoteTypes - Remote types from listParsedTailorDBTypes API
|
|
7151
8352
|
* @param {SchemaSnapshot} snapshot - Local schema snapshot
|
|
7152
8353
|
* @returns {SchemaDrift[]} List of drifts detected
|
|
7153
8354
|
*/
|
|
@@ -7224,24 +8425,6 @@ function formatSchemaDrifts(drifts) {
|
|
|
7224
8425
|
*
|
|
7225
8426
|
* Bundles migration scripts to be executed via TestExecScript API
|
|
7226
8427
|
*/
|
|
7227
|
-
const REQUIRED_PACKAGES = ["kysely", "@tailor-platform/function-kysely-tailordb"];
|
|
7228
|
-
let dependencyCheckDone = false;
|
|
7229
|
-
/**
|
|
7230
|
-
* Check if required packages for migration bundling are installed.
|
|
7231
|
-
* Logs a warning if any are missing.
|
|
7232
|
-
*/
|
|
7233
|
-
function checkMigrationDependencies() {
|
|
7234
|
-
if (dependencyCheckDone) return;
|
|
7235
|
-
dependencyCheckDone = true;
|
|
7236
|
-
const require$1 = createRequire(path.resolve(process.cwd(), "package.json"));
|
|
7237
|
-
const missing = [];
|
|
7238
|
-
for (const pkg of REQUIRED_PACKAGES) try {
|
|
7239
|
-
require$1.resolve(pkg);
|
|
7240
|
-
} catch {
|
|
7241
|
-
missing.push(pkg);
|
|
7242
|
-
}
|
|
7243
|
-
if (missing.length > 0) logger.warn(`Missing optional dependencies for migration bundling: ${missing.join(", ")}. Install them in your project: pnpm add -D ${missing.join(" ")}`);
|
|
7244
|
-
}
|
|
7245
8428
|
/**
|
|
7246
8429
|
* Bundle a single migration script
|
|
7247
8430
|
*
|
|
@@ -7256,15 +8439,13 @@ function checkMigrationDependencies() {
|
|
|
7256
8439
|
* @returns {Promise<MigrationBundleResult>} Bundled migration result
|
|
7257
8440
|
*/
|
|
7258
8441
|
async function bundleMigrationScript(sourceFile, namespace, migrationNumber) {
|
|
7259
|
-
checkMigrationDependencies();
|
|
7260
8442
|
const outputDir = path.resolve(getDistDir(), "migrations");
|
|
7261
8443
|
fs$2.mkdirSync(outputDir, { recursive: true });
|
|
7262
8444
|
const entryPath = path.join(outputDir, `migration_${namespace}_${migrationNumber}.entry.js`);
|
|
7263
8445
|
const outputPath = path.join(outputDir, `migration_${namespace}_${migrationNumber}.js`);
|
|
7264
8446
|
const entryContent = ml`
|
|
7265
8447
|
import { main as _migrationMain } from "${path.resolve(sourceFile).replace(/\\/g, "/")}";
|
|
7266
|
-
import { Kysely } from "kysely";
|
|
7267
|
-
import { TailordbDialect } from "@tailor-platform/function-kysely-tailordb";
|
|
8448
|
+
import { Kysely, TailordbDialect } from "@tailor-platform/sdk/kysely";
|
|
7268
8449
|
|
|
7269
8450
|
function getDB(namespace) {
|
|
7270
8451
|
const client = new tailordb.Client({ namespace });
|
|
@@ -7446,19 +8627,11 @@ async function getCurrentMigrationNumber(client, workspaceId, namespace) {
|
|
|
7446
8627
|
*/
|
|
7447
8628
|
async function detectPendingMigrations(client, workspaceId, namespacesWithMigrations) {
|
|
7448
8629
|
const pendingMigrations = [];
|
|
7449
|
-
const maxVersionEnv = process.env.TAILOR_INTERNAL_APPLY_MIGRATION_VERSION;
|
|
7450
|
-
const maxVersion = maxVersionEnv ? parseInt(maxVersionEnv, 10) : void 0;
|
|
7451
|
-
if (maxVersion !== void 0 && !Number.isInteger(maxVersion)) throw new Error(`Invalid TAILOR_INTERNAL_APPLY_MIGRATION_VERSION: "${maxVersionEnv}". Must be a valid integer.`);
|
|
7452
|
-
if (maxVersion !== void 0) {
|
|
7453
|
-
logger.newline();
|
|
7454
|
-
logger.info(`Limiting migrations to version ${styles.bold(formatMigrationNumber(maxVersion))} or earlier`);
|
|
7455
|
-
}
|
|
7456
8630
|
for (const { namespace, migrationsDir } of namespacesWithMigrations) {
|
|
7457
8631
|
const currentMigration = await getCurrentMigrationNumber(client, workspaceId, namespace);
|
|
7458
8632
|
const migrationFiles = getMigrationFiles(migrationsDir);
|
|
7459
8633
|
for (const file of migrationFiles) {
|
|
7460
8634
|
if (file.number <= currentMigration) continue;
|
|
7461
|
-
if (maxVersion !== void 0 && file.number > maxVersion) continue;
|
|
7462
8635
|
const diffPath = getMigrationFilePath(migrationsDir, file.number, "diff");
|
|
7463
8636
|
if (!fs$2.existsSync(diffPath)) continue;
|
|
7464
8637
|
const diff = loadDiff(diffPath);
|
|
@@ -7597,35 +8770,6 @@ function groupMigrationsByNamespace(migrations) {
|
|
|
7597
8770
|
}
|
|
7598
8771
|
return grouped;
|
|
7599
8772
|
}
|
|
7600
|
-
/**
|
|
7601
|
-
* Build filtered types map for a specific migration version
|
|
7602
|
-
* @param {number} maxVersion - Maximum migration version to reconstruct
|
|
7603
|
-
* @param {Application} application - Application instance
|
|
7604
|
-
* @param {LoadedConfig} config - Loaded application config (includes path)
|
|
7605
|
-
* @returns {Promise<Map<string, Record<string, TailorDBType>>>} Filtered types by namespace
|
|
7606
|
-
*/
|
|
7607
|
-
async function buildFilteredTypesForVersion(maxVersion, application, config) {
|
|
7608
|
-
const namespacesWithMigrations = getNamespacesWithMigrations(config, path.dirname(config.path));
|
|
7609
|
-
const filteredTypesByNamespace = /* @__PURE__ */ new Map();
|
|
7610
|
-
for (const { namespace, migrationsDir } of namespacesWithMigrations) {
|
|
7611
|
-
const snapshot = reconstructSnapshotFromMigrations(migrationsDir, maxVersion);
|
|
7612
|
-
if (!snapshot) throw new Error(`No migrations found in ${migrationsDir}`);
|
|
7613
|
-
const tailordb = application.tailorDBServices.find((tdb) => tdb.namespace === namespace);
|
|
7614
|
-
if (!tailordb) throw new Error(`TailorDB service not found for namespace: ${namespace}`);
|
|
7615
|
-
await tailordb.loadTypes();
|
|
7616
|
-
const localTypes = tailordb.getTypes();
|
|
7617
|
-
const filteredTypes = {};
|
|
7618
|
-
for (const typeName of Object.keys(snapshot.types)) {
|
|
7619
|
-
const localType = localTypes[typeName];
|
|
7620
|
-
if (localType) {
|
|
7621
|
-
const snapshotType = snapshot.types[typeName];
|
|
7622
|
-
filteredTypes[typeName] = filterTypeToSnapshot(localType, snapshotType);
|
|
7623
|
-
}
|
|
7624
|
-
}
|
|
7625
|
-
filteredTypesByNamespace.set(namespace, filteredTypes);
|
|
7626
|
-
}
|
|
7627
|
-
return filteredTypesByNamespace;
|
|
7628
|
-
}
|
|
7629
8773
|
|
|
7630
8774
|
//#endregion
|
|
7631
8775
|
//#region src/cli/apply/services/tailordb/index.ts
|
|
@@ -7778,11 +8922,30 @@ async function validateAndDetectMigrations(client, workspaceId, typesByNamespace
|
|
|
7778
8922
|
return pendingMigrations;
|
|
7779
8923
|
}
|
|
7780
8924
|
/**
|
|
8925
|
+
* Build migration execution context for script-based migrations.
|
|
8926
|
+
* @param client - Operator client instance
|
|
8927
|
+
* @param migrationContext - Planned TailorDB context
|
|
8928
|
+
* @param migrationsRequiringScripts - Migrations that require scripts
|
|
8929
|
+
* @returns Migration context for script execution
|
|
8930
|
+
*/
|
|
8931
|
+
function buildMigrationContextForScripts(client, migrationContext, migrationsRequiringScripts) {
|
|
8932
|
+
const authService = migrationContext.application.authService;
|
|
8933
|
+
if (!authService) throw new Error("Auth configuration is required to execute migration scripts.");
|
|
8934
|
+
const dbConfigMap = {};
|
|
8935
|
+
for (const migration of migrationsRequiringScripts) if (!(migration.namespace in dbConfigMap)) dbConfigMap[migration.namespace] = migrationContext.config.db?.[migration.namespace];
|
|
8936
|
+
return {
|
|
8937
|
+
client,
|
|
8938
|
+
workspaceId: migrationContext.workspaceId,
|
|
8939
|
+
authNamespace: authService.config.name,
|
|
8940
|
+
machineUsers: authService.config.machineUsers ? Object.keys(authService.config.machineUsers) : void 0,
|
|
8941
|
+
dbConfig: dbConfigMap
|
|
8942
|
+
};
|
|
8943
|
+
}
|
|
8944
|
+
/**
|
|
7781
8945
|
* Apply TailorDB-related changes for the given phase.
|
|
7782
8946
|
* @param client - Operator client instance
|
|
7783
8947
|
* @param result - Planned TailorDB changes
|
|
7784
8948
|
* @param phase - Apply phase (defaults to "create-update")
|
|
7785
|
-
* @returns Promise that resolves when TailorDB changes are applied
|
|
7786
8949
|
*/
|
|
7787
8950
|
async function applyTailorDB(client, result, phase = "create-update") {
|
|
7788
8951
|
const { changeSet, context: migrationContext } = result;
|
|
@@ -7798,21 +8961,8 @@ async function applyTailorDB(client, result, phase = "create-update") {
|
|
|
7798
8961
|
processedTypes.reset();
|
|
7799
8962
|
deletedResources.reset();
|
|
7800
8963
|
await executeServicesCreation(client, changeSet);
|
|
7801
|
-
let migrationCtx;
|
|
7802
8964
|
const migrationsRequiringScripts = pendingMigrations.filter((m) => m.diff.requiresMigrationScript);
|
|
7803
|
-
|
|
7804
|
-
const authService = migrationContext.application.authService;
|
|
7805
|
-
if (!authService) throw new Error("Auth configuration is required to execute migration scripts.");
|
|
7806
|
-
const dbConfigMap = {};
|
|
7807
|
-
for (const migration of migrationsRequiringScripts) if (!(migration.namespace in dbConfigMap)) dbConfigMap[migration.namespace] = migrationContext.config.db?.[migration.namespace];
|
|
7808
|
-
migrationCtx = {
|
|
7809
|
-
client,
|
|
7810
|
-
workspaceId: migrationContext.workspaceId,
|
|
7811
|
-
authNamespace: authService.config.name,
|
|
7812
|
-
machineUsers: authService.config.machineUsers ? Object.keys(authService.config.machineUsers) : void 0,
|
|
7813
|
-
dbConfig: dbConfigMap
|
|
7814
|
-
};
|
|
7815
|
-
}
|
|
8965
|
+
const migrationCtx = migrationsRequiringScripts.length > 0 ? buildMigrationContextForScripts(client, migrationContext, migrationsRequiringScripts) : void 0;
|
|
7816
8966
|
if (migrationsRequiringScripts.length > 0) {
|
|
7817
8967
|
logger.info(`Executing ${migrationsRequiringScripts.length} data migration(s)...`);
|
|
7818
8968
|
logger.newline();
|
|
@@ -7850,17 +9000,6 @@ async function applyTailorDB(client, result, phase = "create-update") {
|
|
|
7850
9000
|
await Promise.all(changeSet.gqlPermission.deletes.map((del) => client.deleteTailorDBGQLPermission(del.request)));
|
|
7851
9001
|
await Promise.all(changeSet.type.deletes.map((del) => client.deleteTailorDBType(del.request)));
|
|
7852
9002
|
} else if (phase === "delete-services") await Promise.all(changeSet.service.deletes.map((del) => client.deleteTailorDBService(del.request)));
|
|
7853
|
-
if (phase === "create-update") {
|
|
7854
|
-
const maxVersionEnv = process.env.TAILOR_INTERNAL_APPLY_MIGRATION_VERSION;
|
|
7855
|
-
if (maxVersionEnv) {
|
|
7856
|
-
const maxVersion = parseInt(maxVersionEnv, 10);
|
|
7857
|
-
if (Number.isInteger(maxVersion)) {
|
|
7858
|
-
const configDir = path.dirname(migrationContext.config.path);
|
|
7859
|
-
const namespacesWithMigrations = getNamespacesWithMigrations(migrationContext.config, configDir);
|
|
7860
|
-
for (const { namespace } of namespacesWithMigrations) await updateMigrationLabel(client, migrationContext.workspaceId, namespace, maxVersion);
|
|
7861
|
-
}
|
|
7862
|
-
}
|
|
7863
|
-
}
|
|
7864
9003
|
}
|
|
7865
9004
|
/**
|
|
7866
9005
|
* Handle optional-to-required field change error with helpful message
|
|
@@ -7890,23 +9029,27 @@ function buildBreakingChangesMap(pendingMigrations) {
|
|
|
7890
9029
|
return map;
|
|
7891
9030
|
}
|
|
7892
9031
|
/**
|
|
7893
|
-
*
|
|
7894
|
-
*
|
|
7895
|
-
*
|
|
7896
|
-
* - Adding unique constraint (post: add unique)
|
|
7897
|
-
* - Removing enum values (pre: add new values only, post: remove old values)
|
|
7898
|
-
* @param {DiffChange} change - Diff change to check
|
|
7899
|
-
* @returns {boolean} True if the change requires pre/post-migration handling
|
|
9032
|
+
* Apply pre-migration schema adjustments to avoid breaking changes before scripts run.
|
|
9033
|
+
* @param fields - Field configs to adjust
|
|
9034
|
+
* @param typeChanges - Breaking changes for a type
|
|
7900
9035
|
*/
|
|
7901
|
-
function
|
|
7902
|
-
const
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
9036
|
+
function applyPreMigrationFieldAdjustments(fields, typeChanges) {
|
|
9037
|
+
for (const [fieldName, change] of typeChanges) {
|
|
9038
|
+
const field = fields[fieldName];
|
|
9039
|
+
if (!field) continue;
|
|
9040
|
+
const before = change.before;
|
|
9041
|
+
const after = change.after;
|
|
9042
|
+
if (change.kind === "field_added" && after?.required) field.required = false;
|
|
9043
|
+
if (change.kind !== "field_modified") continue;
|
|
9044
|
+
if (!before?.required && after?.required) field.required = false;
|
|
9045
|
+
if (!(before?.unique ?? false) && (after?.unique ?? false)) field.unique = false;
|
|
9046
|
+
if (before?.allowedValues && after?.allowedValues) {
|
|
9047
|
+
if (before.allowedValues.filter((value) => !after.allowedValues.includes(value)).length > 0) field.allowedValues = [...new Set([...before.allowedValues, ...after.allowedValues])].map((value) => ({
|
|
9048
|
+
value,
|
|
9049
|
+
description: ""
|
|
9050
|
+
}));
|
|
9051
|
+
}
|
|
9052
|
+
}
|
|
7910
9053
|
}
|
|
7911
9054
|
/**
|
|
7912
9055
|
* Get the set of type names affected by a migration
|
|
@@ -7946,11 +9089,11 @@ async function executeServicesCreation(client, changeSet) {
|
|
|
7946
9089
|
const processedTypes = {
|
|
7947
9090
|
created: /* @__PURE__ */ new Set(),
|
|
7948
9091
|
updated: /* @__PURE__ */ new Set(),
|
|
7949
|
-
gqlPermissionsProcessed:
|
|
9092
|
+
gqlPermissionsProcessed: /* @__PURE__ */ new Set(),
|
|
7950
9093
|
reset() {
|
|
7951
9094
|
this.created.clear();
|
|
7952
9095
|
this.updated.clear();
|
|
7953
|
-
this.gqlPermissionsProcessed
|
|
9096
|
+
this.gqlPermissionsProcessed.clear();
|
|
7954
9097
|
}
|
|
7955
9098
|
};
|
|
7956
9099
|
/**
|
|
@@ -7963,57 +9106,68 @@ const processedTypes = {
|
|
|
7963
9106
|
async function executeSingleMigrationPrePhase(client, changeSet, migration) {
|
|
7964
9107
|
const breakingChanges = buildBreakingChangesMap([migration]);
|
|
7965
9108
|
const affectedTypes = getAffectedTypeNames(migration);
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
const
|
|
7977
|
-
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
7993
|
-
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
}
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8015
|
-
|
|
8016
|
-
|
|
9109
|
+
const createdBeforeMigration = new Set(processedTypes.created);
|
|
9110
|
+
await Promise.all([
|
|
9111
|
+
...changeSet.type.creates.filter((create$1) => {
|
|
9112
|
+
const typeName = create$1.request.tailordbType?.name;
|
|
9113
|
+
return typeName && affectedTypes.has(typeName) && !createdBeforeMigration.has(typeName);
|
|
9114
|
+
}).map((create$1) => {
|
|
9115
|
+
const typeName = create$1.request.tailordbType?.name;
|
|
9116
|
+
if (typeName) processedTypes.created.add(typeName);
|
|
9117
|
+
const typeChanges = typeName ? breakingChanges.get(typeName) : void 0;
|
|
9118
|
+
if (!typeChanges || typeChanges.size === 0) return client.createTailorDBType(create$1.request);
|
|
9119
|
+
const clonedRequest = structuredClone(create$1.request);
|
|
9120
|
+
if (clonedRequest.tailordbType?.schema?.fields) applyPreMigrationFieldAdjustments(clonedRequest.tailordbType.schema.fields, typeChanges);
|
|
9121
|
+
return client.createTailorDBType(clonedRequest);
|
|
9122
|
+
}),
|
|
9123
|
+
...changeSet.type.creates.filter((create$1) => {
|
|
9124
|
+
const typeName = create$1.request.tailordbType?.name;
|
|
9125
|
+
return typeName && affectedTypes.has(typeName) && createdBeforeMigration.has(typeName);
|
|
9126
|
+
}).map((create$1) => {
|
|
9127
|
+
const typeName = create$1.request.tailordbType?.name;
|
|
9128
|
+
if (typeName) processedTypes.updated.add(typeName);
|
|
9129
|
+
const typeChanges = typeName ? breakingChanges.get(typeName) : void 0;
|
|
9130
|
+
if (!typeChanges || typeChanges.size === 0) return client.updateTailorDBType({
|
|
9131
|
+
workspaceId: create$1.request.workspaceId,
|
|
9132
|
+
namespaceName: create$1.request.namespaceName,
|
|
9133
|
+
tailordbType: create$1.request.tailordbType
|
|
9134
|
+
});
|
|
9135
|
+
const clonedRequest = structuredClone(create$1.request);
|
|
9136
|
+
if (clonedRequest.tailordbType?.schema?.fields) applyPreMigrationFieldAdjustments(clonedRequest.tailordbType.schema.fields, typeChanges);
|
|
9137
|
+
return client.updateTailorDBType({
|
|
9138
|
+
workspaceId: create$1.request.workspaceId,
|
|
9139
|
+
namespaceName: create$1.request.namespaceName,
|
|
9140
|
+
tailordbType: clonedRequest.tailordbType
|
|
9141
|
+
});
|
|
9142
|
+
}),
|
|
9143
|
+
...changeSet.type.updates.filter((update) => {
|
|
9144
|
+
const typeName = update.request.tailordbType?.name;
|
|
9145
|
+
return typeName && affectedTypes.has(typeName);
|
|
9146
|
+
}).map((update) => {
|
|
9147
|
+
const typeName = update.request.tailordbType?.name;
|
|
9148
|
+
if (typeName) processedTypes.updated.add(typeName);
|
|
9149
|
+
const typeChanges = typeName ? breakingChanges.get(typeName) : void 0;
|
|
9150
|
+
if (!typeChanges || typeChanges.size === 0) return client.updateTailorDBType(update.request);
|
|
9151
|
+
const clonedRequest = structuredClone(update.request);
|
|
9152
|
+
if (clonedRequest.tailordbType?.schema?.fields) applyPreMigrationFieldAdjustments(clonedRequest.tailordbType.schema.fields, typeChanges);
|
|
9153
|
+
return client.updateTailorDBType(clonedRequest);
|
|
9154
|
+
})
|
|
9155
|
+
]);
|
|
9156
|
+
if (!processedTypes.gqlPermissionsProcessed.has(migration.namespace)) {
|
|
9157
|
+
const gqlPermissionCreatesForNamespace = changeSet.gqlPermission.creates.filter((create$1) => create$1.request.namespaceName === migration.namespace);
|
|
9158
|
+
const gqlPermissionUpdatesForNamespace = changeSet.gqlPermission.updates.filter((update) => update.request.namespaceName === migration.namespace);
|
|
9159
|
+
const gqlPermissionTypeNames = new Set(gqlPermissionCreatesForNamespace.map((create$1) => create$1.name));
|
|
9160
|
+
const missingTypeCreates = changeSet.type.creates.filter((create$1) => {
|
|
9161
|
+
const typeName = create$1.request.tailordbType?.name;
|
|
9162
|
+
return create$1.request.namespaceName === migration.namespace && typeName && gqlPermissionTypeNames.has(typeName) && !processedTypes.created.has(typeName);
|
|
9163
|
+
});
|
|
9164
|
+
if (missingTypeCreates.length > 0) await Promise.all(missingTypeCreates.map((create$1) => {
|
|
9165
|
+
const typeName = create$1.request.tailordbType?.name;
|
|
9166
|
+
if (typeName) processedTypes.created.add(typeName);
|
|
9167
|
+
return client.createTailorDBType(create$1.request);
|
|
9168
|
+
}));
|
|
9169
|
+
processedTypes.gqlPermissionsProcessed.add(migration.namespace);
|
|
9170
|
+
await Promise.all([...gqlPermissionCreatesForNamespace.map((create$1) => client.createTailorDBGQLPermission(create$1.request)), ...gqlPermissionUpdatesForNamespace.map((update) => client.updateTailorDBGQLPermission(update.request))]);
|
|
8017
9171
|
}
|
|
8018
9172
|
}
|
|
8019
9173
|
/**
|
|
@@ -8090,23 +9244,9 @@ async function planTailorDB(context) {
|
|
|
8090
9244
|
tailordbs.push(tailordb);
|
|
8091
9245
|
}
|
|
8092
9246
|
const executors = forRemoval ? [] : Object.values(await application.executorService?.loadExecutors() ?? {});
|
|
8093
|
-
let filteredTypesByNamespace;
|
|
8094
|
-
let skipSchemaCheck = false;
|
|
8095
|
-
if (!forRemoval) {
|
|
8096
|
-
const maxVersionEnv = process.env.TAILOR_INTERNAL_APPLY_MIGRATION_VERSION;
|
|
8097
|
-
if (maxVersionEnv) {
|
|
8098
|
-
const maxVersion = parseInt(maxVersionEnv, 10);
|
|
8099
|
-
if (!Number.isInteger(maxVersion)) throw new Error(`Invalid TAILOR_INTERNAL_APPLY_MIGRATION_VERSION: "${maxVersionEnv}". Must be a valid integer.`);
|
|
8100
|
-
logger.info(`Using schema reconstructed up to migration version ${styles.bold(formatMigrationNumber(maxVersion))}`);
|
|
8101
|
-
logger.info("Schema check will be skipped (local types are ahead of target version)", { mode: "plain" });
|
|
8102
|
-
logger.newline();
|
|
8103
|
-
skipSchemaCheck = true;
|
|
8104
|
-
filteredTypesByNamespace = await buildFilteredTypesForVersion(maxVersion, application, config);
|
|
8105
|
-
}
|
|
8106
|
-
}
|
|
8107
9247
|
const { changeSet: serviceChangeSet, conflicts, unmanaged, resourceOwners } = await planServices(client, workspaceId, application.name, tailordbs);
|
|
8108
9248
|
const deletedServices = serviceChangeSet.deletes.map((del) => del.name);
|
|
8109
|
-
const typeChangeSet = await planTypes(client, workspaceId, tailordbs, executors, deletedServices
|
|
9249
|
+
const typeChangeSet = await planTypes(client, workspaceId, tailordbs, executors, deletedServices);
|
|
8110
9250
|
const gqlPermissionChangeSet = await planGqlPermissions(client, workspaceId, tailordbs, deletedServices);
|
|
8111
9251
|
serviceChangeSet.print();
|
|
8112
9252
|
typeChangeSet.print();
|
|
@@ -8124,7 +9264,7 @@ async function planTailorDB(context) {
|
|
|
8124
9264
|
workspaceId,
|
|
8125
9265
|
application,
|
|
8126
9266
|
config,
|
|
8127
|
-
noSchemaCheck:
|
|
9267
|
+
noSchemaCheck: noSchemaCheck ?? false
|
|
8128
9268
|
}
|
|
8129
9269
|
};
|
|
8130
9270
|
}
|
|
@@ -8919,17 +10059,59 @@ async function loadWorkflowScripts() {
|
|
|
8919
10059
|
* @returns Promise that resolves when apply completes
|
|
8920
10060
|
*/
|
|
8921
10061
|
async function apply(options) {
|
|
8922
|
-
const { config } = await loadConfig(options?.configPath);
|
|
10062
|
+
const { config, plugins } = await loadConfig(options?.configPath);
|
|
8923
10063
|
const dryRun = options?.dryRun ?? false;
|
|
8924
10064
|
const yes = options?.yes ?? false;
|
|
8925
10065
|
const buildOnly = options?.buildOnly ?? process.env.TAILOR_PLATFORM_SDK_BUILD_ONLY === "true";
|
|
8926
|
-
|
|
8927
|
-
|
|
10066
|
+
let pluginManager;
|
|
10067
|
+
if (plugins.length > 0) pluginManager = new PluginManager(plugins);
|
|
10068
|
+
await generateUserTypes({
|
|
10069
|
+
config,
|
|
10070
|
+
configPath: config.path,
|
|
10071
|
+
plugins
|
|
10072
|
+
});
|
|
10073
|
+
const application = defineApplication({
|
|
10074
|
+
config,
|
|
10075
|
+
pluginManager
|
|
10076
|
+
});
|
|
8928
10077
|
let workflowResult;
|
|
8929
10078
|
if (application.workflowConfig) workflowResult = await loadAndCollectJobs(application.workflowConfig);
|
|
8930
10079
|
const triggerContext = await buildTriggerContext(application.workflowConfig);
|
|
10080
|
+
let tailordbTypesLoaded = false;
|
|
10081
|
+
let pluginExecutorFiles = [];
|
|
10082
|
+
if (application.tailorDBServices.length > 0) {
|
|
10083
|
+
for (const tailordb of application.tailorDBServices) {
|
|
10084
|
+
await tailordb.loadTypes();
|
|
10085
|
+
await tailordb.processNamespacePlugins();
|
|
10086
|
+
}
|
|
10087
|
+
tailordbTypesLoaded = true;
|
|
10088
|
+
}
|
|
10089
|
+
const pluginOutputDir = path.join(getDistDir(), "plugin");
|
|
10090
|
+
const typeGenerationResult = generatePluginTypeFiles(pluginManager?.getPluginGeneratedTypes() ?? [], pluginOutputDir);
|
|
10091
|
+
const sourceTypeInfoMap = /* @__PURE__ */ new Map();
|
|
10092
|
+
for (const db$1 of application.tailorDBServices) {
|
|
10093
|
+
const typeSourceInfo = db$1.getTypeSourceInfo();
|
|
10094
|
+
for (const [typeName, sourceInfo] of Object.entries(typeSourceInfo)) if (sourceInfo.filePath) sourceTypeInfoMap.set(typeName, {
|
|
10095
|
+
filePath: sourceInfo.filePath,
|
|
10096
|
+
exportName: sourceInfo.exportName
|
|
10097
|
+
});
|
|
10098
|
+
}
|
|
10099
|
+
const generatedExecutorFiles = generatePluginExecutorFiles(pluginManager?.getPluginGeneratedExecutorsWithImportPath() ?? [], pluginOutputDir, typeGenerationResult, sourceTypeInfoMap, config.path);
|
|
10100
|
+
const pluginExecutorFileSet = new Set(generatedExecutorFiles);
|
|
10101
|
+
const legacyPluginExecutorDir = path.join(getDistDir(), "plugin-executors");
|
|
10102
|
+
if (fs$2.existsSync(legacyPluginExecutorDir)) {
|
|
10103
|
+
const legacyFiles = fs$2.readdirSync(legacyPluginExecutorDir).filter((f) => f.endsWith(".ts")).map((f) => path.join(legacyPluginExecutorDir, f));
|
|
10104
|
+
for (const file of legacyFiles) pluginExecutorFileSet.add(file);
|
|
10105
|
+
}
|
|
10106
|
+
pluginExecutorFiles = Array.from(pluginExecutorFileSet);
|
|
10107
|
+
const executorService = application.executorService ?? (pluginExecutorFiles.length > 0 ? createExecutorService({
|
|
10108
|
+
config: { files: [] },
|
|
10109
|
+
pluginManager
|
|
10110
|
+
}) : void 0);
|
|
10111
|
+
const mutableApplication = application;
|
|
10112
|
+
mutableApplication.executorService = executorService;
|
|
8931
10113
|
for (const app$1 of application.applications) for (const pipeline$1 of app$1.resolverServices) await buildPipeline(pipeline$1.namespace, pipeline$1.config, triggerContext);
|
|
8932
|
-
if (
|
|
10114
|
+
if (executorService) await buildExecutor(executorService.config, triggerContext, pluginExecutorFiles);
|
|
8933
10115
|
let workflowBuildResult;
|
|
8934
10116
|
if (workflowResult && workflowResult.jobs.length > 0) {
|
|
8935
10117
|
const mainJobNames = workflowResult.workflowSources.map((ws) => ws.workflow.mainJob.name);
|
|
@@ -8944,9 +10126,15 @@ async function apply(options) {
|
|
|
8944
10126
|
workspaceId: options?.workspaceId,
|
|
8945
10127
|
profile: options?.profile
|
|
8946
10128
|
});
|
|
8947
|
-
for (const tailordb of application.tailorDBServices)
|
|
10129
|
+
if (!tailordbTypesLoaded) for (const tailordb of application.tailorDBServices) {
|
|
10130
|
+
await tailordb.loadTypes();
|
|
10131
|
+
await tailordb.processNamespacePlugins();
|
|
10132
|
+
}
|
|
8948
10133
|
for (const pipeline$1 of application.resolverServices) await pipeline$1.loadResolvers();
|
|
8949
|
-
if (
|
|
10134
|
+
if (executorService) {
|
|
10135
|
+
await executorService.loadExecutors();
|
|
10136
|
+
if (pluginExecutorFiles.length > 0) await executorService.loadPluginExecutorFiles(pluginExecutorFiles);
|
|
10137
|
+
}
|
|
8950
10138
|
if (workflowResult) printLoadedWorkflows(workflowResult);
|
|
8951
10139
|
logger.newline();
|
|
8952
10140
|
const ctx = {
|
|
@@ -8993,6 +10181,14 @@ async function apply(options) {
|
|
|
8993
10181
|
resourceType: "StaticWebsite",
|
|
8994
10182
|
resourceName: del.name
|
|
8995
10183
|
});
|
|
10184
|
+
for (const del of auth.changeSet.oauth2Client.deletes) importantDeletions.push({
|
|
10185
|
+
resourceType: "OAuth2 client",
|
|
10186
|
+
resourceName: del.name
|
|
10187
|
+
});
|
|
10188
|
+
for (const replace of auth.changeSet.oauth2Client.replaces) importantDeletions.push({
|
|
10189
|
+
resourceType: "OAuth2 client (client type change)",
|
|
10190
|
+
resourceName: replace.name
|
|
10191
|
+
});
|
|
8996
10192
|
await confirmImportantResourceDeletion(importantDeletions, yes);
|
|
8997
10193
|
const resourceOwners = new Set([
|
|
8998
10194
|
...tailorDB.resourceOwners,
|
|
@@ -9015,10 +10211,10 @@ async function apply(options) {
|
|
|
9015
10211
|
logger.info("Dry run enabled. No changes applied.");
|
|
9016
10212
|
return;
|
|
9017
10213
|
}
|
|
9018
|
-
await applyTailorDB(client, tailorDB, "create-update");
|
|
9019
10214
|
await applyStaticWebsite(client, staticWebsite, "create-update");
|
|
9020
10215
|
await applyIdP(client, idp, "create-update");
|
|
9021
10216
|
await applyAuth(client, auth, "create-update");
|
|
10217
|
+
await applyTailorDB(client, tailorDB, "create-update");
|
|
9022
10218
|
await applyPipeline(client, pipeline, "create-update");
|
|
9023
10219
|
await applyPipeline(client, pipeline, "delete-resources");
|
|
9024
10220
|
await applyAuth(client, auth, "delete-resources");
|
|
@@ -9039,8 +10235,12 @@ async function apply(options) {
|
|
|
9039
10235
|
async function buildPipeline(namespace, config, triggerContext) {
|
|
9040
10236
|
await bundleResolvers(namespace, config, triggerContext);
|
|
9041
10237
|
}
|
|
9042
|
-
async function buildExecutor(config, triggerContext) {
|
|
9043
|
-
await bundleExecutors(
|
|
10238
|
+
async function buildExecutor(config, triggerContext, additionalFiles) {
|
|
10239
|
+
await bundleExecutors({
|
|
10240
|
+
config,
|
|
10241
|
+
triggerContext,
|
|
10242
|
+
additionalFiles
|
|
10243
|
+
});
|
|
9044
10244
|
}
|
|
9045
10245
|
async function buildWorkflow(collectedJobs, mainJobNames, env, triggerContext) {
|
|
9046
10246
|
return bundleWorkflowJobs(collectedJobs, mainJobNames, env, triggerContext);
|
|
@@ -11154,11 +12354,17 @@ function createDependencyWatcher(options = {}) {
|
|
|
11154
12354
|
* Creates a generation manager.
|
|
11155
12355
|
* @param config - Application configuration
|
|
11156
12356
|
* @param generators - List of generators
|
|
12357
|
+
* @param plugins - List of plugins
|
|
11157
12358
|
* @param configPath - Path to the configuration file
|
|
11158
12359
|
* @returns GenerationManager instance
|
|
11159
12360
|
*/
|
|
11160
|
-
function createGenerationManager(config, generators = [], configPath) {
|
|
11161
|
-
|
|
12361
|
+
function createGenerationManager(config, generators = [], plugins = [], configPath) {
|
|
12362
|
+
let pluginManager;
|
|
12363
|
+
if (plugins.length > 0) pluginManager = new PluginManager(plugins);
|
|
12364
|
+
const application = defineApplication({
|
|
12365
|
+
config,
|
|
12366
|
+
pluginManager
|
|
12367
|
+
});
|
|
11162
12368
|
const baseDir = path.join(getDistDir(), "generated");
|
|
11163
12369
|
fs$2.mkdirSync(baseDir, { recursive: true });
|
|
11164
12370
|
const services = {
|
|
@@ -11208,7 +12414,8 @@ function createGenerationManager(config, generators = [], configPath) {
|
|
|
11208
12414
|
results.tailordbResults[namespace][typeName] = await processType({
|
|
11209
12415
|
type,
|
|
11210
12416
|
namespace,
|
|
11211
|
-
source: typeInfo.sourceInfo[typeName]
|
|
12417
|
+
source: typeInfo.sourceInfo[typeName],
|
|
12418
|
+
plugins: typeInfo.pluginAttachments.get(typeName) ?? []
|
|
11212
12419
|
});
|
|
11213
12420
|
} catch (error) {
|
|
11214
12421
|
logger.error(`Error processing type ${styles.bold(typeName)} in ${namespace} with generator ${gen.id}`);
|
|
@@ -11381,13 +12588,15 @@ function createGenerationManager(config, generators = [], configPath) {
|
|
|
11381
12588
|
logger.newline();
|
|
11382
12589
|
logger.log(`Generation for application: ${styles.highlight(application.config.name)}`);
|
|
11383
12590
|
const app = application;
|
|
11384
|
-
for (const db of app.tailorDBServices) {
|
|
11385
|
-
const namespace = db.namespace;
|
|
12591
|
+
for (const db$1 of app.tailorDBServices) {
|
|
12592
|
+
const namespace = db$1.namespace;
|
|
11386
12593
|
try {
|
|
11387
|
-
await db.loadTypes();
|
|
12594
|
+
await db$1.loadTypes();
|
|
12595
|
+
await db$1.processNamespacePlugins();
|
|
11388
12596
|
services.tailordb[namespace] = {
|
|
11389
|
-
types: db.getTypes(),
|
|
11390
|
-
sourceInfo: db.getTypeSourceInfo()
|
|
12597
|
+
types: db$1.getTypes(),
|
|
12598
|
+
sourceInfo: db$1.getTypeSourceInfo(),
|
|
12599
|
+
pluginAttachments: db$1.getPluginAttachments()
|
|
11391
12600
|
};
|
|
11392
12601
|
} catch (error) {
|
|
11393
12602
|
logger.error(`Error loading types for TailorDB service ${styles.bold(namespace)}`);
|
|
@@ -11395,8 +12604,23 @@ function createGenerationManager(config, generators = [], configPath) {
|
|
|
11395
12604
|
if (!watch$1) throw error;
|
|
11396
12605
|
}
|
|
11397
12606
|
}
|
|
12607
|
+
const pluginOutputDir = path.join(getDistDir(), "plugin");
|
|
12608
|
+
const typeGenerationResult = generatePluginTypeFiles(pluginManager?.getPluginGeneratedTypes() ?? [], pluginOutputDir);
|
|
12609
|
+
const sourceTypeInfoMap = /* @__PURE__ */ new Map();
|
|
12610
|
+
for (const db$1 of app.tailorDBServices) {
|
|
12611
|
+
const typeSourceInfo = db$1.getTypeSourceInfo();
|
|
12612
|
+
for (const [typeName, sourceInfo] of Object.entries(typeSourceInfo)) if (sourceInfo.filePath) sourceTypeInfoMap.set(typeName, {
|
|
12613
|
+
filePath: sourceInfo.filePath,
|
|
12614
|
+
exportName: sourceInfo.exportName
|
|
12615
|
+
});
|
|
12616
|
+
}
|
|
12617
|
+
const generatedExecutorFiles = generatePluginExecutorFiles(pluginManager?.getPluginGeneratedExecutorsWithImportPath() ?? [], pluginOutputDir, typeGenerationResult, sourceTypeInfoMap, configPath);
|
|
12618
|
+
const executorService = application.executorService ?? (generatedExecutorFiles.length > 0 ? createExecutorService({
|
|
12619
|
+
config: { files: [] },
|
|
12620
|
+
pluginManager
|
|
12621
|
+
}) : void 0);
|
|
11398
12622
|
if (app.authService) await app.authService.resolveNamespaces();
|
|
11399
|
-
if (app.tailorDBServices.length > 0) logger.newline();
|
|
12623
|
+
if (app.tailorDBServices.length > 0 || generatedExecutorFiles.length > 0) logger.newline();
|
|
11400
12624
|
const tailordbOnlyGens = generators.filter((g) => onlyHas(g, "tailordb"));
|
|
11401
12625
|
if (tailordbOnlyGens.length > 0) {
|
|
11402
12626
|
await runGenerators(tailordbOnlyGens, watch$1);
|
|
@@ -11421,9 +12645,11 @@ function createGenerationManager(config, generators = [], configPath) {
|
|
|
11421
12645
|
await runGenerators(nonExecutorGens, watch$1);
|
|
11422
12646
|
logger.newline();
|
|
11423
12647
|
}
|
|
11424
|
-
|
|
11425
|
-
|
|
11426
|
-
|
|
12648
|
+
await executorService?.loadExecutors();
|
|
12649
|
+
if (generatedExecutorFiles.length > 0) await executorService?.loadPluginExecutorFiles(generatedExecutorFiles);
|
|
12650
|
+
const allExecutors = executorService?.getExecutors() ?? {};
|
|
12651
|
+
Object.entries(allExecutors).forEach(([key, executor]) => {
|
|
12652
|
+
services.executor[key] = executor;
|
|
11427
12653
|
});
|
|
11428
12654
|
const executorGens = generators.filter((g) => hasAll(g, "executor"));
|
|
11429
12655
|
if (executorGens.length > 0) {
|
|
@@ -11438,9 +12664,9 @@ function createGenerationManager(config, generators = [], configPath) {
|
|
|
11438
12664
|
});
|
|
11439
12665
|
if (configPath) await watcher.addWatchGroup("Config", [configPath]);
|
|
11440
12666
|
const app = application;
|
|
11441
|
-
for (const db of app.tailorDBServices) {
|
|
11442
|
-
const dbNamespace = db.namespace;
|
|
11443
|
-
await watcher?.addWatchGroup(`TailorDB/${dbNamespace}`, db.config.files);
|
|
12667
|
+
for (const db$1 of app.tailorDBServices) {
|
|
12668
|
+
const dbNamespace = db$1.namespace;
|
|
12669
|
+
await watcher?.addWatchGroup(`TailorDB/${dbNamespace}`, db$1.config.files);
|
|
11444
12670
|
}
|
|
11445
12671
|
for (const resolverService of app.resolverServices) {
|
|
11446
12672
|
const resolverNamespace = resolverService.namespace;
|
|
@@ -11456,10 +12682,14 @@ function createGenerationManager(config, generators = [], configPath) {
|
|
|
11456
12682
|
* @returns Promise that resolves when generation (and watch, if enabled) completes
|
|
11457
12683
|
*/
|
|
11458
12684
|
async function generate$1(options) {
|
|
11459
|
-
const { config, generators } = await loadConfig(options?.configPath);
|
|
12685
|
+
const { config, generators, plugins } = await loadConfig(options?.configPath);
|
|
11460
12686
|
const watch$1 = options?.watch ?? false;
|
|
11461
|
-
await generateUserTypes(
|
|
11462
|
-
|
|
12687
|
+
await generateUserTypes({
|
|
12688
|
+
config,
|
|
12689
|
+
configPath: config.path,
|
|
12690
|
+
plugins
|
|
12691
|
+
});
|
|
12692
|
+
const manager = createGenerationManager(config, generators, plugins, config.path);
|
|
11463
12693
|
await manager.generate(watch$1);
|
|
11464
12694
|
if (watch$1) await manager.watch();
|
|
11465
12695
|
}
|
|
@@ -11779,7 +13009,7 @@ async function loadOptions$9(options) {
|
|
|
11779
13009
|
return {
|
|
11780
13010
|
client,
|
|
11781
13011
|
workspaceId,
|
|
11782
|
-
application: defineApplication(config),
|
|
13012
|
+
application: defineApplication({ config }),
|
|
11783
13013
|
config
|
|
11784
13014
|
};
|
|
11785
13015
|
}
|
|
@@ -12008,7 +13238,7 @@ function generateDbTypesFromSnapshot(snapshot, diff) {
|
|
|
12008
13238
|
" * DO NOT EDIT - This file is auto-generated by the migration system.",
|
|
12009
13239
|
" */",
|
|
12010
13240
|
"",
|
|
12011
|
-
`import { ${imports.join(", ")} } from "kysely";`,
|
|
13241
|
+
`import { ${imports.join(", ")} } from "@tailor-platform/sdk/kysely";`,
|
|
12012
13242
|
"",
|
|
12013
13243
|
...utilityTypeDeclarations,
|
|
12014
13244
|
"",
|
|
@@ -12033,7 +13263,7 @@ function generateEmptyDbTypes(namespace) {
|
|
|
12033
13263
|
" * DO NOT EDIT - This file is auto-generated by the migration system.",
|
|
12034
13264
|
" */",
|
|
12035
13265
|
"",
|
|
12036
|
-
"import { type Transaction as KyselyTransaction } from \"kysely\";",
|
|
13266
|
+
"import { type Transaction as KyselyTransaction } from \"@tailor-platform/sdk/kysely\";",
|
|
12037
13267
|
"",
|
|
12038
13268
|
"// eslint-disable-next-line @typescript-eslint/no-empty-object-type",
|
|
12039
13269
|
"interface Database {}",
|
|
@@ -12420,7 +13650,7 @@ async function handleInitOption(namespaces, skipConfirmation) {
|
|
|
12420
13650
|
*/
|
|
12421
13651
|
async function generate(options) {
|
|
12422
13652
|
logBetaWarning("tailordb migration");
|
|
12423
|
-
const { config } = await loadConfig(options.configPath);
|
|
13653
|
+
const { config, plugins } = await loadConfig(options.configPath);
|
|
12424
13654
|
const namespacesWithMigrations = getNamespacesWithMigrations(config, path.dirname(config.path));
|
|
12425
13655
|
if (namespacesWithMigrations.length === 0) {
|
|
12426
13656
|
logger.warn("No TailorDB namespaces with migrations config found.");
|
|
@@ -12428,8 +13658,13 @@ async function generate(options) {
|
|
|
12428
13658
|
return;
|
|
12429
13659
|
}
|
|
12430
13660
|
if (options.init) await handleInitOption(namespacesWithMigrations, options.yes);
|
|
12431
|
-
|
|
12432
|
-
|
|
13661
|
+
let pluginManager;
|
|
13662
|
+
if (plugins.length > 0) pluginManager = new PluginManager(plugins);
|
|
13663
|
+
const { defineApplication: defineApplication$1 } = await import("./application-DM4zTgXU.mjs");
|
|
13664
|
+
const application = defineApplication$1({
|
|
13665
|
+
config,
|
|
13666
|
+
pluginManager
|
|
13667
|
+
});
|
|
12433
13668
|
for (const { namespace, migrationsDir } of namespacesWithMigrations) {
|
|
12434
13669
|
logger.info(`Processing namespace: ${styles.bold(namespace)}`);
|
|
12435
13670
|
assertValidMigrationFiles(migrationsDir, namespace);
|
|
@@ -12439,6 +13674,7 @@ async function generate(options) {
|
|
|
12439
13674
|
continue;
|
|
12440
13675
|
}
|
|
12441
13676
|
await tailordbService.loadTypes();
|
|
13677
|
+
await tailordbService.processNamespacePlugins();
|
|
12442
13678
|
const currentSnapshot = createSnapshotFromLocalTypes(tailordbService.getTypes(), namespace);
|
|
12443
13679
|
let previousSnapshot = null;
|
|
12444
13680
|
try {
|
|
@@ -13634,5 +14870,5 @@ const updateCommand = defineCommand({
|
|
|
13634
14870
|
});
|
|
13635
14871
|
|
|
13636
14872
|
//#endregion
|
|
13637
|
-
export { jobsCommand as $,
|
|
13638
|
-
//# sourceMappingURL=update-
|
|
14873
|
+
export { jobsCommand as $, initOperatorClient as $t, generateCommand as A, getMigrationFiles as At, getMachineUserToken as B, generateUserTypes as Bt, resumeCommand as C, compareLocalTypesWithSnapshot as Ct, truncate as D, getLatestMigrationNumber as Dt, listWorkflows as E, formatMigrationNumber as Et, removeCommand$1 as F, formatDiffSummary as Ft, generateCommand$1 as G, fetchLatestToken as Gt, listCommand$5 as H, getDistDir as Ht, listCommand$4 as I, formatMigrationDiff as It, triggerCommand as J, readPlatformConfig as Jt, listWebhookExecutors as K, loadAccessToken as Kt, listOAuth2Clients as L, hasChanges as Lt, show as M, isValidMigrationNumber as Mt, showCommand as N, loadDiff as Nt, truncateCommand as O, getMigrationDirPath as Ot, remove as P, reconstructSnapshotFromMigrations as Pt, getExecutorJob as Q, initOAuth2Client as Qt, getCommand$1 as R, getNamespacesWithMigrations as Rt, healthCommand as S, SCHEMA_FILE_NAME as St, listCommand$3 as T, createSnapshotFromLocalTypes as Tt, listMachineUsers as U, apiCall as Ut, tokenCommand as V, loadConfig as Vt, generate$1 as W, apiCommand as Wt, listCommand$6 as X, fetchAll as Xt, triggerExecutor as Y, writePlatformConfig as Yt, listExecutors as Z, fetchUserInfo as Zt, createCommand as _, bundleMigrationScript as _t, listCommand as a, jsonArgs as an, getWorkflow as at, listCommand$2 as b, INITIAL_SCHEMA_NUMBER as bt, inviteUser as c, listWorkflowExecutions as ct, listCommand$1 as d, apply as dt, readPackageJson as en, listExecutorJobs as et, listWorkspaces as f, applyCommand as ft, deleteWorkspace as g, parseMigrationLabelNumber as gt, deleteCommand as h, MIGRATION_LABEL_KEY as ht, removeUser as i, deploymentArgs as in, getCommand$2 as it, logBetaWarning as j, getNextMigrationNumber as jt, generate as k, getMigrationFilePath as kt, restoreCommand as l, getCommand$3 as lt, getWorkspace as m, waitForExecution$1 as mt, updateUser as n, commonArgs as nn, startCommand as nt, listUsers as o, withCommonArgs as on, executionsCommand as ot, getCommand as p, executeScript as pt, webhookCommand as q, loadWorkspaceId as qt, removeCommand as r, confirmationArgs as rn, startWorkflow as rt, inviteCommand as s, workspaceArgs as sn, getWorkflowExecution as st, updateCommand as t, PATScope as tn, watchExecutorJob as tt, restoreWorkspace as u, getExecutor as ut, createWorkspace as v, DB_TYPES_FILE_NAME as vt, resumeWorkflow as w, compareSnapshots as wt, getAppHealth as x, MIGRATE_FILE_NAME as xt, listApps as y, DIFF_FILE_NAME as yt, getOAuth2Client as z, trnPrefix as zt };
|
|
14874
|
+
//# sourceMappingURL=update-B_W-UQnS.mjs.map
|