@impactor/nodejs 3.0.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/src/fs.spec.js ADDED
@@ -0,0 +1,251 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
2
+ import { resolve } from "./fs-sync";
3
+ import {
4
+ copy,
5
+ getEntries,
6
+ getModifiedTime,
7
+ getSize,
8
+ isDir,
9
+ mkdir,
10
+ move,
11
+ read,
12
+ remove,
13
+ write
14
+ } from "./fs";
15
+ import { existsSync } from "node:fs";
16
+ import { objectType } from "@impactor/javascript";
17
+ import { utimes } from "node:fs/promises";
18
+ let dir = resolve(import.meta.dirname, "./test!!/fs"), file = dir + "/file.txt";
19
+ beforeEach(() => remove(dir).then(() => write(file, "ok")));
20
+ afterEach(() => remove(dir));
21
+ test("mkdir", () => {
22
+ expect(existsSync(`${dir}/mkdir`)).toBeFalsy();
23
+ return mkdir(`${dir}/mkdir`).then(
24
+ (_value) => expect(existsSync(dir)).toBeTruthy()
25
+ );
26
+ });
27
+ test("write", () => {
28
+ expect(existsSync(`${dir}/write.txt`)).toBeFalsy();
29
+ return mkdir(dir).then(() => write(`${dir}/write.txt`, "ok")).then(() => {
30
+ expect(existsSync(`${dir}/write.txt`)).toBeTruthy();
31
+ });
32
+ });
33
+ test("write in non-existing dir", () => {
34
+ let file2 = dir + "/non-existing/file.txt";
35
+ expect(existsSync(file2)).toBeFalsy();
36
+ return write(file2, "ok").then(() => {
37
+ expect(existsSync(file2)).toBeTruthy();
38
+ });
39
+ });
40
+ test("getSize", () => Promise.all([
41
+ write(`${dir}/get-size/file1.txt`, "ok"),
42
+ write(`${dir}/get-size/file2.txt`, "ok")
43
+ ]).then(
44
+ () => Promise.all([
45
+ // file
46
+ getSize(`${dir}/get-size/file1.txt`),
47
+ // array of files and directories (sum of all sizes)
48
+ getSize([`${dir}/get-size/file1.txt`, `${dir}/get-size/file2.txt`])
49
+ // directory (sum of its contents sizes)
50
+ // getSize(`${dir}/get-size`),
51
+ ])
52
+ ).then((value) => expect(value).toEqual([2, 4])));
53
+ test("isDir", () => Promise.all([isDir(file), isDir(dir)]).then(
54
+ (value) => expect(value).toEqual([false, true])
55
+ ));
56
+ test("getModifiedTime -> file", () => Promise.all([getModifiedTime(file), getModifiedTime(dir)]).then((value) => {
57
+ expect(Math.floor(value[0])).toBeGreaterThanOrEqual(1624906832178);
58
+ expect(Math.floor(value[1])).toBeGreaterThanOrEqual(1624906832178);
59
+ }));
60
+ test("move", () => {
61
+ let file2 = dir + "/file2.txt";
62
+ expect(existsSync(file)).toBeTruthy();
63
+ expect(existsSync(file2)).toBeFalsy();
64
+ return move(file, file2).then(() => {
65
+ expect(existsSync(file)).toBeFalsy();
66
+ expect(existsSync(file2)).toBeTruthy();
67
+ });
68
+ });
69
+ test("read", () => {
70
+ let fileJson = dir + "/file.json", fileArray = dir + "/array.json", fileJsonComments = dir + "/comments.json";
71
+ let contentJsonComments = `
72
+ // this file is created to test reading .json files that contains comments
73
+ // to test stripComments()
74
+
75
+ {
76
+ /* it should remove all comments */
77
+ /* even this
78
+ multi-line comment
79
+ */
80
+ // also this comment
81
+
82
+ "x": 1,
83
+ "hello": "ok"
84
+ }
85
+ `;
86
+ return Promise.all([
87
+ read(file),
88
+ write(fileJson, { x: 1, y: 2 }).then(() => read(fileJson)),
89
+ write(fileArray, [1, 2, 3]).then(() => read(fileArray)),
90
+ write(fileJsonComments, contentJsonComments).then(
91
+ () => read(fileJsonComments)
92
+ )
93
+ ]).then((value) => {
94
+ let [txt, json, array, jsonWithComments] = value;
95
+ expect(txt.length).toEqual(2);
96
+ expect(txt).toContain("ok");
97
+ expect(objectType(txt)).toEqual("string");
98
+ expect(objectType(json)).toEqual("object");
99
+ expect(objectType(jsonWithComments)).toEqual("object");
100
+ expect(objectType(array)).toEqual("array");
101
+ expect(json).toEqual({ x: 1, y: 2 });
102
+ expect(jsonWithComments).toEqual({ x: 1, hello: "ok" });
103
+ expect(array).toEqual([1, 2, 3]);
104
+ });
105
+ });
106
+ test("read from a non-existing file", () => expect(
107
+ read(`${dir}/non-existing.txt`, { maxAge: 24 * 60 * 60 })
108
+ ).rejects.toThrow("no such file or directory"));
109
+ test("read: maxAge", (done) => {
110
+ let file2 = dir + "/file.txt";
111
+ write(file2, "ok").then(() => read(file2, { maxAge: 24 * 60 * 60 })).then((content) => {
112
+ expect(content).toEqual("ok");
113
+ done();
114
+ }).catch((error) => done(error));
115
+ });
116
+ test("read from an expired cache", () => {
117
+ let file2 = dir + "/file.txt";
118
+ let date = /* @__PURE__ */ new Date(), today = date.getDate();
119
+ date.setDate(today - 1);
120
+ let yesterday = date.getTime() / 1e3;
121
+ return expect(
122
+ write(file2, "ok").then(() => utimes(file2, yesterday, yesterday)).then(() => read(file2, { maxAge: 1 }))
123
+ ).rejects.toThrow("expired file");
124
+ });
125
+ test("remove a dir", () => {
126
+ expect(existsSync(file)).toBeTruthy();
127
+ expect(existsSync(dir)).toBeTruthy();
128
+ return remove([dir]).then(() => {
129
+ expect(existsSync(file)).toBeFalsy();
130
+ expect(existsSync(dir)).toBeFalsy();
131
+ });
132
+ });
133
+ test("remove a non-exists path", () => {
134
+ let file2 = `${dir}/non-existing/file.txt`;
135
+ return remove(file2).then(() => expect(existsSync(file2)).toBeFalsy());
136
+ });
137
+ test("copy a directory and its sub-directories", () => {
138
+ return Promise.all([
139
+ write(`${dir}/copy-dir/file.txt`, ""),
140
+ write(`${dir}/copy-dir/sub-dir/file2.txt`, "")
141
+ ]).then(() => copy(`${dir}/copy-dir`, `${dir}/copy-dir2`)).then(() => {
142
+ expect(existsSync(`${dir}/copy-dir2/file.txt`)).toBeTruthy();
143
+ expect(existsSync(`${dir}/copy-dir2/sub-dir/file2.txt`)).toBeTruthy();
144
+ });
145
+ });
146
+ describe("getEntries", () => {
147
+ let entries = ["file.txt", "file.js"];
148
+ beforeEach(() => {
149
+ return remove(dir).then(
150
+ () => Promise.all(
151
+ entries.map((element) => {
152
+ return write(`${dir}/${element}`, "").then(
153
+ () => write(`${dir}/subdir/${element}`, "")
154
+ );
155
+ })
156
+ )
157
+ );
158
+ });
159
+ test("list all entries recursively", () => {
160
+ return getEntries(dir).then((result) => {
161
+ expect(result.sort()).toEqual(
162
+ // all files in dir with full path
163
+ [
164
+ ...entries.map((element) => resolve(`${dir}/${element}`)).concat(
165
+ entries.map((element) => resolve(`${dir}/subdir/${element}`))
166
+ ),
167
+ resolve(`${dir}/subdir`)
168
+ ].sort()
169
+ );
170
+ });
171
+ });
172
+ test("filter by function", () => {
173
+ return getEntries(dir, (element) => element.includes(".js")).then(
174
+ (result) => {
175
+ expect(result).toEqual([
176
+ resolve(`${dir}/file.js`),
177
+ resolve(`${dir}/subdir/file.js`)
178
+ ]);
179
+ }
180
+ );
181
+ });
182
+ test("filter by regex", () => {
183
+ return getEntries(dir, /subdir/).then((result) => {
184
+ expect(result.sort()).toEqual(
185
+ [
186
+ ...entries.map((element) => resolve(`${dir}/subdir/${element}`)),
187
+ resolve(`${dir}/subdir`)
188
+ ].sort()
189
+ );
190
+ });
191
+ });
192
+ test("filter by type: files", () => {
193
+ return getEntries(dir, "files").then((result) => {
194
+ expect(result.sort()).toEqual(
195
+ entries.map((element) => resolve(`${dir}/${element}`)).concat(entries.map((element) => resolve(`${dir}/subdir/${element}`))).sort()
196
+ );
197
+ });
198
+ });
199
+ test("filter by type: dirs", () => getEntries(dir, "dirs").then((result) => {
200
+ expect(result).toEqual([resolve(`${dir}/subdir`)]);
201
+ }));
202
+ test("depth=0", async () => {
203
+ for (let element of entries) {
204
+ await write(`${dir}/subdir/extra/${element}`, "");
205
+ }
206
+ return getEntries(dir, void 0, 0).then((result) => {
207
+ expect(result.sort()).toEqual(
208
+ [
209
+ ...entries.map((element) => resolve(`${dir}/${element}`)),
210
+ resolve(`${dir}/subdir`)
211
+ ].sort()
212
+ );
213
+ });
214
+ });
215
+ test("depth=1", async () => {
216
+ for (let element of entries) {
217
+ await write(`${dir}/subdir/extra/${element}`, "");
218
+ }
219
+ return getEntries(dir, void 0, 1).then((result) => {
220
+ expect(result.sort()).toEqual(
221
+ [
222
+ ...entries.map((element) => resolve(`${dir}/${element}`)),
223
+ resolve(`${dir}/subdir`),
224
+ resolve(`${dir}/subdir/extra`)
225
+ ].concat(entries.map((element) => resolve(`${dir}/subdir/${element}`))).sort()
226
+ );
227
+ });
228
+ });
229
+ test("depth=2", async () => {
230
+ for (let element of entries) {
231
+ await write(`${dir}/subdir/extra/${element}`, "");
232
+ }
233
+ return getEntries(dir, void 0, 2).then((result) => {
234
+ expect(result.sort()).toEqual(
235
+ [
236
+ ...entries.map((element) => resolve(`${dir}/${element}`)),
237
+ resolve(`${dir}/subdir`),
238
+ resolve(`${dir}/subdir/extra`)
239
+ ].concat(entries.map((element) => resolve(`${dir}/subdir/${element}`))).concat(
240
+ entries.map((element) => resolve(`${dir}/subdir/extra/${element}`))
241
+ ).sort()
242
+ );
243
+ });
244
+ });
245
+ test("non existing dir", () => {
246
+ expect.hasAssertions();
247
+ return expect(getEntries(dir + "/non-existing")).rejects.toThrow(
248
+ "no such file or directory"
249
+ );
250
+ });
251
+ });
package/src/https.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { type RequestOptions } from 'node:https';
2
+ import { URL } from 'node:url';
3
+ export default function request(url: string | URL, data?: any, options?: RequestOptions): Promise<any>;
package/src/https.js ADDED
@@ -0,0 +1,52 @@
1
+ import https from "node:https";
2
+ import { parse } from "content-type";
3
+ import { objectType } from "@impactor/javascript";
4
+ function request(url, data, options) {
5
+ return new Promise((resolve, reject) => {
6
+ let isObject = ["array", "object"].includes(objectType(data));
7
+ let requestOptions = Object.assign(
8
+ { method: data ? "POST" : "GET" },
9
+ options || {}
10
+ );
11
+ if (isObject) {
12
+ requestOptions.headers = {
13
+ "content-type": "application/json",
14
+ ...requestOptions.headers
15
+ };
16
+ }
17
+ let responseChunks = [];
18
+ let request_ = https.request(url, requestOptions, (res) => {
19
+ res.setEncoding("utf8");
20
+ res.on("data", function(chunk) {
21
+ responseChunks.push(chunk.toString());
22
+ });
23
+ res.on("end", () => {
24
+ try {
25
+ let response = responseChunks.join("");
26
+ let type = parse(res).type;
27
+ if (type === "application/json") {
28
+ response = JSON.parse(response);
29
+ }
30
+ if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
31
+ reject(
32
+ ["array", "object"].includes(objectType(response)) ? { code: res.statusCode, ...response } : { code: res.statusCode, message: response }
33
+ );
34
+ } else {
35
+ resolve(response);
36
+ }
37
+ } catch (error) {
38
+ reject(error);
39
+ }
40
+ });
41
+ });
42
+ request_.on("error", (error) => reject(error));
43
+ if (requestOptions.method.toUpperCase() === "POST") {
44
+ let dataString = isObject ? JSON.stringify(data) : data;
45
+ request_.write(dataString);
46
+ }
47
+ request_.end();
48
+ });
49
+ }
50
+ export {
51
+ request as default
52
+ };
@@ -0,0 +1,19 @@
1
+ import { expect, test } from "@jest/globals";
2
+ import request from "./https";
3
+ test("request", (done) => {
4
+ request(`https://jsonplaceholder.typicode.com/posts/1`).then((result) => {
5
+ expect(result.userId).toEqual(1);
6
+ done();
7
+ }).catch((error) => done(error));
8
+ });
9
+ test("request: POST", (done) => {
10
+ request(`https://jsonplaceholder.typicode.com/posts`, {
11
+ title: "test post request"
12
+ }).then((result) => {
13
+ expect(result.title).toEqual("test post request");
14
+ done();
15
+ }).catch((error) => done(error));
16
+ });
17
+ test("request: catching errors", () => expect(
18
+ request(`https://jsonplaceholder.typicode.com/wrong-endpoint`)
19
+ ).rejects.toEqual({ code: 404 }));
@@ -0,0 +1,13 @@
1
+ import { type ExecOptions, type ExecSyncOptions, type SpawnOptionsWithoutStdio } from 'node:child_process';
2
+ import { type Obj } from '@impactor/javascript';
3
+ export interface Argv {
4
+ cmd: Array<string>;
5
+ options: Obj;
6
+ external: string;
7
+ }
8
+ export declare function parseArgv(arguments_?: string | Array<string>): Argv;
9
+ export declare function toArgv(argumentsObject: Argv): string;
10
+ export declare function execSync(cmd: string, options?: ExecSyncOptions): Buffer | string;
11
+ export declare function exec(cmd: string, options?: ExecOptions): Promise<string>;
12
+ export declare function spawn(cmd: string, options?: SpawnOptionsWithoutStdio): Promise<void>;
13
+ export declare function runTask(tasks: Obj, arguments_?: string | Array<string>): Promise<void>;
package/src/process.js ADDED
@@ -0,0 +1,203 @@
1
+ import { argv } from "node:process";
2
+ import {
3
+ exec as _exec,
4
+ execSync as _execSync,
5
+ spawn as _spawn
6
+ } from "node:child_process";
7
+ import {
8
+ chunk,
9
+ dotNotationToObject,
10
+ flatten,
11
+ objectType,
12
+ toNumber
13
+ } from "@impactor/javascript";
14
+ function parseArgv(arguments_) {
15
+ if (!arguments_) {
16
+ arguments_ = argv.slice(2);
17
+ } else if (typeof arguments_ === "string") {
18
+ arguments_ = arguments_.split(" ");
19
+ }
20
+ let argumentsObject = { cmd: [], options: {}, external: "" };
21
+ let setOption = (key, value) => {
22
+ if (typeof value === "string") {
23
+ if (value === "true") {
24
+ value = true;
25
+ } else if (value === "false") {
26
+ value = false;
27
+ } else {
28
+ value = toNumber(value);
29
+ }
30
+ }
31
+ if (argumentsObject.options[key]) {
32
+ if (!Array.isArray(argumentsObject.options[key])) {
33
+ argumentsObject.options[key] = [argumentsObject.options[key]];
34
+ }
35
+ value = argumentsObject.options[key].concat(value);
36
+ }
37
+ if (key.includes(".")) {
38
+ let keys = key.split(".");
39
+ key = keys.shift();
40
+ argumentsObject.options[key] = dotNotationToObject(keys, value);
41
+ } else {
42
+ argumentsObject.options[key] = value;
43
+ }
44
+ };
45
+ if (arguments_.includes("--")) {
46
+ let chunks = chunk(arguments_, arguments_.indexOf("--") || 0);
47
+ argumentsObject.external = chunks[1]?.slice(1)?.join(" ");
48
+ arguments_ = chunks[0];
49
+ }
50
+ for (let index = 0; index < arguments_.length; index++) {
51
+ let argument = arguments_[index].trim();
52
+ if (argument === "") {
53
+ continue;
54
+ }
55
+ if (/^--no-.+/.test(argument)) {
56
+ let match = argument.match(/^--no-(.+)/), key = match[1];
57
+ setOption(key, false);
58
+ } else if (/^--.+=/.test(argument)) {
59
+ let match = argument.match(/^--([^=]+)=(.*)$/s), key = match[1], value = match[2];
60
+ setOption(key, value);
61
+ } else if (/^--.+/.test(argument)) {
62
+ let match = argument.match(/^--(.+)/), key = match[1], next = arguments_[index + 1];
63
+ if (next !== void 0 && !next.startsWith("-")) {
64
+ setOption(key, next);
65
+ index++;
66
+ } else {
67
+ setOption(key, true);
68
+ }
69
+ } else if (/^-[^-]+/.test(argument)) {
70
+ let letters = argument.slice(1, -1).split("");
71
+ let broken = false;
72
+ for (let index_ = 0; index_ < letters.length; index_++) {
73
+ let next2 = argument.slice(index_ + 2);
74
+ if (next2 === "-") {
75
+ setOption(letters[index_], "-");
76
+ continue;
77
+ }
78
+ if (/[A-Za-z]/.test(letters[index_]) && /=/.test(next2)) {
79
+ setOption(letters[index_], next2.split("=")[1]);
80
+ broken = true;
81
+ break;
82
+ } else if (/[A-Za-z]/.test(letters[index_]) && /-?\d+(?:\.\d*)?(?:e-?\d+)?$/.test(next2)) {
83
+ setOption(letters[index_], next2);
84
+ broken = true;
85
+ break;
86
+ } else if (letters[index_ + 1] && /\W/.test(letters[index_ + 1])) {
87
+ setOption(letters[index_], argument.slice(index_ + 2));
88
+ broken = true;
89
+ break;
90
+ } else {
91
+ setOption(letters[index_], true);
92
+ }
93
+ }
94
+ let key = argument.at(-1), next = arguments_[index + 1];
95
+ if (!broken && key !== "-") {
96
+ if (next && !/^(?:-|--)[^-]/.test(next)) {
97
+ setOption(key, next);
98
+ index++;
99
+ } else {
100
+ setOption(key, true);
101
+ }
102
+ }
103
+ } else {
104
+ argumentsObject.cmd.push(argument);
105
+ }
106
+ }
107
+ return argumentsObject;
108
+ }
109
+ function toArgv(argumentsObject) {
110
+ let argv2 = "";
111
+ for (let element of argumentsObject.cmd) {
112
+ argv2 += element + " ";
113
+ }
114
+ let setOption = function(key, value) {
115
+ let argv3 = "";
116
+ if (Array.isArray(value)) {
117
+ for (let element of value) {
118
+ argv3 += `--${key}=${element} `;
119
+ }
120
+ } else {
121
+ argv3 += `--${key}=${value} `;
122
+ }
123
+ return argv3;
124
+ };
125
+ for (let key in argumentsObject.options) {
126
+ if (argumentsObject.options.hasOwnProperty(key)) {
127
+ let value = argumentsObject.options[key];
128
+ if (objectType(value) === "object") {
129
+ for (let [k, v] of Object.entries(flatten({ [key]: value }))) {
130
+ argv2 += setOption(k, v);
131
+ }
132
+ } else {
133
+ argv2 += setOption(key, value);
134
+ }
135
+ }
136
+ }
137
+ if (argumentsObject.external) {
138
+ let external = argumentsObject.external.trim();
139
+ if (external !== "") {
140
+ argv2 += `-- ${argumentsObject.external}`;
141
+ }
142
+ }
143
+ return argv2;
144
+ }
145
+ function execSync(cmd, options) {
146
+ let opts = { stdio: "inherit", ...options };
147
+ return _execSync(cmd, opts);
148
+ }
149
+ function exec(cmd, options) {
150
+ return new Promise((res, rej) => {
151
+ _exec(cmd, options, (error, stdout, stderr) => {
152
+ if (error) {
153
+ error.message = error?.message || stderr || stdout;
154
+ rej(error);
155
+ } else {
156
+ res(stdout);
157
+ }
158
+ });
159
+ });
160
+ }
161
+ function spawn(cmd, options) {
162
+ return new Promise((res, rej) => {
163
+ let childProcess = _spawn(cmd, options);
164
+ childProcess.stdout.on("data", (data) => console.log(data.toString()));
165
+ childProcess.stderr.on("data", (err) => console.error(err.toString()));
166
+ childProcess.on("exit", (exitCode) => {
167
+ if (exitCode) rej();
168
+ else res();
169
+ });
170
+ });
171
+ }
172
+ async function runTask(tasks, arguments_) {
173
+ let parsedArguments = parseArgv(arguments_), task = parsedArguments.cmd.shift();
174
+ if (!task) {
175
+ throw new Error("task not provided!");
176
+ } else if (!(task in tasks)) {
177
+ throw new Error(`unknown task ${task}`);
178
+ }
179
+ try {
180
+ let optionsString = "";
181
+ for (let key in parsedArguments.options) {
182
+ optionsString += `--${key}=${parsedArguments.options[key]} `;
183
+ }
184
+ console.log(
185
+ `>> running the task: ${task} ${parsedArguments.cmd.join(
186
+ " "
187
+ )} ${optionsString}`
188
+ );
189
+ await tasks[task](...parsedArguments.cmd, parsedArguments.options);
190
+ console.log(">> Done");
191
+ } catch (error) {
192
+ error.task = task;
193
+ throw error;
194
+ }
195
+ }
196
+ export {
197
+ exec,
198
+ execSync,
199
+ parseArgv,
200
+ runTask,
201
+ spawn,
202
+ toArgv
203
+ };
@@ -0,0 +1,49 @@
1
+ import { expect, test } from "@jest/globals";
2
+ import { parseArgv, toArgv } from "./process";
3
+ let input = "cmd1 cmd2 - --a 1 --b=2 --c true --d false --e --no-f -g -h hh -ijk ok -l- --num=1 --arr=1 --arr=2 --obj.a.b=1 -mn-o -p - -q123 --whitespaces=a\nb c --no-exit -- ext1 ext2";
4
+ let parsed = {
5
+ cmd: ["cmd1", "cmd2", "-"],
6
+ options: {
7
+ a: 1,
8
+ b: 2,
9
+ c: true,
10
+ d: false,
11
+ e: true,
12
+ f: false,
13
+ g: true,
14
+ h: "hh",
15
+ i: true,
16
+ j: true,
17
+ k: "ok",
18
+ l: "-",
19
+ num: 1,
20
+ arr: [1, 2],
21
+ obj: { a: { b: 1 } },
22
+ m: true,
23
+ n: "-o",
24
+ p: "-",
25
+ q: 123,
26
+ whitespaces: "a\nb c",
27
+ exit: false
28
+ },
29
+ external: "ext1 ext2"
30
+ };
31
+ test("parseArgv()", () => {
32
+ expect(parseArgv(input)).toEqual(parsed);
33
+ });
34
+ test("parseArgv(): contains extra spaces", () => {
35
+ expect(parseArgv("cmd --a=1 --b=2")).toEqual({
36
+ cmd: ["cmd"],
37
+ options: { a: 1, b: 2 },
38
+ external: ""
39
+ });
40
+ });
41
+ test("toArgv()", () => {
42
+ let parsedArguments = {
43
+ cmd: ["cmd1", "cmd2"],
44
+ options: { a: "x", b: 1, c: true, d: false, e: { f: 1 }, g: [1, 2] },
45
+ external: "--external params"
46
+ };
47
+ let arguments_ = `cmd1 cmd2 --a=x --b=1 --c=true --d=false --e.f=1 --g=1 --g=2 -- --external params`;
48
+ expect(toArgv(parsedArguments)).toEqual(arguments_);
49
+ });