UNPKG

@microsoft/atlas-css

Version:

Styles backing the Atlas Design System used by Microsoft's Developer Relations.

620 lines (586 loc) • 27.5 kB
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