@lark-apaas/miaoda-cli 0.1.46 → 0.1.47

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.
@@ -10,6 +10,7 @@ const node_child_process_1 = require("node:child_process");
10
10
  const node_fs_1 = __importDefault(require("node:fs"));
11
11
  const node_path_1 = __importDefault(require("node:path"));
12
12
  const env_1 = require("../../../utils/env");
13
+ const engineering_state_1 = require("../../../utils/engineering-state");
13
14
  const install_1 = require("./install");
14
15
  /**
15
16
  * event marker 目录,写死 `/tmp/event`;写前 mkdir -p。沙箱里 code server 轮询此目录,
@@ -61,6 +62,7 @@ function runAsyncInstallWorker(opts) {
61
62
  }
62
63
  catch (err) {
63
64
  const msg = err instanceof Error ? err.message : String(err);
65
+ recordInstallState(opts.targetDir, msg);
64
66
  if (sandbox) {
65
67
  writeMarker(eventDir, MARKER_FAILED, {
66
68
  ts: nowIso(),
@@ -70,6 +72,7 @@ function runAsyncInstallWorker(opts) {
70
72
  }
71
73
  return;
72
74
  }
75
+ recordInstallState(opts.targetDir, result.source === 'failed' ? (result.error ?? 'npm install failed') : undefined);
73
76
  if (!sandbox)
74
77
  return;
75
78
  if (result.source === 'failed') {
@@ -87,6 +90,24 @@ function runAsyncInstallWorker(opts) {
87
90
  });
88
91
  }
89
92
  }
93
+ /**
94
+ * 本地执行记录:init --strict 据此区分"后台安装成功 / 失败 / 仍在进行或被中断"。
95
+ * 中断(worker 没跑完)时记录停在 dispatch 写下的 running,不会被当成成功。
96
+ */
97
+ function recordInstallState(targetDir, error) {
98
+ try {
99
+ (0, engineering_state_1.writeEngineeringState)(targetDir, {
100
+ operation: 'init',
101
+ status: error === undefined ? 'succeeded' : 'failed',
102
+ stage: 'install',
103
+ code: error === undefined ? undefined : 'INIT_INSTALL_FAILED',
104
+ message: error?.slice(0, 500),
105
+ });
106
+ }
107
+ catch {
108
+ // 执行记录写不了不影响安装结果与 marker
109
+ }
110
+ }
90
111
  /**
91
112
  * 派发 detached 后台进程跑依赖安装并立即返回。
92
113
  * 用 `node -e` 直接 require 本模块(编译产物)调 runAsyncInstallWorker,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ASYNC_INSTALL_LOG = exports.runAsyncInstallWorker = exports.dispatchAsyncInstall = exports.resolveNpmInstallRegistry = exports.installDependencies = exports.writeSparkMeta = exports.readSparkMeta = exports.IMPORT_SKIP_FILES = exports.IMPORT_SKIP_DIRS = exports.importSourceSrc = exports.TEMPLATE_PACKAGE_BY_STACK = exports.STACK_BY_APP_TYPE = exports.APP_TYPES = exports.LOCAL_STEERING_STACKS = exports.SUPPORTED_STACKS = exports.resolveStack = exports.renderTemplate = void 0;
3
+ exports.checkWorkspaceReady = exports.ASYNC_INSTALL_LOG = exports.runAsyncInstallWorker = exports.dispatchAsyncInstall = exports.resolveNpmInstallRegistry = exports.installDependencies = exports.writeSparkMeta = exports.readSparkMeta = exports.IMPORT_SKIP_FILES = exports.IMPORT_SKIP_DIRS = exports.importSourceSrc = exports.TEMPLATE_PACKAGE_BY_STACK = exports.STACK_BY_APP_TYPE = exports.APP_TYPES = exports.LOCAL_STEERING_STACKS = exports.SUPPORTED_STACKS = exports.resolveStack = exports.renderTemplate = void 0;
4
4
  var template_1 = require("./template");
5
5
  Object.defineProperty(exports, "renderTemplate", { enumerable: true, get: function () { return template_1.renderTemplate; } });
6
6
  Object.defineProperty(exports, "resolveStack", { enumerable: true, get: function () { return template_1.resolveStack; } });
@@ -23,3 +23,5 @@ var async_install_1 = require("./async-install");
23
23
  Object.defineProperty(exports, "dispatchAsyncInstall", { enumerable: true, get: function () { return async_install_1.dispatchAsyncInstall; } });
24
24
  Object.defineProperty(exports, "runAsyncInstallWorker", { enumerable: true, get: function () { return async_install_1.runAsyncInstallWorker; } });
25
25
  Object.defineProperty(exports, "ASYNC_INSTALL_LOG", { enumerable: true, get: function () { return async_install_1.ASYNC_INSTALL_LOG; } });
26
+ var workspace_check_1 = require("./workspace-check");
27
+ Object.defineProperty(exports, "checkWorkspaceReady", { enumerable: true, get: function () { return workspace_check_1.checkWorkspaceReady; } });
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.checkWorkspaceReady = checkWorkspaceReady;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const fullstack_managed_files_1 = require("../../../config/fullstack-managed-files");
10
+ const engineering_state_1 = require("../../../utils/engineering-state");
11
+ const error_1 = require("../../../utils/error");
12
+ const spark_meta_1 = require("../../../utils/spark-meta");
13
+ const detect_1 = require("../../../services/app/migrate/detect");
14
+ /** 各 stack 的必要文件(相对项目根) */
15
+ const REQUIRED_FILES_BY_STACK = {
16
+ 'nestjs-react-fullstack': [
17
+ 'package.json',
18
+ 'server/main.ts',
19
+ 'server/app.module.ts',
20
+ 'client/index.html',
21
+ 'vite.config.ts',
22
+ 'nest-cli.json',
23
+ 'tsconfig.node.json',
24
+ ...fullstack_managed_files_1.FULLSTACK_MANAGED_SCRIPT_FILES,
25
+ ],
26
+ 'vite-react': ['package.json', 'index.html', 'vite.config.ts'],
27
+ };
28
+ function hasDeclaredDeps(pkgPath) {
29
+ try {
30
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, 'utf-8'));
31
+ return (Object.keys(pkg.dependencies ?? {}).length + Object.keys(pkg.devDependencies ?? {}).length > 0);
32
+ }
33
+ catch {
34
+ return true;
35
+ }
36
+ }
37
+ function nodeModulesPopulated(targetDir) {
38
+ const nm = node_path_1.default.join(targetDir, 'node_modules');
39
+ try {
40
+ return node_fs_1.default.readdirSync(nm).some((name) => !name.startsWith('.'));
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ function checkWorkspaceReady(opts) {
47
+ const { targetDir } = opts;
48
+ const problems = [];
49
+ let stack;
50
+ try {
51
+ const meta = (0, spark_meta_1.readSparkMetaStrict)(targetDir);
52
+ stack = meta?.stack !== undefined && meta.stack !== '' ? meta.stack : undefined;
53
+ if (stack === undefined) {
54
+ problems.push({
55
+ code: 'META_INCOMPLETE',
56
+ message: '.spark/meta.json missing stack',
57
+ path: '.spark/meta.json',
58
+ });
59
+ }
60
+ }
61
+ catch (err) {
62
+ problems.push({
63
+ code: err instanceof error_1.AppError ? err.code : 'SPARK_META_CORRUPT',
64
+ message: err instanceof Error ? err.message : String(err),
65
+ path: '.spark/meta.json',
66
+ });
67
+ return { ready: false, problems };
68
+ }
69
+ if (opts.expectedStack !== undefined && stack !== undefined && stack !== opts.expectedStack) {
70
+ problems.push({
71
+ code: 'STACK_MISMATCH',
72
+ message: `project stack '${stack}' differs from requested '${opts.expectedStack}'`,
73
+ });
74
+ }
75
+ if (stack !== undefined) {
76
+ const detected = (0, detect_1.detectDiskStack)(targetDir);
77
+ if (detected !== 'unknown' && detected !== stack) {
78
+ problems.push({
79
+ code: 'LAYOUT_MISMATCH',
80
+ message: `.spark/meta.json says '${stack}' but project layout on disk is '${detected}'; run \`miaoda app migrate --to ${stack}\``,
81
+ });
82
+ }
83
+ for (const rel of REQUIRED_FILES_BY_STACK[stack] ?? ['package.json']) {
84
+ if (!node_fs_1.default.existsSync(node_path_1.default.join(targetDir, rel))) {
85
+ problems.push({ code: 'REQUIRED_FILE_MISSING', message: `${rel} not found`, path: rel });
86
+ }
87
+ }
88
+ }
89
+ const pkgPath = node_path_1.default.join(targetDir, 'package.json');
90
+ if (node_fs_1.default.existsSync(pkgPath) && hasDeclaredDeps(pkgPath) && !nodeModulesPopulated(targetDir)) {
91
+ problems.push({
92
+ code: 'DEPS_NOT_INSTALLED',
93
+ message: 'node_modules missing or empty; run `miaoda app sync` to install dependencies',
94
+ path: 'node_modules',
95
+ });
96
+ }
97
+ const state = (0, engineering_state_1.readEngineeringState)(targetDir);
98
+ if (state !== undefined && state.status !== 'succeeded') {
99
+ problems.push({
100
+ code: state.status === 'running' ? 'LAST_OPERATION_UNFINISHED' : 'LAST_OPERATION_FAILED',
101
+ message: `last ${state.operation} ${state.status} at stage '${state.stage}'${state.code !== undefined ? ` (${state.code})` : ''}`,
102
+ });
103
+ }
104
+ return { ready: problems.length === 0, stack, problems };
105
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectDiskStack = detectDiskStack;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const VITE_CONFIG_FILES = [
10
+ 'vite.config.ts',
11
+ 'vite.config.js',
12
+ 'vite.config.mjs',
13
+ 'vite.config.mts',
14
+ 'vite.config.cjs',
15
+ ];
16
+ function isDir(p) {
17
+ try {
18
+ return node_fs_1.default.statSync(p).isDirectory();
19
+ }
20
+ catch {
21
+ return false;
22
+ }
23
+ }
24
+ function detectDiskStack(dir) {
25
+ const has = (rel) => node_fs_1.default.existsSync(node_path_1.default.join(dir, rel));
26
+ const hasViteConfig = VITE_CONFIG_FILES.some(has);
27
+ const legacyLayout = isDir(node_path_1.default.join(dir, 'src')) || has('index.html');
28
+ if (hasViteConfig && legacyLayout)
29
+ return 'vite-react';
30
+ const fullstackLayout = has('server/main.ts') && (isDir(node_path_1.default.join(dir, 'client', 'src')) || has('client/index.html'));
31
+ if (fullstackLayout)
32
+ return 'nestjs-react-fullstack';
33
+ return 'unknown';
34
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectDiskStack = exports.MIGRATION_PURPOSES = exports.runMigration = void 0;
4
+ var run_1 = require("./run");
5
+ Object.defineProperty(exports, "runMigration", { enumerable: true, get: function () { return run_1.runMigration; } });
6
+ Object.defineProperty(exports, "MIGRATION_PURPOSES", { enumerable: true, get: function () { return run_1.MIGRATION_PURPOSES; } });
7
+ var detect_1 = require("./detect");
8
+ Object.defineProperty(exports, "detectDiskStack", { enumerable: true, get: function () { return detect_1.detectDiskStack; } });
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ /**
3
+ * 沙箱内重启被转换工程的 dev 进程:向 `node …scripts/dev.js` 主进程发 SIGTERM,由平台
4
+ * supervisor 重新 exec dev.sh → dev.js 加载新脚本与依赖。这只是重启信号,新进程是否健康由
5
+ * 调用方核验。
6
+ *
7
+ * 只认工作目录等于被转换目录的进程:dev.js 启动即 chdir 到自己所在的工程根,cwd 唯一对应它
8
+ * 服务的工程。按命令行全局匹配会连带杀掉同一沙箱里其他目录的预览进程(例如在工程副本里执行
9
+ * migrate 时,真实应用的预览也被重启)。.spark/meta.json 的 app_id 区分不了副本与原工程,沙箱
10
+ * 也没有所有调用方都会设置的工作区路径环境变量,所以都不用来判定。
11
+ *
12
+ * 读 /proc 是 Linux-only:只在沙箱(恒为 Linux)调用;/proc 不可读时视为没有可重启的进程。
13
+ */
14
+ var __importDefault = (this && this.__importDefault) || function (mod) {
15
+ return (mod && mod.__esModule) ? mod : { "default": mod };
16
+ };
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.signalDevRestart = signalDevRestart;
19
+ const node_fs_1 = __importDefault(require("node:fs"));
20
+ const node_path_1 = __importDefault(require("node:path"));
21
+ const DEV_PROCESS_RE = /node.*scripts\/dev\.js/;
22
+ /** 返回已发出重启信号的进程号;没有命中返回空数组 */
23
+ function signalDevRestart(projectDir, deps = {}) {
24
+ const procRoot = deps.procRoot ?? '/proc';
25
+ const kill = deps.kill ??
26
+ ((pid) => {
27
+ process.kill(pid, 'SIGTERM');
28
+ });
29
+ let project;
30
+ let entries;
31
+ try {
32
+ project = node_fs_1.default.realpathSync(projectDir);
33
+ entries = node_fs_1.default.readdirSync(procRoot);
34
+ }
35
+ catch {
36
+ return [];
37
+ }
38
+ const signaled = [];
39
+ for (const entry of entries) {
40
+ if (!/^\d+$/.test(entry) || Number(entry) === process.pid)
41
+ continue;
42
+ try {
43
+ const cmdline = node_fs_1.default
44
+ .readFileSync(node_path_1.default.join(procRoot, entry, 'cmdline'), 'utf-8')
45
+ .replaceAll('\0', ' ');
46
+ if (!DEV_PROCESS_RE.test(cmdline))
47
+ continue;
48
+ // 内核给出的 cwd 已是解析过符号链接的绝对路径
49
+ if (node_fs_1.default.readlinkSync(node_path_1.default.join(procRoot, entry, 'cwd')) !== project)
50
+ continue;
51
+ kill(Number(entry));
52
+ signaled.push(Number(entry));
53
+ }
54
+ catch {
55
+ // 进程已退出或无权读取:不是本工程可重启的进程
56
+ }
57
+ }
58
+ return signaled;
59
+ }