@tailor-platform/sdk 1.17.1 → 1.18.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 +16 -0
- package/dist/application-Csj7Ow5Q.mjs +8 -0
- package/dist/{application-BMDE8KqK.mjs → application-gWUyKuzv.mjs} +120 -1618
- package/dist/application-gWUyKuzv.mjs.map +1 -0
- package/dist/brand-BZJCv6UY.mjs +28 -0
- package/dist/brand-BZJCv6UY.mjs.map +1 -0
- package/dist/cli/index.mjs +38 -20
- package/dist/cli/index.mjs.map +1 -1
- package/dist/cli/lib.d.mts +10 -33
- package/dist/cli/lib.mjs +10 -5
- package/dist/cli/lib.mjs.map +1 -1
- package/dist/configure/index.d.mts +4 -4
- package/dist/configure/index.mjs +10 -19
- package/dist/configure/index.mjs.map +1 -1
- package/dist/enum-constants-Cwd4qdpa.mjs +115 -0
- package/dist/enum-constants-Cwd4qdpa.mjs.map +1 -0
- package/dist/file-utils-cqcpFk87.mjs +139 -0
- package/dist/file-utils-cqcpFk87.mjs.map +1 -0
- package/dist/index-BKXch-td.d.mts +18 -0
- package/dist/index-C3Ib7pFc.d.mts +18 -0
- package/dist/{index-CVcYqZSf.d.mts → index-DP8EB9FK.d.mts} +12 -5
- package/dist/index-SqWgrTnF.d.mts +20 -0
- package/dist/index-sSDpuVQY.d.mts +18 -0
- package/dist/{jiti-BrELlEYT.mjs → jiti-DHlauMCo.mjs} +2 -2
- package/dist/{jiti-BrELlEYT.mjs.map → jiti-DHlauMCo.mjs.map} +1 -1
- package/dist/{job-CULA2Pvf.mjs → job-2Q82qQ6N.mjs} +27 -5
- package/dist/job-2Q82qQ6N.mjs.map +1 -0
- package/dist/kysely-type-DtUUoAi3.mjs +259 -0
- package/dist/kysely-type-DtUUoAi3.mjs.map +1 -0
- package/dist/plugin/builtin/enum-constants/index.d.mts +4 -0
- package/dist/plugin/builtin/enum-constants/index.mjs +3 -0
- package/dist/plugin/builtin/file-utils/index.d.mts +4 -0
- package/dist/plugin/builtin/file-utils/index.mjs +3 -0
- package/dist/plugin/builtin/kysely-type/index.d.mts +4 -0
- package/dist/plugin/builtin/kysely-type/index.mjs +3 -0
- package/dist/plugin/builtin/seed/index.d.mts +4 -0
- package/dist/plugin/builtin/seed/index.mjs +3 -0
- package/dist/plugin/index.d.mts +3 -3
- package/dist/plugin/index.mjs +11 -11
- package/dist/plugin/index.mjs.map +1 -1
- package/dist/{schema-R5TxC5Pn.mjs → schema-WDvc7Zel.mjs} +4 -3
- package/dist/schema-WDvc7Zel.mjs.map +1 -0
- package/dist/seed-Dm7lrGZ3.mjs +1050 -0
- package/dist/seed-Dm7lrGZ3.mjs.map +1 -0
- package/dist/{src-DMROgdcL.mjs → src-i4uqS1G4.mjs} +2 -2
- package/dist/{src-DMROgdcL.mjs.map → src-i4uqS1G4.mjs.map} +1 -1
- package/dist/types-Bhl_wAM2.d.mts +151 -0
- package/dist/{types-b-ig8nW_.mjs → types-ClK_HJ0G.mjs} +1 -1
- package/dist/{types-b-ig8nW_.mjs.map → types-ClK_HJ0G.mjs.map} +1 -1
- package/dist/{types-CZZBCaxB.d.mts → types-DdvTxFiD.d.mts} +1324 -988
- package/dist/{update-CUBVjZbL.mjs → update-BoNKMti-.mjs} +268 -97
- package/dist/update-BoNKMti-.mjs.map +1 -0
- package/dist/utils/test/index.d.mts +4 -4
- package/dist/utils/test/index.mjs +3 -2
- package/dist/utils/test/index.mjs.map +1 -1
- package/docs/cli/application.md +106 -14
- package/docs/cli/auth.md +92 -12
- package/docs/cli/completion.md +18 -2
- package/docs/cli/executor.md +122 -14
- package/docs/cli/function.md +32 -4
- package/docs/cli/secret.md +134 -18
- package/docs/cli/staticwebsite.md +60 -8
- package/docs/cli/tailordb.md +148 -20
- package/docs/cli/user.md +154 -22
- package/docs/cli/workflow.md +100 -12
- package/docs/cli/workspace.md +274 -38
- package/docs/generator/custom.md +2 -2
- package/docs/plugin/custom.md +270 -163
- package/docs/plugin/index.md +48 -2
- package/package.json +22 -2
- package/dist/application-BMDE8KqK.mjs.map +0 -1
- package/dist/application-Dni_W16P.mjs +0 -4
- package/dist/job-CULA2Pvf.mjs.map +0 -1
- package/dist/schema-R5TxC5Pn.mjs.map +0 -1
- package/dist/types-DthzUFfx.d.mts +0 -372
- package/dist/update-CUBVjZbL.mjs.map +0 -1
- /package/dist/{chunk-GMkBE123.mjs → chunk-CqAI0b6X.mjs} +0 -0
|
@@ -0,0 +1,1050 @@
|
|
|
1
|
+
import { t as isPluginGeneratedType } from "./types-ClK_HJ0G.mjs";
|
|
2
|
+
import * as path from "pathe";
|
|
3
|
+
import ml from "multiline-ts";
|
|
4
|
+
|
|
5
|
+
//#region src/plugin/builtin/seed/idp-user-processor.ts
|
|
6
|
+
/**
|
|
7
|
+
* Processes auth configuration to generate IdP user seed metadata
|
|
8
|
+
* @param auth - Auth configuration from generator
|
|
9
|
+
* @returns IdP user metadata or undefined if not applicable
|
|
10
|
+
*/
|
|
11
|
+
function processIdpUser(auth) {
|
|
12
|
+
if (auth.idProvider?.kind !== "BuiltInIdP" || !auth.userProfile) return;
|
|
13
|
+
const { typeName, usernameField } = auth.userProfile;
|
|
14
|
+
return {
|
|
15
|
+
name: "_User",
|
|
16
|
+
dependencies: [typeName],
|
|
17
|
+
dataFile: "data/_User.jsonl",
|
|
18
|
+
idpNamespace: auth.idProvider.namespace,
|
|
19
|
+
schema: {
|
|
20
|
+
usernameField,
|
|
21
|
+
userTypeName: typeName
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Generates the server-side IDP seed script code for testExecScript execution.
|
|
27
|
+
* Uses the global tailor.idp.Client - no bundling required.
|
|
28
|
+
* @param idpNamespace - The IDP namespace name
|
|
29
|
+
* @returns Script code string
|
|
30
|
+
*/
|
|
31
|
+
function generateIdpSeedScriptCode(idpNamespace) {
|
|
32
|
+
return ml`
|
|
33
|
+
export async function main(input) {
|
|
34
|
+
const client = new tailor.idp.Client({ namespace: "${idpNamespace}" });
|
|
35
|
+
const errors = [];
|
|
36
|
+
let processed = 0;
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < input.users.length; i++) {
|
|
39
|
+
try {
|
|
40
|
+
await client.createUser(input.users[i]);
|
|
41
|
+
processed++;
|
|
42
|
+
console.log(\`[_User] \${i + 1}/\${input.users.length}: \${input.users[i].name}\`);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
errors.push(\`Row \${i} (\${input.users[i].name}): \${message}\`);
|
|
46
|
+
console.error(\`[_User] Row \${i} failed: \${message}\`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
success: errors.length === 0,
|
|
52
|
+
processed,
|
|
53
|
+
errors,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Generates the server-side IDP truncation script code for testExecScript execution.
|
|
60
|
+
* Lists all users with pagination and deletes each one.
|
|
61
|
+
* @param idpNamespace - The IDP namespace name
|
|
62
|
+
* @returns Script code string
|
|
63
|
+
*/
|
|
64
|
+
function generateIdpTruncateScriptCode(idpNamespace) {
|
|
65
|
+
return ml`
|
|
66
|
+
export async function main() {
|
|
67
|
+
const client = new tailor.idp.Client({ namespace: "${idpNamespace}" });
|
|
68
|
+
const errors = [];
|
|
69
|
+
let deleted = 0;
|
|
70
|
+
|
|
71
|
+
// List all users with pagination
|
|
72
|
+
let nextToken = undefined;
|
|
73
|
+
const allUsers = [];
|
|
74
|
+
do {
|
|
75
|
+
const response = await client.users(nextToken ? { nextToken } : undefined);
|
|
76
|
+
allUsers.push(...(response.users || []));
|
|
77
|
+
nextToken = response.nextToken;
|
|
78
|
+
} while (nextToken);
|
|
79
|
+
|
|
80
|
+
console.log(\`Found \${allUsers.length} IDP users to delete\`);
|
|
81
|
+
|
|
82
|
+
for (const user of allUsers) {
|
|
83
|
+
try {
|
|
84
|
+
await client.deleteUser(user.id);
|
|
85
|
+
deleted++;
|
|
86
|
+
console.log(\`[_User] Deleted \${deleted}/\${allUsers.length}: \${user.name}\`);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
89
|
+
errors.push(\`User \${user.id} (\${user.name}): \${message}\`);
|
|
90
|
+
console.error(\`[_User] Delete failed for \${user.name}: \${message}\`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
success: errors.length === 0,
|
|
96
|
+
deleted,
|
|
97
|
+
total: allUsers.length,
|
|
98
|
+
errors,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Generates the schema file content for IdP users with foreign key
|
|
105
|
+
* @param usernameField - Username field name
|
|
106
|
+
* @param userTypeName - TailorDB user type name
|
|
107
|
+
* @returns Schema file contents
|
|
108
|
+
*/
|
|
109
|
+
function generateIdpUserSchemaFile(usernameField, userTypeName) {
|
|
110
|
+
return ml`
|
|
111
|
+
import { t } from "@tailor-platform/sdk";
|
|
112
|
+
import { createStandardSchema } from "@tailor-platform/sdk/test";
|
|
113
|
+
import { defineSchema } from "@toiroakr/lines-db";
|
|
114
|
+
|
|
115
|
+
const schemaType = t.object({
|
|
116
|
+
name: t.string(),
|
|
117
|
+
password: t.string(),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Simple identity hook for _User (no TailorDB backing type)
|
|
121
|
+
const hook = <T>(data: unknown) => data as T;
|
|
122
|
+
|
|
123
|
+
export const schema = defineSchema(
|
|
124
|
+
createStandardSchema(schemaType, hook),
|
|
125
|
+
{
|
|
126
|
+
primaryKey: "name",
|
|
127
|
+
indexes: [
|
|
128
|
+
{ name: "_user_name_unique_idx", columns: ["name"], unique: true },
|
|
129
|
+
],
|
|
130
|
+
foreignKeys: [
|
|
131
|
+
{
|
|
132
|
+
column: "name",
|
|
133
|
+
references: {
|
|
134
|
+
table: "${userTypeName}",
|
|
135
|
+
column: "${usernameField}",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/plugin/builtin/seed/lines-db-processor.ts
|
|
147
|
+
/**
|
|
148
|
+
* Processes TailorDB types to generate lines-db metadata
|
|
149
|
+
* @param type - Parsed TailorDB type
|
|
150
|
+
* @param source - Source file info
|
|
151
|
+
* @returns Generated lines-db metadata
|
|
152
|
+
*/
|
|
153
|
+
function processLinesDb(type, source) {
|
|
154
|
+
if (isPluginGeneratedType(source)) return processLinesDbForPluginType(type, source);
|
|
155
|
+
if (!source.filePath) throw new Error(`Missing source info for type ${type.name}`);
|
|
156
|
+
if (!source.exportName) throw new Error(`Missing export name for type ${type.name}`);
|
|
157
|
+
const { optionalFields, omitFields, indexes, foreignKeys } = extractFieldMetadata(type);
|
|
158
|
+
return {
|
|
159
|
+
typeName: type.name,
|
|
160
|
+
exportName: source.exportName,
|
|
161
|
+
importPath: source.filePath,
|
|
162
|
+
optionalFields,
|
|
163
|
+
omitFields,
|
|
164
|
+
foreignKeys,
|
|
165
|
+
indexes
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Process lines-db metadata for plugin-generated types
|
|
170
|
+
* @param type - Parsed TailorDB type
|
|
171
|
+
* @param source - Plugin-generated type source info
|
|
172
|
+
* @returns Generated lines-db metadata with plugin source
|
|
173
|
+
*/
|
|
174
|
+
function processLinesDbForPluginType(type, source) {
|
|
175
|
+
const { optionalFields, omitFields, indexes, foreignKeys } = extractFieldMetadata(type);
|
|
176
|
+
return {
|
|
177
|
+
typeName: type.name,
|
|
178
|
+
exportName: source.exportName,
|
|
179
|
+
importPath: "",
|
|
180
|
+
optionalFields,
|
|
181
|
+
omitFields,
|
|
182
|
+
foreignKeys,
|
|
183
|
+
indexes,
|
|
184
|
+
pluginSource: source
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Extract field metadata from TailorDB type
|
|
189
|
+
* @param type - Parsed TailorDB type
|
|
190
|
+
* @returns Field metadata including optional fields, omit fields, indexes, and foreign keys
|
|
191
|
+
*/
|
|
192
|
+
function extractFieldMetadata(type) {
|
|
193
|
+
const optionalFields = ["id"];
|
|
194
|
+
const omitFields = [];
|
|
195
|
+
const indexes = [];
|
|
196
|
+
const foreignKeys = [];
|
|
197
|
+
for (const [fieldName, field] of Object.entries(type.fields)) {
|
|
198
|
+
if (field.config.hooks?.create) optionalFields.push(fieldName);
|
|
199
|
+
if (field.config.serial) omitFields.push(fieldName);
|
|
200
|
+
if (field.config.unique) indexes.push({
|
|
201
|
+
name: `${type.name.toLowerCase()}_${fieldName}_unique_idx`,
|
|
202
|
+
columns: [fieldName],
|
|
203
|
+
unique: true
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (type.indexes) for (const [indexName, indexDef] of Object.entries(type.indexes)) indexes.push({
|
|
207
|
+
name: indexName,
|
|
208
|
+
columns: indexDef.fields,
|
|
209
|
+
unique: indexDef.unique
|
|
210
|
+
});
|
|
211
|
+
for (const [fieldName, field] of Object.entries(type.fields)) if (field.relation) foreignKeys.push({
|
|
212
|
+
column: fieldName,
|
|
213
|
+
references: {
|
|
214
|
+
table: field.relation.targetType,
|
|
215
|
+
column: field.relation.key
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
return {
|
|
219
|
+
optionalFields,
|
|
220
|
+
omitFields,
|
|
221
|
+
indexes,
|
|
222
|
+
foreignKeys
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Generate schema options code for lines-db
|
|
227
|
+
* @param foreignKeys - Foreign key definitions
|
|
228
|
+
* @param indexes - Index definitions
|
|
229
|
+
* @returns Schema options code string
|
|
230
|
+
*/
|
|
231
|
+
function generateSchemaOptions(foreignKeys, indexes) {
|
|
232
|
+
const schemaOptions = [];
|
|
233
|
+
if (foreignKeys.length > 0) {
|
|
234
|
+
schemaOptions.push(`foreignKeys: [`);
|
|
235
|
+
foreignKeys.forEach((fk) => {
|
|
236
|
+
schemaOptions.push(` ${JSON.stringify(fk)},`);
|
|
237
|
+
});
|
|
238
|
+
schemaOptions.push(`],`);
|
|
239
|
+
}
|
|
240
|
+
if (indexes.length > 0) {
|
|
241
|
+
schemaOptions.push(`indexes: [`);
|
|
242
|
+
indexes.forEach((index) => {
|
|
243
|
+
schemaOptions.push(` ${JSON.stringify(index)},`);
|
|
244
|
+
});
|
|
245
|
+
schemaOptions.push("],");
|
|
246
|
+
}
|
|
247
|
+
return schemaOptions.length > 0 ? [
|
|
248
|
+
"\n {",
|
|
249
|
+
...schemaOptions.map((option) => ` ${option}`),
|
|
250
|
+
" }"
|
|
251
|
+
].join("\n") : "";
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Generates the schema file content for lines-db (for user-defined types with import)
|
|
255
|
+
* @param metadata - lines-db metadata
|
|
256
|
+
* @param importPath - Import path for the TailorDB type
|
|
257
|
+
* @returns Schema file contents
|
|
258
|
+
*/
|
|
259
|
+
function generateLinesDbSchemaFile(metadata, importPath) {
|
|
260
|
+
const { exportName, optionalFields, omitFields, foreignKeys, indexes } = metadata;
|
|
261
|
+
return ml`
|
|
262
|
+
import { t } from "@tailor-platform/sdk";
|
|
263
|
+
import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test";
|
|
264
|
+
import { defineSchema } from "@toiroakr/lines-db";
|
|
265
|
+
import { ${exportName} } from "${importPath}";
|
|
266
|
+
|
|
267
|
+
${ml`
|
|
268
|
+
const schemaType = t.object({
|
|
269
|
+
...${exportName}.pickFields(${JSON.stringify(optionalFields)}, { optional: true }),
|
|
270
|
+
...${exportName}.omitFields(${JSON.stringify([...optionalFields, ...omitFields])}),
|
|
271
|
+
});
|
|
272
|
+
`}
|
|
273
|
+
|
|
274
|
+
const hook = createTailorDBHook(${exportName});
|
|
275
|
+
|
|
276
|
+
export const schema = defineSchema(
|
|
277
|
+
createStandardSchema(schemaType, hook),${generateSchemaOptions(foreignKeys, indexes)}
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
`;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Generates the schema file content using getGeneratedType API
|
|
284
|
+
* (for plugin-generated types)
|
|
285
|
+
* @param metadata - lines-db metadata (must have pluginSource)
|
|
286
|
+
* @param params - Plugin import paths
|
|
287
|
+
* @returns Schema file contents
|
|
288
|
+
*/
|
|
289
|
+
function generateLinesDbSchemaFileWithPluginAPI(metadata, params) {
|
|
290
|
+
const { typeName, exportName, optionalFields, omitFields, foreignKeys, indexes, pluginSource } = metadata;
|
|
291
|
+
if (!pluginSource) throw new Error(`pluginSource is required for plugin-generated type "${typeName}"`);
|
|
292
|
+
const { configImportPath, originalImportPath } = params;
|
|
293
|
+
const schemaTypeCode = ml`
|
|
294
|
+
const schemaType = t.object({
|
|
295
|
+
...${exportName}.pickFields(${JSON.stringify(optionalFields)}, { optional: true }),
|
|
296
|
+
...${exportName}.omitFields(${JSON.stringify([...optionalFields, ...omitFields])}),
|
|
297
|
+
});
|
|
298
|
+
`;
|
|
299
|
+
const schemaOptionsCode = generateSchemaOptions(foreignKeys, indexes);
|
|
300
|
+
if (pluginSource.originalExportName && originalImportPath && pluginSource.generatedTypeKind) return ml`
|
|
301
|
+
import { join } from "node:path";
|
|
302
|
+
import { t } from "@tailor-platform/sdk";
|
|
303
|
+
import { getGeneratedType } from "@tailor-platform/sdk/plugin";
|
|
304
|
+
import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test";
|
|
305
|
+
import { defineSchema } from "@toiroakr/lines-db";
|
|
306
|
+
import { ${pluginSource.originalExportName} } from "${originalImportPath}";
|
|
307
|
+
|
|
308
|
+
const configPath = join(import.meta.dirname, "${configImportPath}");
|
|
309
|
+
const ${exportName} = await getGeneratedType(configPath, "${pluginSource.pluginId}", ${pluginSource.originalExportName}, "${pluginSource.generatedTypeKind}");
|
|
310
|
+
|
|
311
|
+
${schemaTypeCode}
|
|
312
|
+
|
|
313
|
+
const hook = createTailorDBHook(${exportName});
|
|
314
|
+
|
|
315
|
+
export const schema = defineSchema(
|
|
316
|
+
createStandardSchema(schemaType, hook),${schemaOptionsCode}
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
`;
|
|
320
|
+
if (!pluginSource.generatedTypeKind) throw new Error(`Namespace plugin "${pluginSource.pluginId}" must provide generatedTypeKind for type "${typeName}"`);
|
|
321
|
+
return ml`
|
|
322
|
+
import { join } from "node:path";
|
|
323
|
+
import { t } from "@tailor-platform/sdk";
|
|
324
|
+
import { getGeneratedType } from "@tailor-platform/sdk/plugin";
|
|
325
|
+
import { createTailorDBHook, createStandardSchema } from "@tailor-platform/sdk/test";
|
|
326
|
+
import { defineSchema } from "@toiroakr/lines-db";
|
|
327
|
+
|
|
328
|
+
const configPath = join(import.meta.dirname, "${configImportPath}");
|
|
329
|
+
const ${exportName} = await getGeneratedType(configPath, "${pluginSource.pluginId}", null, "${pluginSource.generatedTypeKind}");
|
|
330
|
+
|
|
331
|
+
${schemaTypeCode}
|
|
332
|
+
|
|
333
|
+
const hook = createTailorDBHook(${exportName});
|
|
334
|
+
|
|
335
|
+
export const schema = defineSchema(
|
|
336
|
+
createStandardSchema(schemaType, hook),${schemaOptionsCode}
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
//#endregion
|
|
343
|
+
//#region src/plugin/builtin/seed/seed-type-processor.ts
|
|
344
|
+
/**
|
|
345
|
+
* Processes TailorDB types to extract seed type information
|
|
346
|
+
* @param type - Parsed TailorDB type
|
|
347
|
+
* @param namespace - Namespace of the type
|
|
348
|
+
* @returns Seed type information
|
|
349
|
+
*/
|
|
350
|
+
function processSeedTypeInfo(type, namespace) {
|
|
351
|
+
const dependencies = Array.from(Object.values(type.fields).reduce((set, field) => {
|
|
352
|
+
const targetType = field.relation?.targetType ?? field.config.foreignKeyType;
|
|
353
|
+
if (targetType && targetType !== type.name) set.add(targetType);
|
|
354
|
+
return set;
|
|
355
|
+
}, /* @__PURE__ */ new Set()));
|
|
356
|
+
return {
|
|
357
|
+
name: type.name,
|
|
358
|
+
namespace,
|
|
359
|
+
dependencies,
|
|
360
|
+
dataFile: `data/${type.name}.jsonl`
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
//#endregion
|
|
365
|
+
//#region src/plugin/builtin/seed/index.ts
|
|
366
|
+
const SeedGeneratorID = "@tailor-platform/seed";
|
|
367
|
+
/**
|
|
368
|
+
* Generate the IdP user seed function code using tailor.idp.Client via testExecScript
|
|
369
|
+
* @param hasIdpUser - Whether IdP user is included
|
|
370
|
+
* @param idpNamespace - The IDP namespace name
|
|
371
|
+
* @returns JavaScript code for IdP user seeding function
|
|
372
|
+
*/
|
|
373
|
+
function generateIdpUserSeedFunction(hasIdpUser, idpNamespace) {
|
|
374
|
+
if (!hasIdpUser || !idpNamespace) return "";
|
|
375
|
+
return ml`
|
|
376
|
+
// Seed _User via tailor.idp.Client (server-side)
|
|
377
|
+
const seedIdpUser = async () => {
|
|
378
|
+
console.log(styleText("cyan", " Seeding _User via tailor.idp.Client..."));
|
|
379
|
+
const dataDir = join(configDir, "data");
|
|
380
|
+
const data = loadSeedData(dataDir, ["_User"]);
|
|
381
|
+
const rows = data["_User"] || [];
|
|
382
|
+
if (rows.length === 0) {
|
|
383
|
+
console.log(styleText("dim", " No _User data to seed"));
|
|
384
|
+
return { success: true };
|
|
385
|
+
}
|
|
386
|
+
console.log(styleText("dim", \` Processing \${rows.length} _User records...\`));
|
|
387
|
+
|
|
388
|
+
const idpSeedCode = \/* js *\/\`${generateIdpSeedScriptCode(idpNamespace).replace(/`/g, "\\`").replace(/\$/g, "\\$")}\`;
|
|
389
|
+
|
|
390
|
+
const result = await executeScript({
|
|
391
|
+
client: operatorClient,
|
|
392
|
+
workspaceId,
|
|
393
|
+
name: "seed-idp-user.ts",
|
|
394
|
+
code: idpSeedCode,
|
|
395
|
+
arg: JSON.stringify({ users: rows }),
|
|
396
|
+
invoker: {
|
|
397
|
+
namespace: authNamespace,
|
|
398
|
+
machineUserName,
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
if (result.logs) {
|
|
403
|
+
for (const line of result.logs.split("\\n").filter(Boolean)) {
|
|
404
|
+
console.log(styleText("dim", \` \${line}\`));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (result.success) {
|
|
409
|
+
let parsed;
|
|
410
|
+
try {
|
|
411
|
+
parsed = JSON.parse(result.result || "{}");
|
|
412
|
+
} catch (e) {
|
|
413
|
+
console.error(styleText("red", \` ✗ Failed to parse seed result: \${e.message}\`));
|
|
414
|
+
return { success: false };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (parsed.processed) {
|
|
418
|
+
console.log(styleText("green", \` ✓ _User: \${parsed.processed} rows processed\`));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (!parsed.success) {
|
|
422
|
+
const errors = Array.isArray(parsed.errors) ? parsed.errors : [];
|
|
423
|
+
for (const err of errors) {
|
|
424
|
+
console.error(styleText("red", \` ✗ \${err}\`));
|
|
425
|
+
}
|
|
426
|
+
return { success: false };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return { success: true };
|
|
430
|
+
} else {
|
|
431
|
+
console.error(styleText("red", \` ✗ Seed failed: \${result.error}\`));
|
|
432
|
+
return { success: false };
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
`;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Generate the IdP user seed call code
|
|
439
|
+
* @param hasIdpUser - Whether IdP user is included
|
|
440
|
+
* @returns JavaScript code for calling IdP user seeding
|
|
441
|
+
*/
|
|
442
|
+
function generateIdpUserSeedCall(hasIdpUser) {
|
|
443
|
+
if (!hasIdpUser) return "";
|
|
444
|
+
return ml`
|
|
445
|
+
// Seed _User if included and not skipped
|
|
446
|
+
const shouldSeedUser = !skipIdp && (!entitiesToProcess || entitiesToProcess.includes("_User"));
|
|
447
|
+
if (hasIdpUser && shouldSeedUser) {
|
|
448
|
+
const result = await seedIdpUser();
|
|
449
|
+
if (!result.success) {
|
|
450
|
+
allSuccess = false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
`;
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Generate the IdP user truncation function code using tailor.idp.Client via testExecScript
|
|
457
|
+
* @param hasIdpUser - Whether IdP user is included
|
|
458
|
+
* @param idpNamespace - The IDP namespace name
|
|
459
|
+
* @returns JavaScript code for IdP user truncation function
|
|
460
|
+
*/
|
|
461
|
+
function generateIdpUserTruncateFunction(hasIdpUser, idpNamespace) {
|
|
462
|
+
if (!hasIdpUser || !idpNamespace) return "";
|
|
463
|
+
return ml`
|
|
464
|
+
// Truncate _User via tailor.idp.Client (server-side)
|
|
465
|
+
const truncateIdpUser = async () => {
|
|
466
|
+
console.log(styleText("cyan", "Truncating _User via tailor.idp.Client..."));
|
|
467
|
+
|
|
468
|
+
const idpTruncateCode = \/* js *\/\`${generateIdpTruncateScriptCode(idpNamespace).replace(/`/g, "\\`").replace(/\$/g, "\\$")}\`;
|
|
469
|
+
|
|
470
|
+
const result = await executeScript({
|
|
471
|
+
client: operatorClient,
|
|
472
|
+
workspaceId,
|
|
473
|
+
name: "truncate-idp-user.ts",
|
|
474
|
+
code: idpTruncateCode,
|
|
475
|
+
arg: JSON.stringify({}),
|
|
476
|
+
invoker: {
|
|
477
|
+
namespace: authNamespace,
|
|
478
|
+
machineUserName,
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
if (result.logs) {
|
|
483
|
+
for (const line of result.logs.split("\\n").filter(Boolean)) {
|
|
484
|
+
console.log(styleText("dim", \` \${line}\`));
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (result.success) {
|
|
489
|
+
let parsed;
|
|
490
|
+
try {
|
|
491
|
+
parsed = JSON.parse(result.result || "{}");
|
|
492
|
+
} catch (e) {
|
|
493
|
+
console.error(styleText("red", \` ✗ Failed to parse truncation result: \${e.message}\`));
|
|
494
|
+
return { success: false };
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (parsed.deleted !== undefined) {
|
|
498
|
+
console.log(styleText("green", \` ✓ _User: \${parsed.deleted} users deleted\`));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (!parsed.success) {
|
|
502
|
+
const errors = Array.isArray(parsed.errors) ? parsed.errors : [];
|
|
503
|
+
for (const err of errors) {
|
|
504
|
+
console.error(styleText("red", \` ✗ \${err}\`));
|
|
505
|
+
}
|
|
506
|
+
return { success: false };
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return { success: true };
|
|
510
|
+
} else {
|
|
511
|
+
console.error(styleText("red", \` ✗ Truncation failed: \${result.error}\`));
|
|
512
|
+
return { success: false };
|
|
513
|
+
}
|
|
514
|
+
};
|
|
515
|
+
`;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Generate the IdP user truncation call code within the truncate block
|
|
519
|
+
* @param hasIdpUser - Whether IdP user is included
|
|
520
|
+
* @returns JavaScript code for calling IdP user truncation
|
|
521
|
+
*/
|
|
522
|
+
function generateIdpUserTruncateCall(hasIdpUser) {
|
|
523
|
+
if (!hasIdpUser) return "";
|
|
524
|
+
return ml`
|
|
525
|
+
// Truncate _User if applicable
|
|
526
|
+
const shouldTruncateUser = !skipIdp && !hasNamespace && (!hasTypes || entitiesToProcess.includes("_User"));
|
|
527
|
+
if (hasIdpUser && shouldTruncateUser) {
|
|
528
|
+
const truncResult = await truncateIdpUser();
|
|
529
|
+
if (!truncResult.success) {
|
|
530
|
+
console.error(styleText("red", "IDP user truncation failed."));
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
`;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Generates the exec.mjs script content using testExecScript API for TailorDB types
|
|
538
|
+
* and tailor.idp.Client for _User (IdP managed)
|
|
539
|
+
* @param defaultMachineUserName - Default machine user name from generator config (can be overridden at runtime)
|
|
540
|
+
* @param relativeConfigPath - Config path relative to exec script
|
|
541
|
+
* @param namespaceConfigs - Namespace configurations with types and dependencies
|
|
542
|
+
* @param hasIdpUser - Whether _User is included
|
|
543
|
+
* @param idpNamespace - The IDP namespace name, or null if not applicable
|
|
544
|
+
* @returns exec.mjs file contents
|
|
545
|
+
*/
|
|
546
|
+
function generateExecScript(defaultMachineUserName, relativeConfigPath, namespaceConfigs, hasIdpUser, idpNamespace) {
|
|
547
|
+
const namespaceEntitiesEntries = namespaceConfigs.map(({ namespace, types }) => {
|
|
548
|
+
return ` "${namespace}": [\n${types.map((e) => ` "${e}",`).join("\n")}\n ]`;
|
|
549
|
+
}).join(",\n");
|
|
550
|
+
const namespaceDepsEntries = namespaceConfigs.map(({ namespace, dependencies }) => {
|
|
551
|
+
return ` "${namespace}": {\n${Object.entries(dependencies).map(([type, deps]) => ` "${type}": [${deps.map((d) => `"${d}"`).join(", ")}]`).join(",\n")}\n }`;
|
|
552
|
+
}).join(",\n");
|
|
553
|
+
return ml`
|
|
554
|
+
import { readFileSync } from "node:fs";
|
|
555
|
+
import { join } from "node:path";
|
|
556
|
+
import { parseArgs, styleText } from "node:util";
|
|
557
|
+
import { createInterface } from "node:readline";
|
|
558
|
+
import {
|
|
559
|
+
show,
|
|
560
|
+
truncate,
|
|
561
|
+
bundleSeedScript,
|
|
562
|
+
chunkSeedData,
|
|
563
|
+
executeScript,
|
|
564
|
+
initOperatorClient,
|
|
565
|
+
loadAccessToken,
|
|
566
|
+
loadWorkspaceId,
|
|
567
|
+
} from "@tailor-platform/sdk/cli";
|
|
568
|
+
|
|
569
|
+
// Parse command-line arguments
|
|
570
|
+
const { values, positionals } = parseArgs({
|
|
571
|
+
options: {
|
|
572
|
+
"machine-user": { type: "string", short: "m" },
|
|
573
|
+
namespace: { type: "string", short: "n" },
|
|
574
|
+
"skip-idp": { type: "boolean", default: false },
|
|
575
|
+
truncate: { type: "boolean", default: false },
|
|
576
|
+
yes: { type: "boolean", default: false },
|
|
577
|
+
profile: { type: "string", short: "p" },
|
|
578
|
+
help: { type: "boolean", short: "h", default: false },
|
|
579
|
+
},
|
|
580
|
+
allowPositionals: true,
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
if (values.help) {
|
|
584
|
+
console.log(\`
|
|
585
|
+
Usage: node exec.mjs [options] [types...]
|
|
586
|
+
|
|
587
|
+
Options:
|
|
588
|
+
-m, --machine-user <name> Machine user name for authentication (required if not configured)
|
|
589
|
+
-n, --namespace <ns> Process all types in specified namespace (excludes _User)
|
|
590
|
+
--skip-idp Skip IdP user (_User) entity
|
|
591
|
+
--truncate Truncate tables before seeding
|
|
592
|
+
--yes Skip confirmation prompts (for truncate)
|
|
593
|
+
-p, --profile <name> Workspace profile name
|
|
594
|
+
-h, --help Show help
|
|
595
|
+
|
|
596
|
+
Examples:
|
|
597
|
+
node exec.mjs -m admin # Process all types with machine user
|
|
598
|
+
node exec.mjs --namespace <namespace> # Process tailordb namespace only (no _User)
|
|
599
|
+
node exec.mjs User Order # Process specific types only
|
|
600
|
+
node exec.mjs --skip-idp # Process all except _User
|
|
601
|
+
node exec.mjs --truncate # Truncate all tables, then seed all
|
|
602
|
+
node exec.mjs --truncate --yes # Truncate all tables without confirmation, then seed all
|
|
603
|
+
node exec.mjs --truncate --namespace <namespace> # Truncate tailordb, then seed tailordb
|
|
604
|
+
node exec.mjs --truncate User Order # Truncate User and Order, then seed them
|
|
605
|
+
\`);
|
|
606
|
+
process.exit(0);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Helper function to prompt for y/n confirmation
|
|
610
|
+
const promptConfirmation = (question) => {
|
|
611
|
+
const rl = createInterface({
|
|
612
|
+
input: process.stdin,
|
|
613
|
+
output: process.stdout,
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
return new Promise((resolve) => {
|
|
617
|
+
rl.question(styleText("yellow", question), (answer) => {
|
|
618
|
+
rl.close();
|
|
619
|
+
resolve(answer.toLowerCase().trim());
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
const configDir = import.meta.dirname;
|
|
625
|
+
const configPath = join(configDir, "${relativeConfigPath}");
|
|
626
|
+
|
|
627
|
+
// Determine machine user name (CLI argument takes precedence over config default)
|
|
628
|
+
const defaultMachineUser = ${defaultMachineUserName ? `"${defaultMachineUserName}"` : "undefined"};
|
|
629
|
+
const machineUserName = values["machine-user"] || defaultMachineUser;
|
|
630
|
+
|
|
631
|
+
if (!machineUserName) {
|
|
632
|
+
console.error(styleText("red", "Error: Machine user name is required."));
|
|
633
|
+
console.error(styleText("yellow", "Specify --machine-user <name> or configure machineUserName in generator options."));
|
|
634
|
+
process.exit(1);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Entity configuration
|
|
638
|
+
const namespaceEntities = {
|
|
639
|
+
${namespaceEntitiesEntries}
|
|
640
|
+
};
|
|
641
|
+
const namespaceDeps = {
|
|
642
|
+
${namespaceDepsEntries}
|
|
643
|
+
};
|
|
644
|
+
const entities = Object.values(namespaceEntities).flat();
|
|
645
|
+
const hasIdpUser = ${String(hasIdpUser)};
|
|
646
|
+
|
|
647
|
+
// Determine which entities to process
|
|
648
|
+
let entitiesToProcess = null;
|
|
649
|
+
|
|
650
|
+
const hasNamespace = !!values.namespace;
|
|
651
|
+
const hasTypes = positionals.length > 0;
|
|
652
|
+
const skipIdp = values["skip-idp"];
|
|
653
|
+
|
|
654
|
+
// Validate mutually exclusive options
|
|
655
|
+
const optionCount = [hasNamespace, hasTypes].filter(Boolean).length;
|
|
656
|
+
if (optionCount > 1) {
|
|
657
|
+
console.error(styleText("red", "Error: Options --namespace and type names are mutually exclusive."));
|
|
658
|
+
process.exit(1);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// --skip-idp and --namespace are redundant (namespace already excludes _User)
|
|
662
|
+
if (skipIdp && hasNamespace) {
|
|
663
|
+
console.warn(styleText("yellow", "Warning: --skip-idp is redundant with --namespace (namespace filtering already excludes _User)."));
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Filter by namespace (automatically excludes _User as it has no namespace)
|
|
667
|
+
if (hasNamespace) {
|
|
668
|
+
const namespace = values.namespace;
|
|
669
|
+
entitiesToProcess = namespaceEntities[namespace];
|
|
670
|
+
|
|
671
|
+
if (!entitiesToProcess || entitiesToProcess.length === 0) {
|
|
672
|
+
console.error(styleText("red", \`Error: No entities found in namespace "\${namespace}"\`));
|
|
673
|
+
console.error(styleText("yellow", \`Available namespaces: \${Object.keys(namespaceEntities).join(", ")}\`));
|
|
674
|
+
process.exit(1);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
console.log(styleText("cyan", \`Filtering by namespace: \${namespace}\`));
|
|
678
|
+
console.log(styleText("dim", \`Entities: \${entitiesToProcess.join(", ")}\`));
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// Filter by specific types
|
|
682
|
+
if (hasTypes) {
|
|
683
|
+
const requestedTypes = positionals;
|
|
684
|
+
const notFoundTypes = [];
|
|
685
|
+
const allTypes = hasIdpUser ? [...entities, "_User"] : entities;
|
|
686
|
+
|
|
687
|
+
entitiesToProcess = requestedTypes.filter((type) => {
|
|
688
|
+
if (!allTypes.includes(type)) {
|
|
689
|
+
notFoundTypes.push(type);
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
return true;
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
if (notFoundTypes.length > 0) {
|
|
696
|
+
console.error(styleText("red", \`Error: The following types were not found: \${notFoundTypes.join(", ")}\`));
|
|
697
|
+
console.error(styleText("yellow", \`Available types: \${allTypes.join(", ")}\`));
|
|
698
|
+
process.exit(1);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
console.log(styleText("cyan", \`Filtering by types: \${entitiesToProcess.join(", ")}\`));
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Apply --skip-idp filter
|
|
705
|
+
if (skipIdp) {
|
|
706
|
+
if (entitiesToProcess) {
|
|
707
|
+
entitiesToProcess = entitiesToProcess.filter((entity) => entity !== "_User");
|
|
708
|
+
} else {
|
|
709
|
+
entitiesToProcess = entities.filter((entity) => entity !== "_User");
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Get application info
|
|
714
|
+
const appInfo = await show({ configPath, profile: values.profile });
|
|
715
|
+
const authNamespace = appInfo.auth;
|
|
716
|
+
|
|
717
|
+
// Initialize operator client (once for all namespaces)
|
|
718
|
+
const accessToken = await loadAccessToken({ profile: values.profile, useProfile: true });
|
|
719
|
+
const workspaceId = await loadWorkspaceId({ profile: values.profile });
|
|
720
|
+
const operatorClient = await initOperatorClient(accessToken);
|
|
721
|
+
|
|
722
|
+
${generateIdpUserTruncateFunction(hasIdpUser, idpNamespace)}
|
|
723
|
+
|
|
724
|
+
// Truncate tables if requested
|
|
725
|
+
if (values.truncate) {
|
|
726
|
+
const answer = values.yes ? "y" : await promptConfirmation("Are you sure you want to truncate? (y/n): ");
|
|
727
|
+
if (answer !== "y") {
|
|
728
|
+
console.log(styleText("yellow", "Truncate cancelled."));
|
|
729
|
+
process.exit(0);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
console.log(styleText("cyan", "Truncating tables..."));
|
|
733
|
+
|
|
734
|
+
try {
|
|
735
|
+
if (hasNamespace) {
|
|
736
|
+
await truncate({
|
|
737
|
+
configPath,
|
|
738
|
+
profile: values.profile,
|
|
739
|
+
namespace: values.namespace,
|
|
740
|
+
});
|
|
741
|
+
} else if (hasTypes) {
|
|
742
|
+
const typesToTruncate = entitiesToProcess.filter((t) => t !== "_User");
|
|
743
|
+
if (typesToTruncate.length > 0) {
|
|
744
|
+
await truncate({
|
|
745
|
+
configPath,
|
|
746
|
+
profile: values.profile,
|
|
747
|
+
types: typesToTruncate,
|
|
748
|
+
});
|
|
749
|
+
} else {
|
|
750
|
+
console.log(styleText("dim", "No TailorDB types to truncate (only _User was specified)."));
|
|
751
|
+
}
|
|
752
|
+
} else {
|
|
753
|
+
await truncate({
|
|
754
|
+
configPath,
|
|
755
|
+
profile: values.profile,
|
|
756
|
+
all: true,
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
} catch (error) {
|
|
760
|
+
console.error(styleText("red", \`Truncate failed: \${error.message}\`));
|
|
761
|
+
process.exit(1);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
${generateIdpUserTruncateCall(hasIdpUser)}
|
|
765
|
+
|
|
766
|
+
console.log(styleText("green", "Truncate completed."));
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
console.log(styleText("cyan", "\\nStarting seed data generation..."));
|
|
770
|
+
if (skipIdp) {
|
|
771
|
+
console.log(styleText("dim", \` Skipping IdP user (_User)\`));
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Load seed data from JSONL files
|
|
775
|
+
const loadSeedData = (dataDir, typeNames) => {
|
|
776
|
+
const data = {};
|
|
777
|
+
for (const typeName of typeNames) {
|
|
778
|
+
const jsonlPath = join(dataDir, \`\${typeName}.jsonl\`);
|
|
779
|
+
try {
|
|
780
|
+
const content = readFileSync(jsonlPath, "utf-8").trim();
|
|
781
|
+
if (content) {
|
|
782
|
+
data[typeName] = content.split("\\n").map((line) => JSON.parse(line));
|
|
783
|
+
} else {
|
|
784
|
+
data[typeName] = [];
|
|
785
|
+
}
|
|
786
|
+
} catch (error) {
|
|
787
|
+
if (error.code === "ENOENT") {
|
|
788
|
+
data[typeName] = [];
|
|
789
|
+
} else {
|
|
790
|
+
throw error;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return data;
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
// Topological sort for dependency order
|
|
798
|
+
const topologicalSort = (types, deps) => {
|
|
799
|
+
const visited = new Set();
|
|
800
|
+
const result = [];
|
|
801
|
+
|
|
802
|
+
const visit = (type) => {
|
|
803
|
+
if (visited.has(type)) return;
|
|
804
|
+
visited.add(type);
|
|
805
|
+
const typeDeps = deps[type] || [];
|
|
806
|
+
for (const dep of typeDeps) {
|
|
807
|
+
if (types.includes(dep)) {
|
|
808
|
+
visit(dep);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
result.push(type);
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
for (const type of types) {
|
|
815
|
+
visit(type);
|
|
816
|
+
}
|
|
817
|
+
return result;
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
// Seed TailorDB types via testExecScript
|
|
821
|
+
const seedViaTestExecScript = async (namespace, typesToSeed, deps) => {
|
|
822
|
+
const dataDir = join(configDir, "data");
|
|
823
|
+
const sortedTypes = topologicalSort(typesToSeed, deps);
|
|
824
|
+
const data = loadSeedData(dataDir, sortedTypes);
|
|
825
|
+
|
|
826
|
+
// Skip if no data
|
|
827
|
+
const typesWithData = sortedTypes.filter((t) => data[t] && data[t].length > 0);
|
|
828
|
+
if (typesWithData.length === 0) {
|
|
829
|
+
console.log(styleText("dim", \` [\${namespace}] No data to seed\`));
|
|
830
|
+
return { success: true, processed: {} };
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
console.log(styleText("cyan", \` [\${namespace}] Seeding \${typesWithData.length} types via Kysely batch insert...\`));
|
|
834
|
+
|
|
835
|
+
// Bundle seed script
|
|
836
|
+
const bundled = await bundleSeedScript(namespace, typesWithData);
|
|
837
|
+
|
|
838
|
+
// Chunk seed data to fit within gRPC message size limits
|
|
839
|
+
const chunks = chunkSeedData({
|
|
840
|
+
data,
|
|
841
|
+
order: sortedTypes,
|
|
842
|
+
codeByteSize: new TextEncoder().encode(bundled.bundledCode).length,
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
if (chunks.length === 0) {
|
|
846
|
+
console.log(styleText("dim", \` [\${namespace}] No data to seed\`));
|
|
847
|
+
return { success: true, processed: {} };
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
if (chunks.length > 1) {
|
|
851
|
+
console.log(styleText("dim", \` Split into \${chunks.length} chunks\`));
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const allProcessed = {};
|
|
855
|
+
let hasError = false;
|
|
856
|
+
const allErrors = [];
|
|
857
|
+
|
|
858
|
+
for (const chunk of chunks) {
|
|
859
|
+
if (chunks.length > 1) {
|
|
860
|
+
console.log(styleText("dim", \` Chunk \${chunk.index + 1}/\${chunk.total}: \${chunk.order.join(", ")}\`));
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Execute seed script for this chunk
|
|
864
|
+
const result = await executeScript({
|
|
865
|
+
client: operatorClient,
|
|
866
|
+
workspaceId,
|
|
867
|
+
name: \`seed-\${namespace}.ts\`,
|
|
868
|
+
code: bundled.bundledCode,
|
|
869
|
+
arg: JSON.stringify({ data: chunk.data, order: chunk.order }),
|
|
870
|
+
invoker: {
|
|
871
|
+
namespace: authNamespace,
|
|
872
|
+
machineUserName,
|
|
873
|
+
},
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
// Parse result and display logs
|
|
877
|
+
if (result.logs) {
|
|
878
|
+
for (const line of result.logs.split("\\n").filter(Boolean)) {
|
|
879
|
+
console.log(styleText("dim", \` \${line}\`));
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
if (result.success) {
|
|
884
|
+
let parsed;
|
|
885
|
+
try {
|
|
886
|
+
const parsedResult = JSON.parse(result.result || "{}");
|
|
887
|
+
parsed = parsedResult && typeof parsedResult === "object" ? parsedResult : {};
|
|
888
|
+
} catch (error) {
|
|
889
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
890
|
+
console.error(styleText("red", \` ✗ Failed to parse seed result: \${message}\`));
|
|
891
|
+
hasError = true;
|
|
892
|
+
allErrors.push(message);
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const processed = parsed.processed || {};
|
|
897
|
+
for (const [type, count] of Object.entries(processed)) {
|
|
898
|
+
allProcessed[type] = (allProcessed[type] || 0) + count;
|
|
899
|
+
console.log(styleText("green", \` ✓ \${type}: \${count} rows inserted\`));
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (!parsed.success) {
|
|
903
|
+
const errors = Array.isArray(parsed.errors) ? parsed.errors : [];
|
|
904
|
+
const errorMessage =
|
|
905
|
+
errors.length > 0 ? errors.join("\\n ") : "Seed script reported failure";
|
|
906
|
+
console.error(styleText("red", \` ✗ Seed failed:\\n \${errorMessage}\`));
|
|
907
|
+
hasError = true;
|
|
908
|
+
allErrors.push(errorMessage);
|
|
909
|
+
}
|
|
910
|
+
} else {
|
|
911
|
+
console.error(styleText("red", \` ✗ Seed failed: \${result.error}\`));
|
|
912
|
+
hasError = true;
|
|
913
|
+
allErrors.push(result.error);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (hasError) {
|
|
918
|
+
return { success: false, error: allErrors.join("\\n") };
|
|
919
|
+
}
|
|
920
|
+
return { success: true, processed: allProcessed };
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
${generateIdpUserSeedFunction(hasIdpUser, idpNamespace)}
|
|
924
|
+
|
|
925
|
+
// Main execution
|
|
926
|
+
try {
|
|
927
|
+
let allSuccess = true;
|
|
928
|
+
|
|
929
|
+
// Determine which namespaces and types to process
|
|
930
|
+
const namespacesToProcess = hasNamespace
|
|
931
|
+
? [values.namespace]
|
|
932
|
+
: Object.keys(namespaceEntities);
|
|
933
|
+
|
|
934
|
+
for (const namespace of namespacesToProcess) {
|
|
935
|
+
const nsTypes = namespaceEntities[namespace] || [];
|
|
936
|
+
const nsDeps = namespaceDeps[namespace] || {};
|
|
937
|
+
|
|
938
|
+
// Filter types if specific types requested
|
|
939
|
+
let typesToSeed = entitiesToProcess
|
|
940
|
+
? nsTypes.filter((t) => entitiesToProcess.includes(t))
|
|
941
|
+
: nsTypes;
|
|
942
|
+
|
|
943
|
+
if (typesToSeed.length === 0) continue;
|
|
944
|
+
|
|
945
|
+
const result = await seedViaTestExecScript(namespace, typesToSeed, nsDeps);
|
|
946
|
+
if (!result.success) {
|
|
947
|
+
allSuccess = false;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
${generateIdpUserSeedCall(hasIdpUser)}
|
|
952
|
+
|
|
953
|
+
if (allSuccess) {
|
|
954
|
+
console.log(styleText("green", "\\n✓ Seed data generation completed successfully"));
|
|
955
|
+
} else {
|
|
956
|
+
console.error(styleText("red", "\\n✗ Seed data generation completed with errors"));
|
|
957
|
+
process.exit(1);
|
|
958
|
+
}
|
|
959
|
+
} catch (error) {
|
|
960
|
+
console.error(styleText("red", \`\\n✗ Seed data generation failed: \${error.message}\`));
|
|
961
|
+
process.exit(1);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
`;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Plugin that generates seed data files with Kysely batch insert and tailor.idp.Client for _User.
|
|
968
|
+
* @param options - Plugin options
|
|
969
|
+
* @param options.distPath - Output directory path for generated seed files
|
|
970
|
+
* @param options.machineUserName - Default machine user name for authentication
|
|
971
|
+
* @returns Plugin instance with onTailorDBReady hook
|
|
972
|
+
*/
|
|
973
|
+
function seedPlugin(options) {
|
|
974
|
+
return {
|
|
975
|
+
id: SeedGeneratorID,
|
|
976
|
+
description: "Generates seed data files (Kysely batch insert + tailor.idp.Client for _User)",
|
|
977
|
+
pluginConfig: options,
|
|
978
|
+
async onTailorDBReady(ctx) {
|
|
979
|
+
const files = [];
|
|
980
|
+
const namespaceConfigs = [];
|
|
981
|
+
for (const ns of ctx.tailordb) {
|
|
982
|
+
const types = [];
|
|
983
|
+
const dependencies = {};
|
|
984
|
+
for (const [typeName, type] of Object.entries(ns.types)) {
|
|
985
|
+
const source = ns.sourceInfo.get(typeName);
|
|
986
|
+
const typeInfo = processSeedTypeInfo(type, ns.namespace);
|
|
987
|
+
const linesDb = processLinesDb(type, source);
|
|
988
|
+
types.push(typeInfo.name);
|
|
989
|
+
dependencies[typeInfo.name] = typeInfo.dependencies;
|
|
990
|
+
files.push({
|
|
991
|
+
path: path.join(ctx.pluginConfig.distPath, typeInfo.dataFile),
|
|
992
|
+
content: "",
|
|
993
|
+
skipIfExists: true
|
|
994
|
+
});
|
|
995
|
+
const schemaOutputPath = path.join(ctx.pluginConfig.distPath, "data", `${linesDb.typeName}.schema.ts`);
|
|
996
|
+
if (linesDb.pluginSource && linesDb.pluginSource.pluginImportPath) {
|
|
997
|
+
let originalImportPath;
|
|
998
|
+
if (linesDb.pluginSource.originalFilePath && linesDb.pluginSource.originalExportName) {
|
|
999
|
+
const relativePath = path.relative(path.dirname(schemaOutputPath), linesDb.pluginSource.originalFilePath);
|
|
1000
|
+
originalImportPath = relativePath.replace(/\.ts$/, "").startsWith(".") ? relativePath.replace(/\.ts$/, "") : `./${relativePath.replace(/\.ts$/, "")}`;
|
|
1001
|
+
}
|
|
1002
|
+
const schemaContent = generateLinesDbSchemaFileWithPluginAPI(linesDb, {
|
|
1003
|
+
configImportPath: path.relative(path.dirname(schemaOutputPath), ctx.configPath),
|
|
1004
|
+
originalImportPath
|
|
1005
|
+
});
|
|
1006
|
+
files.push({
|
|
1007
|
+
path: schemaOutputPath,
|
|
1008
|
+
content: schemaContent
|
|
1009
|
+
});
|
|
1010
|
+
} else {
|
|
1011
|
+
const relativePath = path.relative(path.dirname(schemaOutputPath), linesDb.importPath);
|
|
1012
|
+
const schemaContent = generateLinesDbSchemaFile(linesDb, relativePath.replace(/\.ts$/, "").startsWith(".") ? relativePath.replace(/\.ts$/, "") : `./${relativePath.replace(/\.ts$/, "")}`);
|
|
1013
|
+
files.push({
|
|
1014
|
+
path: schemaOutputPath,
|
|
1015
|
+
content: schemaContent
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
namespaceConfigs.push({
|
|
1020
|
+
namespace: ns.namespace,
|
|
1021
|
+
types,
|
|
1022
|
+
dependencies
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
const idpUser = ctx.auth ? processIdpUser(ctx.auth) : null;
|
|
1026
|
+
const hasIdpUser = idpUser !== null;
|
|
1027
|
+
if (idpUser) {
|
|
1028
|
+
files.push({
|
|
1029
|
+
path: path.join(ctx.pluginConfig.distPath, idpUser.dataFile),
|
|
1030
|
+
content: "",
|
|
1031
|
+
skipIfExists: true
|
|
1032
|
+
});
|
|
1033
|
+
files.push({
|
|
1034
|
+
path: path.join(ctx.pluginConfig.distPath, "data", `${idpUser.name}.schema.ts`),
|
|
1035
|
+
content: generateIdpUserSchemaFile(idpUser.schema.usernameField, idpUser.schema.userTypeName)
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
const relativeConfigPath = path.relative(ctx.pluginConfig.distPath, ctx.configPath);
|
|
1039
|
+
files.push({
|
|
1040
|
+
path: path.join(ctx.pluginConfig.distPath, "exec.mjs"),
|
|
1041
|
+
content: generateExecScript(ctx.pluginConfig.machineUserName, relativeConfigPath, namespaceConfigs, hasIdpUser, idpUser?.idpNamespace ?? null)
|
|
1042
|
+
});
|
|
1043
|
+
return { files };
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
//#endregion
|
|
1049
|
+
export { seedPlugin as n, SeedGeneratorID as t };
|
|
1050
|
+
//# sourceMappingURL=seed-Dm7lrGZ3.mjs.map
|