---
title: "独立开发环境"
description: "安装精确版本的 SDK kit，并区分应用安装模式与宿主源码联调模式。"
canonical_url: "https://docs.mcent.cn/qicore/development-workspace"
---
# 独立开发环境

> 安装精确版本的 SDK kit，并区分应用安装模式与宿主源码联调模式。

应用工程与官方宿主可以放在不同目录。应用依赖由自己的锁文件固定，开发宿主由开发者显式选择。

## 准备工具与 SDK kit

开发基线为 Node.js 24、pnpm 11.25.0，以及 PATH 中可用的 MySQL 8 工具（`mysqld/mysql/mysqladmin`）和 Redis 工具（`redis-server/redis-cli`）。具体依赖版本继续以项目的 `package.json` 和锁文件为准。

从可信交付方取得匹配的 SDK kit。当前契约中的两个包为：

<table>
<thead>
  <tr>
    <th>
      包
    </th>
    
    <th>
      当前版本
    </th>
    
    <th>
      kit 档案名
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        @qicore/application-sdk
      </code>
    </td>
    
    <td>
      <code>
        0.2.0
      </code>
    </td>
    
    <td>
      <code>
        qicore-application-sdk-0.2.0.tgz
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        @orangezh1/qisvc-console-ui
      </code>
    </td>
    
    <td>
      <code>
        0.1.0
      </code>
    </td>
    
    <td>
      <code>
        orangezh1-qisvc-console-ui-0.1.0.tgz
      </code>
    </td>
  </tr>
</tbody>
</table>

SDK kit 还包含 `sdk-kit.json` 和独立 `install.mjs`。这里的 SDK 是面向应用开发者的交付接口，不假定这些版本已经发布到公开 npm 仓库。表中列出本文核对的接口版本，实际项目以取得的匹配 kit 为准，不能据此推导其他宿主版本的兼容范围。

## 声明和安装

在应用根 `package.json` 合并以下安装片段；已有工作区可按对应相对路径设置自己的依赖：

```json
{
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "qicore dev",
    "dev:stop": "qicore dev:stop"
  },
  "qicore": {
    "sdk": {
      "@qicore/application-sdk": "0.2.0",
      "@orangezh1/qisvc-console-ui": "0.1.0"
    }
  },
  "dependencies": {
    "@qicore/application-sdk": "file:.local/qicore-sdk/qicore-application-sdk-0.2.0.tgz",
    "@orangezh1/qisvc-console-ui": "file:.local/qicore-sdk/orangezh1-qisvc-console-ui-0.1.0.tgz"
  }
}
```

`qicore.sdk` 的包名和精确版本必须与 kit 一致。安装器先检查两份档案的版本、大小和 SHA256，全部通过后才写入应用的 `.local/qicore-sdk`。

```bash
node /path/to/sdk-kit/install.mjs /path/to/notes-application
```

进入应用工程，已有匹配锁文件时执行：

```bash
pnpm install --frozen-lockfile
```

首次创建锁文件或显式升级 SDK 时，先按应用自己的依赖流程生成并审查锁文件，再使用冻结安装验证。不要手改已交付的同版本 TGZ；哈希核对也不能替代可信的传输来源。

## 应用开发者：安装模式

这里的官方宿主是包含开发脚本的匹配开发工程。首次使用时，先在该宿主目录执行 `pnpm install --frozen-lockfile`，再回到应用目录；宿主开发入口会使用自己的依赖编译公共 API 和客户端。生产实例的运行目录不能代替开发宿主。

实现应用自己的开发入口后，在应用目录启动：

```bash
pnpm dev --host /path/to/official-qicore --state /path/to/private-dev-state --port 4300
```

也可通过 `QICORE_DEV_HOST` 指定官方宿主。未指定宿主会返回 `QICORE_DEV_HOST_REQUIRED`；CLI 不查找某个固定的相邻目录。

SDK CLI 为宿主传入当前应用路径和 `--sdk-mode installed`。应用的服务端、后台和客户端构建使用自己安装的 SDK 与 Console UI，宿主提供公共 API、身份、数据库及代理。

停止时使用同一宿主和状态目录：

```bash
pnpm dev:stop --host /path/to/official-qicore --state /path/to/private-dev-state
```

停止会释放该工作区拥有的进程，保留开发数据。不要把状态目录当作可随时清空的缓存。

## 框架维护者：源码模式

在官方宿主工程中执行：

```bash
pnpm dev --app /path/to/notes-application
pnpm dev --app /path/to/application-a --app /path/to/application-b
pnpm dev:stop
```

宿主入口默认 `--sdk-mode source`，用于联调公共 SDK 源码热更新。此模式与应用锁定的安装模式分别验证。不传 `--app` 时运行默认零应用框架。

## 声明开发入口

应用根 `package.json` 还需提供：

```json
{
  "qicore": {
    "development": {
      "entry": "scripts/development.mjs",
      "serverWatch": ["apps/api/src", "apps/api/prisma", "application.json"]
    }
  }
}
```

该对象应与上面的 `qicore.sdk` 合并。此处监听路径对应[应用开发工程推荐结构](https://docs.mcent.cn/qicore/application-development#%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E5%B7%A5%E7%A8%8B%E6%8E%A8%E8%8D%90%E7%BB%93%E6%9E%84)；采用其他目录布局时同步修改。入口和监听路径相对应用根，不能为绝对路径或包含 `..`。宿主只调用声明的入口，不推断应用源码布局。

<table>
<thead>
  <tr>
    <th>
      模式
    </th>
    
    <th>
      应用入口的责任
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        server
      </code>
    </td>
    
    <td>
      完成编译后退出，在 <code>
        QICORE_DEV_APP_OUTPUT
      </code>
      
       输出 <code>
        application.json
      </code>
      
      、<code>
        entry.mjs
      </code>
      
      ，按需输出模型片段、迁移和 H5 路由表
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        admin
      </code>
    </td>
    
    <td>
      仅在声明 <code>
        web.admin
      </code>
      
       时启动，监听 <code>
        127.0.0.1:QICORE_DEV_ADMIN_PORT
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        h5
      </code>
    </td>
    
    <td>
      仅在声明 <code>
        web.h5
      </code>
      
       时启动，监听 <code>
        127.0.0.1:QICORE_DEV_H5_PORT
      </code>
      
      ，使用分配的应用基址
    </td>
  </tr>
</tbody>
</table>

入口还会收到 `NODE_ENV=development`、`QICORE_DEV_WORKSPACE`、`QICORE_DEV_EMPTY_ENV`、`QICORE_DEV_SDK_PATH` 和 `QICORE_DEV_CONSOLE_UI_PATH`。前端构建应读取提供的 SDK 位置、应用身份和端口，不固定到某台开发机。

长驻前端进程应处理 IPC 消息 `qicore-development-stop`，关闭自身 watcher 和监听端口。

## 最小服务端开发闭环

下面选用 SDK 已有的工厂模板，完成一个无业务表、无前端、无持久任务的开发装配。先完成上面的 SDK 安装和开发入口声明；首次工程先生成锁文件，再通过冻结安装核对依赖。

### 1. 准备应用描述和入口源码

在应用根创建 `application.json`：

```json
{
  "format": "qicore-application-v1",
  "key": "notes-app",
  "packageKey": "app_notes_demo",
  "version": "0.1.0",
  "name": "笔记示例应用",
  "pendingTasks": []
}
```

示例身份只用于本地开发，不能当作真实已登记、已授权的发行产品。从已安装 SDK 的公开模板路径复制工厂入口，在应用根执行：

```bash
node --input-type=module -e "import {mkdir,copyFile} from 'node:fs/promises';import {constants} from 'node:fs';import {fileURLToPath} from 'node:url';await mkdir('apps/api/src',{recursive:true});await copyFile(fileURLToPath(import.meta.resolve('@qicore/application-sdk/templates/application-package/entry.mjs')),'apps/api/src/entry.mjs',constants.COPYFILE_EXCL)"
```

命令在目标文件已存在时拒绝覆盖。模板入口通过相邻的 `application.json` 取得身份；它将在下面的装配输出目录中执行，而不是直接从源码目录启动。此方案保留已有 `createApplication(serverSdk)`，不再用 `applicationEntry()` 包装一次。

### 2. 实现 server 模式

创建 `scripts/development.mjs`：

```js
import { copyFile, mkdir } from 'node:fs/promises'
import { dirname, isAbsolute, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'

if (process.argv[2] !== 'server') throw Error('此最小示例只支持 server 模式')
const selected = process.env.QICORE_DEV_APP_OUTPUT
if (process.env.NODE_ENV !== 'development' || !selected || !isAbsolute(selected)) {
  throw Error('需要开发宿主提供绝对路径 QICORE_DEV_APP_OUTPUT')
}
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const output = resolve(selected)
if (output === root) throw Error('装配输出不能覆盖应用源码根')
await mkdir(output, { recursive: true })
await copyFile(join(root, 'application.json'), join(output, 'application.json'))
await copyFile(join(root, 'apps/api/src/entry.mjs'), join(output, 'entry.mjs'))
```

本例源码是可直接执行的 JavaScript，因此 server 模式只复制两份必要文件并退出。加入业务构建或模型后，再替换为自己的编译、模型生成及迁移输出。描述没有 `web`，宿主不会调用本例未实现的 admin/h5 模式。

### 3. 先独立核对装配输出

可在 Linux/WSL 中选择一个空的专用临时输出目录，单独执行该开发入口：

```bash
NODE_ENV=development QICORE_DEV_APP_OUTPUT=/path/to/empty-output node scripts/development.mjs server
```

创建 `scripts/verify-dev-output.mjs`，检查输出描述、工厂和最小运行时；该检查不连接数据库、不启动宿主：

```js
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import { join, resolve } from 'node:path'
import { pathToFileURL } from 'node:url'
import { applicationDescriptorSchema } from '@qicore/application-sdk/contracts'

if (!process.argv[2]) throw Error('请提供专用装配输出目录')
const output = resolve(process.argv[2])
const descriptor = applicationDescriptorSchema.parse(
  JSON.parse(await readFile(join(output, 'application.json'), 'utf8'))
)
const { createApplication } = await import(pathToFileURL(join(output, 'entry.mjs')).href)
const component = createApplication({ version: 1 })
assert.equal(component.format, descriptor.format)
assert.equal(component.version, descriptor.version)
assert.equal(component.definition.key, descriptor.key)
assert.equal(component.definition.licensePackageKey, descriptor.packageKey)
assert.equal(component.definition.name, descriptor.name)
const runtime = component.createRuntime({ db: {}, storage: {} })
assert.equal(runtime.key, descriptor.key)
assert.deepEqual(runtime.consumerPaths, [])
for (const hook of ['connect', 'initialize', 'tick', 'admin', 'consumer', 'health', 'close']) {
  assert.equal(typeof runtime[hook], 'function', hook)
}
assert.equal(await runtime.health(), true)
console.log('最小服务端装配输出检查通过')
```

```bash
node scripts/verify-dev-output.mjs /path/to/empty-output
```

此处 `{ version: 1 }` 只用于 SDK 原始空模板的独立检查，不能作为带真实业务的宿主替身。业务开始使用公共 API 或数据库后，继续在真实开发宿主验证。

### 4. 启动、确认加载并停止

```bash
pnpm dev --host /path/to/official-qicore --state /path/to/private-dev-state --port 4300
```

首次状态目录应不存在或为空；再次启动复用该宿主已经登记的同一目录。不同宿主不要共用状态目录。

启动日志应出现本应用 server 编译完成及工作区 ready。查看统一入口 `http://127.0.0.1:4300/__qicore_dev/status`，确认 `ready: true`、`building: false`，并在 `applications` 中找到 `notes-app` 和 `0.1.0`。API 健康入口 `http://127.0.0.1:4301/health` 会同时检查宿主和已装配运行时。

此最小模板没有业务 API 或菜单；加载成功不等于已经有笔记功能。检查后在另一个应用终端执行：

```bash
pnpm dev:stop --host /path/to/official-qicore --state /path/to/private-dev-state
```

确认该工作区进程与端口已释放，开发数据仍保留。独立检查创建的临时输出在不再排障后按其归属清理，不处理其他项目目录。

## 热更新与边界

后台页面与客户端页面由各自开发服务热更新。服务端编译成功并完成迁移核查后才重载；编译失败保留上次可用 API。已记录迁移被修改、删除或中断时会阻止重载。

框架模型、应用身份、版本、开发脚本或端口配置发生变化时，停止对应工作区后重新启动。新的 key 不能接管旧包的数据和历史权限归属。

默认统一入口为 3500，API 为 3501，框架后台为 3502；`--port` 可以选择另一组工作区端口。端口被占用时核对归属，不停止其他项目的服务。

开发状态、授权身份、数据库与文件只属于本地工作区，不进入发行包。下一步阅读[应用描述与运行时](https://docs.mcent.cn/qicore/application-manifest)。


## Sitemap

See the full [sitemap](https://docs.mcent.cn/sitemap.md) for all pages.
