@avleon/cli 0.0.41 → 0.0.43

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.
@@ -0,0 +1,69 @@
1
+ name: Publish to npm
2
+
3
+
4
+
5
+ on:
6
+
7
+ push:
8
+
9
+ tags:
10
+
11
+ - 'v*' # Trigger only when a tag like v1.0.0 is pushed
12
+
13
+
14
+
15
+ jobs:
16
+
17
+ publish:
18
+
19
+ runs-on: ubuntu-latest
20
+
21
+
22
+
23
+ steps:
24
+
25
+ - name: Checkout repository
26
+
27
+ uses: actions/checkout@v4
28
+
29
+ - name: Setup Node.js
30
+
31
+ uses: actions/setup-node@v4
32
+
33
+ with:
34
+
35
+ node-version: 20
36
+
37
+ registry-url: 'https://registry.npmjs.org'
38
+
39
+
40
+
41
+ - name: Install pnpm
42
+
43
+ uses: pnpm/action-setup@v4
44
+
45
+ with:
46
+
47
+ version: 9
48
+
49
+
50
+
51
+ - name: Install dependencies
52
+
53
+ run: pnpm install --frozen-lockfile
54
+
55
+
56
+ - name: Build package
57
+
58
+ run: pnpm run build
59
+
60
+
61
+
62
+ - name: Publish to npm
63
+
64
+ run: pnpm publish --access public --no-git-checks
65
+
66
+ env:
67
+
68
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
69
+
package/dist/cli.js CHANGED
@@ -49,6 +49,7 @@ const commands_1 = require("./commands");
49
49
  const child_process_1 = require("child_process");
50
50
  const create_1 = require("./create");
51
51
  const service_1 = require("./service");
52
+ const lodash_1 = require("lodash");
52
53
  function iqraCli() { }
53
54
  const listedCommands = commands_1.commandList
54
55
  .flatMap((x) => x.Command)
@@ -116,28 +117,28 @@ async function createModel(name, options = { force: false, orm: false }) {
116
117
  console.log("Model already exists!", "Use -f or --force to overwrite.\r");
117
118
  return;
118
119
  }
119
- const basicModel = `
120
- \t
121
- export class ${modelName}{
122
- id:number;
123
- name: string;
124
- }
120
+ const basicModel = `
121
+ \t
122
+ export class ${modelName}{
123
+ id:number;
124
+ name: string;
125
+ }
125
126
  `;
126
- const ormModel = `
127
- import {Entity, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
128
- \t
129
- @Entity({name:'${modelName}'})
130
- export class ${modelName}{
131
-
132
- @PrimaryGeneratedColumn()
133
- id:number;
134
-
135
- @CreateDateColumn()
136
- createdAt: Date;
137
-
138
- @UpdateDateColumn()
139
- updatedAt: Date;
140
- }
127
+ const ormModel = `
128
+ import {Entity, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
129
+ \t
130
+ @Entity({name:'${modelName}'})
131
+ export class ${modelName}{
132
+
133
+ @PrimaryGeneratedColumn()
134
+ id:number;
135
+
136
+ @CreateDateColumn()
137
+ createdAt: Date;
138
+
139
+ @UpdateDateColumn()
140
+ updatedAt: Date;
141
+ }
141
142
  `;
142
143
  const formatModel = await prettier.format(options.orm ? ormModel : basicModel, {
143
144
  singleQuote: true,
@@ -163,19 +164,19 @@ async function createMiddleware(name, options = { force: false }) {
163
164
  recursive: true,
164
165
  });
165
166
  }
166
- const basicMiddleware = `
167
- \t
168
- import { Middleware, IMiddleware } from '@avleon/core';
169
-
170
- @Middleware
171
- export class ${modelName}Middleware implements IMiddleware{
172
-
173
- invoke(req:IRequest){
174
- // your logic goes here
175
- return req;
176
- }
177
-
178
- }
167
+ const basicMiddleware = `
168
+ \t
169
+ import { Middleware, IMiddleware } from '@avleon/core';
170
+
171
+ @Middleware
172
+ export class ${modelName}Middleware implements IMiddleware{
173
+
174
+ invoke(req:IRequest){
175
+ // your logic goes here
176
+ return req;
177
+ }
178
+
179
+ }
179
180
  `;
180
181
  const formatModel = await prettier.format(basicMiddleware, {
181
182
  singleQuote: true,
@@ -190,40 +191,41 @@ async function createConfig(name, options = { force: false }) {
190
191
  throw new Error("Config name not found . use --name or node artisan make:config JwtConfig");
191
192
  }
192
193
  const modelName = name.at(0).toUpperCase() + name.slice(1);
193
- const modelExists = (0, fs_1.existsSync)(path_1.default.join(process.cwd(), `./src/cinfigs/${modelName.toLowerCase()}.ts`));
194
+ const modelClassName = (0, lodash_1.capitalize)(modelName).endsWith('Config') ? (0, lodash_1.capitalize)(modelName) : (0, lodash_1.capitalize)(modelName) + "Config";
195
+ const modelExists = (0, fs_1.existsSync)(path_1.default.join(process.cwd(), `./src/config/${modelName.toLowerCase()}.ts`));
194
196
  if (modelExists && !options.force) {
195
197
  console.log("Config already exists!", "Use -f or --force to overwrite.\r");
196
198
  return;
197
199
  }
198
- const modelFolderExists = (0, fs_1.existsSync)(path_1.default.join(process.cwd(), "./src/configs"));
200
+ const modelFolderExists = (0, fs_1.existsSync)(path_1.default.join(process.cwd(), "./src/config"));
199
201
  if (!modelFolderExists) {
200
- await promises_1.default.mkdir(path_1.default.join(process.cwd(), "./src/configs"), {
202
+ await promises_1.default.mkdir(path_1.default.join(process.cwd(), "./src/config"), {
201
203
  recursive: true,
202
204
  });
203
205
  }
204
- const basicMiddleware = `
205
- \t
206
- import { Config, IConfig } from '@avleon/core';
207
-
208
- @AppConfig
209
- export class ${modelName}Middleware implements IConfig<T>{
210
-
211
- config(env: Environment){
212
- // config must return config
213
- return {
214
-
215
- };
216
- }
217
-
218
- }
206
+ const basicMiddleware = `
207
+ \t
208
+ import { Config, IConfig } from '@avleon/core';
209
+
210
+ @AppConfig
211
+ export class ${modelClassName} implements IConfig<T>{
212
+
213
+ config(env: Environment){
214
+ // config must return config
215
+ return {
216
+
217
+ };
218
+ }
219
+
220
+ }
219
221
  `;
220
222
  const formatModel = await prettier.format(basicMiddleware, {
221
223
  singleQuote: true,
222
224
  singleAttributePerLine: true,
223
225
  parser: "typescript",
224
226
  });
225
- await promises_1.default.writeFile(path_1.default.join(process.cwd(), "./src/middlewares/" + modelName.toLowerCase() + ".ts"), formatModel);
226
- console.log("Middleware created succssessfully!");
227
+ await promises_1.default.writeFile(path_1.default.join(process.cwd(), "./src/config/" + modelName.toLowerCase() + ".ts"), formatModel);
228
+ console.log("Config created succssessfully!");
227
229
  }
228
230
  async function run() {
229
231
  try {
package/dist/commands.js CHANGED
@@ -22,22 +22,22 @@ exports.commandList = [
22
22
  Command: "make:controller",
23
23
  Alias: "m:c",
24
24
  Options: `-f, --force for force create\n -r or --resource for resource controller`,
25
- Description: `Create a new controller. Use -f or --force to overwrite existing controller. If you want to create a resource controller pass options -r or --resource.
26
- You may also pass model by -m or --model.\n
27
- ${chalk_1.default.blueBright("Note:")} when using a resource contorller iqra will automaticall check for dtos related to model name. If -m is user then
28
- it will check for dtos like UserCreateDto and UserUpdateDto. If dtos not found it will create automatically using
29
- model properties.
25
+ Description: `Create a new controller. Use -f or --force to overwrite existing controller. If you want to create a resource controller pass options -r or --resource.
26
+ You may also pass model by -m or --model.\n
27
+ ${chalk_1.default.blueBright("Note:")} when using a resource contorller iqra will automaticall check for dtos related to model name. If -m is user then
28
+ it will check for dtos like UserCreateDto and UserUpdateDto. If dtos not found it will create automatically using
29
+ model properties.
30
30
  `,
31
31
  },
32
32
  {
33
33
  Command: "make:service",
34
34
  Alias: "m:s",
35
35
  Options: `-f, --force for force create\n -r or --resource for resource controller, --dtos`,
36
- Description: `Create a new controller. Use -f or --force to overwrite existing controller. If you want to create a resource controller pass options -r or --resource.
37
- You may also pass model by -m or --model.\n
38
- ${chalk_1.default.blueBright("Note:")} when using a resource contorller iqra will automaticall check for dtos related to model name. If -m is user then
39
- it will check for dtos like UserCreateDto and UserUpdateDto. If dtos not found it will create automatically using
40
- model properties.
36
+ Description: `Create a new controller. Use -f or --force to overwrite existing controller. If you want to create a resource controller pass options -r or --resource.
37
+ You may also pass model by -m or --model.\n
38
+ ${chalk_1.default.blueBright("Note:")} when using a resource contorller iqra will automaticall check for dtos related to model name. If -m is user then
39
+ it will check for dtos like UserCreateDto and UserUpdateDto. If dtos not found it will create automatically using
40
+ model properties.
41
41
  `,
42
42
  },
43
43
  {
package/dist/create.js CHANGED
@@ -106,18 +106,42 @@ async function createApplication(fname, flags = {}) {
106
106
  await fs_extra_1.default.ensureDir(appPaths.root + "/public");
107
107
  // copy
108
108
  // home controller
109
- const homeContrlelr = `
110
- import { ApiController, Get } from "@avleon/core";
111
-
112
-
113
- @ApiController
114
- export class HomeController{
115
-
116
- @Get()
117
- sayHello(){
118
- return "Hello World!";
119
- }
120
- }
109
+ const homeService = `
110
+ import { AppService } from '@avleon/core';
111
+
112
+
113
+ @AppService
114
+ export class HomeService{
115
+
116
+ sayHello(){
117
+ return "Hello World!";
118
+ }
119
+ }
120
+ `;
121
+ const formatHomeService = await prettier.format(homeService, {
122
+ singleQuote: true,
123
+ singleAttributePerLine: true,
124
+ parser: "typescript",
125
+ });
126
+ await fs_extra_1.default.writeFile(path_1.default.join(appPaths.services, "home.service.ts"), formatHomeService);
127
+ // home controller
128
+ const homeContrlelr = `
129
+ import { ApiController, Get } from '@avleon/core';
130
+ import { HomeService } from '../services/home.service';
131
+
132
+
133
+ @ApiController
134
+ export class HomeController{
135
+
136
+ constructor(
137
+ private readonly homeService: HomeService
138
+ ){}
139
+
140
+ @Get()
141
+ sayHello(){
142
+ return this.homeService.sayHello();
143
+ }
144
+ }
121
145
  `;
122
146
  const formatHomeController = await prettier.format(homeContrlelr, {
123
147
  singleQuote: true,
@@ -125,22 +149,52 @@ export class HomeController{
125
149
  parser: "typescript",
126
150
  });
127
151
  await fs_extra_1.default.writeFile(path_1.default.join(appPaths.controllers, "home.controller.ts"), formatHomeController);
152
+ const homeContrlelrTest = `
153
+ import { AvleonTest } from "@avleon/core";
154
+ import { HomeController } from "./home.controller"
155
+
156
+ describe('Home Controller', () => {
157
+ let controller!: HomeController;
158
+
159
+ beforeAll(()=>{
160
+ controller = AvleonTest.getController(HomeController);
161
+ })
162
+
163
+ it('should be defined', () => {
164
+ expect(controller).toBeDefined()
165
+ })
166
+
167
+ it('should have sayhello function', () => {
168
+ expect(controller.sayHello).toBeDefined
169
+ })
170
+
171
+ it('should return sayhello', () => {
172
+ expect(controller.sayHello()).toBe("Hello World!");
173
+ })
174
+ })
175
+ `;
176
+ const formatHomeControllerTest = await prettier.format(homeContrlelrTest, {
177
+ singleQuote: true,
178
+ singleAttributePerLine: true,
179
+ parser: "typescript",
180
+ });
181
+ await fs_extra_1.default.writeFile(path_1.default.join(appPaths.controllers, "home.controller.spec.ts"), formatHomeControllerTest);
128
182
  // openapi config file
129
- const openapiConfigFile = `
130
- import { AppConfig, Environment, IConfig } from "@avleon/core";
131
-
132
- @AppConfig
133
- export class OpenApiConfig implements IConfig {
134
-
135
- config(env: Environment) {
136
- return {
137
- info: {
138
- title: '${(0, lodash_1.capitalize)(fname)}',
139
- version: '0.0.1',
140
- },
141
- }
142
- }
143
- }
183
+ const openapiConfigFile = `
184
+ import { AppConfig, Environment, IConfig } from "@avleon/core";
185
+
186
+ @AppConfig
187
+ export class OpenApiConfig implements IConfig {
188
+
189
+ config(env: Environment) {
190
+ return {
191
+ info: {
192
+ title: '${(0, lodash_1.capitalize)(fname)}',
193
+ version: '0.0.1',
194
+ },
195
+ }
196
+ }
197
+ }
144
198
  `;
145
199
  const formatConfigFile = await prettier.format(openapiConfigFile, {
146
200
  singleQuote: true,
@@ -149,22 +203,22 @@ export class OpenApiConfig implements IConfig {
149
203
  });
150
204
  await fs_extra_1.default.writeFile(path_1.default.join(appPaths.configs, "openapi.config.ts"), formatConfigFile);
151
205
  // main file
152
- const mainFunc = `
153
- import { Avleon } from '@avleon/core';
154
- import { OpenApiConfig } from './config/openapi.config';
155
- import { HomeController } from './controllers/home.controller';
156
-
157
-
158
- const app = Avleon.createApplication();
159
-
160
- if(app.isDevelopemtn()){
161
- app.useOpenApi(OpenApiConfig);
162
- }
163
-
164
- app.useControllers([ HomeController ]);
165
- app.useStaticFiles();
166
-
167
- export default app;
206
+ const mainFunc = `
207
+ import { Avleon } from '@avleon/core';
208
+ import { OpenApiConfig } from './config/openapi.config';
209
+ import { HomeController } from './controllers/home.controller';
210
+
211
+
212
+ const app = Avleon.createApplication();
213
+
214
+ if(app.isDevelopment()){
215
+ app.useOpenApi(OpenApiConfig);
216
+ }
217
+
218
+ app.useControllers([ HomeController ]);
219
+ app.useStaticFiles();
220
+
221
+ export default app;
168
222
  `;
169
223
  const formatMainFunc = await prettier.format(mainFunc, {
170
224
  singleQuote: true,
@@ -173,15 +227,14 @@ export default app;
173
227
  });
174
228
  await fs_extra_1.default.writeFile(path_1.default.join(appPaths.src, "app.ts"), formatMainFunc);
175
229
  // create serve.ts
176
- const serveFunc = `
177
- import app from "./app";
178
- import { capitalize } from "lodash";
179
-
180
- async function start(){
181
- app.run(); // default run on :4000
182
- }
183
-
184
- start();
230
+ const serveFunc = `
231
+ import app from "./app";
232
+
233
+ async function start(){
234
+ app.run(); // default run on :4000
235
+ }
236
+
237
+ start();
185
238
  `;
186
239
  const formatServeFunc = await prettier.format(serveFunc, {
187
240
  singleQuote: true,
@@ -190,94 +243,96 @@ start();
190
243
  });
191
244
  await fs_extra_1.default.writeFile(path_1.default.join(appPaths.src, "serve.ts"), formatServeFunc);
192
245
  // create package.json
193
- const packageJson = `
194
- {
195
- "name": "${fname}",
196
- "version": "0.0.1",
197
- "description": "Basic avleon application",
198
- "main": "index.js",
199
- "scripts": {
200
- "start:dev": "nodemon ./src/serve.ts",
201
- "start:debug": "nodemon --watch ./src --ext ts --exec node --inspect=0.0.0.0:9229 -r ts-node/register ./src/serve.ts",
202
- "clean": "rimraf ./dist",
203
- "build": "npm run clean & tsc --project ./tsconfig.build.json",
204
- "start": "node ./dist/serve.js",
205
- "test": "jest",
206
- "test:watch": "jest --watch",
207
- "test:cov": "jest --coverage",
208
- "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
209
- "test:e2e": "jest --config ./test/e2e-spec.json"
210
- },
211
- "keywords": ["avleon","nodejs","typescript"],
212
- "license": "ISC",
213
- "dependencies": {
214
- "@avleon/core": "^0.0.36",
215
- "@avleon/cli": "^0.0.40",
216
- "class-transformer": "^0.5.1",
217
- "class-validator": "^0.14.1",
218
- "reflect-metadata": "^0.2.2"
219
- },
220
- "devDependencies": {
221
- "@types/jest": "^29.5.14",
222
- "jest": "^29.7.0",
223
- "ts-jest": "^29.2.6",
224
- "nodemon": "^3.1.7",
225
- "ts-node": "^10.9.2",
226
- "typescript": "^5.7.2"
227
- },
228
- "jest": {
229
- "preset": "ts-jest",
230
- "rootDir": "src",
231
- "testRegex": ".*/\/\.spec/\/\.ts$",
232
- "collectCoverageFrom": [
233
- "**/*.(t|j)s"
234
- ],
235
- "coverageDirectory": "../coverage",
236
- "testEnvironment": "node"
237
- }
238
-
239
- }
246
+ const packageJson = `
247
+ {
248
+ "name": "${(0, lodash_1.snakeCase)(fname)}",
249
+ "version": "0.0.1",
250
+ "description": "Basic avleon application",
251
+ "main": "dist/serve.js",
252
+ "scripts": {
253
+ "start:dev": "nodemon ./src/serve.ts",
254
+ "start:debug": "nodemon --watch ./src --ext ts --exec node --inspect=0.0.0.0:9229 -r ts-node/register ./src/serve.ts",
255
+ "clean": "rimraf ./dist",
256
+ "build": "npm run clean & tsc --project ./tsconfig.build.json",
257
+ "start": "node ./dist/serve.js",
258
+ "test": "jest",
259
+ "test:watch": "jest --watch",
260
+ "test:cov": "jest --coverage",
261
+ "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
262
+ "test:e2e": "jest --config ./test/e2e-spec.json"
263
+ },
264
+ "keywords": ["avleon","nodejs","typescript"],
265
+ "license": "ISC",
266
+ "dependencies": {
267
+ "@avleon/core": "^0.0.36",
268
+ "@avleon/cli": "^0.0.42",
269
+ "class-transformer": "^0.5.1",
270
+ "class-validator": "^0.14.1",
271
+ "rimraf": "^6.0.1",
272
+ "reflect-metadata": "^0.2.2"
273
+ },
274
+ "devDependencies": {
275
+ "@types/jest": "^29.5.14",
276
+ "@types/node": "^24.0.3",
277
+ "jest": "^29.7.0",
278
+ "ts-jest": "^29.2.6",
279
+ "nodemon": "^3.1.7",
280
+ "ts-node": "^10.9.2",
281
+ "typescript": "^5.7.2"
282
+ },
283
+ "jest": {
284
+ "preset": "ts-jest",
285
+ "rootDir": "src",
286
+ "testRegex": ".*.spec.ts$",
287
+ "collectCoverageFrom": [
288
+ "**/*.(t|j)s"
289
+ ],
290
+ "coverageDirectory": "../coverage",
291
+ "testEnvironment": "node"
292
+ }
293
+
294
+ }
240
295
  `;
241
296
  const formattedPackageJson = await prettier.format(packageJson, {
242
297
  parser: "json",
243
298
  });
244
299
  await fs_extra_1.default.writeFile(path_1.default.join(appPaths.root, "package.json"), formattedPackageJson);
245
- const tsconfigJson = await prettier.format(`{
246
- "compilerOptions": {
247
- "target": "ES2017",
248
- "module": "CommonJS",
249
- "moduleResolution": "node",
250
- "experimentalDecorators": true,
251
- "emitDecoratorMetadata": true,
252
- "outDir": "./dist",
253
- "esModuleInterop": true,
254
- "allowSyntheticDefaultImports": true,
255
- "forceConsistentCasingInFileNames": true,
256
- "skipLibCheck": true
257
- },
258
- "include": ["src/**/*"],
259
- "exclude": ["node_modules", "dist"]
300
+ const tsconfigJson = await prettier.format(`{
301
+ "compilerOptions": {
302
+ "target": "ES2017",
303
+ "module": "CommonJS",
304
+ "moduleResolution": "node",
305
+ "experimentalDecorators": true,
306
+ "emitDecoratorMetadata": true,
307
+ "outDir": "./dist",
308
+ "esModuleInterop": true,
309
+ "allowSyntheticDefaultImports": true,
310
+ "forceConsistentCasingInFileNames": true,
311
+ "skipLibCheck": true
312
+ },
313
+ "include": ["src/**/*"],
314
+ "exclude": ["node_modules", "dist"]
260
315
  }`, { parser: "json" });
261
316
  await fs_extra_1.default.writeFile(path_1.default.join(appPaths.root, "tsconfig.json"), tsconfigJson);
262
- const tsconfigBuildJson = await prettier.format(`{
263
- "extends": "./tsconfig.json",
264
- "exclude": ["node_modules","coverage", "test", "dist", "**/*spec.ts"]
265
- }
317
+ const tsconfigBuildJson = await prettier.format(`{
318
+ "extends": "./tsconfig.json",
319
+ "exclude": ["node_modules","coverage", "test", "dist", "**/*spec.ts"]
320
+ }
266
321
  `, { parser: "json" });
267
322
  await fs_extra_1.default.writeFile(path_1.default.join(appPaths.root, "tsconfig.build.json"), tsconfigBuildJson);
268
323
  // write e2e-test.json
269
- const e2eJson = await prettier.format(`{
270
- "moduleFileExtensions": [
271
- "js",
272
- "json",
273
- "ts"
274
- ],
275
- "rootDir": ".",
276
- "testEnvironment": "node",
277
- "testRegex": ".e2e-spec.ts$",
278
- "transform": {
279
- "^.+/\/\.(t|j)s$": "ts-jest"
280
- }
324
+ const e2eJson = await prettier.format(`{
325
+ "moduleFileExtensions": [
326
+ "js",
327
+ "json",
328
+ "ts"
329
+ ],
330
+ "rootDir": ".",
331
+ "testEnvironment": "node",
332
+ "testRegex": ".e2e-spec.ts$",
333
+ "transform": {
334
+ "^.+/\/\.(t|j)s$": "ts-jest"
335
+ }
281
336
  }`, { parser: "json" });
282
337
  await fs_extra_1.default.writeFile(path_1.default.join(appPaths.root, "test/e2e-spec.json"), e2eJson);
283
338
  await fs_extra_1.default.writeFile(path_1.default.join(appPaths.root, ".gitignore"), `node_modules/\ndist/\n.env`);
package/package.json CHANGED
@@ -1,18 +1,12 @@
1
1
  {
2
2
  "name": "@avleon/cli",
3
- "version": "0.0.41",
3
+ "version": "0.0.43",
4
4
  "bin": {
5
5
  "avleon": "./dist/cli.js"
6
6
  },
7
7
  "description": "",
8
8
  "main": "dist/index.js",
9
9
  "types": "dist/index.d.ts",
10
- "scripts": {
11
- "build": "rimraf dist & tsc",
12
- "clean": "rm -rf ./dist",
13
- "prepublish": "npm run build",
14
- "watch": "tsc-watch"
15
- },
16
10
  "keywords": [
17
11
  "cli",
18
12
  "backend-api"
@@ -40,5 +34,14 @@
40
34
  "repository": {
41
35
  "type": "git",
42
36
  "url": "git+https://github.com/avleonjs/avleon-cli"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "scripts": {
42
+ "build": "rimraf dist & tsc",
43
+ "clean": "rm -rf ./dist",
44
+ "prepublish": "npm run build",
45
+ "watch": "tsc-watch"
43
46
  }
44
- }
47
+ }