@assembly-js/app-foundation 0.14.5 → 0.14.6
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 +3 -0
- package/bin/scan-routes.mjs +51 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -19,6 +19,9 @@ A patch release never changes what an app's build requires; a minor release may
|
|
|
19
19
|
not by hand; see `changes/README.md`. CI fails a PR that changes a check script without a
|
|
20
20
|
`bump: minor` change file.
|
|
21
21
|
|
|
22
|
+
## 0.14.6
|
|
23
|
+
- [behavior] The route scanner now leaves a handler wrapped in `automationActionRoute` or `cronRoute` out of the cross-app contract whichever way it is exported, including `export function POST(req) { return automationActionRoute(...)(req) }` and arrow handlers that return the wrapper call.
|
|
24
|
+
|
|
22
25
|
## 0.14.5
|
|
23
26
|
- [guidance] The build agent now knows the public API accepts `select` (one option key string), `checkbox` (JSON boolean) and `date` (`YYYY-MM-DD`) custom fields, and that only `multiSelect` takes an array.
|
|
24
27
|
|
package/bin/scan-routes.mjs
CHANGED
|
@@ -189,6 +189,50 @@ export function readPrivateRoutes(root) {
|
|
|
189
189
|
|
|
190
190
|
const hasModifier = (ts, node, kind) => node.modifiers?.some((m) => m.kind === kind) ?? false;
|
|
191
191
|
|
|
192
|
+
const unwrapExpression = (ts, node) => {
|
|
193
|
+
while (node && (ts.isParenthesizedExpression(node) || ts.isAwaitExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression?.(node))) {
|
|
194
|
+
node = node.expression;
|
|
195
|
+
}
|
|
196
|
+
return node;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Return statements of a function body, not those of functions nested in it.
|
|
200
|
+
function ownReturns(ts, body) {
|
|
201
|
+
const out = [];
|
|
202
|
+
const visit = (node) => {
|
|
203
|
+
if (ts.isReturnStatement(node)) out.push(node);
|
|
204
|
+
if (!ts.isFunctionLike(node)) ts.forEachChild(node, visit);
|
|
205
|
+
};
|
|
206
|
+
ts.forEachChild(body, visit);
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// A function handler whose only return is `wrapper(...)(req)` is the same
|
|
211
|
+
// wrapped route as `export const GET = wrapper(...)`, so it yields the inner
|
|
212
|
+
// `wrapper(...)` call. A handler with any other return path serves that path
|
|
213
|
+
// unwrapped, so it stays an operation.
|
|
214
|
+
function wrappedCallOf(ts, fn) {
|
|
215
|
+
const body = fn?.body;
|
|
216
|
+
if (!body) return null;
|
|
217
|
+
let expr = body;
|
|
218
|
+
if (ts.isBlock(body)) {
|
|
219
|
+
const returns = ownReturns(ts, body);
|
|
220
|
+
if (returns.length !== 1 || !body.statements.includes(returns[0])) return null;
|
|
221
|
+
expr = returns[0].expression;
|
|
222
|
+
}
|
|
223
|
+
const outer = unwrapExpression(ts, expr);
|
|
224
|
+
if (!outer || !ts.isCallExpression(outer)) return null;
|
|
225
|
+
const inner = unwrapExpression(ts, outer.expression);
|
|
226
|
+
return inner && ts.isCallExpression(inner) && ts.isIdentifier(inner.expression) ? inner : null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// The expression wrapperName reads for a handler binding.
|
|
230
|
+
function handlerInit(ts, init) {
|
|
231
|
+
const node = unwrapExpression(ts, init);
|
|
232
|
+
if (node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node))) return wrappedCallOf(ts, node) ?? init;
|
|
233
|
+
return init ?? null;
|
|
234
|
+
}
|
|
235
|
+
|
|
192
236
|
// Returns the exported handler bindings and whether the export surface is
|
|
193
237
|
// opaque (`export *`, CommonJS) so names cannot be read statically.
|
|
194
238
|
export function analyseExports(ts, file, text) {
|
|
@@ -206,17 +250,20 @@ export function analyseExports(ts, file, text) {
|
|
|
206
250
|
for (const stmt of sf.statements) {
|
|
207
251
|
if (ts.isVariableStatement(stmt) && !hasModifier(ts, stmt, ts.SyntaxKind.ExportKeyword)) {
|
|
208
252
|
for (const decl of stmt.declarationList.declarations) {
|
|
209
|
-
if (ts.isIdentifier(decl.name) && decl.initializer) locals.set(decl.name.text, decl.initializer);
|
|
253
|
+
if (ts.isIdentifier(decl.name) && decl.initializer) locals.set(decl.name.text, handlerInit(ts, decl.initializer));
|
|
210
254
|
}
|
|
255
|
+
} else if (ts.isFunctionDeclaration(stmt) && !hasModifier(ts, stmt, ts.SyntaxKind.ExportKeyword) && stmt.name) {
|
|
256
|
+
const wrapped = wrappedCallOf(ts, stmt);
|
|
257
|
+
if (wrapped) locals.set(stmt.name.text, wrapped);
|
|
211
258
|
}
|
|
212
259
|
}
|
|
213
260
|
|
|
214
261
|
for (const stmt of sf.statements) {
|
|
215
262
|
if (ts.isFunctionDeclaration(stmt) && hasModifier(ts, stmt, ts.SyntaxKind.ExportKeyword) && stmt.name) {
|
|
216
|
-
add(stmt.name.text);
|
|
263
|
+
add(stmt.name.text, wrappedCallOf(ts, stmt));
|
|
217
264
|
} else if (ts.isVariableStatement(stmt) && hasModifier(ts, stmt, ts.SyntaxKind.ExportKeyword)) {
|
|
218
265
|
for (const decl of stmt.declarationList.declarations) {
|
|
219
|
-
if (ts.isIdentifier(decl.name)) add(decl.name.text, decl.initializer
|
|
266
|
+
if (ts.isIdentifier(decl.name)) add(decl.name.text, handlerInit(ts, decl.initializer));
|
|
220
267
|
else if (ts.isObjectBindingPattern(decl.name)) {
|
|
221
268
|
for (const el of decl.name.elements) if (ts.isIdentifier(el.name)) add(el.name.text);
|
|
222
269
|
}
|
|
@@ -241,10 +288,7 @@ export function analyseExports(ts, file, text) {
|
|
|
241
288
|
// resolution, so `export const POST = cronRoute(...)` is recognised without
|
|
242
289
|
// following the import.
|
|
243
290
|
export function wrapperName(ts, init) {
|
|
244
|
-
|
|
245
|
-
while (node && (ts.isParenthesizedExpression(node) || ts.isAwaitExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression?.(node))) {
|
|
246
|
-
node = node.expression;
|
|
247
|
-
}
|
|
291
|
+
const node = unwrapExpression(ts, init);
|
|
248
292
|
if (node && ts.isCallExpression(node) && ts.isIdentifier(node.expression)) return node.expression.text;
|
|
249
293
|
return null;
|
|
250
294
|
}
|