chore(config): ng-config (#506)

This commit is contained in:
Jan Kuri 2016-04-27 00:48:41 +02:00 committed by Hans
parent 8ede7a6897
commit 8ae3cb6d6e
10 changed files with 227 additions and 33 deletions

View File

@ -1,3 +1,21 @@
{
"routes": []
}
"project": {
"version": "<%= version %>",
"name": "<%= htmlComponentName %>"
},
"apps": [
{"main": "src/client/main.ts", "tsconfig": "src/client/tsconfig.json"}
],
"addons": [],
"packages": [],
"e2e": {
"protractor": {
"config": "protractor.conf.js"
}
},
"test": {
"karma": {
"config": "karma.conf.js"
}
}
}

View File

@ -1,4 +1,5 @@
var stringUtils = require('ember-cli/lib/utilities/string');
const path = require('path');
const stringUtils = require('ember-cli/lib/utilities/string');
module.exports = {
description: '',
@ -6,11 +7,13 @@ module.exports = {
locals: function(options) {
//TODO: pull value from config
this.styleExt = 'css';
this.version = require(path.resolve(__dirname, '..', '..', '..', '..', 'package.json')).version;
return {
htmlComponentName: stringUtils.dasherize(options.entity.name),
jsComponentName: stringUtils.classify(options.entity.name),
styleExt: this.styleExt
styleExt: this.styleExt,
version: this.version
};
},

View File

@ -6,7 +6,7 @@ import {CliConfig} from '../models/config';
const GetCommand = Command.extend({
name: 'get',
description: 'Set a value in the configuration.',
works: 'outsideProject',
works: 'everywhere',
availableOptions: [],
@ -26,4 +26,3 @@ const GetCommand = Command.extend({
});
module.exports = GetCommand;
module.exports.overrideCore = true;

View File

@ -5,7 +5,7 @@ import {CliConfig} from '../models/config';
const SetCommand = Command.extend({
name: 'set',
description: 'Set a value in the configuration.',
works: 'outsideProject',
works: 'everywhere',
availableOptions: [
{ name: 'global', type: Boolean, default: false, aliases: ['g'] },
@ -22,4 +22,3 @@ const SetCommand = Command.extend({
});
module.exports = SetCommand;
module.exports.overrideCore = true;

View File

@ -1,8 +1,15 @@
/* jshint node: true */
'use strict';
const config = require('./models/config');
module.exports = {
name: 'ng2',
config: function () {
this.project.config = this.project.config || config.CliConfig.fromProject();
},
includedCommands: function () {
return {
'new': require('./commands/new'),

View File

@ -1,19 +1,16 @@
import * as fs from 'fs';
import * as path from 'path';
const schemaPath = path.resolve(process.env.CLI_ROOT, 'lib/config/schema.json');
const schema = require(schemaPath);
export const CLI_CONFIG_FILE_NAME = 'angular-cli.json';
export interface CliConfigJson {
routes?: { [name: string]: any },
packages?: { [name: string]: any }
}
export const ARRAY_METHODS = ['push', 'splice', 'sort', 'reverse', 'pop', 'shift'];
function _findUp(name: string, from: string) {
let currentDir = from;
while (currentDir && currentDir != '/') {
while (currentDir && currentDir !== path.parse(currentDir).root) {
const p = path.join(currentDir, name);
if (fs.existsSync(p)) {
return p;
@ -27,9 +24,22 @@ function _findUp(name: string, from: string) {
export class CliConfig {
constructor(private _config: CliConfigJson = CliConfig.fromProject()) {}
private _config: any;
save(path: string = CliConfig.configFilePath()) {
constructor(path?: string) {
if (path) {
try {
fs.accessSync(path);
this._config = require(path);
} catch (e) {
throw new Error(`Config file does not exits.`);
}
} else {
this._config = this._fromProject();
}
}
save(path: string = this._configFilePath()) {
if (!path) {
throw new Error('Could not find config path.');
}
@ -38,18 +48,55 @@ export class CliConfig {
}
set(jsonPath: string, value: any, force: boolean = false): boolean {
let { parent, name, remaining } = this._findParent(jsonPath);
while (force && remaining) {
if (remaining.indexOf('.') != -1) {
// Create an empty map.
// TODO: create the object / array based on the Schema of the configuration.
parent[name] = {};
}
let method: any = null;
let splittedPath = jsonPath.split('.');
if (ARRAY_METHODS.indexOf(splittedPath[splittedPath.length - 1]) != -1) {
method = splittedPath[splittedPath.length - 1];
splittedPath.splice(splittedPath.length - 1, 1);
jsonPath = splittedPath.join('.');
}
parent[name] = value;
return true;
let { parent, name, remaining } = this._findParent(jsonPath);
let properties: any;
let additionalProperties: boolean;
const checkPath = jsonPath.split('.').reduce((o, i) => {
if (!o || !o.properties) {
throw new Error(`Invalid config path.`);
}
properties = o.properties;
additionalProperties = o.additionalProperties;
return o.properties[i];
}, schema);
const configPath = jsonPath.split('.').reduce((o, i) => o[i], this._config);
if (!properties[name] && !additionalProperties) {
throw new Error(`${name} is not a known property.`);
}
if (method) {
if (Array.isArray(configPath) && checkPath.type === 'array') {
[][method].call(configPath, value);
return true;
} else {
throw new Error(`Trying to use array method on non-array property type.`);
}
}
if (typeof checkPath.type === 'string' && isNaN(value)) {
parent[name] = value;
return true;
}
if (typeof checkPath.type === 'number' && !isNaN(value)) {
parent[name] = value;
return true;
}
if (typeof value != checkPath.type) {
throw new Error(`Invalid value type. Trying to set ${typeof value} to ${path.type}`);
}
}
get(jsonPath: string): any {
@ -110,7 +157,7 @@ export class CliConfig {
return { parent, name };
}
static configFilePath(projectPath?: string): string {
private _configFilePath(projectPath?: string): string {
// Find the configuration, either where specified, in the angular-cli project
// (if it's in node_modules) or from the current process.
return (projectPath && _findUp(CLI_CONFIG_FILE_NAME, projectPath))
@ -118,8 +165,8 @@ export class CliConfig {
|| _findUp(CLI_CONFIG_FILE_NAME, process.cwd());
}
static fromProject(): CliConfigJson {
const configPath = this.configFilePath();
private _fromProject(): any {
const configPath = this._configFilePath();
return configPath ? require(configPath) : {};
}
}

View File

@ -96,5 +96,8 @@ module.exports = function(options) {
// ensure the environemnt variable for dynamic paths
process.env.PWD = process.env.PWD || process.cwd();
process.env.CLI_ROOT = process.env.CLI_ROOT || path.resolve(__dirname, '..', '..');
return cli(options);
}

View File

@ -22,7 +22,7 @@
"items": {
"type": "object",
"properties": {
"root": "string",
"main": "string",
"tsconfig": "string"
},
"additionalProperties": false

View File

@ -0,0 +1,85 @@
import {CliConfig} from '../../addon/ng2/models/config';
import * as fs from 'fs';
import * as path from 'path';
const expect = require('chai').expect;
const config = path.resolve(process.cwd(), 'addon/ng2/blueprints/ng2/files/angular-cli.json');
const configCopy = path.resolve(process.cwd(), 'angular-cli.json');
function getContents() {
return require(configCopy);
}
describe('Config Tests', () => {
before(() => {
process.chdir(process.cwd());
});
beforeEach(() => {
let contents = JSON.parse(fs.readFileSync(config, 'utf8'));
fs.writeFileSync(configCopy, JSON.stringify(contents, null, 2), 'utf8');
});
afterEach(() => {
try {
fs.accessSync(configCopy);
fs.unlinkSync(configCopy);
} catch (e) { /* */ }
});
it('Throws an error if config file not exists', () => {
fs.unlinkSync(configCopy);
let fn = () => {
return new CliConfig('foobar.json');
}
expect(fn).to.throw(Error);
});
it('Updates property of type `string` successfully', () => {
let c = new CliConfig(configCopy);
c.set('project.name', 'new-project-name');
c.save();
let contents = getContents();
expect(contents).to.be.an('object');
expect(contents.project.name).to.exist;
expect(contents.project.name).to.be.equal('new-project-name');
});
it('Throws an error if try to assign property that does not exists', () => {
let c = new CliConfig(configCopy);
let fn = () => {
c.set('project.foo', 'bar');
c.save();
}
expect(fn).to.throw(Error);
});
it('Throws an error if try to use array method on property of type `string`', () => {
let c = new CliConfig(configCopy);
let fn = () => {
c.set('project.name.push', 'new-project-name');
c.save();
}
expect(fn).to.throw(Error);
});
it('Throws an error if try to use `number` on property of type `string`', () => {
let c = new CliConfig(configCopy);
let fn = () => {
c.set('project.name', 42);
c.save();
}
expect(fn).to.throw(Error);
});
});

View File

@ -1,12 +1,45 @@
/* eslint-disable no-console */
'use strict';
const fs = require('fs');
const ts = require('typescript');
const old = require.extensions['.ts'];
require.extensions['.ts'] = function(m, filename) {
if (!filename.match(/angular-cli/) && filename.match(/node_modules/)) {
if (old) {
return old(m, filename);
}
return m._compile(fs.readFileSync(filename), filename);
}
const source = fs.readFileSync(filename).toString();
try {
const result = ts.transpile(source, {
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJs
});
// Send it to node to execute.
return m._compile(result, filename);
} catch (err) {
console.error('Error while running script "' + filename + '":');
console.error(err.stack);
throw err;
}
};
var Mocha = require('mocha');
var glob = require('glob');
var path = require('path');
var root = 'tests/{unit,acceptance,e2e}';
var specFiles = glob.sync(root + '/**/*.spec.js');
var root = 'tests/{acceptance,models,e2e}';
var specFiles = glob.sync(root + '/**/*.spec.*');
var mocha = new Mocha({ timeout: 5000, reporter: 'spec' });
process.env.CLI_ROOT = process.env.CLI_ROOT || path.resolve(__dirname, '..');
specFiles.forEach(mocha.addFile.bind(mocha));
mocha.run(function (failures) {