teamix-evo 0.6.1 → 0.8.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/README.md +28 -24
- package/dist/core/index.d.ts +363 -1
- package/dist/core/index.js +2227 -145
- package/dist/core/index.js.map +1 -1
- package/dist/index.js +3565 -468
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/core/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// src/core/tokens-init.ts
|
|
2
2
|
import * as path9 from "path";
|
|
3
3
|
import * as fs6 from "fs/promises";
|
|
4
|
-
import { createRequire as createRequire2 } from "module";
|
|
5
4
|
import {
|
|
6
5
|
loadTokensPackageManifest,
|
|
7
6
|
getVariantEntry
|
|
@@ -97,6 +96,19 @@ import {
|
|
|
97
96
|
validateSkillsLock,
|
|
98
97
|
TokensPackLockSchema
|
|
99
98
|
} from "@teamix-evo/registry";
|
|
99
|
+
|
|
100
|
+
// src/utils/error.ts
|
|
101
|
+
function getErrorMessage(err) {
|
|
102
|
+
if (err instanceof Error) return err.message;
|
|
103
|
+
if (typeof err === "string") return err;
|
|
104
|
+
try {
|
|
105
|
+
return JSON.stringify(err);
|
|
106
|
+
} catch {
|
|
107
|
+
return String(err);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/core/state.ts
|
|
100
112
|
var TEAMIX_DIR = ".teamix-evo";
|
|
101
113
|
var CONFIG_FILE = "config.json";
|
|
102
114
|
var MANIFEST_FILE = "manifest.json";
|
|
@@ -120,7 +132,7 @@ async function readProjectConfig(projectRoot) {
|
|
|
120
132
|
data = JSON.parse(raw);
|
|
121
133
|
} catch (err) {
|
|
122
134
|
throw new Error(
|
|
123
|
-
`Corrupted config.json (${err
|
|
135
|
+
`Corrupted config.json (${getErrorMessage(err)}). Fix the JSON manually or remove the file to start fresh; refusing to clobber prior config.`
|
|
124
136
|
);
|
|
125
137
|
}
|
|
126
138
|
const result = validateConfig(data);
|
|
@@ -145,7 +157,7 @@ async function readInstalledManifest(projectRoot) {
|
|
|
145
157
|
data = JSON.parse(raw);
|
|
146
158
|
} catch (err) {
|
|
147
159
|
throw new Error(
|
|
148
|
-
`Corrupted manifest.json (${err
|
|
160
|
+
`Corrupted manifest.json (${getErrorMessage(err)}). Fix the JSON manually or remove the file to start fresh; refusing to clobber prior install records.`
|
|
149
161
|
);
|
|
150
162
|
}
|
|
151
163
|
const result = validateInstalled(data);
|
|
@@ -173,9 +185,7 @@ async function readTokensLock(projectRoot) {
|
|
|
173
185
|
}
|
|
174
186
|
return parsed.data;
|
|
175
187
|
} catch (err) {
|
|
176
|
-
logger.warn(
|
|
177
|
-
`Failed to parse tokens-lock.json: ${err.message}`
|
|
178
|
-
);
|
|
188
|
+
logger.warn(`Failed to parse tokens-lock.json: ${getErrorMessage(err)}`);
|
|
179
189
|
return null;
|
|
180
190
|
}
|
|
181
191
|
}
|
|
@@ -206,7 +216,7 @@ async function readSkillsLock(projectRoot) {
|
|
|
206
216
|
return result.data;
|
|
207
217
|
} catch (err) {
|
|
208
218
|
logger.warn(
|
|
209
|
-
`Failed to parse skills manifest.lock.json: ${err
|
|
219
|
+
`Failed to parse skills manifest.lock.json: ${getErrorMessage(err)}`
|
|
210
220
|
);
|
|
211
221
|
return null;
|
|
212
222
|
}
|
|
@@ -221,6 +231,12 @@ async function writeSkillsLock(projectRoot, lock) {
|
|
|
221
231
|
await writeFileSafe(lockPath, JSON.stringify(lock, null, 2) + "\n");
|
|
222
232
|
logger.debug(`Wrote skills lock \u2192 ${lockPath}`);
|
|
223
233
|
}
|
|
234
|
+
function findInstalledPackage(installed, packageName) {
|
|
235
|
+
if (!installed) return null;
|
|
236
|
+
const matches = installed.installed.filter((p) => p.package === packageName);
|
|
237
|
+
if (matches.length === 0) return null;
|
|
238
|
+
return matches[matches.length - 1] ?? null;
|
|
239
|
+
}
|
|
224
240
|
|
|
225
241
|
// src/core/skills-client.ts
|
|
226
242
|
import * as path3 from "path";
|
|
@@ -255,7 +271,9 @@ import * as path7 from "path";
|
|
|
255
271
|
import * as fs5 from "fs/promises";
|
|
256
272
|
import {
|
|
257
273
|
replaceManagedRegion,
|
|
258
|
-
hasManagedRegion
|
|
274
|
+
hasManagedRegion,
|
|
275
|
+
extractFrontmatter,
|
|
276
|
+
replaceFrontmatter
|
|
259
277
|
} from "@teamix-evo/registry";
|
|
260
278
|
|
|
261
279
|
// src/ide/QoderAdapter.ts
|
|
@@ -339,6 +357,8 @@ async function loadTemplateFile(filePath) {
|
|
|
339
357
|
// src/utils/path.ts
|
|
340
358
|
import * as path6 from "path";
|
|
341
359
|
import * as fs4 from "fs/promises";
|
|
360
|
+
import { createRequire as createRequire2 } from "module";
|
|
361
|
+
var require3 = createRequire2(import.meta.url);
|
|
342
362
|
function resolveSourcePath(source, variantDir, packageRoot) {
|
|
343
363
|
if (source.startsWith("_template/")) {
|
|
344
364
|
return path6.join(packageRoot, source);
|
|
@@ -358,6 +378,10 @@ async function walkDir(dir) {
|
|
|
358
378
|
}
|
|
359
379
|
return files;
|
|
360
380
|
}
|
|
381
|
+
function resolveTokensPackageRoot(packageName) {
|
|
382
|
+
const pkgJson = require3.resolve(`${packageName}/package.json`);
|
|
383
|
+
return path6.dirname(pkgJson);
|
|
384
|
+
}
|
|
361
385
|
|
|
362
386
|
// src/core/skills-installer.ts
|
|
363
387
|
async function installSkills(options) {
|
|
@@ -394,9 +418,9 @@ async function writeSkillSource(skill, options) {
|
|
|
394
418
|
const { data, packageRoot, projectRoot } = options;
|
|
395
419
|
const sourceAbs = path7.resolve(packageRoot, skill.source);
|
|
396
420
|
const targetDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
397
|
-
const
|
|
421
|
+
const stat5 = await fs5.stat(sourceAbs);
|
|
398
422
|
const records = [];
|
|
399
|
-
if (
|
|
423
|
+
if (stat5.isFile()) {
|
|
400
424
|
const targetFile = path7.join(targetDir, "SKILL.md");
|
|
401
425
|
const content = await renderSkillContent(sourceAbs, skill, data);
|
|
402
426
|
await writeFileSafe(targetFile, content);
|
|
@@ -455,7 +479,7 @@ async function writeMirrorContent(targetFile, sourceContent, managedRegions, sou
|
|
|
455
479
|
if (matchedRegions.length === 0) {
|
|
456
480
|
if (existing !== sourceContent) {
|
|
457
481
|
logger.warn(
|
|
458
|
-
`Mirror drift detected at ${targetFile} \u2014 overwriting from source. Edit ${sourceFile} (not the mirror) and re-run \`teamix-evo skills sync\`.`
|
|
482
|
+
`Mirror drift detected at ${targetFile} \u2014 overwriting from source. Edit ${sourceFile} (not the mirror) and re-run \`npx teamix-evo@latest skills sync\`.`
|
|
459
483
|
);
|
|
460
484
|
await writeFileSafe(targetFile, sourceContent);
|
|
461
485
|
return sourceContent;
|
|
@@ -463,6 +487,10 @@ async function writeMirrorContent(targetFile, sourceContent, managedRegions, sou
|
|
|
463
487
|
return existing;
|
|
464
488
|
}
|
|
465
489
|
let merged = existing;
|
|
490
|
+
const upstreamFm = extractFrontmatter(sourceContent);
|
|
491
|
+
if (upstreamFm) {
|
|
492
|
+
merged = replaceFrontmatter(merged, upstreamFm);
|
|
493
|
+
}
|
|
466
494
|
for (const id of matchedRegions) {
|
|
467
495
|
const newRegion = extractRegionBody(sourceContent, id);
|
|
468
496
|
if (newRegion === null) continue;
|
|
@@ -524,11 +552,7 @@ async function updateSkills(options) {
|
|
|
524
552
|
if (idFilter && !idFilter.has(skill.id)) continue;
|
|
525
553
|
const skillIdes = skill.ides.filter((i) => ides.includes(i));
|
|
526
554
|
if (skillIdes.length === 0) continue;
|
|
527
|
-
const sourceRecords = await rewriteSkillSource(
|
|
528
|
-
skill,
|
|
529
|
-
options,
|
|
530
|
-
summary
|
|
531
|
-
);
|
|
555
|
+
const sourceRecords = await rewriteSkillSource(skill, options, summary);
|
|
532
556
|
updated.push(...sourceRecords);
|
|
533
557
|
for (const ide of skillIdes) {
|
|
534
558
|
const mirrorRecords = await mirrorSkillToIde(
|
|
@@ -546,8 +570,8 @@ async function rewriteSkillSource(skill, options, summary) {
|
|
|
546
570
|
const { data, packageRoot, projectRoot } = options;
|
|
547
571
|
const sourceAbs = path7.resolve(packageRoot, skill.source);
|
|
548
572
|
const targetDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
549
|
-
const
|
|
550
|
-
if (!
|
|
573
|
+
const stat5 = await fs5.stat(sourceAbs);
|
|
574
|
+
if (!stat5.isFile()) {
|
|
551
575
|
await ensureDir(targetDir);
|
|
552
576
|
const entries = await walkDir(sourceAbs);
|
|
553
577
|
const records = [];
|
|
@@ -557,34 +581,56 @@ async function rewriteSkillSource(skill, options, summary) {
|
|
|
557
581
|
if (skill.template && targetFile2.endsWith(".hbs")) {
|
|
558
582
|
targetFile2 = targetFile2.slice(0, -4);
|
|
559
583
|
}
|
|
560
|
-
const
|
|
584
|
+
const newContent2 = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs5.readFile(entry, "utf-8");
|
|
561
585
|
const exists2 = await fileExists(targetFile2);
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
586
|
+
const written2 = await rewriteSingleFile({
|
|
587
|
+
targetFile: targetFile2,
|
|
588
|
+
newContent: newContent2,
|
|
589
|
+
exists: exists2,
|
|
590
|
+
updateStrategy: skill.updateStrategy,
|
|
591
|
+
managedRegions: skill.managedRegions,
|
|
592
|
+
projectRoot,
|
|
593
|
+
summary
|
|
594
|
+
});
|
|
569
595
|
const relWritten = path7.relative(targetDir, targetFile2);
|
|
570
|
-
records.push(makeSourceRecord(skill, targetFile2,
|
|
596
|
+
records.push(makeSourceRecord(skill, targetFile2, written2, relWritten));
|
|
571
597
|
}
|
|
572
598
|
return records;
|
|
573
599
|
}
|
|
574
600
|
const targetFile = path7.join(targetDir, "SKILL.md");
|
|
575
601
|
const newContent = await renderSkillContent(sourceAbs, skill, data);
|
|
576
602
|
const exists = await fileExists(targetFile);
|
|
577
|
-
|
|
603
|
+
const written = await rewriteSingleFile({
|
|
604
|
+
targetFile,
|
|
605
|
+
newContent,
|
|
606
|
+
exists,
|
|
607
|
+
updateStrategy: skill.updateStrategy,
|
|
608
|
+
managedRegions: skill.managedRegions,
|
|
609
|
+
projectRoot,
|
|
610
|
+
summary
|
|
611
|
+
});
|
|
612
|
+
return [makeSourceRecord(skill, targetFile, written)];
|
|
613
|
+
}
|
|
614
|
+
async function rewriteSingleFile(args) {
|
|
615
|
+
const {
|
|
616
|
+
targetFile,
|
|
617
|
+
newContent,
|
|
618
|
+
exists,
|
|
619
|
+
updateStrategy,
|
|
620
|
+
managedRegions,
|
|
621
|
+
projectRoot,
|
|
622
|
+
summary
|
|
623
|
+
} = args;
|
|
624
|
+
if (updateStrategy === "frozen") {
|
|
578
625
|
if (exists) {
|
|
579
626
|
summary.skipped++;
|
|
580
|
-
|
|
581
|
-
return [makeSourceRecord(skill, targetFile, current2)];
|
|
627
|
+
return await readFileOrNull(targetFile) ?? newContent;
|
|
582
628
|
}
|
|
583
629
|
await writeFileSafe(targetFile, newContent);
|
|
584
630
|
summary.created++;
|
|
585
|
-
return
|
|
631
|
+
return newContent;
|
|
586
632
|
}
|
|
587
|
-
if (
|
|
633
|
+
if (updateStrategy === "regenerable" || !exists) {
|
|
588
634
|
if (exists) {
|
|
589
635
|
await backupFile(targetFile, projectRoot);
|
|
590
636
|
summary.overwritten++;
|
|
@@ -592,11 +638,15 @@ async function rewriteSkillSource(skill, options, summary) {
|
|
|
592
638
|
summary.created++;
|
|
593
639
|
}
|
|
594
640
|
await writeFileSafe(targetFile, newContent);
|
|
595
|
-
return
|
|
641
|
+
return newContent;
|
|
596
642
|
}
|
|
597
643
|
const current = await readFileOrNull(targetFile);
|
|
598
644
|
let merged = current ?? newContent;
|
|
599
|
-
|
|
645
|
+
const upstreamFm = extractFrontmatter(newContent);
|
|
646
|
+
if (upstreamFm) {
|
|
647
|
+
merged = replaceFrontmatter(merged, upstreamFm);
|
|
648
|
+
}
|
|
649
|
+
for (const regionId of managedRegions ?? []) {
|
|
600
650
|
const re = new RegExp(
|
|
601
651
|
`<!-- teamix-evo:managed:start id="${escapeRegExp(
|
|
602
652
|
regionId
|
|
@@ -616,10 +666,12 @@ async function rewriteSkillSource(skill, options, summary) {
|
|
|
616
666
|
}
|
|
617
667
|
}
|
|
618
668
|
}
|
|
619
|
-
|
|
620
|
-
|
|
669
|
+
if (merged !== current) {
|
|
670
|
+
await backupFile(targetFile, projectRoot);
|
|
671
|
+
await writeFileSafe(targetFile, merged);
|
|
672
|
+
}
|
|
621
673
|
summary.managed++;
|
|
622
|
-
return
|
|
674
|
+
return merged;
|
|
623
675
|
}
|
|
624
676
|
function escapeRegExp(str) {
|
|
625
677
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -676,7 +728,7 @@ async function removeSkillFiles(records) {
|
|
|
676
728
|
removed.push(r.target);
|
|
677
729
|
} catch (err) {
|
|
678
730
|
if (err.code !== "ENOENT") {
|
|
679
|
-
logger.warn(`Failed to remove ${r.target}: ${err
|
|
731
|
+
logger.warn(`Failed to remove ${r.target}: ${getErrorMessage(err)}`);
|
|
680
732
|
}
|
|
681
733
|
}
|
|
682
734
|
}
|
|
@@ -711,11 +763,14 @@ async function ensureMcpJson(projectRoot) {
|
|
|
711
763
|
const mcpPath = path8.join(projectRoot, ".mcp.json");
|
|
712
764
|
if (await fileExists(mcpPath)) return "exists";
|
|
713
765
|
try {
|
|
714
|
-
await writeFileSafe(
|
|
766
|
+
await writeFileSafe(
|
|
767
|
+
mcpPath,
|
|
768
|
+
JSON.stringify(MCP_JSON_CONTENT, null, 2) + "\n"
|
|
769
|
+
);
|
|
715
770
|
logger.debug(`Wrote .mcp.json \u2192 ${mcpPath}`);
|
|
716
771
|
return "created";
|
|
717
772
|
} catch (err) {
|
|
718
|
-
logger.warn(`Failed to write .mcp.json: ${err
|
|
773
|
+
logger.warn(`Failed to write .mcp.json: ${getErrorMessage(err)}`);
|
|
719
774
|
return "failed";
|
|
720
775
|
}
|
|
721
776
|
}
|
|
@@ -888,8 +943,8 @@ async function finalizeSkillsInstall(args) {
|
|
|
888
943
|
onlyIds
|
|
889
944
|
});
|
|
890
945
|
const config = existingConfig ?? {
|
|
891
|
-
$schema: "https://teamix-evo.dev/schema/config/
|
|
892
|
-
schemaVersion:
|
|
946
|
+
$schema: "https://teamix-evo.dev/schema/config/v2.json",
|
|
947
|
+
schemaVersion: 2,
|
|
893
948
|
ide: ideIdent,
|
|
894
949
|
packages: {}
|
|
895
950
|
};
|
|
@@ -972,7 +1027,6 @@ var CONSUMER_OVERRIDES_FILE = "tokens.overrides.css";
|
|
|
972
1027
|
var EMPTY_OVERRIDES_TEMPLATE = `/* User-owned token overrides \u2014 frozen on subsequent installs. */
|
|
973
1028
|
/* See @teamix-evo/tokens variant theme.css for available CSS custom properties. */
|
|
974
1029
|
`;
|
|
975
|
-
var require3 = createRequire2(import.meta.url);
|
|
976
1030
|
async function runTokensInit(options) {
|
|
977
1031
|
const { projectRoot, variant, ide } = options;
|
|
978
1032
|
const packageName = options.packageName ?? DEFAULT_TOKENS_PACKAGE;
|
|
@@ -984,7 +1038,7 @@ async function runTokensInit(options) {
|
|
|
984
1038
|
const known = catalog.variants.map((v) => v.name).join(", ");
|
|
985
1039
|
throw new Error(
|
|
986
1040
|
`Unknown variant "${variant}". Available variants: ${known || "(none)"}.
|
|
987
|
-
Run \`teamix-evo tokens list-variants\` to see all options.`
|
|
1041
|
+
Run \`npx teamix-evo@latest tokens list-variants\` to see all options.`
|
|
988
1042
|
);
|
|
989
1043
|
}
|
|
990
1044
|
const existingConfig = await readProjectConfig(projectRoot);
|
|
@@ -1036,8 +1090,8 @@ Run \`teamix-evo tokens list-variants\` to see all options.`
|
|
|
1036
1090
|
JSON.stringify(lock, null, 2) + "\n"
|
|
1037
1091
|
);
|
|
1038
1092
|
const config = {
|
|
1039
|
-
$schema: "https://teamix-evo.dev/schema/config/
|
|
1040
|
-
schemaVersion:
|
|
1093
|
+
$schema: "https://teamix-evo.dev/schema/config/v2.json",
|
|
1094
|
+
schemaVersion: 2,
|
|
1041
1095
|
ide: existingConfig?.ide ?? ide,
|
|
1042
1096
|
packages: {
|
|
1043
1097
|
...existingConfig?.packages ?? {},
|
|
@@ -1090,7 +1144,9 @@ async function tryAutoInstallVariantSkills(args) {
|
|
|
1090
1144
|
manifestSkillIds = new Set(manifest.skills.map((s) => s.id));
|
|
1091
1145
|
} catch (err) {
|
|
1092
1146
|
logger.warn(
|
|
1093
|
-
`Skipping skills auto-install: could not load skills manifest (${
|
|
1147
|
+
`Skipping skills auto-install: could not load skills manifest (${getErrorMessage(
|
|
1148
|
+
err
|
|
1149
|
+
)}).`
|
|
1094
1150
|
);
|
|
1095
1151
|
return {
|
|
1096
1152
|
attempted: [],
|
|
@@ -1140,7 +1196,7 @@ async function tryAutoInstallVariantSkills(args) {
|
|
|
1140
1196
|
};
|
|
1141
1197
|
} catch (err) {
|
|
1142
1198
|
logger.warn(
|
|
1143
|
-
`Skills auto-install failed (continuing): ${err
|
|
1199
|
+
`Skills auto-install failed (continuing): ${getErrorMessage(err)}`
|
|
1144
1200
|
);
|
|
1145
1201
|
return {
|
|
1146
1202
|
attempted: desired,
|
|
@@ -1154,10 +1210,7 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
|
|
|
1154
1210
|
const sourceAbs = path9.join(packageRoot, fileRelToPackage);
|
|
1155
1211
|
const base = path9.basename(fileRelToPackage);
|
|
1156
1212
|
if (base === "theme.css") {
|
|
1157
|
-
const targetRel = path9.posix.join(
|
|
1158
|
-
CONSUMER_TOKENS_DIR,
|
|
1159
|
-
CONSUMER_THEME_FILE
|
|
1160
|
-
);
|
|
1213
|
+
const targetRel = path9.posix.join(CONSUMER_TOKENS_DIR, CONSUMER_THEME_FILE);
|
|
1161
1214
|
const targetAbs = path9.join(projectRoot, targetRel);
|
|
1162
1215
|
const content = await fs6.readFile(sourceAbs, "utf-8");
|
|
1163
1216
|
await writeFileSafe(targetAbs, content);
|
|
@@ -1194,10 +1247,6 @@ async function installVariantFile(fileRelToPackage, packageRoot, projectRoot) {
|
|
|
1194
1247
|
}
|
|
1195
1248
|
return null;
|
|
1196
1249
|
}
|
|
1197
|
-
function resolveTokensPackageRoot(packageName) {
|
|
1198
|
-
const pkgJson = require3.resolve(`${packageName}/package.json`);
|
|
1199
|
-
return path9.dirname(pkgJson);
|
|
1200
|
-
}
|
|
1201
1250
|
async function listTokenVariants(packageName = DEFAULT_TOKENS_PACKAGE, packageRoot) {
|
|
1202
1251
|
const root = packageRoot ?? resolveTokensPackageRoot(packageName);
|
|
1203
1252
|
const catalog = await loadTokensPackageManifest(root);
|
|
@@ -1414,8 +1463,8 @@ async function runUiInit(options) {
|
|
|
1414
1463
|
const tsx = options.tsx ?? true;
|
|
1415
1464
|
const rsc = options.rsc ?? false;
|
|
1416
1465
|
const config = existingConfig ?? {
|
|
1417
|
-
$schema: "https://teamix-evo.dev/schema/config/
|
|
1418
|
-
schemaVersion:
|
|
1466
|
+
$schema: "https://teamix-evo.dev/schema/config/v2.json",
|
|
1467
|
+
schemaVersion: 2,
|
|
1419
1468
|
ide: ideIdent,
|
|
1420
1469
|
packages: {}
|
|
1421
1470
|
};
|
|
@@ -1583,7 +1632,7 @@ async function removeUiFiles(records) {
|
|
|
1583
1632
|
removed.push(r.target);
|
|
1584
1633
|
} catch (err) {
|
|
1585
1634
|
if (err.code !== "ENOENT") {
|
|
1586
|
-
logger.warn(`Failed to remove ${r.target}: ${err
|
|
1635
|
+
logger.warn(`Failed to remove ${r.target}: ${getErrorMessage(err)}`);
|
|
1587
1636
|
}
|
|
1588
1637
|
}
|
|
1589
1638
|
}
|
|
@@ -1985,120 +2034,2147 @@ async function patchPackageJsonScripts(projectRoot) {
|
|
|
1985
2034
|
}
|
|
1986
2035
|
}
|
|
1987
2036
|
|
|
1988
|
-
// src/core/
|
|
1989
|
-
import * as path14 from "path";
|
|
2037
|
+
// src/core/agents-md.ts
|
|
1990
2038
|
import * as fs10 from "fs/promises";
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
const
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2039
|
+
import * as path14 from "path";
|
|
2040
|
+
async function runGenerateAgentsMd(options) {
|
|
2041
|
+
const { projectRoot, variant, skillIds } = options;
|
|
2042
|
+
const ordered = [...skillIds].sort(
|
|
2043
|
+
(a, b) => bucketRank(a) - bucketRank(b) || a.localeCompare(b)
|
|
2044
|
+
);
|
|
2045
|
+
const sections = [];
|
|
2046
|
+
const missingSkillIds = [];
|
|
2047
|
+
for (const id of ordered) {
|
|
2048
|
+
const { section, missing } = await renderSkillSection(projectRoot, id);
|
|
2049
|
+
sections.push(section);
|
|
2050
|
+
if (missing) missingSkillIds.push(id);
|
|
2051
|
+
}
|
|
2052
|
+
const body = renderAgentsMd({ variant, sections });
|
|
2053
|
+
const target = path14.join(projectRoot, "AGENTS.md");
|
|
2054
|
+
await fs10.writeFile(target, body, "utf8");
|
|
2055
|
+
return {
|
|
2056
|
+
path: target,
|
|
2057
|
+
skillCount: ordered.length,
|
|
2058
|
+
missingSkillIds
|
|
2059
|
+
};
|
|
2060
|
+
}
|
|
2061
|
+
function bucketRank(id) {
|
|
2062
|
+
if (id.startsWith("teamix-evo-design-")) return 0;
|
|
2063
|
+
if (id.startsWith("teamix-evo-code-")) return 1;
|
|
2064
|
+
return 2;
|
|
2065
|
+
}
|
|
2066
|
+
async function renderSkillSection(projectRoot, skillId) {
|
|
2067
|
+
const skillPath = path14.join(
|
|
2068
|
+
projectRoot,
|
|
2069
|
+
".teamix-evo",
|
|
2070
|
+
"skills",
|
|
2071
|
+
skillId,
|
|
2072
|
+
"SKILL.md"
|
|
2073
|
+
);
|
|
2074
|
+
const lines = [];
|
|
2075
|
+
lines.push(`### ${skillId}`);
|
|
2076
|
+
let parts = null;
|
|
2077
|
+
let missing = false;
|
|
2078
|
+
try {
|
|
2079
|
+
const raw = await fs10.readFile(skillPath, "utf8");
|
|
2080
|
+
parts = extractDescriptionParts(raw);
|
|
2081
|
+
} catch {
|
|
2082
|
+
missing = true;
|
|
2083
|
+
}
|
|
2084
|
+
if (parts?.capability) {
|
|
2085
|
+
lines.push(`- ${parts.capability}`);
|
|
2086
|
+
}
|
|
2087
|
+
lines.push(
|
|
2088
|
+
`- **TRIGGER**: ${parts?.trigger ?? "\u672A\u914D\u7F6E\u89E6\u53D1\u6761\u4EF6\uFF0C\u9700\u624B\u52A8\u6FC0\u6D3B\u8BE5 skill\u3002"}`
|
|
2089
|
+
);
|
|
2090
|
+
lines.push(
|
|
2091
|
+
`- **SKIP**: ${parts?.skip ?? "\u672A\u914D\u7F6E\u8DF3\u8FC7\u6761\u4EF6\uFF0C\u6309 TRIGGER \u515C\u5E95\u5224\u5B9A\u3002"}`
|
|
2092
|
+
);
|
|
2093
|
+
if (parts?.coordinates) {
|
|
2094
|
+
lines.push(`- **Coordinates with**: ${parts.coordinates}`);
|
|
2095
|
+
}
|
|
2096
|
+
lines.push(`- **\u4F4D\u7F6E**: \`.teamix-evo/skills/${skillId}/SKILL.md\``);
|
|
2097
|
+
return { section: lines.join("\n"), missing };
|
|
2098
|
+
}
|
|
2099
|
+
function renderAgentsMd(args) {
|
|
2100
|
+
const { variant, sections } = args;
|
|
2101
|
+
const skillBlock = sections.length > 0 ? sections.join("\n\n") : "_\uFF08\u672C\u5DE5\u7A0B\u672A\u88C5\u914D\u5DE5\u7A0B\u7EA7 skill\u3002\uFF09_";
|
|
2102
|
+
return `# AGENTS.md
|
|
2103
|
+
|
|
2104
|
+
> \u672C\u5DE5\u7A0B\u5DF2\u88C5\u914D Teamix Evo AI skills\u3002AI \u52A9\u624B\u5728\u4EE5\u4E0B\u573A\u666F\u4E0B**\u5FC5\u987B\u5148\u8BFB\u5BF9\u5E94 skill** \u518D\u52A8\u624B\u3002
|
|
2105
|
+
> \u672C\u6587\u4EF6\u7531 \`teamix-evo init\` / \`create-teamix-evo\` \u81EA\u52A8\u751F\u6210\uFF08regenerable\uFF0C[ADR 0038](https://github.com/teamix-evo/teamix-evo/blob/main/docs/adr/0038-create-agents-md-skill-trigger-fallback.md)\uFF09\uFF0C\u5237\u65B0\u65B9\u5F0F\u89C1\u5E95\u90E8\u3002
|
|
2106
|
+
|
|
2107
|
+
## \u5DF2\u88C5 Skills\uFF08variant: ${variant}\uFF09
|
|
2108
|
+
|
|
2109
|
+
${skillBlock}
|
|
2110
|
+
|
|
2111
|
+
## \u89E6\u53D1\u515C\u5E95\u89C4\u5219
|
|
2112
|
+
|
|
2113
|
+
- \u5199\u65B0 \`.tsx\` / \`.ts\` \u524D\uFF0C\u5BF9\u7167\u4E0A\u8FF0 TRIGGER \u5224\u5B9A\u662F\u5426\u547D\u4E2D
|
|
2114
|
+
- \u547D\u4E2D\u5219\u5148\u8BFB\u5BF9\u5E94 \`SKILL.md\`\uFF0C\u518D\u52A8\u624B\uFF1B\u4E8C\u8005\u540C\u65F6\u547D\u4E2D\u5219\u4E24\u4E2A\u90FD\u8BFB
|
|
2115
|
+
- \u6A21\u7CCA\u573A\u666F\uFF1A\u5148\u6309 SKIP \u53CD\u5411\u6392\u9664\uFF0C\u5269\u4F59\u552F\u4E00 skill \u5373\u4E3A\u5165\u53E3
|
|
2116
|
+
- \u751F\u547D\u5468\u671F\u547D\u4EE4\uFF08\`init\` / \`update\` / \`add\`\uFF09\u8D70 \`teamix-evo-manage\`\uFF08\u5168\u5C40 skill\uFF0C\u672C\u6587\u4EF6\u4E0D\u5217\uFF09
|
|
2117
|
+
|
|
2118
|
+
> \u5237\u65B0\u672C\u6587\u4EF6\uFF1A\`npx teamix-evo skills add\` \u6216\u91CD\u8DD1 \`npm create teamix-evo\` / \`teamix-evo init\`\u3002
|
|
2119
|
+
`;
|
|
2120
|
+
}
|
|
2121
|
+
function extractDescriptionParts(fileContent) {
|
|
2122
|
+
const description = extractDescriptionBlock(fileContent);
|
|
2123
|
+
if (description == null) return null;
|
|
2124
|
+
const allLines = description.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2125
|
+
let capability = "";
|
|
2126
|
+
for (const line of allLines) {
|
|
2127
|
+
if (/^(TRIGGER when:|SKIP:|Coordinates with:)/i.test(line)) break;
|
|
2128
|
+
capability = capability ? `${capability} ${line}` : line;
|
|
2129
|
+
}
|
|
2130
|
+
return {
|
|
2131
|
+
capability: capability.trim(),
|
|
2132
|
+
trigger: extractSection(description, "TRIGGER when:"),
|
|
2133
|
+
skip: extractSection(description, "SKIP:"),
|
|
2134
|
+
coordinates: extractSection(description, "Coordinates with:")
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
2137
|
+
function extractDescriptionBlock(fileContent) {
|
|
2138
|
+
const lines = fileContent.split("\n");
|
|
2139
|
+
if (lines[0]?.trim() !== "---") return null;
|
|
2140
|
+
let endIdx = -1;
|
|
2141
|
+
for (let i = 1; i < lines.length; i++) {
|
|
2142
|
+
if (lines[i]?.trim() === "---") {
|
|
2143
|
+
endIdx = i;
|
|
2144
|
+
break;
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
if (endIdx === -1) return null;
|
|
2148
|
+
const fmLines = lines.slice(1, endIdx);
|
|
2149
|
+
let startIdx = -1;
|
|
2150
|
+
let inlineValue = null;
|
|
2151
|
+
let blockMode = "inline";
|
|
2152
|
+
for (let i = 0; i < fmLines.length; i++) {
|
|
2153
|
+
const m = fmLines[i]?.match(/^description:\s*(\|[+-]?|>[+-]?)?\s*(.*)$/);
|
|
2154
|
+
if (m) {
|
|
2155
|
+
startIdx = i;
|
|
2156
|
+
const indicator = (m[1] ?? "").trim();
|
|
2157
|
+
const rest = m[2] ?? "";
|
|
2158
|
+
if (indicator.startsWith("|")) blockMode = "literal";
|
|
2159
|
+
else if (indicator.startsWith(">")) blockMode = "folded";
|
|
2160
|
+
else {
|
|
2161
|
+
blockMode = "inline";
|
|
2162
|
+
inlineValue = rest;
|
|
2163
|
+
}
|
|
2164
|
+
break;
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
if (startIdx === -1) return null;
|
|
2168
|
+
if (blockMode === "inline") {
|
|
2169
|
+
return inlineValue ?? "";
|
|
2170
|
+
}
|
|
2171
|
+
const body = [];
|
|
2172
|
+
let blockIndent = -1;
|
|
2173
|
+
for (let i = startIdx + 1; i < fmLines.length; i++) {
|
|
2174
|
+
const line = fmLines[i] ?? "";
|
|
2175
|
+
if (line.trim() === "") {
|
|
2176
|
+
body.push("");
|
|
2177
|
+
continue;
|
|
2178
|
+
}
|
|
2179
|
+
const indentMatch = line.match(/^(\s+)/);
|
|
2180
|
+
const indent = indentMatch ? indentMatch[1].length : 0;
|
|
2181
|
+
if (indent === 0) break;
|
|
2182
|
+
if (blockIndent === -1) blockIndent = indent;
|
|
2183
|
+
if (indent < blockIndent) break;
|
|
2184
|
+
body.push(line.slice(blockIndent));
|
|
2185
|
+
}
|
|
2186
|
+
while (body.length > 0 && body[body.length - 1] === "") body.pop();
|
|
2187
|
+
return body.join("\n");
|
|
2188
|
+
}
|
|
2189
|
+
function extractSection(description, marker) {
|
|
2190
|
+
const markers = ["TRIGGER when:", "SKIP:", "Coordinates with:"];
|
|
2191
|
+
const lines = description.split("\n");
|
|
2192
|
+
let inSection = false;
|
|
2193
|
+
const collected = [];
|
|
2194
|
+
for (const line of lines) {
|
|
2195
|
+
const trimmed = line.trim();
|
|
2196
|
+
const startsWithMarker = trimmed.toLowerCase().startsWith(marker.toLowerCase());
|
|
2197
|
+
if (!inSection && startsWithMarker) {
|
|
2198
|
+
inSection = true;
|
|
2199
|
+
collected.push(trimmed.slice(marker.length).trim());
|
|
2200
|
+
continue;
|
|
2201
|
+
}
|
|
2202
|
+
if (inSection) {
|
|
2203
|
+
const hitNextMarker = markers.some(
|
|
2204
|
+
(m) => m !== marker && trimmed.toLowerCase().startsWith(m.toLowerCase())
|
|
2012
2205
|
);
|
|
2013
|
-
|
|
2206
|
+
if (hitNextMarker) break;
|
|
2207
|
+
if (trimmed === "") {
|
|
2208
|
+
if (collected[collected.length - 1] === "") break;
|
|
2209
|
+
collected.push("");
|
|
2210
|
+
continue;
|
|
2211
|
+
}
|
|
2212
|
+
collected.push(trimmed);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
if (!inSection) return null;
|
|
2216
|
+
const joined = collected.filter((l) => l !== "").join(" ").replace(/\s+/g, " ").trim();
|
|
2217
|
+
return joined || null;
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
// src/core/init-detect.ts
|
|
2221
|
+
import * as fs11 from "fs/promises";
|
|
2222
|
+
import * as path15 from "path";
|
|
2223
|
+
var IGNORED_TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
2224
|
+
".git",
|
|
2225
|
+
".gitignore",
|
|
2226
|
+
".gitattributes",
|
|
2227
|
+
".gitkeep",
|
|
2228
|
+
".DS_Store",
|
|
2229
|
+
"Thumbs.db",
|
|
2230
|
+
".idea",
|
|
2231
|
+
".vscode",
|
|
2232
|
+
".qoder",
|
|
2233
|
+
".claude",
|
|
2234
|
+
"README.md",
|
|
2235
|
+
"README",
|
|
2236
|
+
"README.txt",
|
|
2237
|
+
"LICENSE",
|
|
2238
|
+
"LICENSE.md",
|
|
2239
|
+
"LICENSE.txt"
|
|
2240
|
+
]);
|
|
2241
|
+
async function detectProjectState(cwd) {
|
|
2242
|
+
const absCwd = path15.resolve(cwd);
|
|
2243
|
+
const teamixDir = getTeamixDir(absCwd);
|
|
2244
|
+
const hasTeamixDir = await fileExists(teamixDir);
|
|
2245
|
+
if (hasTeamixDir) {
|
|
2246
|
+
return {
|
|
2247
|
+
state: "teamix-evo-installed",
|
|
2248
|
+
cwd: absCwd,
|
|
2249
|
+
hasTeamixDir: true,
|
|
2250
|
+
hasPackageJson: await fileExists(path15.join(absCwd, "package.json")),
|
|
2251
|
+
significantEntries: []
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
let entries;
|
|
2255
|
+
try {
|
|
2256
|
+
entries = await fs11.readdir(absCwd);
|
|
2257
|
+
} catch (err) {
|
|
2258
|
+
if (err.code === "ENOENT") {
|
|
2259
|
+
return {
|
|
2260
|
+
state: "empty",
|
|
2261
|
+
cwd: absCwd,
|
|
2262
|
+
hasTeamixDir: false,
|
|
2263
|
+
hasPackageJson: false,
|
|
2264
|
+
significantEntries: []
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
throw err;
|
|
2268
|
+
}
|
|
2269
|
+
const significant = entries.filter((e) => !IGNORED_TOP_LEVEL.has(e));
|
|
2270
|
+
const hasPackageJson = entries.includes("package.json");
|
|
2271
|
+
if (significant.length === 0) {
|
|
2272
|
+
return {
|
|
2273
|
+
state: "empty",
|
|
2274
|
+
cwd: absCwd,
|
|
2275
|
+
hasTeamixDir: false,
|
|
2276
|
+
hasPackageJson: false,
|
|
2277
|
+
significantEntries: []
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
return {
|
|
2281
|
+
state: "non-teamix-evo",
|
|
2282
|
+
cwd: absCwd,
|
|
2283
|
+
hasTeamixDir: false,
|
|
2284
|
+
hasPackageJson,
|
|
2285
|
+
significantEntries: significant.slice(0, 20).sort()
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
// src/core/init-conflicts.ts
|
|
2290
|
+
import * as crypto from "crypto";
|
|
2291
|
+
import * as fs12 from "fs/promises";
|
|
2292
|
+
import * as path16 from "path";
|
|
2293
|
+
var TAILWIND_CONFIG_CANDIDATES = [
|
|
2294
|
+
"tailwind.config.ts",
|
|
2295
|
+
"tailwind.config.js",
|
|
2296
|
+
"tailwind.config.cjs",
|
|
2297
|
+
"tailwind.config.mjs"
|
|
2298
|
+
];
|
|
2299
|
+
var TOKENS_FILE_CANDIDATES = [
|
|
2300
|
+
"src/design-tokens.css",
|
|
2301
|
+
"src/styles/design-tokens.css",
|
|
2302
|
+
"src/styles/tokens.css"
|
|
2303
|
+
];
|
|
2304
|
+
var TOKENS_DIR_CANDIDATES = ["tokens"];
|
|
2305
|
+
var INDEX_CSS_CANDIDATES = [
|
|
2306
|
+
"src/index.css",
|
|
2307
|
+
"src/main.css",
|
|
2308
|
+
"src/app.css",
|
|
2309
|
+
"src/styles/index.css",
|
|
2310
|
+
"src/styles/globals.css"
|
|
2311
|
+
];
|
|
2312
|
+
var SHADCN_FILE_CANDIDATES = ["src/lib/utils.ts"];
|
|
2313
|
+
var SHADCN_DIR_CANDIDATES = ["src/components/ui"];
|
|
2314
|
+
async function isDir(target) {
|
|
2315
|
+
try {
|
|
2316
|
+
const stat5 = await fs12.stat(target);
|
|
2317
|
+
return stat5.isDirectory();
|
|
2318
|
+
} catch {
|
|
2319
|
+
return false;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
async function dirHasContent(target) {
|
|
2323
|
+
try {
|
|
2324
|
+
const entries = await fs12.readdir(target);
|
|
2325
|
+
return entries.length > 0;
|
|
2326
|
+
} catch {
|
|
2327
|
+
return false;
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
function fingerprint(parts) {
|
|
2331
|
+
const hash = crypto.createHash("sha256");
|
|
2332
|
+
for (const p of [...parts].sort()) hash.update(p);
|
|
2333
|
+
return `sha256:${hash.digest("hex").slice(0, 16)}`;
|
|
2334
|
+
}
|
|
2335
|
+
async function readPackageJson(cwd) {
|
|
2336
|
+
const raw = await readFileOrNull(path16.join(cwd, "package.json"));
|
|
2337
|
+
if (raw === null) return null;
|
|
2338
|
+
try {
|
|
2339
|
+
return JSON.parse(raw);
|
|
2340
|
+
} catch {
|
|
2341
|
+
return null;
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
function detectTailwindMajor(pkg) {
|
|
2345
|
+
if (!pkg) return null;
|
|
2346
|
+
const v = pkg.dependencies?.tailwindcss ?? pkg.devDependencies?.tailwindcss ?? null;
|
|
2347
|
+
if (!v) return null;
|
|
2348
|
+
const m = /(\d+)/.exec(v);
|
|
2349
|
+
if (!m) return null;
|
|
2350
|
+
const major = Number.parseInt(m[1], 10);
|
|
2351
|
+
if (major === 3) return 3;
|
|
2352
|
+
if (major === 4) return 4;
|
|
2353
|
+
return null;
|
|
2354
|
+
}
|
|
2355
|
+
async function detectAgentsMd(cwd) {
|
|
2356
|
+
const target = path16.join(cwd, "AGENTS.md");
|
|
2357
|
+
const content = await readFileOrNull(target);
|
|
2358
|
+
const exists = content !== null;
|
|
2359
|
+
return {
|
|
2360
|
+
key: "agents-md",
|
|
2361
|
+
exists,
|
|
2362
|
+
paths: exists ? ["AGENTS.md"] : [],
|
|
2363
|
+
fingerprint: exists ? fingerprint([content]) : void 0,
|
|
2364
|
+
recommendedStrategy: exists ? "merge-managed" : "overwrite",
|
|
2365
|
+
availableStrategies: ["merge-managed", "overwrite", "skip"]
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
async function detectComponentsJson(cwd) {
|
|
2369
|
+
const target = path16.join(cwd, "components.json");
|
|
2370
|
+
const content = await readFileOrNull(target);
|
|
2371
|
+
const exists = content !== null;
|
|
2372
|
+
return {
|
|
2373
|
+
key: "components-json",
|
|
2374
|
+
exists,
|
|
2375
|
+
paths: exists ? ["components.json"] : [],
|
|
2376
|
+
fingerprint: exists ? fingerprint([content]) : void 0,
|
|
2377
|
+
recommendedStrategy: exists ? "diff-prompt" : "overwrite",
|
|
2378
|
+
availableStrategies: ["diff-prompt", "overwrite", "skip"]
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
async function detectTailwindConfig(cwd) {
|
|
2382
|
+
const matched = [];
|
|
2383
|
+
const contents = [];
|
|
2384
|
+
for (const rel2 of TAILWIND_CONFIG_CANDIDATES) {
|
|
2385
|
+
const c = await readFileOrNull(path16.join(cwd, rel2));
|
|
2386
|
+
if (c !== null) {
|
|
2387
|
+
matched.push(rel2);
|
|
2388
|
+
contents.push(c);
|
|
2014
2389
|
}
|
|
2015
2390
|
}
|
|
2391
|
+
const pkg = await readPackageJson(cwd);
|
|
2392
|
+
const tailwindMajor = detectTailwindMajor(pkg);
|
|
2393
|
+
const exists = matched.length > 0;
|
|
2016
2394
|
return {
|
|
2017
|
-
|
|
2018
|
-
|
|
2395
|
+
key: "tailwind-config",
|
|
2396
|
+
exists,
|
|
2397
|
+
paths: matched,
|
|
2398
|
+
fingerprint: exists ? fingerprint(contents) : void 0,
|
|
2399
|
+
recommendedStrategy: exists ? "backup-overwrite" : "overwrite",
|
|
2400
|
+
availableStrategies: ["backup-overwrite", "overwrite", "skip"],
|
|
2401
|
+
meta: { tailwindMajor }
|
|
2019
2402
|
};
|
|
2020
2403
|
}
|
|
2021
|
-
async function
|
|
2022
|
-
const
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
const templateContent = await loadTemplateFile(sourcePath);
|
|
2031
|
-
content = renderTemplate(templateContent, data);
|
|
2032
|
-
} else {
|
|
2033
|
-
content = await fs10.readFile(sourcePath, "utf-8");
|
|
2404
|
+
async function detectTokens(cwd) {
|
|
2405
|
+
const matched = [];
|
|
2406
|
+
const contents = [];
|
|
2407
|
+
for (const rel2 of TOKENS_FILE_CANDIDATES) {
|
|
2408
|
+
const c = await readFileOrNull(path16.join(cwd, rel2));
|
|
2409
|
+
if (c !== null) {
|
|
2410
|
+
matched.push(rel2);
|
|
2411
|
+
contents.push(c);
|
|
2412
|
+
}
|
|
2034
2413
|
}
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2414
|
+
for (const rel2 of TOKENS_DIR_CANDIDATES) {
|
|
2415
|
+
const abs = path16.join(cwd, rel2);
|
|
2416
|
+
if (await isDir(abs) && await dirHasContent(abs)) {
|
|
2417
|
+
matched.push(`${rel2}/`);
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
const exists = matched.length > 0;
|
|
2038
2421
|
return {
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2422
|
+
key: "tokens",
|
|
2423
|
+
exists,
|
|
2424
|
+
paths: matched,
|
|
2425
|
+
fingerprint: contents.length > 0 ? fingerprint(contents) : void 0,
|
|
2426
|
+
recommendedStrategy: exists ? "migrate" : "overwrite",
|
|
2427
|
+
availableStrategies: ["migrate", "coexist", "overwrite", "skip"]
|
|
2043
2428
|
};
|
|
2044
2429
|
}
|
|
2045
|
-
async function
|
|
2046
|
-
const
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2430
|
+
async function detectIndexCss(cwd) {
|
|
2431
|
+
const matched = [];
|
|
2432
|
+
const contents = [];
|
|
2433
|
+
for (const rel2 of INDEX_CSS_CANDIDATES) {
|
|
2434
|
+
const c = await readFileOrNull(path16.join(cwd, rel2));
|
|
2435
|
+
if (c !== null) {
|
|
2436
|
+
matched.push(rel2);
|
|
2437
|
+
contents.push(c);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
const exists = matched.length > 0;
|
|
2441
|
+
return {
|
|
2442
|
+
key: "index-css",
|
|
2443
|
+
exists,
|
|
2444
|
+
paths: matched,
|
|
2445
|
+
fingerprint: exists ? fingerprint(contents) : void 0,
|
|
2446
|
+
recommendedStrategy: exists ? "append" : "overwrite",
|
|
2447
|
+
availableStrategies: ["append", "diff-prompt", "overwrite", "skip"]
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
async function detectShadcnSource(cwd) {
|
|
2451
|
+
const matched = [];
|
|
2452
|
+
for (const rel2 of SHADCN_FILE_CANDIDATES) {
|
|
2453
|
+
if (await fileExists(path16.join(cwd, rel2))) matched.push(rel2);
|
|
2454
|
+
}
|
|
2455
|
+
for (const rel2 of SHADCN_DIR_CANDIDATES) {
|
|
2456
|
+
const abs = path16.join(cwd, rel2);
|
|
2457
|
+
if (await isDir(abs) && await dirHasContent(abs)) {
|
|
2458
|
+
matched.push(`${rel2}/`);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
let componentCount = 0;
|
|
2462
|
+
try {
|
|
2463
|
+
const uiDir = path16.join(cwd, "src/components/ui");
|
|
2464
|
+
if (await isDir(uiDir)) {
|
|
2465
|
+
const entries = await fs12.readdir(uiDir);
|
|
2466
|
+
componentCount = entries.filter(
|
|
2467
|
+
(e) => e.endsWith(".tsx") || e.endsWith(".ts")
|
|
2468
|
+
).length;
|
|
2469
|
+
}
|
|
2470
|
+
} catch {
|
|
2471
|
+
}
|
|
2472
|
+
const exists = matched.length > 0;
|
|
2473
|
+
return {
|
|
2474
|
+
key: "shadcn-source",
|
|
2475
|
+
exists,
|
|
2476
|
+
paths: matched,
|
|
2477
|
+
recommendedStrategy: exists ? "skip-existing" : "overwrite",
|
|
2478
|
+
availableStrategies: ["skip-existing", "per-file-prompt", "overwrite"],
|
|
2479
|
+
meta: { componentCount }
|
|
2480
|
+
};
|
|
2481
|
+
}
|
|
2482
|
+
async function detectConflicts(cwd) {
|
|
2483
|
+
const absCwd = path16.resolve(cwd);
|
|
2484
|
+
const items = await Promise.all([
|
|
2485
|
+
detectAgentsMd(absCwd),
|
|
2486
|
+
detectComponentsJson(absCwd),
|
|
2487
|
+
detectTailwindConfig(absCwd),
|
|
2488
|
+
detectTokens(absCwd),
|
|
2489
|
+
detectIndexCss(absCwd),
|
|
2490
|
+
detectShadcnSource(absCwd)
|
|
2491
|
+
]);
|
|
2492
|
+
return {
|
|
2493
|
+
cwd: absCwd,
|
|
2494
|
+
items,
|
|
2495
|
+
hasAnyConflict: items.some((i) => i.exists)
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
// src/core/legacy-tokens-migrate.ts
|
|
2500
|
+
import * as path17 from "path";
|
|
2501
|
+
import * as fs13 from "fs/promises";
|
|
2502
|
+
var CONSUMER_TOKENS_DIR2 = "tokens";
|
|
2503
|
+
var CONSUMER_OVERRIDES_FILE2 = "tokens.overrides.css";
|
|
2504
|
+
var EMPTY_OVERRIDES_TEMPLATE2 = `/* User-owned token overrides \u2014 frozen on subsequent installs. */
|
|
2505
|
+
/* See @teamix-evo/tokens variant theme.css for available CSS custom properties. */
|
|
2506
|
+
`;
|
|
2507
|
+
function buildMigrationBanner(legacyRel, isoTimestamp) {
|
|
2508
|
+
return `/* === Migrated from ${legacyRel} on ${isoTimestamp} === */
|
|
2509
|
+
`;
|
|
2510
|
+
}
|
|
2511
|
+
function computeBackupRel(legacyRel, isoTimestamp) {
|
|
2512
|
+
const tsSafe = isoTimestamp.replace(/[:.]/g, "-");
|
|
2513
|
+
return path17.posix.join(
|
|
2514
|
+
".teamix-evo",
|
|
2515
|
+
".backups",
|
|
2516
|
+
`${legacyRel}.${tsSafe}.bak`
|
|
2050
2517
|
);
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
const
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
if (
|
|
2059
|
-
|
|
2518
|
+
}
|
|
2519
|
+
async function migrateLegacyTokens(options) {
|
|
2520
|
+
const { projectRoot, legacyPaths, dryRun = false } = options;
|
|
2521
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2522
|
+
const uniqueLegacy = [];
|
|
2523
|
+
for (const raw of legacyPaths) {
|
|
2524
|
+
const norm = raw.split(path17.sep).join("/");
|
|
2525
|
+
if (!seen.has(norm)) {
|
|
2526
|
+
seen.add(norm);
|
|
2527
|
+
uniqueLegacy.push(norm);
|
|
2060
2528
|
}
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2529
|
+
}
|
|
2530
|
+
const overridesRel = path17.posix.join(
|
|
2531
|
+
CONSUMER_TOKENS_DIR2,
|
|
2532
|
+
CONSUMER_OVERRIDES_FILE2
|
|
2533
|
+
);
|
|
2534
|
+
const overridesAbs = path17.join(projectRoot, overridesRel);
|
|
2535
|
+
const existingOverrides = await readFileOrNull(overridesAbs);
|
|
2536
|
+
const overridesCreated = existingOverrides === null;
|
|
2537
|
+
const baseContent = existingOverrides === null ? EMPTY_OVERRIDES_TEMPLATE2 : existingOverrides;
|
|
2538
|
+
const migrated = [];
|
|
2539
|
+
const skipped = [];
|
|
2540
|
+
const isoTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2541
|
+
let appendedPayload = "";
|
|
2542
|
+
for (const legacyRel of uniqueLegacy) {
|
|
2543
|
+
const legacyAbs = path17.join(projectRoot, legacyRel);
|
|
2544
|
+
const content = await readFileOrNull(legacyAbs);
|
|
2545
|
+
if (content === null) {
|
|
2546
|
+
skipped.push({ from: legacyRel, reason: "not-found" });
|
|
2547
|
+
continue;
|
|
2067
2548
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2549
|
+
if (content.trim() === "") {
|
|
2550
|
+
skipped.push({ from: legacyRel, reason: "empty" });
|
|
2551
|
+
continue;
|
|
2552
|
+
}
|
|
2553
|
+
const banner = buildMigrationBanner(legacyRel, isoTimestamp);
|
|
2554
|
+
const separator = appendedPayload === "" && baseContent.endsWith("\n\n") ? "" : "\n";
|
|
2555
|
+
const block = `${separator}${banner}${content}${content.endsWith("\n") ? "" : "\n"}`;
|
|
2556
|
+
appendedPayload += block;
|
|
2557
|
+
const backupRel = dryRun ? null : computeBackupRel(legacyRel, isoTimestamp);
|
|
2558
|
+
migrated.push({
|
|
2559
|
+
from: legacyRel,
|
|
2560
|
+
backupTo: backupRel,
|
|
2561
|
+
bytesAppended: Buffer.byteLength(block, "utf-8")
|
|
2076
2562
|
});
|
|
2077
|
-
logger.debug(` Written: ${targetRel}`);
|
|
2078
2563
|
}
|
|
2079
|
-
|
|
2564
|
+
if (dryRun || migrated.length === 0) {
|
|
2565
|
+
return {
|
|
2566
|
+
migrated,
|
|
2567
|
+
skipped,
|
|
2568
|
+
overridesPath: overridesRel,
|
|
2569
|
+
overridesCreated: dryRun ? overridesCreated : false,
|
|
2570
|
+
dryRun
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
await ensureDir(path17.dirname(overridesAbs));
|
|
2574
|
+
const merged = baseContent + appendedPayload;
|
|
2575
|
+
const tmp = overridesAbs + ".tmp";
|
|
2576
|
+
await fs13.writeFile(tmp, merged, "utf-8");
|
|
2577
|
+
await fs13.rename(tmp, overridesAbs);
|
|
2578
|
+
for (const entry of migrated) {
|
|
2579
|
+
const legacyAbs = path17.join(projectRoot, entry.from);
|
|
2580
|
+
const backupAbs = path17.join(projectRoot, entry.backupTo);
|
|
2581
|
+
await ensureDir(path17.dirname(backupAbs));
|
|
2582
|
+
const srcContent = await readFileOrNull(legacyAbs);
|
|
2583
|
+
if (srcContent === null) {
|
|
2584
|
+
logger.debug(
|
|
2585
|
+
`legacy-tokens-migrate: source disappeared before backup: ${entry.from}`
|
|
2586
|
+
);
|
|
2587
|
+
continue;
|
|
2588
|
+
}
|
|
2589
|
+
await fs13.writeFile(backupAbs, srcContent, "utf-8");
|
|
2590
|
+
if (await fileExists(legacyAbs)) {
|
|
2591
|
+
await fs13.unlink(legacyAbs);
|
|
2592
|
+
}
|
|
2593
|
+
logger.debug(
|
|
2594
|
+
`legacy-tokens-migrate: ${entry.from} \u2192 ${overridesRel} (backup: ${entry.backupTo})`
|
|
2595
|
+
);
|
|
2596
|
+
}
|
|
2597
|
+
return {
|
|
2598
|
+
migrated,
|
|
2599
|
+
skipped,
|
|
2600
|
+
overridesPath: overridesRel,
|
|
2601
|
+
overridesCreated,
|
|
2602
|
+
dryRun: false
|
|
2603
|
+
};
|
|
2080
2604
|
}
|
|
2081
2605
|
|
|
2082
|
-
// src/core/
|
|
2083
|
-
import * as
|
|
2084
|
-
import * as
|
|
2606
|
+
// src/core/snapshot.ts
|
|
2607
|
+
import * as fs14 from "fs/promises";
|
|
2608
|
+
import * as path18 from "path";
|
|
2609
|
+
var TEAMIX_DIR2 = ".teamix-evo";
|
|
2610
|
+
var SNAPSHOTS_DIR = ".snapshots";
|
|
2611
|
+
var LOGS_DIR = "logs";
|
|
2612
|
+
var META_FILE = "_meta.json";
|
|
2613
|
+
var DEFAULT_KEEP = 5;
|
|
2614
|
+
function isoToFsSafe(iso) {
|
|
2615
|
+
return iso.replace(/[:.]/g, "-");
|
|
2616
|
+
}
|
|
2617
|
+
function fsSafeToIso(safe) {
|
|
2618
|
+
return safe.replace(
|
|
2619
|
+
/^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})(Z)$/,
|
|
2620
|
+
"$1T$2:$3:$4.$5$6"
|
|
2621
|
+
);
|
|
2622
|
+
}
|
|
2623
|
+
async function createSnapshot(projectRoot, opts = {}) {
|
|
2624
|
+
const teamixDir = path18.join(projectRoot, TEAMIX_DIR2);
|
|
2625
|
+
try {
|
|
2626
|
+
const stat5 = await fs14.stat(teamixDir);
|
|
2627
|
+
if (!stat5.isDirectory()) return null;
|
|
2628
|
+
} catch (err) {
|
|
2629
|
+
if (err.code === "ENOENT") return null;
|
|
2630
|
+
throw err;
|
|
2631
|
+
}
|
|
2632
|
+
const isoTs = (/* @__PURE__ */ new Date()).toISOString();
|
|
2633
|
+
const ts = isoToFsSafe(isoTs);
|
|
2634
|
+
const snapshotRoot = path18.join(teamixDir, SNAPSHOTS_DIR);
|
|
2635
|
+
const target = path18.join(snapshotRoot, ts);
|
|
2636
|
+
await fs14.mkdir(target, { recursive: true });
|
|
2637
|
+
const entries = await fs14.readdir(teamixDir, { withFileTypes: true });
|
|
2638
|
+
for (const entry of entries) {
|
|
2639
|
+
if (entry.name === SNAPSHOTS_DIR) continue;
|
|
2640
|
+
if (entry.name === LOGS_DIR) continue;
|
|
2641
|
+
const src = path18.join(teamixDir, entry.name);
|
|
2642
|
+
const dst = path18.join(target, entry.name);
|
|
2643
|
+
await fs14.cp(src, dst, { recursive: true });
|
|
2644
|
+
}
|
|
2645
|
+
const meta = {
|
|
2646
|
+
ts: isoTs,
|
|
2647
|
+
reason: opts.reason ?? "manual"
|
|
2648
|
+
};
|
|
2649
|
+
await fs14.writeFile(
|
|
2650
|
+
path18.join(target, META_FILE),
|
|
2651
|
+
JSON.stringify(meta, null, 2) + "\n",
|
|
2652
|
+
"utf-8"
|
|
2653
|
+
);
|
|
2654
|
+
logger.debug(
|
|
2655
|
+
`Snapshot created \u2192 ${path18.relative(projectRoot, target)} (${meta.reason})`
|
|
2656
|
+
);
|
|
2657
|
+
const keep = opts.keep ?? DEFAULT_KEEP;
|
|
2658
|
+
await pruneSnapshots(projectRoot, keep, { protectedTs: opts.protectedTs });
|
|
2659
|
+
return { ts, path: target };
|
|
2660
|
+
}
|
|
2661
|
+
async function listSnapshots(projectRoot) {
|
|
2662
|
+
const snapshotRoot = path18.join(projectRoot, TEAMIX_DIR2, SNAPSHOTS_DIR);
|
|
2663
|
+
let entries;
|
|
2664
|
+
try {
|
|
2665
|
+
entries = await fs14.readdir(snapshotRoot, { withFileTypes: true });
|
|
2666
|
+
} catch (err) {
|
|
2667
|
+
if (err.code === "ENOENT") return [];
|
|
2668
|
+
throw err;
|
|
2669
|
+
}
|
|
2670
|
+
const result = [];
|
|
2671
|
+
for (const entry of entries) {
|
|
2672
|
+
if (!entry.isDirectory()) continue;
|
|
2673
|
+
const dir = path18.join(snapshotRoot, entry.name);
|
|
2674
|
+
let isoTs = null;
|
|
2675
|
+
let reason = null;
|
|
2676
|
+
try {
|
|
2677
|
+
const raw = await fs14.readFile(path18.join(dir, META_FILE), "utf-8");
|
|
2678
|
+
const parsed = JSON.parse(raw);
|
|
2679
|
+
if (typeof parsed.ts === "string") isoTs = parsed.ts;
|
|
2680
|
+
if (typeof parsed.reason === "string" && ["init", "update", "switch", "restore", "manual"].includes(
|
|
2681
|
+
parsed.reason
|
|
2682
|
+
)) {
|
|
2683
|
+
reason = parsed.reason;
|
|
2684
|
+
}
|
|
2685
|
+
} catch {
|
|
2686
|
+
isoTs = fsSafeToIso(entry.name);
|
|
2687
|
+
}
|
|
2688
|
+
result.push({ ts: entry.name, isoTs, reason, path: dir });
|
|
2689
|
+
}
|
|
2690
|
+
result.sort((a, b) => a.ts < b.ts ? 1 : a.ts > b.ts ? -1 : 0);
|
|
2691
|
+
return result;
|
|
2692
|
+
}
|
|
2693
|
+
async function pruneSnapshots(projectRoot, keep = DEFAULT_KEEP, opts = {}) {
|
|
2694
|
+
if (keep < 0)
|
|
2695
|
+
throw new Error(`pruneSnapshots: keep must be >= 0, got ${keep}`);
|
|
2696
|
+
const snapshots = await listSnapshots(projectRoot);
|
|
2697
|
+
if (snapshots.length <= keep) return [];
|
|
2698
|
+
const tail = snapshots.slice(keep);
|
|
2699
|
+
const toRemove = opts.protectedTs ? tail.filter((s) => s.ts !== opts.protectedTs) : tail;
|
|
2700
|
+
const removed = [];
|
|
2701
|
+
for (const snap of toRemove) {
|
|
2702
|
+
await fs14.rm(snap.path, { recursive: true, force: true });
|
|
2703
|
+
removed.push(snap.ts);
|
|
2704
|
+
logger.debug(`Pruned snapshot ${snap.ts}`);
|
|
2705
|
+
}
|
|
2706
|
+
return removed.reverse();
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
// src/core/project-init.ts
|
|
2710
|
+
var BASELINE_UI_ENTRIES = [
|
|
2711
|
+
"button",
|
|
2712
|
+
"button-group",
|
|
2713
|
+
"input",
|
|
2714
|
+
"form",
|
|
2715
|
+
"card",
|
|
2716
|
+
"collapsible",
|
|
2717
|
+
"dialog",
|
|
2718
|
+
"dropdown-menu",
|
|
2719
|
+
"tabs",
|
|
2720
|
+
"table",
|
|
2721
|
+
"sidebar",
|
|
2722
|
+
"page-shell",
|
|
2723
|
+
"page-header"
|
|
2724
|
+
];
|
|
2725
|
+
var CRITICAL_STEPS = /* @__PURE__ */ new Set([
|
|
2726
|
+
"tokens",
|
|
2727
|
+
"skills",
|
|
2728
|
+
"ui-init"
|
|
2729
|
+
]);
|
|
2730
|
+
var IMPLEMENTED_STRATEGIES = {
|
|
2731
|
+
"agents-md": ["merge-managed", "overwrite", "skip"],
|
|
2732
|
+
tokens: ["migrate", "overwrite", "skip"],
|
|
2733
|
+
"components-json": ["overwrite", "skip"],
|
|
2734
|
+
"shadcn-source": ["overwrite", "skip-existing", "skip"],
|
|
2735
|
+
"tailwind-config": ["skip"],
|
|
2736
|
+
"index-css": ["skip"]
|
|
2737
|
+
};
|
|
2738
|
+
function pickIde(ides) {
|
|
2739
|
+
return ides[0] ?? "qoder";
|
|
2740
|
+
}
|
|
2741
|
+
function strategyImplemented(key, strategy) {
|
|
2742
|
+
return IMPLEMENTED_STRATEGIES[key]?.includes(strategy) ?? false;
|
|
2743
|
+
}
|
|
2744
|
+
async function resolveUiEntries(options) {
|
|
2745
|
+
if (options.uiEntries && options.uiEntries.length > 0) {
|
|
2746
|
+
return [...options.uiEntries];
|
|
2747
|
+
}
|
|
2748
|
+
if (options.answers.uiSelection === "all") {
|
|
2749
|
+
const { manifest } = await loadUiData("@teamix-evo/ui");
|
|
2750
|
+
return manifest.entries.map((e) => e.id);
|
|
2751
|
+
}
|
|
2752
|
+
return [...BASELINE_UI_ENTRIES];
|
|
2753
|
+
}
|
|
2754
|
+
async function runProjectInit(options) {
|
|
2755
|
+
const { projectRoot, answers, dryRun = false, onStep } = options;
|
|
2756
|
+
const ide = pickIde(answers.ides);
|
|
2757
|
+
const steps = [];
|
|
2758
|
+
const pending = [];
|
|
2759
|
+
let snapshot = null;
|
|
2760
|
+
let snapshotError;
|
|
2761
|
+
if (!dryRun) {
|
|
2762
|
+
try {
|
|
2763
|
+
snapshot = await createSnapshot(projectRoot, { reason: "init" });
|
|
2764
|
+
} catch (err) {
|
|
2765
|
+
snapshotError = getErrorMessage(err);
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
let aborted = false;
|
|
2769
|
+
const firstFailure = {
|
|
2770
|
+
value: null
|
|
2771
|
+
};
|
|
2772
|
+
function record(step) {
|
|
2773
|
+
steps.push(step);
|
|
2774
|
+
onStep?.(step);
|
|
2775
|
+
}
|
|
2776
|
+
function recordPending(key) {
|
|
2777
|
+
const strategy = answers.conflictDecisions[key];
|
|
2778
|
+
if (!strategy) return;
|
|
2779
|
+
if (strategyImplemented(key, strategy)) return;
|
|
2780
|
+
pending.push({
|
|
2781
|
+
key,
|
|
2782
|
+
strategy,
|
|
2783
|
+
reason: `Strategy "${strategy}" requires the managed-region engine (batch 4); recorded for follow-up.`
|
|
2784
|
+
});
|
|
2785
|
+
}
|
|
2786
|
+
function recordFailure(name, err) {
|
|
2787
|
+
const message = getErrorMessage(err);
|
|
2788
|
+
record({ name, status: "fail", detail: message });
|
|
2789
|
+
if (!firstFailure.value)
|
|
2790
|
+
firstFailure.value = { step: name, error: message };
|
|
2791
|
+
if (CRITICAL_STEPS.has(name)) aborted = true;
|
|
2792
|
+
}
|
|
2793
|
+
const tokensDecision = answers.conflictDecisions.tokens;
|
|
2794
|
+
const legacyTokensPaths = options.legacyTokensPaths ?? [];
|
|
2795
|
+
const wantMigrate = tokensDecision === "migrate" && legacyTokensPaths.length > 0;
|
|
2796
|
+
if (tokensDecision === "skip") {
|
|
2797
|
+
record({
|
|
2798
|
+
name: "tokens",
|
|
2799
|
+
status: "skip",
|
|
2800
|
+
detail: "conflict strategy = skip"
|
|
2801
|
+
});
|
|
2802
|
+
} else if (dryRun) {
|
|
2803
|
+
const planDetail = wantMigrate ? `runTokensInit(variant=${answers.variant}); migrateLegacyTokens(${legacyTokensPaths.length} file${legacyTokensPaths.length === 1 ? "" : "s"})` : `runTokensInit(variant=${answers.variant})`;
|
|
2804
|
+
record({
|
|
2805
|
+
name: "tokens",
|
|
2806
|
+
status: "planned",
|
|
2807
|
+
detail: planDetail
|
|
2808
|
+
});
|
|
2809
|
+
} else {
|
|
2810
|
+
try {
|
|
2811
|
+
const result = await runTokensInit({
|
|
2812
|
+
projectRoot,
|
|
2813
|
+
variant: answers.variant,
|
|
2814
|
+
ide
|
|
2815
|
+
});
|
|
2816
|
+
let detail = result.status === "installed" ? `${result.packageName}@${result.version} (${result.count} files)` : result.status;
|
|
2817
|
+
if (wantMigrate) {
|
|
2818
|
+
try {
|
|
2819
|
+
const m = await migrateLegacyTokens({
|
|
2820
|
+
projectRoot,
|
|
2821
|
+
legacyPaths: legacyTokensPaths
|
|
2822
|
+
});
|
|
2823
|
+
detail += `; migrated ${m.migrated.length}/${legacyTokensPaths.length} legacy file${legacyTokensPaths.length === 1 ? "" : "s"} \u2192 ${m.overridesPath}`;
|
|
2824
|
+
if (m.skipped.length > 0) {
|
|
2825
|
+
detail += ` (skipped ${m.skipped.length})`;
|
|
2826
|
+
}
|
|
2827
|
+
} catch (err) {
|
|
2828
|
+
detail += `; migrate failed: ${getErrorMessage(err)}`;
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
record({
|
|
2832
|
+
name: "tokens",
|
|
2833
|
+
status: "ok",
|
|
2834
|
+
detail
|
|
2835
|
+
});
|
|
2836
|
+
} catch (err) {
|
|
2837
|
+
recordFailure("tokens", err);
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
recordPending("tokens");
|
|
2841
|
+
const codeSkillId = `teamix-evo-code-${answers.variant}`;
|
|
2842
|
+
if (dryRun) {
|
|
2843
|
+
record({
|
|
2844
|
+
name: "skills",
|
|
2845
|
+
status: "planned",
|
|
2846
|
+
detail: `runSkillsAdd(${codeSkillId})`
|
|
2847
|
+
});
|
|
2848
|
+
} else if (aborted) {
|
|
2849
|
+
record({
|
|
2850
|
+
name: "skills",
|
|
2851
|
+
status: "skip",
|
|
2852
|
+
detail: "aborted: earlier critical step failed"
|
|
2853
|
+
});
|
|
2854
|
+
} else {
|
|
2855
|
+
try {
|
|
2856
|
+
const result = await runSkillsAdd({
|
|
2857
|
+
projectRoot,
|
|
2858
|
+
names: [codeSkillId],
|
|
2859
|
+
ides: answers.ides,
|
|
2860
|
+
scope: answers.scope,
|
|
2861
|
+
ide
|
|
2862
|
+
});
|
|
2863
|
+
record({
|
|
2864
|
+
name: "skills",
|
|
2865
|
+
status: "ok",
|
|
2866
|
+
detail: result.status === "installed" ? `added: ${result.addedSkillIds.join(", ") || "none"}; existing: ${result.skippedSkillIds.join(", ") || "none"}` : result.status
|
|
2867
|
+
});
|
|
2868
|
+
} catch (err) {
|
|
2869
|
+
recordFailure("skills", err);
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
const agentsMdDecision = answers.conflictDecisions["agents-md"];
|
|
2873
|
+
const agentsMdSkillIds = [
|
|
2874
|
+
`teamix-evo-design-${answers.variant}`,
|
|
2875
|
+
codeSkillId
|
|
2876
|
+
];
|
|
2877
|
+
if (agentsMdDecision === "skip") {
|
|
2878
|
+
record({
|
|
2879
|
+
name: "agents-md",
|
|
2880
|
+
status: "skip",
|
|
2881
|
+
detail: "conflict strategy = skip"
|
|
2882
|
+
});
|
|
2883
|
+
} else if (dryRun) {
|
|
2884
|
+
record({
|
|
2885
|
+
name: "agents-md",
|
|
2886
|
+
status: "planned",
|
|
2887
|
+
detail: `runGenerateAgentsMd(${agentsMdSkillIds.length} skills)`
|
|
2888
|
+
});
|
|
2889
|
+
} else {
|
|
2890
|
+
try {
|
|
2891
|
+
const result = await runGenerateAgentsMd({
|
|
2892
|
+
projectRoot,
|
|
2893
|
+
variant: answers.variant,
|
|
2894
|
+
skillIds: agentsMdSkillIds
|
|
2895
|
+
});
|
|
2896
|
+
record({
|
|
2897
|
+
name: "agents-md",
|
|
2898
|
+
status: "ok",
|
|
2899
|
+
detail: `${result.skillCount} skill index${result.missingSkillIds.length > 0 ? ` (missing SKILL.md: ${result.missingSkillIds.join(", ")})` : ""}`
|
|
2900
|
+
});
|
|
2901
|
+
} catch (err) {
|
|
2902
|
+
recordFailure("agents-md", err);
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
recordPending("agents-md");
|
|
2906
|
+
const componentsJsonDecision = answers.conflictDecisions["components-json"];
|
|
2907
|
+
const shadcnDecision = answers.conflictDecisions["shadcn-source"];
|
|
2908
|
+
const skipUiInit = !answers.withUi || componentsJsonDecision === "skip";
|
|
2909
|
+
if (skipUiInit) {
|
|
2910
|
+
record({
|
|
2911
|
+
name: "ui-init",
|
|
2912
|
+
status: "skip",
|
|
2913
|
+
detail: !answers.withUi ? "withUi = false" : "components.json conflict strategy = skip"
|
|
2914
|
+
});
|
|
2915
|
+
record({
|
|
2916
|
+
name: "ui-add",
|
|
2917
|
+
status: "skip",
|
|
2918
|
+
detail: !answers.withUi ? "withUi = false" : "components.json conflict strategy = skip"
|
|
2919
|
+
});
|
|
2920
|
+
} else if (dryRun) {
|
|
2921
|
+
record({
|
|
2922
|
+
name: "ui-init",
|
|
2923
|
+
status: "planned",
|
|
2924
|
+
detail: "runUiInit()"
|
|
2925
|
+
});
|
|
2926
|
+
const entries = await resolveUiEntries(options).catch(() => [
|
|
2927
|
+
...BASELINE_UI_ENTRIES
|
|
2928
|
+
]);
|
|
2929
|
+
record({
|
|
2930
|
+
name: "ui-add",
|
|
2931
|
+
status: "planned",
|
|
2932
|
+
detail: `runUiAdd(${entries.length} entries: ${entries.slice(0, 3).join(", ")}${entries.length > 3 ? "\u2026" : ""})`
|
|
2933
|
+
});
|
|
2934
|
+
} else {
|
|
2935
|
+
if (aborted) {
|
|
2936
|
+
record({
|
|
2937
|
+
name: "ui-init",
|
|
2938
|
+
status: "skip",
|
|
2939
|
+
detail: "aborted: earlier critical step failed"
|
|
2940
|
+
});
|
|
2941
|
+
record({
|
|
2942
|
+
name: "ui-add",
|
|
2943
|
+
status: "skip",
|
|
2944
|
+
detail: "aborted: earlier critical step failed"
|
|
2945
|
+
});
|
|
2946
|
+
} else {
|
|
2947
|
+
let uiInitOk = false;
|
|
2948
|
+
try {
|
|
2949
|
+
const initResult = await runUiInit({ projectRoot, ide });
|
|
2950
|
+
record({
|
|
2951
|
+
name: "ui-init",
|
|
2952
|
+
status: "ok",
|
|
2953
|
+
detail: initResult.status === "installed" ? "config.json packages.ui written" : initResult.status
|
|
2954
|
+
});
|
|
2955
|
+
uiInitOk = true;
|
|
2956
|
+
} catch (err) {
|
|
2957
|
+
recordFailure("ui-init", err);
|
|
2958
|
+
}
|
|
2959
|
+
if (!uiInitOk) {
|
|
2960
|
+
record({
|
|
2961
|
+
name: "ui-add",
|
|
2962
|
+
status: "skip",
|
|
2963
|
+
detail: "aborted: ui-init failed"
|
|
2964
|
+
});
|
|
2965
|
+
} else if (shadcnDecision === "skip") {
|
|
2966
|
+
record({
|
|
2967
|
+
name: "ui-add",
|
|
2968
|
+
status: "skip",
|
|
2969
|
+
detail: "shadcn-source conflict strategy = skip"
|
|
2970
|
+
});
|
|
2971
|
+
} else {
|
|
2972
|
+
try {
|
|
2973
|
+
const entries = await resolveUiEntries(options);
|
|
2974
|
+
const addResult = await runUiAdd({
|
|
2975
|
+
projectRoot,
|
|
2976
|
+
ids: entries,
|
|
2977
|
+
// 'overwrite' strategy → overwrite=true; everything else (incl.
|
|
2978
|
+
// 'skip-existing' which is the default) → overwrite=false.
|
|
2979
|
+
overwrite: shadcnDecision === "overwrite"
|
|
2980
|
+
});
|
|
2981
|
+
record({
|
|
2982
|
+
name: "ui-add",
|
|
2983
|
+
status: "ok",
|
|
2984
|
+
detail: `${addResult.orderedIds.length} entries (${addResult.written} written, ${addResult.skipped} skipped)`
|
|
2985
|
+
});
|
|
2986
|
+
} catch (err) {
|
|
2987
|
+
recordFailure("ui-add", err);
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
recordPending("components-json");
|
|
2993
|
+
recordPending("shadcn-source");
|
|
2994
|
+
if (!answers.withLint) {
|
|
2995
|
+
record({
|
|
2996
|
+
name: "lint",
|
|
2997
|
+
status: "skip",
|
|
2998
|
+
detail: "withLint = false"
|
|
2999
|
+
});
|
|
3000
|
+
} else if (dryRun) {
|
|
3001
|
+
record({
|
|
3002
|
+
name: "lint",
|
|
3003
|
+
status: "planned",
|
|
3004
|
+
detail: "runLintInit()"
|
|
3005
|
+
});
|
|
3006
|
+
} else {
|
|
3007
|
+
try {
|
|
3008
|
+
const result = await runLintInit({
|
|
3009
|
+
projectRoot,
|
|
3010
|
+
skipInstall: options.skipInstall ?? false
|
|
3011
|
+
});
|
|
3012
|
+
record({
|
|
3013
|
+
name: "lint",
|
|
3014
|
+
status: "ok",
|
|
3015
|
+
detail: result.status === "installed" ? `eslint=${result.eslint}, stylelint=${result.stylelint}` : result.status
|
|
3016
|
+
});
|
|
3017
|
+
} catch (err) {
|
|
3018
|
+
recordFailure("lint", err);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
recordPending("tailwind-config");
|
|
3022
|
+
recordPending("index-css");
|
|
3023
|
+
const status = dryRun ? "dry-run" : steps.some((s) => s.status === "fail") ? "partial" : "installed";
|
|
3024
|
+
const out = {
|
|
3025
|
+
status,
|
|
3026
|
+
steps,
|
|
3027
|
+
pendingConflictWork: pending,
|
|
3028
|
+
snapshot
|
|
3029
|
+
};
|
|
3030
|
+
if (snapshotError) out.snapshotError = snapshotError;
|
|
3031
|
+
if (firstFailure.value) {
|
|
3032
|
+
out.resumeHint = {
|
|
3033
|
+
failedAt: firstFailure.value.step,
|
|
3034
|
+
completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
|
|
3035
|
+
failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
|
|
3036
|
+
error: firstFailure.value.error,
|
|
3037
|
+
// Every sub-step is idempotent (returns `already-initialized` when its
|
|
3038
|
+
// state file already exists), so a plain re-run resumes from the
|
|
3039
|
+
// failed step. A richer --resume model lands with batch 3 (lock
|
|
3040
|
+
// snapshot + restore).
|
|
3041
|
+
resumeCommand: "teamix-evo init"
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
return out;
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
// src/core/project-update.ts
|
|
3048
|
+
import * as path24 from "path";
|
|
3049
|
+
import {
|
|
3050
|
+
loadTokensPackageManifest as loadTokensPackageManifest3,
|
|
3051
|
+
getVariantEntry as getVariantEntry3
|
|
3052
|
+
} from "@teamix-evo/registry";
|
|
3053
|
+
|
|
3054
|
+
// src/core/tokens-update.ts
|
|
3055
|
+
import * as path20 from "path";
|
|
3056
|
+
import * as fs15 from "fs/promises";
|
|
3057
|
+
import {
|
|
3058
|
+
loadTokensPackageManifest as loadTokensPackageManifest2,
|
|
3059
|
+
getVariantEntry as getVariantEntry2
|
|
3060
|
+
} from "@teamix-evo/registry";
|
|
3061
|
+
|
|
3062
|
+
// src/core/upgrade-hints.ts
|
|
3063
|
+
import * as path19 from "path";
|
|
3064
|
+
var TEAMIX_DIR3 = ".teamix-evo";
|
|
3065
|
+
var HINTS_DIR = ".upgrade-hints";
|
|
3066
|
+
function isoToFsSafe2(iso) {
|
|
3067
|
+
return iso.replace(/[:.]/g, "-");
|
|
3068
|
+
}
|
|
3069
|
+
async function writeTokensUpgradeHint(options) {
|
|
3070
|
+
if (options.renames.length === 0) return null;
|
|
3071
|
+
const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3072
|
+
const fsTs = isoToFsSafe2(isoTs);
|
|
3073
|
+
const filename = `tokens-${fsTs}.json`;
|
|
3074
|
+
const target = path19.join(
|
|
3075
|
+
options.projectRoot,
|
|
3076
|
+
TEAMIX_DIR3,
|
|
3077
|
+
HINTS_DIR,
|
|
3078
|
+
filename
|
|
3079
|
+
);
|
|
3080
|
+
const payload = {
|
|
3081
|
+
schemaVersion: 1,
|
|
3082
|
+
ts: isoTs,
|
|
3083
|
+
package: "tokens",
|
|
3084
|
+
trigger: options.trigger,
|
|
3085
|
+
fromVariant: options.fromVariant,
|
|
3086
|
+
toVariant: options.toVariant,
|
|
3087
|
+
fromVersion: options.fromVersion,
|
|
3088
|
+
toVersion: options.toVersion,
|
|
3089
|
+
renames: options.renames
|
|
3090
|
+
};
|
|
3091
|
+
await writeFileSafe(target, JSON.stringify(payload, null, 2) + "\n");
|
|
3092
|
+
return {
|
|
3093
|
+
path: target,
|
|
3094
|
+
ts: fsTs,
|
|
3095
|
+
renameCount: options.renames.length
|
|
3096
|
+
};
|
|
3097
|
+
}
|
|
3098
|
+
function selectApplicableRenames(renames, fromVersion, toVersion) {
|
|
3099
|
+
return renames.filter(
|
|
3100
|
+
(r) => compareSemver(r.sinceVersion, fromVersion) > 0 && compareSemver(r.sinceVersion, toVersion) <= 0
|
|
3101
|
+
).sort((a, b) => compareSemver(a.sinceVersion, b.sinceVersion));
|
|
3102
|
+
}
|
|
3103
|
+
function compareSemver(a, b) {
|
|
3104
|
+
const [aMain = "", aRest = ""] = a.split("-", 2);
|
|
3105
|
+
const [bMain = "", bRest = ""] = b.split("-", 2);
|
|
3106
|
+
const aParts = aMain.split(".").map((n) => Number.parseInt(n, 10));
|
|
3107
|
+
const bParts = bMain.split(".").map((n) => Number.parseInt(n, 10));
|
|
3108
|
+
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
3109
|
+
const ai = aParts[i] ?? 0;
|
|
3110
|
+
const bi = bParts[i] ?? 0;
|
|
3111
|
+
if (ai !== bi) return ai - bi;
|
|
3112
|
+
}
|
|
3113
|
+
if (aRest === "" && bRest !== "") return 1;
|
|
3114
|
+
if (aRest !== "" && bRest === "") return -1;
|
|
3115
|
+
return aRest.localeCompare(bRest, void 0, { numeric: true });
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3118
|
+
// src/core/managed-merge.ts
|
|
3119
|
+
import { hasManagedRegion as hasManagedRegion2, replaceManagedRegion as replaceManagedRegion2 } from "@teamix-evo/registry";
|
|
3120
|
+
function mergeManagedRegions(upstreamContent, consumerContent) {
|
|
3121
|
+
let updated = consumerContent;
|
|
3122
|
+
const re = /<!-- teamix-evo:managed:start id="([^"]+)" -->([\s\S]*?)<!-- teamix-evo:managed:end(?: id="\1")? -->/g;
|
|
3123
|
+
let match;
|
|
3124
|
+
while ((match = re.exec(upstreamContent)) !== null) {
|
|
3125
|
+
const id = match[1];
|
|
3126
|
+
const body = match[2].replace(/^\n/, "").replace(/\n$/, "");
|
|
3127
|
+
if (!hasManagedRegion2(updated, id)) {
|
|
3128
|
+
throw new Error(
|
|
3129
|
+
`Managed region "${id}" missing from consumer file \u2014 refusing to silently rewrite (ADR 0003).`
|
|
3130
|
+
);
|
|
3131
|
+
}
|
|
3132
|
+
updated = replaceManagedRegion2(updated, id, body);
|
|
3133
|
+
}
|
|
3134
|
+
return updated;
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
// src/core/tokens-update.ts
|
|
3138
|
+
var DEFAULT_TOKENS_PACKAGE2 = "@teamix-evo/tokens";
|
|
3139
|
+
var CONSUMER_BASENAME_BY_UPSTREAM = {
|
|
3140
|
+
"theme.css": "tokens.theme.css",
|
|
3141
|
+
"overrides.css": "tokens.overrides.css"
|
|
3142
|
+
};
|
|
3143
|
+
async function runTokensUpdate(options) {
|
|
3144
|
+
const { projectRoot } = options;
|
|
3145
|
+
const packageName = options.packageName ?? DEFAULT_TOKENS_PACKAGE2;
|
|
3146
|
+
const config = await readProjectConfig(projectRoot);
|
|
3147
|
+
if (!config?.packages?.tokens) {
|
|
3148
|
+
return { status: "not-initialized" };
|
|
3149
|
+
}
|
|
3150
|
+
const currentVariant = config.packages.tokens.variant;
|
|
3151
|
+
const currentVersion = config.packages.tokens.version;
|
|
3152
|
+
const packageRoot = options.packageRoot ?? resolveTokensPackageRoot(packageName);
|
|
3153
|
+
const catalog = await loadTokensPackageManifest2(packageRoot);
|
|
3154
|
+
const variantEntry = getVariantEntry2(catalog, currentVariant);
|
|
3155
|
+
if (!variantEntry) {
|
|
3156
|
+
throw new Error(
|
|
3157
|
+
`Currently installed variant "${currentVariant}" no longer exists in ${packageName}@${catalog.version}. Available: ${catalog.variants.map((v) => v.name).join(", ")}. Run \`npx teamix-evo@latest tokens uninstall\` then \`npx teamix-evo@latest tokens init <variant>\` to switch.`
|
|
3158
|
+
);
|
|
3159
|
+
}
|
|
3160
|
+
const upstreamByBasename = /* @__PURE__ */ new Map();
|
|
3161
|
+
for (const fileRel of variantEntry.files) {
|
|
3162
|
+
upstreamByBasename.set(
|
|
3163
|
+
path20.basename(fileRel),
|
|
3164
|
+
path20.join(packageRoot, fileRel)
|
|
3165
|
+
);
|
|
3166
|
+
}
|
|
3167
|
+
const prior = await readInstalledManifest(projectRoot) ?? {
|
|
3168
|
+
schemaVersion: 1,
|
|
3169
|
+
installed: []
|
|
3170
|
+
};
|
|
3171
|
+
const installedIdx = prior.installed.findIndex(
|
|
3172
|
+
(p) => p.package === packageName
|
|
3173
|
+
);
|
|
3174
|
+
const priorResources = installedIdx >= 0 ? prior.installed[installedIdx].resources : [];
|
|
3175
|
+
const rewritten = [];
|
|
3176
|
+
const managedReplaced = [];
|
|
3177
|
+
const preserved = [];
|
|
3178
|
+
const frozenDrift = [];
|
|
3179
|
+
const refreshedResources = [];
|
|
3180
|
+
for (const resource of priorResources) {
|
|
3181
|
+
const consumerAbs = path20.isAbsolute(resource.target) ? resource.target : path20.join(projectRoot, resource.target);
|
|
3182
|
+
const consumerBasename = path20.basename(resource.target);
|
|
3183
|
+
const upstreamBasename = lookupUpstreamBasename(consumerBasename);
|
|
3184
|
+
const upstreamAbs = upstreamBasename ? upstreamByBasename.get(upstreamBasename) : void 0;
|
|
3185
|
+
if (resource.strategy === "regenerable") {
|
|
3186
|
+
if (!upstreamAbs) {
|
|
3187
|
+
refreshedResources.push(resource);
|
|
3188
|
+
continue;
|
|
3189
|
+
}
|
|
3190
|
+
const content = await fs15.readFile(upstreamAbs, "utf-8");
|
|
3191
|
+
await writeFileSafe(consumerAbs, content);
|
|
3192
|
+
rewritten.push(resource.target);
|
|
3193
|
+
refreshedResources.push({
|
|
3194
|
+
...resource,
|
|
3195
|
+
hash: computeHash(content)
|
|
3196
|
+
});
|
|
3197
|
+
continue;
|
|
3198
|
+
}
|
|
3199
|
+
if (resource.strategy === "managed") {
|
|
3200
|
+
if (!upstreamAbs || !await fileExists(consumerAbs)) {
|
|
3201
|
+
refreshedResources.push(resource);
|
|
3202
|
+
continue;
|
|
3203
|
+
}
|
|
3204
|
+
const upstreamContent = await fs15.readFile(upstreamAbs, "utf-8");
|
|
3205
|
+
const consumerContent = await fs15.readFile(consumerAbs, "utf-8");
|
|
3206
|
+
const merged = mergeManagedRegions(upstreamContent, consumerContent);
|
|
3207
|
+
if (merged !== consumerContent) {
|
|
3208
|
+
await writeFileSafe(consumerAbs, merged);
|
|
3209
|
+
managedReplaced.push(resource.target);
|
|
3210
|
+
}
|
|
3211
|
+
refreshedResources.push({
|
|
3212
|
+
...resource,
|
|
3213
|
+
hash: computeHash(merged)
|
|
3214
|
+
});
|
|
3215
|
+
continue;
|
|
3216
|
+
}
|
|
3217
|
+
if (await fileExists(consumerAbs)) preserved.push(resource.target);
|
|
3218
|
+
if (upstreamAbs) {
|
|
3219
|
+
const upstreamContent = await fs15.readFile(upstreamAbs, "utf-8");
|
|
3220
|
+
const upstreamHash = computeHash(upstreamContent);
|
|
3221
|
+
if (resource.hash && upstreamHash !== resource.hash) {
|
|
3222
|
+
frozenDrift.push({
|
|
3223
|
+
target: resource.target,
|
|
3224
|
+
reason: "upstream-changed"
|
|
3225
|
+
});
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
refreshedResources.push(resource);
|
|
3229
|
+
}
|
|
3230
|
+
if (variantEntry.version === currentVersion) {
|
|
3231
|
+
if (installedIdx >= 0) {
|
|
3232
|
+
prior.installed[installedIdx] = {
|
|
3233
|
+
...prior.installed[installedIdx],
|
|
3234
|
+
resources: refreshedResources
|
|
3235
|
+
};
|
|
3236
|
+
await writeInstalledManifest(projectRoot, prior);
|
|
3237
|
+
}
|
|
3238
|
+
return {
|
|
3239
|
+
status: "up-to-date",
|
|
3240
|
+
packageName,
|
|
3241
|
+
variant: currentVariant,
|
|
3242
|
+
version: currentVersion,
|
|
3243
|
+
frozenDrift
|
|
3244
|
+
};
|
|
3245
|
+
}
|
|
3246
|
+
const lock = {
|
|
3247
|
+
schemaVersion: 1,
|
|
3248
|
+
variant: {
|
|
3249
|
+
name: variantEntry.name,
|
|
3250
|
+
displayName: variantEntry.displayName,
|
|
3251
|
+
version: variantEntry.version,
|
|
3252
|
+
from: packageName
|
|
3253
|
+
},
|
|
3254
|
+
packageVersion: catalog.version,
|
|
3255
|
+
linked: variantEntry.linked,
|
|
3256
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3257
|
+
};
|
|
3258
|
+
await writeFileSafe(
|
|
3259
|
+
path20.join(projectRoot, ".teamix-evo", "tokens-lock.json"),
|
|
3260
|
+
JSON.stringify(lock, null, 2) + "\n"
|
|
3261
|
+
);
|
|
3262
|
+
config.packages.tokens.version = variantEntry.version;
|
|
3263
|
+
await writeProjectConfig(projectRoot, config);
|
|
3264
|
+
if (installedIdx >= 0) {
|
|
3265
|
+
prior.installed[installedIdx] = {
|
|
3266
|
+
...prior.installed[installedIdx],
|
|
3267
|
+
version: variantEntry.version,
|
|
3268
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3269
|
+
resources: refreshedResources
|
|
3270
|
+
};
|
|
3271
|
+
await writeInstalledManifest(projectRoot, prior);
|
|
3272
|
+
}
|
|
3273
|
+
const renames = selectApplicableRenames(
|
|
3274
|
+
variantEntry.renames ?? [],
|
|
3275
|
+
currentVersion,
|
|
3276
|
+
variantEntry.version
|
|
3277
|
+
);
|
|
3278
|
+
let hintPath;
|
|
3279
|
+
if (renames.length > 0) {
|
|
3280
|
+
const hint = await writeTokensUpgradeHint({
|
|
3281
|
+
projectRoot,
|
|
3282
|
+
trigger: "update",
|
|
3283
|
+
fromVariant: currentVariant,
|
|
3284
|
+
toVariant: currentVariant,
|
|
3285
|
+
fromVersion: currentVersion,
|
|
3286
|
+
toVersion: variantEntry.version,
|
|
3287
|
+
renames
|
|
3288
|
+
});
|
|
3289
|
+
if (hint) hintPath = hint.path;
|
|
3290
|
+
}
|
|
3291
|
+
return {
|
|
3292
|
+
status: "updated",
|
|
3293
|
+
packageName,
|
|
3294
|
+
variant: currentVariant,
|
|
3295
|
+
from: currentVersion,
|
|
3296
|
+
to: variantEntry.version,
|
|
3297
|
+
rewritten,
|
|
3298
|
+
managedReplaced,
|
|
3299
|
+
preserved,
|
|
3300
|
+
frozenDrift,
|
|
3301
|
+
renames,
|
|
3302
|
+
...hintPath ? { hintPath } : {}
|
|
3303
|
+
};
|
|
3304
|
+
}
|
|
3305
|
+
function lookupUpstreamBasename(consumerBasename) {
|
|
3306
|
+
for (const [upstream, consumer] of Object.entries(
|
|
3307
|
+
CONSUMER_BASENAME_BY_UPSTREAM
|
|
3308
|
+
)) {
|
|
3309
|
+
if (consumer === consumerBasename) return upstream;
|
|
3310
|
+
}
|
|
3311
|
+
return consumerBasename;
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
// src/core/ui-upgrade-detector.ts
|
|
3315
|
+
import * as fs16 from "fs/promises";
|
|
3316
|
+
import * as path21 from "path";
|
|
3317
|
+
var PACKAGE_NAME = {
|
|
3318
|
+
ui: "@teamix-evo/ui",
|
|
3319
|
+
"biz-ui": "@teamix-evo/biz-ui"
|
|
3320
|
+
};
|
|
3321
|
+
var ALIAS_KEY = {
|
|
3322
|
+
ui: "components",
|
|
3323
|
+
"biz-ui": "business"
|
|
3324
|
+
};
|
|
3325
|
+
var COMPONENT_FILE_RE = /\.(tsx|ts)$/;
|
|
3326
|
+
var SKIP_FILENAMES = /* @__PURE__ */ new Set(["index.ts", "index.tsx"]);
|
|
3327
|
+
async function detectComponentLineage(options) {
|
|
3328
|
+
const { projectRoot, category } = options;
|
|
3329
|
+
const config = options.config ?? await readProjectConfig(projectRoot);
|
|
3330
|
+
const installed = options.installed ?? await readInstalledManifest(projectRoot);
|
|
3331
|
+
const installDir = resolveInstallDir(category, config);
|
|
3332
|
+
const installDirAbs = path21.join(projectRoot, installDir);
|
|
3333
|
+
const installDirExists = await directoryExists(installDirAbs);
|
|
3334
|
+
const hasComponentsJson = await fileExists(
|
|
3335
|
+
path21.join(projectRoot, "components.json")
|
|
3336
|
+
);
|
|
3337
|
+
const installedPkg = findInstalledPackage(installed, PACKAGE_NAME[category]);
|
|
3338
|
+
const registeredIds = installedPkg ? extractIds(installedPkg).sort() : [];
|
|
3339
|
+
const onDiskIds = installDirExists ? await listComponentIds(installDirAbs) : [];
|
|
3340
|
+
const registeredSet = new Set(registeredIds);
|
|
3341
|
+
const unregisteredIds = onDiskIds.filter((id) => !registeredSet.has(id)).sort();
|
|
3342
|
+
const lineage = classifyLineage({
|
|
3343
|
+
hasInstalled: installedPkg !== null,
|
|
3344
|
+
hasComponentsJson,
|
|
3345
|
+
onDiskIds,
|
|
3346
|
+
unregisteredIds
|
|
3347
|
+
});
|
|
3348
|
+
return {
|
|
3349
|
+
category,
|
|
3350
|
+
lineage,
|
|
3351
|
+
installDir,
|
|
3352
|
+
installDirExists,
|
|
3353
|
+
hasComponentsJson,
|
|
3354
|
+
registeredIds,
|
|
3355
|
+
unregisteredIds,
|
|
3356
|
+
installedVersion: installedPkg?.version ?? null,
|
|
3357
|
+
installedVariant: installedPkg?.variant ?? null
|
|
3358
|
+
};
|
|
3359
|
+
}
|
|
3360
|
+
function resolveInstallDir(category, config) {
|
|
3361
|
+
const aliasMap = config?.packages?.ui?.aliases ?? config?.packages?.["biz-ui"]?.aliases ?? DEFAULT_UI_ALIASES;
|
|
3362
|
+
const key = ALIAS_KEY[category];
|
|
3363
|
+
return aliasMap[key] ?? DEFAULT_UI_ALIASES[key];
|
|
3364
|
+
}
|
|
3365
|
+
function extractIds(pkg) {
|
|
3366
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3367
|
+
for (const r of pkg.resources) {
|
|
3368
|
+
const colon = r.id.indexOf(":");
|
|
3369
|
+
ids.add(colon >= 0 ? r.id.slice(0, colon) : r.id);
|
|
3370
|
+
}
|
|
3371
|
+
return [...ids];
|
|
3372
|
+
}
|
|
3373
|
+
async function directoryExists(p) {
|
|
3374
|
+
try {
|
|
3375
|
+
const stat5 = await fs16.stat(p);
|
|
3376
|
+
return stat5.isDirectory();
|
|
3377
|
+
} catch {
|
|
3378
|
+
return false;
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
async function listComponentIds(installDirAbs) {
|
|
3382
|
+
const entries = await fs16.readdir(installDirAbs, { withFileTypes: true });
|
|
3383
|
+
const ids = [];
|
|
3384
|
+
for (const e of entries) {
|
|
3385
|
+
if (!e.isFile()) continue;
|
|
3386
|
+
if (SKIP_FILENAMES.has(e.name)) continue;
|
|
3387
|
+
if (!COMPONENT_FILE_RE.test(e.name)) continue;
|
|
3388
|
+
ids.push(e.name.replace(COMPONENT_FILE_RE, ""));
|
|
3389
|
+
}
|
|
3390
|
+
return ids.sort();
|
|
3391
|
+
}
|
|
3392
|
+
function classifyLineage(args) {
|
|
3393
|
+
const { hasInstalled, hasComponentsJson, onDiskIds, unregisteredIds } = args;
|
|
3394
|
+
if (hasInstalled) {
|
|
3395
|
+
return unregisteredIds.length === 0 ? "teamix-evo" : "mixed";
|
|
3396
|
+
}
|
|
3397
|
+
if (onDiskIds.length === 0) return "absent";
|
|
3398
|
+
return hasComponentsJson ? "shadcn-native" : "custom-only";
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
// src/core/ui-upgrade.ts
|
|
3402
|
+
import * as path23 from "path";
|
|
2085
3403
|
import { createRequire as createRequire5 } from "module";
|
|
2086
|
-
import {
|
|
2087
|
-
|
|
3404
|
+
import {
|
|
3405
|
+
loadUiPackageManifest as loadUiPackageManifest3,
|
|
3406
|
+
loadVariantUiPackageManifest as loadVariantUiPackageManifest2
|
|
3407
|
+
} from "@teamix-evo/registry";
|
|
3408
|
+
|
|
3409
|
+
// src/core/ui-upgrade-staging.ts
|
|
3410
|
+
import * as path22 from "path";
|
|
3411
|
+
var TEAMIX_DIR4 = ".teamix-evo";
|
|
3412
|
+
var STAGING_DIR = ".upgrade-staging";
|
|
3413
|
+
var PACKAGE_NAME2 = {
|
|
3414
|
+
ui: "@teamix-evo/ui",
|
|
3415
|
+
"biz-ui": "@teamix-evo/biz-ui"
|
|
3416
|
+
};
|
|
3417
|
+
function isoToFsSafe3(iso) {
|
|
3418
|
+
return iso.replace(/[:.]/g, "-");
|
|
3419
|
+
}
|
|
3420
|
+
async function buildUiUpgradeStaging(options) {
|
|
3421
|
+
const { lineageReport, category } = options;
|
|
3422
|
+
if (lineageReport.lineage !== "teamix-evo" && lineageReport.lineage !== "mixed") {
|
|
3423
|
+
return null;
|
|
3424
|
+
}
|
|
3425
|
+
const installed = options.installed ?? await readInstalledManifest(options.projectRoot);
|
|
3426
|
+
const installedPkg = findInstalledPackage(installed, PACKAGE_NAME2[category]);
|
|
3427
|
+
if (!installedPkg) return null;
|
|
3428
|
+
const isoTs = options.isoTs ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3429
|
+
const fsTs = isoToFsSafe3(isoTs);
|
|
3430
|
+
const stagingDir = path22.join(
|
|
3431
|
+
options.projectRoot,
|
|
3432
|
+
TEAMIX_DIR4,
|
|
3433
|
+
STAGING_DIR,
|
|
3434
|
+
`${category}-${fsTs}`
|
|
3435
|
+
);
|
|
3436
|
+
const entryMap = new Map(
|
|
3437
|
+
options.manifest.entries.map((e) => [e.id, e])
|
|
3438
|
+
);
|
|
3439
|
+
const resByEntryId = collectResourcesByEntry(installedPkg.resources);
|
|
3440
|
+
const onlyIds = options.onlyIds && options.onlyIds.length > 0 ? new Set(options.onlyIds) : null;
|
|
3441
|
+
const entries = [];
|
|
3442
|
+
for (const id of lineageReport.registeredIds) {
|
|
3443
|
+
if (onlyIds && !onlyIds.has(id)) continue;
|
|
3444
|
+
const built = await processRegistered({
|
|
3445
|
+
id,
|
|
3446
|
+
entry: entryMap.get(id),
|
|
3447
|
+
resource: resByEntryId.get(id),
|
|
3448
|
+
packageRoot: options.packageRoot,
|
|
3449
|
+
entryPackageRoot: options.entryPackageRoot,
|
|
3450
|
+
aliases: options.aliases,
|
|
3451
|
+
stagingDir,
|
|
3452
|
+
projectRoot: options.projectRoot,
|
|
3453
|
+
category,
|
|
3454
|
+
sourceVersion: options.manifest.version
|
|
3455
|
+
});
|
|
3456
|
+
if (built) entries.push(built);
|
|
3457
|
+
}
|
|
3458
|
+
for (const id of lineageReport.unregisteredIds) {
|
|
3459
|
+
if (onlyIds && !onlyIds.has(id)) continue;
|
|
3460
|
+
const built = await processForeign({
|
|
3461
|
+
id,
|
|
3462
|
+
installDirAbs: path22.join(options.projectRoot, lineageReport.installDir),
|
|
3463
|
+
stagingDir,
|
|
3464
|
+
projectRoot: options.projectRoot,
|
|
3465
|
+
category
|
|
3466
|
+
});
|
|
3467
|
+
if (built) entries.push(built);
|
|
3468
|
+
}
|
|
3469
|
+
if (entries.length === 0) return null;
|
|
3470
|
+
const byRisk = aggregateByRisk(entries);
|
|
3471
|
+
const manifestOut = {
|
|
3472
|
+
schemaVersion: 1,
|
|
3473
|
+
ts: isoTs,
|
|
3474
|
+
package: category,
|
|
3475
|
+
trigger: options.trigger,
|
|
3476
|
+
variant: lineageReport.installedVariant ?? "_flat",
|
|
3477
|
+
fromVersion: lineageReport.installedVersion ?? "",
|
|
3478
|
+
toVersion: options.manifest.version,
|
|
3479
|
+
lineage: lineageReport.lineage,
|
|
3480
|
+
summary: { total: entries.length, byRisk },
|
|
3481
|
+
entries
|
|
3482
|
+
};
|
|
3483
|
+
await ensureDir(stagingDir);
|
|
3484
|
+
await writeFileSafe(
|
|
3485
|
+
path22.join(stagingDir, "meta.json"),
|
|
3486
|
+
JSON.stringify(manifestOut, null, 2) + "\n"
|
|
3487
|
+
);
|
|
3488
|
+
return { stagingDir, manifest: manifestOut };
|
|
3489
|
+
}
|
|
3490
|
+
async function processRegistered(args) {
|
|
3491
|
+
const { id, entry, resource, stagingDir, projectRoot, category } = args;
|
|
3492
|
+
if (!resource) return null;
|
|
3493
|
+
const currentSource = await readFileOrNull(resource.target);
|
|
3494
|
+
if (currentSource === null) {
|
|
3495
|
+
return buildBreakingEntry({
|
|
3496
|
+
id,
|
|
3497
|
+
category,
|
|
3498
|
+
resource,
|
|
3499
|
+
projectRoot,
|
|
3500
|
+
stagingDir,
|
|
3501
|
+
currentSource: "",
|
|
3502
|
+
hint: "installed file missing on disk"
|
|
3503
|
+
});
|
|
3504
|
+
}
|
|
3505
|
+
if (!entry) {
|
|
3506
|
+
return buildBreakingEntry({
|
|
3507
|
+
id,
|
|
3508
|
+
category,
|
|
3509
|
+
resource,
|
|
3510
|
+
projectRoot,
|
|
3511
|
+
stagingDir,
|
|
3512
|
+
currentSource,
|
|
3513
|
+
hint: "entry removed in upstream package"
|
|
3514
|
+
});
|
|
3515
|
+
}
|
|
3516
|
+
const file = entry.files[0];
|
|
3517
|
+
if (!file) return null;
|
|
3518
|
+
const rootForEntry = args.entryPackageRoot?.get(id) ?? args.packageRoot;
|
|
3519
|
+
const sourceAbs = path22.resolve(rootForEntry, file.source);
|
|
3520
|
+
const raw = await readFileOrNull(sourceAbs);
|
|
3521
|
+
if (raw === null) {
|
|
3522
|
+
return null;
|
|
3523
|
+
}
|
|
3524
|
+
const incomingTransformed = rewriteImports(raw, args.aliases);
|
|
3525
|
+
const incomingHash = computeHash(incomingTransformed);
|
|
3526
|
+
const currentExt = path22.extname(resource.target) || ".tsx";
|
|
3527
|
+
const incomingExt = path22.extname(file.targetName) || currentExt;
|
|
3528
|
+
const currentRel = `${id}/current${currentExt}`;
|
|
3529
|
+
const incomingRel = `${id}/incoming${incomingExt}`;
|
|
3530
|
+
await writeFileSafe(path22.join(stagingDir, currentRel), currentSource);
|
|
3531
|
+
await writeFileSafe(path22.join(stagingDir, incomingRel), incomingTransformed);
|
|
3532
|
+
const diff = classifyRisk({
|
|
3533
|
+
currentHash: resource.hash,
|
|
3534
|
+
incomingHash,
|
|
3535
|
+
currentSource,
|
|
3536
|
+
incomingSource: incomingTransformed,
|
|
3537
|
+
multiFile: entry.files.length > 1
|
|
3538
|
+
});
|
|
3539
|
+
return {
|
|
3540
|
+
id,
|
|
3541
|
+
category,
|
|
3542
|
+
current: {
|
|
3543
|
+
target: path22.relative(projectRoot, resource.target),
|
|
3544
|
+
hash: resource.hash,
|
|
3545
|
+
sourceLineage: "teamix-evo"
|
|
3546
|
+
},
|
|
3547
|
+
incoming: {
|
|
3548
|
+
sourceVersion: args.sourceVersion,
|
|
3549
|
+
hash: incomingHash,
|
|
3550
|
+
relPath: incomingRel
|
|
3551
|
+
},
|
|
3552
|
+
diff
|
|
3553
|
+
};
|
|
3554
|
+
}
|
|
3555
|
+
async function processForeign(args) {
|
|
3556
|
+
const { id, installDirAbs, stagingDir, projectRoot, category } = args;
|
|
3557
|
+
const tsx = path22.join(installDirAbs, `${id}.tsx`);
|
|
3558
|
+
const ts = path22.join(installDirAbs, `${id}.ts`);
|
|
3559
|
+
const target = await fileExists(tsx) ? tsx : await fileExists(ts) ? ts : null;
|
|
3560
|
+
if (!target) return null;
|
|
3561
|
+
const raw = await readFileOrNull(target);
|
|
3562
|
+
if (raw === null) return null;
|
|
3563
|
+
const ext = path22.extname(target);
|
|
3564
|
+
const currentRel = `${id}/current${ext}`;
|
|
3565
|
+
await writeFileSafe(path22.join(stagingDir, currentRel), raw);
|
|
3566
|
+
return {
|
|
3567
|
+
id,
|
|
3568
|
+
category,
|
|
3569
|
+
current: {
|
|
3570
|
+
target: path22.relative(projectRoot, target),
|
|
3571
|
+
hash: computeHash(raw),
|
|
3572
|
+
sourceLineage: "custom"
|
|
3573
|
+
},
|
|
3574
|
+
diff: {
|
|
3575
|
+
riskLevel: "foreign",
|
|
3576
|
+
hints: [
|
|
3577
|
+
"component is on disk but not registered in .teamix-evo/manifest.json",
|
|
3578
|
+
"AI should propose: (a) ignore, (b) re-register via teamix-evo ui add, or (c) remove"
|
|
3579
|
+
],
|
|
3580
|
+
filesChangedCount: 0
|
|
3581
|
+
}
|
|
3582
|
+
};
|
|
3583
|
+
}
|
|
3584
|
+
async function buildBreakingEntry(args) {
|
|
3585
|
+
const ext = path22.extname(args.resource.target) || ".tsx";
|
|
3586
|
+
const currentRel = `${args.id}/current${ext}`;
|
|
3587
|
+
await writeFileSafe(
|
|
3588
|
+
path22.join(args.stagingDir, currentRel),
|
|
3589
|
+
args.currentSource
|
|
3590
|
+
);
|
|
3591
|
+
return {
|
|
3592
|
+
id: args.id,
|
|
3593
|
+
category: args.category,
|
|
3594
|
+
current: {
|
|
3595
|
+
target: path22.relative(args.projectRoot, args.resource.target),
|
|
3596
|
+
hash: args.resource.hash,
|
|
3597
|
+
sourceLineage: "teamix-evo"
|
|
3598
|
+
},
|
|
3599
|
+
diff: {
|
|
3600
|
+
riskLevel: "breaking",
|
|
3601
|
+
hints: [args.hint],
|
|
3602
|
+
filesChangedCount: 0
|
|
3603
|
+
}
|
|
3604
|
+
};
|
|
3605
|
+
}
|
|
3606
|
+
function classifyRisk(args) {
|
|
3607
|
+
if (args.currentHash === args.incomingHash) {
|
|
3608
|
+
return { riskLevel: "unchanged", hints: [], filesChangedCount: 0 };
|
|
3609
|
+
}
|
|
3610
|
+
const curExports = extractExportNames(args.currentSource);
|
|
3611
|
+
const newExports = extractExportNames(args.incomingSource);
|
|
3612
|
+
const removedExports = setDiff(curExports, newExports);
|
|
3613
|
+
const addedExports = setDiff(newExports, curExports);
|
|
3614
|
+
const curVariants = extractCvaVariantValues(args.currentSource);
|
|
3615
|
+
const newVariants = extractCvaVariantValues(args.incomingSource);
|
|
3616
|
+
const removedVariants = setDiff(curVariants, newVariants);
|
|
3617
|
+
const addedVariants = setDiff(newVariants, curVariants);
|
|
3618
|
+
const hints = [];
|
|
3619
|
+
for (const e of removedExports) hints.push(`removed export: ${e}`);
|
|
3620
|
+
for (const e of addedExports) hints.push(`new export: ${e}`);
|
|
3621
|
+
for (const v of removedVariants) hints.push(`removed cva variant: ${v}`);
|
|
3622
|
+
for (const v of addedVariants) hints.push(`new cva variant: ${v}`);
|
|
3623
|
+
if (args.multiFile) hints.push("multi-file entry; only first file staged");
|
|
3624
|
+
let riskLevel;
|
|
3625
|
+
if (removedExports.length > 0 || removedVariants.length > 0) {
|
|
3626
|
+
riskLevel = "risky";
|
|
3627
|
+
} else if (addedExports.length > 0 || addedVariants.length > 0 || args.multiFile) {
|
|
3628
|
+
riskLevel = "upgradable-medium";
|
|
3629
|
+
} else {
|
|
3630
|
+
riskLevel = "upgradable-low";
|
|
3631
|
+
}
|
|
3632
|
+
return { riskLevel, hints, filesChangedCount: 1 };
|
|
3633
|
+
}
|
|
3634
|
+
function extractExportNames(src) {
|
|
3635
|
+
const names = /* @__PURE__ */ new Set();
|
|
3636
|
+
const re = /^\s*export\s+(?:default\s+)?(?:async\s+)?(?:const|let|var|function|class|interface|type|enum)\s+(\w+)/gm;
|
|
3637
|
+
let m;
|
|
3638
|
+
while ((m = re.exec(src)) !== null) {
|
|
3639
|
+
if (m[1]) names.add(m[1]);
|
|
3640
|
+
}
|
|
3641
|
+
for (const dm of src.matchAll(/^\s*export\s+default\s+(\w+)\s*;/gm)) {
|
|
3642
|
+
if (dm[1]) names.add(dm[1]);
|
|
3643
|
+
}
|
|
3644
|
+
return [...names];
|
|
3645
|
+
}
|
|
3646
|
+
function extractCvaVariantValues(src) {
|
|
3647
|
+
const block = extractVariantsBlock(src);
|
|
3648
|
+
if (block === null) return [];
|
|
3649
|
+
const names = /* @__PURE__ */ new Set();
|
|
3650
|
+
for (const groupBody of extractGroupBodies(block)) {
|
|
3651
|
+
for (const km of groupBody.matchAll(/^\s*(?:['"]?)(\w+)(?:['"]?)\s*:/gm)) {
|
|
3652
|
+
if (km[1]) names.add(km[1]);
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
return [...names];
|
|
3656
|
+
}
|
|
3657
|
+
function extractVariantsBlock(src) {
|
|
3658
|
+
const idx = src.search(/\bvariants\s*:\s*\{/);
|
|
3659
|
+
if (idx < 0) return null;
|
|
3660
|
+
const open = src.indexOf("{", idx);
|
|
3661
|
+
if (open < 0) return null;
|
|
3662
|
+
let depth = 0;
|
|
3663
|
+
for (let i = open; i < src.length; i++) {
|
|
3664
|
+
const c = src[i];
|
|
3665
|
+
if (c === "{") depth++;
|
|
3666
|
+
else if (c === "}") {
|
|
3667
|
+
depth--;
|
|
3668
|
+
if (depth === 0) return src.slice(open + 1, i);
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
return null;
|
|
3672
|
+
}
|
|
3673
|
+
function* extractGroupBodies(block) {
|
|
3674
|
+
const re = /(\w+)\s*:\s*\{/g;
|
|
3675
|
+
let m;
|
|
3676
|
+
while ((m = re.exec(block)) !== null) {
|
|
3677
|
+
const open = block.indexOf("{", m.index);
|
|
3678
|
+
if (open < 0) continue;
|
|
3679
|
+
let depth = 0;
|
|
3680
|
+
for (let i = open; i < block.length; i++) {
|
|
3681
|
+
const c = block[i];
|
|
3682
|
+
if (c === "{") depth++;
|
|
3683
|
+
else if (c === "}") {
|
|
3684
|
+
depth--;
|
|
3685
|
+
if (depth === 0) {
|
|
3686
|
+
yield block.slice(open + 1, i);
|
|
3687
|
+
re.lastIndex = i + 1;
|
|
3688
|
+
break;
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
}
|
|
3693
|
+
}
|
|
3694
|
+
function setDiff(a, b) {
|
|
3695
|
+
const bset = new Set(b);
|
|
3696
|
+
return a.filter((x) => !bset.has(x)).sort();
|
|
3697
|
+
}
|
|
3698
|
+
function collectResourcesByEntry(resources) {
|
|
3699
|
+
const out = /* @__PURE__ */ new Map();
|
|
3700
|
+
for (const r of resources) {
|
|
3701
|
+
const colon = r.id.indexOf(":");
|
|
3702
|
+
const eid = colon >= 0 ? r.id.slice(0, colon) : r.id;
|
|
3703
|
+
if (!out.has(eid)) out.set(eid, r);
|
|
3704
|
+
}
|
|
3705
|
+
return out;
|
|
3706
|
+
}
|
|
3707
|
+
function aggregateByRisk(entries) {
|
|
3708
|
+
const out = {};
|
|
3709
|
+
for (const e of entries) {
|
|
3710
|
+
const k = e.diff.riskLevel;
|
|
3711
|
+
out[k] = (out[k] ?? 0) + 1;
|
|
3712
|
+
}
|
|
3713
|
+
return out;
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3716
|
+
// src/core/ui-upgrade.ts
|
|
3717
|
+
var nodeRequire = createRequire5(import.meta.url);
|
|
2088
3718
|
function resolvePackageRoot4(packageName) {
|
|
3719
|
+
const pkgJsonPath = nodeRequire.resolve(`${packageName}/package.json`);
|
|
3720
|
+
return path23.dirname(pkgJsonPath);
|
|
3721
|
+
}
|
|
3722
|
+
async function buildStaging(args) {
|
|
3723
|
+
const { category, projectRoot, aliases, lineageReport, trigger, onlyIds } = args;
|
|
3724
|
+
if (category === "ui") {
|
|
3725
|
+
const root = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
|
|
3726
|
+
const manifest = await loadUiPackageManifest3(root);
|
|
3727
|
+
return buildUiUpgradeStaging({
|
|
3728
|
+
projectRoot,
|
|
3729
|
+
category,
|
|
3730
|
+
manifest,
|
|
3731
|
+
packageRoot: root,
|
|
3732
|
+
aliases,
|
|
3733
|
+
lineageReport,
|
|
3734
|
+
trigger,
|
|
3735
|
+
onlyIds
|
|
3736
|
+
});
|
|
3737
|
+
}
|
|
3738
|
+
const bizRoot = args.bizUiPackageRoot ?? resolvePackageRoot4("@teamix-evo/biz-ui");
|
|
3739
|
+
const variant = lineageReport.installedVariant ?? "_flat";
|
|
3740
|
+
const variantDir = path23.join(bizRoot, "variants", variant);
|
|
3741
|
+
const variantManifest = await loadVariantUiPackageManifest2(variantDir);
|
|
3742
|
+
const uiRoot = args.uiPackageRoot ?? resolvePackageRoot4("@teamix-evo/ui");
|
|
3743
|
+
const uiManifest = await loadUiPackageManifest3(uiRoot);
|
|
3744
|
+
const entryPackageRoot = /* @__PURE__ */ new Map();
|
|
3745
|
+
const merged = [];
|
|
3746
|
+
for (const e of variantManifest.entries) {
|
|
3747
|
+
entryPackageRoot.set(e.id, variantDir);
|
|
3748
|
+
merged.push(e);
|
|
3749
|
+
}
|
|
3750
|
+
for (const e of uiManifest.entries) {
|
|
3751
|
+
if (entryPackageRoot.has(e.id)) continue;
|
|
3752
|
+
entryPackageRoot.set(e.id, uiRoot);
|
|
3753
|
+
merged.push(e);
|
|
3754
|
+
}
|
|
3755
|
+
const synthetic = {
|
|
3756
|
+
schemaVersion: 1,
|
|
3757
|
+
package: "ui",
|
|
3758
|
+
version: variantManifest.version,
|
|
3759
|
+
engines: variantManifest.engines,
|
|
3760
|
+
entries: merged
|
|
3761
|
+
};
|
|
3762
|
+
return buildUiUpgradeStaging({
|
|
3763
|
+
projectRoot,
|
|
3764
|
+
category,
|
|
3765
|
+
manifest: synthetic,
|
|
3766
|
+
packageRoot: variantDir,
|
|
3767
|
+
entryPackageRoot,
|
|
3768
|
+
aliases,
|
|
3769
|
+
lineageReport,
|
|
3770
|
+
trigger,
|
|
3771
|
+
onlyIds
|
|
3772
|
+
});
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
// src/core/project-update.ts
|
|
3776
|
+
var DEFAULT_TOKENS_PACKAGE3 = "@teamix-evo/tokens";
|
|
3777
|
+
var DEFAULT_SKILLS_PACKAGE4 = "@teamix-evo/skills";
|
|
3778
|
+
var CRITICAL_STEPS2 = /* @__PURE__ */ new Set(["tokens"]);
|
|
3779
|
+
async function runProjectUpdate(options) {
|
|
3780
|
+
const { projectRoot, dryRun = false, onStep } = options;
|
|
3781
|
+
const tokensPackage = options.tokensPackageName ?? DEFAULT_TOKENS_PACKAGE3;
|
|
3782
|
+
const skillsPackage = options.skillsPackageName ?? DEFAULT_SKILLS_PACKAGE4;
|
|
3783
|
+
const config = await readProjectConfig(projectRoot);
|
|
3784
|
+
if (!config) {
|
|
3785
|
+
return { status: "not-initialized" };
|
|
3786
|
+
}
|
|
3787
|
+
let snapshot = null;
|
|
3788
|
+
let snapshotError;
|
|
3789
|
+
if (!dryRun) {
|
|
3790
|
+
try {
|
|
3791
|
+
snapshot = await createSnapshot(projectRoot, { reason: "update" });
|
|
3792
|
+
} catch (err) {
|
|
3793
|
+
snapshotError = getErrorMessage(err);
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
const steps = [];
|
|
3797
|
+
let aborted = false;
|
|
3798
|
+
const firstFailure = { value: null };
|
|
3799
|
+
function record(step) {
|
|
3800
|
+
steps.push(step);
|
|
3801
|
+
onStep?.(step);
|
|
3802
|
+
}
|
|
3803
|
+
function recordFailure(name, err) {
|
|
3804
|
+
const message = getErrorMessage(err);
|
|
3805
|
+
record({ name, status: "fail", detail: message });
|
|
3806
|
+
if (!firstFailure.value) {
|
|
3807
|
+
firstFailure.value = { step: name, error: message };
|
|
3808
|
+
}
|
|
3809
|
+
if (CRITICAL_STEPS2.has(name)) aborted = true;
|
|
3810
|
+
}
|
|
3811
|
+
let anyChanged = false;
|
|
3812
|
+
if (!config.packages?.tokens) {
|
|
3813
|
+
record({
|
|
3814
|
+
name: "tokens",
|
|
3815
|
+
status: "skip",
|
|
3816
|
+
detail: "tokens not installed"
|
|
3817
|
+
});
|
|
3818
|
+
} else if (dryRun) {
|
|
3819
|
+
try {
|
|
3820
|
+
const plan = await planTokensUpdate(tokensPackage, config);
|
|
3821
|
+
record({ name: "tokens", status: "planned", detail: plan });
|
|
3822
|
+
} catch (err) {
|
|
3823
|
+
recordFailure("tokens", err);
|
|
3824
|
+
}
|
|
3825
|
+
} else {
|
|
3826
|
+
try {
|
|
3827
|
+
const result = await runTokensUpdate({
|
|
3828
|
+
projectRoot,
|
|
3829
|
+
packageName: tokensPackage
|
|
3830
|
+
});
|
|
3831
|
+
if (result.status === "not-initialized") {
|
|
3832
|
+
record({
|
|
3833
|
+
name: "tokens",
|
|
3834
|
+
status: "skip",
|
|
3835
|
+
detail: "tokens not installed"
|
|
3836
|
+
});
|
|
3837
|
+
} else if (result.status === "up-to-date") {
|
|
3838
|
+
const driftSuffix = result.frozenDrift.length > 0 ? `, frozen drift: ${result.frozenDrift.length}` : "";
|
|
3839
|
+
record({
|
|
3840
|
+
name: "tokens",
|
|
3841
|
+
status: "ok",
|
|
3842
|
+
detail: `up-to-date (${result.variant} v${result.version}${driftSuffix})`
|
|
3843
|
+
});
|
|
3844
|
+
} else {
|
|
3845
|
+
anyChanged = true;
|
|
3846
|
+
const extras = [];
|
|
3847
|
+
if (result.managedReplaced.length > 0)
|
|
3848
|
+
extras.push(`managed: ${result.managedReplaced.length}`);
|
|
3849
|
+
if (result.frozenDrift.length > 0)
|
|
3850
|
+
extras.push(`frozen drift: ${result.frozenDrift.length}`);
|
|
3851
|
+
const suffix = extras.length > 0 ? ` [${extras.join(", ")}]` : "";
|
|
3852
|
+
record({
|
|
3853
|
+
name: "tokens",
|
|
3854
|
+
status: "ok",
|
|
3855
|
+
detail: `${result.variant} v${result.from} \u2192 v${result.to}${suffix}`
|
|
3856
|
+
});
|
|
3857
|
+
}
|
|
3858
|
+
} catch (err) {
|
|
3859
|
+
recordFailure("tokens", err);
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
if (!config.packages?.skills) {
|
|
3863
|
+
record({
|
|
3864
|
+
name: "skills",
|
|
3865
|
+
status: "skip",
|
|
3866
|
+
detail: "skills not installed"
|
|
3867
|
+
});
|
|
3868
|
+
} else if (aborted) {
|
|
3869
|
+
record({
|
|
3870
|
+
name: "skills",
|
|
3871
|
+
status: "skip",
|
|
3872
|
+
detail: "aborted: earlier critical step failed"
|
|
3873
|
+
});
|
|
3874
|
+
} else {
|
|
3875
|
+
try {
|
|
3876
|
+
const result = await runSkillsUpdate({
|
|
3877
|
+
projectRoot,
|
|
3878
|
+
dryRun,
|
|
3879
|
+
packageName: skillsPackage
|
|
3880
|
+
});
|
|
3881
|
+
if (result.status === "no-skills") {
|
|
3882
|
+
record({
|
|
3883
|
+
name: "skills",
|
|
3884
|
+
status: "skip",
|
|
3885
|
+
detail: "no skills locked"
|
|
3886
|
+
});
|
|
3887
|
+
} else if (result.status === "no-changes") {
|
|
3888
|
+
record({
|
|
3889
|
+
name: "skills",
|
|
3890
|
+
status: "ok",
|
|
3891
|
+
detail: `up-to-date (${result.checkedSkillIds.length} skill(s) at v${result.version})`
|
|
3892
|
+
});
|
|
3893
|
+
} else if (result.status === "dry-run") {
|
|
3894
|
+
const bumps = result.plan.filter((p) => p.action === "version-bump");
|
|
3895
|
+
const detail = bumps.length === 0 ? `up-to-date (${result.plan.length} skill(s) checked at v${result.currentVersion})` : `${bumps.length} skill(s) would update: ${result.currentVersion} \u2192 ${result.availableVersion}`;
|
|
3896
|
+
record({ name: "skills", status: "planned", detail });
|
|
3897
|
+
} else {
|
|
3898
|
+
anyChanged = true;
|
|
3899
|
+
const summary = result.updatedSkillIds.length > 0 ? `updated: ${result.updatedSkillIds.join(", ")} (v${result.version})` : `refreshed at v${result.version}`;
|
|
3900
|
+
record({ name: "skills", status: "ok", detail: summary });
|
|
3901
|
+
}
|
|
3902
|
+
} catch (err) {
|
|
3903
|
+
recordFailure("skills", err);
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
await runComponentSourceStep("ui", {
|
|
3907
|
+
projectRoot,
|
|
3908
|
+
config,
|
|
3909
|
+
dryRun,
|
|
3910
|
+
record,
|
|
3911
|
+
recordFailure
|
|
3912
|
+
});
|
|
3913
|
+
await runComponentSourceStep("biz-ui", {
|
|
3914
|
+
projectRoot,
|
|
3915
|
+
config,
|
|
3916
|
+
dryRun,
|
|
3917
|
+
record,
|
|
3918
|
+
recordFailure
|
|
3919
|
+
});
|
|
3920
|
+
const out = (() => {
|
|
3921
|
+
if (firstFailure.value) {
|
|
3922
|
+
return {
|
|
3923
|
+
status: "partial",
|
|
3924
|
+
steps,
|
|
3925
|
+
resumeHint: {
|
|
3926
|
+
failedAt: firstFailure.value.step,
|
|
3927
|
+
completed: steps.filter((s) => s.status === "ok").map((s) => s.name),
|
|
3928
|
+
failed: steps.filter((s) => s.status === "fail").map((s) => s.name),
|
|
3929
|
+
error: firstFailure.value.error,
|
|
3930
|
+
resumeCommand: "teamix-evo update"
|
|
3931
|
+
},
|
|
3932
|
+
snapshot,
|
|
3933
|
+
...snapshotError ? { snapshotError } : {}
|
|
3934
|
+
};
|
|
3935
|
+
}
|
|
3936
|
+
if (dryRun) return { status: "dry-run", steps, snapshot: null };
|
|
3937
|
+
if (anyChanged)
|
|
3938
|
+
return {
|
|
3939
|
+
status: "updated",
|
|
3940
|
+
steps,
|
|
3941
|
+
snapshot,
|
|
3942
|
+
...snapshotError ? { snapshotError } : {}
|
|
3943
|
+
};
|
|
3944
|
+
return {
|
|
3945
|
+
status: "up-to-date",
|
|
3946
|
+
steps,
|
|
3947
|
+
snapshot,
|
|
3948
|
+
...snapshotError ? { snapshotError } : {}
|
|
3949
|
+
};
|
|
3950
|
+
})();
|
|
3951
|
+
return out;
|
|
3952
|
+
}
|
|
3953
|
+
async function runComponentSourceStep(category, args) {
|
|
3954
|
+
const { projectRoot, config, dryRun, record, recordFailure } = args;
|
|
3955
|
+
const cfgKey = category === "ui" ? "ui" : "biz-ui";
|
|
3956
|
+
if (!config.packages?.[cfgKey]) {
|
|
3957
|
+
record({
|
|
3958
|
+
name: category,
|
|
3959
|
+
status: "skip",
|
|
3960
|
+
detail: `${category} not installed`
|
|
3961
|
+
});
|
|
3962
|
+
return;
|
|
3963
|
+
}
|
|
3964
|
+
const aliases = config.packages.ui?.aliases ?? config.packages["biz-ui"]?.aliases;
|
|
3965
|
+
if (!aliases) {
|
|
3966
|
+
record({
|
|
3967
|
+
name: category,
|
|
3968
|
+
status: "skip",
|
|
3969
|
+
detail: `${category} aliases not configured`
|
|
3970
|
+
});
|
|
3971
|
+
return;
|
|
3972
|
+
}
|
|
3973
|
+
let report;
|
|
3974
|
+
try {
|
|
3975
|
+
report = await detectComponentLineage({
|
|
3976
|
+
projectRoot,
|
|
3977
|
+
category,
|
|
3978
|
+
config
|
|
3979
|
+
});
|
|
3980
|
+
} catch (err) {
|
|
3981
|
+
record({
|
|
3982
|
+
name: category,
|
|
3983
|
+
status: "skip",
|
|
3984
|
+
detail: `lineage detect failed: ${getErrorMessage(err)}`
|
|
3985
|
+
});
|
|
3986
|
+
return;
|
|
3987
|
+
}
|
|
3988
|
+
if (report.lineage !== "teamix-evo" && report.lineage !== "mixed") {
|
|
3989
|
+
record({
|
|
3990
|
+
name: category,
|
|
3991
|
+
status: "skip",
|
|
3992
|
+
detail: `lineage=${report.lineage}; nothing to stage`
|
|
3993
|
+
});
|
|
3994
|
+
return;
|
|
3995
|
+
}
|
|
3996
|
+
if (dryRun) {
|
|
3997
|
+
const total = report.registeredIds.length + report.unregisteredIds.length;
|
|
3998
|
+
record({
|
|
3999
|
+
name: category,
|
|
4000
|
+
status: "planned",
|
|
4001
|
+
detail: total === 0 ? "no components to stage" : `${total} component(s) to stage (lineage=${report.lineage})`
|
|
4002
|
+
});
|
|
4003
|
+
return;
|
|
4004
|
+
}
|
|
4005
|
+
try {
|
|
4006
|
+
const built = await buildStaging({
|
|
4007
|
+
category,
|
|
4008
|
+
projectRoot,
|
|
4009
|
+
aliases,
|
|
4010
|
+
lineageReport: report,
|
|
4011
|
+
trigger: "update",
|
|
4012
|
+
onlyIds: []
|
|
4013
|
+
});
|
|
4014
|
+
if (built === null) {
|
|
4015
|
+
record({
|
|
4016
|
+
name: category,
|
|
4017
|
+
status: "skip",
|
|
4018
|
+
detail: "no entries to stage"
|
|
4019
|
+
});
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
const stagingRel = path24.relative(projectRoot, built.stagingDir);
|
|
4023
|
+
const summary = summarizeStagingRisk(built.manifest.summary.byRisk);
|
|
4024
|
+
record({
|
|
4025
|
+
name: category,
|
|
4026
|
+
status: "ok",
|
|
4027
|
+
detail: `${built.manifest.summary.total} component(s) staged${summary ? ` (${summary})` : ""}, see ${stagingRel}`
|
|
4028
|
+
});
|
|
4029
|
+
} catch (err) {
|
|
4030
|
+
recordFailure(category, err);
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
function summarizeStagingRisk(byRisk) {
|
|
4034
|
+
const order = [
|
|
4035
|
+
"risky",
|
|
4036
|
+
"breaking",
|
|
4037
|
+
"foreign",
|
|
4038
|
+
"upgradable-medium",
|
|
4039
|
+
"upgradable-low",
|
|
4040
|
+
"unchanged"
|
|
4041
|
+
];
|
|
4042
|
+
const parts = [];
|
|
4043
|
+
for (const k of order) {
|
|
4044
|
+
const v = byRisk[k];
|
|
4045
|
+
if (v && v > 0) parts.push(`${v} ${k}`);
|
|
4046
|
+
}
|
|
4047
|
+
return parts.join(", ");
|
|
4048
|
+
}
|
|
4049
|
+
async function planTokensUpdate(tokensPackage, config) {
|
|
4050
|
+
const tokensCfg = config.packages?.tokens;
|
|
4051
|
+
if (!tokensCfg) return "tokens not installed";
|
|
4052
|
+
const packageRoot = resolveTokensPackageRoot(tokensPackage);
|
|
4053
|
+
const catalog = await loadTokensPackageManifest3(packageRoot);
|
|
4054
|
+
const variantEntry = getVariantEntry3(catalog, tokensCfg.variant);
|
|
4055
|
+
if (!variantEntry) {
|
|
4056
|
+
return `variant "${tokensCfg.variant}" no longer in ${tokensPackage}@${catalog.version} \u2014 uninstall + re-init to switch`;
|
|
4057
|
+
}
|
|
4058
|
+
if (variantEntry.version === tokensCfg.version) {
|
|
4059
|
+
return `up-to-date (${tokensCfg.variant} v${tokensCfg.version})`;
|
|
4060
|
+
}
|
|
4061
|
+
return `${tokensCfg.variant} v${tokensCfg.version} \u2192 v${variantEntry.version}`;
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
// src/core/installer.ts
|
|
4065
|
+
import * as path25 from "path";
|
|
4066
|
+
import * as fs17 from "fs/promises";
|
|
4067
|
+
async function installResources(options) {
|
|
4068
|
+
const { projectRoot, manifest, data, variantDir, packageRoot } = options;
|
|
4069
|
+
const installedResources = [];
|
|
4070
|
+
for (const resource of manifest.resources) {
|
|
4071
|
+
logger.debug(`Installing resource: ${resource.id} \u2192 ${resource.target}`);
|
|
4072
|
+
if (resource.recursive) {
|
|
4073
|
+
const results = await installRecursiveResource(
|
|
4074
|
+
resource,
|
|
4075
|
+
projectRoot,
|
|
4076
|
+
data,
|
|
4077
|
+
variantDir,
|
|
4078
|
+
packageRoot
|
|
4079
|
+
);
|
|
4080
|
+
installedResources.push(...results);
|
|
4081
|
+
} else {
|
|
4082
|
+
const result = await installSingleResource(
|
|
4083
|
+
resource,
|
|
4084
|
+
projectRoot,
|
|
4085
|
+
data,
|
|
4086
|
+
variantDir,
|
|
4087
|
+
packageRoot
|
|
4088
|
+
);
|
|
4089
|
+
installedResources.push(result);
|
|
4090
|
+
}
|
|
4091
|
+
}
|
|
4092
|
+
return {
|
|
4093
|
+
resources: installedResources,
|
|
4094
|
+
count: installedResources.length
|
|
4095
|
+
};
|
|
4096
|
+
}
|
|
4097
|
+
async function installSingleResource(resource, projectRoot, data, variantDir, packageRoot) {
|
|
4098
|
+
const sourcePath = resolveSourcePath(
|
|
4099
|
+
resource.source,
|
|
4100
|
+
variantDir,
|
|
4101
|
+
packageRoot
|
|
4102
|
+
);
|
|
4103
|
+
const targetPath = path25.join(projectRoot, resource.target);
|
|
4104
|
+
let content;
|
|
4105
|
+
if (resource.template) {
|
|
4106
|
+
const templateContent = await loadTemplateFile(sourcePath);
|
|
4107
|
+
content = renderTemplate(templateContent, data);
|
|
4108
|
+
} else {
|
|
4109
|
+
content = await fs17.readFile(sourcePath, "utf-8");
|
|
4110
|
+
}
|
|
4111
|
+
await writeFileSafe(targetPath, content);
|
|
4112
|
+
const hash = computeHash(content);
|
|
4113
|
+
logger.debug(` Written: ${resource.target} (${resource.updateStrategy})`);
|
|
4114
|
+
return {
|
|
4115
|
+
id: resource.id,
|
|
4116
|
+
target: resource.target,
|
|
4117
|
+
hash,
|
|
4118
|
+
strategy: resource.updateStrategy
|
|
4119
|
+
};
|
|
4120
|
+
}
|
|
4121
|
+
async function installRecursiveResource(resource, projectRoot, data, variantDir, packageRoot) {
|
|
4122
|
+
const sourcePath = resolveSourcePath(
|
|
4123
|
+
resource.source,
|
|
4124
|
+
variantDir,
|
|
4125
|
+
packageRoot
|
|
4126
|
+
);
|
|
4127
|
+
const targetDir = path25.join(projectRoot, resource.target);
|
|
4128
|
+
const results = [];
|
|
4129
|
+
await ensureDir(targetDir);
|
|
4130
|
+
const entries = await walkDir(sourcePath);
|
|
4131
|
+
for (const entry of entries) {
|
|
4132
|
+
const relPath = path25.relative(sourcePath, entry);
|
|
4133
|
+
let targetFile = path25.join(targetDir, relPath);
|
|
4134
|
+
if (resource.template && targetFile.endsWith(".hbs")) {
|
|
4135
|
+
targetFile = targetFile.slice(0, -4);
|
|
4136
|
+
}
|
|
4137
|
+
let content;
|
|
4138
|
+
if (resource.template && entry.endsWith(".hbs")) {
|
|
4139
|
+
const templateContent = await loadTemplateFile(entry);
|
|
4140
|
+
content = renderTemplate(templateContent, data);
|
|
4141
|
+
} else {
|
|
4142
|
+
content = await fs17.readFile(entry, "utf-8");
|
|
4143
|
+
}
|
|
4144
|
+
await writeFileSafe(targetFile, content);
|
|
4145
|
+
const hash = computeHash(content);
|
|
4146
|
+
const targetRel = path25.relative(projectRoot, targetFile);
|
|
4147
|
+
results.push({
|
|
4148
|
+
id: `${resource.id}:${relPath}`,
|
|
4149
|
+
target: targetRel,
|
|
4150
|
+
hash,
|
|
4151
|
+
strategy: resource.updateStrategy
|
|
4152
|
+
});
|
|
4153
|
+
logger.debug(` Written: ${targetRel}`);
|
|
4154
|
+
}
|
|
4155
|
+
return results;
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
// src/core/registry-client.ts
|
|
4159
|
+
import * as path26 from "path";
|
|
4160
|
+
import * as fs18 from "fs/promises";
|
|
4161
|
+
import { createRequire as createRequire6 } from "module";
|
|
4162
|
+
import { loadVariantManifest } from "@teamix-evo/registry";
|
|
4163
|
+
var require6 = createRequire6(import.meta.url);
|
|
4164
|
+
function resolvePackageRoot5(packageName) {
|
|
2089
4165
|
const pkgJsonPath = require6.resolve(`${packageName}/package.json`);
|
|
2090
|
-
return
|
|
4166
|
+
return path26.dirname(pkgJsonPath);
|
|
2091
4167
|
}
|
|
2092
4168
|
async function loadVariantData(packageName, variant) {
|
|
2093
|
-
const packageRoot =
|
|
2094
|
-
const variantDir =
|
|
4169
|
+
const packageRoot = resolvePackageRoot5(packageName);
|
|
4170
|
+
const variantDir = path26.join(packageRoot, "library", variant);
|
|
2095
4171
|
logger.debug(`Resolved variant dir: ${variantDir}`);
|
|
2096
4172
|
logger.debug(`Package root: ${packageRoot}`);
|
|
2097
4173
|
const manifest = await loadVariantManifest(variantDir);
|
|
2098
4174
|
let data = {};
|
|
2099
|
-
const dataPath =
|
|
4175
|
+
const dataPath = path26.join(variantDir, "_data.json");
|
|
2100
4176
|
try {
|
|
2101
|
-
const raw = await
|
|
4177
|
+
const raw = await fs18.readFile(dataPath, "utf-8");
|
|
2102
4178
|
data = JSON.parse(raw);
|
|
2103
4179
|
} catch (err) {
|
|
2104
4180
|
if (err.code !== "ENOENT") {
|
|
@@ -2111,7 +4187,10 @@ async function loadVariantData(packageName, variant) {
|
|
|
2111
4187
|
export {
|
|
2112
4188
|
DEFAULT_UI_ALIASES,
|
|
2113
4189
|
DEFAULT_UI_ICON_LIBRARY,
|
|
4190
|
+
detectConflicts,
|
|
4191
|
+
detectProjectState,
|
|
2114
4192
|
ensureTeamixDir,
|
|
4193
|
+
extractDescriptionParts,
|
|
2115
4194
|
getTeamixDir,
|
|
2116
4195
|
installResources,
|
|
2117
4196
|
installSkills,
|
|
@@ -2129,7 +4208,10 @@ export {
|
|
|
2129
4208
|
removeSkillFiles,
|
|
2130
4209
|
removeUiFiles,
|
|
2131
4210
|
runBizUiAdd,
|
|
4211
|
+
runGenerateAgentsMd,
|
|
2132
4212
|
runLintInit,
|
|
4213
|
+
runProjectInit,
|
|
4214
|
+
runProjectUpdate,
|
|
2133
4215
|
runSkillsAdd,
|
|
2134
4216
|
runSkillsInit,
|
|
2135
4217
|
runSkillsUpdate,
|