@qmuse/qmuse-cli 1.0.3-dev.3 → 1.0.3-dev.4

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/dist/cli.js CHANGED
@@ -21,6 +21,7 @@ import { clearPendingImport, completeManagedImport, loadManagedProject, saveMana
21
21
  import { clearImportFailureDiagnostic, writeImportFailureDiagnostic, } from './import/diagnostic.js';
22
22
  import { checkCloudInit, CLOUD_INIT_SUCCESS_MESSAGE } from './cloud.js';
23
23
  import { QMUSE_CLI_VERSION } from './version.js';
24
+ import { startUpdateNotice } from './update-notice.js';
24
25
  import { startDevelopment } from './dev/index.js';
25
26
  // ---------------- 参数解析 ----------------
26
27
  const BOOL_FLAGS = new Set([
@@ -727,6 +728,11 @@ async function main() {
727
728
  return;
728
729
  }
729
730
  const root = command[0];
731
+ const finishUpdateNotice = startUpdateNotice({
732
+ enabled: root === 'import' && process.stderr.isTTY === true && flags['--json'] !== true,
733
+ notify: message => process.stderr.write(`${message}\n`),
734
+ });
735
+ process.once('exit', finishUpdateNotice);
730
736
  try {
731
737
  switch (root) {
732
738
  case 'whoami':
@@ -761,8 +767,13 @@ async function main() {
761
767
  }
762
768
  }
763
769
  catch (err) {
770
+ finishUpdateNotice();
764
771
  const msg = err instanceof Error ? err.message : String(err);
765
772
  fail(msg);
766
773
  }
774
+ finally {
775
+ finishUpdateNotice();
776
+ process.off('exit', finishUpdateNotice);
777
+ }
767
778
  }
768
779
  main();
@@ -54,8 +54,17 @@ export async function uploadImportArchive(input, cfg) {
54
54
  }
55
55
  return result.data.sourceToken;
56
56
  }
57
- export function startAppImport(body, cfg) {
58
- return apiCall('POST', '/api/app-import/start', body, cfg);
57
+ export async function startAppImport(body, cfg) {
58
+ const result = await apiCall('POST', '/api/app-import/start', body, cfg);
59
+ if (!result.ok && result.errorCode === 'APP_IMPORT_VERSION_CONFLICT') {
60
+ const raw = result.raw;
61
+ const latest = raw?.error?.latestVersionId;
62
+ const version = raw?.error?.appId === body.targetAppId &&
63
+ typeof latest === 'string' && /^[A-Za-z0-9_-]{1,128}$/u.test(latest)
64
+ ? `(当前平台版本 ${latest})` : '';
65
+ result.errorMessage = `应用已产生新版本${version}。请同步最新源码后重新导入;确认以本地源码覆盖时,重新执行 qmuse import . --force。`;
66
+ }
67
+ return result;
59
68
  }
60
69
  export function queryAppImportStatus(jobId, cfg) {
61
70
  return apiCall('POST', '/api/app-import/status', { jobId }, cfg);
@@ -0,0 +1,80 @@
1
+ import { readFileSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import semver from 'semver';
5
+ import { QMUSE_CLI_DISTRIBUTION, QMUSE_CLI_VERSION } from './version.js';
6
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
7
+ export function updateMessage(current, latest, distribution) {
8
+ if (typeof latest !== 'string' || !semver.valid(latest) || semver.prerelease(latest) ||
9
+ !semver.valid(current) || !semver.gt(latest, current))
10
+ return;
11
+ const command = distribution === 'PUBLIC'
12
+ ? 'npm install -g @qmuse/qmuse-cli@latest --registry=https://registry.npmjs.org/'
13
+ : 'tnpm install -g @alipay/qmuse-cli@latest';
14
+ return `qmuse-cli 有新版本 ${latest}(当前 ${current})。建议更新:${command}`;
15
+ }
16
+ // Check alongside the command; finishing a command never waits for the registry.
17
+ export function startUpdateNotice(options) {
18
+ if (!options.enabled)
19
+ return () => { };
20
+ const distribution = options.distribution ?? QMUSE_CLI_DISTRIBUTION;
21
+ const current = options.current ?? QMUSE_CLI_VERSION;
22
+ const directory = options.cacheDirectory ?? join(homedir(), '.cache', 'qmuse-cli');
23
+ const cachePath = join(directory, `update-${distribution.toLowerCase()}.json`);
24
+ const controller = new AbortController();
25
+ let latest;
26
+ let finished = false;
27
+ let cached = false;
28
+ try {
29
+ const value = JSON.parse(readFileSync(cachePath, 'utf8'));
30
+ const age = Date.now() - value.checkedAt;
31
+ if (Number.isFinite(age) && age >= 0 && age < CACHE_TTL_MS) {
32
+ latest = value.latest;
33
+ cached = true;
34
+ }
35
+ }
36
+ catch { /* Missing or corrupt cache must not affect the command. */ }
37
+ let timer;
38
+ if (!cached) {
39
+ timer = setTimeout(() => controller.abort(), 2000);
40
+ timer.unref();
41
+ const url = distribution === 'PUBLIC'
42
+ ? 'https://registry.npmjs.org/@qmuse%2fqmuse-cli/latest'
43
+ : 'https://registry.antgroup-inc.cn/@alipay%2fqmuse-cli/latest';
44
+ void (async () => {
45
+ try {
46
+ const response = await (options.fetcher ?? fetch)(url, {
47
+ signal: controller.signal,
48
+ redirect: 'error',
49
+ headers: { Accept: 'application/json' },
50
+ });
51
+ if (!response.ok)
52
+ return;
53
+ const metadata = await response.json();
54
+ if (finished || controller.signal.aborted || typeof metadata.version !== 'string' ||
55
+ !semver.valid(metadata.version) || semver.prerelease(metadata.version))
56
+ return;
57
+ latest = metadata.version;
58
+ try {
59
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
60
+ writeFileSync(cachePath, JSON.stringify({ checkedAt: Date.now(), latest }), { mode: 0o600 });
61
+ }
62
+ catch { /* Cache is best effort. */ }
63
+ }
64
+ catch { /* Update checks never fail the user's command. */ }
65
+ finally {
66
+ clearTimeout(timer);
67
+ }
68
+ })();
69
+ }
70
+ return () => {
71
+ if (finished)
72
+ return;
73
+ finished = true;
74
+ clearTimeout(timer);
75
+ controller.abort();
76
+ const message = updateMessage(current, latest, distribution);
77
+ if (message)
78
+ options.notify(message);
79
+ };
80
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qmuse/qmuse-cli",
3
- "version": "1.0.3-dev.3",
3
+ "version": "1.0.3-dev.4",
4
4
  "description": "将本地 React 项目安全导入 QMuse 的命令行客户端",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,6 +21,7 @@
21
21
  "dependencies": {
22
22
  "@napi-rs/keyring": "2.0.0",
23
23
  "ignore": "^7.0.8",
24
+ "semver": "7.7.4",
24
25
  "yazl": "^3.3.1"
25
26
  },
26
27
  "files": [
@@ -33,6 +34,7 @@
33
34
  "devDependencies": {
34
35
  "@playwright/test": "1.63.0",
35
36
  "@types/node": "^22.10.0",
37
+ "@types/semver": "7.7.1",
36
38
  "@types/yazl": "^3.3.1",
37
39
  "tsx": "^4.19.2",
38
40
  "typescript": "^5.9.3",