@microsoft/atlas-css
Version:
Styles backing the Atlas Design System used by Microsoft's Developer Relations.
620 lines (586 loc) • 27.5 kB
JavaScript
const { readFile, writeFile, mkdir, rm } = require('fs/promises');
const path = require('path');
const os = require('os');
const { quicktype, InputData, jsonInputForTargetLanguage } = require('quicktype-core');
const sass = require('sass');
const { exporter } = require('sass-export');
// sass-export@2.1.2 is unmaintained and compiles token values with the
// deprecated legacy `sass.renderSync` API, wrapping each value in the global
// `inspect()` built-in. Both are deprecated (legacy-js-api is removed in Dart
// Sass 2.0, global-builtin in 3.0) and flood the build with warnings. It
// resolves the same cached `sass` module we require here, so we route its
// single legacy call through the modern `compileString` API and silence the
// global-builtin deprecation, which originates in sass-export's own wrapper
// rather than any Atlas source. The stripped token files contain no
// `@use`/`@import`, so no load paths are needed, and sass-export only reads
// `.css` off the returned object.
const legacyRenderSync = sass.renderSync.bind(sass);
sass.renderSync = options => {
if (options && typeof options.data === 'string') {
const { css } = sass.compileString(options.data, {
loadPaths: options.includePaths,
style: options.outputStyle,
silenceDeprecations: ['global-builtin']
});
return { css };
}
return legacyRenderSync(options);
};
createTokens();
async function createTokens() {
const filePathStem = path.join(process.cwd(), './src/tokens');
const indexPath = path.resolve(filePathStem, 'index.scss');
const filePaths = await getInputFilesFromIndex(filePathStem, indexPath);
checkFileComments(filePaths);
// sass-export concatenates files via stdin and cannot resolve @use paths.
// Create temp copies with @use lines stripped for sass-export processing.
// Also strip namespace prefixes (e.g., palette.$foo → $foo) since sass-export
// relies on files being concatenated and doesn't understand module namespaces.
const tempDir = path.join(os.tmpdir(), 'atlas-tokens-' + Date.now());
await mkdir(tempDir, { recursive: true });
const tempFilePaths = await Promise.all(
filePaths.map(async fp => {
const content = await readFile(fp, 'utf8');
const stripped = content
.split('\n')
.filter(line => !line.trim().startsWith('@use '))
.join('\n')
.replace(/[\w-]+\.\$/g, '$');
const tempPath = path.join(tempDir, path.basename(fp));
await writeFile(tempPath, stripped);
return tempPath;
})
);
/** @type {import('./types').SassExportOptions} */
const options = {
inputFiles: tempFilePaths
};
const exportedTokens = exporter(options).getStructured();
const collection = getSortedOrder(collectTokens(exportedTokens));
// Clean up temp files
await rm(tempDir, { recursive: true, force: true });
const outfolder = './dist';
const outfileStem = path.join(outfolder, 'tokens');
try {
await mkdir(outfolder, { recursive: true });
await Promise.all([
writeFile(`${outfileStem}.json`, JSON.stringify(collection)),
quicktypeJSON('AtlasTokens', JSON.stringify(collection), `${outfileStem}.ts`, 'typescript')
]);
console.log(`Tokens written to "${path.join(process.cwd(), `/dist/${outfileStem}.json`)}".`);
} catch (err) {
throw new Error(`Problem writing output: ${err}`);
}
}
/**
* Read the token index file's content and generate a list of import paths in its respective order
* @param {string} filePathStem tokens directory path
* @param {string} indexPath tokens index file path
* @returns {Promise<string[]>}
*/
async function getInputFilesFromIndex(filePathStem, indexPath) {
/** @type {string[]} */
const filePaths = [];
try {
const indexFile = (await readFile(indexPath)).toString();
const lines = indexFile.split('\n').reduce((arr, line) => {
if (line.includes('@import') || line.includes('@forward')) {
const filePath = line
.replace('@import', '')
.replace('@forward', '')
.replaceAll(`'`, '')
.replace(';', '')
.trim();
arr.push(path.join(filePathStem, filePath));
}
return arr;
}, filePaths);
return lines;
} catch (err) {
throw err;
}
}
/**
* Print a warning if a token file lacks the required sass-export-section comments.
* @param {string[]} paths token file paths
*/
function checkFileComments(paths) {
const promises = paths.map(async path => {
try {
const result = await readFile(path, 'utf8');
if (!result.includes('@sass-export-section')) {
console.log(`Warning: ${path} is missing @sass-export-section annotations.`);
}
} catch (err) {
throw new Error(`Problem reading token files: ${err}`);
}
});
return Promise.all(promises);
}
/**
* Sort the grouped tokens alphabetically
* @param {import('./types').SassExportCollection} collection collection of grouped tokens to be sorted
* @returns {import('./types').SassExportCollection}
*/
function getSortedOrder(collection) {
return Object.fromEntries(Object.entries(collection).sort());
}
/**
* Condense the raw data from sass-export into simple objects
* @param {import('./types').SassExportTokens} tokens raw token data by group
* @returns {import('./types').SassExportCollection}
*/
function collectTokens(tokens) {
/** @type {import('./types').SassExportCollection} */
const collection = {};
for (const [parent, tokenValues] of Object.entries(tokens)) {
//Currently using sass-export-section annotations in the token files for grouping.
//Tokens without annotations will be combined in the variables array.
if (parent === 'variables') {
continue;
}
const collectedValues = tokenValues.reduce(
(
/** @type {import('./types').SassExportCollection} */ all,
/** @type {import('./types').SassExportTokenItem} */ current
) => {
const tokenName = current.name;
/** @type {string | import('./types').SassExportTokenNestedItem} */
const values = current.mapValue
? { ...getNestedTokens(current) }[tokenName]
: convertBoolean(current.compiledValue.replaceAll(' , ', ', '));
all[parent] = {
...all[parent],
[tokenName]: values
};
return all;
},
{}
);
collection[parent] = {
name: parent,
location: `/css/src/tokens/${parent}.scss`,
tokens: collectedValues[parent]
};
}
return collection;
}
/**
* Aggregate nested values when token value is a Sass map
* Note, this currently only targets two levels of nesting.
* Sass-export seems to have trouble parsing maps that have more two levels.
* @param {import('./types').SassExportTokenItem} tokenItem
* @returns {import('./types').SassExportNestedCollection}
*/
function getNestedTokens(tokenItem) {
const { name, compiledValue, mapValue } = tokenItem;
/** @type {import('./types').SassExportNestedCollection} */
const newCompiledValue = {};
const nested = containsMapValue(tokenItem);
if (!nested) {
parseCompiledValue(compiledValue, name, newCompiledValue);
}
if (mapValue && nested) {
for (const child of mapValue) {
parseCompiledValue(child.compiledValue, child.name, newCompiledValue);
}
}
return { [name]: nested ? newCompiledValue : newCompiledValue[name] };
}
/**
* Return a boolean if the token item has nested maps
* Note, this currently only targets two levels of nesting.
* @param {import('./types').SassExportTokenItem} tokenItem
* @returns {boolean}
*/
function containsMapValue(tokenItem) {
if (tokenItem.mapValue) {
for (const values of tokenItem.mapValue) {
if (values.hasOwnProperty('mapValue')) {
return true;
}
}
}
return false;
}
/**
* Return an object of tokens by parsing the token item's compiled value string
* @param {string} compiledValue
* @param {string} name
* @param {{[name: string]: import('./types').SassExportTokenNestedItem}} collection
* @returns {import('./types').SassExportNestedCollection}
*/
function parseCompiledValue(compiledValue, name, collection) {
const parsedValue = compiledValue
.replace(/^\(/, '')
.replace(/\((?![^-]*: )/, '')
.replace(/\)$/, '')
.replaceAll(/\){2,}/g, ')')
.split(/\,(?![^"]*\))/);
return parsedValue.reduce((subCollection, val) => {
const [subKey, value] = val.replaceAll(' ', '').replaceAll('"', '').split(':');
subCollection[name] = {
...subCollection[name],
[subKey]: convertBoolean(value.replace(/^\(/, ''))
};
return subCollection;
}, collection);
}
/**
* Convert boolean string to a boolean value
* @param {string} str
* @returns {boolean | string}
*/
function convertBoolean(str) {
if (str === 'true') {
return true;
}
if (str === 'false') {
return false;
}
return str;
}
/**
*
* @param {string} typeName The name of the global type
* @param {string} jsonString The JSON to parse
* @param {string} outfile Where to save the file
* @param {string} targetLanguage To which language to convert the types
* @returns promise
*/
async function quicktypeJSON(typeName, jsonString, outfile, targetLanguage = 'typescript') {
const jsonInput = jsonInputForTargetLanguage(targetLanguage);
// We could add multiple samples for the same desired
// type, or many sources for other types. Here we're
// just making one type from one piece of sample JSON.
await jsonInput.addSource({
name: typeName,
samples: [jsonString]
});
const inputData = new InputData();
inputData.addInput(jsonInput);
const result = await quicktype({
inputData,
lang: targetLanguage
});
return writeFile(outfile, result.lines.join('\n'));
}
// SIG // Begin signature block
// SIG // MIIoyQYJKoZIhvcNAQcCoIIoujCCKLYCAQExDzANBglg
// SIG // hkgBZQMEAgEFADB3BgorBgEEAYI3AgEEoGkwZzAyBgor
// SIG // BgEEAYI3AgEeMCQCAQEEEBDgyQbOONQRoqMAEEvTUJAC
// SIG // AQACAQACAQACAQACAQAwMTANBglghkgBZQMEAgEFAAQg
// SIG // /AM7K9R/xrUhI65/ZslqEtPEhrLhlsJtyCiqAcI0DxCg
// SIG // gg3SMIIGvDCCBKSgAwIBAgITMwAAANLYbEaxncayoAAA
// SIG // AAAA0jANBgkqhkiG9w0BAQwFADBiMQswCQYDVQQGEwJV
// SIG // UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
// SIG // MTMwMQYDVQQDEypBenVyZSBSU0EgUHVibGljIFNlcnZp
// SIG // Y2VzIENvZGUgU2lnbmluZyBQQ0EwHhcNMjYwMzA1MTkw
// SIG // NjE4WhcNMjcwMzAzMTkwNjE4WjCBgjELMAkGA1UEBhMC
// SIG // VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT
// SIG // B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
// SIG // b3JhdGlvbjEsMCoGA1UEAxMjQXp1cmUgUHVibGljIFNl
// SIG // cnZpY2VzIFJTQSBDb2RlIFNpZ24wggGiMA0GCSqGSIb3
// SIG // DQEBAQUAA4IBjwAwggGKAoIBgQCzWKZG9P7t0hKBimeC
// SIG // eMptumOPqwHZzspivt8o8D+6mS0L1WzQjEvrAtSYvzQI
// SIG // MJXDWAf7ItbG2MxPPOVPtHNQaA8+mr9GdNgsb9bJM2hK
// SIG // 4y4P1elGhGGX8NimfQZW9TeSAZsYe0OfRjMdwIphuyjh
// SIG // 8kbARy3/mnDliXCdGj0u1Ft1qw87+bT6FlZ8Pv+OykUW
// SIG // /WAM28r56qu1C5qm6Xx5LvRBxzk+i4K+Mu17yN3ALlad
// SIG // 5w9uetGBa3WK1eN8ftCd2K0SV0AzpPVcuWbuYhhdifMW
// SIG // trtg4r7pgWxQDBu6gSicXRt7oBFY56ilLxNqzs+UrhTq
// SIG // Kq1taSw1qC3s0KzyxEC/Bz2+OFjnfbeZQxF2dMJ1r2Vw
// SIG // E5nW65uckXepwgofsKgs806jTkt4Br8Kd0PAwmC9qF4T
// SIG // xereCls2gIQmvAJxkACZnCbXGJp7XZ6Xsw+nykQzpiov
// SIG // dlNLwTm/zzkDQSws7z+7m6jZEB3d7hiOqsuz47MEfmUJ
// SIG // qluOioDD6d8CAwEAAaOCAcgwggHEMA4GA1UdDwEB/wQE
// SIG // AwIHgDAfBgNVHSUEGDAWBggrBgEFBQcDAwYKKwYBBAGC
// SIG // N1sBATAdBgNVHQ4EFgQUYqV4NR+DJwhQtHAKZqMo4HsM
// SIG // aRkwVAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jv
// SIG // c29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEW
// SIG // MBQGA1UEBRMNNDY5OTgxKzUwNzE4NzAfBgNVHSMEGDAW
// SIG // gBTxL7qRFnzefVInMfV6+9VYWWk6PTBvBgNVHR8EaDBm
// SIG // MGSgYqBghl5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20v
// SIG // cGtpb3BzL2NybC9BenVyZSUyMFJTQSUyMFB1YmxpYyUy
// SIG // MFNlcnZpY2VzJTIwQ29kZSUyMFNpZ25pbmclMjBQQ0Eu
// SIG // Y3JsMHwGCCsGAQUFBwEBBHAwbjBsBggrBgEFBQcwAoZg
// SIG // aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9j
// SIG // ZXJ0cy9BenVyZSUyMFJTQSUyMFB1YmxpYyUyMFNlcnZp
// SIG // Y2VzJTIwQ29kZSUyMFNpZ25pbmclMjBQQ0EuY3J0MAwG
// SIG // A1UdEwEB/wQCMAAwDQYJKoZIhvcNAQEMBQADggIBAKMM
// SIG // UX0YVUUCTqE50xhl9Pk0hRfkLD14eIdvc+/cwwgKs2mR
// SIG // DMG66/dGboyhlj1pogVsnvh0ByqJoFQYP/bqzQIgXJZM
// SIG // 778xRQ3b5REzU+6sEFdnDBdWe2lT3SoEZD4gPGcqj6BZ
// SIG // w/1dJDLeenv9hUIqZMGE28IZx26CIVXoieUq9Bj69Z0n
// SIG // o0bzuzx20QwOqZ6yhiUT9UtOzSF55T3DJlx3COEYfQQP
// SIG // Oomvj2V6lIVpum3v7OFSjGzTJaCcY/JKYqDHrcDCdmau
// SIG // 3zAiCMkNJk5Nmsg5BPjes7MML/cC+6fSs+L3N1c8ilGt
// SIG // Y7ghulNnBnJv+UZuD7ig5gANLk4wK/I5Y2BtxAtNowsy
// SIG // nthb9Tnck5nYJ6i3HyWUc24TuXXAF++dG+H8Yes16vJL
// SIG // HqH5b2xywVJelLFpYOj7kvGT8jJFZKUAJe1c//D1rBkA
// SIG // Y+BT39mkAEd81B59pTHOwVS9uVxcwGa3yEXryhWbh44q
// SIG // LQE9kfh8bypq2vLeSYZQX5kn3Lxyzk+W41MmabZHSu2R
// SIG // X7ve201BkkpGuq8PL7IdQT8uba6Wqv7Pewiz4HsUfc0l
// SIG // +PhcXgfn1ArJiUW1Sww92sLFk7C0UCufMjtxvxMwkfJc
// SIG // edZWVele21OzOEXSzFf5OkPg6ne7JOvZeJYmCaFITJE+
// SIG // 9ha6TSs+TczP61FR7+ptMIIHDjCCBPagAwIBAgITMwAA
// SIG // AAKyxJOIeFns0wAAAAAAAjANBgkqhkiG9w0BAQwFADBb
// SIG // MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0
// SIG // IENvcnBvcmF0aW9uMSwwKgYDVQQDEyNNaWNyb3NvZnQg
// SIG // UlNBIFNlcnZpY2VzIFJvb3QgQ0EgMjAyMTAeFw0yMTA5
// SIG // MDIxNzQxMTlaFw0zNjA5MDIxNzUxMTlaMGIxCzAJBgNV
// SIG // BAYTAlVTMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y
// SIG // YXRpb24xMzAxBgNVBAMTKkF6dXJlIFJTQSBQdWJsaWMg
// SIG // U2VydmljZXMgQ29kZSBTaWduaW5nIFBDQTCCAiIwDQYJ
// SIG // KoZIhvcNAQEBBQADggIPADCCAgoCggIBAKXd/Sy91nFg
// SIG // seVJOFgeRhVxrcahyp1YGSN0FpOEgEREVb3ND/QgI7I0
// SIG // yd7XG6OE8Vomr5FMxvK8TvJ4Lc6LP9BDz2GSa1M0LlzH
// SIG // KX757/24C0ZndzccA1qQi00+BmmOr4plmxRzTFv4Phdw
// SIG // 8yBPF9GDvClqV8ASvvbitfjaD7dVPOFLb7N7fvt/qWog
// SIG // GN5eis0FXCqVJdmPZZaX2h4iG0otsAhfq8yvSlJ0YwO4
// SIG // i5GDeLQwTsMN1Rf2UAHQKCUYkFsLSQ0mqbaRbDZhB+2p
// SIG // FL/q/c2a6hlHLnapYyfwlNFXkDhwAFWEzfwFHER2oR42
// SIG // UayfN9tsO/p2tWk33CrnHdndJDrIZ6oQ3D+Ngol/TR8B
// SIG // AgXCIM6se6YlLDTsxRwh9QUDq7KVhKy58HGKJUqwgIW0
// SIG // E7cvlzUl0Hft/ebhALZyFDkhof9C5Cq4c/486XLjQq0n
// SIG // buKsFNhQU0yvABR3eohw63Kps66Uma48oE0JmqOxmzrP
// SIG // vrITYcsnByKleiHn+4yq+Ts/KrtqkQwQcuikMPrZwXCt
// SIG // sYkxMUyUn8gr8oew22WDeIQECAM1Cz9TcdJadsrToKqX
// SIG // Qa2bAn/AABAYyogPPONfGvojTI3DlYD42etMa/gPeZJa
// SIG // vX+z7x8d/4eYBnJ9WFSi9q0v+vLOGc3fyM2KQtq5eVbH
// SIG // X5rVyWc6bJ35AgMBAAGjggHCMIIBvjAQBgkrBgEEAYI3
// SIG // FQEEAwIBADAdBgNVHQ4EFgQU8S+6kRZ83n1SJzH1evvV
// SIG // WFlpOj0wVAYDVR0gBE0wSzBJBgRVHSAAMEEwPwYIKwYB
// SIG // BQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w
// SIG // a2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTAZBgkrBgEE
// SIG // AYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYw
// SIG // DwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBQODLFk
// SIG // ab0tsdVrJqZH6lZOgMPtijBmBgNVHR8EXzBdMFugWaBX
// SIG // hlVodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
// SIG // L2NybC9NaWNyb3NvZnQlMjBSU0ElMjBTZXJ2aWNlcyUy
// SIG // MFJvb3QlMjBDQSUyMDIwMjEuY3JsMHMGCCsGAQUFBwEB
// SIG // BGcwZTBjBggrBgEFBQcwAoZXaHR0cDovL3d3dy5taWNy
// SIG // b3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNyb3NvZnQl
// SIG // MjBSU0ElMjBTZXJ2aWNlcyUyMFJvb3QlMjBDQSUyMDIw
// SIG // MjEuY3J0MA0GCSqGSIb3DQEBDAUAA4ICAQBin7PMBnXj
// SIG // nIJ0x++LnudLDWWnZ8dZmJ14DuZfUss/doUThLAM4crr
// SIG // HaTbJoulUUELNd2AnOpX/Z4tenUMT3sjYIdPYyJfIYWP
// SIG // RqfI6Nbz+JVK7RRvn2nl5EEMIuRE6UKS9ZGBbf02a7sb
// SIG // 04E/7BN/NhhrmtS/tVFjfRrrVh9zXku45rqWuCwUTzg3
// SIG // EqxKQ8OGbtjBQtq/Syb/clm5BHsoh3XhMnb9VLv3G1du
// SIG // Nf90FL5/o88XZ4L18nx1lfky2nllY4HIA8PK8AarqAW4
// SIG // iKSTA3EGqn8s/47WtQKT+qED2YbZXVOYL+L7vQDCnFbw
// SIG // hgAx6ucuMz7Ae1rqibg3AjsC7U5M3oA/vqAHDKDA3mdM
// SIG // 5D6L/ZEdQgaG20HhUOSQ+CiQD3TyHSiVCfVMuTv83IiK
// SIG // Cni3LW/23tHC2tbN57rlhMcoyjIi+IVd7j7s41MFBaDw
// SIG // JrmfXn/YM+lR/5QqvO7zWAbbr/XU531v3jr/jBilmrqt
// SIG // 6U/b7y8TXyA9nYxV9iSMFmcbyIi2xgdcAHhxnpXcvcvy
// SIG // FWET6YiJiyeSJZwwJv8gwXiBF+Zh0IHArl6KVsbAdsAT
// SIG // uP1TCEBpPynXZmkviIEWPtnv315ZjTC7nPoOpSnOVaO7
// SIG // wZztrOefZunI5fBxw7mG1oyoRnADZawiFsVo9J/cDu15
// SIG // ErRCfDQRhwSiBTGCGk8wghpLAgEBMHkwYjELMAkGA1UE
// SIG // BhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh
// SIG // dGlvbjEzMDEGA1UEAxMqQXp1cmUgUlNBIFB1YmxpYyBT
// SIG // ZXJ2aWNlcyBDb2RlIFNpZ25pbmcgUENBAhMzAAAA0ths
// SIG // RrGdxrKgAAAAAADSMA0GCWCGSAFlAwQCAQUAoIGQMBkG
// SIG // CSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMC8GCSqGSIb3
// SIG // DQEJBDEiBCDqYlGcvkIBD6nPW9B9BR3CKKu2fy3e52f1
// SIG // QlYkTCPhjzBCBgorBgEEAYI3AgEMMTQwMqAUgBIATQBp
// SIG // AGMAcgBvAHMAbwBmAHShGoAYaHR0cDovL3d3dy5taWNy
// SIG // b3NvZnQuY29tMA0GCSqGSIb3DQEBAQUABIIBgJOTqHtz
// SIG // aEzNTslSaqppS7wlmsB1gWg7fdEWeepKE1OaapYpnUuU
// SIG // BEmUQ+7o8S4eESzs9JUHow0+bVN9Rb2r717O7n+8UI7+
// SIG // gBpE7bUPvTPLtsrFRdXloh9wq/UCpSfKjvhBEnopmCQu
// SIG // m9YaHtpslDrmfsVbmNA1LR3fDoS4PB93RExpq7o++iKQ
// SIG // Hp4+mW6hxQcWmbeDmi+zUGEdNfa4PJpHuQUf2SrIsiAy
// SIG // /WPQ54nqAwBzWEOCUVzPD9pZju+WMbdZ5nGz/epb2nmS
// SIG // A4RXaJDCQenosNCCWG+q/g7M0Slfj6ZJ9mzeuFHcnOjH
// SIG // xtslvPYXARbgGOeWXgCdfWFg/yFbN84pWzawQc03BBsH
// SIG // QqawEBaAw7MItMwpmvxqjQuPPV0bNivYo90ph9aMT2xD
// SIG // ezf8blEMHOB2vqtjk5RIK0T77RKaB8Z7kL2j0OvuQyV6
// SIG // UXSnl9jm9JUDBiE7esGxVoIRdgWN1RKhVWW47JXTnHDK
// SIG // xcsPi937NYZMNZN1UWkc9qGCF5QwgheQBgorBgEEAYI3
// SIG // AwMBMYIXgDCCF3wGCSqGSIb3DQEHAqCCF20wghdpAgED
// SIG // MQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsqhkiG9w0BCRAB
// SIG // BKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0G
// SIG // CWCGSAFlAwQCAQUABCDwDZzZbdhzHJAg5YyQrpxAav6q
// SIG // SaRvvM/tbys30a34jQIGarUnmhsaGBMyMDI2MTAxOTIz
// SIG // MzEzNi45NjJaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQG
// SIG // EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
// SIG // BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
// SIG // cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
// SIG // cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxk
// SIG // IFRTUyBFU046QTkzNS0wM0UwLUQ5NDcxJTAjBgNVBAMT
// SIG // HE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WgghHq
// SIG // MIIHIDCCBQigAwIBAgITMwAAAifVwIPDsS5XLQABAAAC
// SIG // JzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzET
// SIG // MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk
// SIG // bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
// SIG // aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt
// SIG // cCBQQ0EgMjAxMDAeFw0yNjAyMTkxOTQwMDRaFw0yNzA1
// SIG // MTcxOTQwMDRaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UE
// SIG // CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
// SIG // MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUw
// SIG // IwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRp
// SIG // b25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTkz
// SIG // NS0wM0UwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBU
// SIG // aW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
// SIG // AQUAA4ICDwAwggIKAoICAQDixWy1fDOSL4qj3A1pady+
// SIG // elIDLwnF3UuLzJIOWwGHcEgrxxwtnyviUIDmmxylTUl1
// SIG // u+2rBPp2zT4BwwQhvGaJpExqvPLlDFlbfmSflKI86eFq
// SIG // ofiZ7j8NTRO4l7wGg9Njm+muNauTcFW2qdfIjKE950Ok
// SIG // rm9MnMOGYy+fibNYdxTPRPq1T4MLZK3s3vdMyMEOldcO
// SIG // QkSKpxD6/1Gk6gOmCu2KgI8f0ex6vYxnKDl9W0OLSEa/
// SIG // 6y82oIbsm+1QBifOQ47xWKTG1CmvtGr85LzA75/MAcUm
// SIG // Rw5/of/qET0UFV1WulMcJrI6DASAsNCNB+6WLrotuBZA
// SIG // j+VMlqbn5RMZ6Q4IY7JwaAiIXh7VjxrnwUOYZG8WEGhf
// SIG // rA98di+7LEn9AqvvEOyG+UQcjVhCCbMGXigJXSApeyeW
// SIG // upCsD0jgQMNCxfB5BLBDWxgdY3dJBEPgxfkgTDQLBggt
// SIG // Vv2d5CYxHKgIItB4bI5eSb5jkIG2WotnFetT0legpw/E
// SIG // ozwf39ao6tENY21eVWIzRw/GsmvwjYQF6vVrxOD0pGVs
// SIG // fqGF8s3VPeY7hI2TxHFMqNA0IB/a2NLY7JTxYAKAP/11
// SIG // EJZt7xbqDLMgD1YDdGEzGpQijm3nAPCL2CebP/jmu90a
// SIG // bJ2W425yglGHTI/nCBrwSpfRCgwzrfFelJaCKM6+35aF
// SIG // fwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFNLW58N4MGSG
// SIG // 6ud7jWqgT92orfReMB8GA1UdIwQYMBaAFJ+nFV0AXmJd
// SIG // g/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0
// SIG // dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3Js
// SIG // L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy
// SIG // MDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYB
// SIG // BQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w
// SIG // a2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFt
// SIG // cCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQC
// SIG // MAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0P
// SIG // AQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQAqncud
// SIG // 4PSC1teb2H6nRuy7sDiKK13FXJirVB4Tfwjdo2Mb+QL4
// SIG // j7wZ/k4G9P0CANHZFrDQcK0VFDTysrYu8Z0Aha14acDZ
// SIG // PsyIoPvAGRRhaHEuf7NckRjkfa/ylo1KyII8jbL9N9sJ
// SIG // AqBPL8V4FNBjljv+1GHDOw127rZz5ZSTPoAPb2SA0v5y
// SIG // DgcpUMfxglPyp6cnPPoQpTtD9OGx8Dwm2P+o1TPxBIy6
// SIG // I0T9RauulogVCvKwflfeLTcKAvnSG1rCjerSXmU1DNXO
// SIG // sAD/bsrSjgbX5mAbD7XTRMF/vawAWESFcn/BjjizxeWZ
// SIG // b00aYSlkJA2rVtFlMM481aVWXdAbXPP5RzUiWTlgyHf/
// SIG // G7lCxHYWGIZuB13T3aI6Y8mEgn/ou40aiFJo8r0+i0P5
// SIG // GdNneWtxiR0CMKUfko+5s/73cwe1Wfp8BKXa270cicVQ
// SIG // asFf5sRV7pFm+V7fNRXwCu7anTOmga76zO7/2t+zOlib
// SIG // vphT+Q6Zd+B2qYsSn4xBaY+YzHpnycLW5cvJyhPxBCcb
// SIG // 1oRYfhRzCADb2utI2EtGCjc2P2ii4LyR4QMb/n8cOweL
// SIG // 9IqVTKKzzVk+zZJxV3vrp4LyuQXw0O30la6BcHdNAAAB
// SIG // 9UC83zs3G9d+AlIfZLM97tMUNKWjbBpIirFx6LTDFXVt
// SIG // ZQd7hqzLYByjbjH0ujCCB3EwggVZoAMCAQICEzMAAAAV
// SIG // xedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQELBQAwgYgx
// SIG // CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
// SIG // MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
// SIG // b3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jv
// SIG // c29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
// SIG // MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIy
// SIG // NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
// SIG // bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT
// SIG // FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd
// SIG // TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIi
// SIG // MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM
// SIG // 57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/
// SIG // bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1
// SIG // jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzBRMhx
// SIG // XFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41
// SIG // JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP
// SIG // 1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCOMcg1KL3j
// SIG // tIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF
// SIG // 50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg
// SIG // 3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F
// SIG // 37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
// SIG // lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlM
// SIG // jgK8QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLX
// SIG // pyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJ
// SIG // YfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzF
// SIG // ER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+e
// SIG // FnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYB
// SIG // BAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS
// SIG // /mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0A
// SIG // XmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYMKwYB
// SIG // BAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93
// SIG // d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBv
// SIG // c2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZ
// SIG // BgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8E
// SIG // BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAW
// SIG // gBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBN
// SIG // MEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
// SIG // cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAx
// SIG // MC0wNi0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsG
// SIG // AQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20v
// SIG // cGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIz
// SIG // LmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4Swf
// SIG // ZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518Jx
// SIG // Nj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6
// SIG // th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9I
// SIG // dQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2pFaq
// SIG // 95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBew
// SIG // VIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7
// SIG // bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa
// SIG // 2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzH
// SIG // VG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZ
// SIG // c9d/HltEAY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakUR
// SIG // R6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
// SIG // QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKi
// SIG // excdFYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+AN
// SIG // uOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/
// SIG // OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQd
// SIG // VTNYs6FwZvKhggNNMIICNQIBATCB+aGB0aSBzjCByzEL
// SIG // MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
// SIG // EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
// SIG // c29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9z
// SIG // b2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEnMCUGA1UECxMe
// SIG // blNoaWVsZCBUU1MgRVNOOkE5MzUtMDNFMC1EOTQ3MSUw
// SIG // IwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2
// SIG // aWNloiMKAQEwBwYFKw4DAhoDFQAjHzqthPwO0GDckDMA
// SIG // 6x54lIiMKqCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMw
// SIG // EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
// SIG // b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
// SIG // b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
// SIG // IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7oCXWzAi
// SIG // GA8yMDI2MTAxOTEzMTkyM1oYDzIwMjYxMDIwMTMxOTIz
// SIG // WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDugJdbAgEA
// SIG // MAcCAQACAhgSMAcCAQACAhP/MAoCBQDugejbAgEAMDYG
// SIG // CisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAI
// SIG // AgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEL
// SIG // BQADggEBABq1xQlrtNUMJvdAerfEoStowqFZisj9yhsF
// SIG // tGfUZGu+ionIFVEVnL0gU4E+Ohh9IgeFY1k+EOlnFgA1
// SIG // 4WGrxm8Qt4Ih6g26ZLrMl10umwMkzfrTUCTLZzKNhHZ1
// SIG // oHBai5SRjzxtzWXW2j3QB+frNWz/xJjKxJA87BYDRzIV
// SIG // H5axrFj9WVsFR4WpZZZ0cCaMKsEOnTLtAjiiSHgv7joC
// SIG // 6uQOjW8sgCrYNC1ntHqbJvzU+Cy1n9odf3zVEoQX1y8K
// SIG // /86p1ebDPBViEpBmFCb35S1dafxnMQB2ZoTgkxQCNZnz
// SIG // bwJGmsDjVBxbH9PaoRmXDWTqy6GY66SyIG35B79saBYx
// SIG // ggQNMIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEG
// SIG // A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
// SIG // ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
// SIG // MSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ
// SIG // Q0EgMjAxMAITMwAAAifVwIPDsS5XLQABAAACJzANBglg
// SIG // hkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqG
// SIG // SIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCCJuvj+ZnDt
// SIG // 9nDJqjGKjehaGDbde425iP64t26majTT5zCB+gYLKoZI
// SIG // hvcNAQkQAi8xgeowgecwgeQwgb0EIOXnARo1oVIcOLJK
// SIG // DqlE0adq/jZ9TXdlnXWRcXGThBFyMIGYMIGApH4wfDEL
// SIG // MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
// SIG // EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv
// SIG // c29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9z
// SIG // b2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIn1cCD
// SIG // w7EuVy0AAQAAAicwIgQgVa/KqwrfnftknM70ZMrmSNmc
// SIG // HHkD/MlDJk0wLpx9Yc8wDQYJKoZIhvcNAQELBQAEggIA
// SIG // nwTk+/MAlZi/4soMXbHmPEKon4/ysniv7gz8eweFqEdN
// SIG // 8QVaLJ6mhSqkDZzxZC6ar5YKlshVOrRvQRYW4XfP/eGx
// SIG // +EFGmeUhiC7oXcoz//K8EmqWMek+kTFGIXe6urC+z0+5
// SIG // eY3n4GEfAuqjrZhjPhalsqHMsA4b/FIZetoLz6fm3k/7
// SIG // ryJ4/aazMOECh7oVU6Xj+IlI7r7GBu4oJavgycgaR4ye
// SIG // GQc+DXWb33ksvjoxwPvPaejptNVFib8rQIQ6PsE+hPya
// SIG // UcwLC1CtLpJD5heciD2ZXbCEd4P4eP69T8uL+ktmhBcQ
// SIG // 5W90us+QhUF4bvZBjPysc8MvkfKYXrEBBjgbG9xb5a50
// SIG // kP9t8mJ+RZdZl8KYUTAmQClUyAiCODrZhgoaul9xXQUb
// SIG // 0lqrBk75HH/od5E0tthuLIsi+CPzqqtQFMYRjg25vwdl
// SIG // CthIwunKo8MODHOWD5GKm+jPM7TI+W7LB64493F0GZRd
// SIG // UQ65AvVEjmNQTGbTqOgZRQkm+dfIvQ/DQeZWN8GYt0s3
// SIG // HjUOl387p0jTwFYJSsiJuZNSUmrUOy8jPjwUeAxH1Rzd
// SIG // ikrWGtgEHaYLQI2f0/9EUJWb43P7DBDM5JbCgEzM/Un5
// SIG // bwA9reIGTgxzAior4/FoVYP1QudFngnj0JyEX6Mvn3CO
// SIG // 5y17V11U3uetFaafkffAA/Q=
// SIG // End signature block