Skip to content

micheletriaca/sfdy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sfdy

sfdy is a command line tool to work with the Salesforce Metadata API. It has been built to work around strange behaviors and known limitations of the API, and to simplify the implementation of a continuous integration process. It applies useful patches to avoid common problems when deploying metadata, and it exposes a simple interface to build your own plugins

  1. Requirements
  2. Usage
  3. Why not SFDX?
  4. Installation
  5. Features
  6. Changelog

Requirements

Usage

sfdy is meant to be mainly used as a command line tool. It can also be used as a library since it exposes a small API.

Type sfdy --help to see available commands. Type sfdy [command] --help to see available options for a specific command

Why not SFDX?

SFDX is a tool to work with scratch orgs and with modular projects.

In a typical salesforce project, development starts in a scratch org or a classic sandbox. Even if you use scratch orgs, however, sooner or later you'll have to deploy your sfdx project to a classic sandbox (a shared, persistent development sandbox used to test integrations, a UAT sandbox, etc.). After the code is deployed to a classic sandbox, we are right back to where we started.

Moreover, DX requires the developer to break down their entire Enterprise org into individual projects, but sometimes this is not possible/advisable or the sub-projects are still too big to be worked on by a single developer. Salesforce metadata are deeply interconnected, and every module is very likely to use a subset of common functionalities (standard objects, layout, flexipages). It is often a nightmare to divide an enterprise project in modules because those modules are not independent of each other.

Finally, this tool solves some problems that SFDX does not address, and gives the developer an easy way to customize a Salesforce CI process the way HE/SHE wants. To have the best possible experience, use this tool in conjunction with the VSCode plugin fast-sfdc. patches and even your custom plugins are automatically applied in both your CI flow and your local development environment!

Installation

npm install -g sfdy

then go to the root folder of a Salesforce project, and type

sfdy init

this command creates a .sfdy.json file within the root folder of your current workspace with the configuration of the 'standard' patches (more on this later)

Features

  1. Authenticate to Salesforce
  2. Retrieve full metadata (based on package.xml)
  3. Retrieve partial metadata (glob pattern or metadata-based)
  4. Deploy full metadata (based on package.xml)
  5. Deploy partial metadata (glob pattern or diff between 2 git branches)
  6. Perform a quick deploy
  7. Deploy a destructive changeset (glob pattern or metadata-based)
  8. Publish a community
  9. Apply 'standard' patches and renderers to metadata
  10. Build your own plugins (pre-deploy and after-retrieve)
  11. Build your own renderers
  12. Use sfdy as a library

Authenticate to Salesforce

You can pass a username and password in all the available commands. For example:

sfdy retrieve -u USERNAME -p PASSWORD ...

Otherwise, just type:

sfdy auth -s

This command will start an oauth2 web server flow and will output a refresh token and an instance URL.

The -s flag should be used when connecting to a sandbox.

The refresh token can be used in all the available commands as an authentication method instead of username+password. Example:

sfdy retrieve --refresh-token REFRESH_TOKEN --instance-url INSTANCE_URL -s ...

If you want to avoid passing refresh token and instance url all the time, you can use the auth command in this way:

eval $(sfdy auth -s -e)

This will set the returned refresh token and instance url as environment variables. The subsequent commands will read them from the environment. Example:

sfdy retrieve -s ...

Otherwise, you can just export them manually:

sfdy auth -s
export SFDY_REFRESH_TOKEN=refreshtoken
export SFDY_INSTANCE_URL=instanceurl

By default, the SFDY connected app will be used. If you want to use yours, you can pass a user-defined client_id and client_secret in all the available commands.

sfdy auth --client-id CLIENT_ID --client-secret CLIENT_SECRET -s ...

sfdy retrieve --refresh-token REFRESH_TOKEN --instance-url INSTANCE_URL --client-id CLIENT_ID --client-secret CLIENT_SECRET -s ...

Or you can use the env vars:

export SFDY_REFRESH_TOKEN=refreshtoken
export SFDY_INSTANCE_URL=instanceurl
export SFDY_CLIENT_ID=clientid
export SFDY_CLIENT_SECRET=clientsecret
sfdy retrieve -s ...

Warning: You must add http://localhost:3000/callback as redirect_uri in your connected app setup

Retrieve full metadata

From the root folder of your salesforce project, type:

sfdy retrieve -u USERNAME -p PASSWORD -s

This command will retrieve all metadata specified in package.xml and will apply any enabled patch.

The -s flag should be used when connecting to a sandbox.

Retrieve partial metadata

using --files

sfdy retrieve -u USERNAME -p PASSWORD -s --files='objects/*,!objects/{Account,Contact}*,site*/**/*'

This command will retrieve all objects present in the local objects folder, except those whose name starts with Account or Contact, and will retrieve all metadata (present in the local project) whose folder starts with site (for example sites, siteDotCom)

The --files consist of a comma-separated list of glob patterns

Warning: Negated patterns always have the highest precedence

using --meta

sfdy retrieve -u USERNAME -p PASSWORD -s --meta='CustomObject/Account,FlexiPage/*'

This command will retrieve the Account object and all the flexipages present on the target salesforce environment

Warning: the --meta option builds an ad-hoc package.xml to retrieve the data. Glob patterns cannot be used in this case. You can use a wildcard only if that metadata type supports it

Deploy full metadata

sfdy deploy -u USERNAME -p PASSWORD -s

This command will apply any enabled pre-deploy patch and will deploy all metadata specified in package.xml.

The -s flag should be used when connecting to a sandbox.

Deploy partial metadata

using --files

sfdy deploy -u USERNAME -p PASSWORD -s --files='objects/*,!objects/Account*,site*/**/*'

This command will deploy all objects present in the local objects folder, except those whose name starts with Account, and will deploy all metadata (present in the local project) whose folder starts with site (for example sites, siteDotCom)

The --files consist of a comma-separated list of glob patterns

Warning: Negated patterns always have the highest precedence

using --diff

sfdy deploy -u USERNAME -p PASSWORD -s --diff='behindBranch..aheadBranch'

The --diff flag is used to compute the list of files that needs to be deployed by comparing 2 git branches. (examples: --diff='origin/myBranch..HEAD' or --diff='branch1..branch2). As an example of a use case, you can trigger a deployment to the DEV environment when you create a pull request to the dev branch. The deployment will contain only the files that have been modified in the pull-request

Warning: the --diff option requires git. To use this feature you should be versioning your Salesforce project

using --diff and --files together

sfdy deploy -u USERNAME -p PASSWORD -s --diff='behindBranch..aheadBranch' --files='!siteDotCom/**/*'

This is useful if you want to perform a delta deployment skipping some metadata that could be included by the --diff option. Typical use case: production and sandbox orgs have different versions and some metadata can't be deployed

Perform a quick deploy

sfdy deploy -u USERNAME -p PASSWORD -s --quick-deploy=0Af1k000028frDD

This command will perform a quick deployment of the deployment identified by the passed id

Deploy a destructive changeset

Just run a partial deployment passing the --destructive flag

sfdy deploy -u USERNAME -p PASSWORD -s --files='objects/*,!objects/Account*,site*/**/*' --destructive

You can also run a destructive deploy changeset with a custom destructiveChanges.xml path:

sfdy deploy -u USERNAME -p PASSWORD -s --destructive <destructiveChanges.xml path>

Optionally you can pass the --ignoreWarnings flag to ignore deploy warnings

sfdy deploy -u USERNAME -p PASSWORD -s --destructive <destructiveChanges.xml path> --ignoreWarnings

Warning: Full destructive deploy is deliberately not supported

Warning: This command deletes the metadata files from Salesforce, but they remain on the filesystem

Publish a community

sfdy community:publish -u USERNAME -p PASSWORD -s --community-name=myCoolCommunity

This command will programmatically publish your Experience Bundle. Execute it in a pipeline after a deployment to avoid the manual publish step!

Apply 'standard' patches and renderers to metadata

Sfdy provides several ready-to-use patches that you may find useful. All these patches serve 2 purposes:

  1. Remove useless metadata (not translated fields, useless FLS in permission sets, roles that are automatically managed by salesforce, profile permissions in standard profiles, and stuff created by managed packages at installation time)
  2. Add useful metadata. We want our repo to be the 'source of truth' (ALL profile permissions, not only the enabled ones. ALL object permissions. Profile configuration of objects/applications/tabs that we DON'T want to version because they're not used or we're not the maintainer of that metadata)

All of these patches can be disabled, so you can incrementally adopt them or skip a specific patch if you don't find it useful.

In addition to metadata patching, sfdy provides an out-of-the-box renderer to handle static resource bundles.

First of all, create the configuration file .sfdy.json in the root folder of the salesforce project:

sfdy init

The configuration file is a JSON object:

{
  "permissionSets": {
    "stripUselessFls": true
  },
  "objectTranslations": {
    "stripUntranslatedFields": true,
    "stripNotVersionedFields": true
  },
  "preDeployPlugins": [],
  "postRetrievePlugins": [],
  "renderers": [],
  "profiles": {
    "addAllUserPermissions": false,
    "addDisabledVersionedObjects": true,
    "addExtraObjects": ["*", "!*__?", "!CommSubscription*"],
    "addExtraTabVisibility": ["standard-*"],
    "addExtraApplications": ["standard__*"],
    "stripUserPermissionsFromStandardProfiles": true,
    "stripUnversionedStuff": true
  },
  "roles": {
    "stripPartnerRoles": true
  },
  "staticResources": {
    "useBundleRenderer": ["*.resource"]
  },
  "stripManagedPackageFields": ["et4ae5"]
}

permissionSets

Patch Description
stripUselessFls if stripUselessFls is true, fieldPermissions in which both readable and editable tags are false are removed from the XML. They are totally redundand since a PermissionSet can only add permissions.

objectTranslations

Patch Metadata Description
stripUntranslatedFields Translations, CustomObjectTranslation, GlobalValueSetTranslation, StandardValueSetTranslation if stripUntranslatedFields is true, untranslated tags are removed from the XML.
stripNotVersionedFields CustomObjectTranslation if stripNotVersionedFields is true, translated fields that are not present in the file system in the corresponding .object files, are removed from the XML.

profiles

Patch Description
addAllUserPermissions Salesforce does not retrieve disabled userPermissions. If addAllUserPermissions is true, all permissions are retrieved
addDisabledVersionedObjects Salesforce does not retrieve totally disabled objects. If addDisabledVersionedObjects is true, sfdy retrieves also objectsPermissions of objects that are present in the file system (and of course tracked by version control systems)) but are disabled for the profile
addExtraObjects Sometimes you want to explicitly configure the access level to some objects even if you're not interested in versioning the whole object metadata. Now you can. addExtraObjects is an array of glob patterns of the objects of which objectPermissions you want to add to the profile (the glob patterns match against the <member> content in package.xml)
addExtraTabVisibility Sometimes you want to explicitly set the TabVisibility of some tabs even if you're not interested in versioning the object/tab metadata. Now you can. addExtraTabVisibility is an array of glob patterns of the tabs whose tabVisibilities you want to add to the profile (the glob patterns match against the <member> content in package.xml)
stripUserPermissionsFromStandardProfiles User Permissions are not editable in standard profiles, and they change almost every Salesforce release causing errors that can be avoided. Set this flag to true to automatically remove them
stripUnversionedStuff This flag 'sanitizes' the profiles, removing fieldPermissions, classAccesses, pageAccesses, layoutAssignments that are not related to stuff tracked under version control. I can't really see any reason not to enable this option, that can help avoiding errors made by developers during code/metadata versioning

roles

Patch Description
stripPartnerRoles if stripPartnerRoles is true, roles that end with PartnerUser[0-9]*.role are removed even if a * is used in package.xml. They are automatically created by Salesforce when you create a Partner Account, so there's no need to track them them using version control

staticResources

Renderer Metadata Description
useBundleRenderer StaticResource glob pattern to identify static resource files to handle as an uncompressed bundle. The contentType of the .resource-meta.xml file must be application/zip. This renderer retrieves directly the uncompressed folder instead of the .resource file. If you deploy a single file inside the bundle, the .resource file is rebuilded behind the scenes and deployed in place of the single specified file

other

Patch Metadata Description
stripManagedPackageFields CustomObject, PermissionSet, Profile Array of namespaces of stuff created by managed packages (eg Marketing Cloud) that we don't want to track changes using Version Control. This plugin removes fields, picklistValues, weblinks from CustomObject and fieldPermissions from Profile and PermisissionSet

Build your own plugins

sfdy offers a convenient way to develop your own plugins. This is useful in many cases. A simple use case might be changing named credentials endpoints or email addresses in the workflow's email alerts based on the target org, but the possibilities are endless. You can even query salesforce (rest API or tooling API) to conditionally apply transformations to the metadata based on information coming from the target org.

All the standard plugins are built using the plugin engine of sfdy, so the best reference to understand how to develop a custom plugin is to look at the plugins folder in which all the standard plugins reside.

A plugin is a .js module that exports a function with this signature:

module.exports = async (context, helpers, utils) => {
  //TODO -> Plugin implementation
}

context

  • sfdcConnector - an instance of a Salesforce connector. It exposes 2 methods, query and rest
  • environment - The value of the environment variable environment. It can be used to execute different patches in different sandboxes
  • username - The username used to login
  • log - A log function that should be used instead of console.log if you want to log something. The reason is that, when used as a library, sfdy can accept a custom logger implementation. When used as a command line tool, the log function fallbacks to console.log
  • pkg - A JSON representation of the package.xml
  • config - The content of .sfdy.json (as a JSON object)

helpers

  • xmlTransformer (pattern, callback1) - This helper allows the developer to easily transform one or more metadata (identified by pattern), using a callback function. See examples to understand how to use it
  • modifyRawContent (pattern, callback2) - This helper allows the developer to manipulate the whole metadata file. It is useful if you want to edit a file that is not an XML, or if you want to apply drastic transformations
  • filterMetadata (filterFn) - This helper can be used in a post-retrieve plugin to filter out unwanted metadata
  • requireMetadata (pattern, callback3) - This helper can be used to define dependencies between metadata. For example, a Profile must be retrieved together with CustomObject metadata to also get the list of fieldPermissions. By defining such a dependency using requireMetadata, whenever you retrieve a Profile, all dependent metadata are automatically included in the package.xml and eventually discarded at the end of the retrieve operation, just to retrieve all the related parts of the original metadata you wanted to retrieve
  • addRemapper (regex, callback4) - This helper can be used to map an arbitrary file to a file representing Salesforce metadata. For example, it can be used to instruct sfdy to deploy/retrieve a .resource file when you deploy/retrieve a file inside an uncompressed bundle. regex is a RegExp object defining the matching patterns
callback1 (filename, fJson, requireFiles, addFiles, cleanFiles)
  • filename - The current filename
  • fJson - JSON representation of the XML. You can modify the object to modify the XML
  • requireFiles (filenames: string[]): Promise<Entry[]> - An async function taking an array of glob patterns and returning an array of { fileName: string, data: Buffer } objects representing files. The files are taken from memory if you are requiring files that you are retrieving/deploying, otherwise, they are searched in the filesystem. These files will be added to the files that will be retrieved/deployed unless you specify a filter with the filterMetadata helper. This helper is useful when you want to act on metadata based on another one (for example you need to retrieve the versioned fields from a .object file to add/delete FLS from profiles).
  • addFiles (entries: Entry[]) - A function taking an array of { fileName: string, data: Buffer } objects representing files. This function is similar to requireFiles. The main difference is that requireFiles looks for existing files, while addFiles let you add arbitrary data to the retrieved/deployed files. For this reason, it is best suited to be used in a renderer
  • cleanFiles (filenames: string[]) - A function taking an array of glob patterns. This function lets you specify files that should be deleted. It should be used in the context of an after-retrieve plugin or a transform renderer (for example it is used to clean up an uncompressed staticresource bundle before uncompressing the .resource file coming from Salesforce)
callback2 (filename, file, requireFiles, addFiles, cleanFiles)
  • filename - The current filename
  • file is an object containing a data field. data is a buffer containing the whole file. You can modify data to modify the file
  • requireFiles (filenames: string[]): Promise<Entry[]> - See callback1
  • addFiles (entries: Entry[]) - See callback1
  • cleanFiles (filenames: string[]) - See callback1
filterFn (filename)
  • filename: string - The current filename, including the path (for example classes/MyClass.cls)
callback3 ({ filterPackage, requirePackage })
  • filterPackage (arrayOfMetadata: string[]) - A function taking an array of metadata that should be included together with metadata matched by pattern. The 'companions' will be retrieved only if they are present in the stored package.xml. For example, if you retrieve a profile, the profile will be retrieved together with the referenced CustomObject

  • requirePackage (arrayOfMetadata: string[]) - The same as filterPackage, but the included metadata will be added to package.xml whether they were present before or not. In this case, arrayOfMetadata is an array of 'pseudo' glob patterns (ex. ['CustomApplication/*', 'CustomObject/Account'])

callback4 (fileName, regexp): string
  • fileName: string - The current filename, including the path (for example classes/MyClass.cls)
  • regexp: RegExp - The regexp originally passed to the helper
  • return value: a string representing the mapped filename

utils { parseXml, buildXml, parseXmlNoArray }

Helpers function. See here

To instruct sfdy to use your plugin, you have to configure the path of your plugin in the .sfdy.json file:

{
  "preDeployPlugins": ["sfdy-plugins/my-awesome-plugin.js"],
  "postRetrievePlugins": ["sfdy-plugins/my-wonderful-plugin.js"]
}

You have 2 different 'hooks' to choose from:

  • postRetrievePlugins are executed just before the metadata retrieved from Salesforce is stored on the filesystem
  • preDeployPlugins are executed before deploying metadata to Salesforce

Examples

Change the endpoint of a named credential (better suited as a preDeployPlugin)
module.exports = async ({ environment, log }, helpers) => {
  helpers.xmlTransformer('namedCredentials/*', async (filename, fJson) => {
    log(`Patching ${filename}...`)    
    if(filename === 'idontwanttochangethis.NamedCredential') return

    switch(environment) {
      case 'uat':
        fJson.endpoint = 'https://uat-endpoint.com/restservice'
        break
      case 'prod':
        fJson.endpoint = 'https://prod-endpoint.com/restservice'
        break
      default:
        fJson.endpoint = 'https://test-endpoint.com/restservice'
        break
      
      log('Done')    
    }
  })
}
Remove every field and every apex class that starts with Test_ (better suited as a postRetrievePlugin)
module.exports = async ({ environment, log }, helpers) => {
  helpers.xmlTransformer('objects/*', async (filename, fJson) => {
    log(`Patching ${filename}...`)    
    fJson.fields = (fJson.fields || []).filter(field => !field.FullName[0].startsWith('Test_'))
    log('Done')        
  })

  helpers.filterMetadata(fileName => !/classes\/Test_[^\/]+\.cls$/.test(fileName))
}

Warning: fJson contains the json representation of the metadata file. The root tag of the metadata is omitted for convenience. Every tag is treated as an array

Warning: The callback function ot the xmlTransformer helper MUST return a Promise

Query Salesforce to apply advanced transformations

See this

Define dependencies between metadata

See this

Build your own renderers

A renderer is a .js file that exports an object with this signature:

module.exports = {
  transform: async (context, helpers, utils) => {
    //TODO -> Transform
  },
  untransform: async (context, helpers, utils) => {
    //TODO -> Untransform
  }
}

The transform function is applied after the retrieve operation and after the execution of the post-retrieve plugins. The untransform function is applied as soon as you start a deployment, before the application of the pre-deploy plugins and the actual deployment.

A renderer can be used to transform the metadata in the format you like. For example, you could think to split a .object file into different files, one for fields and one per recordtypes, or to even convert everything in JSON, or represent some information as a .csv file. You can do what best fit your needs.

To instruct sfdy to use your renderer, you have to configure the path of your renderer in the .sfdy.json file:

{
  "renderers": ["sfdy-plugins/my-awesome-renderer.js"]
}

Tip: You do not have to include the renderer whithin your salesforce project to be able to use it, so you can use your plugin referencing it from all of your project workspaces!

Example - Store profiles as JSON files

module.exports = {
  transform: async (context, helpers, { parseXmlNoArray }) => {
    helpers.modifyRawContent('profiles/*', async (filename, file) => {
      const fJson = await parseXmlNoArray(file.data)
      file.data = Buffer.from(JSON.stringify(fJson, null, 2), 'utf8')
    })
  },
  untransform: async (context, helpers, { buildXml }) => {
    helpers.modifyRawContent('profiles/*', async (filename, file) => {
      const fJson = JSON.parse(file.data.toString())
      file.data = Buffer.from(buildXml(fJson), 'utf8')
    })
  }
}

Example - Handle zip staticresources as uncompressed folders

See here

Use sfdy as a library

It's as simple as that:

npm i sfdy

retrieve

const retrieve = require('sfdy/src/retrieve')

retrieve({
  basePath: 'root/folder',
  config: {
    //.sfdy.json like config
  },
  files: [ /*specific files*/ ],
  loginOpts: {
    serverUrl: creds.url,
    username: creds.username,
    password: creds.password
  },
  meta: [/*specific meta*/]
  logger: (msg: string) => logger.appendLine(msg)
}).then(() => console.log('Done!'))

deploy

const deploy = require('sfdy/src/deploy')

deploy({
  logger: (msg: string) => logger.appendLine(msg),
  preDeployPlugins,
  renderers,
  basePath: 'root/folder',
  loginOpts: {
    serverUrl: creds.url,
    username: creds.username,
    password: creds.password
  },
  checkOnly,
  files: ['specific', 'files']
}).then(() => console.log('Done!'))

Changelog

  • 1.7.8

    • Fix error when retrieving profiles that contain '
  • 1.7.7

    • Fix quick deploy polling
  • 1.7.6

    • Fix quick deploy polling
  • 1.7.5

    • Printing deployment id
    • Better error reporting when package.xml is corrupt
  • 1.7.4

    • Bugfixing
  • 1.7.3

    • Bugfixing
  • 1.7.2

    • Handling Territory2* metadata, that works differently from everything else
    • Updated dependencies to fix vulnerabilities
  • 1.7.1

    • Added --ignoreWarnings option to sfdy deploy to ignore deployment warnings. If you don't pass this option, a warning during the deployment will cause the entire deployment to fail. Thanks, maiantialberto
    • Corrected several typos in the documentation
  • 1.7.0

    • Added community:publish command to publish an experience bundle after deployment
    • Added --quick-deploy=deploymentId option to sfdy deploy to perform a quick deploy
  • 1.6.5

    • Fix instanceUrl redirection to new *.sandbox.my.salesforce.com domains when using oauth2
  • 1.6.4

    • Faster getListOfSrcFiles when using **/*
    • Added web scope to Oauth2 auth
    • Faster PermissionSet retrieval
  • 1.6.3

    • Fixed plugins context variable when OAuth2 flow is used
  • 1.6.2

    • Fixed broken soapLogin
  • 1.6.1

    • Minor bug fixing
  • 1.6.0

    • Oauth2 web server auth flow: get a refresh token using an oauth2 flow. If you have enabled MFA you should use this auth method
  • 1.5.3

    • Minor bug fixing
  • 1.5.2

    • Retrieve: When --files option is used, the real list of files is shown instead of the raw glob patterns
    • Bugfixing: glob expression parser now recognizes expressions with {}
    • Bugfixing: negated glob patterns have the highest priority
    • Bugfixing: fix retrieve of in-folder metadata (for example a report in a folder) when using --meta
  • 1.5.1

    • Bugfixing: fix deployment of static resource bundle
  • 1.5.0

    • Deploy: added the possibility to skip some files from deploying. To do that, add for example "excludeFiles": ["lwc/**/__tests__/**/*"] to .sfdy.json
  • 1.4.7

    • Bugfixing: fix another regression handling folder in an in-folder metadata
  • 1.4.6

    • Bugfixing: fix regression handling folders in in-folder metadata
  • 1.4.5

    • Bugfixing: solved the 'multiple metadata types in same folder' issue. It is now possible to retrieve and deploy correctly wave metadata
  • 1.4.4

    • Destructive changeset: added the possibility to pass both a package.xml or a glob pattern. See here. Thanks, zerbfra
    • Bugfixing: fixed crash when --diff returned only files outside the src folder
  • 1.4.3

    • Bugfixing: fixed exclusion glob pattern when using --files option
  • 1.4.2

    • Bugfixing: fixed issue when deploying ExperienceBundle
  • 1.4.1

    • Bugfixing: fixed issue when deploying with --diff a report in a nested folder
  • 1.4.0

    • Transformer API. API to load unrendered files in memory
  • 1.3.6

    • Bugfixing: --diff and --files flag can be used together
  • 1.3.5

  • 1.3.4

  • 1.3.3

    • Bugfixing
    • --files option: now you can pass specific file paths (not glob patterns) even if the files are not present in the filesystem
  • 1.3.2

    • README.md fixes
    • Static resource bundle renderer cleans .resource file when active, and the uncompressed folder when inactive
  • 1.3.1

    • README.md fixes
  • 1.3.0

    • Added addRemapper helper function
    • Added addFiles, cleanFiles utility functions to plugin helpers. (See here)
    • Static resource bundle renderer
  • 1.2.0

  • 1.1.0

    • First release