/ 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  };