teamix-evo 0.1.0 → 0.3.0
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/LICENSE +21 -0
- package/README.md +222 -0
- package/dist/core/index.d.ts +442 -0
- package/dist/core/index.js +1521 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.js +2607 -578
- package/dist/index.js.map +1 -1
- package/package.json +34 -13
|
@@ -0,0 +1,1521 @@
|
|
|
1
|
+
// src/core/design-init.ts
|
|
2
|
+
import * as path9 from "path";
|
|
3
|
+
import * as fs6 from "fs/promises";
|
|
4
|
+
import { createRequire as createRequire2 } from "module";
|
|
5
|
+
import {
|
|
6
|
+
loadDesignPack,
|
|
7
|
+
loadDesignPackageManifest,
|
|
8
|
+
mergeDefaultAndVariant
|
|
9
|
+
} from "@teamix-evo/registry";
|
|
10
|
+
|
|
11
|
+
// src/utils/fs.ts
|
|
12
|
+
import * as fs from "fs/promises";
|
|
13
|
+
import * as path from "path";
|
|
14
|
+
|
|
15
|
+
// src/utils/logger.ts
|
|
16
|
+
import { red, yellow, cyan, green, gray } from "kolorist";
|
|
17
|
+
var isDebug = process.env.TEAMIX_DEBUG === "1";
|
|
18
|
+
var logger = {
|
|
19
|
+
info(msg) {
|
|
20
|
+
console.log(cyan("\u2139"), msg);
|
|
21
|
+
},
|
|
22
|
+
warn(msg) {
|
|
23
|
+
console.warn(yellow("\u26A0"), msg);
|
|
24
|
+
},
|
|
25
|
+
error(msg) {
|
|
26
|
+
console.error(red("\u2716"), msg);
|
|
27
|
+
},
|
|
28
|
+
success(msg) {
|
|
29
|
+
console.log(green("\u2714"), msg);
|
|
30
|
+
},
|
|
31
|
+
debug(msg) {
|
|
32
|
+
if (isDebug) {
|
|
33
|
+
console.log(gray("\u22A1"), gray(msg));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/utils/fs.ts
|
|
39
|
+
async function ensureDir(dir) {
|
|
40
|
+
await fs.mkdir(dir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
async function writeFileSafe(filePath, content) {
|
|
43
|
+
const dir = path.dirname(filePath);
|
|
44
|
+
await ensureDir(dir);
|
|
45
|
+
const tmp = filePath + ".tmp";
|
|
46
|
+
await fs.writeFile(tmp, content, "utf-8");
|
|
47
|
+
await fs.rename(tmp, filePath);
|
|
48
|
+
}
|
|
49
|
+
async function readFileOrNull(filePath) {
|
|
50
|
+
try {
|
|
51
|
+
return await fs.readFile(filePath, "utf-8");
|
|
52
|
+
} catch (err) {
|
|
53
|
+
if (err.code === "ENOENT") {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function backupFile(filePath, projectRoot) {
|
|
60
|
+
const content = await readFileOrNull(filePath);
|
|
61
|
+
if (content === null) {
|
|
62
|
+
logger.debug(`Skip backup: ${filePath} does not exist`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const rel2 = path.relative(projectRoot, filePath);
|
|
66
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
67
|
+
const backupPath = path.join(
|
|
68
|
+
projectRoot,
|
|
69
|
+
".teamix-evo",
|
|
70
|
+
".backups",
|
|
71
|
+
`${rel2}.${timestamp}.bak`
|
|
72
|
+
);
|
|
73
|
+
await ensureDir(path.dirname(backupPath));
|
|
74
|
+
await fs.writeFile(backupPath, content, "utf-8");
|
|
75
|
+
logger.debug(`Backed up ${rel2} \u2192 ${path.relative(projectRoot, backupPath)}`);
|
|
76
|
+
}
|
|
77
|
+
async function fileExists(filePath) {
|
|
78
|
+
try {
|
|
79
|
+
await fs.access(filePath);
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/utils/hash.ts
|
|
87
|
+
import { createHash } from "crypto";
|
|
88
|
+
function computeHash(content) {
|
|
89
|
+
const hash = createHash("sha256").update(content, "utf-8").digest("hex");
|
|
90
|
+
return `sha256:${hash}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/core/design-pack-classify.ts
|
|
94
|
+
import * as path2 from "path";
|
|
95
|
+
var TEAMIX_DIR = ".teamix-evo/design";
|
|
96
|
+
var TOKENS_DIR = ".teamix-evo/tokens";
|
|
97
|
+
var TOKENS_PACK_PREFIX = "foundations/tokens/";
|
|
98
|
+
var ROOT_MANAGED_FILES = {
|
|
99
|
+
"DESIGN.md": { target: "DESIGN.md", managedRegions: ["core"] },
|
|
100
|
+
"AGENTS.md": { target: "AGENTS.md", managedRegions: ["teamix-evo"] },
|
|
101
|
+
"CLAUDE.md": { target: "CLAUDE.md", managedRegions: ["teamix-evo"] }
|
|
102
|
+
};
|
|
103
|
+
var FROZEN_FILES = /* @__PURE__ */ new Set([
|
|
104
|
+
"foundations/tokens/tokens.overrides.css"
|
|
105
|
+
]);
|
|
106
|
+
function classifyPackFile(relPath) {
|
|
107
|
+
if (path2.basename(relPath) === "README.md") {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
const rootManaged = ROOT_MANAGED_FILES[relPath];
|
|
111
|
+
if (rootManaged) {
|
|
112
|
+
return {
|
|
113
|
+
target: rootManaged.target,
|
|
114
|
+
strategy: "managed",
|
|
115
|
+
managedRegions: rootManaged.managedRegions,
|
|
116
|
+
isFrozen: false
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (relPath.startsWith(TOKENS_PACK_PREFIX)) {
|
|
120
|
+
const rel2 = relPath.slice(TOKENS_PACK_PREFIX.length);
|
|
121
|
+
const target = path2.posix.join(TOKENS_DIR, rel2);
|
|
122
|
+
if (FROZEN_FILES.has(relPath)) {
|
|
123
|
+
return { target, strategy: "frozen", isFrozen: true };
|
|
124
|
+
}
|
|
125
|
+
return { target, strategy: "regenerable", isFrozen: false };
|
|
126
|
+
}
|
|
127
|
+
if (FROZEN_FILES.has(relPath)) {
|
|
128
|
+
return {
|
|
129
|
+
target: path2.posix.join(TEAMIX_DIR, relPath),
|
|
130
|
+
strategy: "frozen",
|
|
131
|
+
isFrozen: true
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
target: path2.posix.join(TEAMIX_DIR, relPath),
|
|
136
|
+
strategy: "regenerable",
|
|
137
|
+
isFrozen: false
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/core/state.ts
|
|
142
|
+
import * as path3 from "path";
|
|
143
|
+
import {
|
|
144
|
+
validateConfig,
|
|
145
|
+
validateInstalled,
|
|
146
|
+
validateSkillsLock,
|
|
147
|
+
DesignPackLockSchema
|
|
148
|
+
} from "@teamix-evo/registry";
|
|
149
|
+
var TEAMIX_DIR2 = ".teamix-evo";
|
|
150
|
+
var CONFIG_FILE = "config.json";
|
|
151
|
+
var MANIFEST_FILE = "manifest.json";
|
|
152
|
+
var DESIGN_DIR = "design";
|
|
153
|
+
var DESIGN_LOCK_FILE = "pack.lock.json";
|
|
154
|
+
var SKILLS_DIR = "skills";
|
|
155
|
+
var SKILLS_LOCK_FILE = "manifest.lock.json";
|
|
156
|
+
function getTeamixDir(projectRoot) {
|
|
157
|
+
return path3.join(projectRoot, TEAMIX_DIR2);
|
|
158
|
+
}
|
|
159
|
+
async function ensureTeamixDir(projectRoot) {
|
|
160
|
+
const dir = getTeamixDir(projectRoot);
|
|
161
|
+
await ensureDir(dir);
|
|
162
|
+
return dir;
|
|
163
|
+
}
|
|
164
|
+
async function readProjectConfig(projectRoot) {
|
|
165
|
+
const configPath = path3.join(projectRoot, TEAMIX_DIR2, CONFIG_FILE);
|
|
166
|
+
const raw = await readFileOrNull(configPath);
|
|
167
|
+
if (raw === null) return null;
|
|
168
|
+
try {
|
|
169
|
+
const data = JSON.parse(raw);
|
|
170
|
+
const result = validateConfig(data);
|
|
171
|
+
if (!result.success) {
|
|
172
|
+
logger.warn(`Invalid config.json: ${result.error}`);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
return result.data;
|
|
176
|
+
} catch (err) {
|
|
177
|
+
logger.warn(`Failed to parse config.json: ${err.message}`);
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function writeProjectConfig(projectRoot, config) {
|
|
182
|
+
const configPath = path3.join(projectRoot, TEAMIX_DIR2, CONFIG_FILE);
|
|
183
|
+
await writeFileSafe(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
184
|
+
logger.debug(`Wrote config \u2192 ${configPath}`);
|
|
185
|
+
}
|
|
186
|
+
async function readInstalledManifest(projectRoot) {
|
|
187
|
+
const manifestPath = path3.join(projectRoot, TEAMIX_DIR2, MANIFEST_FILE);
|
|
188
|
+
const raw = await readFileOrNull(manifestPath);
|
|
189
|
+
if (raw === null) return null;
|
|
190
|
+
try {
|
|
191
|
+
const data = JSON.parse(raw);
|
|
192
|
+
const result = validateInstalled(data);
|
|
193
|
+
if (!result.success) {
|
|
194
|
+
logger.warn(`Invalid manifest.json: ${result.error}`);
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
return result.data;
|
|
198
|
+
} catch (err) {
|
|
199
|
+
logger.warn(`Failed to parse manifest.json: ${err.message}`);
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function writeInstalledManifest(projectRoot, manifest) {
|
|
204
|
+
const manifestPath = path3.join(projectRoot, TEAMIX_DIR2, MANIFEST_FILE);
|
|
205
|
+
await writeFileSafe(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
206
|
+
logger.debug(`Wrote manifest \u2192 ${manifestPath}`);
|
|
207
|
+
}
|
|
208
|
+
async function readDesignPackLock(projectRoot) {
|
|
209
|
+
const lockPath = path3.join(
|
|
210
|
+
projectRoot,
|
|
211
|
+
TEAMIX_DIR2,
|
|
212
|
+
DESIGN_DIR,
|
|
213
|
+
DESIGN_LOCK_FILE
|
|
214
|
+
);
|
|
215
|
+
const raw = await readFileOrNull(lockPath);
|
|
216
|
+
if (raw === null) return null;
|
|
217
|
+
try {
|
|
218
|
+
const parsed = DesignPackLockSchema.safeParse(JSON.parse(raw));
|
|
219
|
+
if (!parsed.success) {
|
|
220
|
+
logger.warn(`Invalid design pack.lock.json: ${parsed.error.message}`);
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
return parsed.data;
|
|
224
|
+
} catch (err) {
|
|
225
|
+
logger.warn(
|
|
226
|
+
`Failed to parse design pack.lock.json: ${err.message}`
|
|
227
|
+
);
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async function readDesignVariant(projectRoot) {
|
|
232
|
+
const lock = await readDesignPackLock(projectRoot);
|
|
233
|
+
return lock?.variant.name ?? null;
|
|
234
|
+
}
|
|
235
|
+
function getSkillsSourceDir(projectRoot, skillName) {
|
|
236
|
+
const base = path3.join(projectRoot, TEAMIX_DIR2, SKILLS_DIR);
|
|
237
|
+
return skillName ? path3.join(base, skillName) : base;
|
|
238
|
+
}
|
|
239
|
+
async function readSkillsLock(projectRoot) {
|
|
240
|
+
const lockPath = path3.join(
|
|
241
|
+
projectRoot,
|
|
242
|
+
TEAMIX_DIR2,
|
|
243
|
+
SKILLS_DIR,
|
|
244
|
+
SKILLS_LOCK_FILE
|
|
245
|
+
);
|
|
246
|
+
const raw = await readFileOrNull(lockPath);
|
|
247
|
+
if (raw === null) return null;
|
|
248
|
+
try {
|
|
249
|
+
const data = JSON.parse(raw);
|
|
250
|
+
const result = validateSkillsLock(data);
|
|
251
|
+
if (!result.success) {
|
|
252
|
+
logger.warn(`Invalid skills manifest.lock.json: ${result.error}`);
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
return result.data;
|
|
256
|
+
} catch (err) {
|
|
257
|
+
logger.warn(
|
|
258
|
+
`Failed to parse skills manifest.lock.json: ${err.message}`
|
|
259
|
+
);
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async function writeSkillsLock(projectRoot, lock) {
|
|
264
|
+
const lockPath = path3.join(
|
|
265
|
+
projectRoot,
|
|
266
|
+
TEAMIX_DIR2,
|
|
267
|
+
SKILLS_DIR,
|
|
268
|
+
SKILLS_LOCK_FILE
|
|
269
|
+
);
|
|
270
|
+
await writeFileSafe(lockPath, JSON.stringify(lock, null, 2) + "\n");
|
|
271
|
+
logger.debug(`Wrote skills lock \u2192 ${lockPath}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/core/skills-client.ts
|
|
275
|
+
import * as path4 from "path";
|
|
276
|
+
import * as fs2 from "fs/promises";
|
|
277
|
+
import { createRequire } from "module";
|
|
278
|
+
import { loadSkillsPackageManifest } from "@teamix-evo/registry";
|
|
279
|
+
var require2 = createRequire(import.meta.url);
|
|
280
|
+
function resolvePackageRoot(packageName) {
|
|
281
|
+
const pkgJsonPath = require2.resolve(`${packageName}/package.json`);
|
|
282
|
+
return path4.dirname(pkgJsonPath);
|
|
283
|
+
}
|
|
284
|
+
async function loadSkillsData(packageName) {
|
|
285
|
+
const packageRoot = resolvePackageRoot(packageName);
|
|
286
|
+
logger.debug(`Resolved skills package root: ${packageRoot}`);
|
|
287
|
+
const manifest = await loadSkillsPackageManifest(packageRoot);
|
|
288
|
+
let data = {};
|
|
289
|
+
const dataPath = path4.join(packageRoot, "_data.json");
|
|
290
|
+
try {
|
|
291
|
+
const raw = await fs2.readFile(dataPath, "utf-8");
|
|
292
|
+
data = JSON.parse(raw);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
if (err.code !== "ENOENT") {
|
|
295
|
+
throw err;
|
|
296
|
+
}
|
|
297
|
+
logger.debug(`No _data.json found at ${dataPath}, using empty data`);
|
|
298
|
+
}
|
|
299
|
+
return { manifest, data, packageRoot };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/core/skills-installer.ts
|
|
303
|
+
import * as path8 from "path";
|
|
304
|
+
import * as fs5 from "fs/promises";
|
|
305
|
+
import { replaceManagedRegion } from "@teamix-evo/registry";
|
|
306
|
+
|
|
307
|
+
// src/ide/QoderAdapter.ts
|
|
308
|
+
import * as os from "os";
|
|
309
|
+
import * as path5 from "path";
|
|
310
|
+
var QoderAdapter = class {
|
|
311
|
+
kind = "qoder";
|
|
312
|
+
name = "qoder";
|
|
313
|
+
getProjectRoot() {
|
|
314
|
+
return process.cwd();
|
|
315
|
+
}
|
|
316
|
+
detectIde() {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
getSkillTargetDir(skillName, scope, projectRoot) {
|
|
320
|
+
const base = scope === "global" ? path5.join(os.homedir(), ".qoder") : path5.join(projectRoot ?? this.getProjectRoot(), ".qoder");
|
|
321
|
+
return path5.join(base, "skills", skillName);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// src/ide/ClaudeAdapter.ts
|
|
326
|
+
import * as os2 from "os";
|
|
327
|
+
import * as path6 from "path";
|
|
328
|
+
var ClaudeAdapter = class {
|
|
329
|
+
kind = "claude";
|
|
330
|
+
name = "claude";
|
|
331
|
+
getProjectRoot() {
|
|
332
|
+
return process.cwd();
|
|
333
|
+
}
|
|
334
|
+
detectIde() {
|
|
335
|
+
return Boolean(process.env.CLAUDECODE);
|
|
336
|
+
}
|
|
337
|
+
getSkillTargetDir(skillName, scope, projectRoot) {
|
|
338
|
+
const base = scope === "global" ? path6.join(os2.homedir(), ".claude") : path6.join(projectRoot ?? this.getProjectRoot(), ".claude");
|
|
339
|
+
return path6.join(base, "skills", skillName);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// src/ide/index.ts
|
|
344
|
+
function getAdapter(kind) {
|
|
345
|
+
switch (kind) {
|
|
346
|
+
case "qoder":
|
|
347
|
+
return new QoderAdapter();
|
|
348
|
+
case "claude":
|
|
349
|
+
return new ClaudeAdapter();
|
|
350
|
+
default: {
|
|
351
|
+
const _exhaustive = kind;
|
|
352
|
+
throw new Error(`Unsupported IDE kind: ${_exhaustive}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/utils/template.ts
|
|
358
|
+
import Handlebars from "handlebars";
|
|
359
|
+
import * as fs3 from "fs/promises";
|
|
360
|
+
Handlebars.registerHelper("lowercase", (str) => {
|
|
361
|
+
return typeof str === "string" ? str.toLowerCase() : String(str ?? "").toLowerCase();
|
|
362
|
+
});
|
|
363
|
+
var compiledCache = /* @__PURE__ */ new Map();
|
|
364
|
+
var MAX_CACHE_SIZE = 64;
|
|
365
|
+
function getCompiledTemplate(templateContent) {
|
|
366
|
+
let compiled = compiledCache.get(templateContent);
|
|
367
|
+
if (!compiled) {
|
|
368
|
+
if (compiledCache.size >= MAX_CACHE_SIZE) {
|
|
369
|
+
const firstKey = compiledCache.keys().next().value;
|
|
370
|
+
compiledCache.delete(firstKey);
|
|
371
|
+
}
|
|
372
|
+
compiled = Handlebars.compile(templateContent, { noEscape: true });
|
|
373
|
+
compiledCache.set(templateContent, compiled);
|
|
374
|
+
}
|
|
375
|
+
return compiled;
|
|
376
|
+
}
|
|
377
|
+
function renderTemplate(templateContent, data) {
|
|
378
|
+
const compiled = getCompiledTemplate(templateContent);
|
|
379
|
+
return compiled(data);
|
|
380
|
+
}
|
|
381
|
+
async function loadTemplateFile(filePath) {
|
|
382
|
+
return fs3.readFile(filePath, "utf-8");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/utils/path.ts
|
|
386
|
+
import * as path7 from "path";
|
|
387
|
+
import * as fs4 from "fs/promises";
|
|
388
|
+
function resolveSourcePath(source, variantDir, packageRoot) {
|
|
389
|
+
if (source.startsWith("_template/")) {
|
|
390
|
+
return path7.join(packageRoot, source);
|
|
391
|
+
}
|
|
392
|
+
return path7.join(variantDir, source);
|
|
393
|
+
}
|
|
394
|
+
async function walkDir(dir) {
|
|
395
|
+
const files = [];
|
|
396
|
+
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
397
|
+
for (const entry of entries) {
|
|
398
|
+
const fullPath = path7.join(dir, entry.name);
|
|
399
|
+
if (entry.isDirectory()) {
|
|
400
|
+
files.push(...await walkDir(fullPath));
|
|
401
|
+
} else if (entry.isFile()) {
|
|
402
|
+
files.push(fullPath);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return files;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/core/skills-installer.ts
|
|
409
|
+
async function installSkills(options) {
|
|
410
|
+
const { manifest, ides, scope, onlyIds } = options;
|
|
411
|
+
const installed = [];
|
|
412
|
+
const targets = manifest.skills.filter(
|
|
413
|
+
(s) => !onlyIds || onlyIds.includes(s.id)
|
|
414
|
+
);
|
|
415
|
+
for (const skill of targets) {
|
|
416
|
+
const skillIdes = skill.ides.filter((i) => ides.includes(i));
|
|
417
|
+
if (skillIdes.length === 0) {
|
|
418
|
+
logger.warn(
|
|
419
|
+
`Skill "${skill.name}" supports [${skill.ides.join(
|
|
420
|
+
","
|
|
421
|
+
)}], no overlap with [${ides.join(",")}]; skipped.`
|
|
422
|
+
);
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
const sourceRecords = await writeSkillSource(skill, options);
|
|
426
|
+
installed.push(...sourceRecords);
|
|
427
|
+
for (const ide of skillIdes) {
|
|
428
|
+
const mirrorRecords = await mirrorSkillToIde(
|
|
429
|
+
skill,
|
|
430
|
+
ide,
|
|
431
|
+
scope,
|
|
432
|
+
options.projectRoot
|
|
433
|
+
);
|
|
434
|
+
installed.push(...mirrorRecords);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return { resources: installed, count: installed.length };
|
|
438
|
+
}
|
|
439
|
+
async function writeSkillSource(skill, options) {
|
|
440
|
+
const { data, packageRoot, projectRoot } = options;
|
|
441
|
+
const sourceAbs = path8.resolve(packageRoot, skill.source);
|
|
442
|
+
const targetDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
443
|
+
const stat2 = await fs5.stat(sourceAbs);
|
|
444
|
+
const records = [];
|
|
445
|
+
if (stat2.isFile()) {
|
|
446
|
+
const targetFile = path8.join(targetDir, "SKILL.md");
|
|
447
|
+
const content = await renderSkillContent(sourceAbs, skill, data);
|
|
448
|
+
await writeFileSafe(targetFile, content);
|
|
449
|
+
records.push(makeSourceRecord(skill, targetFile, content));
|
|
450
|
+
logger.debug(` Wrote source: ${targetFile}`);
|
|
451
|
+
return records;
|
|
452
|
+
}
|
|
453
|
+
await ensureDir(targetDir);
|
|
454
|
+
const entries = await walkDir(sourceAbs);
|
|
455
|
+
for (const entry of entries) {
|
|
456
|
+
const rel2 = path8.relative(sourceAbs, entry);
|
|
457
|
+
let targetFile = path8.join(targetDir, rel2);
|
|
458
|
+
if (skill.template && targetFile.endsWith(".hbs")) {
|
|
459
|
+
targetFile = targetFile.slice(0, -4);
|
|
460
|
+
}
|
|
461
|
+
const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs5.readFile(entry, "utf-8");
|
|
462
|
+
await writeFileSafe(targetFile, content);
|
|
463
|
+
const relWritten = path8.relative(targetDir, targetFile);
|
|
464
|
+
records.push(makeSourceRecord(skill, targetFile, content, relWritten));
|
|
465
|
+
logger.debug(` Wrote source: ${targetFile}`);
|
|
466
|
+
}
|
|
467
|
+
return records;
|
|
468
|
+
}
|
|
469
|
+
async function mirrorSkillToIde(skill, ide, scope, projectRoot) {
|
|
470
|
+
const sourceDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
471
|
+
const adapter = getAdapter(ide);
|
|
472
|
+
const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
|
|
473
|
+
const records = [];
|
|
474
|
+
const sourceFiles = await walkDir(sourceDir);
|
|
475
|
+
await ensureDir(targetDir);
|
|
476
|
+
for (const src of sourceFiles) {
|
|
477
|
+
const rel2 = path8.relative(sourceDir, src);
|
|
478
|
+
const targetFile = path8.join(targetDir, rel2);
|
|
479
|
+
const content = await fs5.readFile(src, "utf-8");
|
|
480
|
+
await writeFileSafe(targetFile, content);
|
|
481
|
+
records.push(makeMirrorRecord(skill, targetFile, content, ide, scope, rel2));
|
|
482
|
+
logger.debug(` Mirrored ${ide}:${scope}: ${targetFile}`);
|
|
483
|
+
}
|
|
484
|
+
return records;
|
|
485
|
+
}
|
|
486
|
+
async function renderSkillContent(sourceAbs, skill, data) {
|
|
487
|
+
if (skill.template ?? sourceAbs.endsWith(".hbs")) {
|
|
488
|
+
const tpl = await loadTemplateFile(sourceAbs);
|
|
489
|
+
return renderTemplate(tpl, { ...data, skill });
|
|
490
|
+
}
|
|
491
|
+
return fs5.readFile(sourceAbs, "utf-8");
|
|
492
|
+
}
|
|
493
|
+
function makeSourceRecord(skill, targetAbs, content, rel2) {
|
|
494
|
+
const id = rel2 ? `${skill.id}:source:${rel2}` : `${skill.id}:source`;
|
|
495
|
+
return {
|
|
496
|
+
id,
|
|
497
|
+
target: targetAbs,
|
|
498
|
+
hash: computeHash(content),
|
|
499
|
+
strategy: skill.updateStrategy
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
function makeMirrorRecord(skill, targetAbs, content, ide, scope, rel2) {
|
|
503
|
+
const id = rel2 && rel2 !== "SKILL.md" ? `${skill.id}:${rel2}` : skill.id;
|
|
504
|
+
return {
|
|
505
|
+
id,
|
|
506
|
+
target: targetAbs,
|
|
507
|
+
hash: computeHash(content),
|
|
508
|
+
strategy: skill.updateStrategy,
|
|
509
|
+
ide,
|
|
510
|
+
scope
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
async function updateSkills(options) {
|
|
514
|
+
const { manifest, ides, scope, projectRoot } = options;
|
|
515
|
+
const summary = { overwritten: 0, managed: 0, skipped: 0, created: 0 };
|
|
516
|
+
const updated = [];
|
|
517
|
+
for (const skill of manifest.skills) {
|
|
518
|
+
const skillIdes = skill.ides.filter((i) => ides.includes(i));
|
|
519
|
+
if (skillIdes.length === 0) continue;
|
|
520
|
+
const sourceRecords = await rewriteSkillSource(
|
|
521
|
+
skill,
|
|
522
|
+
options,
|
|
523
|
+
summary
|
|
524
|
+
);
|
|
525
|
+
updated.push(...sourceRecords);
|
|
526
|
+
for (const ide of skillIdes) {
|
|
527
|
+
const mirrorRecords = await mirrorSkillToIde(
|
|
528
|
+
skill,
|
|
529
|
+
ide,
|
|
530
|
+
scope,
|
|
531
|
+
projectRoot
|
|
532
|
+
);
|
|
533
|
+
updated.push(...mirrorRecords);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
return { resources: updated, summary };
|
|
537
|
+
}
|
|
538
|
+
async function rewriteSkillSource(skill, options, summary) {
|
|
539
|
+
const { data, packageRoot, projectRoot } = options;
|
|
540
|
+
const sourceAbs = path8.resolve(packageRoot, skill.source);
|
|
541
|
+
const targetDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
542
|
+
const stat2 = await fs5.stat(sourceAbs);
|
|
543
|
+
if (!stat2.isFile()) {
|
|
544
|
+
await ensureDir(targetDir);
|
|
545
|
+
const entries = await walkDir(sourceAbs);
|
|
546
|
+
const records = [];
|
|
547
|
+
for (const entry of entries) {
|
|
548
|
+
const rel2 = path8.relative(sourceAbs, entry);
|
|
549
|
+
let targetFile2 = path8.join(targetDir, rel2);
|
|
550
|
+
if (skill.template && targetFile2.endsWith(".hbs")) {
|
|
551
|
+
targetFile2 = targetFile2.slice(0, -4);
|
|
552
|
+
}
|
|
553
|
+
const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs5.readFile(entry, "utf-8");
|
|
554
|
+
const exists2 = await fileExists(targetFile2);
|
|
555
|
+
if (exists2) {
|
|
556
|
+
await backupFile(targetFile2, projectRoot);
|
|
557
|
+
summary.overwritten++;
|
|
558
|
+
} else {
|
|
559
|
+
summary.created++;
|
|
560
|
+
}
|
|
561
|
+
await writeFileSafe(targetFile2, content);
|
|
562
|
+
const relWritten = path8.relative(targetDir, targetFile2);
|
|
563
|
+
records.push(makeSourceRecord(skill, targetFile2, content, relWritten));
|
|
564
|
+
}
|
|
565
|
+
return records;
|
|
566
|
+
}
|
|
567
|
+
const targetFile = path8.join(targetDir, "SKILL.md");
|
|
568
|
+
const newContent = await renderSkillContent(sourceAbs, skill, data);
|
|
569
|
+
const exists = await fileExists(targetFile);
|
|
570
|
+
if (skill.updateStrategy === "frozen") {
|
|
571
|
+
if (exists) {
|
|
572
|
+
summary.skipped++;
|
|
573
|
+
const current2 = await readFileOrNull(targetFile) ?? newContent;
|
|
574
|
+
return [makeSourceRecord(skill, targetFile, current2)];
|
|
575
|
+
}
|
|
576
|
+
await writeFileSafe(targetFile, newContent);
|
|
577
|
+
summary.created++;
|
|
578
|
+
return [makeSourceRecord(skill, targetFile, newContent)];
|
|
579
|
+
}
|
|
580
|
+
if (skill.updateStrategy === "regenerable" || !exists) {
|
|
581
|
+
if (exists) {
|
|
582
|
+
await backupFile(targetFile, projectRoot);
|
|
583
|
+
summary.overwritten++;
|
|
584
|
+
} else {
|
|
585
|
+
summary.created++;
|
|
586
|
+
}
|
|
587
|
+
await writeFileSafe(targetFile, newContent);
|
|
588
|
+
return [makeSourceRecord(skill, targetFile, newContent)];
|
|
589
|
+
}
|
|
590
|
+
const current = await readFileOrNull(targetFile);
|
|
591
|
+
let merged = current ?? newContent;
|
|
592
|
+
for (const regionId of skill.managedRegions ?? []) {
|
|
593
|
+
const re = new RegExp(
|
|
594
|
+
`<!-- teamix-evo:managed:start id="${escapeRegExp(
|
|
595
|
+
regionId
|
|
596
|
+
)}" -->([\\s\\S]*?)<!-- teamix-evo:managed:end id="${escapeRegExp(
|
|
597
|
+
regionId
|
|
598
|
+
)}" -->`
|
|
599
|
+
);
|
|
600
|
+
const match = newContent.match(re);
|
|
601
|
+
if (match) {
|
|
602
|
+
const region = match[1].replace(/^\n/, "").replace(/\n$/, "");
|
|
603
|
+
try {
|
|
604
|
+
merged = replaceManagedRegion(merged, regionId, region);
|
|
605
|
+
} catch {
|
|
606
|
+
logger.warn(
|
|
607
|
+
`Managed region "${regionId}" not found in ${targetFile}. Skipped.`
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
await backupFile(targetFile, projectRoot);
|
|
613
|
+
await writeFileSafe(targetFile, merged);
|
|
614
|
+
summary.managed++;
|
|
615
|
+
return [makeSourceRecord(skill, targetFile, merged)];
|
|
616
|
+
}
|
|
617
|
+
function escapeRegExp(str) {
|
|
618
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
619
|
+
}
|
|
620
|
+
async function syncSkillsToIdes(options) {
|
|
621
|
+
const { projectRoot, skills, ides, scope, onlyIds } = options;
|
|
622
|
+
const out = [];
|
|
623
|
+
const targets = skills.filter((s) => !onlyIds || onlyIds.includes(s.id));
|
|
624
|
+
for (const skill of targets) {
|
|
625
|
+
const sourceDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
626
|
+
if (!await fileExists(sourceDir)) {
|
|
627
|
+
logger.warn(
|
|
628
|
+
`Skill "${skill.id}" has no source at ${sourceDir}; skipped.`
|
|
629
|
+
);
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
for (const ide of ides) {
|
|
633
|
+
const adapter = getAdapter(ide);
|
|
634
|
+
const targetDir = adapter.getSkillTargetDir(
|
|
635
|
+
skill.name,
|
|
636
|
+
scope,
|
|
637
|
+
projectRoot
|
|
638
|
+
);
|
|
639
|
+
await ensureDir(targetDir);
|
|
640
|
+
const sourceFiles = await walkDir(sourceDir);
|
|
641
|
+
for (const src of sourceFiles) {
|
|
642
|
+
const rel2 = path8.relative(sourceDir, src);
|
|
643
|
+
const targetFile = path8.join(targetDir, rel2);
|
|
644
|
+
const content = await fs5.readFile(src, "utf-8");
|
|
645
|
+
await writeFileSafe(targetFile, content);
|
|
646
|
+
out.push({
|
|
647
|
+
id: rel2 === "SKILL.md" ? skill.id : `${skill.id}:${rel2}`,
|
|
648
|
+
target: targetFile,
|
|
649
|
+
hash: computeHash(content),
|
|
650
|
+
strategy: skill.updateStrategy,
|
|
651
|
+
ide,
|
|
652
|
+
scope
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return { resources: out, count: out.length };
|
|
658
|
+
}
|
|
659
|
+
async function removeSkillFiles(records) {
|
|
660
|
+
const removed = [];
|
|
661
|
+
for (const r of records) {
|
|
662
|
+
try {
|
|
663
|
+
await fs5.unlink(r.target);
|
|
664
|
+
removed.push(r.target);
|
|
665
|
+
} catch (err) {
|
|
666
|
+
if (err.code !== "ENOENT") {
|
|
667
|
+
logger.warn(`Failed to remove ${r.target}: ${err.message}`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
const parents = new Set(records.map((r) => path8.dirname(r.target)));
|
|
672
|
+
for (const dir of parents) {
|
|
673
|
+
try {
|
|
674
|
+
const entries = await fs5.readdir(dir);
|
|
675
|
+
if (entries.length === 0) await fs5.rmdir(dir);
|
|
676
|
+
} catch {
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return removed;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// src/core/skills-add.ts
|
|
683
|
+
var DEFAULT_SKILLS_PACKAGE = "@teamix-evo/skills";
|
|
684
|
+
var FLAT_VARIANT = "_flat";
|
|
685
|
+
async function runSkillsAdd(options) {
|
|
686
|
+
const { projectRoot, names: requestedNames } = options;
|
|
687
|
+
const packageName = options.packageName ?? DEFAULT_SKILLS_PACKAGE;
|
|
688
|
+
const ideIdent = options.ide ?? "qoder";
|
|
689
|
+
const isIncremental = !!requestedNames && requestedNames.length > 0;
|
|
690
|
+
await ensureTeamixDir(projectRoot);
|
|
691
|
+
const existingConfig = await readProjectConfig(projectRoot);
|
|
692
|
+
const existingSkillsCfg = existingConfig?.packages?.skills;
|
|
693
|
+
if (!isIncremental && existingSkillsCfg) {
|
|
694
|
+
return { status: "already-added" };
|
|
695
|
+
}
|
|
696
|
+
const ides = options.ides && options.ides.length > 0 ? [...options.ides] : existingSkillsCfg?.ides ? [...existingSkillsCfg.ides] : [];
|
|
697
|
+
const scope = options.scope ?? existingSkillsCfg?.scope;
|
|
698
|
+
if (ides.length === 0) {
|
|
699
|
+
throw new Error("At least one IDE must be selected.");
|
|
700
|
+
}
|
|
701
|
+
if (!scope) {
|
|
702
|
+
throw new Error("Scope must be specified (project | global).");
|
|
703
|
+
}
|
|
704
|
+
const { manifest, data, packageRoot } = await loadSkillsData(packageName);
|
|
705
|
+
const currentDesignVariant = await readDesignVariant(projectRoot);
|
|
706
|
+
if (isIncremental) {
|
|
707
|
+
const known = new Set(manifest.skills.map((s) => s.id));
|
|
708
|
+
const unknown = requestedNames.filter((n) => !known.has(n));
|
|
709
|
+
if (unknown.length > 0) {
|
|
710
|
+
const available = [...known].join(", ");
|
|
711
|
+
throw new Error(
|
|
712
|
+
`Unknown skill id(s): ${unknown.join(", ")}. Available: ${available || "(none)"}.`
|
|
713
|
+
);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
const existingInstalled = await readInstalledManifest(projectRoot);
|
|
717
|
+
const existingPkg = existingInstalled?.installed.find(
|
|
718
|
+
(p) => p.package === packageName
|
|
719
|
+
);
|
|
720
|
+
const existingLock = await readSkillsLock(projectRoot);
|
|
721
|
+
const existingSkillIds = /* @__PURE__ */ new Set([
|
|
722
|
+
...Object.keys(existingLock?.skills ?? {}),
|
|
723
|
+
// Legacy fallback: pre-ADR-0013 installs only had manifest.json. Derive
|
|
724
|
+
// skill ids by stripping the trailing :source / :sub-file suffix.
|
|
725
|
+
...(existingPkg?.resources ?? []).map((r) => r.id.split(":")[0])
|
|
726
|
+
]);
|
|
727
|
+
let onlyIds;
|
|
728
|
+
let skippedSkillIds;
|
|
729
|
+
if (isIncremental) {
|
|
730
|
+
skippedSkillIds = requestedNames.filter((n) => existingSkillIds.has(n));
|
|
731
|
+
onlyIds = requestedNames.filter((n) => !existingSkillIds.has(n));
|
|
732
|
+
} else {
|
|
733
|
+
skippedSkillIds = [];
|
|
734
|
+
onlyIds = manifest.skills.filter((s) => {
|
|
735
|
+
if (!s.variant) return true;
|
|
736
|
+
if (!currentDesignVariant) {
|
|
737
|
+
logger.debug(
|
|
738
|
+
`Skipping variant-bound skill "${s.id}" (variant=${s.variant}): no design pack installed; will be picked up when "design init" runs.`
|
|
739
|
+
);
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
if (s.variant !== currentDesignVariant) {
|
|
743
|
+
logger.debug(
|
|
744
|
+
`Skipping variant-bound skill "${s.id}" (variant=${s.variant}): current design variant is "${currentDesignVariant}".`
|
|
745
|
+
);
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
748
|
+
return true;
|
|
749
|
+
}).map((s) => s.id);
|
|
750
|
+
}
|
|
751
|
+
if (isIncremental && onlyIds.length === 0) {
|
|
752
|
+
return {
|
|
753
|
+
status: "installed",
|
|
754
|
+
packageName,
|
|
755
|
+
version: existingSkillsCfg?.version ?? manifest.version,
|
|
756
|
+
ides,
|
|
757
|
+
scope,
|
|
758
|
+
skillCount: 0,
|
|
759
|
+
fileCount: 0,
|
|
760
|
+
resources: [],
|
|
761
|
+
addedSkillIds: [],
|
|
762
|
+
skippedSkillIds
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
const result = await installSkills({
|
|
766
|
+
projectRoot,
|
|
767
|
+
manifest,
|
|
768
|
+
data,
|
|
769
|
+
packageRoot,
|
|
770
|
+
ides,
|
|
771
|
+
scope,
|
|
772
|
+
onlyIds
|
|
773
|
+
});
|
|
774
|
+
const config = existingConfig ?? {
|
|
775
|
+
$schema: "https://teamix-evo.dev/schema/config/v1.json",
|
|
776
|
+
schemaVersion: 1,
|
|
777
|
+
ide: ideIdent,
|
|
778
|
+
packages: {}
|
|
779
|
+
};
|
|
780
|
+
config.packages.skills = {
|
|
781
|
+
variant: FLAT_VARIANT,
|
|
782
|
+
version: manifest.version,
|
|
783
|
+
ides,
|
|
784
|
+
scope
|
|
785
|
+
};
|
|
786
|
+
await writeProjectConfig(projectRoot, config);
|
|
787
|
+
const installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
788
|
+
const installedManifest = existingInstalled ?? {
|
|
789
|
+
schemaVersion: 1,
|
|
790
|
+
installed: []
|
|
791
|
+
};
|
|
792
|
+
const idx = installedManifest.installed.findIndex(
|
|
793
|
+
(p) => p.package === packageName
|
|
794
|
+
);
|
|
795
|
+
const mergedResources = mergeInstalledResources(
|
|
796
|
+
existingPkg?.resources ?? [],
|
|
797
|
+
result.resources
|
|
798
|
+
);
|
|
799
|
+
const entry = {
|
|
800
|
+
package: packageName,
|
|
801
|
+
variant: FLAT_VARIANT,
|
|
802
|
+
version: manifest.version,
|
|
803
|
+
installedAt,
|
|
804
|
+
resources: mergedResources
|
|
805
|
+
};
|
|
806
|
+
if (idx >= 0) installedManifest.installed[idx] = entry;
|
|
807
|
+
else installedManifest.installed.push(entry);
|
|
808
|
+
await writeInstalledManifest(projectRoot, installedManifest);
|
|
809
|
+
const lock = existingLock ?? {
|
|
810
|
+
schemaVersion: 1,
|
|
811
|
+
skills: {}
|
|
812
|
+
};
|
|
813
|
+
for (const skillId of onlyIds) {
|
|
814
|
+
const skillDef = manifest.skills.find((s) => s.id === skillId);
|
|
815
|
+
if (!skillDef) continue;
|
|
816
|
+
const mirroredTo = skillDef.ides.filter((i) => ides.includes(i));
|
|
817
|
+
lock.skills[skillId] = {
|
|
818
|
+
version: skillDef.version,
|
|
819
|
+
from: packageName,
|
|
820
|
+
installedAt,
|
|
821
|
+
scope,
|
|
822
|
+
mirroredTo
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
await writeSkillsLock(projectRoot, lock);
|
|
826
|
+
return {
|
|
827
|
+
status: "installed",
|
|
828
|
+
packageName,
|
|
829
|
+
version: manifest.version,
|
|
830
|
+
ides,
|
|
831
|
+
scope,
|
|
832
|
+
skillCount: onlyIds.length,
|
|
833
|
+
fileCount: result.count,
|
|
834
|
+
resources: result.resources,
|
|
835
|
+
addedSkillIds: onlyIds,
|
|
836
|
+
skippedSkillIds
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
function mergeInstalledResources(existing, next) {
|
|
840
|
+
const map = /* @__PURE__ */ new Map();
|
|
841
|
+
const key = (r) => `${r.id}|${r.ide ?? ""}|${r.scope ?? ""}`;
|
|
842
|
+
for (const r of existing) map.set(key(r), r);
|
|
843
|
+
for (const r of next) map.set(key(r), r);
|
|
844
|
+
return [...map.values()];
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/core/design-init.ts
|
|
848
|
+
var BASELINE_DESIGN_RULES_SKILL = "teamix-evo-design-rules";
|
|
849
|
+
var DEFAULT_SKILLS_PACKAGE2 = "@teamix-evo/skills";
|
|
850
|
+
var DEFAULT_AUTO_SKILL_IDES = ["qoder", "claude"];
|
|
851
|
+
var DEFAULT_AUTO_SKILL_SCOPE = "project";
|
|
852
|
+
var DEFAULT_DESIGN_PACKAGE = "@teamix-evo/design";
|
|
853
|
+
var require3 = createRequire2(import.meta.url);
|
|
854
|
+
async function runDesignInit(options) {
|
|
855
|
+
const { projectRoot, variant, ide } = options;
|
|
856
|
+
const packageName = options.packageName ?? DEFAULT_DESIGN_PACKAGE;
|
|
857
|
+
await ensureTeamixDir(projectRoot);
|
|
858
|
+
const existingConfig = await readProjectConfig(projectRoot);
|
|
859
|
+
if (existingConfig?.packages?.design) {
|
|
860
|
+
return {
|
|
861
|
+
status: "already-initialized",
|
|
862
|
+
existingVariant: existingConfig.packages.design.variant
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
const packageRoot = options.packageRoot ?? resolveDesignPackageRoot(packageName);
|
|
866
|
+
const catalog = await loadDesignPackageManifest(packageRoot);
|
|
867
|
+
const variantEntry = catalog.variants.find((v) => v.name === variant);
|
|
868
|
+
if (!variantEntry) {
|
|
869
|
+
const known = catalog.variants.map((v) => v.name).join(", ");
|
|
870
|
+
throw new Error(
|
|
871
|
+
`Design variant not found: "${variant}". Known variants: ${known || "(none)"}. Hint: run "teamix-evo design list-variants" to see all.`
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
const defaultDir = path9.join(packageRoot, "default");
|
|
875
|
+
const variantDir = path9.join(packageRoot, "variants", variant);
|
|
876
|
+
const defaultPack = await loadDesignPack(defaultDir);
|
|
877
|
+
const variantPack = await loadDesignPack(variantDir);
|
|
878
|
+
const merge = await mergeDefaultAndVariant(defaultDir, variantDir);
|
|
879
|
+
const installed = [];
|
|
880
|
+
for (const file of merge.files) {
|
|
881
|
+
const result = await installPackFile(file, projectRoot);
|
|
882
|
+
if (result) installed.push(result);
|
|
883
|
+
}
|
|
884
|
+
const lock = {
|
|
885
|
+
schemaVersion: 1,
|
|
886
|
+
default: { version: defaultPack.version, from: packageName },
|
|
887
|
+
variant: {
|
|
888
|
+
name: variantPack.name,
|
|
889
|
+
displayName: variantPack.displayName,
|
|
890
|
+
version: variantPack.version,
|
|
891
|
+
from: packageName
|
|
892
|
+
},
|
|
893
|
+
linked: variantPack.linked,
|
|
894
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
895
|
+
};
|
|
896
|
+
await writeFileSafe(
|
|
897
|
+
path9.join(projectRoot, ".teamix-evo", "design", "pack.lock.json"),
|
|
898
|
+
JSON.stringify(lock, null, 2) + "\n"
|
|
899
|
+
);
|
|
900
|
+
const config = {
|
|
901
|
+
$schema: "https://teamix-evo.dev/schema/config/v1.json",
|
|
902
|
+
schemaVersion: 1,
|
|
903
|
+
ide,
|
|
904
|
+
packages: {
|
|
905
|
+
design: {
|
|
906
|
+
variant,
|
|
907
|
+
version: variantPack.version,
|
|
908
|
+
tailwind: "v4"
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
await writeProjectConfig(projectRoot, config);
|
|
913
|
+
const installedManifest = {
|
|
914
|
+
schemaVersion: 1,
|
|
915
|
+
installed: [
|
|
916
|
+
{
|
|
917
|
+
package: packageName,
|
|
918
|
+
variant,
|
|
919
|
+
version: variantPack.version,
|
|
920
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
921
|
+
resources: installed
|
|
922
|
+
}
|
|
923
|
+
]
|
|
924
|
+
};
|
|
925
|
+
await writeInstalledManifest(projectRoot, installedManifest);
|
|
926
|
+
const skills = await tryAutoInstallVariantSkills({
|
|
927
|
+
projectRoot,
|
|
928
|
+
variant,
|
|
929
|
+
ide
|
|
930
|
+
});
|
|
931
|
+
return {
|
|
932
|
+
status: "installed",
|
|
933
|
+
packageName,
|
|
934
|
+
variant,
|
|
935
|
+
version: variantPack.version,
|
|
936
|
+
count: installed.length,
|
|
937
|
+
resources: installed,
|
|
938
|
+
merge: {
|
|
939
|
+
overrides: merge.overrides,
|
|
940
|
+
variantAdds: merge.variantAdds,
|
|
941
|
+
defaultPassThrough: merge.defaultPassThrough
|
|
942
|
+
},
|
|
943
|
+
skills
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
async function tryAutoInstallVariantSkills(args) {
|
|
947
|
+
const { projectRoot, variant, ide } = args;
|
|
948
|
+
const variantSkillId = `${BASELINE_DESIGN_RULES_SKILL}-${variant}`;
|
|
949
|
+
const desired = [BASELINE_DESIGN_RULES_SKILL, variantSkillId];
|
|
950
|
+
let manifestSkillIds;
|
|
951
|
+
try {
|
|
952
|
+
const { manifest } = await loadSkillsData(DEFAULT_SKILLS_PACKAGE2);
|
|
953
|
+
manifestSkillIds = new Set(manifest.skills.map((s) => s.id));
|
|
954
|
+
} catch (err) {
|
|
955
|
+
logger.warn(
|
|
956
|
+
`Skipping skills auto-install: could not load skills manifest (${err.message}).`
|
|
957
|
+
);
|
|
958
|
+
return {
|
|
959
|
+
attempted: [],
|
|
960
|
+
addedSkillIds: [],
|
|
961
|
+
skippedSkillIds: [],
|
|
962
|
+
missing: desired
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
const present = desired.filter((id) => manifestSkillIds.has(id));
|
|
966
|
+
const missing = desired.filter((id) => !manifestSkillIds.has(id));
|
|
967
|
+
if (missing.length > 0) {
|
|
968
|
+
logger.warn(
|
|
969
|
+
`Skills auto-install: not found in manifest, skipping: ${missing.join(", ")}.`
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
if (present.length === 0) {
|
|
973
|
+
return {
|
|
974
|
+
attempted: desired,
|
|
975
|
+
addedSkillIds: [],
|
|
976
|
+
skippedSkillIds: [],
|
|
977
|
+
missing
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
try {
|
|
981
|
+
const result = await runSkillsAdd({
|
|
982
|
+
projectRoot,
|
|
983
|
+
names: present,
|
|
984
|
+
ides: DEFAULT_AUTO_SKILL_IDES,
|
|
985
|
+
scope: DEFAULT_AUTO_SKILL_SCOPE,
|
|
986
|
+
ide
|
|
987
|
+
});
|
|
988
|
+
if (result.status !== "installed") {
|
|
989
|
+
return {
|
|
990
|
+
attempted: desired,
|
|
991
|
+
addedSkillIds: [],
|
|
992
|
+
skippedSkillIds: present,
|
|
993
|
+
missing
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
return {
|
|
997
|
+
attempted: desired,
|
|
998
|
+
addedSkillIds: result.addedSkillIds,
|
|
999
|
+
skippedSkillIds: result.skippedSkillIds,
|
|
1000
|
+
missing
|
|
1001
|
+
};
|
|
1002
|
+
} catch (err) {
|
|
1003
|
+
logger.warn(
|
|
1004
|
+
`Skills auto-install failed (continuing): ${err.message}`
|
|
1005
|
+
);
|
|
1006
|
+
return {
|
|
1007
|
+
attempted: desired,
|
|
1008
|
+
addedSkillIds: [],
|
|
1009
|
+
skippedSkillIds: [],
|
|
1010
|
+
missing
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
async function installPackFile(file, projectRoot) {
|
|
1015
|
+
const cls = classifyPackFile(file.relPath);
|
|
1016
|
+
if (cls === null) return null;
|
|
1017
|
+
const targetAbs = path9.join(projectRoot, cls.target);
|
|
1018
|
+
if (cls.isFrozen && await fileExists(targetAbs)) {
|
|
1019
|
+
const existing = await fs6.readFile(targetAbs, "utf-8");
|
|
1020
|
+
return {
|
|
1021
|
+
id: `pack:${file.relPath}`,
|
|
1022
|
+
target: cls.target,
|
|
1023
|
+
hash: computeHash(existing),
|
|
1024
|
+
strategy: cls.strategy
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
const content = await fs6.readFile(file.sourcePath, "utf-8");
|
|
1028
|
+
await writeFileSafe(targetAbs, content);
|
|
1029
|
+
return {
|
|
1030
|
+
id: `pack:${file.relPath}`,
|
|
1031
|
+
target: cls.target,
|
|
1032
|
+
hash: computeHash(content),
|
|
1033
|
+
strategy: cls.strategy
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
function resolveDesignPackageRoot(packageName) {
|
|
1037
|
+
const pkgJson = require3.resolve(`${packageName}/package.json`);
|
|
1038
|
+
return path9.dirname(pkgJson);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// src/core/ui-init.ts
|
|
1042
|
+
var DEFAULT_UI_ALIASES = {
|
|
1043
|
+
components: "src/components/ui",
|
|
1044
|
+
hooks: "src/hooks",
|
|
1045
|
+
utils: "src/lib/utils",
|
|
1046
|
+
lib: "src/lib",
|
|
1047
|
+
business: "src/components/business",
|
|
1048
|
+
templates: "src/templates"
|
|
1049
|
+
};
|
|
1050
|
+
var DEFAULT_UI_ICON_LIBRARY = "lucide";
|
|
1051
|
+
async function runUiInit(options) {
|
|
1052
|
+
const { projectRoot } = options;
|
|
1053
|
+
const ideIdent = options.ide ?? "qoder";
|
|
1054
|
+
await ensureTeamixDir(projectRoot);
|
|
1055
|
+
const existingConfig = await readProjectConfig(projectRoot);
|
|
1056
|
+
if (existingConfig?.packages?.ui) {
|
|
1057
|
+
return { status: "already-initialized" };
|
|
1058
|
+
}
|
|
1059
|
+
const aliases = {
|
|
1060
|
+
components: options.aliases?.components ?? DEFAULT_UI_ALIASES.components,
|
|
1061
|
+
hooks: options.aliases?.hooks ?? DEFAULT_UI_ALIASES.hooks,
|
|
1062
|
+
utils: options.aliases?.utils ?? DEFAULT_UI_ALIASES.utils,
|
|
1063
|
+
lib: options.aliases?.lib ?? DEFAULT_UI_ALIASES.lib,
|
|
1064
|
+
business: options.aliases?.business ?? DEFAULT_UI_ALIASES.business,
|
|
1065
|
+
templates: options.aliases?.templates ?? DEFAULT_UI_ALIASES.templates
|
|
1066
|
+
};
|
|
1067
|
+
const iconLibrary = options.iconLibrary ?? DEFAULT_UI_ICON_LIBRARY;
|
|
1068
|
+
const tsx = options.tsx ?? true;
|
|
1069
|
+
const rsc = options.rsc ?? false;
|
|
1070
|
+
const config = existingConfig ?? {
|
|
1071
|
+
$schema: "https://teamix-evo.dev/schema/config/v1.json",
|
|
1072
|
+
schemaVersion: 1,
|
|
1073
|
+
ide: ideIdent,
|
|
1074
|
+
packages: {}
|
|
1075
|
+
};
|
|
1076
|
+
config.packages.ui = {
|
|
1077
|
+
variant: "_flat",
|
|
1078
|
+
version: "0.0.0",
|
|
1079
|
+
aliases,
|
|
1080
|
+
iconLibrary,
|
|
1081
|
+
tsx,
|
|
1082
|
+
rsc
|
|
1083
|
+
};
|
|
1084
|
+
await writeProjectConfig(projectRoot, config);
|
|
1085
|
+
return {
|
|
1086
|
+
status: "installed",
|
|
1087
|
+
aliases,
|
|
1088
|
+
iconLibrary,
|
|
1089
|
+
tsx,
|
|
1090
|
+
rsc
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// src/core/ui-client.ts
|
|
1095
|
+
import * as path10 from "path";
|
|
1096
|
+
import * as fs7 from "fs/promises";
|
|
1097
|
+
import { createRequire as createRequire3 } from "module";
|
|
1098
|
+
import { loadUiPackageManifest } from "@teamix-evo/registry";
|
|
1099
|
+
var require4 = createRequire3(import.meta.url);
|
|
1100
|
+
function resolvePackageRoot2(packageName) {
|
|
1101
|
+
const pkgJsonPath = require4.resolve(`${packageName}/package.json`);
|
|
1102
|
+
return path10.dirname(pkgJsonPath);
|
|
1103
|
+
}
|
|
1104
|
+
async function loadUiData(packageName) {
|
|
1105
|
+
const packageRoot = resolvePackageRoot2(packageName);
|
|
1106
|
+
logger.debug(`Resolved ui package root: ${packageRoot}`);
|
|
1107
|
+
const manifest = await loadUiPackageManifest(packageRoot);
|
|
1108
|
+
let data = {};
|
|
1109
|
+
const dataPath = path10.join(packageRoot, "_data.json");
|
|
1110
|
+
try {
|
|
1111
|
+
const raw = await fs7.readFile(dataPath, "utf-8");
|
|
1112
|
+
data = JSON.parse(raw);
|
|
1113
|
+
} catch (err) {
|
|
1114
|
+
if (err.code !== "ENOENT") {
|
|
1115
|
+
throw err;
|
|
1116
|
+
}
|
|
1117
|
+
logger.debug(`No _data.json found at ${dataPath}, using empty data`);
|
|
1118
|
+
}
|
|
1119
|
+
return { manifest, data, packageRoot };
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// src/core/ui-installer.ts
|
|
1123
|
+
import * as path11 from "path";
|
|
1124
|
+
import * as fs8 from "fs/promises";
|
|
1125
|
+
import { resolveUiEntryOrder } from "@teamix-evo/registry";
|
|
1126
|
+
|
|
1127
|
+
// src/utils/transform-imports.ts
|
|
1128
|
+
var SOURCE_ROOT_TO_ALIAS_KEY = {
|
|
1129
|
+
components: "components",
|
|
1130
|
+
hooks: "hooks",
|
|
1131
|
+
utils: "utils",
|
|
1132
|
+
lib: "lib"
|
|
1133
|
+
};
|
|
1134
|
+
function rewriteImports(source, aliases) {
|
|
1135
|
+
return source.replace(
|
|
1136
|
+
/(['"])@\/([a-z][a-z0-9-]*)(\/[^'"]*)?\1/g,
|
|
1137
|
+
(full, quote, root, rest) => {
|
|
1138
|
+
const aliasKey = SOURCE_ROOT_TO_ALIAS_KEY[root];
|
|
1139
|
+
if (!aliasKey) return full;
|
|
1140
|
+
const alias = aliases[aliasKey];
|
|
1141
|
+
const normalized = aliasToImportPath(alias);
|
|
1142
|
+
const flatRest = flattenRestPath(rest);
|
|
1143
|
+
return `${quote}${normalized}${flatRest}${quote}`;
|
|
1144
|
+
}
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
function flattenRestPath(rest) {
|
|
1148
|
+
if (!rest) return "";
|
|
1149
|
+
const segments = rest.split("/");
|
|
1150
|
+
if (segments.length === 3) {
|
|
1151
|
+
return `/${segments[2]}`;
|
|
1152
|
+
}
|
|
1153
|
+
return rest;
|
|
1154
|
+
}
|
|
1155
|
+
function aliasToImportPath(alias) {
|
|
1156
|
+
const trimmed = alias.replace(/^\.\//, "").replace(/\/$/, "");
|
|
1157
|
+
if (trimmed.startsWith("src/")) {
|
|
1158
|
+
return `@/${trimmed.slice("src/".length)}`;
|
|
1159
|
+
}
|
|
1160
|
+
return `@/${trimmed}`;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// src/core/ui-installer.ts
|
|
1164
|
+
var DESIGN_COMPONENTS_DIR = ".teamix-evo/design/components";
|
|
1165
|
+
async function installUiEntries(options) {
|
|
1166
|
+
const {
|
|
1167
|
+
projectRoot,
|
|
1168
|
+
manifest,
|
|
1169
|
+
packageRoot,
|
|
1170
|
+
aliases,
|
|
1171
|
+
requested,
|
|
1172
|
+
skipExisting = true
|
|
1173
|
+
} = options;
|
|
1174
|
+
const orderedIds = resolveUiEntryOrder(manifest.entries, requested);
|
|
1175
|
+
const idToEntry = new Map(manifest.entries.map((e) => [e.id, e]));
|
|
1176
|
+
const resources = [];
|
|
1177
|
+
const npmDeps = {};
|
|
1178
|
+
const metaFiles = [];
|
|
1179
|
+
let written = 0;
|
|
1180
|
+
let skipped = 0;
|
|
1181
|
+
for (const id of orderedIds) {
|
|
1182
|
+
const entry = idToEntry.get(id);
|
|
1183
|
+
if (!entry) continue;
|
|
1184
|
+
if (entry.dependencies) {
|
|
1185
|
+
for (const [name, range] of Object.entries(entry.dependencies)) {
|
|
1186
|
+
npmDeps[name] = range;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
for (const file of entry.files) {
|
|
1190
|
+
const targetAbs = resolveTargetPath(projectRoot, aliases, entry, file);
|
|
1191
|
+
const exists = await fileExists(targetAbs);
|
|
1192
|
+
if (exists && skipExisting && (entry.updateStrategy ?? "frozen") === "frozen") {
|
|
1193
|
+
logger.info(` skip (frozen, exists): ${rel(projectRoot, targetAbs)}`);
|
|
1194
|
+
skipped++;
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
const sourceAbs = path11.resolve(packageRoot, file.source);
|
|
1198
|
+
const raw = await fs8.readFile(sourceAbs, "utf-8");
|
|
1199
|
+
const transformed = rewriteImports(raw, aliases);
|
|
1200
|
+
await writeFileSafe(targetAbs, transformed);
|
|
1201
|
+
written++;
|
|
1202
|
+
logger.info(` write: ${rel(projectRoot, targetAbs)}`);
|
|
1203
|
+
resources.push({
|
|
1204
|
+
id: `${entry.id}:${file.targetName}`,
|
|
1205
|
+
target: targetAbs,
|
|
1206
|
+
hash: computeHash(transformed),
|
|
1207
|
+
strategy: entry.updateStrategy ?? "frozen"
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
if (entry.meta) {
|
|
1211
|
+
const metaSourceAbs = path11.resolve(packageRoot, entry.meta);
|
|
1212
|
+
const metaContent = await fs8.readFile(metaSourceAbs, "utf-8");
|
|
1213
|
+
const metaTargetAbs = path11.join(
|
|
1214
|
+
projectRoot,
|
|
1215
|
+
DESIGN_COMPONENTS_DIR,
|
|
1216
|
+
`${entry.id}.meta.md`
|
|
1217
|
+
);
|
|
1218
|
+
await writeFileSafe(metaTargetAbs, metaContent);
|
|
1219
|
+
metaFiles.push(metaTargetAbs);
|
|
1220
|
+
resources.push({
|
|
1221
|
+
id: `${entry.id}:meta`,
|
|
1222
|
+
target: metaTargetAbs,
|
|
1223
|
+
hash: computeHash(metaContent),
|
|
1224
|
+
strategy: "regenerable"
|
|
1225
|
+
});
|
|
1226
|
+
logger.info(` meta: ${rel(projectRoot, metaTargetAbs)}`);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
return {
|
|
1230
|
+
orderedIds,
|
|
1231
|
+
resources,
|
|
1232
|
+
npmDependencies: npmDeps,
|
|
1233
|
+
written,
|
|
1234
|
+
skipped,
|
|
1235
|
+
metaFiles
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
function resolveTargetPath(projectRoot, aliases, entry, file) {
|
|
1239
|
+
const aliasDir = aliases[file.targetAlias];
|
|
1240
|
+
if (!aliasDir) {
|
|
1241
|
+
throw new Error(
|
|
1242
|
+
`Entry "${entry.id}" requires alias "${file.targetAlias}" but it is not configured.`
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1245
|
+
return path11.join(projectRoot, aliasDir, file.targetName);
|
|
1246
|
+
}
|
|
1247
|
+
function rel(projectRoot, abs) {
|
|
1248
|
+
return path11.relative(projectRoot, abs);
|
|
1249
|
+
}
|
|
1250
|
+
async function removeUiFiles(records) {
|
|
1251
|
+
const removed = [];
|
|
1252
|
+
for (const r of records) {
|
|
1253
|
+
try {
|
|
1254
|
+
await fs8.unlink(r.target);
|
|
1255
|
+
removed.push(r.target);
|
|
1256
|
+
} catch (err) {
|
|
1257
|
+
if (err.code !== "ENOENT") {
|
|
1258
|
+
logger.warn(`Failed to remove ${r.target}: ${err.message}`);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
const parents = new Set(records.map((r) => path11.dirname(r.target)));
|
|
1263
|
+
for (const dir of parents) {
|
|
1264
|
+
try {
|
|
1265
|
+
const entries = await fs8.readdir(dir);
|
|
1266
|
+
if (entries.length === 0) await fs8.rmdir(dir);
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
return removed;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// src/core/ui-add.ts
|
|
1274
|
+
var DEFAULT_UI_PACKAGE = "@teamix-evo/ui";
|
|
1275
|
+
async function runUiAdd(options) {
|
|
1276
|
+
const { projectRoot, ids, overwrite } = options;
|
|
1277
|
+
const packageName = options.packageName ?? DEFAULT_UI_PACKAGE;
|
|
1278
|
+
if (ids.length === 0) {
|
|
1279
|
+
throw new Error("At least one entry id must be provided.");
|
|
1280
|
+
}
|
|
1281
|
+
const config = await readProjectConfig(projectRoot);
|
|
1282
|
+
const uiCfg = config?.packages?.ui;
|
|
1283
|
+
if (!config || !uiCfg?.aliases) {
|
|
1284
|
+
throw new Error(
|
|
1285
|
+
"UI not initialized. Run `runUiInit` (or `teamix-evo ui init`) first."
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
const { manifest, packageRoot } = await loadUiData(packageName);
|
|
1289
|
+
const knownIds = new Set(manifest.entries.map((e) => e.id));
|
|
1290
|
+
const unknown = ids.filter((id) => !knownIds.has(id));
|
|
1291
|
+
if (unknown.length > 0) {
|
|
1292
|
+
throw new Error(
|
|
1293
|
+
`Unknown entry id(s): ${unknown.map((s) => `"${s}"`).join(", ")}. Run \`teamix-evo ui list\` to see options.`
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
const result = await installUiEntries({
|
|
1297
|
+
projectRoot,
|
|
1298
|
+
manifest,
|
|
1299
|
+
packageRoot,
|
|
1300
|
+
aliases: uiCfg.aliases,
|
|
1301
|
+
requested: ids,
|
|
1302
|
+
skipExisting: !overwrite
|
|
1303
|
+
});
|
|
1304
|
+
const installed = await readInstalledManifest(
|
|
1305
|
+
projectRoot
|
|
1306
|
+
) ?? { schemaVersion: 1, installed: [] };
|
|
1307
|
+
const idx = installed.installed.findIndex((p) => p.package === packageName);
|
|
1308
|
+
const prior = idx >= 0 ? installed.installed[idx] : null;
|
|
1309
|
+
const mergedResources = mergeResources(
|
|
1310
|
+
prior?.resources ?? [],
|
|
1311
|
+
result.resources
|
|
1312
|
+
);
|
|
1313
|
+
const entry = {
|
|
1314
|
+
package: packageName,
|
|
1315
|
+
variant: "_flat",
|
|
1316
|
+
version: manifest.version,
|
|
1317
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1318
|
+
resources: mergedResources
|
|
1319
|
+
};
|
|
1320
|
+
if (idx >= 0) installed.installed[idx] = entry;
|
|
1321
|
+
else installed.installed.push(entry);
|
|
1322
|
+
await writeInstalledManifest(projectRoot, installed);
|
|
1323
|
+
if (uiCfg.version !== manifest.version) {
|
|
1324
|
+
uiCfg.version = manifest.version;
|
|
1325
|
+
await writeProjectConfig(projectRoot, config);
|
|
1326
|
+
}
|
|
1327
|
+
return {
|
|
1328
|
+
packageName,
|
|
1329
|
+
orderedIds: result.orderedIds,
|
|
1330
|
+
written: result.written,
|
|
1331
|
+
skipped: result.skipped,
|
|
1332
|
+
metaFiles: result.metaFiles,
|
|
1333
|
+
npmDependencies: result.npmDependencies,
|
|
1334
|
+
resources: result.resources
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
function mergeResources(prior, next) {
|
|
1338
|
+
const merged = /* @__PURE__ */ new Map();
|
|
1339
|
+
for (const r of prior) merged.set(r.id, r);
|
|
1340
|
+
for (const r of next) merged.set(r.id, r);
|
|
1341
|
+
return Array.from(merged.values());
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// src/core/ui-list.ts
|
|
1345
|
+
var DEFAULT_UI_PACKAGE2 = "@teamix-evo/ui";
|
|
1346
|
+
async function runUiList(options) {
|
|
1347
|
+
const { projectRoot, installedOnly } = options;
|
|
1348
|
+
const packageName = options.packageName ?? DEFAULT_UI_PACKAGE2;
|
|
1349
|
+
const { manifest } = await loadUiData(packageName);
|
|
1350
|
+
const installedManifest = await readInstalledManifest(projectRoot);
|
|
1351
|
+
const installedIds = /* @__PURE__ */ new Set();
|
|
1352
|
+
const uiPkg = installedManifest?.installed.find(
|
|
1353
|
+
(p) => p.package === packageName
|
|
1354
|
+
);
|
|
1355
|
+
for (const r of uiPkg?.resources ?? []) {
|
|
1356
|
+
const colon = r.id.indexOf(":");
|
|
1357
|
+
installedIds.add(colon >= 0 ? r.id.slice(0, colon) : r.id);
|
|
1358
|
+
}
|
|
1359
|
+
const entries = manifest.entries.filter((e) => !installedOnly || installedIds.has(e.id)).map((e) => ({
|
|
1360
|
+
id: e.id,
|
|
1361
|
+
type: e.type,
|
|
1362
|
+
description: e.description,
|
|
1363
|
+
installed: installedIds.has(e.id)
|
|
1364
|
+
}));
|
|
1365
|
+
return {
|
|
1366
|
+
packageName,
|
|
1367
|
+
total: manifest.entries.length,
|
|
1368
|
+
installedCount: installedIds.size,
|
|
1369
|
+
entries
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
// src/core/installer.ts
|
|
1374
|
+
import * as path12 from "path";
|
|
1375
|
+
import * as fs9 from "fs/promises";
|
|
1376
|
+
async function installResources(options) {
|
|
1377
|
+
const { projectRoot, manifest, data, variantDir, packageRoot } = options;
|
|
1378
|
+
const installedResources = [];
|
|
1379
|
+
for (const resource of manifest.resources) {
|
|
1380
|
+
logger.debug(`Installing resource: ${resource.id} \u2192 ${resource.target}`);
|
|
1381
|
+
if (resource.recursive) {
|
|
1382
|
+
const results = await installRecursiveResource(
|
|
1383
|
+
resource,
|
|
1384
|
+
projectRoot,
|
|
1385
|
+
data,
|
|
1386
|
+
variantDir,
|
|
1387
|
+
packageRoot
|
|
1388
|
+
);
|
|
1389
|
+
installedResources.push(...results);
|
|
1390
|
+
} else {
|
|
1391
|
+
const result = await installSingleResource(
|
|
1392
|
+
resource,
|
|
1393
|
+
projectRoot,
|
|
1394
|
+
data,
|
|
1395
|
+
variantDir,
|
|
1396
|
+
packageRoot
|
|
1397
|
+
);
|
|
1398
|
+
installedResources.push(result);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
return {
|
|
1402
|
+
resources: installedResources,
|
|
1403
|
+
count: installedResources.length
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
async function installSingleResource(resource, projectRoot, data, variantDir, packageRoot) {
|
|
1407
|
+
const sourcePath = resolveSourcePath(
|
|
1408
|
+
resource.source,
|
|
1409
|
+
variantDir,
|
|
1410
|
+
packageRoot
|
|
1411
|
+
);
|
|
1412
|
+
const targetPath = path12.join(projectRoot, resource.target);
|
|
1413
|
+
let content;
|
|
1414
|
+
if (resource.template) {
|
|
1415
|
+
const templateContent = await loadTemplateFile(sourcePath);
|
|
1416
|
+
content = renderTemplate(templateContent, data);
|
|
1417
|
+
} else {
|
|
1418
|
+
content = await fs9.readFile(sourcePath, "utf-8");
|
|
1419
|
+
}
|
|
1420
|
+
await writeFileSafe(targetPath, content);
|
|
1421
|
+
const hash = computeHash(content);
|
|
1422
|
+
logger.debug(` Written: ${resource.target} (${resource.updateStrategy})`);
|
|
1423
|
+
return {
|
|
1424
|
+
id: resource.id,
|
|
1425
|
+
target: resource.target,
|
|
1426
|
+
hash,
|
|
1427
|
+
strategy: resource.updateStrategy
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
async function installRecursiveResource(resource, projectRoot, data, variantDir, packageRoot) {
|
|
1431
|
+
const sourcePath = resolveSourcePath(
|
|
1432
|
+
resource.source,
|
|
1433
|
+
variantDir,
|
|
1434
|
+
packageRoot
|
|
1435
|
+
);
|
|
1436
|
+
const targetDir = path12.join(projectRoot, resource.target);
|
|
1437
|
+
const results = [];
|
|
1438
|
+
await ensureDir(targetDir);
|
|
1439
|
+
const entries = await walkDir(sourcePath);
|
|
1440
|
+
for (const entry of entries) {
|
|
1441
|
+
const relPath = path12.relative(sourcePath, entry);
|
|
1442
|
+
let targetFile = path12.join(targetDir, relPath);
|
|
1443
|
+
if (resource.template && targetFile.endsWith(".hbs")) {
|
|
1444
|
+
targetFile = targetFile.slice(0, -4);
|
|
1445
|
+
}
|
|
1446
|
+
let content;
|
|
1447
|
+
if (resource.template && entry.endsWith(".hbs")) {
|
|
1448
|
+
const templateContent = await loadTemplateFile(entry);
|
|
1449
|
+
content = renderTemplate(templateContent, data);
|
|
1450
|
+
} else {
|
|
1451
|
+
content = await fs9.readFile(entry, "utf-8");
|
|
1452
|
+
}
|
|
1453
|
+
await writeFileSafe(targetFile, content);
|
|
1454
|
+
const hash = computeHash(content);
|
|
1455
|
+
const targetRel = path12.relative(projectRoot, targetFile);
|
|
1456
|
+
results.push({
|
|
1457
|
+
id: `${resource.id}:${relPath}`,
|
|
1458
|
+
target: targetRel,
|
|
1459
|
+
hash,
|
|
1460
|
+
strategy: resource.updateStrategy
|
|
1461
|
+
});
|
|
1462
|
+
logger.debug(` Written: ${targetRel}`);
|
|
1463
|
+
}
|
|
1464
|
+
return results;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// src/core/registry-client.ts
|
|
1468
|
+
import * as path13 from "path";
|
|
1469
|
+
import * as fs10 from "fs/promises";
|
|
1470
|
+
import { createRequire as createRequire4 } from "module";
|
|
1471
|
+
import { loadVariantManifest } from "@teamix-evo/registry";
|
|
1472
|
+
var require5 = createRequire4(import.meta.url);
|
|
1473
|
+
function resolvePackageRoot3(packageName) {
|
|
1474
|
+
const pkgJsonPath = require5.resolve(`${packageName}/package.json`);
|
|
1475
|
+
return path13.dirname(pkgJsonPath);
|
|
1476
|
+
}
|
|
1477
|
+
async function loadVariantData(packageName, variant) {
|
|
1478
|
+
const packageRoot = resolvePackageRoot3(packageName);
|
|
1479
|
+
const variantDir = path13.join(packageRoot, "library", variant);
|
|
1480
|
+
logger.debug(`Resolved variant dir: ${variantDir}`);
|
|
1481
|
+
logger.debug(`Package root: ${packageRoot}`);
|
|
1482
|
+
const manifest = await loadVariantManifest(variantDir);
|
|
1483
|
+
let data = {};
|
|
1484
|
+
const dataPath = path13.join(variantDir, "_data.json");
|
|
1485
|
+
try {
|
|
1486
|
+
const raw = await fs10.readFile(dataPath, "utf-8");
|
|
1487
|
+
data = JSON.parse(raw);
|
|
1488
|
+
} catch (err) {
|
|
1489
|
+
if (err.code !== "ENOENT") {
|
|
1490
|
+
throw err;
|
|
1491
|
+
}
|
|
1492
|
+
logger.debug(`No _data.json found at ${dataPath}, using empty data`);
|
|
1493
|
+
}
|
|
1494
|
+
return { manifest, data, variantDir, packageRoot };
|
|
1495
|
+
}
|
|
1496
|
+
export {
|
|
1497
|
+
DEFAULT_UI_ALIASES,
|
|
1498
|
+
DEFAULT_UI_ICON_LIBRARY,
|
|
1499
|
+
ensureTeamixDir,
|
|
1500
|
+
getTeamixDir,
|
|
1501
|
+
installResources,
|
|
1502
|
+
installSkills,
|
|
1503
|
+
installUiEntries,
|
|
1504
|
+
loadSkillsData,
|
|
1505
|
+
loadUiData,
|
|
1506
|
+
loadVariantData,
|
|
1507
|
+
readInstalledManifest,
|
|
1508
|
+
readProjectConfig,
|
|
1509
|
+
removeSkillFiles,
|
|
1510
|
+
removeUiFiles,
|
|
1511
|
+
runDesignInit,
|
|
1512
|
+
runSkillsAdd,
|
|
1513
|
+
runUiAdd,
|
|
1514
|
+
runUiInit,
|
|
1515
|
+
runUiList,
|
|
1516
|
+
syncSkillsToIdes,
|
|
1517
|
+
updateSkills,
|
|
1518
|
+
writeInstalledManifest,
|
|
1519
|
+
writeProjectConfig
|
|
1520
|
+
};
|
|
1521
|
+
//# sourceMappingURL=index.js.map
|