teamix-evo 0.2.0 → 0.3.1
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/README.md +76 -50
- package/dist/core/index.d.ts +94 -16
- package/dist/core/index.js +727 -372
- package/dist/core/index.js.map +1 -1
- package/dist/index.js +1758 -997
- package/dist/index.js.map +1 -1
- package/package.json +11 -5
package/dist/core/index.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
// src/core/
|
|
2
|
-
import * as
|
|
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
|
|
3
12
|
import * as fs from "fs/promises";
|
|
4
|
-
import
|
|
5
|
-
import { loadVariantManifest } from "@teamix-evo/registry";
|
|
13
|
+
import * as path from "path";
|
|
6
14
|
|
|
7
15
|
// src/utils/logger.ts
|
|
8
16
|
import { red, yellow, cyan, green, gray } from "kolorist";
|
|
@@ -27,52 +35,20 @@ var logger = {
|
|
|
27
35
|
}
|
|
28
36
|
};
|
|
29
37
|
|
|
30
|
-
// src/core/registry-client.ts
|
|
31
|
-
var require2 = createRequire(import.meta.url);
|
|
32
|
-
function resolvePackageRoot(packageName) {
|
|
33
|
-
const pkgJsonPath = require2.resolve(`${packageName}/package.json`);
|
|
34
|
-
return path.dirname(pkgJsonPath);
|
|
35
|
-
}
|
|
36
|
-
async function loadVariantData(packageName, variant) {
|
|
37
|
-
const packageRoot = resolvePackageRoot(packageName);
|
|
38
|
-
const variantDir = path.join(packageRoot, "library", variant);
|
|
39
|
-
logger.debug(`Resolved variant dir: ${variantDir}`);
|
|
40
|
-
logger.debug(`Package root: ${packageRoot}`);
|
|
41
|
-
const manifest = await loadVariantManifest(variantDir);
|
|
42
|
-
let data = {};
|
|
43
|
-
const dataPath = path.join(variantDir, "_data.json");
|
|
44
|
-
try {
|
|
45
|
-
const raw = await fs.readFile(dataPath, "utf-8");
|
|
46
|
-
data = JSON.parse(raw);
|
|
47
|
-
} catch (err) {
|
|
48
|
-
if (err.code !== "ENOENT") {
|
|
49
|
-
throw err;
|
|
50
|
-
}
|
|
51
|
-
logger.debug(`No _data.json found at ${dataPath}, using empty data`);
|
|
52
|
-
}
|
|
53
|
-
return { manifest, data, variantDir, packageRoot };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// src/core/installer.ts
|
|
57
|
-
import * as path4 from "path";
|
|
58
|
-
import * as fs5 from "fs/promises";
|
|
59
|
-
|
|
60
38
|
// src/utils/fs.ts
|
|
61
|
-
import * as fs2 from "fs/promises";
|
|
62
|
-
import * as path2 from "path";
|
|
63
39
|
async function ensureDir(dir) {
|
|
64
|
-
await
|
|
40
|
+
await fs.mkdir(dir, { recursive: true });
|
|
65
41
|
}
|
|
66
42
|
async function writeFileSafe(filePath, content) {
|
|
67
|
-
const dir =
|
|
43
|
+
const dir = path.dirname(filePath);
|
|
68
44
|
await ensureDir(dir);
|
|
69
45
|
const tmp = filePath + ".tmp";
|
|
70
|
-
await
|
|
71
|
-
await
|
|
46
|
+
await fs.writeFile(tmp, content, "utf-8");
|
|
47
|
+
await fs.rename(tmp, filePath);
|
|
72
48
|
}
|
|
73
49
|
async function readFileOrNull(filePath) {
|
|
74
50
|
try {
|
|
75
|
-
return await
|
|
51
|
+
return await fs.readFile(filePath, "utf-8");
|
|
76
52
|
} catch (err) {
|
|
77
53
|
if (err.code === "ENOENT") {
|
|
78
54
|
return null;
|
|
@@ -86,21 +62,21 @@ async function backupFile(filePath, projectRoot) {
|
|
|
86
62
|
logger.debug(`Skip backup: ${filePath} does not exist`);
|
|
87
63
|
return;
|
|
88
64
|
}
|
|
89
|
-
const rel2 =
|
|
65
|
+
const rel2 = path.relative(projectRoot, filePath);
|
|
90
66
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
91
|
-
const backupPath =
|
|
67
|
+
const backupPath = path.join(
|
|
92
68
|
projectRoot,
|
|
93
69
|
".teamix-evo",
|
|
94
70
|
".backups",
|
|
95
71
|
`${rel2}.${timestamp}.bak`
|
|
96
72
|
);
|
|
97
|
-
await ensureDir(
|
|
98
|
-
await
|
|
99
|
-
logger.debug(`Backed up ${rel2} \u2192 ${
|
|
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)}`);
|
|
100
76
|
}
|
|
101
77
|
async function fileExists(filePath) {
|
|
102
78
|
try {
|
|
103
|
-
await
|
|
79
|
+
await fs.access(filePath);
|
|
104
80
|
return true;
|
|
105
81
|
} catch {
|
|
106
82
|
return false;
|
|
@@ -114,157 +90,71 @@ function computeHash(content) {
|
|
|
114
90
|
return `sha256:${hash}`;
|
|
115
91
|
}
|
|
116
92
|
|
|
117
|
-
// src/
|
|
118
|
-
import
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
compiledCache.set(templateContent, compiled);
|
|
134
|
-
}
|
|
135
|
-
return compiled;
|
|
136
|
-
}
|
|
137
|
-
function renderTemplate(templateContent, data) {
|
|
138
|
-
const compiled = getCompiledTemplate(templateContent);
|
|
139
|
-
return compiled(data);
|
|
140
|
-
}
|
|
141
|
-
async function loadTemplateFile(filePath) {
|
|
142
|
-
return fs3.readFile(filePath, "utf-8");
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// src/utils/path.ts
|
|
146
|
-
import * as path3 from "path";
|
|
147
|
-
import * as fs4 from "fs/promises";
|
|
148
|
-
function resolveSourcePath(source, variantDir, packageRoot) {
|
|
149
|
-
if (source.startsWith("_template/")) {
|
|
150
|
-
return path3.join(packageRoot, source);
|
|
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;
|
|
151
109
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
files.push(...await walkDir(fullPath));
|
|
161
|
-
} else if (entry.isFile()) {
|
|
162
|
-
files.push(fullPath);
|
|
163
|
-
}
|
|
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
|
+
};
|
|
164
118
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const { projectRoot, manifest, data, variantDir, packageRoot } = options;
|
|
171
|
-
const installedResources = [];
|
|
172
|
-
for (const resource of manifest.resources) {
|
|
173
|
-
logger.debug(`Installing resource: ${resource.id} \u2192 ${resource.target}`);
|
|
174
|
-
if (resource.recursive) {
|
|
175
|
-
const results = await installRecursiveResource(
|
|
176
|
-
resource,
|
|
177
|
-
projectRoot,
|
|
178
|
-
data,
|
|
179
|
-
variantDir,
|
|
180
|
-
packageRoot
|
|
181
|
-
);
|
|
182
|
-
installedResources.push(...results);
|
|
183
|
-
} else {
|
|
184
|
-
const result = await installSingleResource(
|
|
185
|
-
resource,
|
|
186
|
-
projectRoot,
|
|
187
|
-
data,
|
|
188
|
-
variantDir,
|
|
189
|
-
packageRoot
|
|
190
|
-
);
|
|
191
|
-
installedResources.push(result);
|
|
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 };
|
|
192
124
|
}
|
|
125
|
+
return { target, strategy: "regenerable", isFrozen: false };
|
|
193
126
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const sourcePath = resolveSourcePath(
|
|
201
|
-
resource.source,
|
|
202
|
-
variantDir,
|
|
203
|
-
packageRoot
|
|
204
|
-
);
|
|
205
|
-
const targetPath = path4.join(projectRoot, resource.target);
|
|
206
|
-
let content;
|
|
207
|
-
if (resource.template) {
|
|
208
|
-
const templateContent = await loadTemplateFile(sourcePath);
|
|
209
|
-
content = renderTemplate(templateContent, data);
|
|
210
|
-
} else {
|
|
211
|
-
content = await fs5.readFile(sourcePath, "utf-8");
|
|
127
|
+
if (FROZEN_FILES.has(relPath)) {
|
|
128
|
+
return {
|
|
129
|
+
target: path2.posix.join(TEAMIX_DIR, relPath),
|
|
130
|
+
strategy: "frozen",
|
|
131
|
+
isFrozen: true
|
|
132
|
+
};
|
|
212
133
|
}
|
|
213
|
-
await writeFileSafe(targetPath, content);
|
|
214
|
-
const hash = computeHash(content);
|
|
215
|
-
logger.debug(` Written: ${resource.target} (${resource.updateStrategy})`);
|
|
216
134
|
return {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
strategy: resource.updateStrategy
|
|
135
|
+
target: path2.posix.join(TEAMIX_DIR, relPath),
|
|
136
|
+
strategy: "regenerable",
|
|
137
|
+
isFrozen: false
|
|
221
138
|
};
|
|
222
139
|
}
|
|
223
|
-
async function installRecursiveResource(resource, projectRoot, data, variantDir, packageRoot) {
|
|
224
|
-
const sourcePath = resolveSourcePath(
|
|
225
|
-
resource.source,
|
|
226
|
-
variantDir,
|
|
227
|
-
packageRoot
|
|
228
|
-
);
|
|
229
|
-
const targetDir = path4.join(projectRoot, resource.target);
|
|
230
|
-
const results = [];
|
|
231
|
-
await ensureDir(targetDir);
|
|
232
|
-
const entries = await walkDir(sourcePath);
|
|
233
|
-
for (const entry of entries) {
|
|
234
|
-
const relPath = path4.relative(sourcePath, entry);
|
|
235
|
-
let targetFile = path4.join(targetDir, relPath);
|
|
236
|
-
if (resource.template && targetFile.endsWith(".hbs")) {
|
|
237
|
-
targetFile = targetFile.slice(0, -4);
|
|
238
|
-
}
|
|
239
|
-
let content;
|
|
240
|
-
if (resource.template && entry.endsWith(".hbs")) {
|
|
241
|
-
const templateContent = await loadTemplateFile(entry);
|
|
242
|
-
content = renderTemplate(templateContent, data);
|
|
243
|
-
} else {
|
|
244
|
-
content = await fs5.readFile(entry, "utf-8");
|
|
245
|
-
}
|
|
246
|
-
await writeFileSafe(targetFile, content);
|
|
247
|
-
const hash = computeHash(content);
|
|
248
|
-
const targetRel = path4.relative(projectRoot, targetFile);
|
|
249
|
-
results.push({
|
|
250
|
-
id: `${resource.id}:${relPath}`,
|
|
251
|
-
target: targetRel,
|
|
252
|
-
hash,
|
|
253
|
-
strategy: resource.updateStrategy
|
|
254
|
-
});
|
|
255
|
-
logger.debug(` Written: ${targetRel}`);
|
|
256
|
-
}
|
|
257
|
-
return results;
|
|
258
|
-
}
|
|
259
140
|
|
|
260
141
|
// src/core/state.ts
|
|
261
|
-
import * as
|
|
262
|
-
import {
|
|
263
|
-
|
|
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";
|
|
264
150
|
var CONFIG_FILE = "config.json";
|
|
265
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";
|
|
266
156
|
function getTeamixDir(projectRoot) {
|
|
267
|
-
return
|
|
157
|
+
return path3.join(projectRoot, TEAMIX_DIR2);
|
|
268
158
|
}
|
|
269
159
|
async function ensureTeamixDir(projectRoot) {
|
|
270
160
|
const dir = getTeamixDir(projectRoot);
|
|
@@ -272,7 +162,7 @@ async function ensureTeamixDir(projectRoot) {
|
|
|
272
162
|
return dir;
|
|
273
163
|
}
|
|
274
164
|
async function readProjectConfig(projectRoot) {
|
|
275
|
-
const configPath =
|
|
165
|
+
const configPath = path3.join(projectRoot, TEAMIX_DIR2, CONFIG_FILE);
|
|
276
166
|
const raw = await readFileOrNull(configPath);
|
|
277
167
|
if (raw === null) return null;
|
|
278
168
|
try {
|
|
@@ -289,12 +179,12 @@ async function readProjectConfig(projectRoot) {
|
|
|
289
179
|
}
|
|
290
180
|
}
|
|
291
181
|
async function writeProjectConfig(projectRoot, config) {
|
|
292
|
-
const configPath =
|
|
182
|
+
const configPath = path3.join(projectRoot, TEAMIX_DIR2, CONFIG_FILE);
|
|
293
183
|
await writeFileSafe(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
294
184
|
logger.debug(`Wrote config \u2192 ${configPath}`);
|
|
295
185
|
}
|
|
296
186
|
async function readInstalledManifest(projectRoot) {
|
|
297
|
-
const manifestPath =
|
|
187
|
+
const manifestPath = path3.join(projectRoot, TEAMIX_DIR2, MANIFEST_FILE);
|
|
298
188
|
const raw = await readFileOrNull(manifestPath);
|
|
299
189
|
if (raw === null) return null;
|
|
300
190
|
try {
|
|
@@ -311,90 +201,94 @@ async function readInstalledManifest(projectRoot) {
|
|
|
311
201
|
}
|
|
312
202
|
}
|
|
313
203
|
async function writeInstalledManifest(projectRoot, manifest) {
|
|
314
|
-
const manifestPath =
|
|
204
|
+
const manifestPath = path3.join(projectRoot, TEAMIX_DIR2, MANIFEST_FILE);
|
|
315
205
|
await writeFileSafe(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
316
206
|
logger.debug(`Wrote manifest \u2192 ${manifestPath}`);
|
|
317
207
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
await ensureTeamixDir(projectRoot);
|
|
325
|
-
const existingConfig = await readProjectConfig(projectRoot);
|
|
326
|
-
if (existingConfig?.packages?.design) {
|
|
327
|
-
return {
|
|
328
|
-
status: "already-initialized",
|
|
329
|
-
existingVariant: existingConfig.packages.design.variant
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
const { manifest, data, variantDir, packageRoot } = await loadVariantData(
|
|
333
|
-
packageName,
|
|
334
|
-
variant
|
|
208
|
+
async function readDesignPackLock(projectRoot) {
|
|
209
|
+
const lockPath = path3.join(
|
|
210
|
+
projectRoot,
|
|
211
|
+
TEAMIX_DIR2,
|
|
212
|
+
DESIGN_DIR,
|
|
213
|
+
DESIGN_LOCK_FILE
|
|
335
214
|
);
|
|
336
|
-
const
|
|
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(
|
|
337
241
|
projectRoot,
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
version: manifest.version,
|
|
351
|
-
tailwind
|
|
352
|
-
}
|
|
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;
|
|
353
254
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
packageName,
|
|
372
|
-
variant,
|
|
373
|
-
version: manifest.version,
|
|
374
|
-
tailwind,
|
|
375
|
-
count: result.count,
|
|
376
|
-
resources: result.resources
|
|
377
|
-
};
|
|
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}`);
|
|
378
272
|
}
|
|
379
273
|
|
|
380
274
|
// src/core/skills-client.ts
|
|
381
|
-
import * as
|
|
382
|
-
import * as
|
|
383
|
-
import { createRequire
|
|
275
|
+
import * as path4 from "path";
|
|
276
|
+
import * as fs2 from "fs/promises";
|
|
277
|
+
import { createRequire } from "module";
|
|
384
278
|
import { loadSkillsPackageManifest } from "@teamix-evo/registry";
|
|
385
|
-
var
|
|
386
|
-
function
|
|
387
|
-
const pkgJsonPath =
|
|
388
|
-
return
|
|
279
|
+
var require2 = createRequire(import.meta.url);
|
|
280
|
+
function resolvePackageRoot(packageName) {
|
|
281
|
+
const pkgJsonPath = require2.resolve(`${packageName}/package.json`);
|
|
282
|
+
return path4.dirname(pkgJsonPath);
|
|
389
283
|
}
|
|
390
284
|
async function loadSkillsData(packageName) {
|
|
391
|
-
const packageRoot =
|
|
285
|
+
const packageRoot = resolvePackageRoot(packageName);
|
|
392
286
|
logger.debug(`Resolved skills package root: ${packageRoot}`);
|
|
393
287
|
const manifest = await loadSkillsPackageManifest(packageRoot);
|
|
394
288
|
let data = {};
|
|
395
|
-
const dataPath =
|
|
289
|
+
const dataPath = path4.join(packageRoot, "_data.json");
|
|
396
290
|
try {
|
|
397
|
-
const raw = await
|
|
291
|
+
const raw = await fs2.readFile(dataPath, "utf-8");
|
|
398
292
|
data = JSON.parse(raw);
|
|
399
293
|
} catch (err) {
|
|
400
294
|
if (err.code !== "ENOENT") {
|
|
@@ -406,13 +300,13 @@ async function loadSkillsData(packageName) {
|
|
|
406
300
|
}
|
|
407
301
|
|
|
408
302
|
// src/core/skills-installer.ts
|
|
409
|
-
import * as
|
|
410
|
-
import * as
|
|
303
|
+
import * as path8 from "path";
|
|
304
|
+
import * as fs5 from "fs/promises";
|
|
411
305
|
import { replaceManagedRegion } from "@teamix-evo/registry";
|
|
412
306
|
|
|
413
307
|
// src/ide/QoderAdapter.ts
|
|
414
308
|
import * as os from "os";
|
|
415
|
-
import * as
|
|
309
|
+
import * as path5 from "path";
|
|
416
310
|
var QoderAdapter = class {
|
|
417
311
|
kind = "qoder";
|
|
418
312
|
name = "qoder";
|
|
@@ -423,14 +317,14 @@ var QoderAdapter = class {
|
|
|
423
317
|
return true;
|
|
424
318
|
}
|
|
425
319
|
getSkillTargetDir(skillName, scope, projectRoot) {
|
|
426
|
-
const base = scope === "global" ?
|
|
427
|
-
return
|
|
320
|
+
const base = scope === "global" ? path5.join(os.homedir(), ".qoder") : path5.join(projectRoot ?? this.getProjectRoot(), ".qoder");
|
|
321
|
+
return path5.join(base, "skills", skillName);
|
|
428
322
|
}
|
|
429
323
|
};
|
|
430
324
|
|
|
431
325
|
// src/ide/ClaudeAdapter.ts
|
|
432
326
|
import * as os2 from "os";
|
|
433
|
-
import * as
|
|
327
|
+
import * as path6 from "path";
|
|
434
328
|
var ClaudeAdapter = class {
|
|
435
329
|
kind = "claude";
|
|
436
330
|
name = "claude";
|
|
@@ -441,8 +335,8 @@ var ClaudeAdapter = class {
|
|
|
441
335
|
return Boolean(process.env.CLAUDECODE);
|
|
442
336
|
}
|
|
443
337
|
getSkillTargetDir(skillName, scope, projectRoot) {
|
|
444
|
-
const base = scope === "global" ?
|
|
445
|
-
return
|
|
338
|
+
const base = scope === "global" ? path6.join(os2.homedir(), ".claude") : path6.join(projectRoot ?? this.getProjectRoot(), ".claude");
|
|
339
|
+
return path6.join(base, "skills", skillName);
|
|
446
340
|
}
|
|
447
341
|
};
|
|
448
342
|
|
|
@@ -460,6 +354,57 @@ function getAdapter(kind) {
|
|
|
460
354
|
}
|
|
461
355
|
}
|
|
462
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
|
+
|
|
463
408
|
// src/core/skills-installer.ts
|
|
464
409
|
async function installSkills(options) {
|
|
465
410
|
const { manifest, ides, scope, onlyIds } = options;
|
|
@@ -477,60 +422,85 @@ async function installSkills(options) {
|
|
|
477
422
|
);
|
|
478
423
|
continue;
|
|
479
424
|
}
|
|
425
|
+
const sourceRecords = await writeSkillSource(skill, options);
|
|
426
|
+
installed.push(...sourceRecords);
|
|
480
427
|
for (const ide of skillIdes) {
|
|
481
|
-
const
|
|
482
|
-
|
|
428
|
+
const mirrorRecords = await mirrorSkillToIde(
|
|
429
|
+
skill,
|
|
430
|
+
ide,
|
|
431
|
+
scope,
|
|
432
|
+
options.projectRoot
|
|
433
|
+
);
|
|
434
|
+
installed.push(...mirrorRecords);
|
|
483
435
|
}
|
|
484
436
|
}
|
|
485
437
|
return { resources: installed, count: installed.length };
|
|
486
438
|
}
|
|
487
|
-
async function
|
|
439
|
+
async function writeSkillSource(skill, options) {
|
|
488
440
|
const { data, packageRoot, projectRoot } = options;
|
|
489
|
-
const
|
|
490
|
-
const targetDir =
|
|
491
|
-
const
|
|
492
|
-
const
|
|
493
|
-
const results = [];
|
|
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 = [];
|
|
494
445
|
if (stat2.isFile()) {
|
|
495
|
-
const targetFile =
|
|
446
|
+
const targetFile = path8.join(targetDir, "SKILL.md");
|
|
496
447
|
const content = await renderSkillContent(sourceAbs, skill, data);
|
|
497
448
|
await writeFileSafe(targetFile, content);
|
|
498
|
-
|
|
499
|
-
logger.debug(` Wrote
|
|
500
|
-
return
|
|
449
|
+
records.push(makeSourceRecord(skill, targetFile, content));
|
|
450
|
+
logger.debug(` Wrote source: ${targetFile}`);
|
|
451
|
+
return records;
|
|
501
452
|
}
|
|
502
453
|
await ensureDir(targetDir);
|
|
503
454
|
const entries = await walkDir(sourceAbs);
|
|
504
455
|
for (const entry of entries) {
|
|
505
|
-
const rel2 =
|
|
506
|
-
let targetFile =
|
|
456
|
+
const rel2 = path8.relative(sourceAbs, entry);
|
|
457
|
+
let targetFile = path8.join(targetDir, rel2);
|
|
507
458
|
if (skill.template && targetFile.endsWith(".hbs")) {
|
|
508
459
|
targetFile = targetFile.slice(0, -4);
|
|
509
460
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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");
|
|
517
480
|
await writeFileSafe(targetFile, content);
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
);
|
|
521
|
-
logger.debug(` Wrote ${ide}:${scope}: ${targetFile}`);
|
|
481
|
+
records.push(makeMirrorRecord(skill, targetFile, content, ide, scope, rel2));
|
|
482
|
+
logger.debug(` Mirrored ${ide}:${scope}: ${targetFile}`);
|
|
522
483
|
}
|
|
523
|
-
return
|
|
484
|
+
return records;
|
|
524
485
|
}
|
|
525
486
|
async function renderSkillContent(sourceAbs, skill, data) {
|
|
526
487
|
if (skill.template ?? sourceAbs.endsWith(".hbs")) {
|
|
527
488
|
const tpl = await loadTemplateFile(sourceAbs);
|
|
528
489
|
return renderTemplate(tpl, { ...data, skill });
|
|
529
490
|
}
|
|
530
|
-
return
|
|
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
|
+
};
|
|
531
501
|
}
|
|
532
|
-
function
|
|
533
|
-
const id = rel2 ? `${skill.id}:${rel2}` : skill.id;
|
|
502
|
+
function makeMirrorRecord(skill, targetAbs, content, ide, scope, rel2) {
|
|
503
|
+
const id = rel2 && rel2 !== "SKILL.md" ? `${skill.id}:${rel2}` : skill.id;
|
|
534
504
|
return {
|
|
535
505
|
id,
|
|
536
506
|
target: targetAbs,
|
|
@@ -541,55 +511,46 @@ function makeInstalledRecord(skill, targetAbs, content, ide, scope, rel2) {
|
|
|
541
511
|
};
|
|
542
512
|
}
|
|
543
513
|
async function updateSkills(options) {
|
|
544
|
-
const { manifest, ides, scope,
|
|
514
|
+
const { manifest, ides, scope, projectRoot } = options;
|
|
545
515
|
const summary = { overwritten: 0, managed: 0, skipped: 0, created: 0 };
|
|
546
516
|
const updated = [];
|
|
547
|
-
const installedMap = /* @__PURE__ */ new Map();
|
|
548
|
-
for (const r of installed) {
|
|
549
|
-
installedMap.set(installedKey(r), r);
|
|
550
|
-
}
|
|
551
517
|
for (const skill of manifest.skills) {
|
|
552
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);
|
|
553
526
|
for (const ide of skillIdes) {
|
|
554
|
-
const
|
|
527
|
+
const mirrorRecords = await mirrorSkillToIde(
|
|
555
528
|
skill,
|
|
556
529
|
ide,
|
|
557
530
|
scope,
|
|
558
|
-
|
|
559
|
-
packageRoot,
|
|
560
|
-
projectRoot,
|
|
561
|
-
installedMap,
|
|
562
|
-
summary
|
|
531
|
+
projectRoot
|
|
563
532
|
);
|
|
564
|
-
updated.push(...
|
|
533
|
+
updated.push(...mirrorRecords);
|
|
565
534
|
}
|
|
566
535
|
}
|
|
567
536
|
return { resources: updated, summary };
|
|
568
537
|
}
|
|
569
|
-
async function
|
|
570
|
-
const
|
|
571
|
-
const
|
|
572
|
-
const
|
|
573
|
-
const stat2 = await
|
|
574
|
-
const records = [];
|
|
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);
|
|
575
543
|
if (!stat2.isFile()) {
|
|
576
|
-
const entries = await walkDir(sourceAbs);
|
|
577
544
|
await ensureDir(targetDir);
|
|
545
|
+
const entries = await walkDir(sourceAbs);
|
|
546
|
+
const records = [];
|
|
578
547
|
for (const entry of entries) {
|
|
579
|
-
const rel2 =
|
|
580
|
-
let targetFile2 =
|
|
548
|
+
const rel2 = path8.relative(sourceAbs, entry);
|
|
549
|
+
let targetFile2 = path8.join(targetDir, rel2);
|
|
581
550
|
if (skill.template && targetFile2.endsWith(".hbs")) {
|
|
582
551
|
targetFile2 = targetFile2.slice(0, -4);
|
|
583
552
|
}
|
|
584
|
-
|
|
585
|
-
if (skill.template && entry.endsWith(".hbs")) {
|
|
586
|
-
content = renderTemplate(await loadTemplateFile(entry), {
|
|
587
|
-
...data,
|
|
588
|
-
skill
|
|
589
|
-
});
|
|
590
|
-
} else {
|
|
591
|
-
content = await fs7.readFile(entry, "utf-8");
|
|
592
|
-
}
|
|
553
|
+
const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs5.readFile(entry, "utf-8");
|
|
593
554
|
const exists2 = await fileExists(targetFile2);
|
|
594
555
|
if (exists2) {
|
|
595
556
|
await backupFile(targetFile2, projectRoot);
|
|
@@ -598,30 +559,23 @@ async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRo
|
|
|
598
559
|
summary.created++;
|
|
599
560
|
}
|
|
600
561
|
await writeFileSafe(targetFile2, content);
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
);
|
|
562
|
+
const relWritten = path8.relative(targetDir, targetFile2);
|
|
563
|
+
records.push(makeSourceRecord(skill, targetFile2, content, relWritten));
|
|
604
564
|
}
|
|
605
565
|
return records;
|
|
606
566
|
}
|
|
607
|
-
const targetFile =
|
|
567
|
+
const targetFile = path8.join(targetDir, "SKILL.md");
|
|
608
568
|
const newContent = await renderSkillContent(sourceAbs, skill, data);
|
|
609
569
|
const exists = await fileExists(targetFile);
|
|
610
|
-
const installedKeyStr = `${skill.id}|${ide}|${scope}`;
|
|
611
|
-
const prior = installedMap.get(installedKeyStr);
|
|
612
570
|
if (skill.updateStrategy === "frozen") {
|
|
613
571
|
if (exists) {
|
|
614
572
|
summary.skipped++;
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
];
|
|
573
|
+
const current2 = await readFileOrNull(targetFile) ?? newContent;
|
|
574
|
+
return [makeSourceRecord(skill, targetFile, current2)];
|
|
618
575
|
}
|
|
619
576
|
await writeFileSafe(targetFile, newContent);
|
|
620
577
|
summary.created++;
|
|
621
|
-
|
|
622
|
-
makeInstalledRecord(skill, targetFile, newContent, ide, scope)
|
|
623
|
-
);
|
|
624
|
-
return records;
|
|
578
|
+
return [makeSourceRecord(skill, targetFile, newContent)];
|
|
625
579
|
}
|
|
626
580
|
if (skill.updateStrategy === "regenerable" || !exists) {
|
|
627
581
|
if (exists) {
|
|
@@ -631,15 +585,11 @@ async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRo
|
|
|
631
585
|
summary.created++;
|
|
632
586
|
}
|
|
633
587
|
await writeFileSafe(targetFile, newContent);
|
|
634
|
-
|
|
635
|
-
makeInstalledRecord(skill, targetFile, newContent, ide, scope)
|
|
636
|
-
);
|
|
637
|
-
return records;
|
|
588
|
+
return [makeSourceRecord(skill, targetFile, newContent)];
|
|
638
589
|
}
|
|
639
590
|
const current = await readFileOrNull(targetFile);
|
|
640
|
-
let
|
|
641
|
-
const
|
|
642
|
-
for (const regionId of regionIds) {
|
|
591
|
+
let merged = current ?? newContent;
|
|
592
|
+
for (const regionId of skill.managedRegions ?? []) {
|
|
643
593
|
const re = new RegExp(
|
|
644
594
|
`<!-- teamix-evo:managed:start id="${escapeRegExp(
|
|
645
595
|
regionId
|
|
@@ -651,7 +601,7 @@ async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRo
|
|
|
651
601
|
if (match) {
|
|
652
602
|
const region = match[1].replace(/^\n/, "").replace(/\n$/, "");
|
|
653
603
|
try {
|
|
654
|
-
|
|
604
|
+
merged = replaceManagedRegion(merged, regionId, region);
|
|
655
605
|
} catch {
|
|
656
606
|
logger.warn(
|
|
657
607
|
`Managed region "${regionId}" not found in ${targetFile}. Skipped.`
|
|
@@ -660,22 +610,57 @@ async function updateSkillForIde(skill, ide, scope, data, packageRoot, projectRo
|
|
|
660
610
|
}
|
|
661
611
|
}
|
|
662
612
|
await backupFile(targetFile, projectRoot);
|
|
663
|
-
await writeFileSafe(targetFile,
|
|
613
|
+
await writeFileSafe(targetFile, merged);
|
|
664
614
|
summary.managed++;
|
|
665
|
-
|
|
666
|
-
return records;
|
|
667
|
-
}
|
|
668
|
-
function installedKey(r) {
|
|
669
|
-
return `${r.id}|${r.ide ?? ""}|${r.scope ?? ""}`;
|
|
615
|
+
return [makeSourceRecord(skill, targetFile, merged)];
|
|
670
616
|
}
|
|
671
617
|
function escapeRegExp(str) {
|
|
672
618
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
673
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
|
+
}
|
|
674
659
|
async function removeSkillFiles(records) {
|
|
675
660
|
const removed = [];
|
|
676
661
|
for (const r of records) {
|
|
677
662
|
try {
|
|
678
|
-
await
|
|
663
|
+
await fs5.unlink(r.target);
|
|
679
664
|
removed.push(r.target);
|
|
680
665
|
} catch (err) {
|
|
681
666
|
if (err.code !== "ENOENT") {
|
|
@@ -683,11 +668,11 @@ async function removeSkillFiles(records) {
|
|
|
683
668
|
}
|
|
684
669
|
}
|
|
685
670
|
}
|
|
686
|
-
const parents = new Set(records.map((r) =>
|
|
671
|
+
const parents = new Set(records.map((r) => path8.dirname(r.target)));
|
|
687
672
|
for (const dir of parents) {
|
|
688
673
|
try {
|
|
689
|
-
const entries = await
|
|
690
|
-
if (entries.length === 0) await
|
|
674
|
+
const entries = await fs5.readdir(dir);
|
|
675
|
+
if (entries.length === 0) await fs5.rmdir(dir);
|
|
691
676
|
} catch {
|
|
692
677
|
}
|
|
693
678
|
}
|
|
@@ -717,6 +702,7 @@ async function runSkillsAdd(options) {
|
|
|
717
702
|
throw new Error("Scope must be specified (project | global).");
|
|
718
703
|
}
|
|
719
704
|
const { manifest, data, packageRoot } = await loadSkillsData(packageName);
|
|
705
|
+
const currentDesignVariant = await readDesignVariant(projectRoot);
|
|
720
706
|
if (isIncremental) {
|
|
721
707
|
const known = new Set(manifest.skills.map((s) => s.id));
|
|
722
708
|
const unknown = requestedNames.filter((n) => !known.has(n));
|
|
@@ -731,9 +717,13 @@ async function runSkillsAdd(options) {
|
|
|
731
717
|
const existingPkg = existingInstalled?.installed.find(
|
|
732
718
|
(p) => p.package === packageName
|
|
733
719
|
);
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
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
|
+
]);
|
|
737
727
|
let onlyIds;
|
|
738
728
|
let skippedSkillIds;
|
|
739
729
|
if (isIncremental) {
|
|
@@ -741,7 +731,22 @@ async function runSkillsAdd(options) {
|
|
|
741
731
|
onlyIds = requestedNames.filter((n) => !existingSkillIds.has(n));
|
|
742
732
|
} else {
|
|
743
733
|
skippedSkillIds = [];
|
|
744
|
-
onlyIds = manifest.skills.
|
|
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);
|
|
745
750
|
}
|
|
746
751
|
if (isIncremental && onlyIds.length === 0) {
|
|
747
752
|
return {
|
|
@@ -779,6 +784,7 @@ async function runSkillsAdd(options) {
|
|
|
779
784
|
scope
|
|
780
785
|
};
|
|
781
786
|
await writeProjectConfig(projectRoot, config);
|
|
787
|
+
const installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
782
788
|
const installedManifest = existingInstalled ?? {
|
|
783
789
|
schemaVersion: 1,
|
|
784
790
|
installed: []
|
|
@@ -794,12 +800,29 @@ async function runSkillsAdd(options) {
|
|
|
794
800
|
package: packageName,
|
|
795
801
|
variant: FLAT_VARIANT,
|
|
796
802
|
version: manifest.version,
|
|
797
|
-
installedAt
|
|
803
|
+
installedAt,
|
|
798
804
|
resources: mergedResources
|
|
799
805
|
};
|
|
800
806
|
if (idx >= 0) installedManifest.installed[idx] = entry;
|
|
801
807
|
else installedManifest.installed.push(entry);
|
|
802
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);
|
|
803
826
|
return {
|
|
804
827
|
status: "installed",
|
|
805
828
|
packageName,
|
|
@@ -821,12 +844,208 @@ function mergeInstalledResources(existing, next) {
|
|
|
821
844
|
return [...map.values()];
|
|
822
845
|
}
|
|
823
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
|
+
|
|
824
1041
|
// src/core/ui-init.ts
|
|
825
1042
|
var DEFAULT_UI_ALIASES = {
|
|
826
1043
|
components: "src/components/ui",
|
|
827
1044
|
hooks: "src/hooks",
|
|
828
1045
|
utils: "src/lib/utils",
|
|
829
|
-
lib: "src/lib"
|
|
1046
|
+
lib: "src/lib",
|
|
1047
|
+
business: "src/components/business",
|
|
1048
|
+
templates: "src/templates"
|
|
830
1049
|
};
|
|
831
1050
|
var DEFAULT_UI_ICON_LIBRARY = "lucide";
|
|
832
1051
|
async function runUiInit(options) {
|
|
@@ -841,7 +1060,9 @@ async function runUiInit(options) {
|
|
|
841
1060
|
components: options.aliases?.components ?? DEFAULT_UI_ALIASES.components,
|
|
842
1061
|
hooks: options.aliases?.hooks ?? DEFAULT_UI_ALIASES.hooks,
|
|
843
1062
|
utils: options.aliases?.utils ?? DEFAULT_UI_ALIASES.utils,
|
|
844
|
-
lib: options.aliases?.lib ?? DEFAULT_UI_ALIASES.lib
|
|
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
|
|
845
1066
|
};
|
|
846
1067
|
const iconLibrary = options.iconLibrary ?? DEFAULT_UI_ICON_LIBRARY;
|
|
847
1068
|
const tsx = options.tsx ?? true;
|
|
@@ -872,22 +1093,22 @@ async function runUiInit(options) {
|
|
|
872
1093
|
|
|
873
1094
|
// src/core/ui-client.ts
|
|
874
1095
|
import * as path10 from "path";
|
|
875
|
-
import * as
|
|
1096
|
+
import * as fs7 from "fs/promises";
|
|
876
1097
|
import { createRequire as createRequire3 } from "module";
|
|
877
1098
|
import { loadUiPackageManifest } from "@teamix-evo/registry";
|
|
878
1099
|
var require4 = createRequire3(import.meta.url);
|
|
879
|
-
function
|
|
1100
|
+
function resolvePackageRoot2(packageName) {
|
|
880
1101
|
const pkgJsonPath = require4.resolve(`${packageName}/package.json`);
|
|
881
1102
|
return path10.dirname(pkgJsonPath);
|
|
882
1103
|
}
|
|
883
1104
|
async function loadUiData(packageName) {
|
|
884
|
-
const packageRoot =
|
|
1105
|
+
const packageRoot = resolvePackageRoot2(packageName);
|
|
885
1106
|
logger.debug(`Resolved ui package root: ${packageRoot}`);
|
|
886
1107
|
const manifest = await loadUiPackageManifest(packageRoot);
|
|
887
1108
|
let data = {};
|
|
888
1109
|
const dataPath = path10.join(packageRoot, "_data.json");
|
|
889
1110
|
try {
|
|
890
|
-
const raw = await
|
|
1111
|
+
const raw = await fs7.readFile(dataPath, "utf-8");
|
|
891
1112
|
data = JSON.parse(raw);
|
|
892
1113
|
} catch (err) {
|
|
893
1114
|
if (err.code !== "ENOENT") {
|
|
@@ -900,7 +1121,7 @@ async function loadUiData(packageName) {
|
|
|
900
1121
|
|
|
901
1122
|
// src/core/ui-installer.ts
|
|
902
1123
|
import * as path11 from "path";
|
|
903
|
-
import * as
|
|
1124
|
+
import * as fs8 from "fs/promises";
|
|
904
1125
|
import { resolveUiEntryOrder } from "@teamix-evo/registry";
|
|
905
1126
|
|
|
906
1127
|
// src/utils/transform-imports.ts
|
|
@@ -918,10 +1139,19 @@ function rewriteImports(source, aliases) {
|
|
|
918
1139
|
if (!aliasKey) return full;
|
|
919
1140
|
const alias = aliases[aliasKey];
|
|
920
1141
|
const normalized = aliasToImportPath(alias);
|
|
921
|
-
|
|
1142
|
+
const flatRest = flattenRestPath(rest);
|
|
1143
|
+
return `${quote}${normalized}${flatRest}${quote}`;
|
|
922
1144
|
}
|
|
923
1145
|
);
|
|
924
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
|
+
}
|
|
925
1155
|
function aliasToImportPath(alias) {
|
|
926
1156
|
const trimmed = alias.replace(/^\.\//, "").replace(/\/$/, "");
|
|
927
1157
|
if (trimmed.startsWith("src/")) {
|
|
@@ -965,7 +1195,7 @@ async function installUiEntries(options) {
|
|
|
965
1195
|
continue;
|
|
966
1196
|
}
|
|
967
1197
|
const sourceAbs = path11.resolve(packageRoot, file.source);
|
|
968
|
-
const raw = await
|
|
1198
|
+
const raw = await fs8.readFile(sourceAbs, "utf-8");
|
|
969
1199
|
const transformed = rewriteImports(raw, aliases);
|
|
970
1200
|
await writeFileSafe(targetAbs, transformed);
|
|
971
1201
|
written++;
|
|
@@ -979,7 +1209,7 @@ async function installUiEntries(options) {
|
|
|
979
1209
|
}
|
|
980
1210
|
if (entry.meta) {
|
|
981
1211
|
const metaSourceAbs = path11.resolve(packageRoot, entry.meta);
|
|
982
|
-
const metaContent = await
|
|
1212
|
+
const metaContent = await fs8.readFile(metaSourceAbs, "utf-8");
|
|
983
1213
|
const metaTargetAbs = path11.join(
|
|
984
1214
|
projectRoot,
|
|
985
1215
|
DESIGN_COMPONENTS_DIR,
|
|
@@ -1021,7 +1251,7 @@ async function removeUiFiles(records) {
|
|
|
1021
1251
|
const removed = [];
|
|
1022
1252
|
for (const r of records) {
|
|
1023
1253
|
try {
|
|
1024
|
-
await
|
|
1254
|
+
await fs8.unlink(r.target);
|
|
1025
1255
|
removed.push(r.target);
|
|
1026
1256
|
} catch (err) {
|
|
1027
1257
|
if (err.code !== "ENOENT") {
|
|
@@ -1032,8 +1262,8 @@ async function removeUiFiles(records) {
|
|
|
1032
1262
|
const parents = new Set(records.map((r) => path11.dirname(r.target)));
|
|
1033
1263
|
for (const dir of parents) {
|
|
1034
1264
|
try {
|
|
1035
|
-
const entries = await
|
|
1036
|
-
if (entries.length === 0) await
|
|
1265
|
+
const entries = await fs8.readdir(dir);
|
|
1266
|
+
if (entries.length === 0) await fs8.rmdir(dir);
|
|
1037
1267
|
} catch {
|
|
1038
1268
|
}
|
|
1039
1269
|
}
|
|
@@ -1139,6 +1369,130 @@ async function runUiList(options) {
|
|
|
1139
1369
|
entries
|
|
1140
1370
|
};
|
|
1141
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
|
+
}
|
|
1142
1496
|
export {
|
|
1143
1497
|
DEFAULT_UI_ALIASES,
|
|
1144
1498
|
DEFAULT_UI_ICON_LIBRARY,
|
|
@@ -1159,6 +1513,7 @@ export {
|
|
|
1159
1513
|
runUiAdd,
|
|
1160
1514
|
runUiInit,
|
|
1161
1515
|
runUiList,
|
|
1516
|
+
syncSkillsToIdes,
|
|
1162
1517
|
updateSkills,
|
|
1163
1518
|
writeInstalledManifest,
|
|
1164
1519
|
writeProjectConfig
|