Skip to content

Commit 8d1edc5

Browse files
Merge pull request #71 from imranmomin/netlify-createSite
Netlify - Create Site
2 parents 086e54e + a128e00 commit 8d1edc5

File tree

9 files changed

+234
-172
lines changed

9 files changed

+234
-172
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
node_modules
44
*.js
55
*.js.map
6-
.gitignore
6+
.gitignore
7+
*.code-workspace

src/.editorconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 4
6+
charset = utf-8
7+
trim_trailing_whitespace = false
8+
insert_final_newline = false

src/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ Skip build process during deployment.
5353
This can be used when you are sure that you haven't changed anything and want to deploy with the latest artifact.
5454
This command causes the `--configuration` setting to have no effect.
5555

56+
#### --create <a name="create"></a>
57+
* __optional__
58+
* Default: `false` (string)
59+
* Example:
60+
* `ng deploy --create` – Will create a new site if there is no site id or the site id is does not exists on netlify
5661

5762
## License
5863

src/deploy/index.ts

Lines changed: 133 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,140 @@
11
import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
22
import { json } from '@angular-devkit/core';
33
import { Schema } from './schema';
4-
54
const NetlifyAPI = require('netlify');
65

76
export default createBuilder<any>(
8-
async (builderConfig: Schema, context: BuilderContext): Promise<BuilderOutput> => {
9-
context.reportStatus(`Executing deploy...`);
10-
context.logger.info(`Executing netlify deploy command ...... `);
11-
let buildResult;
12-
if (builderConfig.noBuild) {
13-
context.logger.info(`📦 Skipping build`);
14-
buildResult = true;
15-
} else {
16-
const configuration = builderConfig.configuration ? builderConfig.configuration : 'production';
17-
18-
const overrides = {
19-
// this is an example how to override the workspace set of options
20-
...(builderConfig.baseHref && { baseHref: builderConfig.baseHref })
21-
};
22-
23-
if (!context.target) {
24-
throw new Error('Cannot deploy the application without a target');
25-
}
26-
27-
context.logger.info(`📦 Building "${context.target.project}". Configuration: "${configuration}".${builderConfig.baseHref ? ' Your base-href: "' + builderConfig.baseHref + '"' : ''}`);
28-
29-
const build = await context.scheduleTarget({
30-
target: 'build',
31-
project: context.target !== undefined ? context.target.project : '',
32-
configuration
33-
}, overrides as json.JsonObject);
34-
35-
buildResult = await build.result;
36-
}
37-
38-
if (buildResult.success || buildResult) {
39-
context.logger.info(`✔ Build Completed`);
40-
const netlifyToken = process.env.NETLIFY_TOKEN || builderConfig.netlifyToken;
41-
if (netlifyToken == '' || netlifyToken == undefined) {
42-
context.logger.error("🚨 Netlify Token not found !");
43-
return { success: false };
44-
}
45-
const client = new NetlifyAPI(netlifyToken,
46-
{
47-
userAgent: 'netlify/js-client',
48-
scheme: 'https',
49-
host: 'api.netlify.com',
50-
pathPrefix: '/api/v1',
51-
globalParams: {}
7+
async (builderConfig: Schema, context: BuilderContext): Promise<BuilderOutput> => {
8+
context.reportStatus(`Executing deploy...`);
9+
context.logger.info(`Executing netlify deploy command ...... `);
10+
11+
if (builderConfig.noBuild) {
12+
context.logger.info(`📦 Skipping build`);
13+
} else {
14+
const configuration = builderConfig.configuration || 'production';
15+
16+
const overrides = {
17+
// this is an example how to override the workspace set of options
18+
...(builderConfig.baseHref && { baseHref: builderConfig.baseHref })
19+
};
20+
21+
if (!context.target) {
22+
throw new Error('Cannot build the application without a target');
23+
}
24+
25+
const baseHref = builderConfig.baseHref ? `Your base-href: "${builderConfig.baseHref}` : '';
26+
context.logger.info(`📦 Building "${context.target.project}". Configuration: "${configuration}". ${baseHref}`);
27+
28+
const build = await context.scheduleTarget({
29+
target: 'build',
30+
project: context.target.project || '',
31+
configuration
32+
}, overrides as json.JsonObject);
33+
34+
const buildResult = await build.result;
35+
36+
if (buildResult.success !== true) {
37+
context.logger.error(`❌ Application build failed`);
38+
return {
39+
error: `❌ Application build failed`,
40+
success: false
41+
};
42+
}
43+
44+
context.logger.info(`✔ Build Completed`);
45+
}
46+
47+
const netlifyToken = process.env.NETLIFY_TOKEN || builderConfig.netlifyToken;
48+
if (netlifyToken === '' || netlifyToken === undefined) {
49+
context.logger.error("🚨 Netlify Token not found !");
50+
return { success: false };
51+
}
52+
53+
let siteId = process.env.NETLIFY_API_ID || builderConfig.siteId;
54+
if (siteId === '' || siteId === undefined) {
55+
// site id is needed if the create option is false
56+
if (builderConfig.create === false) {
57+
context.logger.error("🚨 API ID (Site ID) not found !");
58+
return { success: false };
59+
}
60+
}
61+
62+
const client = new NetlifyAPI(netlifyToken, {
63+
userAgent: 'netlify/js-client',
64+
scheme: 'https',
65+
host: 'api.netlify.com',
66+
pathPrefix: '/api/v1',
67+
globalParams: {}
5268
});
53-
let sites;
54-
try {
55-
sites = await client.listSites();
56-
} catch (e) {
57-
context.logger.error("🚨 Netlify Token Rejected");
58-
return { success: false };
59-
}
60-
context.logger.info(`✔ User Verified`);
61-
const siteId = process.env.NETLIFY_API_ID || builderConfig.siteId;
62-
if (siteId == '' || siteId == undefined) {
63-
context.logger.error("🚨 API ID (Site ID) not found !");
64-
return { success: false };
65-
}
66-
const isSiteValid = sites.find(site => siteId === site.site_id);
67-
if (isSiteValid) {
68-
context.logger.info(`✔ Site ID Confirmed`);
69-
70-
const response = await client.deploy(siteId, builderConfig.outputPath);
71-
context.logger.info(`Deploying project from the location 📂 ./"${builderConfig.outputPath}`);
72-
context.logger.info(`\n ✔ Your updated site 🕸 is running at ${response && response.deploy && response.deploy.ssl_url}`);
73-
74-
return { success: true };
75-
}
76-
else {
77-
context.logger.error(`❌ Site ID not found`);
78-
return { success: false };
79-
}
80-
} else {
81-
context.logger.error(`❌ Application build failed`);
82-
return {
83-
error: `❌ Application build failed`,
84-
success: false
85-
};
86-
}
87-
88-
});
69+
70+
// let check if the site exists
71+
let site;
72+
try {
73+
// only when the site id is set
74+
if (siteId) {
75+
site = await client.getSite({ site_id: siteId });
76+
}
77+
} catch (e) {
78+
switch (e.status) {
79+
case 404:
80+
context.logger.error(`❌ Site "${siteId}" : Not found`);
81+
// if the create is false - just return the error
82+
if (builderConfig.create !== true) {
83+
return {
84+
success: false
85+
};
86+
}
87+
break;
88+
case 401:
89+
context.logger.fatal("🚨 Netlify: Unauthorized Token");
90+
return {
91+
success: false
92+
};
93+
default:
94+
// for all other errors
95+
return {
96+
error: e.message,
97+
success: false
98+
};
99+
}
100+
}
101+
102+
// lets create the site
103+
if (!site && builderConfig.create) {
104+
try {
105+
context.logger.info(`Creating new site for the application`);
106+
site = await client.createSite();
107+
siteId = site.id as string;
108+
context.logger.info(`✔ Site "${site.name}" (${siteId}) created. Please update the angular.json so on the next run we can re-deploy on the same site`);
109+
} catch (e) {
110+
context.logger.error("🚨 Unable to create the site");
111+
return {
112+
error: e.message,
113+
success: false
114+
};
115+
}
116+
}
117+
118+
// if we still don't have the site return with error
119+
if (!site) {
120+
context.logger.error("🚨 Unable to deploy as we don't have any context about the site");
121+
return {
122+
error: "🚨 Unable to deploy as we don't have any context about the site",
123+
success: false
124+
};
125+
}
126+
127+
// lets deploy the application to the site
128+
try {
129+
context.logger.info(`Deploying project from 📂 ./${builderConfig.outputPath}`);
130+
const response = await client.deploy(siteId, builderConfig.outputPath);
131+
context.logger.info(`✔ Your updated site 🕸 is running at ${response.deploy.ssl_url}`);
132+
return { success: true };
133+
} catch (e) {
134+
context.logger.error("❌ Deployment failed");
135+
return {
136+
error: e.message,
137+
success: false
138+
};
139+
}
140+
});

src/deploy/schema.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export interface Schema {
55
netlifyToken?: string;
66
siteId?: string;
77
baseHref?: string;
8+
create?: boolean;
89
}

src/deploy/schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
"baseHref": {
3030
"type": "string",
3131
"description": "This is an example how to override the workspace set of options. --- Base url for the application being built. Same as `ng build --base-href=/XXX/`."
32+
},
33+
"create": {
34+
"type": "boolean",
35+
"default": false,
36+
"description": "Creates the site if it does not exists or no site id is set"
3237
}
3338
}
3439
}

src/ng-add/index.ts

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
import { Rule, SchematicContext, SchematicsException, Tree, chain } from '@angular-devkit/schematics';
22
import { experimental, JsonParseMode, parseJson } from '@angular-devkit/core';
3-
import {
4-
addPackageJsonDependency,
5-
NodeDependency,
6-
NodeDependencyType
7-
} from 'schematics-utilities';
3+
import { addPackageJsonDependency, NodeDependency, NodeDependencyType } from 'schematics-utilities';
84

95
function addPackageJsonDependencies(): Rule {
106
return (host: Tree, context: SchematicContext) => {
7+
8+
// always add the package under dev dependencies
119
const dependencies: NodeDependency[] = [
12-
{ type: NodeDependencyType.Default, version: '~3.0.0', name: '@netlify-builder/deploy' }
10+
{ type: NodeDependencyType.Dev, version: '~3.1.0', name: '@netlify-builder/deploy' }
1311
];
14-
12+
1513
dependencies.forEach(dependency => {
1614
addPackageJsonDependency(host, dependency);
1715
context.logger.log('info', `✅️ Added "${dependency.name}" into ${dependency.type}`);
@@ -22,30 +20,28 @@ function addPackageJsonDependencies(): Rule {
2220
}
2321

2422
function getWorkspace(host: Tree): { path: string; workspace: experimental.workspace.WorkspaceSchema } {
25-
const possibleFiles = ['/angular.json', '/.angular.json'];
26-
const path = possibleFiles.filter(path => host.exists(path))[0];
23+
const possibleFiles = ['/angular.json', './angular.json'];
24+
const path = possibleFiles.find(path => host.exists(path));
25+
26+
if (!path) {
27+
throw new SchematicsException(`Could not find angular.json`);
28+
}
2729

2830
const configBuffer = host.read(path);
29-
if (configBuffer === null) {
31+
if (!configBuffer) {
3032
throw new SchematicsException(`Could not find angular.json`);
3133
}
32-
const content = configBuffer.toString();
3334

35+
const content = configBuffer.toString();
3436
let workspace: experimental.workspace.WorkspaceSchema;
37+
3538
try {
36-
workspace = (parseJson(
37-
content,
38-
JsonParseMode.Loose
39-
) as {}) as experimental.workspace.WorkspaceSchema;
39+
workspace = <any>parseJson(content, JsonParseMode.Loose) as experimental.workspace.WorkspaceSchema;
4040
} catch (e) {
41-
throw new SchematicsException(`Could not parse angular.json: ` + e.message);
41+
throw new SchematicsException(`Could not parse angular.json: ${e.message}`);
4242
}
4343

44-
return {
45-
path,
46-
workspace
47-
};
48-
44+
return { path, workspace };
4945
}
5046

5147
interface NgAddOptions {
@@ -56,7 +52,7 @@ interface NgAddOptions {
5652

5753
export function netlifyBuilder(options: NgAddOptions): Rule {
5854
return (tree: Tree, _context: SchematicContext) => {
59-
// Verifying Angular.json
55+
// get the workspace details
6056
const { path: workspacePath, workspace } = getWorkspace(tree);
6157

6258
// getting project name
@@ -93,9 +89,7 @@ export function netlifyBuilder(options: NgAddOptions): Rule {
9389
!project.architect.build.options.outputPath
9490
) {
9591
throw new SchematicsException(
96-
`Cannot read the output path (architect.build.options.outputPath) of the Angular project "${
97-
options.project
98-
}" in angular.json`
92+
`Cannot read the output path(architect.build.options.outputPath) of the Angular project "${options.project}" in angular.json`
9993
);
10094
}
10195

@@ -108,19 +102,15 @@ export function netlifyBuilder(options: NgAddOptions): Rule {
108102
"siteId": options.siteID,
109103
}
110104
}
111-
112-
tree.overwrite(workspacePath, JSON.stringify(workspace, null, 2));
113105

106+
tree.overwrite(workspacePath, JSON.stringify(workspace, null, 2));
114107
return tree;
115-
116108
};
117109
}
118110

119111
export default function (options: NgAddOptions): Rule {
120-
return chain(
121-
[
122-
netlifyBuilder(options),
123-
addPackageJsonDependencies()
124-
]
125-
)
112+
return chain([
113+
netlifyBuilder(options),
114+
addPackageJsonDependencies()
115+
]);
126116
}

0 commit comments

Comments
 (0)