@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,24 @@
1
+ function renderDevPage(port) {
2
+ const title = "Backend dev service is not ready";
3
+ const subtitle = "This app is running in dev mode and expects the backend service to be started manually inside the app container.";
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
+ "Open a shell with <code>lzc-cli project exec /bin/sh</code> and start or restart the backend process with <code>/app/run.sh</code>.",
8
+ "Refresh this page after port <code>" + String(port) + "</code> is ready.",
9
+ ];
10
+ const portText = port === undefined || port === null ? "" : String(port);
11
+ const items = steps.map(function (step) {
12
+ return "<li>" + step + "</li>";
13
+ }).join("");
14
+ const portBadge = portText ? "<div class=\"pill\">Expected local port: " + portText + "</div>" : "";
15
+ return [
16
+ "<!doctype html>",
17
+ "<html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>", pageTitle, "</title><style>",
18
+ "body{margin:0;padding:32px;font-family:ui-sans-serif,system-ui,sans-serif;background:#f5f8fc;color:#112033}",
19
+ ".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)}",
20
+ "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}",
21
+ "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}",
22
+ "</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>",
23
+ ].join("");
24
+ }
@@ -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,10 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ mirror_root="/lzcapp/cache/project-mirror"
5
+ if command -v mvn >/dev/null 2>&1 && [ -f "$mirror_root/pom.xml" ]; then
6
+ cd "$mirror_root"
7
+ exec mvn spring-boot:run
8
+ fi
9
+
10
+ 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
+ }
@@ -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 Java 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">Java Version</p>
142
+ <h1>Todo demo powered by Spring Boot</h1>
143
+ <p>
144
+ This template includes a Java 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>
@@ -1,29 +1,26 @@
1
- # 懒猫云应用
1
+ # Lazycat Vue App
2
2
 
3
- ## 开发
3
+ ## First Deploy
4
+ ```bash
5
+ lzc-cli project deploy
6
+ lzc-cli project info
4
7
  ```
5
- lzc-cli project devshell
6
- ```
7
-
8
- 进入应用容器,并且自动同步当前目录内容到容器
9
- 然后就可以启动你的应用了,例如前端
10
- npm install
11
- npm run dev
12
8
 
13
- ## 构建
14
- ```
15
- lzc-cli project build -o you-awesome.lpk
16
- ```
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`.
17
12
 
18
- 会在当前的目录下构建出一个 lpk 包。
13
+ Open the app first.
14
+ If the frontend dev server is not running yet, the app page will show the expected port and next step.
19
15
 
20
- ## 安装
21
- ```
22
- lzc-cli app install you-awesome.lpk
16
+ ## Frontend Dev Loop
17
+ ```bash
18
+ npm run dev
23
19
  ```
24
20
 
25
- 会安装在你的微服应用中,安装成功后可在懒猫微服启动器中查看!
21
+ Then refresh the app page.
26
22
 
27
- ## 交流和帮助
28
-
29
- 你可以在 https://bbs.lazycat.cloud/ 畅所欲言。
23
+ ## Logs
24
+ ```bash
25
+ lzc-cli project log -f
26
+ ```
@@ -0,0 +1,7 @@
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
@@ -1,9 +1,7 @@
1
- # 整个文件中,可以通过 ${var} 的方式,使用 manifest 字段指定的文件定义的值
2
-
3
1
  # buildscript
4
2
  # - 可以为构建脚本的路径地址
5
3
  # - 如果构建命令简单,也可以直接写 sh 的命令
6
- buildscript: npm run build
4
+ buildscript: npm install && npm run build
7
5
 
8
6
  # manifest: 指定 lpk 包的 manifest.yml 文件路径
9
7
  manifest: ./lzc-manifest.yml
@@ -11,49 +9,38 @@ manifest: ./lzc-manifest.yml
11
9
  # contentdir: 指定打包的内容,将会打包到 lpk 中
12
10
  contentdir: ./dist
13
11
 
12
+ # images: 在盒子侧构建镜像并写入 lpk v2 images 目录
13
+ # - key 是 image alias
14
+ # - 在 lzc-manifest.yml 中通过 embed:alias 引用
15
+ # - dockerfile 与 dockerfile-content 二选一
16
+ # - context 可选,默认是 dockerfile 所在目录
17
+ # images: 在盒子侧构建镜像并写入 lpk v2 images 目录
18
+ # - key 是 image alias
19
+ # - 在 lzc-manifest.yml 中通过 embed:alias 引用
20
+ # - dockerfile 与 dockerfile-content 二选一
21
+ # - context 可选,默认是 dockerfile 所在目录
22
+ # images:
23
+ # my-nginx:
24
+ # context: ./image
25
+ # dockerfile: ./image/Dockerfile
26
+ # # 可选,默认 registry.lazycat.cloud
27
+ # upstream-match: registry.lazycat.cloud
28
+ # other-image:
29
+ # dockerfile: ./other/Dockerfile
30
+ # inline-image:
31
+ # context: ./
32
+ # dockerfile-content: |
33
+ # FROM alpine:3.20
34
+ # RUN echo "hello"
35
+ #
36
+ # 构建时默认是混合模式:
37
+ # - 先沿父镜像链向上找 upstream
38
+ # - 找到 upstream 后,仅 embed 非 upstream 的 layers
39
+ # - 如果未匹配到 upstream,则自动全量 embed
40
+
14
41
  # pkgout: lpk 包的输出路径
15
42
  pkgout: ./
16
43
 
17
44
  # icon 指定 lpk 包 icon 的路径路径,如果不指定将会警告
18
45
  # icon 仅仅允许 png 后缀的文件
19
46
  icon: ./lzc-icon.png
20
-
21
- # devshell 自定义应用的开发容器环境
22
- # - routes 指定应用容器的访问路由
23
-
24
- # devshell 没有指定 image 的情况,将会默认使用 registry.lazycat.cloud/lzc-cli/devshell:v0.0.5
25
- # devshell:
26
- # routes:
27
- # - /=http://127.0.0.1:8080
28
-
29
- # devshell 指定 image 的情况
30
- # devshell:
31
- # routes:
32
- # - /=http://127.0.0.1:3000
33
- # image: registry.lazycat.cloud/lzc-cli/devshell:v0.0.5
34
-
35
- # devshell 指定构建Dockerfile
36
- # image 字段如果没有定义,将默认使用 ${package}-devshell:${version}
37
- # devshell:
38
- # routes:
39
- # - /=http://127.0.0.1:3000
40
- # image: ${package}-devshell:${version}
41
- # pull_policy: build
42
- # build: .
43
-
44
- # dvshell 指定开发依赖的情况
45
- # 这种情况下,选用 alpine:latest 作为基础镜像,在 dependencies 中添加所需要的开发依赖即可
46
- # 如果 dependencies 和 build 同时存在,将会优先使用 dependencies
47
- devshell:
48
- routes:
49
- - /=http://127.0.0.1:3000
50
- dependencies:
51
- - nodejs
52
- - npm
53
- # setupscript 每次进入到app container后都会执行的配置脚本
54
- # - 可以为脚本的路径地址
55
- # - 如果构建命令简单,也可以直接写 sh 的命令
56
- # setupscript: export GOPROXY=https://goproxy.cn
57
- # setupscript: ./setupscript.sh
58
- setupscript: |
59
- export npm_config_registry=https://registry.npmmirror.com
@@ -0,0 +1,50 @@
1
+ function renderDevPage(state, port) {
2
+ const pageTitle = "Frontend Dev";
3
+ const portText = port === undefined || port === null ? "" : String(port);
4
+ const configs = {
5
+ not_linked: {
6
+ title: "Dev machine is not linked yet",
7
+ subtitle: "This app is waiting for a frontend dev server from the machine that runs lzc-cli project deploy.",
8
+ steps: [
9
+ "Run <code>lzc-cli project deploy</code> on your dev machine.",
10
+ "Start your local dev server with <code>npm run dev</code>.",
11
+ "Refresh this page after the dev server is listening on port <code>" + portText + "</code>.",
12
+ ],
13
+ },
14
+ offline: {
15
+ title: "Dev machine is offline",
16
+ subtitle: "The linked dev machine is not reachable right now.",
17
+ steps: [
18
+ "Bring the dev machine online.",
19
+ "Start your local dev server with <code>npm run dev</code>.",
20
+ "Refresh this page after port <code>" + portText + "</code> is ready.",
21
+ ],
22
+ },
23
+ not_ready: {
24
+ title: "Frontend dev server is not ready",
25
+ subtitle: "This app is running in dev mode and will proxy requests to your linked dev machine.",
26
+ steps: [
27
+ "Start your local dev server with <code>npm run dev</code>.",
28
+ "Make sure the dev server listens on port <code>" + portText + "</code>.",
29
+ "Refresh this page after the server is ready.",
30
+ ],
31
+ },
32
+ };
33
+ const config = configs[state] ?? configs.not_ready;
34
+ const title = config.title;
35
+ const subtitle = config.subtitle;
36
+ const steps = config.steps;
37
+ const items = steps.map(function (step) {
38
+ return "<li>" + step + "</li>";
39
+ }).join("");
40
+ const portBadge = portText ? "<div class=\"pill\">Expected local port: " + portText + "</div>" : "";
41
+ return [
42
+ "<!doctype html>",
43
+ "<html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>", pageTitle, "</title><style>",
44
+ "body{margin:0;padding:32px;font-family:ui-sans-serif,system-ui,sans-serif;background:#f5f8fc;color:#112033}",
45
+ ".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)}",
46
+ "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}",
47
+ "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}",
48
+ "</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>",
49
+ ].join("");
50
+ }