Update script to download and import firefox schemas (fixes #1210)
This commit is contained in:
Родитель
20a41d7dfb
Коммит
e49684e56d
|
@ -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',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Загрузка…
Ссылка в новой задаче