Skip to content

Commit bf6d1d5

Browse files
committed
Merge remote-tracking branch 'mgechev/ng-deploy' into ng-deploy
# Conflicts: # .gitignore # LICENSE # README.md # package.json
2 parents 61e7cb8 + 240e8c5 commit bf6d1d5

18 files changed

+740
-303
lines changed

.gitignore

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
1-
.DS_Store
21
node_modules
3-
npm-debug.log
2+
.tmp
3+
.sass-cache
4+
app/bower_components
5+
.DS_Store
6+
.bash_history
7+
*.swp
8+
*.swo
9+
*.d.ts
10+
11+
*.classpath
12+
*.project
13+
*.settings/
14+
*.classpath
15+
*.project
16+
*.settings/
17+
18+
.vim/bundle
19+
nvim/autoload
20+
nvim/plugged
21+
nvim/doc
22+
nvim/swaps
23+
nvim/colors
24+
dist

LICENSE

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2017 Johannes Hoppe, https://angular.schule/
3+
Copyright (c) 2017-2019 Johannes Hoppe
4+
Copyright (c) 2019 Minko Gechev
45

56
Permission is hereby granted, free of charge, to any person obtaining a copy of
67
this software and associated documentation files (the "Software"), to deal in

builders.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"$schema": "@angular-devkit/architect/src/builders-schema.json",
3+
"builders": {
4+
"deploy": {
5+
"implementation": "./deploy/builder",
6+
"schema": "./deploy/schema.json",
7+
"description": "Deploy builder"
8+
}
9+
}
10+
}

collection.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"$schema": "@angular-devkit/schematics/collection-schema.json",
3+
"schematics": {
4+
"ng-add": {
5+
"description": "Add GitHub pages deploy schematic",
6+
"factory": "./ng-add#ngAdd"
7+
}
8+
}
9+
}

deploy/actions.spec.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { JsonObject, logging } from '@angular-devkit/core';
2+
import {
3+
BuilderContext,
4+
BuilderRun,
5+
ScheduleOptions,
6+
Target
7+
} from '@angular-devkit/architect/src/index';
8+
import deploy from './actions';
9+
10+
let context: BuilderContext;
11+
12+
const PROJECT = 'pirojok-project';
13+
14+
describe('Deploy Angular apps', () => {
15+
beforeEach(() => initMocks());
16+
17+
it('should invoke the builder', async () => {
18+
const spy = spyOn(context, 'scheduleTarget').and.callThrough();
19+
await deploy(
20+
{
21+
publish: (_: string, __: any) => Promise.resolve()
22+
},
23+
context,
24+
'host',
25+
{}
26+
);
27+
expect(spy).toHaveBeenCalled();
28+
expect(spy).toHaveBeenCalledWith(
29+
{
30+
target: 'build',
31+
configuration: 'production',
32+
project: PROJECT
33+
},
34+
{}
35+
);
36+
});
37+
38+
it('should invoke ghpages.publish', async () => {
39+
const mock = {
40+
publish: (_: string, __: any) => Promise.resolve()
41+
};
42+
const spy = spyOn(mock, 'publish').and.callThrough();
43+
await deploy(mock, context, 'host', {});
44+
expect(spy).toHaveBeenCalled();
45+
expect(spy).toHaveBeenCalledWith('host', {});
46+
});
47+
48+
describe('error handling', () => {
49+
it('throws if there is no target project', async () => {
50+
context.target = undefined;
51+
try {
52+
await deploy(
53+
{
54+
publish: (_: string, __: any) => Promise.resolve()
55+
},
56+
context,
57+
'host',
58+
{}
59+
);
60+
fail();
61+
} catch (e) {
62+
expect(e.message).toMatch(/Cannot execute the build target/);
63+
}
64+
});
65+
});
66+
});
67+
68+
const initMocks = () => {
69+
context = {
70+
target: {
71+
configuration: 'production',
72+
project: PROJECT,
73+
target: 'foo'
74+
},
75+
builder: {
76+
builderName: 'mock',
77+
description: 'mock',
78+
optionSchema: false
79+
},
80+
currentDirectory: 'cwd',
81+
id: 1,
82+
logger: new logging.NullLogger() as any,
83+
workspaceRoot: 'cwd',
84+
addTeardown: _ => {},
85+
validateOptions: _ => Promise.resolve({} as any),
86+
getBuilderNameForTarget: () => Promise.resolve(''),
87+
analytics: null as any,
88+
getTargetOptions: (_: Target) => Promise.resolve({}),
89+
reportProgress: (_: number, __?: number, ___?: string) => {},
90+
reportStatus: (_: string) => {},
91+
reportRunning: () => {},
92+
scheduleBuilder: (_: string, __?: JsonObject, ___?: ScheduleOptions) =>
93+
Promise.resolve({} as BuilderRun),
94+
scheduleTarget: (_: Target, __?: JsonObject, ___?: ScheduleOptions) =>
95+
Promise.resolve({} as BuilderRun)
96+
};
97+
};

deploy/actions.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { BuilderContext } from '@angular-devkit/architect';
2+
import { GHPages } from '../interfaces';
3+
import { Schema as RealDeployOptions } from './schema';
4+
import { json } from '@angular-devkit/core';
5+
type DeployOptions = RealDeployOptions & json.JsonObject;
6+
7+
export default async function deploy(
8+
ghPages: GHPages,
9+
context: BuilderContext,
10+
projectRoot: string,
11+
options: DeployOptions
12+
) {
13+
if (!context.target) {
14+
throw new Error('Cannot execute the build target');
15+
}
16+
17+
context.logger.info(`📦 Building "${context.target.project}"`);
18+
19+
const run = await context.scheduleTarget(
20+
{
21+
target: 'build',
22+
project: context.target.project,
23+
configuration: 'production'
24+
},
25+
options
26+
);
27+
await run.result;
28+
29+
try {
30+
await ghPages.publish(projectRoot, {});
31+
if (options.deployUrl) {
32+
context.logger.info(
33+
`🚀 Your application is now available at ${options.deployUrl}`
34+
);
35+
} else {
36+
context.logger.info(`🚀 Your application is now on GitHub pages!`);
37+
}
38+
} catch (e) {
39+
context.logger.error(e);
40+
}
41+
}

deploy/builder.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {
2+
BuilderContext,
3+
BuilderOutput,
4+
createBuilder
5+
} from '@angular-devkit/architect';
6+
import { NodeJsSyncHost } from '@angular-devkit/core/node';
7+
import deploy from './actions';
8+
import { experimental, join, normalize, json } from '@angular-devkit/core';
9+
import { Schema as RealDeployOptions } from './schema';
10+
type DeployOptions = RealDeployOptions & json.JsonObject;
11+
12+
const ghpages = require('gh-pages');
13+
14+
// Call the createBuilder() function to create a builder. This mirrors
15+
// createJobHandler() but add typings specific to Architect Builders.
16+
export default createBuilder<any>(
17+
async (
18+
options: DeployOptions,
19+
context: BuilderContext
20+
): Promise<BuilderOutput> => {
21+
// The project root is added to a BuilderContext.
22+
const root = normalize(context.workspaceRoot);
23+
const workspace = new experimental.workspace.Workspace(
24+
root,
25+
new NodeJsSyncHost()
26+
);
27+
await workspace
28+
.loadWorkspaceFromHost(normalize('angular.json'))
29+
.toPromise();
30+
31+
if (!context.target) {
32+
throw new Error('Cannot deploy the application without a target');
33+
}
34+
35+
const targets = workspace.getProjectTargets(context.target.project);
36+
37+
if (
38+
!targets ||
39+
!targets.build ||
40+
!targets.build.options ||
41+
!targets.build.options.outputPath
42+
) {
43+
throw new Error('Cannot find the project output directory');
44+
}
45+
46+
try {
47+
await deploy(
48+
ghpages,
49+
context,
50+
join(workspace.root, targets.build.options.outputPath),
51+
options
52+
);
53+
} catch (e) {
54+
context.logger.error('Error when trying to deploy: ', e.message);
55+
return { success: false };
56+
}
57+
58+
return { success: true };
59+
}
60+
);

deploy/schema.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"id": "Schema",
3+
"title": "schema",
4+
"description": "Deployment of Angular CLI applications to GitHub pages",
5+
"properties": {
6+
"baseHref": {
7+
"type": "string",
8+
"description": "Base url for the application being built."
9+
},
10+
"deployUrl": {
11+
"type": "string",
12+
"description": "URL where files will be deployed."
13+
}
14+
}
15+
}

index.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import './ng-add.spec';
2+
import './deploy/actions.spec';

index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './public_api';

0 commit comments

Comments
 (0)