Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/build/lib/policies/policyGenerator.ts
4772 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import minimist from 'minimist';
7
import * as fs from 'fs';
8
import path from 'path';
9
import { type CategoryDto, type ExportedPolicyDataDto } from './policyDto.ts';
10
import * as JSONC from 'jsonc-parser';
11
import { BooleanPolicy } from './booleanPolicy.ts';
12
import { NumberPolicy } from './numberPolicy.ts';
13
import { ObjectPolicy } from './objectPolicy.ts';
14
import { StringEnumPolicy } from './stringEnumPolicy.ts';
15
import { StringPolicy } from './stringPolicy.ts';
16
import { type Version, type LanguageTranslations, type Policy, type Translations, Languages, type ProductJson } from './types.ts';
17
import { renderGP, renderJsonPolicies, renderMacOSPolicy } from './render.ts';
18
19
const product: ProductJson = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, '../../../product.json'), 'utf8'));
20
const packageJson = JSON.parse(fs.readFileSync(path.join(import.meta.dirname, '../../../package.json'), 'utf8'));
21
22
async function getSpecificNLS(resourceUrlTemplate: string, languageId: string, version: Version): Promise<LanguageTranslations> {
23
const resource = {
24
publisher: 'ms-ceintl',
25
name: `vscode-language-pack-${languageId}`,
26
version: `${version[0]}.${version[1]}.${version[2]}`,
27
path: 'extension/translations/main.i18n.json'
28
};
29
30
const url = resourceUrlTemplate.replace(/\{([^}]+)\}/g, (_, key) => resource[key as keyof typeof resource]);
31
const res = await fetch(url);
32
33
if (res.status !== 200) {
34
throw new Error(`[${res.status}] Error downloading language pack ${languageId}@${version}`);
35
}
36
37
const { contents: result } = await res.json() as { contents: LanguageTranslations };
38
39
// TODO: support module namespacing
40
// Flatten all moduleName keys to empty string
41
const flattened: LanguageTranslations = { '': {} };
42
for (const moduleName in result) {
43
for (const nlsKey in result[moduleName]) {
44
flattened[''][nlsKey] = result[moduleName][nlsKey];
45
}
46
}
47
48
return flattened;
49
}
50
51
function parseVersion(version: string): Version {
52
const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version)!;
53
return [parseInt(major), parseInt(minor), parseInt(patch)];
54
}
55
56
function compareVersions(a: Version, b: Version): number {
57
if (a[0] !== b[0]) { return a[0] - b[0]; }
58
if (a[1] !== b[1]) { return a[1] - b[1]; }
59
return a[2] - b[2];
60
}
61
62
async function queryVersions(serviceUrl: string, languageId: string): Promise<Version[]> {
63
const res = await fetch(`${serviceUrl}/extensionquery`, {
64
method: 'POST',
65
headers: {
66
'Accept': 'application/json;api-version=3.0-preview.1',
67
'Content-Type': 'application/json',
68
'User-Agent': 'VS Code Build',
69
},
70
body: JSON.stringify({
71
filters: [{ criteria: [{ filterType: 7, value: `ms-ceintl.vscode-language-pack-${languageId}` }] }],
72
flags: 0x1
73
})
74
});
75
76
if (res.status !== 200) {
77
throw new Error(`[${res.status}] Error querying for extension: ${languageId}`);
78
}
79
80
const result = await res.json() as { results: [{ extensions: { versions: { version: string }[] }[] }] };
81
return result.results[0].extensions[0].versions.map(v => parseVersion(v.version)).sort(compareVersions);
82
}
83
84
async function getNLS(extensionGalleryServiceUrl: string, resourceUrlTemplate: string, languageId: string, version: Version) {
85
const versions = await queryVersions(extensionGalleryServiceUrl, languageId);
86
const nextMinor: Version = [version[0], version[1] + 1, 0];
87
const compatibleVersions = versions.filter(v => compareVersions(v, nextMinor) < 0);
88
const latestCompatibleVersion = compatibleVersions.at(-1)!; // order is newest to oldest
89
90
if (!latestCompatibleVersion) {
91
throw new Error(`No compatible language pack found for ${languageId} for version ${version}`);
92
}
93
94
return await getSpecificNLS(resourceUrlTemplate, languageId, latestCompatibleVersion);
95
}
96
97
// TODO: add more policy types
98
const PolicyTypes = [
99
BooleanPolicy,
100
NumberPolicy,
101
StringEnumPolicy,
102
StringPolicy,
103
ObjectPolicy
104
];
105
106
async function parsePolicies(policyDataFile: string): Promise<Policy[]> {
107
const contents = JSONC.parse(await fs.promises.readFile(policyDataFile, { encoding: 'utf8' })) as ExportedPolicyDataDto;
108
const categories = new Map<string, CategoryDto>();
109
for (const category of contents.categories) {
110
categories.set(category.key, category);
111
}
112
113
const policies: Policy[] = [];
114
for (const policy of contents.policies) {
115
const category = categories.get(policy.category);
116
if (!category) {
117
throw new Error(`Unknown category: ${policy.category}`);
118
}
119
120
let result: Policy | undefined;
121
for (const policyType of PolicyTypes) {
122
if (result = policyType.from(category, policy)) {
123
break;
124
}
125
}
126
127
if (!result) {
128
throw new Error(`Unsupported policy type: ${policy.type} for policy ${policy.name}`);
129
}
130
131
policies.push(result);
132
}
133
134
// Sort policies first by category name, then by policy name
135
policies.sort((a, b) => {
136
const categoryCompare = a.category.name.value.localeCompare(b.category.name.value);
137
if (categoryCompare !== 0) {
138
return categoryCompare;
139
}
140
return a.name.localeCompare(b.name);
141
});
142
143
return policies;
144
}
145
146
async function getTranslations(): Promise<Translations> {
147
const extensionGalleryServiceUrl = product.extensionsGallery?.serviceUrl;
148
149
if (!extensionGalleryServiceUrl) {
150
console.warn(`Skipping policy localization: No 'extensionGallery.serviceUrl' found in 'product.json'.`);
151
return [];
152
}
153
154
const resourceUrlTemplate = product.extensionsGallery?.resourceUrlTemplate;
155
156
if (!resourceUrlTemplate) {
157
console.warn(`Skipping policy localization: No 'resourceUrlTemplate' found in 'product.json'.`);
158
return [];
159
}
160
161
const version = parseVersion(packageJson.version);
162
const languageIds = Object.keys(Languages);
163
164
return await Promise.all(languageIds.map(
165
languageId => getNLS(extensionGalleryServiceUrl, resourceUrlTemplate, languageId, version)
166
.then(languageTranslations => ({ languageId, languageTranslations }))
167
));
168
}
169
170
async function windowsMain(policies: Policy[], translations: Translations) {
171
const root = '.build/policies/win32';
172
const { admx, adml } = renderGP(product, policies, translations);
173
174
await fs.promises.rm(root, { recursive: true, force: true });
175
await fs.promises.mkdir(root, { recursive: true });
176
177
await fs.promises.writeFile(path.join(root, `${product.win32RegValueName}.admx`), admx.replace(/\r?\n/g, '\n'));
178
179
for (const { languageId, contents } of adml) {
180
const languagePath = path.join(root, languageId === 'en-us' ? 'en-us' : Languages[languageId as keyof typeof Languages]);
181
await fs.promises.mkdir(languagePath, { recursive: true });
182
await fs.promises.writeFile(path.join(languagePath, `${product.win32RegValueName}.adml`), contents.replace(/\r?\n/g, '\n'));
183
}
184
}
185
186
async function darwinMain(policies: Policy[], translations: Translations) {
187
const bundleIdentifier = product.darwinBundleIdentifier;
188
if (!bundleIdentifier || !product.darwinProfilePayloadUUID || !product.darwinProfileUUID) {
189
throw new Error(`Missing required product information.`);
190
}
191
const root = '.build/policies/darwin';
192
const { profile, manifests } = renderMacOSPolicy(product, policies, translations);
193
194
await fs.promises.rm(root, { recursive: true, force: true });
195
await fs.promises.mkdir(root, { recursive: true });
196
await fs.promises.writeFile(path.join(root, `${bundleIdentifier}.mobileconfig`), profile.replace(/\r?\n/g, '\n'));
197
198
for (const { languageId, contents } of manifests) {
199
const languagePath = path.join(root, languageId === 'en-us' ? 'en-us' : Languages[languageId as keyof typeof Languages]);
200
await fs.promises.mkdir(languagePath, { recursive: true });
201
await fs.promises.writeFile(path.join(languagePath, `${bundleIdentifier}.plist`), contents.replace(/\r?\n/g, '\n'));
202
}
203
}
204
205
async function linuxMain(policies: Policy[]) {
206
const root = '.build/policies/linux';
207
const policyFileContents = JSON.stringify(renderJsonPolicies(policies), undefined, 4);
208
209
await fs.promises.rm(root, { recursive: true, force: true });
210
await fs.promises.mkdir(root, { recursive: true });
211
212
const jsonPath = path.join(root, `policy.json`);
213
await fs.promises.writeFile(jsonPath, policyFileContents.replace(/\r?\n/g, '\n'));
214
}
215
216
async function main() {
217
const args = minimist(process.argv.slice(2));
218
if (args._.length !== 2) {
219
console.error(`Usage: node build/lib/policies <policy-data-file> <darwin|win32|linux>`);
220
process.exit(1);
221
}
222
223
const policyDataFile = args._[0];
224
const platform = args._[1];
225
const [policies, translations] = await Promise.all([parsePolicies(policyDataFile), getTranslations()]);
226
227
if (platform === 'darwin') {
228
await darwinMain(policies, translations);
229
} else if (platform === 'win32') {
230
await windowsMain(policies, translations);
231
} else if (platform === 'linux') {
232
await linuxMain(policies);
233
} else {
234
console.error(`Usage: node build/lib/policies <policy-data-file> <darwin|win32|linux>`);
235
process.exit(1);
236
}
237
}
238
239
if (import.meta.main) {
240
main().catch(err => {
241
console.error(err);
242
process.exit(1);
243
});
244
}
245
246