@lowdefy/build 5.3.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.
@@ -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;
@@ -23,6 +23,7 @@ import writeIconImports from './writeIconImports.js';
23
23
  import writeOperatorImports from './writeOperatorImports.js';
24
24
  import writeOperatorSchemaMap from './writeOperatorSchemaMap.js';
25
25
  import writeGlobalsCss from './writeGlobalsCss.js';
26
+ import writeServerExternalPackages from './writeServerExternalPackages.js';
26
27
  async function writePluginImports({ components, context }) {
27
28
  await writeActionImports({
28
29
  components,
@@ -68,6 +69,10 @@ async function writePluginImports({ components, context }) {
68
69
  components,
69
70
  context
70
71
  });
72
+ await writeServerExternalPackages({
73
+ components,
74
+ context
75
+ });
71
76
  // Write block package names for Next.js transpilePackages (CSS imports).
72
77
  const blockPackages = [
73
78
  ...new Set((components.imports.blocks ?? []).map((b)=>b.package))
@@ -0,0 +1,121 @@
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 fs from 'fs';
16
+ import path from 'path';
17
+ import { createRequire } from 'node:module';
18
+ import { ConfigError } from '@lowdefy/errors';
19
+ // Collect every unique plugin package across all import categories.
20
+ // Mirrors the dependency walk in updateServerPackageJson.js so externalisation
21
+ // covers connections/operators/actions etc., not just blocks.
22
+ function collectPluginPackages(imports) {
23
+ const categories = [
24
+ imports.actions,
25
+ imports.agents,
26
+ imports.auth?.adapters,
27
+ imports.auth?.callbacks,
28
+ imports.auth?.events,
29
+ imports.auth?.providers,
30
+ imports.blocks,
31
+ imports.connections,
32
+ imports.icons,
33
+ imports.requests,
34
+ imports.operators?.client,
35
+ imports.operators?.server
36
+ ];
37
+ const packages = new Set();
38
+ for (const list of categories){
39
+ if (!Array.isArray(list)) continue;
40
+ for (const item of list){
41
+ if (item?.package) packages.add(item.package);
42
+ }
43
+ }
44
+ return packages;
45
+ }
46
+ // Direct resolve of `${pkg}/package.json` fails for plugins whose exports map
47
+ // uses a catch-all like `"./*": "./dist/*"` (e.g. blocks-tiptap, plugin-aws),
48
+ // because it routes the request into `./dist/package.json`. Fall back to
49
+ // resolving a known subpath and walking up to the real package root.
50
+ function findPluginPackageJson({ requireFromServer, pkg }) {
51
+ try {
52
+ const direct = requireFromServer.resolve(`${pkg}/package.json`);
53
+ const json = JSON.parse(fs.readFileSync(direct, 'utf8'));
54
+ if (json.name === pkg) return json;
55
+ } catch {
56
+ // Fall through to walk-up resolution.
57
+ }
58
+ const subpaths = [
59
+ 'types',
60
+ 'metas',
61
+ 'blocks',
62
+ 'connections'
63
+ ];
64
+ for (const sub of subpaths){
65
+ let entry;
66
+ try {
67
+ entry = requireFromServer.resolve(`${pkg}/${sub}`);
68
+ } catch {
69
+ continue;
70
+ }
71
+ let dir = path.dirname(entry);
72
+ const root = path.parse(dir).root;
73
+ while(dir !== root){
74
+ const candidate = path.join(dir, 'package.json');
75
+ if (fs.existsSync(candidate)) {
76
+ try {
77
+ const json = JSON.parse(fs.readFileSync(candidate, 'utf8'));
78
+ if (json.name === pkg) return json;
79
+ } catch {
80
+ // Malformed package.json — try the next ancestor.
81
+ }
82
+ }
83
+ dir = path.dirname(dir);
84
+ }
85
+ }
86
+ return null;
87
+ }
88
+ async function writeServerExternalPackages({ components, context }) {
89
+ const requireFromServer = createRequire(path.join(context.directories.server, 'package.json'));
90
+ const pluginPackages = collectPluginPackages(components.imports ?? {});
91
+ // Track which plugin contributed each external so error messages can name them.
92
+ const externalToPlugins = new Map();
93
+ for (const pkg of pluginPackages){
94
+ const pluginPkgJson = findPluginPackageJson({
95
+ requireFromServer,
96
+ pkg
97
+ });
98
+ const list = pluginPkgJson?.lowdefy?.serverExternalPackages;
99
+ if (!Array.isArray(list)) continue;
100
+ for (const external of list){
101
+ if (!externalToPlugins.has(external)) {
102
+ externalToPlugins.set(external, []);
103
+ }
104
+ externalToPlugins.get(external).push(pkg);
105
+ }
106
+ }
107
+ // Next.js rejects entries that are also in transpilePackages. Block packages
108
+ // are always transpiled, so a plugin externalising one would silently break
109
+ // the build at next start — fail early with a pointer to the offender.
110
+ const transpiledBlockPackages = new Set((components.imports?.blocks ?? []).map((b)=>b.package));
111
+ for (const [external, plugins] of externalToPlugins){
112
+ if (transpiledBlockPackages.has(external)) {
113
+ throw new ConfigError(`Plugin "${plugins.join('", "')}" declared serverExternalPackages entry "${external}", which is also a transpiled block package. A package cannot be both transpiled and externalised.`);
114
+ }
115
+ }
116
+ const sorted = [
117
+ ...externalToPlugins.keys()
118
+ ].sort();
119
+ await context.writeBuildArtifact('serverExternalPackages.json', JSON.stringify(sorted));
120
+ }
121
+ export default writeServerExternalPackages;
@@ -18,9 +18,10 @@ import createCounter from './utils/createCounter.js';
18
18
  import createHandleWarning from './utils/createHandleWarning.js';
19
19
  import createReadConfigFile from './utils/readConfigFile.js';
20
20
  import createWriteBuildArtifact from './utils/writeBuildArtifact.js';
21
+ import defaultMessagesMap from './defaultMessagesMap.js';
21
22
  import defaultPackages from './defaultPackages.js';
22
23
  import defaultTypesMap from './defaultTypesMap.js';
23
- function createContext({ customTypesMap, directories, logger, refResolver, stage = 'prod' }) {
24
+ function createContext({ customMessagesMap, customTypesMap, directories, logger, refResolver, stage = 'prod' }) {
24
25
  const context = {
25
26
  defaultPackageNames: new Set(defaultPackages),
26
27
  agentIds: new Set(),
@@ -63,6 +64,10 @@ function createContext({ customTypesMap, directories, logger, refResolver, stage
63
64
  defaultTypesMap,
64
65
  customTypesMap
65
66
  ]),
67
+ messagesMap: mergeObjects([
68
+ defaultMessagesMap,
69
+ customMessagesMap
70
+ ]),
66
71
  writeBuildArtifact: createWriteBuildArtifact({
67
72
  directories
68
73
  })
@@ -0,0 +1,3 @@
1
+ const defaultMessagesMap = {};
2
+
3
+ export default defaultMessagesMap;