Update script to download and import firefox schemas (fixes #1210)

This commit is contained in:
Mark Striemer 2017-03-27 18:11:30 -05:00
Родитель 20a41d7dfb
Коммит e49684e56d
4 изменённых файлов: 233 добавлений и 22 удалений

Просмотреть файл

@ -3,5 +3,50 @@
require('babel-register'); require('babel-register');
require('babel-polyfill'); require('babel-polyfill');
require('../src/schema/firefox-schemas-import') const fs = require('fs');
.importSchemas('src/schema/firefox', 'src/schema/updates'); const path = require('path');
const schemaImport = require('../src/schema/firefox-schemas-import');
const firefoxDir = 'tmp/firefox';
const importedDir = 'src/schema/imported';
const updatesDir = 'src/schema/updates';
const version = process.argv[2];
if (!/^[0-9]+$/.test(version)) {
console.error(`Usage: ${process.argv[1]} version`);
process.exit(1);
}
function emptyDir(dir) {
fs.readdirSync(dir).forEach((file) => {
fs.unlinkSync(path.join(dir, file));
});
}
try {
fs.mkdirSync(firefoxDir);
} catch (e) {
// The folder already existed, remove any old schema files.
emptyDir(firefoxDir);
}
// Remove the old schema files.
emptyDir(importedDir);
schemaImport.fetchSchemas(version, firefoxDir)
.then(() => {
schemaImport.importSchemas(firefoxDir, updatesDir, importedDir);
})
.then(() => {
emptyDir(firefoxDir);
fs.rmdirSync(firefoxDir);
})
.catch((error) => {
/* eslint-disable no-console */
console.error(error.toString());
console.error(error.stack);
/* eslint-enable no-console */
process.exit(2);
});

Просмотреть файл

@ -37,7 +37,8 @@
"chai": "3.5.0", "chai": "3.5.0",
"comment-json": "1.1.3", "comment-json": "1.1.3",
"coveralls": "2.13.0", "coveralls": "2.13.0",
"deepmerge": "^1.3.2", "deepmerge": "1.3.2",
"fstream": "1.0.11",
"gfm.css": "1.1.1", "gfm.css": "1.1.1",
"grunt": "1.0.1", "grunt": "1.0.1",
"grunt-contrib-clean": "1.0.0", "grunt-contrib-clean": "1.0.0",
@ -59,8 +60,10 @@
"markdown-it-emoji": "1.3.0", "markdown-it-emoji": "1.3.0",
"mocha": "3.1.2", "mocha": "3.1.2",
"mocha-multi": "0.10.0", "mocha-multi": "0.10.0",
"request": "2.81.0",
"shelljs": "0.7.7", "shelljs": "0.7.7",
"sinon": "2.1.0", "sinon": "2.1.0",
"tar": "2.2.1",
"webpack": "1.14.0", "webpack": "1.14.0",
"webpack-dev-server": "1.16.2" "webpack-dev-server": "1.16.2"
}, },

Просмотреть файл

@ -1,8 +1,11 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import zlib from 'zlib';
import commentJson from 'comment-json'; import commentJson from 'comment-json';
import merge from 'deepmerge'; import merge from 'deepmerge';
import request from 'request';
import tar from 'tar';
const FLAG_PATTERN_REGEX = /^\(\?[im]*\)(.*)/; const FLAG_PATTERN_REGEX = /^\(\?[im]*\)(.*)/;
const UNRECOGNIZED_PROPERTY_REFS = [ const UNRECOGNIZED_PROPERTY_REFS = [
@ -10,6 +13,16 @@ const UNRECOGNIZED_PROPERTY_REFS = [
'manifest#/types/UnrecognizedProperty', 'manifest#/types/UnrecognizedProperty',
]; ];
const schemaRegexes = [
new RegExp('browser/components/extensions/schemas/.*\.json'),
new RegExp('toolkit/components/extensions/schemas/.*\.json'),
];
export const refMap = {
ExtensionURL: 'manifest#/types/ExtensionURL',
HttpURL: 'manifest#/types/HttpURL',
};
// Reference some functions on inner so they can be stubbed in tests. // Reference some functions on inner so they can be stubbed in tests.
export const inner = {}; export const inner = {};
@ -88,6 +101,8 @@ export function rewriteValue(key, value) {
} else if (key === '$ref') { } else if (key === '$ref') {
if (value.includes('#/types')) { if (value.includes('#/types')) {
return value; return value;
} else if (value in refMap) {
return refMap[value];
} }
let path = value; let path = value;
let schemaId = ''; let schemaId = '';
@ -208,7 +223,7 @@ export function rewriteExtend(schemas, schemaId) {
const extendId = extendSchema.namespace; const extendId = extendSchema.namespace;
const extendDefinitions = {}; const extendDefinitions = {};
const extendTypes = {}; const extendTypes = {};
extendSchema.types.forEach((type) => { (extendSchema.types || []).forEach((type) => {
const { $extend, id, ...rest } = type; const { $extend, id, ...rest } = type;
if ($extend) { if ($extend) {
// Move the $extend into definitions. // Move the $extend into definitions.
@ -235,13 +250,21 @@ export function rewriteExtend(schemas, schemaId) {
return { definitions, refs, types }; return { definitions, refs, types };
} }
inner.normalizeSchema = (schemas) => { inner.normalizeSchema = (schemas, file) => {
let extendSchemas; let extendSchemas;
let primarySchema; let primarySchema;
if (schemas.length === 1) { if (schemas.length === 1) {
primarySchema = schemas[0]; // If there is only a manifest namespace then this just extends the manifest.
extendSchemas = []; if (schemas[0].namespace === 'manifest' && file !== 'manifest.json') {
primarySchema = {
namespace: file.slice(0, file.indexOf('.')),
};
extendSchemas = [schemas[0]];
} else {
primarySchema = schemas[0];
extendSchemas = [];
}
} else { } else {
extendSchemas = schemas.slice(0, schemas.length - 1); extendSchemas = schemas.slice(0, schemas.length - 1);
primarySchema = schemas[schemas.length - 1]; primarySchema = schemas[schemas.length - 1];
@ -258,8 +281,8 @@ inner.normalizeSchema = (schemas) => {
}; };
}; };
inner.loadSchema = (schema) => { inner.loadSchema = (schema, file) => {
const { id, ...rest } = inner.normalizeSchema(schema); const { id, ...rest } = inner.normalizeSchema(schema, file);
const newSchema = { id, ...inner.rewriteObject(rest) }; const newSchema = { id, ...inner.rewriteObject(rest) };
if (id === 'manifest') { if (id === 'manifest') {
newSchema.$ref = '#/types/WebExtensionManifest'; newSchema.$ref = '#/types/WebExtensionManifest';
@ -271,7 +294,7 @@ export function processSchemas(schemas, ourSchemas) {
const loadedSchemas = {}; const loadedSchemas = {};
schemas.forEach(({ file, schema }) => { schemas.forEach(({ file, schema }) => {
// Convert the Firefox schema to more standard JSON schema. // Convert the Firefox schema to more standard JSON schema.
const loadedSchema = inner.loadSchema(schema); const loadedSchema = inner.loadSchema(schema, file);
loadedSchemas[loadedSchema.id] = { file, schema: loadedSchema }; loadedSchemas[loadedSchema.id] = { file, schema: loadedSchema };
}); });
// Now that everything is loaded, we can finish mapping the non-standard // Now that everything is loaded, we can finish mapping the non-standard
@ -303,11 +326,11 @@ function schemaFiles(basePath) {
return fs.readdirSync(basePath); return fs.readdirSync(basePath);
} }
function writeSchemasToFile(basePath, loadedSchemas) { function writeSchemasToFile(basePath, importedPath, loadedSchemas) {
// Write out the schemas. // Write out the schemas.
Object.keys(loadedSchemas).forEach((id) => { Object.keys(loadedSchemas).forEach((id) => {
const { file, schema } = loadedSchemas[id]; const { file, schema } = loadedSchemas[id];
writeSchema(path.join(basePath, '..', 'imported'), file, schema); writeSchema(importedPath, file, schema);
}); });
} }
@ -324,9 +347,35 @@ function loadSchemasFromFile(basePath) {
return schemas; return schemas;
} }
export function importSchemas(firefoxPath, ourPath) { export function importSchemas(firefoxPath, ourPath, importedPath) {
const rawSchemas = loadSchemasFromFile(firefoxPath); const rawSchemas = loadSchemasFromFile(firefoxPath);
const ourSchemas = readSchema(ourPath, 'manifest.json'); const ourSchemas = readSchema(ourPath, 'manifest.json');
const processedSchemas = processSchemas(rawSchemas, ourSchemas); const processedSchemas = processSchemas(rawSchemas, ourSchemas);
writeSchemasToFile(firefoxPath, processedSchemas); writeSchemasToFile(firefoxPath, importedPath, processedSchemas);
}
function downloadUrl(version) {
return `https://hg.mozilla.org/mozilla-central/archive/FIREFOX_AURORA_${version}_BASE.tar.gz`;
}
inner.isBrowserSchema = (path) => {
return schemaRegexes.some((re) => re.test(path));
};
export function fetchSchemas(version, outputPath) {
return new Promise((resolve) => {
request.get(downloadUrl(version))
.pipe(zlib.createGunzip())
// eslint-disable-next-line new-cap
.pipe(tar.Parse())
.on('entry', (entry) => {
if (inner.isBrowserSchema(entry.path)) {
const filePath = path.join(outputPath, path.basename(entry.path));
entry.pipe(fs.createWriteStream(filePath));
}
})
.on('end', () => {
resolve();
});
});
} }

Просмотреть файл

@ -1,11 +1,18 @@
import fs from 'fs'; import fs from 'fs';
import fstream from 'fstream';
import path from 'path'; import path from 'path';
import zlib from 'zlib';
import request from 'request';
import tar from 'tar';
import { import {
fetchSchemas,
importSchemas,
inner, inner,
loadTypes, loadTypes,
importSchemas,
processSchemas, processSchemas,
refMap,
rewriteExtend, rewriteExtend,
rewriteKey, rewriteKey,
rewriteOptionalToRequired, rewriteOptionalToRequired,
@ -15,6 +22,16 @@ import {
describe('firefox schema import', () => { describe('firefox schema import', () => {
let sandbox; let sandbox;
function createDir(dirPath) {
fs.mkdirSync(dirPath);
}
function removeDir(dirPath) {
fs.readdirSync(dirPath).forEach(
(file) => fs.unlinkSync(path.join(dirPath, file)));
fs.rmdirSync(dirPath);
}
beforeEach(() => { beforeEach(() => {
sandbox = sinon.sandbox.create(); sandbox = sinon.sandbox.create();
}); });
@ -188,6 +205,17 @@ describe('firefox schema import', () => {
rewriteValue('additionalProperties', { $ref: 'UnrecognizedProperty' }), rewriteValue('additionalProperties', { $ref: 'UnrecognizedProperty' }),
undefined); undefined);
}); });
describe('known refs that are not specific', () => {
beforeEach(() => { refMap.SomeType = 'manifest#/types/SomeType'; });
afterEach(() => { delete refMap.SomeType; });
it('get rewritten to good paths', () => {
assert.equal(
rewriteValue('$ref', 'SomeType'),
'manifest#/types/SomeType');
});
});
}); });
describe('rewriteKey', () => { describe('rewriteKey', () => {
@ -275,7 +303,7 @@ describe('firefox schema import', () => {
}); });
}); });
it('handles a single schema in the array', () => { it('handles the manifest schema', () => {
const schemas = [ const schemas = [
{ {
namespace: 'manifest', namespace: 'manifest',
@ -283,7 +311,7 @@ describe('firefox schema import', () => {
}, },
]; ];
assert.deepEqual( assert.deepEqual(
inner.normalizeSchema(schemas), inner.normalizeSchema(schemas, 'manifest.json'),
{ {
id: 'manifest', id: 'manifest',
types: { Permission: { id: 'Permission', type: 'string' } }, types: { Permission: { id: 'Permission', type: 'string' } },
@ -291,6 +319,41 @@ describe('firefox schema import', () => {
refs: {}, refs: {},
}); });
}); });
it('handles manifest extensions without a schema', () => {
const schemas = [
{
namespace: 'manifest',
types: [{
$extend: 'WebExtensionManifest',
properties: {
chrome_url_overrides: {
type: 'object',
},
},
}],
},
];
assert.deepEqual(
inner.normalizeSchema(schemas, 'url_overrides.json'),
{
id: 'url_overrides',
types: {},
definitions: {
WebExtensionManifest: {
properties: {
chrome_url_overrides: { type: 'object' },
},
},
},
refs: {
'url_overrides#/definitions/WebExtensionManifest': {
namespace: 'manifest',
type: 'WebExtensionManifest',
},
},
});
});
}); });
describe('loadSchema', () => { describe('loadSchema', () => {
@ -823,17 +886,15 @@ describe('firefox schema import', () => {
const expectedPath = 'tests/schema/expected'; const expectedPath = 'tests/schema/expected';
beforeEach(() => { beforeEach(() => {
fs.mkdirSync(outputPath); createDir(outputPath);
}); });
afterEach(() => { afterEach(() => {
schemaFiles.forEach( removeDir(outputPath);
(file) => fs.unlinkSync(path.join(outputPath, file)));
fs.rmdirSync(outputPath);
}); });
it('imports schemas from filesystem', () => { it('imports schemas from filesystem', () => {
importSchemas(firefoxPath, ourPath); importSchemas(firefoxPath, ourPath, outputPath);
schemaFiles.forEach((file) => { schemaFiles.forEach((file) => {
assert.deepEqual( assert.deepEqual(
JSON.parse(fs.readFileSync(path.join(outputPath, file))), JSON.parse(fs.readFileSync(path.join(outputPath, file))),
@ -841,4 +902,57 @@ describe('firefox schema import', () => {
}); });
}); });
}); });
describe('fetchSchemas', () => {
const outputPath = 'tests/schema/imported';
beforeEach(() => {
createDir(outputPath);
});
afterEach(() => {
removeDir(outputPath);
});
it('downloads the firefox source and extracts the schemas', () => {
// eslint-disable-next-line new-cap
const packer = tar.Pack({ noProprietary: true });
const schemaPath = 'tests/schema/firefox';
// eslint-disable-next-line new-cap
const tarball = fstream.Reader({ path: schemaPath, type: 'Directory' })
.pipe(packer)
.pipe(zlib.createGzip());
sandbox
.stub(inner, 'isBrowserSchema')
.withArgs('firefox/cookies.json')
.returns(false)
.withArgs('firefox/manifest.json')
.returns(true);
sandbox
.stub(request, 'get')
.withArgs('https://hg.mozilla.org/mozilla-central/archive/FIREFOX_AURORA_54_BASE.tar.gz')
.returns(tarball);
assert.deepEqual(fs.readdirSync(outputPath), []);
return fetchSchemas(54, outputPath)
.then(() => {
assert.deepEqual(fs.readdirSync(outputPath), ['manifest.json']);
});
});
});
describe('isBrowserSchema', () => {
it('pulls in browser and toolkit schemas', () => {
const files = [
'moz/browser/components/extensions/schemas/bookmarks.json',
'moz/toolkit/components/extensions/schemas/manifest.json',
'moz/toolkit/components/extensions/schemas/Schemas.jsm',
];
assert.deepEqual(
files.filter((f) => inner.isBrowserSchema(f)),
[
'moz/browser/components/extensions/schemas/bookmarks.json',
'moz/toolkit/components/extensions/schemas/manifest.json',
]);
});
});
}); });