Compare commits

...
25 Commits
Author SHA1 Message Date
dni ⚡andGitHub 3c92ef2900 FIX: #1661 (#1663)
fixup
2023-05-04 17:22:24 +02:00
dni ⚡andGitHub 7feac3bb5e add lnbits version to frontend (#1677) 2023-05-04 17:21:37 +02:00
Tiago VasconcelosandGitHub 590b52cb31 Update print.html (#1678)
* Update print.html

Fix print.html not getting the JS libraries

* Update print.html

Add `INCLUDED_CSS` also
2023-05-04 17:21:18 +02:00
callebtcandGitHub b4fce8b00b fix internal invoice check (#1681)
* fix internal invoice check

* return error

* make format
2023-05-04 17:20:37 +02:00
ArcandGitHub bd47527d34 Merge pull request #1672 from lnbits/paymentsocketjson
Output json to payment websocket
2023-05-02 18:42:51 +01:00
ben 171aa08e0c output json 2023-05-02 18:40:46 +01:00
ArcandGitHub bc2c5fd1a9 Merge pull request #1648 from lnbits/typeignore-generic
removing type ignores from views/generic.py
2023-05-02 14:50:00 +01:00
ArcandGitHub 67c377affc Merge pull request #1670 from lnbits/fixsparkimage
Fixes spark image on homepage
2023-05-02 14:25:59 +01:00
ben 09d67249cc format 2023-05-02 14:11:01 +01:00
ben 068965d81b Missing div 2023-05-02 14:07:25 +01:00
ben aec15ae820 format 2023-05-02 13:43:54 +01:00
ben c714a2641d Adds websocket for payments 2023-05-02 12:53:36 +01:00
ben b9d3fc5744 Fix spark image 2023-05-02 12:27:51 +01:00
dni ⚡andcalle 21157fffe2 removing type ignores from views/generic.py
suggestion motorina1

Update lnbits/core/views/generic.py

Co-authored-by: calle <93376500+callebtc@users.noreply.github.com>

quickfix
2023-04-27 16:28:16 +02:00
dni ⚡andGitHub f8058825d8 FEAT: introduce bundle_assets variable and decouple it from DEBUG (#1660) 2023-04-27 16:05:49 +02:00
dni ⚡andGitHub bfb27020ee regression: fatfinger removed block styles (#1659)
quickfix

format
2023-04-27 15:38:52 +02:00
dni ⚡andGitHub 667de4d1f0 generate .super_user file on startup if adminui is enabled (#1651)
* generate .super_user file on startup if adminui is enabled
* only save super_user id instead of url
2023-04-25 10:25:50 +02:00
dni ⚡andGitHub 5c119e903d increment cache version for service-worker.js (#1644)
* increment cache version
* add serviceworker incrementer on make bundle
2023-04-25 10:25:09 +02:00
dni ⚡andGitHub 3f3af53240 prepare lnbits version 0.10.5 (#1649) 2023-04-25 10:24:24 +02:00
dni ⚡andGitHub 1b84ebf13d FEAT: build static files with npm (sass, concat, minify), remove build step from python, include minified bundle files (#1601) 2023-04-21 14:51:46 +02:00
dni ⚡andGitHub bef2d4a72e remove boltz_client as dependecy (#1610) 2023-04-21 14:42:52 +02:00
dni ⚡andGitHub 04471b8b5b remove python38 from pyproject.toml (#1632) 2023-04-21 14:37:26 +02:00
ArcandGitHub be1d515307 Merge pull request #1642 from lnbits/new-version
change version to 0.10.4
2023-04-21 13:15:28 +01:00
dni ⚡ 87d1de2a31 change version to 0.10.4.1 2023-04-21 13:18:03 +02:00
dni ⚡ 0546fb0a77 change version to 0.10.4 2023-04-21 13:08:01 +02:00
25 changed files with 2149 additions and 915 deletions
+3 -1
View File
@@ -23,6 +23,7 @@ tests/data/*.sqlite3
*.pyc
*.env
.env
.super_user
data
*.sqlite3
@@ -32,7 +33,8 @@ __bundle__
coverage.xml
node_modules
lnbits/static/bundle.*
lnbits/static/bundle.js
lnbits/static/bundle.css
docker
# Nix
+15 -16
View File
@@ -73,20 +73,19 @@ bak:
# LNBITS_DATABASE_URL=postgres://postgres:postgres@0.0.0.0:5432/postgres
#
updatevendor:
sass:
npm run sass
bundle:
npm install
cp ./node_modules/moment/moment.js ./lnbits/static/vendor/
cp ./node_modules/underscore/underscore.js ./lnbits/static/vendor/
cp ./node_modules/axios/dist/axios.js ./lnbits/static/vendor/
cp ./node_modules/vue/dist/vue.js ./lnbits/static/vendor/
cp ./node_modules/vue-router/dist/vue-router.js ./lnbits/static/vendor/
cp ./node_modules/vue-qrcode-reader/dist/vue-qrcode-reader.browser.js ./lnbits/static/vendor/
cp ./node_modules/@chenfengyuan/vue-qrcode/dist/vue-qrcode.js ./lnbits/static/vendor/
cp ./node_modules/vuex/dist/vuex.js ./lnbits/static/vendor/
cp ./node_modules/quasar/dist/quasar.ie.polyfills.umd.min.js ./lnbits/static/vendor/
cp ./node_modules/quasar/dist/quasar.umd.js ./lnbits/static/vendor/
cp ./node_modules/chart.js/dist/Chart.bundle.js ./lnbits/static/vendor/
cp ./node_modules/quasar/dist/quasar.css ./lnbits/static/vendor/
cp ./node_modules/chart.js/dist/Chart.css ./lnbits/static/vendor/
cp ./node_modules/vue-qrcode-reader/dist/vue-qrcode-reader.css ./lnbits/static/vendor/
cp ./node_modules/vue-i18n/dist/vue-i18n.js ./lnbits/static/vendor/
npm run sass
npm run vendor_copy
npm run vendor_json
poetry run ./node_modules/.bin/prettier -w ./lnbits/static/vendor.json
npm run vendor_bundle_css
npm run vendor_minify_css
npm run vendor_bundle_js
npm run vendor_minify_js
# increment serviceworker version
sed -i -e "s/CACHE_VERSION =.*/CACHE_VERSION = $$(awk '/CACHE_VERSION =/ { print 1+$$4 }' lnbits/core/static/js/service-worker.js)/" \
lnbits/core/static/js/service-worker.js
-19
View File
@@ -1,6 +1,4 @@
import asyncio
import os
import warnings
import click
from loguru import logger
@@ -20,23 +18,6 @@ def db_migrate():
asyncio.create_task(migrate_databases())
@click.command("assets")
def handle_assets():
transpile_scss()
def transpile_scss():
with warnings.catch_warnings():
warnings.simplefilter("ignore")
from scss.compiler import compile_string # type: ignore
with open(os.path.join(settings.lnbits_path, "static/scss/base.scss")) as scss:
with open(
os.path.join(settings.lnbits_path, "static/css/base.css"), "w"
) as css:
css.write(compile_string(scss.read()))
async def migrate_databases():
"""Creates the necessary databases if they don't exist already; or migrates them."""
+17 -1
View File
@@ -35,6 +35,7 @@ from .crud import (
create_wallet,
delete_wallet_payment,
get_account,
get_standalone_payment,
get_super_settings,
get_wallet,
get_wallet_payment,
@@ -66,7 +67,6 @@ async def create_invoice(
internal: Optional[bool] = False,
conn: Optional[Connection] = None,
) -> Tuple[str, str]:
if not amount > 0:
raise InvoiceFailure("Amountless invoices not supported.")
@@ -157,6 +157,18 @@ async def pay_invoice(
# check_internal() returns the checking_id of the invoice we're waiting for (pending only)
internal_checking_id = await check_internal(invoice.payment_hash, conn=conn)
if internal_checking_id:
# perform additional checks on the internal payment
# the payment hash is not enough to make sure that this is the same invoice
internal_invoice = await get_standalone_payment(
internal_checking_id, incoming=True, conn=conn
)
assert internal_invoice is not None
if (
internal_invoice.amount != invoice.amount_msat
or internal_invoice.bolt11 != payment_request
):
raise PaymentFailure("Invalid invoice.")
logger.debug(f"creating temporary internal payment with id {internal_id}")
# create a new payment from this wallet
await create_payment(
@@ -447,6 +459,10 @@ async def check_admin_settings():
admin_url = f"{settings.lnbits_baseurl}wallet?usr={settings.super_user}"
logger.success(f"✔️ Access super user account at: {admin_url}")
# saving it to .super_user file
with open(".super_user", "w") as file:
file.write(settings.super_user)
# callback for saas
if (
settings.lnbits_saas_callback
+3 -2
View File
@@ -1,5 +1,6 @@
// the cache version gets updated every time there is a new deployment
const CACHE_VERSION = 1
// update cache version every time there is a new deployment
// so the service worker reinitializes the cache
const CACHE_VERSION = 5
const CURRENT_CACHE = `lnbits-${CACHE_VERSION}-`
const getApiKey = request => {
+2
View File
@@ -9,6 +9,7 @@ from lnbits.tasks import SseListenersDict, register_invoice_listener
from . import db
from .crud import get_balance_notify
from .models import Payment
from .services import websocketUpdater
api_invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict(
"api_invoice_listeners"
@@ -37,6 +38,7 @@ async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue):
logger.trace("received invoice paid event")
# send information to sse channel
await dispatch_api_invoice_listeners(payment)
await websocketUpdater(payment.wallet_id, payment.dict())
# dispatch webhook
if payment.webhook and not payment.webhook_status:
+8 -9
View File
@@ -141,15 +141,14 @@
></q-img>
</a>
</div>
<div class="col q-pl-md">&nbsp;</div>
</div>
<div class="col">
<a href="https://github.com/shesek/spark-wallet">
<q-img
contain
:src="($q.dark.isActive) ? '/static/images/spark.png' : '/static/images/sparkl.png'"
></q-img>
</a>
<div class="col q-pl-md">
<a href="https://github.com/shesek/spark-wallet">
<q-img
contain
:src="($q.dark.isActive) ? '/static/images/spark.png' : '/static/images/sparkl.png'"
></q-img>
</a>
</div>
</div>
</div>
+6 -3
View File
@@ -32,11 +32,14 @@ async def api_get_settings(
@core_app.put(
"/admin/api/v1/settings/",
status_code=HTTPStatus.OK,
dependencies=[Depends(check_admin)],
)
async def api_update_settings(data: EditableSettings):
async def api_update_settings(
data: EditableSettings, user: User = Depends(check_admin)
):
await update_admin_settings(data)
update_cached_settings(dict(data))
admin_settings = await get_admin_settings(user.super_user)
assert admin_settings, "Updated admin settings not found."
update_cached_settings(admin_settings.dict())
return {"status": "Success"}
+24 -16
View File
@@ -68,7 +68,9 @@ async def extensions(
# Update user as his extensions have been updated
if enable or disable:
user = await get_user(user.id) # type: ignore
updated_user = await get_user(user.id)
assert updated_user, "User does not exist."
user = updated_user
return template_renderer().TemplateResponse(
"core/extensions.html", {"request": request, "user": user.dict()}
@@ -186,8 +188,10 @@ async def wallet(
wallet_name = nme
if not user_id:
user = await get_user((await create_account()).id)
logger.info(f"Create user {user.id}") # type: ignore
new_user = await create_account()
user = await get_user(new_user.id)
assert user, "Newly created user has to exist."
logger.info(f"Create user {user.id}")
else:
user = await get_user(user_id)
if not user:
@@ -209,23 +213,23 @@ async def wallet(
user.super_user = True
if not wallet_id:
if user.wallets and not wallet_name: # type: ignore
wallet = user.wallets[0] # type: ignore
if user.wallets and not wallet_name:
wallet = user.wallets[0]
else:
wallet = await create_wallet(user_id=user.id, wallet_name=wallet_name) # type: ignore
wallet = await create_wallet(user_id=user.id, wallet_name=wallet_name)
logger.info(
f"Created new wallet {wallet_name if wallet_name else '(no name)'} for user {user.id}" # type: ignore
f"Created new wallet {wallet_name if wallet_name else '(no name)'} for user {user.id}"
)
return RedirectResponse(
f"/wallet?usr={user.id}&wal={wallet.id}", # type: ignore
f"/wallet?usr={user.id}&wal={wallet.id}",
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
)
logger.debug(
f"Access {'user '+ user.id + ' ' if user else ''} {'wallet ' + wallet_name if wallet_name else ''}"
)
userwallet = user.get_wallet(wallet_id) # type: ignore
userwallet = user.get_wallet(wallet_id)
if not userwallet:
return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "Wallet not found"}
@@ -235,10 +239,10 @@ async def wallet(
"core/wallet.html",
{
"request": request,
"user": user.dict(), # type: ignore
"user": user.dict(),
"wallet": userwallet.dict(),
"service_fee": settings.lnbits_service_fee,
"web_manifest": f"/manifest/{user.id}.webmanifest", # type: ignore
"web_manifest": f"/manifest/{user.id}.webmanifest",
},
)
@@ -294,18 +298,21 @@ async def lnurl_full_withdraw_callback(request: Request):
@core_html_routes.get("/deletewallet", response_class=RedirectResponse)
async def deletewallet(wal: str = Query(...), usr: str = Query(...)):
user = await get_user(usr)
user_wallet_ids = [u.id for u in user.wallets] # type: ignore
if not user:
raise HTTPException(HTTPStatus.FORBIDDEN, "User not found.")
user_wallet_ids = [u.id for u in user.wallets]
if wal not in user_wallet_ids:
raise HTTPException(HTTPStatus.FORBIDDEN, "Not your wallet.")
else:
await delete_wallet(user_id=user.id, wallet_id=wal) # type: ignore
await delete_wallet(user_id=user.id, wallet_id=wal)
user_wallet_ids.remove(wal)
logger.debug("Deleted wallet {wal} of user {user.id}")
if user_wallet_ids:
return RedirectResponse(
url_for("/wallet", usr=user.id, wal=user_wallet_ids[0]), # type: ignore
url_for("/wallet", usr=user.id, wal=user_wallet_ids[0]),
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
)
@@ -328,7 +335,8 @@ async def lnurlwallet(request: Request):
async with db.connect() as conn:
account = await create_account(conn=conn)
user = await get_user(account.id, conn=conn)
wallet = await create_wallet(user_id=user.id, conn=conn) # type: ignore
assert user, "Newly created user not found."
wallet = await create_wallet(user_id=user.id, conn=conn)
asyncio.create_task(
redeem_lnurl_withdraw(
@@ -341,7 +349,7 @@ async def lnurlwallet(request: Request):
)
return RedirectResponse(
f"/wallet?usr={user.id}&wal={wallet.id}", # type: ignore
f"/wallet?usr={user.id}&wal={wallet.id}",
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
)
+12 -27
View File
@@ -1,3 +1,5 @@
import json
from pathlib import Path
from typing import Any, List, Optional, Type
import jinja2
@@ -15,27 +17,6 @@ from lnbits.settings import settings
from .extension_manager import get_valid_extensions
vendored_js = [
"/static/vendor/moment.js",
"/static/vendor/underscore.js",
"/static/vendor/axios.js",
"/static/vendor/vue.js",
"/static/vendor/vue-router.js",
"/static/vendor/vue-qrcode-reader.browser.js",
"/static/vendor/vue-qrcode.js",
"/static/vendor/vue-i18n.js",
"/static/vendor/vuex.js",
"/static/vendor/quasar.ie.polyfills.umd.min.js",
"/static/vendor/quasar.umd.js",
"/static/vendor/Chart.bundle.js",
]
vendored_css = [
"/static/vendor/quasar.css",
"/static/vendor/Chart.css",
"/static/vendor/vue-qrcode-reader.css",
]
def urlsafe_short_hash() -> str:
return shortuuid.uuid()
@@ -68,7 +49,8 @@ def template_renderer(additional_folders: Optional[List] = None) -> Jinja2Templa
t.env.globals["SITE_TAGLINE"] = settings.lnbits_site_tagline
t.env.globals["SITE_DESCRIPTION"] = settings.lnbits_site_description
t.env.globals["LNBITS_THEME_OPTIONS"] = settings.lnbits_theme_options
t.env.globals["LNBITS_VERSION"] = settings.lnbits_commit
t.env.globals["COMMIT_VERSION"] = settings.lnbits_commit
t.env.globals["LNBITS_VERSION"] = settings.version
t.env.globals["LNBITS_ADMIN_UI"] = settings.lnbits_admin_ui
t.env.globals["EXTENSIONS"] = [
e
@@ -78,12 +60,15 @@ def template_renderer(additional_folders: Optional[List] = None) -> Jinja2Templa
if settings.lnbits_custom_logo:
t.env.globals["USE_CUSTOM_LOGO"] = settings.lnbits_custom_logo
if settings.debug:
t.env.globals["VENDORED_JS"] = vendored_js
t.env.globals["VENDORED_CSS"] = vendored_css
if settings.bundle_assets:
t.env.globals["INCLUDED_JS"] = ["/static/bundle.min.js"]
t.env.globals["INCLUDED_CSS"] = ["/static/bundle.min.css"]
else:
t.env.globals["VENDORED_JS"] = ["/static/bundle.js"]
t.env.globals["VENDORED_CSS"] = ["/static/bundle.css"]
vendor_filepath = Path(settings.lnbits_path, "static", "vendor.json")
with open(vendor_filepath) as vendor_file:
vendor_files = json.loads(vendor_file.read())
t.env.globals["INCLUDED_JS"] = vendor_files["js"]
t.env.globals["INCLUDED_CSS"] = vendor_files["css"]
return t
+1
View File
@@ -227,6 +227,7 @@ class EditableSettings(
class EnvSettings(LNbitsSettings):
debug: bool = Field(default=False)
bundle_assets: bool = Field(default=True)
host: str = Field(default="127.0.0.1")
port: int = Field(default=5000)
forwarded_allow_ips: str = Field(default="*")
+1
View File
File diff suppressed because one or more lines are too long
+55
View File
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
base.css
+545
View File
@@ -0,0 +1,545 @@
[data-theme=classic] .q-drawer--dark,
body[data-theme=classic].body--dark,
[data-theme=classic] .q-menu--dark {
background: #1f2234 !important;
}
/* IF WANTING TO SET A DARKER BG COLOR IN THE FUTURE
// set a darker body bg for all themes, when in "dark mode"
body[data-theme='classic'].body--dark {
background: scale-color($color, $lightness: -60%);
}
*/
[data-theme=classic] .q-card--dark,
[data-theme=classic] .q-stepper--dark {
background: #333646 !important;
}
[data-theme=classic] .bg-primary {
background: #673ab7 !important;
}
[data-theme=classic] .text-primary {
color: #673ab7 !important;
}
[data-theme=classic] .bg-secondary {
background: #9c27b0 !important;
}
[data-theme=classic] .text-secondary {
color: #9c27b0 !important;
}
[data-theme=classic] .bg-dark {
background: #1f2234 !important;
}
[data-theme=classic] .text-dark {
color: #1f2234 !important;
}
[data-theme=classic] .bg-info {
background: #333646 !important;
}
[data-theme=classic] .text-info {
color: #333646 !important;
}
[data-theme=classic] .bg-marginal-bg {
background: #1f2234 !important;
}
[data-theme=classic] .text-marginal-bg {
color: #1f2234 !important;
}
[data-theme=classic] .bg-marginal-text {
background: #fff !important;
}
[data-theme=classic] .text-marginal-text {
color: #fff !important;
}
[data-theme=bitcoin] .q-drawer--dark,
body[data-theme=bitcoin].body--dark,
[data-theme=bitcoin] .q-menu--dark {
background: #2d293b !important;
}
/* IF WANTING TO SET A DARKER BG COLOR IN THE FUTURE
// set a darker body bg for all themes, when in "dark mode"
body[data-theme='bitcoin'].body--dark {
background: scale-color($color, $lightness: -60%);
}
*/
[data-theme=bitcoin] .q-card--dark,
[data-theme=bitcoin] .q-stepper--dark {
background: #333646 !important;
}
[data-theme=bitcoin] .bg-primary {
background: #ff9853 !important;
}
[data-theme=bitcoin] .text-primary {
color: #ff9853 !important;
}
[data-theme=bitcoin] .bg-secondary {
background: #ff7353 !important;
}
[data-theme=bitcoin] .text-secondary {
color: #ff7353 !important;
}
[data-theme=bitcoin] .bg-dark {
background: #2d293b !important;
}
[data-theme=bitcoin] .text-dark {
color: #2d293b !important;
}
[data-theme=bitcoin] .bg-info {
background: #333646 !important;
}
[data-theme=bitcoin] .text-info {
color: #333646 !important;
}
[data-theme=bitcoin] .bg-marginal-bg {
background: #2d293b !important;
}
[data-theme=bitcoin] .text-marginal-bg {
color: #2d293b !important;
}
[data-theme=bitcoin] .bg-marginal-text {
background: #fff !important;
}
[data-theme=bitcoin] .text-marginal-text {
color: #fff !important;
}
[data-theme=freedom] .q-drawer--dark,
body[data-theme=freedom].body--dark,
[data-theme=freedom] .q-menu--dark {
background: #0a0a0a !important;
}
/* IF WANTING TO SET A DARKER BG COLOR IN THE FUTURE
// set a darker body bg for all themes, when in "dark mode"
body[data-theme='freedom'].body--dark {
background: scale-color($color, $lightness: -60%);
}
*/
[data-theme=freedom] .q-card--dark,
[data-theme=freedom] .q-stepper--dark {
background: #1b1b1b !important;
}
[data-theme=freedom] .bg-primary {
background: #e22156 !important;
}
[data-theme=freedom] .text-primary {
color: #e22156 !important;
}
[data-theme=freedom] .bg-secondary {
background: #b91a45 !important;
}
[data-theme=freedom] .text-secondary {
color: #b91a45 !important;
}
[data-theme=freedom] .bg-dark {
background: #0a0a0a !important;
}
[data-theme=freedom] .text-dark {
color: #0a0a0a !important;
}
[data-theme=freedom] .bg-info {
background: #1b1b1b !important;
}
[data-theme=freedom] .text-info {
color: #1b1b1b !important;
}
[data-theme=freedom] .bg-marginal-bg {
background: #2d293b !important;
}
[data-theme=freedom] .text-marginal-bg {
color: #2d293b !important;
}
[data-theme=freedom] .bg-marginal-text {
background: #fff !important;
}
[data-theme=freedom] .text-marginal-text {
color: #fff !important;
}
[data-theme=cyber] .q-drawer--dark,
body[data-theme=cyber].body--dark,
[data-theme=cyber] .q-menu--dark {
background: #0a0a0a !important;
}
/* IF WANTING TO SET A DARKER BG COLOR IN THE FUTURE
// set a darker body bg for all themes, when in "dark mode"
body[data-theme='cyber'].body--dark {
background: scale-color($color, $lightness: -60%);
}
*/
[data-theme=cyber] .q-card--dark,
[data-theme=cyber] .q-stepper--dark {
background: #1b1b1b !important;
}
[data-theme=cyber] .bg-primary {
background: #7cb342 !important;
}
[data-theme=cyber] .text-primary {
color: #7cb342 !important;
}
[data-theme=cyber] .bg-secondary {
background: #558b2f !important;
}
[data-theme=cyber] .text-secondary {
color: #558b2f !important;
}
[data-theme=cyber] .bg-dark {
background: #0a0a0a !important;
}
[data-theme=cyber] .text-dark {
color: #0a0a0a !important;
}
[data-theme=cyber] .bg-info {
background: #1b1b1b !important;
}
[data-theme=cyber] .text-info {
color: #1b1b1b !important;
}
[data-theme=cyber] .bg-marginal-bg {
background: #2d293b !important;
}
[data-theme=cyber] .text-marginal-bg {
color: #2d293b !important;
}
[data-theme=cyber] .bg-marginal-text {
background: #fff !important;
}
[data-theme=cyber] .text-marginal-text {
color: #fff !important;
}
[data-theme=mint] .q-drawer--dark,
body[data-theme=mint].body--dark,
[data-theme=mint] .q-menu--dark {
background: #1f342b !important;
}
/* IF WANTING TO SET A DARKER BG COLOR IN THE FUTURE
// set a darker body bg for all themes, when in "dark mode"
body[data-theme='mint'].body--dark {
background: scale-color($color, $lightness: -60%);
}
*/
[data-theme=mint] .q-card--dark,
[data-theme=mint] .q-stepper--dark {
background: #334642 !important;
}
[data-theme=mint] .bg-primary {
background: #3ab77d !important;
}
[data-theme=mint] .text-primary {
color: #3ab77d !important;
}
[data-theme=mint] .bg-secondary {
background: #27b065 !important;
}
[data-theme=mint] .text-secondary {
color: #27b065 !important;
}
[data-theme=mint] .bg-dark {
background: #1f342b !important;
}
[data-theme=mint] .text-dark {
color: #1f342b !important;
}
[data-theme=mint] .bg-info {
background: #334642 !important;
}
[data-theme=mint] .text-info {
color: #334642 !important;
}
[data-theme=mint] .bg-marginal-bg {
background: #1f342b !important;
}
[data-theme=mint] .text-marginal-bg {
color: #1f342b !important;
}
[data-theme=mint] .bg-marginal-text {
background: #fff !important;
}
[data-theme=mint] .text-marginal-text {
color: #fff !important;
}
[data-theme=autumn] .q-drawer--dark,
body[data-theme=autumn].body--dark,
[data-theme=autumn] .q-menu--dark {
background: #34291f !important;
}
/* IF WANTING TO SET A DARKER BG COLOR IN THE FUTURE
// set a darker body bg for all themes, when in "dark mode"
body[data-theme='autumn'].body--dark {
background: scale-color($color, $lightness: -60%);
}
*/
[data-theme=autumn] .q-card--dark,
[data-theme=autumn] .q-stepper--dark {
background: #463f33 !important;
}
[data-theme=autumn] .bg-primary {
background: #b7763a !important;
}
[data-theme=autumn] .text-primary {
color: #b7763a !important;
}
[data-theme=autumn] .bg-secondary {
background: #b07927 !important;
}
[data-theme=autumn] .text-secondary {
color: #b07927 !important;
}
[data-theme=autumn] .bg-dark {
background: #34291f !important;
}
[data-theme=autumn] .text-dark {
color: #34291f !important;
}
[data-theme=autumn] .bg-info {
background: #463f33 !important;
}
[data-theme=autumn] .text-info {
color: #463f33 !important;
}
[data-theme=autumn] .bg-marginal-bg {
background: #342a1f !important;
}
[data-theme=autumn] .text-marginal-bg {
color: #342a1f !important;
}
[data-theme=autumn] .bg-marginal-text {
background: rgb(255, 255, 255) !important;
}
[data-theme=autumn] .text-marginal-text {
color: rgb(255, 255, 255) !important;
}
[data-theme=flamingo] .q-drawer--dark,
body[data-theme=flamingo].body--dark,
[data-theme=flamingo] .q-menu--dark {
background: #803a45 !important;
}
/* IF WANTING TO SET A DARKER BG COLOR IN THE FUTURE
// set a darker body bg for all themes, when in "dark mode"
body[data-theme='flamingo'].body--dark {
background: scale-color($color, $lightness: -60%);
}
*/
[data-theme=flamingo] .q-card--dark,
[data-theme=flamingo] .q-stepper--dark {
background: #ec7599 !important;
}
[data-theme=flamingo] .bg-primary {
background: #d11d53 !important;
}
[data-theme=flamingo] .text-primary {
color: #d11d53 !important;
}
[data-theme=flamingo] .bg-secondary {
background: #db3e6d !important;
}
[data-theme=flamingo] .text-secondary {
color: #db3e6d !important;
}
[data-theme=flamingo] .bg-dark {
background: #803a45 !important;
}
[data-theme=flamingo] .text-dark {
color: #803a45 !important;
}
[data-theme=flamingo] .bg-info {
background: #ec7599 !important;
}
[data-theme=flamingo] .text-info {
color: #ec7599 !important;
}
[data-theme=flamingo] .bg-marginal-bg {
background: #803a45 !important;
}
[data-theme=flamingo] .text-marginal-bg {
color: #803a45 !important;
}
[data-theme=flamingo] .bg-marginal-text {
background: rgb(255, 255, 255) !important;
}
[data-theme=flamingo] .text-marginal-text {
color: rgb(255, 255, 255) !important;
}
[data-theme=monochrome] .q-drawer--dark,
body[data-theme=monochrome].body--dark,
[data-theme=monochrome] .q-menu--dark {
background: #000 !important;
}
/* IF WANTING TO SET A DARKER BG COLOR IN THE FUTURE
// set a darker body bg for all themes, when in "dark mode"
body[data-theme='monochrome'].body--dark {
background: scale-color($color, $lightness: -60%);
}
*/
[data-theme=monochrome] .q-card--dark,
[data-theme=monochrome] .q-stepper--dark {
background: rgb(39, 39, 39) !important;
}
[data-theme=monochrome] .bg-primary {
background: #494949 !important;
}
[data-theme=monochrome] .text-primary {
color: #494949 !important;
}
[data-theme=monochrome] .bg-secondary {
background: #6b6b6b !important;
}
[data-theme=monochrome] .text-secondary {
color: #6b6b6b !important;
}
[data-theme=monochrome] .bg-dark {
background: #000 !important;
}
[data-theme=monochrome] .text-dark {
color: #000 !important;
}
[data-theme=monochrome] .bg-info {
background: rgb(39, 39, 39) !important;
}
[data-theme=monochrome] .text-info {
color: rgb(39, 39, 39) !important;
}
[data-theme=monochrome] .bg-marginal-bg {
background: #000 !important;
}
[data-theme=monochrome] .text-marginal-bg {
color: #000 !important;
}
[data-theme=monochrome] .bg-marginal-text {
background: rgb(255, 255, 255) !important;
}
[data-theme=monochrome] .text-marginal-text {
color: rgb(255, 255, 255) !important;
}
[data-theme=freedom] .q-drawer--dark {
background: #0a0a0a !important;
}
[data-theme=freedom] .q-header {
background: #0a0a0a !important;
}
[data-theme=cyber] .q-drawer--dark {
background: #0a0a0a !important;
}
[data-theme=cyber] .q-header {
background: #0a0a0a !important;
}
[data-theme=salvador] .q-drawer--dark {
background: #242424 !important;
}
[data-theme=salvador] .q-header {
background: #0f47af !important;
}
[data-theme=flamingo] .q-drawer--dark {
background: #e75480 !important;
}
[data-theme=flamingo] .q-header {
background: #e75480 !important;
}
[v-cloak] {
display: none;
}
body.body--dark .q-table--dark {
background: transparent;
}
body.body--dark .q-field--error .text-negative,
body.body--dark .q-field--error .q-field__messages {
color: yellow !important;
}
.lnbits-drawer__q-list .q-item {
padding-top: 5px !important;
padding-bottom: 5px !important;
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
.lnbits-drawer__q-list .q-item.q-item--active {
color: inherit;
font-weight: bold;
}
.lnbits__dialog-card {
width: 500px;
}
.q-table--dense th:first-child,
.q-table--dense td:first-child,
.q-table--dense .q-table__bottom {
padding-left: 6px !important;
}
.q-table--dense th:last-child,
.q-table--dense td:last-child,
.q-table--dense .q-table__bottom {
padding-right: 6px !important;
}
a.inherit {
color: inherit;
text-decoration: none;
}
video {
border-radius: 3px;
}
@font-face {
font-family: "Material Icons";
font-style: normal;
font-weight: 400;
src: url(/static/fonts/material-icons-v50.woff2) format("woff2");
}
.material-icons {
font-family: "Material Icons";
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-moz-font-feature-settings: "liga";
-moz-osx-font-smoothing: grayscale;
}
.q-rating__icon {
font-size: 1em;
}
.text-wrap {
word-break: break-word;
}
.q-card code {
overflow-wrap: break-word;
}
+1
View File
@@ -37,6 +37,7 @@ window.localisation.en = {
view_swagger_docs: 'View LNbits Swagger API docs',
api_docs: 'Api docs',
commit_version: 'Commit version',
lnbits_version: 'LNbits version',
runs_on: 'Runs on',
credit_hint: 'Press Enter to credit account',
credit_label: '%{denomination} to credit',
+1 -1
View File
@@ -188,7 +188,7 @@ video {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(../fonts/material-icons-v50.woff2) format('woff2');
src: url(/static/fonts/material-icons-v50.woff2) format('woff2');
}
.material-icons {
+30
View File
@@ -0,0 +1,30 @@
{
"js": [
"/static/vendor/moment.js",
"/static/vendor/underscore.js",
"/static/vendor/axios.js",
"/static/vendor/vue.js",
"/static/vendor/vue-router.js",
"/static/vendor/vue-qrcode-reader.browser.js",
"/static/vendor/vue-qrcode.js",
"/static/vendor/vuex.js",
"/static/vendor/quasar.ie.polyfills.umd.min.js",
"/static/vendor/quasar.umd.js",
"/static/vendor/Chart.bundle.js",
"/static/vendor/vue-i18n.js",
"/static/i18n/i18n.js",
"/static/i18n/en.js",
"/static/i18n/de.js",
"/static/i18n/es.js",
"/static/i18n/jp.js",
"/static/js/base.js",
"/static/js/components.js",
"/static/js/bolt11-decoder.js"
],
"css": [
"/static/vendor/quasar.css",
"/static/vendor/Chart.css",
"/static/vendor/vue-qrcode-reader.css",
"/static/css/base.css"
]
}
+9 -17
View File
@@ -2,12 +2,9 @@
<html lang="en">
<head>
{% for url in VENDORED_CSS %}
{% for url in INCLUDED_CSS %}
<link rel="stylesheet" type="text/css" href="{{ url }}" />
{% endfor %}
<!---->
<link rel="stylesheet" type="text/css" href="/static/css/base.css" />
{% block styles %}{% endblock %}
{% endfor %} {% block styles %}{% endblock %}
<title>{% block title %}{{ SITE_TITLE }}{% endblock %}</title>
<meta charset="utf-8" />
<meta
@@ -243,10 +240,13 @@
<q-toolbar-title class="text-caption">
{{ SITE_TITLE }}, {{SITE_TAGLINE}}
<br />
<small v-if="'{{SITE_TITLE}}' == 'LNbits'"
<small
>{% raw %}{{ $t('lnbits_version') }}{% endraw %}:
{{LNBITS_VERSION}},
</small>
<small
>{% raw %}{{ $t('commit_version') }}{% endraw %}:
{{LNBITS_VERSION}}</small
{{COMMIT_VERSION}}</small
>
</q-toolbar-title>
<q-space></q-space>
@@ -284,18 +284,10 @@
{% block vue_templates %}{% endblock %}
<!---->
{% for url in VENDORED_JS %}
{% for url in INCLUDED_JS %}
<script src="{{ url }}"></script>
{% endfor %}
<!---->
<script src="/static/i18n/i18n.js"></script>
<script src="/static/i18n/en.js"></script>
<script src="/static/i18n/de.js"></script>
<script src="/static/i18n/es.js"></script>
<script src="/static/i18n/jp.js"></script>
<script src="/static/js/base.js"></script>
<script src="/static/js/components.js"></script>
<script src="/static/js/bolt11-decoder.js"></script>
<script type="text/javascript">
const themes = {{ LNBITS_THEME_OPTIONS | tojson }}
const LNBITS_DENOMINATION = {{ LNBITS_DENOMINATION | tojson}}
+2 -2
View File
@@ -2,7 +2,7 @@
<html lang="en">
<head>
{% for url in VENDORED_CSS %}
{% for url in INCLUDED_CSS %}
<link rel="stylesheet" type="text/css" href="{{ url }}" />
{% endfor %}
<style>
@@ -33,7 +33,7 @@
</q-page-container>
</q-layout>
{% for url in VENDORED_JS %}
{% for url in INCLUDED_JS %}
<script src="{{ url }}"></script>
{% endfor %}
<!---->
+1328 -642
View File
File diff suppressed because it is too large Load Diff
+77 -18
View File
@@ -1,20 +1,79 @@
{
"name": "lnbits",
"devDependencies": {
"prettier": "2.8.3",
"pyright": "1.1.289"
},
"dependencies": {
"@chenfengyuan/vue-qrcode": "1.0.2",
"axios": "^1.3.4",
"chart.js": "2.9",
"moment": "^2.29.4",
"quasar": "1.13.2",
"underscore": "^1.13.6",
"vue": "2.6.12",
"vue-i18n": "^8.28.2",
"vue-qrcode-reader": "2.2",
"vue-router": "3.4.3",
"vuex": "3.5.1"
}
"name": "lnbits",
"scripts": {
"sass": "./node_modules/.bin/sass ./lnbits/static/scss/base.scss > ./lnbits/static/css/base.css",
"vendor_copy": "node -e \"require('./package.json').vendor.forEach((file) => require('fs').copyFileSync(file, './lnbits/static/vendor/'+file.split('/').pop()))\"",
"vendor_json": "node -e \"require('fs').writeFileSync('./lnbits/static/vendor.json', JSON.stringify(require('./package.json').bundle))\"",
"vendor_bundle_css": "node -e \"require('concat')(require('./package.json').bundle.css.map(a => './lnbits/'+a), './lnbits/static/bundle.css')\"",
"vendor_bundle_js": "node -e \"require('concat')(require('./package.json').bundle.js.map(a => './lnbits/'+a), './lnbits/static/bundle.js')\"",
"vendor_minify_css": "./node_modules/.bin/minify ./lnbits/static/bundle.css > ./lnbits/static/bundle.min.css",
"vendor_minify_js": "./node_modules/.bin/minify ./lnbits/static/bundle.js > ./lnbits/static/bundle.min.js"
},
"devDependencies": {
"concat": "^1.0.3",
"minify": "^9.2.0",
"sass": "^1.60.0",
"prettier": "2.8.3",
"pyright": "1.1.289"
},
"dependencies": {
"@chenfengyuan/vue-qrcode": "1.0.2",
"axios": "^1.3.4",
"chart.js": "2.9",
"moment": "^2.29.4",
"quasar": "1.13.2",
"underscore": "^1.13.6",
"vue": "2.6.12",
"vue-i18n": "^8.28.2",
"vue-qrcode-reader": "2.2",
"vue-router": "3.4.3",
"vuex": "3.5.1"
},
"vendor": [
"./node_modules/moment/moment.js",
"./node_modules/underscore/underscore.js",
"./node_modules/axios/dist/axios.js",
"./node_modules/vue/dist/vue.js",
"./node_modules/vue-router/dist/vue-router.js",
"./node_modules/vue-qrcode-reader/dist/vue-qrcode-reader.browser.js",
"./node_modules/@chenfengyuan/vue-qrcode/dist/vue-qrcode.js",
"./node_modules/vuex/dist/vuex.js",
"./node_modules/quasar/dist/quasar.ie.polyfills.umd.min.js",
"./node_modules/quasar/dist/quasar.umd.js",
"./node_modules/chart.js/dist/Chart.bundle.js",
"./node_modules/quasar/dist/quasar.css",
"./node_modules/chart.js/dist/Chart.css",
"./node_modules/vue-qrcode-reader/dist/vue-qrcode-reader.css",
"./node_modules/vue-i18n/dist/vue-i18n.js"
],
"bundle": {
"js": [
"/static/vendor/moment.js",
"/static/vendor/underscore.js",
"/static/vendor/axios.js",
"/static/vendor/vue.js",
"/static/vendor/vue-router.js",
"/static/vendor/vue-qrcode-reader.browser.js",
"/static/vendor/vue-qrcode.js",
"/static/vendor/vuex.js",
"/static/vendor/quasar.ie.polyfills.umd.min.js",
"/static/vendor/quasar.umd.js",
"/static/vendor/Chart.bundle.js",
"/static/vendor/vue-i18n.js",
"/static/i18n/i18n.js",
"/static/i18n/en.js",
"/static/i18n/de.js",
"/static/i18n/es.js",
"/static/i18n/jp.js",
"/static/js/base.js",
"/static/js/components.js",
"/static/js/bolt11-decoder.js"
],
"css": [
"/static/vendor/quasar.css",
"/static/vendor/Chart.css",
"/static/vendor/vue-qrcode-reader.css",
"/static/css/base.css"
]
}
}
Generated
+6 -68
View File
@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry and should not be changed by hand.
# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
[[package]]
name = "anyio"
@@ -35,14 +35,14 @@ files = [
[[package]]
name = "astroid"
version = "2.15.2"
version = "2.15.3"
description = "An abstract syntax tree for Python with inference support."
category = "dev"
optional = false
python-versions = ">=3.7.2"
files = [
{file = "astroid-2.15.2-py3-none-any.whl", hash = "sha256:dea89d9f99f491c66ac9c04ebddf91e4acf8bd711722175fe6245c0725cc19bb"},
{file = "astroid-2.15.2.tar.gz", hash = "sha256:6e61b85c891ec53b07471aec5878f4ac6446a41e590ede0f2ce095f39f7d49dd"},
{file = "astroid-2.15.3-py3-none-any.whl", hash = "sha256:f11e74658da0f2a14a8d19776a8647900870a63de71db83713a8e77a6af52662"},
{file = "astroid-2.15.3.tar.gz", hash = "sha256:44224ad27c54d770233751315fa7f74c46fa3ee0fab7beef1065f99f09897efe"},
]
[package.dependencies]
@@ -160,24 +160,6 @@ d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "boltz-client"
version = "0.1.3"
description = "python boltz client"
category = "main"
optional = false
python-versions = ">=3.7,<4.0"
files = [
{file = "boltz_client-0.1.3-py3-none-any.whl", hash = "sha256:67a231de6cc0876376e32aa177eb572ef14e869a645ef4565f307daa527f1e76"},
{file = "boltz_client-0.1.3.tar.gz", hash = "sha256:1573be84ea547578591d78f5f1ea65ccda01edcca0aa6f077bcd5a497622cef5"},
]
[package.dependencies]
click = ">=8"
embit = ">=0.4"
httpx = ">=0.23"
websockets = ">=10"
[[package]]
name = "cashu"
version = "0.9.0"
@@ -586,19 +568,6 @@ files = [
{file = "embit-0.4.9.tar.gz", hash = "sha256:992332bd89af6e2d027e26fe437eb14aa33997db08c882c49064d49c3e6f4ab9"},
]
[[package]]
name = "enum34"
version = "1.1.10"
description = "Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "enum34-1.1.10-py2-none-any.whl", hash = "sha256:a98a201d6de3f2ab3db284e70a33b0f896fbf35f8086594e8c9e74b909058d53"},
{file = "enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328"},
{file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"},
]
[[package]]
name = "environs"
version = "9.5.0"
@@ -1144,21 +1113,6 @@ files = [
{file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"},
]
[[package]]
name = "pathlib2"
version = "2.3.7.post1"
description = "Object-oriented filesystem paths"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"},
{file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"},
]
[package.dependencies]
six = "*"
[[package]]
name = "pathspec"
version = "0.11.1"
@@ -1490,22 +1444,6 @@ files = [
[package.extras]
png = ["pypng (>=0.0.13)"]
[[package]]
name = "pyscss"
version = "1.4.0"
description = "pyScss, a Scss compiler for Python"
category = "main"
optional = false
python-versions = "*"
files = [
{file = "pyScss-1.4.0.tar.gz", hash = "sha256:8f35521ffe36afa8b34c7d6f3195088a7057c185c2b8f15ee459ab19748669ff"},
]
[package.dependencies]
enum34 = "*"
pathlib2 = "*"
six = "*"
[[package]]
name = "pysocks"
version = "1.7.1"
@@ -2162,5 +2100,5 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
[metadata]
lock-version = "2.0"
python-versions = "^3.10 | ^3.9 | ^3.8.1"
content-hash = "d8cae4bcda9fcc8db58c08e1027d1e735dfe87e77584d7763348a6183c0fbad8"
python-versions = "^3.10 | ^3.9"
content-hash = "175f26c804fefbac97f2ddaff2132e004bb105fe37761eb52cc9e5ed377eddf3"
+3 -9
View File
@@ -1,15 +1,11 @@
[tool.poetry]
name = "lnbits"
version = "0.10.3"
version = "0.10.5"
description = "LNbits, free and open-source Lightning wallet and accounts system."
authors = ["Alan Bits <alan@lnbits.com>"]
[tool.poetry.build]
generate-setup-file = false
script = "tools/build.py"
[tool.poetry.dependencies]
python = "^3.10 | ^3.9 | ^3.8.1"
python = "^3.10 | ^3.9"
bech32 = "1.2.0"
bitstring = "3.1.9"
click = "8.0.4"
@@ -22,7 +18,6 @@ lnurl = "0.3.6"
psycopg2-binary = "2.9.1"
pydantic = "1.10.4"
pyqrcode = "1.2.1"
pyScss = "1.4.0"
shortuuid = "1.0.11"
sqlalchemy = "1.3.24"
sqlalchemy-aio = "0.17.0"
@@ -37,7 +32,6 @@ protobuf = "4.21.12"
Cerberus = "1.3.4"
async-timeout = "4.0.2"
pyln-client = "0.11.1"
boltz-client = "0.1.3"
cashu = "0.9.0"
[tool.poetry.group.dev.dependencies]
@@ -53,7 +47,7 @@ types-protobuf = "^3.19.22"
pylint = "^2.17.2"
[build-system]
requires = ["poetry-core>=1.0.0", "pyscss"]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
-63
View File
@@ -1,63 +0,0 @@
import os
import warnings
from pathlib import Path
LNBITS_PATH = Path("lnbits").absolute()
# from ..lnbits.helpers import vendored_js, vendored_css
vendored_js = [
"/static/vendor/moment.js",
"/static/vendor/underscore.js",
"/static/vendor/axios.js",
"/static/vendor/vue.js",
"/static/vendor/vue-router.js",
"/static/vendor/vue-qrcode-reader.browser.js",
"/static/vendor/vue-qrcode.js",
"/static/vendor/vue-i18n.js",
"/static/vendor/vuex.js",
"/static/vendor/quasar.ie.polyfills.umd.min.js",
"/static/vendor/quasar.umd.js",
"/static/vendor/Chart.bundle.js",
]
vendored_css = [
"/static/vendor/quasar.css",
"/static/vendor/Chart.css",
"/static/vendor/vue-qrcode-reader.css",
]
def url_for_vendored(abspath: str) -> str:
return f"/{os.path.relpath(abspath, LNBITS_PATH)}"
def transpile_scss():
with warnings.catch_warnings():
warnings.simplefilter("ignore")
from scss.compiler import compile_string # type: ignore
with open(os.path.join(LNBITS_PATH, "static/scss/base.scss")) as scss:
with open(os.path.join(LNBITS_PATH, "static/css/base.css"), "w") as css:
css.write(compile_string(scss.read()))
def bundle_vendored():
for files, outputpath in [
(vendored_js, os.path.join(LNBITS_PATH, "static/bundle.js")),
(vendored_css, os.path.join(LNBITS_PATH, "static/bundle.css")),
]:
output = ""
for path in files:
with open(f"{LNBITS_PATH}{path}") as f:
output += f.read() + ";\n"
with open(outputpath, "w") as f:
f.write(output)
def build():
transpile_scss()
bundle_vendored()
if __name__ == "__main__":
build()