@lowdefy/build 5.1.0 → 5.2.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 (53) hide show
  1. package/dist/build/buildApi/buildRoutine/countStepTypes.js +3 -0
  2. package/dist/build/buildApi/buildRoutine/setStepId.js +3 -2
  3. package/dist/build/buildApi/buildRoutine/validateStep.js +19 -0
  4. package/dist/build/buildApi/validateEndpoint.js +10 -0
  5. package/dist/build/buildApi/validateStepReferences.js +4 -4
  6. package/dist/build/buildAuth/buildApiAuth.js +2 -1
  7. package/dist/build/buildAuth/buildPageAuth.js +2 -1
  8. package/dist/build/buildAuth/getApiRoles.js +12 -6
  9. package/dist/build/buildAuth/getPageRoles.js +12 -6
  10. package/dist/build/buildAuth/getProtectedApi.js +3 -2
  11. package/dist/build/buildAuth/getProtectedPages.js +3 -2
  12. package/dist/build/buildAuth/matchPattern.js +22 -0
  13. package/dist/build/buildConnections.js +42 -4
  14. package/dist/build/buildJs/jsMapParser.js +25 -12
  15. package/dist/build/buildJs/writeJs.js +2 -2
  16. package/dist/build/buildMenu.js +41 -0
  17. package/dist/build/buildModuleDefs.js +97 -0
  18. package/dist/build/buildModules.js +96 -0
  19. package/dist/build/buildPages/buildBlock/buildBlock.js +2 -2
  20. package/dist/build/buildPages/buildBlock/buildEvents.js +16 -1
  21. package/dist/build/buildPages/buildBlock/buildSubBlocks.js +2 -1
  22. package/dist/build/buildPages/buildBlock/validateBlock.js +3 -3
  23. package/dist/build/buildPages/buildPage.js +1 -0
  24. package/dist/build/buildPages/validateCallApiRefs.js +31 -0
  25. package/dist/build/buildRefs/getModuleRefContent.js +81 -0
  26. package/dist/build/buildRefs/makeRefDefinition.js +6 -0
  27. package/dist/build/buildRefs/walker.js +424 -44
  28. package/dist/build/fetchGitHubModule.js +94 -0
  29. package/dist/build/fetchModules.js +60 -0
  30. package/dist/build/full/buildPages.js +10 -1
  31. package/dist/build/full/writePages.js +1 -1
  32. package/dist/build/jit/buildPageJit.js +34 -4
  33. package/dist/build/jit/collectSkeletonSourceFiles.js +8 -0
  34. package/dist/build/jit/createPageRegistry.js +10 -1
  35. package/dist/build/jit/shallowBuild.js +22 -11
  36. package/dist/build/jit/writePageJit.js +2 -2
  37. package/dist/build/jit/writeSourcelessPages.js +1 -1
  38. package/dist/build/parseModuleSource.js +48 -0
  39. package/dist/build/registerModules.js +242 -0
  40. package/dist/build/resolveDepTarget.js +43 -0
  41. package/dist/build/resolveModuleDependencies.js +60 -0
  42. package/dist/build/resolveModuleOperators.js +27 -0
  43. package/dist/build/testSchema.js +22 -11
  44. package/dist/build/writePluginImports/writeGlobalsCss.js +1 -1
  45. package/dist/createContext.js +4 -0
  46. package/dist/defaultPackages.js +51 -0
  47. package/dist/defaultTypesMap.js +399 -357
  48. package/dist/index.js +16 -1
  49. package/dist/indexDev.js +3 -1
  50. package/dist/lowdefySchema.js +58 -0
  51. package/dist/scripts/generateDefaultTypes.js +1 -35
  52. package/package.json +46 -42
  53. package/dist/build/jit/stripPageContent.js +0 -29
@@ -0,0 +1,242 @@
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 semver from 'semver';
17
+ import { type } from '@lowdefy/helpers';
18
+ import { ConfigError } from '@lowdefy/errors';
19
+ import operators from '@lowdefy/operators-js/operators/build';
20
+ import { resolve, WalkContext } from './buildRefs/walker.js';
21
+ import getRefContent from './buildRefs/getRefContent.js';
22
+ import makeRefDefinition from './buildRefs/makeRefDefinition.js';
23
+ import evaluateStaticOperators from './buildRefs/evaluateStaticOperators.js';
24
+ import collectDynamicIdentifiers from './collectDynamicIdentifiers.js';
25
+ import validateOperatorsDynamic from './validateOperatorsDynamic.js';
26
+ validateOperatorsDynamic({
27
+ operators
28
+ });
29
+ const dynamicIdentifiers = collectDynamicIdentifiers({
30
+ operators
31
+ });
32
+ function validateRequiredVars(varDefs, consumerVars, entryId, source, prefix = '') {
33
+ for (const [varName, varDef] of Object.entries(varDefs)){
34
+ const fullName = prefix ? `${prefix}.${varName}` : varName;
35
+ if (varDef.properties) {
36
+ if (!type.isNone(consumerVars[varName]) && !type.isObject(consumerVars[varName])) {
37
+ throw new ConfigError(`Module "${entryId}" (${source}) var "${fullName}" must be type "object" ` + `(has properties) but got "${type.typeOf(consumerVars[varName])}".`);
38
+ }
39
+ const consumerObj = type.isObject(consumerVars[varName]) ? consumerVars[varName] : {};
40
+ for (const key of Object.keys(consumerObj)){
41
+ if (!varDef.properties[key]) {
42
+ throw new ConfigError(`Module "${entryId}" (${source}) var "${fullName}" has undeclared ` + `property "${key}". Declared properties: ${Object.keys(varDef.properties).join(', ')}.`);
43
+ }
44
+ }
45
+ validateRequiredVars(varDef.properties, consumerObj, entryId, source, fullName);
46
+ } else if (varDef.required && type.isUndefined(varDef.default) && type.isNone(consumerVars[varName])) {
47
+ throw new ConfigError(`Module "${entryId}" (${source}) requires var "${fullName}"` + (varDef.description ? `\n - ${varDef.description}` : '') + `\n - Define it in lowdefy.yaml under modules[id=${entryId}].vars.${fullName}`);
48
+ }
49
+ }
50
+ }
51
+ function validateVarTypes(varDefs, resolvedVarCache, entryId, source, prefix = '') {
52
+ for (const [varName, varDef] of Object.entries(varDefs)){
53
+ const fullName = prefix ? `${prefix}.${varName}` : varName;
54
+ const value = resolvedVarCache[fullName];
55
+ if (varDef.type && !type.isNone(value)) {
56
+ if (type.typeOf(value) !== varDef.type) {
57
+ throw new ConfigError(`Module "${entryId}" (${source}) var "${fullName}" must be type ` + `"${varDef.type}" but got "${type.typeOf(value)}".` + (varDef.description ? `\n - ${varDef.description}` : ''));
58
+ }
59
+ }
60
+ if (varDef.properties) {
61
+ validateVarTypes(varDef.properties, resolvedVarCache, entryId, source, fullName);
62
+ }
63
+ }
64
+ }
65
+ async function resolveLocalManifest({ entry, resolvedPaths, context }) {
66
+ if (!entry.id || !type.isString(entry.id)) {
67
+ throw new ConfigError("Module entry 'id' is required and must be a string.");
68
+ }
69
+ if (entry.id.includes('/')) {
70
+ throw new ConfigError(`Module entry id "${entry.id}" must not contain '/'. ` + `Use a flat identifier like "team-users".`);
71
+ }
72
+ if (entry.id === '__proto__' || entry.id === 'constructor' || entry.id === 'prototype') {
73
+ throw new ConfigError(`Module entry id "${entry.id}" is a reserved name.`);
74
+ }
75
+ if (!entry.source || !type.isString(entry.source)) {
76
+ throw new ConfigError(`Module entry "${entry.id}": 'source' is required and must be a string.`);
77
+ }
78
+ if (Object.hasOwn(context.modules, entry.id)) {
79
+ throw new ConfigError(`Duplicate module entry id "${entry.id}".`);
80
+ }
81
+ const { packageRoot, moduleRoot, isLocal } = resolvedPaths;
82
+ const moduleYamlPath = path.join(moduleRoot, 'module.lowdefy.yaml');
83
+ // Use makeRefDefinition + getRefContent to read and parse module.lowdefy.yaml.
84
+ // The absolute path works because path.resolve(configDir, absolutePath) = absolutePath.
85
+ const refDef = makeRefDefinition(moduleYamlPath, null, context.refMap);
86
+ const content = await getRefContent({
87
+ context,
88
+ refDef,
89
+ referencedFrom: null
90
+ });
91
+ // Run walker with shouldStop preserving content that may contain cross-module refs
92
+ const ctx = new WalkContext({
93
+ buildContext: context,
94
+ refId: refDef.id,
95
+ sourceRefId: null,
96
+ vars: {},
97
+ moduleRoot,
98
+ packageRoot,
99
+ path: '',
100
+ currentFile: moduleYamlPath,
101
+ refChain: new Set(refDef.path ? [
102
+ refDef.path
103
+ ] : []),
104
+ operators,
105
+ env: process.env,
106
+ dynamicIdentifiers,
107
+ shouldStop: (childPath)=>{
108
+ if (/^vars(\.[^.]+\.properties)*\.[^.]+\.default(\..*)?$/.test(childPath)) return 'preserve';
109
+ if (/^components\.\d+\.component$/.test(childPath)) return 'preserve';
110
+ if (/^pages(\..*)?$/.test(childPath)) return 'preserve';
111
+ if (/^api(\..*)?$/.test(childPath)) return 'preserve';
112
+ if (/^connections(\..*)?$/.test(childPath)) return 'preserve';
113
+ if (/^menus\.\d+\.links$/.test(childPath)) return 'preserve';
114
+ return false;
115
+ }
116
+ });
117
+ const manifest = await resolve(content, ctx);
118
+ // Parse dependencies array from manifest
119
+ const dependencies = manifest.dependencies ?? [];
120
+ for (const dep of dependencies){
121
+ if (!type.isString(dep.id)) {
122
+ throw new ConfigError(`Module "${entry.id}": each item in "dependencies" must have a string "id".`);
123
+ }
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.
155
+ const varDefs = manifest.vars ?? {};
156
+ validateRequiredVars(varDefs, entry.vars ?? {}, entry.id, entry.source);
157
+ // Validate plugin dependencies against app's declared plugins
158
+ const requiredPlugins = manifest.plugins ?? [];
159
+ const appPlugins = (context.plugins ?? []).reduce((map, p)=>map.set(p.name, p.version), new Map());
160
+ for (const plugin of requiredPlugins){
161
+ if (context.defaultPackageNames.has(plugin.name)) {
162
+ continue;
163
+ }
164
+ const appVersion = appPlugins.get(plugin.name);
165
+ if (!appVersion) {
166
+ throw new ConfigError(`Module "${entry.id}" requires plugin "${plugin.name}" version "${plugin.version}".\n` + `Add it to your app's plugins array in lowdefy.yaml:\n\n` + ` plugins:\n` + ` - name: "${plugin.name}"\n` + ` version: "${semver.minVersion(plugin.version)}"`);
167
+ }
168
+ if (appVersion.startsWith('workspace:')) {
169
+ continue;
170
+ }
171
+ if (!semver.satisfies(appVersion, plugin.version)) {
172
+ throw new ConfigError(`Module "${entry.id}" requires plugin "${plugin.name}" version "${plugin.version}" ` + `but the app has version "${appVersion}" installed. ` + `Update the plugin to a compatible version.`);
173
+ }
174
+ }
175
+ context.modules[entry.id] = {
176
+ id: entry.id,
177
+ source: entry.source,
178
+ packageRoot,
179
+ moduleRoot,
180
+ isLocal,
181
+ consumerVars: entry.vars ?? {},
182
+ varDefs,
183
+ resolvedVarCache: {},
184
+ connections: entry.connections ?? {},
185
+ manifest,
186
+ dependencies,
187
+ exports,
188
+ moduleDependencies: entry.dependencies ?? {},
189
+ refDef
190
+ };
191
+ }
192
+ async function resolveFullManifest({ entryId, context }) {
193
+ const moduleEntry = context.modules[entryId];
194
+ const { manifest, packageRoot, moduleRoot, moduleDependencies, refDef } = moduleEntry;
195
+ const moduleYamlPath = path.join(moduleRoot, 'module.lowdefy.yaml');
196
+ const ctx = new WalkContext({
197
+ buildContext: context,
198
+ refId: refDef.id,
199
+ sourceRefId: null,
200
+ vars: {},
201
+ moduleDependencies,
202
+ moduleEntry,
203
+ moduleRoot,
204
+ packageRoot,
205
+ path: '',
206
+ currentFile: moduleYamlPath,
207
+ refChain: new Set(refDef.path ? [
208
+ refDef.path
209
+ ] : []),
210
+ operators,
211
+ env: process.env,
212
+ dynamicIdentifiers,
213
+ shouldStop: (childPath)=>{
214
+ if (/^vars(\.[^.]+\.properties)*\.[^.]+\.default(\..*)?$/.test(childPath)) return 'preserve';
215
+ if (/^components\.\d+\.component$/.test(childPath)) return 'preserve';
216
+ return false;
217
+ }
218
+ });
219
+ let resolved = await resolve(manifest, ctx);
220
+ resolved = evaluateStaticOperators({
221
+ context,
222
+ input: resolved,
223
+ refDef
224
+ });
225
+ // Filter null entries produced by _ref resolution failures
226
+ for (const key of [
227
+ 'pages',
228
+ 'connections',
229
+ 'api'
230
+ ]){
231
+ if (type.isArray(resolved[key])) {
232
+ resolved[key] = resolved[key].filter((item)=>!type.isNone(item));
233
+ }
234
+ }
235
+ moduleEntry.manifest = resolved;
236
+ // Validate var types against lazily-resolved values
237
+ const varDefs = moduleEntry.varDefs;
238
+ if (Object.keys(varDefs).length > 0) {
239
+ validateVarTypes(varDefs, moduleEntry.resolvedVarCache, entryId, moduleEntry.source);
240
+ }
241
+ }
242
+ export { resolveLocalManifest, resolveFullManifest };
@@ -0,0 +1,43 @@
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 } from '@lowdefy/errors';
16
+ function resolveDepTarget({ moduleEntry, depName, context, configKey, usage }) {
17
+ const prefix = usage ? `${usage} ` : '';
18
+ // App-level: module name IS the entry ID (no dependency mapping)
19
+ if (!moduleEntry) {
20
+ const targetEntry = context.modules[depName];
21
+ if (!targetEntry) {
22
+ throw new ConfigError(`${prefix}references module "${depName}" but no module with that entry id was registered.`, {
23
+ configKey
24
+ });
25
+ }
26
+ return targetEntry;
27
+ }
28
+ const wiring = moduleEntry.moduleDependencies ?? {};
29
+ const targetEntryId = wiring[depName];
30
+ if (!targetEntryId) {
31
+ throw new ConfigError(`${prefix}in module "${moduleEntry.id}" references dependency "${depName}" but no mapping exists. ` + `Add dependencies.${depName} to module "${moduleEntry.id}".`, {
32
+ configKey
33
+ });
34
+ }
35
+ const targetEntry = context.modules[targetEntryId];
36
+ if (!targetEntry) {
37
+ throw new ConfigError(`${prefix}in module "${moduleEntry.id}" references dependency "${depName}" which maps to "${targetEntryId}", ` + `but no module with entry id "${targetEntryId}" was registered.`, {
38
+ configKey
39
+ });
40
+ }
41
+ return targetEntry;
42
+ }
43
+ export default resolveDepTarget;
@@ -0,0 +1,60 @@
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 } from '@lowdefy/errors';
16
+ import { type } from '@lowdefy/helpers';
17
+ function resolveModuleDependencies({ context }) {
18
+ for (const [entryId, moduleEntry] of Object.entries(context.modules)){
19
+ const dependencies = moduleEntry.dependencies;
20
+ const wiring = moduleEntry.moduleDependencies;
21
+ // Auto-wire: for each declared dependency without an explicit mapping,
22
+ // look for a module entry whose ID matches the dependency name
23
+ for (const dep of dependencies){
24
+ if (type.isUndefined(wiring[dep.id])) {
25
+ if (context.modules[dep.id]) {
26
+ wiring[dep.id] = dep.id;
27
+ }
28
+ }
29
+ }
30
+ // Validate the wiring
31
+ const declaredIds = new Set(dependencies.map((r)=>r.id));
32
+ // 1. Every declared dependency must be mapped (after auto-wire)
33
+ for (const dep of dependencies){
34
+ if (type.isUndefined(wiring[dep.id])) {
35
+ throw new ConfigError(`Module "${entryId}" declares dependency "${dep.id}" but no mapping provided ` + `and no module entry "${dep.id}" exists.` + (dep.description ? `\n ${dep.id}: ${dep.description}` : '') + `\n Add dependencies.${dep.id} to the "${entryId}" entry in lowdefy.yaml, ` + `or add a module entry with id "${dep.id}".`);
36
+ }
37
+ }
38
+ // 2. No unknown keys in the wiring map
39
+ for (const key of Object.keys(wiring)){
40
+ if (!declaredIds.has(key)) {
41
+ throw new ConfigError(`Module "${entryId}" does not declare dependency "${key}". ` + `Check dependencies in lowdefy.yaml.` + `\n Declared dependencies: ${[
42
+ ...declaredIds
43
+ ].join(', ') || '(none)'}`);
44
+ }
45
+ }
46
+ // 3. All target entry IDs exist
47
+ for (const [depName, targetEntryId] of Object.entries(wiring)){
48
+ if (!context.modules[targetEntryId]) {
49
+ throw new ConfigError(`dependencies.${depName} references "${targetEntryId}" ` + `but no module entry "${targetEntryId}" exists.`);
50
+ }
51
+ }
52
+ // 4. No self-referencing dependencies
53
+ for (const [depName, targetEntryId] of Object.entries(wiring)){
54
+ if (targetEntryId === entryId) {
55
+ throw new ConfigError(`Module "${entryId}" dependency "${depName}" maps to itself. ` + `A module cannot depend on its own entry.`);
56
+ }
57
+ }
58
+ }
59
+ }
60
+ export default resolveModuleDependencies;
@@ -0,0 +1,27 @@
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
+ */ function scopeMenuItemIds(links, entryId) {
16
+ if (!Array.isArray(links)) return;
17
+ for (const item of links){
18
+ if (!item) continue;
19
+ if (item.id) {
20
+ item.id = `${entryId}/${item.id}`;
21
+ }
22
+ if (Array.isArray(item.links)) {
23
+ scopeMenuItemIds(item.links, entryId);
24
+ }
25
+ }
26
+ }
27
+ export { scopeMenuItemIds };
@@ -13,9 +13,23 @@
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */ import { validate } from '@lowdefy/ajv';
16
- import { ConfigError, shouldSuppressBuildCheck } from '@lowdefy/errors';
16
+ import { type } from '@lowdefy/helpers';
17
+ import { ConfigWarning, shouldSuppressBuildCheck } from '@lowdefy/errors';
17
18
  import findConfigKey from '../utils/findConfigKey.js';
18
19
  import lowdefySchema from '../lowdefySchema.js';
20
+ function getValueAtPath(obj, pathParts) {
21
+ let current = obj;
22
+ for (const part of pathParts){
23
+ if (type.isNone(current)) return undefined;
24
+ current = type.isArray(current) ? current[parseInt(part, 10)] : current[part];
25
+ }
26
+ return current;
27
+ }
28
+ // Schema validation emits warnings rather than errors. Focused validations
29
+ // in each build step (validateBlock, buildConnections, buildEvents, etc.)
30
+ // provide better error messages with full context (pageId, blockId, etc.).
31
+ // Schema warnings still surface useful hints like typos caught by
32
+ // additionalProperties and property type mismatches.
19
33
  function testSchema({ components, context }) {
20
34
  const { valid, errors } = validate({
21
35
  schema: lowdefySchema,
@@ -52,20 +66,17 @@ function testSchema({ components, context }) {
52
66
  let message = error.message;
53
67
  if (error.params?.additionalProperty) {
54
68
  message = `${message} - "${error.params.additionalProperty}"`;
55
- } else if (propertyName) {
69
+ } else if (propertyName && error.keyword !== 'errorMessage') {
56
70
  message = `"${propertyName}" ${message}`;
57
71
  }
58
- const configError = new ConfigError(message, {
72
+ const received = getValueAtPath(components, instancePath);
73
+ const warning = new ConfigWarning(message, {
59
74
  configKey,
60
- checkSlug: 'schema'
75
+ checkSlug: 'schema',
76
+ received
61
77
  });
62
- if (!shouldSuppressBuildCheck(configError, context.keyMap)) {
63
- if (!context.errors) {
64
- // If no error collection array, throw immediately (fallback for tests)
65
- throw configError;
66
- }
67
- // Collect error object - logging happens at checkpoints in index.js
68
- context.errors.push(configError);
78
+ if (!shouldSuppressBuildCheck(warning, context.keyMap)) {
79
+ context.handleWarning(warning);
69
80
  }
70
81
  });
71
82
  }
@@ -139,7 +139,7 @@ ${themeVars}
139
139
  await context.writeBuildArtifact('layer-order.css', '/* Generated by Lowdefy build */\n@layer theme, base, antd, components, utilities;\n');
140
140
  await context.writeBuildArtifact('tailwind-candidates.css', '/* Generated by Lowdefy build — rewritten on page changes to trigger CSS recompilation */\n');
141
141
  for (const [pageId, content] of context.tailwindContentMap ?? []){
142
- await writeFile(path.join(context.directories.server, 'lowdefy-build', 'tailwind', `${pageId}.html`), '<!-- Generated by Lowdefy build -->\n' + content);
142
+ await writeFile(path.join(context.directories.server, 'lowdefy-build', 'tailwind', `${encodeURIComponent(pageId)}.html`), '<!-- Generated by Lowdefy build -->\n' + content);
143
143
  }
144
144
  // Collect Tailwind class candidates from block plugin JS source files.
145
145
  // Resolves from the server directory so block packages are reachable regardless
@@ -18,9 +18,11 @@ 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 defaultPackages from './defaultPackages.js';
21
22
  import defaultTypesMap from './defaultTypesMap.js';
22
23
  function createContext({ customTypesMap, directories, logger, refResolver, stage = 'prod' }) {
23
24
  const context = {
25
+ defaultPackageNames: new Set(defaultPackages),
24
26
  connectionIds: new Set(),
25
27
  directories,
26
28
  errors: [],
@@ -28,6 +30,8 @@ function createContext({ customTypesMap, directories, logger, refResolver, stage
28
30
  warnings: [],
29
31
  keyMap: {},
30
32
  logger,
33
+ // Null prototype prevents pollution via attacker-controlled entry.id.
34
+ modules: Object.create(null),
31
35
  readConfigFile: createReadConfigFile({
32
36
  directories
33
37
  }),
@@ -0,0 +1,51 @@
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
+ */ const defaultPackages = [
16
+ '@lowdefy/actions-core',
17
+ '@lowdefy/actions-pdf-make',
18
+ '@lowdefy/blocks-aggrid',
19
+ '@lowdefy/blocks-antd',
20
+ '@lowdefy/blocks-basic',
21
+ '@lowdefy/blocks-diff',
22
+ '@lowdefy/blocks-echarts',
23
+ '@lowdefy/blocks-google-maps',
24
+ '@lowdefy/blocks-loaders',
25
+ '@lowdefy/blocks-markdown',
26
+ '@lowdefy/blocks-qr',
27
+ '@lowdefy/blocks-tiptap',
28
+ '@lowdefy/connection-axios-http',
29
+ '@lowdefy/connection-elasticsearch',
30
+ '@lowdefy/connection-test',
31
+ '@lowdefy/connection-google-sheets',
32
+ '@lowdefy/connection-knex',
33
+ '@lowdefy/connection-mongodb',
34
+ '@lowdefy/connection-redis',
35
+ '@lowdefy/connection-sendgrid',
36
+ '@lowdefy/connection-stripe',
37
+ '@lowdefy/operators-change-case',
38
+ '@lowdefy/operators-diff',
39
+ '@lowdefy/operators-js',
40
+ '@lowdefy/operators-jsonata',
41
+ '@lowdefy/operators-dayjs',
42
+ '@lowdefy/operators-mql',
43
+ '@lowdefy/operators-nunjucks',
44
+ '@lowdefy/operators-uuid',
45
+ '@lowdefy/operators-yaml',
46
+ '@lowdefy/plugin-auth0',
47
+ '@lowdefy/plugin-aws',
48
+ '@lowdefy/plugin-csv',
49
+ '@lowdefy/plugin-next-auth'
50
+ ];
51
+ export default defaultPackages;