/ sla.js
sla.js
1 const fs = require('fs'); 2 const path = require('path'); 3 const execa = require('execa'); 4 const tempy = require('tempy'); 5 6 function getRtfUnicodeEscapedString(text) { 7 let result = ''; 8 for (let i = 0; i < text.length; i++) { 9 if (text[i] === '\\' || text[i] === '{' || text[i] === '}' || text[i] === '\n') { 10 result += `\\${text[i]}`; 11 } else if (text[i] === '\r') { 12 // ignore 13 } else if (text.charCodeAt(i) <= 0x7F) { 14 result += text[i]; 15 } else { 16 result += `\\u${text.codePointAt(i)}?`; 17 } 18 } 19 20 return result; 21 } 22 23 function wrapInRtf(text) { 24 return '\t$"7B5C 7274 6631 5C61 6E73 695C 616E 7369"\n' + 25 '\t$"6370 6731 3235 325C 636F 636F 6172 7466"\n' + 26 '\t$"3135 3034 5C63 6F63 6F61 7375 6272 7466"\n' + 27 '\t$"3833 300A 7B5C 666F 6E74 7462 6C5C 6630"\n' + 28 '\t$"5C66 7377 6973 735C 6663 6861 7273 6574"\n' + 29 '\t$"3020 4865 6C76 6574 6963 613B 7D0A 7B5C"\n' + 30 '\t$"636F 6C6F 7274 626C 3B5C 7265 6432 3535"\n' + 31 '\t$"5C67 7265 656E 3235 355C 626C 7565 3235"\n' + 32 '\t$"353B 7D0A 7B5C 2A5C 6578 7061 6E64 6564"\n' + 33 '\t$"636F 6C6F 7274 626C 3B3B 7D0A 5C70 6172"\n' + 34 '\t$"645C 7478 3536 305C 7478 3131 3230 5C74"\n' + 35 '\t$"7831 3638 305C 7478 3232 3430 5C74 7832"\n' + 36 '\t$"3830 305C 7478 3333 3630 5C74 7833 3932"\n' + 37 '\t$"305C 7478 3434 3830 5C74 7835 3034 305C"\n' + 38 '\t$"7478 3536 3030 5C74 7836 3136 305C 7478"\n' + 39 '\t$"616C 5C70 6172 7469 6768 7465 6E66 6163"\n' + 40 '\t$"746F 7230 0A0A 5C66 305C 6673 3234 205C"\n' + 41 `${serializeString('63663020' + Buffer.from(getRtfUnicodeEscapedString(text)).toString('hex').toUpperCase() + '7D')}`; 42 } 43 44 function serializeString(text) { 45 return '\t$"' + text.match(/.{1,32}/g).map(x => x.match(/.{1,4}/g).join(' ')).join('"\n\t$"') + '"'; 46 } 47 48 module.exports = async (dmgPath, dmgFormat) => { 49 // Valid SLA filenames 50 const rawSlaFile = path.join(process.cwd(), 'sla.r'); 51 const rtfSlaFile = path.join(process.cwd(), 'license.rtf'); 52 const txtSlaFile = path.join(process.cwd(), 'license.txt'); 53 54 const hasRaw = fs.existsSync(rawSlaFile); 55 const hasRtf = fs.existsSync(rtfSlaFile); 56 const hasTxt = fs.existsSync(txtSlaFile); 57 58 if (!hasRaw && !hasRtf && !hasTxt) { 59 return; 60 } 61 62 const tempDmgPath = tempy.file({extension: 'dmg'}); 63 64 // UDCO or UDRO format is required to be able to unflatten 65 // Convert and unflatten DMG (original format will be restored at the end) 66 await execa('/usr/bin/hdiutil', ['convert', '-format', 'UDCO', dmgPath, '-o', tempDmgPath]); 67 await execa('/usr/bin/hdiutil', ['unflatten', tempDmgPath]); 68 69 if (hasRaw) { 70 // If user-defined sla.r file exists, add it to dmg with 'rez' utility 71 await execa('/usr/bin/rez', ['-a', rawSlaFile, '-o', tempDmgPath]); 72 } else { 73 // Generate sla.r file from text/rtf file 74 // Use base.r file as a starting point 75 let data = fs.readFileSync(path.join(__dirname, 'base.r'), 'utf8'); 76 let plainText = ''; 77 78 // Generate RTF version and preserve plain text 79 data += '\ndata \'RTF \' (5000, "English") {\n'; 80 81 if (hasRtf) { 82 data += serializeString((fs.readFileSync(rtfSlaFile).toString('hex').toUpperCase())); 83 ({stdout: plainText} = await execa('/usr/bin/textutil', ['-convert', 'txt', '-stdout', rtfSlaFile])); 84 } else { 85 plainText = fs.readFileSync(txtSlaFile, 'utf8'); 86 data += wrapInRtf(plainText); 87 } 88 89 data += '\n};\n'; 90 91 // Generate plain text version 92 // Used as an alternate for command-line deployments 93 data += '\ndata \'TEXT\' (5000, "English") {\n'; 94 data += serializeString(Buffer.from(plainText, 'utf8').toString('hex').toUpperCase()); 95 data += '\n};\n'; 96 97 // Save sla.r file, add it to DMG with `rez` utility 98 const tempSlaFile = tempy.file({extension: 'r'}); 99 fs.writeFileSync(tempSlaFile, data, 'utf8'); 100 await execa('/usr/bin/rez', ['-a', tempSlaFile, '-o', tempDmgPath]); 101 } 102 103 // Flatten and convert back to original dmgFormat 104 await execa('/usr/bin/hdiutil', ['flatten', tempDmgPath]); 105 await execa('/usr/bin/hdiutil', ['convert', '-format', dmgFormat, tempDmgPath, '-o', dmgPath, '-ov']); 106 };