@lazycatcloud/lzc-cli 1.3.17 → 2.0.0-pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +47 -7
  2. package/changelog.md +14 -0
  3. package/lib/app/apkshell.js +7 -44
  4. package/lib/app/index.js +178 -64
  5. package/lib/app/lpk_build.js +446 -61
  6. package/lib/app/lpk_build_images.js +749 -0
  7. package/lib/app/lpk_create.js +192 -45
  8. package/lib/app/lpk_create_generator.js +141 -13
  9. package/lib/app/lpk_devshell.js +33 -19
  10. package/lib/app/lpk_embed_images.js +257 -0
  11. package/lib/app/lpk_installer.js +17 -9
  12. package/lib/app/manifest_build.js +259 -0
  13. package/lib/app/project_cp.js +59 -0
  14. package/lib/app/project_deploy.js +58 -0
  15. package/lib/app/project_exec.js +82 -0
  16. package/lib/app/project_info.js +106 -0
  17. package/lib/app/project_log.js +62 -0
  18. package/lib/app/project_runtime.js +356 -0
  19. package/lib/app/project_start.js +95 -0
  20. package/lib/app/project_sync.js +499 -0
  21. package/lib/appstore/apkshell.js +50 -0
  22. package/lib/box/index.js +101 -4
  23. package/lib/box/ssh_remote.js +259 -0
  24. package/lib/build_remote.js +21 -0
  25. package/lib/debug_bridge.js +891 -83
  26. package/lib/docker/index.js +30 -10
  27. package/lib/i18n/locales/en/translation.json +262 -255
  28. package/lib/i18n/locales/zh/translation.json +262 -255
  29. package/lib/lpk/core.js +488 -0
  30. package/lib/lpk/index.js +210 -0
  31. package/lib/migrate/index.js +52 -0
  32. package/lib/package_info.js +135 -0
  33. package/lib/shellapi.js +35 -1
  34. package/lib/sig/core.js +254 -0
  35. package/lib/sig/index.js +88 -0
  36. package/lib/utils.js +94 -15
  37. package/package.json +3 -3
  38. package/scripts/cli.js +6 -0
  39. package/scripts/smoke/frontend-dev-entry.mjs +104 -0
  40. package/scripts/smoke/template-project.mjs +311 -0
  41. package/template/_lpk/README.md +15 -4
  42. package/template/_lpk/gui-vnc.manifest.yml.in +18 -0
  43. package/template/_lpk/hello-vue.manifest.yml.in +38 -0
  44. package/template/_lpk/manifest.yml.in +4 -11
  45. package/template/_lpk/package.yml.in +7 -0
  46. package/template/_lpk/todolist-golang.manifest.yml.in +30 -0
  47. package/template/_lpk/todolist-java.manifest.yml.in +29 -0
  48. package/template/_lpk/todolist-python.manifest.yml.in +37 -0
  49. package/template/_lpk/todolist-serverless.manifest.yml.in +38 -0
  50. package/template/_lpk/vue.lzc-build.yml.in +0 -44
  51. package/template/blank/lzc-build.dev.yml +4 -0
  52. package/template/blank/lzc-build.yml +24 -41
  53. package/template/blank/lzc-manifest.yml +7 -9
  54. package/template/blank/package.yml +7 -0
  55. package/template/golang/Dockerfile +19 -0
  56. package/template/golang/Dockerfile.dev +20 -0
  57. package/template/golang/README.md +44 -0
  58. package/template/golang/_gitignore +3 -0
  59. package/template/golang/_lzcdevignore +21 -0
  60. package/template/golang/go.mod +3 -0
  61. package/template/golang/lzc-build.dev.yml +12 -0
  62. package/template/golang/lzc-build.yml +16 -0
  63. package/template/golang/lzc-icon.png +0 -0
  64. package/template/golang/main.go +252 -0
  65. package/template/golang/manifest.dev.page.js +24 -0
  66. package/template/golang/run.sh +10 -0
  67. package/template/golang/web/index.html +238 -0
  68. package/template/gui-vnc/README.md +23 -0
  69. package/template/gui-vnc/_gitignore +2 -0
  70. package/template/gui-vnc/images/Dockerfile +30 -0
  71. package/template/gui-vnc/images/kasmvnc.yaml +33 -0
  72. package/template/gui-vnc/images/startup-script.desktop +9 -0
  73. package/template/gui-vnc/images/startup-script.sh +6 -0
  74. package/template/gui-vnc/lzc-build.dev.yml +4 -0
  75. package/template/gui-vnc/lzc-build.yml +18 -0
  76. package/template/gui-vnc/lzc-icon.png +0 -0
  77. package/template/python/Dockerfile +15 -0
  78. package/template/python/Dockerfile.dev +18 -0
  79. package/template/python/README.md +50 -0
  80. package/template/python/_gitignore +3 -0
  81. package/template/python/_lzcdevignore +21 -0
  82. package/template/python/app.py +110 -0
  83. package/template/python/lzc-build.dev.yml +12 -0
  84. package/template/python/lzc-build.yml +16 -0
  85. package/template/python/lzc-icon.png +0 -0
  86. package/template/python/manifest.dev.page.js +25 -0
  87. package/template/python/requirements.txt +1 -0
  88. package/template/python/run.sh +14 -0
  89. package/template/python/web/index.html +238 -0
  90. package/template/springboot/Dockerfile +20 -0
  91. package/template/springboot/Dockerfile.dev +20 -0
  92. package/template/springboot/README.md +44 -0
  93. package/template/springboot/_gitignore +3 -0
  94. package/template/springboot/_lzcdevignore +21 -0
  95. package/template/springboot/lzc-build.dev.yml +12 -0
  96. package/template/springboot/lzc-build.yml +16 -0
  97. package/template/springboot/lzc-icon.png +0 -0
  98. package/template/springboot/manifest.dev.page.js +24 -0
  99. package/template/springboot/pom.xml +38 -0
  100. package/template/springboot/run.sh +10 -0
  101. package/template/springboot/src/main/java/cloud/lazycat/app/Application.java +132 -0
  102. package/template/springboot/src/main/resources/application.properties +1 -0
  103. package/template/springboot/src/main/resources/static/index.html +238 -0
  104. package/template/vue/README.md +18 -21
  105. package/template/vue/lzc-build.dev.yml +7 -0
  106. package/template/vue/lzc-build.yml +30 -43
  107. package/template/vue/manifest.dev.page.js +50 -0
  108. package/template/vue/src/App.vue +36 -25
  109. package/template/vue/src/style.css +106 -49
  110. package/template/vue-minidb/README.md +26 -0
  111. package/template/vue-minidb/_gitignore +25 -0
  112. package/template/vue-minidb/index.html +13 -0
  113. package/template/vue-minidb/lzc-build.dev.yml +7 -0
  114. package/template/vue-minidb/lzc-build.yml +46 -0
  115. package/template/vue-minidb/lzc-icon.png +0 -0
  116. package/template/vue-minidb/manifest.dev.page.js +50 -0
  117. package/template/vue-minidb/package.json +21 -0
  118. package/template/vue-minidb/public/vite.svg +1 -0
  119. package/template/vue-minidb/src/App.vue +206 -0
  120. package/template/vue-minidb/src/assets/vue.svg +1 -0
  121. package/template/vue-minidb/src/main.ts +5 -0
  122. package/template/vue-minidb/src/style.css +136 -0
  123. package/template/vue-minidb/src/vite-env.d.ts +1 -0
  124. package/template/vue-minidb/tsconfig.app.json +24 -0
  125. package/template/vue-minidb/tsconfig.json +7 -0
  126. package/template/vue-minidb/tsconfig.node.json +22 -0
  127. package/template/vue-minidb/vite.config.ts +10 -0
  128. /package/template/{vue → vue-minidb}/src/components/HelloWorld.vue +0 -0
@@ -0,0 +1,18 @@
1
+ FROM registry.lazycat.cloud/lzc/lzcapp:3.20.3
2
+
3
+ RUN apk add --no-cache python3 py3-pip
4
+ WORKDIR /app
5
+
6
+ ENV LZC_PY_DEV=1
7
+ ENV PYTHONUNBUFFERED=1
8
+
9
+ COPY requirements.txt /app/requirements.txt
10
+ RUN pip3 install --no-cache-dir -r /app/requirements.txt --break-system-packages
11
+
12
+ COPY app.py /app/app.py
13
+ COPY run.sh /app/run.sh
14
+ RUN chmod +x /app/run.sh
15
+ COPY web /app/web
16
+
17
+ EXPOSE 3000
18
+ CMD ["/app/run.sh"]
@@ -0,0 +1,50 @@
1
+ # Lazycat Python Todo App
2
+
3
+ ## First Deploy
4
+ ```bash
5
+ lzc-cli project deploy
6
+ lzc-cli project info
7
+ ```
8
+
9
+ By default, project commands use `lzc-build.dev.yml` when it exists.
10
+ Each command prints the active `Build config`.
11
+ Use `--release` if you want to operate on `lzc-build.yml`.
12
+
13
+ Open the app first.
14
+ In dev mode, the Python service starts automatically.
15
+ If the page still shows the backend dev guide, it will tell you the expected port and next step.
16
+
17
+ ## Recommended Backend Dev Loop
18
+ ```bash
19
+ lzc-cli project sync --watch
20
+ ```
21
+
22
+ The dev image starts the Python service automatically and reloads on source changes.
23
+ If you changed dependencies or need to restart the process manually:
24
+
25
+ ```bash
26
+ lzc-cli project exec /bin/sh
27
+ /app/run.sh
28
+ ```
29
+
30
+ You can also build locally and use `lzc-cli project cp` instead of `project sync --watch`.
31
+
32
+ ## Troubleshooting
33
+ ```bash
34
+ lzc-cli project log -f
35
+ lzc-cli project exec /bin/sh
36
+ ```
37
+
38
+ ## API
39
+ ```text
40
+ GET /api/health
41
+ GET /api/todos
42
+ POST /api/todos
43
+ PUT /api/todos/{id}/toggle
44
+ DELETE /api/todos/{id}
45
+ ```
46
+
47
+ ## Data Path
48
+ ```text
49
+ /lzcapp/var/todos.json
50
+ ```
@@ -0,0 +1,3 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
@@ -0,0 +1,21 @@
1
+ .git
2
+ .git/**
3
+ node_modules
4
+ node_modules/**
5
+ .venv
6
+ .venv/**
7
+ dist
8
+ dist/**
9
+ build
10
+ build/**
11
+ __pycache__
12
+ __pycache__/**
13
+ .idea
14
+ .idea/**
15
+ .vscode
16
+ .vscode/**
17
+ .DS_Store
18
+ .lzc-cli-*
19
+ .lzc-cli-*/**
20
+ *.lpk
21
+ *.lpk.tar
@@ -0,0 +1,110 @@
1
+ import json
2
+ import os
3
+ import time
4
+ from flask import Flask, send_from_directory, request
5
+
6
+ app = Flask(__name__, static_folder="/app/web", static_url_path="")
7
+ DATA_FILE = "/lzcapp/var/todos.json"
8
+ todos = []
9
+ next_id = 1
10
+
11
+
12
+ def now_ms():
13
+ return int(time.time() * 1000)
14
+
15
+ def load_todos():
16
+ global todos, next_id
17
+ os.makedirs(os.path.dirname(DATA_FILE), exist_ok=True)
18
+ if not os.path.exists(DATA_FILE):
19
+ return
20
+ with open(DATA_FILE, "r", encoding="utf-8") as fp:
21
+ raw = fp.read().strip()
22
+ if not raw:
23
+ return
24
+ loaded = json.loads(raw)
25
+ if not isinstance(loaded, list):
26
+ raise ValueError("todos data must be a list")
27
+ todos = loaded
28
+ max_id = 0
29
+ for item in todos:
30
+ if isinstance(item, dict):
31
+ max_id = max(max_id, int(item.get("id", 0)))
32
+ next_id = max_id + 1
33
+
34
+ def save_todos():
35
+ os.makedirs(os.path.dirname(DATA_FILE), exist_ok=True)
36
+ tmp_file = DATA_FILE + ".tmp"
37
+ with open(tmp_file, "w", encoding="utf-8") as fp:
38
+ json.dump(todos, fp, ensure_ascii=False, indent=2)
39
+ os.replace(tmp_file, DATA_FILE)
40
+
41
+ load_todos()
42
+
43
+
44
+ @app.get("/")
45
+ def index():
46
+ return send_from_directory(app.static_folder, "index.html")
47
+
48
+
49
+ @app.get('/api/health')
50
+ def health():
51
+ return {'status': 'ok'}
52
+
53
+
54
+ @app.get("/api/todos")
55
+ def list_todos():
56
+ sorted_todos = sorted(todos, key=lambda item: item["updatedAt"], reverse=True)
57
+ return {"items": sorted_todos}
58
+
59
+
60
+ @app.post("/api/todos")
61
+ def add_todo():
62
+ global next_id
63
+ payload = request.get_json(silent=True) or {}
64
+ title = str(payload.get("title", "")).strip()
65
+ if not title:
66
+ return {"error": "title is required"}, 400
67
+ todo = {
68
+ "id": next_id,
69
+ "title": title,
70
+ "done": False,
71
+ "updatedAt": now_ms(),
72
+ }
73
+ next_id += 1
74
+ todos.append(todo)
75
+ try:
76
+ save_todos()
77
+ except Exception:
78
+ return {"error": "failed to persist todos"}, 500
79
+ return todo
80
+
81
+
82
+ @app.put("/api/todos/<int:todo_id>/toggle")
83
+ def toggle_todo(todo_id):
84
+ for todo in todos:
85
+ if todo["id"] == todo_id:
86
+ todo["done"] = not todo["done"]
87
+ todo["updatedAt"] = now_ms()
88
+ try:
89
+ save_todos()
90
+ except Exception:
91
+ return {"error": "failed to persist todos"}, 500
92
+ return todo
93
+ return {"error": "todo not found"}, 404
94
+
95
+
96
+ @app.delete("/api/todos/<int:todo_id>")
97
+ def delete_todo(todo_id):
98
+ for index, todo in enumerate(todos):
99
+ if todo["id"] == todo_id:
100
+ todos.pop(index)
101
+ try:
102
+ save_todos()
103
+ except Exception:
104
+ return {"error": "failed to persist todos"}, 500
105
+ return {"ok": True}
106
+ return {"error": "todo not found"}, 404
107
+
108
+
109
+ if __name__ == '__main__':
110
+ app.run(host='0.0.0.0', port=3000)
@@ -0,0 +1,12 @@
1
+ # 开发态覆盖配置
2
+
3
+ # pkg_id_suffix: append an extra package id segment for dev deploys
4
+ pkg_id_suffix: dev
5
+
6
+ envs:
7
+ - DEV_MODE=1
8
+
9
+ images:
10
+ app-runtime:
11
+ context: ./
12
+ dockerfile: ./Dockerfile.dev
@@ -0,0 +1,16 @@
1
+ # manifest: 指定 lpk 的 manifest 文件
2
+ manifest: ./lzc-manifest.yml
3
+
4
+ # pkgout: lpk 输出目录
5
+ pkgout: ./
6
+
7
+ # icon: 应用图标(png)
8
+ icon: ./lzc-icon.png
9
+
10
+ # images: 构建容器镜像并写入 lpk v2 的 images 目录
11
+ images:
12
+ app-runtime:
13
+ # context: Docker 构建上下文目录
14
+ context: ./
15
+ # dockerfile: 镜像构建文件
16
+ dockerfile: ./Dockerfile
Binary file
@@ -0,0 +1,25 @@
1
+ function renderDevPage(port) {
2
+ const title = "Python dev service is not ready";
3
+ const subtitle = "This app starts the Python dev service automatically in dev mode. If the page still shows this guide, the process is still starting or needs attention.";
4
+ const pageTitle = "Backend Dev";
5
+ const steps = [
6
+ "Sync your latest code into the container with <code>lzc-cli project sync --watch</code> or copy it with <code>lzc-cli project cp</code>.",
7
+ "Check the service log with <code>lzc-cli project log -f</code>.",
8
+ "If you need to restart the process manually, open <code>lzc-cli project exec /bin/sh</code> and run <code>/app/run.sh</code>.",
9
+ "Refresh this page after port <code>" + String(port) + "</code> is ready.",
10
+ ];
11
+ const portText = port === undefined || port === null ? "" : String(port);
12
+ const items = steps.map(function (step) {
13
+ return "<li>" + step + "</li>";
14
+ }).join("");
15
+ const portBadge = portText ? "<div class=\"pill\">Expected local port: " + portText + "</div>" : "";
16
+ return [
17
+ "<!doctype html>",
18
+ "<html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>", pageTitle, "</title><style>",
19
+ "body{margin:0;padding:32px;font-family:ui-sans-serif,system-ui,sans-serif;background:#f5f8fc;color:#112033}",
20
+ ".card{max-width:760px;margin:0 auto;background:#fff;border:1px solid #d9e4f2;border-radius:18px;padding:24px;box-shadow:0 16px 40px rgba(17,32,51,.08)}",
21
+ "h1{margin:0 0 12px;font-size:32px}.muted{color:#51627a}.pill{display:inline-block;margin:16px 0 0;padding:6px 10px;border-radius:999px;background:#eef5ff;color:#0c5dd6;font-weight:600}",
22
+ "ol{margin:18px 0 0;padding-left:20px;line-height:1.8}code{background:#f2f6fb;border-radius:8px;padding:2px 6px}button{margin-top:20px;border:0;border-radius:10px;padding:10px 14px;background:#112033;color:#fff;cursor:pointer}",
23
+ "</style></head><body><main class=\"card\"><h1>", title, "</h1><p class=\"muted\">", subtitle, "</p>", portBadge, "<ol>", items, "</ol><button onclick=\"location.reload()\">Refresh</button></main></body></html>",
24
+ ].join("");
25
+ }
@@ -0,0 +1 @@
1
+ flask==3.0.3
@@ -0,0 +1,14 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ mirror_root="/lzcapp/cache/project-mirror"
5
+ app_file="/app/app.py"
6
+ if [ -f "$mirror_root/app.py" ]; then
7
+ app_file="$mirror_root/app.py"
8
+ fi
9
+
10
+ if [ "${LZC_PY_DEV:-}" = "1" ]; then
11
+ exec python3 -m flask --app "$app_file" run --debug --host 0.0.0.0 --port 3000
12
+ fi
13
+
14
+ exec python3 "$app_file"
@@ -0,0 +1,238 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Lazycat Python Todo Template</title>
7
+ <style>
8
+ :root {
9
+ --bg-1: #f8fbff;
10
+ --bg-2: #ecf6ff;
11
+ --card: #ffffff;
12
+ --text: #142033;
13
+ --muted: #4e617b;
14
+ --line: #d8e4f2;
15
+ --brand: #0076d6;
16
+ --brand-2: #00a5a5;
17
+ }
18
+ * { box-sizing: border-box; }
19
+ body {
20
+ margin: 0;
21
+ min-height: 100vh;
22
+ display: grid;
23
+ place-items: center;
24
+ padding: 24px;
25
+ font-family: 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
26
+ background: radial-gradient(circle at top right, var(--bg-2), var(--bg-1) 42%);
27
+ color: var(--text);
28
+ }
29
+ .card {
30
+ width: min(920px, 100%);
31
+ background: var(--card);
32
+ border: 1px solid var(--line);
33
+ border-radius: 22px;
34
+ padding: 26px;
35
+ box-shadow: 0 18px 48px rgba(20, 32, 51, 0.08);
36
+ }
37
+ .tag {
38
+ margin: 0;
39
+ color: var(--brand);
40
+ font-weight: 700;
41
+ letter-spacing: .08em;
42
+ text-transform: uppercase;
43
+ font-size: .85rem;
44
+ }
45
+ h1 { margin: 10px 0 12px; font-size: clamp(1.8rem, 3.2vw, 2.7rem); }
46
+ p { margin: 0; color: var(--muted); }
47
+ .btns {
48
+ margin-top: 18px;
49
+ display: flex;
50
+ gap: 10px;
51
+ flex-wrap: wrap;
52
+ }
53
+ a {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ text-decoration: none;
58
+ border-radius: 10px;
59
+ padding: 10px 14px;
60
+ border: 1px solid var(--line);
61
+ color: var(--text);
62
+ font-weight: 600;
63
+ }
64
+ a.primary {
65
+ color: #fff;
66
+ border-color: transparent;
67
+ background: linear-gradient(135deg, var(--brand), var(--brand-2));
68
+ }
69
+ .panel {
70
+ margin-top: 22px;
71
+ border: 1px solid var(--line);
72
+ border-radius: 14px;
73
+ padding: 14px;
74
+ background: #fbfdff;
75
+ }
76
+ .sync-note {
77
+ margin-top: 8px;
78
+ font-size: .92rem;
79
+ }
80
+ .form {
81
+ margin-top: 12px;
82
+ display: flex;
83
+ gap: 10px;
84
+ }
85
+ .form input {
86
+ flex: 1;
87
+ border: 1px solid var(--line);
88
+ border-radius: 10px;
89
+ padding: 10px 12px;
90
+ min-width: 0;
91
+ }
92
+ button {
93
+ border: 1px solid var(--line);
94
+ border-radius: 10px;
95
+ padding: 9px 12px;
96
+ cursor: pointer;
97
+ background: #fff;
98
+ }
99
+ ul {
100
+ margin: 12px 0 0;
101
+ padding: 0;
102
+ list-style: none;
103
+ display: grid;
104
+ gap: 10px;
105
+ }
106
+ li {
107
+ border: 1px solid var(--line);
108
+ border-radius: 10px;
109
+ padding: 10px 12px;
110
+ display: flex;
111
+ justify-content: space-between;
112
+ align-items: center;
113
+ gap: 8px;
114
+ background: #fff;
115
+ }
116
+ label {
117
+ display: flex;
118
+ align-items: center;
119
+ gap: 8px;
120
+ }
121
+ .done {
122
+ text-decoration: line-through;
123
+ color: var(--muted);
124
+ }
125
+ .danger {
126
+ color: #a63e37;
127
+ }
128
+ @media (max-width: 640px) {
129
+ .form {
130
+ flex-direction: column;
131
+ }
132
+ li {
133
+ flex-direction: column;
134
+ align-items: flex-start;
135
+ }
136
+ }
137
+ </style>
138
+ </head>
139
+ <body>
140
+ <main class="card">
141
+ <p class="tag">Python Version</p>
142
+ <h1>Todo demo powered by Flask API</h1>
143
+ <p>
144
+ This template includes a Python backend and a simple Todo app to help you start fast.
145
+ </p>
146
+
147
+ <div class="btns">
148
+ <a class="primary" href="https://lazycat.cloud/" target="_blank" rel="noreferrer">Visit lazycat.cloud</a>
149
+ <a href="https://developer.lazycat.cloud/" target="_blank" rel="noreferrer">Open Developer Docs</a>
150
+ </div>
151
+
152
+ <section class="panel">
153
+ <strong>Todo quick demo</strong>
154
+ <p class="sync-note">Data syncs automatically across devices when opened with the same app account.</p>
155
+ <div class="form">
156
+ <input id="todoInput" placeholder="Add a task for your app kickoff" />
157
+ <button id="addBtn" type="button">Add</button>
158
+ </div>
159
+ <ul id="todoList"></ul>
160
+ </section>
161
+ </main>
162
+ <script>
163
+ const todoInput = document.getElementById('todoInput');
164
+ const addBtn = document.getElementById('addBtn');
165
+ const todoList = document.getElementById('todoList');
166
+
167
+ async function fetchTodos() {
168
+ const resp = await fetch('/api/todos');
169
+ const data = await resp.json();
170
+ return Array.isArray(data.items) ? data.items : [];
171
+ }
172
+
173
+ function renderTodos(items) {
174
+ todoList.innerHTML = '';
175
+ for (const todo of items) {
176
+ const li = document.createElement('li');
177
+
178
+ const label = document.createElement('label');
179
+ const checkbox = document.createElement('input');
180
+ checkbox.type = 'checkbox';
181
+ checkbox.checked = !!todo.done;
182
+ checkbox.addEventListener('change', async () => {
183
+ await fetch(`/api/todos/${todo.id}/toggle`, { method: 'PUT' });
184
+ await refreshTodos();
185
+ });
186
+
187
+ const span = document.createElement('span');
188
+ span.textContent = todo.title;
189
+ if (todo.done) {
190
+ span.className = 'done';
191
+ }
192
+ label.append(checkbox, span);
193
+
194
+ const delBtn = document.createElement('button');
195
+ delBtn.type = 'button';
196
+ delBtn.className = 'danger';
197
+ delBtn.textContent = 'Delete';
198
+ delBtn.addEventListener('click', async () => {
199
+ await fetch(`/api/todos/${todo.id}`, { method: 'DELETE' });
200
+ await refreshTodos();
201
+ });
202
+
203
+ li.append(label, delBtn);
204
+ todoList.appendChild(li);
205
+ }
206
+ }
207
+
208
+ async function refreshTodos() {
209
+ const items = await fetchTodos();
210
+ renderTodos(items);
211
+ }
212
+
213
+ async function addTodo() {
214
+ const title = todoInput.value.trim();
215
+ if (!title) return;
216
+ await fetch('/api/todos', {
217
+ method: 'POST',
218
+ headers: { 'Content-Type': 'application/json' },
219
+ body: JSON.stringify({ title }),
220
+ });
221
+ todoInput.value = '';
222
+ await refreshTodos();
223
+ }
224
+
225
+ addBtn.addEventListener('click', addTodo);
226
+ todoInput.addEventListener('keyup', async (event) => {
227
+ if (event.key === 'Enter') {
228
+ await addTodo();
229
+ }
230
+ });
231
+
232
+ refreshTodos();
233
+ setInterval(refreshTodos, 2500);
234
+ document.addEventListener('visibilitychange', refreshTodos);
235
+ window.addEventListener('focus', refreshTodos);
236
+ </script>
237
+ </body>
238
+ </html>
@@ -0,0 +1,20 @@
1
+ FROM registry.lazycat.cloud/lzc/lzcapp:3.20.3 AS builder
2
+
3
+ RUN apk add --no-cache openjdk17 maven
4
+ WORKDIR /workspace
5
+
6
+ COPY pom.xml ./
7
+ COPY src ./src
8
+ RUN mvn -DskipTests package
9
+
10
+ FROM registry.lazycat.cloud/lzc/lzcapp:3.20.3
11
+
12
+ RUN apk add --no-cache openjdk17-jre-headless
13
+ WORKDIR /app
14
+
15
+ COPY --from=builder /workspace/target/*.jar /app/app.jar
16
+ COPY run.sh /app/run.sh
17
+ RUN chmod +x /app/run.sh
18
+
19
+ EXPOSE 8080
20
+ CMD ["/app/run.sh"]
@@ -0,0 +1,20 @@
1
+ FROM registry.lazycat.cloud/lzc/lzcapp:3.20.3 AS builder
2
+
3
+ RUN apk add --no-cache openjdk17 maven
4
+ WORKDIR /workspace
5
+
6
+ COPY pom.xml ./
7
+ COPY src ./src
8
+ RUN mvn -DskipTests package
9
+
10
+ FROM registry.lazycat.cloud/lzc/lzcapp:3.20.3
11
+
12
+ RUN apk add --no-cache openjdk17 maven
13
+ WORKDIR /app
14
+
15
+ COPY --from=builder /workspace/target/*.jar /app/app.jar
16
+ COPY run.sh /app/run.sh
17
+ RUN chmod +x /app/run.sh
18
+
19
+ EXPOSE 8080
20
+ CMD ["sh", "-lc", "trap : TERM INT; sleep infinity & wait"]
@@ -0,0 +1,44 @@
1
+ # Lazycat Java Spring Boot Todo App
2
+
3
+ ## First Deploy
4
+ ```bash
5
+ lzc-cli project deploy
6
+ lzc-cli project info
7
+ ```
8
+
9
+ By default, project commands use `lzc-build.dev.yml` when it exists.
10
+ Each command prints the active `Build config`.
11
+ Use `--release` if you want to operate on `lzc-build.yml`.
12
+
13
+ Open the app first.
14
+ If the backend dev service is not ready yet, the app page will show the expected port and what to do next.
15
+ In dev mode, the Spring Boot service is started manually inside the app container.
16
+
17
+ ## Recommended Backend Dev Loop
18
+ ```bash
19
+ lzc-cli project sync --watch
20
+ lzc-cli project exec /bin/sh
21
+ /app/run.sh
22
+ ```
23
+
24
+ You can also build locally and use `lzc-cli project cp` instead of `project sync --watch`.
25
+
26
+ ## Troubleshooting
27
+ ```bash
28
+ lzc-cli project log -f
29
+ lzc-cli project exec /bin/sh
30
+ ```
31
+
32
+ ## API
33
+ ```text
34
+ GET /api/health
35
+ GET /api/todos
36
+ POST /api/todos
37
+ PUT /api/todos/{id}/toggle
38
+ DELETE /api/todos/{id}
39
+ ```
40
+
41
+ ## Data Path
42
+ ```text
43
+ /lzcapp/var/todos.json
44
+ ```
@@ -0,0 +1,3 @@
1
+ /target/
2
+ /.idea/
3
+ *.iml
@@ -0,0 +1,21 @@
1
+ .git
2
+ .git/**
3
+ node_modules
4
+ node_modules/**
5
+ .venv
6
+ .venv/**
7
+ dist
8
+ dist/**
9
+ build
10
+ build/**
11
+ __pycache__
12
+ __pycache__/**
13
+ .idea
14
+ .idea/**
15
+ .vscode
16
+ .vscode/**
17
+ .DS_Store
18
+ .lzc-cli-*
19
+ .lzc-cli-*/**
20
+ *.lpk
21
+ *.lpk.tar
@@ -0,0 +1,12 @@
1
+ # 开发态覆盖配置
2
+
3
+ # pkg_id_suffix: append an extra package id segment for dev deploys
4
+ pkg_id_suffix: dev
5
+
6
+ envs:
7
+ - DEV_MODE=1
8
+
9
+ images:
10
+ app-runtime:
11
+ context: ./
12
+ dockerfile: ./Dockerfile.dev
@@ -0,0 +1,16 @@
1
+ # manifest: 指定 lpk 的 manifest 文件
2
+ manifest: ./lzc-manifest.yml
3
+
4
+ # pkgout: lpk 输出目录
5
+ pkgout: ./
6
+
7
+ # icon: 应用图标(png)
8
+ icon: ./lzc-icon.png
9
+
10
+ # images: 构建容器镜像并写入 lpk v2 的 images 目录
11
+ images:
12
+ app-runtime:
13
+ # context: Docker 构建上下文目录
14
+ context: ./
15
+ # dockerfile: 镜像构建文件
16
+ dockerfile: ./Dockerfile
Binary file