@lazycatcloud/lzc-cli 1.3.13 → 2.0.0-pre.0

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 (104) hide show
  1. package/README.md +30 -5
  2. package/changelog.md +16 -0
  3. package/lib/app/index.js +174 -58
  4. package/lib/app/lpk_build.js +197 -18
  5. package/lib/app/lpk_build_images.js +728 -0
  6. package/lib/app/lpk_create.js +96 -23
  7. package/lib/app/lpk_create_generator.js +150 -12
  8. package/lib/app/lpk_devshell.js +35 -21
  9. package/lib/app/lpk_embed_images.js +257 -0
  10. package/lib/app/lpk_installer.js +15 -7
  11. package/lib/app/project_cp.js +64 -0
  12. package/lib/app/project_deploy.js +33 -0
  13. package/lib/app/project_exec.js +45 -0
  14. package/lib/app/project_info.js +106 -0
  15. package/lib/app/project_log.js +67 -0
  16. package/lib/app/project_runtime.js +261 -0
  17. package/lib/app/project_start.js +100 -0
  18. package/lib/appstore/index.js +56 -16
  19. package/lib/appstore/publish.js +16 -13
  20. package/lib/box/index.js +103 -6
  21. package/lib/box/ssh_remote.js +259 -0
  22. package/lib/build_remote.js +22 -0
  23. package/lib/config/index.js +4 -3
  24. package/lib/debug_bridge.js +837 -44
  25. package/lib/docker/index.js +30 -10
  26. package/lib/i18n/index.js +1 -0
  27. package/lib/i18n/locales/en/translation.json +263 -250
  28. package/lib/i18n/locales/zh/translation.json +57 -44
  29. package/lib/lpk/core.js +487 -0
  30. package/lib/lpk/index.js +210 -0
  31. package/lib/shellapi.js +5 -5
  32. package/lib/sig/core.js +254 -0
  33. package/lib/sig/index.js +88 -0
  34. package/lib/utils.js +17 -12
  35. package/package.json +4 -3
  36. package/scripts/cli.js +4 -0
  37. package/template/_lpk/README.md +11 -3
  38. package/template/_lpk/gui-vnc.manifest.yml.in +27 -0
  39. package/template/_lpk/manifest.yml.in +4 -2
  40. package/template/_lpk/todolist-golang.manifest.yml.in +16 -0
  41. package/template/_lpk/todolist-java.manifest.yml.in +15 -0
  42. package/template/_lpk/todolist-python.manifest.yml.in +15 -0
  43. package/template/_lpk/vue.lzc-build.yml.in +0 -44
  44. package/template/blank/_gitignore +1 -0
  45. package/template/blank/lzc-build.yml +25 -40
  46. package/template/blank/lzc-manifest.yml +14 -7
  47. package/template/golang/Dockerfile +19 -0
  48. package/template/golang/README.md +33 -0
  49. package/template/golang/_gitignore +3 -0
  50. package/template/golang/go.mod +3 -0
  51. package/template/golang/lzc-build.yml +21 -0
  52. package/template/golang/lzc-icon.png +0 -0
  53. package/template/golang/main.go +252 -0
  54. package/template/golang/run.sh +3 -0
  55. package/template/golang/web/index.html +238 -0
  56. package/template/gui-vnc/README.md +19 -0
  57. package/template/gui-vnc/_gitignore +2 -0
  58. package/template/gui-vnc/images/Dockerfile +30 -0
  59. package/template/gui-vnc/images/kasmvnc.yaml +33 -0
  60. package/template/gui-vnc/images/startup-script.desktop +9 -0
  61. package/template/gui-vnc/images/startup-script.sh +6 -0
  62. package/template/gui-vnc/lzc-build.yml +23 -0
  63. package/template/gui-vnc/lzc-icon.png +0 -0
  64. package/template/python/Dockerfile +15 -0
  65. package/template/python/README.md +33 -0
  66. package/template/python/_gitignore +3 -0
  67. package/template/python/app.py +110 -0
  68. package/template/python/lzc-build.yml +21 -0
  69. package/template/python/lzc-icon.png +0 -0
  70. package/template/python/requirements.txt +1 -0
  71. package/template/python/run.sh +3 -0
  72. package/template/python/web/index.html +238 -0
  73. package/template/springboot/Dockerfile +20 -0
  74. package/template/springboot/README.md +33 -0
  75. package/template/springboot/_gitignore +3 -0
  76. package/template/springboot/lzc-build.yml +21 -0
  77. package/template/springboot/lzc-icon.png +0 -0
  78. package/template/springboot/pom.xml +38 -0
  79. package/template/springboot/run.sh +3 -0
  80. package/template/springboot/src/main/java/cloud/lazycat/app/Application.java +132 -0
  81. package/template/springboot/src/main/resources/application.properties +1 -0
  82. package/template/springboot/src/main/resources/static/index.html +238 -0
  83. package/template/vue/README.md +17 -7
  84. package/template/vue/_gitignore +1 -0
  85. package/template/vue/lzc-build.yml +31 -42
  86. package/template/vue/src/App.vue +36 -25
  87. package/template/vue/src/style.css +106 -49
  88. package/template/vue-minidb/README.md +34 -0
  89. package/template/vue-minidb/_gitignore +26 -0
  90. package/template/vue-minidb/index.html +13 -0
  91. package/template/vue-minidb/lzc-build.yml +48 -0
  92. package/template/vue-minidb/lzc-icon.png +0 -0
  93. package/template/vue-minidb/package.json +21 -0
  94. package/template/vue-minidb/public/vite.svg +1 -0
  95. package/template/vue-minidb/src/App.vue +206 -0
  96. package/template/vue-minidb/src/assets/vue.svg +1 -0
  97. package/template/vue-minidb/src/main.ts +5 -0
  98. package/template/vue-minidb/src/style.css +136 -0
  99. package/template/vue-minidb/src/vite-env.d.ts +1 -0
  100. package/template/vue-minidb/tsconfig.app.json +24 -0
  101. package/template/vue-minidb/tsconfig.json +7 -0
  102. package/template/vue-minidb/tsconfig.node.json +22 -0
  103. package/template/vue-minidb/vite.config.ts +10 -0
  104. /package/template/{vue → vue-minidb}/src/components/HelloWorld.vue +0 -0
@@ -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 ["java", "-jar", "/app/app.jar"]
@@ -0,0 +1,33 @@
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
+ Then open the app from launcher or browser.
10
+
11
+ ## API
12
+ ```text
13
+ GET /api/health
14
+ GET /api/todos
15
+ POST /api/todos
16
+ PUT /api/todos/{id}/toggle
17
+ DELETE /api/todos/{id}
18
+ ```
19
+
20
+ ## Data Path
21
+ ```text
22
+ /lzcapp/var/todos.json
23
+ ```
24
+
25
+ ## Build LPK
26
+ ```bash
27
+ lzc-cli project build -o springboot-app.lpk
28
+ ```
29
+
30
+ ## Install
31
+ ```bash
32
+ lzc-cli lpk install springboot-app.lpk
33
+ ```
@@ -0,0 +1,3 @@
1
+ /target/
2
+ /.idea/
3
+ *.iml
@@ -0,0 +1,21 @@
1
+ # 可选基础文件: lzc-build.base.yml(与当前文件同目录,先加载再与当前文件合并)
2
+
3
+ # manifest: 指定 lpk 的 manifest 文件
4
+ manifest: ./lzc-manifest.yml
5
+
6
+ # contentdir: 需要打包进 lpk 的目录
7
+ contentdir: ./
8
+
9
+ # pkgout: lpk 输出目录
10
+ pkgout: ./
11
+
12
+ # icon: 应用图标(png)
13
+ icon: ./lzc-icon.png
14
+
15
+ # images: 构建容器镜像并写入 lpk v2 的 images 目录
16
+ images:
17
+ app-runtime:
18
+ # context: Docker 构建上下文目录
19
+ context: ./
20
+ # dockerfile: 镜像构建文件
21
+ dockerfile: ./Dockerfile
Binary file
@@ -0,0 +1,38 @@
1
+ <project xmlns="http://maven.apache.org/POM/4.0.0"
2
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4
+ <modelVersion>4.0.0</modelVersion>
5
+
6
+ <parent>
7
+ <groupId>org.springframework.boot</groupId>
8
+ <artifactId>spring-boot-starter-parent</artifactId>
9
+ <version>3.3.2</version>
10
+ <relativePath/>
11
+ </parent>
12
+
13
+ <groupId>cloud.lazycat.app</groupId>
14
+ <artifactId>springboot-app</artifactId>
15
+ <version>0.0.1</version>
16
+ <name>springboot-app</name>
17
+ <description>Spring Boot template for lzc-cli</description>
18
+
19
+ <properties>
20
+ <java.version>17</java.version>
21
+ </properties>
22
+
23
+ <dependencies>
24
+ <dependency>
25
+ <groupId>org.springframework.boot</groupId>
26
+ <artifactId>spring-boot-starter-web</artifactId>
27
+ </dependency>
28
+ </dependencies>
29
+
30
+ <build>
31
+ <plugins>
32
+ <plugin>
33
+ <groupId>org.springframework.boot</groupId>
34
+ <artifactId>spring-boot-maven-plugin</artifactId>
35
+ </plugin>
36
+ </plugins>
37
+ </build>
38
+ </project>
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ set -e
3
+ exec java -jar /app/app.jar
@@ -0,0 +1,132 @@
1
+ package cloud.lazycat.app;
2
+
3
+ import java.io.IOException;
4
+ import java.nio.file.Files;
5
+ import java.nio.file.Path;
6
+ import java.nio.file.StandardCopyOption;
7
+ import java.util.ArrayList;
8
+ import java.util.Comparator;
9
+ import java.util.List;
10
+ import java.util.Map;
11
+ import java.util.concurrent.ConcurrentHashMap;
12
+ import java.util.concurrent.atomic.AtomicLong;
13
+
14
+ import com.fasterxml.jackson.databind.ObjectMapper;
15
+
16
+ import org.springframework.boot.SpringApplication;
17
+ import org.springframework.boot.autoconfigure.SpringBootApplication;
18
+ import org.springframework.http.HttpStatus;
19
+ import org.springframework.web.bind.annotation.GetMapping;
20
+ import org.springframework.web.bind.annotation.PathVariable;
21
+ import org.springframework.web.bind.annotation.PostMapping;
22
+ import org.springframework.web.bind.annotation.PutMapping;
23
+ import org.springframework.web.bind.annotation.DeleteMapping;
24
+ import org.springframework.web.bind.annotation.RequestBody;
25
+ import org.springframework.web.bind.annotation.RestController;
26
+ import org.springframework.web.server.ResponseStatusException;
27
+
28
+ @SpringBootApplication
29
+ @RestController
30
+ public class Application {
31
+ private final Map<Long, TodoItem> todos = new ConcurrentHashMap<>();
32
+ private final AtomicLong nextId = new AtomicLong(1);
33
+ private final ObjectMapper objectMapper = new ObjectMapper();
34
+ private final Path todosFile = Path.of("/lzcapp/var/todos.json");
35
+
36
+ public Application() {
37
+ try {
38
+ loadTodos();
39
+ } catch (IOException e) {
40
+ throw new IllegalStateException("failed to load todos", e);
41
+ }
42
+ }
43
+
44
+ public static void main(String[] args) {
45
+ SpringApplication.run(Application.class, args);
46
+ }
47
+
48
+ @GetMapping("/api/health")
49
+ public Map<String, String> health() {
50
+ return Map.of("status", "ok");
51
+ }
52
+
53
+ @GetMapping("/api/todos")
54
+ public Map<String, List<TodoItem>> listTodos() {
55
+ List<TodoItem> items = new ArrayList<>(todos.values());
56
+ items.sort(Comparator.comparingLong(TodoItem::updatedAt).reversed());
57
+ return Map.of("items", items);
58
+ }
59
+
60
+ @PostMapping("/api/todos")
61
+ public TodoItem addTodo(@RequestBody TodoCreateRequest request) {
62
+ String title = request.title() == null ? "" : request.title().trim();
63
+ if (title.isEmpty()) {
64
+ throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "title is required");
65
+ }
66
+ TodoItem todo = new TodoItem(nextId.getAndIncrement(), title, false, System.currentTimeMillis());
67
+ todos.put(todo.id(), todo);
68
+ try {
69
+ persistTodos();
70
+ } catch (IOException e) {
71
+ throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "failed to persist todos");
72
+ }
73
+ return todo;
74
+ }
75
+
76
+ @PutMapping("/api/todos/{id}/toggle")
77
+ public TodoItem toggleTodo(@PathVariable("id") long id) {
78
+ TodoItem found = todos.get(id);
79
+ if (found == null) {
80
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND, "todo not found");
81
+ }
82
+ TodoItem updated = new TodoItem(found.id(), found.title(), !found.done(), System.currentTimeMillis());
83
+ todos.put(id, updated);
84
+ try {
85
+ persistTodos();
86
+ } catch (IOException e) {
87
+ throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "failed to persist todos");
88
+ }
89
+ return updated;
90
+ }
91
+
92
+ @DeleteMapping("/api/todos/{id}")
93
+ public Map<String, Boolean> deleteTodo(@PathVariable("id") long id) {
94
+ TodoItem removed = todos.remove(id);
95
+ if (removed == null) {
96
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND, "todo not found");
97
+ }
98
+ try {
99
+ persistTodos();
100
+ } catch (IOException e) {
101
+ throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "failed to persist todos");
102
+ }
103
+ return Map.of("ok", true);
104
+ }
105
+
106
+ private synchronized void loadTodos() throws IOException {
107
+ Files.createDirectories(todosFile.getParent());
108
+ if (!Files.exists(todosFile)) {
109
+ return;
110
+ }
111
+ TodoItem[] loaded = objectMapper.readValue(todosFile.toFile(), TodoItem[].class);
112
+ long maxId = 0;
113
+ for (TodoItem item : loaded) {
114
+ todos.put(item.id(), item);
115
+ maxId = Math.max(maxId, item.id());
116
+ }
117
+ nextId.set(maxId + 1);
118
+ }
119
+
120
+ private synchronized void persistTodos() throws IOException {
121
+ Files.createDirectories(todosFile.getParent());
122
+ List<TodoItem> items = new ArrayList<>(todos.values());
123
+ items.sort(Comparator.comparingLong(TodoItem::id));
124
+ Path tmpFile = Path.of(todosFile.toString() + ".tmp");
125
+ objectMapper.writerWithDefaultPrettyPrinter().writeValue(tmpFile.toFile(), items);
126
+ Files.move(tmpFile, todosFile, StandardCopyOption.REPLACE_EXISTING);
127
+ }
128
+
129
+ record TodoCreateRequest(String title) {}
130
+
131
+ record TodoItem(long id, String title, boolean done, long updatedAt) {}
132
+ }