libdragon 10.7.1 → 10.8.1
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 +153 -29
- package/index.js +0 -0
- package/modules/actions/destroy.js +6 -3
- package/modules/actions/disasm.js +10 -2
- package/modules/actions/docker-utils.js +5 -0
- package/modules/actions/exec.js +36 -5
- package/modules/actions/help.js +10 -7
- package/modules/actions/init.js +166 -94
- package/modules/actions/install.js +8 -12
- package/modules/actions/make.js +5 -2
- package/modules/actions/npm-utils.js +8 -2
- package/modules/actions/start.js +22 -14
- package/modules/actions/stop.js +6 -2
- package/modules/actions/update-and-start.js +5 -2
- package/modules/actions/update.js +17 -16
- package/modules/actions/utils.js +31 -9
- package/modules/actions/version.js +3 -3
- package/modules/constants.js +4 -2
- package/modules/helpers.js +126 -7
- package/modules/parameters.js +44 -13
- package/modules/project-info.js +56 -17
- package/package.json +57 -4
- package/CHANGELOG.md +0 -582
package/README.md
CHANGED
|
@@ -2,11 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/anacierdem/libdragon-docker/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
|
-
This is a wrapper for a docker container to make managing the libdragon toolchain easier.
|
|
5
|
+
This is a wrapper for a docker container to make managing the libdragon toolchain easier. It has the additional advantage that libdragon toolchain and library can be installed on a per-project basis instead of managing system-wide installations.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Prerequisites
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
You should have [docker](https://www.docker.com/products/docker-desktop) (`>= 18`) installed on your system.
|
|
10
|
+
|
|
11
|
+
`git` is not strictly required to use the tool. Still, it is highly recommended to have git on your host machine as it will be used instead of the one in the container.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
This is primarily a node.js script which is also packaged as an executable. You have two options:
|
|
16
|
+
|
|
17
|
+
### pre-built
|
|
18
|
+
|
|
19
|
+
Download the [pre-built executable](https://github.com/anacierdem/libdragon-docker/releases/latest) for your system and put it somewhere on your PATH. It is discouraged to put it in your project folder.
|
|
20
|
+
|
|
21
|
+
<details>
|
|
22
|
+
<summary>Windows instructions</summary>
|
|
23
|
+
|
|
24
|
+
- Download the Windows executable and copy it to `C:\bin`
|
|
25
|
+
- Press `Windows + R` key combination and then enter `rundll32 sysdm.cpl,EditEnvironmentVariables`
|
|
26
|
+
- In the `Environment Variables` window find the `Path` variable under `User variables for <your user name>`
|
|
27
|
+
- Double click it and add a new entry as `C:\bin`
|
|
28
|
+
- Restart your computer.
|
|
29
|
+
- You should now be able to use the `libdragon` on a command line or PowerShell.
|
|
30
|
+
- To update it with a new version, just replace the file in `C:\bin`
|
|
31
|
+
|
|
32
|
+
</details>
|
|
33
|
+
|
|
34
|
+
<details>
|
|
35
|
+
<summary>MacOS instructions</summary>
|
|
36
|
+
|
|
37
|
+
- Download the MacOS executable and copy it to `/usr/local/bin`
|
|
38
|
+
- Right click it and choose `Open`.
|
|
39
|
+
- It will show a warning, approve it by clicking `Open` again. You can close the newly opened terminal window.
|
|
40
|
+
- You should now be able to use the `libdragon` command.
|
|
41
|
+
- To update it with a new version, replace the file in `/usr/local/bin` and repeat the other steps.
|
|
42
|
+
|
|
43
|
+
</details>
|
|
44
|
+
|
|
45
|
+
<details>
|
|
46
|
+
<summary>Linux instructions</summary>
|
|
47
|
+
|
|
48
|
+
- You should already know this :)
|
|
49
|
+
|
|
50
|
+
</details>
|
|
51
|
+
|
|
52
|
+
### via NPM
|
|
53
|
+
|
|
54
|
+
Install [node.js](https://nodejs.org/en/download/) (`>= 14`) and install `libdragon` as a global NPM package;
|
|
55
|
+
|
|
56
|
+
npm install -g libdragon
|
|
57
|
+
|
|
58
|
+
To update the tool to the latest, do `npm i -g libdragon@latest`.
|
|
59
|
+
|
|
60
|
+
## Quick Guide
|
|
61
|
+
|
|
62
|
+
Navigate to the folder you want to initialize your project and invoke libdragon;
|
|
10
63
|
|
|
11
64
|
libdragon init
|
|
12
65
|
|
|
@@ -22,21 +75,60 @@ To update the library and rebuild/install all the artifacts;
|
|
|
22
75
|
|
|
23
76
|
libdragon update
|
|
24
77
|
|
|
25
|
-
|
|
78
|
+
In general, you can invoke libdragon as follows;
|
|
26
79
|
|
|
27
|
-
|
|
80
|
+
libdragon [flags] <action>
|
|
28
81
|
|
|
29
|
-
|
|
82
|
+
Run `libdragon help [action]` for more details on individual actions.
|
|
30
83
|
|
|
31
|
-
|
|
84
|
+
## Recipes
|
|
32
85
|
|
|
33
|
-
|
|
86
|
+
### Using a different branch of libdragon
|
|
34
87
|
|
|
35
|
-
|
|
88
|
+
Initialize your project as usual:
|
|
36
89
|
|
|
37
|
-
libdragon
|
|
90
|
+
libdragon init
|
|
38
91
|
|
|
39
|
-
|
|
92
|
+
Then switch the submodule to the desired branch:
|
|
93
|
+
|
|
94
|
+
cd ./libdragon
|
|
95
|
+
git checkout opengl
|
|
96
|
+
cd ..
|
|
97
|
+
libdragon install
|
|
98
|
+
|
|
99
|
+
If your changes are on a different remote, then you will need to manage your git remotes as usual.
|
|
100
|
+
|
|
101
|
+
### Testing changes on libdragon
|
|
102
|
+
|
|
103
|
+
As libdragon is an actively developed library, you may find yourself at a position where you want to change a few things on it and see how it works. In general, if you modify the files in `libdragon` folder of your project, you can install that version to the docker container by simply running:
|
|
104
|
+
|
|
105
|
+
libdragon install
|
|
106
|
+
|
|
107
|
+
This will update all the artifacts in your container and your new code will start linking against the new version when you re-build it via `libdragon make`. The build system should pick up the change in the library and re-compile the dependent files.
|
|
108
|
+
|
|
109
|
+
Instead of depending on the above command, you can re-build the library by making it a make dependency in your project:
|
|
110
|
+
|
|
111
|
+
```makefile
|
|
112
|
+
libdragon-install: libdragon
|
|
113
|
+
$(MAKE) -C ./libdragon install
|
|
114
|
+
|
|
115
|
+
libdragon:
|
|
116
|
+
$(MAKE) -C ./libdragon
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
If your build now depends on `libdragon-install`, it will force an install (which should be pretty quick if you don't have changes) and force the build system to rebuild your project when necessary.
|
|
120
|
+
|
|
121
|
+
If you clone this repository, this setup is pretty much ready for you. Make sure you have a working libdragon setup and you get the submodules (e.g `git submodule update --init`). Then you can run `libdragon make bench` to execute the code in `./src` with your library changes. Also see [test bench](#local-test-bench).
|
|
122
|
+
|
|
123
|
+
When managing your changes to the library, you have a few options:
|
|
124
|
+
|
|
125
|
+
#### **Using `submodule` vendor strategy.**
|
|
126
|
+
|
|
127
|
+
To be able to share your project with the library change, you would need to push it somewhere public and make sure it is cloned properly by your contributors. This is not recommended for keeping track of your changes but is very useful if you plan to contribute it back to upstream. In the latter case, you can push your submodule branch to your fork and easily open a PR.
|
|
128
|
+
|
|
129
|
+
#### **Using `subtree` vendor strategy.**
|
|
130
|
+
|
|
131
|
+
To be able to share your project with the library change, you just commit your changes. This is very useful for keeping track of your changes specific to your project. On the other hand this is not recommended if you plan to contribute it back to upstream because you will need to make sure your libdragon commits are isolated and do the juggling when pushing it somewhere via `git subtree push`.
|
|
40
132
|
|
|
41
133
|
## Working on this repository
|
|
42
134
|
|
|
@@ -86,15 +178,40 @@ This repository also uses [ed64](https://github.com/anacierdem/ed64), so you can
|
|
|
86
178
|
|
|
87
179
|
There are also additional vscode launch configurations to build libdragon examples and tests based on the currently built and installed libdragon in the docker container. Most of these will always rebuild so that they will use the latest if you made and installed an alternative libdragon. The test bench itself and a few examples (that use the new build system) will already rebuild and reinstall libdragon automatically. These will always produce a rom image using the latest libdragon code in the active repository via its make dependencies. You can clean everything with the `clean` task (open the command palette and choose `Run Task -> clean`).
|
|
88
180
|
|
|
181
|
+
### Developing the tool itself
|
|
182
|
+
|
|
183
|
+
For a quick development loop it really helps linking the code in this repository as the global libdragon installation. To do this run;
|
|
184
|
+
|
|
185
|
+
npm link
|
|
186
|
+
|
|
187
|
+
in the root of the repository. Once you do this, running `libdragon` will use the code here rather than the actual npm installation. Then you can test your changes in the libdragon project here or elsewhere on your computer.
|
|
188
|
+
|
|
189
|
+
When you are happy with your changes, you can verify you conform to the coding standards via:
|
|
190
|
+
|
|
191
|
+
npm run format-check
|
|
192
|
+
npm run lint-check
|
|
193
|
+
|
|
194
|
+
You can auto-fix applicable errors by running `format` and `lint` scripts instead. Additionally, typescript is used as the type system. To be able to get away with transpiling the code during development, jsDoc flavor of types are used instead of inline ones. To check if your types, run:
|
|
195
|
+
|
|
196
|
+
npm run tsc
|
|
197
|
+
|
|
198
|
+
This repository uses [`semantic-release`](https://github.com/semantic-release/semantic-release) and manages releases from specially formatted commit messages. To simplify creating them you can use:
|
|
199
|
+
|
|
200
|
+
npx cz
|
|
201
|
+
|
|
202
|
+
It will create a `semantic-release` compatible commit from your current staged changes.
|
|
203
|
+
|
|
89
204
|
## As an NPM dependency
|
|
90
205
|
|
|
91
206
|
You can install libdragon as an NPM dependency by `npm install libdragon --save` in order to use docker in your N64 projects. A `libdragon` command similar to global intallation is provided that can be used in your NPM scripts as follows;
|
|
92
207
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
208
|
+
```json
|
|
209
|
+
"scripts": {
|
|
210
|
+
"prepare": "libdragon init"
|
|
211
|
+
"build": "libdragon make",
|
|
212
|
+
"clean": "libdragon make clean"
|
|
213
|
+
}
|
|
214
|
+
```
|
|
98
215
|
|
|
99
216
|
See [here](https://github.com/anacierdem/ed64-example) for a full example.
|
|
100
217
|
|
|
@@ -104,25 +221,32 @@ You can make an NPM package that a `libdragon` project can depend on. Just inclu
|
|
|
104
221
|
|
|
105
222
|
For example this `package.json` in the dependent project;
|
|
106
223
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
224
|
+
```json
|
|
225
|
+
{
|
|
226
|
+
"name": "libdragonDependentProject",
|
|
227
|
+
"version": "1.0.0",
|
|
228
|
+
"description": "...",
|
|
229
|
+
"scripts": {
|
|
230
|
+
"build": "libdragon make",
|
|
231
|
+
"clean": "libdragon make clean",
|
|
232
|
+
"prepare": "libdragon init"
|
|
233
|
+
},
|
|
234
|
+
"dependencies": {
|
|
235
|
+
"libdragon": <version>,
|
|
236
|
+
"ed64": <version>
|
|
120
237
|
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
121
240
|
|
|
122
241
|
will init the container for this project and run `make && make install` for `ed64` upon running `npm install`. To develop a dependency [this](https://github.com/anacierdem/libdragon-dependency) is a good starting point.
|
|
123
242
|
|
|
124
243
|
This is an experimental dependency management.
|
|
125
244
|
|
|
245
|
+
## TODOS
|
|
246
|
+
|
|
247
|
+
- [ ] Skip CI checks for irrelevant changes.
|
|
248
|
+
- [ ] Verify the NPM dependency mechanism is still working and add a test.
|
|
249
|
+
|
|
126
250
|
## Funding
|
|
127
251
|
|
|
128
252
|
If this tool helped you, consider supporting its development by sponsoring it!
|
package/index.js
CHANGED
|
File without changes
|
|
@@ -6,6 +6,9 @@ const { CONFIG_FILE, LIBDRAGON_PROJECT_MANIFEST } = require('../constants');
|
|
|
6
6
|
const { fileExists, dirExists, log } = require('../helpers');
|
|
7
7
|
const chalk = require('chalk');
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
11
|
+
*/
|
|
9
12
|
const destroy = async (libdragonInfo) => {
|
|
10
13
|
await destroyContainer(libdragonInfo);
|
|
11
14
|
|
|
@@ -22,13 +25,13 @@ const destroy = async (libdragonInfo) => {
|
|
|
22
25
|
log(chalk.green('Done cleanup.'));
|
|
23
26
|
};
|
|
24
27
|
|
|
25
|
-
module.exports = {
|
|
28
|
+
module.exports = /** @type {const} */ ({
|
|
26
29
|
name: 'destroy',
|
|
27
30
|
fn: destroy,
|
|
28
|
-
|
|
31
|
+
forwardsRestParams: false,
|
|
29
32
|
usage: {
|
|
30
33
|
name: 'destroy',
|
|
31
34
|
summary: 'Do clean-up for current project.',
|
|
32
35
|
description: `Removes libdragon configuration from current project and removes any known containers but will not touch previously vendored files. \`libdragon\` will not work anymore for this project.`,
|
|
33
36
|
},
|
|
34
|
-
};
|
|
37
|
+
});
|
|
@@ -10,6 +10,11 @@ const {
|
|
|
10
10
|
ParameterError,
|
|
11
11
|
} = require('../helpers');
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} stop
|
|
15
|
+
* @param {string} start
|
|
16
|
+
* @return {Promise<string>}
|
|
17
|
+
*/
|
|
13
18
|
const findElf = async (stop, start = '.') => {
|
|
14
19
|
start = path.resolve(start);
|
|
15
20
|
|
|
@@ -43,6 +48,9 @@ const findElf = async (stop, start = '.') => {
|
|
|
43
48
|
}
|
|
44
49
|
};
|
|
45
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @param {import('../project-info').LibdragonInfo} info
|
|
53
|
+
*/
|
|
46
54
|
const disasm = async (info) => {
|
|
47
55
|
let elfPath;
|
|
48
56
|
if (info.options.FILE) {
|
|
@@ -89,7 +97,7 @@ const disasm = async (info) => {
|
|
|
89
97
|
});
|
|
90
98
|
};
|
|
91
99
|
|
|
92
|
-
module.exports = {
|
|
100
|
+
module.exports = /** @type {const} */ ({
|
|
93
101
|
name: 'disasm',
|
|
94
102
|
fn: disasm,
|
|
95
103
|
forwardsRestParams: true,
|
|
@@ -118,4 +126,4 @@ module.exports = {
|
|
|
118
126
|
},
|
|
119
127
|
],
|
|
120
128
|
},
|
|
121
|
-
};
|
|
129
|
+
});
|
package/modules/actions/exec.js
CHANGED
|
@@ -14,6 +14,10 @@ const { start } = require('./start');
|
|
|
14
14
|
const { dockerHostUserParams } = require('./docker-utils');
|
|
15
15
|
const { installDependencies } = require('./utils');
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
19
|
+
* @returns
|
|
20
|
+
*/
|
|
17
21
|
function dockerRelativeWorkdir(libdragonInfo) {
|
|
18
22
|
return (
|
|
19
23
|
CONTAINER_TARGET_PATH +
|
|
@@ -22,10 +26,20 @@ function dockerRelativeWorkdir(libdragonInfo) {
|
|
|
22
26
|
);
|
|
23
27
|
}
|
|
24
28
|
|
|
29
|
+
/**
|
|
30
|
+
*
|
|
31
|
+
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
32
|
+
* @returns
|
|
33
|
+
*/
|
|
25
34
|
function dockerRelativeWorkdirParams(libdragonInfo) {
|
|
26
35
|
return ['--workdir', dockerRelativeWorkdir(libdragonInfo)];
|
|
27
36
|
}
|
|
28
37
|
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @param {import('../project-info').LibdragonInfo} info
|
|
41
|
+
* @returns
|
|
42
|
+
*/
|
|
29
43
|
const exec = async (info) => {
|
|
30
44
|
const parameters = info.options.EXTRA_PARAMS.slice(1);
|
|
31
45
|
log(
|
|
@@ -37,6 +51,7 @@ const exec = async (info) => {
|
|
|
37
51
|
|
|
38
52
|
const stdin = new PassThrough();
|
|
39
53
|
|
|
54
|
+
/** @type {string[]} */
|
|
40
55
|
const paramsWithConvertedPaths = parameters.map((item) => {
|
|
41
56
|
if (item.startsWith('-')) {
|
|
42
57
|
return item;
|
|
@@ -49,9 +64,14 @@ const exec = async (info) => {
|
|
|
49
64
|
return item;
|
|
50
65
|
});
|
|
51
66
|
|
|
67
|
+
/**
|
|
68
|
+
*
|
|
69
|
+
* @param {import('../project-info').LibdragonInfo} libdragonInfo
|
|
70
|
+
* @param {import('../helpers').SpawnOptions} opts
|
|
71
|
+
* @returns
|
|
72
|
+
*/
|
|
52
73
|
const tryCmd = (libdragonInfo, opts = {}) => {
|
|
53
74
|
const enableTTY = Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
54
|
-
|
|
55
75
|
return (
|
|
56
76
|
libdragonInfo.containerId &&
|
|
57
77
|
dockerExec(
|
|
@@ -80,6 +100,11 @@ const exec = async (info) => {
|
|
|
80
100
|
};
|
|
81
101
|
|
|
82
102
|
let started = false;
|
|
103
|
+
/**
|
|
104
|
+
*
|
|
105
|
+
* @param {import('fs').ReadStream=} stdin
|
|
106
|
+
* @returns
|
|
107
|
+
*/
|
|
83
108
|
const startOnceAndCmd = async (stdin) => {
|
|
84
109
|
if (!started) {
|
|
85
110
|
const newId = await start(info);
|
|
@@ -121,7 +146,11 @@ const exec = async (info) => {
|
|
|
121
146
|
inheritStderr: false,
|
|
122
147
|
// In the first run, pass the stdin to the process if it is not a TTY
|
|
123
148
|
// o/w we loose a user input unnecesarily somehow.
|
|
124
|
-
stdin:
|
|
149
|
+
stdin:
|
|
150
|
+
!process.stdin.isTTY &&
|
|
151
|
+
/** @type {import('fs').ReadStream} */ (
|
|
152
|
+
/** @type {unknown} */ (process.stdin)
|
|
153
|
+
),
|
|
125
154
|
});
|
|
126
155
|
} catch (e) {
|
|
127
156
|
if (
|
|
@@ -131,12 +160,14 @@ const exec = async (info) => {
|
|
|
131
160
|
) {
|
|
132
161
|
throw e;
|
|
133
162
|
}
|
|
134
|
-
await startOnceAndCmd(
|
|
163
|
+
await startOnceAndCmd(
|
|
164
|
+
/** @type {import('fs').ReadStream} */ (/** @type {unknown} */ (stdin))
|
|
165
|
+
);
|
|
135
166
|
}
|
|
136
167
|
return info;
|
|
137
168
|
};
|
|
138
169
|
|
|
139
|
-
module.exports = {
|
|
170
|
+
module.exports = /** @type {const} */ ({
|
|
140
171
|
name: 'exec',
|
|
141
172
|
fn: exec,
|
|
142
173
|
forwardsRestParams: true,
|
|
@@ -152,4 +183,4 @@ module.exports = {
|
|
|
152
183
|
|
|
153
184
|
Must be run in an initialized libdragon project.`,
|
|
154
185
|
},
|
|
155
|
-
};
|
|
186
|
+
});
|
package/modules/actions/help.js
CHANGED
|
@@ -3,7 +3,10 @@ const commandLineUsage = require('command-line-usage');
|
|
|
3
3
|
|
|
4
4
|
const { print } = require('../helpers');
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* @param {import('../project-info').CLIInfo} info
|
|
8
|
+
*/
|
|
9
|
+
const printUsage = async (info) => {
|
|
7
10
|
const actions = require('./');
|
|
8
11
|
const globalOptionDefinitions = [
|
|
9
12
|
{
|
|
@@ -39,7 +42,7 @@ const printUsage = (info) => {
|
|
|
39
42
|
|
|
40
43
|
To disable auto-vendoring, init with \`manual\`. With \`manual\`, libdragon files are expected at the location provided by \`--directory\` flag and the user is responsible for vendoring and updating them. This will allow using any other manual vendoring method.
|
|
41
44
|
|
|
42
|
-
You can always switch
|
|
45
|
+
You can always switch strategy by re-running \`init\` with \`--strategy\`, though you will be responsible for handling the old vendored files as they will be kept as is.
|
|
43
46
|
|
|
44
47
|
With the \`manual\` strategy, it is still recommended to have a git repository at project root such that container actions can execute faster by caching the container id inside the \`.git\` folder.\n`,
|
|
45
48
|
alias: 's',
|
|
@@ -48,11 +51,12 @@ const printUsage = (info) => {
|
|
|
48
51
|
},
|
|
49
52
|
];
|
|
50
53
|
|
|
51
|
-
const actionsToShow =
|
|
54
|
+
const actionsToShow = /** @type {(keyof actions)[]} */ (
|
|
52
55
|
info?.options.EXTRA_PARAMS?.filter(
|
|
53
56
|
(action) =>
|
|
54
57
|
Object.keys(actions).includes(action) && !['help'].includes(action)
|
|
55
|
-
) ?? (info ? [info.options.CURRENT_ACTION.name] : [])
|
|
58
|
+
) ?? (info ? [info.options.CURRENT_ACTION.name] : [])
|
|
59
|
+
);
|
|
56
60
|
|
|
57
61
|
const sections = [
|
|
58
62
|
{
|
|
@@ -96,13 +100,12 @@ const printUsage = (info) => {
|
|
|
96
100
|
print(usage);
|
|
97
101
|
};
|
|
98
102
|
|
|
99
|
-
module.exports = {
|
|
103
|
+
module.exports = /** @type {const} */ ({
|
|
100
104
|
name: 'help',
|
|
101
105
|
fn: printUsage,
|
|
102
106
|
forwardsRestParams: true,
|
|
103
|
-
mustHaveProject: false,
|
|
104
107
|
usage: {
|
|
105
108
|
name: 'help [action]',
|
|
106
109
|
summary: 'Display this help information or details for the given action.',
|
|
107
110
|
},
|
|
108
|
-
};
|
|
111
|
+
});
|