mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
fix: persist migrated tlon install specs
This commit is contained in:
@@ -3,6 +3,7 @@ import os from "node:os";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import { createConfigIO } from "./io.js";
|
import { createConfigIO } from "./io.js";
|
||||||
|
import { migrateLegacyConfig } from "./legacy-migrate.js";
|
||||||
|
|
||||||
async function withTempHome(run: (home: string) => Promise<void>): Promise<void> {
|
async function withTempHome(run: (home: string) => Promise<void>): Promise<void> {
|
||||||
const home = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-config-"));
|
const home = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-config-"));
|
||||||
@@ -212,4 +213,61 @@ describe("config io paths", () => {
|
|||||||
expect(snapshot.config.plugins?.installs?.tlon?.resolvedSpec).toBeUndefined();
|
expect(snapshot.config.plugins?.installs?.tlon?.resolvedSpec).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("persists migrated tlon install specs back to disk", async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
const configDir = path.join(home, ".openclaw");
|
||||||
|
await fs.mkdir(configDir, { recursive: true });
|
||||||
|
const configPath = path.join(configDir, "openclaw.json");
|
||||||
|
await fs.writeFile(
|
||||||
|
configPath,
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
plugins: {
|
||||||
|
installs: {
|
||||||
|
tlon: {
|
||||||
|
source: "npm",
|
||||||
|
spec: "@openclaw/tlon@2026.2.21",
|
||||||
|
resolvedName: "@openclaw/tlon",
|
||||||
|
resolvedVersion: "2026.2.21",
|
||||||
|
resolvedSpec: "@openclaw/tlon@2026.2.21",
|
||||||
|
integrity: "sha512-old",
|
||||||
|
shasum: "old",
|
||||||
|
resolvedAt: "2026-03-01T00:00:00.000Z",
|
||||||
|
installPath: "/tmp/tlon",
|
||||||
|
version: "2026.2.21",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
|
||||||
|
const io = createIoForHome(home);
|
||||||
|
const parsed = JSON.parse(await fs.readFile(configPath, "utf-8")) as unknown;
|
||||||
|
const migrated = migrateLegacyConfig(parsed);
|
||||||
|
|
||||||
|
expect(migrated.config).toBeTruthy();
|
||||||
|
await io.writeConfigFile(migrated.config!);
|
||||||
|
|
||||||
|
const written = JSON.parse(await fs.readFile(configPath, "utf-8")) as {
|
||||||
|
plugins?: {
|
||||||
|
installs?: {
|
||||||
|
tlon?: {
|
||||||
|
spec?: string;
|
||||||
|
resolvedSpec?: string;
|
||||||
|
resolvedName?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(written.plugins?.installs?.tlon?.spec).toBe("@tloncorp/openclaw@2026.2.21");
|
||||||
|
expect(written.plugins?.installs?.tlon?.resolvedSpec).toBeUndefined();
|
||||||
|
expect(written.plugins?.installs?.tlon?.resolvedName).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -41,8 +41,7 @@ import {
|
|||||||
readConfigIncludeFileWithGuards,
|
readConfigIncludeFileWithGuards,
|
||||||
resolveConfigIncludes,
|
resolveConfigIncludes,
|
||||||
} from "./includes.js";
|
} from "./includes.js";
|
||||||
import { migrateLegacyConfig } from "./legacy-migrate.js";
|
import { applyLegacyMigrations, findLegacyConfigIssues } from "./legacy.js";
|
||||||
import { findLegacyConfigIssues } from "./legacy.js";
|
|
||||||
import { applyMergePatch } from "./merge-patch.js";
|
import { applyMergePatch } from "./merge-patch.js";
|
||||||
import { normalizeExecSafeBinProfilesInConfig } from "./normalize-exec-safe-bin.js";
|
import { normalizeExecSafeBinProfilesInConfig } from "./normalize-exec-safe-bin.js";
|
||||||
import { normalizeConfigPaths } from "./normalize-paths.js";
|
import { normalizeConfigPaths } from "./normalize-paths.js";
|
||||||
@@ -719,18 +718,49 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
) {
|
) {
|
||||||
const legacyIssues = findLegacyConfigIssues(resolvedConfigRaw, sourceRaw);
|
const legacyIssues = findLegacyConfigIssues(resolvedConfigRaw, sourceRaw);
|
||||||
if (shouldAutoMigrateLegacyIssues(legacyIssues)) {
|
if (shouldAutoMigrateLegacyIssues(legacyIssues)) {
|
||||||
const migrated = migrateLegacyConfig(resolvedConfigRaw);
|
const migrated = applyLegacyMigrations(resolvedConfigRaw);
|
||||||
if (migrated.config) {
|
if (migrated.next) {
|
||||||
return {
|
const validatedMigrated = validateConfigObjectWithPlugins(migrated.next);
|
||||||
legacyIssues,
|
if (validatedMigrated.ok) {
|
||||||
validated: validateConfigObjectWithPlugins(migrated.config),
|
return {
|
||||||
};
|
legacyIssues,
|
||||||
|
validated: validatedMigrated,
|
||||||
|
effectiveResolvedConfigRaw: migrated.next,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
legacyIssues,
|
legacyIssues,
|
||||||
validated: validateConfigObjectWithPlugins(resolvedConfigRaw),
|
validated: validateConfigObjectWithPlugins(resolvedConfigRaw),
|
||||||
|
effectiveResolvedConfigRaw: resolvedConfigRaw,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateResolvedRawConfigWithAutoMigration(
|
||||||
|
resolvedConfigRaw: unknown,
|
||||||
|
sourceRaw?: unknown,
|
||||||
|
) {
|
||||||
|
const legacyIssues = findLegacyConfigIssues(resolvedConfigRaw, sourceRaw);
|
||||||
|
if (shouldAutoMigrateLegacyIssues(legacyIssues)) {
|
||||||
|
const migrated = applyLegacyMigrations(resolvedConfigRaw);
|
||||||
|
if (migrated.next) {
|
||||||
|
const validatedMigrated = validateConfigObjectRawWithPlugins(migrated.next);
|
||||||
|
if (validatedMigrated.ok) {
|
||||||
|
return {
|
||||||
|
legacyIssues,
|
||||||
|
validated: validatedMigrated,
|
||||||
|
effectiveResolvedConfigRaw: migrated.next,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
legacyIssues,
|
||||||
|
validated: validateConfigObjectRawWithPlugins(resolvedConfigRaw),
|
||||||
|
effectiveResolvedConfigRaw: resolvedConfigRaw,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -978,10 +1008,8 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const resolvedConfigRaw = readResolution.resolvedConfigRaw;
|
const resolvedConfigRaw = readResolution.resolvedConfigRaw;
|
||||||
const { legacyIssues, validated } = validateResolvedConfigWithAutoMigration(
|
const { legacyIssues, validated, effectiveResolvedConfigRaw } =
|
||||||
resolvedConfigRaw,
|
validateResolvedConfigWithAutoMigration(resolvedConfigRaw, parsedRes.parsed);
|
||||||
parsedRes.parsed,
|
|
||||||
);
|
|
||||||
if (!validated.ok) {
|
if (!validated.ok) {
|
||||||
return {
|
return {
|
||||||
snapshot: {
|
snapshot: {
|
||||||
@@ -989,7 +1017,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
exists: true,
|
exists: true,
|
||||||
raw,
|
raw,
|
||||||
parsed: parsedRes.parsed,
|
parsed: parsedRes.parsed,
|
||||||
resolved: coerceConfig(resolvedConfigRaw),
|
resolved: coerceConfig(effectiveResolvedConfigRaw),
|
||||||
valid: false,
|
valid: false,
|
||||||
config: coerceConfig(resolvedConfigRaw),
|
config: coerceConfig(resolvedConfigRaw),
|
||||||
hash,
|
hash,
|
||||||
@@ -1021,7 +1049,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
parsed: parsedRes.parsed,
|
parsed: parsedRes.parsed,
|
||||||
// Use resolvedConfigRaw (after $include and ${ENV} substitution but BEFORE runtime defaults)
|
// Use resolvedConfigRaw (after $include and ${ENV} substitution but BEFORE runtime defaults)
|
||||||
// for config set/unset operations (issue #6070)
|
// for config set/unset operations (issue #6070)
|
||||||
resolved: coerceConfig(resolvedConfigRaw),
|
resolved: coerceConfig(effectiveResolvedConfigRaw),
|
||||||
valid: true,
|
valid: true,
|
||||||
config: snapshotConfig,
|
config: snapshotConfig,
|
||||||
hash,
|
hash,
|
||||||
@@ -1118,7 +1146,7 @@ export function createConfigIO(overrides: ConfigIoDeps = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const validated = validateConfigObjectRawWithPlugins(persistCandidate);
|
const { validated } = validateResolvedRawConfigWithAutoMigration(persistCandidate);
|
||||||
if (!validated.ok) {
|
if (!validated.ok) {
|
||||||
const issue = validated.issues[0];
|
const issue = validated.issues[0];
|
||||||
const pathLabel = issue?.path ? issue.path : "<root>";
|
const pathLabel = issue?.path ? issue.path : "<root>";
|
||||||
|
|||||||
Reference in New Issue
Block a user