Skip to content

Instantly share code, notes, and snippets.

@tarot
Created June 19, 2015 11:26
Show Gist options
  • Select an option

  • Save tarot/8c3eabd446b6befca8f3 to your computer and use it in GitHub Desktop.

Select an option

Save tarot/8c3eabd446b6befca8f3 to your computer and use it in GitHub Desktop.
ProfileのfieldPremissionとobjectPermissionを全部trueにする
{
"dependencies": {
"bluebird": "^2.9.9",
"commander": "^2.7.1",
"dotenv": "^0.5.1",
"glob": "^5.0.3",
"jsforce": "^1.4.0",
"jszip": "^2.5.0",
"lodash": "^3.5.0",
"shelljs": "^0.4.0",
"streamifier": "^0.1.0",
"temp": "^0.8.1",
"unorm": "^1.3.3",
"unzip": "^0.1.11",
"xml2js": "^0.4.6"
}
}
jsforce = require 'jsforce'
Promise = require 'bluebird'
unzip = require 'unzip'
_ = require 'lodash'
xml2js = require 'xml2js'
glob = require 'glob'
path = require 'path'
fs = require 'fs'
unorm = require 'unorm'
temp = require('temp').track()
jszip = require 'jszip'
streamifier = require 'streamifier'
program = require 'commander'
require 'shelljs/global'
program
.option '-d, --deploy', 'deploy actually (default: dry run)'
.option '--deploy-dir <dir>', 'tmpdir for deploy packages (default: tmpdir)'
.option '--retrieve-dir <dir>', 'retrieve destination (default: tmpdir)'
.option '-u, --username <username>', 'username (default: process.env.SALESFORCE_USERNAME)'
.option '-p, --password <password>', 'password (default: process.env.SALESFORCE_PASSWORD)'
.option '--host <host>', 'host (default: process.env.SALESFORCE_HOST)'
.parse process.argv
require('dotenv').load()
username = program.username || process.env.SALESFORCE_USERNAME
password = program.password || process.env.SALESFORCE_PASSWORD
host = program.host || process.env.SALESFORCE_HOST || 'login.salesforce.com'
host = "https://#{host}" unless host.match /^https:\/\//
api_version = process.env.SALESFORCE_API_VERSION || '33.0'
dry_run = !program.deploy
Promise.promisifyAll xml2js.Parser.prototype
_.mixin
# xml2jsのexplicitArray: false対策。jsforceのlistMetadataもこれを使っている
wrap_array: (x) ->
return [] unless x?
if @isArray x then x else [x]
# 3つずつ取得したlistMetadataを統合する
_.extend require('jsforce/lib/api/metadata').prototype,
list_all: (queries, version) ->
queries = [] unless _.isArray queries
metadata = @
Promise.reduce _.chunk(queries, 3), (all, queries) ->
metadata.list(queries, version).then (res) -> all.concat _.wrap_array(res)
, []
class Manifest
parse_list_metadata_result: (list_metadata_result) ->
types = _(list_metadata_result)
.wrap_array()
.groupBy (e) -> e.type
.mapValues (vs) -> vs.map (e) -> e.fullName
.value()
_.extend @, types
return @
to_unpackaged_request: (version = api_version) ->
unpackaged:
version: version
types: _.map @, (v, k) -> name: k, members: v
to_xml: (version = api_version) ->
pkg =
'$': xmlns: 'http://soap.sforce.com/2006/04/metadata'
types: _.map @, (v, k) -> name: k, members: v
version: version
new xml2js.Builder().buildObject(Package: pkg)
class Profile
constructor: (name, xml) ->
@_profile = xml.Profile
@_profile.fieldPermissions = _.wrap_array @_profile.fieldPermissions
@_profile.objectPermissions = _.wrap_array @_profile.objectPermissions
@name = name
to_meta: ->
profile = _.pick @_profile, ['$', 'custom', 'fieldPermissions', 'userLicense', 'objectPermissions']
if profile.fieldPermissions?
profile.fieldPermissions = profile.fieldPermissions.map (field_permission) ->
editable: 'true', readable: 'true', field: field_permission.field
if profile.objectPermissions?
profile.objectPermissions = profile.objectPermissions.map (e) ->
allowCreate: 'true'
allowDelete: 'true'
allowEdit: 'true'
allowRead: 'true'
modifyAllRecords: 'true'
viewAllRecords: 'true'
object: e.object
Profile: profile
[retrieve_dir, deploy_dir] = [program.retrieveDir, program.deployDir].map (dir) ->
if dir
rm '-rf', dir
mkdir '-p', dir
return dir
else temp.mkdirSync()
connection = new jsforce.Connection loginUrl: host
connection.login(username, password)
.then ->
connection.version = api_version
connection.metadata.pollInterval = 5000
connection.metadata.pollTimeout = connection.metadata.pollInterval * 50
.then ->
console.log 'metadata.describe...'
connection.metadata.describe()
.then (describe) ->
console.log 'list metadata...'
queries = _(describe.metadataObjects)
.filter (e) -> e.xmlName.match /^(CustomObject|Profile)$/
.map (e) ->
_.toArray(e.childXmlNames).concat [e.xmlName]
.flatten()
.map (e) -> type: e
.value()
connection.metadata.list_all queries
.then (meta) ->
console.log 'retrieve and unzip...'
manifest = new Manifest().parse_list_metadata_result meta
new Promise (resolve, reject) ->
connection.metadata.retrieve manifest.to_unpackaged_request()
.stream()
.pipe unzip.Extract(path: retrieve_dir)
.on 'close', resolve
.on 'error', reject
.then ->
console.log 'create profiles...'
mkdir '-p', path.join(deploy_dir, 'profiles')
Promise.all(
glob.sync(path.join retrieve_dir, 'unpackaged', 'profiles', '*.profile')
.map (file) ->
new xml2js.Parser(explicitArray: false).parseStringAsync(fs.readFileSync file)
.then (xml) -> new Profile(unorm.nfc(path.basename(file, '.profile')), xml)
)
.each (profile) ->
dest_file = path.join deploy_dir, 'profiles', "#{profile.name}.profile"
fs.writeFileSync dest_file, new xml2js.Builder().buildObject(profile.to_meta())
.then (profiles) ->
console.log 'create package.xml...'
manifest = new Manifest()
manifest.Profile = profiles.map (e) -> e.name
dest_file = path.join deploy_dir, 'package.xml'
fs.writeFileSync dest_file, manifest.to_xml()
.then ->
console.log 'zip...'
glob.sync(path.join deploy_dir, '**', '*').reduce (zip, e) ->
stat = fs.statSync(e)
if stat.isDirectory()
zip.folder unorm.nfc(path.join('unpackaged', path.relative(deploy_dir, e)))
else if stat.isFile()
zip.file unorm.nfc(path.join('unpackaged', path.relative(deploy_dir, e))), fs.readFileSync(e)
return zip
, new jszip()
.then (zip) ->
console.log 'deploy...'
pkg = streamifier.createReadStream zip.generate(type: 'nodebuffer', platform: process.platform)
connection.metadata.deploy pkg, checkOnly: dry_run, rollbackOnError: true
.complete (error, result) ->
if error?
console.error error
else if !result.success
connection.metadata.checkDeployStatus result.id, true
.then (result) ->
console.error result
result.details.componentFailures.forEach (e) -> console.error e
else console.log result
.fail ->
console.error arguments
@tarot
Copy link
Copy Markdown
Author

tarot commented Jun 19, 2015

使い方

$ coffee update_premission.coffee

クレデンシャルは.env

SALESFORCE_USERNAME=
SALESFORCE_PASSWORD=
SALESFORCE_HOST=https://login.salesforce.com
SALESFORCE_API_VERSION=33.0

コマンドラインオプションでもOK

  Options:

    -h, --help                 output usage information
    -d, --deploy               deploy actually (default: dry run)
    --deploy-dir <dir>         tmpdir for deploy packages (default: tmpdir)
    --retrieve-dir <dir>       retrieve destination (default: tmpdir)
    -u, --username <username>  username (default: process.env.SALESFORCE_USERNAME)
    -p, --password <password>  password (default: process.env.SALESFORCE_PASSWORD)
    --host <host>              host (default: process.env.SALESFORCE_HOST)

-dオプション付けないとcheckOnly、--deploy-dirオプションや--retrieve-dirオプションを付ると中間ファイルを確認できる(デフォルトはtempDir)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment