"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const child_process_1 = require("child_process"); const fs_1 = require("fs"); const path_1 = __importDefault(require("path")); const byline_1 = __importDefault(require("byline")); const ripgrep_1 = require("@vscode/ripgrep"); const tree_sitter_1 = __importDefault(require("tree-sitter")); const { typescript } = require('tree-sitter-typescript'); const product = require('../../product.json'); const packageJson = require('../../package.json'); function isNlsString(value) { return value ? typeof value !== 'string' : false; } function isStringArray(value) { return !value.some(s => isNlsString(s)); } function isNlsStringArray(value) { return value.every(s => isNlsString(s)); } var PolicyType; (function (PolicyType) { PolicyType["Boolean"] = "boolean"; PolicyType["Number"] = "number"; PolicyType["Object"] = "object"; PolicyType["String"] = "string"; PolicyType["StringEnum"] = "stringEnum"; })(PolicyType || (PolicyType = {})); function renderADMLString(prefix, moduleName, nlsString, translations) { let value; if (translations) { const moduleTranslations = translations[moduleName]; if (moduleTranslations) { value = moduleTranslations[nlsString.nlsKey]; } } if (!value) { value = nlsString.value; } return `${value}`; } function renderProfileString(_prefix, moduleName, nlsString, translations) { let value; if (translations) { const moduleTranslations = translations[moduleName]; if (moduleTranslations) { value = moduleTranslations[nlsString.nlsKey]; } } if (!value) { value = nlsString.value; } return value; } class BasePolicy { type; name; category; minimumVersion; description; moduleName; constructor(type, name, category, minimumVersion, description, moduleName) { this.type = type; this.name = name; this.category = category; this.minimumVersion = minimumVersion; this.description = description; this.moduleName = moduleName; } renderADMLString(nlsString, translations) { return renderADMLString(this.name, this.moduleName, nlsString, translations); } renderADMX(regKey) { return [ ``, ` `, ` `, ` `, ...this.renderADMXElements(), ` `, `` ]; } renderADMLStrings(translations) { return [ `${this.name}`, this.renderADMLString(this.description, translations) ]; } renderADMLPresentation() { return `${this.renderADMLPresentationContents()}`; } renderProfile() { return [`${this.name}`, this.renderProfileValue()]; } renderProfileManifest(translations) { return ` ${this.renderProfileManifestValue(translations)} `; } } class BooleanPolicy extends BasePolicy { static from(name, category, minimumVersion, description, moduleName, settingNode) { const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'boolean') { return undefined; } return new BooleanPolicy(name, category, minimumVersion, description, moduleName); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.Boolean, name, category, minimumVersion, description, moduleName); } renderADMXElements() { return [ ``, ` `, `` ]; } renderADMLPresentationContents() { return `${this.name}`; } renderProfileValue() { return ``; } renderProfileManifestValue(translations) { return `pfm_default pfm_description ${renderProfileString(this.name, this.moduleName, this.description, translations)} pfm_name ${this.name} pfm_title ${this.name} pfm_type boolean`; } } class ParseError extends Error { constructor(message, moduleName, node) { super(`${message}. ${moduleName}.ts:${node.startPosition.row + 1}`); } } class NumberPolicy extends BasePolicy { defaultValue; static from(name, category, minimumVersion, description, moduleName, settingNode) { const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'number') { return undefined; } const defaultValue = getNumberProperty(moduleName, settingNode, 'default'); if (typeof defaultValue === 'undefined') { throw new ParseError(`Missing required 'default' property.`, moduleName, settingNode); } return new NumberPolicy(name, category, minimumVersion, description, moduleName, defaultValue); } constructor(name, category, minimumVersion, description, moduleName, defaultValue) { super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); this.defaultValue = defaultValue; } renderADMXElements() { return [ `` // `` ]; } renderADMLPresentationContents() { return `${this.name}`; } renderProfileValue() { return `${this.defaultValue}`; } renderProfileManifestValue(translations) { return `pfm_default ${this.defaultValue} pfm_description ${renderProfileString(this.name, this.moduleName, this.description, translations)} pfm_name ${this.name} pfm_title ${this.name} pfm_type integer`; } } class StringPolicy extends BasePolicy { static from(name, category, minimumVersion, description, moduleName, settingNode) { const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'string') { return undefined; } return new StringPolicy(name, category, minimumVersion, description, moduleName); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.String, name, category, minimumVersion, description, moduleName); } renderADMXElements() { return [``]; } renderADMLPresentationContents() { return ``; } renderProfileValue() { return ``; } renderProfileManifestValue(translations) { return `pfm_default pfm_description ${renderProfileString(this.name, this.moduleName, this.description, translations)} pfm_name ${this.name} pfm_title ${this.name} pfm_type string`; } } class ObjectPolicy extends BasePolicy { static from(name, category, minimumVersion, description, moduleName, settingNode) { const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'object' && type !== 'array') { return undefined; } return new ObjectPolicy(name, category, minimumVersion, description, moduleName); } constructor(name, category, minimumVersion, description, moduleName) { super(PolicyType.Object, name, category, minimumVersion, description, moduleName); } renderADMXElements() { return [``]; } renderADMLPresentationContents() { return ``; } renderProfileValue() { return ``; } renderProfileManifestValue(translations) { return `pfm_default pfm_description ${renderProfileString(this.name, this.moduleName, this.description, translations)} pfm_name ${this.name} pfm_title ${this.name} pfm_type string `; } } class StringEnumPolicy extends BasePolicy { enum_; enumDescriptions; static from(name, category, minimumVersion, description, moduleName, settingNode) { const type = getStringProperty(moduleName, settingNode, 'type'); if (type !== 'string') { return undefined; } const enum_ = getStringArrayProperty(moduleName, settingNode, 'enum'); if (!enum_) { return undefined; } if (!isStringArray(enum_)) { throw new ParseError(`Property 'enum' should not be localized.`, moduleName, settingNode); } const enumDescriptions = getStringArrayProperty(moduleName, settingNode, 'enumDescriptions'); if (!enumDescriptions) { throw new ParseError(`Missing required 'enumDescriptions' property.`, moduleName, settingNode); } else if (!isNlsStringArray(enumDescriptions)) { throw new ParseError(`Property 'enumDescriptions' should be localized.`, moduleName, settingNode); } return new StringEnumPolicy(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions); } constructor(name, category, minimumVersion, description, moduleName, enum_, enumDescriptions) { super(PolicyType.StringEnum, name, category, minimumVersion, description, moduleName); this.enum_ = enum_; this.enumDescriptions = enumDescriptions; } renderADMXElements() { return [ ``, ...this.enum_.map((value, index) => ` ${value}`), `` ]; } renderADMLStrings(translations) { return [ ...super.renderADMLStrings(translations), ...this.enumDescriptions.map(e => this.renderADMLString(e, translations)) ]; } renderADMLPresentationContents() { return ``; } renderProfileValue() { return `${this.enum_[0]}`; } renderProfileManifestValue(translations) { return `pfm_default ${this.enum_[0]} pfm_description ${renderProfileString(this.name, this.moduleName, this.description, translations)} pfm_name ${this.name} pfm_title ${this.name} pfm_type string pfm_range_list ${this.enum_.map(e => `${e}`).join('\n ')} `; } } const NumberQ = { Q: `(number) @value`, value(matches) { const match = matches[0]; if (!match) { return undefined; } const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; if (!value) { throw new Error(`Missing required 'value' property.`); } return parseInt(value); } }; const StringQ = { Q: `[ (string (string_fragment) @value) (call_expression function: [ (identifier) @localizeFn (#eq? @localizeFn localize) (member_expression object: (identifier) @nlsObj (#eq? @nlsObj nls) property: (property_identifier) @localizeFn (#eq? @localizeFn localize) ) ] arguments: (arguments (string (string_fragment) @nlsKey) (string (string_fragment) @value)) ) ]`, value(matches) { const match = matches[0]; if (!match) { return undefined; } const value = match.captures.filter(c => c.name === 'value')[0]?.node.text; if (!value) { throw new Error(`Missing required 'value' property.`); } const nlsKey = match.captures.filter(c => c.name === 'nlsKey')[0]?.node.text; if (nlsKey) { return { value, nlsKey }; } else { return value; } } }; const StringArrayQ = { Q: `(array ${StringQ.Q})`, value(matches) { if (matches.length === 0) { return undefined; } return matches.map(match => { return StringQ.value([match]); }); } }; function getProperty(qtype, moduleName, node, key) { const query = new tree_sitter_1.default.Query(typescript, `( (pair key: [(property_identifier)(string)] @key value: ${qtype.Q} ) (#any-of? @key "${key}" "'${key}'") )`); try { const matches = query.matches(node).filter(m => m.captures[0].node.parent?.parent === node); return qtype.value(matches); } catch (e) { throw new ParseError(e.message, moduleName, node); } } function getNumberProperty(moduleName, node, key) { return getProperty(NumberQ, moduleName, node, key); } function getStringProperty(moduleName, node, key) { return getProperty(StringQ, moduleName, node, key); } function getStringArrayProperty(moduleName, node, key) { return getProperty(StringArrayQ, moduleName, node, key); } // TODO: add more policy types const PolicyTypes = [ BooleanPolicy, NumberPolicy, StringEnumPolicy, StringPolicy, ObjectPolicy ]; function getPolicy(moduleName, configurationNode, settingNode, policyNode, categories) { const name = getStringProperty(moduleName, policyNode, 'name'); if (!name) { throw new ParseError(`Missing required 'name' property`, moduleName, policyNode); } else if (isNlsString(name)) { throw new ParseError(`Property 'name' should be a literal string`, moduleName, policyNode); } const categoryName = getStringProperty(moduleName, configurationNode, 'title'); if (!categoryName) { throw new ParseError(`Missing required 'title' property`, moduleName, configurationNode); } else if (!isNlsString(categoryName)) { throw new ParseError(`Property 'title' should be localized`, moduleName, configurationNode); } const categoryKey = `${categoryName.nlsKey}:${categoryName.value}`; let category = categories.get(categoryKey); if (!category) { category = { moduleName, name: categoryName }; categories.set(categoryKey, category); } const minimumVersion = getStringProperty(moduleName, policyNode, 'minimumVersion'); if (!minimumVersion) { throw new ParseError(`Missing required 'minimumVersion' property.`, moduleName, policyNode); } else if (isNlsString(minimumVersion)) { throw new ParseError(`Property 'minimumVersion' should be a literal string.`, moduleName, policyNode); } const description = getStringProperty(moduleName, policyNode, 'description') ?? getStringProperty(moduleName, settingNode, 'description'); if (!description) { throw new ParseError(`Missing required 'description' property.`, moduleName, settingNode); } if (!isNlsString(description)) { throw new ParseError(`Property 'description' should be localized.`, moduleName, settingNode); } let result; for (const policyType of PolicyTypes) { if (result = policyType.from(name, category, minimumVersion, description, moduleName, settingNode)) { break; } } if (!result) { throw new ParseError(`Failed to parse policy '${name}'.`, moduleName, settingNode); } return result; } function getPolicies(moduleName, node) { const query = new tree_sitter_1.default.Query(typescript, ` ( (call_expression function: (member_expression property: (property_identifier) @registerConfigurationFn) (#eq? @registerConfigurationFn registerConfiguration) arguments: (arguments (object (pair key: [(property_identifier)(string)] @propertiesKey (#any-of? @propertiesKey "properties" "'properties'") value: (object (pair key: [(property_identifier)(string)(computed_property_name)] value: (object (pair key: [(property_identifier)(string)] @policyKey (#any-of? @policyKey "policy" "'policy'") value: (object) @policy )) @setting )) )) @configuration) ) ) `); const categories = new Map(); return query.matches(node).map(m => { const configurationNode = m.captures.filter(c => c.name === 'configuration')[0].node; const settingNode = m.captures.filter(c => c.name === 'setting')[0].node; const policyNode = m.captures.filter(c => c.name === 'policy')[0].node; return getPolicy(moduleName, configurationNode, settingNode, policyNode, categories); }); } async function getFiles(root) { return new Promise((c, e) => { const result = []; const rg = (0, child_process_1.spawn)(ripgrep_1.rgPath, ['-l', 'registerConfiguration\\(', '-g', 'src/**/*.ts', '-g', '!src/**/test/**', root]); const stream = (0, byline_1.default)(rg.stdout.setEncoding('utf8')); stream.on('data', path => result.push(path)); stream.on('error', err => e(err)); stream.on('end', () => c(result)); }); } function renderADMX(regKey, versions, categories, policies) { versions = versions.map(v => v.replace(/\./g, '_')); return ` ${versions.map(v => ``).join(`\n `)} ${categories.map(c => ``).join(`\n `)} ${policies.map(p => p.renderADMX(regKey)).flat().join(`\n `)} `; } function renderADML(appName, versions, categories, policies, translations) { return ` ${appName} ${versions.map(v => `${appName} >= ${v}`).join(`\n `)} ${categories.map(c => renderADMLString('Category', c.moduleName, c.name, translations)).join(`\n `)} ${policies.map(p => p.renderADMLStrings(translations)).flat().join(`\n `)} ${policies.map(p => p.renderADMLPresentation()).join(`\n `)} `; } function renderProfileManifest(appName, bundleIdentifier, _versions, _categories, policies, translations) { const requiredPayloadFields = ` pfm_default Configure ${appName} pfm_name PayloadDescription pfm_title Payload Description pfm_type string pfm_default ${appName} pfm_name PayloadDisplayName pfm_require always pfm_title Payload Display Name pfm_type string pfm_default ${bundleIdentifier} pfm_name PayloadIdentifier pfm_require always pfm_title Payload Identifier pfm_type string pfm_default ${bundleIdentifier} pfm_name PayloadType pfm_require always pfm_title Payload Type pfm_type string pfm_default pfm_name PayloadUUID pfm_require always pfm_title Payload UUID pfm_type string pfm_default 1 pfm_name PayloadVersion pfm_range_list 1 pfm_require always pfm_title Payload Version pfm_type integer pfm_default Microsoft pfm_name PayloadOrganization pfm_title Payload Organization pfm_type string `; const profileManifestSubkeys = policies.map(policy => { return policy.renderProfileManifest(translations); }).join(''); return ` pfm_app_url https://code.visualstudio.com/ pfm_description ${appName} Managed Settings pfm_documentation_url https://code.visualstudio.com/docs/setup/enterprise pfm_domain ${bundleIdentifier} pfm_format_version 1 pfm_interaction combined pfm_last_modified ${new Date().toISOString().replace(/\.\d+Z$/, 'Z')} pfm_platforms macOS pfm_subkeys ${requiredPayloadFields} ${profileManifestSubkeys} pfm_title ${appName} pfm_unique pfm_version 1 `; } function renderMacOSPolicy(policies, translations) { const appName = product.nameLong; const bundleIdentifier = product.darwinBundleIdentifier; const payloadUUID = product.darwinProfilePayloadUUID; const UUID = product.darwinProfileUUID; const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); const categories = [...new Set(policies.map(p => p.category))]; const policyEntries = policies.map(policy => policy.renderProfile()) .flat() .map(entry => `\t\t\t\t${entry}`) .join('\n'); return { profile: ` PayloadContent PayloadDisplayName ${appName} PayloadIdentifier ${bundleIdentifier}.${UUID} PayloadType ${bundleIdentifier} PayloadUUID ${UUID} PayloadVersion 1 ${policyEntries} PayloadDescription This profile manages ${appName}. For more information see https://code.visualstudio.com/docs/setup/enterprise PayloadDisplayName ${appName} PayloadIdentifier ${bundleIdentifier} PayloadOrganization Microsoft PayloadType Configuration PayloadUUID ${payloadUUID} PayloadVersion 1 TargetDeviceType 5 `, manifests: [{ languageId: 'en-us', contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies) }, ...translations.map(({ languageId, languageTranslations }) => ({ languageId, contents: renderProfileManifest(appName, bundleIdentifier, versions, categories, policies, languageTranslations) })) ] }; } function renderGP(policies, translations) { const appName = product.nameLong; const regKey = product.win32RegValueName; const versions = [...new Set(policies.map(p => p.minimumVersion)).values()].sort(); const categories = [...Object.values(policies.reduce((acc, p) => ({ ...acc, [p.category.name.nlsKey]: p.category }), {}))]; return { admx: renderADMX(regKey, versions, categories, policies), adml: [ { languageId: 'en-us', contents: renderADML(appName, versions, categories, policies) }, ...translations.map(({ languageId, languageTranslations }) => ({ languageId, contents: renderADML(appName, versions, categories, policies, languageTranslations) })) ] }; } const Languages = { 'fr': 'fr-fr', 'it': 'it-it', 'de': 'de-de', 'es': 'es-es', 'ru': 'ru-ru', 'zh-hans': 'zh-cn', 'zh-hant': 'zh-tw', 'ja': 'ja-jp', 'ko': 'ko-kr', 'cs': 'cs-cz', 'pt-br': 'pt-br', 'tr': 'tr-tr', 'pl': 'pl-pl', }; async function getSpecificNLS(resourceUrlTemplate, languageId, version) { const resource = { publisher: 'ms-ceintl', name: `vscode-language-pack-${languageId}`, version: `${version[0]}.${version[1]}.${version[2]}`, path: 'extension/translations/main.i18n.json' }; const url = resourceUrlTemplate.replace(/\{([^}]+)\}/g, (_, key) => resource[key]); const res = await fetch(url); if (res.status !== 200) { throw new Error(`[${res.status}] Error downloading language pack ${languageId}@${version}`); } const { contents: result } = await res.json(); return result; } function parseVersion(version) { const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version); return [parseInt(major), parseInt(minor), parseInt(patch)]; } function compareVersions(a, b) { if (a[0] !== b[0]) { return a[0] - b[0]; } if (a[1] !== b[1]) { return a[1] - b[1]; } return a[2] - b[2]; } async function queryVersions(serviceUrl, languageId) { const res = await fetch(`${serviceUrl}/extensionquery`, { method: 'POST', headers: { 'Accept': 'application/json;api-version=3.0-preview.1', 'Content-Type': 'application/json', 'User-Agent': 'VS Code Build', }, body: JSON.stringify({ filters: [{ criteria: [{ filterType: 7, value: `ms-ceintl.vscode-language-pack-${languageId}` }] }], flags: 0x1 }) }); if (res.status !== 200) { throw new Error(`[${res.status}] Error querying for extension: ${languageId}`); } const result = await res.json(); return result.results[0].extensions[0].versions.map(v => parseVersion(v.version)).sort(compareVersions); } async function getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageId, version) { const versions = await queryVersions(extensionGalleryServiceUrl, languageId); const nextMinor = [version[0], version[1] + 1, 0]; const compatibleVersions = versions.filter(v => compareVersions(v, nextMinor) < 0); const latestCompatibleVersion = compatibleVersions.at(-1); // order is newest to oldest if (!latestCompatibleVersion) { throw new Error(`No compatible language pack found for ${languageId} for version ${version}`); } return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion); } async function parsePolicies() { const parser = new tree_sitter_1.default(); parser.setLanguage(typescript); const files = await getFiles(process.cwd()); const base = path_1.default.join(process.cwd(), 'src'); const policies = []; for (const file of files) { const moduleName = path_1.default.relative(base, file).replace(/\.ts$/i, '').replace(/\\/g, '/'); const contents = await fs_1.promises.readFile(file, { encoding: 'utf8' }); const tree = parser.parse(contents); policies.push(...getPolicies(moduleName, tree.rootNode)); } return policies; } async function getTranslations() { const extensionGalleryServiceUrl = product.extensionsGallery?.serviceUrl; if (!extensionGalleryServiceUrl) { console.warn(`Skipping policy localization: No 'extensionGallery.serviceUrl' found in 'product.json'.`); return []; } const resourceUrlTemplate = product.extensionsGallery?.resourceUrlTemplate; if (!resourceUrlTemplate) { console.warn(`Skipping policy localization: No 'resourceUrlTemplate' found in 'product.json'.`); return []; } const version = parseVersion(packageJson.version); const languageIds = Object.keys(Languages); return await Promise.all(languageIds.map(languageId => getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageId, version) .then(languageTranslations => ({ languageId, languageTranslations })))); } async function windowsMain(policies, translations) { const root = '.build/policies/win32'; const { admx, adml } = await renderGP(policies, translations); await fs_1.promises.rm(root, { recursive: true, force: true }); await fs_1.promises.mkdir(root, { recursive: true }); await fs_1.promises.writeFile(path_1.default.join(root, `${product.win32RegValueName}.admx`), admx.replace(/\r?\n/g, '\n')); for (const { languageId, contents } of adml) { const languagePath = path_1.default.join(root, languageId === 'en-us' ? 'en-us' : Languages[languageId]); await fs_1.promises.mkdir(languagePath, { recursive: true }); await fs_1.promises.writeFile(path_1.default.join(languagePath, `${product.win32RegValueName}.adml`), contents.replace(/\r?\n/g, '\n')); } } async function darwinMain(policies, translations) { const bundleIdentifier = product.darwinBundleIdentifier; if (!bundleIdentifier || !product.darwinProfilePayloadUUID || !product.darwinProfileUUID) { throw new Error(`Missing required product information.`); } const root = '.build/policies/darwin'; const { profile, manifests } = await renderMacOSPolicy(policies, translations); await fs_1.promises.rm(root, { recursive: true, force: true }); await fs_1.promises.mkdir(root, { recursive: true }); await fs_1.promises.writeFile(path_1.default.join(root, `${bundleIdentifier}.mobileconfig`), profile.replace(/\r?\n/g, '\n')); for (const { languageId, contents } of manifests) { const languagePath = path_1.default.join(root, languageId === 'en-us' ? 'en-us' : Languages[languageId]); await fs_1.promises.mkdir(languagePath, { recursive: true }); await fs_1.promises.writeFile(path_1.default.join(languagePath, `${bundleIdentifier}.plist`), contents.replace(/\r?\n/g, '\n')); } } async function main() { const [policies, translations] = await Promise.all([parsePolicies(), getTranslations()]); const platform = process.argv[2]; if (platform === 'darwin') { await darwinMain(policies, translations); } else if (platform === 'win32') { await windowsMain(policies, translations); } else { console.error(`Usage: node build/lib/policies `); process.exit(1); } } if (require.main === module) { main().catch(err => { if (err instanceof ParseError) { console.error(`Parse Error:`, err.message); } else { console.error(err); } process.exit(1); }); } //# sourceMappingURL=policies.js.map