@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
1 lines • 10.6 kB
Source Map (JSON)
{"version":3,"sources":["../../../packages/tools/gulp-update-notes-resource/index.ts"],"names":[],"mappings":"","file":"index.d.ts","sourcesContent":["'use strict';\r\n\r\nimport { Buffer } from 'buffer';\r\nimport fs from 'fs';\r\nimport log from 'fancy-log';\r\nimport pluginError from 'plugin-error';\r\nimport through2 from 'through2';\r\nimport Vinyl from 'vinyl';\r\nimport * as util from '../utilities';\r\n\r\nconst PLUGIN_NAME = 'gulp-update-notes-resource';\r\n\r\ninterface Options {\r\n /**\r\n * resource name.\r\n */\r\n resourceName: string;\r\n\r\n /**\r\n * path of update notes file.\r\n */\r\n updateNotes: string;\r\n\r\n /**\r\n * depth of folder structure from the locale folder. (default is 1)\r\n */\r\n localeOffset: number;\r\n}\r\n\r\nfunction gulpUpdateNotesResources(options: Options) {\r\n function findResourceKey(node: any, results: string[]): void {\r\n const match = 'resources:strings:';\r\n const keys = Object.keys(node);\r\n if (keys && keys.length > 0) {\r\n keys.forEach(key => {\r\n const target = node[key];\r\n if (target) {\r\n if (typeof target === 'string') {\r\n if (target.indexOf(match) === 0) {\r\n const value = target.substring(match.length);\r\n if (results.indexOf(value) < 0) {\r\n results.push(value);\r\n }\r\n }\r\n } else {\r\n findResourceKey(target, results);\r\n }\r\n }\r\n });\r\n }\r\n }\r\n\r\n // override options settings if not specified.\r\n options = <Options>Object.assign(\r\n {\r\n updateNotes: 'packages/shell/src/assets/updates/update-notes.json',\r\n localeOffset: 1\r\n },\r\n options || {});\r\n\r\n const resources: any = {};\r\n const keyToResources: any = {};\r\n\r\n return through2.obj(\r\n /**\r\n * Transform\r\n */\r\n function (file, encoding, callback) {\r\n let locale = 'default';\r\n if (resources.default) {\r\n // if default is set already, find current locale from the file path.\r\n const pathSegments = file.history[0].split('\\\\');\r\n locale = pathSegments[pathSegments.length - (2 + options.localeOffset)];\r\n }\r\n\r\n resources[locale] = {};\r\n if (resources.default) {\r\n // if default is set already, pre-fill all resource items from default.\r\n for (const resourceId in resources.default) {\r\n if (resources.default.hasOwnProperty(resourceId)) {\r\n resources[locale][resourceId] = resources.default[resourceId];\r\n }\r\n }\r\n }\r\n\r\n // remove comments, /* multiline comment */ and // one line comment and \"//\": \"JSON element comment\"\r\n const content = file.contents.toString('utf8')\r\n .replace(/(\\/\\*([^*]|[\\n]|(\\*+([^*/]|[\\n])))*\\*\\/+)|( +\\/\\/.*)|( +\\\"\\/\\/\\\".*)/g, '');\r\n const object = JSON.parse(content);\r\n if (object[options.resourceName] && object[options.resourceName]['App']['UpdateNotes']) {\r\n // supports <ResourceName>.Manifest.<KeyName> format.\r\n const items = object[options.resourceName]['App']['UpdateNotes'];\r\n for (const key in items) {\r\n if (items.hasOwnProperty(key)) {\r\n // Each key will have two children, title and description\r\n // no need to separate in JSON form\r\n resources[locale][key] = items[key];\r\n\r\n // Used to keep an alternate mapping we use later\r\n if (!keyToResources.hasOwnProperty(key)) {\r\n keyToResources[key] = {};\r\n }\r\n\r\n if (keyToResources.hasOwnProperty(key) &&\r\n !keyToResources[key].hasOwnProperty(locale)) {\r\n keyToResources[key][locale] = {};\r\n }\r\n keyToResources[key][locale] = items[key];\r\n }\r\n }\r\n } else {\r\n // supports <ResourceName>_App_UpdateNotes format.\r\n const resourceUpdateNotes = options.resourceName + '_App_UpdateNotes_';\r\n for (const name in object) {\r\n if (object.hasOwnProperty(name)) {\r\n const index = name.indexOf(resourceUpdateNotes);\r\n if (index === 0) {\r\n // Check if this resource has the specific substring or not\r\n const value = object[name];\r\n\r\n // If it contains '_' we split it so we keep only the key\r\n const resourceId2 = name.substring(resourceUpdateNotes.length);\r\n\r\n // Split to separate <KeyValue>_<type>\r\n // Possible types are title or description\r\n const splitText = resourceId2.split('_');\r\n\r\n const key = splitText[0];\r\n const type = splitText[1];\r\n if (!resources[locale].hasOwnProperty(key)) {\r\n resources[locale][key] = {};\r\n }\r\n resources[locale][key][type] = value;\r\n\r\n // Used to keep a mapping of keys to resources for faster\r\n // processing later\r\n if (!keyToResources.hasOwnProperty(key)) {\r\n keyToResources[key] = {};\r\n }\r\n\r\n if (keyToResources.hasOwnProperty(key) &&\r\n !keyToResources[key].hasOwnProperty(locale)) {\r\n keyToResources[key][locale] = {};\r\n }\r\n keyToResources[key][locale][type] = value;\r\n }\r\n }\r\n }\r\n }\r\n\r\n return callback();\r\n },\r\n\r\n /**\r\n * Flush\r\n */\r\n function (callback) {\r\n try {\r\n const updateNotesObject = JSON.parse(fs.readFileSync(options.updateNotes, 'utf8'));\r\n\r\n const keys: string[] = [];\r\n findResourceKey(updateNotesObject, keys);\r\n const missingType: string[] = [];\r\n const missing: string[] = [];\r\n const unused: string[] = Object.keys(resources.default);\r\n\r\n keys.forEach(function (key) {\r\n const index = unused.indexOf(key);\r\n if (index >= 0) {\r\n // If the item exists, remove it from the unused set\r\n unused.splice(index, 1);\r\n\r\n // We have to check each item has a title and description\r\n const typeKeys = Object.keys(keyToResources[key].default);\r\n const requiredTypesSet = new Set();\r\n requiredTypesSet.add('title');\r\n requiredTypesSet.add('description');\r\n typeKeys.forEach(item => {\r\n if (requiredTypesSet.has(item)) {\r\n requiredTypesSet.delete(item);\r\n }\r\n });\r\n\r\n // If it did not have we save it throw error later\r\n if (requiredTypesSet.size > 0) {\r\n missingType.push(key);\r\n }\r\n } else {\r\n missing.push(key);\r\n }\r\n });\r\n\r\n if (missing.length > 0) {\r\n missing.forEach(item => {\r\n log.error('Missing resource (update-notes.json): ' + item);\r\n });\r\n throw new Error('Missing resource found in manifest.json!');\r\n }\r\n\r\n if (unused.length > 0) {\r\n // allow to use unused resources to share the same strings.json between GWv1 and GWv2.\r\n unused.forEach(item => {\r\n log.warn('Unused resource (update-notes.json): ' + item);\r\n });\r\n }\r\n\r\n if (missingType.length > 0) {\r\n missingType.forEach(item => {\r\n log.error('Missing title or description (update-notes.json): ' + item);\r\n });\r\n throw new Error('Key without title or description found in strings.resjson!');\r\n }\r\n\r\n updateNotesObject.map((item, index) => {\r\n if (item.hasOwnProperty('stringJsonKey')) {\r\n const key = item.stringJsonKey.replace('resources:strings:', '');\r\n // Key to resource maps key -> all locales\r\n item.resources = keyToResources[key];\r\n }\r\n updateNotesObject[index] = item;\r\n });\r\n\r\n const updateNotesFile = new Vinyl({\r\n cwd: '.',\r\n base: '.',\r\n path: './update-notes.json',\r\n contents: Buffer.from(JSON.stringify(updateNotesObject, null, 2), 'utf8')\r\n });\r\n\r\n this.push(updateNotesFile);\r\n } catch (e) {\r\n const error = (!e.plugin || (e.plugin !== PLUGIN_NAME)) ?\r\n util.extendError(new pluginError({ plugin: PLUGIN_NAME, message: e.message }), e) : e;\r\n log.error(error);\r\n }\r\n\r\n callback();\r\n });\r\n}\r\n\r\nmodule.exports = gulpUpdateNotesResources;\r\n"]}