@lowdefy/build 4.6.0 → 4.7.1
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/dist/build/buildImports/buildIconImports.js +1 -31
- package/dist/build/buildImports/iconPackages.js +49 -0
- package/dist/build/buildRefs/buildRefs.js +35 -19
- package/dist/build/buildRefs/evaluateStaticOperators.js +5 -9
- package/dist/build/buildRefs/makeRefDefinition.js +4 -2
- package/dist/build/buildRefs/parseRefContent.js +8 -1
- package/dist/build/buildRefs/walker.js +348 -0
- package/dist/build/jit/buildPageJit.js +77 -30
- package/dist/build/jit/createPageRegistry.js +6 -1
- package/dist/build/jit/detectMissingIcons.js +36 -0
- package/dist/build/jit/extractIconData.js +54 -0
- package/dist/build/jit/shallowBuild.js +5 -4
- package/dist/build/jit/updateIconImportsJit.js +45 -0
- package/dist/build/jit/{stripPageContent.js → writeIconsDynamic.js} +5 -8
- package/dist/defaultTypesMap.js +347 -338
- package/dist/index.js +8 -6
- package/dist/indexDev.js +1 -0
- package/dist/lowdefySchema.js +0 -1
- package/dist/utils/makeId.js +3 -0
- package/package.json +42 -42
- package/dist/build/buildRefs/createRefReviver.js +0 -28
- package/dist/build/buildRefs/evaluateBuildOperators.js +0 -53
- package/dist/build/buildRefs/getRefsFromFile.js +0 -42
- package/dist/build/buildRefs/populateRefs.js +0 -105
- package/dist/build/buildRefs/recursiveBuild.js +0 -133
- package/dist/build/jit/getRefPositions.js +0 -38
|
@@ -16,24 +16,49 @@
|
|
|
16
16
|
import path from 'path';
|
|
17
17
|
import { serializer, type } from '@lowdefy/helpers';
|
|
18
18
|
import { ConfigError, LowdefyInternalError } from '@lowdefy/errors';
|
|
19
|
+
import operators from '@lowdefy/operators-js/operators/build';
|
|
19
20
|
import addKeys from '../addKeys.js';
|
|
20
21
|
import buildPage from '../buildPages/buildPage.js';
|
|
21
22
|
import validateLinkReferences from '../buildPages/validateLinkReferences.js';
|
|
22
23
|
import validatePayloadReferences from '../buildPages/validatePayloadReferences.js';
|
|
23
24
|
import validateServerStateReferences from '../buildPages/validateServerStateReferences.js';
|
|
24
25
|
import validateStateReferences from '../buildPages/validateStateReferences.js';
|
|
26
|
+
import collectDynamicIdentifiers from '../collectDynamicIdentifiers.js';
|
|
25
27
|
import createCheckDuplicateId from '../../utils/createCheckDuplicateId.js';
|
|
26
28
|
import createContext from '../../createContext.js';
|
|
27
|
-
import createRefReviver from '../buildRefs/createRefReviver.js';
|
|
28
|
-
import evaluateBuildOperators from '../buildRefs/evaluateBuildOperators.js';
|
|
29
29
|
import evaluateStaticOperators from '../buildRefs/evaluateStaticOperators.js';
|
|
30
|
+
import getRefContent from '../buildRefs/getRefContent.js';
|
|
30
31
|
import jsMapParser from '../buildJs/jsMapParser.js';
|
|
31
32
|
import makeRefDefinition from '../buildRefs/makeRefDefinition.js';
|
|
32
|
-
import
|
|
33
|
+
import { resolve, WalkContext, cloneForResolve, tagRefDeep } from '../buildRefs/walker.js';
|
|
34
|
+
import validateOperatorsDynamic from '../validateOperatorsDynamic.js';
|
|
35
|
+
import writeMaps from '../writeMaps.js';
|
|
36
|
+
import detectMissingIcons from './detectMissingIcons.js';
|
|
33
37
|
import detectMissingPluginPackages from './detectMissingPluginPackages.js';
|
|
38
|
+
import updateIconImportsJit from './updateIconImportsJit.js';
|
|
34
39
|
import updateServerPackageJsonJit from './updateServerPackageJsonJit.js';
|
|
35
40
|
import validatePageTypes from './validatePageTypes.js';
|
|
36
41
|
import writePageJit from './writePageJit.js';
|
|
42
|
+
validateOperatorsDynamic({
|
|
43
|
+
operators
|
|
44
|
+
});
|
|
45
|
+
const dynamicIdentifiers = collectDynamicIdentifiers({
|
|
46
|
+
operators
|
|
47
|
+
});
|
|
48
|
+
async function updateDynamicIcons({ page, context }) {
|
|
49
|
+
if (!context.iconImports) return;
|
|
50
|
+
const missingIcons = detectMissingIcons({
|
|
51
|
+
page,
|
|
52
|
+
iconImports: context.iconImports
|
|
53
|
+
});
|
|
54
|
+
if (missingIcons.length > 0) {
|
|
55
|
+
await updateIconImportsJit({
|
|
56
|
+
newIcons: missingIcons,
|
|
57
|
+
iconImports: context.iconImports,
|
|
58
|
+
context
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
37
62
|
async function buildPageJit({ pageId, pageRegistry, context, directories, logger }) {
|
|
38
63
|
// Use provided context or create a minimal one for JIT builds
|
|
39
64
|
const buildContext = context ?? createContext({
|
|
@@ -59,7 +84,12 @@ async function buildPageJit({ pageId, pageRegistry, context, directories, logger
|
|
|
59
84
|
const pagePath = path.join(buildContext.directories.build, 'pages', pageId, `${pageId}.json`);
|
|
60
85
|
try {
|
|
61
86
|
const content = await fs.promises.readFile(pagePath, 'utf8');
|
|
62
|
-
|
|
87
|
+
const page = serializer.deserialize(JSON.parse(content));
|
|
88
|
+
await updateDynamicIcons({
|
|
89
|
+
page,
|
|
90
|
+
context: buildContext
|
|
91
|
+
});
|
|
92
|
+
return page;
|
|
63
93
|
} catch (err) {
|
|
64
94
|
if (err.code !== 'ENOENT') throw err;
|
|
65
95
|
}
|
|
@@ -76,18 +106,20 @@ async function buildPageJit({ pageId, pageRegistry, context, directories, logger
|
|
|
76
106
|
let resolvedVars = null;
|
|
77
107
|
if (unresolvedVars) {
|
|
78
108
|
const varRefDef = makeRefDefinition({}, null, buildContext.refMap);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
109
|
+
const varCtx = new WalkContext({
|
|
110
|
+
buildContext,
|
|
111
|
+
refId: varRefDef.id,
|
|
112
|
+
sourceRefId: null,
|
|
113
|
+
vars: {},
|
|
114
|
+
path: '',
|
|
115
|
+
currentFile: pageEntry.refPath ?? pageEntry.resolverOriginal?.resolver ?? '',
|
|
116
|
+
refChain: new Set(),
|
|
117
|
+
operators,
|
|
118
|
+
env: process.env,
|
|
119
|
+
dynamicIdentifiers,
|
|
120
|
+
shouldStop: null
|
|
90
121
|
});
|
|
122
|
+
resolvedVars = await resolve(cloneForResolve(unresolvedVars), varCtx);
|
|
91
123
|
}
|
|
92
124
|
let refDef;
|
|
93
125
|
if (pageEntry.resolverOriginal) {
|
|
@@ -105,17 +137,25 @@ async function buildPageJit({ pageId, pageRegistry, context, directories, logger
|
|
|
105
137
|
refDef = makeRefDefinition(refDefinition, null, buildContext.refMap);
|
|
106
138
|
buildContext.refMap[refDef.id].path = refDef.path;
|
|
107
139
|
}
|
|
108
|
-
|
|
140
|
+
const pageContent = await getRefContent({
|
|
109
141
|
context: buildContext,
|
|
110
142
|
refDef,
|
|
111
|
-
|
|
143
|
+
referencedFrom: null
|
|
112
144
|
});
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
refDef
|
|
145
|
+
const pageCtx = new WalkContext({
|
|
146
|
+
buildContext,
|
|
147
|
+
refId: refDef.id,
|
|
148
|
+
sourceRefId: null,
|
|
149
|
+
vars: refDef.vars ?? {},
|
|
150
|
+
path: '',
|
|
151
|
+
currentFile: refDef.path ?? '',
|
|
152
|
+
refChain: new Set(),
|
|
153
|
+
operators,
|
|
154
|
+
env: process.env,
|
|
155
|
+
dynamicIdentifiers,
|
|
156
|
+
shouldStop: null
|
|
118
157
|
});
|
|
158
|
+
let processed = await resolve(pageContent, pageCtx);
|
|
119
159
|
processed = evaluateStaticOperators({
|
|
120
160
|
context: buildContext,
|
|
121
161
|
input: processed,
|
|
@@ -129,14 +169,9 @@ async function buildPageJit({ pageId, pageRegistry, context, directories, logger
|
|
|
129
169
|
throw new ConfigError(`Page "${pageId}" not found in resolved page source file.`);
|
|
130
170
|
}
|
|
131
171
|
}
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
// can't link objects to their source file and errors fall back to lowdefy.yaml.
|
|
136
|
-
const reviver = createRefReviver(refDef.id);
|
|
137
|
-
processed = serializer.copy(processed, {
|
|
138
|
-
reviver
|
|
139
|
-
});
|
|
172
|
+
// Tag all objects with ~r for ref provenance (normally done inside _ref
|
|
173
|
+
// resolution by the walker; JIT resolves the page file directly).
|
|
174
|
+
tagRefDeep(processed, refDef.id);
|
|
140
175
|
// Apply skeleton-computed auth (buildAuth ran during skeleton build)
|
|
141
176
|
processed.auth = pageEntry.auth;
|
|
142
177
|
// Add keys to the resolved page
|
|
@@ -144,6 +179,11 @@ async function buildPageJit({ pageId, pageRegistry, context, directories, logger
|
|
|
144
179
|
components: processed,
|
|
145
180
|
context: buildContext
|
|
146
181
|
});
|
|
182
|
+
// Write keyMap/refMap so the error handler reads JIT entries from disk.
|
|
183
|
+
// JIT addKeys assigns fresh ~k values that aren't in the skeleton keyMap.
|
|
184
|
+
await writeMaps({
|
|
185
|
+
context: buildContext
|
|
186
|
+
});
|
|
147
187
|
// Initialize linkActionRefs for buildPage (normally done by buildPages)
|
|
148
188
|
if (!buildContext.linkActionRefs) {
|
|
149
189
|
buildContext.linkActionRefs = [];
|
|
@@ -181,6 +221,13 @@ async function buildPageJit({ pageId, pageRegistry, context, directories, logger
|
|
|
181
221
|
]
|
|
182
222
|
};
|
|
183
223
|
}
|
|
224
|
+
// Detect icons in the JIT-resolved page that weren't discovered during skeleton build.
|
|
225
|
+
// Placed after detectMissingPluginPackages so we skip this when packages are being
|
|
226
|
+
// installed (the server restarts and icons will be discovered on the next build).
|
|
227
|
+
await updateDynamicIcons({
|
|
228
|
+
page: processed,
|
|
229
|
+
context: buildContext
|
|
230
|
+
});
|
|
184
231
|
// Validate link, state, payload, and server-state references
|
|
185
232
|
const pageIds = Object.keys(pageRegistry);
|
|
186
233
|
validateLinkReferences({
|
|
@@ -66,10 +66,15 @@ function createPageRegistry({ components, context }) {
|
|
|
66
66
|
const keyMapEntry = context.keyMap[page['~k']];
|
|
67
67
|
const refId = keyMapEntry?.['~r'] ?? null;
|
|
68
68
|
const sourceRef = !type.isNone(refId) ? findPageSourceRef(refId, context.refMap, unresolvedRefVars) : null;
|
|
69
|
+
// Inline pages (defined directly in lowdefy.yaml) have a refId pointing to
|
|
70
|
+
// the root ref but findPageSourceRef returns null because there is no
|
|
71
|
+
// separate source file. Set refId to null so buildPageJit serves them from
|
|
72
|
+
// the pre-built artifact written by buildShallowPages.
|
|
73
|
+
const isInline = !type.isNone(refId) && sourceRef === null && !type.isNone(context.refMap[refId]) && type.isNone(context.refMap[refId].parent);
|
|
69
74
|
registry.set(page.id, {
|
|
70
75
|
pageId: page.id,
|
|
71
76
|
auth: page.auth,
|
|
72
|
-
refId,
|
|
77
|
+
refId: isInline ? null : refId,
|
|
73
78
|
refPath: sourceRef?.path ?? null,
|
|
74
79
|
unresolvedVars: sourceRef?.unresolvedVars ?? null,
|
|
75
80
|
resolverOriginal: sourceRef?.original ?? null
|
|
@@ -0,0 +1,36 @@
|
|
|
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 iconPackages from '../buildImports/iconPackages.js';
|
|
16
|
+
function detectMissingIcons({ page, iconImports }) {
|
|
17
|
+
const pageJson = JSON.stringify(page);
|
|
18
|
+
const newIcons = [];
|
|
19
|
+
for (const [iconPackage, regex] of Object.entries(iconPackages)){
|
|
20
|
+
const existing = iconImports.find((entry)=>entry.package === iconPackage);
|
|
21
|
+
const existingSet = new Set(existing?.icons ?? []);
|
|
22
|
+
const seen = new Set();
|
|
23
|
+
for (const match of pageJson.matchAll(regex)){
|
|
24
|
+
const iconName = match[1];
|
|
25
|
+
if (!existingSet.has(iconName) && !seen.has(iconName)) {
|
|
26
|
+
seen.add(iconName);
|
|
27
|
+
newIcons.push({
|
|
28
|
+
icon: iconName,
|
|
29
|
+
package: iconPackage
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return newIcons;
|
|
35
|
+
}
|
|
36
|
+
export default detectMissingIcons;
|
|
@@ -0,0 +1,54 @@
|
|
|
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 { createRequire } from 'module';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
// Matches the JSON data argument inside GenIcon({...})(props) in react-icons source.
|
|
18
|
+
// react-icons icons are generated functions of the form:
|
|
19
|
+
// function IconName(props) { return GenIcon({...})(props); }
|
|
20
|
+
// Tolerates optional whitespace around GenIcon call and props argument.
|
|
21
|
+
const genIconDataRegex = /GenIcon\s*\(([\s\S]*?)\)\s*\(\s*props\s*\)/;
|
|
22
|
+
function extractIconData({ icons, directories, logger }) {
|
|
23
|
+
const serverRequire = createRequire(path.join(directories.server, 'package.json'));
|
|
24
|
+
const iconDataMap = {};
|
|
25
|
+
const moduleCache = {};
|
|
26
|
+
for (const { icon, package: pkg } of icons){
|
|
27
|
+
if (!moduleCache[pkg]) {
|
|
28
|
+
try {
|
|
29
|
+
moduleCache[pkg] = serverRequire(pkg);
|
|
30
|
+
} catch {
|
|
31
|
+
if (logger) {
|
|
32
|
+
logger.warn(`Could not load icon package "${pkg}" for dynamic icon extraction.`);
|
|
33
|
+
}
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const iconFn = moduleCache[pkg][icon];
|
|
38
|
+
if (!iconFn) continue;
|
|
39
|
+
const match = iconFn.toString().match(genIconDataRegex);
|
|
40
|
+
if (match) {
|
|
41
|
+
try {
|
|
42
|
+
iconDataMap[icon] = JSON.parse(match[1]);
|
|
43
|
+
} catch {
|
|
44
|
+
if (logger) {
|
|
45
|
+
logger.warn(`Could not parse icon data for "${icon}" from "${pkg}".`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} else if (logger) {
|
|
49
|
+
logger.warn(`Could not extract icon data for "${icon}" from "${pkg}". The icon will show as a fallback.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return iconDataMap;
|
|
53
|
+
}
|
|
54
|
+
export default extractIconData;
|
|
@@ -48,7 +48,6 @@ import writePluginImports from '../writePluginImports/writePluginImports.js';
|
|
|
48
48
|
import addInstalledTypes from './addInstalledTypes.js';
|
|
49
49
|
import buildJsShallow from './buildJsShallow.js';
|
|
50
50
|
import buildShallowPages from './buildShallowPages.js';
|
|
51
|
-
import stripPageContent from './stripPageContent.js';
|
|
52
51
|
import writeSourcelessPages from './writeSourcelessPages.js';
|
|
53
52
|
async function shallowBuild(options) {
|
|
54
53
|
makeId.reset();
|
|
@@ -73,9 +72,6 @@ async function shallowBuild(options) {
|
|
|
73
72
|
components,
|
|
74
73
|
context
|
|
75
74
|
});
|
|
76
|
-
stripPageContent({
|
|
77
|
-
components
|
|
78
|
-
});
|
|
79
75
|
tryBuildStep(testSchema, 'testSchema', {
|
|
80
76
|
components,
|
|
81
77
|
context
|
|
@@ -201,6 +197,7 @@ async function shallowBuild(options) {
|
|
|
201
197
|
context
|
|
202
198
|
});
|
|
203
199
|
await context.writeBuildArtifact('jsMap.json', JSON.stringify(context.jsMap));
|
|
200
|
+
await context.writeBuildArtifact('idCounter.json', JSON.stringify(makeId.counter));
|
|
204
201
|
await context.writeBuildArtifact('customTypesMap.json', JSON.stringify(options.customTypesMap ?? {}));
|
|
205
202
|
// Persist snapshot of installed packages for JIT missing-package detection.
|
|
206
203
|
// Written as a build artifact so JIT builds compare against the skeleton
|
|
@@ -212,6 +209,10 @@ async function shallowBuild(options) {
|
|
|
212
209
|
components,
|
|
213
210
|
context
|
|
214
211
|
});
|
|
212
|
+
// Persist icon imports snapshot for JIT icon detection.
|
|
213
|
+
// When buildPageJit resolves a page, it compares discovered icons against
|
|
214
|
+
// this snapshot and regenerates plugins/icons.js if new icons are found.
|
|
215
|
+
await context.writeBuildArtifact('iconImports.json', JSON.stringify(components.imports.icons));
|
|
215
216
|
await writePageRegistry({
|
|
216
217
|
pageRegistry,
|
|
217
218
|
context
|
|
@@ -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 extractIconData from './extractIconData.js';
|
|
16
|
+
import writeIconsDynamic from './writeIconsDynamic.js';
|
|
17
|
+
async function updateIconImportsJit({ newIcons, iconImports, context }) {
|
|
18
|
+
for (const { icon, package: pkg } of newIcons){
|
|
19
|
+
let entry = iconImports.find((e)=>e.package === pkg);
|
|
20
|
+
if (!entry) {
|
|
21
|
+
entry = {
|
|
22
|
+
icons: [],
|
|
23
|
+
package: pkg
|
|
24
|
+
};
|
|
25
|
+
iconImports.push(entry);
|
|
26
|
+
}
|
|
27
|
+
// Guard against concurrent JIT builds adding the same icon
|
|
28
|
+
if (!entry.icons.includes(icon)) {
|
|
29
|
+
entry.icons.push(icon);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
await context.writeBuildArtifact('iconImports.json', JSON.stringify(iconImports));
|
|
33
|
+
// Extract SVG tree data from react-icons and write a self-contained JS module
|
|
34
|
+
// that the client can fetch at runtime without a Next.js rebuild.
|
|
35
|
+
const newIconData = extractIconData({
|
|
36
|
+
icons: newIcons,
|
|
37
|
+
directories: context.directories,
|
|
38
|
+
logger: context.logger
|
|
39
|
+
});
|
|
40
|
+
await writeIconsDynamic({
|
|
41
|
+
newIconData,
|
|
42
|
+
context
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export default updateIconImportsJit;
|
|
@@ -12,12 +12,9 @@
|
|
|
12
12
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
13
|
See the License for the specific language governing permissions and
|
|
14
14
|
limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
delete page[key];
|
|
20
|
-
}
|
|
21
|
-
}
|
|
15
|
+
*/ async function writeIconsDynamic({ newIconData, context }) {
|
|
16
|
+
Object.assign(context.dynamicIconData, newIconData);
|
|
17
|
+
const content = `export default ${JSON.stringify(context.dynamicIconData)};\n`;
|
|
18
|
+
await context.writeBuildArtifact('plugins/iconsDynamic.js', content);
|
|
22
19
|
}
|
|
23
|
-
export default
|
|
20
|
+
export default writeIconsDynamic;
|