@geminilight/mindos 0.5.43 → 0.5.44
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/app/app/api/ask-sessions/route.ts +6 -4
- package/app/app/api/update-status/route.ts +19 -0
- package/app/components/CreateSpaceModal.tsx +87 -13
- package/app/components/DirPicker.tsx +2 -1
- package/app/components/GuideCard.tsx +38 -26
- package/app/components/HomeContent.tsx +6 -13
- package/app/components/ask/AskContent.tsx +10 -3
- package/app/components/ask/SessionHistory.tsx +39 -3
- package/app/components/explore/ExploreContent.tsx +50 -19
- package/app/components/explore/use-cases.ts +41 -13
- package/app/components/panels/DiscoverPanel.tsx +89 -99
- package/app/components/settings/SettingsContent.tsx +1 -1
- package/app/components/settings/UpdateTab.tsx +145 -28
- package/app/hooks/useAskSession.ts +24 -0
- package/app/lib/i18n-en.ts +25 -4
- package/app/lib/i18n-zh.ts +25 -4
- package/app/lib/utils.ts +11 -0
- package/bin/cli.js +87 -15
- package/bin/lib/startup.js +4 -0
- package/bin/lib/update-status.js +115 -0
- package/package.json +1 -1
- package/scripts/fix-postcss-deps.cjs +4 -2
package/app/lib/i18n-en.ts
CHANGED
|
@@ -25,6 +25,9 @@ export const en = {
|
|
|
25
25
|
spaceLocation: 'Location',
|
|
26
26
|
rootLevel: 'Root',
|
|
27
27
|
optional: 'optional',
|
|
28
|
+
aiInit: 'AI initialize content',
|
|
29
|
+
aiInitHint: 'AI will generate README and INSTRUCTION for this space',
|
|
30
|
+
aiInitNoKey: 'Configure an API key in Settings → AI to enable',
|
|
28
31
|
createSpace: 'Create',
|
|
29
32
|
cancelCreate: 'Cancel',
|
|
30
33
|
continueEditing: 'Continue editing',
|
|
@@ -108,6 +111,10 @@ export const en = {
|
|
|
108
111
|
'What are the key points?',
|
|
109
112
|
'Find related notes on this topic',
|
|
110
113
|
],
|
|
114
|
+
sessionHistory: 'Session History',
|
|
115
|
+
clearAll: 'Clear all',
|
|
116
|
+
confirmClear: 'Confirm clear?',
|
|
117
|
+
noSessions: 'No saved sessions.',
|
|
111
118
|
},
|
|
112
119
|
panels: {
|
|
113
120
|
agents: {
|
|
@@ -161,6 +168,8 @@ export const en = {
|
|
|
161
168
|
pluginMarketDesc: 'Extend how files are rendered and edited',
|
|
162
169
|
skillMarket: 'Skill Market',
|
|
163
170
|
skillMarketDesc: 'Add new abilities to your AI agents',
|
|
171
|
+
spaceTemplates: 'Space Templates',
|
|
172
|
+
spaceTemplatesDesc: 'Pre-built space structures for common workflows',
|
|
164
173
|
comingSoon: 'Coming soon',
|
|
165
174
|
viewAll: 'View all use cases',
|
|
166
175
|
tryIt: 'Try',
|
|
@@ -439,7 +448,9 @@ export const en = {
|
|
|
439
448
|
updatingHint: 'This may take 1–3 minutes. Do not close this page.',
|
|
440
449
|
updated: 'Updated successfully! Reloading...',
|
|
441
450
|
timeout: 'Update may still be in progress.',
|
|
442
|
-
timeoutHint: '
|
|
451
|
+
timeoutHint: 'The server may need more time to rebuild. Try refreshing.',
|
|
452
|
+
refreshButton: 'Refresh Page',
|
|
453
|
+
retryButton: 'Retry Update',
|
|
443
454
|
error: 'Failed to check for updates. Check your network connection.',
|
|
444
455
|
checkButton: 'Check for Updates',
|
|
445
456
|
updateButton: (version: string) => `Update to v${version}`,
|
|
@@ -666,12 +677,22 @@ export const en = {
|
|
|
666
677
|
subtitle: 'Discover what you can do with MindOS — pick a scenario and try it now.',
|
|
667
678
|
tryIt: 'Try it',
|
|
668
679
|
categories: {
|
|
669
|
-
'
|
|
670
|
-
'
|
|
671
|
-
'
|
|
680
|
+
'knowledge-management': 'Knowledge Management',
|
|
681
|
+
'memory-sync': 'Memory Sync',
|
|
682
|
+
'auto-execute': 'Auto Execute',
|
|
683
|
+
'experience-evolution': 'Experience Evolution',
|
|
684
|
+
'human-insights': 'Human Insights',
|
|
685
|
+
'audit-control': 'Audit & Control',
|
|
686
|
+
},
|
|
687
|
+
scenarios: {
|
|
688
|
+
'first-day': 'First Day',
|
|
689
|
+
'daily': 'Daily Work',
|
|
690
|
+
'project': 'Project Work',
|
|
672
691
|
'advanced': 'Advanced',
|
|
673
692
|
},
|
|
674
693
|
all: 'All',
|
|
694
|
+
byCapability: 'By Capability',
|
|
695
|
+
byScenario: 'By Scenario',
|
|
675
696
|
c1: {
|
|
676
697
|
title: 'Inject Your Identity',
|
|
677
698
|
desc: 'Tell all AI agents who you are — preferences, tech stack, communication style — in one shot.',
|
package/app/lib/i18n-zh.ts
CHANGED
|
@@ -50,6 +50,9 @@ export const zh = {
|
|
|
50
50
|
spaceLocation: '位置',
|
|
51
51
|
rootLevel: '根目录',
|
|
52
52
|
optional: '可选',
|
|
53
|
+
aiInit: 'AI 初始化内容',
|
|
54
|
+
aiInitHint: 'AI 将为此空间生成 README 和 INSTRUCTION',
|
|
55
|
+
aiInitNoKey: '在 设置 → AI 中配置 API 密钥以启用',
|
|
53
56
|
createSpace: '创建',
|
|
54
57
|
cancelCreate: '取消',
|
|
55
58
|
continueEditing: '继续编辑',
|
|
@@ -133,6 +136,10 @@ export const zh = {
|
|
|
133
136
|
'这篇文档的核心要点是什么?',
|
|
134
137
|
'查找与这个主题相关的笔记',
|
|
135
138
|
],
|
|
139
|
+
sessionHistory: '对话历史',
|
|
140
|
+
clearAll: '清除全部',
|
|
141
|
+
confirmClear: '确认清除?',
|
|
142
|
+
noSessions: '暂无历史对话。',
|
|
136
143
|
},
|
|
137
144
|
panels: {
|
|
138
145
|
agents: {
|
|
@@ -186,6 +193,8 @@ export const zh = {
|
|
|
186
193
|
pluginMarketDesc: '扩展文件的渲染和编辑方式',
|
|
187
194
|
skillMarket: '技能市场',
|
|
188
195
|
skillMarketDesc: '为 AI 智能体添加新能力',
|
|
196
|
+
spaceTemplates: '空间模板',
|
|
197
|
+
spaceTemplatesDesc: '预设的空间结构,适用于常见工作流场景',
|
|
189
198
|
comingSoon: '即将推出',
|
|
190
199
|
viewAll: '查看所有使用案例',
|
|
191
200
|
tryIt: '试试',
|
|
@@ -464,7 +473,9 @@ export const zh = {
|
|
|
464
473
|
updatingHint: '预计 1–3 分钟,请勿关闭此页面。',
|
|
465
474
|
updated: '更新成功!正在刷新...',
|
|
466
475
|
timeout: '更新可能仍在进行中。',
|
|
467
|
-
timeoutHint: '
|
|
476
|
+
timeoutHint: '服务器可能需要更多时间重新构建,请尝试刷新页面。',
|
|
477
|
+
refreshButton: '刷新页面',
|
|
478
|
+
retryButton: '重试更新',
|
|
468
479
|
error: '检查更新失败,请检查网络连接。',
|
|
469
480
|
checkButton: '检查更新',
|
|
470
481
|
updateButton: (version: string) => `更新到 v${version}`,
|
|
@@ -691,12 +702,22 @@ export const zh = {
|
|
|
691
702
|
subtitle: '发现 MindOS 能帮你做什么 — 选一个场景,立即体验。',
|
|
692
703
|
tryIt: '试一试',
|
|
693
704
|
categories: {
|
|
694
|
-
'
|
|
695
|
-
'
|
|
696
|
-
'
|
|
705
|
+
'knowledge-management': '知识管理',
|
|
706
|
+
'memory-sync': '记忆同步',
|
|
707
|
+
'auto-execute': '自动执行',
|
|
708
|
+
'experience-evolution': '经验进化',
|
|
709
|
+
'human-insights': '人类洞察',
|
|
710
|
+
'audit-control': '审计纠错',
|
|
711
|
+
},
|
|
712
|
+
scenarios: {
|
|
713
|
+
'first-day': '初次使用',
|
|
714
|
+
'daily': '日常工作',
|
|
715
|
+
'project': '项目协作',
|
|
697
716
|
'advanced': '高级',
|
|
698
717
|
},
|
|
699
718
|
all: '全部',
|
|
719
|
+
byCapability: '按能力',
|
|
720
|
+
byScenario: '按场景',
|
|
700
721
|
c1: {
|
|
701
722
|
title: '注入身份',
|
|
702
723
|
desc: '让所有 AI Agent 一次认识你 — 偏好、技术栈、沟通风格。',
|
package/app/lib/utils.ts
CHANGED
|
@@ -32,3 +32,14 @@ export function relativeTime(mtime: number, labels: {
|
|
|
32
32
|
if (days < 7) return labels.daysAgo(days);
|
|
33
33
|
return new Date(mtime).toLocaleDateString();
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
/** Extract leading emoji from a string, e.g. "📝 Notes" → "📝" */
|
|
37
|
+
export function extractEmoji(name: string): string {
|
|
38
|
+
const match = name.match(/^[\p{Emoji_Presentation}\p{Extended_Pictographic}]+/u);
|
|
39
|
+
return match?.[0] ?? '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Strip leading emoji+space from a string, e.g. "📝 Notes" → "Notes" */
|
|
43
|
+
export function stripEmoji(name: string): string {
|
|
44
|
+
return name.replace(/^[\p{Emoji_Presentation}\p{Extended_Pictographic}\s]+/u, '') || name;
|
|
45
|
+
}
|
package/bin/cli.js
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* mindos config validate — validate config file
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
|
-
import { execSync } from 'node:child_process';
|
|
41
|
+
import { execSync, spawn as nodeSpawn } from 'node:child_process';
|
|
42
42
|
import { existsSync, readFileSync, writeFileSync, rmSync, cpSync } from 'node:fs';
|
|
43
43
|
import { resolve } from 'node:path';
|
|
44
44
|
import { homedir } from 'node:os';
|
|
@@ -697,32 +697,41 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
697
697
|
|
|
698
698
|
// ── update ─────────────────────────────────────────────────────────────────
|
|
699
699
|
update: async () => {
|
|
700
|
+
const { writeUpdateStatus, writeUpdateFailed, clearUpdateStatus } = await import('./lib/update-status.js');
|
|
700
701
|
const currentVersion = (() => {
|
|
701
702
|
try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; }
|
|
702
703
|
})();
|
|
703
704
|
console.log(`\n${bold('⬆ Updating MindOS...')} ${dim(`(current: ${currentVersion})`)}\n`);
|
|
705
|
+
|
|
706
|
+
// Stage 1: Download
|
|
707
|
+
writeUpdateStatus('downloading', { fromVersion: currentVersion });
|
|
704
708
|
try {
|
|
705
709
|
execSync('npm install -g @geminilight/mindos@latest', { stdio: 'inherit' });
|
|
706
710
|
} catch {
|
|
711
|
+
writeUpdateFailed('downloading', 'npm install failed', { fromVersion: currentVersion });
|
|
707
712
|
console.error(red('Update failed. Try: npm install -g @geminilight/mindos@latest'));
|
|
708
713
|
process.exit(1);
|
|
709
714
|
}
|
|
710
715
|
if (existsSync(BUILD_STAMP)) rmSync(BUILD_STAMP);
|
|
711
716
|
|
|
712
|
-
//
|
|
717
|
+
// Resolve the new installation path (after npm install -g, ROOT is stale)
|
|
718
|
+
const updatedRoot = getUpdatedRoot();
|
|
719
|
+
const newVersion = (() => {
|
|
720
|
+
try { return JSON.parse(readFileSync(resolve(updatedRoot, 'package.json'), 'utf-8')).version; } catch { return '?'; }
|
|
721
|
+
})();
|
|
722
|
+
const vOpts = { fromVersion: currentVersion, toVersion: newVersion };
|
|
723
|
+
|
|
724
|
+
// Stage 2: Skills
|
|
725
|
+
writeUpdateStatus('skills', vOpts);
|
|
713
726
|
try {
|
|
714
|
-
const newRoot = getUpdatedRoot();
|
|
715
727
|
const { checkSkillVersions, updateSkill } = await import('./lib/skill-check.js');
|
|
716
|
-
const mismatches = checkSkillVersions(
|
|
728
|
+
const mismatches = checkSkillVersions(updatedRoot);
|
|
717
729
|
for (const m of mismatches) {
|
|
718
730
|
updateSkill(m.bundledPath, m.installPath);
|
|
719
731
|
console.log(` ${green('✓')} ${dim(`Skill ${m.name}: v${m.installed} → v${m.bundled}`)}`);
|
|
720
732
|
}
|
|
721
733
|
} catch { /* best-effort */ }
|
|
722
734
|
|
|
723
|
-
const newVersion = (() => {
|
|
724
|
-
try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; }
|
|
725
|
-
})();
|
|
726
735
|
if (newVersion !== currentVersion) {
|
|
727
736
|
console.log(`\n${green(`✔ Updated ${currentVersion} → ${newVersion}`)}`);
|
|
728
737
|
} else {
|
|
@@ -746,10 +755,12 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
746
755
|
console.log(cyan('\n Daemon is running — stopping to apply the new version...'));
|
|
747
756
|
await runGatewayCommand('stop');
|
|
748
757
|
|
|
749
|
-
//
|
|
750
|
-
|
|
751
|
-
buildIfNeeded(
|
|
758
|
+
// Stage 3: Rebuild
|
|
759
|
+
writeUpdateStatus('rebuilding', vOpts);
|
|
760
|
+
buildIfNeeded(updatedRoot);
|
|
752
761
|
|
|
762
|
+
// Stage 4: Restart
|
|
763
|
+
writeUpdateStatus('restarting', vOpts);
|
|
753
764
|
await runGatewayCommand('install');
|
|
754
765
|
// install() starts the service:
|
|
755
766
|
// - systemd: daemon-reload + enable + start
|
|
@@ -772,17 +783,78 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
772
783
|
console.log(` ${green('●')} MCP ${cyan(`http://localhost:${mcpPort}/mcp`)}`);
|
|
773
784
|
console.log(`\n ${dim('View changelog:')} ${cyan('https://github.com/GeminiLight/MindOS/releases')}`);
|
|
774
785
|
console.log(`${'─'.repeat(53)}\n`);
|
|
786
|
+
writeUpdateStatus('done', vOpts);
|
|
775
787
|
} else {
|
|
788
|
+
writeUpdateFailed('restarting', 'Server did not come back up in time', vOpts);
|
|
776
789
|
console.error(red('✘ MindOS did not come back up in time. Check logs: mindos logs\n'));
|
|
777
790
|
process.exit(1);
|
|
778
791
|
}
|
|
779
792
|
} else {
|
|
780
|
-
// Non-daemon mode:
|
|
781
|
-
|
|
793
|
+
// Non-daemon mode: check if a MindOS instance is currently running
|
|
794
|
+
// (e.g. user started via `mindos start`, or GUI triggered this update).
|
|
795
|
+
// If so, stop it and restart from the NEW installation path.
|
|
796
|
+
const updateConfig = (() => {
|
|
797
|
+
try { return JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch { return {}; }
|
|
798
|
+
})();
|
|
799
|
+
const webPort = Number(updateConfig.port ?? 3456);
|
|
800
|
+
const mcpPort = Number(updateConfig.mcpPort ?? 8781);
|
|
801
|
+
|
|
802
|
+
const wasRunning = await isPortInUse(webPort) || await isPortInUse(mcpPort);
|
|
803
|
+
|
|
804
|
+
if (wasRunning) {
|
|
805
|
+
console.log(cyan('\n MindOS is running — restarting to apply the new version...'));
|
|
806
|
+
stopMindos();
|
|
807
|
+
// Wait for ports to free (up to 15s)
|
|
808
|
+
const deadline = Date.now() + 15_000;
|
|
809
|
+
while (Date.now() < deadline) {
|
|
810
|
+
const busy = await isPortInUse(webPort) || await isPortInUse(mcpPort);
|
|
811
|
+
if (!busy) break;
|
|
812
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
813
|
+
}
|
|
782
814
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
815
|
+
// Stage 3: Rebuild
|
|
816
|
+
writeUpdateStatus('rebuilding', vOpts);
|
|
817
|
+
buildIfNeeded(updatedRoot);
|
|
818
|
+
|
|
819
|
+
// Stage 4: Restart
|
|
820
|
+
writeUpdateStatus('restarting', vOpts);
|
|
821
|
+
const newCliPath = resolve(updatedRoot, 'bin', 'cli.js');
|
|
822
|
+
const childEnv = { ...process.env };
|
|
823
|
+
delete childEnv.MINDOS_WEB_PORT;
|
|
824
|
+
delete childEnv.MINDOS_MCP_PORT;
|
|
825
|
+
delete childEnv.MIND_ROOT;
|
|
826
|
+
delete childEnv.AUTH_TOKEN;
|
|
827
|
+
delete childEnv.WEB_PASSWORD;
|
|
828
|
+
const child = nodeSpawn(
|
|
829
|
+
process.execPath, [newCliPath, 'start'],
|
|
830
|
+
{ detached: true, stdio: 'ignore', env: childEnv },
|
|
831
|
+
);
|
|
832
|
+
child.unref();
|
|
833
|
+
|
|
834
|
+
console.log(dim(' (Waiting for Web UI to come back up...)'));
|
|
835
|
+
const ready = await waitForHttp(webPort, { retries: 120, intervalMs: 2000, label: 'Web UI', logFile: LOG_PATH });
|
|
836
|
+
if (ready) {
|
|
837
|
+
const localIP = getLocalIP();
|
|
838
|
+
console.log(`\n${'─'.repeat(53)}`);
|
|
839
|
+
console.log(`${green('✔')} ${bold(`MindOS updated: ${currentVersion} → ${newVersion}`)}\n`);
|
|
840
|
+
console.log(` ${green('●')} Web UI ${cyan(`http://localhost:${webPort}`)}`);
|
|
841
|
+
if (localIP) console.log(` ${cyan(`http://${localIP}:${webPort}`)}`);
|
|
842
|
+
console.log(` ${green('●')} MCP ${cyan(`http://localhost:${mcpPort}/mcp`)}`);
|
|
843
|
+
console.log(`\n ${dim('View changelog:')} ${cyan('https://github.com/GeminiLight/MindOS/releases')}`);
|
|
844
|
+
console.log(`${'─'.repeat(53)}\n`);
|
|
845
|
+
writeUpdateStatus('done', vOpts);
|
|
846
|
+
} else {
|
|
847
|
+
writeUpdateFailed('restarting', 'Server did not come back up in time', vOpts);
|
|
848
|
+
console.error(red('✘ MindOS did not come back up in time. Check logs: mindos logs\n'));
|
|
849
|
+
process.exit(1);
|
|
850
|
+
}
|
|
851
|
+
} else {
|
|
852
|
+
// No running instance — just build and tell user to start manually
|
|
853
|
+
buildIfNeeded(updatedRoot);
|
|
854
|
+
console.log(`\n${green('✔')} ${bold(`Updated: ${currentVersion} → ${newVersion}`)}`);
|
|
855
|
+
console.log(dim(' Run `mindos start` to start the updated version.'));
|
|
856
|
+
console.log(` ${dim('View changelog:')} ${cyan('https://github.com/GeminiLight/MindOS/releases')}\n`);
|
|
857
|
+
}
|
|
786
858
|
}
|
|
787
859
|
},
|
|
788
860
|
|
package/bin/lib/startup.js
CHANGED
|
@@ -5,6 +5,7 @@ import { bold, dim, cyan, green, yellow } from './colors.js';
|
|
|
5
5
|
import { getSyncStatus } from './sync.js';
|
|
6
6
|
import { checkForUpdate, printUpdateHint } from './update-check.js';
|
|
7
7
|
import { runSkillCheck } from './skill-check.js';
|
|
8
|
+
import { clearUpdateStatus } from './update-status.js';
|
|
8
9
|
|
|
9
10
|
export function getLocalIP() {
|
|
10
11
|
try {
|
|
@@ -18,6 +19,9 @@ export function getLocalIP() {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export async function printStartupInfo(webPort, mcpPort) {
|
|
22
|
+
// Clear stale update status from previous update cycles
|
|
23
|
+
clearUpdateStatus();
|
|
24
|
+
|
|
21
25
|
// Fire update check immediately (non-blocking)
|
|
22
26
|
const updatePromise = checkForUpdate().catch(() => null);
|
|
23
27
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { MINDOS_DIR } from './constants.js';
|
|
4
|
+
|
|
5
|
+
const STATUS_PATH = resolve(MINDOS_DIR, 'update-status.json');
|
|
6
|
+
|
|
7
|
+
const STAGE_ORDER = ['downloading', 'skills', 'rebuilding', 'restarting'];
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Write update progress to ~/.mindos/update-status.json.
|
|
11
|
+
*
|
|
12
|
+
* @param {'downloading'|'skills'|'rebuilding'|'restarting'|'done'|'failed'} stage
|
|
13
|
+
* @param {{ error?: string, fromVersion?: string, toVersion?: string }} [opts]
|
|
14
|
+
*/
|
|
15
|
+
export function writeUpdateStatus(stage, opts = {}) {
|
|
16
|
+
const stages = STAGE_ORDER.map((id) => {
|
|
17
|
+
const idx = STAGE_ORDER.indexOf(id);
|
|
18
|
+
const currentIdx = STAGE_ORDER.indexOf(stage);
|
|
19
|
+
let status = 'pending';
|
|
20
|
+
if (stage === 'done' || stage === 'failed') {
|
|
21
|
+
// done: all stages done; failed: stages up to current are done, current is failed
|
|
22
|
+
if (stage === 'done') {
|
|
23
|
+
status = 'done';
|
|
24
|
+
} else {
|
|
25
|
+
// For 'failed', we don't know which stage failed from just 'failed'
|
|
26
|
+
// So we keep whatever was last written — this function is called per-stage
|
|
27
|
+
status = 'pending';
|
|
28
|
+
}
|
|
29
|
+
} else if (idx < currentIdx) {
|
|
30
|
+
status = 'done';
|
|
31
|
+
} else if (idx === currentIdx) {
|
|
32
|
+
status = 'running';
|
|
33
|
+
}
|
|
34
|
+
return { id, status };
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// For 'done', mark all as done
|
|
38
|
+
if (stage === 'done') {
|
|
39
|
+
stages.forEach((s) => { s.status = 'done'; });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const data = {
|
|
43
|
+
stage,
|
|
44
|
+
stages,
|
|
45
|
+
error: opts.error || null,
|
|
46
|
+
version: {
|
|
47
|
+
from: opts.fromVersion || null,
|
|
48
|
+
to: opts.toVersion || null,
|
|
49
|
+
},
|
|
50
|
+
startedAt: stage === 'downloading'
|
|
51
|
+
? new Date().toISOString()
|
|
52
|
+
: readCurrentStartedAt(),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
writeFileSync(STATUS_PATH, JSON.stringify(data, null, 2), 'utf-8');
|
|
57
|
+
} catch { /* best-effort — directory may not exist yet */ }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Mark a specific stage as failed. Preserves progress of prior stages.
|
|
62
|
+
*/
|
|
63
|
+
export function writeUpdateFailed(failedStage, errorMessage, opts = {}) {
|
|
64
|
+
const stages = STAGE_ORDER.map((id) => {
|
|
65
|
+
const idx = STAGE_ORDER.indexOf(id);
|
|
66
|
+
const failedIdx = STAGE_ORDER.indexOf(failedStage);
|
|
67
|
+
if (idx < failedIdx) return { id, status: 'done' };
|
|
68
|
+
if (idx === failedIdx) return { id, status: 'failed' };
|
|
69
|
+
return { id, status: 'pending' };
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const data = {
|
|
73
|
+
stage: 'failed',
|
|
74
|
+
stages,
|
|
75
|
+
error: errorMessage,
|
|
76
|
+
version: {
|
|
77
|
+
from: opts.fromVersion || null,
|
|
78
|
+
to: opts.toVersion || null,
|
|
79
|
+
},
|
|
80
|
+
startedAt: readCurrentStartedAt(),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
writeFileSync(STATUS_PATH, JSON.stringify(data, null, 2), 'utf-8');
|
|
85
|
+
} catch { /* best-effort */ }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Read the current update status. Returns null if no status file.
|
|
90
|
+
*/
|
|
91
|
+
export function readUpdateStatus() {
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(readFileSync(STATUS_PATH, 'utf-8'));
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Remove the status file (called on startup to clear stale state).
|
|
101
|
+
*/
|
|
102
|
+
export function clearUpdateStatus() {
|
|
103
|
+
try {
|
|
104
|
+
if (existsSync(STATUS_PATH)) unlinkSync(STATUS_PATH);
|
|
105
|
+
} catch { /* best-effort */ }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function readCurrentStartedAt() {
|
|
109
|
+
try {
|
|
110
|
+
const existing = JSON.parse(readFileSync(STATUS_PATH, 'utf-8'));
|
|
111
|
+
return existing.startedAt || new Date().toISOString();
|
|
112
|
+
} catch {
|
|
113
|
+
return new Date().toISOString();
|
|
114
|
+
}
|
|
115
|
+
}
|
package/package.json
CHANGED
|
@@ -16,9 +16,11 @@ const { join } = require('path');
|
|
|
16
16
|
const { execSync } = require('child_process');
|
|
17
17
|
|
|
18
18
|
const postcssDir = join('node_modules', 'next', 'node_modules', 'postcss');
|
|
19
|
-
|
|
19
|
+
// Check for an actual dependency, not just the node_modules directory
|
|
20
|
+
// (npm sometimes leaves an empty node_modules with only .bin and .package-lock.json)
|
|
21
|
+
const marker = join(postcssDir, 'node_modules', 'source-map-js');
|
|
20
22
|
|
|
21
|
-
if (existsSync(postcssDir) && !existsSync(
|
|
23
|
+
if (existsSync(postcssDir) && !existsSync(marker)) {
|
|
22
24
|
try {
|
|
23
25
|
execSync('npm install --no-save --install-strategy=nested', {
|
|
24
26
|
cwd: postcssDir,
|