akari-video 0.1.11 → 0.1.13
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/bin/akari.mjs +2 -0
- package/package.json +2 -2
- package/src/cli.mjs +43 -10
- package/src/messages.mjs +56 -8
- package/src/migrate-command.mjs +127 -0
- package/src/self-update.mjs +4 -0
- package/src/status-command.mjs +10 -2
- package/src/status-core/status.mjs +35 -26
- package/src/update-check.mjs +49 -10
- package/vendor/.akari-capability-sources.json +1 -0
- package/vendor/docs/contract-2026-07-17-data-contract-versioning.md +33 -0
- package/vendor/docs/contract-2026-07-22-render-basics.md +2 -0
- package/vendor/docs/contract-2026-07-25-r6-audio-tracks-and-trim.md +41 -0
- package/vendor/docs/contract-2026-08-02-preview-parity.md +1 -1
- package/vendor/docs/contract-2026-08-03-caption-display-encoding-qc-v1.md +34 -0
- package/vendor/docs/contract-2026-08-12-still-image-cut-source-v0.md +13 -0
- package/vendor/docs/contract-2026-08-18-v1-render-parity.md +115 -0
- package/vendor/packages/akari-launcher/package.json +2 -2
- package/vendor/packages/audio-library-setup/README.md +2 -2
- package/vendor/packages/audio-library-setup/bin/beat-grid.mjs +20 -12
- package/vendor/packages/audio-library-setup/shared/beat-grid.mjs +60 -11
- package/vendor/packages/audio-library-setup/test/beat-grid.test.mjs +58 -24
- package/vendor/packages/edit-lint/src/edit-lint.mjs +522 -625
- package/vendor/packages/edit-store/lib/caption-display.js +4 -2
- package/vendor/packages/edit-store/lib/edit-store.d.ts +24 -25
- package/vendor/packages/edit-store/lib/edit-store.js +87 -576
- package/vendor/packages/edit-store/lib/edit-v2.d.ts +188 -0
- package/vendor/packages/edit-store/lib/edit-v2.js +367 -0
- package/vendor/packages/edit-store/lib/index.d.ts +6 -0
- package/vendor/packages/edit-store/lib/index.js +12 -0
- package/vendor/packages/edit-store/lib/internal-model.d.ts +192 -0
- package/vendor/packages/edit-store/lib/internal-model.js +832 -0
- package/vendor/packages/edit-store/lib/migrate/error.d.ts +4 -0
- package/vendor/packages/edit-store/lib/migrate/error.js +13 -0
- package/vendor/packages/edit-store/lib/migrate/index.d.ts +53 -0
- package/vendor/packages/edit-store/lib/migrate/index.js +611 -0
- package/vendor/packages/edit-store/lib/migrate/legacy-parse.d.ts +31 -0
- package/vendor/packages/edit-store/lib/migrate/legacy-parse.js +556 -0
- package/vendor/packages/edit-store/lib/retime.d.ts +9 -0
- package/vendor/packages/edit-store/lib/retime.js +93 -0
- package/vendor/packages/edit-store/lib/timeline-map.d.ts +1 -8
- package/vendor/packages/edit-store/lib/timeline-map.js +1 -48
- package/vendor/packages/edit-store/lib/track-order.d.ts +38 -0
- package/vendor/packages/edit-store/lib/track-order.js +63 -0
- package/vendor/packages/edit-store/lib/webview-kernel.js +0 -37
- package/vendor/packages/edit-store/lib/write-gate.d.ts +41 -15
- package/vendor/packages/edit-store/lib/write-gate.js +91 -70
- package/vendor/packages/media-bin/package.json +2 -1
- package/vendor/packages/media-bin/scripts/build-whisper.mjs +224 -0
- package/vendor/packages/media-bin/scripts/fetch-binaries.mjs +22 -1
- package/vendor/packages/media-bin/src/binary-manifest.mjs +60 -3
- package/vendor/packages/media-bin/src/index.mjs +71 -17
- package/vendor/packages/media-bin/test/whisper.test.mjs +142 -0
- package/vendor/packages/project-scaffold/src/index.mjs +10 -1
- package/vendor/packages/project-scaffold/test/create-project.test.mjs +10 -2
- package/vendor/packages/schemas/bin/validate-edit.mjs +12 -1
- package/vendor/packages/schemas/edit.schema.json +314 -5
- package/vendor/packages/schemas/examples/edit-sfx-fade-invalid/edit.json +10 -0
- package/vendor/packages/schemas/examples/edit-sfx-in-out-valid/edit.json +1 -1
- package/vendor/packages/schemas/examples/edit-v2-audio-track-valid/edit.json +56 -0
- package/vendor/packages/schemas/examples/edit-v2-fractional-fps-invalid/edit.json +6 -0
- package/vendor/packages/schemas/examples/edit-v2-html-in-out-invalid/edit.json +19 -0
- package/vendor/packages/schemas/examples/edit-v2-item-kind-field-invalid/edit.json +20 -0
- package/vendor/packages/schemas/examples/edit-v2-items-content-invalid/edit.json +13 -0
- package/vendor/packages/schemas/examples/edit-v2-minimal-valid/edit.json +6 -0
- package/vendor/packages/schemas/examples/edit-v2-telop-in-out-invalid/edit.json +19 -0
- package/vendor/packages/schemas/examples/edit-v2-valid/edit.json +122 -0
- package/vendor/packages/schemas/test/edit-v2-schema.test.mjs +179 -0
- package/vendor/packages/schemas/test/validate-edit.test.mjs +17 -0
- package/vendor/skills/analyze-footage/media-and-transcript.md +15 -10
- package/vendor/skills/edit-plan/SKILL.md +13 -11
- package/vendor/skills/edit-plan/approvals-and-generation.md +2 -2
- package/vendor/skills/edit-plan/beat-sync.md +52 -104
- package/vendor/skills/edit-plan/beats.md +11 -9
- package/vendor/skills/edit-plan/emphasis-detection.md +11 -44
- package/vendor/skills/edit-plan/execution.md +75 -93
- package/vendor/skills/edit-plan/report-guide.md +4 -4
- package/vendor/skills/edit-plan/workflow.md +1 -1
- package/vendor/skills/export-nle/SKILL.md +26 -6
- package/vendor/skills/overlay-authoring/SKILL.md +13 -1
- package/vendor/skills/setup-library/SKILL.md +1 -1
- package/vendor/skills/setup-library/tools-check.md +74 -17
- package/vendor/skills/verify/SKILL.md +26 -16
package/bin/akari.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { runAcceptCommand } from '../src/accept-command.mjs';
|
|
|
10
10
|
import { runCapabilityCommand } from '../src/capability-command.mjs';
|
|
11
11
|
import { runStoreCommand } from '../src/store-command.mjs';
|
|
12
12
|
import { runAssetsCommand } from '../src/assets-command.mjs';
|
|
13
|
+
import { runMigrateCommand } from '../src/migrate-command.mjs';
|
|
13
14
|
import { maybeApplyPendingUpdateOnLaunch, readOwnVersion } from '../src/update-check.mjs';
|
|
14
15
|
import { describeCliHelp } from '../src/messages.mjs';
|
|
15
16
|
|
|
@@ -67,6 +68,7 @@ const invoke = (argv[0] === '--version' || argv[0] === '-v') ? printVersion()
|
|
|
67
68
|
: argv[0] === 'capability' ? runCapabilityCommand(argv.slice(1))
|
|
68
69
|
: argv[0] === 'store' ? runStoreCommand(argv.slice(1))
|
|
69
70
|
: argv[0] === 'assets' ? runAssetsCommand(argv.slice(1))
|
|
71
|
+
: argv[0] === 'migrate' ? runMigrateCommand(argv.slice(1))
|
|
70
72
|
: run(argv);
|
|
71
73
|
|
|
72
74
|
const result = await invoke.catch((error) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "akari-video",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "AKARI Video launcher CLI — start an AI-edited video project from any directory: scaffold, connection check, then hand over to Claude Code (or opencode). AKARI Video を opencode や Claude Code で、どのディレクトリからでも始めるための `akari` ランチャー CLI。接続確認(doctor)→ 未セットアップならプロジェクト雛形を作成 → AI エージェントを起動する。外部 npm 依存ゼロ(Node.js 組み込みモジュールのみ)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,6 +37,6 @@
|
|
|
37
37
|
"scripts": {
|
|
38
38
|
"prepack": "node scripts/prepack.mjs",
|
|
39
39
|
"postpack": "node scripts/prepack.mjs clean",
|
|
40
|
-
"test": "node --test test/*.mjs"
|
|
40
|
+
"test": "node --test --test-concurrency=1 test/*.mjs"
|
|
41
41
|
}
|
|
42
42
|
}
|
package/src/cli.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { resolveLauncherAssets } from './repo-assets.mjs';
|
|
|
6
6
|
import { detectProjectState } from './project-state.mjs';
|
|
7
7
|
import { findClaudeExecutable, findOpencodeExecutable } from './path-lookup.mjs';
|
|
8
8
|
import { loadTaskLabels } from './task-labels.mjs';
|
|
9
|
-
import { describeIntake, claudeMissingGuidance, opencodeMissingGuidance, describeUpdateCommand, describeVersionStatus, formatUpdateNotice } from './messages.mjs';
|
|
9
|
+
import { describeInstalledVersions, describeIntake, claudeMissingGuidance, opencodeMissingGuidance, describeUpdateCommand, describeVersionStatus, formatUpdateNotice } from './messages.mjs';
|
|
10
10
|
import { resolveEffectiveProjectRoot } from './first-run.mjs';
|
|
11
11
|
import { maybeShowAssetIntroNotice } from './sounds-setup.mjs';
|
|
12
12
|
import {
|
|
@@ -14,9 +14,11 @@ import {
|
|
|
14
14
|
compareVersions,
|
|
15
15
|
isValidFeedShape,
|
|
16
16
|
readCacheSync,
|
|
17
|
+
readInstalledAppVersion,
|
|
17
18
|
readOwnVersion,
|
|
18
19
|
recordDismissalSync,
|
|
19
20
|
resolveCachePath,
|
|
21
|
+
resolveInstalledVersionInfo,
|
|
20
22
|
triggerBackgroundRefresh
|
|
21
23
|
} from './update-check.mjs';
|
|
22
24
|
import { applySelfUpdate, isRunningFromAppDir, rollbackSelfUpdate } from './self-update.mjs';
|
|
@@ -41,7 +43,8 @@ export async function run(args, options = {}) {
|
|
|
41
43
|
const spawnOpencode = options.spawnOpencode ?? defaultSpawnOpencode;
|
|
42
44
|
const env = options.env ?? process.env;
|
|
43
45
|
const platform = options.platform ?? process.platform;
|
|
44
|
-
const
|
|
46
|
+
const versionInfo = resolveCommandVersionInfo(options, env);
|
|
47
|
+
const currentVersion = versionInfo.currentVersion;
|
|
45
48
|
const now = options.now ?? new Date();
|
|
46
49
|
|
|
47
50
|
// --opencode / --claude / --claudecode / --yes / --here フラグを解析
|
|
@@ -92,13 +95,13 @@ export async function run(args, options = {}) {
|
|
|
92
95
|
} catch (error) {
|
|
93
96
|
log(`接続確認でエラーが発生しました(続行します): ${error instanceof Error ? error.message : String(error)}`);
|
|
94
97
|
}
|
|
95
|
-
log(describeVersionStatus(
|
|
98
|
+
log(describeVersionStatus(versionInfo, readCacheSync(resolveCachePath(env))));
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
// 新版通知(契約 §4-1): キャッシュの読み比較のみ・ネットワークには一切触れない
|
|
99
102
|
// (起動をブロックしない)。fetch は detached な子プロセスへ切り離し、
|
|
100
103
|
// 結果は次回セッションで効く。
|
|
101
|
-
const updateNotice = formatUpdateNotice((options.checkUpdate ?? checkForUpdateSync)({ currentVersion, env }));
|
|
104
|
+
const updateNotice = formatUpdateNotice((options.checkUpdate ?? checkForUpdateSync)({ currentVersion, versionInfo, env }));
|
|
102
105
|
if (updateNotice) {
|
|
103
106
|
log(updateNotice);
|
|
104
107
|
}
|
|
@@ -178,22 +181,26 @@ function defaultSpawnOpencode(opencodePath, args, projectRoot) {
|
|
|
178
181
|
/**
|
|
179
182
|
* `akari update`: 新版があり、かつ自己更新の対象(app 経由実行 + フィードに
|
|
180
183
|
* `components.app` がある)なら DL → sha256 検証 → 適用まで実行する(契約 §11。
|
|
184
|
+
* `app/.akari-install-ref` がある管理インストールは、npm 側 CLI から実行した場合も対象。
|
|
181
185
|
* 「update は明示操作」なので U2 の沈黙原則は適用せず、失敗は必ず表示する)。
|
|
182
186
|
* 対象外(npm グローバル / git checkout・旧フィード)のときは従来どおり
|
|
183
187
|
* **案内するだけ**(自動実行はしない — 契約 §4-1)に縮退する。`--dismiss` は
|
|
184
188
|
* 自己更新を試みず、キャッシュに載っている最新版の通知を今後出さないよう記録するだけ
|
|
185
|
-
* (既存挙動を維持)。`--rollback` は直前 1
|
|
189
|
+
* (既存挙動を維持)。`--force` は同じ版の本体も再導入し、`--rollback` は直前 1 世代
|
|
190
|
+
* (`~/.akari/app-previous/`)へ戻す。
|
|
186
191
|
* ネットワークに触れるのは自己更新の DL 区間のみ — フィード自体は既存キャッシュ由来
|
|
187
192
|
* (最新情報は `akari` 起動時のバックグラウンド fetch で更新される)。
|
|
188
193
|
*/
|
|
189
194
|
export async function runUpdateCommand(args, options = {}) {
|
|
190
195
|
const log = options.log ?? ((line) => console.log(line));
|
|
191
196
|
const env = options.env ?? process.env;
|
|
192
|
-
const
|
|
197
|
+
const versionInfo = resolveCommandVersionInfo(options, env);
|
|
198
|
+
const currentVersion = versionInfo.currentVersion;
|
|
193
199
|
const cachePath = resolveCachePath(env);
|
|
194
200
|
const cache = readCacheSync(cachePath);
|
|
195
201
|
const dismissRequested = args.includes('--dismiss');
|
|
196
202
|
const rollbackRequested = args.includes('--rollback');
|
|
203
|
+
const forceRequested = args.includes('--force');
|
|
197
204
|
|
|
198
205
|
if (rollbackRequested) {
|
|
199
206
|
return (options.rollbackSelfUpdate ?? rollbackSelfUpdate)({ env, log });
|
|
@@ -206,7 +213,7 @@ export async function runUpdateCommand(args, options = {}) {
|
|
|
206
213
|
dismissed = true;
|
|
207
214
|
}
|
|
208
215
|
const finalCache = dismissed ? readCacheSync(cachePath) : cache;
|
|
209
|
-
for (const line of describeUpdateCommand({ currentVersion, cache: finalCache, dismissed })) {
|
|
216
|
+
for (const line of describeUpdateCommand({ currentVersion, versionInfo, cache: finalCache, dismissed })) {
|
|
210
217
|
log(line);
|
|
211
218
|
}
|
|
212
219
|
return { exitCode: 0 };
|
|
@@ -214,18 +221,28 @@ export async function runUpdateCommand(args, options = {}) {
|
|
|
214
221
|
|
|
215
222
|
const feed = cache?.feed;
|
|
216
223
|
const updateAvailable = isValidFeedShape(feed) && compareVersions(feed.product, currentVersion) > 0;
|
|
217
|
-
const
|
|
224
|
+
const reinstallRequested = forceRequested && isValidFeedShape(feed) && compareVersions(feed.product, currentVersion) >= 0;
|
|
225
|
+
const hasManagedApp = !!readInstalledAppVersion(env);
|
|
226
|
+
const selfUpdateEligible = (updateAvailable || reinstallRequested)
|
|
218
227
|
&& !!feed.components?.app?.url
|
|
219
228
|
&& !!feed.components?.app?.sha256
|
|
220
|
-
&& (options.isRunningFromAppDir ?? isRunningFromAppDir)({ env, launcherRoot: options.launcherRoot });
|
|
229
|
+
&& (hasManagedApp || (options.isRunningFromAppDir ?? isRunningFromAppDir)({ env, launcherRoot: options.launcherRoot }));
|
|
221
230
|
|
|
222
231
|
if (!selfUpdateEligible) {
|
|
223
|
-
for (const line of describeUpdateCommand({ currentVersion, cache, dismissed: false })) {
|
|
232
|
+
for (const line of describeUpdateCommand({ currentVersion, versionInfo, cache, dismissed: false })) {
|
|
224
233
|
log(line);
|
|
225
234
|
}
|
|
226
235
|
return { exitCode: 0 };
|
|
227
236
|
}
|
|
228
237
|
|
|
238
|
+
for (const line of describeInstalledVersions(versionInfo)) {
|
|
239
|
+
log(line);
|
|
240
|
+
}
|
|
241
|
+
log(`最新バージョン: v${feed.product}`);
|
|
242
|
+
if (forceRequested) {
|
|
243
|
+
log(`--force: 本体 v${currentVersion} を入れ直します。`);
|
|
244
|
+
}
|
|
245
|
+
|
|
229
246
|
return (options.applySelfUpdate ?? applySelfUpdate)({
|
|
230
247
|
env,
|
|
231
248
|
feed,
|
|
@@ -234,3 +251,19 @@ export async function runUpdateCommand(args, options = {}) {
|
|
|
234
251
|
runNpmInstall: options.runNpmInstall
|
|
235
252
|
});
|
|
236
253
|
}
|
|
254
|
+
|
|
255
|
+
function resolveCommandVersionInfo(options, env) {
|
|
256
|
+
// 既存テスト/埋め込み利用の currentVersion 注入は CLI 版注入としても扱う。
|
|
257
|
+
const cliVersion = options.cliVersion ?? options.currentVersion ?? readOwnVersion();
|
|
258
|
+
if (options.currentVersion !== undefined) {
|
|
259
|
+
const appVersion = options.appVersion ?? null;
|
|
260
|
+
return {
|
|
261
|
+
cliVersion,
|
|
262
|
+
appVersion,
|
|
263
|
+
currentVersion: options.currentVersion,
|
|
264
|
+
source: appVersion ? 'install-ref' : 'cli-fallback',
|
|
265
|
+
mismatch: appVersion !== null && compareVersions(cliVersion, appVersion) !== 0
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
return resolveInstalledVersionInfo({ env, cliVersion });
|
|
269
|
+
}
|
package/src/messages.mjs
CHANGED
|
@@ -59,18 +59,64 @@ export function opencodeMissingGuidance() {
|
|
|
59
59
|
*/
|
|
60
60
|
export function formatUpdateNotice(status) {
|
|
61
61
|
if (!status?.available) {
|
|
62
|
+
if (status?.mismatch) {
|
|
63
|
+
return formatVersionMismatch(status);
|
|
64
|
+
}
|
|
62
65
|
return null;
|
|
63
66
|
}
|
|
64
|
-
|
|
67
|
+
const current = status.mismatch
|
|
68
|
+
? `CLI v${status.cliVersion} / 本体 v${status.appVersion} → ${versionRelationLabel(status)}`
|
|
69
|
+
: `現在 v${status.currentVersion}`;
|
|
70
|
+
return `⬆ AKARI Video v${status.latestVersion}${channelSuffix(status.channel)}があります(${current})→ 詳細: akari update`;
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
/** `akari doctor` 系出力に足す 1 行(現在版 + フィード取得状態)。 */
|
|
68
|
-
export function describeVersionStatus(
|
|
74
|
+
export function describeVersionStatus(versionOrInfo, cache) {
|
|
75
|
+
if (typeof versionOrInfo === 'string') {
|
|
76
|
+
if (!cache?.feed) {
|
|
77
|
+
return `バージョン: v${versionOrInfo}(更新フィード: 未取得)`;
|
|
78
|
+
}
|
|
79
|
+
const fetchedAt = typeof cache.fetched_at === 'string' ? cache.fetched_at : '不明';
|
|
80
|
+
return `バージョン: v${versionOrInfo}(更新フィード: 取得済み・${fetchedAt} 時点)`;
|
|
81
|
+
}
|
|
82
|
+
const info = normalizeVersionInfo(versionOrInfo);
|
|
83
|
+
const installed = describeInstalledVersions(info).join(' / ');
|
|
69
84
|
if (!cache?.feed) {
|
|
70
|
-
return
|
|
85
|
+
return `${installed}(更新フィード: 未取得)`;
|
|
71
86
|
}
|
|
72
87
|
const fetchedAt = typeof cache.fetched_at === 'string' ? cache.fetched_at : '不明';
|
|
73
|
-
return
|
|
88
|
+
return `${installed}(更新フィード: 取得済み・${fetchedAt} 時点)`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function describeInstalledVersions(versionOrInfo) {
|
|
92
|
+
const info = normalizeVersionInfo(versionOrInfo);
|
|
93
|
+
if (!info.appVersion) {
|
|
94
|
+
return [
|
|
95
|
+
`現在のバージョン: v${info.currentVersion}`,
|
|
96
|
+
`CLI バージョン: v${info.cliVersion}`,
|
|
97
|
+
`本体バージョン: 未記録(更新判定は CLI v${info.currentVersion} へフォールバック)`
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
const lines = [`CLI バージョン: v${info.cliVersion}`, `本体バージョン: v${info.appVersion}(更新判定の基準)`];
|
|
101
|
+
if (info.mismatch) {
|
|
102
|
+
lines.push(`版のずれ: CLI v${info.cliVersion} / 本体 v${info.appVersion} → ${versionRelationLabel(info)}`);
|
|
103
|
+
}
|
|
104
|
+
return lines;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function normalizeVersionInfo(value) {
|
|
108
|
+
if (typeof value === 'string') {
|
|
109
|
+
return { cliVersion: value, appVersion: null, currentVersion: value, mismatch: false };
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function versionRelationLabel(info) {
|
|
115
|
+
return compareVersions(info.appVersion, info.cliVersion) < 0 ? '本体が古い' : 'CLI と本体の版が不一致';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function formatVersionMismatch(info) {
|
|
119
|
+
return `⚠ CLI v${info.cliVersion} / 本体 v${info.appVersion} → ${versionRelationLabel(info)}。\`akari update\` で本体を更新してください。`;
|
|
74
120
|
}
|
|
75
121
|
|
|
76
122
|
/**
|
|
@@ -107,8 +153,9 @@ export function creatorRootPromptText(defaultPath) {
|
|
|
107
153
|
* `akari update` の出力本文(複数行)。フィード未取得・最新・新版ありで案内が変わる。
|
|
108
154
|
* `dismissed` は今回の実行で dismiss 記録を書いたかどうか(表示文言の切り替えのみに使う)。
|
|
109
155
|
*/
|
|
110
|
-
export function describeUpdateCommand({ currentVersion, cache, dismissed }) {
|
|
111
|
-
const
|
|
156
|
+
export function describeUpdateCommand({ currentVersion, versionInfo, cache, dismissed }) {
|
|
157
|
+
const info = versionInfo ?? normalizeVersionInfo(currentVersion);
|
|
158
|
+
const lines = versionInfo ? describeInstalledVersions(info) : [`現在のバージョン: v${currentVersion}`];
|
|
112
159
|
const feed = cache?.feed;
|
|
113
160
|
if (!feed) {
|
|
114
161
|
lines.push('最新情報をまだ取得できていません(オフライン、または初回起動直後の可能性があります)。');
|
|
@@ -121,7 +168,7 @@ export function describeUpdateCommand({ currentVersion, cache, dismissed }) {
|
|
|
121
168
|
lines.push(`リリースノート: ${feed.notes_url}`);
|
|
122
169
|
}
|
|
123
170
|
|
|
124
|
-
if (compareVersions(feed.product, currentVersion) <= 0) {
|
|
171
|
+
if (compareVersions(feed.product, info.currentVersion) <= 0) {
|
|
125
172
|
lines.push('お使いのバージョンは最新です。');
|
|
126
173
|
return lines;
|
|
127
174
|
}
|
|
@@ -222,8 +269,9 @@ export function describeCliHelp() {
|
|
|
222
269
|
' (引数なし) プロジェクトを開いて AI エージェントを起動(未作成なら自動作成)',
|
|
223
270
|
' store connect アカウント連携(無料の素材パックと購入済み素材が使えるようになる)',
|
|
224
271
|
' sounds 公式音源ライブラリを一括ダウンロード(無料)',
|
|
225
|
-
' update
|
|
272
|
+
' update [--force] 更新を確認する(--force で本体を入れ直す)',
|
|
226
273
|
' status 接続状態を確認する',
|
|
274
|
+
' migrate [dir] 古い edit.json を退避バックアップ付きで v2 へ変換',
|
|
227
275
|
'',
|
|
228
276
|
'開発者向け:',
|
|
229
277
|
' --opencode Claude Code の代わりに opencode を起動する',
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
|
|
6
|
+
import { resolveLauncherAssets } from './repo-assets.mjs';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
export async function runMigrateCommand(args, options = {}) {
|
|
11
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
12
|
+
const error = options.error ?? ((line) => console.error(line));
|
|
13
|
+
const cwd = options.cwd ?? process.cwd();
|
|
14
|
+
const parsed = parseArguments(args, cwd);
|
|
15
|
+
if (!parsed.ok) {
|
|
16
|
+
error(parsed.message);
|
|
17
|
+
return { exitCode: 2 };
|
|
18
|
+
}
|
|
19
|
+
if (parsed.help) {
|
|
20
|
+
for (const line of migrateHelp()) log(line);
|
|
21
|
+
return { exitCode: 0 };
|
|
22
|
+
}
|
|
23
|
+
const projectRoot = parsed.projectRoot;
|
|
24
|
+
const editPath = path.join(projectRoot, 'edit.json');
|
|
25
|
+
let text;
|
|
26
|
+
try {
|
|
27
|
+
text = await (options.readFile ?? readFile)(editPath, 'utf8');
|
|
28
|
+
} catch (cause) {
|
|
29
|
+
error(`edit.json を読めません: ${editPath} (${messageOf(cause)})`);
|
|
30
|
+
return { exitCode: 2 };
|
|
31
|
+
}
|
|
32
|
+
const migrate = options.migrate ?? loadMigrateModule(options.assets ?? resolveLauncherAssets());
|
|
33
|
+
const proposal = migrate.planMigration(projectRoot, editPath, text, { now: options.now });
|
|
34
|
+
if (proposal.ok === false) {
|
|
35
|
+
if (parsed.json) {
|
|
36
|
+
log(JSON.stringify({ ok: false, error: 'このプロジェクトは変換できません', blockers: proposal.blockers }));
|
|
37
|
+
} else {
|
|
38
|
+
error('このプロジェクトは変換できません。');
|
|
39
|
+
for (const blocker of proposal.blockers) error(`- ${blocker}`);
|
|
40
|
+
}
|
|
41
|
+
return { exitCode: 2 };
|
|
42
|
+
}
|
|
43
|
+
if (parsed.json) {
|
|
44
|
+
log(JSON.stringify({
|
|
45
|
+
ok: true, dryRun: parsed.dryRun, version: proposal.version,
|
|
46
|
+
filePath: proposal.filePath, backupPath: proposal.backupPath, changes: proposal.changes
|
|
47
|
+
}));
|
|
48
|
+
} else {
|
|
49
|
+
log(`変換対象: ${proposal.filePath} (version ${proposal.version} -> 2)`);
|
|
50
|
+
for (const change of proposal.changes) log(`- ${change.path}: ${change.note}`);
|
|
51
|
+
log(`変換前の退避先: ${proposal.backupPath}`);
|
|
52
|
+
}
|
|
53
|
+
if (parsed.dryRun) {
|
|
54
|
+
if (!parsed.json) log('--dry-run のため、ファイルは変更しません。');
|
|
55
|
+
return { exitCode: 0, proposal };
|
|
56
|
+
}
|
|
57
|
+
if (!parsed.yes) {
|
|
58
|
+
const isTTY = options.isTTY ?? (process.stdin.isTTY && process.stdout.isTTY);
|
|
59
|
+
if (!isTTY) {
|
|
60
|
+
error('非 TTY では明示承認が必要です。内容を確認し、--yes を付けて再実行してください。');
|
|
61
|
+
return { exitCode: 2, proposal };
|
|
62
|
+
}
|
|
63
|
+
const accepted = options.confirm
|
|
64
|
+
? await options.confirm()
|
|
65
|
+
: await promptForConfirmation();
|
|
66
|
+
if (!accepted) {
|
|
67
|
+
if (!parsed.json) log('変換しませんでした。edit.json は変更されていません。');
|
|
68
|
+
return { exitCode: 0, proposal };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
await migrate.applyMigration(proposal);
|
|
72
|
+
if (!parsed.json) log(`version 2 へ変換しました。元ファイル: ${proposal.backupPath}`);
|
|
73
|
+
return { exitCode: 0, proposal };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function loadMigrateModule(assets) {
|
|
77
|
+
const modulePath = path.join(assets.repoRoot, 'packages', 'edit-store', 'lib', 'migrate', 'index.js');
|
|
78
|
+
try {
|
|
79
|
+
return require(modulePath);
|
|
80
|
+
} catch (cause) {
|
|
81
|
+
throw new Error(`変換器を読み込めません: ${modulePath} (${messageOf(cause)})`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseArguments(args, cwd) {
|
|
86
|
+
const flags = new Set(args.filter(value => value.startsWith('-')));
|
|
87
|
+
const unknown = [...flags].filter(value => !['--yes', '-y', '--dry-run', '--json', '--help', '-h'].includes(value));
|
|
88
|
+
if (unknown.length > 0) return { ok: false, message: `未知のオプションです: ${unknown.join(', ')}` };
|
|
89
|
+
const positional = args.filter(value => !value.startsWith('-'));
|
|
90
|
+
if (positional.length > 1) return { ok: false, message: '引数のプロジェクトディレクトリは 1 つだけ指定できます。' };
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
help: flags.has('--help') || flags.has('-h'),
|
|
94
|
+
yes: flags.has('--yes') || flags.has('-y'),
|
|
95
|
+
dryRun: flags.has('--dry-run'),
|
|
96
|
+
json: flags.has('--json'),
|
|
97
|
+
projectRoot: path.resolve(cwd, positional[0] ?? '.')
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function promptForConfirmation() {
|
|
102
|
+
const readline = createInterface({ input: process.stdin, output: process.stdout });
|
|
103
|
+
try {
|
|
104
|
+
const answer = await readline.question('上記の内容で version 2 へ変換しますか? [y/N] ');
|
|
105
|
+
return /^(?:y|yes)$/iu.test(answer.trim());
|
|
106
|
+
} finally {
|
|
107
|
+
readline.close();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function migrateHelp() {
|
|
112
|
+
return [
|
|
113
|
+
'使い方: akari migrate [dir] [--yes] [--dry-run] [--json]',
|
|
114
|
+
'',
|
|
115
|
+
'v0/v1 の edit.json を v2 へ片道変換します。',
|
|
116
|
+
'既定は変更内容を表示して y/n で確認し、変換前の全文を .akari/backup/ へ退避します。',
|
|
117
|
+
'',
|
|
118
|
+
' --yes, -y 表示後の確認を省略',
|
|
119
|
+
' --dry-run 提案の表示だけで書き込まない',
|
|
120
|
+
' --json 機械可読な JSON を出力',
|
|
121
|
+
' --help, -h このヘルプを表示'
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function messageOf(error) {
|
|
126
|
+
return error instanceof Error ? error.message : String(error);
|
|
127
|
+
}
|
package/src/self-update.mjs
CHANGED
|
@@ -378,6 +378,10 @@ export function rollbackSelfUpdate({ env = process.env, log = () => {} } = {}) {
|
|
|
378
378
|
}
|
|
379
379
|
|
|
380
380
|
const version = readVersionAt(appDir);
|
|
381
|
+
if (version) {
|
|
382
|
+
// install-ref が無い古い app-previous へ戻した場合も、以後の更新判定を本体版基準に保つ。
|
|
383
|
+
writeFileSync(join(appDir, '.akari-install-ref'), `v${version}\n`, 'utf8');
|
|
384
|
+
}
|
|
381
385
|
log(version ? `v${version} へロールバックしました` : 'ロールバックしました');
|
|
382
386
|
return { exitCode: 0, rolledBack: true, version };
|
|
383
387
|
}
|
package/src/status-command.mjs
CHANGED
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
serializeWorkspaceStatus,
|
|
9
9
|
formatWorkspaceStatusSummary,
|
|
10
10
|
} from "./status-core/status.mjs";
|
|
11
|
+
import { describeVersionStatus } from "./messages.mjs";
|
|
12
|
+
import { readCacheSync, readOwnVersion, resolveCachePath, resolveInstalledVersionInfo } from "./update-check.mjs";
|
|
11
13
|
|
|
12
14
|
export async function runStatusCommand(argv, options = {}) {
|
|
13
15
|
const log = options.log ?? ((line) => process.stdout.write(line));
|
|
@@ -19,18 +21,24 @@ export async function runStatusCommand(argv, options = {}) {
|
|
|
19
21
|
error(cause instanceof Error ? cause.message : String(cause));
|
|
20
22
|
return { exitCode: 2 };
|
|
21
23
|
}
|
|
24
|
+
const env = options.env ?? process.env;
|
|
25
|
+
const versionInfo = options.versionInfo ?? resolveInstalledVersionInfo({
|
|
26
|
+
env,
|
|
27
|
+
cliVersion: options.cliVersion ?? readOwnVersion(),
|
|
28
|
+
});
|
|
29
|
+
const versionLine = describeVersionStatus(versionInfo, readCacheSync(resolveCachePath(env)));
|
|
22
30
|
if (detectStatusScope(parsed.projectRoot) === "workspace") {
|
|
23
31
|
const workspace = resolveWorkspaceStatus(parsed.projectRoot);
|
|
24
32
|
// fail-safe: a broken root.json falls through to the unchanged project-scope path below.
|
|
25
33
|
if (workspace) {
|
|
26
|
-
log(parsed.json ? serializeWorkspaceStatus(workspace) : `${formatWorkspaceStatusSummary(workspace)}\n`);
|
|
34
|
+
log(parsed.json ? serializeWorkspaceStatus(workspace) : `${versionLine}\n${formatWorkspaceStatusSummary(workspace)}\n`);
|
|
27
35
|
return { exitCode: 0, workspace };
|
|
28
36
|
}
|
|
29
37
|
}
|
|
30
38
|
const status = parsed.full
|
|
31
39
|
? await resolveFullProjectStatus(parsed.projectRoot)
|
|
32
40
|
: resolveProjectStatus(parsed.projectRoot, { mode: "fast" });
|
|
33
|
-
log(parsed.json ? serializeStatus(status) : `${formatStatusOutput(status)}\n`);
|
|
41
|
+
log(parsed.json ? serializeStatus(status) : `${versionLine}\n${formatStatusOutput(status)}\n`);
|
|
34
42
|
return { exitCode: status.state_health === "inconclusive" ? 1 : 0, status };
|
|
35
43
|
}
|
|
36
44
|
|
|
@@ -7,6 +7,7 @@ import { resolveProjectDisplayName } from "./display-name.mjs";
|
|
|
7
7
|
import { inspectFullIntegrity } from "./integrity.mjs";
|
|
8
8
|
import { validateAndCountReview } from "./review.mjs";
|
|
9
9
|
|
|
10
|
+
|
|
10
11
|
export {
|
|
11
12
|
detectStatusScope,
|
|
12
13
|
resolveWorkspaceStatus,
|
|
@@ -218,30 +219,38 @@ function validatePlan(value, problems) {
|
|
|
218
219
|
|
|
219
220
|
function validateEdit(value, problems) {
|
|
220
221
|
if (!value) return;
|
|
221
|
-
if (value.version !==
|
|
222
|
+
if (value.version !== 2) {
|
|
222
223
|
problems.push(`edit.json has unsupported version ${String(value.version)}`);
|
|
223
224
|
return;
|
|
224
225
|
}
|
|
225
|
-
if (!isRecord(value.output) || !Array.isArray(value.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
|
|
226
|
+
if (!isRecord(value.output) || !Array.isArray(value.sources) || !Array.isArray(value.tracks)) {
|
|
227
|
+
problems.push("edit.json v2 has an invalid output/sources/tracks shape");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const sourceIds = new Set();
|
|
231
|
+
for (const source of value.sources) {
|
|
232
|
+
if (!isNonEmptyString(source?.id) || !isNonEmptyString(source?.path) || sourceIds.has(source.id)) {
|
|
233
|
+
problems.push("edit.json v2 sources are invalid or duplicated");
|
|
231
234
|
return;
|
|
232
235
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
236
|
+
sourceIds.add(source.id);
|
|
237
|
+
}
|
|
238
|
+
const trackIds = new Set();
|
|
239
|
+
for (const track of value.tracks) {
|
|
240
|
+
const hasItems = Array.isArray(track?.items);
|
|
241
|
+
const hasContent = isRecord(track?.content);
|
|
242
|
+
if (!isNonEmptyString(track?.id) || trackIds.has(track.id)
|
|
243
|
+
|| (track?.lane !== "visual" && track?.lane !== "audio") || hasItems === hasContent) {
|
|
244
|
+
problems.push("edit.json v2 tracks are invalid or duplicated");
|
|
245
|
+
return;
|
|
240
246
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
247
|
+
trackIds.add(track.id);
|
|
248
|
+
if (!hasItems) continue;
|
|
249
|
+
for (const item of track.items) {
|
|
250
|
+
if (!isRecord(item) || !isRecord(item.source)
|
|
251
|
+
|| (item.source.kind === "media" && !sourceIds.has(item.source.src))) {
|
|
252
|
+
problems.push(`edit.json v2 item references unknown source ${String(item?.source?.src)}`);
|
|
253
|
+
return;
|
|
245
254
|
}
|
|
246
255
|
}
|
|
247
256
|
}
|
|
@@ -270,14 +279,14 @@ function resolveMaterialState({ projectRoot, edit, interpretation, problems, war
|
|
|
270
279
|
let fixed = false;
|
|
271
280
|
if (edit) {
|
|
272
281
|
fixed = true;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
282
|
+
const mainVisualTrack = Array.isArray(edit.tracks)
|
|
283
|
+
? edit.tracks.find(track => track?.lane === "visual" && Array.isArray(track.items)) : null;
|
|
284
|
+
const usedIds = new Set((mainVisualTrack?.items ?? [])
|
|
285
|
+
.filter(item => item?.source?.kind === "media")
|
|
286
|
+
.map(item => item.source.src));
|
|
287
|
+
sources = (Array.isArray(edit.sources) ? edit.sources : [])
|
|
288
|
+
.filter(source => usedIds.has(source?.id) && isNonEmptyString(source?.path))
|
|
289
|
+
.map(source => resolveProjectPath(projectRoot, source.path, `edit source ${source.id}`, problems));
|
|
281
290
|
} else if (interpretation && Array.isArray(interpretation.inputs?.analyses)) {
|
|
282
291
|
fixed = true;
|
|
283
292
|
for (const [index, entry] of interpretation.inputs.analyses.entries()) {
|
package/src/update-check.mjs
CHANGED
|
@@ -55,12 +55,43 @@ export function resolveCachePath(env = process.env) {
|
|
|
55
55
|
return join(resolveAkariHome(env), 'update-check.json');
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export function resolveInstallRefPath(env = process.env) {
|
|
59
|
+
return join(resolveAkariHome(env), 'app', '.akari-install-ref');
|
|
60
|
+
}
|
|
61
|
+
|
|
58
62
|
/** launcher 自身の package.json version(D3 裁定により現状はプロダクト版と一致)。 */
|
|
59
63
|
export function readOwnVersion() {
|
|
60
64
|
const raw = readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8');
|
|
61
65
|
return JSON.parse(raw).version;
|
|
62
66
|
}
|
|
63
67
|
|
|
68
|
+
/**
|
|
69
|
+
* 実際に render-cut / edit-lint を実行する本体の導入版。
|
|
70
|
+
* install.sh / self-update.mjs が書く `vX.Y.Z` を読み、無い・壊れている場合は null。
|
|
71
|
+
*/
|
|
72
|
+
export function readInstalledAppVersion(env = process.env) {
|
|
73
|
+
try {
|
|
74
|
+
const raw = readFileSync(resolveInstallRefPath(env), 'utf8').trim();
|
|
75
|
+
const match = raw.match(/^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/);
|
|
76
|
+
return match?.[1] ?? null;
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 更新判定に使う版と、CLI / 本体のずれを一度に解決する。 */
|
|
83
|
+
export function resolveInstalledVersionInfo({ env = process.env, cliVersion = readOwnVersion() } = {}) {
|
|
84
|
+
const installedAppVersion = readInstalledAppVersion(env);
|
|
85
|
+
const currentVersion = installedAppVersion ?? cliVersion;
|
|
86
|
+
return {
|
|
87
|
+
cliVersion,
|
|
88
|
+
appVersion: installedAppVersion,
|
|
89
|
+
currentVersion,
|
|
90
|
+
source: installedAppVersion ? 'install-ref' : 'cli-fallback',
|
|
91
|
+
mismatch: installedAppVersion !== null && compareVersions(cliVersion, installedAppVersion) !== 0
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
64
95
|
/** "major.minor.patch" の先頭 3 要素だけを数値比較する(prerelease 考慮不要 — 契約 D4: stable のみ)。 */
|
|
65
96
|
export function compareVersions(a, b) {
|
|
66
97
|
const pa = parseVersionTriplet(a);
|
|
@@ -104,23 +135,25 @@ function writeCacheSync(cachePath, cache) {
|
|
|
104
135
|
/**
|
|
105
136
|
* キャッシュと現在版から、新版通知を出すべきかを判定する(同期・純粋関数・I/O なし)。
|
|
106
137
|
*/
|
|
107
|
-
export function evaluateUpdateStatus({ currentVersion, cache }) {
|
|
138
|
+
export function evaluateUpdateStatus({ currentVersion, cache, cliVersion = currentVersion, appVersion = null, source = 'cli-fallback', mismatch = false }) {
|
|
139
|
+
const versionDetails = { currentVersion, cliVersion, appVersion, source, mismatch };
|
|
108
140
|
const feed = cache?.feed;
|
|
109
141
|
if (!isValidFeedShape(feed)) {
|
|
110
|
-
return { available: false };
|
|
142
|
+
return { available: false, ...versionDetails };
|
|
111
143
|
}
|
|
112
144
|
const latest = feed.product;
|
|
113
145
|
if (compareVersions(latest, currentVersion) <= 0) {
|
|
114
|
-
return { available: false };
|
|
146
|
+
return { available: false, latestVersion: latest, ...versionDetails };
|
|
115
147
|
}
|
|
116
148
|
const dismissedAt = cache?.dismissed?.[latest];
|
|
117
|
-
|
|
118
|
-
|
|
149
|
+
// CLI と本体の取り残しは release 通知の dismiss より強い。黙って古い本体を使わせない。
|
|
150
|
+
if (dismissedAt && !mismatch) {
|
|
151
|
+
return { available: false, dismissed: true, latestVersion: latest, ...versionDetails };
|
|
119
152
|
}
|
|
120
153
|
return {
|
|
121
154
|
available: true,
|
|
122
155
|
latestVersion: latest,
|
|
123
|
-
|
|
156
|
+
...versionDetails,
|
|
124
157
|
channel: typeof feed.channel === 'string' ? feed.channel : undefined,
|
|
125
158
|
notesUrl: typeof feed.notes_url === 'string' ? feed.notes_url : undefined,
|
|
126
159
|
cli: feed.components?.cli
|
|
@@ -131,9 +164,13 @@ export function evaluateUpdateStatus({ currentVersion, cache }) {
|
|
|
131
164
|
* `akari` 起動時の同期チェック。ファイルの読み比較のみ(ネットワーク I/O 無し)。
|
|
132
165
|
* fetch 関数への参照すら持たない — 「起動をブロックしない」を構造で保証する。
|
|
133
166
|
*/
|
|
134
|
-
export function checkForUpdateSync({ currentVersion, env = process.env } = {}) {
|
|
167
|
+
export function checkForUpdateSync({ currentVersion, versionInfo, env = process.env } = {}) {
|
|
135
168
|
const cache = readCacheSync(resolveCachePath(env));
|
|
136
|
-
|
|
169
|
+
const versions = versionInfo
|
|
170
|
+
?? (currentVersion === undefined
|
|
171
|
+
? resolveInstalledVersionInfo({ env })
|
|
172
|
+
: { cliVersion: currentVersion, appVersion: null, currentVersion, source: 'cli-fallback', mismatch: false });
|
|
173
|
+
return evaluateUpdateStatus({ ...versions, currentVersion: currentVersion ?? versions.currentVersion, cache });
|
|
137
174
|
}
|
|
138
175
|
|
|
139
176
|
/** 「この版の通知を今後出さない」を記録する(同期・ローカル I/O のみ)。 */
|
|
@@ -157,6 +194,7 @@ export function recordDismissalSync({ version, env = process.env, now = new Date
|
|
|
157
194
|
*
|
|
158
195
|
* `launcherRoot` はテスト用の注入口(`isRunningFromAppDir` へそのまま渡す。
|
|
159
196
|
* `runUpdateCommand` の `options.launcherRoot` と同じ流儀)。
|
|
197
|
+
* app 外から動く npm CLI でも install-ref があれば管理本体を staging 対象にする。
|
|
160
198
|
*/
|
|
161
199
|
export async function maybeStageInBackground({ env = process.env, feed, fetchImpl = globalThis.fetch, timeoutMs, extract, launcherRoot } = {}) {
|
|
162
200
|
if (env.AKARI_NO_AUTO_UPDATE === '1') {
|
|
@@ -166,10 +204,11 @@ export async function maybeStageInBackground({ env = process.env, feed, fetchImp
|
|
|
166
204
|
if (!appComponent?.url || !appComponent?.sha256) {
|
|
167
205
|
return null;
|
|
168
206
|
}
|
|
169
|
-
if (compareVersions(feed.product,
|
|
207
|
+
if (compareVersions(feed.product, resolveInstalledVersionInfo({ env }).currentVersion) <= 0) {
|
|
170
208
|
return null;
|
|
171
209
|
}
|
|
172
|
-
|
|
210
|
+
// npm 側 CLI と本体が分離していても、install-ref があれば管理対象の本体を更新できる。
|
|
211
|
+
if (!isRunningFromAppDir({ env, launcherRoot }) && !readInstalledAppVersion(env)) {
|
|
173
212
|
return null;
|
|
174
213
|
}
|
|
175
214
|
// 沈黙原則(U2 を継承): staging の失敗メッセージはどこにも表示しない(log は no-op)。
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"docs/contract-2026-08-12-still-image-cut-source-v0.md",
|
|
40
40
|
"docs/contract-2026-08-13-avatar-drive-v0.md",
|
|
41
41
|
"docs/contract-2026-08-14-avatar-vrm-v0.md",
|
|
42
|
+
"docs/contract-2026-08-18-v1-render-parity.md",
|
|
42
43
|
"packages/akari-launcher/package.json",
|
|
43
44
|
"packages/akari-launcher/README.md",
|
|
44
45
|
"packages/akari-tools/package.json",
|