coloco 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coloco/__init__.py +12 -0
- coloco/__main__.py +15 -0
- coloco/api.py +110 -0
- coloco/app.py +58 -0
- coloco/cli/api.py +48 -0
- coloco/cli/createapp.py +36 -0
- coloco/cli/dev.py +16 -0
- coloco/cli/node.py +49 -0
- coloco/cli/package.py +0 -0
- coloco/codegen.py +61 -0
- coloco/exceptions.py +32 -0
- coloco/lifespan.py +30 -0
- coloco/requirements.txt +6 -0
- coloco/static.py +12 -0
- coloco/templates/docker/.dockerignore-tpl +16 -0
- coloco/templates/docker/Dockerfile-tpl +59 -0
- coloco/templates/docker/docker-compose.yml-tpl +29 -0
- coloco/templates/standard/+app/404.svelte-tpl +1 -0
- coloco/templates/standard/+app/app.css-tpl +79 -0
- coloco/templates/standard/+app/app.svelte-tpl +25 -0
- coloco/templates/standard/+app/index.html-tpl +15 -0
- coloco/templates/standard/+app/index.svelte-tpl +1 -0
- coloco/templates/standard/+app/main.ts +9 -0
- coloco/templates/standard/+node/openapi-ts.config.ts-tpl +10 -0
- coloco/templates/standard/+node/package.json-tpl +24 -0
- coloco/templates/standard/+node/svelte.config.js-tpl +7 -0
- coloco/templates/standard/+node/tsconfig.json-tpl +43 -0
- coloco/templates/standard/+node/vite-env.d.ts-tpl +2 -0
- coloco/templates/standard/+node/vite.config.ts-tpl +34 -0
- coloco/templates/standard/.gitignore-tpl +6 -0
- coloco/templates/standard/README.md-tpl +3 -0
- coloco/templates/standard/example/api.py-tpl +6 -0
- coloco/templates/standard/example/index.svelte-tpl +7 -0
- coloco/templates/standard/main.py-tpl +3 -0
- coloco/templates/standard/requirements.txt-tpl +1 -0
- coloco/templates/standard/tsconfig.json-tpl +3 -0
- coloco-0.1.1.dist-info/METADATA +58 -0
- coloco-0.1.1.dist-info/RECORD +40 -0
- coloco-0.1.1.dist-info/WHEEL +4 -0
- coloco-0.1.1.dist-info/entry_points.txt +5 -0
coloco/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .app import create_app
|
|
2
|
+
from .codegen import generate_openapi_schema, generate_openapi_code
|
|
3
|
+
from .exceptions import UserError
|
|
4
|
+
from .api import api
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"api",
|
|
8
|
+
"create_app",
|
|
9
|
+
"generate_openapi_schema",
|
|
10
|
+
"generate_openapi_code",
|
|
11
|
+
"UserError",
|
|
12
|
+
]
|
coloco/__main__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from .cli.dev import dev
|
|
3
|
+
from .cli.api import app as api_app
|
|
4
|
+
from .cli.node import app as node_app
|
|
5
|
+
from .cli.createapp import createapp
|
|
6
|
+
|
|
7
|
+
app = typer.Typer()
|
|
8
|
+
|
|
9
|
+
app.command()(dev)
|
|
10
|
+
app.add_typer(node_app, name="node")
|
|
11
|
+
app.add_typer(api_app, name="api")
|
|
12
|
+
app.command()(createapp)
|
|
13
|
+
|
|
14
|
+
if __name__ == "__main__":
|
|
15
|
+
app()
|
coloco/api.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
from fastapi import APIRouter, FastAPI
|
|
3
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
4
|
+
import logging
|
|
5
|
+
from os import environ
|
|
6
|
+
from .codegen import (
|
|
7
|
+
custom_generate_unique_id,
|
|
8
|
+
generate_openapi_code,
|
|
9
|
+
generate_openapi_schema,
|
|
10
|
+
)
|
|
11
|
+
from .lifespan import execute_lifespan, register_lifespan
|
|
12
|
+
from .static import bind_static
|
|
13
|
+
from .exceptions import bind_exceptions
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logging.basicConfig(level=logging.INFO)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@asynccontextmanager
|
|
20
|
+
async def generate_openapi(app: FastAPI):
|
|
21
|
+
logging.info("Generating OpenAPI schema...")
|
|
22
|
+
generate_openapi_schema(app)
|
|
23
|
+
generate_openapi_code(host=f"http://localhost:5172", diff_files=True)
|
|
24
|
+
|
|
25
|
+
yield
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_api(is_dev: bool = False):
|
|
29
|
+
if not is_dev:
|
|
30
|
+
kwargs = {
|
|
31
|
+
"openapi_url": None,
|
|
32
|
+
"docs_url": None,
|
|
33
|
+
"redoc_url": None,
|
|
34
|
+
}
|
|
35
|
+
else:
|
|
36
|
+
kwargs = {
|
|
37
|
+
"lifespan": execute_lifespan,
|
|
38
|
+
}
|
|
39
|
+
register_lifespan(generate_openapi)
|
|
40
|
+
|
|
41
|
+
api = FastAPI(
|
|
42
|
+
generate_unique_id_function=custom_generate_unique_id,
|
|
43
|
+
**kwargs,
|
|
44
|
+
)
|
|
45
|
+
api.service = CORSMiddleware(
|
|
46
|
+
app=api,
|
|
47
|
+
allow_origins=[
|
|
48
|
+
f"http://localhost:5173",
|
|
49
|
+
"https://mysite.app",
|
|
50
|
+
],
|
|
51
|
+
allow_credentials=True,
|
|
52
|
+
allow_methods=["*"],
|
|
53
|
+
allow_headers=["*"],
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# bind_static(api)
|
|
57
|
+
bind_exceptions(api)
|
|
58
|
+
|
|
59
|
+
return api
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ========================= Global routing =========================
|
|
63
|
+
|
|
64
|
+
global_router = APIRouter()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def api(func):
|
|
68
|
+
# Auto-route
|
|
69
|
+
return _add_global_route([f"/{func.__name__}"], {}, func, "GET")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _add_global_route(args, kwargs, func, method: str):
|
|
73
|
+
# Prepend module name to path
|
|
74
|
+
path = args[0] if args else kwargs.get("path", "")
|
|
75
|
+
path = (
|
|
76
|
+
# TODO: Make this configurable
|
|
77
|
+
"/api/"
|
|
78
|
+
+ func.__module__.rsplit(".", 1)[0].replace(".", "/")
|
|
79
|
+
+ ("" if path.startswith("/") else "/")
|
|
80
|
+
+ path
|
|
81
|
+
)
|
|
82
|
+
if args:
|
|
83
|
+
args = (path, *args[1:])
|
|
84
|
+
else:
|
|
85
|
+
kwargs["path"] = path
|
|
86
|
+
|
|
87
|
+
return global_router.api_route(
|
|
88
|
+
*args,
|
|
89
|
+
**{
|
|
90
|
+
**kwargs,
|
|
91
|
+
"summary": (kwargs.get("summary", "") + f" ({func.__module__})").strip(),
|
|
92
|
+
"methods": [method],
|
|
93
|
+
},
|
|
94
|
+
)(func)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _make_route_decorator(method: str):
|
|
98
|
+
def route_wrapper(*args, **kwargs):
|
|
99
|
+
def handler_wrapper(func):
|
|
100
|
+
return _add_global_route(args, kwargs, func, method)
|
|
101
|
+
|
|
102
|
+
return handler_wrapper
|
|
103
|
+
|
|
104
|
+
return route_wrapper
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
api.get = _make_route_decorator("GET")
|
|
108
|
+
api.post = _make_route_decorator("POST")
|
|
109
|
+
api.put = _make_route_decorator("PUT")
|
|
110
|
+
api.delete = _make_route_decorator("DELETE")
|
coloco/app.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from .api import create_api, global_router
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from fastapi import FastAPI
|
|
4
|
+
from importlib import import_module
|
|
5
|
+
import os
|
|
6
|
+
from rich import print
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class ColocoApp:
|
|
11
|
+
api: FastAPI
|
|
12
|
+
name: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def find_api_files(directory):
|
|
19
|
+
api_files = []
|
|
20
|
+
try:
|
|
21
|
+
with os.scandir(directory) as entries:
|
|
22
|
+
for entry in entries:
|
|
23
|
+
if entry.is_dir():
|
|
24
|
+
# Skip directories starting with "+" and "node_modules"
|
|
25
|
+
if (
|
|
26
|
+
not entry.name.startswith("+")
|
|
27
|
+
and not entry.name == "node_modules"
|
|
28
|
+
and not entry.name == "coloco"
|
|
29
|
+
):
|
|
30
|
+
api_files.extend(find_api_files(entry.path))
|
|
31
|
+
elif entry.is_file() and entry.name == "api.py":
|
|
32
|
+
api_files.append(entry.path)
|
|
33
|
+
except (PermissionError, FileNotFoundError) as e:
|
|
34
|
+
print(f"Error accessing {directory}: {e}")
|
|
35
|
+
return api_files
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def create_app(name: str):
|
|
39
|
+
api = create_api(is_dev=True)
|
|
40
|
+
|
|
41
|
+
# Discover all api.py files from root, excluding node_modules and +app
|
|
42
|
+
api_files = find_api_files(".")
|
|
43
|
+
for api_file in api_files:
|
|
44
|
+
# convert python file path to module path
|
|
45
|
+
module_name = api_file.replace("./", "").replace(".py", "").replace("/", ".")
|
|
46
|
+
try:
|
|
47
|
+
module = import_module(module_name)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
print(f"[red]Error importing '{api_file}': {e}[/red]")
|
|
50
|
+
continue
|
|
51
|
+
# if not hasattr(module, "router"):
|
|
52
|
+
# print(f"[red]Module '{api_file}' has no router[/red]")
|
|
53
|
+
# continue
|
|
54
|
+
# api.include_router(module.router)
|
|
55
|
+
|
|
56
|
+
api.include_router(global_router)
|
|
57
|
+
|
|
58
|
+
return ColocoApp(api=api, name=name)
|
coloco/cli/api.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from importlib import import_module
|
|
2
|
+
import typer
|
|
3
|
+
from rich import print
|
|
4
|
+
import uvicorn
|
|
5
|
+
from ..app import ColocoApp
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
app = typer.Typer()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@app.command()
|
|
12
|
+
def serve(
|
|
13
|
+
app: str,
|
|
14
|
+
host: str = "127.0.0.1",
|
|
15
|
+
port: int = 80,
|
|
16
|
+
log_level: str = "info",
|
|
17
|
+
reload=False,
|
|
18
|
+
):
|
|
19
|
+
if not "." in app:
|
|
20
|
+
print(
|
|
21
|
+
"[red]App should be the name of a variable in a python file, example: main.py -> api = main.api[/red]"
|
|
22
|
+
)
|
|
23
|
+
raise typer.Abort()
|
|
24
|
+
|
|
25
|
+
module_name, var_name = app.rsplit(".", 1)
|
|
26
|
+
try:
|
|
27
|
+
module = import_module(module_name)
|
|
28
|
+
except ImportError:
|
|
29
|
+
print(f"[red]Module or python file {module_name} not found[/red]")
|
|
30
|
+
raise typer.Abort()
|
|
31
|
+
|
|
32
|
+
if not hasattr(module, var_name):
|
|
33
|
+
print(f"[red]Variable {var_name} not found in module {module_name}[/red]")
|
|
34
|
+
raise typer.Abort()
|
|
35
|
+
|
|
36
|
+
var = getattr(module, var_name)
|
|
37
|
+
|
|
38
|
+
if not isinstance(var, ColocoApp):
|
|
39
|
+
print(f"[red]{var_name} is not a ColocoApp. Please use create_app[/red]")
|
|
40
|
+
raise typer.Abort()
|
|
41
|
+
|
|
42
|
+
uvicorn.run(
|
|
43
|
+
f"{module_name}:{var_name}.api.service",
|
|
44
|
+
host=host,
|
|
45
|
+
port=port,
|
|
46
|
+
reload=reload,
|
|
47
|
+
log_level=log_level,
|
|
48
|
+
)
|
coloco/cli/createapp.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich import print
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def createapp(name: str):
|
|
8
|
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
9
|
+
template_dir = f"{current_dir}/../templates/standard"
|
|
10
|
+
install_dir = f"{os.getcwd()}/{name}"
|
|
11
|
+
print(f"Creating app {name}...")
|
|
12
|
+
print(f"App created in {install_dir}")
|
|
13
|
+
|
|
14
|
+
template_vars = {
|
|
15
|
+
"project_name": name,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# Create directory
|
|
19
|
+
os.makedirs(install_dir, exist_ok=True)
|
|
20
|
+
|
|
21
|
+
# Copy all tpl files in folders and subfolders to install_dir under their relative paths
|
|
22
|
+
for root, dirs, files in os.walk(template_dir):
|
|
23
|
+
relative_path = os.path.relpath(root, template_dir)
|
|
24
|
+
install_subdir = os.path.join(install_dir, relative_path)
|
|
25
|
+
if not os.path.exists(install_subdir):
|
|
26
|
+
os.makedirs(install_subdir)
|
|
27
|
+
for file in files:
|
|
28
|
+
if file.endswith("-tpl"):
|
|
29
|
+
with open(os.path.join(root, file), "r") as f:
|
|
30
|
+
content = f.read()
|
|
31
|
+
for key, value in template_vars.items():
|
|
32
|
+
content = content.replace(f"{{{{ {key} }}}}", value)
|
|
33
|
+
with open(
|
|
34
|
+
os.path.join(install_dir, relative_path, file[:-4]), "w"
|
|
35
|
+
) as f:
|
|
36
|
+
f.write(content)
|
coloco/cli/dev.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from rich import print
|
|
2
|
+
from .api import serve
|
|
3
|
+
from subprocess import Popen
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def dev(app: str, host: str = "127.0.0.1"):
|
|
7
|
+
node = Popen([f"npm run dev"], cwd="+node", shell=True)
|
|
8
|
+
serve(
|
|
9
|
+
app=app,
|
|
10
|
+
host=host,
|
|
11
|
+
port=5172,
|
|
12
|
+
reload=True,
|
|
13
|
+
log_level="debug",
|
|
14
|
+
)
|
|
15
|
+
node.terminate()
|
|
16
|
+
node.wait()
|
coloco/cli/node.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
import os
|
|
3
|
+
from rich import print
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
app = typer.Typer()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@app.command()
|
|
11
|
+
def install():
|
|
12
|
+
"""Installs node dependencies for the project"""
|
|
13
|
+
|
|
14
|
+
# if not exists +node/package.json, raise error
|
|
15
|
+
if not os.path.exists("+node/package.json"):
|
|
16
|
+
print(
|
|
17
|
+
"[red]Error: +node/package.json not found. Please ensure you are in a coloco project directory.[/red]"
|
|
18
|
+
)
|
|
19
|
+
raise typer.Abort()
|
|
20
|
+
|
|
21
|
+
# copy +node/package.json to /package.json
|
|
22
|
+
shutil.copyfile("+node/package.json", "package.json")
|
|
23
|
+
if os.path.exists("+node/package-lock.json"):
|
|
24
|
+
shutil.copyfile("+node/package-lock.json", "package-lock.json")
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
# run npm install
|
|
28
|
+
subprocess.run(["npm", "install"], cwd=".")
|
|
29
|
+
|
|
30
|
+
# move package.json and package-lock.json back to +node
|
|
31
|
+
shutil.move("package.json", "+node/package.json")
|
|
32
|
+
shutil.move("package-lock.json", "+node/package-lock.json")
|
|
33
|
+
except Exception as e:
|
|
34
|
+
print(f"[red]Error installing packages: {e}[/red]")
|
|
35
|
+
try:
|
|
36
|
+
os.remove("package.json")
|
|
37
|
+
os.remove("package-lock.json")
|
|
38
|
+
except Exception:
|
|
39
|
+
pass
|
|
40
|
+
raise typer.Abort()
|
|
41
|
+
|
|
42
|
+
print("[green]Packages installed successfully.[/green]")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.command()
|
|
46
|
+
def dev():
|
|
47
|
+
"""Runs the node dev server"""
|
|
48
|
+
print("[green]Running node dev server...[/green]")
|
|
49
|
+
subprocess.run(["npm", "run", "dev"], cwd="+node")
|
coloco/cli/package.py
ADDED
|
File without changes
|
coloco/codegen.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
from fastapi.routing import APIRoute
|
|
3
|
+
import json
|
|
4
|
+
from subprocess import run
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import filecmp
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def compare_and_copy(source_dir, target_dir):
|
|
12
|
+
"""
|
|
13
|
+
Compares files in two directories and copies files from source to target if they differ, creating target directories if needed.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
source_dir (str): Path to the source directory.
|
|
17
|
+
target_dir (str): Path to the target directory.
|
|
18
|
+
"""
|
|
19
|
+
for root, _, files in os.walk(source_dir):
|
|
20
|
+
rel_path = os.path.relpath(root, source_dir) # Get relative path from source
|
|
21
|
+
target_path = os.path.join(target_dir, rel_path)
|
|
22
|
+
os.makedirs(
|
|
23
|
+
target_path, exist_ok=True
|
|
24
|
+
) # Create target directory if it doesn't exist
|
|
25
|
+
|
|
26
|
+
for file in files:
|
|
27
|
+
source_file = os.path.join(root, file)
|
|
28
|
+
target_file = os.path.join(target_path, file)
|
|
29
|
+
|
|
30
|
+
if not os.path.exists(target_file) or not filecmp.cmp(
|
|
31
|
+
source_file, target_file
|
|
32
|
+
):
|
|
33
|
+
shutil.copy2(source_file, target_file)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def custom_generate_unique_id(route: APIRoute) -> str:
|
|
37
|
+
return f"{route.name}"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def generate_openapi_schema(app: FastAPI, path="/tmp/openapi.json"):
|
|
41
|
+
with open(path, "w") as f:
|
|
42
|
+
json.dump(app.openapi(), f)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def generate_openapi_code(
|
|
46
|
+
host,
|
|
47
|
+
spec_path="/tmp/openapi.json",
|
|
48
|
+
output_dir="/app/+app/.generated/client",
|
|
49
|
+
diff_files=False,
|
|
50
|
+
):
|
|
51
|
+
temp_dir = "/tmp/backend_api"
|
|
52
|
+
|
|
53
|
+
run(
|
|
54
|
+
f"npx openapi-ts "
|
|
55
|
+
f"--base {host} "
|
|
56
|
+
f"--input {spec_path} "
|
|
57
|
+
f"--output {temp_dir if diff_files else output_dir} ".split(),
|
|
58
|
+
cwd="/app/+node",
|
|
59
|
+
)
|
|
60
|
+
if diff_files:
|
|
61
|
+
compare_and_copy(temp_dir, output_dir)
|
coloco/exceptions.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
from fastapi.responses import JSONResponse
|
|
3
|
+
from fastapi.requests import Request
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class UserError(Exception):
|
|
7
|
+
status_code: int
|
|
8
|
+
code: str
|
|
9
|
+
|
|
10
|
+
def __init__(self, *args: object, status_code=400, code="bad_request") -> None:
|
|
11
|
+
super().__init__(*args)
|
|
12
|
+
self.code = code
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ServerError(Exception):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def bind_exceptions(api: FastAPI):
|
|
20
|
+
@api.exception_handler(UserError)
|
|
21
|
+
async def user_error_handler(request: Request, exc: UserError):
|
|
22
|
+
return JSONResponse(
|
|
23
|
+
status_code=exc.status_code,
|
|
24
|
+
content={"error": {"code": exc.code, "message": f"{exc}"}},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@api.exception_handler(Exception)
|
|
28
|
+
async def exception_handler(request: Request, exc: Exception):
|
|
29
|
+
return JSONResponse(
|
|
30
|
+
status_code=500,
|
|
31
|
+
content={"error": {"code": "api_error", "message": f"{exc}"}},
|
|
32
|
+
)
|
coloco/lifespan.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
from asyncio import iscoroutinefunction
|
|
3
|
+
from fastapi import FastAPI
|
|
4
|
+
|
|
5
|
+
lifespan_wrappers = []
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@asynccontextmanager
|
|
9
|
+
async def execute_lifespan(app: FastAPI):
|
|
10
|
+
contexts = []
|
|
11
|
+
|
|
12
|
+
for lifespan_handler in lifespan_wrappers:
|
|
13
|
+
generator = lifespan_handler(app)
|
|
14
|
+
contexts.append(generator)
|
|
15
|
+
if hasattr(generator, "__aenter__"):
|
|
16
|
+
await generator.__aenter__()
|
|
17
|
+
else:
|
|
18
|
+
next(generator)
|
|
19
|
+
|
|
20
|
+
yield
|
|
21
|
+
|
|
22
|
+
while contexts and (generator := contexts.pop()):
|
|
23
|
+
if hasattr(generator, "__aexit__"):
|
|
24
|
+
await generator.__aexit__(None, None, None)
|
|
25
|
+
else:
|
|
26
|
+
next(generator)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def register_lifespan(handler):
|
|
30
|
+
lifespan_wrappers.append(handler)
|
coloco/requirements.txt
ADDED
coloco/static.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
from fastapi.responses import FileResponse
|
|
3
|
+
from fastapi.staticfiles import StaticFiles
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Static Files
|
|
7
|
+
def bind_static(api: FastAPI):
|
|
8
|
+
api.mount("/static", StaticFiles(directory="static"), name="static")
|
|
9
|
+
|
|
10
|
+
@api.get("/")
|
|
11
|
+
def index():
|
|
12
|
+
return FileResponse("static/index.html")
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Dev Container
|
|
2
|
+
|
|
3
|
+
FROM python:3.12-alpine
|
|
4
|
+
|
|
5
|
+
RUN apk add --update nodejs npm
|
|
6
|
+
|
|
7
|
+
# Python Install
|
|
8
|
+
WORKDIR /app
|
|
9
|
+
ADD ./requirements.txt /app/requirements.txt
|
|
10
|
+
RUN python -m pip install -r /app/requirements.txt
|
|
11
|
+
|
|
12
|
+
# TODO: remove this when coloco is a package
|
|
13
|
+
ADD ./coloco/requirements.txt /app/coloco/requirements.txt
|
|
14
|
+
RUN python -m pip install -r /app/coloco/requirements.txt
|
|
15
|
+
|
|
16
|
+
# Node Install
|
|
17
|
+
RUN mkdir /.npm && chown -R 1000:1000 "/.npm"
|
|
18
|
+
ADD ./+node/package.json /app/package.json
|
|
19
|
+
ADD ./+node/package-lock.json /app/package-lock.json
|
|
20
|
+
RUN npm install
|
|
21
|
+
|
|
22
|
+
# Codegen
|
|
23
|
+
# RUN npm install -g @hey-api/client-fetch @hey-api/openapi-ts
|
|
24
|
+
|
|
25
|
+
ADD . /app
|
|
26
|
+
|
|
27
|
+
# ADD backend /api
|
|
28
|
+
# RUN python -c "from main import app; from coloco.codegen import generate_openapi_schema; generate_openapi_schema(app.api, '/tmp/openapi.json')"
|
|
29
|
+
|
|
30
|
+
# # Frontend Build
|
|
31
|
+
|
|
32
|
+
# FROM node:20-alpine as frontend
|
|
33
|
+
|
|
34
|
+
# WORKDIR /app
|
|
35
|
+
|
|
36
|
+
# ADD frontend/package.json /app/package.json
|
|
37
|
+
# ADD frontend/package-lock.json /app/package-lock.json
|
|
38
|
+
|
|
39
|
+
# RUN npm install
|
|
40
|
+
|
|
41
|
+
# COPY --from=backend /tmp/openapi.json /tmp/openapi.json
|
|
42
|
+
|
|
43
|
+
# RUN npx @hey-api/openapi-ts \
|
|
44
|
+
# --base https://mysite.app \
|
|
45
|
+
# --input /tmp/openapi.json \
|
|
46
|
+
# --output /app/src/backend \
|
|
47
|
+
# --client legacy/fetch
|
|
48
|
+
|
|
49
|
+
# ADD frontend /app
|
|
50
|
+
# RUN npm run build
|
|
51
|
+
|
|
52
|
+
# # Combined Image
|
|
53
|
+
|
|
54
|
+
# FROM backend
|
|
55
|
+
|
|
56
|
+
# COPY --from=frontend /app/dist /api/static
|
|
57
|
+
# ENV MODE=production
|
|
58
|
+
|
|
59
|
+
CMD ["python", "-m", "coloco", "start"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
services:
|
|
2
|
+
app:
|
|
3
|
+
build: .
|
|
4
|
+
volumes:
|
|
5
|
+
- ./:/app
|
|
6
|
+
- /app/node_modules
|
|
7
|
+
user: 1000:1000
|
|
8
|
+
ports:
|
|
9
|
+
- 5172:5172
|
|
10
|
+
- 5173:5173
|
|
11
|
+
entrypoint: python -m coloco
|
|
12
|
+
command: dev --host 0.0.0.0 main.app
|
|
13
|
+
tty: true
|
|
14
|
+
|
|
15
|
+
# Helper Scripts
|
|
16
|
+
install-local-node:
|
|
17
|
+
profiles: [ script ]
|
|
18
|
+
build: .
|
|
19
|
+
user: 1000:1000
|
|
20
|
+
volumes:
|
|
21
|
+
- ./:/app
|
|
22
|
+
command: python -m coloco node install
|
|
23
|
+
|
|
24
|
+
shell:
|
|
25
|
+
profiles: [ script ]
|
|
26
|
+
build: .
|
|
27
|
+
volumes:
|
|
28
|
+
- ./:/app
|
|
29
|
+
command: sh
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Not found!
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
3
|
+
line-height: 1.5;
|
|
4
|
+
font-weight: 400;
|
|
5
|
+
|
|
6
|
+
color-scheme: light dark;
|
|
7
|
+
color: rgba(255, 255, 255, 0.87);
|
|
8
|
+
background-color: #242424;
|
|
9
|
+
|
|
10
|
+
font-synthesis: none;
|
|
11
|
+
text-rendering: optimizeLegibility;
|
|
12
|
+
-webkit-font-smoothing: antialiased;
|
|
13
|
+
-moz-osx-font-smoothing: grayscale;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
a {
|
|
17
|
+
font-weight: 500;
|
|
18
|
+
color: #646cff;
|
|
19
|
+
text-decoration: inherit;
|
|
20
|
+
}
|
|
21
|
+
a:hover {
|
|
22
|
+
color: #535bf2;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
body {
|
|
26
|
+
margin: 0;
|
|
27
|
+
display: flex;
|
|
28
|
+
place-items: center;
|
|
29
|
+
min-width: 320px;
|
|
30
|
+
min-height: 100vh;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
h1 {
|
|
34
|
+
font-size: 3.2em;
|
|
35
|
+
line-height: 1.1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.card {
|
|
39
|
+
padding: 2em;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#app {
|
|
43
|
+
max-width: 1280px;
|
|
44
|
+
margin: 0 auto;
|
|
45
|
+
padding: 2rem;
|
|
46
|
+
text-align: center;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
button {
|
|
50
|
+
border-radius: 8px;
|
|
51
|
+
border: 1px solid transparent;
|
|
52
|
+
padding: 0.6em 1.2em;
|
|
53
|
+
font-size: 1em;
|
|
54
|
+
font-weight: 500;
|
|
55
|
+
font-family: inherit;
|
|
56
|
+
background-color: #1a1a1a;
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
transition: border-color 0.25s;
|
|
59
|
+
}
|
|
60
|
+
button:hover {
|
|
61
|
+
border-color: #646cff;
|
|
62
|
+
}
|
|
63
|
+
button:focus,
|
|
64
|
+
button:focus-visible {
|
|
65
|
+
outline: 4px auto -webkit-focus-ring-color;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@media (prefers-color-scheme: light) {
|
|
69
|
+
:root {
|
|
70
|
+
color: #213547;
|
|
71
|
+
background-color: #ffffff;
|
|
72
|
+
}
|
|
73
|
+
a:hover {
|
|
74
|
+
color: #747bff;
|
|
75
|
+
}
|
|
76
|
+
button {
|
|
77
|
+
background-color: #f9f9f9;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { setupClient } from "@coloco/client";
|
|
3
|
+
import { route, Router, getRoutes } from "@coloco/router";
|
|
4
|
+
|
|
5
|
+
import Index from "./index.svelte";
|
|
6
|
+
import NotFound from "./404.svelte";
|
|
7
|
+
|
|
8
|
+
setupClient({
|
|
9
|
+
baseUrl: "http://localhost:5172",
|
|
10
|
+
});
|
|
11
|
+
const routes = getRoutes({
|
|
12
|
+
index: Index,
|
|
13
|
+
notFound: NotFound,
|
|
14
|
+
});
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<main>
|
|
18
|
+
Main Page<br /><br />
|
|
19
|
+
|
|
20
|
+
<a use:route href="/">Home</a><br />
|
|
21
|
+
<a use:route href="/example">Example</a><br />
|
|
22
|
+
<a use:route href="/missing">Missing</a>
|
|
23
|
+
|
|
24
|
+
<Router basePath="/" {routes} />
|
|
25
|
+
</main>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>{{ project_name }}</title>
|
|
8
|
+
</head>
|
|
9
|
+
|
|
10
|
+
<body>
|
|
11
|
+
<div id="app"></div>
|
|
12
|
+
<script type="module" src="/main.ts"></script>
|
|
13
|
+
</body>
|
|
14
|
+
|
|
15
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
I am the home page!
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defaultPlugins } from '@hey-api/openapi-ts';
|
|
2
|
+
import { defineConfig } from '@coloco/codegen-svelte';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
plugins: [
|
|
6
|
+
...defaultPlugins,
|
|
7
|
+
'@hey-api/client-fetch',
|
|
8
|
+
defineConfig({ name: 'coloco-codegen', outputPath: './api' }),
|
|
9
|
+
],
|
|
10
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{ project_name }}",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@hey-api/client-fetch": "^0.8.2",
|
|
14
|
+
"@hey-api/openapi-ts": "^0.64.7",
|
|
15
|
+
"@mateothegreat/svelte5-router": "^0.1.38",
|
|
16
|
+
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
|
17
|
+
"@tsconfig/svelte": "^5.0.4",
|
|
18
|
+
"@types/node": "^22.13.4",
|
|
19
|
+
"svelte": "^5.19.6",
|
|
20
|
+
"svelte-check": "^4.1.4",
|
|
21
|
+
"typescript": "~5.7.2",
|
|
22
|
+
"vite": "^6.1.0"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"rootDirs": [
|
|
4
|
+
"..",
|
|
5
|
+
"../+app/.generated/client/api"
|
|
6
|
+
],
|
|
7
|
+
"verbatimModuleSyntax": true,
|
|
8
|
+
"isolatedModules": true,
|
|
9
|
+
"lib": [
|
|
10
|
+
"esnext",
|
|
11
|
+
"DOM",
|
|
12
|
+
"DOM.Iterable"
|
|
13
|
+
],
|
|
14
|
+
"moduleResolution": "bundler",
|
|
15
|
+
"module": "esnext",
|
|
16
|
+
"noEmit": true,
|
|
17
|
+
"target": "esnext",
|
|
18
|
+
// Project
|
|
19
|
+
"allowJs": true,
|
|
20
|
+
"checkJs": true,
|
|
21
|
+
"esModuleInterop": true,
|
|
22
|
+
"forceConsistentCasingInFileNames": true,
|
|
23
|
+
"resolveJsonModule": true,
|
|
24
|
+
"skipLibCheck": true,
|
|
25
|
+
"sourceMap": true,
|
|
26
|
+
"strict": true,
|
|
27
|
+
"paths": {
|
|
28
|
+
"@api/*": [
|
|
29
|
+
"../+app/.generated/client/api/*"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
"include": [
|
|
34
|
+
"../**/*.ts",
|
|
35
|
+
"../**/*.js",
|
|
36
|
+
"../**/*.svelte",
|
|
37
|
+
"./vite.config.ts"
|
|
38
|
+
],
|
|
39
|
+
"exclude": [
|
|
40
|
+
"../node_modules/**",
|
|
41
|
+
"../dist/**"
|
|
42
|
+
]
|
|
43
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import path, { dirname, resolve, relative } from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { defineConfig } from 'vite'
|
|
4
|
+
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const root = resolve(__dirname, '../');
|
|
8
|
+
|
|
9
|
+
// https://vite.dev/config/
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
plugins: [svelte()],
|
|
12
|
+
root: resolve(root, '+app'),
|
|
13
|
+
server: {
|
|
14
|
+
host: "0.0.0.0",
|
|
15
|
+
port: 5172,
|
|
16
|
+
},
|
|
17
|
+
clearScreen: false,
|
|
18
|
+
resolve: {
|
|
19
|
+
// @ts-ignore
|
|
20
|
+
alias: [{
|
|
21
|
+
find: './api',
|
|
22
|
+
customResolver: (_, filePath: string) => {
|
|
23
|
+
const subFolder = path.relative(root, dirname(filePath));
|
|
24
|
+
const apiFile = resolve(root, `+app/.generated/client/api/${subFolder}/api.ts`);
|
|
25
|
+
return apiFile;
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
{
|
|
30
|
+
find: '@api',
|
|
31
|
+
replacement: '/.generated/client/api'
|
|
32
|
+
}]
|
|
33
|
+
}
|
|
34
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
coloco
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: coloco
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A kit for creating FastAPI + Svelte applications
|
|
5
|
+
Author-Email: Channel Cat <channelcat@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: aerich[toml]==0.8.1
|
|
9
|
+
Requires-Dist: fastapi==0.115.0
|
|
10
|
+
Requires-Dist: PyJWT==2.10.1
|
|
11
|
+
Requires-Dist: tortoise-orm[asyncpg]==0.24.0
|
|
12
|
+
Requires-Dist: typer==0.15.2
|
|
13
|
+
Requires-Dist: uvicorn==0.31.0
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# Coloco
|
|
17
|
+
|
|
18
|
+
A kit for creating FastAPI + Svelte applications focusing on locality of code and decreased boilerplate. Create simple full-stack apps with built-in codegen. Deploy with a package that can be hosted with python or a docker container.
|
|
19
|
+
|
|
20
|
+
File-based routing for your front-end and back-end. Expose API endpoints with docs via `fastapi`. Generate a front-end with `svelte`.
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
|
|
24
|
+
`hello/api.py`
|
|
25
|
+
```python
|
|
26
|
+
from coloco import api
|
|
27
|
+
|
|
28
|
+
@api
|
|
29
|
+
def test(name: str) -> str:
|
|
30
|
+
return f"Hello {name}!"
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`hello/index.svelte`
|
|
35
|
+
```svelte
|
|
36
|
+
<script lang="ts">
|
|
37
|
+
import { test } from "./api";
|
|
38
|
+
|
|
39
|
+
const results = test({ query: { name: "DoItLive" } });
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
{#if $results.loading}
|
|
43
|
+
Loading...
|
|
44
|
+
{:else}
|
|
45
|
+
The server says {$results.data}
|
|
46
|
+
{/if}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Serves the page `myapp.com/hello`, which calls `myapp.com/hello/test?name=DoItLive` and prints the message `Hello DoItLive!`
|
|
50
|
+
|
|
51
|
+
# Opinions
|
|
52
|
+
|
|
53
|
+
This framework is opinionated and combines the following tools/concepts:
|
|
54
|
+
* FastAPI
|
|
55
|
+
* Svelte
|
|
56
|
+
* openapi-ts (codegen)
|
|
57
|
+
* file-based routing (using svelte5-router)
|
|
58
|
+
* tortoise-orm (optional)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
coloco-0.1.1.dist-info/METADATA,sha256=FLq10eRmhTIaFAmsa6lLJqzx6O3dk7sO0aVqKty6tFA,1518
|
|
2
|
+
coloco-0.1.1.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
|
|
3
|
+
coloco-0.1.1.dist-info/entry_points.txt,sha256=l_QRZQ74Kdu8aNNB5RiEEVYVrvKCHM0WdtYgUqqFAtQ,55
|
|
4
|
+
coloco/__init__.py,sha256=yYAiAeadI1L4m7Wn6trYgHVuxyn3cnZDgaqA2oSZx9g,272
|
|
5
|
+
coloco/__main__.py,sha256=wkZHdang9DYsMek093i4ROu42iZV4i-FWsiZyvtlkSQ,325
|
|
6
|
+
coloco/api.py,sha256=dXyrHSrg9ViVmB9Ue_HEBHVOiYkN77AL5GvtQ_xUb2c,2689
|
|
7
|
+
coloco/app.py,sha256=ycpeA7KdRhLz9h3pZ1XvYFZgg5TBLJsUpsyiMt8DEYk,1811
|
|
8
|
+
coloco/cli/api.py,sha256=zcfz75hwjUMOCo5N5R9LK7aZQtZPfCeJNAKnIWbnio4,1198
|
|
9
|
+
coloco/cli/createapp.py,sha256=Q3iVAFFEewQt4M1fCQr3FsFttHmZ_-qrDD2er5KDU58,1277
|
|
10
|
+
coloco/cli/dev.py,sha256=Erlr2wpZKQsZ3KD8LUZeCW2tPAT4-UhAnSGCe7NggjU,338
|
|
11
|
+
coloco/cli/node.py,sha256=2Edem4VHFlqaSzeocUB6UW9lam_zXfdHtYq9w6Lp2Qs,1449
|
|
12
|
+
coloco/cli/package.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
coloco/codegen.py,sha256=tnJWrfmKA3j02sc7xoqFJ2ppJ9myRgO6PEE6G7xIhH0,1751
|
|
14
|
+
coloco/exceptions.py,sha256=BySMyiOXD5SRJtwlGXF4jYMb9sr8-dYPjVWCTZfmipY,900
|
|
15
|
+
coloco/lifespan.py,sha256=pevEU39If_ZG4ZpIr67m6xBbuyUL4hj90m3xcrmsWl8,751
|
|
16
|
+
coloco/requirements.txt,sha256=V6EW53zuC-LEY3dNsdTB3CfUG3Abd4ydntG5p4WoFcc,110
|
|
17
|
+
coloco/static.py,sha256=JBT1INCnVxFr5Hs_NDFcbgvuPOVeBGhyFi_ghTeDsR4,321
|
|
18
|
+
coloco/templates/docker/.dockerignore-tpl,sha256=8Mh1Wn_ovoAc87fQQFzFXT9D9FLfybWXubL761LTin8,150
|
|
19
|
+
coloco/templates/docker/Dockerfile-tpl,sha256=htNfEhztkNN8KCnNfLxB85pjt7U2b0h1IfbA5OOlNe0,1390
|
|
20
|
+
coloco/templates/docker/docker-compose.yml-tpl,sha256=FwXUjjfdba-H_uQQw3d3h8prIh4VSBsvirknJWT3UBM,505
|
|
21
|
+
coloco/templates/standard/+app/404.svelte-tpl,sha256=08j-HyNRVk0_7vkDYZd7zn9EBuNUY_rIVo3hWC-kaK4,11
|
|
22
|
+
coloco/templates/standard/+app/app.css-tpl,sha256=j203I7qRmvrHhnAvht5M7E_C42uFmRUfS_oDw4QgI3M,1276
|
|
23
|
+
coloco/templates/standard/+app/app.svelte-tpl,sha256=ifSBefC8aKbAZZph9j4vmK0S1HEWURUTPQcv-nI0t98,562
|
|
24
|
+
coloco/templates/standard/+app/index.html-tpl,sha256=Xj75ChU4p2Z3xYmo29lyL-ZcdidAXxcTRqkI9Ubczl4,293
|
|
25
|
+
coloco/templates/standard/+app/index.svelte-tpl,sha256=vi1aEQc2-wbrBViLCuBwrRtYyiM3nJuxVOHsrNrkuxY,20
|
|
26
|
+
coloco/templates/standard/+app/main.ts,sha256=3eHW-F83rtlq_eViA4j9cz_iR0Iohy_yIqIUV3CsZ3k,173
|
|
27
|
+
coloco/templates/standard/+node/openapi-ts.config.ts-tpl,sha256=mSyYFM_aQgiFcYuabZLkhp5Rb7KzuVHwCEdXNTqphuM,266
|
|
28
|
+
coloco/templates/standard/+node/package.json-tpl,sha256=5r4QMJfFgokPGti5uELWf5YfzQ5JbEWWf054-Ozw_Y0,648
|
|
29
|
+
coloco/templates/standard/+node/svelte.config.js-tpl,sha256=p-Cm75sgYAkseh3qatad7bhkKtFtQaP5uK6o38ynxe0,228
|
|
30
|
+
coloco/templates/standard/+node/tsconfig.json-tpl,sha256=uuDCX91BAyZarfWxYuZPvfp7iUsFKxfne5th4bizrUY,997
|
|
31
|
+
coloco/templates/standard/+node/vite-env.d.ts-tpl,sha256=zJwBq4LNhm1ZaxEChjeihMPMpLEV0qpaAsMdLFmXf8E,71
|
|
32
|
+
coloco/templates/standard/+node/vite.config.ts-tpl,sha256=IDv90NL_kMXv3eldiD8Vr_n_nZkzcR4R0ELbT2ASNVg,892
|
|
33
|
+
coloco/templates/standard/.gitignore-tpl,sha256=yWO0ZrTXlv3Z5tLLmEKQtmaZatULdkCaC_NspJsvjuQ,56
|
|
34
|
+
coloco/templates/standard/README.md-tpl,sha256=-0YAyuEkTdTypMdb-mQw3yfwH88HhBafyn_RR3zWT1k,71
|
|
35
|
+
coloco/templates/standard/example/api.py-tpl,sha256=ITFVOSFL5rnJo1QQdC9vkJcF0Twd4khwXMEMKDFAzbQ,85
|
|
36
|
+
coloco/templates/standard/example/index.svelte-tpl,sha256=t9nGPzUMAeyJffNhFOC2pUXOIGBBsmmVr1NYSxu7Dqk,203
|
|
37
|
+
coloco/templates/standard/main.py-tpl,sha256=gYbd_nc4GqGL4phhS7ykpeG4zjJ4glSKC0ZhzhcJD_4,75
|
|
38
|
+
coloco/templates/standard/requirements.txt-tpl,sha256=VZLfR6dJPIzBonJFK-DbkLX6ShyJRYpX4K0g912pB0c,6
|
|
39
|
+
coloco/templates/standard/tsconfig.json-tpl,sha256=yux2KOqNYDboxbZfg5o-z_F9cQqkKkdAwYa86MGPc00,42
|
|
40
|
+
coloco-0.1.1.dist-info/RECORD,,
|