Skip to content

feat(deploy:lwc): Metadata API fallback for Tooling schema-ref bug#441

Merged
msrivastav13 merged 4 commits into
msrivastav13:masterfrom
jprichter:feature/lwc-metadata-fallback
Jun 4, 2026
Merged

feat(deploy:lwc): Metadata API fallback for Tooling schema-ref bug#441
msrivastav13 merged 4 commits into
msrivastav13:masterfrom
jprichter:feature/lwc-metadata-fallback

Conversation

@jprichter

Copy link
Copy Markdown
Contributor

Closes #434

Summary

deploy:lwc fast-saves bundles via the Tooling API. A Tooling API Salesforce bug rejects any source importing a custom field via @salesforce/schema/ with FIELD_INTEGRITY_EXCEPTION, even though the identical source deploys fine through the Metadata API.

This PR makes deploy:lwc automatically retry through the Metadata API when a Tooling create/update fails with that specific signature. The Tooling path is still attempted first; the fallback is transparent (spinner continues, normal success/failure shown).

What changed

  • New src/service/metadataDeployLwc.ts: isToolingSchemaRefBug (signature detection; genuine compile errors are not matched), buildLwcPackageXml, and metadataFallbackDeploy (zips the bundle, deploys via Metadata API, polls to completion).
  • src/commands/deploy/lwc.ts: the inner catch now checks the predicate and delegates to the service; non-matching errors keep the original behavior.
  • README.md: documents the fallback.

Directory deploys zip the local files; single-file deploys query the rest of the
bundle from the org and overlay the changed file so only the single-file changes on the org. Resource paths are validated (lwc/…, no ..) before zipping.

Testing

New unit tests in test/commands/deploy/lwc.test.ts cover detection, both fallback paths, both-fail messaging, timeout, path-safety, and the no-fallback regression guard.

npm test45 passing; npm run posttest clean.

Additionally, you can test against this sample project by deploying it first using sf project deploy start then editing force-app/main/default/lwc/ccdxSample/ccdxSample.js and using this plugin to save. The save will fail against master` and succeed against this branch.

jprichter added 2 commits June 2, 2026 20:25
Automatically retries an LWC bundle save through the Metadata API when the Tooling API rejects it with FIELD_INTEGRITY_EXCEPTION / "Invalid reference... of type sobjectField" (a known Salesforce bug on @salesforce/schema custom-field imports). The Tooling path is still attempted first.
@msrivastav13

Copy link
Copy Markdown
Owner

Thanks for the thorough PR — the detection is appropriately narrow, the timeout path is safe, and the test coverage is solid. A few things I'd like addressed before merge:

1. Create-then-fallback drops *-meta.xml from the zip (edge case)

When a bundle doesn't yet exist, createLWCBundle mutates validFiles to filter out all .xml files (src/commands/deploy/lwc.ts:170-174, including *.js-meta.xml). The re-read fileBodyArray = await getFileBodyMap(validFiles) faithfully syncs to that filtered list, which is correct for the Tooling upsert. But if the subsequent upsertLWCDefinition then throws the schema-ref bug, metadataFallbackDeploy receives validFiles/filePath with no meta.xml — and a LightningComponentBundle Metadata deploy without its .js-meta.xml will fail.

This is a narrow case (brand-new bundle and schema-ref bug on the very first save) and the originally reported scenario in #434 (editing an existing bundle) is unaffected, since createLWCBundle is skipped there and the meta.xml stays in validFiles. Could you either guard against it or add a comment so the fallback doesn't surface a confusing secondary error?

2. Buffer.from(source, 'utf8') assumes Source is never null

In the single-file path, org resources are queried and their Source is zipped directly. If any LightningComponentResource.Source comes back null/undefined, Buffer.from(null, 'utf8') throws. Cheap to guard with source ?? ''.

4. No spinner feedback during the (multi-second) fallback

The spinner keeps spinning through the Metadata deploy + polling but the text stays Saving. Since this path is deliberately slower, a this.spinner.status = '…retrying via Metadata API' would set user expectations. Purely UX, optional.

@jprichter

jprichter commented Jun 3, 2026

Copy link
Copy Markdown
Contributor Author

@msrivastav13 thanks for the review. I'll make these changes tonight.

For point 1, for the new-bundle path, I'll put in a guard that detects the incompletle bundle and returns a message e.g. "Please re-run against the full bundle directory". This'll happen before the deploy.

For point 2, the save will still fail, but we'll get a more useful Metadata API error instead of a TypeError from the JavaScript.

For your last point, I personally would skip the spinner and leave this retry hidden from the user. Errors will still surface, but they'll be blind to the fact that we're retrying with the Metadata API. We can leave the debug breadcrumb for devs. Let me know if you feel differently.

@msrivastav13

Copy link
Copy Markdown
Owner

Yes sure

jprichter and others added 2 commits June 3, 2026 17:56
Previously the fallback only covered Tooling resource update/create on an existing bundle. A first-time save of an LWC that imports a custom field hits the same schema-ref bug during LightningComponentBundle creation, which escaped to the outer catch with no fallback.

createLWCBundle is now wrapped so a schema-ref bug re-reads the full bundle from disk (including js-meta.xml) and deploys via the Metadata API, which creates the bundle. Genuine create errors (e.g. LWC1503) still surface immediately. Create and update paths share a runMetadataFallback helper; metadataFallbackDeploy is unchanged.

Confirmed E2E against a scratch org (API 66): the bug surfaces as AURA_COMPILE_ERROR (not FIELD_INTEGRITY_EXCEPTION), and the message regex distinguishes it from genuine compile errors that share the same errorCode. 50 unit tests passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jprichter

Copy link
Copy Markdown
Contributor Author

@msrivastav13, I made additional changes beyond what we discussed. The previous work only addressed fallbacks for updates. The fallback now also covers bundle creation.

While testing against a scratch org, I hit the gap you'd expect from the earlier discussion: a first-time save of an LWC importing a custom field fails the same way, but the error was thrown during LightningComponentBundle creation, so it surfaced the Tooling API error with no retry.

Two findings from my testing drove the fix:

The jsforce path surfaces the bug as AURA_COMPILE_ERROR, not the FIELD_INTEGRITY_EXCEPTION previously documented from raw REST. A genuine parse error (LWC1503) carries that same errorCode — so it's the message regex, not the code, that correctly tells them apart. Both cases are now pinned by unit tests.

A failed Tooling bundle-create is atomic (no orphan shell), so no cleanup is needed.

Implementation: createLWCBundle is wrapped so a schema-ref bug re-reads the full bundle from disk (incl. js-meta.xml) and deploys via the Metadata API, which creates the bundle; genuine errors still fail fast. Create/update now share one helper, and metadataFallbackDeploy is unchanged. Verified create + update end-to-end; 50 unit tests passing.

@msrivastav13 msrivastav13 left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @jprichter — pulled the branch, built it, and ran the suite locally. tsc --noEmit clean, 57 passing, posttest clean. All three review points are addressed, and I confirmed the verifying tests:

  • meta.xml on create-then-fallback — early bail with re-run guidance when js-meta.xml is absent, never builds an incomplete zip (test 4b).
  • null Source — coalesced to '', no Buffer.from(null) TypeError (test 5b).
  • spinner — kept hidden per our discussion, debug breadcrumb retained.

Nice catch extending the fallback to bundle creation (test 4c), and the AURA_COMPILE_ERROR vs LWC1503 finding is a good one — agreed that the message regex, not the errorCode, is what correctly discriminates, and the regression guard pins that down.

Approving. 🚀

@msrivastav13 msrivastav13 merged commit 8df0aa0 into msrivastav13:master Jun 4, 2026
1 check passed
@jprichter jprichter deleted the feature/lwc-metadata-fallback branch June 4, 2026 14:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FIELD_INTEGRITY_EXCEPTION: Invalid reference CustomObject__c.CustomField__c of type sobjectField in file ccdxSample.js: Source

2 participants