Merge pull request #45 from Azure/chore/cleanup

style: add prettier + cleanups
This commit is contained in:
Shmuela Jacobs 2019-08-15 11:08:54 -04:00 коммит произвёл GitHub
Родитель 39e824487c 1ebac2cd0e
Коммит 3a6bacedff
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
32 изменённых файлов: 968 добавлений и 1072 удалений

2
.gitignore поставляемый
Просмотреть файл

@ -335,7 +335,6 @@ src/**/*.js
!src/__mocks__/*.js
src/**/*.js.map
src/**/*.d.ts
!src/@types/progress.d.ts
lib/**/*
# IDEs
@ -355,3 +354,4 @@ yarn.lock
.DS_Store
out/
coverage/

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

@ -2,3 +2,4 @@
*.test.json
__mocks__
__tests__
coverage/

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

@ -1,12 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
module.exports = {
"roots": [
"<rootDir>/src"
],
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
};

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

@ -11,14 +11,20 @@
"scripts": {
"build": "tsc -p tsconfig.json && npm run copy:builders:json && npm run copy:ngadd:json && tsc -p tsconfig.json",
"start": "npm run build:watch",
"build:watch": "npm run build -- -w",
"build:watch": "npm run build -s -- -w",
"format": "npm run format:check -s -- --write",
"format:check": "prettier -l \"./src/**/*.{json,ts}\"",
"test:jest": "jest",
"test:jest:watch": "jest --watch",
"test:coverage": "jest --coverage",
"copy:builders:json": "cp ./src/builders/*.json ./out/builders",
"copy:ngadd:json": "cp ./src/ng-add/*.json ./out/ng-add"
},
"keywords": [
"schematics"
"schematics",
"angular",
"azure",
"deploy"
],
"author": {
"name": "Shmuela Jacobs",
@ -32,6 +38,10 @@
{
"name": "Chris Noring",
"url": "https://twitter.com/chris_noring"
},
{
"name": "Yohan Lasorsa",
"url": "https://twitter.com/sinedied"
}
],
"homepage": "https://github.com/Azure/ng-deploy-azure/",
@ -59,11 +69,8 @@
"@azure/storage-blob": "^10.3.0",
"adal-node": "^0.1.28",
"chalk": "^2.4.2",
"cli-ux": "^5.2.1",
"conf": "^3.0.0",
"configstore": "^4.0.0",
"fuzzy": "^0.1.3",
"gfycat-style-urls": "^1.0.3",
"glob": "^7.1.3",
"inquirer": "^6.2.2",
"inquirer-autocomplete-prompt": "^1.0.1",
@ -71,27 +78,40 @@
"ora": "^3.4.0",
"progress": "^2.0.3",
"promise-limit": "^2.7.0",
"tslib": "^1.9.3",
"typescript": "~3.4.0"
},
"devDependencies": {
"@schematics/angular": "^8.2.0",
"@types/chai": "^4.0.4",
"@types/conf": "^2.1.0",
"@types/configstore": "^4.0.0",
"@types/glob": "^7.1.1",
"@types/inquirer": "0.0.44",
"@types/jasmine": "^3.3.12",
"@types/jest": "^24.0.13",
"@types/mime-types": "^2.1.0",
"@types/mocha": "^5.2.5",
"@types/node": "^10.12.18",
"jasmine": "^3.0.0",
"@types/progress": "^2.0.3",
"husky": "^3.0.2",
"jest": "^24.8.0",
"prettier": "^1.16.4",
"prettier": "^1.18.2",
"pretty-quick": "^1.11.1",
"schematics-utilities": "^1.1.2",
"ts-jest": "^24.0.2",
"tslint-angular": "^1.1.2",
"typescript": "~3.4.0"
},
"jest": {
"roots": [
"<rootDir>/src"
],
"transform": {
"^.+\\.tsx?$": "ts-jest"
}
},
"prettier": {
"singleQuote": true,
"printWidth": 120
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
}
}

1
src/@types/progress.d.ts поставляемый
Просмотреть файл

@ -1 +0,0 @@
declare module 'progress';

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

@ -24,7 +24,11 @@ import { getAccountKey } from '../../util/azure/account';
import chalk from 'chalk';
import { loginToAzure } from '../../util/azure/auth';
export default async function deploy(context: BuilderContext, projectRoot: string, azureHostingConfig?: AzureHostingConfig) {
export default async function deploy(
context: BuilderContext,
projectRoot: string,
azureHostingConfig?: AzureHostingConfig
) {
if (!azureHostingConfig) {
throw new Error('Cannot find Azure hosting config for your app in azure.json');
}
@ -38,7 +42,9 @@ export default async function deploy(context: BuilderContext, projectRoot: strin
!azureHostingConfig.app.project ||
!azureHostingConfig.app.target
) {
throw new Error('Azure hosting config is missing some details. Please run "ng add ng-deploy-azure" and select a storage account.');
throw new Error(
'Azure hosting config is missing some details. Please run "ng add ng-deploy-azure" and select a storage account.'
);
}
const auth = await loginToAzure(context.logger);
@ -52,7 +58,7 @@ export default async function deploy(context: BuilderContext, projectRoot: strin
if (files.length === 0) {
// build the project
context.logger.info(`The folder ${ azureHostingConfig.app.path } is empty.`);
context.logger.info(`The folder ${azureHostingConfig.app.path} is empty.`);
if (!context.target) {
throw new Error('Cannot execute the target');
}
@ -64,7 +70,7 @@ export default async function deploy(context: BuilderContext, projectRoot: strin
if (azureHostingConfig.app.configuration) {
target.configuration = azureHostingConfig.app.configuration;
}
context.logger.info(`📦 Running "${ azureHostingConfig.app.target }" on "${ context.target.project }"`);
context.logger.info(`📦 Running "${azureHostingConfig.app.target}" on "${context.target.project}"`);
const run = await context.scheduleTarget(target);
await run.result;
@ -77,35 +83,35 @@ export default async function deploy(context: BuilderContext, projectRoot: strin
const client = new StorageManagementClient(credentials, azureHostingConfig.azureHosting.subscription);
const accountKey = await getAccountKey(
azureHostingConfig.azureHosting.account, client, azureHostingConfig.azureHosting.resourceGroupName);
const pipeline = ServiceURL.newPipeline(
new SharedKeyCredential(azureHostingConfig.azureHosting.account, accountKey)
azureHostingConfig.azureHosting.account,
client,
azureHostingConfig.azureHosting.resourceGroupName
);
const pipeline = ServiceURL.newPipeline(new SharedKeyCredential(azureHostingConfig.azureHosting.account, accountKey));
const serviceURL = new ServiceURL(
`https://${ azureHostingConfig.azureHosting.account }.blob.core.windows.net`,
`https://${azureHostingConfig.azureHosting.account}.blob.core.windows.net`,
pipeline
);
await uploadFilesToAzure(serviceURL, context, filesPath, files);
const accountProps = await client.storageAccounts.getProperties(
azureHostingConfig.azureHosting.resourceGroupName, azureHostingConfig.azureHosting.account);
azureHostingConfig.azureHosting.resourceGroupName,
azureHostingConfig.azureHosting.account
);
const endpoint = accountProps.primaryEndpoints && accountProps.primaryEndpoints.web;
context.logger.info(
chalk.green(`see your deployed site at ${ endpoint }`)
);
context.logger.info(chalk.green(`see your deployed site at ${endpoint}`));
// TODO: log url for account at Azure portal
}
function getFiles(context: BuilderContext, filesPath: string, projectRoot: string) {
return glob.sync(`**`, {
ignore: ['.git', '.azez.json'],
cwd: filesPath,
nodir: true,
nodir: true
});
}
@ -118,10 +124,9 @@ export async function uploadFilesToAzure(
context.logger.info('preparing static deploy');
const containerURL = ContainerURL.fromServiceURL(serviceURL, '$web');
const bar = new ProgressBar(
'[:bar] :current/:total files uploaded | :percent done | :elapseds | eta: :etas',
{ total: files.length }
);
const bar = new ProgressBar('[:bar] :current/:total files uploaded | :percent done | :elapseds | eta: :etas', {
total: files.length
});
bar.tick(0);

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

@ -36,7 +36,11 @@ export default createBuilder<any>(
}
);
export function getAzureHostingConfig(projectRoot: string, target: string, azureConfigFile: string): AzureHostingConfig | undefined {
export function getAzureHostingConfig(
projectRoot: string,
target: string,
azureConfigFile: string
): AzureHostingConfig | undefined {
const azureJson: AzureJSON = JSON.parse(readFileSync(join(projectRoot, azureConfigFile), 'UTF-8'));
const projects = azureJson.hosting;
return projects.find(project => project.app.project === target);

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

@ -28,8 +28,12 @@ describe('ng add @azure/ng-deploy', () => {
const testRunner = new SchematicTestRunner('schematics', collectionPath);
async function initAngularProject(): Promise<UnitTestTree> {
const appTree = await testRunner.runExternalSchematicAsync('@schematics/angular', 'workspace', workspaceOptions).toPromise();
return await testRunner.runExternalSchematicAsync('@schematics/angular', 'application', appOptions, appTree).toPromise();
const appTree = await testRunner
.runExternalSchematicAsync('@schematics/angular', 'workspace', workspaceOptions)
.toPromise();
return await testRunner
.runExternalSchematicAsync('@schematics/angular', 'application', appOptions, appTree)
.toPromise();
}
it('fails with a missing tree', async () => {
@ -38,7 +42,7 @@ describe('ng add @azure/ng-deploy', () => {
it('adds azure deploy to an existing project', async () => {
let appTree = await initAngularProject();
appTree = await testRunner.runSchematicAsync('ng-add', {}, appTree).toPromise()
appTree = await testRunner.runSchematicAsync('ng-add', {}, appTree).toPromise();
const angularJson = JSON.parse(appTree.readContent('/angular.json'));
expect(angularJson.projects[appOptions.name].architect.deploy).toBeDefined();
@ -50,15 +54,15 @@ describe('ng add @azure/ng-deploy', () => {
hosting: [
{
app: {
configuration: "production",
path: "dist/test-app",
project: "test-app",
target: "build",
configuration: 'production',
path: 'dist/test-app',
project: 'test-app',
target: 'build'
},
azureHosting: {
account: "fakeStorageAccount",
resourceGroupName: "fake-resource-group",
subscription: "fake-subscription-1234",
account: 'fakeStorageAccount',
resourceGroupName: 'fake-resource-group',
subscription: 'fake-subscription-1234'
}
}
]
@ -68,7 +72,7 @@ describe('ng add @azure/ng-deploy', () => {
it('should overwrite existing hosting config', async () => {
// Simulate existing app setup
let appTree = await initAngularProject();
appTree = await testRunner.runSchematicAsync('ng-add', {}, appTree).toPromise()
appTree = await testRunner.runSchematicAsync('ng-add', {}, appTree).toPromise();
appTree.overwrite('/azure.json', appTree.readContent('azure.json').replace(/fake/g, 'existing'));
const confirmMock = confirm as jest.Mock;
@ -76,7 +80,7 @@ describe('ng add @azure/ng-deploy', () => {
confirmMock.mockImplementationOnce(() => Promise.resolve(true));
// Run ng add @azure/deploy on existing project
appTree = await testRunner.runSchematicAsync('ng-add', {}, appTree).toPromise()
appTree = await testRunner.runSchematicAsync('ng-add', {}, appTree).toPromise();
expect(confirm).toHaveBeenCalledTimes(1);
expect(appTree.files).toContain('/azure.json');
@ -86,15 +90,15 @@ describe('ng add @azure/ng-deploy', () => {
hosting: [
{
app: {
configuration: "production",
path: "dist/test-app",
project: "test-app",
target: "build",
configuration: 'production',
path: 'dist/test-app',
project: 'test-app',
target: 'build'
},
azureHosting: {
account: "fakeStorageAccount",
resourceGroupName: "fake-resource-group",
subscription: "fake-subscription-1234",
account: 'fakeStorageAccount',
resourceGroupName: 'fake-resource-group',
subscription: 'fake-subscription-1234'
}
}
]
@ -104,7 +108,7 @@ describe('ng add @azure/ng-deploy', () => {
it('should keep existing hosting config', async () => {
// Simulate existing app setup
let appTree = await initAngularProject();
appTree = await testRunner.runSchematicAsync('ng-add', {}, appTree).toPromise()
appTree = await testRunner.runSchematicAsync('ng-add', {}, appTree).toPromise();
appTree.overwrite('/azure.json', appTree.readContent('azure.json').replace(/fake/g, 'existing'));
const confirmMock = confirm as jest.Mock;
@ -112,7 +116,7 @@ describe('ng add @azure/ng-deploy', () => {
confirmMock.mockImplementationOnce(() => Promise.resolve(false));
// Run ng add @azure/deploy on existing project
appTree = await testRunner.runSchematicAsync('ng-add', {}, appTree).toPromise()
appTree = await testRunner.runSchematicAsync('ng-add', {}, appTree).toPromise();
expect(confirm).toHaveBeenCalledTimes(1);
expect(appTree.files).toContain('/azure.json');
@ -122,15 +126,15 @@ describe('ng add @azure/ng-deploy', () => {
hosting: [
{
app: {
configuration: "production",
path: "dist/test-app",
project: "test-app",
target: "build",
configuration: 'production',
path: 'dist/test-app',
project: 'test-app',
target: 'build'
},
azureHosting: {
account: "existingStorageAccount",
resourceGroupName: "existing-resource-group",
subscription: "existing-subscription-1234",
account: 'existingStorageAccount',
resourceGroupName: 'existing-resource-group',
subscription: 'existing-subscription-1234'
}
}
]

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

@ -15,9 +15,7 @@ import { AddOptions } from '../util/shared/types';
export function ngAdd(_options: AddOptions): Rule {
return (tree: Tree, _context: SchematicContext) => {
return chain([
addDeployAzure(_options)
])(tree, _context);
return chain([addDeployAzure(_options)])(tree, _context);
};
}
@ -27,8 +25,7 @@ export function addDeployAzure(_options: AddOptions): Rule {
const azureJson = readAzureJson(tree);
const hostingConfig = azureJson ? getAzureHostingConfig(azureJson, project.projectName) : null;
if (!hostingConfig || await confirm(`Overwrite existing Azure config for ${ project.projectName }?`)) {
if (!hostingConfig || (await confirm(`Overwrite existing Azure config for ${project.projectName}?`))) {
const auth = await loginToAzure(_context.logger);
const credentials = auth.credentials as DeviceTokenCredentials;
const subscription = await selectSubscription(auth.subscriptions, _options, _context.logger);
@ -51,7 +48,6 @@ export function addDeployAzure(_options: AddOptions): Rule {
// TODO: log url for account at Azure portal
generateAzureJson(tree, appDeployConfig, azureDeployConfig);
}
project.addLogoutArchitect();

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

@ -3,7 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const loginToAzure = () => Promise.resolve({
export const loginToAzure = () =>
Promise.resolve({
credentials: null,
subscriptions: []
});
});

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

@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/
export async function getResourceGroups() {
return Promise.resolve([{
return Promise.resolve([
{
id: '1',
name: 'mock',
location: 'location'
@ -18,9 +19,8 @@ export async function getResourceGroups() {
id: '3',
name: 'mock3',
location: 'location'
}]);
}
]);
}
export const createResourceGroup = jest.fn((name: string) => Promise.resolve({ name }))
export const createResourceGroup = jest.fn((name: string) => Promise.resolve({ name }));

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

@ -3,7 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const getResourceGroup = () => Promise.resolve({
export const getResourceGroup = () =>
Promise.resolve({
id: '4321',
name: 'fake-resource-group',
location: 'westus'

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

@ -3,5 +3,4 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const selectSubscription = () => Promise.resolve('fake-subscription-1234');
export const selectSubscription = () => Promise.resolve('fake-subscription-1234');

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

@ -40,8 +40,8 @@ export async function getAccount(
client: StorageManagementClient,
resourceGroup: ResourceGroup,
options: AddOptions,
logger: Logger) {
logger: Logger
) {
let accountName = options.account || '';
let needToCreateAccount = false;
@ -52,7 +52,7 @@ export async function getAccount(
function getInitialAccountName() {
const normalizedProjectNameArray = options.project.match(/[a-zA-Z0-9]/g);
const normalizedProjectName = normalizedProjectNameArray ? normalizedProjectNameArray.join('') : '';
return `${ normalizedProjectName }static`;
return `${normalizedProjectName}static`;
}
const initialName = getInitialAccountName();
@ -63,27 +63,29 @@ export async function getAccount(
newAccountPromptOptions.defaultGenerator = generateDefaultAccountName;
newAccountPromptOptions.validate = validateAccountName;
if (accountName) {
const account = accounts.find(acc => acc.name === accountName);
if (!!account) { // account exists
if (!!account) {
// account exists
// TODO: check account configuration
logger.info(`Using existing account ${ accountName }`);
} else { // create account with this name, if valid
logger.info(`Using existing account ${accountName}`);
} else {
// create account with this name, if valid
const valid = await validateAccountName(accountName);
if (!valid) {
accountName = (await newItemPrompt(newAccountPromptOptions)).newAccount;
}
needToCreateAccount = true;
}
} else { // no account flag
} else {
// no account flag
if (!options.manual) { // quickstart - create w/ default name
if (!options.manual) {
// quickstart - create w/ default name
accountName = await generateDefaultAccountName(initialName);
needToCreateAccount = true;
} else { // select from list or create new
} else {
// select from list or create new
const result = await filteredList(accounts as AccountDetails[], accountPromptOptions, newAccountPromptOptions);
needToCreateAccount = !!result.newAccount;
accountName = result.newAccount || result.account.name;
@ -91,7 +93,7 @@ export async function getAccount(
}
if (needToCreateAccount) {
spinner.start(`creating ${ accountName }`);
spinner.start(`creating ${accountName}`);
await createAccount(accountName, client, resourceGroup.name, resourceGroup.location);
spinner.succeed();
}
@ -128,18 +130,9 @@ export async function setStaticSiteToPublic(serviceURL: ServiceURL) {
});
}
export async function getAccountKey(
account: any,
client: StorageManagementClient,
resourceGroup: any
) {
const accountKeysRes = await client.storageAccounts.listKeys(
resourceGroup,
account
);
const accountKey = (accountKeysRes.keys || []).filter(
key => (key.permissions || '').toUpperCase() === 'FULL'
)[0];
export async function getAccountKey(account: any, client: StorageManagementClient, resourceGroup: any) {
const accountKeysRes = await client.storageAccounts.listKeys(resourceGroup, account);
const accountKey = (accountKeysRes.keys || []).filter(key => (key.permissions || '').toUpperCase() === 'FULL')[0];
if (!accountKey || !accountKey.value) {
process.exit(1);
return '';
@ -153,15 +146,11 @@ export async function createAccount(
resourceGroupName: string,
location: string
) {
const poller = await client.storageAccounts.beginCreate(
resourceGroupName,
account,
{
const poller = await client.storageAccounts.beginCreate(resourceGroupName, account, {
kind: 'StorageV2',
location,
sku: { name: 'Standard_LRS' }
}
);
});
await poller.pollUntilFinished();
spinner.start('Retrieving account keys');
@ -174,23 +163,14 @@ export async function createAccount(
spinner.start('Creating web container');
await createWebContainer(client, resourceGroupName, account);
spinner.succeed();
const pipeline = ServiceURL.newPipeline(
new SharedKeyCredential(account, accountKey)
);
const serviceURL = new ServiceURL(
`https://${ account }.blob.core.windows.net`,
pipeline
);
const pipeline = ServiceURL.newPipeline(new SharedKeyCredential(account, accountKey));
const serviceURL = new ServiceURL(`https://${account}.blob.core.windows.net`, pipeline);
spinner.start('Setting container to be publicly available static site');
await setStaticSiteToPublic(serviceURL);
spinner.succeed();
}
export async function createWebContainer(
client: StorageManagementClient,
resourceGroup: any,
account: any
) {
export async function createWebContainer(client: StorageManagementClient, resourceGroup: any, account: any) {
await client.blobContainers.create(resourceGroup, account, '$web', {
publicAccess: 'Container',
metadata: {

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

@ -2,11 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {
interactiveLoginWithAuthResponse,
DeviceTokenCredentials,
AuthResponse
} from '@azure/ms-rest-nodeauth';
import { interactiveLoginWithAuthResponse, DeviceTokenCredentials, AuthResponse } from '@azure/ms-rest-nodeauth';
import { MemoryCache } from 'adal-node';
import { Environment } from '@azure/ms-rest-azure-env';
import Conf from 'conf';
@ -26,14 +22,12 @@ export async function clearCreds() {
}
export async function loginToAzure(logger: Logger): Promise<AuthResponse> {
let auth = await globalConfig.get(AUTH) as AuthResponse | null;
let auth = (await globalConfig.get(AUTH)) as AuthResponse | null;
if (auth && auth.credentials) {
const creds = auth.credentials as DeviceTokenCredentials;
const cache = new MemoryCache();
cache.add(creds.tokenCache._entries, () => {
});
cache.add(creds.tokenCache._entries, () => {});
auth.credentials = new DeviceTokenCredentials(
creds.clientId,
@ -41,7 +35,8 @@ export async function loginToAzure(logger: Logger): Promise<AuthResponse> {
creds.username,
creds.tokenAudience,
new Environment(creds.environment),
cache);
cache
);
const token = await auth.credentials.getToken();
if (new Date(token.expiresOn).getTime() < Date.now()) {

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

@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getLocation } from '../locations';
import { getLocation } from './locations';
describe('location', () => {
test('should return undefined when locationName is undefined', () => {
@ -15,4 +15,4 @@ describe('location', () => {
expect(actual && actual.id).toBe('southafricanorth');
expect(actual && actual.name).toBe('South Africa North');
});
})
});

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

@ -8,122 +8,122 @@ export interface StorageLocation {
}
export const defaultLocation = {
'id': 'westus',
'name': 'West US'
id: 'westus',
name: 'West US'
};
export const locations = [
{
'id': 'eastasia',
'name': 'East Asia'
id: 'eastasia',
name: 'East Asia'
},
{
'id': 'southeastasia',
'name': 'Southeast Asia'
id: 'southeastasia',
name: 'Southeast Asia'
},
{
'id': 'centralus',
'name': 'Central US'
id: 'centralus',
name: 'Central US'
},
{
'id': 'eastus',
'name': 'East US'
id: 'eastus',
name: 'East US'
},
{
'id': 'eastus2',
'name': 'East US 2'
id: 'eastus2',
name: 'East US 2'
},
{
'id': 'westus',
'name': 'West US'
id: 'westus',
name: 'West US'
},
{
'id': 'northcentralus',
'name': 'North Central US'
id: 'northcentralus',
name: 'North Central US'
},
{
'id': 'southcentralus',
'name': 'South Central US'
id: 'southcentralus',
name: 'South Central US'
},
{
'id': 'northeurope',
'name': 'North Europe'
id: 'northeurope',
name: 'North Europe'
},
{
'id': 'westeurope',
'name': 'West Europe'
id: 'westeurope',
name: 'West Europe'
},
{
'id': 'japanwest',
'name': 'Japan West'
id: 'japanwest',
name: 'Japan West'
},
{
'id': 'japaneast',
'name': 'Japan East'
id: 'japaneast',
name: 'Japan East'
},
{
'id': 'brazilsouth',
'name': 'Brazil South'
id: 'brazilsouth',
name: 'Brazil South'
},
{
'id': 'australiaeast',
'name': 'Australia East'
id: 'australiaeast',
name: 'Australia East'
},
{
'id': 'australiasoutheast',
'name': 'Australia Southeast'
id: 'australiasoutheast',
name: 'Australia Southeast'
},
{
'id': 'southindia',
'name': 'South India'
id: 'southindia',
name: 'South India'
},
{
'id': 'centralindia',
'name': 'Central India'
id: 'centralindia',
name: 'Central India'
},
{
'id': 'westindia',
'name': 'West India'
id: 'westindia',
name: 'West India'
},
{
'id': 'canadacentral',
'name': 'Canada Central'
id: 'canadacentral',
name: 'Canada Central'
},
{
'id': 'canadaeast',
'name': 'Canada East'
id: 'canadaeast',
name: 'Canada East'
},
{
'id': 'uksouth',
'name': 'UK South'
id: 'uksouth',
name: 'UK South'
},
{
'id': 'ukwest',
'name': 'UK West'
id: 'ukwest',
name: 'UK West'
},
{
'id': 'westcentralus',
'name': 'West Central US'
id: 'westcentralus',
name: 'West Central US'
},
{
'id': 'westus2',
'name': 'West US 2'
id: 'westus2',
name: 'West US 2'
},
{
'id': 'koreacentral',
'name': 'Korea Central'
id: 'koreacentral',
name: 'Korea Central'
},
{
'id': 'koreasouth',
'name': 'Korea South'
id: 'koreasouth',
name: 'Korea South'
},
{
'id': 'francecentral',
'name': 'France Central'
id: 'francecentral',
name: 'France Central'
},
{
'id': 'southafricanorth',
'name': 'South Africa North'
id: 'southafricanorth',
name: 'South Africa North'
}
];

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

@ -2,10 +2,10 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ResourceManagementClient } from "@azure/arm-resources";
import { ListItem } from "../prompt/list";
import { DeviceTokenCredentials } from "@azure/ms-rest-nodeauth";
import { ResourceGroupsCreateOrUpdateResponse } from "@azure/arm-resources/esm/models";
import { ResourceManagementClient } from '@azure/arm-resources';
import { ListItem } from '../prompt/list';
import { DeviceTokenCredentials } from '@azure/ms-rest-nodeauth';
import { ResourceGroupsCreateOrUpdateResponse } from '@azure/arm-resources/esm/models';
export interface ResourceGroupDetails extends ListItem {
id: string;
@ -16,7 +16,7 @@ export interface ResourceGroupDetails extends ListItem {
export async function getResourceGroups(creds: DeviceTokenCredentials, subscription: string) {
const client = new ResourceManagementClient(creds, subscription);
const resourceGroupList = await client.resourceGroups.list() as ResourceGroupDetails[];
const resourceGroupList = (await client.resourceGroups.list()) as ResourceGroupDetails[];
return resourceGroupList;
}
@ -28,7 +28,8 @@ export async function createResourceGroup(
): Promise<ResourceGroupsCreateOrUpdateResponse> {
// TODO: throws an error here if the subscription is wrong
const client = new ResourceManagementClient(creds, subscription);
const resourceGroupRes = await client.resourceGroups.createOrUpdate(name, { location });
const resourceGroupRes = await client.resourceGroups.createOrUpdate(name, {
location
});
return resourceGroupRes;
}

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

@ -2,10 +2,9 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getResourceGroup, ResourceGroup } from '../resource-group';
import { getResourceGroup, ResourceGroup } from './resource-group';
import { DeviceTokenCredentials } from '@azure/ms-rest-nodeauth';
import { AddOptions } from '../../shared/types';
import { AddOptions } from '../shared/types';
const RESOURCE_GROUP = 'GROUP';
@ -21,11 +20,11 @@ const logger = {
fatal: jest.fn()
};
jest.mock('../resource-group-helper');
jest.mock('../../prompt/name-generator');
jest.mock('../../prompt/spinner');
jest.mock('./resource-group-helper');
jest.mock('../prompt/name-generator');
jest.mock('../prompt/spinner');
import { createResourceGroup } from '../resource-group-helper';
import { createResourceGroup } from './resource-group-helper';
const createResourceGroupMock: jest.Mock<any, any> = <jest.Mock<any, any>>createResourceGroup;
describe('resource group', () => {
@ -34,21 +33,23 @@ describe('resource group', () => {
createResourceGroupMock.mockClear();
});
test.only('should create resource group', async() => {
test.only('should create resource group', async () => {
const subscription = '';
await getResourceGroup(credentials, subscription, options, logger);
expect(createResourceGroupMock.mock.calls[0][0]).toBe(RESOURCE_GROUP);
});
test('should use existing resource group and return it', async() => {
test('should use existing resource group and return it', async () => {
// there needs to be a match towards resource group list
const subscription = '';
const existingMockResourceGroup = 'mock2'
const optionsWithMatch = { ...options, resourceGroup: existingMockResourceGroup };
const existingMockResourceGroup = 'mock2';
const optionsWithMatch = {
...options,
resourceGroup: existingMockResourceGroup
};
const resourceGroup: ResourceGroup = await getResourceGroup(credentials, subscription, optionsWithMatch, logger);
expect(createResourceGroupMock.mock.calls.length).toBe(0);
expect(logger.info.mock.calls.length).toBe(1);

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

@ -39,9 +39,11 @@ const locationPromptOptions = {
};
export async function getResourceGroup(
creds: DeviceTokenCredentials, subscription: string, options: AddOptions, logger: Logger
creds: DeviceTokenCredentials,
subscription: string,
options: AddOptions,
logger: Logger
): Promise<ResourceGroup> {
let resourceGroupName = options.resourceGroup || '';
let location = getLocation(options.location);
@ -53,26 +55,26 @@ export async function getResourceGroup(
const initialName = options.project + '-static-deploy';
const defaultResourceGroupName = await resourceGroupNameGenerator(initialName, resourceGroupList);
if (!options.manual) { // quickstart
if (!options.manual) {
// quickstart
resourceGroupName = resourceGroupName || defaultResourceGroupName;
location = location || defaultLocation;
}
if (!!resourceGroupName) { // provided or quickstart + default
if (!!resourceGroupName) {
// provided or quickstart + default
result = resourceGroupList.find(rg => rg.name === resourceGroupName);
if (!!result) {
logger.info(`Using existing resource group ${ resourceGroupName }`);
logger.info(`Using existing resource group ${resourceGroupName}`);
}
} else { // not provided + manual
} else {
// not provided + manual
// TODO: default name can be assigned later, only if creating a new resource group.
// TODO: check availability of the default name
newResourceGroupsPromptOptions.default = defaultResourceGroupName;
result = (await filteredList(
resourceGroupList,
resourceGroupsPromptOptions,
newResourceGroupsPromptOptions));
result = await filteredList(resourceGroupList, resourceGroupsPromptOptions, newResourceGroupsPromptOptions);
// TODO: add check whether the new resource group doesn't already exist.
// Currently throws an error of exists in a different location:
@ -83,8 +85,8 @@ export async function getResourceGroup(
}
if (!result || result.newResourceGroup) {
location = location || await askLocation(); // if quickstart - location defined above
spinner.start(`Creating resource group ${ resourceGroupName } at ${ location.name } (${ location.id })`);
location = location || (await askLocation()); // if quickstart - location defined above
spinner.start(`Creating resource group ${resourceGroupName} at ${location.name} (${location.id})`);
result = await createResourceGroup(resourceGroupName, subscription, creds, location.id);
spinner.succeed();
}

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

@ -2,15 +2,14 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { selectSubscription } from '../subscription';
import { selectSubscription } from './subscription';
import { LinkedSubscription } from '@azure/ms-rest-nodeauth';
import { AddOptions } from '../../shared/types';
import { AddOptions } from '../shared/types';
jest.mock('inquirer');
// AddOptions, Logger
const SUBID = '124';
const SUBNAME = 'name';
@ -38,26 +37,28 @@ describe('subscription', () => {
});
test('should throw error when input is an EMPTY array', async () => {
const errorMessage = 'You don\'t have any active subscriptions. ' +
const errorMessage =
"You don't have any active subscriptions. " +
'Head to https://azure.com/free and sign in. From there you can create a new subscription ' +
'and then you can come back and try again.';
expect(selectSubscription([], optionsMock, loggerMock)).rejects.toEqual(new Error(errorMessage))
expect(selectSubscription([], optionsMock, loggerMock)).rejects.toEqual(new Error(errorMessage));
});
test('provided sub id DOES NOT match when provided in options', async() => {
const subs = <Array<LinkedSubscription>>[{
test('provided sub id DOES NOT match when provided in options', async () => {
const subs = <Array<LinkedSubscription>>[
{
id: '456',
name: 'a sub'
}];
}
];
selectSubscription(subs, optionsMock, loggerMock);
const warnCalledTwice = loggerMock.warn.mock.calls.length === 2;
expect(loggerMock.warn.mock.calls[0][0]).toBe(`The provided subscription ID does not exist.`);
expect(loggerMock.warn.mock.calls[1][0]).toBe(`Using subscription ${subs[0].name} - ${subs[0].id}`)
expect(loggerMock.warn.mock.calls[1][0]).toBe(`Using subscription ${subs[0].name} - ${subs[0].id}`);
expect(warnCalledTwice).toBeTruthy();
});
@ -73,21 +74,18 @@ describe('subscription', () => {
});
test('should throw error when input is undefined', async () => {
const errorMessage = 'API returned no subscription IDs. It should. ' +
'Log in to https://portal.azure.com and see if there\'s something wrong with your account.';
const errorMessage =
'API returned no subscription IDs. It should. ' +
"Log in to https://portal.azure.com and see if there's something wrong with your account.";
// this one looks a bit weird because method is `async`, otherwise throwError() helper should be used
expect(selectSubscription(undefined, optionsMock, loggerMock)).rejects.toEqual(new Error(errorMessage))
expect(selectSubscription(undefined, optionsMock, loggerMock)).rejects.toEqual(new Error(errorMessage));
});
test('should prompt user to select a subscription if more than one subscription', async () => {
const expected = 'subMock'; // check inquirer.js at __mocks__ at root level
const subs = <Array<LinkedSubscription>>[
{ id: 'abc', name: 'subMock' },
{ id: '123', name: 'sub2' }
];
const subs = <Array<LinkedSubscription>>[{ id: 'abc', name: 'subMock' }, { id: '123', name: 'sub2' }];
const actual = await selectSubscription(subs, optionsMock, loggerMock);
// TODO verify that prompt is being invoked
@ -95,4 +93,3 @@ describe('subscription', () => {
expect(actual).toEqual(expected);
});
});

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

@ -6,7 +6,6 @@ import { LinkedSubscription } from '@azure/ms-rest-nodeauth';
import { prompt } from 'inquirer';
import { AddOptions, Logger } from '../shared/types';
export async function selectSubscription(
subs: LinkedSubscription[] | undefined,
options: AddOptions,
@ -15,7 +14,7 @@ export async function selectSubscription(
if (Array.isArray(subs)) {
if (subs.length === 0) {
throw new Error(
'You don\'t have any active subscriptions. ' +
"You don't have any active subscriptions. " +
'Head to https://azure.com/free and sign in. From there you can create a new subscription ' +
'and then you can come back and try again.'
);
@ -35,7 +34,7 @@ export async function selectSubscription(
if (subs.length === 1) {
if (subProvided) {
logger.warn(`Using subscription ${ subs[0].name } - ${ subs[0].id }`);
logger.warn(`Using subscription ${subs[0].name} - ${subs[0].id}`);
}
return subs[0].id;
} else {
@ -44,7 +43,7 @@ export async function selectSubscription(
type: 'list',
name: 'sub',
choices: subs.map(choice => ({
name: `${ choice.name }${ choice.id }`,
name: `${choice.name}${choice.id}`,
value: choice.id
})),
message: 'Under which subscription should we put this static site?'
@ -56,6 +55,6 @@ export async function selectSubscription(
throw new Error(
'API returned no subscription IDs. It should. ' +
'Log in to https://portal.azure.com and see if there\'s something wrong with your account.'
"Log in to https://portal.azure.com and see if there's something wrong with your account."
);
}

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

@ -1,7 +0,0 @@
console.log('using mock spinner');
export const spinner = {
start: jest.fn(),
stop: jest.fn(),
succeed: jest.fn()
}

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

@ -10,7 +10,7 @@ export interface PromptOptions {
name?: string;
message: string;
default?: string;
defaultGenerator?: ((name: string) => Promise<string>);
defaultGenerator?: (name: string) => Promise<string>;
title?: string;
validate?: any;
id: string;
@ -38,11 +38,11 @@ export async function filteredList(list: ListItem[], listOptions: PromptOptions,
}
export async function newItemPrompt(newItemOptions: PromptOptions) {
let item, valid = true;
const defaultValue =
newItemOptions.defaultGenerator ?
await newItemOptions.defaultGenerator(newItemOptions.default || '') :
newItemOptions.default;
let item,
valid = true;
const defaultValue = newItemOptions.defaultGenerator
? await newItemOptions.defaultGenerator(newItemOptions.default || '')
: newItemOptions.default;
do {
item = await (inquirer as any).prompt({
type: 'input',

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

@ -7,7 +7,7 @@ export async function generateName(name: string, validate: (name: string) => Pro
do {
valid = await validate(name);
if (!valid) {
name = `${ name }${ Math.ceil(Math.random() * 100) }`;
name = `${name}${Math.ceil(Math.random() * 100)}`;
}
} while (!valid);
return name;

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

@ -8,21 +8,15 @@ const ora = require('ora');
export const spinner = ora({
text: 'Rounding up all the reptiles',
spinner: {
frames: [
chalk.red('▌'),
chalk.green('▀'),
chalk.yellow('▐'),
chalk.blue('▄')
],
frames: [chalk.red('▌'), chalk.green('▀'), chalk.yellow('▐'), chalk.blue('▄')],
interval: 100
},
}
});
export function spin (msg?: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
export function spin(msg?: string) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function () {
descriptor.value = async function() {
spinner.start(msg);
let result;
try {
@ -35,6 +29,4 @@ export function spin (msg?: string) {
};
return descriptor;
};
}

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

@ -27,7 +27,9 @@ export class AngularWorkspace {
this.project = this.getProject(options);
this.target = 'build'; // TODO allow configuration of other options
this.configuration = 'production';
this.path = this.project.architect ? this.project.architect[this.target].options.outputPath : `dist/${ this.projectName }`;
this.path = this.project.architect
? this.project.architect[this.target].options.outputPath
: `dist/${this.projectName}`;
}
getPath() {
@ -47,10 +49,7 @@ export class AngularWorkspace {
getWorkspace() {
let schema: experimental.workspace.WorkspaceSchema;
try {
schema = parseJson(
this.content,
JsonParseMode.Loose
) as {} as experimental.workspace.WorkspaceSchema;
schema = (parseJson(this.content, JsonParseMode.Loose) as {}) as experimental.workspace.WorkspaceSchema;
} catch (e) {
throw new SchematicsException(`Could not parse angular.json: ` + e.message);
}
@ -73,7 +72,6 @@ export class AngularWorkspace {
}
getProject(options: any) {
const project = this.schema.projects[this.projectName];
if (!project) {
throw new SchematicsException('Project is not defined in this workspace');
@ -83,12 +81,15 @@ export class AngularWorkspace {
throw new SchematicsException(`Deploy requires a project type of "application" in angular.json`);
}
if (!project.architect ||
if (
!project.architect ||
!project.architect.build ||
!project.architect.build.options ||
!project.architect.build.options.outputPath) {
!project.architect.build.options.outputPath
) {
throw new SchematicsException(
`Cannot read the output path (architect.build.options.outputPath) of project "${ this.projectName }" in angular.json`);
`Cannot read the output path (architect.build.options.outputPath) of project "${this.projectName}" in angular.json`
);
}
return project;
@ -126,5 +127,4 @@ export class AngularWorkspace {
this.updateTree();
}
}

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

@ -78,7 +78,7 @@ function safeReadJSON(path: string, tree: Tree) {
}
return JSON.parse(json.toString());
} catch (e) {
throw new SchematicsException(`Error when parsing ${ path }: ${ e.message }`);
throw new SchematicsException(`Error when parsing ${path}: ${e.message}`);
}
}

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

@ -1,11 +1,7 @@
{
"compilerOptions": {
"baseUrl": "tsconfig",
"lib": [
"es2018",
"es2015",
"dom"
],
"lib": ["es2018", "dom"],
"outDir": "out",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
@ -24,17 +20,8 @@
"sourceMap": true,
"strictNullChecks": true,
"target": "es6",
"types": [
"jest",
"node"
]
"types": ["jest", "node"]
},
"include": [
"src/**/*"
],
"exclude": [
"src/*/files/**/*",
"**/__mocks__/*",
"**/__tests__/*"
]
"include": ["src/**/*"],
"exclude": ["**/*.spec.ts", "src/*/files/**/*", "**/__mocks__/*", "**/__tests__/*"]
}

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

@ -1,69 +0,0 @@
{
"rulesDirectory": ["codelyzer"],
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [true, "check-space"],
"curly": true,
"deprecation": {
"severity": "warn"
},
"eofline": true,
"forin": true,
"import-blacklist": [true, "rxjs/Rx"],
"import-spacing": true,
"indent": [true, "spaces"],
"interface-over-type-literal": true,
"label-position": true,
"max-classes-per-file": [true, 1],
"max-line-length": [true, 140],
"member-access": false,
"member-ordering": [
true,
{
"order": ["static-field", "instance-field", "static-method", "instance-method"]
}
],
"no-arg": true,
"no-bitwise": true,
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [true, "ignore-params"],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [true, "check-open-brace", "check-catch", "check-else", "check-whitespace"],
"prefer-const": true,
"quotemark": [true, "single"],
"radix": true,
"semicolon": [true, "always"],
"triple-equals": [true, "allow-null-check"],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"unified-signatures": true,
"variable-name": false,
"whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"]
}
}