opencode-thrum-pro 0.1.17 → 0.1.19
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/installer.js +63 -6
- package/dist/installer.test.js +49 -0
- package/package.json +1 -1
package/dist/installer.js
CHANGED
|
@@ -50,6 +50,68 @@ function copyDirRecursive(src, dest) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
// Older pro releases (0.1.x) installed flat (skills/<name>) with the marker in
|
|
54
|
+
// skills/thrum, the same layout open-core still uses. OpenCode resolves
|
|
55
|
+
// duplicate skill names to the flat copy, so stale flat pro skills would shadow
|
|
56
|
+
// the nested ones. Remove them only when the install is positively identified
|
|
57
|
+
// as pro; anything else (open-core, unknown version, unreadable) is left alone.
|
|
58
|
+
function isLegacyProInstall(markerDir) {
|
|
59
|
+
const read = (name) => {
|
|
60
|
+
try {
|
|
61
|
+
return fs.readFileSync(path.join(markerDir, name), "utf8").trim();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const flavor = read(FLAVOR_FILE);
|
|
68
|
+
if (flavor !== undefined)
|
|
69
|
+
return flavor === "pro";
|
|
70
|
+
const version = read(VERSION_FILE);
|
|
71
|
+
return version !== undefined && /^0\.1\.\d+([-+].*)?$/.test(version);
|
|
72
|
+
}
|
|
73
|
+
async function cleanLegacyFlatInstall(skillsDir, log) {
|
|
74
|
+
const markerDir = path.join(skillsDir, DEFAULT_SKILLS_SUBDIR);
|
|
75
|
+
if (!fs.existsSync(path.join(markerDir, VERSION_FILE)))
|
|
76
|
+
return;
|
|
77
|
+
if (!isLegacyProInstall(markerDir)) {
|
|
78
|
+
await log(`Legacy flat install found at ${markerDir}; not removed (not identified as an old pro install). If it is one, delete it and its sibling skill dirs manually.`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const assetsSkillsDir = path.join(ASSETS_DIR, "skills");
|
|
82
|
+
let removed = 0;
|
|
83
|
+
if (fs.existsSync(assetsSkillsDir)) {
|
|
84
|
+
for (const entry of fs.readdirSync(assetsSkillsDir, { withFileTypes: true })) {
|
|
85
|
+
if (!entry.isDirectory())
|
|
86
|
+
continue;
|
|
87
|
+
const flat = path.join(skillsDir, entry.name);
|
|
88
|
+
if (!fs.existsSync(flat))
|
|
89
|
+
continue;
|
|
90
|
+
try {
|
|
91
|
+
fs.rmSync(flat, { recursive: true });
|
|
92
|
+
removed++;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// best-effort; a locked dir must not block the install
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const marker of [VERSION_FILE, FLAVOR_FILE]) {
|
|
100
|
+
try {
|
|
101
|
+
fs.rmSync(path.join(markerDir, marker), { force: true });
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// best-effort
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
fs.rmdirSync(markerDir); // only succeeds when empty
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// not empty or already gone
|
|
112
|
+
}
|
|
113
|
+
await log(`Removed ${removed} legacy flat pro skill dirs under ${skillsDir}`);
|
|
114
|
+
}
|
|
53
115
|
export async function installAssets(ctx, opts = {}) {
|
|
54
116
|
const configDir = getConfigDir();
|
|
55
117
|
const skillsDir = path.join(configDir, "skills");
|
|
@@ -73,12 +135,7 @@ export async function installAssets(ctx, opts = {}) {
|
|
|
73
135
|
body: { service: "opencode-thrum", level: "info", message: msg },
|
|
74
136
|
});
|
|
75
137
|
if (subdir !== DEFAULT_SKILLS_SUBDIR) {
|
|
76
|
-
|
|
77
|
-
// vs open-core (same marker file, no flavor field), so never delete it.
|
|
78
|
-
const legacy = path.join(skillsDir, DEFAULT_SKILLS_SUBDIR, VERSION_FILE);
|
|
79
|
-
if (fs.existsSync(legacy)) {
|
|
80
|
-
await log(`Legacy flat install found at ${path.join(skillsDir, DEFAULT_SKILLS_SUBDIR)}; not removed (cannot tell pro from open-core). If it is an old pro install, delete it and its sibling skill dirs manually.`);
|
|
81
|
-
}
|
|
138
|
+
await cleanLegacyFlatInstall(skillsDir, log);
|
|
82
139
|
}
|
|
83
140
|
// Copy each skill subdirectory
|
|
84
141
|
const assetsSkillsDir = path.join(ASSETS_DIR, "skills");
|
package/dist/installer.test.js
CHANGED
|
@@ -51,3 +51,52 @@ test("invalid subdir falls back to default", () => {
|
|
|
51
51
|
}
|
|
52
52
|
assert.equal(sanitizeSkillsSubdir("thrum-pro"), "thrum-pro");
|
|
53
53
|
});
|
|
54
|
+
function seedLegacy(cfg, marker) {
|
|
55
|
+
const skills = path.join(cfg, "skills");
|
|
56
|
+
const thrumDir = path.join(skills, "thrum");
|
|
57
|
+
fs.mkdirSync(thrumDir, { recursive: true });
|
|
58
|
+
if (marker.version !== undefined)
|
|
59
|
+
fs.writeFileSync(path.join(thrumDir, ".thrum-plugin-version"), marker.version);
|
|
60
|
+
if (marker.flavor !== undefined)
|
|
61
|
+
fs.writeFileSync(path.join(thrumDir, ".thrum-plugin-flavor"), marker.flavor);
|
|
62
|
+
for (const n of ["configure-roles", "adversarial-critique", "user-own-skill"]) {
|
|
63
|
+
fs.mkdirSync(path.join(skills, n), { recursive: true });
|
|
64
|
+
fs.writeFileSync(path.join(skills, n, "SKILL.md"), "stale");
|
|
65
|
+
}
|
|
66
|
+
return skills;
|
|
67
|
+
}
|
|
68
|
+
test("pro: legacy flavor=pro removes matching flat dirs, keeps others", withConfig(async (cfg) => {
|
|
69
|
+
const skills = seedLegacy(cfg, { version: "0.1.16", flavor: "pro" });
|
|
70
|
+
await installAssets(ctx, { skillsSubdir: "thrum-pro" });
|
|
71
|
+
assert.ok(!fs.existsSync(path.join(skills, "configure-roles")));
|
|
72
|
+
assert.ok(!fs.existsSync(path.join(skills, "adversarial-critique")));
|
|
73
|
+
assert.ok(fs.existsSync(path.join(skills, "user-own-skill", "SKILL.md")));
|
|
74
|
+
assert.ok(!fs.existsSync(path.join(skills, "thrum", ".thrum-plugin-version")));
|
|
75
|
+
assert.ok(!fs.existsSync(path.join(skills, "thrum", ".thrum-plugin-flavor")));
|
|
76
|
+
assert.ok(fs.existsSync(path.join(skills, "thrum-pro", "configure-roles")));
|
|
77
|
+
}));
|
|
78
|
+
test("pro: legacy 0.1.16 without flavor is removed", withConfig(async (cfg) => {
|
|
79
|
+
const skills = seedLegacy(cfg, { version: "0.1.16" });
|
|
80
|
+
await installAssets(ctx, { skillsSubdir: "thrum-pro" });
|
|
81
|
+
assert.ok(!fs.existsSync(path.join(skills, "configure-roles")));
|
|
82
|
+
assert.ok(fs.existsSync(path.join(skills, "user-own-skill")));
|
|
83
|
+
assert.ok(!fs.existsSync(path.join(skills, "thrum", ".thrum-plugin-version")));
|
|
84
|
+
}));
|
|
85
|
+
test("pro: legacy 0.3.105 without flavor removes nothing", withConfig(async (cfg) => {
|
|
86
|
+
const skills = seedLegacy(cfg, { version: "0.3.105" });
|
|
87
|
+
await installAssets(ctx, { skillsSubdir: "thrum-pro" });
|
|
88
|
+
assert.ok(fs.existsSync(path.join(skills, "configure-roles", "SKILL.md")));
|
|
89
|
+
assert.ok(fs.existsSync(path.join(skills, "thrum", ".thrum-plugin-version")));
|
|
90
|
+
}));
|
|
91
|
+
test("pro: legacy flavor=open-core removes nothing", withConfig(async (cfg) => {
|
|
92
|
+
const skills = seedLegacy(cfg, { version: "0.1.16", flavor: "open-core" });
|
|
93
|
+
await installAssets(ctx, { skillsSubdir: "thrum-pro" });
|
|
94
|
+
assert.ok(fs.existsSync(path.join(skills, "configure-roles", "SKILL.md")));
|
|
95
|
+
assert.ok(fs.existsSync(path.join(skills, "thrum", ".thrum-plugin-flavor")));
|
|
96
|
+
}));
|
|
97
|
+
test("open-core build: existing flat install is not cleaned", withConfig(async (cfg) => {
|
|
98
|
+
const skills = seedLegacy(cfg, { version: "0.1.16", flavor: "pro" });
|
|
99
|
+
await installAssets(ctx, { skillsSubdir: "thrum" });
|
|
100
|
+
assert.ok(fs.existsSync(path.join(skills, "user-own-skill", "SKILL.md")));
|
|
101
|
+
assert.equal(fs.readFileSync(path.join(skills, "thrum", ".thrum-plugin-flavor"), "utf8"), "open-core");
|
|
102
|
+
}));
|