@lazycatcloud/lzc-cli 2.0.0-pre.0 → 2.0.0-pre.2
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.
- package/README.md +46 -7
- package/changelog.md +56 -19
- package/lib/app/apkshell.js +7 -44
- package/lib/app/index.js +5 -1
- package/lib/app/lpk_build.js +266 -56
- package/lib/app/lpk_build_images.js +424 -229
- package/lib/app/lpk_build_images_local.js +425 -0
- package/lib/app/lpk_build_images_pack_local.js +409 -0
- package/lib/app/lpk_create.js +158 -83
- package/lib/app/lpk_create_generator.js +35 -42
- package/lib/app/lpk_devshell.js +6 -2
- package/lib/app/lpk_installer.js +4 -3
- package/lib/app/manifest_build.js +259 -0
- package/lib/app/project_cp.js +5 -10
- package/lib/app/project_deploy.js +80 -11
- package/lib/app/project_exec.js +48 -11
- package/lib/app/project_info.js +59 -59
- package/lib/app/project_log.js +5 -10
- package/lib/app/project_runtime.js +113 -18
- package/lib/app/project_start.js +6 -11
- package/lib/app/project_sync.js +499 -0
- package/lib/appstore/apkshell.js +50 -0
- package/lib/appstore/publish.js +54 -15
- package/lib/build_remote.js +0 -1
- package/lib/config/index.js +1 -1
- package/lib/debug_bridge.js +217 -47
- package/lib/i18n/locales/en/translation.json +262 -262
- package/lib/i18n/locales/zh/translation.json +262 -262
- package/lib/lpk/core.js +2 -1
- package/lib/migrate/index.js +52 -0
- package/lib/package_info.js +135 -0
- package/lib/shellapi.js +35 -1
- package/lib/sig/core.js +2 -2
- package/lib/utils.js +92 -15
- package/package.json +89 -89
- package/scripts/cli.js +2 -0
- package/scripts/smoke/frontend-dev-entry.mjs +104 -0
- package/scripts/smoke/template-project.mjs +311 -0
- package/template/_lpk/README.md +6 -3
- package/template/_lpk/gui-vnc.manifest.yml.in +0 -9
- package/template/_lpk/hello-vue.manifest.yml.in +38 -0
- package/template/_lpk/manifest.yml.in +0 -9
- package/template/_lpk/package.yml.in +7 -0
- package/template/_lpk/todolist-golang.manifest.yml.in +23 -9
- package/template/_lpk/todolist-java.manifest.yml.in +23 -9
- package/template/_lpk/todolist-python.manifest.yml.in +31 -9
- package/template/_lpk/todolist-serverless.manifest.yml.in +38 -0
- package/template/blank/lzc-build.dev.yml +4 -0
- package/template/blank/lzc-build.yml +0 -2
- package/template/blank/lzc-manifest.yml +3 -12
- package/template/blank/package.yml +7 -0
- package/template/golang/Dockerfile +1 -1
- package/template/golang/Dockerfile.dev +20 -0
- package/template/golang/README.md +22 -11
- package/template/golang/_lzcdevignore +21 -0
- package/template/golang/lzc-build.dev.yml +12 -0
- package/template/golang/lzc-build.yml +0 -5
- package/template/golang/main.go +1 -1
- package/template/golang/manifest.dev.page.js +24 -0
- package/template/golang/run.sh +7 -0
- package/template/gui-vnc/README.md +5 -1
- package/template/gui-vnc/lzc-build.dev.yml +4 -0
- package/template/gui-vnc/lzc-build.yml +0 -5
- package/template/python/Dockerfile +2 -2
- package/template/python/Dockerfile.dev +18 -0
- package/template/python/README.md +28 -11
- package/template/python/_lzcdevignore +21 -0
- package/template/python/app.py +1 -1
- package/template/python/lzc-build.dev.yml +12 -0
- package/template/python/lzc-build.yml +0 -5
- package/template/python/manifest.dev.page.js +25 -0
- package/template/python/run.sh +12 -1
- package/template/springboot/Dockerfile +1 -1
- package/template/springboot/Dockerfile.dev +20 -0
- package/template/springboot/README.md +22 -11
- package/template/springboot/_lzcdevignore +21 -0
- package/template/springboot/lzc-build.dev.yml +12 -0
- package/template/springboot/lzc-build.yml +0 -5
- package/template/springboot/manifest.dev.page.js +24 -0
- package/template/springboot/run.sh +7 -0
- package/template/vue/README.md +14 -27
- package/template/vue/_gitignore +0 -1
- package/template/vue/lzc-build.dev.yml +7 -0
- package/template/vue/lzc-build.yml +0 -2
- package/template/vue/manifest.dev.page.js +50 -0
- package/template/vue/src/App.vue +1 -1
- package/template/vue-minidb/README.md +11 -19
- package/template/vue-minidb/_gitignore +0 -1
- package/template/vue-minidb/lzc-build.dev.yml +7 -0
- package/template/vue-minidb/lzc-build.yml +0 -2
- package/template/vue-minidb/manifest.dev.page.js +50 -0
- package/template/blank/_gitignore +0 -1
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const ENV_KEY_REGEXP = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
5
|
+
const BUILD_DIRECTIVE_PREFIX = '#@build';
|
|
6
|
+
|
|
7
|
+
function normalizeDirectiveValue(rawValue) {
|
|
8
|
+
const trimmed = String(rawValue ?? '').trim();
|
|
9
|
+
if (!trimmed) {
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
13
|
+
return trimmed.slice(1, -1);
|
|
14
|
+
}
|
|
15
|
+
return trimmed;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function normalizeBuildEnvEntries(rawEnvs) {
|
|
19
|
+
if (rawEnvs === undefined || rawEnvs === null) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
if (!Array.isArray(rawEnvs)) {
|
|
23
|
+
throw new Error('envs must be a string array');
|
|
24
|
+
}
|
|
25
|
+
const normalized = [];
|
|
26
|
+
const seen = new Set();
|
|
27
|
+
for (let index = 0; index < rawEnvs.length; index += 1) {
|
|
28
|
+
const rawEntry = rawEnvs[index];
|
|
29
|
+
if (typeof rawEntry !== 'string') {
|
|
30
|
+
throw new Error(`envs[${index}] must be a string`);
|
|
31
|
+
}
|
|
32
|
+
const entry = rawEntry.trim();
|
|
33
|
+
if (!entry) {
|
|
34
|
+
throw new Error(`envs[${index}] must not be empty`);
|
|
35
|
+
}
|
|
36
|
+
const equalIndex = entry.indexOf('=');
|
|
37
|
+
if (equalIndex <= 0) {
|
|
38
|
+
throw new Error(`envs[${index}] must use KEY=VALUE format`);
|
|
39
|
+
}
|
|
40
|
+
const key = entry.slice(0, equalIndex).trim();
|
|
41
|
+
const value = entry.slice(equalIndex + 1);
|
|
42
|
+
if (!ENV_KEY_REGEXP.test(key)) {
|
|
43
|
+
throw new Error(`envs[${index}] has invalid key "${key}"`);
|
|
44
|
+
}
|
|
45
|
+
if (seen.has(key)) {
|
|
46
|
+
throw new Error(`envs contains duplicated key "${key}"`);
|
|
47
|
+
}
|
|
48
|
+
seen.add(key);
|
|
49
|
+
normalized.push(`${key}=${value}`);
|
|
50
|
+
}
|
|
51
|
+
return normalized;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function buildVarsFromEnvEntries(rawEnvs) {
|
|
55
|
+
const envs = normalizeBuildEnvEntries(rawEnvs);
|
|
56
|
+
const result = {};
|
|
57
|
+
for (const entry of envs) {
|
|
58
|
+
const equalIndex = entry.indexOf('=');
|
|
59
|
+
const key = entry.slice(0, equalIndex);
|
|
60
|
+
const value = entry.slice(equalIndex + 1);
|
|
61
|
+
result[key] = value;
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseBuildDirective(line) {
|
|
67
|
+
const trimmed = String(line ?? '').trim();
|
|
68
|
+
if (!trimmed.startsWith(BUILD_DIRECTIVE_PREFIX)) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const body = trimmed.slice(BUILD_DIRECTIVE_PREFIX.length).trim();
|
|
72
|
+
if (!body) {
|
|
73
|
+
throw new Error('build directive is empty');
|
|
74
|
+
}
|
|
75
|
+
const spaceIndex = body.indexOf(' ');
|
|
76
|
+
const command = (spaceIndex >= 0 ? body.slice(0, spaceIndex) : body).trim();
|
|
77
|
+
const args = (spaceIndex >= 0 ? body.slice(spaceIndex + 1) : '').trim();
|
|
78
|
+
if (!command) {
|
|
79
|
+
throw new Error('build directive command is empty');
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
command,
|
|
83
|
+
args,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function evaluateBuildCondition(expr, context) {
|
|
88
|
+
const normalized = String(expr ?? '').trim();
|
|
89
|
+
if (!normalized) {
|
|
90
|
+
throw new Error('build if condition is empty');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let match = normalized.match(/^profile\s*(==|=|!=)\s*(.+)$/);
|
|
94
|
+
if (match) {
|
|
95
|
+
const expected = normalizeDirectiveValue(match[2]);
|
|
96
|
+
const actual = String(context.profile ?? '');
|
|
97
|
+
return match[1] === '!=' ? actual !== expected : actual === expected;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
match = normalized.match(/^env\.([A-Za-z_][A-Za-z0-9_]*)\s*(==|=|!=)\s*(.+)$/);
|
|
101
|
+
if (match) {
|
|
102
|
+
const key = match[1];
|
|
103
|
+
const expected = normalizeDirectiveValue(match[3]);
|
|
104
|
+
const actual = String(context.envs?.[key] ?? '');
|
|
105
|
+
return match[2] === '!=' ? actual !== expected : actual === expected;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
match = normalized.match(/^env\.([A-Za-z_][A-Za-z0-9_]*)$/);
|
|
109
|
+
if (match) {
|
|
110
|
+
const key = match[1];
|
|
111
|
+
return String(context.envs?.[key] ?? '').trim() !== '';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
throw new Error(`unsupported build condition "${normalized}"`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function resolveIncludePath(rawPath, rootDir) {
|
|
118
|
+
const normalized = normalizeDirectiveValue(rawPath);
|
|
119
|
+
if (!normalized) {
|
|
120
|
+
throw new Error('build include path is empty');
|
|
121
|
+
}
|
|
122
|
+
if (path.isAbsolute(normalized)) {
|
|
123
|
+
return normalized;
|
|
124
|
+
}
|
|
125
|
+
return path.resolve(rootDir, normalized);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function formatSourceRef(filePath, lineNumber) {
|
|
129
|
+
return `${filePath}:${lineNumber}`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function indentIncludedText(text, indent) {
|
|
133
|
+
if (!text) {
|
|
134
|
+
return '';
|
|
135
|
+
}
|
|
136
|
+
const prefix = String(indent ?? '');
|
|
137
|
+
return String(text)
|
|
138
|
+
.split('\n')
|
|
139
|
+
.map((line) => {
|
|
140
|
+
if (!line) {
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
return `${prefix}${line}`;
|
|
144
|
+
})
|
|
145
|
+
.join('\n');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function processManifestText(rawText, context, options) {
|
|
149
|
+
const filePath = options.filePath;
|
|
150
|
+
const rootDir = options.rootDir;
|
|
151
|
+
const allowDirectives = options.allowDirectives;
|
|
152
|
+
const lines = String(rawText ?? '').split('\n');
|
|
153
|
+
const output = [];
|
|
154
|
+
const stack = [{
|
|
155
|
+
type: 'root',
|
|
156
|
+
active: true,
|
|
157
|
+
}];
|
|
158
|
+
|
|
159
|
+
const currentActive = () => stack[stack.length - 1].active;
|
|
160
|
+
|
|
161
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
162
|
+
const line = lines[index];
|
|
163
|
+
const directive = parseBuildDirective(line);
|
|
164
|
+
if (!directive) {
|
|
165
|
+
if (currentActive()) {
|
|
166
|
+
output.push(line);
|
|
167
|
+
}
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!allowDirectives) {
|
|
172
|
+
throw new Error(`nested build directive is not allowed in included file at ${formatSourceRef(filePath, index + 1)}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
switch (directive.command) {
|
|
176
|
+
case 'if': {
|
|
177
|
+
const parentActive = currentActive();
|
|
178
|
+
const condition = evaluateBuildCondition(directive.args, context);
|
|
179
|
+
stack.push({
|
|
180
|
+
type: 'if',
|
|
181
|
+
parentActive,
|
|
182
|
+
condition,
|
|
183
|
+
elseSeen: false,
|
|
184
|
+
active: parentActive && condition,
|
|
185
|
+
});
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
case 'else': {
|
|
189
|
+
if (stack.length === 1 || stack[stack.length - 1].type !== 'if') {
|
|
190
|
+
throw new Error(`build else without matching if at ${formatSourceRef(filePath, index + 1)}`);
|
|
191
|
+
}
|
|
192
|
+
const top = stack[stack.length - 1];
|
|
193
|
+
if (top.elseSeen) {
|
|
194
|
+
throw new Error(`duplicate build else at ${formatSourceRef(filePath, index + 1)}`);
|
|
195
|
+
}
|
|
196
|
+
top.elseSeen = true;
|
|
197
|
+
top.active = top.parentActive && !top.condition;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
case 'end': {
|
|
201
|
+
if (stack.length === 1 || stack[stack.length - 1].type !== 'if') {
|
|
202
|
+
throw new Error(`build end without matching if at ${formatSourceRef(filePath, index + 1)}`);
|
|
203
|
+
}
|
|
204
|
+
stack.pop();
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case 'include': {
|
|
208
|
+
if (!currentActive()) {
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
const lineIndent = line.match(/^\s*/)?.[0] ?? '';
|
|
212
|
+
const includePath = resolveIncludePath(directive.args, rootDir);
|
|
213
|
+
if (!fs.existsSync(includePath)) {
|
|
214
|
+
throw new Error(`build include file not found at ${formatSourceRef(filePath, index + 1)}: ${includePath}`);
|
|
215
|
+
}
|
|
216
|
+
const includedText = fs.readFileSync(includePath, 'utf8');
|
|
217
|
+
const rendered = processManifestText(includedText, context, {
|
|
218
|
+
filePath: includePath,
|
|
219
|
+
rootDir,
|
|
220
|
+
allowDirectives: false,
|
|
221
|
+
});
|
|
222
|
+
if (rendered.length > 0) {
|
|
223
|
+
output.push(indentIncludedText(rendered, lineIndent));
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
default:
|
|
228
|
+
throw new Error(`unsupported build directive "${directive.command}" at ${formatSourceRef(filePath, index + 1)}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (stack.length !== 1) {
|
|
233
|
+
throw new Error(`unclosed build if block in ${filePath}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return output.join('\n');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function preprocessManifestText(rawText, context, options = {}) {
|
|
240
|
+
const filePath = options.filePath || '<manifest>';
|
|
241
|
+
const rootDir = options.rootDir || process.cwd();
|
|
242
|
+
return processManifestText(rawText, {
|
|
243
|
+
profile: String(context?.profile ?? '').trim(),
|
|
244
|
+
envs: { ...(context?.envs ?? {}) },
|
|
245
|
+
}, {
|
|
246
|
+
filePath,
|
|
247
|
+
rootDir,
|
|
248
|
+
allowDirectives: true,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function preprocessManifestFile(manifestPath, context) {
|
|
253
|
+
const filePath = path.resolve(manifestPath);
|
|
254
|
+
const rawText = fs.readFileSync(filePath, 'utf8');
|
|
255
|
+
return preprocessManifestText(rawText, context, {
|
|
256
|
+
filePath,
|
|
257
|
+
rootDir: path.dirname(filePath),
|
|
258
|
+
});
|
|
259
|
+
}
|
package/lib/app/project_cp.js
CHANGED
|
@@ -2,8 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import * as tar from 'tar';
|
|
4
4
|
import logger from 'loglevel';
|
|
5
|
-
import {
|
|
6
|
-
import { resolveProjectRuntime, ensureProjectServiceRunning } from './project_runtime.js';
|
|
5
|
+
import { addProjectTargetOptions, resolveProjectRuntime, ensureProjectServiceRunning } from './project_runtime.js';
|
|
7
6
|
|
|
8
7
|
function normalizeSourcePath(source) {
|
|
9
8
|
const sourcePath = path.resolve(process.cwd(), String(source));
|
|
@@ -36,19 +35,15 @@ export function projectCpCommand() {
|
|
|
36
35
|
type: 'string',
|
|
37
36
|
default: 'app',
|
|
38
37
|
});
|
|
39
|
-
args
|
|
40
|
-
alias: 'config',
|
|
41
|
-
describe: 'Build config file name',
|
|
42
|
-
type: 'string',
|
|
43
|
-
default: DEFAULT_BUILD_CONFIG_FILE,
|
|
44
|
-
});
|
|
38
|
+
addProjectTargetOptions(args);
|
|
45
39
|
},
|
|
46
|
-
handler: async ({ src, dest, service, config }) => {
|
|
40
|
+
handler: async ({ src, dest, service, config, dev, release }) => {
|
|
47
41
|
if (String(dest).includes(':')) {
|
|
48
42
|
throw new Error('Destination path must not include service prefix');
|
|
49
43
|
}
|
|
50
44
|
|
|
51
|
-
const runtime = await resolveProjectRuntime(process.cwd(), config);
|
|
45
|
+
const runtime = await resolveProjectRuntime(process.cwd(), { config, dev, release, command: 'lzc-cli project cp' });
|
|
46
|
+
logger.info(`Build config: ${runtime.configPath}`);
|
|
52
47
|
const targetService = String(service || 'app');
|
|
53
48
|
const containerId = await ensureProjectServiceRunning(runtime, targetService);
|
|
54
49
|
|
|
@@ -1,33 +1,102 @@
|
|
|
1
1
|
import logger from 'loglevel';
|
|
2
2
|
import { LpkBuild } from './lpk_build.js';
|
|
3
|
-
import {
|
|
3
|
+
import { printProjectInfo } from './project_info.js';
|
|
4
|
+
import { addProjectTargetOptions, resolveProjectRuntime, resolveProjectDeployConfigPath, getProjectDeployInfo, getProjectErrmsgByDeployId } from './project_runtime.js';
|
|
5
|
+
|
|
6
|
+
export function isAppContainerNotReadyError(error) {
|
|
7
|
+
const message = String(error?.message ?? error ?? '');
|
|
8
|
+
return message.includes('No such container') || message.includes('is not running');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function trimErrmsgByDockerConfigs(text) {
|
|
12
|
+
const raw = String(text ?? '');
|
|
13
|
+
const marker = '---------------docker-configs:-----------';
|
|
14
|
+
const idx = raw.indexOf(marker);
|
|
15
|
+
if (idx >= 0) {
|
|
16
|
+
return raw.slice(0, idx).trim();
|
|
17
|
+
}
|
|
18
|
+
return raw.trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function buildProjectStartupFailureMessage(deploy = {}, errmsg = '') {
|
|
22
|
+
const instanceStatus = String(deploy?.instanceStatus ?? '').trim() || '(unknown)';
|
|
23
|
+
const errorReason = String(deploy?.errorReason ?? '').trim();
|
|
24
|
+
const cleanedErrmsg = trimErrmsgByDockerConfigs(errmsg);
|
|
25
|
+
const details = [];
|
|
26
|
+
if (errorReason) {
|
|
27
|
+
details.push(`error reason: ${errorReason}`);
|
|
28
|
+
}
|
|
29
|
+
if (cleanedErrmsg) {
|
|
30
|
+
details.push(`error message:\n${cleanedErrmsg}`);
|
|
31
|
+
}
|
|
32
|
+
const suffix = details.length > 0 ? `\n${details.join('\n')}` : '';
|
|
33
|
+
return `Project app entered error state before dev.id sync. Instance status: ${instanceStatus}.${suffix}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function detectProjectStartupFailure(runtime) {
|
|
37
|
+
const deploy = await getProjectDeployInfo(runtime);
|
|
38
|
+
const appStatus = String(deploy?.appStatus ?? '').trim();
|
|
39
|
+
const instanceStatus = String(deploy?.instanceStatus ?? '').trim();
|
|
40
|
+
const instanceStatusLower = instanceStatus.toLowerCase();
|
|
41
|
+
if (appStatus === 'NotInstalled') {
|
|
42
|
+
throw new Error('Project package is not installed after deploy. Please retry and run "lzc-cli project info" for details.');
|
|
43
|
+
}
|
|
44
|
+
if (!instanceStatusLower.includes('error')) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const errmsg = await getProjectErrmsgByDeployId(runtime, deploy.deployId);
|
|
48
|
+
throw new Error(buildProjectStartupFailureMessage(deploy, errmsg));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function syncProjectDevID(runtime, { waitForContainer = false } = {}) {
|
|
52
|
+
const devId = await runtime.bridge.resolveDevId();
|
|
53
|
+
logger.info(`Sync dev.id: ${devId || '<empty>'}`);
|
|
54
|
+
const maxAttempts = waitForContainer ? 30 : 1;
|
|
55
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
56
|
+
try {
|
|
57
|
+
await runtime.bridge.syncDevID(runtime.pkgId, devId, runtime.userApp);
|
|
58
|
+
logger.info('dev id synced successfully.');
|
|
59
|
+
return;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (!waitForContainer || !isAppContainerNotReadyError(error)) {
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
await detectProjectStartupFailure(runtime);
|
|
65
|
+
if (attempt === maxAttempts) {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
if (attempt === 1) {
|
|
69
|
+
logger.info('Waiting for app container before syncing dev.id...');
|
|
70
|
+
}
|
|
71
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
4
75
|
|
|
5
76
|
export function projectDeployCommand() {
|
|
6
77
|
return {
|
|
7
78
|
command: 'deploy',
|
|
8
79
|
desc: 'Build and install project to target box',
|
|
9
80
|
builder: (args) => {
|
|
10
|
-
args
|
|
11
|
-
alias: 'config',
|
|
12
|
-
describe: 'Build config file name',
|
|
13
|
-
type: 'string',
|
|
14
|
-
});
|
|
81
|
+
addProjectTargetOptions(args);
|
|
15
82
|
},
|
|
16
|
-
handler: async ({ config }) => {
|
|
17
|
-
const configPath = resolveProjectDeployConfigPath(process.cwd(), config);
|
|
83
|
+
handler: async ({ config, dev, release }) => {
|
|
84
|
+
const configPath = resolveProjectDeployConfigPath(process.cwd(), { config, dev, release, command: 'lzc-cli project deploy' });
|
|
18
85
|
const runtime = await resolveProjectRuntime(process.cwd(), configPath);
|
|
19
|
-
logger.info(`
|
|
86
|
+
logger.info(`Build config: ${runtime.configPath}`);
|
|
20
87
|
|
|
21
|
-
const lpkBuild = await new LpkBuild(runtime.projectCwd, runtime.configName).init();
|
|
88
|
+
const lpkBuild = await new LpkBuild(runtime.projectCwd, runtime.configName, { forceV2: true }).init();
|
|
22
89
|
lpkBuild.onBeforeBuildPackage(async (options) => {
|
|
23
|
-
delete options
|
|
90
|
+
delete options.devshell;
|
|
24
91
|
return options;
|
|
25
92
|
});
|
|
26
93
|
const pkgPath = await lpkBuild.exec();
|
|
27
94
|
|
|
28
95
|
logger.info(`Install package: ${pkgPath}`);
|
|
29
96
|
await runtime.bridge.install(pkgPath, runtime.pkgId);
|
|
97
|
+
await syncProjectDevID(runtime, { waitForContainer: true });
|
|
30
98
|
logger.info('Project deployed successfully.');
|
|
99
|
+
await printProjectInfo(runtime);
|
|
31
100
|
},
|
|
32
101
|
};
|
|
33
102
|
}
|
package/lib/app/project_exec.js
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import logger from 'loglevel';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { addProjectTargetOptions, resolveProjectRuntime, ensureProjectServiceRunning } from './project_runtime.js';
|
|
3
|
+
import { DEFAULT_PROJECT_SYNC_TARGET } from './project_sync.js';
|
|
4
|
+
|
|
5
|
+
export async function ensureWorkdir(runtime, service, workdir) {
|
|
6
|
+
const normalized = String(workdir || '').trim();
|
|
7
|
+
if (!normalized) {
|
|
8
|
+
return '';
|
|
9
|
+
}
|
|
10
|
+
await runtime.bridge.lzcDockerComposeCapture([
|
|
11
|
+
'-p',
|
|
12
|
+
runtime.composeProjectName,
|
|
13
|
+
'exec',
|
|
14
|
+
'-T',
|
|
15
|
+
String(service || 'app'),
|
|
16
|
+
'mkdir',
|
|
17
|
+
'-p',
|
|
18
|
+
normalized,
|
|
19
|
+
]);
|
|
20
|
+
return normalized;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolveProjectExecCommandArgs(cmd, passthrough) {
|
|
24
|
+
if (Array.isArray(cmd) && cmd.length > 0) {
|
|
25
|
+
return cmd.map((item) => String(item));
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(passthrough) && passthrough.length > 0) {
|
|
28
|
+
return passthrough.map((item) => String(item));
|
|
29
|
+
}
|
|
30
|
+
return ['/bin/sh'];
|
|
31
|
+
}
|
|
4
32
|
|
|
5
33
|
export function projectExecCommand() {
|
|
6
34
|
return {
|
|
@@ -13,26 +41,35 @@ export function projectExecCommand() {
|
|
|
13
41
|
type: 'string',
|
|
14
42
|
default: 'app',
|
|
15
43
|
});
|
|
16
|
-
args
|
|
17
|
-
alias: 'config',
|
|
18
|
-
describe: 'Build config file name',
|
|
19
|
-
type: 'string',
|
|
20
|
-
default: DEFAULT_BUILD_CONFIG_FILE,
|
|
21
|
-
});
|
|
44
|
+
addProjectTargetOptions(args);
|
|
22
45
|
args.option('t', {
|
|
23
46
|
alias: 'tty',
|
|
24
47
|
describe: 'Allocate tty for exec',
|
|
25
48
|
type: 'boolean',
|
|
26
49
|
default: true,
|
|
27
50
|
});
|
|
51
|
+
args.option('w', {
|
|
52
|
+
alias: 'workdir',
|
|
53
|
+
describe: 'Working directory inside container',
|
|
54
|
+
type: 'string',
|
|
55
|
+
default: DEFAULT_PROJECT_SYNC_TARGET,
|
|
56
|
+
});
|
|
57
|
+
args.parserConfiguration({
|
|
58
|
+
'populate--': true,
|
|
59
|
+
});
|
|
28
60
|
},
|
|
29
|
-
handler: async ({ cmd, service, config, tty }) => {
|
|
30
|
-
const runtime = await resolveProjectRuntime(process.cwd(), config);
|
|
61
|
+
handler: async ({ cmd, '--': passthrough, service, config, dev, release, tty, workdir }) => {
|
|
62
|
+
const runtime = await resolveProjectRuntime(process.cwd(), { config, dev, release, command: 'lzc-cli project exec' });
|
|
63
|
+
logger.info(`Build config: ${runtime.configPath}`);
|
|
31
64
|
const targetService = String(service || 'app');
|
|
32
65
|
await ensureProjectServiceRunning(runtime, targetService);
|
|
33
|
-
const
|
|
66
|
+
const normalizedWorkdir = await ensureWorkdir(runtime, targetService, workdir);
|
|
67
|
+
const execCommand = resolveProjectExecCommandArgs(cmd, passthrough);
|
|
34
68
|
|
|
35
69
|
const composeArgs = ['-p', runtime.composeProjectName, 'exec'];
|
|
70
|
+
if (normalizedWorkdir) {
|
|
71
|
+
composeArgs.push('--workdir', normalizedWorkdir);
|
|
72
|
+
}
|
|
36
73
|
if (!tty) {
|
|
37
74
|
composeArgs.push('-T');
|
|
38
75
|
}
|
package/lib/app/project_info.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logger from 'loglevel';
|
|
2
|
-
import {
|
|
3
|
-
import { resolveProjectRuntime, getProjectDeployInfo, getProjectComposePs, getProjectErrmsgByDeployId } from './project_runtime.js';
|
|
2
|
+
import { addProjectTargetOptions, resolveProjectRuntime, getProjectDeployInfo, getProjectComposePs, getProjectErrmsgByDeployId } from './project_runtime.js';
|
|
4
3
|
|
|
5
4
|
function takeFirstLines(text, maxLines = 50) {
|
|
6
5
|
const lines = String(text ?? '').split(/\r?\n/);
|
|
@@ -38,69 +37,70 @@ function resolveTargetUrl(domain) {
|
|
|
38
37
|
return `https://${rawDomain}`;
|
|
39
38
|
}
|
|
40
39
|
|
|
40
|
+
export async function printProjectInfo(runtime) {
|
|
41
|
+
const deploy = await getProjectDeployInfo(runtime);
|
|
42
|
+
const instanceStatus = String(deploy.instanceStatus ?? '').trim();
|
|
43
|
+
const instanceStatusLower = instanceStatus.toLowerCase();
|
|
44
|
+
const targetMode = runtime.bridge.isBuildRemoteMode() ? 'build-remote' : 'box-shell';
|
|
45
|
+
const targetUrl = resolveTargetUrl(deploy.domain);
|
|
46
|
+
|
|
47
|
+
logger.info(`Target mode: ${targetMode}`);
|
|
48
|
+
logger.info(`Target box: ${runtime.bridge.boxname}`);
|
|
49
|
+
if (targetUrl) {
|
|
50
|
+
logger.info(`Target URL: ${targetUrl}`);
|
|
51
|
+
}
|
|
52
|
+
logger.info(`Project package: ${runtime.pkgId}`);
|
|
53
|
+
logger.info(`Local version: ${deploy.localVersion || '(empty)'}`);
|
|
54
|
+
logger.info(`Deployed status: ${deploy.appStatus || '(unknown)'}`);
|
|
55
|
+
logger.info(`Instance status: ${deploy.instanceStatus || '(unknown)'}`);
|
|
56
|
+
logger.info(`Deployed version: ${deploy.deployedVersion || '(empty)'}`);
|
|
57
|
+
logger.info(`Current version deployed: ${deploy.currentVersionDeployed ? 'yes' : 'no'}`);
|
|
58
|
+
if (deploy.deployId) {
|
|
59
|
+
logger.info(`Deploy DIR: /lzcsys/data/system/pkgm/run/${deploy.deployId}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (deploy.isRunning) {
|
|
63
|
+
logger.info('Project app is running.');
|
|
64
|
+
const psOutput = await getProjectComposePs(runtime);
|
|
65
|
+
if (psOutput) {
|
|
66
|
+
console.log(psOutput);
|
|
67
|
+
}
|
|
68
|
+
return deploy;
|
|
69
|
+
}
|
|
70
|
+
if (instanceStatusLower.includes('starting')) {
|
|
71
|
+
logger.info('Project app is starting. Please wait and run "lzc-cli project info" again.');
|
|
72
|
+
return deploy;
|
|
73
|
+
}
|
|
74
|
+
if (instanceStatusLower.includes('error')) {
|
|
75
|
+
logger.info('Project app is in error state.');
|
|
76
|
+
const errmsg = await getProjectErrmsgByDeployId(runtime, deploy.deployId);
|
|
77
|
+
const cleanedErrmsg = trimErrmsgByDockerConfigs(errmsg);
|
|
78
|
+
if (cleanedErrmsg) {
|
|
79
|
+
const { text: partialErrmsg, truncated } = takeFirstLines(cleanedErrmsg, 50);
|
|
80
|
+
logger.info(`Error message (first 50 lines):\n${partialErrmsg}`);
|
|
81
|
+
if (truncated) {
|
|
82
|
+
logger.info('Need more logs? Run "lzc-cli project log" for full output.');
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
logger.info('Error message: (empty)');
|
|
86
|
+
}
|
|
87
|
+
return deploy;
|
|
88
|
+
}
|
|
89
|
+
logger.info('Project app is not running. Run "lzc-cli project start" first.');
|
|
90
|
+
return deploy;
|
|
91
|
+
}
|
|
92
|
+
|
|
41
93
|
export function projectInfoCommand() {
|
|
42
94
|
return {
|
|
43
95
|
command: 'info',
|
|
44
96
|
desc: 'Show project deployment and runtime info',
|
|
45
97
|
builder: (args) => {
|
|
46
|
-
args
|
|
47
|
-
alias: 'config',
|
|
48
|
-
describe: 'Build config file name',
|
|
49
|
-
type: 'string',
|
|
50
|
-
default: DEFAULT_BUILD_CONFIG_FILE,
|
|
51
|
-
});
|
|
98
|
+
addProjectTargetOptions(args);
|
|
52
99
|
},
|
|
53
|
-
handler: async ({ config }) => {
|
|
54
|
-
const runtime = await resolveProjectRuntime(process.cwd(), config);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const instanceStatusLower = instanceStatus.toLowerCase();
|
|
58
|
-
const targetMode = runtime.bridge.isBuildRemoteMode() ? 'build-remote' : 'box-shell';
|
|
59
|
-
const targetUrl = resolveTargetUrl(deploy.domain);
|
|
60
|
-
|
|
61
|
-
logger.info(`Target mode: ${targetMode}`);
|
|
62
|
-
logger.info(`Target box: ${runtime.bridge.boxname}`);
|
|
63
|
-
if (targetUrl) {
|
|
64
|
-
logger.info(`Target URL: ${targetUrl}`);
|
|
65
|
-
}
|
|
66
|
-
logger.info(`Project package: ${runtime.pkgId}`);
|
|
67
|
-
logger.info(`Local version: ${deploy.localVersion || '(empty)'}`);
|
|
68
|
-
logger.info(`Deployed status: ${deploy.appStatus || '(unknown)'}`);
|
|
69
|
-
logger.info(`Instance status: ${deploy.instanceStatus || '(unknown)'}`);
|
|
70
|
-
logger.info(`Deployed version: ${deploy.deployedVersion || '(empty)'}`);
|
|
71
|
-
logger.info(`Current version deployed: ${deploy.currentVersionDeployed ? 'yes' : 'no'}`);
|
|
72
|
-
if (deploy.deployId) {
|
|
73
|
-
logger.info(`Deploy DIR: /lzcsys/data/system/pkgm/run/${deploy.deployId}`);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (deploy.isRunning) {
|
|
77
|
-
logger.info('Project app is running.');
|
|
78
|
-
const psOutput = await getProjectComposePs(runtime);
|
|
79
|
-
if (psOutput) {
|
|
80
|
-
console.log(psOutput);
|
|
81
|
-
}
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
if (instanceStatusLower.includes('starting')) {
|
|
85
|
-
logger.info('Project app is starting. Please wait and run "lzc-cli project info" again.');
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (instanceStatusLower.includes('error')) {
|
|
89
|
-
logger.info('Project app is in error state.');
|
|
90
|
-
const errmsg = await getProjectErrmsgByDeployId(runtime, deploy.deployId);
|
|
91
|
-
const cleanedErrmsg = trimErrmsgByDockerConfigs(errmsg);
|
|
92
|
-
if (cleanedErrmsg) {
|
|
93
|
-
const { text: partialErrmsg, truncated } = takeFirstLines(cleanedErrmsg, 50);
|
|
94
|
-
logger.info(`Error message (first 50 lines):\n${partialErrmsg}`);
|
|
95
|
-
if (truncated) {
|
|
96
|
-
logger.info('Need more logs? Run "lzc-cli project log" for full output.');
|
|
97
|
-
}
|
|
98
|
-
} else {
|
|
99
|
-
logger.info('Error message: (empty)');
|
|
100
|
-
}
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
logger.info('Project app is not running. Run "lzc-cli project start" first.');
|
|
100
|
+
handler: async ({ config, dev, release }) => {
|
|
101
|
+
const runtime = await resolveProjectRuntime(process.cwd(), { config, dev, release, command: 'lzc-cli project info' });
|
|
102
|
+
logger.info(`Build config: ${runtime.configPath}`);
|
|
103
|
+
await printProjectInfo(runtime);
|
|
104
104
|
},
|
|
105
105
|
};
|
|
106
106
|
}
|
package/lib/app/project_log.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import logger from 'loglevel';
|
|
2
|
-
import {
|
|
3
|
-
import { resolveProjectRuntime, ensureProjectRunning, ensureProjectServiceRunning, getComposeProject } from './project_runtime.js';
|
|
2
|
+
import { addProjectTargetOptions, resolveProjectRuntime, ensureProjectRunning, ensureProjectServiceRunning, getComposeProject } from './project_runtime.js';
|
|
4
3
|
|
|
5
4
|
export function projectLogCommand() {
|
|
6
5
|
return {
|
|
@@ -12,12 +11,7 @@ export function projectLogCommand() {
|
|
|
12
11
|
describe: 'Service name in docker compose project',
|
|
13
12
|
type: 'string',
|
|
14
13
|
});
|
|
15
|
-
args
|
|
16
|
-
alias: 'config',
|
|
17
|
-
describe: 'Build config file name',
|
|
18
|
-
type: 'string',
|
|
19
|
-
default: DEFAULT_BUILD_CONFIG_FILE,
|
|
20
|
-
});
|
|
14
|
+
addProjectTargetOptions(args);
|
|
21
15
|
args.option('f', {
|
|
22
16
|
alias: 'follow',
|
|
23
17
|
describe: 'Follow log output',
|
|
@@ -34,8 +28,9 @@ export function projectLogCommand() {
|
|
|
34
28
|
type: 'string',
|
|
35
29
|
});
|
|
36
30
|
},
|
|
37
|
-
handler: async ({ service, config, follow, tail, since }) => {
|
|
38
|
-
const runtime = await resolveProjectRuntime(process.cwd(), config);
|
|
31
|
+
handler: async ({ service, config, dev, release, follow, tail, since }) => {
|
|
32
|
+
const runtime = await resolveProjectRuntime(process.cwd(), { config, dev, release, command: 'lzc-cli project log' });
|
|
33
|
+
logger.info(`Build config: ${runtime.configPath}`);
|
|
39
34
|
const targetService = String(service ?? '').trim();
|
|
40
35
|
if (targetService) {
|
|
41
36
|
await ensureProjectServiceRunning(runtime, targetService);
|