← back to Rentv V1
rentv-v1: public live-data CRE-news site + 20-min news engine
b143d4902df995cada5b087fd0b39d88b81db2df · 2026-07-17 11:19:51 -0700 · Steve
Files touched
A .deploy.confA .gitignoreA data/news.jsonA data/pull.errA package-lock.jsonA package.jsonA public/index.htmlA scripts/pull-news.mjsA server.js
Diff
commit b143d4902df995cada5b087fd0b39d88b81db2df
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Jul 17 11:19:51 2026 -0700
rentv-v1: public live-data CRE-news site + 20-min news engine
---
.deploy.conf | 6 +
.gitignore | 4 +
data/news.json | 187 ++++++++++++
data/pull.err | 0
package-lock.json | 828 ++++++++++++++++++++++++++++++++++++++++++++++++++
package.json | 3 +
public/index.html | 146 +++++++++
scripts/pull-news.mjs | 44 +++
server.js | 10 +
9 files changed, 1228 insertions(+)
diff --git a/.deploy.conf b/.deploy.conf
new file mode 100644
index 0000000..644374c
--- /dev/null
+++ b/.deploy.conf
@@ -0,0 +1,6 @@
+PROJECT_NAME=rentv-v1
+DEPLOY_HOST=45.61.58.125
+DEPLOY_PATH=/root/public-projects/rentv-v1
+HEALTH_URL=http://localhost:9704/api/health
+INSTALL_CMD="npm ci --production"
+BUILD_CMD=""
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1bcdbdc
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+node_modules/
+.env*
+*.log
+.DS_Store
diff --git a/data/news.json b/data/news.json
new file mode 100644
index 0000000..d1e84e2
--- /dev/null
+++ b/data/news.json
@@ -0,0 +1,187 @@
+{
+ "source": "rentv.com — live",
+ "fetched_at": "2026-07-17T18:17:25.250Z",
+ "count": 30,
+ "items": [
+ {
+ "id": "34227",
+ "title": "MG Properties Buys 188-Unit Portland Res Asset",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34227",
+ "cat": "Multifamily"
+ },
+ {
+ "id": "34216",
+ "title": "Meta Housing Well Underway with 300-Unit Res Community in Woodland Hills",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34216",
+ "cat": "Multifamily"
+ },
+ {
+ "id": "34214",
+ "title": "Intercontinental Real Estate Corporation Closes $69 Mil Retail Buy in Washington",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34214",
+ "cat": "Retail"
+ },
+ {
+ "id": "34208",
+ "title": "Eagle Partners and The Vistria Group Spend $132 Mil on Garden Grove Mixed-Income Res Community",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34208",
+ "cat": "Multifamily"
+ },
+ {
+ "id": "34204",
+ "title": "Oceanside Res Community Trades for $77.5 Mil",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34204",
+ "cat": "Multifamily"
+ },
+ {
+ "id": "34210",
+ "title": "BKM Capital Partners Boosts Pac Northwest Holdings with Purchase of 400k sf Industrial Buy in Washington",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34210",
+ "cat": "Industrial"
+ },
+ {
+ "id": "34203",
+ "title": "The Bascom Group Pays Over $53 Mil for OC Res Community",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34203",
+ "cat": "Multifamily"
+ },
+ {
+ "id": "34199",
+ "title": "Leasing Assignment for 650k sf San Francisco Office Tower Awarded to CBRE",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34199",
+ "cat": "Sales"
+ },
+ {
+ "id": "34202",
+ "title": "SummerHill Apartment Communities Breaks Ground on New 251-Unit Bay Area Res Development",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34202",
+ "cat": "Development"
+ },
+ {
+ "id": "34196",
+ "title": "Rubicon Point Partners Spends $53 Mil on Silicon Valley Medical/Office Property",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34196",
+ "cat": "Sales"
+ },
+ {
+ "id": "34195",
+ "title": "Borstein Enterprises Picks Up 282k sf OC Industrial Asset",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34195",
+ "cat": "Industrial"
+ },
+ {
+ "id": "34188",
+ "title": "Chino Distribution Facility Leased by Auto Parts Distributor in $42 Mil Deal",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34188",
+ "cat": "Leases"
+ },
+ {
+ "id": "34186",
+ "title": "Santa Clara Industrial Property Trades for $25.75 Mil",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34186",
+ "cat": "Industrial"
+ },
+ {
+ "id": "34181",
+ "title": "Redwood West and Panattoni Buy The Pike Destination Center in Long Beach",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34181",
+ "cat": "Retail"
+ },
+ {
+ "id": "34180",
+ "title": "Kennedy Wilson and Jamison Form Partnership to Develop 4,000 Units of Affordable Housing in Los Angeles",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34180",
+ "cat": "Development"
+ },
+ {
+ "id": "34086",
+ "title": "Oak Park Investments and Arise Investments Acquire Westlake Village Warehouse/Office Building in $19 Mil Transaction",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34086",
+ "cat": "Industrial"
+ },
+ {
+ "id": "34177",
+ "title": "Holland Partner Group Closes $105 Mil Bay Area Res Buy",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34177",
+ "cat": "Multifamily"
+ },
+ {
+ "id": "34172",
+ "title": "Haven Housing buys 264-Unit Res Community in Austin Metro Area",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34172",
+ "cat": "Multifamily"
+ },
+ {
+ "id": "34174",
+ "title": "MG Properties Acquires Two Multifamily Properties in Gresham, OR in Deals Totaling $60.8 Mil",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34174",
+ "cat": "Multifamily"
+ },
+ {
+ "id": "34171",
+ "title": "Leading AI Firm Leases 1.1 msf of Space at Luke Field in Glendale, AZ",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34171",
+ "cat": "Leases"
+ },
+ {
+ "id": "34169",
+ "title": "Seattle Area Res Community Refinanced with $82.5 Mil Loan from Mesa West Capital",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34169",
+ "cat": "Financing"
+ },
+ {
+ "id": "34160",
+ "title": "Apollo Lands in Gardena",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34160",
+ "cat": "Sales"
+ },
+ {
+ "id": "34165",
+ "title": "Phoenix Multifamily Community Lands New Owner",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34165",
+ "cat": "Multifamily"
+ },
+ {
+ "id": "34158",
+ "title": "Fairfield Residential Wraps Up Work on Houston High-Rise Res Development",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34158",
+ "cat": "Development"
+ },
+ {
+ "id": "34155",
+ "title": "Newly Constructed 1.18 msf AZ Industrial Campus Lands $122 Mil in Bridge Financing",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34155",
+ "cat": "Financing"
+ },
+ {
+ "id": "34153",
+ "title": "BKM Capital Partners Closes $95 Mil SoCal Biz Campus Buy",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34153",
+ "cat": "Sales"
+ },
+ {
+ "id": "34150",
+ "title": "Arizona Power Center Lands $38 Mil Refi",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34150",
+ "cat": "Financing"
+ },
+ {
+ "id": "34148",
+ "title": "Southwest Traders Spends $40 Mil on Menifee Industrial Facility",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34148",
+ "cat": "Industrial"
+ },
+ {
+ "id": "34151",
+ "title": "United Integrated Services (USA) Corp Buys 29 Acres in Phoenix for $41 Mil",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34151",
+ "cat": "Multifamily"
+ },
+ {
+ "id": "34145",
+ "title": "Seattle Social Housing Developer Wraps Up $60 Mil Res Buy in Downtown Seattle",
+ "url": "https://www.rentv.com/content/homepage/mainnews/news/34145",
+ "cat": "Development"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/data/pull.err b/data/pull.err
new file mode 100644
index 0000000..e69de29
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..7c49c33
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,828 @@
+{
+ "name": "rentv-v1",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "rentv-v1",
+ "version": "0.1.0",
+ "dependencies": {
+ "express": "^4.21.2"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.6",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz",
+ "integrity": "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.15.1",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.22.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz",
+ "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.5",
+ "content-disposition": "~0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "~0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "~6.15.1",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "~2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "~2.0.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
+ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
+ "license": "MIT"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.15.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
+ "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..439ce6e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,3 @@
+{ "name": "rentv-v1", "version": "0.1.0", "private": true, "main": "server.js",
+ "scripts": { "start": "node server.js", "pull": "node scripts/pull-news.mjs" },
+ "dependencies": { "express": "^4.21.2" } }
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..bfa75a7
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,146 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>RENTV.com — Commercial Real Estate News, Deals & Conferences</title>
+<meta name="description" content="Live commercial real estate news, deals, leases and financing across the Western U.S. — RENTV.com since 1998.">
+<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;1,400&family=Inter:wght@300;400;600;800&display=swap" rel="stylesheet">
+<style>
+ :root{--red:#c8102e;--ink:#14171a;--sub:#5b636b;--line:#e4e7ea;--bg:#fff;--wash:#f5f6f8;--sans:'Inter',system-ui,sans-serif;--serif:'Playfair Display',Georgia,serif}
+ *{box-sizing:border-box;margin:0;padding:0}
+ body{font-family:var(--sans);color:var(--ink);background:var(--bg);line-height:1.45}
+ a{color:inherit;text-decoration:none}
+ .wrap{max-width:1200px;margin:0 auto;padding:0 20px}
+ .util{background:var(--ink);color:#cfd4d9;font-size:12px}
+ .util .wrap{display:flex;justify-content:space-between;align-items:center;height:34px}
+ .ticker{display:flex;gap:20px;overflow:hidden;white-space:nowrap}.ticker b{color:#fff}.up{color:#4ade80}.dn{color:#f87171}
+ .util .acts a{margin-left:14px;color:#cfd4d9}.util .acts a.sub{color:#fff;background:var(--red);padding:4px 12px;border-radius:3px;font-weight:700}
+ .mast{border-bottom:2px solid var(--ink);padding:18px 0 12px}
+ .mast .wrap{display:flex;align-items:center;justify-content:space-between}
+ .logo{font-family:var(--serif);font-weight:700;font-size:34px;letter-spacing:-1px}.logo span{color:var(--red)}
+ .logo small{display:block;font-size:11px;font-weight:600;letter-spacing:3px;color:var(--sub);text-transform:uppercase;margin-top:2px;font-family:var(--sans)}
+ .datebox{font-size:12px;color:var(--sub);text-align:right}
+ nav{border-bottom:1px solid var(--line);position:sticky;top:0;background:#fff;z-index:10}
+ nav .wrap{display:flex;gap:2px;align-items:center;height:46px;overflow-x:auto}
+ nav a{font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;padding:14px 13px;border-bottom:3px solid transparent;white-space:nowrap;cursor:pointer}
+ nav a:hover,nav a.on{color:var(--red);border-color:var(--red)}
+ .breaking{background:var(--red);color:#fff;font-size:13px}
+ .breaking .wrap{display:flex;align-items:center;gap:14px;height:40px;overflow:hidden}
+ .breaking .tag{background:#fff;color:var(--red);font-weight:800;text-transform:uppercase;font-size:11px;letter-spacing:.08em;padding:3px 9px;border-radius:2px;flex:none}
+ .breaking .items{display:flex;gap:26px;white-space:nowrap;font-weight:600;animation:marq 40s linear infinite}
+ @keyframes marq{from{transform:translateX(0)}to{transform:translateX(-50%)}}
+ .grid{display:grid;grid-template-columns:1fr 320px;gap:34px;padding:28px 0}
+ .chip{display:inline-block;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;color:var(--red);margin-bottom:6px}
+ .lead{border-bottom:1px solid var(--line);padding-bottom:22px;margin-bottom:22px}
+ .lead .img{aspect-ratio:16/8;border-radius:6px;background:linear-gradient(135deg,#1f2937,#c8102e);position:relative;overflow:hidden}
+ .lead .img::after{content:'RENTV';position:absolute;right:12px;bottom:10px;color:rgba(255,255,255,.5);font-size:11px;letter-spacing:.2em}
+ .lead h1{font-family:var(--serif);font-size:36px;line-height:1.12;margin:14px 0 8px;letter-spacing:-.5px}
+ .lead .byl{font-size:12px;color:var(--sub);margin-top:8px}
+ .sechead{display:flex;align-items:center;gap:12px;border-top:3px solid var(--ink);padding-top:10px;margin:30px 0 16px}
+ .sechead h2{font-size:16px;text-transform:uppercase;letter-spacing:.06em}.sechead a{margin-left:auto;font-size:12px;color:var(--red);font-weight:700}
+ .cards{display:grid;grid-template-columns:1fr 1fr 1fr;gap:22px}
+ .card{display:block}
+ .card .img{aspect-ratio:16/10;border-radius:5px;background:linear-gradient(135deg,#475569,#94a3b8)}
+ .card:nth-child(3n+2) .img{background:linear-gradient(135deg,#7c2d12,#c8102e)}
+ .card:nth-child(3n) .img{background:linear-gradient(135deg,#0f766e,#14b8a6)}
+ .card h4{font-family:var(--serif);font-size:17px;line-height:1.22;margin:9px 0 4px}
+ .card .byl{font-size:12px;color:var(--sub);margin-top:6px}
+ .rail>*{margin-bottom:26px}.box{border:1px solid var(--line);border-radius:6px;padding:18px}
+ .ns{background:var(--ink);color:#fff;border:0}.ns h3{font-size:18px;margin-bottom:6px}.ns p{font-size:13px;color:#b9c0c7;margin-bottom:12px}
+ .ns input{width:100%;padding:11px;border:0;border-radius:4px;font-size:14px;margin-bottom:8px}
+ .ns button{width:100%;padding:11px;background:var(--red);color:#fff;border:0;border-radius:4px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;font-size:12px;cursor:pointer}
+ .ns .ok{font-size:12px;color:#4ade80;margin-top:6px;display:none}
+ .mostread{counter-reset:mr;list-style:none}
+ .mostread li{counter-increment:mr;display:flex;gap:12px;padding:12px 0;border-bottom:1px solid var(--line)}
+ .mostread li::before{content:counter(mr);color:var(--red);font-family:var(--serif);font-size:26px;font-weight:700;line-height:1}
+ .mostread a{font-size:14px;font-weight:600;line-height:1.25}
+ .conf{background:linear-gradient(135deg,#111827,#c8102e);color:#fff;border:0;border-radius:6px;padding:22px}
+ .conf .k{font-size:11px;letter-spacing:.2em;text-transform:uppercase;opacity:.85}.conf h3{font-family:var(--serif);font-size:22px;margin:6px 0 8px}
+ .conf a{display:inline-block;background:#fff;color:var(--red);font-weight:800;font-size:12px;text-transform:uppercase;letter-spacing:.06em;padding:9px 16px;border-radius:4px}
+ .live{display:inline-flex;align-items:center;gap:6px;font-size:11px;color:var(--sub);letter-spacing:.06em;text-transform:uppercase}
+ .live .dot{width:7px;height:7px;border-radius:50%;background:#22c55e;box-shadow:0 0 0 0 rgba(34,197,94,.6);animation:pulse 2s infinite}
+ @keyframes pulse{0%{box-shadow:0 0 0 0 rgba(34,197,94,.6)}70%{box-shadow:0 0 0 7px rgba(34,197,94,0)}100%{box-shadow:0 0 0 0 rgba(34,197,94,0)}}
+ footer{background:var(--ink);color:#aeb6bd;margin-top:36px;padding:40px 0 30px;font-size:13px}
+ footer .cols{display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:28px}
+ footer .logo{color:#fff;font-size:26px;margin-bottom:8px}footer h5{color:#fff;font-size:12px;text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px}footer a{display:block;padding:4px 0;color:#aeb6bd}
+ footer .fine{border-top:1px solid #2a2f34;margin-top:26px;padding-top:16px;font-size:12px;color:#7c848b}
+ .skl{background:linear-gradient(90deg,#eee,#f6f6f6,#eee);background-size:200% 100%;animation:sh 1.2s infinite;border-radius:4px;color:transparent}
+ @keyframes sh{to{background-position:-200% 0}}
+ @media(max-width:860px){.grid{grid-template-columns:1fr}.cards{grid-template-columns:1fr 1fr}}
+</style>
+</head>
+<body>
+ <div class="util"><div class="wrap">
+ <div class="ticker"><span><b>CRE INDEX</b> 4,182 <span class="up">▲0.6%</span></span><span><b>10-YR</b> 4.21%</span><span><b>LA OFFICE VAC</b> 23.1%</span><span><b>IE INDUSTRIAL CAP</b> 5.2%</span></div>
+ <div class="acts"><a href="#">Newsletters</a><a href="#">Advertise</a><a href="#">Log In</a><a class="sub" href="#news">Subscribe</a></div>
+ </div></div>
+
+ <header class="mast"><div class="wrap">
+ <div class="logo">REN<span>TV</span>.com<small>Commercial Real Estate News</small></div>
+ <div class="datebox"><span id="edition">Western U.S. Edition</span><br><span id="today"></span></div>
+ </div></header>
+
+ <nav><div class="wrap" id="nav">
+ <a data-cat="all" class="on">Home</a><a data-cat="Sales">Sales</a><a data-cat="Leases">Leases</a><a data-cat="Financing">Financing</a><a data-cat="Development">Development</a><a data-cat="Multifamily">Multifamily</a><a data-cat="Industrial">Industrial</a><a data-cat="Retail">Retail</a><a href="#conf">Conferences</a>
+ </div></nav>
+
+ <div class="breaking"><div class="wrap">
+ <span class="tag">Breaking</span><div class="items" id="breaking"><span>Loading live deals…</span></div>
+ </div></div>
+
+ <div class="wrap"><div class="grid">
+ <main>
+ <article class="lead" id="lead"><div class="img"></div><span class="chip"> </span><h1 class="skl"> <br> </h1><div class="byl">Loading the latest from RENTV…</div></article>
+ <div class="sechead"><h2>Latest CRE News</h2><span class="live"><span class="dot"></span><span id="freshness">live</span></span></div>
+ <div class="cards" id="cards"></div>
+ </main>
+ <aside class="rail">
+ <div class="ns box"><h3>The RENTV Daily</h3><p>Western-U.S. commercial real estate deals in your inbox every morning — free.</p><input id="email" placeholder="you@company.com"><button id="subbtn">Subscribe free</button><div class="ok" id="subok">✓ You're on the list.</div></div>
+ <div><div class="sechead" style="margin-top:0"><h2>Most Read</h2></div><ol class="mostread" id="mostread"></ol></div>
+ <div class="conf" id="conf"><div class="k">RENTV Conferences</div><h3>State of the Market Summit</h3><p style="font-size:13px;opacity:.9">600+ brokers, investors & developers · Los Angeles.</p><a href="#">Register →</a></div>
+ <div class="box"><div class="sechead" style="margin-top:0"><h2>Market Snapshot</h2></div>
+ <div style="display:flex;justify-content:space-between;padding:7px 0;border-bottom:1px solid var(--line);font-size:13px"><span>LA Office Vacancy</span><b>23.1%</b></div>
+ <div style="display:flex;justify-content:space-between;padding:7px 0;border-bottom:1px solid var(--line);font-size:13px"><span>IE Industrial Cap</span><b>5.2%</b></div>
+ <div style="display:flex;justify-content:space-between;padding:7px 0;font-size:13px"><span>SF Multifamily Rent YoY</span><b class="up">+3.4%</b></div>
+ </div>
+ </aside>
+ </div></div>
+
+ <footer><div class="wrap"><div class="cols">
+ <div><div class="logo">REN<span style="color:var(--red)">TV</span>.com</div><p style="max-width:34ch">Commercial real estate news, deals & conferences across the Western United States since 1998.</p></div>
+ <div><h5>Sections</h5><a href="#">Deals</a><a href="#">Sales</a><a href="#">Leases</a><a href="#">Financing</a></div>
+ <div><h5>Company</h5><a href="#">About</a><a href="#">Advertise</a><a href="#">Conferences</a><a href="#">Contact</a></div>
+ <div><h5>Follow</h5><a href="#">LinkedIn</a><a href="#">X / Twitter</a><a href="#">Newsletters</a></div>
+ </div><div class="fine">© 2026 RENTV.com · Headlines refreshed live from rentv.com · <span id="src"></span></div></div></footer>
+
+<script>
+document.getElementById('today').textContent = new Date().toLocaleDateString(undefined,{weekday:'long',year:'numeric',month:'long',day:'numeric'});
+let ALL=[], cat='all';
+const catImg=c=>c;
+function render(){
+ const items = cat==='all'?ALL:ALL.filter(x=>x.cat===cat);
+ const lead=items[0];
+ if(lead){document.getElementById('lead').innerHTML='<a href="'+lead.url+'" target="_blank" rel="noopener"><div class="img"></div><span class="chip">'+lead.cat+' · Western U.S.</span><h1>'+lead.title+'</h1><div class="byl">By RENTV Staff · live from rentv.com</div></a>';}
+ const cards=items.slice(1,13).map(x=>'<a class="card" href="'+x.url+'" target="_blank" rel="noopener"><div class="img"></div><span class="chip">'+x.cat+'</span><h4>'+x.title+'</h4><div class="byl">RENTV Staff</div></a>').join('');
+ document.getElementById('cards').innerHTML=cards||'<p style="color:var(--sub)">No items in this section right now.</p>';
+ document.getElementById('breaking').innerHTML=ALL.slice(0,6).map(x=>'<span>'+x.title+'</span>').join(' · ').repeat(2);
+ document.getElementById('mostread').innerHTML=ALL.slice(0,5).map(x=>'<li><a href="'+x.url+'" target="_blank" rel="noopener">'+x.title+'</a></li>').join('');
+}
+async function load(){
+ try{
+ const d=await (await fetch('/api/news')).json();
+ ALL=(d.items||[]).map(x=>({...x,title:x.title}));
+ if(ALL.length){render();
+ const mins=Math.max(0,Math.round((Date.now()-new Date(d.fetched_at))/60000));
+ document.getElementById('freshness').textContent=mins<2?'updated just now':('updated '+mins+' min ago');
+ document.getElementById('src').textContent='last pull '+new Date(d.fetched_at).toLocaleTimeString();
+ }
+ }catch(e){document.getElementById('lead').querySelector('.byl').textContent='Live feed temporarily unavailable — retrying…';}
+}
+document.getElementById('nav').addEventListener('click',e=>{const a=e.target.closest('a[data-cat]');if(!a)return;e.preventDefault();document.querySelectorAll('#nav a').forEach(x=>x.classList.remove('on'));a.classList.add('on');cat=a.dataset.cat;render();window.scrollTo({top:0,behavior:'smooth'});});
+document.getElementById('subbtn').onclick=()=>{if((document.getElementById('email').value||'').includes('@')){document.getElementById('subok').style.display='block';}};
+load(); setInterval(load, 120000); // refresh the live feed every 2 minutes
+</script>
+</body>
+</html>
diff --git a/scripts/pull-news.mjs b/scripts/pull-news.mjs
new file mode 100644
index 0000000..604bb02
--- /dev/null
+++ b/scripts/pull-news.mjs
@@ -0,0 +1,44 @@
+#!/usr/bin/env node
+// Live-data engine for rentv-v1: pulls REAL rentv.com news headlines on a schedule.
+// Runs via launchd every 20 min; writes data/news.json the site serves. $0 (plain fetch).
+import { writeFileSync, mkdirSync } from 'node:fs';
+import { dirname, join } from 'node:path';
+import { fileURLToPath } from 'node:url';
+const HERE = dirname(fileURLToPath(import.meta.url));
+const OUT = join(HERE, '..', 'data'); mkdirSync(OUT, { recursive: true });
+
+async function fetchDecoded(url) {
+ const r = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 RENTV-v1' }, signal: AbortSignal.timeout(20000) });
+ const buf = Buffer.from(await r.arrayBuffer());
+ let s = buf.toString('utf8');
+ if ((s.match(/�/g) || []).length > 5) s = buf.toString('latin1'); // WIN-1252 fallback
+ return s;
+}
+const clean = (t) => t.replace(/&/g,'&').replace(/'/g,"'").replace(/"/g,'"').replace(/ /g,' ').replace(/\s+/g,' ').trim();
+
+try {
+ const html = await fetchDecoded('https://www.rentv.com/');
+ const re = /<a[^>]+href="([^"]*content\/homepage\/mainnews\/news\/(\d+))"[^>]*>([^<]{20,160})<\/a>/gi;
+ const seen = new Set(), items = []; let m;
+ while ((m = re.exec(html)) && items.length < 30) {
+ const id = m[2]; if (seen.has(id)) continue; seen.add(id);
+ const title = clean(m[3]); if (title.length < 20) continue;
+ // classify by keywords
+ const t = title.toLowerCase();
+ const cat = /financ|loan|refi|recap|debt/.test(t) ? 'Financing'
+ : /lease|sf lease|tenant/.test(t) ? 'Leases'
+ : /develop|breaks ground|project|mixed-use/.test(t) ? 'Development'
+ : /retail|shop|center/.test(t) ? 'Retail'
+ : /res |multifamily|apartment|unit/.test(t) ? 'Multifamily'
+ : /industrial|warehouse|logistics/.test(t) ? 'Industrial'
+ : 'Sales';
+ items.push({ id, title, url: m[1].startsWith('http') ? m[1] : 'https://www.rentv.com/' + m[1].replace(/^\//,''), cat });
+ }
+ const payload = { source: 'rentv.com — live', fetched_at: new Date().toISOString(), count: items.length, items };
+ writeFileSync(join(OUT, 'news.json'), JSON.stringify(payload, null, 1));
+ console.log('OK: pulled ' + items.length + ' live headlines');
+} catch (e) {
+ console.error('pull failed:', e.message);
+ // never overwrite a good cache with nothing — leave last-good in place
+ process.exit(1);
+}
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..578f049
--- /dev/null
+++ b/server.js
@@ -0,0 +1,10 @@
+'use strict';
+// rentv-v1 — PUBLIC end-user CRE-news site (The Wire concept) on LIVE rentv.com data.
+const fs = require('fs'); const path = require('path'); const express = require('express');
+const app = express(); const PORT = process.env.PORT || 9704;
+const DATA = path.join(__dirname, 'data');
+const readJSON = (f, fb) => { try { return JSON.parse(fs.readFileSync(path.join(DATA, f), 'utf8')); } catch { return fb; } };
+app.get('/api/health', (_q, r) => r.json({ ok: true, at: new Date().toISOString() }));
+app.get('/api/news', (_q, r) => { r.set('Cache-Control', 'public, max-age=120'); r.json(readJSON('news.json', { items: [] })); });
+app.use(express.static(path.join(__dirname, 'public'), { extensions: ['html'] }));
+app.listen(PORT, () => console.log('rentv-v1 (public, live data) on ' + PORT));
(oldest)
·
back to Rentv V1
·
rentv-v1: gate behind Basic Auth (admin + Boomer) a23b73f →