We are using the MetadataService.cls together with jszip as a kind of Swiss-Army knife to access almost everthing in CodeFusion.
To make Lightning Components accessible I first updated MetadataService.cls from v29 to v32.
The next step has nothing to do with Lightning but my biggest challenge was that some functions has been deprecated in v31. To avoid a redesign of existing code, I hacked MetadataService.cls v32 and reintroduced the old functions as described here MetadataAPI: Missing MetadataPort.checkStatus(String[] asyncProcessId) in MetadataService.cls v32.0
After that it was quite easy to make small tweaks to the APEX, Visualforce and JavaScript methods to load and save AuraDefinitionBundles. We are not using the CRUD functions. And sorry, the provided code is uncleaned and probably not working out of it's context, but could show the pattern quite well.
The basic approach in APEX goes like this (which actually also works for Lightning Components in it's manifestation as AuraDefinitionBundle). But remember the generic (Swiss-Army-Knife) approach, so you won't find any explicit mentioning of AuraDefinitionBundle in the code. Everything will be controlled with MetaDataType
(='AuraDefinitionBundle') and MetaDataItem
(='YourLightningComponentName')
Retrieve Metadata
public PageReference retrieveMetadataItem() {
RetrieveCompleted = false;
DeployCompleted = false;
MetaDataRetrieveZip = null;
MetadataFiles = null;
// Construct unmanaged package with list of desired components to retrieve in zip
MetadataService.MetadataPort
service = createService();
MetadataService.RetrieveRequest
retrieveRequest = new MetadataService.RetrieveRequest();
retrieveRequest.apiVersion = this.MetadataAPIVersion;
retrieveRequest.packageNames = null;
retrieveRequest.singlePackage = true;
retrieveRequest.specificFiles = null;
retrieveRequest.unpackaged = new MetadataService.Package_x();
retrieveRequest.unpackaged.types = new List<MetadataService.PackageTypeMembers>();
MetadataService.PackageTypeMembers
packageType = new MetadataService.PackageTypeMembers();
packageType.name = MetaDataType;
packageType.members = new String[] { MetaDataItem };
retrieveRequest.unpackaged.types.add(packageType);
AsyncResultRetrieve = service.retrieve(retrieveRequest);
return null;
}
public PageReference checkAsyncRequestRetrieve() {
errors = 0;
// Check the status of the retrieve request
MetadataService.MetadataPort
service = createService();
MetadataService.AsyncResult[]
aysncResults = service.checkStatus(new String[] { AsyncResultRetrieve.Id });
if(aysncResults[0].done) {
if(aysncResults[0].state == 'Error') { // async errors:
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Info, aysncResults[0].message));
AsyncResultRetrieve = null;
} else { // async no errors:
MetadataService.RetrieveResult // Place Base64 encoded zip data onto the page for the JSZip library to handle
retrieveResult = service.checkRetrieveStatus(AsyncResultRetrieve.id);
if(retrieveResult.messages != null) {
xt.msg('Error '+retrieveResult.messages[0].problem ,'error');
errors = 1;
} else {
xt.msg('Retrieved '+MetaDataItem,'confirm');
}
MetaDataRetrieveZip = retrieveResult.zipFile;
MetadataFiles = new List<MetadataFile>();
AsyncResultRetrieve = null;
Retrieving = false;
}
} else {
//xt.msgp('Retrieving metadata...');
}
return null;
}
Deploy Metadata
public PageReference deployZip() {
xt.msgp( 'MedadataAPI: deploying... <span class="asyncCheckCount"></span>');
Deploying = true;
// Deploy zip file posted back from the page action function
MetadataService.MetadataPort service = createService();
MetadataService.DeployOptions deployOptions = new MetadataService.DeployOptions();
deployOptions.allowMissingFiles = false;
deployOptions.autoUpdatePackage = false;
deployOptions.checkOnly = false;
deployOptions.ignoreWarnings = false;
deployOptions.performRetrieve = false;
deployOptions.purgeOnDelete = false;
deployOptions.rollbackOnError = true;
deployOptions.runAllTests = false;
deployOptions.runTests = null;
deployOptions.singlePackage = true;
AsyncResultDeploy = service.deploy(ZipData, DeployOptions);
return null;
}
public PageReference checkAsyncRequestDeploy() { // Check the status of the retrieve request
try {
errors = 0;
string successTexts = '';
MetadataService.MetadataPort
service = createService();
MetadataService.AsyncResult[]
aysncResults = service.checkStatus(new String[] { AsyncResultDeploy.Id });
if(aysncResults[0].done) { // async done
this.DeployCompleted = true; // update global completed
Deploying = false;
if(aysncResults[0].state == 'Error') { // async errors:
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Info, aysncResults[0].message));
AsyncResultDeploy = null;
} else { // async no errors:
xt.msg('Deployment complete');
MetadataService.DeployResult
deployResult = service.checkDeployStatus(AsyncResultDeploy.Id); // Deployment errors?
//v29
if(deployResult.details != null ) {
if(deployResult.details.componentFailures != null ){
for(MetadataService.DeployMessage deployMessage : deployResult.details.componentFailures) {
xt.msg(''
+ deployMessage.fileName
+ ' (Line: ' + deployMessage.lineNumber + ': Column:' + deployMessage.columnNumber + ') : '
+ deployMessage.problem
,'error');
errors++;
}
}
if(deployResult.details.componentSuccesses != null ){
for(MetadataService.DeployMessage deployMessage : deployResult.details.componentSuccesses) {
successTexts += deployMessage.fileName + (deployMessage.id != null ? ''+'<br>'+deployMessage.id : '')+'<br>';
}
}
if(errors==0) {
xt.msg(''+successTexts
,'confirm');
if(versionManagementIsActive) versionCommit( MetadataItem, MetadataType, OrgNamespacePrefix );
}
}
AsyncResultDeploy = null;
}
} else {
xt.msgp( 'MedadataAPI: deploying... <span class="asyncCheckCount"></span>');
}
} catch( Exception e ){
xt.msg(''
+'catched APEX Error:'+'<br>' + e.getTypeName()+'<br>' + e.getMessage()+'<br>'
+'in Line: '+e.getLineNumber()+'<br>' + e.getStackTraceString()+'<br>'
,'error');
}
return null;
}
Zipping
On the Visualforce/Javascript side you have to deal with a zip file, which you have to extract. In it's content, you'll get all your Lightning files as described here: https://www.salesforce.com/us/developer/docs/api_meta/index_Left.htm#CSHID=meta_auradefinitionbundle.htm|StartTopic=Content%2Fmeta_auradefinitionbundle.htm|SkinName=webhelp
File Suffix and Directory Location
A Lightning bundle is a folder that contains definition files. Unlike
other metadata components, an AuraDefinitionBundle component isn’t
represented by a single component file but instead by a collection of
component definition files. Each definition file represents a resource
in a bundle, such as markup, applications, code files (including
controllers and helpers), events, documentation, and interfaces. For
example, this directory structure shows the hierarchy of the folders
and files for two bundles: bundle1 and bundle2.
aura
bundle1
bundle1.cmp
bundle1Controller.js
bundle2
bundle2.app
bundle2.cmp
bundle2Controller.js
bundle2.auradoc
As an unfortunate there is no zip-support in APEX, so you have to help yourself using JSZip. You can make your changes to the extracted files, update the zip and use the deploy functions to get everything back to Salesforce. But it's slow: it'll take about 5-10 seconds or longer to save... If you want something faster, have a look here: Calling ToolingAPI to save an AuraDefinition using PATCH via JavaScript possible? - this one will save small files in less than 0.5 seconds thanks to Doug Chasmans new approch:
We implemented the Tooling API for Lightning Components backend
differently that those others because I refused to go with the de
facto slower approach and I hope other meta objects will follow our
example :-)
Thumbs up!