Skip to content

Commit c9f20e7

Browse files
committed
Initial commit
0 parents  commit c9f20e7

File tree

13 files changed

+645
-0
lines changed

13 files changed

+645
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
package-lock.json

LICENSE.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2019 Marton Sagi, contributors
4+
5+
Permission is hereby granted, free of charge, to any person
6+
obtaining a copy of this software and associated documentation
7+
files (the "Software"), to deal in the Software without
8+
restriction, including without limitation the rights to use,
9+
copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the
11+
Software is furnished to do so, subject to the following
12+
conditions:
13+
14+
The above copyright notice and this permission notice shall be
15+
included in all copies or substantial portions of the Software.
16+
17+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24+
OTHER DEALINGS IN THE SOFTWARE.

README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Yeoman Generator for Dynamics 365 Business Central
2+
3+
A Yeoman generator to help get you started with Business Central extension development.
4+
Example code is based on this Hello World repository: https://dev.azure.com/businesscentralapps/HelloWorld
5+
6+
## Getting started
7+
8+
* Install: `npm install -g generator-dynbc`
9+
* Run: `yo dynbc`
10+
11+
## Commands
12+
13+
* `yo dynbc` shows a wizard for generating an app
14+
* `yo dynbc <name> --skip-quiz` generates a new app with the name `<name>`
15+
* `yo dynbc --help` shows available commands and options
16+
17+
## Options
18+
19+
* `--help,-h`: shows available commands and options
20+
* `--skip-quiz`: skips wizard to use specified options instead
21+
* `--start-id`: first Object ID of your range
22+
* `--range`: number of Objects
23+
* `--suffix`: company suffix to be added to Object names
24+
* `--publisher`: publisher name
25+
26+
## Usage
27+
28+
### A) Wizard
29+
30+
`yo dynbc MyNewApp`
31+
32+
Wizard questions:
33+
```
34+
? App name -> MyNewApp
35+
? First Object ID -> 50000
36+
? Number of Objects -> 10
37+
? Publisher name -> My Company
38+
? Preferred Object Suffix -> XXX
39+
? Would you like to add Unit Testing? (Y/n) -> Y
40+
? Test Suite Name -> DEFAULT
41+
```
42+
Output:
43+
```
44+
create MyNewAppApp\app.json
45+
create MyNewAppApp\src\PageExt.HelloWorld.al
46+
create MyNewAppTest\Scenarios\ScenarioTemplate.ps1
47+
create MyNewAppTest\tests\HelloWorldTests.al
48+
create MyNewAppTest\app.json
49+
create MyNewAppTest\src\TestSuiteInstaller.al
50+
create MyNewAppTest\src\TestSuiteMgmt.al
51+
create MyNewApp.code-workspace
52+
```
53+
54+
### B) Unattended mode
55+
56+
This mode automatically generates Unit Testing with "DEFAULT" Test Suite.
57+
58+
`yo dynbc MyNewApp --start-id 50000 --range 10 --publisher "My Company" --suffix DBC --skip-quiz`
59+
60+
Output:
61+
```
62+
unattended mode...
63+
--skip-quiz selected, skipping wizard...
64+
create MyNewAppApp\app.json
65+
create MyNewAppApp\src\PageExt.HelloWorld.al
66+
create MyNewAppTest\Scenarios\ScenarioTemplate.ps1
67+
create MyNewAppTest\tests\HelloWorldTests.al
68+
create MyNewAppTest\app.json
69+
create MyNewAppTest\src\TestSuiteInstaller.al
70+
create MyNewAppTest\src\TestSuiteMgmt.al
71+
create MyNewApp.code-workspace
72+
```
73+

app/index.js

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
'use strict';
2+
3+
const path = require('path');
4+
const uuidv4 = require('uuid/v4');
5+
const Generator = require('yeoman-generator');
6+
7+
String.prototype.ucFirst = function () {
8+
return this.charAt(0).toUpperCase() + this.slice(1);
9+
}
10+
11+
module.exports = class extends Generator {
12+
constructor(args, opts) {
13+
super(args, opts);
14+
15+
this.argument('name', {
16+
type: String,
17+
required: false,
18+
description: 'App name'
19+
});
20+
21+
this.option('start-id', {
22+
type: Number,
23+
required: false,
24+
description: 'First Object ID'
25+
});
26+
27+
this.option('range', {
28+
type: Number,
29+
required: false,
30+
description: 'Number of Objects'
31+
});
32+
33+
this.option('suffix', {
34+
type: String,
35+
required: false,
36+
description: 'Preferred Object Suffix'
37+
});
38+
39+
this.option('publisher', {
40+
type: String,
41+
required: false,
42+
description: 'Publisher name'
43+
});
44+
45+
this.appname = this.appname.ucFirst();
46+
47+
this.option('skip-quiz', {
48+
type: Boolean,
49+
description: 'Skip user-friendly wizard. Use this for background automation.',
50+
default: false
51+
});
52+
}
53+
54+
async prompting() {
55+
if (this.options.skipQuiz) {
56+
this.answers = {};
57+
this.log('unattended mode...');
58+
this.log('--skip-quiz selected, skipping wizard...');
59+
return;
60+
}
61+
62+
this.answers = await this.prompt([
63+
{
64+
type: "input",
65+
name: "name",
66+
message: "App name",
67+
default: `${this.options['name'] || this.appname}` // Default to current folder name
68+
},
69+
{
70+
type: "number",
71+
name: "startId",
72+
message: "First Object ID",
73+
default: 50000
74+
},
75+
{
76+
type: "number",
77+
name: "range",
78+
message: "Number of Objects",
79+
default: 10
80+
},
81+
{
82+
type: "input",
83+
name: "publisher",
84+
message: "Publisher name"
85+
},
86+
{
87+
type: "input",
88+
name: "suffix",
89+
message: "Preferred Object Suffix"
90+
},
91+
{
92+
type: "confirm",
93+
name: "testApp",
94+
message: "Would you like to add Unit Testing?",
95+
default: true
96+
},
97+
{
98+
type: "input",
99+
name: "testSuiteName",
100+
message: "Test Suite Name",
101+
default: "DEFAULT",
102+
when: (input) => {
103+
return input.testApp;
104+
}
105+
}
106+
]);
107+
}
108+
109+
writing() {
110+
this.appname = this.answers.name || this.options['name'];
111+
this.appFolderName = `${this.appname}App`;
112+
this.testFolderName = `${this.appname}Test`;
113+
this.suffix = this.answers.suffix || this.options.suffix || '';
114+
this.publisher = this.answers.publisher || this.options.publisher || '';
115+
this.appGuid = uuidv4();
116+
this.testGuid = uuidv4();
117+
this.startId = this.answers.startId || this.options.startId;
118+
this.range = this.answers.range ||this.options.range;
119+
this.endId = this.startId + this.range - 1;
120+
if (this.endId < this.startId) {
121+
this.endId = this.startId + 10;
122+
}
123+
124+
this.testApp = this.options.skipQuiz ? true : this.answers.testApp;
125+
this.testStartId = this.endId + 1;
126+
this.testEndId = this.testStartId + this.range - 1;
127+
this.testNextId = this.testStartId + 1;
128+
this.firstTestCuId = this.testNextId + 1;
129+
this.testSuiteName = this.answers.testSuiteName || 'DEFAULT';
130+
131+
this.fs.copyTpl(
132+
this.templatePath('app/app.json'),
133+
this.destinationPath(`${this.appFolderName}/app.json`),
134+
{
135+
appGuid: this.appGuid,
136+
appname: this.appname,
137+
startId: this.startId,
138+
endId: this.endId,
139+
publisher: this.publisher
140+
}
141+
);
142+
143+
this.fs.copyTpl(
144+
this.templatePath('app/src/PageExt.HelloWorld.al'),
145+
this.destinationPath(`${this.appFolderName}/src/PageExt.HelloWorld.al`),
146+
{
147+
startId: this.startId,
148+
suffix: this.suffix
149+
}
150+
);
151+
152+
if (this.testApp) {
153+
this.fs.copy(
154+
this.templatePath('test/Scenarios/*'),
155+
this.destinationPath(path.join(this.testFolderName, 'Scenarios'))
156+
);
157+
158+
this.fs.copy(
159+
this.templatePath('test/tests/*'),
160+
this.destinationPath(path.join(this.testFolderName, 'tests'))
161+
);
162+
163+
this.fs.copyTpl(
164+
this.templatePath('test/app.json'),
165+
this.destinationPath(`${this.testFolderName}/app.json`),
166+
{
167+
appGuid: this.appGuid,
168+
appname: this.appname,
169+
testGuid: this.testGuid,
170+
testStartId: this.testStartId,
171+
testEndId: this.testEndId,
172+
publisher: this.publisher
173+
}
174+
);
175+
176+
this.fs.copyTpl(
177+
this.templatePath('test/src/TestSuiteInstaller.al'),
178+
this.destinationPath(`${this.testFolderName}/src/TestSuiteInstaller.al`),
179+
{
180+
testSuiteName: this.testSuiteName,
181+
testStartId: this.testStartId,
182+
testNextId: this.testNextId,
183+
testEndId: this.testEndId,
184+
suffix: this.suffix
185+
}
186+
);
187+
188+
this.fs.copyTpl(
189+
this.templatePath('test/src/TestSuiteMgmt.al'),
190+
this.destinationPath(`${this.testFolderName}/src/TestSuiteMgmt.al`),
191+
{
192+
testStartId: this.testStartId,
193+
suffix: this.suffix
194+
}
195+
);
196+
197+
this.fs.copyTpl(
198+
this.templatePath('test/tests/HelloWorldTests.al'),
199+
this.destinationPath(`${this.testFolderName}/tests/HelloWorldTests.al`),
200+
{
201+
firstTestCuId: this.firstTestCuId,
202+
suffix: this.suffix
203+
}
204+
);
205+
}
206+
207+
this.fs.copyTpl(
208+
this.templatePath('bc.code-workspace'),
209+
this.destinationPath(`${this.appname}.code-workspace`),
210+
{
211+
appFolderName: this.appFolderName,
212+
testFolderName: this.testFolderName
213+
}
214+
);
215+
}
216+
};

app/templates/app/app.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"id": "<%= appGuid %>",
3+
"name": "<%= appname %>",
4+
"publisher": "<%= publisher %>",
5+
"brief": "",
6+
"description": "",
7+
"version": "1.0.0.0",
8+
"privacyStatement": "",
9+
"EULA": "",
10+
"help": "",
11+
"url": "",
12+
"logo": "",
13+
"target":"Extension",
14+
"showMyCode": true,
15+
"features": [
16+
"TranslationFile"
17+
],
18+
"capabilities": [],
19+
"dependencies": [],
20+
"screenshots": [],
21+
"platform": "14.0.0.0",
22+
"application": "14.0.0.0",
23+
"runtime": "3.0",
24+
"idRange": {
25+
"from": <%= startId %>,
26+
"to": <%= endId %>
27+
}
28+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// https://dev.azure.com/businesscentralapps/HelloWorld
2+
3+
// Welcome to your new AL extension.
4+
// Remember that object names and IDs should be unique across all extensions.
5+
// AL snippets start with t*, like tpageext - give them a try and happy coding!
6+
7+
pageextension <%= startId %> CustomerListExt_<%= suffix %> extends "Customer List"
8+
{
9+
trigger OnOpenPage();
10+
begin
11+
Message('App published: Hello world');
12+
end;
13+
}

app/templates/bc.code-workspace

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"extensions": {
3+
"recommendations": [
4+
"waldo.al-extension-pack"
5+
]
6+
},
7+
"folders": [
8+
{
9+
"path": "<%= appFolderName %>"
10+
},
11+
{
12+
"path": "<%= testFolderName %>"
13+
}
14+
],
15+
"settings": {
16+
"al.codeAnalyzers": [
17+
"${AppSourceCop}",
18+
"${CodeCop}",
19+
"${UICop}"
20+
],
21+
"al.enableCodeAnalysis": true,
22+
"al.enableCodeActions": true
23+
}
24+
}

0 commit comments

Comments
 (0)