diff --git a/docs/documentation/stories/asset-configuration.md b/docs/documentation/stories/asset-configuration.md index ef910b1d8561..f6f1f06f1238 100644 --- a/docs/documentation/stories/asset-configuration.md +++ b/docs/documentation/stories/asset-configuration.md @@ -60,3 +60,24 @@ to `true` on your asset definition, like so: ``` This needs to be set for every assets you want to write outside of your build output directory. + +## Read assets outside of `dist/` + +This needs to be set for every assets you want to write outside of your build output directory. + +Because reading files in your project isn't an expected effect of `ng build`, it is disabled by +default on every assets. In order to allow this behaviour, you need to set `allowOutsideReadDir` +to `true` on your asset definition, like so: + +```json +"assets": [ + { + "glob": "**/*", + "input": "../not-dist/some/folder/", + "output": "./assets/", + "allowOutsideReadDir": true + }, +] +``` + +This needs to be set for every assets you want to write outside of your build output directory. diff --git a/packages/@angular/cli/lib/config/schema.json b/packages/@angular/cli/lib/config/schema.json index b48bdabf990b..c37b654bf006 100644 --- a/packages/@angular/cli/lib/config/schema.json +++ b/packages/@angular/cli/lib/config/schema.json @@ -91,6 +91,11 @@ "type": "boolean", "description": "Allow assets to be copied outside the outDir.", "default": false + }, + "allowOutsideReadDir": { + "type": "boolean", + "description": "Allow assets to be read outside the project dir.", + "default": false } }, "additionalProperties": false diff --git a/packages/@angular/cli/models/webpack-configs/common.ts b/packages/@angular/cli/models/webpack-configs/common.ts index 6ac61f5a18fc..588807fe6ef1 100644 --- a/packages/@angular/cli/models/webpack-configs/common.ts +++ b/packages/@angular/cli/models/webpack-configs/common.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import { HashedModuleIdsPlugin } from 'webpack'; import * as CopyWebpackPlugin from 'copy-webpack-plugin'; +import chalk from 'chalk'; import { extraEntryParser, getOutputHashFormat, AssetPattern } from './utils'; import { isDirectory } from '../../utilities/is-directory'; import { requireProjectModule } from '../../utilities/require-project-module'; @@ -106,19 +107,28 @@ export function getCommonConfig(wco: WebpackConfigOptions) { if (!asset.allowOutsideOutDir) { const message = 'An asset cannot be written to a location outside of the output path. ' - + 'You can override this message by setting the `allowOutsideOutDir` ' - + 'property on the asset to true in the CLI configuration.'; + + 'You can override this message by setting the `allowOutsideOutDir` ' + + 'property on the asset to true in the CLI configuration.'; throw new SilentError(message); } } // Prevent asset configurations from reading files outside of the project. const projectRelativeInput = path.relative(projectRoot, asset.input); - if (projectRelativeInput.startsWith('..') || path.isAbsolute(projectRelativeInput)) { - const message = 'An asset cannot be read from a location outside the project.'; + if ((projectRelativeInput.startsWith('..') || path.isAbsolute(projectRelativeInput)) + && !asset.allowOutsideReadDir) { + + const message = `${asset.input} cannot be read from a location outside the project.` + + 'You can override this message by setting the `allowOutsideReadDir` ' + + 'property on the asset to true in the CLI configuration.'; throw new SilentError(message); } + if (asset.allowOutsideReadDir) { + console.log(chalk.yellow('The allowOutsideReadDir option' + + ' is on that could be a security risk')); + } + // Ensure trailing slash. if (isDirectory(path.resolve(asset.input))) { asset.input += '/'; @@ -190,7 +200,8 @@ export function getCommonConfig(wco: WebpackConfigOptions) { : 'rxjs/_esm5/path-mapping'; const rxPaths = requireProjectModule(projectRoot, rxjsPathMappingImport); alias = rxPaths(nodeModules); - } catch (e) { } + } catch (e) { + } // Allow loaders to be in a node_modules nested inside the CLI package const loaderNodeModules = ['node_modules']; diff --git a/packages/@angular/cli/models/webpack-configs/utils.ts b/packages/@angular/cli/models/webpack-configs/utils.ts index a7a019aea872..8e5515635da3 100644 --- a/packages/@angular/cli/models/webpack-configs/utils.ts +++ b/packages/@angular/cli/models/webpack-configs/utils.ts @@ -98,4 +98,5 @@ export interface AssetPattern { input?: string; output?: string; allowOutsideOutDir?: boolean; + allowOutsideReadDir?: boolean; } diff --git a/tests/e2e/tests/basic/assets.ts b/tests/e2e/tests/basic/assets.ts index 41d36a5463d0..5527cca48f41 100644 --- a/tests/e2e/tests/basic/assets.ts +++ b/tests/e2e/tests/basic/assets.ts @@ -83,6 +83,14 @@ export default function () { ]; })) .then(() => expectToFail(() => ng('build'))) + .then(() => updateJsonFile('.angular-cli.json', configJson => { + const app = configJson['apps'][0]; + app['assets'] = [ + { 'glob': '**/*', 'input': '/temp-folder/outside-allowed/of/project', 'output': 'temp', + 'allowOutsideReadDir': true } + ]; + })) + .then(() => ng('build')) // Add asset config in .angular-cli.json. .then(() => updateJsonFile('.angular-cli.json', configJson => {