fix: catch unsupported Custom Action return types before deploy - #48
Conversation
Greptile SummaryThis PR strengthens Custom Action return-type validation and updates deployment tooling.
Confidence Score: 3/5The PR should not merge until suffix-named imported return types are rejected by the deploy gate. The nested-generic path is addressed, but an imported package class ending in Struct or Record is absent from Files Needing Attention: src/flutterFlowArtifactValidation.js Important Files Changed
|
The return-type check only rejected types declared by `class`/`enum` in the
same push, so an action returning an imported package type (nfc_manager's
NfcTag) or a type already in the project was invisible to it. Those pushes
passed validateFileMap and FlutterFlow rejected them with "Unable to process
return parameter".
Check the return type against the types FlutterFlow's Action Return Value
selector exposes instead, so any unsupported type is caught regardless of
where it is defined. This activates the existing deploy gate in
validateDartFile rather than adding a second one.
Also fixes three ways the check silently skipped enforcement:
- nested generics deeper than one level (Future<List<NdefMessage>>) are now
parsed with a balanced-bracket scan instead of a regex
- artifact display names ("Write NFC Tag") now match their Dart identifier
(writeNfcTag) instead of failing to match and skipping the check
- class/enum names appearing only in comments or strings no longer register
as declared types
Struct Data Types and both no-return spellings (Future, Future<void>) stay
allowed so side-effect actions are not blocked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
When the artifact name does not match any function, the check fell back to the first Future signature in the file. A private helper declared above the action (Future<NfcTag> _readTag()) would then be read as the action's return type and hard-block a valid deploy. Prefer a public signature, since FlutterFlow never calls a private function as an action. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The allowlist was built from the app's own prompt rules and missed two types FlutterFlow's Return Value selector actually offers: Document, which generates as <Collection>Record, and TimestampRange, which generates as DateTimeRange. Both would have been wrongly blocked before deploy. Verified the allowlist against the documented selector list (Integer, Double, Boolean, String, Image/Video/Audio Path, Color, Document, Document Reference, JSON, DateTime, TimestampRange, plus List and Data Types). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
getUnsupportedReturnTypeIdentifiers treated any identifier ending in "Struct" or "Record" as an existing FlutterFlow project type, without checking whether it was actually declared as a class/enum in the same push. A real Data Type or Firestore Record is never defined via `class X` in a pushed Code File - it already exists in the project - so a declared name with that suffix is a local Dart class wearing the naming convention, and FlutterFlow still rejects it as a return value exactly like an imported type. Pass declaredTypes into the suffix check: a *Struct/*Record identifier is only treated as a real project type when it is NOT declared in this push. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
DEPLOYMENT.md referenced this script, but it never existed - deploys were done by manually uploading dist/ one-off. Vite content-hashes built assets (index-<hash>.js), so every rebuild orphaned the previous hash's file on the server; three such orphans had already accumulated (visible in .gitignore's history of index-*.js entries). The script uploads every file in dist/, then prunes any remote file that no longer exists locally, scoped to only the directories dist/ itself manages (the account root, api/, assets/). It never lists or touches a remote directory with no local counterpart, so nothing outside dist's own footprint can be affected. --dry-run previews both the upload and prune sets without changing the server. Password resolves from FTP_PASSWORD if set, else macOS Keychain, matching the existing DEPLOYMENT.md instructions. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 35186025 | Triggered | FTP Credentials | 5deb4fd | scripts/deploy_ftp.py | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
Problem
Custom Actions returning a type FlutterFlow cannot represent were reaching FlutterFlow and being rejected at push time with "Unable to process return parameter" — the reported recurring deployment failure (
write_nfc_tag.dart,scan_and_execute_nfc_tag.dart).The first attempt in this branch checked the return type against types declared by
class/enumin the same push. Those actions returnedNfcTag/NdefMessage, imported from thenfc_managerpackage and never declared in the push — so the check found nothing,validateFileMappassed, and the deploy went out and failed remotely.Fix
Check the return type against an allowlist of what FlutterFlow's Custom Action Return Value selector actually accepts, so an unsupported type is caught regardless of where it is defined. This activates the deploy gate that already exists (
validateDartFile→validateFileMap→ all three commit paths) rather than adding a second one.Ways enforcement was silently skipped, all fixed:
Future<NfcTag>(imported type)Future<List<NdefMessage>>(2-deep generic)"Write NFC Tag"vswriteNfcTagclass Resultin a doc commentclass FooStruct/FooRecorddeclared in the same pushA private helper (
Future<NfcTag> _readTag()) declared above the action is no longer mistaken for the entry point, which would have hard-blocked a valid deploy.The last row was a code-review finding on this PR: a real FlutterFlow Data Type or Firestore Record is never defined via
class Xin a pushed Code File — it already exists in the project — so a*Struct/*Record-suffixed name declared in the same push is a local Dart class wearing the naming convention, not the real thing, and still can't be used as a return value.Allowlist
Verified against FlutterFlow's documented return value types: Integer, Double, Boolean, String (also Image/Video/Audio Path), Color, Document (
*Record), Document Reference, JSON, DateTime, TimestampRange (DateTimeRange), the List option, and Data Types (*Struct).Both no-return spellings (
Future,Future<void>) stay allowed so side-effect actions are not blocked — FlutterFlow's own boilerplate emits a bareFuture.Deploy tooling
DEPLOYMENT.mdreferencedscripts/deploy_ftp.py, which never existed. Added it: uploadsdist/, then prunes remote files that no longer exist locally, scoped only to directoriesdist/itself manages (root,api/,assets/) — so Vite's content-hashed bundles (index-<hash>.js) don't accumulate on the server the way three prior ones already had. It never touches a remote directory with no local counterpart.--dry-runpreviews both the upload and prune sets.Testing
npm test— 90/90 passing, including a regression test reproducing the exact NFC failure, plus deep-generic, display-name, comment-pollution, private-helper, declared-suffix, and full allowlist coverage.Deployed and verified live at customcode.connectio.com.au: correct bundle serving, fix confirmed present in the shipped code, stale bundles pruned with zero collateral (dry-run checked before every delete).
Notes
validateFileMap/validateDartFilelive inapp.jsand are not exported, so the wiring itself is verified by trace and simulation rather than by test.LatLng/FFPlace/FFUploadedFileare allowed but documented only for arguments; left permissive deliberately, since wrongly blocking them would stop a working deploy with no override, whereas allowing them falls back to FlutterFlow's own error.