@lowdefy/build 5.2.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/build/buildAgents.js +249 -0
  2. package/dist/build/buildApi/buildRoutine/countStepTypes.js +1 -1
  3. package/dist/build/buildApi/buildRoutine/setStepId.js +6 -2
  4. package/dist/build/buildApi/buildRoutine/validateStep.js +18 -0
  5. package/dist/build/buildApp.js +18 -5
  6. package/dist/build/buildImports/buildImportsDev.js +5 -0
  7. package/dist/build/buildImports/buildImportsProd.js +1 -0
  8. package/dist/build/buildJs/writeJs.js +2 -2
  9. package/dist/build/buildMenu.js +5 -0
  10. package/dist/build/buildModuleDefs.js +52 -1
  11. package/dist/build/buildModules.js +1 -1
  12. package/dist/build/buildPages/validateCallApiRefs.js +9 -0
  13. package/dist/build/buildRefs/getUserJavascriptFunction.js +6 -9
  14. package/dist/build/buildRefs/runTransformer.js +3 -2
  15. package/dist/build/buildRefs/walker.js +22 -39
  16. package/dist/build/buildTypes.js +7 -0
  17. package/dist/build/codegenI18nLocales.js +53 -0
  18. package/dist/build/copyAgentFileSystems.js +45 -0
  19. package/dist/build/full/updateServerPackageJson.js +1 -0
  20. package/dist/build/jit/shallowBuild.js +31 -0
  21. package/dist/build/registerModules.js +11 -33
  22. package/dist/build/writeAgents.js +26 -0
  23. package/dist/build/writeAppMeta.js +19 -0
  24. package/dist/build/writeI18n.js +117 -0
  25. package/dist/build/writePluginImports/writeAgentImports.js +22 -0
  26. package/dist/build/writePluginImports/writePluginImports.js +10 -0
  27. package/dist/build/writePluginImports/writeServerExternalPackages.js +121 -0
  28. package/dist/createContext.js +8 -1
  29. package/dist/defaultMessagesMap.js +3 -0
  30. package/dist/defaultPackages.js +7 -0
  31. package/dist/defaultTypesMap.js +590 -377
  32. package/dist/index.js +30 -0
  33. package/dist/lowdefySchema.js +414 -0
  34. package/dist/scripts/generateDefaultMessages.js +37 -0
  35. package/dist/scripts/generateDefaultTypes.js +1 -0
  36. package/dist/test-utils/testContext.js +2 -0
  37. package/dist/utils/createPluginTypesMap.js +7 -0
  38. package/package.json +51 -44
@@ -330,11 +330,6 @@ function resolveModulePageId(arg, moduleEntry, context, configKey) {
330
330
  configKey
331
331
  });
332
332
  }
333
- if (!(moduleEntry.exports?.pages ?? []).some((p)=>p.id === arg)) {
334
- throw new ConfigError(`Module "${moduleEntry.id}" does not export page "${arg}".`, {
335
- configKey
336
- });
337
- }
338
333
  return `${moduleEntry.id}/${arg}`;
339
334
  }
340
335
  if (type.isObject(arg) && type.isString(arg.id) && type.isString(arg.module)) {
@@ -345,12 +340,6 @@ function resolveModulePageId(arg, moduleEntry, context, configKey) {
345
340
  configKey,
346
341
  usage: `_module.pageId { id: "${arg.id}", module: "${arg.module}" }`
347
342
  });
348
- if (!(targetEntry.exports?.pages ?? []).some((p)=>p.id === arg.id)) {
349
- const caller = moduleEntry ? `Module "${moduleEntry.id}"` : 'App config';
350
- throw new ConfigError(`${caller} references page "${arg.id}" ` + `from "${arg.module}" (entry "${targetEntry.id}"), ` + `but that module does not export page "${arg.id}".`, {
351
- configKey
352
- });
353
- }
354
343
  return `${targetEntry.id}/${arg.id}`;
355
344
  }
356
345
  throw new ConfigError('_module.pageId requires a string or object { id, module }.', {
@@ -365,11 +354,6 @@ function resolveModuleConnectionId(arg, moduleEntry, context, configKey) {
365
354
  configKey
366
355
  });
367
356
  }
368
- if (!(moduleEntry.exports?.connections ?? []).some((c)=>c.id === arg)) {
369
- throw new ConfigError(`Module "${moduleEntry.id}" does not export connection "${arg}".`, {
370
- configKey
371
- });
372
- }
373
357
  const remapping = moduleEntry.connections ?? {};
374
358
  if (remapping[arg]) {
375
359
  return remapping[arg];
@@ -384,12 +368,6 @@ function resolveModuleConnectionId(arg, moduleEntry, context, configKey) {
384
368
  configKey,
385
369
  usage: `_module.connectionId { id: "${arg.id}", module: "${arg.module}" }`
386
370
  });
387
- if (!(targetEntry.exports?.connections ?? []).some((c)=>c.id === arg.id)) {
388
- const caller = moduleEntry ? `Module "${moduleEntry.id}"` : 'App config';
389
- throw new ConfigError(`${caller} references connection "${arg.id}" ` + `from "${arg.module}" (entry "${targetEntry.id}"), ` + `but that module does not export connection "${arg.id}".`, {
390
- configKey
391
- });
392
- }
393
371
  const targetRemapping = targetEntry.connections ?? {};
394
372
  if (targetRemapping[arg.id]) {
395
373
  return targetRemapping[arg.id];
@@ -408,11 +386,6 @@ function resolveModuleEndpointId(arg, moduleEntry, context, configKey) {
408
386
  configKey
409
387
  });
410
388
  }
411
- if (!(moduleEntry.exports?.api ?? []).some((e)=>e.id === arg)) {
412
- throw new ConfigError(`Module "${moduleEntry.id}" does not export endpoint "${arg}".`, {
413
- configKey
414
- });
415
- }
416
389
  return `${moduleEntry.id}/${arg}`;
417
390
  }
418
391
  if (type.isObject(arg) && type.isString(arg.id) && type.isString(arg.module)) {
@@ -423,12 +396,6 @@ function resolveModuleEndpointId(arg, moduleEntry, context, configKey) {
423
396
  configKey,
424
397
  usage: `_module.endpointId { id: "${arg.id}", module: "${arg.module}" }`
425
398
  });
426
- if (!(targetEntry.exports?.api ?? []).some((e)=>e.id === arg.id)) {
427
- const caller = moduleEntry ? `Module "${moduleEntry.id}"` : 'App config';
428
- throw new ConfigError(`${caller} references endpoint "${arg.id}" ` + `from "${arg.module}" (entry "${targetEntry.id}"), ` + `but that module does not export endpoint "${arg.id}".`, {
429
- configKey
430
- });
431
- }
432
399
  return `${targetEntry.id}/${arg.id}`;
433
400
  }
434
401
  throw new ConfigError('_module.endpointId requires a string or object { id, module }.', {
@@ -503,8 +470,16 @@ async function resolveRef(node, ctx) {
503
470
  refDef.key = await resolve(cloneForResolve(refDef.key), ctx);
504
471
  }
505
472
  // 4. Module path resolution: resolve relative paths from the module root
506
- if (ctx.moduleRoot && type.isString(refDef.path) && !path.isAbsolute(refDef.path)) {
507
- refDef.path = path.resolve(ctx.moduleRoot, refDef.path);
473
+ if (ctx.moduleRoot) {
474
+ if (type.isString(refDef.path) && !path.isAbsolute(refDef.path)) {
475
+ refDef.path = path.resolve(ctx.moduleRoot, refDef.path);
476
+ }
477
+ if (type.isString(refDef.resolver) && !path.isAbsolute(refDef.resolver)) {
478
+ refDef.resolver = path.resolve(ctx.moduleRoot, refDef.resolver);
479
+ }
480
+ if (type.isString(refDef.transformer) && !path.isAbsolute(refDef.transformer)) {
481
+ refDef.transformer = path.resolve(ctx.moduleRoot, refDef.transformer);
482
+ }
508
483
  }
509
484
  // 5. Update refMap with resolved path; store original for resolver refs
510
485
  ctx.refMap[refDef.id].path = refDef.path;
@@ -512,9 +487,16 @@ async function resolveRef(node, ctx) {
512
487
  ctx.refMap[refDef.id].original = refDef.original;
513
488
  }
514
489
  // 6. Path escape constraint: module refs cannot escape the package root
515
- if (ctx.packageRoot && refDef.path) {
516
- if (!refDef.path.startsWith(ctx.packageRoot + '/') && refDef.path !== ctx.packageRoot) {
517
- throw new ConfigError(`Module ref path "${refDef.path}" escapes the package root.`);
490
+ if (ctx.packageRoot) {
491
+ for (const field of [
492
+ 'path',
493
+ 'resolver',
494
+ 'transformer'
495
+ ]){
496
+ const value = refDef[field];
497
+ if (type.isString(value) && !value.startsWith(ctx.packageRoot + '/') && value !== ctx.packageRoot) {
498
+ throw new ConfigError(`Module ref ${field} "${value}" escapes the package root.`);
499
+ }
518
500
  }
519
501
  }
520
502
  // 7. Circular detection
@@ -628,7 +610,8 @@ async function resolveRef(node, ctx) {
628
610
  content = await runTransformer({
629
611
  context: ctx.buildContext,
630
612
  input: content,
631
- refDef
613
+ refDef,
614
+ referencedFrom: ctx.currentFile
632
615
  });
633
616
  // 14. Extract key
634
617
  content = getKey({
@@ -65,6 +65,7 @@ function buildTypes({ components, context }) {
65
65
  typeCounters.actions.increment('SetDarkMode');
66
66
  components.types = {
67
67
  actions: {},
68
+ agents: {},
68
69
  auth: {
69
70
  adapters: {},
70
71
  callbacks: {},
@@ -86,6 +87,12 @@ function buildTypes({ components, context }) {
86
87
  store: components.types.actions,
87
88
  typeClass: 'Action'
88
89
  });
90
+ buildTypeClass(context, {
91
+ counter: typeCounters.agents,
92
+ definitions: context.typesMap.agents,
93
+ store: components.types.agents,
94
+ typeClass: 'Agent'
95
+ });
89
96
  buildTypeClass(context, {
90
97
  counter: typeCounters.auth.adapters,
91
98
  definitions: context.typesMap.auth.adapters,
@@ -0,0 +1,53 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { type } from '@lowdefy/helpers';
16
+ const HEADER = `// Generated by @lowdefy/build. Do not edit.\n`;
17
+ const SAFE_IDENTIFIER_REGEX = /^[A-Za-z0-9_-]+$/;
18
+ function isSafeIdentifier(value) {
19
+ return type.isString(value) && SAFE_IDENTIFIER_REGEX.test(value);
20
+ }
21
+ function buildAntdLoaders(locales) {
22
+ const entries = locales.filter((locale)=>isSafeIdentifier(locale.code) && isSafeIdentifier(locale.antd)).map((locale)=>` '${locale.code}': () => import('antd/locale/${locale.antd}.js').then((m) => m.default ?? m),`);
23
+ return `${HEADER}const loaders = {\n${entries.join('\n')}\n};\nexport default loaders;\n`;
24
+ }
25
+ function buildDayjsImports(locales) {
26
+ const validDayjsLocales = locales.filter((locale)=>isSafeIdentifier(locale.code) && isSafeIdentifier(locale.dayjs));
27
+ const ids = Array.from(new Set(validDayjsLocales.map((locale)=>locale.dayjs)));
28
+ const imports = ids.map((id)=>`import 'dayjs/locale/${id}.js';`).join('\n');
29
+ const localeMap = validDayjsLocales.map((locale)=>` '${locale.code}': '${locale.dayjs}',`).join('\n');
30
+ return `${HEADER}${imports}\nconst localeMap = {\n${localeMap}\n};\nexport default localeMap;\n`;
31
+ }
32
+ // antd X (@ant-design/x@2.7.x) only ships two locale packs: en_US and zh_CN.
33
+ // Other locales fall back to en_US for X-native strings (New chat, OK/Cancel,
34
+ // Stop loading, Like/Dislike, etc.). App authors override per-locale via
35
+ // config.i18n.messages.{locale}.agent.antdx.* keys handled outside this codegen.
36
+ const ANTD_X_SUPPORTED = new Set([
37
+ 'en_US',
38
+ 'zh_CN'
39
+ ]);
40
+ function buildAntdXLoaders(locales) {
41
+ const entries = locales.filter((locale)=>isSafeIdentifier(locale.code) && isSafeIdentifier(locale.antd)).map((locale)=>{
42
+ const pack = ANTD_X_SUPPORTED.has(locale.antd) ? locale.antd : 'en_US';
43
+ return ` '${locale.code}': () => import('@ant-design/x/locale/${pack}.js').then((m) => m.default ?? m),`;
44
+ });
45
+ return `${HEADER}const loaders = {\n${entries.join('\n')}\n};\nexport default loaders;\n`;
46
+ }
47
+ async function codegenI18nLocales({ components, context }) {
48
+ const locales = components.config?.i18n?.locales ?? [];
49
+ await context.writeBuildArtifact('i18n/antdLocales.js', buildAntdLoaders(locales));
50
+ await context.writeBuildArtifact('i18n/antdXLocales.js', buildAntdXLoaders(locales));
51
+ await context.writeBuildArtifact('i18n/dayjsLocales.js', buildDayjsImports(locales));
52
+ }
53
+ export default codegenI18nLocales;
@@ -0,0 +1,45 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import path from 'path';
16
+ import fs from 'fs';
17
+ import { copyFileOrDirectory } from '@lowdefy/node-utils';
18
+ async function copyAgentFileSystems({ components, context }) {
19
+ const basePaths = [];
20
+ const seen = new Set();
21
+ for (const agent of components.agents ?? []){
22
+ const basePath = agent.properties?.fileSystem?.basePath;
23
+ if (!basePath || typeof basePath !== 'string') continue;
24
+ if (seen.has(basePath)) continue;
25
+ seen.add(basePath);
26
+ basePaths.push(basePath);
27
+ }
28
+ // Manifest is consumed by next.config.js to populate outputFileTracingIncludes,
29
+ // so the Next.js tracer bundles these directories on Vercel and standalone builds.
30
+ await context.writeBuildArtifact('agentFileSystems.json', JSON.stringify(basePaths));
31
+ if (context.directories.config === context.directories.server) return;
32
+ for (const basePath of basePaths){
33
+ const source = path.resolve(context.directories.config, basePath);
34
+ if (!fs.existsSync(source)) continue;
35
+ const dest = path.resolve(context.directories.server, basePath);
36
+ try {
37
+ await copyFileOrDirectory(source, dest);
38
+ } catch (err) {
39
+ throw new Error(`Failed to copy fileSystem basePath "${basePath}" to server directory: ${err.message}`, {
40
+ cause: err
41
+ });
42
+ }
43
+ }
44
+ }
45
+ export default copyAgentFileSystems;
@@ -25,6 +25,7 @@ async function updateServerPackageJson({ components, context }) {
25
25
  });
26
26
  }
27
27
  getPackages(components.types.actions);
28
+ getPackages(components.types.agents);
28
29
  getPackages(components.types.auth.adapters);
29
30
  getPackages(components.types.auth.callbacks);
30
31
  getPackages(components.types.auth.events);
@@ -23,6 +23,7 @@ import addKeys from '../addKeys.js';
23
23
  import buildApp from '../buildApp.js';
24
24
  import buildAuth from '../buildAuth/buildAuth.js';
25
25
  import buildConnections from '../buildConnections.js';
26
+ import buildAgents from '../buildAgents.js';
26
27
  import buildApi from '../buildApi/buildApi.js';
27
28
  import buildLogger from '../buildLogger.js';
28
29
  import buildImports from '../buildImports/buildImports.js';
@@ -32,17 +33,22 @@ import buildModules from '../buildModules.js';
32
33
  import buildRefs from '../buildRefs/buildRefs.js';
33
34
  import buildTypes from '../buildTypes.js';
34
35
  import cleanBuildDirectory from '../cleanBuildDirectory.js';
36
+ import copyAgentFileSystems from '../copyAgentFileSystems.js';
35
37
  import copyPublicFolder from '../copyPublicFolder.js';
36
38
  import testSchema from '../testSchema.js';
37
39
  import validateConfig from '../validateConfig.js';
38
40
  import writeApp from '../writeApp.js';
41
+ import writeAppMeta from '../writeAppMeta.js';
39
42
  import writeAuth from '../writeAuth.js';
40
43
  import writeConfig from '../writeConfig.js';
41
44
  import writeConnections from '../writeConnections.js';
45
+ import writeAgents from '../writeAgents.js';
42
46
  import writeApi from '../writeApi.js';
43
47
  import writeGlobal from '../writeGlobal.js';
44
48
  import writeJs from '../buildJs/writeJs.js';
45
49
  import writeLogger from '../writeLogger.js';
50
+ import codegenI18nLocales from '../codegenI18nLocales.js';
51
+ import writeI18n from '../writeI18n.js';
46
52
  import writeTheme from '../writeTheme.js';
47
53
  import writeMaps from '../writeMaps.js';
48
54
  import updateServerPackageJson from '../full/updateServerPackageJson.js';
@@ -146,6 +152,10 @@ async function shallowBuild(options) {
146
152
  components,
147
153
  context
148
154
  });
155
+ tryBuildStep(buildAgents, 'buildAgents', {
156
+ components,
157
+ context
158
+ });
149
159
  const { pageRegistry, sourcelessPageArtifacts } = buildShallowPages({
150
160
  components,
151
161
  context
@@ -195,6 +205,10 @@ async function shallowBuild(options) {
195
205
  components,
196
206
  context
197
207
  });
208
+ await writeAppMeta({
209
+ components,
210
+ context
211
+ });
198
212
  await writeAuth({
199
213
  components,
200
214
  context
@@ -207,6 +221,10 @@ async function shallowBuild(options) {
207
221
  components,
208
222
  context
209
223
  });
224
+ await writeAgents({
225
+ components,
226
+ context
227
+ });
210
228
  await writeConfig({
211
229
  components,
212
230
  context
@@ -219,6 +237,14 @@ async function shallowBuild(options) {
219
237
  components,
220
238
  context
221
239
  });
240
+ await writeI18n({
241
+ components,
242
+ context
243
+ });
244
+ await codegenI18nLocales({
245
+ components,
246
+ context
247
+ });
222
248
  await writeLogger({
223
249
  components,
224
250
  context
@@ -242,6 +268,7 @@ async function shallowBuild(options) {
242
268
  await context.writeBuildArtifact('jsMap.json', JSON.stringify(context.jsMap));
243
269
  await context.writeBuildArtifact('idCounter.json', JSON.stringify(makeId.counter));
244
270
  await context.writeBuildArtifact('customTypesMap.json', JSON.stringify(options.customTypesMap ?? {}));
271
+ await context.writeBuildArtifact('customMessagesMap.json', JSON.stringify(options.customMessagesMap ?? {}));
245
272
  // Persist snapshot of installed packages for JIT missing-package detection.
246
273
  // Written as a build artifact so JIT builds compare against the skeleton
247
274
  // build state, not a potentially-updated package.json (race condition).
@@ -265,6 +292,10 @@ async function shallowBuild(options) {
265
292
  components,
266
293
  context
267
294
  });
295
+ await copyAgentFileSystems({
296
+ components,
297
+ context
298
+ });
268
299
  return {
269
300
  components,
270
301
  pageRegistry,
@@ -122,40 +122,19 @@ async function resolveLocalManifest({ entry, resolvedPaths, context }) {
122
122
  throw new ConfigError(`Module "${entry.id}": each item in "dependencies" must have a string "id".`);
123
123
  }
124
124
  }
125
- // Parse exports object from manifest
126
- const rawExports = manifest.exports ?? {};
127
- const exportSections = [
128
- 'pages',
129
- 'components',
130
- 'menus',
131
- 'connections',
132
- 'api'
133
- ];
134
- const exports = {};
135
- for (const section of exportSections){
136
- const items = rawExports[section] ?? [];
137
- if (!type.isArray(items)) {
138
- throw new ConfigError(`Module "${entry.id}": exports.${section} must be an array.`);
139
- }
140
- for (const item of items){
141
- if (!type.isString(item.id)) {
142
- throw new ConfigError(`Module "${entry.id}": each item in exports.${section} must have a string "id".`);
143
- }
144
- }
145
- exports[section] = items;
146
- }
147
- // Reject unknown keys in exports
148
- for (const key of Object.keys(rawExports)){
149
- if (!exportSections.includes(key)) {
150
- throw new ConfigError(`Module "${entry.id}": unknown exports section "${key}". ` + `Valid sections: ${exportSections.join(', ')}.`);
151
- }
152
- }
153
- // Validate required vars without defaults (needs raw defs + consumer values only).
154
- // Type validation moves to after Phase 2 because defaults are resolved lazily.
125
+ // Capture var definitions for the registered module entry.
126
+ // Required-var validation is deferred to Phase 2.5 (buildModuleDefs.js)
127
+ // because entry.vars may contain unresolved _refs at this point.
128
+ // Type validation runs at the end of Phase 3 against the lazily-populated
129
+ // resolvedVarCache.
155
130
  const varDefs = manifest.vars ?? {};
156
- validateRequiredVars(varDefs, entry.vars ?? {}, entry.id, entry.source);
157
131
  // Validate plugin dependencies against app's declared plugins
158
132
  const requiredPlugins = manifest.plugins ?? [];
133
+ for (const plugin of requiredPlugins){
134
+ if (!type.isString(plugin.version)) {
135
+ throw new ConfigError(`Module "${entry.id}": plugin "${plugin.name}" must declare a "version" ` + `(semver range) in module.lowdefy.yaml.`);
136
+ }
137
+ }
159
138
  const appPlugins = (context.plugins ?? []).reduce((map, p)=>map.set(p.name, p.version), new Map());
160
139
  for (const plugin of requiredPlugins){
161
140
  if (context.defaultPackageNames.has(plugin.name)) {
@@ -184,7 +163,6 @@ async function resolveLocalManifest({ entry, resolvedPaths, context }) {
184
163
  connections: entry.connections ?? {},
185
164
  manifest,
186
165
  dependencies,
187
- exports,
188
166
  moduleDependencies: entry.dependencies ?? {},
189
167
  refDef
190
168
  };
@@ -239,4 +217,4 @@ async function resolveFullManifest({ entryId, context }) {
239
217
  validateVarTypes(varDefs, moduleEntry.resolvedVarCache, entryId, moduleEntry.source);
240
218
  }
241
219
  }
242
- export { resolveLocalManifest, resolveFullManifest };
220
+ export { resolveLocalManifest, resolveFullManifest, validateRequiredVars };
@@ -0,0 +1,26 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { type, serializer } from '@lowdefy/helpers';
16
+ async function writeAgents({ components, context }) {
17
+ if (type.isNone(components.agents)) return;
18
+ if (!type.isArray(components.agents)) {
19
+ throw new Error(`Agents is not an array.`);
20
+ }
21
+ const writePromises = components.agents.map(async (agent)=>{
22
+ await context.writeBuildArtifact(`agents/${agent.agentId}.json`, serializer.serializeToString(agent));
23
+ });
24
+ return Promise.all(writePromises);
25
+ }
26
+ export default writeAgents;
@@ -0,0 +1,19 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { serializer } from '@lowdefy/helpers';
16
+ async function writeAppMeta({ components, context }) {
17
+ await context.writeBuildArtifact('appMeta.json', serializer.serializeToString(components.appMeta));
18
+ }
19
+ export default writeAppMeta;
@@ -0,0 +1,117 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import { ConfigError, ConfigWarning } from '@lowdefy/errors';
16
+ import { mergeObjects, serializer, type } from '@lowdefy/helpers';
17
+ function collectPluginMessages({ messagesMap, declaredCodes }) {
18
+ const collected = {};
19
+ Object.values(messagesMap ?? {}).forEach((pluginCatalog)=>{
20
+ if (!type.isObject(pluginCatalog)) return;
21
+ Object.entries(pluginCatalog).forEach(([code, perLocale])=>{
22
+ if (!declaredCodes.has(code)) return;
23
+ if (!type.isObject(perLocale)) return;
24
+ collected[code] = mergeObjects([
25
+ collected[code] ?? {},
26
+ perLocale
27
+ ]);
28
+ });
29
+ });
30
+ return collected;
31
+ }
32
+ async function writeI18n({ components, context }) {
33
+ const i18n = components.config?.i18n;
34
+ if (type.isNone(i18n)) {
35
+ await context.writeBuildArtifact('i18n.json', serializer.serializeToString({}));
36
+ return;
37
+ }
38
+ if (!type.isObject(i18n)) {
39
+ throw new ConfigError('App "config.i18n" should be an object.', {
40
+ configKey: i18n?.['~k']
41
+ });
42
+ }
43
+ if (type.isNone(i18n.defaultLocale) || !type.isString(i18n.defaultLocale)) {
44
+ throw new ConfigError('App "config.i18n" requires a string "defaultLocale".', {
45
+ configKey: i18n['~k']
46
+ });
47
+ }
48
+ if (!type.isArray(i18n.locales) || i18n.locales.length === 0) {
49
+ throw new ConfigError('App "config.i18n" requires a non-empty "locales" array.', {
50
+ configKey: i18n['~k']
51
+ });
52
+ }
53
+ const codes = new Set();
54
+ i18n.locales.forEach((locale)=>{
55
+ if (!type.isObject(locale) || !type.isString(locale.code)) {
56
+ throw new ConfigError('App "config.i18n.locales[]" requires a string "code".', {
57
+ configKey: locale?.['~k'] ?? i18n['~k']
58
+ });
59
+ }
60
+ if (codes.has(locale.code)) {
61
+ throw new ConfigError(`Duplicate locale code "${locale.code}" in "config.i18n.locales".`, {
62
+ configKey: locale['~k']
63
+ });
64
+ }
65
+ codes.add(locale.code);
66
+ });
67
+ if (!codes.has(i18n.defaultLocale)) {
68
+ throw new ConfigError(`App "config.i18n.defaultLocale" "${i18n.defaultLocale}" must be present in "locales".`, {
69
+ configKey: i18n['~k']
70
+ });
71
+ }
72
+ const messages = i18n.messages ?? {};
73
+ if (!type.isObject(messages)) {
74
+ throw new ConfigError('App "config.i18n.messages" should be an object.', {
75
+ configKey: i18n['~k']
76
+ });
77
+ }
78
+ Object.keys(messages).forEach((code)=>{
79
+ if (!codes.has(code)) {
80
+ context.handleWarning(new ConfigWarning(`App "config.i18n.messages.${code}" references a locale not declared in "locales".`, {
81
+ configKey: i18n['~k']
82
+ }));
83
+ }
84
+ });
85
+ const pluginMessages = collectPluginMessages({
86
+ messagesMap: context.messagesMap,
87
+ declaredCodes: codes
88
+ });
89
+ const mergedMessages = {};
90
+ codes.forEach((code)=>{
91
+ const userPerLocale = messages[code];
92
+ const pluginPerLocale = pluginMessages[code];
93
+ const combined = mergeObjects([
94
+ pluginPerLocale ?? {},
95
+ userPerLocale ?? {}
96
+ ]);
97
+ if (Object.keys(combined).length === 0) {
98
+ context.handleWarning(new ConfigWarning(`App "config.i18n" has no messages for locale "${code}". Falls back to "en-US".`, {
99
+ configKey: i18n['~k']
100
+ }));
101
+ return;
102
+ }
103
+ mergedMessages[code] = combined;
104
+ });
105
+ const artifact = {
106
+ defaultLocale: i18n.defaultLocale,
107
+ locales: i18n.locales.map((locale)=>({
108
+ code: locale.code,
109
+ label: locale.label,
110
+ antd: locale.antd,
111
+ dayjs: locale.dayjs
112
+ })),
113
+ messages: mergedMessages
114
+ };
115
+ await context.writeBuildArtifact('i18n.json', serializer.serializeToString(artifact));
116
+ }
117
+ export default writeI18n;
@@ -0,0 +1,22 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import generateImportFile from './generateImportFile.js';
16
+ async function writeAgentImports({ components, context }) {
17
+ await context.writeBuildArtifact('plugins/agents.js', generateImportFile({
18
+ imports: components.imports.agents,
19
+ importPath: 'agents'
20
+ }));
21
+ }
22
+ export default writeAgentImports;
@@ -14,6 +14,7 @@
14
14
  limitations under the License.
15
15
  */ import writeActionImports from './writeActionImports.js';
16
16
  import writeActionSchemaMap from './writeActionSchemaMap.js';
17
+ import writeAgentImports from './writeAgentImports.js';
17
18
  import writeAuthImports from './writeAuthImports.js';
18
19
  import writeBlockImports from './writeBlockImports.js';
19
20
  import writeBlockSchemaMap from './writeBlockSchemaMap.js';
@@ -22,6 +23,7 @@ import writeIconImports from './writeIconImports.js';
22
23
  import writeOperatorImports from './writeOperatorImports.js';
23
24
  import writeOperatorSchemaMap from './writeOperatorSchemaMap.js';
24
25
  import writeGlobalsCss from './writeGlobalsCss.js';
26
+ import writeServerExternalPackages from './writeServerExternalPackages.js';
25
27
  async function writePluginImports({ components, context }) {
26
28
  await writeActionImports({
27
29
  components,
@@ -31,6 +33,10 @@ async function writePluginImports({ components, context }) {
31
33
  components,
32
34
  context
33
35
  });
36
+ await writeAgentImports({
37
+ components,
38
+ context
39
+ });
34
40
  await writeAuthImports({
35
41
  components,
36
42
  context
@@ -63,6 +69,10 @@ async function writePluginImports({ components, context }) {
63
69
  components,
64
70
  context
65
71
  });
72
+ await writeServerExternalPackages({
73
+ components,
74
+ context
75
+ });
66
76
  // Write block package names for Next.js transpilePackages (CSS imports).
67
77
  const blockPackages = [
68
78
  ...new Set((components.imports.blocks ?? []).map((b)=>b.package))