~repos /website

#astro#js#html#css

git clone https://pyrossh.dev/repos/website.git

木 Personal website of pyrossh. Built with astrojs, shiki, vite.


ba093c22 pyrossh

3 weeks ago
update alchemy code
Files changed (4) hide show
  1. alchemy.run.ts +72 -69
  2. bun.lock +3 -0
  3. package.json +2 -1
  4. server.js +0 -128
alchemy.run.ts CHANGED
@@ -14,64 +14,60 @@
14
14
  * 1. Create the certificate manually in ACM (us-east-1 region)
15
15
  * 2. Uncomment and provide the certificate ARN in the ViewerCertificate config
16
16
  */
17
-
17
+ import { $ } from "bun";
18
18
  import alchemy from "alchemy";
19
19
  import AWS from "alchemy/aws/control";
20
20
  import { AccountId } from "alchemy/aws";
21
21
 
22
22
  const domainName = "pyrossh.dev";
23
- const app = await alchemy("website");
23
+ const app = await alchemy("");
24
+
25
+ const Tags = [
26
+ { Key: "Stage", Value: app.stage },
27
+ { Key: "ProvisionedBy", Value: "Alchemy" }
28
+ ]
29
+ const Comment = `ProvisionedBy Alchemy - ${app.stage}`;
24
30
 
25
31
  // Route53 Zone
26
- const hostedZone = await AWS.Route53.HostedZone("main", {
32
+ const hostedZone = await AWS.Route53.HostedZone(`hosted-zone-${app.stage}`, {
27
33
  Name: domainName,
28
- HostedZoneTags: [
34
+ HostedZoneTags: Tags,
29
- { Key: "Environment", Value: "Production" },
30
- { Key: "ProvisionedBy", Value: "Alchemy" }
31
- ],
32
35
  HostedZoneConfig: {
33
- Comment: "ProvisionedBy Alchemy"
36
+ Comment: Comment,
34
37
  },
35
- adopt: true,
36
38
  });
37
39
 
38
40
  // S3 Buckets with Public Access Block Configuration
39
- const websiteBucket = await AWS.S3.Bucket("website-bucket", {
41
+ const websiteBucket = await AWS.S3.Bucket(`website-bucket-${app.stage}`, {
40
- BucketName: "pyrossh-website",
42
+ BucketName: `pyrossh-website-${app.stage}`,
41
43
  PublicAccessBlockConfiguration: {
42
44
  BlockPublicAcls: true,
43
45
  BlockPublicPolicy: true,
44
46
  IgnorePublicAcls: true,
45
47
  RestrictPublicBuckets: true
46
48
  },
47
- Tags: [
49
+ Tags: Tags,
48
- { Key: "Environment", Value: "Production" },
49
- { Key: "ProvisionedBy", Value: "Alchemy" }
50
- ]
51
50
  });
52
51
 
53
- const reposBucket = await AWS.S3.Bucket("repos-bucket", {
52
+ const reposBucket = await AWS.S3.Bucket(`repos-bucket-${app.stage}`, {
54
- BucketName: "pyrossh-repos",
53
+ BucketName: `pyrossh-repos-${app.stage}`,
55
54
  PublicAccessBlockConfiguration: {
56
55
  BlockPublicAcls: true,
57
56
  BlockPublicPolicy: true,
58
57
  IgnorePublicAcls: true,
59
58
  RestrictPublicBuckets: true
60
59
  },
61
- Tags: [
60
+ Tags: Tags,
62
- { Key: "Environment", Value: "Production" },
63
- { Key: "ProvisionedBy", Value: "Alchemy" }
64
- ]
65
61
  });
66
62
 
67
63
  // CloudFront Origin Access Control
68
- const s3Oac = await AWS.CloudFront.OriginAccessControl("s3-oac", {
64
+ const s3OAC = await AWS.CloudFront.OriginAccessControl(`s3-oac-${app.stage}`, {
69
65
  OriginAccessControlConfig: {
70
- Name: "s3-oac",
66
+ Name: `s3-oac-${app.stage}`,
71
67
  OriginAccessControlOriginType: "s3",
72
68
  SigningBehavior: "always",
73
- SigningProtocol: "sigv4"
69
+ SigningProtocol: "sigv4",
74
- }
70
+ },
75
71
  });
76
72
 
77
73
  // ACM Certificate (in us-east-1 for CloudFront)
@@ -80,47 +76,50 @@ const s3Oac = await AWS.CloudFront.OriginAccessControl("s3-oac", {
80
76
  // You may need to adjust this based on your Alchemy setup
81
77
 
82
78
  // CloudFront Functions
83
- const htmlRedirector = await AWS.CloudFront.Function("html-redirector", {
79
+ const htmlRedirector = await AWS.CloudFront.Function(`html-redirector-${app.stage}`, {
84
- FunctionCode: `function handler(event) {
85
- const request = event.request;
80
+ Name: `html_redirector_${app.stage}`,
86
- const uri = request.uri;
87
-
88
- if (uri.endsWith('/')) {
89
- request.uri += 'index.html';
90
- } else if (!uri.startsWith('/_astro')) {
91
- request.uri += '/index.html';
92
- }
93
- return request;
94
- }`,
95
81
  FunctionConfig: {
96
82
  Comment: "HTML redirector function",
97
83
  Runtime: "cloudfront-js-2.0"
98
84
  },
85
+ FunctionCode: `function handler(event) {
99
- Name: "html_redirector"
86
+ const request = event.request;
87
+ const uri = request.uri;
88
+
89
+ if (uri.endsWith('/')) {
90
+ request.uri += 'index.html';
91
+ } else if (!uri.startsWith('/_astro')) {
92
+ request.uri += '/index.html';
93
+ }
94
+ return request;
95
+ }`,
96
+ AutoPublish: true,
100
97
  });
101
98
 
102
- const gitRedirector = await AWS.CloudFront.Function("git-redirector", {
99
+ const gitRedirector = await AWS.CloudFront.Function(`git-redirector-${app.stage}`, {
103
- FunctionCode: `function handler(event) {
104
- const request = event.request;
100
+ Name: `git_redirector_${app.stage}`,
105
- const uri = request.uri;
106
- request.uri = "/" + request.uri.replace("/repos/", "");
107
- return request;
108
- }`,
109
101
  FunctionConfig: {
110
102
  Comment: "Git redirector function",
111
103
  Runtime: "cloudfront-js-2.0"
112
104
  },
105
+ FunctionCode: `function handler(event) {
113
- Name: "git_redirector"
106
+ const request = event.request;
107
+ const uri = request.uri;
108
+ request.uri = "/" + request.uri.replace("/repos/", "");
109
+ return request;
110
+ }`,
111
+ AutoPublish: true,
114
112
  });
115
113
 
116
114
  // CloudFront Distribution
117
- const s3Distribution = await AWS.CloudFront.Distribution("s3-distribution", {
115
+ const s3Distribution = await AWS.CloudFront.Distribution(`s3-distribution-${app.stage}`, {
116
+ Tags: Tags,
118
117
  DistributionConfig: {
119
118
  Origins: [
120
119
  {
121
120
  DomainName: websiteBucket.RegionalDomainName,
122
121
  Id: websiteBucket.RegionalDomainName,
123
- OriginAccessControlId: s3Oac.Id,
122
+ OriginAccessControlId: s3OAC.Id,
124
123
  S3OriginConfig: {
125
124
  OriginAccessIdentity: ""
126
125
  }
@@ -128,7 +127,7 @@ const s3Distribution = await AWS.CloudFront.Distribution("s3-distribution", {
128
127
  {
129
128
  DomainName: reposBucket.RegionalDomainName,
130
129
  Id: reposBucket.RegionalDomainName,
131
- OriginAccessControlId: s3Oac.Id,
130
+ OriginAccessControlId: s3OAC.Id,
132
131
  S3OriginConfig: {
133
132
  OriginAccessIdentity: ""
134
133
  }
@@ -136,7 +135,7 @@ const s3Distribution = await AWS.CloudFront.Distribution("s3-distribution", {
136
135
  ],
137
136
  Enabled: true,
138
137
  IPV6Enabled: true,
139
- Aliases: [domainName],
138
+ // Aliases: [domainName],
140
139
  DefaultRootObject: "index.html",
141
140
  CustomErrorResponses: [400, 403, 404, 405, 414, 416, 500, 501, 502, 503, 504].map(code => ({
142
141
  ErrorCode: code,
@@ -212,18 +211,18 @@ const s3Distribution = await AWS.CloudFront.Distribution("s3-distribution", {
212
211
  }
213
212
  },
214
213
  ViewerCertificate: {
215
- CloudFrontDefaultCertificate: false,
214
+ CloudFrontDefaultCertificate: true,
215
+ // AcmCertificateArn: "arn:aws:acm:us-east-1:122129753516:certificate/d04cd0a5-623c-4d18-a9d1-f0d8df3e999d",
216
216
  // AcmCertificateArn: domainSslCertificate.CertificateArn,
217
- // Uncomment above and create certificate separately or provide ARN
218
- SslSupportMethod: "sni-only"
217
+ // SslSupportMethod: "sni-only"
219
218
  },
220
- Comment: "Pyrossh Website Distribution"
219
+ Comment: Comment,
221
220
  }
222
221
  });
223
222
 
224
223
  // S3 Bucket Policies
225
- const websiteBucketPolicy = await AWS.S3.BucketPolicy("website-bucket-policy", {
224
+ const websiteBucketPolicy = await AWS.S3.BucketPolicy(`website-bucket-policy-${app.stage}`, {
226
- Bucket: websiteBucket.BucketName || "pyrossh-website",
225
+ Bucket: websiteBucket.BucketName || `pyrossh-website-${app.stage}`,
227
226
  PolicyDocument: {
228
227
  Version: "2012-10-17",
229
228
  Statement: [{
@@ -245,8 +244,8 @@ const websiteBucketPolicy = await AWS.S3.BucketPolicy("website-bucket-policy", {
245
244
  }
246
245
  });
247
246
 
248
- const reposBucketPolicy = await AWS.S3.BucketPolicy("repos-bucket-policy", {
247
+ const reposBucketPolicy = await AWS.S3.BucketPolicy(`repos-bucket-policy-${app.stage}`, {
249
- Bucket: reposBucket.BucketName || "pyrossh-repos",
248
+ Bucket: reposBucket.BucketName!,
250
249
  PolicyDocument: {
251
250
  Version: "2012-10-17",
252
251
  Statement: [{
@@ -269,15 +268,19 @@ const reposBucketPolicy = await AWS.S3.BucketPolicy("repos-bucket-policy", {
269
268
  });
270
269
 
271
270
  // Route53 A Record
272
- const aRecordDomain = await AWS.Route53.RecordSet("a-record-domain", {
271
+ // const aRecordDomain = await AWS.Route53.RecordSet("a-record-domain", {
273
- HostedZoneId: hostedZone.Id,
272
+ // HostedZoneId: hostedZone.Id,
274
- Name: domainName,
273
+ // Name: domainName,
275
- Type: "A",
274
+ // Type: "A",
276
- AliasTarget: {
275
+ // AliasTarget: {
277
- DNSName: s3Distribution.DomainName,
276
+ // DNSName: s3Distribution.DomainName,
278
- HostedZoneId: s3Distribution.DomainName, // CloudFront hosted zone ID
277
+ // HostedZoneId: s3Distribution.DomainName, // CloudFront hosted zone ID
279
- EvaluateTargetHealth: false
278
+ // EvaluateTargetHealth: false
280
- }
279
+ // }
281
- });
280
+ // });
282
281
 
283
- await app.finalize();
282
+ await app.finalize();
283
+ await $`rm -rf dist`
284
+ await $`bun run build`
285
+ await $`aws s3 sync --delete ./dist/ s3://${websiteBucket.BucketName}`
286
+ await $`aws cloudfront create-invalidation --distribution-id ${s3Distribution.Id} --paths "/*" --no-cli-pager`
bun.lock CHANGED
@@ -19,6 +19,7 @@
19
19
  "timeago.js": "^4.0.2",
20
20
  },
21
21
  "devDependencies": {
22
+ "@aws-sdk/client-sts": "^3.932.0",
22
23
  "@iconify-json/material-symbols": "^1.2.39",
23
24
  "@iconify-json/mdi": "^1.2.3",
24
25
  "@playwright/test": "^1.52.0",
@@ -69,6 +70,8 @@
69
70
 
70
71
  "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.932.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.930.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-XHqHa5iv2OQsKoM2tUQXs7EAyryploC00Wg0XSFra/KAKqyGizUb5XxXsGlyqhebB29Wqur+zwiRwNmejmN0+Q=="],
71
72
 
73
+ "@aws-sdk/client-sts": ["@aws-sdk/client-sts@3.932.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.932.0", "@aws-sdk/credential-provider-node": "3.932.0", "@aws-sdk/middleware-host-header": "3.930.0", "@aws-sdk/middleware-logger": "3.930.0", "@aws-sdk/middleware-recursion-detection": "3.930.0", "@aws-sdk/middleware-user-agent": "3.932.0", "@aws-sdk/region-config-resolver": "3.930.0", "@aws-sdk/types": "3.930.0", "@aws-sdk/util-endpoints": "3.930.0", "@aws-sdk/util-user-agent-browser": "3.930.0", "@aws-sdk/util-user-agent-node": "3.932.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.2", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.9", "@smithy/middleware-retry": "^4.4.9", "@smithy/middleware-serde": "^4.2.5", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.8", "@smithy/util-defaults-mode-node": "^4.2.11", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-nH9ZKV7IN7uVU2HS67cknjTcwBRXgwKRCkhmOF/pn4r4aReD7LhUGCua7svttjGV9rkdlYUPEVW++0gk4FkoSg=="],
74
+
72
75
  "@aws-sdk/core": ["@aws-sdk/core@3.932.0", "", { "dependencies": { "@aws-sdk/types": "3.930.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.2", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.5", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-AS8gypYQCbNojwgjvZGkJocC2CoEICDx9ZJ15ILsv+MlcCVLtUJSRSx3VzJOUY2EEIaGLRrPNlIqyn/9/fySvA=="],
73
76
 
74
77
  "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.932.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.932.0", "@aws-sdk/types": "3.930.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-PWHX32VVYYy/Jbk8TdIwK/ydcC72xjN2OMFIhiOFcpCDF8Cq6yBb0DC90g1dENlJE57VTAs4bwpN290lSfZ1zw=="],
package.json CHANGED
@@ -8,6 +8,7 @@
8
8
  "dev": "astro dev",
9
9
  "build": "astro build",
10
10
  "build:tauri": "astro build",
11
+ "infra:deploy": "alchemy deploy --stage prd",
11
12
  "dev:android": "tauri android dev -v",
12
13
  "build:android": "tauri android build --aab",
13
14
  "build:android:sign": "bundletool build-apks --bundle=src-tauri/gen/android/app/build/outputs/bundle/universalRelease/app-universal-release.aab --output=./app-universal-release.apks --mode=universal --ks=../config/pyrossh_keystore.jks --ks-key-alias=pyrossh --ks-pass=pass:Test@123 --key-pass=pass:Test@123",
@@ -23,13 +24,13 @@
23
24
  "astro-color-scheme": "^1.1.5",
24
25
  "astro-expressive-code": "^0.41.3",
25
26
  "astro-icon": "^1.1.5",
26
-
27
27
  "expressive-code-color-chips": "^0.1.2",
28
28
  "pretty-bytes": "^7.1.0",
29
29
  "simple-git": "^3.27.0",
30
30
  "timeago.js": "^4.0.2"
31
31
  },
32
32
  "devDependencies": {
33
+ "@aws-sdk/client-sts": "^3.932.0",
33
34
  "client-s3": "aws-sdk/client-s3",
34
35
  "@iconify-json/mdi": "^1.2.3",
35
36
  "@iconify-json/material-symbols": "^1.2.39",
server.js DELETED
@@ -1,128 +0,0 @@
1
- import { Parser } from 'node-sql-parser';
2
- import { PutObjectCommand, S3Client, GetObjectCommand, paginateListObjectsV2 } from "@aws-sdk/client-s3";
3
-
4
- const bucketName = "";
5
-
6
- // import { S3Client } from "bun";
7
- // const client = new S3Client({
8
- // region: "ap-south-1",
9
- // bucket: "",
10
- // accessKeyId: "",
11
- // secretAccessKey: "",
12
- // endpoint: "https://s3express-aps1-az1.ap-south-1.amazonaws.com",
13
- // });
14
- // const s3file = client.file("123.json");
15
- // await s3file.write(JSON.stringify({ hello: "world" }));
16
-
17
- // const data = await s3file.json()
18
- // console.log(data);
19
-
20
- // const client = new S3Client({});
21
- // const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
22
- // for (const i of arr) {
23
- // await client.send(new PutObjectCommand({
24
- // Bucket: bucketName,
25
- // Key: `posts/${i}.json`,
26
- // ContentType: "application/json",
27
- // Body: JSON.stringify({ id: `${i}`, data: `hello '${i}' world` }),
28
- // }));
29
- // }
30
-
31
- // If-None-Match: * if it doesn't exist 409 - ConditionalRequestConflict 412 Precondition Failed
32
- // If-Match: etag checks if etag is same CAS 409 - ConditionalRequestConflict 412 Precondition Failed
33
-
34
- const getObjectName = (name) => name.split("::")[2];
35
-
36
- const client = new S3Client({});
37
-
38
- const getAllS3Files = async (bucketName, tableName) => {
39
- const totalFiles = [];
40
- for await (const data of paginateListObjectsV2({ client }, { Bucket: bucketName, Prefix: `${tableName}/` })) {
41
- totalFiles.push(...(data.Contents ?? []));
42
- }
43
- return totalFiles;
44
- };
45
-
46
- const sql = async (query) => {
47
- const parser = new Parser();
48
- const { tableList, columnList, ast } = parser.parse(query, {
49
- database: "Hive",
50
- // type: 'table',
51
- parseOptions: {
52
- includeLocations: true
53
- }
54
- });
55
- // console.log(ast);
56
- // console.log(JSON.stringify(ast, 2, 2));
57
- // console.log("tableName", tableName);
58
- const tableName = getObjectName(tableList[0]);
59
- // if (ast.type === "select") {
60
- // }
61
- const contents = await getAllS3Files(bucketName, tableName)
62
- return await Promise.all(contents.map((r) => {
63
- return client.send(new GetObjectCommand({ Bucket: bucketName, Key: r.Key, }))
64
- .then((rr) => rr.Body.transformToString())
65
- .then(JSON.parse)
66
- }));
67
- }
68
-
69
- const rows = await sql('SELECT * FROM posts LIMIT 10');
70
- // const rows = await sql("INSERT INTO posts(id, data) VALUES ('11', 'body');");
71
- console.log(rows);
72
-
73
- // const rows = await sql(`
74
- // CREATE TABLE struct_test(
75
- // property_id INT
76
- // )
77
- // `);
78
-
79
- // console.log(rows);
80
- // const resp = await parseQuery("SELECT * FROM POSTS");
81
- // resp.stmts[0].stmt.SelectStmt.fromClause
82
- // console.log("stmts", stmts);
83
-
84
- // Data Definition Language (DDL): CREATE, ALTER, DROP
85
- // Data Manipulation Language (DML): INSERT, UPDATE, DELETE, MERGE
86
- // Data Query Language (DQL): SELECT
87
- // Data Control Language (DCL): GRANT, REVOKE
88
-
89
- // TABLES/VIEWS/INDEX
90
-
91
-
92
-
93
- //
94
- // import { gluesql } from 'gluesql';
95
-
96
- // const db = gluesql();
97
-
98
- // async function run() {
99
- // await db.query(`
100
- // CREATE TABLE User (id INTEGER, name TEXT);
101
- // CREATE TABLE Device (name TEXT, userId INTEGER);
102
- // INSERT INTO User VALUES
103
- // (1, 'glue'), (2, 'sticky'), (3, 'watt');
104
- // INSERT INTO Device VALUES
105
- // ('Phone', 1), ('Mic', 1), ('Monitor', 3),
106
- // ('Mouse', 2), ('Touchpad', 2);
107
- // `);
108
-
109
- // let sql;
110
-
111
- // sql = 'SHOW TABLES;';
112
- // const [{ tables }] = await db.query(sql);
113
- // console.log(`\n[Query]\n${sql}`);
114
- // console.table(tables);
115
-
116
- // sql = `
117
- // SELECT
118
- // u.name as user,
119
- // d.name as device
120
- // FROM User u
121
- // JOIN Device d ON u.id = d.userId
122
- // `.trim().replace(/[ ]{4}/g, '');
123
- // const [{ rows }] = await db.query(sql);
124
- // console.log(`\n[Query]\n${sql}`);
125
- // console.table(rows);
126
- // }
127
-
128
- // run();