@newlogic-digital/core 0.9.13 → 1.0.0-beta.2
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +15 -45
- package/index.js +262 -43
- package/package.json +13 -58
- package/LICENSE +0 -674
- package/modules/Core.js +0 -626
- package/modules/Emails.js +0 -110
- package/modules/Icons.js +0 -140
- package/modules/Scripts.js +0 -321
- package/modules/Serve.js +0 -124
- package/modules/Styles.js +0 -298
- package/modules/Templates.js +0 -468
- package/modules/Utils.js +0 -299
- package/modules/Watch.js +0 -75
- package/modules/tailwind/index.cjs +0 -84
- package/modules/tailwind/index.js +0 -75
- package/packages/gulp-clean-css/LICENSE +0 -20
- package/packages/gulp-clean-css/README.md +0 -79
- package/packages/gulp-clean-css/index.js +0 -66
- package/packages/gulp-clean-css/package.json +0 -68
- package/packages/gulp-twig2html/CHANGELOG.md +0 -77
- package/packages/gulp-twig2html/LICENSE +0 -22
- package/packages/gulp-twig2html/README.md +0 -112
- package/packages/gulp-twig2html/index.js +0 -30
- package/packages/gulp-twig2html/package.json +0 -47
- package/packages/postcss-inset/CHANGELOG.md +0 -5
- package/packages/postcss-inset/LICENSE.md +0 -106
- package/packages/postcss-inset/README.md +0 -121
- package/packages/postcss-inset/index.cjs.js +0 -49
- package/packages/postcss-inset/index.es.mjs +0 -47
- package/packages/postcss-inset/index.js +0 -47
- package/packages/postcss-inset/package.json +0 -58
- package/packages/twig-renderer/CHANGELOG.md +0 -66
- package/packages/twig-renderer/LICENSE +0 -22
- package/packages/twig-renderer/README.md +0 -93
- package/packages/twig-renderer/package.json +0 -49
- package/packages/twig-renderer/twig-renderer.js +0 -90
package/README.md
CHANGED
@@ -10,9 +10,7 @@
|
|
10
10
|
|
11
11
|
# ⚙️ Newlogic Core
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
Modern principles for creating web applications
|
13
|
+
Modern principles for creating web applications. Powered by Vite and Vituum
|
16
14
|
|
17
15
|
- 💡 Modern principles
|
18
16
|
- 🚀️ Fast development
|
@@ -21,16 +19,14 @@ Modern principles for creating web applications
|
|
21
19
|
- ✉️ Email templates
|
22
20
|
- ⚡ Vite as webserver
|
23
21
|
|
24
|
-
Newlogic Core is
|
22
|
+
Newlogic Core is an integration for [Vituum](https://vituum.dev), and contains set of tools that can be used to create modern web applications. Use of modern Javascript, CSS, ES modules, dynamic imports, etc.
|
25
23
|
|
26
24
|
## 🛠️ Integrated tools
|
25
|
+
* **[Vite](https://vitejs.dev)** next-generation frontend tooling
|
26
|
+
* **[Vituum](https://vituum.dev)** fast prototyping with template engines
|
27
27
|
* **[PostCSS](https://postcss.org/)** with basic plugins and [Tailwind CSS](https://tailwindcss.com/) for utility classes.
|
28
|
-
* **[
|
29
|
-
* **[
|
30
|
-
* **[CleanCSS](https://github.com/jakubpawlowicz/clean-css)** for css optimization and minification
|
31
|
-
* **[PurgeCSS](https://purgecss.com/)** for removing unused CSS
|
32
|
-
* **[TwigJS](https://purgecss.com/)** as template engine
|
33
|
-
* **[Vite](https://vitejs.dev)** for local webserver
|
28
|
+
* **[TwigJS](https://github.com/vituum/vite-plugin-twig)** as template engine twig
|
29
|
+
* **[Latte](https://github.com/vituum/vite-plugin-latte)** as template engine latte
|
34
30
|
|
35
31
|
### 💡 Basic principle
|
36
32
|
|
@@ -40,28 +36,13 @@ PHP programmers often **don't want to configure anything**, basic idea is to add
|
|
40
36
|
|
41
37
|
It doesn't matter if you use Nette, Symfony or Laravel - the structure can be freely adjusted as needed - `resources` and` public`, `src` and` dist` or `app/assets` and` www`
|
42
38
|
|
43
|
-
It's up to you - all paths are freely configurable in `
|
39
|
+
It's up to you - all paths are freely configurable in `vite.config.js` config
|
44
40
|
|
45
41
|
### 📦 Modularity
|
46
42
|
|
47
|
-
Newlogic Core uses
|
48
|
-
|
49
|
-
Source files are divided by modules inside `src` directory - styles, scripts, templates, icons, emails, assets. It is optional which modules you want to use for the project, simple delete the directory. You really only use what you want to use.
|
50
|
-
|
51
|
-
If you use [PhpStorm](https://www.jetbrains.com/phpstorm/) tasks will load for you automatically and dynamically according to the availability of individual modules.
|
52
|
-
|
53
|
-
### ⚡ Without compilation - no building and bundling
|
54
|
-
|
55
|
-
Lets face the facts. PHP programmers **hate javascript compilation**.
|
43
|
+
Newlogic Core uses [Vituum](https://vituum.dev) and [Vite](https://vitejs.dev) for frontend tooling.
|
56
44
|
|
57
|
-
|
58
|
-
|
59
|
-
Javascript sources are executable in browsers via modern solutions like **[Importmaps](https://github.com/WICG/import-maps)** - changes are instant, no waiting for build.
|
60
|
-
|
61
|
-
CSS sources still need to be compiled, because most of standardized features are not yet ready in browsers, but it's getting there.
|
62
|
-
|
63
|
-
### 🧬 Single Page Apps
|
64
|
-
For single page applications, [Vite](https://vitejs.dev/) is integrated, and you can use any SPA framework you want. From Newlogic Core, you can only use additional functionalities such as auto-generation of imports into files within folders, iconfont, etc. Or automate some processes by writing new tasks in `gulpfile.js`.
|
45
|
+
Source files are divided by modules inside `src` directory - styles, scripts, templates, emails, assets. It is optional which modules you want to use for the project, simple delete the directory. You really only use what you want to use.
|
65
46
|
|
66
47
|
## 🪄 Instalation
|
67
48
|
|
@@ -71,33 +52,22 @@ npm i @newlogic-digital/core --save-dev
|
|
71
52
|
|
72
53
|
### Requirements
|
73
54
|
|
74
|
-
- [Node.js LTS (
|
75
|
-
- [NPM (7.x)](https://www.npmjs.com/package/npm) or [Yarn (2.x)](https://yarnpkg.com/)
|
55
|
+
- [Node.js LTS (16.x)](https://nodejs.org/en/download/)
|
76
56
|
|
77
57
|
### Config
|
78
58
|
|
79
|
-
Each Newlogic Core project has to have config via `
|
59
|
+
Each Newlogic Core project has to have config via `vite.config.js`
|
80
60
|
|
81
61
|
```js
|
82
|
-
import {defineConfig} from
|
62
|
+
import { defineConfig } from 'vituum'
|
63
|
+
import core from "@newlogic-digital/core"
|
83
64
|
|
84
|
-
// minimum configuration example
|
85
65
|
export default defineConfig({
|
86
|
-
|
87
|
-
purge: {
|
88
|
-
content: ['src/scripts/**/*.js', 'src/templates/**/*.twig', 'app/Presenters/templates/**/*.latte', 'temp/cdn/*.js']
|
89
|
-
}
|
90
|
-
}
|
66
|
+
integrations: [core()]
|
91
67
|
})
|
92
68
|
```
|
93
69
|
|
94
70
|
You can also try minimal example project [core-starter](https://github.com/newlogic-digital/core-starter)
|
95
71
|
|
96
|
-
## 📌 Future plans
|
97
|
-
- translating docs to english
|
98
|
-
- refactor or rewrite everything 😂
|
99
|
-
- concept is good, but realization could be way better
|
100
|
-
- future rewrite could drop gulp completely and use esbuild for css, js build and vite for dev server
|
101
|
-
|
102
72
|
## Licence
|
103
|
-
|
73
|
+
MIT
|
package/index.js
CHANGED
@@ -1,44 +1,263 @@
|
|
1
|
-
import
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
1
|
+
import tailwind from '@vituum/tailwind'
|
2
|
+
import posthtml from '@vituum/posthtml'
|
3
|
+
import juice from '@vituum/juice'
|
4
|
+
import twig from '@vituum/twig'
|
5
|
+
import latte from '@vituum/latte'
|
6
|
+
import lodash from 'lodash'
|
7
|
+
import minifier from 'html-minifier-terser'
|
8
|
+
import fs from 'fs'
|
9
|
+
import { dirname, resolve } from 'path'
|
10
|
+
import Prism from 'prismjs'
|
11
|
+
import loadLanguages from 'prismjs/components/index.js'
|
12
|
+
import NormalizeWhitespace from 'prismjs/plugins/normalize-whitespace/prism-normalize-whitespace.js'
|
13
|
+
|
14
|
+
loadLanguages(['markup', 'css', 'javascript'])
|
15
|
+
|
16
|
+
const stripIndent = (string) => {
|
17
|
+
const indent = () => {
|
18
|
+
const match = string.match(/^[ \t]*(?=\S)/gm)
|
19
|
+
|
20
|
+
if (!match) {
|
21
|
+
return 0
|
22
|
+
}
|
23
|
+
|
24
|
+
return match.reduce((r, a) => Math.min(r, a.length), Infinity)
|
25
|
+
}
|
26
|
+
|
27
|
+
if (indent() === 0) {
|
28
|
+
return string
|
29
|
+
}
|
30
|
+
|
31
|
+
const regex = new RegExp(`^[ \\t]{${indent()}}`, 'gm')
|
32
|
+
|
33
|
+
return string.replace(regex, '')
|
34
|
+
}
|
35
|
+
|
36
|
+
const defaultConfig = {
|
37
|
+
posthtml: {},
|
38
|
+
juice: {},
|
39
|
+
tailwind: {},
|
40
|
+
twig: {
|
41
|
+
namespaces: {
|
42
|
+
src: resolve(process.cwd(), 'src'),
|
43
|
+
templates: resolve(process.cwd(), 'src/templates')
|
44
|
+
},
|
45
|
+
functions: {
|
46
|
+
pages: () => {
|
47
|
+
return fs.readdirSync('src/views').filter(file => fs.statSync('src/views/' + file).isFile())
|
48
|
+
},
|
49
|
+
fetch: (data) => {
|
50
|
+
if (typeof data !== 'undefined') {
|
51
|
+
if (data.indexOf('http') > -1) {
|
52
|
+
return data
|
53
|
+
} else {
|
54
|
+
let slash = data.indexOf('/') + 1
|
55
|
+
if (slash > 1) {
|
56
|
+
slash = 0
|
57
|
+
}
|
58
|
+
|
59
|
+
return fs.readFileSync(process.cwd() + '/' + data.substring(slash, data.length), 'utf8').toString()
|
60
|
+
}
|
61
|
+
}
|
62
|
+
},
|
63
|
+
randomColor: () => {
|
64
|
+
return '#' + Math.random().toString(16).slice(2, 8)
|
65
|
+
},
|
66
|
+
placeholder: (width, height) => {
|
67
|
+
const colors = ['333', '444', '666', '222', '777', '888', '111']
|
68
|
+
return 'https://via.placeholder.com/' + width + 'x' + height + '/' + colors[Math.floor(Math.random() * colors.length)] + '.webp'
|
69
|
+
},
|
70
|
+
lazy: (width, height) => {
|
71
|
+
const svg = encodeURIComponent(stripIndent('<svg width="' + width + '" height="' + height + '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' + width + ' ' + height + '"></svg>'))
|
72
|
+
|
73
|
+
return 'data:image/svg+xml;charset=UTF-8,' + svg
|
74
|
+
},
|
75
|
+
ratio: (width, height) => {
|
76
|
+
return (height / width) * 100
|
77
|
+
}
|
78
|
+
},
|
79
|
+
filters: {
|
80
|
+
asset: (url) => {
|
81
|
+
return url.replace('/src/', '/')
|
82
|
+
},
|
83
|
+
rem: (value) => {
|
84
|
+
return `${value / 16}rem`
|
85
|
+
},
|
86
|
+
encode64: (path) => {
|
87
|
+
const svg = encodeURIComponent(stripIndent(path))
|
88
|
+
|
89
|
+
return 'data:image/svg+xml;charset=UTF-8,' + svg
|
90
|
+
},
|
91
|
+
exists: (path) => {
|
92
|
+
if (path.indexOf('/') === 0) {
|
93
|
+
path = path.slice(1)
|
94
|
+
}
|
95
|
+
|
96
|
+
return fs.existsSync(resolve(process.cwd(), path))
|
97
|
+
},
|
98
|
+
tel: (value) => {
|
99
|
+
return value.replace(/\s+/g, '').replace('(', '').replace(')', '')
|
100
|
+
}
|
101
|
+
},
|
102
|
+
extensions: [
|
103
|
+
(Twig) => {
|
104
|
+
Twig.exports.extendTag({
|
105
|
+
type: 'json',
|
106
|
+
regex: /^json\s+(.+)$|^json$/,
|
107
|
+
next: ['endjson'],
|
108
|
+
open: true,
|
109
|
+
compile: function(token) {
|
110
|
+
const expression = token.match[1] ?? '\'_null\''
|
111
|
+
|
112
|
+
token.stack = Reflect.apply(Twig.expression.compile, this, [{
|
113
|
+
type: Twig.expression.type.expression,
|
114
|
+
value: expression
|
115
|
+
}]).stack
|
116
|
+
|
117
|
+
delete token.match
|
118
|
+
return token
|
119
|
+
},
|
120
|
+
parse: async function(token, context, chain) {
|
121
|
+
const name = Reflect.apply(Twig.expression.parse, this, [token.stack, context])
|
122
|
+
const output = this.parse(token.output, context)
|
123
|
+
|
124
|
+
const minify = await minifier.minify(output, {
|
125
|
+
collapseWhitespace: true,
|
126
|
+
collapseInlineTagWhitespace: false,
|
127
|
+
minifyCSS: true,
|
128
|
+
removeAttributeQuotes: true,
|
129
|
+
quoteCharacter: '\'',
|
130
|
+
minifyJS: true
|
131
|
+
})
|
132
|
+
|
133
|
+
if (name === '_null') {
|
134
|
+
return {
|
135
|
+
chain,
|
136
|
+
output: JSON.stringify(minify)
|
137
|
+
}
|
138
|
+
} else {
|
139
|
+
return {
|
140
|
+
chain,
|
141
|
+
output: JSON.stringify({
|
142
|
+
[name]: minify
|
143
|
+
})
|
144
|
+
}
|
145
|
+
}
|
146
|
+
}
|
147
|
+
})
|
148
|
+
Twig.exports.extendTag({
|
149
|
+
type: 'endjson',
|
150
|
+
regex: /^endjson$/,
|
151
|
+
next: [],
|
152
|
+
open: false
|
153
|
+
})
|
154
|
+
},
|
155
|
+
(Twig) => {
|
156
|
+
Twig.exports.extendTag({
|
157
|
+
type: "code",
|
158
|
+
regex: /^code\s+(.+)$/,
|
159
|
+
next: ["endcode"], // match the type of the end tag
|
160
|
+
open: true,
|
161
|
+
compile: function (token) {
|
162
|
+
const expression = token.match[1];
|
163
|
+
|
164
|
+
token.stack = Reflect.apply(Twig.expression.compile, this, [{
|
165
|
+
type: Twig.expression.type.expression,
|
166
|
+
value: expression
|
167
|
+
}]).stack;
|
168
|
+
|
169
|
+
delete token.match;
|
170
|
+
return token;
|
171
|
+
},
|
172
|
+
parse: function (token, context, chain) {
|
173
|
+
let type = Reflect.apply(Twig.expression.parse, this, [token.stack, context]);
|
174
|
+
let output = this.parse(token.output, context);
|
175
|
+
let mirror = false;
|
176
|
+
|
177
|
+
if (type.includes(":mirror")) {
|
178
|
+
mirror = true;
|
179
|
+
type = type.replace(":mirror", "")
|
180
|
+
}
|
181
|
+
|
182
|
+
const Normalize = new NormalizeWhitespace({
|
183
|
+
'remove-trailing': true,
|
184
|
+
'remove-indent': true,
|
185
|
+
'left-trim': true,
|
186
|
+
'right-trim': true,
|
187
|
+
});
|
188
|
+
|
189
|
+
const wrap = (code, lang) => {
|
190
|
+
return `<pre class="language-${lang}"><code>${code}</code></pre>`
|
191
|
+
}
|
192
|
+
|
193
|
+
const highlight = (str, lang) => {
|
194
|
+
if (!lang) {
|
195
|
+
return wrap(str, 'text')
|
196
|
+
}
|
197
|
+
lang = lang.toLowerCase()
|
198
|
+
const rawLang = lang
|
199
|
+
if (lang === 'vue' || lang === 'html') {
|
200
|
+
lang = 'markup'
|
201
|
+
}
|
202
|
+
if (lang === 'md') {
|
203
|
+
lang = 'markdown'
|
204
|
+
}
|
205
|
+
if (lang === 'ts') {
|
206
|
+
lang = 'typescript'
|
207
|
+
}
|
208
|
+
if (lang === 'py') {
|
209
|
+
lang = 'python'
|
210
|
+
}
|
211
|
+
if (!Prism.languages[lang]) {
|
212
|
+
try {
|
213
|
+
loadLanguages([lang])
|
214
|
+
} catch (e) {
|
215
|
+
console.warn(`Syntax highlight for language "${lang}" is not supported.`)
|
216
|
+
}
|
217
|
+
}
|
218
|
+
if (Prism.languages[lang]) {
|
219
|
+
const code = Prism.highlight(Normalize.normalize(str), Prism.languages[lang], lang)
|
220
|
+
return wrap(code, rawLang)
|
221
|
+
}
|
222
|
+
return wrap(str, 'text')
|
223
|
+
}
|
224
|
+
|
225
|
+
return {
|
226
|
+
chain: chain,
|
227
|
+
output: `${mirror ? output : ""}${highlight(output, type)}`
|
228
|
+
};
|
229
|
+
}
|
230
|
+
});
|
231
|
+
Twig.exports.extendTag({
|
232
|
+
type: "endcode",
|
233
|
+
regex: /^endcode$/,
|
234
|
+
next: [ ],
|
235
|
+
open: false
|
236
|
+
});
|
237
|
+
}
|
238
|
+
]
|
239
|
+
},
|
240
|
+
latte: {
|
241
|
+
isStringFilter: (filename) => dirname(filename).endsWith('emails')
|
242
|
+
}
|
44
243
|
}
|
244
|
+
|
245
|
+
const integration = (userConfig = {}) => {
|
246
|
+
userConfig = lodash.merge(defaultConfig, userConfig)
|
247
|
+
|
248
|
+
return {
|
249
|
+
config: {
|
250
|
+
integrations: [posthtml(userConfig.posthtml), juice(userConfig.juice), tailwind(userConfig.tailwind), twig(userConfig.twig), latte(userConfig.latte)],
|
251
|
+
server: {
|
252
|
+
open: true,
|
253
|
+
https: true,
|
254
|
+
reload: file => (file.endsWith('.tpl') || file.endsWith('.latte')) && !file.includes('temp/')
|
255
|
+
},
|
256
|
+
templates: {
|
257
|
+
format: 'twig'
|
258
|
+
}
|
259
|
+
}
|
260
|
+
}
|
261
|
+
}
|
262
|
+
|
263
|
+
export default integration
|
package/package.json
CHANGED
@@ -1,72 +1,27 @@
|
|
1
1
|
{
|
2
2
|
"name": "@newlogic-digital/core",
|
3
3
|
"type": "module",
|
4
|
-
"version": "0.
|
4
|
+
"version": "1.0.0-beta.2",
|
5
5
|
"main": "index.js",
|
6
6
|
"author": "New Logic Studio s.r.o.",
|
7
7
|
"description": "Set of tools that can be used to create modern web applications",
|
8
|
-
"license": "
|
9
|
-
"scripts": {
|
10
|
-
"docs:dev": "vitepress dev docs",
|
11
|
-
"docs:build": "vitepress build docs",
|
12
|
-
"docs:serve": "vitepress serve docs",
|
13
|
-
"tailwind": "rollup modules/tailwind/index.js --file modules/tailwind/index.cjs --format cjs"
|
14
|
-
},
|
8
|
+
"license": "MIT",
|
15
9
|
"dependencies": {
|
16
|
-
"@
|
17
|
-
"@
|
18
|
-
"@
|
19
|
-
"@
|
20
|
-
"@
|
21
|
-
"
|
22
|
-
"
|
23
|
-
"
|
24
|
-
"fs-extra": "^10.1.0",
|
25
|
-
"plugin-error": "^1.0.1",
|
26
|
-
"through2": "^4.0.2",
|
27
|
-
"twig": "^1.15.4",
|
28
|
-
"gulp": "^4.0.2",
|
29
|
-
"gulp-data": "^1.3.1",
|
30
|
-
"gulp-htmlmin": "^5.0.1",
|
31
|
-
"gulp-if": "^3.0.0",
|
32
|
-
"gulp-inline-css": "^4.0.0",
|
33
|
-
"gulp-plumber": "^1.2.1",
|
34
|
-
"gulp-postcss": "^9.0.1",
|
35
|
-
"gulp-purgecss": "^4.1.3",
|
36
|
-
"gulp-rename": "^2.0.0",
|
37
|
-
"gulp-replace": "^1.1.3",
|
38
|
-
"gulp-rev": "^9.0.0",
|
39
|
-
"gulp-rev-rewrite": "^5.0.0",
|
40
|
-
"lazypipe": "^1.0.2",
|
41
|
-
"lodash": "^4.17.21",
|
42
|
-
"postcss-custom-media": "^8.0.0",
|
43
|
-
"postcss-custom-properties": "^12.1.7",
|
44
|
-
"postcss-custom-selectors": "^6.0.0",
|
45
|
-
"postcss-import": "^14.1.0",
|
46
|
-
"postcss-nesting": "^10.1.6",
|
47
|
-
"rollup": "^2.73.0",
|
48
|
-
"rollup-plugin-import-map": "^2.2.2",
|
49
|
-
"rollup-plugin-terser": "^7.0.2",
|
50
|
-
"vite": "~2.9.9",
|
51
|
-
"prismjs": "^1.28.0"
|
52
|
-
},
|
53
|
-
"peerDependencies": {
|
54
|
-
"autoprefixer": "^10.4.7",
|
55
|
-
"postcss": "^8.4.13",
|
56
|
-
"tailwindcss": "^3.0.24"
|
57
|
-
},
|
58
|
-
"devDependencies": {
|
59
|
-
"vitepress": "^0.22.4"
|
10
|
+
"@vituum/tailwind": "^0.1.2",
|
11
|
+
"@vituum/posthtml": "^0.1.0",
|
12
|
+
"@vituum/juice": "^0.1.3",
|
13
|
+
"@vituum/twig": "^0.1.0",
|
14
|
+
"@vituum/latte": "^0.1.0",
|
15
|
+
"prismjs": "~1.28.0",
|
16
|
+
"html-minifier-terser": "^7.0.0",
|
17
|
+
"lodash": "^4.17.21"
|
60
18
|
},
|
61
19
|
"files": [
|
62
|
-
"index.js"
|
63
|
-
"modules",
|
64
|
-
"packages"
|
20
|
+
"index.js"
|
65
21
|
],
|
66
22
|
"engines": {
|
67
|
-
"node": ">=
|
68
|
-
"npm": ">=
|
69
|
-
"yarn": ">=2.3.0"
|
23
|
+
"node": ">=16.0.0",
|
24
|
+
"npm": ">=8.0.0"
|
70
25
|
},
|
71
26
|
"repository": {
|
72
27
|
"type": "git",
|