Skip to content

Commit

Permalink
Merge 0099273 into c28a96b
Browse files Browse the repository at this point in the history
  • Loading branch information
sr258 committed Dec 3, 2019
2 parents c28a96b + 0099273 commit d250760
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 97 deletions.
24 changes: 20 additions & 4 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,26 @@ jobs:
with:
node-version: 10.x

- name: npm install, npm run test:coverage
run: |
npm install
npm run test:coverage
- name: Cache node modules
uses: actions/cache@v1
with:
path: node_modules
key: npm-${{ runner.OS }}-build-${{ hashFiles('package-lock.json') }}

- name: install + build
run: npm ci

- name: Cache H5P examples
uses: actions/cache@v1
with:
path: test/data/hub-content
key: h5p-hub-content-${{ hashFiles('test/data/content-type-cache/real-content-types.json') }}

- name: Download content files from H5P Hub
run: npm run download:content

- name: npm test:coverage
run: npm run test:coverage

- name: Coveralls
uses: coverallsapp/github-action@master
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Node CI tests

on: [push]

jobs:
build:

runs-on: ${{ matrix.os }}

strategy:
matrix:
os: [ubuntu-latest, macOS-latest]
node-version: [10.x, 12.x]

steps:
- uses: actions/checkout@v1
- name: Use Node.js ${{ matrix.node-version }} on ${{ matrix.os }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Cache node modules
uses: actions/cache@v1
with:
path: node_modules
key: npm-${{ runner.OS }}-build-${{ hashFiles('package-lock.json') }}
- name: install + build
run: npm ci
- name: Cache H5P examples
uses: actions/cache@v1
with:
path: test/data/hub-content
key: h5p-hub-content-${{ hashFiles('test/data/content-type-cache/real-content-types.json') }}
- name: Download content files from H5P Hub
run: npm run download:content
- name: lint
run: npm run lint
- name: format
run: npm run format:check
- name: unit-tests
run: npm run test
- name: integration-tests
run: npm run test:integration
- name: player-e2e-test
run: npm run test:e2e:player
env:
CI: true
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"scripts": {
"start": "./node_modules/.bin/ts-node examples/express.ts",
"prepare": "npm run download:content-type-cache && npm run download:content && npm run download:core && npm run build",
"prepare": "npm run download:content-type-cache && npm run download:core && npm run build",
"build": "npx tsc -p ./tsconfig.build.json && cp -r src/schemas build/src/schemas && cp -r src/translations build/src/translations",
"build:watch": "npx tsc -w -p ./tsconfig.build.json",
"uninstall": "rm -rf node_modules && rm -rf test/data/hub-content && rm test/data/content-type-cache/real-content-types.json && rm -rf h5p && rm -rf build",
Expand All @@ -18,12 +18,12 @@
"download:content-type-cache": "ts-node scripts/update-real-content-type-cache.ts",
"ci": "npm run build && npm run lint && npm run format:check && npm run test",
"lint": "./node_modules/.bin/tslint --project tsconfig.json --config tslint.json",
"test": "jest --testTimeout=120000 --logHeapUsage --maxWorkers=2",
"test": "jest --testTimeout=120000",
"test:watch": "jest --watch",
"test:e2e": "npm run test:e2e:player",
"test:e2e:player": "./node_modules/.bin/ts-node test/e2e/H5PPlayer.DisplayContent.test.ts",
"test:coverage": "npx jest --config jest.coverage.config.js --collect-coverage --testTimeout=120000",
"test:integration": "npx jest --config jest.integration.config.js --maxWorkers=2 --logHeapUsage",
"test:integration": "npx jest --config jest.integration.config.js",
"format:check": "npx prettier --check \"{src,test,examples}/**/*.ts\"",
"format": "npx prettier --write \"{src,test,examples}/**/*.ts\"",
"semantic-release": "semantic-release"
Expand Down
75 changes: 49 additions & 26 deletions scripts/download-examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,79 @@ const path = require('path');
* @param {string} directoryPath Path to a directory on the disk to save the downloaded H5P to.
*/
const downloadH5pPackages = async (contentTypeCacheFilePath, directoryPath) => {
const machineNames = (await fs.readJSON(contentTypeCacheFilePath)).contentTypes.map(ct => ct.id);
const machineNames = (
await fs.readJSON(contentTypeCacheFilePath)
).contentTypes.map(ct => ct.id);

console.log(`Found ${machineNames.length} packages.`);
console.log(`Found ${machineNames.length} already downloaded packages.`);

// Create the directory if it doesn't exist
if (!(await fs.pathExists(directoryPath))) {
await fs.mkdir(directoryPath)
await fs.mkdir(directoryPath);
}

// Counts how many downloads have been finished
let downloadsFinished = 0;

// Promise.all allows parallel downloads
await Promise.all(
return Promise.all(
machineNames
.filter(machineName => machineName !== "H5P.IFrameEmbed") // IFrameEmbed is broken and is deprecated
.filter(machineName => machineName !== 'H5P.IFrameEmbed') // IFrameEmbed is broken and is deprecated
.filter(machineName => {
if (fs.pathExistsSync(path.join(directoryPath, `${machineName}.h5p`))) {
if (
fs.pathExistsSync(
path.join(directoryPath, `${machineName}.h5p`)
)
) {
downloadsFinished += 1;
console.log(`${downloadsFinished}/${machineNames.length} ${machineName}.h5p has already been downloaded. Skipping!`);
console.log(
`${downloadsFinished}/${machineNames.length} ${machineName}.h5p has already been downloaded. Skipping!`
);
return false;
}
return true;
})
.map(contentType => axios.default.get(`http://api.h5p.org/v1/content-types/${contentType}`, { responseType: "stream" }).then(response => {
return new Promise((resolve) => {
const file = fs.createWriteStream(`${directoryPath}/${contentType}.h5p`);
file.on("finish", () => {
.map(contentType =>
axios.default
.get(`http://api.h5p.org/v1/content-types/${contentType}`, {
responseType: 'stream'
})
.then(response => {
return new Promise(resolve => {
const file = fs.createWriteStream(
`${directoryPath}/${contentType}.h5p`
);
file.on('finish', () => {
downloadsFinished += 1;
console.log(
`Downloaded example ${downloadsFinished}/${machineNames.length}: ${contentType}.h5p`
);
resolve(`${contentType}.h5p`);
});
response.data.pipe(file);
});
})
.catch(error => {
downloadsFinished += 1;
console.log(`Downloaded example ${downloadsFinished}/${machineNames.length}: ${contentType}.h5p`);
resolve(`${contentType}.h5p`);
const errorMessage = `${downloadsFinished}/${machineNames.length} Error downloading ${contentType}: ${error.response.status} ${error.response.statusText}`;
console.error(errorMessage);
return Promise.reject(errorMessage);
})
response.data.pipe(file);
})
}).catch(error => {
downloadsFinished += 1;
console.error(`${downloadsFinished}/${machineNames.length} Error downloading ${contentType}: ${error.response.status} ${error.response.statusText}`);
return Promise.resolve();
})
));
}
)
);
};

const contentTypeCacheFile = path.resolve(process.argv[2]);
const directory = path.resolve(process.argv[3]);

console.log("Downloading content type examples from H5P Hub.");
console.log('Downloading content type examples from H5P Hub.');
console.log(`Using content types from ${contentTypeCacheFile}`);
console.log(`Downloading to ${directory}`);

downloadH5pPackages(contentTypeCacheFile, directory)
.then(() => {
console.log("Download finished!");
});
.then(files => {
console.log(`Download finished! Downloaded ${files.length} files.`);
})
.catch(error => {
process.exit(1);
});
118 changes: 55 additions & 63 deletions src/PackageImporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,80 +217,72 @@ export default class PackageImporter {
this.translationService,
this.config
);
// no need to check result as the validator throws an exception if there is an error
await packageValidator.validatePackage(packagePath, false, true);
// we don't use withDir here, to have better error handling (catch & finally block below)
const { path: tempDirPath } = await dir();
try {
// no need to check result as the validator throws an exception if there is an error
await packageValidator.validatePackage(packagePath, false, true);
// we don't use withDir here, to have better error handling (catch & finally block below)
const { path: tempDirPath } = await dir();
try {
await PackageImporter.extractPackage(packagePath, tempDirPath, {
includeContent:
copyMode === ContentCopyModes.Install ||
copyMode === ContentCopyModes.Temporary,
includeLibraries: installLibraries,
includeMetadata:
copyMode === ContentCopyModes.Install ||
copyMode === ContentCopyModes.Temporary
});
const dirContent = await fsExtra.readdir(tempDirPath);
await PackageImporter.extractPackage(packagePath, tempDirPath, {
includeContent:
copyMode === ContentCopyModes.Install ||
copyMode === ContentCopyModes.Temporary,
includeLibraries: installLibraries,
includeMetadata:
copyMode === ContentCopyModes.Install ||
copyMode === ContentCopyModes.Temporary
});
const dirContent = await fsExtra.readdir(tempDirPath);

// install all libraries
if (installLibraries) {
await Promise.all(
dirContent
.filter(
(dirEntry: string) =>
dirEntry !== 'h5p.json' &&
dirEntry !== 'content'
// install all libraries
if (installLibraries) {
await Promise.all(
dirContent
.filter(
(dirEntry: string) =>
dirEntry !== 'h5p.json' &&
dirEntry !== 'content'
)
.map((dirEntry: string) =>
this.libraryManager.installFromDirectory(
path.join(tempDirPath, dirEntry),
false
)
.map((dirEntry: string) =>
this.libraryManager.installFromDirectory(
path.join(tempDirPath, dirEntry),
false
)
)
);
}
)
);
}

// Copy content files to the repository
if (copyMode === ContentCopyModes.Install) {
if (!this.contentManager) {
throw new Error(
'PackageImporter was initialized without a ContentManager, but you want to copy content from a package. Pass a ContentManager object to the the constructor!'
);
}
return await this.contentManager.copyContentFromDirectory(
tempDirPath,
user,
contentId
// Copy content files to the repository
if (copyMode === ContentCopyModes.Install) {
if (!this.contentManager) {
throw new Error(
'PackageImporter was initialized without a ContentManager, but you want to copy content from a package. Pass a ContentManager object to the the constructor!'
);
}
return await this.contentManager.copyContentFromDirectory(
tempDirPath,
user,
contentId
);
}

// Copy temporary files to the repository
if (copyMode === ContentCopyModes.Temporary) {
if (!this.contentStorer) {
throw new Error(
'PackageImporter was initialized without a ContentStorer, but you want to copy content from a package. Pass a ContentStorer object to the the constructor!'
);
}
return await this.contentStorer.copyFromDirectoryToTemporary(
tempDirPath,
user
// Copy temporary files to the repository
if (copyMode === ContentCopyModes.Temporary) {
if (!this.contentStorer) {
throw new Error(
'PackageImporter was initialized without a ContentStorer, but you want to copy content from a package. Pass a ContentStorer object to the the constructor!'
);
}
} catch (error) {
// if we don't do this, finally weirdly just swallows the errors
throw error;
} finally {
// clean up temporary files in any case
await fsExtra.remove(tempDirPath);
return await this.contentStorer.copyFromDirectoryToTemporary(
tempDirPath,
user
);
}
} catch (error) {
if (error instanceof ValidationError) {
throw new H5pError(error.message); // TODO: create AJAX response?
} else {
throw error;
}
// if we don't do this, finally weirdly just swallows the errors
throw error;
} finally {
// clean up temporary files in any case
await fsExtra.remove(tempDirPath);
}

return { id: undefined, metadata: undefined, parameters: undefined };
Expand Down
4 changes: 3 additions & 1 deletion src/helpers/ValidationError.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import H5pError from './H5pError';

/**
* A ValidationError is an error object that is thrown when the validation of an object
* failed and can't be continued. ValidationError can also be used to store error messages
* if the error that occurred during validation doesn't mean that the validation has to be
* stopped right away. In this case the ValidationError is created, messages are added to it
* and it is thrown later.
*/
export default class ValidationError extends Error {
export default class ValidationError extends H5pError {
/**
* @param {string} message The first error message
*/
Expand Down

0 comments on commit d250760

Please sign in to comment.