@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 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
 
@@ -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 ?? null);
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
- let node = init;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@assembly-js/app-foundation",
3
- "version": "0.14.5",
3
+ "version": "0.14.6",
4
4
  "description": "Versioned foundation helpers and build context for Assembly generated apps",
5
5
  "type": "module",
6
6
  "files": [