Skip to content

Commit 0bd3a97

Browse files
committed
Initial commit.
1 parent 07edb57 commit 0bd3a97

File tree

14 files changed

+4061
-0
lines changed

14 files changed

+4061
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.DS_Store
2+
node_modules/

bin/run

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env node
2+
3+
require('@oclif/command').run()
4+
.catch(require('@oclif/errors/handle'))

bin/run.cmd

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@echo off
2+
3+
node "%~dp0\run" %*

messages/teams.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"commandDescription": "Notify Salesforce Deployment to Microsoft Teams",
3+
"urlFlagDescription": "target URL (for Microsoft Teams: Webhook URL)",
4+
"envFlagDescription": "Name of the target environment",
5+
"branchFlagDescription": "Current Branch name, for Bitbucket use the environment variable $BITBUCKET_BRANCH"
6+
}

package.json

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"name": "sfdx-notify",
3+
"version": "0.0.0",
4+
"author": "Gil Avignon",
5+
"bugs": "https://github.com/gavignon/sfdx-notify/issues",
6+
"dependencies": {
7+
"@oclif/command": "^1",
8+
"@oclif/config": "^1",
9+
"@oclif/errors": "^1",
10+
"@salesforce/command": "^2",
11+
"@salesforce/core": "^2",
12+
"axios": "^0.19.2",
13+
"tslib": "^1"
14+
},
15+
"devDependencies": {
16+
"@oclif/dev-cli": "^1",
17+
"@oclif/plugin-help": "^2",
18+
"@oclif/test": "^1",
19+
"@salesforce/dev-config": "1.4.1",
20+
"@types/chai": "^4",
21+
"@types/mocha": "^5",
22+
"@types/node": "^10",
23+
"chai": "^4",
24+
"globby": "^8",
25+
"mocha": "^5",
26+
"nyc": "^14",
27+
"ts-node": "^8",
28+
"tslint": "^5"
29+
},
30+
"engines": {
31+
"node": ">=8.0.0"
32+
},
33+
"files": [
34+
"/lib",
35+
"/messages",
36+
"/npm-shrinkwrap.json",
37+
"/oclif.manifest.json"
38+
],
39+
"homepage": "https://github.com/gavignon/sfdx-notify",
40+
"keywords": [
41+
"sfdx-plugin"
42+
],
43+
"license": "MIT",
44+
"oclif": {
45+
"commands": "./lib/commands",
46+
"bin": "sfdx",
47+
"topics": {
48+
"hello": {
49+
"description": "Commands to say hello."
50+
}
51+
},
52+
"devPlugins": [
53+
"@oclif/plugin-help"
54+
]
55+
},
56+
"repository": "gavignon/sfdx-notify",
57+
"scripts": {
58+
"lint": "tslint --project . --config tslint.json --format stylish",
59+
"postpack": "rm -f oclif.manifest.json",
60+
"posttest": "tslint -p test -t stylish",
61+
"prepack": "rm -rf lib && tsc -b && oclif-dev manifest && oclif-dev readme",
62+
"test": "nyc --extension .ts mocha --forbid-only \"test/**/*.test.ts\"",
63+
"version": "oclif-dev readme && git add README.md"
64+
}
65+
}

src/commands/notify/teams.ts

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
const childProcess = require('child_process')
2+
import { flags, SfdxCommand } from '@salesforce/command';
3+
import { Messages, SfdxError } from '@salesforce/core';
4+
import { AnyJson } from '@salesforce/ts-types';
5+
import { HttpClient } from '../../utils/HttpClient';
6+
7+
// Initialize Messages with the current plugin directory
8+
Messages.importMessagesDirectory(__dirname);
9+
10+
// Load the specific messages for this file. Messages from @salesforce/command, @salesforce/core,
11+
// or any library that is using the messages framework can also be loaded this way.
12+
const messages = Messages.loadMessages('sfdx-notify', 'teams');
13+
14+
export default class Teams extends SfdxCommand {
15+
16+
public static description = messages.getMessage('commandDescription');
17+
18+
public static examples = [
19+
`$ sfdx notify:teams -u https://outlook.office.com/webhook/WEBHOOK_URL -e UAT -b $BITBUCKET_BRANCH
20+
Notify deployment status on Microsoft Teams... Done!
21+
`
22+
];
23+
24+
public static args = [{}];
25+
26+
protected static flagsConfig = {
27+
url: flags.string({char: 'u', description: messages.getMessage('urlFlagDescription')}),
28+
env: flags.string({char: 'e', description: messages.getMessage('envFlagDescription')}),
29+
branch: flags.string({char: 'b', description: messages.getMessage('branchFlagDescription')})
30+
};
31+
32+
// Comment this out if your command does not require an org username
33+
protected static requiresUsername = false;
34+
35+
// Comment this out if your command does not support a hub org username
36+
protected static supportsDevhubUsername = false;
37+
38+
// Set this to true if your command requires a project workspace; 'requiresProject' is false by default
39+
protected static requiresProject = false;
40+
41+
public async run(): Promise<AnyJson> {
42+
const { stdout: log } = childProcess.spawnSync(
43+
'git',
44+
['log', '5.0..HEAD', '--oneline'],
45+
{ cwd: '/Users/gavignon/dev/CMA CGM/Git', encoding: 'utf8' }
46+
);
47+
48+
let pattern = /[0-9]{5,} \/ (Feature|Fix).*/g;
49+
let matches = log.match(pattern);
50+
51+
// Construct Microsoft Teams Card Data
52+
let features = new Array();
53+
let fixes = new Array();
54+
for(let match of matches){
55+
// Remove technical tags
56+
match = match.replace('[ci skip]','');
57+
let matchParts = match.split('/');
58+
59+
let item = {};
60+
61+
item.number = matchParts[0] !== undefined ? matchParts[0].trim() : '';
62+
item.name = matchParts[2] !== undefined ? matchParts[2].trim() : '';
63+
item.type = 'fix'; // Default value
64+
65+
if(matchParts[1] !== undefined){
66+
if(matchParts[1].includes('Feature')){
67+
item.type = 'feature';
68+
features.push(item);
69+
}else{
70+
fixes.push(item);
71+
}
72+
}
73+
}
74+
75+
let facts = new Array();
76+
let firstFeature = true;
77+
let firstDefect = true;
78+
79+
for(let feature of features){
80+
let fact = {};
81+
fact.name = '';
82+
if(firstFeature){
83+
fact.name = 'User Stories:';
84+
}
85+
fact.value = '**' + feature.number + '** - ' + feature.name;
86+
facts.push(fact);
87+
88+
firstFeature = false;
89+
90+
}
91+
92+
for(let fix of fixes){
93+
let fact = {};
94+
fact.name = '';
95+
if(firstDefect){
96+
fact.name = 'Defects:';
97+
}
98+
fact.value = '**' + fix.number + '** - ' + fix.name;
99+
facts.push(fact);
100+
101+
firstDefect = false;
102+
}
103+
104+
let data =
105+
{
106+
"@type": "MessageCard",
107+
"@context": "http://schema.org/extensions",
108+
"themeColor": "0076D7",
109+
"summary": this.flags.branch + " deployed",
110+
"sections": [{
111+
"activityTitle": this.flags.branch + " deployed",
112+
"activitySubtitle": "on " + this.flags.env,
113+
"facts": facts,
114+
"markdown": true
115+
}]
116+
};
117+
118+
this.ux.startSpinner('Notify deployment status on Microsoft Teams');
119+
await HttpClient.sendRequest(this.flags.url, data);
120+
this.ux.stopSpinner('Done!');
121+
122+
123+
// Return an object to be displayed with --json
124+
return data;
125+
}
126+
}

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default {};

src/utils/HttpClient.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const axios = require('axios')
2+
3+
export class HttpClient {
4+
public static sendRequest(targetUrl, data) {
5+
return axios({
6+
method: 'post',
7+
url: targetUrl,
8+
data: data
9+
})
10+
// .then(function (response) {
11+
// // console.log(response);
12+
// }).catch(function (error) {
13+
// if (error.response) {
14+
// // The request was made and the server responded with a status code
15+
// // that falls out of the range of 2xx
16+
// console.log(error.response.data);
17+
// console.log(error.response.status);
18+
// console.log(error.response.headers);
19+
// } else if (error.request) {
20+
// // The request was made but no response was received
21+
// // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
22+
// // http.ClientRequest in node.js
23+
// console.log(error.request);
24+
// } else {
25+
// // Something happened in setting up the request that triggered an Error
26+
// console.log('Error', error.message);
27+
// }
28+
// console.log(error.config);
29+
// });
30+
}
31+
}

test/commands/hello/org.test.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { expect, test } from '@salesforce/command/lib/test';
2+
import { ensureJsonMap, ensureString } from '@salesforce/ts-types';
3+
4+
describe('hello:org', () => {
5+
test
6+
.withOrg({ username: 'test@org.com' }, true)
7+
.withConnectionRequest(request => {
8+
const requestMap = ensureJsonMap(request);
9+
if (ensureString(requestMap.url).match(/Organization/)) {
10+
return Promise.resolve({ records: [ { Name: 'Super Awesome Org', TrialExpirationDate: '2018-03-20T23:24:11.000+0000'}] });
11+
}
12+
return Promise.resolve({ records: [] });
13+
})
14+
.stdout()
15+
.command(['hello:org', '--targetusername', 'test@org.com'])
16+
.it('runs hello:org --targetusername test@org.com', ctx => {
17+
expect(ctx.stdout).to.contain('Hello world! This is org: Super Awesome Org and I will be around until Tue Mar 20 2018!');
18+
});
19+
});

test/mocha.opts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
--require ts-node/register
2+
--watch-extensions ts
3+
--recursive
4+
--reporter spec
5+
--timeout 5000

test/tsconfig.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "../tsconfig"
3+
}
4+

tsconfig.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"extends": "./node_modules/@salesforce/dev-config/tsconfig",
3+
"compilerOptions": {
4+
"declaration": true,
5+
"outDir": "./lib",
6+
"rootDir": "./src",
7+
"importHelpers": true
8+
},
9+
"include": [
10+
"./src/**/*"
11+
]
12+
}

tslint.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"extends": "@salesforce/dev-config/tslint"
3+
}
4+

0 commit comments

Comments
 (0)