@qmuse/qmuse-cli 1.0.3-dev.0 → 1.0.3-dev.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.
- package/README.md +2 -10
- package/dist/dev/index.js +2 -3
- package/dist/dev/plugin.js +11 -0
- package/dist/dev/transform.js +38 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -95,17 +95,9 @@ qmuse dev . --port 5173
|
|
|
95
95
|
|
|
96
96
|
CLI 获取服务端解析的公开云配置,复用 Vite 的 `MUSE_RUNTIME_CONFIG_PATH` 注入机制。配置放在系统临时目录,退出时删除。Token 按需获取且不落盘,CLI 登录凭据不交给页面。数据库、文件和函数请求由浏览器通过用户 Session 直接访问云端,不使用管理 API Key 或 Dev Key。
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
|
-
fetch: import.meta.env.DEV
|
|
102
|
-
? (window as Window & {
|
|
103
|
-
__QMUSE_LOCAL_DEV__?: { fetch: typeof globalThis.fetch };
|
|
104
|
-
}).__QMUSE_LOCAL_DEV__?.fetch
|
|
105
|
-
: undefined,
|
|
106
|
-
```
|
|
98
|
+
CLI 的 Vite 插件在加载 `src/services/appwrite.ts` 时,通过语法解析为 Runtime 初始化自动注入本地 fetch,不写回项目文件。原版云服务资产和已下载的模板无需手工添加适配,也无需发布 qmuse-cloud 或修改 SDK。支持从 SDK 命名导入(含别名),在顶层使用对象参数初始化一次;不支持的写法在启动或热更新时明确报错。开发时 Runtime 的 fetch 由 CLI 接管,包括对象中已有的自定义 fetch。
|
|
107
99
|
|
|
108
|
-
|
|
100
|
+
只有 `qmuse dev` 启用该插件;普通 `npm run dev` 和生产构建继续使用原始源码。服务端必须先部署 `/api/appwrite/local-dev/context` 和 `/api/appwrite/local-dev/mutation-events`,再发布并安装新版 CLI;缺少接口时不会降级为模拟云服务。
|
|
109
101
|
|
|
110
102
|
页面改动热更新,Schema/函数改动继续执行 `qmuse import .`。导入更新本地绑定后,连接会重新获取配置并刷新页面。切换登录账号或应用绑定需重启连接;退出 CLI 不会自动撤销浏览器已经建立的 AppBase Session。SDK 的匿名降级行为保持不变,验收必须检查实际 Session 用户,不能只看页面是否显示。
|
|
111
103
|
|
package/dist/dev/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { loadConfig } from '../config.js';
|
|
|
9
9
|
import { loadManagedProject } from '../import/project.js';
|
|
10
10
|
import { browserRuntime, loadDevContext } from './context.js';
|
|
11
11
|
import { localDevPlugin } from './plugin.js';
|
|
12
|
+
import { injectLocalRuntimeFetch } from './transform.js';
|
|
12
13
|
export async function startDevelopment(options) {
|
|
13
14
|
const root = await realpath(path.resolve(options.root));
|
|
14
15
|
const require = createRequire(path.join(root, 'package.json'));
|
|
@@ -23,9 +24,7 @@ export async function startDevelopment(options) {
|
|
|
23
24
|
if (!project?.target)
|
|
24
25
|
throw new Error('请先执行 qmuse import . 并等待云服务 READY,再启动本地联调');
|
|
25
26
|
const serviceFile = await readFile(path.join(root, 'src/services/appwrite.ts'), 'utf8').catch(() => '');
|
|
26
|
-
|
|
27
|
-
throw new Error('项目尚未接入本地适配:请按最新版云服务资产在 src/services/appwrite.ts 的 Runtime options 中传入本地 fetch(见 qmuse-cli README)');
|
|
28
|
-
}
|
|
27
|
+
injectLocalRuntimeFetch(serviceFile, path.join(root, 'src/services/appwrite.ts'));
|
|
29
28
|
const context = await loadDevContext(root, options.cfg, options.environment);
|
|
30
29
|
const directory = await mkdtemp(path.join(tmpdir(), 'qmuse-local-dev-'));
|
|
31
30
|
const runtimePath = path.join(directory, 'runtime.json');
|
package/dist/dev/plugin.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { injectLocalRuntimeFetch } from './transform.js';
|
|
2
4
|
const PREFIX = '/__qmuse/local-dev/';
|
|
3
5
|
const TOKEN_HEADER = 'x-qmuse-local-connection';
|
|
4
6
|
const BODY_LIMIT = 64 * 1024;
|
|
@@ -56,10 +58,19 @@ export function bridgeSource(context, connection) {
|
|
|
56
58
|
export function localDevPlugin(options) {
|
|
57
59
|
const connection = randomBytes(32).toString('hex');
|
|
58
60
|
let server;
|
|
61
|
+
let servicePath;
|
|
59
62
|
return {
|
|
60
63
|
name: 'qmuse-local-dev',
|
|
61
64
|
apply: 'serve',
|
|
62
65
|
enforce: 'pre',
|
|
66
|
+
configResolved(config) {
|
|
67
|
+
servicePath = path.resolve(config.root, 'src/services/appwrite.ts');
|
|
68
|
+
},
|
|
69
|
+
transform(code, id) {
|
|
70
|
+
if (id.split('?')[0] !== servicePath)
|
|
71
|
+
return;
|
|
72
|
+
return injectLocalRuntimeFetch(code, servicePath);
|
|
73
|
+
},
|
|
63
74
|
configureServer(vite) {
|
|
64
75
|
server = vite;
|
|
65
76
|
server.middlewares.use((req, res, next) => {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
import MagicString from 'magic-string';
|
|
3
|
+
export function injectLocalRuntimeFetch(code, id) {
|
|
4
|
+
const source = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
5
|
+
const names = new Set();
|
|
6
|
+
for (const statement of source.statements) {
|
|
7
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteral(statement.moduleSpecifier) ||
|
|
8
|
+
statement.moduleSpecifier.text !== '@qmuse/appwrite-runtime-sdk' || statement.importClause?.isTypeOnly)
|
|
9
|
+
continue;
|
|
10
|
+
const bindings = statement.importClause?.namedBindings;
|
|
11
|
+
if (!bindings || !ts.isNamedImports(bindings))
|
|
12
|
+
continue;
|
|
13
|
+
for (const binding of bindings.elements) {
|
|
14
|
+
if (!binding.isTypeOnly && (binding.propertyName ?? binding.name).text === 'createQmuseAppwriteRuntime') {
|
|
15
|
+
names.add(binding.name.text);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const calls = [];
|
|
20
|
+
const visit = (node) => {
|
|
21
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && names.has(node.expression.text))
|
|
22
|
+
calls.push(node);
|
|
23
|
+
ts.forEachChild(node, visit);
|
|
24
|
+
};
|
|
25
|
+
visit(source);
|
|
26
|
+
const call = calls[0];
|
|
27
|
+
const argument = call?.arguments[0];
|
|
28
|
+
// Restrict injection to the official asset's top-level initializer, avoiding shadowed bindings.
|
|
29
|
+
if (calls.length !== 1 || !call || call.arguments.length !== 1 || !argument || !ts.isObjectLiteralExpression(argument) ||
|
|
30
|
+
!ts.isVariableDeclaration(call.parent) || !ts.isVariableDeclarationList(call.parent.parent) ||
|
|
31
|
+
!ts.isVariableStatement(call.parent.parent.parent) || call.parent.parent.parent.parent !== source) {
|
|
32
|
+
throw new Error('无法注入本地云适配:src/services/appwrite.ts 需要从 @qmuse/appwrite-runtime-sdk 命名导入 createQmuseAppwriteRuntime,并在顶层用对象参数初始化一次;请对齐官方云服务资产');
|
|
33
|
+
}
|
|
34
|
+
const result = new MagicString(code);
|
|
35
|
+
result.prependLeft(argument.getStart(source), '{ ...(');
|
|
36
|
+
result.appendLeft(argument.end, '), fetch: window.__QMUSE_LOCAL_DEV__.fetch }');
|
|
37
|
+
return { code: result.toString(), map: result.generateMap({ source: id, includeContent: true, hires: true }) };
|
|
38
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qmuse/qmuse-cli",
|
|
3
|
-
"version": "1.0.3-dev.
|
|
3
|
+
"version": "1.0.3-dev.1",
|
|
4
4
|
"description": "将本地 React 项目安全导入 QMuse 的命令行客户端",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@napi-rs/keyring": "2.0.0",
|
|
23
23
|
"ignore": "^7.0.8",
|
|
24
|
+
"magic-string": "^0.30.17",
|
|
25
|
+
"typescript": "^5.9.3",
|
|
24
26
|
"yazl": "^3.3.1"
|
|
25
27
|
},
|
|
26
28
|
"files": [
|
|
@@ -35,7 +37,6 @@
|
|
|
35
37
|
"@types/node": "^22.10.0",
|
|
36
38
|
"@types/yazl": "^3.3.1",
|
|
37
39
|
"tsx": "^4.19.2",
|
|
38
|
-
"typescript": "^5.9.3",
|
|
39
40
|
"vite": "7.3.6"
|
|
40
41
|
},
|
|
41
42
|
"license": "MIT",
|