@ixon-cdk/core 1.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/api/auth.service.js +53 -0
- package/api/base.service.js +39 -0
- package/api/deploy.service.js +88 -0
- package/config/config.service.js +78 -0
- package/config/schema.d.ts +48 -0
- package/config/schema.json +93 -0
- package/index.js +10 -0
- package/meta-files.js +32 -0
- package/package.json +22 -0
- package/prompts.js +34 -0
- package/server/index.js +116 -0
- package/template/template.service.js +148 -0
- package/utils.js +114 -0
package/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const axios = require("axios");
|
|
3
|
+
const ApiBaseService = require("./base.service");
|
|
4
|
+
|
|
5
|
+
module.exports = class AuthService extends ApiBaseService {
|
|
6
|
+
logIn(credentials) {
|
|
7
|
+
const c = credentials;
|
|
8
|
+
return axios.post(
|
|
9
|
+
`${this._getApiBaseUrl()}/access-tokens`,
|
|
10
|
+
{ expiresIn: 86400 },
|
|
11
|
+
{
|
|
12
|
+
headers: {
|
|
13
|
+
...this._getApiDefaultHeaders(),
|
|
14
|
+
"User-Agent": "ComponentDevKit",
|
|
15
|
+
Authorization: `Basic ${Buffer.from(
|
|
16
|
+
c.email + ":" + (c.otp || "") + ":" + c.password,
|
|
17
|
+
"utf-8"
|
|
18
|
+
).toString("base64")}`,
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
logOut() {
|
|
25
|
+
const secretId = this._getSecretId();
|
|
26
|
+
if (secretId) {
|
|
27
|
+
return axios.delete(`${this._getApiBaseUrl()}/access-tokens/me`, {
|
|
28
|
+
headers: {
|
|
29
|
+
...this._getApiDefaultHeaders(),
|
|
30
|
+
Authorization: this._getApiAuthHeaderValue(),
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return Promise.resolve(null);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
isAuthenticated() {
|
|
38
|
+
return !!this._getSecretId();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
remember(secretId) {
|
|
42
|
+
fs.writeFileSync(this._getAccessTokenFilePath(), secretId, {
|
|
43
|
+
encoding: "utf8",
|
|
44
|
+
flag: "w",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
forget() {
|
|
49
|
+
if (fs.existsSync(this._getAccessTokenFilePath())) {
|
|
50
|
+
fs.rmSync(this._getAccessTokenFilePath());
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const ConfigService = require("../config/config.service");
|
|
4
|
+
|
|
5
|
+
module.exports = class ApiBaseService {
|
|
6
|
+
_configSrv = new ConfigService();
|
|
7
|
+
|
|
8
|
+
_getAccessTokenFilePath() {
|
|
9
|
+
return path.join(process.cwd(), ".accesstoken");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
_getApiAuthHeaderValue() {
|
|
13
|
+
return `Bearer ${this._getSecretId()}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
_getApiBaseUrl() {
|
|
17
|
+
return this._configSrv.getApiBaseUrl();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_getApiDefaultHeaders() {
|
|
21
|
+
return {
|
|
22
|
+
"Content-Type": "application/json",
|
|
23
|
+
"Api-Application": this._configSrv.getApiApplication(),
|
|
24
|
+
"Api-Version": this._configSrv.getApiVersion(),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_getSecretId() {
|
|
29
|
+
let secretId;
|
|
30
|
+
try {
|
|
31
|
+
secretId = fs.readFileSync(this._getAccessTokenFilePath(), {
|
|
32
|
+
encoding: "utf-8",
|
|
33
|
+
});
|
|
34
|
+
} catch {
|
|
35
|
+
// do nothing
|
|
36
|
+
}
|
|
37
|
+
return secretId;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const axios = require("axios");
|
|
4
|
+
const ApiBaseService = require("./base.service");
|
|
5
|
+
|
|
6
|
+
module.exports = class DeployService extends ApiBaseService {
|
|
7
|
+
deploy(companyId, templateId, file) {
|
|
8
|
+
const url = `${this._getApiBaseUrl()}/page-component-templates/${templateId}/version-upload`;
|
|
9
|
+
const body = fs.readFileSync(path.join(process.cwd(), file));
|
|
10
|
+
const headers = {
|
|
11
|
+
...this._getApiDefaultHeaders(),
|
|
12
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
13
|
+
"Api-Company": companyId,
|
|
14
|
+
Authorization: this._getApiAuthHeaderValue(),
|
|
15
|
+
};
|
|
16
|
+
return axios.post(url, body, { headers }).then((res) => res.data.data);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
deployAndPublish(companyId, templateId, file) {
|
|
20
|
+
return this.deploy(companyId, templateId, file).then((data) =>
|
|
21
|
+
this._publish(companyId, data.publicId)
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fetchPublishable(companyId, templateId) {
|
|
26
|
+
return this._fetchDescTemplateVersions(companyId, templateId).then((versions) => {
|
|
27
|
+
const publishedIdx = versions.findIndex(
|
|
28
|
+
(v) => typeof v.publishedOn === "string"
|
|
29
|
+
);
|
|
30
|
+
return publishedIdx !== -1 ? versions.slice(0, publishedIdx) : versions;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
publishLatestVersion(companyId, templateId) {
|
|
35
|
+
return this.fetchPublishable(companyId, templateId).then((data) => {
|
|
36
|
+
if (!data.length) {
|
|
37
|
+
return Promise.reject("Template has no publishable versions");
|
|
38
|
+
}
|
|
39
|
+
const latest = data.sort(
|
|
40
|
+
(v1, v2) => Number(v2.number) - Number(v1.number)
|
|
41
|
+
)[0];
|
|
42
|
+
return this._publish(companyId, latest.publicId);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
publishVersion(companyId, templateId, versionNumber) {
|
|
47
|
+
return this.fetchPublishable(companyId, templateId).then((data) => {
|
|
48
|
+
if (!data.length) {
|
|
49
|
+
return Promise.reject("Template has no publishable versions");
|
|
50
|
+
}
|
|
51
|
+
const _version = data.find((v) => v.number === String(versionNumber));
|
|
52
|
+
if (!!_version) {
|
|
53
|
+
return this._publish(companyId, _version.publicId);
|
|
54
|
+
}
|
|
55
|
+
return Promise.reject("Could not match!");
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_fetchDescTemplateVersions(companyId, templateId) {
|
|
60
|
+
const url = `${this._getApiBaseUrl()}/page-component-template-versions`;
|
|
61
|
+
const params = {
|
|
62
|
+
fields: "*,template(*)",
|
|
63
|
+
filters: `eq(template.publicId,"${templateId}")`,
|
|
64
|
+
"page-size": "100",
|
|
65
|
+
};
|
|
66
|
+
const headers = {
|
|
67
|
+
...this._getApiDefaultHeaders(),
|
|
68
|
+
"Api-Company": companyId,
|
|
69
|
+
Authorization: this._getApiAuthHeaderValue(),
|
|
70
|
+
};
|
|
71
|
+
return axios.get(url, { params, headers }).then((res) => {
|
|
72
|
+
return res.data.data.sort(
|
|
73
|
+
(v1, v2) => Number(v2.number) - Number(v1.number)
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_publish(companyId, versionId) {
|
|
79
|
+
const url = `${this._getApiBaseUrl()}/page-component-template-versions/${versionId}`;
|
|
80
|
+
const body = { published: true };
|
|
81
|
+
const headers = {
|
|
82
|
+
...this._getApiDefaultHeaders(),
|
|
83
|
+
"Api-Company": companyId,
|
|
84
|
+
Authorization: this._getApiAuthHeaderValue(),
|
|
85
|
+
};
|
|
86
|
+
return axios.patch(url, body, { headers });
|
|
87
|
+
}
|
|
88
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const { merge } = require("lodash");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { logFileCrudMessage, logErrorMessage } = require("../utils");
|
|
5
|
+
|
|
6
|
+
module.exports = class ConfigService {
|
|
7
|
+
_config = { components: {} };
|
|
8
|
+
|
|
9
|
+
_path = path.join(process.cwd(), "config.json");
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
let config;
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(this._path)) {
|
|
15
|
+
logErrorMessage(`No config file.`);
|
|
16
|
+
process.exit();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
config = require(this._path);
|
|
21
|
+
} catch {
|
|
22
|
+
logErrorMessage(`Couldn't parse config file.`);
|
|
23
|
+
process.exit();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this._config = config;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
hasComponent(name) {
|
|
30
|
+
return name in this._config.components;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getComponent(name) {
|
|
34
|
+
return this._config.components[name];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getNewComponentRoot() {
|
|
38
|
+
return this._config.newComponentRoot || 'components';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getPrefix() {
|
|
42
|
+
return this._config.prefix || null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getApiApplication() {
|
|
46
|
+
return this._config.apiApplication || "LtDdZKEPa5lK";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getApiBaseUrl() {
|
|
50
|
+
return this._config.apiBaseUrl || "https://api.ayayot.com";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getApiVersion() {
|
|
54
|
+
return this._config.apiVersion || "2";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
addComponent(name, config) {
|
|
58
|
+
this._config.components[name] = config;
|
|
59
|
+
this._sync();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
extendComponent(name, config) {
|
|
63
|
+
this._config.components[name] = merge(
|
|
64
|
+
{},
|
|
65
|
+
this._config.components[name],
|
|
66
|
+
config
|
|
67
|
+
);
|
|
68
|
+
this._sync();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
_sync() {
|
|
72
|
+
fs.writeFileSync(this._path, JSON.stringify(this._config, null, 2) + "\n", {
|
|
73
|
+
encoding: "utf8",
|
|
74
|
+
flag: "w",
|
|
75
|
+
});
|
|
76
|
+
logFileCrudMessage("UPDATE", "config.json");
|
|
77
|
+
}
|
|
78
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @title Custom Component Workspace Configuration
|
|
3
|
+
*/
|
|
4
|
+
export interface schema {
|
|
5
|
+
/**
|
|
6
|
+
* The path where new components will be created, relative to the workspace root.
|
|
7
|
+
* @default "components"
|
|
8
|
+
*/
|
|
9
|
+
newComponentRoot?: string;
|
|
10
|
+
/**
|
|
11
|
+
* API application ID
|
|
12
|
+
* @default "LtDdZKEPa5lK"
|
|
13
|
+
*/
|
|
14
|
+
apiApplication?: string;
|
|
15
|
+
/**
|
|
16
|
+
* API base URL
|
|
17
|
+
* @default "https://api.ayayot.com"
|
|
18
|
+
*/
|
|
19
|
+
apiBaseUrl?: string;
|
|
20
|
+
/**
|
|
21
|
+
* API version
|
|
22
|
+
* @default "2"
|
|
23
|
+
*/
|
|
24
|
+
apiVersion?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The prefix to apply to generated selectors.
|
|
27
|
+
* @minLength 1
|
|
28
|
+
*/
|
|
29
|
+
prefix?: string;
|
|
30
|
+
components: { [name: string]: component };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface component {
|
|
34
|
+
runner: {
|
|
35
|
+
build: {
|
|
36
|
+
/** The builder package name and build command */
|
|
37
|
+
builder: string;
|
|
38
|
+
/** The component input-file */
|
|
39
|
+
input: string;
|
|
40
|
+
};
|
|
41
|
+
deploy?: {
|
|
42
|
+
/** Company ID */
|
|
43
|
+
company: string;
|
|
44
|
+
/** PageComponentTemplate ID */
|
|
45
|
+
template: string;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"newComponentRoot": {
|
|
6
|
+
"type": "string",
|
|
7
|
+
"description": "The path where new components will be created, relative to the workspace root.",
|
|
8
|
+
"default": "components"
|
|
9
|
+
},
|
|
10
|
+
"apiApplication": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "API application ID",
|
|
13
|
+
"default": "LtDdZKEPa5lK"
|
|
14
|
+
},
|
|
15
|
+
"apiBaseUrl": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "API base URL",
|
|
18
|
+
"default": "https://api.ayayot.com"
|
|
19
|
+
},
|
|
20
|
+
"apiVersion": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "API version",
|
|
23
|
+
"default": "2"
|
|
24
|
+
},
|
|
25
|
+
"prefix": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "The prefix to apply to generated selectors.",
|
|
28
|
+
"minLength": 1
|
|
29
|
+
},
|
|
30
|
+
"components": {
|
|
31
|
+
"type": "object",
|
|
32
|
+
"additionalProperties": {
|
|
33
|
+
"$ref": "#/definitions/component"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"required": [
|
|
38
|
+
"components"
|
|
39
|
+
],
|
|
40
|
+
"title": "Custom Component Workspace Configuration",
|
|
41
|
+
"definitions": {
|
|
42
|
+
"component": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"properties": {
|
|
45
|
+
"runner": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"properties": {
|
|
48
|
+
"build": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"properties": {
|
|
51
|
+
"builder": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "The builder package name and build command"
|
|
54
|
+
},
|
|
55
|
+
"input": {
|
|
56
|
+
"type": "string",
|
|
57
|
+
"description": "The component input-file"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"required": [
|
|
61
|
+
"builder",
|
|
62
|
+
"input"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"deploy": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"properties": {
|
|
68
|
+
"company": {
|
|
69
|
+
"type": "string",
|
|
70
|
+
"description": "Company ID"
|
|
71
|
+
},
|
|
72
|
+
"template": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"description": "PageComponentTemplate ID"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"required": [
|
|
78
|
+
"company",
|
|
79
|
+
"template"
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"required": [
|
|
84
|
+
"build"
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"required": [
|
|
89
|
+
"runner"
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
AuthService: require("./api/auth.service"),
|
|
3
|
+
DeployService: require("./api/deploy.service"),
|
|
4
|
+
ConfigService: require("./config/config.service"),
|
|
5
|
+
Server: require("./server"),
|
|
6
|
+
TemplateService: require("./template/template.service"),
|
|
7
|
+
...require("./meta-files"),
|
|
8
|
+
...require("./prompts"),
|
|
9
|
+
...require("./utils"),
|
|
10
|
+
};
|
package/meta-files.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const ICON_FILE_NAME = "icon.svg";
|
|
5
|
+
const MANIFEST_FILE_NAME = "manifest.json";
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
copyMetaFiles: function copyMetaFiles(inputDir, outputDir) {
|
|
9
|
+
[MANIFEST_FILE_NAME, ICON_FILE_NAME].forEach((fileName) => {
|
|
10
|
+
_copyFileNameFromToSync(fileName, inputDir, outputDir);
|
|
11
|
+
});
|
|
12
|
+
},
|
|
13
|
+
watchMetaFiles: function watchMetaFiles(dir, watchCallback) {
|
|
14
|
+
require("chokidar")
|
|
15
|
+
.watch([
|
|
16
|
+
path.join(dir, MANIFEST_FILE_NAME),
|
|
17
|
+
path.join(dir, ICON_FILE_NAME),
|
|
18
|
+
])
|
|
19
|
+
.on("all", () => watchCallback());
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function _copyFileNameFromToSync(fileName, sourceDir, destDir) {
|
|
24
|
+
const sourceFile = path.join(sourceDir, fileName);
|
|
25
|
+
if (fs.existsSync(sourceFile)) {
|
|
26
|
+
const destFile = path.join(destDir, fileName);
|
|
27
|
+
if (!fs.existsSync(destDir)) {
|
|
28
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
fs.copyFileSync(sourceFile, destFile);
|
|
31
|
+
}
|
|
32
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ixon-cdk/core",
|
|
3
|
+
"version": "1.0.0-alpha.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"author": "",
|
|
7
|
+
"license": "ISC",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"archiver": "^5.3.0",
|
|
10
|
+
"axios": "^0.21.4",
|
|
11
|
+
"chalk": "^4.1.2",
|
|
12
|
+
"chokidar": "^3.5.2",
|
|
13
|
+
"debounce": "^1.2.1",
|
|
14
|
+
"express": "^4.17.1",
|
|
15
|
+
"livereload": "^0.9.3",
|
|
16
|
+
"lodash": "^4.17.21",
|
|
17
|
+
"opener": "^1.5.2",
|
|
18
|
+
"prompts": "^2.4.1",
|
|
19
|
+
"rimraf": "^3.0.2",
|
|
20
|
+
"yargs": "^17.1.1"
|
|
21
|
+
}
|
|
22
|
+
}
|
package/prompts.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
promptCompanyId: function(name) {
|
|
3
|
+
return {
|
|
4
|
+
type: "text",
|
|
5
|
+
name,
|
|
6
|
+
message: "What is the ID of your Company?",
|
|
7
|
+
validate: (value) => {
|
|
8
|
+
if (!value) {
|
|
9
|
+
return "Company ID is required.";
|
|
10
|
+
}
|
|
11
|
+
if (!/^[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}$/.test(value)) {
|
|
12
|
+
return "Invalid company ID.";
|
|
13
|
+
}
|
|
14
|
+
return true;
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
},
|
|
18
|
+
promptPageComponentTemplateId: function(name) {
|
|
19
|
+
return {
|
|
20
|
+
type: "text",
|
|
21
|
+
name,
|
|
22
|
+
message: "What is the ID of the PageComponentTemplate?",
|
|
23
|
+
validate: (value) => {
|
|
24
|
+
if (!value) {
|
|
25
|
+
return "Template ID is required.";
|
|
26
|
+
}
|
|
27
|
+
if (!/^[a-zA-Z0-9]{12}$/.test(value)) {
|
|
28
|
+
return "Invalid template ID.";
|
|
29
|
+
}
|
|
30
|
+
return true;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
};
|
package/server/index.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const express = require("express");
|
|
3
|
+
const ConfigService = require("../config/config.service");
|
|
4
|
+
|
|
5
|
+
module.exports = class Server {
|
|
6
|
+
constructor(opts) {
|
|
7
|
+
this._configSrv = new ConfigService();
|
|
8
|
+
this._opts = Object.assign(
|
|
9
|
+
{ port: 8000, componentBasePath: "components" },
|
|
10
|
+
opts
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
serve(names) {
|
|
15
|
+
const app = express();
|
|
16
|
+
|
|
17
|
+
// Cache
|
|
18
|
+
app.use(function(_, res, next) {
|
|
19
|
+
res.header("Cache-Control", "no-store");
|
|
20
|
+
next();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// CORS
|
|
24
|
+
app.use(function(_, res, next) {
|
|
25
|
+
res.header("Access-Control-Allow-Origin", "*");
|
|
26
|
+
res.header(
|
|
27
|
+
"Access-Control-Allow-Headers",
|
|
28
|
+
"Origin, X-Requested-With, Content-Type, Accept"
|
|
29
|
+
);
|
|
30
|
+
next();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Components
|
|
34
|
+
if (names) {
|
|
35
|
+
names.forEach((name) => {
|
|
36
|
+
const outputDir = this._getOutputDir(name);
|
|
37
|
+
if (outputDir) {
|
|
38
|
+
const dir = path.resolve(process.cwd(), outputDir);
|
|
39
|
+
app.use(
|
|
40
|
+
`/${this._opts.componentBasePath}/${name}`,
|
|
41
|
+
express.static(dir)
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Adds live reload watchers
|
|
48
|
+
const lrServer = require("livereload").createServer();
|
|
49
|
+
const watchHandler = () => lrServer.refresh("/");
|
|
50
|
+
const outputs = names.map((name) => this._getOutput(name));
|
|
51
|
+
const watcher = require("chokidar").watch(outputs, { ignoreInitial: true });
|
|
52
|
+
watcher.on("change", require("debounce")(watchHandler, 500));
|
|
53
|
+
process.on("exit", () => {
|
|
54
|
+
lrServer.close();
|
|
55
|
+
watcher.close();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Simulator app
|
|
59
|
+
const appDir = path.dirname(require.resolve("@ixon-cdk/simulator"));
|
|
60
|
+
app.get(`/${this._opts.componentBasePath}/*`, function(req, res) {
|
|
61
|
+
res.sendStatus(404);
|
|
62
|
+
});
|
|
63
|
+
app.get(`/config.json`, (req, res) => {
|
|
64
|
+
res.send({
|
|
65
|
+
api: {
|
|
66
|
+
appId: this._configSrv.getApiApplication(),
|
|
67
|
+
baseUrl: this._configSrv.getApiBaseUrl(),
|
|
68
|
+
version: this._configSrv.getApiVersion(),
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
app.get("*.*", express.static(appDir));
|
|
73
|
+
app.all("*", function(req, res) {
|
|
74
|
+
res.status(200).sendFile(`/index.html`, { root: appDir });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
app.listen(this._opts.port);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
openSimulator(name = null) {
|
|
81
|
+
const url = this._getComponentUrl(name);
|
|
82
|
+
const queryString = url ? `?pct-url=${encodeURIComponent(url)}` : "";
|
|
83
|
+
require("opener")(`${this._getBaseUrl()}/${queryString}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
_getBaseUrl() {
|
|
87
|
+
return `http://localhost:${this._opts.port}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
_getComponentUrl(name) {
|
|
91
|
+
if (name) {
|
|
92
|
+
const output = this._getOutput(name);
|
|
93
|
+
const outputDir = this._getOutputDir(name);
|
|
94
|
+
if (outputDir) {
|
|
95
|
+
const baseUrl = this._getBaseUrl();
|
|
96
|
+
const servePath = this._opts.componentBasePath;
|
|
97
|
+
const outputFileName = output.slice(outputDir.length + 1);
|
|
98
|
+
return `${baseUrl}/${servePath}/${name}/${outputFileName}`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_getOutput(name) {
|
|
105
|
+
if (name) {
|
|
106
|
+
const prefix = this._configSrv.getPrefix();
|
|
107
|
+
const tag = prefix ? `${prefix}-${name}` : name;
|
|
108
|
+
return `dist/${name}/${tag}.min.js`;
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_getOutputDir(name) {
|
|
114
|
+
return name ? `dist/${name}` : null;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const prompts = require("prompts");
|
|
4
|
+
|
|
5
|
+
const { ensureModule, logFileCrudMessage, pascalCase } = require("../utils");
|
|
6
|
+
const ConfigService = require("../config/config.service");
|
|
7
|
+
|
|
8
|
+
module.exports = class TemplateService {
|
|
9
|
+
_configSrv = new ConfigService();
|
|
10
|
+
|
|
11
|
+
_schemas = {};
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this._discover();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async add(componentName, componentPrefix) {
|
|
18
|
+
let schema;
|
|
19
|
+
|
|
20
|
+
const result = await prompts({
|
|
21
|
+
type: "select",
|
|
22
|
+
name: "templateName",
|
|
23
|
+
message: "Pick a template",
|
|
24
|
+
choices: Object.keys(this._schemas).map((key) => ({
|
|
25
|
+
title: this._schemas[key].name,
|
|
26
|
+
value: key,
|
|
27
|
+
})),
|
|
28
|
+
initial: 0,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const tag = componentPrefix
|
|
32
|
+
? `${componentPrefix}-${componentName}`
|
|
33
|
+
: componentName;
|
|
34
|
+
|
|
35
|
+
if (result) {
|
|
36
|
+
schema = this._schemas[result.templateName];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (schema) {
|
|
40
|
+
const context = { name: componentName, tag };
|
|
41
|
+
let variantIdx = null;
|
|
42
|
+
|
|
43
|
+
if (schema.variants) {
|
|
44
|
+
const _result = await prompts({
|
|
45
|
+
type: "select",
|
|
46
|
+
name: "variantIdx",
|
|
47
|
+
message: "Pick a variant",
|
|
48
|
+
choices: schema.variants.map((variant, index) => ({
|
|
49
|
+
title: variant.name,
|
|
50
|
+
value: index,
|
|
51
|
+
})),
|
|
52
|
+
initial: 0,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (_result) {
|
|
56
|
+
variantIdx = _result.variantIdx;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
schema = this._interpolateSchema(schema, context);
|
|
61
|
+
|
|
62
|
+
if (schema.config.runner) {
|
|
63
|
+
const modulesNames = Object.keys(schema.config.runner).reduce(
|
|
64
|
+
(names, cmd) => {
|
|
65
|
+
if (schema.config.runner[cmd].builder) {
|
|
66
|
+
const name = schema.config.runner[cmd].builder.split(":")[0];
|
|
67
|
+
if (!names.includes(name)) {
|
|
68
|
+
return [...names, name];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return names;
|
|
72
|
+
},
|
|
73
|
+
[]
|
|
74
|
+
);
|
|
75
|
+
modulesNames.forEach((name) => ensureModule(name));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const root = this._configSrv.getNewComponentRoot();
|
|
79
|
+
|
|
80
|
+
schema.files.forEach((file) => {
|
|
81
|
+
file.dest = `${root}/${componentName}/${file.dest}`;
|
|
82
|
+
this.createFile(file, context);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (variantIdx !== null) {
|
|
86
|
+
schema.variants[variantIdx].files.forEach((file) => {
|
|
87
|
+
file.dest = `${root}/${componentName}/${file.dest}`;
|
|
88
|
+
this.createFile(file, context);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return schema;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
createFile(file, ctx) {
|
|
97
|
+
fs.mkdirSync(path.dirname(path.join(process.cwd(), file.dest)), {
|
|
98
|
+
recursive: true,
|
|
99
|
+
});
|
|
100
|
+
if (file.interpolateContent) {
|
|
101
|
+
const text = fs.readFileSync(
|
|
102
|
+
path.join(
|
|
103
|
+
path.dirname(require.resolve("@ixon-cdk/templates")),
|
|
104
|
+
file.source
|
|
105
|
+
),
|
|
106
|
+
{ encoding: "utf-8" }
|
|
107
|
+
);
|
|
108
|
+
const data = this._interpolateText(text, ctx);
|
|
109
|
+
fs.writeFileSync(path.join(process.cwd(), file.dest), data, {
|
|
110
|
+
encoding: "utf-8",
|
|
111
|
+
});
|
|
112
|
+
} else {
|
|
113
|
+
fs.copyFileSync(
|
|
114
|
+
path.join(
|
|
115
|
+
path.dirname(require.resolve("@ixon-cdk/templates")),
|
|
116
|
+
file.source
|
|
117
|
+
),
|
|
118
|
+
path.join(process.cwd(), file.dest)
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
logFileCrudMessage("CREATE", file.dest);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
_discover() {
|
|
125
|
+
const dir = path.dirname(require.resolve("@ixon-cdk/templates"));
|
|
126
|
+
const files = fs.readdirSync(dir);
|
|
127
|
+
files.forEach((file) => {
|
|
128
|
+
if (fs.lstatSync(path.join(dir, file)).isDirectory()) {
|
|
129
|
+
const schemaFile = path.join(dir, file, "schema.json");
|
|
130
|
+
if (fs.existsSync(schemaFile)) {
|
|
131
|
+
const schema = JSON.parse(fs.readFileSync(schemaFile));
|
|
132
|
+
this._schemas = Object.assign({}, this._schemas, { [file]: schema });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
_interpolateSchema(schema, params) {
|
|
139
|
+
return JSON.parse(this._interpolateText(JSON.stringify(schema), params));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_interpolateText(text, params) {
|
|
143
|
+
return text
|
|
144
|
+
.replace(/\{name\}/g, params.name)
|
|
145
|
+
.replace(/\{tag\}/g, params.tag)
|
|
146
|
+
.replace(/\{pascalCase\(tag\)\}/g, pascalCase(params.tag));
|
|
147
|
+
}
|
|
148
|
+
};
|
package/utils.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const chalk = require("chalk");
|
|
4
|
+
const flow = require("lodash/flow");
|
|
5
|
+
const camelCase = require("lodash/camelCase");
|
|
6
|
+
const upperFirst = require("lodash/upperFirst");
|
|
7
|
+
|
|
8
|
+
function getArgv() {
|
|
9
|
+
const remain = process.argv.slice(2);
|
|
10
|
+
const argv = require("yargs/yargs")(remain).argv;
|
|
11
|
+
|
|
12
|
+
// version argument fix
|
|
13
|
+
if ("version" in argv) {
|
|
14
|
+
const versionArg = remain.find((arg) =>
|
|
15
|
+
/^--version=("[0-9]+"|'[0-9]+'|[0-9]+)$/.test(arg)
|
|
16
|
+
);
|
|
17
|
+
if (!!versionArg) {
|
|
18
|
+
const matches = versionArg.match(/^--version=['"]?([0-9]+)['"]?$/);
|
|
19
|
+
argv.version = Number(matches[1]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return argv;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function logErrorMessage(message) {
|
|
27
|
+
console.log(chalk.redBright.bold(message));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function logFileCrudMessage(crud, file) {
|
|
31
|
+
switch (crud) {
|
|
32
|
+
case "CREATE":
|
|
33
|
+
case "UPDATE":
|
|
34
|
+
console.log(`${chalk.green(crud)} ${file}`);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function logSuccessMessage(message) {
|
|
40
|
+
console.log(chalk.green(message));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function moduleExists(moduleName) {
|
|
44
|
+
let exists = true;
|
|
45
|
+
try {
|
|
46
|
+
require.resolve(moduleName);
|
|
47
|
+
} catch {
|
|
48
|
+
exists = false;
|
|
49
|
+
}
|
|
50
|
+
return exists;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function ensureModule(moduleName) {
|
|
54
|
+
if (!moduleName.startsWith("@ixon-cdk/")) {
|
|
55
|
+
return logErrorMessage("Cannot install this module.");
|
|
56
|
+
}
|
|
57
|
+
if (!moduleExists(moduleName)) {
|
|
58
|
+
console.log(`Installing package '${moduleName}'...`);
|
|
59
|
+
require("child_process").execSync(`npm install --save-dev ${moduleName}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function zip(output, callback) {
|
|
64
|
+
const outputDir = path.dirname(output);
|
|
65
|
+
const zipFile = path.join(outputDir + ".zip");
|
|
66
|
+
const stream = fs.createWriteStream(zipFile);
|
|
67
|
+
stream.on("close", () => {
|
|
68
|
+
require("rimraf").sync(outputDir);
|
|
69
|
+
callback(zipFile);
|
|
70
|
+
});
|
|
71
|
+
const archive = require("archiver")("zip", { zlib: { level: 9 } });
|
|
72
|
+
archive.pipe(stream);
|
|
73
|
+
archive.directory(outputDir, path.basename(outputDir));
|
|
74
|
+
archive.finalize();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const pascalCase = flow(camelCase, upperFirst);
|
|
78
|
+
|
|
79
|
+
function apiHttpErrorToMessage(error) {
|
|
80
|
+
if (typeof error === "string") {
|
|
81
|
+
logErrorMessage(error);
|
|
82
|
+
process.exit();
|
|
83
|
+
}
|
|
84
|
+
if (error && error.response && error.response.data) {
|
|
85
|
+
const errors = error.response.data.data;
|
|
86
|
+
if (errors) {
|
|
87
|
+
logErrorMessage(
|
|
88
|
+
errors
|
|
89
|
+
.map((err) =>
|
|
90
|
+
!!err.propertyName
|
|
91
|
+
? `${err.propertyName}: ${err.message}`
|
|
92
|
+
: err.message
|
|
93
|
+
)
|
|
94
|
+
.join("\n")
|
|
95
|
+
);
|
|
96
|
+
process.exit();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
logErrorMessage("Unexpected error.");
|
|
100
|
+
console.log(error);
|
|
101
|
+
process.exit();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
getArgv,
|
|
106
|
+
logErrorMessage,
|
|
107
|
+
logFileCrudMessage,
|
|
108
|
+
logSuccessMessage,
|
|
109
|
+
moduleExists,
|
|
110
|
+
ensureModule,
|
|
111
|
+
zip,
|
|
112
|
+
pascalCase,
|
|
113
|
+
apiHttpErrorToMessage,
|
|
114
|
+
};
|